Optimize VariantType to use static type name in game or from non-reloadable assemblies
This avoids many dynamic memory allocations in Visual Scripts and Anim Graph. #
This commit is contained in:
@@ -18,8 +18,10 @@
|
||||
#include "Engine/Core/Math/Ray.h"
|
||||
#include "Engine/Core/Math/Rectangle.h"
|
||||
#include "Engine/Core/Math/Transform.h"
|
||||
#include "Engine/Scripting/BinaryModule.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MUtils.h"
|
||||
@@ -33,6 +35,13 @@
|
||||
#endif
|
||||
#define AsEnum AsUint64
|
||||
|
||||
// Editor can hot-reload assemblies thus cached type names may become invalid, otherwise use modules that are never unloaded and their type names are always valid
|
||||
#if USE_EDITOR
|
||||
#define IS_VARIANT_TYPE_NAME_STATIC(canReload) !canReload
|
||||
#else
|
||||
#define IS_VARIANT_TYPE_NAME_STATIC(canReload) true
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
const char* InBuiltTypesTypeNames[40] =
|
||||
@@ -88,6 +97,7 @@ static_assert((int32)VariantType::Types::MAX == ARRAY_COUNT(InBuiltTypesTypeName
|
||||
VariantType::VariantType(Types type, const StringView& typeName)
|
||||
{
|
||||
Type = type;
|
||||
StaticName = 0;
|
||||
TypeName = nullptr;
|
||||
const int32 length = typeName.Length();
|
||||
if (length)
|
||||
@@ -98,32 +108,41 @@ VariantType::VariantType(Types type, const StringView& typeName)
|
||||
}
|
||||
}
|
||||
|
||||
VariantType::VariantType(Types type, const StringAnsiView& typeName)
|
||||
VariantType::VariantType(Types type, const StringAnsiView& typeName, bool staticName)
|
||||
{
|
||||
Type = type;
|
||||
TypeName = nullptr;
|
||||
int32 length = typeName.Length();
|
||||
if (length)
|
||||
StaticName = staticName && (typeName.HasChars() && typeName[typeName.Length()] == 0); // Require string to be null-terminated (not fully safe check)
|
||||
if (staticName)
|
||||
{
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, typeName.Get(), length);
|
||||
TypeName[length] = 0;
|
||||
TypeName = (char*)typeName.Get();
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeName = nullptr;
|
||||
int32 length = typeName.Length();
|
||||
if (length)
|
||||
{
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, typeName.Get(), length);
|
||||
TypeName[length] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VariantType::VariantType(Types type, const ScriptingType& sType)
|
||||
: VariantType(type)
|
||||
{
|
||||
SetTypeName(sType);
|
||||
}
|
||||
|
||||
VariantType::VariantType(Types type, const MClass* klass)
|
||||
{
|
||||
Type = type;
|
||||
StaticName = false;
|
||||
TypeName = nullptr;
|
||||
#if USE_CSHARP
|
||||
if (klass)
|
||||
{
|
||||
const StringAnsiView typeName = klass->GetFullName();
|
||||
const int32 length = typeName.Length();
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, typeName.Get(), length);
|
||||
TypeName[length] = 0;
|
||||
}
|
||||
SetTypeName(*klass);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -190,9 +209,9 @@ VariantType::VariantType(const StringAnsiView& typeName)
|
||||
if (const auto mclass = Scripting::FindClass(typeName))
|
||||
{
|
||||
if (mclass->IsEnum())
|
||||
new(this) VariantType(Enum, typeName);
|
||||
new(this) VariantType(Enum, mclass);
|
||||
else
|
||||
new(this) VariantType(ManagedObject, typeName);
|
||||
new(this) VariantType(ManagedObject, mclass);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -204,36 +223,48 @@ VariantType::VariantType(const StringAnsiView& typeName)
|
||||
VariantType::VariantType(const VariantType& other)
|
||||
{
|
||||
Type = other.Type;
|
||||
TypeName = nullptr;
|
||||
const int32 length = StringUtils::Length(other.TypeName);
|
||||
if (length)
|
||||
StaticName = other.StaticName;
|
||||
if (StaticName)
|
||||
{
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, other.TypeName, length);
|
||||
TypeName[length] = 0;
|
||||
TypeName = other.TypeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeName = nullptr;
|
||||
const int32 length = StringUtils::Length(other.TypeName);
|
||||
if (length)
|
||||
{
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, other.TypeName, length);
|
||||
TypeName[length] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VariantType::VariantType(VariantType&& other) noexcept
|
||||
{
|
||||
Type = other.Type;
|
||||
StaticName = other.StaticName;
|
||||
TypeName = other.TypeName;
|
||||
other.Type = Null;
|
||||
other.TypeName = nullptr;
|
||||
other.StaticName = 0;
|
||||
}
|
||||
|
||||
VariantType& VariantType::operator=(const Types& type)
|
||||
{
|
||||
Type = type;
|
||||
Allocator::Free(TypeName);
|
||||
if (StaticName)
|
||||
Allocator::Free(TypeName);
|
||||
TypeName = nullptr;
|
||||
StaticName = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
VariantType& VariantType::operator=(VariantType&& other)
|
||||
{
|
||||
ASSERT(this != &other);
|
||||
Swap(Type, other.Type);
|
||||
Swap(Packed, other.Packed);
|
||||
Swap(TypeName, other.TypeName);
|
||||
return *this;
|
||||
}
|
||||
@@ -242,14 +273,23 @@ VariantType& VariantType::operator=(const VariantType& other)
|
||||
{
|
||||
ASSERT(this != &other);
|
||||
Type = other.Type;
|
||||
Allocator::Free(TypeName);
|
||||
TypeName = nullptr;
|
||||
const int32 length = StringUtils::Length(other.TypeName);
|
||||
if (length)
|
||||
if (StaticName)
|
||||
Allocator::Free(TypeName);
|
||||
StaticName = other.StaticName;
|
||||
if (StaticName)
|
||||
{
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, other.TypeName, length);
|
||||
TypeName[length] = 0;
|
||||
TypeName = other.TypeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeName = nullptr;
|
||||
const int32 length = StringUtils::Length(other.TypeName);
|
||||
if (length)
|
||||
{
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, other.TypeName, length);
|
||||
TypeName[length] = 0;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -283,24 +323,45 @@ void VariantType::SetTypeName(const StringView& typeName)
|
||||
{
|
||||
if (StringUtils::Length(TypeName) != typeName.Length())
|
||||
{
|
||||
Allocator::Free(TypeName);
|
||||
if (StaticName)
|
||||
Allocator::Free(TypeName);
|
||||
StaticName = 0;
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(typeName.Length() + 1));
|
||||
TypeName[typeName.Length()] = 0;
|
||||
}
|
||||
StringUtils::ConvertUTF162ANSI(typeName.Get(), TypeName, typeName.Length());
|
||||
}
|
||||
|
||||
void VariantType::SetTypeName(const StringAnsiView& typeName)
|
||||
void VariantType::SetTypeName(const StringAnsiView& typeName, bool staticName)
|
||||
{
|
||||
if (StringUtils::Length(TypeName) != typeName.Length())
|
||||
if (StringUtils::Length(TypeName) != typeName.Length() || StaticName != staticName)
|
||||
{
|
||||
Allocator::Free(TypeName);
|
||||
if (StaticName)
|
||||
Allocator::Free(TypeName);
|
||||
StaticName = staticName;
|
||||
if (staticName)
|
||||
{
|
||||
TypeName = (char*)typeName.Get();
|
||||
return;
|
||||
}
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(typeName.Length() + 1));
|
||||
TypeName[typeName.Length()] = 0;
|
||||
}
|
||||
Platform::MemoryCopy(TypeName, typeName.Get(), typeName.Length());
|
||||
}
|
||||
|
||||
void VariantType::SetTypeName(const ScriptingType& type)
|
||||
{
|
||||
SetTypeName(type.Fullname, IS_VARIANT_TYPE_NAME_STATIC(type.Module->CanReload));
|
||||
}
|
||||
|
||||
void VariantType::SetTypeName(const MClass& klass)
|
||||
{
|
||||
#if USE_CSHARP
|
||||
SetTypeName(klass.GetFullName(), IS_VARIANT_TYPE_NAME_STATIC(klass.GetAssembly()->CanReload()));
|
||||
#endif
|
||||
}
|
||||
|
||||
const char* VariantType::GetTypeName() const
|
||||
{
|
||||
if (TypeName)
|
||||
@@ -322,6 +383,17 @@ VariantType VariantType::GetElementType() const
|
||||
return VariantType();
|
||||
}
|
||||
|
||||
void VariantType::Inline()
|
||||
{
|
||||
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(TypeName);
|
||||
if (typeHandle)
|
||||
SetTypeName(typeHandle.GetType());
|
||||
#if USE_CSHARP
|
||||
else if (const auto mclass = Scripting::FindClass(TypeName))
|
||||
SetTypeName(*mclass);
|
||||
#endif
|
||||
}
|
||||
|
||||
::String VariantType::ToString() const
|
||||
{
|
||||
::String result;
|
||||
@@ -632,8 +704,7 @@ Variant::Variant(ScriptingObject* v)
|
||||
AsObject = v;
|
||||
if (v)
|
||||
{
|
||||
// TODO: optimize VariantType to support statically linked typename of ScriptingType (via 1 bit flag within Types enum, only in game as editor might hot-reload types)
|
||||
Type.SetTypeName(v->GetType().Fullname);
|
||||
Type.SetTypeName(v->GetType());
|
||||
v->Deleted.Bind<Variant, &Variant::OnObjectDeleted>(this);
|
||||
}
|
||||
}
|
||||
@@ -644,9 +715,8 @@ Variant::Variant(Asset* v)
|
||||
AsAsset = v;
|
||||
if (v)
|
||||
{
|
||||
// TODO: optimize VariantType to support statically linked typename of ScriptingType (via 1 bit flag within Types enum, only in game as editor might hot-reload types)
|
||||
Type.SetTypeName(v->GetType().Fullname);
|
||||
v->AddReference();
|
||||
Type.SetTypeName(v->GetType());
|
||||
v->OnUnloaded.Bind<Variant, &Variant::OnAssetUnloaded>(this);
|
||||
}
|
||||
}
|
||||
@@ -3007,16 +3077,16 @@ Variant Variant::NewValue(const StringAnsiView& typeName)
|
||||
switch (type.Type)
|
||||
{
|
||||
case ScriptingTypes::Script:
|
||||
v.SetType(VariantType(VariantType::Object, typeName));
|
||||
v.SetType(VariantType(VariantType::Object, type));
|
||||
v.AsObject = type.Script.Spawn(ScriptingObjectSpawnParams(Guid::New(), typeHandle));
|
||||
if (v.AsObject)
|
||||
v.AsObject->Deleted.Bind<Variant, &Variant::OnObjectDeleted>(&v);
|
||||
break;
|
||||
case ScriptingTypes::Structure:
|
||||
v.SetType(VariantType(VariantType::Structure, typeName));
|
||||
v.SetType(VariantType(VariantType::Structure, type));
|
||||
break;
|
||||
case ScriptingTypes::Enum:
|
||||
v.SetType(VariantType(VariantType::Enum, typeName));
|
||||
v.SetType(VariantType(VariantType::Enum, type));
|
||||
v.AsEnum = 0;
|
||||
break;
|
||||
default:
|
||||
@@ -3030,16 +3100,16 @@ Variant Variant::NewValue(const StringAnsiView& typeName)
|
||||
// Fallback to C#-only types
|
||||
if (mclass->IsEnum())
|
||||
{
|
||||
v.SetType(VariantType(VariantType::Enum, typeName));
|
||||
v.SetType(VariantType(VariantType::Enum, mclass));
|
||||
v.AsEnum = 0;
|
||||
}
|
||||
else if (mclass->IsValueType())
|
||||
{
|
||||
v.SetType(VariantType(VariantType::Structure, typeName));
|
||||
v.SetType(VariantType(VariantType::Structure, mclass));
|
||||
}
|
||||
else
|
||||
{
|
||||
v.SetType(VariantType(VariantType::ManagedObject, typeName));
|
||||
v.SetType(VariantType(VariantType::ManagedObject, mclass));
|
||||
MObject* instance = mclass->CreateInstance();
|
||||
if (instance)
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ struct ScriptingTypeHandle;
|
||||
/// </summary>
|
||||
API_STRUCT(InBuild) struct FLAXENGINE_API VariantType
|
||||
{
|
||||
enum Types
|
||||
enum Types : uint8
|
||||
{
|
||||
Null = 0,
|
||||
Void,
|
||||
@@ -80,10 +80,22 @@ API_STRUCT(InBuild) struct FLAXENGINE_API VariantType
|
||||
};
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The type of the variant.
|
||||
/// </summary>
|
||||
Types Type;
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of the variant.
|
||||
/// </summary>
|
||||
Types Type;
|
||||
|
||||
/// <summary>
|
||||
/// Internal flag used to indicate that pointer to TypeName has been linked from a static/external memory that is stable (eg. ScriptingType or MClass). Allows avoiding dynamic memory allocation.
|
||||
/// </summary>
|
||||
uint8 StaticName : 1;
|
||||
};
|
||||
uint16 Packed;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The optional additional full name of the scripting type. Used for Asset, Object, Enum, Structure types to describe type precisely.
|
||||
@@ -94,17 +106,20 @@ public:
|
||||
FORCE_INLINE VariantType()
|
||||
{
|
||||
Type = Null;
|
||||
StaticName = 0;
|
||||
TypeName = nullptr;
|
||||
}
|
||||
|
||||
FORCE_INLINE explicit VariantType(Types type)
|
||||
{
|
||||
Type = type;
|
||||
StaticName = 0;
|
||||
TypeName = nullptr;
|
||||
}
|
||||
|
||||
explicit VariantType(Types type, const StringView& typeName);
|
||||
explicit VariantType(Types type, const StringAnsiView& typeName);
|
||||
explicit VariantType(Types type, const StringAnsiView& typeName, bool staticName = false);
|
||||
explicit VariantType(Types type, const ScriptingType& sType);
|
||||
explicit VariantType(Types type, const MClass* klass);
|
||||
explicit VariantType(const StringAnsiView& typeName);
|
||||
VariantType(const VariantType& other);
|
||||
@@ -112,7 +127,8 @@ public:
|
||||
|
||||
FORCE_INLINE ~VariantType()
|
||||
{
|
||||
Allocator::Free(TypeName);
|
||||
if (!StaticName)
|
||||
Allocator::Free(TypeName);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -130,9 +146,13 @@ public:
|
||||
|
||||
public:
|
||||
void SetTypeName(const StringView& typeName);
|
||||
void SetTypeName(const StringAnsiView& typeName);
|
||||
void SetTypeName(const StringAnsiView& typeName, bool staticName = false);
|
||||
void SetTypeName(const ScriptingType& type);
|
||||
void SetTypeName(const MClass& klass);
|
||||
const char* GetTypeName() 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();
|
||||
::String ToString() const;
|
||||
};
|
||||
|
||||
|
||||
@@ -683,6 +683,8 @@ BinaryModule* BinaryModule::GetModule(const StringAnsiView& name)
|
||||
|
||||
BinaryModule::BinaryModule()
|
||||
{
|
||||
CanReload = USE_EDITOR;
|
||||
|
||||
// Register
|
||||
GetModules().Add(this);
|
||||
}
|
||||
|
||||
@@ -91,6 +91,11 @@ public:
|
||||
/// </summary>
|
||||
Dictionary<StringAnsi, int32> TypeNameToTypeIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Determinates whether module can be hot-reloaded at runtime. For example, in Editor after scripts recompilation. Some modules such as engine and class library modules are static.
|
||||
/// </summary>
|
||||
bool CanReload;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -34,6 +34,7 @@ private:
|
||||
|
||||
int32 _isLoaded : 1;
|
||||
int32 _isLoading : 1;
|
||||
int32 _canReload : 1;
|
||||
mutable int32 _hasCachedClasses : 1;
|
||||
|
||||
mutable ClassesDictionary _classes;
|
||||
@@ -125,6 +126,14 @@ public:
|
||||
return _isLoaded != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if assembly can be hot-reloaded at runtime. For example, in Editor after scripts recompilation. Some assemblies such as engine and class library modules are static.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool CanReload() const
|
||||
{
|
||||
return USE_EDITOR && _canReload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly name.
|
||||
/// </summary>
|
||||
|
||||
@@ -45,6 +45,7 @@ MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name)
|
||||
: _domain(domain)
|
||||
, _isLoaded(false)
|
||||
, _isLoading(false)
|
||||
, _canReload(true)
|
||||
, _hasCachedClasses(false)
|
||||
, _reloadCount(0)
|
||||
, _name(name)
|
||||
@@ -59,6 +60,7 @@ MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name, const StringAn
|
||||
, _domain(domain)
|
||||
, _isLoaded(false)
|
||||
, _isLoading(false)
|
||||
, _canReload(true)
|
||||
, _hasCachedClasses(false)
|
||||
, _reloadCount(0)
|
||||
, _name(name)
|
||||
|
||||
@@ -874,6 +874,7 @@ bool MAssembly::LoadCorlib()
|
||||
return true;
|
||||
}
|
||||
_hasCachedClasses = false;
|
||||
_canReload = false;
|
||||
CachedAssemblyHandles.Add(_handle, this);
|
||||
|
||||
// End
|
||||
|
||||
@@ -502,6 +502,7 @@ bool Scripting::LoadBinaryModules(const String& path, const String& projectFolde
|
||||
// C#
|
||||
if (managedPath.HasChars() && !((ManagedBinaryModule*)module)->Assembly->IsLoaded())
|
||||
{
|
||||
(((ManagedBinaryModule*)module)->Assembly)->_canReload = module->CanReload;
|
||||
if (((ManagedBinaryModule*)module)->Assembly->Load(managedPath, nativePath))
|
||||
{
|
||||
LOG(Error, "Failed to load C# assembly '{0}' for binary module {1}.", managedPath, name);
|
||||
@@ -528,6 +529,7 @@ bool Scripting::Load()
|
||||
#if USE_CSHARP
|
||||
// Load C# core assembly
|
||||
ManagedBinaryModule* corlib = GetBinaryModuleCorlib();
|
||||
corlib->CanReload = false;
|
||||
if (corlib->Assembly->LoadCorlib())
|
||||
{
|
||||
LOG(Error, "Failed to load corlib C# assembly.");
|
||||
@@ -581,6 +583,8 @@ bool Scripting::Load()
|
||||
LOG(Error, "Failed to load FlaxEngine C# assembly.");
|
||||
return true;
|
||||
}
|
||||
flaxEngineModule->CanReload = false;
|
||||
flaxEngineModule->Assembly->_canReload = false;
|
||||
onEngineLoaded(flaxEngineModule->Assembly);
|
||||
|
||||
// Insert type aliases for vector types that don't exist in C++ but are just typedef (properly redirect them to actual types)
|
||||
|
||||
@@ -78,7 +78,10 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian
|
||||
v.Type = VariantType::Null;
|
||||
const auto mTypeName = SERIALIZE_FIND_MEMBER(stream, "TypeName");
|
||||
if (mTypeName != stream.MemberEnd() && mTypeName->value.IsString())
|
||||
{
|
||||
v.SetTypeName(StringAnsiView(mTypeName->value.GetStringAnsiView()));
|
||||
v.Inline();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -255,6 +255,7 @@ void ReadStream::Read(VariantType& data)
|
||||
ptr++;
|
||||
}
|
||||
*ptr = 0;
|
||||
data.Inline();
|
||||
}
|
||||
else if (typeNameLength > 0)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user