Entity Creation and Destruction from scripts

- ScriptEngine: Allow destroying scripts during runtime
- Scripting:
    - Create new entities using Constructors
    - Added Destroy functions for Entities
    - Added SetScript/RemoveScript functions for Entities
    - Allow adding multiple components at once
    - Added Is-Method
- Mono Wrapper: Added Array-Functions
- WorldEnumerator: Fixed crash due to dangling list pointer after entity creation
- Scene:
    - Basic create Rigidbodies during runtime
    - Start of entity copying (For runtime use)
    - Added DestroyEntityDeferred
- SceneSerializer:
    - Added option to assign new IDs to entities
This commit is contained in:
Simon Lübeß
2023-09-15 23:22:27 +02:00
parent ff54eb738e
commit 754c149493
9 changed files with 470 additions and 62 deletions
+1 -1
View File
@@ -383,7 +383,7 @@ class ScriptClass : SharpClass
{
MonoObject* instance = Mono.mono_object_new(ScriptEngine.[Friend]s_AppDomain, _monoClass);
// Invoke empty constructor to fill fields
// Invoke empty constructor to initialize fields with default values specified in the script itself
Mono.mono_runtime_object_init(instance);
// Invoke constructor with UUID
+19 -1
View File
@@ -238,7 +238,7 @@ static class ScriptEngine
{
for (var entry in _entityScriptInstances)
{
entry.value.ReleaseRef();
entry.value?.ReleaseRef();
}
_entityScriptInstances.Clear();
@@ -257,6 +257,9 @@ static class ScriptEngine
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);
@@ -264,6 +267,21 @@ static class ScriptEngine
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;
}
public static void CopyEditorFieldsToInstance(Entity entity, ScriptComponent* script)
{
Log.EngineLogger.AssertDebug(script.Instance != null);
+91
View File
@@ -8,6 +8,7 @@ using GlitchyEngine.World;
using GlitchyEngine.Core;
using System.Collections;
using Box2D;
using GlitchyEngine.Scripting;
namespace GlitchyEngine.Scripting;
@@ -146,7 +147,56 @@ static class ScriptGlue
#endregion Input
#region Scene/Entity stuff
[RegisterCall("ScriptGlue::Entity_Create")]
static void Entity_Create(MonoObject* scriptInstance, MonoString* monoEntityName, MonoArray* componentTypes, out UUID entityId)
{
char8* entityName = Mono.mono_string_to_utf8(monoEntityName);
Entity entity = ScriptEngine.Context.CreateEntity(StringView(entityName));
Mono.mono_free(entityName);
entityId = entity.UUID;
// TODO: Script instance
if (componentTypes != null)
{
Entity_AddComponents(entityId, componentTypes);
}
}
[RegisterCall("ScriptGlue::Entity_Destroy")]
static void Entity_Destroy(UUID entityId)
{
Entity entity = ScriptEngine.Context.GetEntityByID(entityId);
ScriptEngine.Context.DestroyEntityDeferred(entity);
}
[RegisterCall("ScriptGlue::Entity_CreateInstance")]
static void Entity_CreateInstance(UUID entityId, out UUID newEntityId)
{
Entity entity = ScriptEngine.Context.GetEntityByID(entityId);
Entity newEntity = ScriptEngine.Context.CreateInstance(entity);
newEntityId = newEntity.UUID;
}
[RegisterCall("ScriptGlue::Entity_AddComponents")]
static void Entity_AddComponents(UUID entityId, MonoArray* componentTypes)
{
if (componentTypes == null)
return;
uint length = Mono.mono_array_length(componentTypes);
for (uint i < length)
{
MonoReflectionType* reflectionType = Mono.mono_array_get<MonoReflectionType*>(componentTypes, i);
Entity_AddComponent(entityId, reflectionType);
}
}
[RegisterCall("ScriptGlue::Entity_AddComponent")]
static void Entity_AddComponent(UUID entityId, MonoReflectionType* componentType)
{
@@ -208,6 +258,47 @@ static class ScriptGlue
return ScriptEngine.GetManagedInstance(entityId);
}
[RegisterCall("ScriptGlue::Entity_SetScript")]
static MonoObject* Entity_SetScript(UUID entityId, MonoReflectionType* scriptType)
{
Entity entity = ScriptEngine.Context.GetEntityByID(entityId);
if (!entity.HasComponent<ScriptComponent>())
{
entity.AddComponent<ScriptComponent>();
}
if (entity.TryGetComponent<ScriptComponent>(let scriptComponent))
{
scriptComponent.Instance = null;
MonoType* type = Mono.mono_reflection_type_get_type(scriptType);
scriptComponent.ScriptClassName = StringView(Mono.mono_type_full_name(type));
// 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(entity, scriptComponent);
return scriptComponent.Instance.MonoInstance;
}
return null;
}
[RegisterCall("ScriptGlue::Entity_RemoveScript")]
static void Entity_RemoveScript(UUID entityId)
{
Entity entity = ScriptEngine.Context.GetEntityByID(entityId);
if (entity.TryGetComponent<ScriptComponent>(let scriptComponent))
{
ScriptEngine.DestroyInstance(entity, scriptComponent);
entity.RemoveComponent<ScriptComponent>();
}
}
#endregion
#region TransformComponent
+69 -7
View File
@@ -51,14 +51,17 @@ namespace GlitchyEngine.World
cameraComponent.Camera.SetViewportSize(e.Scene._viewportWidth, e.Scene._viewportHeight);
});
/*_onComponentAddedHandlers.Add(typeof(Rigidbody2DComponent), (e, t, c) => {
if ()
_onComponentAddedHandlers.Add(typeof(Rigidbody2DComponent), (e, t, c) => {
Rigidbody2DComponent* rigidbodyComponent = (.)c;
Scene scene = e.Scene;
Rigidbody2DComponent* rigidBodyComponent = (.)c;
//cameraComponent.Camera.SetViewportSize(e.Scene._viewportWidth, e.Scene._viewportHeight);
});*/
if (scene._physicsWorld2D != null)
{
// TODO: Add rigidbody
Log.EngineLogger.Error("Should have added a rigidbody");
}
});
}
public ~this()
@@ -154,6 +157,14 @@ namespace GlitchyEngine.World
targetEntity.AddComponent<TComponent>(*sourceComponent);
}
}
private static void CopyComponent<TComponent>(Entity source, Entity target) where TComponent : struct, new
{
if (source.TryGetComponent<TComponent>(let component))
{
target.AddComponent<TComponent>(*component);
}
}
b2Vec2 _gravity2D = .(0.0f, -9.8f);
@@ -562,7 +573,7 @@ namespace GlitchyEngine.World
Runtime = Scripts | Physics,
}
//private append List<UUID> _destroyQueue = .();
private append List<Entity> _destroyQueue = .();
public void Update(GameTime gameTime, UpdateMode mode)
{
@@ -680,6 +691,17 @@ namespace GlitchyEngine.World
}
}
}
if (!_destroyQueue.IsEmpty)
{
for (let entity in _destroyQueue)
{
DestroyEntity(entity);
}
_destroyQueue.Clear();
}
}
/// Creates a new Entity with the given name.
@@ -719,6 +741,46 @@ namespace GlitchyEngine.World
_ecsWorld.RemoveEntity(entity.Handle);
}
/** Marks the given entity and it's children, so that they will be destroyed at the end of the frame.
* @param entity The entity to delete.
*/
public void DestroyEntityDeferred(Entity entity)
{
_destroyQueue.Add(entity);
for (Entity child in entity.EnumerateChildren)
{
DestroyEntityDeferred(child);
}
}
/** Creates a copy of the given entity, including all components and children.
* @param entity the entity to copy.
* @returns the newly create entity.
*/
public Entity CreateInstance(Entity entity)
{
Entity CopyEntityAndChildren(Entity original)
{
Entity copy = CreateEntity(original.Name);
// TODO: Copy components
// 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)
{
Entity childCopy = CopyEntityAndChildren(child);
childCopy.Parent = copy;
}
return copy;
}
Entity newEntity = CopyEntityAndChildren(entity);
return newEntity;
}
public Result<Entity> GetEntityByID(UUID id)
{
+37 -24
View File
@@ -22,6 +22,9 @@ class SceneSerializer
private Dictionary<UUID, Entity> _parentIdToChild;
private List<(Entity Entity, UUID ParentId)> _entitiesMissingParent;
// Maps from ID in the prefab file to the actual ID in the scene.
private Dictionary<UUID, UUID> _fileToSceneId;
public this(Scene scene)
{
@@ -262,12 +265,13 @@ class SceneSerializer
Runtime.NotImplemented();
}
public Result<void> Deserialize(StringView filePath)
public Result<void> Deserialize(StringView filePath, bool loadAsPrefab = false)
{
Debug.Profiler.ProfileResourceFunction!();
_parentIdToChild = scope Dictionary<UUID, Entity>();
_entitiesMissingParent = scope List<(Entity Entity, UUID ParentId)>();
_fileToSceneId = scope Dictionary<UUID, UUID>();
String buffer = scope String();
File.ReadAllText(filePath, buffer);
@@ -298,7 +302,7 @@ class SceneSerializer
Try!(reader.EntryEnd());
}
Try!(DeserializeEntity(reader));
Try!(DeserializeEntity(reader, loadAsPrefab));
first = false;
}
@@ -325,23 +329,12 @@ class SceneSerializer
return .Ok;
}
private Result<void> DeserializeEntity(BonReader reader)
/// Deserializes the next entity in the file
/// @param reader the reader
/// @param replaceId If false, the ID that is stored in the file will be used as ID in the scene.
/// If true, the ID in the file will be replaced with a new id (e.g. for loading prefabs)
private Result<void> DeserializeEntity(BonReader reader, bool newId)
{
/*mixin DeserializeAsset<T>(StringView identifier) where T : Asset
{
Asset asset = null;
Try!(Deserialize.Value(reader, identifier, out asset));
if (asset != null && !(asset is T))
{
Log.EngineLogger.Error($"Asset {asset.Identifier} is not a {nameof(T)}.");
return .Err;
}
(T)asset
}*/
mixin DeserializeAssetHandle<T>(StringView identifier) where T : Asset
{
Asset asset = null;
@@ -357,11 +350,29 @@ class SceneSerializer
asset?.Handle ?? .Invalid
}
UUID RemapId(UUID id)
{
if (!newId)
return id;
if (_fileToSceneId.TryGetValue(id, let sceneId))
return sceneId;
// If we remap IDs map the file Id to a random Id.
UUID newId = UUID.Create();
_fileToSceneId.Add(id, newId);
return newId;
}
Try!(reader.ObjectBlock());
Deserialize.Value<uint64>(reader, "Id", let uuid);
Deserialize.Value<uint64>(reader, "Id", let rawUuid);
Entity entity = _scene.CreateEntity("", UUID(uuid));
UUID uuid = RemapId(UUID(rawUuid));
Entity entity = _scene.CreateEntity("", uuid);
while(reader.ObjectHasMore())
{
@@ -416,11 +427,13 @@ class SceneSerializer
let nextId = Try!(reader.Identifier());
if (nextId == "ParentId")
{
UUID pId;
Deserialize.Value(reader, out pId);
UUID rawParentId;
Deserialize.Value(reader, out rawParentId);
reader.EntryEnd();
var parentEntity = _scene.GetEntityByID(pId);
UUID parentId = RemapId(rawParentId);
var parentEntity = _scene.GetEntityByID(parentId);
if (parentEntity case .Ok(let parent))
{
@@ -428,7 +441,7 @@ class SceneSerializer
}
else
{
_entitiesMissingParent.Add((entity, pId));
_entitiesMissingParent.Add((entity, parentId));
}
Deserialize.Value(reader, "Position", out component.[Friend]_position);
+7 -7
View File
@@ -10,16 +10,14 @@ namespace GlitchyEngine.World
{
internal EcsWorld _world;
internal BitArray _bitMask;
internal EcsWorld.BitmaskEntry* _currentEntry;
internal EcsWorld.BitmaskEntry* _endEntry;
internal List<EcsWorld.BitmaskEntry>.Enumerator _entitiesEnumerator;
public bool IsEmpty => _bitMask == null;
public this(EcsWorld world, Type[] componentTypes)
{
_world = world;
_currentEntry = _world._entities.Ptr;
_endEntry = _world._entities.Ptr + _world._entities.Count;
_entitiesEnumerator = _world._entities.GetEnumerator();
_bitMask = new BitArray(_world._componentPools.Count);
for(var type in componentTypes)
@@ -39,7 +37,6 @@ namespace GlitchyEngine.World
Log.EngineLogger.Warning($"Queried component of type \"{type}\" is not registered for this world. The query will never return any results.");
#endif
DeleteAndNullify!(_bitMask);
_endEntry = _currentEntry;
break;
}
}
@@ -47,9 +44,12 @@ namespace GlitchyEngine.World
public Result<EcsEntity> GetNext() mut
{
while(_currentEntry < _endEntry)
if (IsEmpty)
return .Err;
while(_entitiesEnumerator.GetNext() case .Ok(let entry))
{
EcsWorld.BitmaskEntry* entry = _currentEntry++;
//EcsWorld.BitmaskEntry* entry = _currentEntry++;
// Skip deleted entities
if(entry.ID.Index == EcsEntity.InvalidEntity.Index)