using Mono; using System; using System.IO; using System.Collections; using GlitchyEngine.Core; using GlitchyEngine.Math; using GlitchyEngine.World; using System.Diagnostics; namespace GlitchyEngine.Scripting; using internal GlitchyEngine.Scripting; static sealed class ScriptEngineHelper { private static Dictionary _scriptFieldTypes = new Dictionary() { ("System.String", .String), ("System.Boolean", .Bool), ("System.Char", .Char), ("System.SByte", .SByte), ("System.Int16", .Short), ("System.Int32", .Int), ("System.Int64", .Long), ("System.Byte", .Byte), ("System.UInt16", .UShort), ("System.UInt32", .UInt), ("System.UInt64", .ULong), ("System.Single", .Float), ("GlitchyEngine.Math.float2", .float2), ("GlitchyEngine.Math.float3", .float3), ("GlitchyEngine.Math.float4", .float4), ("System.Double", .Double), ("GlitchyEngine.Entity", .Entity), } ~ delete _; public static ScriptFieldType GetScriptFieldType(MonoType* monoType) { StringView typeName = StringView(Mono.mono_type_get_name(monoType)); return _scriptFieldTypes.TryGetValue(typeName, .. let scriptFieldType); } } class EngineClasses { private ScriptClass s_ComponentRoot ~ _?.ReleaseRef(); private ScriptClass s_EntityRoot ~ _?.ReleaseRef(); private ScriptClass s_EngineObject ~ _?.ReleaseRef(); private ScriptClass s_EntityEditor ~ _?.ReleaseRef(); private ScriptClass s_EntitySerializer ~ _?.ReleaseRef(); private ScriptClass s_SerializationContext ~ _?.ReleaseRef(); public ScriptClass ComponentRoot => s_ComponentRoot; public ScriptClass EntityRoot => s_EntityRoot; public ScriptClass EngineObject => s_EngineObject; public ScriptClass EntityEditor => s_EntityEditor; public ScriptClass EntitySerializer => s_EntitySerializer; //public ScriptClass SerializationContext => s_SerializationContext; internal void ReleaseAndNullify() { ReleaseRefAndNullify!(s_EngineObject); ReleaseRefAndNullify!(s_EntityRoot); ReleaseRefAndNullify!(s_ComponentRoot); ReleaseRefAndNullify!(s_EntityEditor); ReleaseRefAndNullify!(s_EntitySerializer); ReleaseRefAndNullify!(s_SerializationContext); } internal void LoadClasses(MonoImage* image) { ReleaseAndNullify(); s_EngineObject = new ScriptClass("GlitchyEngine.Core", "EngineObject", image, .Class); s_EntityRoot = new ScriptClass("GlitchyEngine", "Entity", image, .Entity); s_ComponentRoot = new ScriptClass("GlitchyEngine.Core", "Component", image, .Component); // Editor classes s_EntityEditor = new ScriptClass("GlitchyEngine.Editor", "EntityEditor", image, .Class); s_EntitySerializer = new ScriptClass("GlitchyEngine.Serialization", "EntitySerializer", image, .Class); //s_SerializationContext = new ScriptClass("GlitchyEngine.Serialization", "EntitySerializer.SerializationContext", image, .Class); } } static class ScriptEngine { private static MonoDomain* s_RootDomain; private static MonoDomain* s_AppDomain; private static MonoAssembly* s_CoreAssembly; private static MonoImage* s_CoreAssemblyImage; private static MonoAssembly* s_AppAssembly; private static MonoImage* s_AppAssemblyImage; private static Scene s_Context ~ _?.ReleaseRef(); private static append EngineClasses _classes = .(); public static EngineClasses Classes => _classes; private static Dictionary _sharpClasses = new .() ~ DeleteDictionaryAndReleaseValues!(_); private static Dictionary _entityScripts = new .() ~ DeleteDictionaryAndReleaseValues!(_); private static Dictionary _componentClasses = new .() ~ DeleteDictionaryAndReleaseValues!(_); private static Dictionary _entityScriptInstances = new .() ~ { for (var entry in _) { entry.value?.ReleaseRef(); } delete _; } public static Dictionary EntityClasses => _entityScripts; public static Dictionary ComponentClasses => _componentClasses; /// Gets or sets the current scene context for the runtime. public static Scene Context { get => s_Context; private set => SetReference!(s_Context, value); } private static FileSystemWatcher _userAssemblyWatcher ~ delete _; // TODO: This should be a global setting somewhere private static bool _debuggingEnabled = true; private static String _appAssemblyPath = new .() ~ delete _; internal static class Attributes { internal static MonoClass* s_ShowInEditorAttribute; internal static MonoClass* s_RunInEditModeAttribute; } public class ApplicationData { private bool _isEditor; private bool _isPlayer; private bool _isInEditMode; private bool _isInPlayMode; public bool IsEditor { get => _isEditor; set { _isEditor = value; if (_isEditor) _isPlayer = false; } } public bool IsPlayer { get => _isPlayer; set { _isPlayer = value; if (_isPlayer) _isEditor = false; } } public bool IsInEditMode { get => _isInEditMode; set { _isInEditMode = value; if (_isInEditMode) _isInPlayMode = false; } } public bool IsInPlayMode { get => _isInPlayMode; set { _isInPlayMode = value; if (_isInPlayMode) _isInEditMode = false; } } } private static ApplicationData _applicationData = new ApplicationData() ~ delete _; /// Contains information about the application context public static ApplicationData ApplicationInfo => _applicationData; public static void Init() { InitMono(); ScriptGlue.Init(); LoadScriptAssemblies(); } /// Changes the path to the current app assembly. public static void SetAppAssemblyPath(StringView appAssemblyPath) { _appAssemblyPath.Set(appAssemblyPath); ReloadAssemblies(); } 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() { // We don't need a file system watcher, if we have nothing to watch... if (!File.Exists(_appAssemblyPath)) return; String directory = scope .(); Path.GetDirectoryPath(_appAssemblyPath, directory); /*if (_userAssemblyWatcher != null) // _userAssemblyWatcher.Directory != directory { delete _userAssemblyWatcher; }*/ String fileName = scope .("*/"); Path.GetFileName(_appAssemblyPath, fileName); if (_userAssemblyWatcher == null) { _userAssemblyWatcher = new FileSystemWatcher(directory, fileName); _userAssemblyWatcher.OnChanged.Add(new (fileName) => { _userAssemblyWatcher.StopRaisingEvents(); if (_requestingReload) return; _requestingReload = true; Log.EngineLogger.Info("Script reload requested."); Application.Instance.InvokeOnMainThread(new () => { ReloadAssemblies(); _requestingReload = false; _userAssemblyWatcher.StartRaisingEvents(); }); }); } _userAssemblyWatcher.StartRaisingEvents(); } static void LoadScriptAssemblies() { Debug.Profiler.ProfileFunction!(); CreateAppDomain("GlitchyEngineScriptRuntime"); (s_CoreAssembly, s_CoreAssemblyImage) = LoadAssembly("resources/scripts/ScriptCore.dll", _debuggingEnabled); if (File.Exists(_appAssemblyPath)) (s_AppAssembly, s_AppAssemblyImage) = LoadAssembly(_appAssemblyPath, _debuggingEnabled); else { s_AppAssembly = null; s_AppAssemblyImage = null; } GetCoreAttributes(); Classes.LoadClasses(s_CoreAssemblyImage); ClearDictionaryAndReleaseValues!(_sharpClasses); ClearDictionaryAndReleaseValues!(_componentClasses); ClearDictionaryAndReleaseValues!(_entityScripts); GetComponentsFromAssemblies(); GetEntitiesFromAssemblies(); ScriptGlue.RegisterManagedComponents(); InitAssemblyWatcher(); } /// Starts the script runtime and sets the context scene. public static void StartRuntime(Scene context) { /// At the moment only set the context. Debug.Assert(s_Context == null, "StartRuntime was called twice without StopRuntime in between!"); Context = context; } /// Stopts the script runtime and disposes of all script instances. public static void StopRuntime() { if (Context == null) { // There should be nothing to do, if we have no context Debug.Assert(_entityScriptInstances.Count == 0, "There are script instances but no context scene."); return; } for (let (id, instance) in _entityScriptInstances) { instance?.ReleaseRef(); Entity entity = Context.GetEntityByID(id); // Remove Instance from script ScriptComponent* script = entity.GetComponent(); script.Instance = null; } _entityScriptInstances.Clear(); 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); if (scriptClass == null) return false; UUID entityId = entity.UUID; script.Instance = new ScriptInstance(entityId, scriptClass); script.Instance..ReleaseRef(); if (_entityScriptInstances.TryGetValue(entityId, let currentInstance)) currentInstance.ReleaseRef(); _entityScriptInstances[entityId] = script.Instance..AddRef(); script.Instance.Instantiate(entityId); return true; } public static void DestroyInstance(Entity entity, ScriptComponent* script) { UUID entityId = entity.UUID; if (script.Instance != null) script.Instance = null; script.ScriptClassName = null; if (_entityScriptInstances.TryGetValue(entityId, let currentInstance)) currentInstance.ReleaseRef(); _entityScriptInstances[entityId] = null; } /// Returns an instance that can be used as a reference to the entity with the given ID in Scripts private static MonoObject* GetOrCreateScriptReferenceInstance(UUID entityId) { MonoObject* referencedEntity = GetManagedInstance(entityId); if (referencedEntity == null) { referencedEntity = Classes.EntityRoot.CreateInstance(entityId, let exception); if (exception != null) ScriptEngine.HandleMonoException(exception, null); } return referencedEntity; } /// Creates an instance of the given component class referencing the specified entity instance. private static MonoObject* CreateComponentReferenceInstance(ScriptClass componentClass, UUID id)//MonoObject* entityReferenceInstance) { MonoObject* componentInstance = componentClass.CreateInstance(); MonoClassField* idField = Mono.mono_class_get_field_from_name(componentClass.[Friend]_monoClass, "_uuid"); Classes.ComponentRoot.SetFieldValue(componentInstance, idField, id); /*// TODO: We could cache the property, but this might be fine MonoProperty* entityProperty = Mono.mono_class_get_property_from_name(componentClass.[Friend]_monoClass, "Entity"); MonoObject* exception = null; //#unwarn //Mono.mono_property_set_value(entityProperty, componentInstance, (void**)&entityReferenceInstance, &exception); #unwarn Mono.mono_property_set_value(entityProperty, componentInstance, (void**)&id, &exception); if (exception != null) ScriptEngine.HandleMonoException((MonoException*)exception, null);*/ return componentInstance; } public static void CopyFieldsToInstance(ScriptComponent* targetScript, ScriptComponent* sourceScript, Dictionary sourceIdToTargetId) { Debug.Profiler.ProfileFunction!(); Log.EngineLogger.AssertDebug(targetScript.Instance.ScriptClass == sourceScript.Instance.ScriptClass); for (let (name, scriptField) in sourceScript.Instance.ScriptClass.Fields) { Debug.Profiler.ProfileScope!("Copy Field"); targetScript.Instance.CopyFieldValue(scriptField, sourceScript.Instance); switch (scriptField.FieldType) { case .Entity: let sourceEntityReference = sourceScript.Instance.GetFieldValue(scriptField); MonoObject* referencedEntity = sourceEntityReference; if (sourceEntityReference != null) { let idField = Mono.mono_class_get_field_from_name(Classes.EntityRoot._monoClass, "_uuid"); UUID sourceId = Classes.EntityRoot.GetFieldValue(sourceEntityReference, idField); // Check if we need to translate, copy otherwise if (sourceIdToTargetId.TryGetValue(sourceId, let referencedId)) { // 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. referencedEntity = GetOrCreateScriptReferenceInstance(referencedId); } } targetScript.Instance.SetFieldValue(scriptField, referencedEntity); case .Component: let sourceComponentReference = sourceScript.Instance.GetFieldValue(scriptField); MonoObject* componentInstance = sourceComponentReference; // Get or create entity reference if (sourceComponentReference != null) { let idField = Mono.mono_class_get_field_from_name(Classes.EngineObject._monoClass, "_uuid"); UUID sourceId = Classes.EntityRoot.GetFieldValue(sourceComponentReference, idField); MonoType* fieldMonoType = scriptField.GetMonoType(); SharpType componentType = ScriptEngine.GetSharpType(fieldMonoType); var componentClass = ComponentClasses[componentType.FullName]; if (sourceIdToTargetId.TryGetValue(sourceId, let targetId)) { // Create reference for translated id componentInstance = CreateComponentReferenceInstance(componentClass, targetId); } componentType.ReleaseRef(); } targetScript.Instance.SetFieldValue(scriptField, componentInstance); default: targetScript.Instance.CopyFieldValue(scriptField, sourceScript.Instance); } } } private static MonoAssembly* LoadCSharpAssembly(StringView assemblyPath, bool loadPDB = false) { Debug.Profiler.ProfileFunction!(); List data = new List(1024); File.ReadAll(assemblyPath, data); // NOTE: We can't use this image for anything other than loading the assembly because this image doesn't have a reference to the assembly MonoImageOpenStatus status = .ImageInvalid; MonoImage* image = Mono.mono_image_open_from_data_full(data.Ptr, (.)data.Count, true, &status, false); delete data; if (status != .Ok) { char8* errorMessage = Mono.mono_image_strerror(status); Log.EngineLogger.Error($"Failed to load C# Assembly: \"{StringView(errorMessage)}\""); return null; } if (loadPDB) { String pdbPath = scope .(); Path.ChangeExtension(assemblyPath, ".pdb", pdbPath); if (File.Exists(pdbPath)) { Log.EngineLogger.Trace($"Loading PDB \"{pdbPath}\"..."); List pdbData = new List(1024); let result = File.ReadAll(pdbPath, pdbData); if (result case .Err(let error)) { Log.EngineLogger.Error("Failed to load PDB file ({error})."); } Mono.mono_debug_open_image_from_memory(image, pdbData.Ptr, (int32)pdbData.Count); delete pdbData; } else { Log.EngineLogger.Warning($"Debugging enabled but no PDB-File found \"{assemblyPath}\"."); } } MonoAssembly* assembly = Mono.mono_assembly_load_from_full(image, assemblyPath.ToScopeCStr!(), &status, 0); Mono.mono_image_close(image); return assembly; } static void CreateAppDomain(StringView name) { s_AppDomain = Mono.mono_domain_create_appdomain(name.ToScopeCStr!(), null); Mono.mono_domain_set(s_AppDomain, true); } 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); return (assembly, image); } /// Retrieves the classes for Attributes that are defined in the Core library static void GetCoreAttributes() { // We definitely need these attributes! // TODO: Some only for the editor! Attributes.s_ShowInEditorAttribute = Mono.mono_class_from_name(s_CoreAssemblyImage, "GlitchyEngine.Editor", "ShowInEditorAttribute"); Debug.Assert(Attributes.s_ShowInEditorAttribute != null); Attributes.s_RunInEditModeAttribute = Mono.mono_class_from_name(s_CoreAssemblyImage, "GlitchyEngine.Editor", "RunInEditModeAttribute"); Debug.Assert(Attributes.s_RunInEditModeAttribute != null); } private static void GetEntitiesFromAssemblies() { Debug.Profiler.ProfileFunction!(); if (s_AppAssemblyImage == null) return; MonoTableInfo* typeDefinitionsTable = Mono.mono_image_get_table_info(s_AppAssemblyImage, .MONO_TABLE_TYPEDEF); int32 numTypes = Mono.mono_table_info_get_rows(typeDefinitionsTable); for (int32 i = 0; i < numTypes; i++) { int32[(.)SOME_RANDOM_ENUM.MONO_TYPEDEF_SIZE] cols = .(); Mono.mono_metadata_decode_row(typeDefinitionsTable, i, (.)&cols, (.)SOME_RANDOM_ENUM.MONO_TYPEDEF_SIZE); char8* nameSpace = Mono.mono_metadata_string_heap(s_AppAssemblyImage, (.)cols[(.)SOME_RANDOM_ENUM.MONO_TYPEDEF_NAMESPACE]); char8* name = Mono.mono_metadata_string_heap(s_AppAssemblyImage, (.)cols[(.)SOME_RANDOM_ENUM.MONO_TYPEDEF_NAME]); MonoClass* monoClass = Mono.mono_class_from_name(s_AppAssemblyImage, nameSpace, name); // Check if it is an entity if (monoClass != null && Mono.mono_class_is_subclass_of(monoClass, Classes.EntityRoot.[Friend]_monoClass, false)) { ScriptClass entityScript = new ScriptClass(StringView(nameSpace), StringView(name), s_AppAssemblyImage, .Entity); _entityScripts.Add(entityScript.FullName, entityScript); Log.EngineLogger.Info($"Added entity \"{entityScript.FullName}\""); } } } 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); for (int32 i = 0; i < numTypes; i++) { int32[(.)SOME_RANDOM_ENUM.MONO_TYPEDEF_SIZE] cols = .(); Mono.mono_metadata_decode_row(typeDefinitionsTable, i, (.)&cols, (.)SOME_RANDOM_ENUM.MONO_TYPEDEF_SIZE); char8* nameSpace = Mono.mono_metadata_string_heap(s_CoreAssemblyImage, (.)cols[(.)SOME_RANDOM_ENUM.MONO_TYPEDEF_NAMESPACE]); char8* name = Mono.mono_metadata_string_heap(s_CoreAssemblyImage, (.)cols[(.)SOME_RANDOM_ENUM.MONO_TYPEDEF_NAME]); MonoClass* monoClass = Mono.mono_class_from_name(s_CoreAssemblyImage, nameSpace, name); // Check if it is a component but not the root Component class if (monoClass != null && monoClass != Classes.ComponentRoot.[Friend]_monoClass && Mono.mono_class_is_subclass_of(monoClass, Classes.ComponentRoot.[Friend]_monoClass, false)) { ScriptClass componentClass = new ScriptClass(StringView(nameSpace), StringView(name), s_CoreAssemblyImage, .Component); _componentClasses.Add(componentClass.FullName, componentClass); Log.EngineLogger.Info($"Added component \"{componentClass.FullName}\""); } } } public static void ReloadAssemblies() { Debug.Profiler.ProfileFunction!(); 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(); { 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."); } } } DeserializeScriptInstances(serializedData); ClearDictionaryAndDeleteValues!(serializedData); Log.EngineLogger.Info("Script assemblies reloaded!"); } 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; } internal static void RegisterSharpType(SharpType sharpType) { _sharpClasses.Add(sharpType.FullName, sharpType..AddRef()); } internal static SharpType GetSharpType(MonoType* monoType) { StringView typeName = StringView(Mono.mono_type_get_name(monoType)); if (_sharpClasses.TryGetValue(typeName, let sharpType)) return sharpType..AddRef(); Mono.MonoTypeEnum fieldType = Mono.Mono.mono_type_get_type(monoType); MonoClass* monoClass = Mono.mono_class_from_mono_type(monoType); if (monoClass == null) return null; StringView className = StringView(Mono.mono_class_get_name(monoClass)); StringView classNamespace = StringView(Mono.mono_class_get_namespace(monoClass)); // TODO: at the moment only allow user-structs //if (classNamespace.StartsWith("GlitchyEngine")) // return null; ScriptFieldType scriptType = .None; if (Mono.mono_class_is_enum(monoClass)) { scriptType = .Enum; return new SharpEnum(classNamespace, className, Mono.mono_class_get_image(monoClass)); } else if (fieldType == .Class) { if (Mono.mono_class_is_subclass_of(monoClass, Classes.EntityRoot._monoClass, false)) { scriptType = .Entity; } else if (Mono.mono_class_is_subclass_of(monoClass, Classes.ComponentRoot._monoClass, false)) { scriptType = .Component; } else { scriptType = .Class; } } else if (fieldType == .Valuetype) { scriptType = .Struct; } // TODO! ?! else if (fieldType == .Genericinst) { scriptType = .GenericClass; return null; //return new SharpClass(monoClass, scriptType); } else if (fieldType == .SzArray) { scriptType = .Array; return null; } else if (fieldType == .Object) { // TODO: Help return null; } Log.EngineLogger.AssertDebug(scriptType != .None); return new SharpClass(classNamespace, className, Mono.mono_class_get_image(monoClass), scriptType); } /// Returns the script instance or null. public static MonoObject* GetManagedInstance(UUID entityId) { if (_entityScriptInstances.TryGetValue(entityId, let scriptInstance)) return scriptInstance.MonoInstance; return null; } public static ScriptClass GetScriptClass(StringView name) { EntityClasses.TryGetValue(name, let scriptClass); return scriptClass; } internal static void HandleMonoException(MonoException* exception, ScriptInstance sourceInstance = null) { MonoExceptionHelper wrappedException = new MonoExceptionHelper(exception); String entityInfo = scope .(); if (sourceInstance != null) { wrappedException.Instance = sourceInstance.EntityId; Result sourceEntity = Context.GetEntityByID(sourceInstance.EntityId); if (sourceEntity case .Ok(let e)) { entityInfo.AppendF($" ({e.Name} | {sourceInstance.EntityId})"); } } Log.ClientLogger.Error($"Mono Exception \"{wrappedException.FullName}\": \"{wrappedException.Message}\"{entityInfo}\nStackTrace:\n{wrappedException.StackTrace}", wrappedException); wrappedException.ReleaseRef(); } public static void ShowScriptEditor(Entity entity, ScriptComponent* scriptComponent) { if (scriptComponent.Instance == null) return; ScriptClass scriptClass = ScriptEngine.GetScriptClass(scriptComponent.ScriptClassName); if (scriptClass == null) return; let monoType = scriptClass.GetMonoType(); let monoReflectionType = Mono.mono_type_get_object(s_AppDomain, monoType); let entityId = entity.UUID; #unwarn void*[2] args = .(&entityId, monoReflectionType); let method = Classes.EntityEditor.GetMethod("ShowDefaultEntityEditor", 2); Classes.EntityEditor.Invoke(method, null, &args); } /// Serializes all script instances public static void SerializeScriptInstances(Dictionary allObjects) { Debug.Profiler.ProfileFunction!(); for (let (id, script) in _entityScriptInstances) { SerializedObject object = new SerializedObject(allObjects, script.ScriptClass.FullName, script.EntityId); object.Serialize(script); } } /// Deserializes the given data into the script instances public static void DeserializeScriptInstances(Dictionary allObjects) { Debug.Profiler.ProfileFunction!(); for (let (id, script) in _entityScriptInstances) { if (allObjects.TryGetValue(id, let object)) { object.Deserialize(script); } } } }