Properly unload script assembly when reloading

This commit is contained in:
Simon Lübeß
2026-09-13 17:45:12 +02:00
parent 0087bca27b
commit e3619ae93c
7 changed files with 160 additions and 49 deletions
+14 -5
View File
@@ -29,7 +29,9 @@ static class CoreClrHelper
//static function void(char8* path) LoadScriptAssembly;
static LoadScriptAssemblyFunc _loadScriptAssembly;
static function void() _unloadAssemblies;
static function void() _unloadScriptAssembly;
static function void() _waitUntilOldAssemblyDead;
// public static unsafe void GetScriptClasses(void** outBuffer, long* length)
private function void GetScriptClassesFunc(void** outBuffer, int64* length);
@@ -108,8 +110,10 @@ static class CoreClrHelper
{
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "LoadScriptAssembly", out _loadScriptAssembly);
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "UnloadAssemblies", out _unloadAssemblies);
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "UnloadScriptAssembly", out _unloadScriptAssembly);
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "WaitUntilOldAssemblyDead", out _waitUntilOldAssemblyDead);
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "GetScriptClasses", out _getScriptClasses);
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "FreeScriptClassNames", out _freeScriptClassNames);
@@ -130,16 +134,21 @@ static class CoreClrHelper
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "InvokeEntityOnCollisionEnter2D", out _entityScriptFunctions.OnCollisionEnter2D);
}
public static void LoadAppAssembly(Span<uint8> appAssemblyData, Span<uint8> pdbData)
public static void LoadScriptAssembly(Span<uint8> appAssemblyData, Span<uint8> pdbData)
{
_loadScriptAssembly(appAssemblyData.Ptr, appAssemblyData.Length, pdbData.Ptr, pdbData.Length);
}
public static void UnloadAssemblies()
public static void UnloadScriptAssembly()
{
_unloadAssemblies();
_unloadScriptAssembly();
}
public static void WaitUntilOldAssemblyDead()
{
_waitUntilOldAssemblyDead();
}
/// Gets an array of script class infos. Use FreeScriptClassNames to release the buffer.
public static void GetScriptClasses(out void* outBuffer, out int64 entryCount)
{
@@ -16,6 +16,7 @@ class EntitySerializerWrapper : NewScriptClass
function void DeserializeMethod(UUID entityId, void* serializedObjPtr, void* serializerPtr);
function void SerializeStaticMethod(char8* fullTypeName, void* serializedObjPtr, void* serializerPtr);
function void DeserializeStaticMethod(char8* fullTypeName, void* serializedObjPtr, void* serializerPtr);
function void FinishSerializationMethod(void* serializerPtr);
private CreateSerializationContextMethod _createSerializationContextMethod;
//private ClearSerializationContextMethod _clearSerializationContextMethod;
@@ -25,16 +26,19 @@ class EntitySerializerWrapper : NewScriptClass
private DeserializeMethod _deserializeMethod;
private SerializeStaticMethod _serializeStaticMethod;
private DeserializeStaticMethod _deserializeStaticMethod;
private FinishSerializationMethod _finishSerializationMethod;
[AllowAppend]
public this() : base("GlitchyEngine.Serialization.EntitySerializer", .Empty, .None, false)
{
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "CreateSerializationContext", out _createSerializationContextMethod);
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "DestroySerializationContext", out _destroySerializationContextMethod);
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "EntitySerializer_Serialize", out _serializeMethod);
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "EntitySerializer_Deserialize", out _deserializeMethod);
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "EntitySerializer_SerializeStaticFields", out _serializeStaticMethod);
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "EntitySerializer_DeserializeStaticFields", out _deserializeStaticMethod);
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "EntitySerializer_FinishSerialization", out _finishSerializationMethod);
}
public void CreateSerializationContext(ScriptInstanceSerializer serializer)
@@ -93,4 +97,10 @@ class EntitySerializerWrapper : NewScriptClass
_deserializeStaticMethod(@class.FullName.CStr(), serializedObjectPtr, serializerPtr);
}
public void FinishSerialization(ScriptInstanceSerializer serializer)
{
void* serializerPtr = Internal.UnsafeCastToPtr(serializer);
_finishSerializationMethod(serializerPtr);
}
}
+24 -36
View File
@@ -209,32 +209,6 @@ static class ScriptEngine
CoreClrHelper.Init(ScriptCorePath);
}
/*static void InitMono()
{
Mono.mono_set_assemblies_path("mono/lib/4.5");
if (_debuggingEnabled)
{
char8*[2] options = .(
"--debugger-agent=transport=dt_socket,address=127.0.0.1:2550,server=y,suspend=n,loglevel=3,logfile=MonoDebugger.log",
"--soft-breakpoints"
);
Mono.mono_jit_parse_options(options.Count, &options);
Mono.mono_debug_init(.Mono);
}
s_RootDomain = Mono.mono_jit_init("GlitchyEngineJITRuntime");
Log.EngineLogger.Assert(s_RootDomain != null, "Failed to initialize mono root domain");
if (_debuggingEnabled)
{
Mono.mono_debug_domain_create(s_RootDomain);
}
Mono.mono_thread_set_main(Mono.mono_thread_current());
}*/
static bool _requestingReload = false;
static void InitAssemblyWatcher()
@@ -301,7 +275,7 @@ static class ScriptEngine
File.ReadAll(pdbPath, pdbData);
CoreClrHelper.LoadAppAssembly(data, pdbData);
CoreClrHelper.LoadScriptAssembly(data, pdbData);
GetEntitiesFromAssemblies();
@@ -445,14 +419,15 @@ static class ScriptEngine
{
Debug.Profiler.ProfileFunction!();
Log.EngineLogger.Info("Reloading script assemblies.");
// TODO: Assembly reload might actually take a second. We should consider doing it in a separate thread in the editor.
Log.EngineLogger.Info("Reloading script assemblies.");
ScriptInstanceSerializer contextSerializer = scope .();
contextSerializer.SerializeScriptInstances();
// TODO: Unload script assemblies, remove class handles, fire unload events?
UnloadScriptAssemblies();
LoadScriptAssemblies();
@@ -479,15 +454,28 @@ static class ScriptEngine
Log.EngineLogger.Info("Script assemblies reloaded!");
}
private static void UnloadScriptAssemblies()
{
for (let (id, scriptInstance) in _entityScriptInstances)
{
if (Context.GetEntityByID(id) case .Ok(let entity))
{
ScriptComponent* script = entity.GetComponent<ScriptComponent>();
DestroyInstance(entity, script);
}
else
{
Log.EngineLogger.AssertDebug(false, "Entity script instance exists but no entity?!");
}
}
CoreClrHelper.UnloadScriptAssembly();
CoreClrHelper.WaitUntilOldAssemblyDead();
}
public static void Shutdown()
{
/*Mono.mono_domain_set(s_RootDomain, false);
Mono.mono_domain_unload(s_AppDomain);
s_AppDomain = null;
Mono.mono_jit_cleanup(s_RootDomain);
s_RootDomain = null;*/
UnloadScriptAssemblies();
}
/// Returns the script class with the given name, or null, if no such class exists.
@@ -31,6 +31,15 @@ public class ScriptInstanceSerializer
ScriptEngine.Classes.EntitySerializer.CreateSerializationContext(this);
}
/// Signals, that no more objects will be serialized into this context.
/// This allows the managed serialization context to do cleanup:
/// i. e. for C# we can release references to types which allows us to cleanup the old script assembly.
public void FinishSerialization()
{
ScriptEngine.Classes.EntitySerializer.FinishSerialization(this);
}
/// Serializes all script instances that are currently managed by the ScriptEngine.
public void SerializeScriptInstances()
{
@@ -47,6 +56,8 @@ public class ScriptInstanceSerializer
{
SerializeStaticScriptClassFields(scriptClass);
}
FinishSerialization();
}
/// Serializes the given script instance.
@@ -79,6 +90,8 @@ public class ScriptInstanceSerializer
{
DeserializeStaticScriptClassFields(scriptClass);
}
FinishSerialization();
}
/// Deserializes the data into the given script instance, if there is data available.