diff --git a/GlitchyEngine/src/Scripting/ScriptClass.bf b/GlitchyEngine/src/Scripting/ScriptClass.bf index b68eeed..f17063a 100644 --- a/GlitchyEngine/src/Scripting/ScriptClass.bf +++ b/GlitchyEngine/src/Scripting/ScriptClass.bf @@ -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 diff --git a/GlitchyEngine/src/Scripting/ScriptEngine.bf b/GlitchyEngine/src/Scripting/ScriptEngine.bf index da818cc..07f62f0 100644 --- a/GlitchyEngine/src/Scripting/ScriptEngine.bf +++ b/GlitchyEngine/src/Scripting/ScriptEngine.bf @@ -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); diff --git a/GlitchyEngine/src/Scripting/ScriptGlue.bf b/GlitchyEngine/src/Scripting/ScriptGlue.bf index b6db8e3..9a30638 100644 --- a/GlitchyEngine/src/Scripting/ScriptGlue.bf +++ b/GlitchyEngine/src/Scripting/ScriptGlue.bf @@ -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(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()) + { + entity.AddComponent(); + } + + if (entity.TryGetComponent(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(let scriptComponent)) + { + ScriptEngine.DestroyInstance(entity, scriptComponent); + + entity.RemoveComponent(); + } + } + #endregion #region TransformComponent diff --git a/GlitchyEngine/src/World/Scene.bf b/GlitchyEngine/src/World/Scene.bf index b7dd073..8b012df 100644 --- a/GlitchyEngine/src/World/Scene.bf +++ b/GlitchyEngine/src/World/Scene.bf @@ -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(*sourceComponent); } } + + private static void CopyComponent(Entity source, Entity target) where TComponent : struct, new + { + if (source.TryGetComponent(let component)) + { + target.AddComponent(*component); + } + } b2Vec2 _gravity2D = .(0.0f, -9.8f); @@ -562,7 +573,7 @@ namespace GlitchyEngine.World Runtime = Scripts | Physics, } - //private append List _destroyQueue = .(); + private append List _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 GetEntityByID(UUID id) { diff --git a/GlitchyEngine/src/World/SceneSerializer.bf b/GlitchyEngine/src/World/SceneSerializer.bf index cb15e5b..8912533 100644 --- a/GlitchyEngine/src/World/SceneSerializer.bf +++ b/GlitchyEngine/src/World/SceneSerializer.bf @@ -22,6 +22,9 @@ class SceneSerializer private Dictionary _parentIdToChild; private List<(Entity Entity, UUID ParentId)> _entitiesMissingParent; + + // Maps from ID in the prefab file to the actual ID in the scene. + private Dictionary _fileToSceneId; public this(Scene scene) { @@ -262,12 +265,13 @@ class SceneSerializer Runtime.NotImplemented(); } - public Result Deserialize(StringView filePath) + public Result Deserialize(StringView filePath, bool loadAsPrefab = false) { Debug.Profiler.ProfileResourceFunction!(); _parentIdToChild = scope Dictionary(); _entitiesMissingParent = scope List<(Entity Entity, UUID ParentId)>(); + _fileToSceneId = scope Dictionary(); 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 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 DeserializeEntity(BonReader reader, bool newId) { - /*mixin DeserializeAsset(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(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(reader, "Id", let uuid); + Deserialize.Value(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); diff --git a/GlitchyEngine/src/World/WorldEnumerator.bf b/GlitchyEngine/src/World/WorldEnumerator.bf index c39b7da..d956592 100644 --- a/GlitchyEngine/src/World/WorldEnumerator.bf +++ b/GlitchyEngine/src/World/WorldEnumerator.bf @@ -10,16 +10,14 @@ namespace GlitchyEngine.World { internal EcsWorld _world; internal BitArray _bitMask; - internal EcsWorld.BitmaskEntry* _currentEntry; - internal EcsWorld.BitmaskEntry* _endEntry; + internal List.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 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) diff --git a/GlitchyEngineHelper/src/Mono/Mono.bf b/GlitchyEngineHelper/src/Mono/Mono.bf index 3b0b161..7452995 100644 --- a/GlitchyEngineHelper/src/Mono/Mono.bf +++ b/GlitchyEngineHelper/src/Mono/Mono.bf @@ -207,6 +207,9 @@ static class Mono [LinkName(.C)] public static extern char8* mono_type_get_name(MonoType* type); + [LinkName(.C)] + public static extern char8* mono_type_full_name(MonoType* type); + [LinkName(.C)] public static extern MonoClass* mono_type_get_class(MonoType* type); @@ -269,6 +272,59 @@ static class Mono [LinkName(.C)] public static extern MonoObject* mono_value_box(MonoDomain* domain, MonoClass* klass, gpointer value); + +#region MonoArray + + typealias uintptr_t = uint; + typealias intptr_t = int; + + [LinkName(.C)] + public static extern MonoArray* mono_array_new(MonoDomain *domain, MonoClass *eclass, uintptr_t length); + + [LinkName(.C)] + public static extern MonoArray* mono_array_new_checked(MonoDomain *domain, MonoClass *eclass, uintptr_t length, MonoError *error); + + [LinkName(.C)] + public static extern MonoArray* mono_array_new_full(MonoDomain *domain, MonoClass *array_class, uintptr_t* lengths, uintptr_t* lower_bounds); + + [LinkName(.C)] + public static extern MonoArray* mono_array_new_full_checked(MonoDomain *domain, MonoClass *array_class, uintptr_t* lengths, uintptr_t* lower_bounds, MonoError *error); + + [LinkName(.C)] + public static extern void mono_array_full_copy(MonoArray* src, MonoArray* dest); + + [LinkName(.C)] + public static extern MonoArray* mono_array_clone(MonoArray* array); + + [LinkName(.C)] + public static extern MonoArray* mono_array_clone_checked(MonoArray* array, MonoError* error); + + [LinkName(.C)] + public static extern void* mono_array_addr_with_size(MonoArray *array, int32 size, uintptr_t idx); + + [LinkName(.C)] + public static extern uintptr_t mono_array_length(MonoArray *array); + + [Inline] + public static T* mono_array_addr(MonoArray* array, uintptr_t index) + { + return (T*)mono_array_addr_with_size(array, sizeof(T), index); + } + + [Inline] + public static T mono_array_get(MonoArray* array, uintptr_t index) + { + return *mono_array_addr(array, index); + } + + [Inline] + public static void mono_array_set(MonoArray* array, uintptr_t index, T value) + { + T* entryPtr = mono_array_addr(array, index); + *entryPtr = value; + } + +#endregion } struct MonoDomain; @@ -293,6 +349,8 @@ struct MonoThread; struct MonoVTable; +struct MonoArray; + struct MonoException { void* _bla; diff --git a/ScriptCore/Entity.cs b/ScriptCore/Entity.cs index e28756e..7528e1b 100644 --- a/ScriptCore/Entity.cs +++ b/ScriptCore/Entity.cs @@ -1,26 +1,55 @@ using System; -using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Xml.Linq; using GlitchyEngine.Core; -using GlitchyEngine.Math; +using GlitchyEngine.Extensions; namespace GlitchyEngine; -internal struct EntityHandle -{ - public uint Version; - public uint Index; -} - public class Entity : EngineObject { /// - /// Empty constructor not used. Do NOT USE! + /// Only used by the Engine. Don't use! /// protected Entity() - { } + { + // We don't do anything here. + // This constructor will be called by the Engine to initialize the scripts fields. + // Especially don't call Create here! Because Create would try and create a new entity. + } + + /// + /// Creates a new Entity. + /// + /// The name of the new entity. + public Entity(string name = null) + { + Create(name, null); + } + + /// + /// Creates a new Entity. + /// + /// The name of the new entity. + /// The components that the entity shall have. + public Entity(string name, params Type[] components) + { + Create(name, components); + } + private void Create(string name, Type[] components) + { + ScriptGlue.Entity_Create(this, name, components, out _uuid); + + if (_uuid == UUID.Zero) + { + throw new InvalidOperationException("Failed to create the Entity. Received UUID.Zero from the engine."); + } + } + + /// + /// Creates an instance that represents the Entity with the given id. + /// + /// The ID of the entity that belongs to this instance. internal Entity(UUID uuid) { _uuid = uuid; @@ -68,9 +97,11 @@ public class Entity : EngineObject return null; } - + + #region Add Components + /// - /// Gets the component with the specified component type. + /// Adds the component with the specified type. /// /// The type of the component to add. /// The component. @@ -85,22 +116,85 @@ public class Entity : EngineObject } /// - /// Gets the component with the given component type. + /// Adds the component with the given type. /// /// The type of the component to add. /// The component or null, if the given type is not a valid component type. /// For performance reasons it is recommended to use if possible. public Component AddComponent(Type componentType) { - // TODO: probably throw! - if (!componentType.IsSubclassOf(typeof(Component))) return null; - + if (!componentType.IsSubclassOf(typeof(Component))) + { + throw new ArgumentException($"Invalid component type \"{componentType}\". Must be a subclass of \"Component\".", nameof(componentType)); + } + ScriptGlue.Entity_AddComponent(_uuid, componentType); Component component = Activator.CreateInstance(componentType) as Component; + if (component != null) + component.Entity = this; + return component; } - + + + /// + /// Adds components with the specified types to the entity. + /// + /// The types of the components to add. + /// An array containing the components. + public Component[] AddComponents(params Type[] componentTypes) + { + foreach ((Type componentType, int index) in componentTypes.WithIndex()) + { + if (!componentType.IsSubclassOf(typeof(Component))) + { + throw new ArgumentException($"Invalid component type \"{componentType}\" at index {index}. Must be a subclass of \"Component\".", nameof(componentTypes)); + } + } + + ScriptGlue.Entity_AddComponents(_uuid, componentTypes); + + Component[] components = new Component[componentTypes.Length]; + + foreach ((Type componentType, int index) in componentTypes.WithIndex()) + { + components[index] = Activator.CreateInstance(componentType) as Component; + components[index].Entity = this; + } + + return components; + } + + /// + /// Adds the specified components to the entity. + /// + /// A tuple containing the added components. + public (T1, T2) AddComponents() + where T1 : Component, new() + where T2 : Component, new() + { + ScriptGlue.Entity_AddComponents(_uuid, new []{typeof(T1), typeof(T2)}); + + return (new T1 { Entity = this }, new T2 { Entity = this }); + } + + /// + /// Adds the specified components to the entity. + /// + /// A tuple containing the added components. + public (T1, T2, T3) AddComponents() + where T1 : Component, new() + where T2 : Component, new() + where T3 : Component, new() + { + ScriptGlue.Entity_AddComponents(_uuid, new []{typeof(T1), typeof(T2), typeof(T3)}); + + return (new T1 { Entity = this }, new T2 { Entity = this }, new T3 { Entity = this }); + } + + #endregion Add Components + /// /// Removes the component with the specified component type. /// @@ -117,12 +211,32 @@ public class Entity : EngineObject /// For performance reasons it is recommended to use if possible. public void RemoveComponent(Type componentType) { - // TODO: probably throw! - if (!componentType.IsSubclassOf(typeof(Component))) return; - + if (!componentType.IsSubclassOf(typeof(Component))) + { + throw new ArgumentException($"Invalid component type \"{componentType}\". Must be a subclass of \"Component\".", nameof(componentType)); + } + ScriptGlue.Entity_RemoveComponent(_uuid, componentType); } + /// + /// Sets the script of the entity to the specified type. + /// If necessary removes the existing script. + /// + /// The type of the script. + public T SetScript() where T : Entity + { + return ScriptGlue.Entity_SetScript(_uuid, typeof(T)) as T; + } + + /// + /// Removes the script from the entity. + /// + public void RemoveScript() + { + ScriptGlue.Entity_RemoveScript(_uuid); + } + public Transform Transform => GetComponent(); //public Vector3 Translation @@ -145,6 +259,17 @@ public class Entity : EngineObject return new Entity(entityId); } + + /// + /// Returns true if the entity has a script component of the given type; false if the entity either has no script or the script is not of the specified type. + /// + /// The type of the script. + public bool Is() where T : Entity + { + object scriptInstance = ScriptGlue.Entity_GetScriptInstance(_uuid); + + return scriptInstance is T; + } /// /// Returns the script of the given type, or null, if the entity has no script of the given type. @@ -158,7 +283,30 @@ public class Entity : EngineObject return scriptInstance as T; } - // Will be executed once after the entity as be created. + /// + /// Destroys the entity and all it's children. + /// + /// + /// The destruction will not take place immediately. It will happen at the end of the current frame. + /// + public void Destroy() + { + ScriptGlue.Entity_Destroy(_uuid); + } + + /// + /// Instantiates a copy of the given entity and it's children. + /// + /// The entity to copy. + /// The new entity. + public static Entity CreateInstance(Entity entity) + { + ScriptGlue.Entity_CreateInstance(entity.UUID, out UUID newEntityId); + + return new Entity(newEntityId); + } + + // Will be executed once after the entity has be created. // void OnCreate(); // Will be executed every frame. diff --git a/ScriptCore/ScriptGlue.cs b/ScriptCore/ScriptGlue.cs index 86cdfc3..7bd7226 100644 --- a/ScriptCore/ScriptGlue.cs +++ b/ScriptCore/ScriptGlue.cs @@ -11,10 +11,22 @@ namespace GlitchyEngine; internal static class ScriptGlue { #region Entity + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void Entity_Create(object scriptInstance, string entityName, Type[] componentTypes, out UUID entityId); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void Entity_Destroy(UUID entityId); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void Entity_CreateInstance(UUID entityId, out UUID newEntityId); [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void Entity_AddComponent(UUID entityId, Type componentType); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void Entity_AddComponents(UUID entityId, Type[] componentTypes); + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern bool Entity_HasComponent(UUID entityId, Type componentType); @@ -27,6 +39,12 @@ internal static class ScriptGlue [MethodImpl(MethodImplOptions.InternalCall)] internal static extern object Entity_GetScriptInstance(UUID entityId); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern object Entity_SetScript(UUID entityId, Type scriptType); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void Entity_RemoveScript(UUID entityId); + #endregion Entity #region TransformComponent