浏览代码

Fix locale-dependent data files (#18171) (#18212)

* Fix for locale-dependent data files being read/written
Fixes a variety of issues related to having a locale set on the machine
that uses the comma as the decimal separator.

Fix for locale-dependent data files being read/written
Fixes a variety of issues related to having a locale set on the machine that uses the comma as the decimal separator.

Two main approaches

    For AssetBuilder, always set, globally in the entire application and thus all builders, the "C" locale, which is the culture invariant locale
    For all other applications, create a PALified scoped thread-local locale setter that can be scoped around the parts where files are read or numbers are converted, then add it around all those sensitive places.
        ObjectStream read and write
        Json didn't need handling, as rapidjson already uses a culture invariant number formatter
        Script Canvas read and write (including LUA generation)
        LUA execution (lua will actually fail to work right if its not in the invariant locale)
        Console variable execution (since it comes from a cfg file and that can be considered portable and invariant)
        Added a new test for objectstream that verifies that the invariant locale is working backwards and forwards.
        Added a JSON test that verifies the same.

result: Fixes issue #11139 and also issue #10987 and also issue #11680 and any similar that are to do with loading and saving.

Testing

Reproduced the issues in the above one before fixing it, they work fine after this fix.

Signed-off-by: Nicholas Lawson <[email protected]>
Nicholas Lawson 1 年之前
父节点
当前提交
b1bcd7968e
共有 65 个文件被更改,包括 787 次插入115 次删除
  1. 5 0
      Code/Editor/AnimationContext.cpp
  2. 12 0
      Code/Editor/Controls/ReflectedPropertyControl/ReflectedPropertyItem.cpp
  3. 4 0
      Code/Editor/GameEngine.cpp
  4. 4 0
      Code/Framework/AzCore/AzCore/Console/Console.cpp
  5. 10 0
      Code/Framework/AzCore/AzCore/Console/ConsoleTypeHelpers.inl
  6. 2 0
      Code/Framework/AzCore/AzCore/Math/Color.cpp
  7. 11 0
      Code/Framework/AzCore/AzCore/Math/MathScriptHelpers.cpp
  8. 18 0
      Code/Framework/AzCore/AzCore/Math/MathStringConversions.cpp
  9. 3 0
      Code/Framework/AzCore/AzCore/Math/Obb.cpp
  10. 3 0
      Code/Framework/AzCore/AzCore/Math/Transform.cpp
  11. 3 0
      Code/Framework/AzCore/AzCore/Math/Vector2.cpp
  12. 30 0
      Code/Framework/AzCore/AzCore/Script/ScriptContext.cpp
  13. 42 9
      Code/Framework/AzCore/AzCore/Script/ScriptContextDebug.cpp
  14. 3 0
      Code/Framework/AzCore/AzCore/Serialization/Json/DoubleSerializer.cpp
  15. 35 0
      Code/Framework/AzCore/AzCore/Serialization/Locale.cpp
  16. 42 0
      Code/Framework/AzCore/AzCore/Serialization/Locale.h
  17. 6 0
      Code/Framework/AzCore/AzCore/Serialization/ObjectStream.cpp
  18. 5 0
      Code/Framework/AzCore/AzCore/Serialization/SerializeContext.cpp
  19. 5 0
      Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp
  20. 2 0
      Code/Framework/AzCore/AzCore/azcore_files.cmake
  21. 4 11
      Code/Framework/AzCore/AzCore/std/string/conversions.h
  22. 10 0
      Code/Framework/AzCore/Platform/Android/AzCore/Serialization/Locale_Platform.h
  23. 3 0
      Code/Framework/AzCore/Platform/Android/platform_android_files.cmake
  24. 39 0
      Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Serialization/Locale_UnixLike.cpp
  25. 26 0
      Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Serialization/Locale_UnixLike.h
  26. 48 0
      Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Serialization/Locale_WinAPI.cpp
  27. 34 0
      Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Serialization/Locale_WinAPI.h
  28. 10 0
      Code/Framework/AzCore/Platform/Linux/AzCore/Serialization/Locale_Platform.h
  29. 3 0
      Code/Framework/AzCore/Platform/Linux/platform_linux_files.cmake
  30. 10 0
      Code/Framework/AzCore/Platform/Mac/AzCore/Serialization/Locale_Platform.h
  31. 3 0
      Code/Framework/AzCore/Platform/Mac/platform_mac_files.cmake
  32. 10 0
      Code/Framework/AzCore/Platform/Windows/AzCore/Serialization/Locale_Platform.h
  33. 3 0
      Code/Framework/AzCore/Platform/Windows/platform_windows_files.cmake
  34. 10 0
      Code/Framework/AzCore/Platform/iOS/AzCore/Serialization/Locale_Platform.h
  35. 3 0
      Code/Framework/AzCore/Platform/iOS/platform_ios_files.cmake
  36. 89 32
      Code/Framework/AzCore/Tests/Serialization.cpp
  37. 45 0
      Code/Framework/AzCore/Tests/Serialization/Json/JsonSerializationUtilsTests.cpp
  38. 4 0
      Code/Legacy/CrySystem/LocalizedStringManager.cpp
  39. 5 0
      Code/Legacy/CrySystem/XConsole.cpp
  40. 8 0
      Code/Legacy/CrySystem/XConsoleVariable.cpp
  41. 27 0
      Code/Legacy/CrySystem/XML/XMLBinaryNode.cpp
  42. 9 0
      Code/Legacy/CrySystem/XML/XmlUtils.cpp
  43. 16 37
      Code/Legacy/CrySystem/XML/xml.cpp
  44. 13 0
      Code/Tools/AssetProcessor/AssetBuilder/main.cpp
  45. 11 1
      Gems/AtomLyIntegration/AtomFont/Code/Source/FFontXML.cpp
  46. 5 0
      Gems/AtomTressFX/External/Code/src/TressFX/TressFXAsset.cpp
  47. 4 0
      Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ActorCommands.cpp
  48. 5 1
      Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ActorInstanceCommands.cpp
  49. 3 0
      Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/MetaData.cpp
  50. 9 0
      Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/MotionCommands.cpp
  51. 4 0
      Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/MotionEventCommands.cpp
  52. 5 0
      Gems/EMotionFX/Code/EMotionFX/Exporters/ExporterLib/Exporter/MorphTargetExport.cpp
  53. 5 0
      Gems/EMotionFX/Code/EMotionFX/Exporters/ExporterLib/Exporter/NodeExport.cpp
  54. 3 0
      Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/Workspace.cpp
  55. 5 0
      Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/MorphTargetsWindow/MorphTargetEditWindow.cpp
  56. 7 0
      Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/MorphTargetsWindow/MorphTargetGroupWidget.cpp
  57. 3 0
      Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/TimeView/TimeViewPlugin.cpp
  58. 4 0
      Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/TimeView/TimeViewToolBar.cpp
  59. 5 0
      Gems/EMotionFX/Code/MCore/Source/MCoreCommandManager.cpp
  60. 3 0
      Gems/EMotionFX/Code/Tests/AnimGraphTransitionConditionCommandTests.cpp
  61. 3 0
      Gems/LyShine/Code/Source/Sprite.cpp
  62. 9 0
      Gems/Maestro/Code/Source/Cinematics/Movie.cpp
  63. 5 0
      Gems/MotionMatching/Code/Source/CsvSerializers.cpp
  64. 10 1
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp
  65. 2 23
      Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLuaUtility.cpp

+ 5 - 0
Code/Editor/AnimationContext.cpp

@@ -18,6 +18,7 @@
 #include "TrackView/TrackViewDialog.h"
 #include "TrackView/TrackViewDialog.h"
 #include "ViewManager.h"
 #include "ViewManager.h"
 
 
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/Time/ITime.h>
 #include <AzCore/Time/ITime.h>
 
 
 //////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////
@@ -601,7 +602,11 @@ void CAnimationContext::GoToFrameCmd(IConsoleCmdArgs* pArgs)
         return;
         return;
     }
     }
 
 
+    // console commands are in the invariant locale, for atof()
+    AZ::Locale::ScopedSerializationLocale scopedLocale;
     float targetFrame = (float)atof(pArgs->GetArg(1));
     float targetFrame = (float)atof(pArgs->GetArg(1));
