Merge branch 'master' into ImprovementSlider

This commit is contained in:
Phantom
2026-06-03 15:41:58 +02:00
23 changed files with 233 additions and 105 deletions
+1 -1
View File
@@ -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.",
@@ -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
+15 -5
View File
@@ -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<MaterialBase>(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];
@@ -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.")]
+5 -8
View File
@@ -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;
}
}
@@ -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
+8 -3
View File
@@ -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)
+10
View File
@@ -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<typename T>
static typename TEnableIf<TIsEnum<T>::Value, Variant>::Type Enum(const T value)
{
Variant v;
v.SetType(VariantType(VariantType::Enum, StaticType<T>().GetType()));
v.AsUint64 = (uint64)value;
return MoveTemp(v);
}
template<typename T>
static typename TEnableIf<!TIsEnum<T>::Value && !TIsPointer<T>::Value, Variant>::Type Structure(VariantType&& type, const T& value)
{
+1 -1
View File
@@ -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<void*> fields;
+21 -11
View File
@@ -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();
}
+12 -1
View File
@@ -268,8 +268,14 @@ public:
/// <summary>
/// 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]
/// </summary>
API_FIELD() float RenderingPercentage = 1.0f;
API_FIELD() DEPRECATED("Use RenderScale instead.") float RenderingPercentage = 1.0f;
/// <summary>
/// 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.
/// </summary>
API_FIELD() float RenderScale = 1.0f;
/// <summary>
/// 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];
}
/// <summary>
/// Waits for all scheduled async jobs to complete and clears WaitLabels.
/// </summary>
void FlushWaitLabels();
};
+1 -1
View File
@@ -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;
}
+9
View File
@@ -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();
+5
View File
@@ -32,6 +32,11 @@ API_CLASS(Static) class FLAXENGINE_API Physics
/// </summary>
API_FUNCTION() static PhysicsScene* FindScene(const StringView& name);
/// <summary>
/// Delete an existing scene (excluding the default one).
/// </summary>
API_FUNCTION() static void DeleteScene(PhysicsScene* scene);
public:
/// <summary>
/// The automatic simulation feature. True if perform physics simulation after on fixed update by auto, otherwise user should do it.
+5 -7
View File
@@ -356,9 +356,7 @@ void Renderer::DrawActors(RenderContext& renderContext, const Array<Actor*>& 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)
{
@@ -0,0 +1,15 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
namespace FlaxEngine
{
/// <summary>
/// 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.
/// </summary>
/// <seealso cref="System.Attribute" />
[AttributeUsage(AttributeTargets.Enum)]
public sealed class EnumStringAttribute : Attribute
{
}
}
+26 -37
View File
@@ -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()
+4 -2
View File
@@ -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);
};
@@ -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<EnumStringAttribute>() != 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<EnumStringAttribute>() == 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;
};
+61 -7
View File
@@ -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)
+6 -2
View File
@@ -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<typename T>
inline typename TEnableIf<TIsEnum<T>::Value>::Type Serialize(ISerializable::SerializeStream& stream, const T& v, const void* otherObj)
{
stream.Uint((uint32)v);
SerializeEnum(stream, (uint32)v, StaticType<T>());
}
template<typename T>
inline typename TEnableIf<TIsEnum<T>::Value>::Type Deserialize(ISerializable::DeserializeStream& stream, T& v, ISerializeModifier* modifier)
{
v = (T)DeserializeInt(stream);
v = (T)DeserializeEnum(stream, StaticType<T>());
}
// Common types
+1 -1
View File
@@ -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));
@@ -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<Array<Variant, HeapAllocation>*>(result.AsData);").AppendLine();
header.Append(" array->Resize(length);").AppendLine();
header.Append(" for (int32 i = 0; i < length; i++)").AppendLine();