diff --git a/Flax.flaxproj b/Flax.flaxproj index 04a161cf2..ed79458d2 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -4,7 +4,7 @@ "Major": 1, "Minor": 12, "Revision": 0, - "Build": 6913 + "Build": 6914 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.", diff --git a/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp b/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp index a2c35f38f..e38a7310c 100644 --- a/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp @@ -191,7 +191,7 @@ bool WebPlatformTools::OnPostProcess(CookingData& data) FileSystem::GetChildDirectories(pythons, emscriptenSdk / TEXT("/python")); if (pythons.HasItems()) { - procSettings.Arguments = procSettings.FileName + TEXT(".py ") + procSettings.Arguments; + procSettings.Arguments = String::Format(TEXT("\"{}.py\" {}"), procSettings.FileName, procSettings.Arguments); #if PLATFORM_WINDOWS procSettings.FileName = pythons[0] / TEXT("/python.exe"); #else diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs index 643441c46..dea5247f4 100644 --- a/Source/Editor/Tools/VertexPainting.cs +++ b/Source/Editor/Tools/VertexPainting.cs @@ -8,6 +8,7 @@ using FlaxEditor.Gizmo; using FlaxEditor.GUI.Tabs; using FlaxEditor.Modules; using FlaxEditor.SceneGraph; +using FlaxEditor.Utilities; using FlaxEditor.Viewport.Modes; using FlaxEngine; using FlaxEngine.GUI; @@ -307,7 +308,7 @@ namespace FlaxEditor.Tools public VertexPaintingGizmo Gizmo; public VertexColorsPreviewMode PreviewMode = VertexColorsPreviewMode.RGB; - public float PreviewVertexSize = 6.0f; + public float PreviewVertexSize = 4.0f; public float BrushSize = 100.0f; public float BrushStrength = 1.0f; public float BrushFalloff = 1.0f; @@ -402,7 +403,7 @@ namespace FlaxEditor.Tools if (meshDatas == null) throw new Exception("Missing mesh data of the model to paint."); var instanceTransform = _selectedModel.Transform; - var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize); + var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize * 0.5f); if (_paintUpdateCount == 0 && !_selectedModel.HasVertexColors) { // Initialize the instance vertex colors with originals from the asset @@ -509,6 +510,13 @@ namespace FlaxEditor.Tools return; } + // Increase or decrease brush size with scroll + if (Input.GetKey(KeyboardKeys.Shift) && !Input.GetMouseButton(MouseButton.Right)) + { + _gizmoMode.BrushSize += dt * _gizmoMode.BrushSize * Input.Mouse.ScrollDelta * 5f; + _gizmoMode.BrushSize = Mathf.Clamp(_gizmoMode.BrushSize, 0.0001f, 100000.0f); + } + // Perform detailed tracing to find cursor location for the brush var ray = Owner.MouseRay; var view = new Ray(Owner.ViewPosition, Owner.ViewDirection); @@ -570,7 +578,7 @@ namespace FlaxEditor.Tools } if (_brushModel && _brushMaterial) { - _brushMaterial.SetParameterValue("Color", new Color(1.0f, 0.85f, 0.0f)); // TODO: expose to editor options + _brushMaterial.SetParameterValue("Color", new Color(1.0f, 0.85f, 0.0f)); _brushMaterial.SetParameterValue("DepthBuffer", Owner.RenderTask.Buffers.DepthBuffer); Quaternion rotation = RootNode.RaycastNormalRotation(ref _hitNormal); Matrix transform = Matrix.Scaling(_gizmoMode.BrushSize * 0.01f) * Matrix.RotationQuaternion(rotation) * Matrix.Translation(_hitLocation - viewOrigin); @@ -586,8 +594,10 @@ namespace FlaxEditor.Tools _verticesPreviewMaterial = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WiresDebugMaterial); } var instanceTransform = _selectedModel.Transform; - var modelScaleMatrix = Matrix.Scaling(_gizmoMode.PreviewVertexSize * 0.01f); - var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize); + var distanceScale = (float)Vector3.Distance(instanceTransform.Translation, renderContext.View.Position) / (10.0f * Units.Meters2Units); + var vertexScale = Mathf.Lerp(0.005f, 0.01f, Mathf.Saturate(distanceScale)); + var modelScaleMatrix = Matrix.Scaling(_gizmoMode.PreviewVertexSize * vertexScale); + var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize * 0.5f); var lodIndex = _gizmoMode.ModelLOD == -1 ? RenderTools.ComputeModelLOD(_selectedModel.Model, ref renderContext.View.Position, (float)_selectedModel.Sphere.Radius, ref renderContext) : _gizmoMode.ModelLOD; lodIndex = Mathf.Clamp(lodIndex, 0, meshDatas.Length - 1); var lodData = meshDatas[lodIndex]; diff --git a/Source/Editor/Windows/GraphicsQualityWindow.cs b/Source/Editor/Windows/GraphicsQualityWindow.cs index 27b131404..02e2da9bf 100644 --- a/Source/Editor/Windows/GraphicsQualityWindow.cs +++ b/Source/Editor/Windows/GraphicsQualityWindow.cs @@ -98,10 +98,10 @@ namespace FlaxEditor.Windows [NoSerialize, DefaultValue(1.0f), Limit(0.05f, 5, 0)] [EditorOrder(1400), EditorDisplay("Quality")] [Tooltip("The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer.")] - public float RenderingPercentage + public float RenderScale { - get => MainRenderTask.Instance.RenderingPercentage; - set => MainRenderTask.Instance.RenderingPercentage = value; + get => MainRenderTask.Instance.RenderScale; + set => MainRenderTask.Instance.RenderScale = value; } [NoSerialize, DefaultValue(RenderingUpscaleLocation.AfterAntiAliasingPass), VisibleIf(nameof(UpscaleLocation_Visible))] @@ -113,7 +113,7 @@ namespace FlaxEditor.Windows set => MainRenderTask.Instance.UpscaleLocation = value; } - private bool UpscaleLocation_Visible => MainRenderTask.Instance.RenderingPercentage < 1.0f; + private bool UpscaleLocation_Visible => MainRenderTask.Instance.RenderScale < 1.0f; [NoSerialize, DefaultValue(1.0f), Limit(0, 1)] [EditorOrder(1500), EditorDisplay("Quality"), Tooltip("The global density scale for all foliage instances. The default value is 1. Use values from range 0-1. Lower values decrease amount of foliage instances in-game. Use it to tweak game performance for slower devices.")] diff --git a/Source/Engine/AI/BehaviorKnowledge.cpp b/Source/Engine/AI/BehaviorKnowledge.cpp index 4d8e2eed9..872a12834 100644 --- a/Source/Engine/AI/BehaviorKnowledge.cpp +++ b/Source/Engine/AI/BehaviorKnowledge.cpp @@ -205,10 +205,9 @@ bool BehaviorKnowledge::Set(const StringAnsiView& path, const Variant& value) bool BehaviorKnowledge::HasGoal(ScriptingTypeHandle type) const { - for (int32 i = 0; i < Goals.Count(); i++) + for (const Variant& goal : Goals) { - const ScriptingTypeHandle goalType = Scripting::FindScriptingType(Goals[i].Type.GetTypeName()); - if (goalType == type) + if (goal.Type.GetScriptingType() == type) return true; } return false; @@ -218,8 +217,7 @@ const Variant& BehaviorKnowledge::GetGoal(ScriptingTypeHandle type) const { for (const Variant& goal : Goals) { - const ScriptingTypeHandle goalType = Scripting::FindScriptingType(goal.Type.GetTypeName()); - if (goalType == type) + if (goal.Type.GetScriptingType() == type) return goal; } return Variant::Null; @@ -242,10 +240,9 @@ void BehaviorKnowledge::RemoveGoal(ScriptingTypeHandle type) { for (int32 i = 0; i < Goals.Count(); i++) { - const ScriptingTypeHandle goalType = Scripting::FindScriptingType(Goals[i].Type.GetTypeName()); - if (goalType == type) + if (Goals[i].Type.GetScriptingType() == type) { - Goals.RemoveAt(i); + Goals.RemoveAtKeepOrder(i); break; } } diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 62d901b6f..26b896b02 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -339,7 +339,7 @@ void VisualScriptExecutor::ProcessGroupTools(Box* box, Node* node, Value& value) obj = Value::Null; #else const ScriptingTypeHandle type = Scripting::FindScriptingType(StringAnsiView(typeNameAnsi.Get(), typeName.Length())); - const ScriptingTypeHandle objType = Scripting::FindScriptingType(obj.Type.GetTypeName()); + const ScriptingTypeHandle objType = obj.Type.GetScriptingType(); if (!type || !objType || !objType.IsSubclassOf(type)) obj = Value::Null; #endif diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 7f805f9e3..d4fd3fdce 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -125,7 +125,7 @@ VariantType::VariantType(Types type, const StringAnsiView& typeName, bool static VariantType::VariantType(Types type, const ScriptingType& sType) : VariantType(type) { - SetTypeName(sType.Fullname, sType.Module->CanReload); + SetTypeName(sType.Fullname, !sType.Module->CanReload); } VariantType::VariantType(Types type, const MClass* klass) @@ -345,13 +345,13 @@ void VariantType::SetTypeName(const StringAnsiView& typeName, bool staticName) void VariantType::SetTypeName(const ScriptingType& type) { - SetTypeName(type.Fullname, type.Module->CanReload); + SetTypeName(type.Fullname, !type.Module->CanReload); } void VariantType::SetTypeName(const MClass& klass) { #if USE_CSHARP - SetTypeName(klass.GetFullName(), klass.GetAssembly()->CanReload()); + SetTypeName(klass.GetFullName(), !klass.GetAssembly()->CanReload()); #endif } @@ -362,6 +362,11 @@ const char* VariantType::GetTypeName() const return InBuiltTypesTypeNames[Type]; } +ScriptingTypeHandle VariantType::GetScriptingType() const +{ + return Scripting::FindScriptingType(GetTypeName()); +} + VariantType VariantType::GetElementType() const { if (Type == Array) diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h index 13b928171..9532d4ec4 100644 --- a/Source/Engine/Core/Types/Variant.h +++ b/Source/Engine/Core/Types/Variant.h @@ -151,6 +151,7 @@ public: void SetTypeName(const ScriptingType& type); void SetTypeName(const MClass& klass); const char* GetTypeName() const; + ScriptingTypeHandle GetScriptingType() const; VariantType GetElementType() const; // Drops custom type name into the name allocated by the scripting module to reduce memory allocations when referencing types. void Inline(); @@ -420,6 +421,15 @@ public: return MoveTemp(v); } + template + static typename TEnableIf::Value, Variant>::Type Enum(const T value) + { + Variant v; + v.SetType(VariantType(VariantType::Enum, StaticType().GetType())); + v.AsUint64 = (uint64)value; + return MoveTemp(v); + } + template static typename TEnableIf::Value && !TIsPointer::Value, Variant>::Type Structure(VariantType&& type, const T& value) { diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index feb985cd4..ac6e55de7 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -85,7 +85,7 @@ struct CommandData else if (value.Type.Type == VariantType::Structure) { // Prettify structure printing - ScriptingTypeHandle resultType = Scripting::FindScriptingType(value.Type.GetTypeName()); + ScriptingTypeHandle resultType = value.Type.GetScriptingType(); if (resultType) { Array fields; diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index ad8bbbdc0..31639c39d 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -353,8 +353,11 @@ Viewport SceneRenderTask::GetViewport() const viewport = Buffers->GetViewport(); else viewport = Viewport(0, 0, 1280, 720); - viewport.Width *= RenderingPercentage; - viewport.Height *= RenderingPercentage; +PRAGMA_DISABLE_DEPRECATION_WARNINGS + float renderScale = RenderingPercentage * RenderScale; +PRAGMA_ENABLE_DEPRECATION_WARNINGS + viewport.Width *= renderScale; + viewport.Height *= renderScale; return viewport; } @@ -394,13 +397,16 @@ void SceneRenderTask::OnBegin(GPUContext* context) } // Setup render buffers for the output rendering resolution +PRAGMA_DISABLE_DEPRECATION_WARNINGS + float renderScale = RenderingPercentage * RenderScale; +PRAGMA_ENABLE_DEPRECATION_WARNINGS if (Output) { - Buffers->Init((int32)((float)Output->Width() * RenderingPercentage), (int32)((float)Output->Height() * RenderingPercentage)); + Buffers->Init((int32)((float)Output->Width() * renderScale), (int32)((float)Output->Height() * renderScale)); } else if (SwapChain) { - Buffers->Init((int32)((float)SwapChain->GetWidth() * RenderingPercentage), (int32)((float)SwapChain->GetHeight() * RenderingPercentage)); + Buffers->Init((int32)((float)SwapChain->GetWidth() * renderScale), (int32)((float)SwapChain->GetHeight() * renderScale)); } } @@ -434,7 +440,10 @@ bool SceneRenderTask::Resize(int32 width, int32 height) PROFILE_MEM(Graphics); if (Output && Output->Resize(width, height)) return true; - if (Buffers && Buffers->Init((int32)((float)width * RenderingPercentage), (int32)((float)height * RenderingPercentage))) +PRAGMA_DISABLE_DEPRECATION_WARNINGS + float renderScale = RenderingPercentage * RenderScale; +PRAGMA_ENABLE_DEPRECATION_WARNINGS + if (Buffers && Buffers->Init((int32)((float)width * renderScale), (int32)((float)height * renderScale))) return true; return false; } @@ -477,12 +486,6 @@ void MainRenderTask::OnBegin(GPUContext* context) // Use the main camera for the game (can be later overriden in Begin event by external code) Camera = Camera::GetMainCamera(); -#if !USE_EDITOR - // Sync render buffers size with the backbuffer - const auto size = Screen::GetSize(); - Buffers->Init((int32)(size.X * RenderingPercentage), (int32)(size.Y * RenderingPercentage)); -#endif - SceneRenderTask::OnBegin(context); } @@ -507,3 +510,10 @@ RenderContextBatch::RenderContextBatch(const RenderContext& context) Contexts.Add(context); EnableAsync = JobSystem::GetThreadsCount() > 1; } + +void RenderContextBatch::FlushWaitLabels() +{ + for (const int64 label : WaitLabels) + JobSystem::Wait(label); + WaitLabels.Clear(); +} diff --git a/Source/Engine/Graphics/RenderTask.h b/Source/Engine/Graphics/RenderTask.h index 8cba1006e..a4dbb5151 100644 --- a/Source/Engine/Graphics/RenderTask.h +++ b/Source/Engine/Graphics/RenderTask.h @@ -268,8 +268,14 @@ public: /// /// The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer. + /// [Deprecated in v1.13] /// - API_FIELD() float RenderingPercentage = 1.0f; + API_FIELD() DEPRECATED("Use RenderScale instead.") float RenderingPercentage = 1.0f; + + /// + /// The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer. + /// + API_FIELD() float RenderScale = 1.0f; /// /// The image resolution upscale location within rendering pipeline. Unused if RenderingPercentage is 1. @@ -533,4 +539,9 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API RenderContextBatch { return Contexts.Get()[0]; } + + /// + /// Waits for all scheduled async jobs to complete and clears WaitLabels. + /// + void FlushWaitLabels(); }; diff --git a/Source/Engine/Level/Prefabs/Prefab.cpp b/Source/Engine/Level/Prefabs/Prefab.cpp index 80120ea87..a14eae500 100644 --- a/Source/Engine/Level/Prefabs/Prefab.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.cpp @@ -64,7 +64,7 @@ Actor* Prefab::GetDefaultInstance() // Skip if not loaded if (!IsLoaded()) { - LOG(Warning, "Cannot instantiate object from not loaded prefab asset."); + LOG(Warning, "Cannot instantiate object from not loaded prefab asset ({}, {})", GetPath(), GetID()); return nullptr; } diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index ecd7c1093..24bb4c1a6 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -184,6 +184,15 @@ PhysicsScene* Physics::FindScene(const StringView& name) return nullptr; } +void Physics::DeleteScene(PhysicsScene* scene) +{ + if (scene == nullptr || scene == DefaultScene) + return; + scene->CollectResults(); + Scenes.RemoveKeepOrder(scene); + Delete(scene); +} + bool Physics::GetAutoSimulation() { return !DefaultScene || DefaultScene->GetAutoSimulation(); diff --git a/Source/Engine/Physics/Physics.h b/Source/Engine/Physics/Physics.h index 85cd5e77b..11fd3fb80 100644 --- a/Source/Engine/Physics/Physics.h +++ b/Source/Engine/Physics/Physics.h @@ -32,6 +32,11 @@ API_CLASS(Static) class FLAXENGINE_API Physics /// API_FUNCTION() static PhysicsScene* FindScene(const StringView& name); + /// + /// Delete an existing scene (excluding the default one). + /// + API_FUNCTION() static void DeleteScene(PhysicsScene* scene); + public: /// /// The automatic simulation feature. True if perform physics simulation after on fixed update by auto, otherwise user should do it. diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index a1f531ac1..6173a1f5a 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -356,9 +356,7 @@ void Renderer::DrawActors(RenderContext& renderContext, const Array& cus Level::DrawActors(renderContextBatch, SceneRendering::DrawCategory::SceneDraw); Level::DrawActors(renderContextBatch, SceneRendering::DrawCategory::SceneDrawAsync); JobSystem::SetJobStartingOnDispatch(true); - for (const int64 label : renderContextBatch.WaitLabels) - JobSystem::Wait(label); - renderContextBatch.WaitLabels.Clear(); + renderContextBatch.FlushWaitLabels(); } } @@ -483,9 +481,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont // Wait for async jobs to finish JobSystem::SetJobStartingOnDispatch(true); - for (const int64 label : renderContextBatch.WaitLabels) - JobSystem::Wait(label); - renderContextBatch.WaitLabels.Clear(); + renderContextBatch.FlushWaitLabels(); // Perform custom post-scene drawing (eg. GPU dispatches used by VFX) for (int32 i = 0; i < renderContextBatch.Contexts.Count(); i++) @@ -737,7 +733,9 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont } // Upscaling after scene rendering but before post processing - bool useUpscaling = task->RenderingPercentage < 1.0f; +PRAGMA_DISABLE_DEPRECATION_WARNINGS + bool useUpscaling = task->RenderingPercentage * task->RenderScale < 1.0f; +PRAGMA_ENABLE_DEPRECATION_WARNINGS const Viewport outputViewport = task->GetOutputViewport(); if (useUpscaling && setup.UpscaleLocation == RenderingUpscaleLocation::BeforePostProcessingPass) { diff --git a/Source/Engine/Scripting/Attributes/EnumStringAttribute.cs b/Source/Engine/Scripting/Attributes/EnumStringAttribute.cs new file mode 100644 index 000000000..aa9222c8b --- /dev/null +++ b/Source/Engine/Scripting/Attributes/EnumStringAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// Changes enum serialization to use string names instead of integer values. This makes saved data resilient to enum reordering or changes in values (but not to renaming enums). Deserialization accepts both string names and integer values for backward compatibility. + /// + /// + [AttributeUsage(AttributeTargets.Enum)] + public sealed class EnumStringAttribute : Attribute + { + } +} diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 02358fac0..029a245c3 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -204,7 +204,7 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul Struct.SetField = setField; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items, bool stringSerialization) : ManagedClass(nullptr) , Module(module) , InitRuntime(DefaultInitRuntime) @@ -215,6 +215,7 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul , Size(size) { Enum.Items = items; + Enum.StringSerialization = stringSerialization; } ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, GetInterfaceWrapper getInterfaceWrapper) @@ -270,6 +271,7 @@ ScriptingType::ScriptingType(const ScriptingType& other) break; case ScriptingTypes::Enum: Enum.Items = other.Enum.Items; + Enum.StringSerialization = other.Enum.StringSerialization; break; case ScriptingTypes::Interface: Interface.SetupScriptVTable = other.Interface.SetupScriptVTable; @@ -323,6 +325,7 @@ ScriptingType::ScriptingType(ScriptingType&& other) break; case ScriptingTypes::Enum: Enum.Items = other.Enum.Items; + Enum.StringSerialization = other.Enum.StringSerialization; break; case ScriptingTypes::Interface: Interface.SetupScriptVTable = other.Interface.SetupScriptVTable; @@ -604,71 +607,57 @@ StringAnsiView ScriptingType::GetName() const return Fullname; } +#if BUILD_DEBUG || USE_EDITOR +#define INIT_TYPE(...) \ + module->Types.AddUninitialized(); \ + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, ##__VA_ARGS__); \ + if (module->TypeNameToTypeIndex.ContainsKey(fullname)) \ + LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); \ + module->TypeNameToTypeIndex[fullname] = TypeIndex; +#else +#define INIT_TYPE(...) \ + module->Types.AddUninitialized(); \ + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, ##__VA_ARGS__); \ + module->TypeNameToTypeIndex[fullname] = TypeIndex; +#endif + ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { // Script - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces); } ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { // Class - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, baseType, interfaces); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(size, initRuntime, ctor, dtor, baseType, interfaces); } ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { // Structure - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces); } -ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items) +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items, bool stringSerialization) : ScriptingTypeHandle(module, module->Types.Count()) { // Enum - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, items); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(size, items, stringSerialization); } ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, ScriptingType::GetInterfaceWrapper getInterfaceWrapper) : ScriptingTypeHandle(module, module->Types.Count()) { // Interface - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, initRuntime, setupScriptVTable, setupScriptObjectVTable, getInterfaceWrapper); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(initRuntime, setupScriptVTable, setupScriptObjectVTable, getInterfaceWrapper); } +#undef INIT_TYPE + CriticalSection BinaryModule::Locker; BinaryModule::BinaryModulesList& BinaryModule::GetModules() diff --git a/Source/Engine/Scripting/ScriptingType.h b/Source/Engine/Scripting/ScriptingType.h index e1fb3dc04..8a15dd336 100644 --- a/Source/Engine/Scripting/ScriptingType.h +++ b/Source/Engine/Scripting/ScriptingType.h @@ -266,6 +266,8 @@ struct FLAXENGINE_API ScriptingType { // Enum items table (the last item name is null) EnumItem* Items; + // Enum uses string names serialization instead of integer values. + bool StringSerialization; } Enum; struct @@ -290,7 +292,7 @@ struct FLAXENGINE_API ScriptingType ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime = DefaultInitRuntime, SpawnHandler spawn = DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const InterfaceImplementation* interfaces = nullptr); ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items, bool stringSerialization); ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, GetInterfaceWrapper getInterfaceWrapper); ScriptingType(const ScriptingType& other); ScriptingType(ScriptingType&& other); @@ -339,7 +341,7 @@ struct FLAXENGINE_API ScriptingTypeInitializer : ScriptingTypeHandle ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime = ScriptingType::DefaultInitRuntime, ScriptingType::SpawnHandler spawn = ScriptingType::DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, ScriptingType::SetupScriptVTableHandler setupScriptVTable = nullptr, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); - ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items, bool stringSerialization); ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, ScriptingType::GetInterfaceWrapper getInterfaceWrapper); }; diff --git a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs index b8e07e448..4f2690863 100644 --- a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs +++ b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; namespace FlaxEngine.Json.JsonCustomSerializers @@ -44,6 +45,13 @@ namespace FlaxEngine.Json.JsonCustomSerializers ((JsonObjectContract)contract).ItemReferenceLoopHandling = ReferenceLoopHandling.Serialize; } + // Check if use enum serialization as string + var type = Nullable.GetUnderlyingType(objectType) ?? objectType; + if (type.IsEnum && type.GetCustomAttribute() != null) + { + contract.Converter = new StringEnumConverter(); + } + return contract; } @@ -53,19 +61,19 @@ namespace FlaxEngine.Json.JsonCustomSerializers var contract = base.CreateDictionaryContract(objectType); // Override contract to save enums keys as integer - if (contract.DictionaryKeyType?.IsEnum ?? false) + var keyType = contract.DictionaryKeyType; + if ((keyType?.IsEnum ?? false) && keyType.GetCustomAttribute() == null) { - var enumType = contract.DictionaryKeyType; contract.DictionaryKeyResolver = name => { try { - var e = Enum.Parse(enumType, name); + var e = Enum.Parse(keyType, name); name = Convert.ToInt32(e).ToString(); } - catch + catch (Exception ex) { - // Ignore errors + Debug.Logger.LogHandler.LogWrite(LogType.Warning, $"Failed to parse enum '{name}' as {keyType.Name}: {ex.Message}"); } return name; }; diff --git a/Source/Engine/Serialization/Serialization.cpp b/Source/Engine/Serialization/Serialization.cpp index ec02fd65f..bf3098140 100644 --- a/Source/Engine/Serialization/Serialization.cpp +++ b/Source/Engine/Serialization/Serialization.cpp @@ -49,6 +49,54 @@ void ISerializable::DeserializeIfExists(DeserializeStream& stream, const char* m var = defaultValue;\ } +void Serialization::SerializeEnum(ISerializable::SerializeStream& stream, uint32 v, ScriptingTypeHandle typeHandle) +{ + if (typeHandle) + { + // Check if serialize enum as string + const ScriptingType& type = typeHandle.GetType(); + if (type.Type == ScriptingTypes::Enum && type.Enum.StringSerialization) + { + const auto items = type.Enum.Items; + for (int32 i = 0; items[i].Name; i++) + { + if (items[i].Value == v) + { + stream.String(items[i].Name); + return; + } + } + } + } + stream.Uint(v); +} + +int32 Serialization::DeserializeEnum(ISerializable::DeserializeStream& stream, ScriptingTypeHandle typeHandle) +{ + if (stream.IsString() && typeHandle) + { + // Deserialize enum from string + const ScriptingType& type = typeHandle.GetType(); + if (type.Type == ScriptingTypes::Enum) + { + const auto str = stream.GetStringAnsiView(); + const auto items = type.Enum.Items; + for (int32 i = 0; items[i].Name; i++) + { + if (str == items[i].Name) + { + return (int32)items[i].Value; + } + } + int32 result; + if (!StringUtils::Parse(stream.GetString(), &result)) + return result; + LOG(Warning, "Failed to parse enum '{}' as {}", str.ToString(), type.Fullname.ToString()); + } + } + return DeserializeInt(stream); +} + bool Serialization::ShouldSerialize(const VariantType& v, const void* otherObj) { return !otherObj || v != *(VariantType*)otherObj; @@ -129,7 +177,6 @@ void Serialization::Serialize(ISerializable::SerializeStream& stream, const Vari stream.Int64(v.AsInt64); break; case VariantType::Uint64: - case VariantType::Enum: stream.Uint64(v.AsUint64); break; case VariantType::Float: @@ -222,6 +269,9 @@ void Serialization::Serialize(ISerializable::SerializeStream& stream, const Vari else stream.String("", 0); break; + case VariantType::Enum: + SerializeEnum(stream, (int32)v.AsUint64, v.Type.GetScriptingType()); + break; case VariantType::ManagedObject: case VariantType::Structure: { @@ -276,7 +326,6 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian v.AsInt64 = value.GetInt64(); break; case VariantType::Uint64: - case VariantType::Enum: v.AsUint64 = value.GetUint64(); break; case VariantType::Float: @@ -371,6 +420,9 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian CHECK(value.IsString()); v.SetTypename(value.GetStringAnsiView()); break; + case VariantType::Enum: + v.AsInt64 = DeserializeEnum(value, v.Type.GetScriptingType()); + break; case VariantType::ManagedObject: case VariantType::Structure: { @@ -408,7 +460,7 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian bool Serialization::ShouldSerialize(const Guid& v, const void* otherObj) { - return v.IsValid(); + return !otherObj || v != *(Guid*)otherObj; } void Serialization::Serialize(ISerializable::SerializeStream& stream, const Guid& v, const void* otherObj) @@ -427,10 +479,12 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Guid& const char* b = a + 8; const char* c = b + 8; const char* d = c + 8; - StringUtils::ParseHex(a, 8, &v.A); - StringUtils::ParseHex(b, 8, &v.B); - StringUtils::ParseHex(c, 8, &v.C); - StringUtils::ParseHex(d, 8, &v.D); + bool failed = StringUtils::ParseHex(a, 8, &v.A); + failed |= StringUtils::ParseHex(b, 8, &v.B); + failed |= StringUtils::ParseHex(c, 8, &v.C); + failed |= StringUtils::ParseHex(d, 8, &v.D); + if (failed) + v = Guid::Empty; } bool Serialization::ShouldSerialize(const DateTime& v, const void* otherObj) diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index 9af6d7be1..41ae4898a 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -38,12 +38,16 @@ namespace Serialization int32 result = 0; if (stream.IsInt()) result = stream.GetInt(); + else if (stream.IsInt64()) + result = (int32)stream.GetInt64(); else if (stream.IsFloat()) result = (int32)stream.GetFloat(); else if (stream.IsString()) StringUtils::Parse(stream.GetString(), &result); return result; } + FLAXENGINE_API void SerializeEnum(ISerializable::SerializeStream& stream, uint32 v, ScriptingTypeHandle typeHandle); + FLAXENGINE_API int32 DeserializeEnum(ISerializable::DeserializeStream& stream, ScriptingTypeHandle typeHandle); // In-build types @@ -226,12 +230,12 @@ namespace Serialization template inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, const T& v, const void* otherObj) { - stream.Uint((uint32)v); + SerializeEnum(stream, (uint32)v, StaticType()); } template inline typename TEnableIf::Value>::Type Deserialize(ISerializable::DeserializeStream& stream, T& v, ISerializeModifier* modifier) { - v = (T)DeserializeInt(stream); + v = (T)DeserializeEnum(stream, StaticType()); } // Common types diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index 8b8c27010..2b18a7d71 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -766,7 +766,7 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value) structureValue = Variant::Cast(structureValue, typeVariantType); } structureValue.InvertInline(); // Extract any Float3/Int32 into Structure type from inlined format - const ScriptingTypeHandle structureValueTypeHandle = Scripting::FindScriptingType(structureValue.Type.GetTypeName()); + const ScriptingTypeHandle structureValueTypeHandle = structureValue.Type.GetScriptingType(); if (structureValue.Type.Type != VariantType::Structure || typeHandle != structureValueTypeHandle) { OnError(node, box, String::Format(TEXT("Cannot unpack value of type {0} to structure of type {1}"), structureValue.Type, typeName)); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 6dd2aba6f..c3d2efbd9 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -203,11 +203,11 @@ namespace Flax.Build.Bindings var fullname = apiType.FullNameManaged; if (apiType.IsEnum) - return $"Variant::Enum(VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length})), {value})"; + return $"Variant::Enum(VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR), {value})"; if (apiType.IsStruct && !CppInBuildVariantStructures.Contains(apiType.Name)) { if (apiType.IsInBuild) - return $"Variant::Structure(VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length})), {(typeInfo.IsPtr ? "*" + value : value)})"; + return $"Variant::Structure(VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR), {(typeInfo.IsPtr ? "*" + value : value)})"; return $"Variant::Structure(VariantType(VariantType::Structure, {apiType.FullNameNative}::TypeInitializer.GetType()), {(typeInfo.IsPtr ? "*" + value : value)})"; } } @@ -269,7 +269,7 @@ namespace Flax.Build.Bindings { var elementType = FindApiTypeInfo(buildData, typeInfo.GenericArgs[0], caller); var elementName = $"{(elementType != null ? elementType.FullNameManaged : typeInfo.GenericArgs[0].Type)}[]"; - return $"VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length}))"; + return $"VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length}), !USE_EDITOR)"; } if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) return "VariantType(VariantType::Dictionary)"; @@ -280,11 +280,11 @@ namespace Flax.Build.Bindings { var fullname = apiType.FullNameManaged; if (apiType.IsEnum) - return $"VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length}))"; + return $"VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR)"; if (apiType.IsStruct) { if (apiType.IsInBuild) - return $"VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length}))"; + return $"VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR)"; return $"VariantType(VariantType::Structure, {apiType.FullNameNative}::TypeInitializer.GetType())"; } if (apiType.IsClass) @@ -2761,7 +2761,8 @@ namespace Flax.Build.Bindings contents.Append($"ScriptingTypeInitializer {enumTypeNameInternal}_TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), "); contents.Append($"StringAnsiView(\"{enumTypeNameManaged}\", {enumTypeNameManaged.Length}), "); contents.Append($"sizeof({enumTypeNameNative}), "); - contents.Append($"{enumTypeNameInternal}Internal::Items);").AppendLine(); + var stringSerialization = enumInfo.Attributes != null && enumInfo.Attributes.Contains("EnumString") ? "true" : "false"; + contents.Append($"{enumTypeNameInternal}Internal::Items, {stringSerialization});").AppendLine(); contents.AppendLine($"template<> {moduleInfo.Name.ToUpperInvariant()}_API ScriptingTypeHandle StaticType<{enumTypeNameNative}>() {{ return {enumTypeNameInternal}_TypeInitializer; }}"); } @@ -3103,7 +3104,7 @@ namespace Flax.Build.Bindings header.Append(" Variant result;").AppendLine(); var apiType = FindApiTypeInfo(buildData, valueType, moduleInfo); var elementName = $"{(apiType != null ? apiType.FullNameManaged : valueType.Type)}[]"; - header.Append($" result.SetType(VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length})));").AppendLine(); + header.Append($" result.SetType(VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length}), !USE_EDITOR));").AppendLine(); header.Append(" auto* array = reinterpret_cast*>(result.AsData);").AppendLine(); header.Append(" array->Resize(length);").AppendLine(); header.Append(" for (int32 i = 0; i < length; i++)").AppendLine();