From abb119589c495b425a5ae74f94ae36884cf91bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Sun, 24 Dec 2023 01:33:48 +0100 Subject: [PATCH] Reload scripts in Play Mode!!! - Dont serialize static fields - Added some profiling to ScriptEngine - Removed superseded ScriptFieldMaps --- .../src/EditWindows/ComponentEditWindow.bf | 2 - GlitchyEditor/src/EditorLayer.bf | 4 - GlitchyEngine/src/Scripting/ScriptClass.bf | 2 - GlitchyEngine/src/Scripting/ScriptEngine.bf | 192 ++++-------------- GlitchyEngine/src/Scripting/ScriptGlue.bf | 2 + GlitchyEngine/src/World/SceneSerializer.bf | 18 +- ScriptCore/Serialization/EntitySerializer.cs | 7 +- 7 files changed, 58 insertions(+), 169 deletions(-) diff --git a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf index fcad0e4..68a8c39 100644 --- a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf +++ b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf @@ -560,8 +560,6 @@ namespace GlitchyEditor.EditWindows className == scriptComponent.ScriptClassName)) { scriptComponent.ScriptClassName = scriptClass.FullName; - - ScriptEngine.CreateScriptFieldMap(entity); } } ImGui.EndPopup(); diff --git a/GlitchyEditor/src/EditorLayer.bf b/GlitchyEditor/src/EditorLayer.bf index 04b12f8..e54fe51 100644 --- a/GlitchyEditor/src/EditorLayer.bf +++ b/GlitchyEditor/src/EditorLayer.bf @@ -1003,8 +1003,6 @@ namespace GlitchyEditor // Clear serialized data, so that we don't waste time deserializing it. ClearPrePlaySerializedData(); OnSceneStop(); - - ScriptEngine.ClearEntityScriptFields(); } /// Creates a new scene and openes it. @@ -1130,8 +1128,6 @@ namespace GlitchyEditor { CloseCurrentScene(); - ScriptEngine.ClearEntityScriptFields(); - SceneFilePath = scope String(filename); using (Scene newScene = new Scene()) diff --git a/GlitchyEngine/src/Scripting/ScriptClass.bf b/GlitchyEngine/src/Scripting/ScriptClass.bf index 996f380..c46efd6 100644 --- a/GlitchyEngine/src/Scripting/ScriptClass.bf +++ b/GlitchyEngine/src/Scripting/ScriptClass.bf @@ -79,8 +79,6 @@ public struct ScriptFieldInstance } } -public typealias ScriptFieldMap = Dictionary; - abstract class SharpType : RefCounter { protected String _namespace ~ delete _; diff --git a/GlitchyEngine/src/Scripting/ScriptEngine.bf b/GlitchyEngine/src/Scripting/ScriptEngine.bf index eb22be2..dcf8398 100644 --- a/GlitchyEngine/src/Scripting/ScriptEngine.bf +++ b/GlitchyEngine/src/Scripting/ScriptEngine.bf @@ -135,16 +135,6 @@ static class ScriptEngine get => s_Context; private set => SetReference!(s_Context, value); } - - private static Dictionary _entityFields = new .() ~ - { - for (var value in _entityFields.Values) - { - DeleteDictionaryAndKeys!(value); - } - - delete _; - }; private static FileSystemWatcher _userAssemblyWatcher ~ delete _; @@ -294,20 +284,11 @@ static class ScriptEngine _requestingReload = true; - // TODO: Temporary, we want to be able to reload while in play-mode. (+ Editor Scripts will be a thing some day) - if (_entityScriptInstances.Count > 0) - { - Log.EngineLogger.Warning("There are script instances. Skipping assembly reload."); - return; - } - Log.EngineLogger.Info("Script reload requested."); Application.Instance.InvokeOnMainThread(new () => { - Log.EngineLogger.Info("Reloading scripts..."); ReloadAssemblies(); - Log.EngineLogger.Info("Scripts reloaded!"); _requestingReload = false; _userAssemblyWatcher.StartRaisingEvents(); @@ -319,19 +300,10 @@ static class ScriptEngine _userAssemblyWatcher.StartRaisingEvents(); } - /// Gets rid of all entity fields. - public static void ClearEntityScriptFields() - { - for (var value in _entityFields.Values) - { - DeleteDictionaryAndKeys!(value); - } - - _entityFields.Clear(); - } - static void LoadScriptAssemblies() { + Debug.Profiler.ProfileFunction!(); + CreateAppDomain("GlitchyEngineScriptRuntime"); (s_CoreAssembly, s_CoreAssemblyImage) = LoadAssembly("resources/scripts/ScriptCore.dll", _debuggingEnabled); @@ -393,6 +365,8 @@ static class ScriptEngine Context = null; } + /// Instantiates the entities script components script and runs the constructor. + /// Disposes of and replaces the old instance, if one exists. public static bool InitializeInstance(Entity entity, ScriptComponent* script) { ScriptClass scriptClass = GetScriptClass(script.ScriptClassName); @@ -471,60 +445,6 @@ static class ScriptEngine return componentInstance; } - public static void CopyEditorFieldsToInstance(Entity entity, ScriptComponent* script) - { - Log.EngineLogger.AssertDebug(script.Instance != null); - - // Technically the map is for a different entity (namely the editor-entity), - // however the UUID is the same, so we get the correct field map - let fields = GetScriptFieldMap(entity); - - for (var (fieldName, field) in fields) - { - // TODO: a litte assertion maybe? - - ScriptField scriptField = script.Instance.ScriptClass.Fields[fieldName]; - - switch (scriptField.FieldType) - { - case .Entity: - // On the C# side we actually differentiate between an Entity and the Script - // in the sense that getting an entity and a script yields two different results (one creates a new Entity-Class instance, the other returns the actual instance). - // But here its just easier to always use the script instance. - // Obviously breaks once we support multiple scripts per entity. - UUID referencedId = field.GetData(); - MonoObject* referencedEntity = GetOrCreateScriptReferenceInstance(referencedId); - - script.Instance.SetFieldValue(scriptField, referencedEntity); - case .Component: - // Get or create entity reference - UUID referencedId = field.GetData(); - //MonoObject* referencedEntity = GetOrCreateScriptReferenceInstance(referencedId); - - MonoType* fieldMonoType = scriptField.GetMonoType(); - - SharpType componentType = ScriptEngine.GetSharpType(fieldMonoType); - - var componentClass = ComponentClasses[componentType.FullName]; - - //MonoObject* componentInstance = CreateComponentReferenceInstance(componentClass, referencedEntity); - MonoObject* componentInstance = CreateComponentReferenceInstance(componentClass, referencedId); - - script.Instance.SetFieldValue(scriptField, componentInstance); - - componentType.ReleaseRef(); - case .Struct: - // TODO: Handle Structs when copying fields to instance. - case .Class: - // TODO: Handle Class when copying fields to instance. - case .String: - // TODO: Handle String when copying fields to instance. - default: - script.Instance.SetFieldValue(scriptField, field._data); - } - } - } - public static void CopyFieldsToInstance(ScriptComponent* targetScript, ScriptComponent* sourceScript, Dictionary sourceIdToTargetId) { Debug.Profiler.ProfileFunction!(); @@ -661,6 +581,8 @@ static class ScriptEngine static (MonoAssembly* assembly, MonoImage* image) LoadAssembly(StringView filepath, bool loadPDB = false) { + Debug.Profiler.ProfileFunction!(); + MonoAssembly* assembly = LoadCSharpAssembly(filepath, loadPDB); MonoImage* image = Mono.mono_assembly_get_image(assembly); @@ -680,6 +602,8 @@ static class ScriptEngine private static void GetEntitiesFromAssemblies() { + Debug.Profiler.ProfileFunction!(); + if (s_AppAssemblyImage == null) return; @@ -706,8 +630,11 @@ static class ScriptEngine } } } + private static void GetComponentsFromAssemblies() { + Debug.Profiler.ProfileFunction!(); + MonoTableInfo* typeDefinitionsTable = Mono.mono_image_get_table_info(s_CoreAssemblyImage, .MONO_TABLE_TYPEDEF); int32 numTypes = Mono.mono_table_info_get_rows(typeDefinitionsTable); @@ -736,18 +663,41 @@ static class ScriptEngine { Debug.Profiler.ProfileFunction!(); - Mono.mono_domain_set(s_RootDomain, false); + Log.EngineLogger.Info("Reloading script assemblies."); + + Dictionary serializedData = scope .(); + + SerializeScriptInstances(serializedData); + + Mono.mono_domain_set(s_RootDomain, true); Mono.mono_domain_unload(s_AppDomain); LoadScriptAssemblies(); - // TODO: ScriptFields might get added - // TODO: ScriptField Types may change after reload! - // TODO: Scripts may be renamed (probably not detectable (trivially)) + { + Debug.Profiler.ProfileScope!("Initialize Instances"); + + // We need to create a new instance for every entity + for (let (id, scriptInstance) in _entityScriptInstances) + { + if (Context.GetEntityByID(id) case .Ok(let entity)) + { + ScriptComponent* script = entity.GetComponent(); + InitializeInstance(entity, script); + } + else + { + Log.EngineLogger.AssertDebug(false, "Entities script was just serialized but the entity doesn't exist anymore."); + } + } + } - // TODO: Reload in play mode - // Only scripts that were changed should actually be reinstatiated + DeserializeScriptInstances(serializedData); + + ClearDictionaryAndDeleteValues!(serializedData); + + Log.EngineLogger.Info("Script assemblies reloaded!"); } public static void Shutdown() @@ -838,58 +788,6 @@ static class ScriptEngine return new SharpClass(classNamespace, className, Mono.mono_class_get_image(monoClass), scriptType); } - public static void CreateScriptFieldMap(Entity entity) - { - Log.EngineLogger.AssertDebug(entity.IsValid); - - if (_entityFields.TryGetValue(entity.UUID, var entityFields)) - { - ClearDictionaryAndDeleteKeys!(entityFields); - } - else - { - entityFields = new ScriptFieldMap(); - _entityFields.Add(entity.UUID, entityFields); - } - - let scriptComponent = entity.GetComponent(); - - ScriptClass scriptClass = GetScriptClass(scriptComponent.ScriptClassName); - - Log.EngineLogger.AssertDebug(scriptClass != null); - - void AddFieldsToMap(Dictionary classFields, StringView baseName) - { - for (let (fieldName, field) in classFields) - { - /*if (field.FieldType == .Struct && field.SharpType != null) - { - AddFieldsToMap(field.SharpType.Fields, scope $"{baseName}{fieldName}."); - } - else - { - entityFields.Add(new $"{baseName}{fieldName}", ScriptFieldInstance(field.FieldType)); - }*/ - - entityFields.Add(new String(fieldName), ScriptFieldInstance(field.FieldType)); - } - } - - AddFieldsToMap(scriptClass.Fields, ""); - } - - public static ScriptFieldMap GetScriptFieldMap(Entity entity) - { - Log.EngineLogger.AssertDebug(entity.IsValid); - - let uuid = entity.UUID; - - // TODO: Entites bekommen noch kein Eintrag hier!! - Log.EngineLogger.AssertDebug(_entityFields.ContainsKey(uuid)); - - return _entityFields[uuid]; - } - /// Returns the script instance or null. public static MonoObject* GetManagedInstance(UUID entityId) { @@ -955,11 +853,10 @@ static class ScriptEngine /// Serializes all script instances public static void SerializeScriptInstances(Dictionary allObjects) { + Debug.Profiler.ProfileFunction!(); + for (let (id, script) in _entityScriptInstances) { - if (script.ScriptClass.ClassName != "SerializationTest") - continue; - SerializedObject object = new SerializedObject(allObjects, script.EntityId); object.Serialize(script); } @@ -968,11 +865,10 @@ static class ScriptEngine /// Deserializes the given data into the script instances public static void DeserializeScriptInstances(Dictionary allObjects) { + Debug.Profiler.ProfileFunction!(); + for (let (id, script) in _entityScriptInstances) { - if (script.ScriptClass.ClassName != "SerializationTest") - continue; - if (allObjects.TryGetValue(id, let object)) { object.Deserialize(script); diff --git a/GlitchyEngine/src/Scripting/ScriptGlue.bf b/GlitchyEngine/src/Scripting/ScriptGlue.bf index 0826ae0..a4ff512 100644 --- a/GlitchyEngine/src/Scripting/ScriptGlue.bf +++ b/GlitchyEngine/src/Scripting/ScriptGlue.bf @@ -60,6 +60,8 @@ static class ScriptGlue public static void RegisterManagedComponents() { + Debug.Profiler.ProfileFunction!(); + s_AddComponentMethods.Clear(); s_HasComponentMethods.Clear(); s_RemoveComponentMethods.Clear(); diff --git a/GlitchyEngine/src/World/SceneSerializer.bf b/GlitchyEngine/src/World/SceneSerializer.bf index 75c730c..35b4723 100644 --- a/GlitchyEngine/src/World/SceneSerializer.bf +++ b/GlitchyEngine/src/World/SceneSerializer.bf @@ -209,7 +209,8 @@ class SceneSerializer // TODO: Thats not a good check, I think. At least we know the script class is valid if (ScriptEngine.GetScriptClass(component.ScriptClassName) != null) { - let fields = ScriptEngine.GetScriptFieldMap(entity); + // TODO: Serialize Script Instance! + /*let fields = ScriptEngine.GetScriptFieldMap(entity); writer.Identifier("Fields"); @@ -234,7 +235,7 @@ class SceneSerializer Serialize.Value(writer, ValueView(fieldInstance.Type.GetBeefType(), &fieldInstance.[Friend]_data), gBonEnv); } } - } + }*/ } }); } @@ -624,7 +625,8 @@ class SceneSerializer // But that is a bug for me to rediscover in the distant future, so in case this bug occurred and it took ages for you to // figure out what happened: You are welcome :) - ScriptEngine.CreateScriptFieldMap(entity); + // TODO: We need a new way to Serialize/Deserialize these fields! + /*ScriptEngine.CreateScriptFieldMap(entity); var fields = ScriptEngine.GetScriptFieldMap(entity); @@ -708,19 +710,11 @@ class SceneSerializer } Try!(reader.ArrayBlockEnd()); - } + }*/ } return .Ok; })); - /* - - - SerializeComponent(writer, entity, "ScriptComponent", scope (component) => - { - Serialize.Value(writer, "ScriptClass", component.ScriptClass?.FullName); - }); - */ default: Log.EngineLogger.AssertDebug(false, "Unknown component type"); //return .Err; diff --git a/ScriptCore/Serialization/EntitySerializer.cs b/ScriptCore/Serialization/EntitySerializer.cs index 51e2ac3..527dd06 100644 --- a/ScriptCore/Serialization/EntitySerializer.cs +++ b/ScriptCore/Serialization/EntitySerializer.cs @@ -453,7 +453,8 @@ public static class EntitySerializer } else if (fieldType.IsClass) { - // SerializeClass(fieldName, fieldValue, fieldType); + //DeserializeClass(fieldName, fieldValue, fieldType); + Log.Error("Class is not yet implemented."); } else { @@ -565,6 +566,10 @@ public static class EntitySerializer public static bool SerializeField(FieldInfo fieldInfo) { + // TODO: We want to be able to serialize static fields in the future! + if (fieldInfo.IsStatic) + return false; + var serializeField = fieldInfo.HasCustomAttribute(); var dontSerializeField = fieldInfo.HasCustomAttribute(); //var hideField = fieldInfo.HasCustomAttribute();