Merge branch 'master' into collection-ui

This commit is contained in:
Chandler Cox
2024-01-26 09:54:03 -06:00
616 changed files with 18797 additions and 9179 deletions

View File

@@ -5,11 +5,11 @@ using System;
namespace FlaxEngine
{
/// <summary>
/// Shows property/field in the editor only if the specified member has a given value. Can be used to hide properties based on other properties (also private properties). The given member has to be bool type.
/// Shows property/field in the editor only if the specified member has a given value. Can be used to hide properties based on other properties (also private properties). The given member has to be bool type. Multiple VisibleIf attributes can be added for additional conditions to be met.
/// </summary>
/// <seealso cref="System.Attribute" />
[Serializable]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class VisibleIfAttribute : Attribute
{
/// <summary>

View File

@@ -0,0 +1,21 @@
using System;
namespace FlaxEngine;
/// <summary>
/// This attribute allows for specifying initialization and deinitialization order for plugins.
/// </summary>
[Serializable]
[AttributeUsage(AttributeTargets.Class)]
public class PluginLoadOrderAttribute : Attribute
{
/// <summary>
/// The plugin type to initialize this plugin after.
/// </summary>
public Type InitializeAfter;
/// <summary>
/// The plugin type to deinitialize this plugin before.
/// </summary>
public Type DeinitializeBefore;
}

View File

@@ -693,6 +693,14 @@ void BinaryModule::Destroy(bool isReloading)
}
}
// Remove any scripting events
for (auto i = ScriptingEvents::EventsTable.Begin(); i.IsNotEnd(); ++i)
{
const ScriptingTypeHandle type = i->Key.First;
if (type.Module == this)
ScriptingEvents::EventsTable.Remove(i);
}
// Unregister
GetModules().RemoveKeepOrder(this);
}
@@ -906,6 +914,7 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly)
#if !COMPILE_WITHOUT_CSHARP
PROFILE_CPU();
ASSERT(ClassToTypeIndex.IsEmpty());
ScopeLock lock(Locker);
const auto& classes = assembly->GetClasses();
@@ -1226,17 +1235,17 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
const bool withInterfaces = !mMethod->IsStatic() && mMethod->GetParentClass()->IsInterface();
if (!mMethod->IsStatic())
{
// Box instance into C# object
// Box instance into C# object (and validate the type)
MObject* instanceObject = MUtils::BoxVariant(instance);
const MClass* instanceObjectClass = MCore::Object::GetClass(instanceObject);
// Validate instance
if (!instanceObject || !instanceObjectClass->IsSubClassOf(mMethod->GetParentClass(), withInterfaces))
if (!instanceObject)
{
if (!instanceObject)
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) without object instance", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount);
else
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) with invalid object instance of type '{3}'", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount, String(MUtils::GetClassFullname(instanceObject)));
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) without object instance", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount);
return true;
}
const MClass* instanceObjectClass = MCore::Object::GetClass(instanceObject);
if (!instanceObjectClass->IsSubClassOf(mMethod->GetParentClass(), withInterfaces))
{
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) with invalid object instance of type '{3}'", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount, String(MUtils::GetClassFullname(instanceObject)));
return true;
}
@@ -1270,7 +1279,11 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
// Invoke the method
MObject* exception = nullptr;
#if USE_NETCORE // NetCore uses the same path for both virtual and non-virtual calls
MObject* resultObject = mMethod->Invoke(mInstance, params, &exception);
#else
MObject* resultObject = withInterfaces ? mMethod->InvokeVirtual((MObject*)mInstance, params, &exception) : mMethod->Invoke(mInstance, params, &exception);
#endif
if (exception)
{
MException ex(exception);

View File

@@ -108,6 +108,7 @@ namespace
};
ChunkedArray<Location, 256> ManagedSourceLocations;
ThreadLocal<uint32> ManagedEventsCount;
#endif
#endif
}
@@ -145,7 +146,9 @@ DEFINE_INTERNAL_CALL(void) ProfilerInternal_BeginEvent(MString* nameObj)
srcLoc->color = 0;
}
//static constexpr tracy::SourceLocationData tracySrcLoc{ nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 };
tracy::ScopedZone::Begin(srcLoc);
const bool tracyActive = tracy::ScopedZone::Begin(srcLoc);
if (tracyActive)
ManagedEventsCount.Get()++;
#endif
#endif
#endif
@@ -155,7 +158,12 @@ DEFINE_INTERNAL_CALL(void) ProfilerInternal_EndEvent()
{
#if COMPILE_WITH_PROFILER
#if TRACY_ENABLE
tracy::ScopedZone::End();
uint32& tracyActive = ManagedEventsCount.Get();
if (tracyActive > 0)
{
tracyActive--;
tracy::ScopedZone::End();
}
#endif
ProfilerCPU::EndEvent();
#endif

View File

@@ -18,7 +18,7 @@ private:
#elif USE_NETCORE
void* _handle;
StringAnsi _name;
StringAnsi _namespace_;
StringAnsi _namespace;
uint32 _types = 0;
mutable uint32 _size = 0;
#endif

View File

@@ -1208,6 +1208,21 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, MType* type, bool& failed)
object = nullptr;
return object;
}
case MTypes::Ptr:
switch (value.Type.Type)
{
case VariantType::Pointer:
return &value.AsPointer;
case VariantType::Object:
return &value.AsObject;
case VariantType::Asset:
return &value.AsAsset;
case VariantType::Structure:
case VariantType::Blob:
return &value.AsBlob.Data;
default:
return nullptr;
}
default:
break;
}

View File