+    scopedLocale.Deactivate();
+
     if (pSeq->GetTimeRange().start > targetFrame || targetFrame > pSeq->GetTimeRange().end)
     if (pSeq->GetTimeRange().start > targetFrame || targetFrame > pSeq->GetTimeRange().end)
     {
     {
         gEnv->pLog->LogError("GoToFrame: requested time %f is outside the range of sequence %s (%f, %f)", targetFrame, pSeq->GetName().c_str(), pSeq->GetTimeRange().start, pSeq->GetTimeRange().end);
         gEnv->pLog->LogError("GoToFrame: requested time %f is outside the range of sequence %s (%f, %f)", targetFrame, pSeq->GetName().c_str(), pSeq->GetTimeRange().start, pSeq->GetTimeRange().end);

+ 12 - 0
Code/Editor/Controls/ReflectedPropertyControl/ReflectedPropertyItem.cpp

@@ -18,6 +18,8 @@
 #include "ReflectedPropertyCtrl.h"
 #include "ReflectedPropertyCtrl.h"
 #include "Undo/UndoVariableChange.h"
 #include "Undo/UndoVariableChange.h"
 
 
+#include <AzCore/Serialization/Locale.h>
+
 // default number of increments to cover the range of a property - determined experimentally by feel
 // default number of increments to cover the range of a property - determined experimentally by feel
 const float ReflectedPropertyItem::s_DefaultNumStepIncrements = 500.0f;
 const float ReflectedPropertyItem::s_DefaultNumStepIncrements = 500.0f;
 
 
@@ -32,6 +34,16 @@ const float ReflectedPropertyItem::s_DefaultNumStepIncrements = 500.0f;
 
 
 static ColorF StringToColor(const QString &value)
 static ColorF StringToColor(const QString &value)
 {
 {
+    // note that converting back and forth between these values is happening in 2 cases
+    // one is when its being read from the UI and the other is when it is being read from XML
+    // XML is always in "C" locale, and the GUI is ultimately using @ref type_convertor in Variable.h
+    // which itself uses QString::number, which is always in the "C" locale (invariant culture).
+    // for example, color is converted using the following line
+    // void operator()(const Vec4& value, QString& to) const { to = QString::fromLatin1("%1,%2,%3,%4").arg(value.x).arg(value.y).arg(value.z).arg(value.w); }
+    // qstring::arg uses the "C" Locale unless explicitly using the notation %L1, %L2, etc, so the input will be invariant.
+
+    AZ::Locale::ScopedSerializationLocale localeScope;
+
     ColorF color;
     ColorF color;
     float r, g, b, a;
     float r, g, b, a;
     int res = azsscanf(value.toUtf8().data(), "%f,%f,%f,%f", &r, &g, &b, &a);
     int res = azsscanf(value.toUtf8().data(), "%f,%f,%f,%f", &r, &g, &b, &a);

+ 4 - 0
Code/Editor/GameEngine.cpp

@@ -19,6 +19,7 @@
 #include <AzCore/Component/ComponentApplication.h>
 #include <AzCore/Component/ComponentApplication.h>
 #include <AzCore/IO/IStreamer.h>
 #include <AzCore/IO/IStreamer.h>
 #include <AzCore/IO/Streamer/FileRequest.h>
 #include <AzCore/IO/Streamer/FileRequest.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/std/parallel/binary_semaphore.h>
 #include <AzCore/std/parallel/binary_semaphore.h>
 #include <AzCore/Console/IConsole.h>
 #include <AzCore/Console/IConsole.h>
 
 
@@ -277,6 +278,9 @@ void KillMemory(IConsoleCmdArgs* /* pArgs */)
 
 
 static void CmdGotoEditor(IConsoleCmdArgs* pArgs)
 static void CmdGotoEditor(IConsoleCmdArgs* pArgs)
 {
 {
+    // Console commands are assumed to be in the culture invariant locale since they can come from data files.
+    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
     // feature is mostly useful for QA purposes, this works with the game "goto" command
     // feature is mostly useful for QA purposes, this works with the game "goto" command
     // this console command actually is used by the game command, the editor command shouldn't be used by the user
     // this console command actually is used by the game command, the editor command shouldn't be used by the user
     int iArgCount = pArgs->GetArgCount();
     int iArgCount = pArgs->GetArgCount();

+ 4 - 0
Code/Framework/AzCore/AzCore/Console/Console.cpp

@@ -10,6 +10,7 @@
 #include <AzCore/Console/ILogger.h>
 #include <AzCore/Console/ILogger.h>
 #include <AzCore/Interface/Interface.h>
 #include <AzCore/Interface/Interface.h>
 #include <AzCore/Serialization/Json/JsonSerializationSettings.h>
 #include <AzCore/Serialization/Json/JsonSerializationSettings.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/Settings/CommandLine.h>
 #include <AzCore/Settings/CommandLine.h>
 #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
 #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
 #include <AzCore/StringFunc/StringFunc.h>
 #include <AzCore/StringFunc/StringFunc.h>
@@ -438,6 +439,9 @@ namespace AZ
         ConsoleFunctorFlags requiredClear
         ConsoleFunctorFlags requiredClear
     )
     )
     {
     {
+        // incoming commands are assumed to be in the "C" locale as they might be from portable data files
+        AZ::Locale::ScopedSerializationLocale scopedLocale;
+
         bool result = false;
         bool result = false;
         ConsoleFunctorFlags flags = ConsoleFunctorFlags::Null;
         ConsoleFunctorFlags flags = ConsoleFunctorFlags::Null;
 
 

+ 10 - 0
Code/Framework/AzCore/AzCore/Console/ConsoleTypeHelpers.inl

@@ -13,6 +13,7 @@
 #include <AzCore/std/functional.h>
 #include <AzCore/std/functional.h>
 #include <AzCore/std/ranges/ranges_algorithm.h>
 #include <AzCore/std/ranges/ranges_algorithm.h>
 #include <AzCore/std/string/fixed_string.h>
 #include <AzCore/std/string/fixed_string.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/StringFunc/StringFunc.h>
 #include <AzCore/StringFunc/StringFunc.h>
 #include <AzCore/Math/Vector2.h>
 #include <AzCore/Math/Vector2.h>
 #include <AzCore/Math/Vector3.h>
 #include <AzCore/Math/Vector3.h>
@@ -55,26 +56,31 @@ namespace AZ
 
 
         inline CVarFixedString ValueToString(const AZ::Vector2& value)
         inline CVarFixedString ValueToString(const AZ::Vector2& value)
         {
         {
+            AZ::Locale::ScopedSerializationLocale scopedLocale; // interpret %0.2f as using the "C" locale
             return CVarFixedString::format("%0.2f %0.2f", static_cast<float>(value.GetX()), static_cast<float>(value.GetY()));
             return CVarFixedString::format("%0.2f %0.2f", static_cast<float>(value.GetX()), static_cast<float>(value.GetY()));
         }
         }
 
 
         inline CVarFixedString ValueToString(const AZ::Vector3& value)
         inline CVarFixedString ValueToString(const AZ::Vector3& value)
         {
         {
+            AZ::Locale::ScopedSerializationLocale scopedLocale; // interpret %0.2f as using the "C" locale
             return CVarFixedString::format("%0.2f %0.2f %0.2f", static_cast<float>(value.GetX()), static_cast<float>(value.GetY()), static_cast<float>(value.GetZ()));
             return CVarFixedString::format("%0.2f %0.2f %0.2f", static_cast<float>(value.GetX()), static_cast<float>(value.GetY()), static_cast<float>(value.GetZ()));
         }
         }
 
 
         inline CVarFixedString ValueToString(const AZ::Vector4& value)
         inline CVarFixedString ValueToString(const AZ::Vector4& value)
         {
         {
+            AZ::Locale::ScopedSerializationLocale scopedLocale; // interpret %0.2f as using the "C" locale
             return CVarFixedString::format("%0.2f %0.2f %0.2f %0.2f", static_cast<float>(value.GetX()), static_cast<float>(value.GetY()), static_cast<float>(value.GetZ()), static_cast<float>(value.GetW()));
             return CVarFixedString::format("%0.2f %0.2f %0.2f %0.2f", static_cast<float>(value.GetX()), static_cast<float>(value.GetY()), static_cast<float>(value.GetZ()), static_cast<float>(value.GetW()));
         }
         }
 
 
         inline CVarFixedString ValueToString(const AZ::Quaternion& value)
         inline CVarFixedString ValueToString(const AZ::Quaternion& value)
         {
         {
+            AZ::Locale::ScopedSerializationLocale scopedLocale; // interpret %0.2f as using the "C" locale
             return CVarFixedString::format("%0.2f %0.2f %0.2f %0.2f", static_cast<float>(value.GetX()), static_cast<float>(value.GetY()), static_cast<float>(value.GetZ()), static_cast<float>(value.GetW()));
             return CVarFixedString::format("%0.2f %0.2f %0.2f %0.2f", static_cast<float>(value.GetX()), static_cast<float>(value.GetY()), static_cast<float>(value.GetZ()), static_cast<float>(value.GetW()));
         }
         }
 
 
         inline CVarFixedString ValueToString(const AZ::Color& value)
         inline CVarFixedString ValueToString(const AZ::Color& value)
         {
         {
+            AZ::Locale::ScopedSerializationLocale scopedLocale; // interpret %0.2f as using the "C" locale
             return CVarFixedString::format("%0.2f %0.2f %0.2f %0.2f", static_cast<float>(value.GetR()), static_cast<float>(value.GetG()), static_cast<float>(value.GetB()), static_cast<float>(value.GetA()));
             return CVarFixedString::format("%0.2f %0.2f %0.2f %0.2f", static_cast<float>(value.GetR()), static_cast<float>(value.GetG()), static_cast<float>(value.GetB()), static_cast<float>(value.GetA()));
         }
         }
 
 
@@ -186,6 +192,8 @@ namespace AZ
         {
         {
             if (!arguments.empty())
             if (!arguments.empty())
             {
             {
+                AZ::Locale::ScopedSerializationLocale scopedLocale; // interpret floats using the "C" locale for strod
+
                 AZ::CVarFixedString convertCandidate{ arguments.front() };
                 AZ::CVarFixedString convertCandidate{ arguments.front() };
                 char* endPtr = nullptr;
                 char* endPtr = nullptr;
                 const float converted = static_cast<float>(strtod(convertCandidate.c_str(), &endPtr));
                 const float converted = static_cast<float>(strtod(convertCandidate.c_str(), &endPtr));
@@ -205,6 +213,8 @@ namespace AZ
         {
         {
             if (!arguments.empty())
             if (!arguments.empty())
             {
             {
+                AZ::Locale::ScopedSerializationLocale scopedLocale; // interpret floats using the "C" locale for strod
+
                 AZ::CVarFixedString convertCandidate{ arguments.front() };
                 AZ::CVarFixedString convertCandidate{ arguments.front() };
                 char* endPtr = nullptr;
                 char* endPtr = nullptr;
                 const double converted = strtod(convertCandidate.c_str(), &endPtr);
                 const double converted = strtod(convertCandidate.c_str(), &endPtr);

+ 2 - 0
Code/Framework/AzCore/AzCore/Math/Color.cpp

@@ -8,6 +8,7 @@
 
 
 #include "Color.h"
 #include "Color.h"
 #include <AzCore/Math/MathScriptHelpers.h>
 #include <AzCore/Math/MathScriptHelpers.h>
+#include <AzCore/Serialization/Locale.h>
 
 
 namespace AZ
 namespace AZ
 {
 {
@@ -217,6 +218,7 @@ namespace AZ
 
 
         AZStd::string ColorToString(const Color& c)
         AZStd::string ColorToString(const Color& c)
         {
         {
+            AZ::Locale::ScopedSerializationLocale locale; // colors<--->string should be intepreted in the "C" Locale.
             return AZStd::string::format("(r=%.7f,g=%.7f,b=%.7f,a=%.7f)", static_cast<float>(c.GetR()), static_cast<float>(c.GetG()), static_cast<float>(c.GetB()), static_cast<float>(c.GetA()));
             return AZStd::string::format("(r=%.7f,g=%.7f,b=%.7f,a=%.7f)", static_cast<float>(c.GetR()), static_cast<float>(c.GetG()), static_cast<float>(c.GetB()), static_cast<float>(c.GetA()));
         }
         }
     }
     }

+ 11 - 0
Code/Framework/AzCore/AzCore/Math/MathScriptHelpers.cpp

@@ -11,23 +11,30 @@
 #include <AzCore/Math/Vector4.h>
 #include <AzCore/Math/Vector4.h>
 #include <AzCore/Math/Quaternion.h>
 #include <AzCore/Math/Quaternion.h>
 #include <AzCore/Math/Transform.h>
 #include <AzCore/Math/Transform.h>
+#include <AzCore/Serialization/Locale.h>
 
 
 namespace AZ
 namespace AZ
 {
 {
     AZStd::string Vector3ToString(const Vector3& v)
     AZStd::string Vector3ToString(const Vector3& v)
     {
     {
+        AZ::Locale::ScopedSerializationLocale locale; // Vector3 <---> string interpreted in the "C" Locale.
+
         return AZStd::string::format("(x=%.7f,y=%.7f,z=%.7f)", static_cast<float>(v.GetX()), static_cast<float>(v.GetY()), static_cast<float>(v.GetZ()));
         return AZStd::string::format("(x=%.7f,y=%.7f,z=%.7f)", static_cast<float>(v.GetX()), static_cast<float>(v.GetY()), static_cast<float>(v.GetZ()));
     }
     }
 
 
 
 
     AZStd::string Vector4ToString(const Vector4& v)
     AZStd::string Vector4ToString(const Vector4& v)
     {
     {
+        AZ::Locale::ScopedSerializationLocale locale; // Vector4 <---> string interpreted in the "C" Locale.
+
         return AZStd::string::format("(x=%.7f,y=%.7f,z=%.7f,w=%.7f)", static_cast<float>(v.GetX()), static_cast<float>(v.GetY()), static_cast<float>(v.GetZ()), static_cast<float>(v.GetW()));
         return AZStd::string::format("(x=%.7f,y=%.7f,z=%.7f,w=%.7f)", static_cast<float>(v.GetX()), static_cast<float>(v.GetY()), static_cast<float>(v.GetZ()), static_cast<float>(v.GetW()));
     }
     }
 
 
 
 
     AZStd::string QuaternionToString(const Quaternion& v)
     AZStd::string QuaternionToString(const Quaternion& v)
     {
     {
+        AZ::Locale::ScopedSerializationLocale locale; // Quaternion <---> string should be interpreted in the "C" Locale.
+
         return AZStd::string::format("(x=%.7f,y=%.7f,z=%.7f,w=%.7f)", static_cast<float>(v.GetX()), static_cast<float>(v.GetY()), static_cast<float>(v.GetZ()), static_cast<float>(v.GetW()));
         return AZStd::string::format("(x=%.7f,y=%.7f,z=%.7f,w=%.7f)", static_cast<float>(v.GetX()), static_cast<float>(v.GetY()), static_cast<float>(v.GetZ()), static_cast<float>(v.GetW()));
     }
     }
 
 
@@ -119,6 +126,8 @@ namespace AZ
 
 
     size_t FloatArrayTextSerializer::DataToText(const float* floats, size_t numFloats, char* textBuffer, size_t textBufferSize, bool isDataBigEndian)
     size_t FloatArrayTextSerializer::DataToText(const float* floats, size_t numFloats, char* textBuffer, size_t textBufferSize, bool isDataBigEndian)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for printf-ing floats to be invariant
+
         size_t numWritten = 0;
         size_t numWritten = 0;
         for (size_t i = 0; i < numFloats; ++i)
         for (size_t i = 0; i < numFloats; ++i)
         {
         {
@@ -147,6 +156,8 @@ namespace AZ
 
 
     size_t FloatArrayTextSerializer::TextToData(const char* text, float* floats, size_t numFloats, bool isDataBigEndian)
     size_t FloatArrayTextSerializer::TextToData(const char* text, float* floats, size_t numFloats, bool isDataBigEndian)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for parsing floats via strtod to be invariant
+
         size_t nextNumberIndex = 0;
         size_t nextNumberIndex = 0;
         while (text != nullptr && nextNumberIndex < numFloats)
         while (text != nullptr && nextNumberIndex < numFloats)
         {
         {

+ 18 - 0
Code/Framework/AzCore/AzCore/Math/MathStringConversions.cpp

@@ -21,6 +21,8 @@ namespace AZStd
 {
 {
     void to_string(string& str, const AZ::Vector2& value)
     void to_string(string& str, const AZ::Vector2& value)
     {
     {
+        AZ::Locale::ScopedSerializationLocale locale; // make sure that %f is using the "C" locale (periods instead of commas)
+
         str = AZStd::string::format("%.8f,%.8f", 
         str = AZStd::string::format("%.8f,%.8f", 
             static_cast<float>(value.GetX()), 
             static_cast<float>(value.GetX()), 
             static_cast<float>(value.GetY()));
             static_cast<float>(value.GetY()));
@@ -28,6 +30,8 @@ namespace AZStd
 
 
     void to_string(string& str, const AZ::Vector3& value)
     void to_string(string& str, const AZ::Vector3& value)
     {
     {
+        AZ::Locale::ScopedSerializationLocale locale;
+
         str = AZStd::string::format("%.8f,%.8f,%.8f", 
         str = AZStd::string::format("%.8f,%.8f,%.8f", 
             static_cast<float>(value.GetX()), 
             static_cast<float>(value.GetX()), 
             static_cast<float>(value.GetY()), 
             static_cast<float>(value.GetY()), 
@@ -36,6 +40,8 @@ namespace AZStd
 
 
     void to_string(string& str, const AZ::Vector4& value)
     void to_string(string& str, const AZ::Vector4& value)
     {
     {
+        AZ::Locale::ScopedSerializationLocale locale;
+
         str = AZStd::string::format("%.8f,%.8f,%.8f,%.8f", 
         str = AZStd::string::format("%.8f,%.8f,%.8f,%.8f", 
             static_cast<float>(value.GetX()), 
             static_cast<float>(value.GetX()), 
             static_cast<float>(value.GetY()), 
             static_cast<float>(value.GetY()), 
@@ -45,6 +51,8 @@ namespace AZStd
     
     
     void to_string(string& str, const AZ::Quaternion& value)
     void to_string(string& str, const AZ::Quaternion& value)
     {
     {
+        AZ::Locale::ScopedSerializationLocale locale;
+
         str = AZStd::string::format("%.8f,%.8f,%.8f,%.8f",
         str = AZStd::string::format("%.8f,%.8f,%.8f,%.8f",
             static_cast<float>(value.GetX()),
             static_cast<float>(value.GetX()),
             static_cast<float>(value.GetY()),
             static_cast<float>(value.GetY()),
@@ -54,6 +62,8 @@ namespace AZStd
 
 
     void to_string(string& str, const AZ::Matrix3x3& value)
     void to_string(string& str, const AZ::Matrix3x3& value)
     {
     {
+        AZ::Locale::ScopedSerializationLocale locale;
+
         str = AZStd::string::format(
         str = AZStd::string::format(
             "%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f",
             "%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f",
             static_cast<float>(value(0, 0)), static_cast<float>(value(1, 0)), static_cast<float>(value(2, 0)),
             static_cast<float>(value(0, 0)), static_cast<float>(value(1, 0)), static_cast<float>(value(2, 0)),
@@ -63,6 +73,8 @@ namespace AZStd
 
 
     void to_string(string& str, const AZ::Matrix4x4& value)
     void to_string(string& str, const AZ::Matrix4x4& value)
     {
     {
+        AZ::Locale::ScopedSerializationLocale locale;
+
         str = AZStd::string::format("%.8f,%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f,%.8f", 
         str = AZStd::string::format("%.8f,%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f,%.8f", 
             static_cast<float>(value(0, 0)), static_cast<float>(value(1, 0)), static_cast<float>(value(2, 0)), static_cast<float>(value(3, 0)),
             static_cast<float>(value(0, 0)), static_cast<float>(value(1, 0)), static_cast<float>(value(2, 0)), static_cast<float>(value(3, 0)),
             static_cast<float>(value(0, 1)), static_cast<float>(value(1, 1)), static_cast<float>(value(2, 1)), static_cast<float>(value(3, 1)),
             static_cast<float>(value(0, 1)), static_cast<float>(value(1, 1)), static_cast<float>(value(2, 1)), static_cast<float>(value(3, 1)),
@@ -72,6 +84,8 @@ namespace AZStd
 
 
     void to_string(string& str, const AZ::Transform& value)
     void to_string(string& str, const AZ::Transform& value)
     {
     {
+        AZ::Locale::ScopedSerializationLocale locale;
+
         AZ::Matrix3x4 matrix3x4 = AZ::Matrix3x4::CreateFromTransform(value);
         AZ::Matrix3x4 matrix3x4 = AZ::Matrix3x4::CreateFromTransform(value);
 
 
         str = AZStd::string::format("%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f",
         str = AZStd::string::format("%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f",
@@ -83,6 +97,8 @@ namespace AZStd
 
 
     void to_string(string& str, const AZ::Aabb& value)
     void to_string(string& str, const AZ::Aabb& value)
     {
     {
+        AZ::Locale::ScopedSerializationLocale locale;
+
         str = AZStd::string::format(
         str = AZStd::string::format(
             "%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f",
             "%.8f,%.8f,%.8f\n%.8f,%.8f,%.8f",
             static_cast<float>(value.GetMin().GetX()), static_cast<float>(value.GetMin().GetY()),
             static_cast<float>(value.GetMin().GetX()), static_cast<float>(value.GetMin().GetY()),
@@ -92,6 +108,8 @@ namespace AZStd
 
 
     void to_string(string& str, const AZ::Color& color)
     void to_string(string& str, const AZ::Color& color)
     {
     {
+        AZ::Locale::ScopedSerializationLocale locale;
+
         str = AZStd::string::format("R:%d, G:%d, B:%d A:%d", color.GetR8(), color.GetG8(), color.GetB8(), color.GetA8());
         str = AZStd::string::format("R:%d, G:%d, B:%d A:%d", color.GetR8(), color.GetG8(), color.GetB8(), color.GetA8());
     }
     }
 }
 }

+ 3 - 0
Code/Framework/AzCore/AzCore/Math/Obb.cpp

@@ -10,6 +10,7 @@
 #include <AzCore/Math/Aabb.h>
 #include <AzCore/Math/Aabb.h>
 #include <AzCore/Math/Transform.h>
 #include <AzCore/Math/Transform.h>
 #include <AzCore/Math/MathScriptHelpers.h>
 #include <AzCore/Math/MathScriptHelpers.h>
+#include <AzCore/Serialization/Locale.h>
 
 
 namespace AZ
 namespace AZ
 {
 {
@@ -41,6 +42,8 @@ namespace AZ
 
 
         AZStd::string ObbToString(const Obb& obb)
         AZStd::string ObbToString(const Obb& obb)
         {
         {
+            AZ::Locale::ScopedSerializationLocale locale;
+
             return AZStd::string::format("Position %s AxisX %s AxisY %s AxisZ %s halfLengthX=%.7f halfLengthY=%.7f halfLengthZ=%.7f",
             return AZStd::string::format("Position %s AxisX %s AxisY %s AxisZ %s halfLengthX=%.7f halfLengthY=%.7f halfLengthZ=%.7f",
                 Vector3ToString(obb.GetPosition()).c_str(),
                 Vector3ToString(obb.GetPosition()).c_str(),
                 Vector3ToString(obb.GetAxisX()).c_str(),
                 Vector3ToString(obb.GetAxisX()).c_str(),

+ 3 - 0
Code/Framework/AzCore/AzCore/Math/Transform.cpp

@@ -13,6 +13,7 @@
 #include <AzCore/Math/Obb.h>
 #include <AzCore/Math/Obb.h>
 #include <AzCore/Math/Vector2.h>
 #include <AzCore/Math/Vector2.h>
 #include <AzCore/Math/MathScriptHelpers.h>
 #include <AzCore/Math/MathScriptHelpers.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/Serialization/SerializeContext.h>
 #include <AzCore/Serialization/SerializeContext.h>
 
 
 namespace AZ
 namespace AZ
@@ -178,6 +179,8 @@ namespace AZ
 
 
     size_t TransformSerializer::TextToData(const char* text, unsigned int textVersion, IO::GenericStream& stream, bool isDataBigEndian)
     size_t TransformSerializer::TextToData(const char* text, unsigned int textVersion, IO::GenericStream& stream, bool isDataBigEndian)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for strtod to be invariant
+
         const size_t dataBufferSize = AZStd::max(AZStd::max(NumFloatsVersion1, NumFloatsVersion0), NumFloats);
         const size_t dataBufferSize = AZStd::max(AZStd::max(NumFloatsVersion1, NumFloatsVersion0), NumFloats);
         const size_t numElements = textVersion < 1 ? NumFloatsVersion0 : (textVersion == 1 ? NumFloatsVersion1 : NumFloats);
         const size_t numElements = textVersion < 1 ? NumFloatsVersion0 : (textVersion == 1 ? NumFloatsVersion1 : NumFloats);
 
 

+ 3 - 0
Code/Framework/AzCore/AzCore/Math/Vector2.cpp

@@ -11,6 +11,7 @@
 #include <AzCore/Math/Vector4.h>
 #include <AzCore/Math/Vector4.h>
 #include <AzCore/Math/MathUtils.h>
 #include <AzCore/Math/MathUtils.h>
 #include <AzCore/Math/MathScriptHelpers.h>
 #include <AzCore/Math/MathScriptHelpers.h>
+#include <AzCore/Serialization/Locale.h>
 
 
 namespace AZ
 namespace AZ
 {
 {
@@ -76,6 +77,8 @@ namespace AZ
 
 
         AZStd::string Vector2ToString(const Vector2* thisPtr)
         AZStd::string Vector2ToString(const Vector2* thisPtr)
         {
         {
+            AZ::Locale::ScopedSerializationLocale locale;
+
             return AZStd::string::format("(x=%.7f,y=%.7f)", static_cast<float>(thisPtr->GetX()), static_cast<float>(thisPtr->GetY()));
             return AZStd::string::format("(x=%.7f,y=%.7f)", static_cast<float>(thisPtr->GetX()), static_cast<float>(thisPtr->GetY()));
         }
         }
 
 

+ 30 - 0
Code/Framework/AzCore/AzCore/Script/ScriptContext.cpp

@@ -15,6 +15,7 @@
 #include <AzCore/std/algorithm.h>
 #include <AzCore/std/algorithm.h>
 #include <AzCore/std/string/conversions.h>
 #include <AzCore/std/string/conversions.h>
 #include <AzCore/Script/lua/lua.h>
 #include <AzCore/Script/lua/lua.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/IO/GenericStreams.h>
 #include <AzCore/IO/GenericStreams.h>
 #include <AzCore/RTTI/AttributeReader.h>
 #include <AzCore/RTTI/AttributeReader.h>
 
 
@@ -103,6 +104,9 @@ namespace ScriptContextCpp
     // remove Lua packgage.loadlib immediately after loading libraries to prevent use of unsafe function
     // remove Lua packgage.loadlib immediately after loading libraries to prevent use of unsafe function
     void OpenLuaLibraries(lua_State* m_lua)
     void OpenLuaLibraries(lua_State* m_lua)
     {
     {
+        // LUA Must execute in the "C" Locale.
+        AZ::Locale::ScopedSerializationLocale scopedLocale;
+
         // Lua:
         // Lua:
         luaL_openlibs(m_lua);
         luaL_openlibs(m_lua);
         // Lua:
         // Lua:
@@ -585,6 +589,11 @@ namespace AZ
         //=========================================================================
         //=========================================================================
         bool LuaSafeCall(lua_State* lua, int numParams, int numResults)
         bool LuaSafeCall(lua_State* lua, int numParams, int numResults)
         {
         {
+            // Lua must execute in the "C" Locale.  this does mean that the scoped locale will
+            // bleed into any C++ calls that the pcall actually calls back into, but it is also the assumption
+            // that any C++ calls that lua makes are also in the "C" locale.
+            AZ::Locale::ScopedSerializationLocale scopedLocale;
+
             // Can't do LSV here because of multiple return values
             // Can't do LSV here because of multiple return values
 
 
             // Index the error handler should end up in
             // Index the error handler should end up in
@@ -622,6 +631,7 @@ namespace AZ
         //=========================================================================
         //=========================================================================
         int Class__Index(lua_State* l)
         int Class__Index(lua_State* l)
         {
         {
+            AZ::Locale::ScopedSerializationLocale scopedLocale;
             LSV_BEGIN(l, 1);
             LSV_BEGIN(l, 1);
 
 
             // calling format __index(table,key)
             // calling format __index(table,key)
@@ -709,6 +719,7 @@ namespace AZ
 
 
         int Class__IndexAllowNil(lua_State* l)
         int Class__IndexAllowNil(lua_State* l)
         {
         {
+            AZ::Locale::ScopedSerializationLocale scopedLocale;
             LSV_BEGIN(l, 1);
             LSV_BEGIN(l, 1);
 
 
             // calling format __index(table,key)
             // calling format __index(table,key)
@@ -788,6 +799,7 @@ namespace AZ
         //=========================================================================
         //=========================================================================
         int Class__NewIndex(lua_State* l)
         int Class__NewIndex(lua_State* l)
         {
         {
+            AZ::Locale::ScopedSerializationLocale scopedLocale;
             LSV_BEGIN(l, 0);
             LSV_BEGIN(l, 0);
 
 
             // calling format __index(table,key,value)
             // calling format __index(table,key,value)
@@ -1208,24 +1220,28 @@ namespace AZ
     //////////////////////////////////////////////////////////////////////////
     //////////////////////////////////////////////////////////////////////////
     void ScriptValue<float>::StackPush(lua_State* l, float value)
     void ScriptValue<float>::StackPush(lua_State* l, float value)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale;
         lua_pushnumber(l, value);
         lua_pushnumber(l, value);
     }
     }
 
 
     //////////////////////////////////////////////////////////////////////////
     //////////////////////////////////////////////////////////////////////////
     float ScriptValue<float>::StackRead(lua_State* l, int stackIndex)
     float ScriptValue<float>::StackRead(lua_State* l, int stackIndex)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale;
         return azlua_checknumber<float>(l, stackIndex);
         return azlua_checknumber<float>(l, stackIndex);
     }
     }
 
 
     //////////////////////////////////////////////////////////////////////////
     //////////////////////////////////////////////////////////////////////////
     void ScriptValue<double>::StackPush(lua_State* l, double value)
     void ScriptValue<double>::StackPush(lua_State* l, double value)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale;
         lua_pushnumber(l, static_cast<lua_Number>(value));
         lua_pushnumber(l, static_cast<lua_Number>(value));
     }
     }
 
 
     //////////////////////////////////////////////////////////////////////////
     //////////////////////////////////////////////////////////////////////////
     double ScriptValue<double>::StackRead(lua_State* l, int stackIndex)
     double ScriptValue<double>::StackRead(lua_State* l, int stackIndex)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale;
         return lua_tonumber(l, stackIndex);
         return lua_tonumber(l, stackIndex);
     }
     }
 
 
@@ -2414,6 +2430,8 @@ LUA_API const Node* lua_getDummyNode()
                 typedef T ValueType;
                 typedef T ValueType;
                 static bool FromStack(lua_State* lua, int stackIndex, BehaviorArgument& value, BehaviorClass* valueClass, ScriptContext::StackVariableAllocator* tempAllocator)
                 static bool FromStack(lua_State* lua, int stackIndex, BehaviorArgument& value, BehaviorClass* valueClass, ScriptContext::StackVariableAllocator* tempAllocator)
                 {
                 {
+                    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
                     value.m_typeId = AzTypeInfo<ValueType>::Uuid(); // we should probably store const &
                     value.m_typeId = AzTypeInfo<ValueType>::Uuid(); // we should probably store const &
                     if (value.m_value == nullptr)
                     if (value.m_value == nullptr)
                     {
                     {
@@ -2434,6 +2452,8 @@ LUA_API const Node* lua_getDummyNode()
                 }
                 }
                 static void ToStack(lua_State* lua, BehaviorArgument& value)
                 static void ToStack(lua_State* lua, BehaviorArgument& value)
                 {
                 {
+                    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
                     void* valueAddress = value.GetValueAddress();
                     void* valueAddress = value.GetValueAddress();
                     ValueType actualValue = *reinterpret_cast<ValueType*>(valueAddress);
                     ValueType actualValue = *reinterpret_cast<ValueType*>(valueAddress);
 
 
@@ -5483,6 +5503,13 @@ LUA_API const Node* lua_getDummyNode()
             //////////////////////////////////////////////////////////////////////////
             //////////////////////////////////////////////////////////////////////////
             void BindTo(BehaviorContext* behaviorContext)
             void BindTo(BehaviorContext* behaviorContext)
             {
             {
+                // LUA Must execute in the "C" Locale.  We add this around this root
+                // call so that it doesn't have to be added to every function call inside.
+                // It only truly matters around parsing of float values (commas versus periods)
+                // so it doesn't need to be absolutely everywhere.  Just when its possible for
+                // such code to be encountered.
+                AZ::Locale::ScopedSerializationLocale scopedLocale;
+
                 LSV_BEGIN(m_lua, 0);
                 LSV_BEGIN(m_lua, 0);
 
 
                 if (m_context)
                 if (m_context)
@@ -5609,6 +5636,8 @@ LUA_API const Node* lua_getDummyNode()
             /// execute all script loaded though load function, plus the one you supply as string
             /// execute all script loaded though load function, plus the one you supply as string
             bool Execute(const char* script = nullptr, const char* dbgName = nullptr, size_t dataSize = 0)
             bool Execute(const char* script = nullptr, const char* dbgName = nullptr, size_t dataSize = 0)
             {
             {
+                AZ::Locale::ScopedSerializationLocale scopedLocale;
+
                 LSV_BEGIN(m_lua, 0);
                 LSV_BEGIN(m_lua, 0);
 
 
                 if (script)
                 if (script)
@@ -5640,6 +5669,7 @@ LUA_API const Node* lua_getDummyNode()
             // Load a stream as a function (module)
             // Load a stream as a function (module)
             bool LoadFromStream(IO::GenericStream* stream, const char* debugName, const char* mode, lua_State* lua)
             bool LoadFromStream(IO::GenericStream* stream, const char* debugName, const char* mode, lua_State* lua)
             {
             {
+                AZ::Locale::ScopedSerializationLocale scopedLocale;
                 LSV_BEGIN(m_lua, 1);
                 LSV_BEGIN(m_lua, 1);
 
 
                 using namespace Internal;
                 using namespace Internal;

+ 42 - 9
Code/Framework/AzCore/AzCore/Script/ScriptContextDebug.cpp

@@ -16,6 +16,7 @@
 #include <AzCore/RTTI/AttributeReader.h>
 #include <AzCore/RTTI/AttributeReader.h>
 #include <AzCore/RTTI/BehaviorContextUtilities.h>
 #include <AzCore/RTTI/BehaviorContextUtilities.h>
 #include <AzCore/RTTI/ReflectContext.h>
 #include <AzCore/RTTI/ReflectContext.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/std/string/tokenize.h>
 #include <AzCore/std/string/tokenize.h>
 
 
 extern "C" {
 extern "C" {
@@ -114,6 +115,9 @@ void ScriptContextDebug::DisconnectHook()
 void
 void
 ScriptContextDebug::EnumRegisteredClasses(EnumClass enumClass, EnumMethod enumMethod, EnumProperty enumProperty, void* userData)
 ScriptContextDebug::EnumRegisteredClasses(EnumClass enumClass, EnumMethod enumMethod, EnumProperty enumProperty, void* userData)
 {
 {
+    // LUA Must execute in the "C" Locale.
+    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
     AZ_Assert(enumClass && enumMethod && enumProperty, "Invalid input!");
     AZ_Assert(enumClass && enumMethod && enumProperty, "Invalid input!");
 
 
     lua_State* l = m_context.NativeContext();
     lua_State* l = m_context.NativeContext();
@@ -238,6 +242,9 @@ ScriptContextDebug::EnumRegisteredClasses(EnumClass enumClass, EnumMethod enumMe
 void
 void
 ScriptContextDebug::EnumRegisteredGlobals(EnumMethod enumMethod, EnumProperty enumProperty, void* userData)
 ScriptContextDebug::EnumRegisteredGlobals(EnumMethod enumMethod, EnumProperty enumProperty, void* userData)
 {
 {
+    // LUA Must execute in the "C" Locale.
+    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
     lua_State* l = m_context.NativeContext();
     lua_State* l = m_context.NativeContext();
     lua_rawgeti(l, LUA_REGISTRYINDEX, AZ_LUA_GLOBALS_TABLE_REF); // load the class table
     lua_rawgeti(l, LUA_REGISTRYINDEX, AZ_LUA_GLOBALS_TABLE_REF); // load the class table
 
 
@@ -319,6 +326,9 @@ bool EBusHasHandler(const AZ::BehaviorEBus* bus)
 
 
 void ScriptContextDebug::EnumRegisteredEBuses(EnumEBus enumEBus, EnumEBusSender enumEBusSender, void* userData)
 void ScriptContextDebug::EnumRegisteredEBuses(EnumEBus enumEBus, EnumEBusSender enumEBusSender, void* userData)
 {
 {
+    // LUA Must execute in the "C" Locale.
+    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
     AZ::BehaviorContext* behaviorContext = m_context.GetBoundContext();
     AZ::BehaviorContext* behaviorContext = m_context.GetBoundContext();
     if (!behaviorContext)
     if (!behaviorContext)
     {
     {
@@ -548,6 +558,9 @@ ScriptContextDebug::PopCallstack()
 OSString
 OSString
 ScriptContextDebug::DbgValueToString(int valueIndex)
 ScriptContextDebug::DbgValueToString(int valueIndex)
 {
 {
+    // LUA Must execute in the "C" Locale.
+    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
     lua_State* l = m_context.NativeContext();
     lua_State* l = m_context.NativeContext();
     int type = lua_type(l, valueIndex);
     int type = lua_type(l, valueIndex);
     switch (type)
     switch (type)
@@ -598,6 +611,9 @@ static ScriptContextDebug::BreakpointId MakeBreakpointId(const char* sourceName,
 //=========================================================================
 //=========================================================================
 void LuaHook(lua_State* l, lua_Debug* ar)
 void LuaHook(lua_State* l, lua_Debug* ar)
 {
 {
+    // LUA Must execute in the "C" Locale.
+    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
     // Read contexts
     // Read contexts
     lua_rawgeti(l, LUA_REGISTRYINDEX, AZ_LUA_SCRIPT_CONTEXT_REF);
     lua_rawgeti(l, LUA_REGISTRYINDEX, AZ_LUA_SCRIPT_CONTEXT_REF);
     ScriptContext* sc = reinterpret_cast<ScriptContext*>(lua_touserdata(l, -1));
     ScriptContext* sc = reinterpret_cast<ScriptContext*>(lua_touserdata(l, -1));
@@ -749,6 +765,9 @@ void LuaHook(lua_State* l, lua_Debug* ar)
 void
 void
 ScriptContextDebug::EnumLocals(EnumLocalCallback& cb)
 ScriptContextDebug::EnumLocals(EnumLocalCallback& cb)
 {
 {
+    // LUA Must execute in the "C" Locale.
+    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
     if (cb && m_luaDebug)
     if (cb && m_luaDebug)
     {
     {
         lua_State* l = m_context.NativeContext();
         lua_State* l = m_context.NativeContext();
@@ -887,6 +906,9 @@ ScriptContextDebug::RemoveBreakpoint(Breakpoint& bp)
 void
 void
 ScriptContextDebug::ReadValue(DebugValue& value, VoidPtrArray& tablesVisited, bool isReadOnly)
 ScriptContextDebug::ReadValue(DebugValue& value, VoidPtrArray& tablesVisited, bool isReadOnly)
 {
 {
+    // LUA Must execute in the "C" Locale.
+    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
     lua_State* l = m_context.NativeContext();
     lua_State* l = m_context.NativeContext();
     int valueType = lua_type(l, -1);
     int valueType = lua_type(l, -1);
     value.m_type = static_cast<char>(valueType);
     value.m_type = static_cast<char>(valueType);
@@ -1072,6 +1094,9 @@ ScriptContextDebug::ReadValue(DebugValue& value, VoidPtrArray& tablesVisited, bo
 void
 void
 ScriptContextDebug::WriteValue(const DebugValue& value, const char* valueName, int localIndex, int tableIndex, int userDataStackIndex)
 ScriptContextDebug::WriteValue(const DebugValue& value, const char* valueName, int localIndex, int tableIndex, int userDataStackIndex)
 {
 {
+    // LUA Must execute in the "C" Locale.
+    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
     lua_State* l = m_context.NativeContext();
     lua_State* l = m_context.NativeContext();
     int valueTableIndex = -1;
     int valueTableIndex = -1;
     if (valueName[0] == '[')
     if (valueName[0] == '[')
@@ -1245,16 +1270,18 @@ ScriptContextDebug::WriteValue(const DebugValue& value, const char* valueName, i
                                 }
                                 }
                                 break;
                                 break;
                             case LUA_TNUMBER:
                             case LUA_TNUMBER:
-                                lua_getupvalue(l, -1, 2);
-                                if (lua_isnil(l, -1))
-                                {
-                                    lua_pop(l, 1);
-                                }
-                                else
                                 {
                                 {
-                                    lua_pushvalue(l, -5);    // copy the user data (this pointer)
-                                    lua_pushnumber(l, static_cast<lua_Number>(strtod(subElement.m_value.c_str(), nullptr)));
-                                    lua_call(l, 2, 0);   // call the setter
+                                    lua_getupvalue(l, -1, 2);
+                                    if (lua_isnil(l, -1))
+                                    {
+                                        lua_pop(l, 1);
+                                    }
+                                    else
+                                    {
+                                        lua_pushvalue(l, -5);    // copy the user data (this pointer)
+                                        lua_pushnumber(l, static_cast<lua_Number>(strtod(subElement.m_value.c_str(), nullptr)));
+                                        lua_call(l, 2, 0);   // call the setter
+                                    }
                                 }
                                 }
                                 break;
                                 break;
                             case LUA_TBOOLEAN:
                             case LUA_TBOOLEAN:
@@ -1342,6 +1369,9 @@ ScriptContextDebug::WriteValue(const DebugValue& value, const char* valueName, i
 bool
 bool
 ScriptContextDebug::GetValue(DebugValue& value)
 ScriptContextDebug::GetValue(DebugValue& value)
 {
 {
+    // LUA Must execute in the "C" Locale.
+    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
     value.m_elements.clear();
     value.m_elements.clear();
 
 
     AZStd::vector<AZ::OSString> tokens;
     AZStd::vector<AZ::OSString> tokens;
@@ -1446,6 +1476,9 @@ ScriptContextDebug::GetValue(DebugValue& value)
 bool
 bool
 ScriptContextDebug::SetValue(const DebugValue& sourceValue)
 ScriptContextDebug::SetValue(const DebugValue& sourceValue)
 {
 {
+    // LUA Must execute in the "C" Locale.
+    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
     AZStd::vector<AZ::OSString> tokens;
     AZStd::vector<AZ::OSString> tokens;
     AZStd::tokenize<AZ::OSString>(sourceValue.m_name, ".[] ", tokens);
     AZStd::tokenize<AZ::OSString>(sourceValue.m_name, ".[] ", tokens);
 
 

+ 3 - 0
Code/Framework/AzCore/AzCore/Serialization/Json/DoubleSerializer.cpp

@@ -10,6 +10,7 @@
 
 
 #include <AzCore/Serialization/Json/CastingHelpers.h>
 #include <AzCore/Serialization/Json/CastingHelpers.h>
 #include <AzCore/Serialization/Json/JsonSerialization.h>
 #include <AzCore/Serialization/Json/JsonSerialization.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/Serialization/Json/StackedString.h>
 #include <AzCore/Serialization/Json/StackedString.h>
 #include <AzCore/Memory/SystemAllocator.h>
 #include <AzCore/Memory/SystemAllocator.h>
 #include <AzCore/std/string/string.h>
 #include <AzCore/std/string/string.h>
@@ -34,6 +35,8 @@ namespace AZ
 
 
             char* parseEnd = nullptr;
             char* parseEnd = nullptr;
 
 
+            AZ::Locale::ScopedSerializationLocale scopedLocale; // invariant locale for strtod
+
             errno = 0;
             errno = 0;
             double parsedDouble = strtod(text, &parseEnd);
             double parsedDouble = strtod(text, &parseEnd);
             if (errno == ERANGE)
             if (errno == ERANGE)

+ 35 - 0
Code/Framework/AzCore/AzCore/Serialization/Locale.cpp

@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <AzCore/Serialization/Locale.h>
+
+namespace AZ::Locale
+{
+    ScopedSerializationLocale::ScopedSerializationLocale(bool autoActivate)
+    {
+        if (autoActivate)
+        {
+            Activate();
+        }
+    }
+
+    ScopedSerializationLocale::~ScopedSerializationLocale()
+    {
+        ScopedSerializationLocale_Platform::Deactivate();
+    }
+
+    void ScopedSerializationLocale::Activate()
+    {
+        ScopedSerializationLocale_Platform::Activate();
+    }
+
+    void ScopedSerializationLocale::Deactivate()
+    {
+        ScopedSerializationLocale_Platform::Deactivate();
+    }
+} // namespace AZ::Locale

+ 42 - 0
Code/Framework/AzCore/AzCore/Serialization/Locale.h

@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Serialization/Locale_Platform.h>
+
+namespace AZ
+{
+    namespace Locale
+    {
+        //! This class allows you to activate a culture invariant locale for the current thread, so that numbers and currency
+        //! serialize using the same format regardless of the machine locale.
+        //! 
+        //! Note that there are some cases in the engine where a serialization context object is created for serialize operations,
+        //! which is a great place to store the scoped serialization locale object, but often such a context object is reserved/created
+        //! on a different thread from where the serializing actually happens.  This is why you can opt to not auto-activate the locale
+        //! on construction.
+        //!
+        //! You can repeatedly call Activate and Deactivate.
+        //!
+        //! Platform-specific implementations will not allocate on the heap, so this object can be used before the heap is ready.
+        class ScopedSerializationLocale final : public ScopedSerializationLocale_Platform
+        {
+            public:
+                ScopedSerializationLocale(bool autoActivate = true);
+                ~ScopedSerializationLocale();
+
+                //! Activate the invariant serialization locale for this thread.
+                //! This does not happen automatically.  You must call this to enter the invariant serialization locale.
+                void Activate();
+
+                //! Deactivate the invariant serialization locale for this thread.
+                void Deactivate();
+        };
+    } // namespace Locale
+} // namespace AZ

+ 6 - 0
Code/Framework/AzCore/AzCore/Serialization/ObjectStream.cpp

@@ -12,6 +12,7 @@
 #include <AzCore/Serialization/DataOverlayInstanceMsgs.h>
 #include <AzCore/Serialization/DataOverlayInstanceMsgs.h>
 #include <AzCore/Serialization/DataOverlayProviderMsgs.h>
 #include <AzCore/Serialization/DataOverlayProviderMsgs.h>
 #include <AzCore/Serialization/DynamicSerializableField.h>
 #include <AzCore/Serialization/DynamicSerializableField.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/Asset/AssetCommon.h>
 #include <AzCore/Asset/AssetCommon.h>
 #include <AzCore/Asset/AssetManager.h>
 #include <AzCore/Asset/AssetManager.h>
 #include <AzCore/Debug/Profiler.h>
 #include <AzCore/Debug/Profiler.h>
@@ -118,6 +119,7 @@ namespace AZ
                 , m_pending(0)
                 , m_pending(0)
                 , m_inStream(&m_buffer1)
                 , m_inStream(&m_buffer1)
                 , m_outStream(&m_buffer2)
                 , m_outStream(&m_buffer2)
+                , m_localeScope(false) // do not automatically activate the locale.
             {
             {
                 // Assign default asset filter if none was provided by the user.
                 // Assign default asset filter if none was provided by the user.
                 m_filterDesc = filterDesc;
                 m_filterDesc = filterDesc;
@@ -240,6 +242,7 @@ namespace AZ
             // completed successfully to make sure the equivalent amount
             // completed successfully to make sure the equivalent amount
             // of CloseElements are called
             // of CloseElements are called
             AZStd::vector<bool>                           m_writeElementResultStack;
             AZStd::vector<bool>                           m_writeElementResultStack;
+            Locale::ScopedSerializationLocale             m_localeScope;
         };
         };
 
 
         //=========================================================================
         //=========================================================================
@@ -1960,6 +1963,8 @@ namespace AZ
 
 
             bool result = true;
             bool result = true;
 
 
+            m_localeScope.Activate();
+
             if (m_flags & OPF_SAVING)
             if (m_flags & OPF_SAVING)
             {
             {
                 if (m_type == ST_XML)
                 if (m_type == ST_XML)
@@ -2180,6 +2185,7 @@ namespace AZ
                     m_stream->Write(sizeof(u8), &endTag);
                     m_stream->Write(sizeof(u8), &endTag);
                 }
                 }
             }
             }
+            m_localeScope.Deactivate();
             delete this;
             delete this;
             return success;
             return success;
         }
         }

+ 5 - 0
Code/Framework/AzCore/AzCore/Serialization/SerializeContext.cpp

@@ -12,6 +12,7 @@
 #include <AzCore/Serialization/EditContext.h>
 #include <AzCore/Serialization/EditContext.h>
 #include <AzCore/Serialization/DataOverlay.h>
 #include <AzCore/Serialization/DataOverlay.h>
 #include <AzCore/Serialization/DynamicSerializableField.h>
 #include <AzCore/Serialization/DynamicSerializableField.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/Serialization/Utils.h>
 #include <AzCore/Serialization/Utils.h>
 #include <AzCore/Asset/AssetSerializer.h>
 #include <AzCore/Asset/AssetSerializer.h>
 
 
@@ -295,6 +296,8 @@ namespace AZ
         /// Convert binary data to text
         /// Convert binary data to text
         size_t DataToText(IO::GenericStream& in, IO::GenericStream& out, bool isDataBigEndian /*= false*/) override
         size_t DataToText(IO::GenericStream& in, IO::GenericStream& out, bool isDataBigEndian /*= false*/) override
         {
         {
+            AZ::Locale::ScopedSerializationLocale scopedLocale; // invariant locale for string format
+
             AZ_Assert(in.GetLength() == sizeof(T), "Invalid data size");
             AZ_Assert(in.GetLength() == sizeof(T), "Invalid data size");
 
 
             T value;
             T value;
@@ -308,6 +311,8 @@ namespace AZ
         /// Convert text data to binary, to support loading old version formats. We must respect text version if the text->binary format has changed!
         /// Convert text data to binary, to support loading old version formats. We must respect text version if the text->binary format has changed!
         size_t  TextToData(const char* text, unsigned int textVersion, IO::GenericStream& stream, bool isDataBigEndian = false) override
         size_t  TextToData(const char* text, unsigned int textVersion, IO::GenericStream& stream, bool isDataBigEndian = false) override
         {
         {
+            AZ::Locale::ScopedSerializationLocale scopedLocale; // invariant locale for strtod
+
             AZ_Assert(textVersion == 0, "Unknown float/double version!");
             AZ_Assert(textVersion == 0, "Unknown float/double version!");
             (void)textVersion;
             (void)textVersion;
             double value = strtod(text, nullptr);
             double value = strtod(text, nullptr);

+ 5 - 0
Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp

@@ -16,6 +16,7 @@
 #include <AzCore/Serialization/Json/JsonImporter.h>
 #include <AzCore/Serialization/Json/JsonImporter.h>
 #include <AzCore/Serialization/Json/JsonSerialization.h>
 #include <AzCore/Serialization/Json/JsonSerialization.h>
 #include <AzCore/Serialization/Json/StackedString.h>
 #include <AzCore/Serialization/Json/StackedString.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/Settings/SettingsRegistryImpl.h>
 #include <AzCore/Settings/SettingsRegistryImpl.h>
 #include <AzCore/std/sort.h>
 #include <AzCore/std/sort.h>
 #include <AzCore/std/parallel/scoped_lock.h>
 #include <AzCore/std/parallel/scoped_lock.h>
@@ -613,6 +614,9 @@ namespace AZ
     bool SettingsRegistryImpl::MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view rootKey,
     bool SettingsRegistryImpl::MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view rootKey,
         const CommandLineArgumentSettings& commandLineSettings)
         const CommandLineArgumentSettings& commandLineSettings)
     {
     {
+        // Incoming data is always in "C" locale.
+        AZ::Locale::ScopedSerializationLocale scopedLocale;
+
         if (!commandLineSettings.m_delimiterFunc)
         if (!commandLineSettings.m_delimiterFunc)
         {
         {
             AZ_Error("SettingsRegistry", false,
             AZ_Error("SettingsRegistry", false,
@@ -671,6 +675,7 @@ namespace AZ
             // function is used, there wouldn't need to be a limitation
             // function is used, there wouldn't need to be a limitation
             return false;
             return false;
         }
         }
+
         valueString = value;
         valueString = value;
         const char* valueStringEnd = valueString.c_str() + valueString.size();
         const char* valueStringEnd = valueString.c_str() + valueString.size();
 
 

+ 2 - 0
Code/Framework/AzCore/AzCore/azcore_files.cmake

@@ -559,6 +559,8 @@ set(FILES
     Serialization/EditContextConstants.inl
     Serialization/EditContextConstants.inl
     Serialization/IdUtils.inl
     Serialization/IdUtils.inl
     Serialization/IdUtils.h
     Serialization/IdUtils.h
+    Serialization/Locale.h
+    Serialization/Locale.cpp
     Serialization/Utils.h
     Serialization/Utils.h
     Serialization/SerializationUtils.cpp
     Serialization/SerializationUtils.cpp
     Serialization/ObjectStream.cpp
     Serialization/ObjectStream.cpp

+ 4 - 11
Code/Framework/AzCore/AzCore/std/string/conversions.h

@@ -9,6 +9,7 @@
 
 
 #include <AzCore/std/string/string.h>
 #include <AzCore/std/string/string.h>
 #include <AzCore/std/string/fixed_string.h>
 #include <AzCore/std/string/fixed_string.h>
+#include <AzCore/Serialization/Locale.h>
 
 
 #include <ctype.h>
 #include <ctype.h>
 #include <wctype.h>
 #include <wctype.h>
@@ -184,6 +185,8 @@ namespace AZStd
     template<class Allocator>
     template<class Allocator>
     float stof(const AZStd::basic_string<AZStd::string::value_type, AZStd::string::traits_type, Allocator>& str, AZStd::size_t* idx = 0)
     float stof(const AZStd::basic_string<AZStd::string::value_type, AZStd::string::traits_type, Allocator>& str, AZStd::size_t* idx = 0)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // invariant locale for converting strings to values.
+
         char* ptr;
         char* ptr;
         const char* sChar = str.c_str();
         const char* sChar = str.c_str();
         float result = (float)strtod(sChar, &ptr);
         float result = (float)strtod(sChar, &ptr);
@@ -196,6 +199,7 @@ namespace AZStd
     template<class Allocator>
     template<class Allocator>
     double stod(const AZStd::basic_string<AZStd::string::value_type, AZStd::string::traits_type, Allocator>& str, AZStd::size_t* idx = 0)
     double stod(const AZStd::basic_string<AZStd::string::value_type, AZStd::string::traits_type, Allocator>& str, AZStd::size_t* idx = 0)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // invariant locale for converting strings to values.
         char* ptr;
         char* ptr;
         const char* sChar = str.c_str();
         const char* sChar = str.c_str();
         double result = strtod(sChar, &ptr);
         double result = strtod(sChar, &ptr);
@@ -205,17 +209,6 @@ namespace AZStd
         }
         }
         return result;
         return result;
     }
     }
-    /*
-    template<class Allocator>
-    long double stold(const AZStd::basic_string<string::value_type,string::traits_type,Allocator>& str, size_t *idx = 0)
-    {
-        char* ptr;
-        const char * sChar = str.c_str();
-        long double result = strtold(sChar,&ptr);
-        if(idx)
-            *idx = ptr - sChar;
-        return result;
-    }*/
 
 
     // Standard is messy when it comes to custom string. Let's say we have a string with different allocator ???
     // Standard is messy when it comes to custom string. Let's say we have a string with different allocator ???
     // so we have our (custom) implementations here and wrappers so we are compatible with the standard.
     // so we have our (custom) implementations here and wrappers so we are compatible with the standard.

+ 10 - 0
Code/Framework/AzCore/Platform/Android/AzCore/Serialization/Locale_Platform.h

@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+#pragma once
+
+#include "../../../Common/UnixLike/AzCore/Serialization/Locale_UnixLike.h"

+ 3 - 0
Code/Framework/AzCore/Platform/Android/platform_android_files.cmake

@@ -27,6 +27,9 @@ set(FILES
     ../Common/Unimplemented/AzCore/PlatformIncl_Unimplemented.h
     ../Common/Unimplemented/AzCore/PlatformIncl_Unimplemented.h
     ../Common/UnixLike/AzCore/Platform_UnixLike.cpp
     ../Common/UnixLike/AzCore/Platform_UnixLike.cpp
     AzCore/PlatformIncl_Platform.h
     AzCore/PlatformIncl_Platform.h
+    AzCore/Serialization/Locale_Platform.h
+    ../Common/UnixLike/AzCore/Serialization/Locale_UnixLike.h
+    ../Common/UnixLike/AzCore/Serialization/Locale_UnixLike.cpp
     ../Common/UnixLike/AzCore/Process/ProcessInfo_UnixLike.cpp
     ../Common/UnixLike/AzCore/Process/ProcessInfo_UnixLike.cpp
     ../Common/Unimplemented/AzCore/Debug/StackTracer_Unimplemented.cpp
     ../Common/Unimplemented/AzCore/Debug/StackTracer_Unimplemented.cpp
     ../Common/UnixLike/AzCore/Debug/Trace_UnixLike.cpp
     ../Common/UnixLike/AzCore/Debug/Trace_UnixLike.cpp

+ 39 - 0
Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Serialization/Locale_UnixLike.cpp

@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <AzCore/Serialization/Locale_Platform.h>
+#include <langinfo.h>
+
+namespace AZ::Locale
+{
+    void ScopedSerializationLocale_Platform::Activate()
+    {
+        if (m_isActive)
+        {
+            Deactivate();
+        }
+
+        // The actual cost to create a new locale is extremely low on linux/unix type systems, and doing complex things to 
+        // try to avoid doing so is probably not worth the time it costs to actually do so. If this ever shows up in a profiler,
+        // then it might be better to see if there's a way to avoid calling this object at all or call it fewer times, 
+        // rather than to try to optimize the innards of this actual call.
+        m_createdLocale = newlocale(LC_ALL_MASK, "C", (locale_t)0);
+        uselocale(m_createdLocale);
+        m_isActive = true;
+    }
+
+    void ScopedSerializationLocale_Platform::Deactivate()
+    {
+        if (m_isActive)
+        {
+            uselocale(LC_GLOBAL_LOCALE);
+            freelocale(m_createdLocale);
+            m_isActive = false;
+        }
+    }
+} // namespace AZ::Locale

+ 26 - 0
Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Serialization/Locale_UnixLike.h

@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+#pragma once
+
+#include <locale.h>
+
+namespace AZ
+{
+    namespace Locale
+    {
+        class ScopedSerializationLocale_Platform
+        {
+            public:
+                void Activate();
+                void Deactivate();
+            private:
+                locale_t m_createdLocale;
+                bool m_isActive = false;
+        };
+    }
+}

+ 48 - 0
Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Serialization/Locale_WinAPI.cpp

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <locale.h>
+#include <AzCore/Serialization/Locale_Platform.h>
+
+namespace AZ::Locale
+{
+    void ScopedSerializationLocale_Platform::Activate()
+    {
+        if (m_isActive)
+        {
+            Deactivate();
+        }
+
+        // Save the previous "are threads allowed to have per-thread locale?" setting:
+        m_previousThreadLocalSetting = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
+
+        // Save the current locale. The return value of setlocale depends on the above call happening first.
+        m_previousLocale = setlocale(LC_ALL, nullptr);
+
+        // shortcut here, if we won't have any effect, then don't make any further system calls.
+        if ( (m_previousLocale == "C")&&(m_previousThreadLocalSetting == _ENABLE_PER_THREAD_LOCALE) )
+        {
+            return;
+        }
+
+        // if we get here, we made some sort of change, and must set the locale and also remember to reset it back.
+        setlocale(LC_ALL, "C");
+        m_isActive = true;
+    }
+
+    void ScopedSerializationLocale_Platform::Deactivate()
+    {
+        if (m_isActive)
+        {
+            setlocale(LC_ALL, m_previousLocale.c_str());
+            _configthreadlocale(m_previousThreadLocalSetting);
+            m_isActive = false;
+        }
+    }
+
+} // namespace AZ::Locale

+ 34 - 0
Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Serialization/Locale_WinAPI.h

@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+#pragma once
+
+#include <AzCore/std/string/fixed_string.h>
+
+// must be able to store all possible locale strings, which are not usually very long, 
+// usually something like "English_United States.UTF8" which is one of the longest
+
+// Note that this is also the max defined in Windows.h but we'd like to avoid using windows.h
+// in headers if we can possibly avoid it.
+#define AZ_LOCALE_NAME_MAX_LENGTH 85 
+
+namespace AZ
+{
+    namespace Locale
+    {
+        class ScopedSerializationLocale_Platform
+        {
+            public:
+                void Activate();
+                void Deactivate();
+            private:
+                AZStd::fixed_string<AZ_LOCALE_NAME_MAX_LENGTH> m_previousLocale;
+                int m_previousThreadLocalSetting = 0;
+                bool m_isActive = false;
+        };
+    }
+}

+ 10 - 0
Code/Framework/AzCore/Platform/Linux/AzCore/Serialization/Locale_Platform.h

@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+#pragma once
+
+#include "../../../Common/UnixLike/AzCore/Serialization/Locale_UnixLike.h"

+ 3 - 0
Code/Framework/AzCore/Platform/Linux/platform_linux_files.cmake

@@ -58,6 +58,9 @@ set(FILES
     ../Common/UnixLike/AzCore/PlatformIncl_UnixLike.h
     ../Common/UnixLike/AzCore/PlatformIncl_UnixLike.h
     ../Common/UnixLike/AzCore/Platform_UnixLike.cpp
     ../Common/UnixLike/AzCore/Platform_UnixLike.cpp
     AzCore/PlatformIncl_Platform.h
     AzCore/PlatformIncl_Platform.h
+    AzCore/Serialization/Locale_Platform.h
+    ../Common/UnixLike/AzCore/Serialization/Locale_UnixLike.h
+    ../Common/UnixLike/AzCore/Serialization/Locale_UnixLike.cpp
     ../Common/UnixLike/AzCore/Settings/CommandLineParser_UnixLike.cpp
     ../Common/UnixLike/AzCore/Settings/CommandLineParser_UnixLike.cpp
     ../Common/UnixLike/AzCore/Socket/AzSocket_fwd_UnixLike.h
     ../Common/UnixLike/AzCore/Socket/AzSocket_fwd_UnixLike.h
     ../Common/UnixLike/AzCore/Socket/AzSocket_UnixLike.cpp
     ../Common/UnixLike/AzCore/Socket/AzSocket_UnixLike.cpp

+ 10 - 0
Code/Framework/AzCore/Platform/Mac/AzCore/Serialization/Locale_Platform.h

@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+#pragma once
+
+#include "../../../Common/UnixLike/AzCore/Serialization/Locale_UnixLike.h"

+ 3 - 0
Code/Framework/AzCore/Platform/Mac/platform_mac_files.cmake

@@ -60,6 +60,9 @@ set(FILES
     ../Common/UnixLike/AzCore/PlatformIncl_UnixLike.h
     ../Common/UnixLike/AzCore/PlatformIncl_UnixLike.h
     AzCore/Platform_Mac.cpp
     AzCore/Platform_Mac.cpp
     AzCore/PlatformIncl_Platform.h
     AzCore/PlatformIncl_Platform.h
+    AzCore/Serialization/Locale_Platform.h
+    ../Common/UnixLike/AzCore/Serialization/Locale_UnixLike.h
+    ../Common/UnixLike/AzCore/Serialization/Locale_UnixLike.cpp
     ../Common/UnixLike/AzCore/Settings/CommandLineParser_UnixLike.cpp
     ../Common/UnixLike/AzCore/Settings/CommandLineParser_UnixLike.cpp
     ../Common/UnixLike/AzCore/Socket/AzSocket_fwd_UnixLike.h
     ../Common/UnixLike/AzCore/Socket/AzSocket_fwd_UnixLike.h
     ../Common/UnixLike/AzCore/Socket/AzSocket_UnixLike.cpp
     ../Common/UnixLike/AzCore/Socket/AzSocket_UnixLike.cpp

+ 10 - 0
Code/Framework/AzCore/Platform/Windows/AzCore/Serialization/Locale_Platform.h

@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+#pragma once
+
+#include "../../../Common/WinAPI/AzCore/Serialization/Locale_WinAPI.h"

+ 3 - 0
Code/Framework/AzCore/Platform/Windows/platform_windows_files.cmake

@@ -63,6 +63,9 @@ set(FILES
     AzCore/Platform_Windows.cpp
     AzCore/Platform_Windows.cpp
     AzCore/PlatformIncl_Platform.h
     AzCore/PlatformIncl_Platform.h
     AzCore/PlatformIncl_Windows.h
     AzCore/PlatformIncl_Windows.h
+    AzCore/Serialization/Locale_Platform.h
+    ../Common/WinAPI/AzCore/Serialization/Locale_WinAPI.h
+    ../Common/WinAPI/AzCore/Serialization/Locale_WinAPI.cpp
     ../Common/WinAPI/AzCore/Settings/CommandLineParser_WinAPI.cpp
     ../Common/WinAPI/AzCore/Settings/CommandLineParser_WinAPI.cpp
     ../Common/WinAPI/AzCore/Socket/AzSocket_fwd_WinAPI.h
     ../Common/WinAPI/AzCore/Socket/AzSocket_fwd_WinAPI.h
     ../Common/WinAPI/AzCore/Socket/AzSocket_WinAPI.cpp
     ../Common/WinAPI/AzCore/Socket/AzSocket_WinAPI.cpp

+ 10 - 0
Code/Framework/AzCore/Platform/iOS/AzCore/Serialization/Locale_Platform.h

@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+#pragma once
+
+#include "../../../Common/UnixLike/AzCore/Serialization/Locale_UnixLike.h"

+ 3 - 0
Code/Framework/AzCore/Platform/iOS/platform_ios_files.cmake

@@ -58,6 +58,9 @@ set(FILES
     ../Common/UnixLike/AzCore/PlatformIncl_UnixLike.h
     ../Common/UnixLike/AzCore/PlatformIncl_UnixLike.h
     ../Common/UnixLike/AzCore/Platform_UnixLike.cpp
     ../Common/UnixLike/AzCore/Platform_UnixLike.cpp
     AzCore/PlatformIncl_Platform.h
     AzCore/PlatformIncl_Platform.h
+    AzCore/Serialization/Locale_Platform.h
+    ../Common/UnixLike/AzCore/Serialization/Locale_UnixLike.h
+    ../Common/UnixLike/AzCore/Serialization/Locale_UnixLike.cpp
     ../Common/UnixLike/AzCore/Settings/CommandLineParser_UnixLike.cpp
     ../Common/UnixLike/AzCore/Settings/CommandLineParser_UnixLike.cpp
     ../Common/UnixLike/AzCore/Socket/AzSocket_fwd_UnixLike.h
     ../Common/UnixLike/AzCore/Socket/AzSocket_fwd_UnixLike.h
     ../Common/UnixLike/AzCore/Socket/AzSocket_UnixLike.cpp
     ../Common/UnixLike/AzCore/Socket/AzSocket_UnixLike.cpp

+ 89 - 32
Code/Framework/AzCore/Tests/Serialization.cpp

@@ -65,6 +65,8 @@
 #include <AzCore/UnitTest/TestTypes.h>
 #include <AzCore/UnitTest/TestTypes.h>
 #include <AZTestShared/Utils/Utils.h>
 #include <AZTestShared/Utils/Utils.h>
 
 
+#include <locale.h>
+
 namespace SerializeTestClasses {
 namespace SerializeTestClasses {
     class MyClassBase1
     class MyClassBase1
     {
     {
@@ -1400,17 +1402,53 @@ namespace UnitTest
         void SetUp() override
         void SetUp() override
         {
         {
             Serialization::SetUp();
             Serialization::SetUp();
-
+            setlocale(LC_NUMERIC, "en-US");
             m_context.reset(aznew SerializeContext());
             m_context.reset(aznew SerializeContext());
         }
         }
 
 
         void TearDown() override
         void TearDown() override
         {
         {
+            setlocale(LC_NUMERIC, "en-US");
             m_context.reset();
             m_context.reset();
 
 
             Serialization::TearDown();
             Serialization::TearDown();
         }
         }
 
 
+        void InitializeValues()
+        {
+            m_char = -1;
+            m_short = -2;
+            m_int = -3;
+            m_long = -4;
+            m_s64 = -5;
+            m_uchar = 1;
+            m_ushort = 2;
+            m_uint = 3;
+            m_ulong = 4;
+            m_u64 = 5;
+            m_float = 2.f;
+            m_double = 20.0000005;
+            m_true = true;
+            m_false = false;
+
+            // Math
+            m_uuid = AZ::Uuid::CreateString("{16490FB4-A7CE-4a8a-A882-F98DDA6A788F}");
+            m_vector2 = Vector2(1.0f, 2.0f);
+            m_vector3 = Vector3(3.0f, 4.0f, 5.0f);
+            m_vector4 = Vector4(6.0f, 7.0f, 8.0f, 9.0f);
+
+            m_quaternion = Quaternion::CreateRotationZ(0.7f);
+            m_transform = Transform::CreateRotationX(1.1f);
+            m_matrix3x3 = Matrix3x3::CreateRotationY(0.5f);
+            m_matrix4x4 = Matrix4x4::CreateFromQuaternionAndTranslation(m_quaternion, m_vector3);
+
+            m_aabb.Set(-m_vector3, m_vector3);
+            m_plane.Set(m_vector4);
+
+            m_classicEnum = CE_A;
+            m_classEnum = ClassEnum::B;
+        }
+
         void OnLoadedClassReady(void* classPtr, const Uuid& classId, int* callCount)
         void OnLoadedClassReady(void* classPtr, const Uuid& classId, int* callCount)
         {
         {
             switch ((*callCount)++)
             switch ((*callCount)++)
@@ -1867,37 +1905,7 @@ TEST_F(SerializeBasicTest, DISABLED_BasicTypeTest_Succeed)
 TEST_F(SerializeBasicTest, BasicTypeTest_Succeed)
 TEST_F(SerializeBasicTest, BasicTypeTest_Succeed)
 #endif
 #endif
     {
     {
-        m_char = -1;
-        m_short = -2;
-        m_int = -3;
-        m_long = -4;
-        m_s64 = -5;
-        m_uchar = 1;
-        m_ushort = 2;
-        m_uint = 3;
-        m_ulong = 4;
-        m_u64 = 5;
-        m_float = 2.f;
-        m_double = 20.0000005;
-        m_true = true;
-        m_false = false;
-
-        // Math
-        m_uuid = AZ::Uuid::CreateString("{16490FB4-A7CE-4a8a-A882-F98DDA6A788F}");
-        m_vector2 = Vector2(1.0f, 2.0f);
-        m_vector3 = Vector3(3.0f, 4.0f, 5.0f);
-        m_vector4 = Vector4(6.0f, 7.0f, 8.0f, 9.0f);
-
-        m_quaternion = Quaternion::CreateRotationZ(0.7f);
-        m_transform = Transform::CreateRotationX(1.1f);
-        m_matrix3x3 = Matrix3x3::CreateRotationY(0.5f);
-        m_matrix4x4 = Matrix4x4::CreateFromQuaternionAndTranslation(m_quaternion, m_vector3);
-
-        m_aabb.Set(-m_vector3, m_vector3);
-        m_plane.Set(m_vector4);
-
-        m_classicEnum = CE_A;
-        m_classEnum = ClassEnum::B;
+        InitializeValues();
 
 
         TestFileIOBase fileIO;
         TestFileIOBase fileIO;
         SetRestoreFileIOBaseRAII restoreFileIOScope(fileIO);
         SetRestoreFileIOBaseRAII restoreFileIOScope(fileIO);
@@ -1946,6 +1954,55 @@ TEST_F(SerializeBasicTest, BasicTypeTest_Succeed)
             TestLoad(&stream);
             TestLoad(&stream);
         }
         }
     }
     }
+
+    TEST_F(SerializeBasicTest, BasicTypeTest_LocaleIndependent)
+    {
+        InitializeValues();
+
+        m_s64 = -50000; // ensure that the number is large enough so that if the locale inserts commas, they are there
+        m_float = 20000.5f;
+        m_double = 20000.5; // the number has values after the decimal point and is large enough that it would be formatted with a comma
+
+        TestFileIOBase fileIO;
+        SetRestoreFileIOBaseRAII restoreFileIOScope(fileIO);
+
+        // Store test files within a temporary directory that is deleted
+        // when the variable goes out of scope
+        AZ::Test::ScopedAutoTempDirectory tempDirectory;
+        AZ::IO::Path serializeTestFilePath = tempDirectory.GetDirectory();
+
+        auto readwriteFn = [&](AZ::IO::Path testFilePath, const char* localewrite, const char* localeread, AZ::DataStream::StreamType streamType)
+        {
+            {
+                setlocale(LC_ALL, localewrite);
+                AZ_TracePrintf("SerializeBasicTest", "\nWriting as XML with global locale %s...\n", localewrite);
+                IO::FileIOStream stream(testFilePath.c_str(), IO::OpenMode::ModeWrite);
+                TestSave(&stream, streamType);
+            }
+            {
+                setlocale(LC_ALL, localeread);
+                AZ_TracePrintf("SerializeBasicTest", "Loading as XML with global locale %s...\n", localeread);
+                IO::FileIOStream stream(testFilePath.c_str(), IO::OpenMode::ModeRead);
+                TestLoad(&stream);
+            }
+        };
+        
+
+        // XML version
+        AZ::IO::Path testXmlFilePath = serializeTestFilePath / "serializebasictest_localeindependent.xml";
+        readwriteFn(testXmlFilePath, "en-US", "pl-PL", ObjectStream::ST_XML);
+        readwriteFn(testXmlFilePath, "pl-PL", "en-US", ObjectStream::ST_XML);
+
+        // JSON version
+        AZ::IO::Path testJsonFilePath = serializeTestFilePath / "serializebasictest_localeindependent.json";
+        readwriteFn(testJsonFilePath, "en-US", "pl-PL", ObjectStream::ST_JSON);
+        readwriteFn(testJsonFilePath, "pl-PL", "en-US", ObjectStream::ST_XML);
+        
+        // Binary version
+        AZ::IO::Path testBinFilePath = serializeTestFilePath / "serializebasictest_localeindependent.bin";
+        readwriteFn(testJsonFilePath, "en-US", "pl-PL", ObjectStream::ST_BINARY);
+        readwriteFn(testJsonFilePath, "pl-PL", "en-US", ObjectStream::ST_BINARY);
+    }
     /*
     /*
     * Test serialization of built-in container types
     * Test serialization of built-in container types
     */
     */

+ 45 - 0
Code/Framework/AzCore/Tests/Serialization/Json/JsonSerializationUtilsTests.cpp

@@ -166,6 +166,51 @@ namespace UnitTest
         EXPECT_TRUE(dataToSave == loadedData);
         EXPECT_TRUE(dataToSave == loadedData);
     }
     }
 
 
+    TEST_F(JsonSerializationUtilsTests, SaveLoadObjectToStream_WithCustomLocales_Success)
+    {
+        // This test is nearly identical to the above, but makes sure that if the system has a locale set
+        // which uses a comma as a decimal separator, that the system can still serialize and deserialize
+        // and does so in a way that is locale agnostic.  We do this by setting the locale to one that uses commas
+        // and writing to the buffer (saving) and then setting it to a DIFFERENT locale and reading, in both directions
+        // if the system is sensitive to locale, the test will fail becuase either one direction or the other will use the
+        // system locale.
+
+        auto testLocales = [&](const char* locale1, const char* locale2)
+        {
+            const char* priorLocale = setlocale(LC_ALL, nullptr);
+            setlocale(LC_ALL, locale1);
+            char buffer[1024];
+            IO::MemoryStream stream(buffer, 1024, 0);
+
+            m_serializationSettings.m_keepDefaults = true;
+
+            Test1::TestClass dataToSave;
+            dataToSave.Init();
+            dataToSave.m_float = 10;
+            dataToSave.m_string = "SaveObjectToStreamSuccess";
+
+            Outcome<void, AZStd::string> saveResult = JsonSerializationUtils::SaveObjectToStream(&dataToSave, stream, (Test1::TestClass*)nullptr, &m_serializationSettings);
+
+            EXPECT_TRUE(saveResult.IsSuccess());
+
+            setlocale(LC_ALL, locale2);
+            Test1::TestClass loadedData;
+            stream.Seek(0, IO::GenericStream::ST_SEEK_BEGIN);
+            Outcome<void, AZStd::string> loadResult = JsonSerializationUtils::LoadObjectFromStream(loadedData, stream, &m_deserializationSettings);
+        
+            EXPECT_TRUE(loadResult.IsSuccess());
+
+            EXPECT_TRUE(dataToSave == loadedData);
+
+            setlocale(LC_ALL, priorLocale);
+        };
+
+        testLocales("en-US", "pl-PL");
+        testLocales("pl-PL", "en-US");
+        testLocales("en-US", "pl-PL");
+        testLocales("pl-PL", "en-US");
+    }
+
     TEST_F(JsonSerializationUtilsTests, SaveObjectToStream_Failed_NoSerializationContext)
     TEST_F(JsonSerializationUtilsTests, SaveObjectToStream_Failed_NoSerializationContext)
     {
     {
         char buffer[1024];
         char buffer[1024];

+ 4 - 0
Code/Legacy/CrySystem/LocalizedStringManager.cpp

@@ -27,6 +27,7 @@
 #include <AzCore/std/string/conversions.h>
 #include <AzCore/std/string/conversions.h>
 #include <AzFramework/StringFunc/StringFunc.h>
 #include <AzFramework/StringFunc/StringFunc.h>
 #include <AzCore/std/string/conversions.h>
 #include <AzCore/std/string/conversions.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/Math/Crc.h>
 #include <AzCore/Math/Crc.h>
 
 
 #define MAX_CELL_COUNT 32
 #define MAX_CELL_COUNT 32
@@ -938,6 +939,9 @@ bool CLocalizedStringsManager::DoLoadExcelXmlSpreadsheet(const char* sFileName,
         }
         }
     }
     }
 
 
+    // Use the invariant culture while loading files from disk
+    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
     ListAndClearProblemLabels();
     ListAndClearProblemLabels();
 
 
     IXmlTableReader* const pXmlTableReader = m_pSystem->GetXmlUtils()->CreateXmlTableReader();
     IXmlTableReader* const pXmlTableReader = m_pSystem->GetXmlUtils()->CreateXmlTableReader();

+ 5 - 0
Code/Legacy/CrySystem/XConsole.cpp

@@ -27,6 +27,7 @@
 #include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
 #include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
 #include <AzCore/std/string/conversions.h>
 #include <AzCore/std/string/conversions.h>
 #include <AzCore/std/algorithm.h>
 #include <AzCore/std/algorithm.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/Time/ITime.h>
 #include <AzCore/Time/ITime.h>
 
 
 //#define DEFENCE_CVAR_HASH_LOGGING
 //#define DEFENCE_CVAR_HASH_LOGGING
@@ -103,6 +104,10 @@ void Command_SetWaitSeconds(IConsoleCmdArgs* pCmd)
 
 
     if (pCmd->GetArgCount() > 1)
     if (pCmd->GetArgCount() > 1)
     {
     {
+        // console commands are interpreted in the invarant locale as they come from cfg files which need to be
+        // portable. 
+        AZ::Locale::ScopedSerializationLocale scopedLocale; 
+
         pConsole->m_waitSeconds.SetSeconds(atof(pCmd->GetArg(1)));
         pConsole->m_waitSeconds.SetSeconds(atof(pCmd->GetArg(1)));
         const AZ::TimeMs elaspedTimeMs = AZ::GetRealElapsedTimeMs();
         const AZ::TimeMs elaspedTimeMs = AZ::GetRealElapsedTimeMs();
         pConsole->m_waitSeconds += CTimeValue(AZ::TimeMsToSecondsDouble(elaspedTimeMs));
         pConsole->m_waitSeconds += CTimeValue(AZ::TimeMsToSecondsDouble(elaspedTimeMs));

+ 8 - 0
Code/Legacy/CrySystem/XConsoleVariable.cpp

@@ -17,6 +17,8 @@
 
 
 #include <algorithm>
 #include <algorithm>
 
 
+#include <AzCore/Serialization/Locale.h>
+
 namespace
 namespace
 {
 {
     using stack_string = AZStd::fixed_string<512>;
     using stack_string = AZStd::fixed_string<512>;
@@ -417,6 +419,10 @@ void CXConsoleVariableFloat::Set(const char* s)
     float fValue = 0;
     float fValue = 0;
     if (s)
     if (s)
     {
     {
+        // console commands are interpreted in the invarant locale as they come from cfg files which need to be
+        // portable. 
+        AZ::Locale::ScopedSerializationLocale scopedLocale; 
+
         fValue = (float)atof(s);
         fValue = (float)atof(s);
     }
     }
 
 
@@ -488,6 +494,8 @@ void CXConsoleVariableFloatRef::Set(const char *s)
     float fValue = 0;
     float fValue = 0;
     if (s)
     if (s)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; 
+
         fValue = (float)atof(s);
         fValue = (float)atof(s);
     }
     }
     if (fValue == m_fValue && (m_nFlags & VF_ALWAYSONCHANGE) == 0)
     if (fValue == m_fValue && (m_nFlags & VF_ALWAYSONCHANGE) == 0)

+ 27 - 0
Code/Legacy/CrySystem/XML/XMLBinaryNode.cpp

@@ -11,6 +11,7 @@
 #include "Cry_Color.h"
 #include "Cry_Color.h"
 #include "XMLBinaryNode.h"
 #include "XMLBinaryNode.h"
 
 
+#include <AzCore/Serialization/Locale.h>
 //////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////
 CBinaryXmlData::~CBinaryXmlData()
 CBinaryXmlData::~CBinaryXmlData()
 {
 {
@@ -88,6 +89,8 @@ bool CBinaryXmlNode::getAttr(const char* key, int& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for parsing to be culture invariant
+
         value = atoi(svalue);
         value = atoi(svalue);
         return true;
         return true;
     }
     }
@@ -99,6 +102,8 @@ bool CBinaryXmlNode::getAttr(const char* key, unsigned int& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for parsing to be culture invariant
+
         value = static_cast<unsigned int>(strtoul(svalue, nullptr, 10));
         value = static_cast<unsigned int>(strtoul(svalue, nullptr, 10));
         return true;
         return true;
     }
     }
@@ -111,6 +116,8 @@ bool CBinaryXmlNode::getAttr(const char* key, int64& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for parsing to be culture invariant
+
         value = strtoll(svalue, nullptr, 10);
         value = strtoll(svalue, nullptr, 10);
         return true;
         return true;
     }
     }
@@ -123,6 +130,8 @@ bool CBinaryXmlNode::getAttr(const char* key, uint64& value, bool useHexFormat)
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for parsing to be culture invariant
+
         value = strtoull(svalue, nullptr, useHexFormat ? 16 : 10);
         value = strtoull(svalue, nullptr, useHexFormat ? 16 : 10);
         return true;
         return true;
     }
     }
@@ -134,6 +143,8 @@ bool CBinaryXmlNode::getAttr(const char* key, bool& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for parsing to be culture invariant
+
         value = atoi(svalue) != 0;
         value = atoi(svalue) != 0;
         return true;
         return true;
     }
     }
@@ -145,6 +156,8 @@ bool CBinaryXmlNode::getAttr(const char* key, float& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for parsing to be culture invariant
+
         value = (float)atof(svalue);
         value = (float)atof(svalue);
         return true;
         return true;
     }
     }
@@ -156,6 +169,8 @@ bool CBinaryXmlNode::getAttr(const char* key, double& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for parsing to be culture invariant
+
         value = atof(svalue);
         value = atof(svalue);
         return true;
         return true;
     }
     }
@@ -167,6 +182,8 @@ bool CBinaryXmlNode::getAttr(const char* key, Ang3& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for parsing to be culture invariant
+
         float x, y, z;
         float x, y, z;
         if (azsscanf(svalue, "%f,%f,%f", &x, &y, &z) == 3)
         if (azsscanf(svalue, "%f,%f,%f", &x, &y, &z) == 3)
         {
         {
@@ -183,6 +200,8 @@ bool CBinaryXmlNode::getAttr(const char* key, Vec3& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for parsing to be culture invariant
+
         float x, y, z;
         float x, y, z;
         if (azsscanf(svalue, "%f,%f,%f", &x, &y, &z) == 3)
         if (azsscanf(svalue, "%f,%f,%f", &x, &y, &z) == 3)
         {
         {
@@ -199,6 +218,8 @@ bool CBinaryXmlNode::getAttr(const char* key, Vec4& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for parsing to be culture invariant
+
         float x, y, z, w;
         float x, y, z, w;
         if (azsscanf(svalue, "%f,%f,%f,%f", &x, &y, &z, &w) == 4)
         if (azsscanf(svalue, "%f,%f,%f,%f", &x, &y, &z, &w) == 4)
         {
         {
@@ -215,6 +236,8 @@ bool CBinaryXmlNode::getAttr(const char* key, Vec2& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for parsing to be culture invariant
+
         float x, y;
         float x, y;
         if (azsscanf(svalue, "%f,%f", &x, &y) == 2)
         if (azsscanf(svalue, "%f,%f", &x, &y) == 2)
         {
         {
@@ -231,6 +254,8 @@ bool CBinaryXmlNode::getAttr(const char* key, Quat& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for parsing to be culture invariant
+
         float w, x, y, z;
         float w, x, y, z;
         if (azsscanf(svalue, "%f,%f,%f,%f", &w, &x, &y, &z) == 4)
         if (azsscanf(svalue, "%f,%f,%f,%f", &w, &x, &y, &z) == 4)
         {
         {
@@ -247,6 +272,8 @@ bool CBinaryXmlNode::getAttr(const char* key, ColorB& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // for parsing to be culture invariant
+
         unsigned int r, g, b, a = 255;
         unsigned int r, g, b, a = 255;
         int numFound = azsscanf(svalue, "%u,%u,%u,%u", &r, &g, &b, &a);
         int numFound = azsscanf(svalue, "%u,%u,%u,%u", &r, &g, &b, &a);
         if (numFound == 3 || numFound == 4)
         if (numFound == 3 || numFound == 4)

+ 9 - 0
Code/Legacy/CrySystem/XML/XmlUtils.cpp

@@ -16,6 +16,8 @@
 #include "SerializeXMLReader.h"
 #include "SerializeXMLReader.h"
 #include "SerializeXMLWriter.h"
 #include "SerializeXMLWriter.h"
 
 
+#include <AzCore/Serialization/Locale.h>
+
 #include "XMLBinaryReader.h"
 #include "XMLBinaryReader.h"
 
 
 //////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////
@@ -51,9 +53,13 @@ IXmlParser* CXmlUtils::CreateXmlParser()
 //////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////
 XmlNodeRef CXmlUtils::LoadXmlFromFile(const char* sFilename, bool bReuseStrings)
 XmlNodeRef CXmlUtils::LoadXmlFromFile(const char* sFilename, bool bReuseStrings)
 {
 {
+    // when saving and loading data files to disk, use the invariant locale
+    AZ::Locale::ScopedSerializationLocale scopedLocale; 
+
     // XmlParser is supposed to log warnings and errors (if any),
     // XmlParser is supposed to log warnings and errors (if any),
     // so we don't need to call parser.getErrorString(),
     // so we don't need to call parser.getErrorString(),
     // CryLog() etc here.
     // CryLog() etc here.
+    
     XmlParser parser(bReuseStrings);
     XmlParser parser(bReuseStrings);
     return parser.ParseFile(sFilename, true);
     return parser.ParseFile(sFilename, true);
 }
 }
@@ -61,6 +67,9 @@ XmlNodeRef CXmlUtils::LoadXmlFromFile(const char* sFilename, bool bReuseStrings)
 //////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////
 XmlNodeRef CXmlUtils::LoadXmlFromBuffer(const char* buffer, size_t size, bool bReuseStrings, bool bSuppressWarnings)
 XmlNodeRef CXmlUtils::LoadXmlFromBuffer(const char* buffer, size_t size, bool bReuseStrings, bool bSuppressWarnings)
 {
 {
+     // when saving and loading data files to disk, use the invariant locale
+    AZ::Locale::ScopedSerializationLocale scopedLocale; 
+
     XmlParser parser(bReuseStrings);
     XmlParser parser(bReuseStrings);
     XmlNodeRef node = parser.ParseBuffer(buffer, static_cast<int>(size), true, bSuppressWarnings);
     XmlNodeRef node = parser.ParseBuffer(buffer, static_cast<int>(size), true, bSuppressWarnings);
     return node;
     return node;

+ 16 - 37
Code/Legacy/CrySystem/XML/xml.cpp

@@ -15,6 +15,7 @@
 #include "xml.h"
 #include "xml.h"
 #include <algorithm>
 #include <algorithm>
 #include <stdio.h>
 #include <stdio.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzFramework/Archive/IArchive.h>
 #include <AzFramework/Archive/IArchive.h>
 #include <CryCommon/Cry_Color.h>
 #include <CryCommon/Cry_Color.h>
 #include "XMLBinaryReader.h"
 #include "XMLBinaryReader.h"
@@ -26,31 +27,6 @@
 
 
 #include "System.h"
 #include "System.h"
 
 
-#if AZ_TRAIT_OS_PLATFORM_APPLE || defined(AZ_PLATFORM_LINUX)
-#include <clocale>
-#include <locale>
-
-class LocaleResetter
-{
-public:
-    LocaleResetter()
-    {
-        m_oldLocale = std::setlocale(LC_NUMERIC, "C");
-    }
-    ~LocaleResetter()
-    {
-        std::setlocale(LC_NUMERIC, m_oldLocale);
-    }
-
-private:
-    char* m_oldLocale;
-};
-#define SCOPED_LOCALE_RESETTER LocaleResetter l
-#else
-// noop on Windows
-#define SCOPED_LOCALE_RESETTER
-#endif
-
 // Global counter for memory allocated in XML string pools.
 // Global counter for memory allocated in XML string pools.
 size_t CSimpleStringPool::g_nTotalAllocInXmlStringPools = 0;
 size_t CSimpleStringPool::g_nTotalAllocInXmlStringPools = 0;
 
 
@@ -296,7 +272,7 @@ void CXmlNode::setAttr(const char* key, unsigned int value)
 void CXmlNode::setAttr(const char* key, float value)
 void CXmlNode::setAttr(const char* key, float value)
 {
 {
     char str[128];
     char str[128];
-    SCOPED_LOCALE_RESETTER;
+    AZ::Locale::ScopedSerializationLocale localeResetter;
     sprintf_s(str, FLOAT_FMT, value);
     sprintf_s(str, FLOAT_FMT, value);
     setAttr(key, str);
     setAttr(key, str);
 }
 }
@@ -304,7 +280,7 @@ void CXmlNode::setAttr(const char* key, float value)
 void CXmlNode::setAttr(const char* key, double value)
 void CXmlNode::setAttr(const char* key, double value)
 {
 {
     char str[128];
     char str[128];
-    SCOPED_LOCALE_RESETTER;
+    AZ::Locale::ScopedSerializationLocale localeResetter;
     sprintf_s(str, DOUBLE_FMT, value);
     sprintf_s(str, DOUBLE_FMT, value);
     setAttr(key, str);
     setAttr(key, str);
 }
 }
@@ -335,21 +311,21 @@ void CXmlNode::setAttr(const char* key, uint64 value, bool useHexFormat)
 void CXmlNode::setAttr(const char* key, const Ang3& value)
 void CXmlNode::setAttr(const char* key, const Ang3& value)
 {
 {
     char str[128];
     char str[128];
-    SCOPED_LOCALE_RESETTER;
+    AZ::Locale::ScopedSerializationLocale localeResetter;
     sprintf_s(str, FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT, value.x, value.y, value.z);
     sprintf_s(str, FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT, value.x, value.y, value.z);
     setAttr(key, str);
     setAttr(key, str);
 }
 }
 void CXmlNode::setAttr(const char* key, const Vec3& value)
 void CXmlNode::setAttr(const char* key, const Vec3& value)
 {
 {
     char str[128];
     char str[128];
-    SCOPED_LOCALE_RESETTER;
+    AZ::Locale::ScopedSerializationLocale localeResetter;
     sprintf_s(str, FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT, value.x, value.y, value.z);
     sprintf_s(str, FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT, value.x, value.y, value.z);
     setAttr(key, str);
     setAttr(key, str);
 }
 }
 void CXmlNode::setAttr(const char* key, const Vec4& value)
 void CXmlNode::setAttr(const char* key, const Vec4& value)
 {
 {
     char str[128];
     char str[128];
-    SCOPED_LOCALE_RESETTER;
+    AZ::Locale::ScopedSerializationLocale localeResetter;
     sprintf_s(str, FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT, value.x, value.y, value.z, value.w);
     sprintf_s(str, FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT, value.x, value.y, value.z, value.w);
     setAttr(key, str);
     setAttr(key, str);
 }
 }
@@ -357,7 +333,7 @@ void CXmlNode::setAttr(const char* key, const Vec4& value)
 void CXmlNode::setAttr(const char* key, const Vec2& value)
 void CXmlNode::setAttr(const char* key, const Vec2& value)
 {
 {
     char str[128];
     char str[128];
-    SCOPED_LOCALE_RESETTER;
+    AZ::Locale::ScopedSerializationLocale localeResetter;
     sprintf_s(str, FLOAT_FMT "," FLOAT_FMT, value.x, value.y);
     sprintf_s(str, FLOAT_FMT "," FLOAT_FMT, value.x, value.y);
     setAttr(key, str);
     setAttr(key, str);
 }
 }
@@ -365,7 +341,7 @@ void CXmlNode::setAttr(const char* key, const Vec2& value)
 void CXmlNode::setAttr(const char* key, const Quat& value)
 void CXmlNode::setAttr(const char* key, const Quat& value)
 {
 {
     char str[128];
     char str[128];
-    SCOPED_LOCALE_RESETTER;
+    AZ::Locale::ScopedSerializationLocale localeResetter;
     sprintf_s(str, FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT, value.w, value.v.x, value.v.y, value.v.z);
     sprintf_s(str, FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT, value.w, value.v.x, value.v.y, value.v.z);
     setAttr(key, str);
     setAttr(key, str);
 }
 }
@@ -444,6 +420,7 @@ bool CXmlNode::getAttr(const char* key, float& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale localeResetter;
         value = (float)atof(svalue);
         value = (float)atof(svalue);
         return true;
         return true;
     }
     }
@@ -455,6 +432,7 @@ bool CXmlNode::getAttr(const char* key, double& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale localeResetter;
         value = atof(svalue);
         value = atof(svalue);
         return true;
         return true;
     }
     }
@@ -466,7 +444,7 @@ bool CXmlNode::getAttr(const char* key, Ang3& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
-        SCOPED_LOCALE_RESETTER;
+        AZ::Locale::ScopedSerializationLocale localeResetter;
         float x, y, z;
         float x, y, z;
         if (azsscanf(svalue, "%f,%f,%f", &x, &y, &z) == 3)
         if (azsscanf(svalue, "%f,%f,%f", &x, &y, &z) == 3)
         {
         {
@@ -483,7 +461,7 @@ bool CXmlNode::getAttr(const char* key, Vec3& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
-        SCOPED_LOCALE_RESETTER;
+        AZ::Locale::ScopedSerializationLocale localeResetter;
         float x, y, z;
         float x, y, z;
         if (azsscanf(svalue, "%f,%f,%f", &x, &y, &z) == 3)
         if (azsscanf(svalue, "%f,%f,%f", &x, &y, &z) == 3)
         {
         {
@@ -500,7 +478,7 @@ bool CXmlNode::getAttr(const char* key, Vec4& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
-        SCOPED_LOCALE_RESETTER;
+        AZ::Locale::ScopedSerializationLocale localeResetter;
         float x, y, z, w;
         float x, y, z, w;
         if (azsscanf(svalue, "%f,%f,%f,%f", &x, &y, &z, &w) == 4)
         if (azsscanf(svalue, "%f,%f,%f,%f", &x, &y, &z, &w) == 4)
         {
         {
@@ -518,7 +496,7 @@ bool CXmlNode::getAttr(const char* key, Vec2& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
-        SCOPED_LOCALE_RESETTER;
+        AZ::Locale::ScopedSerializationLocale localeResetter;
         float x, y;
         float x, y;
         if (azsscanf(svalue, "%f,%f", &x, &y) == 2)
         if (azsscanf(svalue, "%f,%f", &x, &y) == 2)
         {
         {
@@ -535,7 +513,7 @@ bool CXmlNode::getAttr(const char* key, Quat& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
-        SCOPED_LOCALE_RESETTER;
+        AZ::Locale::ScopedSerializationLocale localeResetter;
         float w, x, y, z;
         float w, x, y, z;
         if (azsscanf(svalue, "%f,%f,%f,%f", &w, &x, &y, &z) == 4)
         if (azsscanf(svalue, "%f,%f,%f,%f", &w, &x, &y, &z) == 4)
         {
         {
@@ -557,6 +535,7 @@ bool CXmlNode::getAttr(const char* key, ColorB& value) const
     const char* svalue = GetValue(key);
     const char* svalue = GetValue(key);
     if (svalue)
     if (svalue)
     {
     {
+        AZ::Locale::ScopedSerializationLocale localeResetter;
         unsigned int r, g, b, a = 255;
         unsigned int r, g, b, a = 255;
         int numFound = azsscanf(svalue, "%u,%u,%u,%u", &r, &g, &b, &a);
         int numFound = azsscanf(svalue, "%u,%u,%u,%u", &r, &g, &b, &a);
         if (numFound == 3 || numFound == 4)
         if (numFound == 3 || numFound == 4)

+ 13 - 0
Code/Tools/AssetProcessor/AssetBuilder/main.cpp

@@ -9,8 +9,21 @@
 #include "TraceMessageHook.h"
 #include "TraceMessageHook.h"
 #include "AssetBuilderComponent.h"
 #include "AssetBuilderComponent.h"
 
 
+// the user is not expected to interact with the AssetBuilderApplication directly,
+// so it can be always running in the culture-invariant locale.
+#include <locale.h>
+
 int main(int argc, char** argv)
 int main(int argc, char** argv)
 {
 {
+    // globally set the application locale to the culture-invariant locale.
+    // This should cause all reading and writing under all threads to use the invariant locale
+    // So that the application can be run in any locale and still produce the same output.
+    // We would not do this to a front-facing application that needs to actually be localized in a GUI,
+    // but since this application runs headlessly and its job is to crunch invariant locale files into
+    // other invariant locale files, setting it to the invariant locale means that individual builders
+    // don't need to keep track of locale, change it, set it, etc.
+    setlocale(LC_ALL, "C"); 
+
     const AZ::Debug::Trace tracer;
     const AZ::Debug::Trace tracer;
     AssetBuilderApplication app(&argc, &argv);
     AssetBuilderApplication app(&argc, &argv);
     AssetBuilder::TraceMessageHook traceMessageHook; // Hook AZ Debug messages and redirect them to stdout
     AssetBuilder::TraceMessageHook traceMessageHook; // Hook AZ Debug messages and redirect them to stdout

+ 11 - 1
Gems/AtomLyIntegration/AtomFont/Code/Source/FFontXML.cpp

@@ -20,6 +20,7 @@
 #include <CryCommon/Cry_Math.h>
 #include <CryCommon/Cry_Math.h>
 #include <CryCommon/CryPath.h>
 #include <CryCommon/CryPath.h>
 #include <AzCore/PlatformIncl.h>
 #include <AzCore/PlatformIncl.h>
+#include <AzCore/Serialization/Locale.h>
 
 
 //////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////
 // Main loading function
 // Main loading function
@@ -37,9 +38,16 @@ bool AZ::FFont::Load(const char* xmlFile)
         return false;
         return false;
     }
     }
 
 
+    
     AtomFontInternal::XmlFontShader xmlfs(this);
     AtomFontInternal::XmlFontShader xmlfs(this);
-    xmlfs.ScanXmlNodesRecursively(root);
 
 
+    {
+        // use the invariant culture so that if the user has a machine that has comma as the decimal separator,
+        // the font file will still be parsed correctly.
+        AZ::Locale::ScopedSerializationLocale scopedLocale;
+
+        xmlfs.ScanXmlNodesRecursively(root);
+    }
     // if this was not a valid font XML file then return false
     // if this was not a valid font XML file then return false
     if (!m_fontTexture || !m_fontBuffer)
     if (!m_fontTexture || !m_fontBuffer)
     {
     {
@@ -64,6 +72,8 @@ bool AZ::FFont::Load(const char* xmlFile)
             m_effects.clear();
             m_effects.clear();
         }
         }
 
 
+        AZ::Locale::ScopedSerializationLocale scopedLocale;
+
         // parse the font effects file, adding to this font object
         // parse the font effects file, adding to this font object
         AtomFontInternal::XmlFontShader xmlfsEffect(this);
         AtomFontInternal::XmlFontShader xmlfsEffect(this);
         xmlfsEffect.ScanXmlNodesRecursively(fontEffectRoot);
         xmlfsEffect.ScanXmlNodesRecursively(fontEffectRoot);

+ 5 - 0
Gems/AtomTressFX/External/Code/src/TressFX/TressFXAsset.cpp

@@ -34,6 +34,7 @@
 #include <string>
 #include <string>
 
 
 #include <AzCore/Asset/AssetDataStream.h>
 #include <AzCore/Asset/AssetDataStream.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/Math/Aabb.h>
 #include <AzCore/Math/Aabb.h>
 #include <AzFramework/StringFunc/StringFunc.h>
 #include <AzFramework/StringFunc/StringFunc.h>
 
 
@@ -82,6 +83,10 @@ namespace AMD
             return false;
             return false;
         }
         }
 
 
+        // interpret the data in the culture invariant locale so that it doesn't matter
+        // what locale the machine of the user is set to.
+        AZ::Locale::ScopedSerializationLocale scopedLocale;
+
         int numOfBones;
         int numOfBones;
         AZStd::vector<AZStd::string> boneNames;
         AZStd::vector<AZStd::string> boneNames;
 
 

+ 4 - 0
Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ActorCommands.cpp

@@ -6,6 +6,8 @@
  *
  *
  */
  */
 
 
+#include <AzCore/Serialization/Locale.h>
+
 #include <EMotionFX/Source/Actor.h>
 #include <EMotionFX/Source/Actor.h>
 #include <EMotionFX/Source/EventManager.h>
 #include <EMotionFX/Source/EventManager.h>
 #include <EMotionFX/Source/Recorder.h>
 #include <EMotionFX/Source/Recorder.h>
@@ -1082,6 +1084,8 @@ namespace CommandSystem
 
 
         if (!m_useUnitType)
         if (!m_useUnitType)
         {
         {
+            AZ::Locale::ScopedSerializationLocale locale; // make sure '%f' sends using the "C" locale.
+
             const AZStd::string command = AZStd::string::format("ScaleActorData -id %d -scaleFactor %.8f", m_actorId, 1.0f / m_scaleFactor);
             const AZStd::string command = AZStd::string::format("ScaleActorData -id %d -scaleFactor %.8f", m_actorId, 1.0f / m_scaleFactor);
             GetCommandManager()->ExecuteCommandInsideCommand(command, outResult);
             GetCommandManager()->ExecuteCommandInsideCommand(command, outResult);
         }
         }

+ 5 - 1
Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ActorInstanceCommands.cpp

@@ -7,12 +7,14 @@
  */
  */
 
 
 #include "ActorInstanceCommands.h"
 #include "ActorInstanceCommands.h"
+
+#include <AzCore/Serialization/Locale.h>
+
 #include <EMotionFX/Source/ActorManager.h>
 #include <EMotionFX/Source/ActorManager.h>
 #include <MCore/Source/LogManager.h>
 #include <MCore/Source/LogManager.h>
 #include <MCore/Source/StringConversions.h>
 #include <MCore/Source/StringConversions.h>
 #include "CommandManager.h"
 #include "CommandManager.h"
 
 
-
 namespace CommandSystem
 namespace CommandSystem
 {
 {
     //--------------------------------------------------------------------------------
     //--------------------------------------------------------------------------------
@@ -550,6 +552,8 @@ namespace CommandSystem
             const AZ::Vector3 scale = AZ::Vector3::CreateOne();
             const AZ::Vector3 scale = AZ::Vector3::CreateOne();
         #endif
         #endif
 
 
+        AZ::Locale::ScopedSerializationLocale localeScope;  // make sure '%f' uses the "C" Locale.
+
         const AZStd::string command = AZStd::string::format("CreateActorInstance -actorID %i -xPos %f -yPos %f -zPos %f -xScale %f -yScale %f -zScale %f -rot \"%s\"",
         const AZStd::string command = AZStd::string::format("CreateActorInstance -actorID %i -xPos %f -yPos %f -zPos %f -xScale %f -yScale %f -zScale %f -rot \"%s\"",
             actorInstance->GetActor()->GetID(),
             actorInstance->GetActor()->GetID(),
             static_cast<float>(pos.GetX()), static_cast<float>(pos.GetY()), static_cast<float>(pos.GetZ()),
             static_cast<float>(pos.GetX()), static_cast<float>(pos.GetY()), static_cast<float>(pos.GetZ()),

+ 3 - 0
Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/MetaData.cpp

@@ -21,6 +21,7 @@
 #include <EMotionFX/CommandSystem/Source/MotionEventCommands.h>
 #include <EMotionFX/CommandSystem/Source/MotionEventCommands.h>
 #include <MCore/Source/LogManager.h>
 #include <MCore/Source/LogManager.h>
 
 
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/Serialization/Utils.h>
 #include <AzCore/Serialization/Utils.h>
 
 
 namespace CommandSystem
 namespace CommandSystem
@@ -71,6 +72,8 @@ namespace CommandSystem
 
 
     void MetaData::GeneratePhonemeMetaData(EMotionFX::Actor* actor, AZStd::string& outMetaDataString)
     void MetaData::GeneratePhonemeMetaData(EMotionFX::Actor* actor, AZStd::string& outMetaDataString)
     {
     {
+        AZ::Locale::ScopedSerializationLocale localeScope; // ensures that %f uses '.' as decimal separator
+
         const size_t numLODLevels = actor->GetNumLODLevels();
         const size_t numLODLevels = actor->GetNumLODLevels();
         for (size_t lodLevel = 0; lodLevel < numLODLevels; ++lodLevel)
         for (size_t lodLevel = 0; lodLevel < numLODLevels; ++lodLevel)
         {
         {

+ 9 - 0
Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/MotionCommands.cpp

@@ -7,6 +7,9 @@
  */
  */
 
 
 #include "MotionCommands.h"
 #include "MotionCommands.h"
+
+#include <AzCore/Serialization/Locale.h>
+
 #include "CommandManager.h"
 #include "CommandManager.h"
 
 
 #include <MCore/Source/Compare.h>
 #include <MCore/Source/Compare.h>
@@ -22,6 +25,8 @@
 #include <EMotionFX/Source/EventManager.h>
 #include <EMotionFX/Source/EventManager.h>
 #include <EMotionFX/Exporters/ExporterLib/Exporter/Exporter.h>
 #include <EMotionFX/Exporters/ExporterLib/Exporter/Exporter.h>
 
 
+#include <AzCore/Serialization/Locale.h>
+
 #include <AzFramework/API/ApplicationAPI.h>
 #include <AzFramework/API/ApplicationAPI.h>
 
 
 
 
@@ -77,6 +82,8 @@ namespace CommandSystem
 
 
     AZStd::string CommandPlayMotion::PlayBackInfoToCommandParameters(const EMotionFX::PlayBackInfo* playbackInfo)
     AZStd::string CommandPlayMotion::PlayBackInfoToCommandParameters(const EMotionFX::PlayBackInfo* playbackInfo)
     {
     {
+        AZ::Locale::ScopedSerializationLocale localeScope; // ensures that %f uses '.' as decimal separator
+
         return AZStd::string::format("-blendInTime %f -blendOutTime %f -playSpeed %f -targetWeight %f -eventWeightThreshold %f -maxPlayTime %f -numLoops %i -priorityLevel %i -blendMode %i -playMode %i -mirrorMotion %s -mix %s -playNow %s -motionExtraction %s -retarget %s -freezeAtLastFrame %s -enableMotionEvents %s -blendOutBeforeEnded %s -canOverwrite %s -deleteOnZeroWeight %s -inPlace %s",
         return AZStd::string::format("-blendInTime %f -blendOutTime %f -playSpeed %f -targetWeight %f -eventWeightThreshold %f -maxPlayTime %f -numLoops %i -priorityLevel %i -blendMode %i -playMode %i -mirrorMotion %s -mix %s -playNow %s -motionExtraction %s -retarget %s -freezeAtLastFrame %s -enableMotionEvents %s -blendOutBeforeEnded %s -canOverwrite %s -deleteOnZeroWeight %s -inPlace %s",
             playbackInfo->m_blendInTime,
             playbackInfo->m_blendInTime,
             playbackInfo->m_blendOutTime,
             playbackInfo->m_blendOutTime,
@@ -1017,6 +1024,8 @@ namespace CommandSystem
 
 
         if (m_useUnitType == false)
         if (m_useUnitType == false)
         {
         {
+            AZ::Locale::ScopedSerializationLocale locale;
+
             AZStd::string commandString;
             AZStd::string commandString;
             commandString = AZStd::string::format("ScaleMotionData -id %d -scaleFactor %.8f", m_motionId, 1.0f / m_scaleFactor);
             commandString = AZStd::string::format("ScaleMotionData -id %d -scaleFactor %.8f", m_motionId, 1.0f / m_scaleFactor);
             GetCommandManager()->ExecuteCommandInsideCommand(commandString.c_str(), outResult);
             GetCommandManager()->ExecuteCommandInsideCommand(commandString.c_str(), outResult);

+ 4 - 0
Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/MotionEventCommands.cpp

@@ -8,6 +8,9 @@
 
 
 // include the required headers
 // include the required headers
 #include "MotionEventCommands.h"
 #include "MotionEventCommands.h"
+
+#include <AzCore/Serialization/Locale.h>
+
 #include "CommandManager.h"
 #include "CommandManager.h"
 #include <EMotionFX/Source/MotionSystem.h>
 #include <EMotionFX/Source/MotionSystem.h>
 #include <EMotionFX/Source/MotionManager.h>
 #include <EMotionFX/Source/MotionManager.h>
@@ -30,6 +33,7 @@ namespace CommandSystem
     // add a new motion event
     // add a new motion event
     void CommandHelperAddMotionEvent(const EMotionFX::Motion* motion, const char* trackName, float startTime, float endTime, const EMotionFX::EventDataSet& eventDatas, MCore::CommandGroup* commandGroup)
     void CommandHelperAddMotionEvent(const EMotionFX::Motion* motion, const char* trackName, float startTime, float endTime, const EMotionFX::EventDataSet& eventDatas, MCore::CommandGroup* commandGroup)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
         // make sure the motion is valid
         // make sure the motion is valid
         if (motion == nullptr)
         if (motion == nullptr)
         {
         {

+ 5 - 0
Gems/EMotionFX/Code/EMotionFX/Exporters/ExporterLib/Exporter/MorphTargetExport.cpp

@@ -7,6 +7,9 @@
  */
  */
 
 
 #include "Exporter.h"
 #include "Exporter.h"
+
+#include <AzCore/Serialization/Locale.h>
+
 #include <MCore/Source/IDGenerator.h>
 #include <MCore/Source/IDGenerator.h>
 #include <MCore/Source/LogManager.h>
 #include <MCore/Source/LogManager.h>
 #include <EMotionFX/Source/Actor.h>
 #include <EMotionFX/Source/Actor.h>
@@ -22,6 +25,8 @@ namespace ExporterLib
     // save the given morph target
     // save the given morph target
     void SaveMorphTarget(MCore::Stream* file, EMotionFX::Actor* actor, EMotionFX::MorphTarget* inputMorphTarget, size_t lodLevel, MCore::Endian::EEndianType targetEndianType)
     void SaveMorphTarget(MCore::Stream* file, EMotionFX::Actor* actor, EMotionFX::MorphTarget* inputMorphTarget, size_t lodLevel, MCore::Endian::EEndianType targetEndianType)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
+
         MCORE_ASSERT(file);
         MCORE_ASSERT(file);
         MCORE_ASSERT(actor);
         MCORE_ASSERT(actor);
         MCORE_ASSERT(inputMorphTarget);
         MCORE_ASSERT(inputMorphTarget);

+ 5 - 0
Gems/EMotionFX/Code/EMotionFX/Exporters/ExporterLib/Exporter/NodeExport.cpp

@@ -7,6 +7,9 @@
  */
  */
 
 
 #include "Exporter.h"
 #include "Exporter.h"
+
+#include <AzCore/Serialization/Locale.h>
+
 #include <EMotionFX/Source/Node.h>
 #include <EMotionFX/Source/Node.h>
 #include <EMotionFX/Source/Actor.h>
 #include <EMotionFX/Source/Actor.h>
 #include <EMotionFX/Source/NodeGroup.h>
 #include <EMotionFX/Source/NodeGroup.h>
@@ -20,6 +23,8 @@ namespace ExporterLib
 {
 {
     void SaveNode(MCore::Stream* file, EMotionFX::Actor* actor, EMotionFX::Node* node, MCore::Endian::EEndianType targetEndianType)
     void SaveNode(MCore::Stream* file, EMotionFX::Actor* actor, EMotionFX::Node* node, MCore::Endian::EEndianType targetEndianType)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
+
         MCORE_ASSERT(file);
         MCORE_ASSERT(file);
         MCORE_ASSERT(actor);
         MCORE_ASSERT(actor);
         MCORE_ASSERT(node);
         MCORE_ASSERT(node);

+ 3 - 0
Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/Workspace.cpp

@@ -23,6 +23,7 @@
 #include <EMotionFX/Source/ActorManager.h>
 #include <EMotionFX/Source/ActorManager.h>
 
 
 #include <AzCore/IO/Path/Path.h>
 #include <AzCore/IO/Path/Path.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
 #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
 #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
 #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
 
 
@@ -126,6 +127,8 @@ namespace EMStudio
 
 
     bool Workspace::SaveToFile(const char* filename) const
     bool Workspace::SaveToFile(const char* filename) const
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
+
         QSettings settings(filename, QSettings::IniFormat, GetManager()->GetMainWindow());
         QSettings settings(filename, QSettings::IniFormat, GetManager()->GetMainWindow());
 
 
         AZStd::string commandString;
         AZStd::string commandString;

+ 5 - 0
Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/MorphTargetsWindow/MorphTargetEditWindow.cpp

@@ -7,6 +7,9 @@
  */
  */
 
 
 #include "MorphTargetEditWindow.h"
 #include "MorphTargetEditWindow.h"
+
+#include <AzCore/Serialization/Locale.h>
+
 #include "../../../../EMStudioSDK/Source/EMStudioManager.h"
 #include "../../../../EMStudioSDK/Source/EMStudioManager.h"
 #include <QVBoxLayout>
 #include <QVBoxLayout>
 #include <QHBoxLayout>
 #include <QHBoxLayout>
@@ -145,6 +148,8 @@ namespace EMStudio
 
 
     void MorphTargetEditWindow::Accepted()
     void MorphTargetEditWindow::Accepted()
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
+
         const float rangeMin = (float)m_rangeMin->value();
         const float rangeMin = (float)m_rangeMin->value();
         const float rangeMax = (float)m_rangeMax->value();
         const float rangeMax = (float)m_rangeMax->value();
 
 

+ 7 - 0
Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/MorphTargetsWindow/MorphTargetGroupWidget.cpp

@@ -8,6 +8,7 @@
 
 
 #include "MorphTargetGroupWidget.h"
 #include "MorphTargetGroupWidget.h"
 #include <AzCore/Casting/numeric_cast.h>
 #include <AzCore/Casting/numeric_cast.h>
+#include <AzCore/Serialization/Locale.h>
 #include <EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
 #include <EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
 #include <MCore/Source/StringConversions.h>
 #include <MCore/Source/StringConversions.h>
 #include <QVBoxLayout>
 #include <QVBoxLayout>
@@ -148,6 +149,8 @@ namespace EMStudio
     // reset all morph targets
     // reset all morph targets
     void MorphTargetGroupWidget::ResetAll()
     void MorphTargetGroupWidget::ResetAll()
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
+
         // create our command group
         // create our command group
         MCore::CommandGroup commandGroup("Adjust morph targets");
         MCore::CommandGroup commandGroup("Adjust morph targets");
         AZStd::string command;
         AZStd::string command;
@@ -173,6 +176,8 @@ namespace EMStudio
     // manual mode
     // manual mode
     void MorphTargetGroupWidget::ManualModeClicked()
     void MorphTargetGroupWidget::ManualModeClicked()
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
+
         QCheckBox* checkBox = static_cast<QCheckBox*>(sender());
         QCheckBox* checkBox = static_cast<QCheckBox*>(sender());
         const int morphTargetIndex = checkBox->property("MorphTargetIndex").toInt();
         const int morphTargetIndex = checkBox->property("MorphTargetIndex").toInt();
         EMotionFX::MorphTarget* morphTarget = m_morphTargets[morphTargetIndex].m_morphTarget;
         EMotionFX::MorphTarget* morphTarget = m_morphTargets[morphTargetIndex].m_morphTarget;
@@ -207,6 +212,8 @@ namespace EMStudio
     // slider weight released
     // slider weight released
     void MorphTargetGroupWidget::SliderWeightReleased()
     void MorphTargetGroupWidget::SliderWeightReleased()
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
+
         // get the morph target and the morph target instance
         // get the morph target and the morph target instance
         AzQtComponents::SliderDoubleCombo* floatSlider = static_cast<AzQtComponents::SliderDoubleCombo*>(sender());
         AzQtComponents::SliderDoubleCombo* floatSlider = static_cast<AzQtComponents::SliderDoubleCombo*>(sender());
         const int morphTargetIndex = floatSlider->property("MorphTargetIndex").toInt();
         const int morphTargetIndex = floatSlider->property("MorphTargetIndex").toInt();

+ 3 - 0
Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/TimeView/TimeViewPlugin.cpp

@@ -7,6 +7,7 @@
 
 
 #include <Editor/InspectorBus.h>
 #include <Editor/InspectorBus.h>
 #include <AzCore/Math/MathUtils.h>
 #include <AzCore/Math/MathUtils.h>
+#include <AzCore/Serialization/Locale.h>
 #include "TimeViewPlugin.h"
 #include "TimeViewPlugin.h"
 #include "TrackDataHeaderWidget.h"
 #include "TrackDataHeaderWidget.h"
 #include "TrackDataWidget.h"
 #include "TrackDataWidget.h"
@@ -1469,6 +1470,8 @@ namespace EMStudio
 
 
     void TimeViewPlugin::MotionEventChanged(TimeTrackElement* element, double startTime, double endTime)
     void TimeViewPlugin::MotionEventChanged(TimeTrackElement* element, double startTime, double endTime)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
+
         if (element == nullptr)
         if (element == nullptr)
         {
         {
             return;
             return;

+ 4 - 0
Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/TimeView/TimeViewToolBar.cpp

@@ -8,6 +8,8 @@
 
 
 #include <EMotionStudio/Plugins/StandardPlugins/Source/TimeView/TimeViewToolBar.h>
 #include <EMotionStudio/Plugins/StandardPlugins/Source/TimeView/TimeViewToolBar.h>
 
 
+#include <AzCore/Serialization/Locale.h>
+
 #include <EMotionStudio/Plugins/StandardPlugins/Source/TimeView/TimeViewPlugin.h>
 #include <EMotionStudio/Plugins/StandardPlugins/Source/TimeView/TimeViewPlugin.h>
 #include <EMotionStudio/Plugins/StandardPlugins/Source/TimeView/TimeViewShared.h>
 #include <EMotionStudio/Plugins/StandardPlugins/Source/TimeView/TimeViewShared.h>
 #include <EMotionStudio/Plugins/StandardPlugins/Source/TimeView/TrackDataWidget.h>
 #include <EMotionStudio/Plugins/StandardPlugins/Source/TimeView/TrackDataWidget.h>
@@ -353,6 +355,8 @@ namespace EMStudio
 
 
     void TimeViewToolBar::UpdateMotions()
     void TimeViewToolBar::UpdateMotions()
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
+
         MCore::CommandGroup commandGroup("Adjust default motion instances");
         MCore::CommandGroup commandGroup("Adjust default motion instances");
 
 
         const CommandSystem::SelectionList& selection = CommandSystem::GetCommandManager()->GetCurrentSelection();
         const CommandSystem::SelectionList& selection = CommandSystem::GetCommandManager()->GetCurrentSelection();

+ 5 - 0
Gems/EMotionFX/Code/MCore/Source/MCoreCommandManager.cpp

@@ -7,6 +7,9 @@
  */
  */
 
 
 #include "MCoreCommandManager.h"
 #include "MCoreCommandManager.h"
+
+#include <AzCore/Serialization/Locale.h>
+
 #include "LogManager.h"
 #include "LogManager.h"
 #include "CommandManagerCallback.h"
 #include "CommandManagerCallback.h"
 #include "StringConversions.h"
 #include "StringConversions.h"
@@ -1021,6 +1024,8 @@ namespace MCore
         bool handleErrors,
         bool handleErrors,
         bool autoDeleteCommand)
         bool autoDeleteCommand)
     {
     {
+        AZ::Locale::ScopedSerializationLocale localeScope;  // make sure '%f' uses the "C" Locale.
+
 #ifdef MCORE_COMMANDMANAGER_PERFORMANCE
 #ifdef MCORE_COMMANDMANAGER_PERFORMANCE
         Timer commandTimer;
         Timer commandTimer;
 #endif
 #endif

+ 3 - 0
Gems/EMotionFX/Code/Tests/AnimGraphTransitionConditionCommandTests.cpp

@@ -7,6 +7,7 @@
  */
  */
 
 
 #include <AzTest/AzTest.h>
 #include <AzTest/AzTest.h>
+#include <AzCore/Serialization/Locale.h>
 #include <EMotionFX/CommandSystem/Source/AnimGraphConditionCommands.h>
 #include <EMotionFX/CommandSystem/Source/AnimGraphConditionCommands.h>
 #include <EMotionFX/CommandSystem/Source/CommandManager.h>
 #include <EMotionFX/CommandSystem/Source/CommandManager.h>
 #include <EMotionFX/Source/AnimGraph.h>
 #include <EMotionFX/Source/AnimGraph.h>
@@ -44,6 +45,8 @@ namespace EMotionFX
 
 
     TEST_F(AnimGraphConditionCommandTestFixture, AdjustConditionCommandTests)
     TEST_F(AnimGraphConditionCommandTestFixture, AdjustConditionCommandTests)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
+
         AZStd::string result;
         AZStd::string result;
         CommandSystem::CommandManager commandManager;
         CommandSystem::CommandManager commandManager;
 
 

+ 3 - 0
Gems/LyShine/Code/Source/Sprite.cpp

@@ -8,6 +8,7 @@
 #include "Sprite.h"
 #include "Sprite.h"
 #include <CryPath.h>
 #include <CryPath.h>
 #include <ISerialize.h>
 #include <ISerialize.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzFramework/API/ApplicationAPI.h>
 #include <AzFramework/API/ApplicationAPI.h>
 #include <AzFramework/Asset/AssetSystemBus.h>
 #include <AzFramework/Asset/AssetSystemBus.h>
 #include <LyShine/Bus/Sprite/UiSpriteBus.h>
 #include <LyShine/Bus/Sprite/UiSpriteBus.h>
@@ -144,6 +145,8 @@ namespace
     //! Example XML string data: "1.0 2.0"
     //! Example XML string data: "1.0 2.0"
     void SerializeAzVector2(TSerialize ser, const char* attributeName, AZ::Vector2& azVec2)
     void SerializeAzVector2(TSerialize ser, const char* attributeName, AZ::Vector2& azVec2)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale;
+
         if (ser.IsReading())
         if (ser.IsReading())
         {
         {
             AZStd::string stringVal;
             AZStd::string stringVal;

+ 9 - 0
Gems/Maestro/Code/Source/Cinematics/Movie.cpp

@@ -7,6 +7,7 @@
  */
  */
 
 
 #include <AzCore/Component/Entity.h>
 #include <AzCore/Component/Entity.h>
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/Serialization/SerializeContext.h>
 #include <AzCore/Serialization/SerializeContext.h>
 #include <AzCore/std/containers/map.h>
 #include <AzCore/std/containers/map.h>
 #include <AzCore/std/containers/unordered_map.h>
 #include <AzCore/std/containers/unordered_map.h>
@@ -1409,6 +1410,12 @@ void CMovieSystem::GoToFrameCmd(IConsoleCmdArgs* pArgs)
         return;
         return;
     }
     }
 
 
+    // Console commands are always interpreted in the culture invariant locale because
+    // they often come from files (as in, .cfg files) which need to be portable.  We set the scoped locale
+    // to the invariant locale here so that atof() functions in that locale regardless of app locale.
+
+    AZ::Locale::ScopedSerializationLocale scopedLocale;
+
     const char* pSeqName = pArgs->GetArg(1);
     const char* pSeqName = pArgs->GetArg(1);
     float targetFrame = (float)atof(pArgs->GetArg(2));
     float targetFrame = (float)atof(pArgs->GetArg(2));
 
 
@@ -1441,6 +1448,8 @@ void CMovieSystem::PlaySequencesCmd(IConsoleCmdArgs* pArgs)
 
 
 void CMovieSystem::GoToFrame(const char* seqName, float targetFrame)
 void CMovieSystem::GoToFrame(const char* seqName, float targetFrame)
 {
 {
+    AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
+
     assert(seqName != NULL);
     assert(seqName != NULL);
 
 
     if (gEnv->IsEditor() && gEnv->IsEditorGameMode() == false)
     if (gEnv->IsEditor() && gEnv->IsEditorGameMode() == false)

+ 5 - 0
Gems/MotionMatching/Code/Source/CsvSerializers.cpp

@@ -6,6 +6,7 @@
  *
  *
  */
  */
 
 
+#include <AzCore/Serialization/Locale.h>
 #include <AzCore/std/string/conversions.h>
 #include <AzCore/std/string/conversions.h>
 #include <AzCore/IO/GenericStreams.h>
 #include <AzCore/IO/GenericStreams.h>
 #include <EMotionFX/Source/ActorInstance.h>
 #include <EMotionFX/Source/ActorInstance.h>
@@ -49,11 +50,15 @@ namespace EMotionFX::MotionMatching
 
 
     void CsvWriterBase::WriteVector3ToString(const AZ::Vector3& vec, AZStd::string& text)
     void CsvWriterBase::WriteVector3ToString(const AZ::Vector3& vec, AZStd::string& text)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // ensure that %f is read/written using the "C" locale.
+
         text += AZStd::string::format("%.8f,%.8f,%.8f,", vec.GetX(), vec.GetY(), vec.GetZ());
         text += AZStd::string::format("%.8f,%.8f,%.8f,", vec.GetX(), vec.GetY(), vec.GetZ());
     };
     };
 
 
     void CsvWriterBase::WriteFloatArrayToString(const AZStd::vector<float>& values, AZStd::string& text)
     void CsvWriterBase::WriteFloatArrayToString(const AZStd::vector<float>& values, AZStd::string& text)
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // ensure that %f is read/written using the "C" locale.
+
         text.reserve(text.size() + values.size() * 10);
         text.reserve(text.size() + values.size() * 10);
         for (float value : values)
         for (float value : values)
         {
         {

+ 10 - 1
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp

@@ -2441,9 +2441,11 @@ namespace ScriptCanvas
             return true;
             return true;
 
 
         case Data::eType::Number:
         case Data::eType::Number:
+        {
+            AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
             result = AZStd::string::format("%f", *GetAs<Data::NumberType>());
             result = AZStd::string::format("%f", *GetAs<Data::NumberType>());
             return true;
             return true;
-
+        }
         case Data::eType::OBB:
         case Data::eType::OBB:
             result = ToStringOBB(*GetAs<Data::OBBType>());
             result = ToStringOBB(*GetAs<Data::OBBType>());
             return true;
             return true;
@@ -2556,6 +2558,7 @@ namespace ScriptCanvas
 
 
     AZStd::string Datum::ToStringOBB(const Data::OBBType& obb) const
     AZStd::string Datum::ToStringOBB(const Data::OBBType& obb) const
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
         return AZStd::string::format
         return AZStd::string::format
         ("(Position: %s, AxisX: %s, AxisY: %s, AxisZ: %s, halfLengthX: %.7f, halfLengthY: %.7f, halfLengthZ: %.7f)"
         ("(Position: %s, AxisX: %s, AxisY: %s, AxisZ: %s, halfLengthX: %.7f, halfLengthY: %.7f, halfLengthZ: %.7f)"
             , ToStringVector3(obb.GetPosition()).c_str()
             , ToStringVector3(obb.GetPosition()).c_str()
@@ -2574,6 +2577,7 @@ namespace ScriptCanvas
 
 
     AZStd::string Datum::ToStringQuaternion(const Data::QuaternionType& source) const
     AZStd::string Datum::ToStringQuaternion(const Data::QuaternionType& source) const
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
         AZ::Vector3 eulerRotation = AZ::ConvertTransformToEulerDegrees(AZ::Transform::CreateFromQuaternion(source));
         AZ::Vector3 eulerRotation = AZ::ConvertTransformToEulerDegrees(AZ::Transform::CreateFromQuaternion(source));
         return AZStd::string::format
         return AZStd::string::format
         ("(Pitch: %5.2f, Roll: %5.2f, Yaw: %5.2f)"
         ("(Pitch: %5.2f, Roll: %5.2f, Yaw: %5.2f)"
@@ -2584,6 +2588,8 @@ namespace ScriptCanvas
 
 
     AZStd::string Datum::ToStringTransform(const Data::TransformType& source) const
     AZStd::string Datum::ToStringTransform(const Data::TransformType& source) const
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
+
         Data::TransformType copy(source);
         Data::TransformType copy(source);
         AZ::Vector3 pos = copy.GetTranslation();
         AZ::Vector3 pos = copy.GetTranslation();
         float scale = copy.ExtractUniformScale();
         float scale = copy.ExtractUniformScale();
@@ -2599,6 +2605,7 @@ namespace ScriptCanvas
 
 
     AZStd::string Datum::ToStringVector2(const AZ::Vector2& source) const
     AZStd::string Datum::ToStringVector2(const AZ::Vector2& source) const
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
         return AZStd::string::format
         return AZStd::string::format
         ("(X: %f, Y: %f)"
         ("(X: %f, Y: %f)"
             , source.GetX()
             , source.GetX()
@@ -2607,6 +2614,7 @@ namespace ScriptCanvas
 
 
     AZStd::string Datum::ToStringVector3(const AZ::Vector3& source) const
     AZStd::string Datum::ToStringVector3(const AZ::Vector3& source) const
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
         return AZStd::string::format
         return AZStd::string::format
         ("(X: %f, Y: %f, Z: %f)"
         ("(X: %f, Y: %f, Z: %f)"
             , (source.GetX())
             , (source.GetX())
@@ -2616,6 +2624,7 @@ namespace ScriptCanvas
 
 
     AZStd::string Datum::ToStringVector4(const AZ::Vector4& source) const
     AZStd::string Datum::ToStringVector4(const AZ::Vector4& source) const
     {
     {
+        AZ::Locale::ScopedSerializationLocale scopedLocale; // Ensures that %f uses "." as decimal separator
         return AZStd::string::format
         return AZStd::string::format
         ("(X: %f, Y: %f, Z: %f, W: %f)"
         ("(X: %f, Y: %f, Z: %f, W: %f)"
             , (source.GetX())
             , (source.GetX())

+ 2 - 23
Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLuaUtility.cpp

@@ -7,10 +7,9 @@
  */
  */
 
 
 
 
-#include <clocale>
-
 #include <AzCore/Outcome/Outcome.h>
 #include <AzCore/Outcome/Outcome.h>
 #include <AzCore/RTTI/BehaviorContextUtilities.h>
 #include <AzCore/RTTI/BehaviorContextUtilities.h>
+#include <AzCore/Serialization/Locale.h>
 #include <ScriptCanvas/Core/Node.h>
 #include <ScriptCanvas/Core/Node.h>
 #include <ScriptCanvas/Data/Data.h>
 #include <ScriptCanvas/Data/Data.h>
 #include <ScriptCanvas/Debugger/ValidationEvents/DataValidation/ScopedDataConnectionEvent.h>
 #include <ScriptCanvas/Debugger/ValidationEvents/DataValidation/ScopedDataConnectionEvent.h>
@@ -24,26 +23,6 @@
 
 
 #include <ScriptCanvas/Translation/GraphToLuaUtility.h>
 #include <ScriptCanvas/Translation/GraphToLuaUtility.h>
 
 
-namespace GraphToLuaUtilityCpp
-{
-    class ScopedLocale
-    {
-    public:
-        ScopedLocale()
-        {
-            m_previousLocale = std::setlocale(LC_NUMERIC, "en_US.UTF-8");
-        }
-
-        ~ScopedLocale()
-        {
-            std::setlocale(LC_NUMERIC, m_previousLocale);
-        }
-
-    private:
-        char* m_previousLocale = nullptr;
-    };
-
-}
 
 
 namespace ScriptCanvas
 namespace ScriptCanvas
 {
 {
@@ -178,7 +157,7 @@ namespace ScriptCanvas
 
 
         AZStd::string ToValueString(const Datum& datum, const Configuration& config)
         AZStd::string ToValueString(const Datum& datum, const Configuration& config)
         {
         {
-            GraphToLuaUtilityCpp::ScopedLocale scopedLocal;
+            AZ::Locale::ScopedSerializationLocale scopedLocale;
 
 
             switch (datum.GetType().GetType())
             switch (datum.GetType().GetType())
             {
             {