From 446dc2ca2a710a12fb4f4f798cd0b7d58da6724a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Thu, 21 Sep 2023 14:23:00 +0200 Subject: [PATCH] Scripting: Basic copying of entities during runtime + Reference Components using UUID --- GlitchyEngine/src/Scripting/ScriptEngine.bf | 92 ++++++++++++++++--- GlitchyEngine/src/Scripting/ScriptInstance.bf | 7 ++ GlitchyEngine/src/World/Scene.bf | 77 ++++++++++++++-- ScriptCore/Components/Component.cs | 4 +- ScriptCore/Components/RigidBody2D.cs | 20 ++-- ScriptCore/Entity.cs | 14 +-- ScriptCore/Physics/Collision2D.cs | 4 +- 7 files changed, 175 insertions(+), 43 deletions(-) diff --git a/GlitchyEngine/src/Scripting/ScriptEngine.bf b/GlitchyEngine/src/Scripting/ScriptEngine.bf index 1d1b617..753ef60 100644 --- a/GlitchyEngine/src/Scripting/ScriptEngine.bf +++ b/GlitchyEngine/src/Scripting/ScriptEngine.bf @@ -299,20 +299,26 @@ static class ScriptEngine } /// Creates an instance of the given component class referencing the specified entity instance. - private static MonoObject* CreateComponentReferenceInstance(ScriptClass componentClass, MonoObject* entityReferenceInstance) + private static MonoObject* CreateComponentReferenceInstance(ScriptClass componentClass, UUID id)//MonoObject* entityReferenceInstance) { MonoObject* componentInstance = componentClass.CreateInstance(); - // TODO: We could cache the property, but this might be fine + MonoClassField* idField = Mono.mono_class_get_field_from_name(componentClass.[Friend]_monoClass, "_uuid"); + + s_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**)&entityReferenceInstance, &exception); + Mono.mono_property_set_value(entityProperty, componentInstance, (void**)&id, &exception); if (exception != null) - ScriptEngine.HandleMonoException((MonoException*)exception, null); + ScriptEngine.HandleMonoException((MonoException*)exception, null);*/ return componentInstance; } @@ -345,7 +351,7 @@ static class ScriptEngine case .Component: // Get or create entity reference UUID referencedId = field.GetData(); - MonoObject* referencedEntity = GetOrCreateScriptReferenceInstance(referencedId); + //MonoObject* referencedEntity = GetOrCreateScriptReferenceInstance(referencedId); MonoType* fieldMonoType = scriptField.GetMonoType(); @@ -353,7 +359,8 @@ static class ScriptEngine var componentClass = ComponentClasses[componentType.FullName]; - MonoObject* componentInstance = CreateComponentReferenceInstance(componentClass, referencedEntity); + //MonoObject* componentInstance = CreateComponentReferenceInstance(componentClass, referencedEntity); + MonoObject* componentInstance = CreateComponentReferenceInstance(componentClass, referencedId); script.Instance.SetFieldValue(scriptField, componentInstance); @@ -363,26 +370,81 @@ static class ScriptEngine } } } + + 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(s_EntityRoot._monoClass, "_uuid"); + UUID sourceId = s_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: - // We create a new instance of a component class - MonoType* type = scriptField.GetMonoType(); - SharpType sharpType = ScriptEngine.GetSharpType(type); + 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(s_EngineObject._monoClass, "_uuid"); + UUID sourceId = s_EntityRoot.GetFieldValue(sourceComponentReference, idField); + + MonoType* fieldMonoType = scriptField.GetMonoType(); - var componentClass = ComponentClasses[sharpType.FullName]; + SharpType componentType = ScriptEngine.GetSharpType(fieldMonoType); - MonoObject* componentInstance = script.Instance.CreateComponentInstance(componentClass); - script.Instance.SetFieldValue(scriptField, componentInstance); + 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); - sharpType.ReleaseRef(); default: - script.Instance.SetFieldValue(scriptField, field._data); + 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); diff --git a/GlitchyEngine/src/Scripting/ScriptInstance.bf b/GlitchyEngine/src/Scripting/ScriptInstance.bf index 3cb3b5e..9667887 100644 --- a/GlitchyEngine/src/Scripting/ScriptInstance.bf +++ b/GlitchyEngine/src/Scripting/ScriptInstance.bf @@ -114,6 +114,13 @@ class ScriptInstance : RefCounter _scriptClass.SetFieldValue(_instance, field.[Friend]_monoField, value); } + public void CopyFieldValue(ScriptField field, ScriptInstance sourceInstance) + { + // TODO: I hate this! + var data = sourceInstance.GetFieldValue(field); + SetFieldValue(field, data); + } + /// Creates a new instance of the given component class and initializes it for the current entity. public MonoObject* CreateComponentInstance(ScriptClass componentClassType) { diff --git a/GlitchyEngine/src/World/Scene.bf b/GlitchyEngine/src/World/Scene.bf index 8b012df..3f1b2c3 100644 --- a/GlitchyEngine/src/World/Scene.bf +++ b/GlitchyEngine/src/World/Scene.bf @@ -29,6 +29,8 @@ namespace GlitchyEngine.World // Maps ids to the entities they represent. private Dictionary _idToEntity = new .() ~ delete _; + private HashSet _updateBlockList = new .() ~ delete _; + public Entity ActiveCamera => { Entity cameraEntity = .(); @@ -99,7 +101,7 @@ namespace GlitchyEngine.World if (initializeScripts) { - CopyComponents(this, target); + //CopyComponents(this, target); // Copy ScriptComponents... needs extra handling for the script instances for (let (sourceHandle, sourceComponent) in _ecsWorld.Enumerate()) @@ -583,6 +585,8 @@ namespace GlitchyEngine.World if (mode.HasFlag(.Scripts)) { + Debug.Profiler.ProfileScope!("Update scripts"); + // Run scripts for (var (entity, script) in _ecsWorld.Enumerate()) { @@ -595,12 +599,9 @@ namespace GlitchyEngine.World script.Instance.[Friend]OnUpdate(gameTime); } - /*} - - if (mode.HasFlag(.Runtime) || mode.HasFlag(.Editor)) - {*/ + // Run scripts - for (var (entity, script) in _ecsWorld.Enumerate()) + for (let (entity, script) in _ecsWorld.Enumerate()) { if (!script.IsCreated) { @@ -615,7 +616,12 @@ namespace GlitchyEngine.World } if (mode.HasFlag(.Runtime)) - script.Instance.InvokeOnUpdate(gameTime.DeltaTime); + { + if (_updateBlockList.Contains(entity)) + _updateBlockList.Remove(entity); + else + script.Instance.InvokeOnUpdate(gameTime.DeltaTime); + } } } @@ -761,11 +767,50 @@ namespace GlitchyEngine.World */ public Entity CreateInstance(Entity entity) { + Log.EngineLogger.Info("Ja moin"); + + List newEntities = scope .(); + Dictionary sourceToTargetEntity = scope .(); + Dictionary sourceIdToTargetId = scope .(); + Dictionary targetIdToSourceEntity = scope .(); + Entity CopyEntityAndChildren(Entity original) { Entity copy = CreateEntity(original.Name); - // TODO: Copy components + newEntities.Add(copy); + sourceToTargetEntity.Add(original.Handle, copy.Handle); + sourceIdToTargetId.Add(original.UUID, copy.UUID); + targetIdToSourceEntity.Add(copy.UUID, original); + _updateBlockList.Add(copy.Handle); + + CopyComponent(original, copy); + CopyComponent(original, copy); + CopyComponent(original, copy); + CopyComponent(original, copy); + CopyComponent(original, copy); + CopyComponent(original, copy); + CopyComponent(original, copy); + + CopyComponent(original, copy); + CopyComponent(original, copy); + CopyComponent(original, copy); + CopyComponent(original, copy); + + // Copy ScriptComponent... needs extra handling for the script instances + if (original.TryGetComponent(let sourceScript)) + { + ScriptComponent* targetScript = copy.AddComponent(); + + 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); + } // 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... for (let child in original.EnumerateChildren) @@ -778,6 +823,22 @@ namespace GlitchyEngine.World } Entity newEntity = CopyEntityAndChildren(entity); + + for (let copy in newEntities) + { + if (copy.TryGetComponent(let targetScript)) + { + Entity originalEntity = targetIdToSourceEntity[copy.UUID]; + + if (!originalEntity.TryGetComponent(let sourceScript)) + continue; + + ScriptEngine.CopyFieldsToInstance(targetScript, sourceScript, sourceIdToTargetId); + + //targetScript.Instance.InvokeOnCreate(); + } + } + return newEntity; } diff --git a/ScriptCore/Components/Component.cs b/ScriptCore/Components/Component.cs index e108021..796b41a 100644 --- a/ScriptCore/Components/Component.cs +++ b/ScriptCore/Components/Component.cs @@ -2,7 +2,7 @@ using GlitchyEngine.Core; namespace GlitchyEngine; -public abstract class Component +public abstract class Component : EngineObject { - public Entity Entity { get; internal set; } + public Entity Entity => new(_uuid); } \ No newline at end of file diff --git a/ScriptCore/Components/RigidBody2D.cs b/ScriptCore/Components/RigidBody2D.cs index ed0255f..3528396 100644 --- a/ScriptCore/Components/RigidBody2D.cs +++ b/ScriptCore/Components/RigidBody2D.cs @@ -14,7 +14,7 @@ public class Rigidbody2D : Component /// Wake up the body public void ApplyForce(float2 force, float2 point, bool wakeUp = true) { - ScriptGlue.Rigidbody2D_ApplyForce(Entity._uuid, force, point, wakeUp); + ScriptGlue.Rigidbody2D_ApplyForce(_uuid, force, point, wakeUp); } /// @@ -25,7 +25,7 @@ public class Rigidbody2D : Component /// Wake up the body public void ApplyForceToCenter(float2 force, bool wakeUp = true) { - ScriptGlue.Rigidbody2D_ApplyForceToCenter(Entity._uuid, force, wakeUp); + ScriptGlue.Rigidbody2D_ApplyForceToCenter(_uuid, force, wakeUp); } /// @@ -35,11 +35,11 @@ public class Rigidbody2D : Component { get { - ScriptGlue.Rigidbody2D_GetPosition(Entity._uuid, out float2 position); + ScriptGlue.Rigidbody2D_GetPosition(_uuid, out float2 position); return position; } - set => ScriptGlue.Rigidbody2D_SetPosition(Entity._uuid, value); + set => ScriptGlue.Rigidbody2D_SetPosition(_uuid, value); } /// @@ -49,11 +49,11 @@ public class Rigidbody2D : Component { get { - ScriptGlue.Rigidbody2D_GetRotation(Entity._uuid, out float rotation); + ScriptGlue.Rigidbody2D_GetRotation(_uuid, out float rotation); return rotation; } - set => ScriptGlue.Rigidbody2D_SetRotation(Entity._uuid, value); + set => ScriptGlue.Rigidbody2D_SetRotation(_uuid, value); } /// @@ -63,11 +63,11 @@ public class Rigidbody2D : Component { get { - ScriptGlue.Rigidbody2D_GetLinearVelocity(Entity._uuid, out float2 velocity); + ScriptGlue.Rigidbody2D_GetLinearVelocity(_uuid, out float2 velocity); return velocity; } - set => ScriptGlue.Rigidbody2D_SetLinearVelocity(Entity._uuid, value); + set => ScriptGlue.Rigidbody2D_SetLinearVelocity(_uuid, value); } /// @@ -77,10 +77,10 @@ public class Rigidbody2D : Component { get { - ScriptGlue.Rigidbody2D_GetAngularVelocity(Entity._uuid, out float velocity); + ScriptGlue.Rigidbody2D_GetAngularVelocity(_uuid, out float velocity); return velocity; } - set => ScriptGlue.Rigidbody2D_SetAngularVelocity(Entity._uuid, value); + set => ScriptGlue.Rigidbody2D_SetAngularVelocity(_uuid, value); } } diff --git a/ScriptCore/Entity.cs b/ScriptCore/Entity.cs index 7528e1b..3dcf2a5 100644 --- a/ScriptCore/Entity.cs +++ b/ScriptCore/Entity.cs @@ -72,7 +72,7 @@ public class Entity : EngineObject { return new T { - Entity = this + _uuid = _uuid }; } @@ -111,7 +111,7 @@ public class Entity : EngineObject return new T { - Entity = this + _uuid = _uuid }; } @@ -132,7 +132,9 @@ public class Entity : EngineObject Component component = Activator.CreateInstance(componentType) as Component; if (component != null) - component.Entity = this; + { + component._uuid = _uuid; + } return component; } @@ -160,7 +162,7 @@ public class Entity : EngineObject foreach ((Type componentType, int index) in componentTypes.WithIndex()) { components[index] = Activator.CreateInstance(componentType) as Component; - components[index].Entity = this; + components[index]._uuid = _uuid; } return components; @@ -176,7 +178,7 @@ public class Entity : EngineObject { ScriptGlue.Entity_AddComponents(_uuid, new []{typeof(T1), typeof(T2)}); - return (new T1 { Entity = this }, new T2 { Entity = this }); + return (new T1 { _uuid = _uuid }, new T2 { _uuid = _uuid }); } /// @@ -190,7 +192,7 @@ public class Entity : EngineObject { ScriptGlue.Entity_AddComponents(_uuid, new []{typeof(T1), typeof(T2), typeof(T3)}); - return (new T1 { Entity = this }, new T2 { Entity = this }, new T3 { Entity = this }); + return (new T1 { _uuid = _uuid }, new T2 { _uuid = _uuid }, new T3 { _uuid = _uuid }); } #endregion Add Components diff --git a/ScriptCore/Physics/Collision2D.cs b/ScriptCore/Physics/Collision2D.cs index 3017195..a4af231 100644 --- a/ScriptCore/Physics/Collision2D.cs +++ b/ScriptCore/Physics/Collision2D.cs @@ -26,10 +26,10 @@ public struct Collision2D /// Gets the rigidbody whose Collider takes part in the collision. /// This Rigidbody is either a component of the entity whose script instance received the event or a parent of it. /// - public Rigidbody2D Rigidbody => new() { Entity = Entity }; + public Rigidbody2D Rigidbody => new() { _uuid = _entity }; /// /// Gets the other rigidbody whose Collider takes part in the collision. /// - public Rigidbody2D OtherRigidbody => new() { Entity = OtherEntity }; + public Rigidbody2D OtherRigidbody => new() { _uuid = _otherEntity }; }