diff --git a/GlitchyEngine/src/Scripting/ScriptEngine.bf b/GlitchyEngine/src/Scripting/ScriptEngine.bf index c8d84b1..87f78b4 100644 --- a/GlitchyEngine/src/Scripting/ScriptEngine.bf +++ b/GlitchyEngine/src/Scripting/ScriptEngine.bf @@ -859,11 +859,17 @@ static class ScriptEngine for (let (id, script) in _entityScriptInstances) { - SerializedObject object = new SerializedObject(allObjects, script.ScriptClass.FullName, script.EntityId); - object.Serialize(script); + SerializeScriptInstance(script, allObjects); } } + /// Serializes the given script instance + public static void SerializeScriptInstance(ScriptInstance script, Dictionary allObjects) + { + 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) { @@ -871,9 +877,36 @@ static class ScriptEngine for (let (id, script) in _entityScriptInstances) { - if (allObjects.TryGetValue(id, let object)) + DeserializeScriptInstance(id, script, allObjects); + } + } + + /// Deserializes the given script instance. + /// @returns true if the script had serialized data; false otherwise. + public static bool DeserializeScriptInstance(UUID id, ScriptInstance script, Dictionary allObjects) + { + if (allObjects.TryGetValue(id, let object)) + { + object.Deserialize(script); + + return true; + } + + return false; + } + + /// Replaces Entity references in the given serialized data using the specified translation table. + public static void FixupSerializedIds(Dictionary originalToCopyIds, Dictionary allObjects) + { + for (let (id, object) in allObjects) + { + for (var fieldData in ref object.Fields.Values) { - object.Deserialize(script); + if (fieldData.PrimitiveType != .EntityReference && fieldData.PrimitiveType != .ComponentReference) + continue; + + if (originalToCopyIds.TryGetValue(fieldData.Data.EngineObject.ID, let copyId)) + fieldData.Data.EngineObject.ID = copyId; } } } diff --git a/GlitchyEngine/src/World/Scene.bf b/GlitchyEngine/src/World/Scene.bf index 1908377..ad8e643 100644 --- a/GlitchyEngine/src/World/Scene.bf +++ b/GlitchyEngine/src/World/Scene.bf @@ -8,6 +8,7 @@ using GlitchyEngine.Content; using GlitchyEngine.Scripting; using GlitchyEngine.Math; using GlitchyEngine.Scripting.Classes; +using GlitchyEngine.Serialization; namespace GlitchyEngine.World { @@ -773,10 +774,14 @@ namespace GlitchyEngine.World */ public Entity CreateInstance(Entity entity) { + Dictionary serializedData = scope .(); + defer { ClearDictionaryAndDeleteValues!(serializedData); } + List newEntities = scope .(); Dictionary sourceToTargetEntity = scope .(); Dictionary sourceIdToTargetId = scope .(); Dictionary targetIdToSourceEntity = scope .(); + List<(UUID oldId, ScriptInstance scriptInstance)> newScripts = scope .(); Entity CopyEntityAndChildren(Entity original, Entity? copyParent) { @@ -801,7 +806,7 @@ namespace GlitchyEngine.World CopyComponent(original, copy); CopyComponent(original, copy); CopyComponent(original, copy); - + // Copy ScriptComponent... needs extra handling for the script instances if (original.TryGetComponent(let sourceScript)) { @@ -809,15 +814,16 @@ namespace GlitchyEngine.World targetScript.ScriptClassName = sourceScript.ScriptClassName; - // Initializes the created instance - // TODO: this returns false, if no script with ScriptClassName exists, we have to handle this case correctly I think. - ScriptEngine.InitializeInstance(copy, targetScript); - - // TODO: Copy Data from one instance to another - //ScriptEngine.CopyFieldsToInstance(targetScript, sourceScript); - - // TODO: Only if we are in Runtime - targetScript.Instance.InvokeOnCreate(); + if (sourceScript.Instance != null) + { + ScriptEngine.SerializeScriptInstance(sourceScript.Instance, serializedData); + + // Initializes the created instance + // TODO: this returns false, if no script with ScriptClassName exists, we have to handle this case correctly I think. + ScriptEngine.InitializeInstance(copy, targetScript); + + newScripts.Add((original.UUID, targetScript.Instance)); + } } // This is kinda slow because it's in O(n*m) where n is the tree depth and m is the total number of entities in the scene... @@ -830,22 +836,22 @@ namespace GlitchyEngine.World } Entity newEntity = CopyEntityAndChildren(entity, null); - - for (let copy in newEntities) + + // Replace old IDs with new ones + ScriptEngine.FixupSerializedIds(sourceIdToTargetId, serializedData); + + // Use separate loops for deserialization and OnCreate to ensure complete entities and references in OnCreate + + for (let (originalId, newScriptInstance) in newScripts) { - if (copy.TryGetComponent(let targetScript)) - { - Entity originalEntity = targetIdToSourceEntity[copy.UUID]; - - if (!originalEntity.TryGetComponent(let sourceScript)) - continue; - - ScriptEngine.CopyFieldsToInstance(targetScript, sourceScript, sourceIdToTargetId); - - //targetScript.Instance.InvokeOnCreate(); - } + ScriptEngine.DeserializeScriptInstance(originalId, newScriptInstance, serializedData); } + for (let (_, newScriptInstance) in newScripts) + { + if (ScriptEngine.ApplicationInfo.IsInPlayMode || newScriptInstance.ScriptClass.RunInEditMode) + newScriptInstance.InvokeOnCreate(); + } return newEntity; } diff --git a/ScriptCore/Serialization/DeserializationObject.cs b/ScriptCore/Serialization/DeserializationObject.cs index 852c935..019526b 100644 --- a/ScriptCore/Serialization/DeserializationObject.cs +++ b/ScriptCore/Serialization/DeserializationObject.cs @@ -504,7 +504,7 @@ public class DeserializationObject public unsafe object? DeserializeClass(string fieldName, Type fieldType) { - if (fieldType.GetGenericTypeDefinition() == typeof(List<>)) + if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(List<>)) { return DeserializeList(fieldName, fieldType, fieldType.GetGenericArguments()[0]); } @@ -514,7 +514,10 @@ public class DeserializationObject if (isEntity || isComponent) { - var data = (DataHelper.EngineObjectReferenceHelper)GetFieldValue(fieldName, isEntity ? SerializationType.EntityReference : SerializationType.ComponentReference); + object? fieldData = GetFieldValue(fieldName, isEntity ? SerializationType.EntityReference : SerializationType.ComponentReference); + + if (fieldData is not DataHelper.EngineObjectReferenceHelper data) + return NoValueDeserialized; UUID id = data.Id;