@@ -64,8 +64,8 @@ struct MConverter<T, typename TEnableIf<TAnd<TIsPODType<T>, TNot<TIsBaseOf<class
void Unbox(T& result, MObject* data)
{
CHECK(data);
Platform::MemoryCopy(&result, MCore::Object::Unbox(data), sizeof(T));
if (data)
Platform::MemoryCopy(&result, MCore::Object::Unbox(data), sizeof(T));
}
void ToManagedArray(MArray* result, const Span<T>& data)
@@ -118,7 +118,7 @@ struct MConverter<String>
{
MString** dataPtr = MCore::Array::GetAddress<MString*>(data);
for (int32 i = 0; i < result.Length(); i++)
MUtils::ToString(dataPtr[i], result[i]);
MUtils::ToString(dataPtr[i], result.Get()[i]);
}
};
@@ -151,7 +151,7 @@ struct MConverter<StringAnsi>
{
MString** dataPtr = MCore::Array::GetAddress<MString*>(data);
for (int32 i = 0; i < result.Length(); i++)
MUtils::ToString(dataPtr[i], result[i]);
MUtils::ToString(dataPtr[i], result.Get()[i]);
}
};
@@ -184,7 +184,7 @@ struct MConverter<StringView>
{
MString** dataPtr = MCore::Array::GetAddress<MString*>(data);
for (int32 i = 0; i < result.Length(); i++)
MUtils::ToString(dataPtr[i], result[i]);
MUtils::ToString(dataPtr[i], result.Get()[i]);
}
};
@@ -217,7 +217,7 @@ struct MConverter<Variant>
{
MObject** dataPtr = MCore::Array::GetAddress<MObject*>(data);
for (int32 i = 0; i < result.Length(); i++)
result[i] = MUtils::UnboxVariant(dataPtr[i]);
result.Get()[i] = MUtils::UnboxVariant(dataPtr[i]);
}
};
@@ -250,7 +250,7 @@ struct MConverter<T*, typename TEnableIf<TIsBaseOf<class ScriptingObject, T>::Va
{
MObject** dataPtr = MCore::Array::GetAddress<MObject*>(data);
for (int32 i = 0; i < result.Length(); i++)
result[i] = (T*)ScriptingObject::ToNative(dataPtr[i]);
result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]);
}
};
@@ -307,7 +307,7 @@ struct MConverter<ScriptingObjectReference<T>>
{
MObject** dataPtr = MCore::Array::GetAddress<MObject*>(data);
for (int32 i = 0; i < result.Length(); i++)
result[i] = (T*)ScriptingObject::ToNative(dataPtr[i]);
result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]);
}
};
@@ -343,7 +343,7 @@ struct MConverter<AssetReference<T>>
{
MObject** dataPtr = MCore::Array::GetAddress<MObject*>(data);
for (int32 i = 0; i < result.Length(); i++)
result[i] = (T*)ScriptingObject::ToNative(dataPtr[i]);
result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]);
}
};

View File

@@ -320,7 +320,7 @@ namespace FlaxEngine
internal static partial Object Internal_Create2(string typeName);
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceCreated", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
internal static partial void Internal_ManagedInstanceCreated(Object managedInstance, IntPtr theKlass);
internal static partial void Internal_ManagedInstanceCreated(Object managedInstance, IntPtr typeClass);
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceDeleted", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
internal static partial void Internal_ManagedInstanceDeleted(IntPtr nativeInstance);

View File

@@ -13,6 +13,7 @@
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Core/Log.h"
#include "Engine/Scripting/ManagedCLR/MField.h"
Plugin::Plugin(const SpawnParams& params)
: ScriptingObject(params)
@@ -74,14 +75,57 @@ Action PluginManager::PluginsChanged;
namespace PluginManagerImpl
{
bool Initialized = false;
Array<GamePlugin*> GamePlugins;
Array<Plugin*> EditorPlugins;
void LoadPlugin(MClass* klass, bool isEditor);
void OnAssemblyLoaded(MAssembly* assembly);
void OnAssemblyUnloading(MAssembly* assembly);
void OnBinaryModuleLoaded(BinaryModule* module);
void OnScriptsReloading();
void InitializePlugins();
void DeinitializePlugins();
template<typename PluginType = Plugin>
Array<PluginType*> SortPlugins(Array<PluginType*> plugins, MClass* pluginLoadOrderAttribute, MField* typeField)
{
// Sort plugins
Array<PluginType*> newPlugins;
for (int i = 0; i < plugins.Count(); i++)
{
PluginType* plugin = plugins[i];
int32 insertIndex = -1;
for (int j = 0; j < newPlugins.Count(); j++)
{
// Get first instance where a game plugin needs another one before it
auto attribute = newPlugins[j]->GetClass()->GetAttribute(pluginLoadOrderAttribute);
if (attribute == nullptr || MCore::Object::GetClass(attribute) != pluginLoadOrderAttribute)
continue;
// Check if attribute references a valid class
MTypeObject* refType = nullptr;
typeField->GetValue(attribute, &refType);
if (refType == nullptr)
continue;
MType* type = INTERNAL_TYPE_OBJECT_GET(refType);
if (type == nullptr)
continue;
MClass* typeClass = MCore::Type::GetClass(type);
if (plugin->GetClass() == typeClass)
{
insertIndex = j;
break;
}
}
if (insertIndex == -1)
newPlugins.Add(plugin);
else
newPlugins.Insert(insertIndex, plugin);
}
return newPlugins;
}
}
using namespace PluginManagerImpl;
@@ -90,7 +134,7 @@ class PluginManagerService : public EngineService
{
public:
PluginManagerService()
: EngineService(TEXT("Plugin Manager"), 130)
: EngineService(TEXT("Plugin Manager"), 100)
{
}
@@ -107,7 +151,7 @@ void PluginManagerService::InvokeInitialize(Plugin* plugin)
{
if (plugin->_initialized)
return;
StringAnsiView typeName = plugin->GetType().GetName();
const StringAnsiView typeName = plugin->GetType().GetName();
PROFILE_CPU();
ZoneName(typeName.Get(), typeName.Length());
@@ -125,7 +169,7 @@ void PluginManagerService::InvokeDeinitialize(Plugin* plugin)
{
if (!plugin->_initialized)
return;
StringAnsiView typeName = plugin->GetType().GetName();
const StringAnsiView typeName = plugin->GetType().GetName();
PROFILE_CPU();
ZoneName(typeName.Get(), typeName.Length());
@@ -139,30 +183,6 @@ void PluginManagerService::InvokeDeinitialize(Plugin* plugin)
PluginManager::PluginUnloaded(plugin);
}
void PluginManagerImpl::LoadPlugin(MClass* klass, bool isEditor)
{
// Create and check if use it
auto plugin = (Plugin*)Scripting::NewObject(klass);
if (!plugin)
return;
if (!isEditor)
{
GamePlugins.Add((GamePlugin*)plugin);
#if !USE_EDITOR
PluginManagerService::InvokeInitialize(plugin);
#endif
}
#if USE_EDITOR
else
{
EditorPlugins.Add(plugin);
PluginManagerService::InvokeInitialize(plugin);
}
#endif
PluginManager::PluginsChanged();
}
void PluginManagerImpl::OnAssemblyLoaded(MAssembly* assembly)
{
PROFILE_CPU_NAMED("Load Assembly Plugins");
@@ -183,6 +203,7 @@ void PluginManagerImpl::OnAssemblyLoaded(MAssembly* assembly)
#endif
// Process all classes to find plugins
bool loadedAnyPlugin = false;
auto& classes = assembly->GetClasses();
for (auto i = classes.Begin(); i.IsNotEnd(); ++i)
{
@@ -192,40 +213,69 @@ void PluginManagerImpl::OnAssemblyLoaded(MAssembly* assembly)
if (mclass->IsGeneric() || mclass->IsStatic() || mclass->IsAbstract())
continue;
if (mclass->IsSubClassOf(gamePluginClass))
{
LoadPlugin(mclass, false);
}
if (mclass->IsSubClassOf(gamePluginClass)
#if USE_EDITOR
if (mclass->IsSubClassOf(editorPluginClass))
{
LoadPlugin(mclass, true);
}
|| mclass->IsSubClassOf(editorPluginClass)
#endif
)
{
auto plugin = (Plugin*)Scripting::NewObject(mclass);
if (plugin)
{
#if USE_EDITOR
if (mclass->IsSubClassOf(editorPluginClass))
{
EditorPlugins.Add(plugin);
}
else
#endif
{
GamePlugins.Add((GamePlugin*)plugin);
}
loadedAnyPlugin = true;
}
}
}
// Send event and initialize newly added plugins (ignore during startup)
if (loadedAnyPlugin && Initialized)
{
InitializePlugins();
PluginManager::PluginsChanged();
}
}
void PluginManagerImpl::OnAssemblyUnloading(MAssembly* assembly)
{
bool changed = false;
for (int32 i = EditorPlugins.Count() - 1; i >= 0 && EditorPlugins.Count() > 0; i--)
auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute");
auto beforeTypeField = pluginLoadOrderAttribute->GetField("DeinitializeBefore");
ASSERT(beforeTypeField);
#if USE_EDITOR
auto editorPlugins = SortPlugins(EditorPlugins, pluginLoadOrderAttribute, beforeTypeField);
for (int32 i = editorPlugins.Count() - 1; i >= 0 && editorPlugins.Count() > 0; i--)
{
auto plugin = EditorPlugins[i];
auto plugin = editorPlugins[i];
if (plugin->GetType().ManagedClass->GetAssembly() == assembly)
{
PluginManagerService::InvokeDeinitialize(plugin);
EditorPlugins.RemoveAtKeepOrder(i);
EditorPlugins.Remove(plugin);
changed = true;
}
}
for (int32 i = GamePlugins.Count() - 1; i >= 0 && GamePlugins.Count() > 0; i--)
#endif
auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, beforeTypeField);
for (int32 i = gamePlugins.Count() - 1; i >= 0 && gamePlugins.Count() > 0; i--)
{
auto plugin = GamePlugins[i];
auto plugin = gamePlugins[i];
if (plugin->GetType().ManagedClass->GetAssembly() == assembly)
{
PluginManagerService::InvokeDeinitialize(plugin);
GamePlugins.RemoveAtKeepOrder(i);
GamePlugins.Remove(plugin);
changed = true;
}
}
@@ -260,38 +310,83 @@ void PluginManagerImpl::OnBinaryModuleLoaded(BinaryModule* module)
void PluginManagerImpl::OnScriptsReloading()
{
// When scripting is reloading (eg. for hot-reload in Editor) we have to deinitialize plugins (Scripting service destroys C# objects later on)
bool changed = false;
for (int32 i = EditorPlugins.Count() - 1; i >= 0 && EditorPlugins.Count() > 0; i--)
DeinitializePlugins();
}
void PluginManagerImpl::InitializePlugins()
{
if (EditorPlugins.Count() + GamePlugins.Count() == 0)
return;
PROFILE_CPU_NAMED("InitializePlugins");
auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute");
auto afterTypeField = pluginLoadOrderAttribute->GetField("InitializeAfter");
ASSERT(afterTypeField);
#if USE_EDITOR
auto editorPlugins = SortPlugins(EditorPlugins, pluginLoadOrderAttribute, afterTypeField);
for (auto plugin : editorPlugins)
{
auto plugin = EditorPlugins[i];
{
PluginManagerService::InvokeDeinitialize(plugin);
EditorPlugins.RemoveAtKeepOrder(i);
changed = true;
}
PluginManagerService::InvokeInitialize(plugin);
}
for (int32 i = GamePlugins.Count() - 1; i >= 0 && GamePlugins.Count() > 0; i--)
#else
// Game plugins are managed via InitializeGamePlugins/DeinitializeGamePlugins by Editor for play mode
auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, afterTypeField);
for (auto plugin : gamePlugins)
{
auto plugin = GamePlugins[i];
{
PluginManagerService::InvokeDeinitialize(plugin);
GamePlugins.RemoveAtKeepOrder(i);
changed = true;
}
PluginManagerService::InvokeInitialize(plugin);
}
if (changed)
PluginManager::PluginsChanged();
#endif
}
void PluginManagerImpl::DeinitializePlugins()
{
if (EditorPlugins.Count() + GamePlugins.Count() == 0)
return;
PROFILE_CPU_NAMED("DeinitializePlugins");
auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute");
auto beforeTypeField = pluginLoadOrderAttribute->GetField("DeinitializeBefore");
ASSERT(beforeTypeField);
#if USE_EDITOR
auto editorPlugins = SortPlugins(EditorPlugins, pluginLoadOrderAttribute, beforeTypeField);
for (int32 i = editorPlugins.Count() - 1; i >= 0 && editorPlugins.Count() > 0; i--)
{
auto plugin = editorPlugins[i];
PluginManagerService::InvokeDeinitialize(plugin);
EditorPlugins.Remove(plugin);
}
#endif
auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, beforeTypeField);
for (int32 i = gamePlugins.Count() - 1; i >= 0 && gamePlugins.Count() > 0; i--)
{
auto plugin = gamePlugins[i];
PluginManagerService::InvokeDeinitialize(plugin);
GamePlugins.Remove(plugin);
}
PluginManager::PluginsChanged();
}
bool PluginManagerService::Init()
{
Initialized = false;
// Process already loaded modules
for (auto module : BinaryModule::GetModules())
{
OnBinaryModuleLoaded(module);
}
// Invoke plugins initialization for all of them
InitializePlugins();
// Register for new binary modules load actions
Initialized = true;
Scripting::BinaryModuleLoaded.Bind(&OnBinaryModuleLoaded);
Scripting::ScriptsReloading.Bind(&OnScriptsReloading);
@@ -300,28 +395,13 @@ bool PluginManagerService::Init()
void PluginManagerService::Dispose()
{
// Unregister from new modules loading
Initialized = false;
Scripting::BinaryModuleLoaded.Unbind(&OnBinaryModuleLoaded);
Scripting::ScriptsReloading.Unbind(&OnScriptsReloading);
// Cleanup all plugins
PROFILE_CPU_NAMED("Dispose Plugins");
const int32 pluginsCount = EditorPlugins.Count() + GamePlugins.Count();
if (pluginsCount == 0)
return;
LOG(Info, "Unloading {0} plugins", pluginsCount);
for (int32 i = EditorPlugins.Count() - 1; i >= 0 && EditorPlugins.Count() > 0; i--)
{
auto plugin = EditorPlugins[i];
InvokeDeinitialize(plugin);
EditorPlugins.RemoveAtKeepOrder(i);
}
for (int32 i = GamePlugins.Count() - 1; i >= 0 && GamePlugins.Count() > 0; i--)
{
auto plugin = GamePlugins[i];
InvokeDeinitialize(plugin);
GamePlugins.RemoveAtKeepOrder(i);
}
PluginManager::PluginsChanged();
DeinitializePlugins();
}
const Array<GamePlugin*>& PluginManager::GetGamePlugins()
@@ -336,11 +416,13 @@ const Array<Plugin*>& PluginManager::GetEditorPlugins()
Plugin* PluginManager::GetPlugin(const StringView& name)
{
#if USE_EDITOR
for (Plugin* p : EditorPlugins)
{
if (p->GetDescription().Name == name)
return p;
}
#endif
for (GamePlugin* gp : GamePlugins)
{
if (gp->GetDescription().Name == name)
@@ -352,11 +434,13 @@ Plugin* PluginManager::GetPlugin(const StringView& name)
Plugin* PluginManager::GetPlugin(const MClass* type)
{
CHECK_RETURN(type, nullptr);
#if USE_EDITOR
for (Plugin* p : EditorPlugins)
{
if (p->GetClass()->IsSubClassOf(type))
return p;
}
#endif
for (GamePlugin* gp : GamePlugins)
{
if (gp->GetClass()->IsSubClassOf(type))
@@ -368,11 +452,13 @@ Plugin* PluginManager::GetPlugin(const MClass* type)
Plugin* PluginManager::GetPlugin(const ScriptingTypeHandle& type)
{
CHECK_RETURN(type, nullptr);
#if USE_EDITOR
for (Plugin* p : EditorPlugins)
{
if (p->Is(type))
return p;
}
#endif
for (GamePlugin* gp : GamePlugins)
{
if (gp->Is(type))
@@ -386,18 +472,32 @@ Plugin* PluginManager::GetPlugin(const ScriptingTypeHandle& type)
void PluginManager::InitializeGamePlugins()
{
PROFILE_CPU();
for (int32 i = 0; i < GamePlugins.Count(); i++)
auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute");
auto afterTypeField = pluginLoadOrderAttribute->GetField("InitializeAfter");
ASSERT(afterTypeField);
auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, afterTypeField);
for (int32 i = 0; i < gamePlugins.Count(); i++)
{
PluginManagerService::InvokeInitialize(GamePlugins[i]);
PluginManagerService::InvokeInitialize(gamePlugins[i]);
}
}
void PluginManager::DeinitializeGamePlugins()
{
PROFILE_CPU();
for (int32 i = GamePlugins.Count() - 1; i >= 0; i--)
auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute");
auto beforeTypeField = pluginLoadOrderAttribute->GetField("DeinitializeBefore");
ASSERT(beforeTypeField);
auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, beforeTypeField);
for (int32 i = gamePlugins.Count() - 1; i >= 0; i--)
{
PluginManagerService::InvokeDeinitialize(GamePlugins[i]);
PluginManagerService::InvokeDeinitialize(gamePlugins[i]);
}
}

View File

@@ -46,6 +46,7 @@
#include <mono/metadata/metadata.h>
#include <mono/metadata/threads.h>
#include <mono/metadata/reflection.h>
#include <mono/metadata/mono-gc.h>
#include <mono/metadata/mono-private-unstable.h>
typedef char char_t;
#define DOTNET_HOST_MONO_DEBUG 0
@@ -182,22 +183,15 @@ Dictionary<void*, MAssembly*> CachedAssemblyHandles;
/// </summary>
void* GetStaticMethodPointer(const String& methodName);
/// <summary>
/// Calls the managed static method in NativeInterop class with given parameters.
/// </summary>
template<typename RetType, typename... Args>
FORCE_INLINE RetType CallStaticMethodByName(const String& methodName, Args... args)
{
typedef RetType (CORECLR_DELEGATE_CALLTYPE* fun)(Args...);
return ((fun)GetStaticMethodPointer(methodName))(args...);
}
/// <summary>
/// Calls the managed static method with given parameters.
/// </summary>
template<typename RetType, typename... Args>
FORCE_INLINE RetType CallStaticMethod(void* methodPtr, Args... args)
{
#if DOTNET_HOST_MONO
ASSERT_LOW_LAYER(mono_domain_get()); // Ensure that Mono runtime has been attached to this thread
#endif
typedef RetType (CORECLR_DELEGATE_CALLTYPE* fun)(Args...);
return ((fun)methodPtr)(args...);
}
@@ -273,7 +267,7 @@ bool MCore::LoadEngine()
return true;
// Prepare managed side
CallStaticMethodByName<void>(TEXT("Init"));
CallStaticMethod<void>(GetStaticMethodPointer(TEXT("Init")));
#ifdef MCORE_MAIN_MODULE_NAME
// MCORE_MAIN_MODULE_NAME define is injected by Scripting.Build.cs on platforms that use separate shared library for engine symbols
::String flaxLibraryPath(Platform::GetMainDirectory() / TEXT(MACRO_TO_STR(MCORE_MAIN_MODULE_NAME)));
@@ -292,7 +286,8 @@ bool MCore::LoadEngine()
MRootDomain = New<MDomain>("Root");
MDomains.Add(MRootDomain);
char* buildInfo = CallStaticMethodByName<char*>(TEXT("GetRuntimeInformation"));
void* GetRuntimeInformationPtr = GetStaticMethodPointer(TEXT("GetRuntimeInformation"));
char* buildInfo = CallStaticMethod<char*>(GetRuntimeInformationPtr);
LOG(Info, ".NET runtime version: {0}", ::String(buildInfo));
MCore::GC::FreeMemory(buildInfo);
@@ -304,7 +299,7 @@ void MCore::UnloadEngine()
if (!MRootDomain)
return;
PROFILE_CPU();
CallStaticMethodByName<void>(TEXT("Exit"));
CallStaticMethod<void>(GetStaticMethodPointer(TEXT("Exit")));
MDomains.ClearDelete();
MRootDomain = nullptr;
ShutdownHostfxr();
@@ -724,6 +719,7 @@ void GetAssemblyName(void* assemblyHandle, StringAnsi& name, StringAnsi& fullnam
DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* managedClass, void* assemblyHandle)
{
ScopeLock lock(BinaryModule::Locker);
MAssembly* assembly = GetAssembly(assemblyHandle);
if (assembly == nullptr)
{
@@ -737,7 +733,18 @@ DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* man
MClass* klass = New<MClass>(assembly, managedClass->typeHandle, managedClass->name, managedClass->fullname, managedClass->namespace_, managedClass->typeAttributes);
if (assembly != nullptr)
{
const_cast<MAssembly::ClassesDictionary&>(assembly->GetClasses()).Add(klass->GetFullName(), klass);
auto& classes = const_cast<MAssembly::ClassesDictionary&>(assembly->GetClasses());
MClass* oldKlass;
if (classes.TryGet(klass->GetFullName(), oldKlass))
{
LOG(Warning, "Class '{0}' was already added to assembly '{1}'", String(klass->GetFullName()), String(assembly->GetName()));
Delete(klass);
klass = oldKlass;
}
else
{
classes.Add(klass->GetFullName(), klass);
}
}
managedClass->nativePointer = klass;
}
@@ -829,7 +836,7 @@ bool MAssembly::UnloadImage(bool isReloading)
MClass::MClass(const MAssembly* parentAssembly, void* handle, const char* name, const char* fullname, const char* namespace_, MTypeAttributes attributes)
: _handle(handle)
, _name(name)
, _namespace_(namespace_)
, _namespace(namespace_)
, _assembly(parentAssembly)
, _fullname(fullname)
, _hasCachedProperties(false)
@@ -878,7 +885,7 @@ MClass::MClass(const MAssembly* parentAssembly, void* handle, const char* name,
static void* TypeIsEnumPtr = GetStaticMethodPointer(TEXT("TypeIsEnum"));
_isEnum = CallStaticMethod<bool, void*>(TypeIsEnumPtr, handle);
CachedClassHandles.Add(handle, this);
CachedClassHandles[handle] = this;
}
bool MAssembly::ResolveMissingFile(String& assemblyPath) const
@@ -908,7 +915,7 @@ StringAnsiView MClass::GetName() const
StringAnsiView MClass::GetNamespace() const
{
return _namespace_;
return _namespace;
}
MType* MClass::GetType() const
@@ -1556,6 +1563,7 @@ const Array<MObject*>& MProperty::GetAttributes() const
MAssembly* GetAssembly(void* assemblyHandle)
{
ScopeLock lock(BinaryModule::Locker);
MAssembly* assembly;
if (CachedAssemblyHandles.TryGet(assemblyHandle, assembly))
return assembly;
@@ -1564,6 +1572,7 @@ MAssembly* GetAssembly(void* assemblyHandle)
MClass* GetClass(MType* typeHandle)
{
ScopeLock lock(BinaryModule::Locker);
MClass* klass = nullptr;
CachedClassHandles.TryGet(typeHandle, klass);
return nullptr;
@@ -1573,6 +1582,7 @@ MClass* GetOrCreateClass(MType* typeHandle)
{
if (!typeHandle)
return nullptr;
ScopeLock lock(BinaryModule::Locker);
MClass* klass;
if (!CachedClassHandles.TryGet(typeHandle, klass))
{
@@ -1584,7 +1594,12 @@ MClass* GetOrCreateClass(MType* typeHandle)
klass = New<MClass>(assembly, classInfo.typeHandle, classInfo.name, classInfo.fullname, classInfo.namespace_, classInfo.typeAttributes);
if (assembly != nullptr)
{
const_cast<MAssembly::ClassesDictionary&>(assembly->GetClasses()).Add(klass->GetFullName(), klass);
auto& classes = const_cast<MAssembly::ClassesDictionary&>(assembly->GetClasses());
if (classes.ContainsKey(klass->GetFullName()))
{
LOG(Warning, "Class '{0}' was already added to assembly '{1}'", String(klass->GetFullName()), String(assembly->GetName()));
}
classes[klass->GetFullName()] = klass;
}
if (typeHandle != classInfo.typeHandle)
@@ -1635,14 +1650,14 @@ bool InitHostfxr()
const ::String csharpLibraryPath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.dll");
const ::String csharpRuntimeConfigPath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.runtimeconfig.json");
if (!FileSystem::FileExists(csharpLibraryPath))
LOG(Fatal, "Failed to initialize managed runtime, missing file: {0}", csharpLibraryPath);
LOG(Fatal, "Failed to initialize .NET runtime, missing file: {0}", csharpLibraryPath);
if (!FileSystem::FileExists(csharpRuntimeConfigPath))
LOG(Fatal, "Failed to initialize managed runtime, missing file: {0}", csharpRuntimeConfigPath);
LOG(Fatal, "Failed to initialize .NET runtime, missing file: {0}", csharpRuntimeConfigPath);
const FLAX_CORECLR_STRING& libraryPath = FLAX_CORECLR_STRING(csharpLibraryPath);
// Get path to hostfxr library
get_hostfxr_parameters get_hostfxr_params;
get_hostfxr_params.size = sizeof(hostfxr_initialize_parameters);
get_hostfxr_params.size = sizeof(get_hostfxr_parameters);
get_hostfxr_params.assembly_path = libraryPath.Get();
#if PLATFORM_MAC
::String macOSDotnetRoot = TEXT("/usr/local/share/dotnet");
@@ -1688,9 +1703,9 @@ bool InitHostfxr()
Platform::OpenUrl(TEXT("https://dotnet.microsoft.com/en-us/download/dotnet/7.0"));
#endif
#if USE_EDITOR
LOG(Fatal, "Missing .NET 7 SDK installation required to run Flax Editor.");
LOG(Fatal, "Missing .NET 7 or later SDK installation required to run Flax Editor.");
#else
LOG(Fatal, "Missing .NET 7 Runtime installation required to run this application.");
LOG(Fatal, "Missing .NET 7 or later Runtime installation required to run this application.");
#endif
return true;
}
@@ -1720,14 +1735,13 @@ bool InitHostfxr()
return true;
}
// TODO: Implement picking different version of hostfxr, currently prefers highest available version.
// Allow future and preview versions of .NET
String dotnetRollForward;
// TODO: Implement support for picking RC/beta updates of .NET runtime
// Uncomment for enabling support for upcoming .NET major release candidates
#if 0
String dotnetRollForwardPr;
if (Platform::GetEnvironmentVariable(TEXT("DOTNET_ROLL_FORWARD"), dotnetRollForward))
Platform::SetEnvironmentVariable(TEXT("DOTNET_ROLL_FORWARD"), TEXT("LatestMajor"));
if (Platform::GetEnvironmentVariable(TEXT("DOTNET_ROLL_FORWARD_TO_PRERELEASE"), dotnetRollForwardPr))
Platform::SetEnvironmentVariable(TEXT("DOTNET_ROLL_FORWARD_TO_PRERELEASE"), TEXT("1"));
#endif
// Initialize hosting component
const char_t* argv[1] = { libraryPath.Get() };
@@ -1791,9 +1805,10 @@ void* GetStaticMethodPointer(const String& methodName)
void* fun;
if (CachedFunctions.TryGet(methodName, fun))
return fun;
PROFILE_CPU();
const int rc = get_function_pointer(NativeInteropTypeName, FLAX_CORECLR_STRING(methodName).Get(), UNMANAGEDCALLERSONLY_METHOD, nullptr, nullptr, &fun);
if (rc != 0)
LOG(Fatal, "Failed to get unmanaged function pointer for method {0}: 0x{1:x}", methodName.Get(), (unsigned int)rc);
LOG(Fatal, "Failed to get unmanaged function pointer for method '{0}': 0x{1:x}", methodName, (unsigned int)rc);
CachedFunctions.Add(methodName, fun);
return fun;
}
@@ -2012,6 +2027,9 @@ bool InitHostfxr()
//Platform::SetEnvironmentVariable(TEXT("MONO_GC_DEBUG"), TEXT("6:gc-log.txt,check-remset-consistency,nursery-canaries"));
#endif
// Adjust GC threads suspending mode to not block attached native threads (eg. Job System)
Platform::SetEnvironmentVariable(TEXT("MONO_THREADS_SUSPEND"), TEXT("preemptive"));
#if defined(USE_MONO_AOT_MODE)
// Enable AOT mode (per-platform)
mono_jit_set_aot_mode(USE_MONO_AOT_MODE);
@@ -2056,7 +2074,7 @@ bool InitHostfxr()
// Setup debugger
{
int32 debuggerLogLevel = 0;
if (CommandLine::Options.MonoLog.IsTrue())
if (CommandLine::Options.MonoLog.IsTrue() || DOTNET_HOST_MONO_DEBUG)
{
LOG(Info, "Using detailed Mono logging");
mono_trace_set_level_string("debug");
@@ -2139,6 +2157,7 @@ bool InitHostfxr()
LOG(Fatal, "Failed to initialize Mono.");
return true;
}
mono_gc_init_finalizer_thread();
// Log info
char* buildInfo = mono_get_runtime_build_info();
@@ -2163,6 +2182,7 @@ void* GetStaticMethodPointer(const String& methodName)
void* fun;
if (CachedFunctions.TryGet(methodName, fun))
return fun;
PROFILE_CPU();
static MonoClass* nativeInteropClass = nullptr;
if (!nativeInteropClass)
@@ -2187,7 +2207,7 @@ void* GetStaticMethodPointer(const String& methodName)
{
const unsigned short errorCode = mono_error_get_error_code(&error);
const char* errorMessage = mono_error_get_message(&error);
LOG(Fatal, "mono_method_get_unmanaged_callers_only_ftnptr failed with error code 0x{0:x}, {1}", errorCode, String(errorMessage));
LOG(Fatal, "Failed to get unmanaged function pointer for method '{0}': 0x{1:x}, {2}", methodName, errorCode, String(errorMessage));
}
mono_error_cleanup(&error);

View File

@@ -27,6 +27,7 @@ Script::Script(const SpawnParams& params)
, _tickUpdate(false)
, _tickLateUpdate(false)
, _tickLateFixedUpdate(false)
, _wasAwakeCalled(false)
, _wasStartCalled(false)
, _wasEnableCalled(false)
{
@@ -86,7 +87,7 @@ void Script::SetParent(Actor* value, bool canBreakPrefabLink)
// Unlink from the old one
if (_parent)
{
if (!value && _parent->IsDuringPlay() && _parent->IsActiveInHierarchy() && GetEnabled())
if (!value && _parent->IsDuringPlay() && _parent->IsActiveInHierarchy() && GetEnabled() && _wasEnableCalled)
{
// Call disable when script is removed from actor (new actor is null)
Disable();
@@ -241,19 +242,31 @@ String Script::ToString() const
void Script::OnDeleteObject()
{
// Ensure to unlink from the parent (it will call Disable event if required)
SetParent(nullptr);
// Check if remove object from game
if (IsDuringPlay())
// Call OnDisable
if (_wasEnableCalled)
{
Disable();
}
// Call OnDestroy
if (_wasAwakeCalled)
{
_wasAwakeCalled = false;
CHECK_EXECUTE_IN_EDITOR
{
OnDestroy();
}
}
// End play
if (IsDuringPlay())
{
EndPlay();
}
// Unlink from parent
SetParent(nullptr);
// Base
SceneObject::OnDeleteObject();
}
@@ -274,9 +287,14 @@ void Script::Initialize()
if (!IsRegistered())
RegisterObject();
CHECK_EXECUTE_IN_EDITOR
// Call OnAwake
if (!_wasAwakeCalled)
{
OnAwake();
_wasAwakeCalled = true;
CHECK_EXECUTE_IN_EDITOR
{
OnAwake();
}
}
}

View File

@@ -15,15 +15,16 @@ API_CLASS(Abstract) class FLAXENGINE_API Script : public SceneObject
friend SceneTicking;
friend class PrefabInstanceData;
protected:
int32 _enabled : 1;
int32 _tickFixedUpdate : 1;
int32 _tickUpdate : 1;
int32 _tickLateUpdate : 1;
int32 _tickLateFixedUpdate : 1;
int32 _wasStartCalled : 1;
int32 _wasEnableCalled : 1;
uint16 _enabled : 1;
uint16 _tickFixedUpdate : 1;
uint16 _tickUpdate : 1;
uint16 _tickLateUpdate : 1;
uint16 _tickLateFixedUpdate : 1;
uint16 _wasAwakeCalled : 1;
uint16 _wasStartCalled : 1;
uint16 _wasEnableCalled : 1;
#if USE_EDITOR
int32 _executeInEditor : 1;
uint16 _executeInEditor : 1;
#endif
public:

View File

@@ -20,10 +20,7 @@ public class Scripting : EngineModule
void AddFrameworkDefines(string template, int major, int latestMinor)
{
for (int minor = latestMinor; minor >= 0; minor--)
{
options.ScriptingAPI.Defines.Add(string.Format(template, major, minor));
options.ScriptingAPI.Defines.Add(string.Format($"{template}_OR_GREATER", major, minor));
}
}
// .NET
@@ -31,14 +28,15 @@ public class Scripting : EngineModule
options.ScriptingAPI.Defines.Add("USE_NETCORE");
// .NET SDK
AddFrameworkDefines("NET{0}_{1}", 7, 0); // "NET7_0" and "NET7_0_OR_GREATER"
AddFrameworkDefines("NET{0}_{1}", 6, 0);
AddFrameworkDefines("NET{0}_{1}", 5, 0);
var dotnetSdk = DotNetSdk.Instance;
options.ScriptingAPI.Defines.Add("NET");
AddFrameworkDefines("NETCOREAPP{0}_{1}", 3, 1); // "NETCOREAPP3_1" and "NETCOREAPP3_1_OR_GREATER"
AddFrameworkDefines("NETCOREAPP{0}_{1}", 2, 2);
AddFrameworkDefines("NETCOREAPP{0}_{1}", 1, 1);
AddFrameworkDefines("NET{0}_{1}", dotnetSdk.Version.Major, 0); // "NET7_0"
for (int i = 5; i <= dotnetSdk.Version.Major; i++)
AddFrameworkDefines("NET{0}_{1}_OR_GREATER", dotnetSdk.Version.Major, 0); // "NET7_0_OR_GREATER"
options.ScriptingAPI.Defines.Add("NETCOREAPP");
AddFrameworkDefines("NETCOREAPP{0}_{1}_OR_GREATER", 3, 1); // "NETCOREAPP3_1_OR_GREATER"
AddFrameworkDefines("NETCOREAPP{0}_{1}_OR_GREATER", 2, 2);
AddFrameworkDefines("NETCOREAPP{0}_{1}_OR_GREATER", 1, 1);
if (options.Target is EngineTarget engineTarget && engineTarget.UseSeparateMainExecutable(options))
{

View File

@@ -28,8 +28,8 @@
#include "Engine/Content/Content.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Engine/Time.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Platform/MemoryStats.h"
#include "Engine/Serialization/JsonTools.h"
extern void registerFlaxEngineInternalCalls();
@@ -193,6 +193,14 @@ void ScriptingService::Update()
{
PROFILE_CPU_NAMED("Scripting::Update");
INVOKE_EVENT(Update);
#ifdef USE_NETCORE
// Force GC to run in background periodically to avoid large blocking collections causing hitches
if (Time::Update.TicksCount % 60 == 0)
{
MCore::GC::Collect(MCore::GC::MaxGeneration(), MGCCollectionMode::Forced, false, false);
}
#endif
}
void ScriptingService::LateUpdate()
@@ -428,6 +436,7 @@ bool Scripting::Load()
PROFILE_CPU();
// Note: this action can be called from main thread (due to Mono problems with assemblies actions from other threads)
ASSERT(IsInMainThread());
ScopeLock lock(BinaryModule::Locker);
#if USE_CSHARP
// Load C# core assembly
@@ -1017,30 +1026,28 @@ bool Scripting::IsTypeFromGameScripts(MClass* type)
void Scripting::RegisterObject(ScriptingObject* obj)
{
const Guid id = obj->GetID();
ScopeLock lock(_objectsLocker);
//ASSERT(!_objectsDictionary.ContainsValue(obj));
#if ENABLE_ASSERTION
#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING
ScriptingObjectData other;
if (_objectsDictionary.TryGet(obj->GetID(), other))
if (_objectsDictionary.TryGet(id, other))
#else
ScriptingObject* other;
if (_objectsDictionary.TryGet(obj->GetID(), other))
if (_objectsDictionary.TryGet(id, other))
#endif
{
// Something went wrong...
LOG(Error, "Objects registry already contains object with ID={0} (type '{3}')! Trying to register object {1} (type '{2}').", obj->GetID(), obj->ToString(), String(obj->GetClass()->GetFullName()), String(other->GetClass()->GetFullName()));
_objectsDictionary.Remove(obj->GetID());
LOG(Error, "Objects registry already contains object with ID={0} (type '{3}')! Trying to register object {1} (type '{2}').", id, obj->ToString(), String(obj->GetClass()->GetFullName()), String(other->GetClass()->GetFullName()));
}
#else
ASSERT(!_objectsDictionary.ContainsKey(obj->_id));
#endif
#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING
LOG(Info, "[RegisterObject] obj = 0x{0:x}, {1}", (uint64)obj, String(ScriptingObjectData(obj).TypeName));
#endif
_objectsDictionary.Add(obj->GetID(), obj);
_objectsDictionary[id] = obj;
}
void Scripting::UnregisterObject(ScriptingObject* obj)

View File

@@ -135,13 +135,10 @@ namespace FlaxEngine
{
if (e.ExceptionObject is Exception exception)
{
Debug.LogError($"Unhandled Exception: {exception.Message}");
Debug.LogException(exception);
if (e.IsTerminating && !System.Diagnostics.Debugger.IsAttached)
Platform.Fatal($"Unhandled Exception: {exception}");
else
{
Debug.LogError($"Unhandled Exception: {exception.Message}");
Debug.LogException(exception);
}
}
}
@@ -183,6 +180,8 @@ namespace FlaxEngine
private static void OnLocalizationChanged()
{
// iOS uses globalization-invariant mode so ignore it
#if !PLATFORM_IOS
var currentThread = Thread.CurrentThread;
var language = Localization.CurrentLanguage;
if (language != null)
@@ -190,6 +189,7 @@ namespace FlaxEngine
var culture = Localization.CurrentCulture;
if (culture != null)
currentThread.CurrentCulture = culture;
#endif
}
/// <summary>
@@ -271,6 +271,7 @@ namespace FlaxEngine
Foreground = Color.FromBgra(0xFFFFFFFF),
ForegroundGrey = Color.FromBgra(0xFFA9A9B3),
ForegroundDisabled = Color.FromBgra(0xFF787883),
ForegroundViewport = Color.FromBgra(0xFFFFFFFF),
BackgroundHighlighted = Color.FromBgra(0xFF54545C),
BorderHighlighted = Color.FromBgra(0xFF6A6A75),
BackgroundSelected = Color.FromBgra(0xFF007ACC),

View File

@@ -47,8 +47,12 @@ public:
/// </summary>
~ScriptingObjectReferenceBase()
{
if (_object)
_object->Deleted.Unbind<ScriptingObjectReferenceBase, &ScriptingObjectReferenceBase::OnDeleted>(this);
ScriptingObject* obj = _object;
if (obj)
{
_object = nullptr;
obj->Deleted.Unbind<ScriptingObjectReferenceBase, &ScriptingObjectReferenceBase::OnDeleted>(this);
}
}
public:

View File

@@ -38,8 +38,12 @@ public:
/// </summary>
~SoftObjectReferenceBase()
{
if (_object)
_object->Deleted.Unbind<SoftObjectReferenceBase, &SoftObjectReferenceBase::OnDeleted>(this);
ScriptingObject* obj = _object;
if (obj)
{
_object = nullptr;
obj->Deleted.Unbind<SoftObjectReferenceBase, &SoftObjectReferenceBase::OnDeleted>(this);
}
}
public: