From a4c311c2e41c7cb573f21c907d1837bdc5124f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Tue, 26 Dec 2023 02:00:44 +0100 Subject: [PATCH] Implemented Entity and Component Drop-Targets and added Name property for entities - Also fixed non generic GetComponent - Use UUID as Drag-Drop value for entities - Removed unused methods from ComponentEditWindow - Started a marginally type-safe version of ImGui.Payload --- .../src/EditWindows/ComponentEditWindow.bf | 168 ----------------- .../src/EditWindows/EntityHierarchyWindow.bf | 17 +- GlitchyEditor/src/ImGuiExtension.bf | 5 + GlitchyEngine/src/ImGui/ImGuiExtension.bf | 24 +++ GlitchyEngine/src/Scripting/ScriptGlue.bf | 22 +++ GlitchyEngineHelper/src/Mono/Mono.bf | 3 + ScriptCore/Editor/EntityEditor.cs | 171 +++++++++++++++++- ScriptCore/Entity.cs | 23 ++- ScriptCore/ScriptGlue.cs | 6 + 9 files changed, 255 insertions(+), 184 deletions(-) diff --git a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf index b231021..e0f751f 100644 --- a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf +++ b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf @@ -568,174 +568,6 @@ namespace GlitchyEditor.EditWindows ScriptEngine.ShowScriptEditor(entity, scriptComponent); } - - private static Entity? ShowEntitySelector() - { - static char8[128] entitySearch = .(); - - Entity? result = null; - - if (ImGui.BeginPopup("ENTITY_SELECTOR")) - { - ImGui.TextUnformatted("Search:"); - ImGui.SameLine(); - - if (ImGui.InputText("##entitySearcher", &entitySearch, (uint64)entitySearch.Count)) - { - - } - - ImGui.EndPopup(); - } - - return result; - } - - private static void ShowEntityReceiver(ScriptFieldInstance* field, StringView fieldName, ScriptClass scriptClass) - { - var entityId = field.GetData(); - - Result fieldEntity = Editor.Instance.CurrentScene.GetEntityByID(entityId); - - String entityName = scope .(32); - - if (entityId == UUID(0)) - entityName.Set("None"); - else if (fieldEntity case .Ok(Entity e)) - entityName.Set(e.Name); - else - entityName..Clear().AppendF($"Missing entity ({entityId})"); - - ImGui.Text($"{fieldName}: "); - ImGui.SameLine(); - - if (ImGui.Button(entityName)) - { - if (fieldEntity case .Ok) - Editor.Instance.EntityHierarchyWindow.HighlightEntity(fieldEntity); - } - - if (ImGui.BeginDragDropTarget()) - { - ImGui.Payload* peekPayload = ImGui.AcceptDragDropPayload(.Entity, .AcceptPeekOnly); - - bool allowDrop = false; - - if (peekPayload != null) - { - Entity draggedEntity = *(Entity*)peekPayload.Data; - - ScriptClass draggedScriptClass = ScriptEngine.Classes.EntityRoot; - - if (draggedEntity.TryGetComponent(let draggedScript)) - { - var draggedClassName = draggedScript.ScriptClassName; - draggedScriptClass = ScriptEngine.GetScriptClass(draggedClassName); - } - - // TODO: I don't like the fact, that we are using mono directly - - ScriptField scriptField = scriptClass.Fields[fieldName]; - var fieldMonoClass = Mono.mono_type_get_class(scriptField.GetMonoType()); - - allowDrop = draggedScriptClass.[Friend]IsSubclass(fieldMonoClass); - } - - if (allowDrop) - { - ImGui.Payload* payload = ImGui.AcceptDragDropPayload(.Entity); - - if (payload != null) - { - Log.EngineLogger.AssertDebug(payload.DataSize == sizeof(Entity)); - - Entity droppedEntity = *(Entity*)payload.Data; - - if (droppedEntity.IsValid) - field.SetData(droppedEntity.UUID); - } - } - - ImGui.EndDragDropTarget(); - } - } - - //private static Entity? - - private static void ShowComponentReceiver(ScriptFieldInstance* field, StringView fieldName, ScriptClass scriptClass) - { - var entityId = field.GetData(); - - Result fieldEntity = Editor.Instance.CurrentScene.GetEntityByID(entityId); - - String entityName = scope .(32); - - if (entityId == UUID(0)) - entityName.Set("None"); - else if (fieldEntity case .Ok(Entity e)) - entityName.Set(e.Name); - else - entityName..Clear().AppendF($"Missing reference ({entityId})"); - - ImGui.Text($"{fieldName}: "); - ImGui.SameLine(); - - if (ImGui.Button(entityName)) - { - if (fieldEntity case .Ok) - Editor.Instance.EntityHierarchyWindow.HighlightEntity(fieldEntity); - } - - if (ImGui.BeginDragDropTarget()) - { - ImGui.Payload* peekPayload = ImGui.AcceptDragDropPayload(.Entity, .AcceptPeekOnly); - - bool allowDrop = false; - - if (peekPayload != null) - { - Entity draggedEntity = *(Entity*)peekPayload.Data; - - // TODO: I don't like the fact, that we are using mono directly - - ScriptField scriptField = scriptClass.Fields[fieldName]; - - // For some reason we have to retrieve the MonoType like this. Using scriptField.GetMonoType() directly returns the wrong type... - MonoReflectionType* reflectionType = Mono.mono_type_get_object(ScriptEngine.[Friend]s_AppDomain, scriptField.GetMonoType()); - MonoType* actualMonoType = Mono.mono_reflection_type_get_type(reflectionType); - - // TODO: We shouldn't abuse the script glue like that. - // ScriptGlue should only be called by C#, not by Beef. - if (ScriptGlue.[Friend]s_HasComponentMethods.TryGetValue(actualMonoType, let has_component)) - { - allowDrop = has_component(draggedEntity); - } - else - { - Log.EngineLogger.Warning($"No HasComponent-Function found for field {scriptField.Name}"); - allowDrop = false; - } - } - - if (allowDrop) - { - ImGui.Payload* payload = ImGui.AcceptDragDropPayload(.Entity); - - if (payload != null) - { - Log.EngineLogger.AssertDebug(payload.DataSize == sizeof(Entity)); - - Entity droppedEntity = *(Entity*)payload.Data; - - if (droppedEntity.IsValid) - field.SetData(droppedEntity.UUID); - } - } - - ImGui.EndDragDropTarget(); - } - } - private static void LabelColumn(StringView label) { ImGui.TextUnformatted(label); diff --git a/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf b/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf index 5f09bdb..bfb82f9 100644 --- a/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf +++ b/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf @@ -415,7 +415,8 @@ namespace GlitchyEditor.EditWindows { isDragged = true; - ImGui.SetDragDropPayload(.Entity, &tree.Value, sizeof(Entity)); + UUID id = tree.Value.UUID; + ImGui.SetDragDropPayload(.Entity, &id, sizeof(UUID)); ImGui.Text(name); @@ -464,13 +465,13 @@ namespace GlitchyEditor.EditWindows { if(ImGui.BeginDragDropTarget()) { - ImGui.Payload* payload = ImGui.AcceptDragDropPayload(.Entity); + Payload? payload = ImGui.AcceptDragDropPayload(.Entity); if(payload != null) { - Log.ClientLogger.AssertDebug(payload.DataSize == sizeof(Entity)); + UUID movedEntityId = payload->Data; - Entity movedEntity = *(Entity*)payload.Data; + Entity movedEntity = _editor.CurrentScene.GetEntityByID(movedEntityId); bool dropLegal = true; @@ -556,13 +557,13 @@ namespace GlitchyEditor.EditWindows { if(ImGui.BeginDragDropTarget()) { - ImGui.Payload* payload = ImGui.AcceptDragDropPayload(.Entity); + Payload? payload = ImGui.AcceptDragDropPayload(.Entity); if(payload != null) { - Log.ClientLogger.AssertDebug(payload.DataSize == sizeof(Entity)); - - Entity movedEntity = *(Entity*)payload.Data; + UUID movedEntityId = payload->Data; + + Entity movedEntity = _editor.CurrentScene.GetEntityByID(movedEntityId); // Also mark transform as dirty var transformComponent = movedEntity.GetComponent(); diff --git a/GlitchyEditor/src/ImGuiExtension.bf b/GlitchyEditor/src/ImGuiExtension.bf index 1114185..bdc4cd6 100644 --- a/GlitchyEditor/src/ImGuiExtension.bf +++ b/GlitchyEditor/src/ImGuiExtension.bf @@ -21,6 +21,11 @@ enum DragDropPayloadType extension ImGui { + public static Payload? AcceptDragDropPayload(ImGui.DragDropPayloadType type, DragDropFlags flags = .None) where T : struct + { + return AcceptDragDropPayload(type.GetName(), flags); + } + public static Payload* AcceptDragDropPayload(DragDropPayloadType type, DragDropFlags flags = .None) { return AcceptDragDropPayload(type.GetName(), flags); diff --git a/GlitchyEngine/src/ImGui/ImGuiExtension.bf b/GlitchyEngine/src/ImGui/ImGuiExtension.bf index 2440ad8..66ef3ee 100644 --- a/GlitchyEngine/src/ImGui/ImGuiExtension.bf +++ b/GlitchyEngine/src/ImGui/ImGuiExtension.bf @@ -16,6 +16,15 @@ namespace ImGui { using internal GlitchyEngine.Math; + struct Payload where T : struct + { + public ImGui.Payload* Payload; + + public T Data => *(T*)Payload.Data; + + public static ImGui.Payload* operator ->(Payload self) => self.Payload; + } + extension ImGui { extension Vec2 @@ -30,6 +39,21 @@ namespace ImGui public static explicit operator Vec4(float4 v) => .(v.X, v.Y, v.Z, v.W); } + public static Payload? AcceptDragDropPayload(char8* type, DragDropFlags flags = .None) where T : struct + { + ImGui.Payload* payload = ImGui.AcceptDragDropPayload(type, flags); + + if (payload == null) + return null; + + Log.ClientLogger.AssertDebug(payload.DataSize >= sizeof(T)); + + var typedPayload = Payload(); + typedPayload.Payload = payload; + + return typedPayload; + } + /*public static bool IsItemJustDeactivated() { return IsItemDeactivatedAfterEdit(); diff --git a/GlitchyEngine/src/Scripting/ScriptGlue.bf b/GlitchyEngine/src/Scripting/ScriptGlue.bf index 3530d74..d711ece 100644 --- a/GlitchyEngine/src/Scripting/ScriptGlue.bf +++ b/GlitchyEngine/src/Scripting/ScriptGlue.bf @@ -300,6 +300,28 @@ static class ScriptGlue entity.RemoveComponent(); } } + + [RegisterCall("ScriptGlue::Entity_GetName")] + static MonoString* Entity_GetName(UUID entityId) + { + Entity entity = ScriptEngine.Context.GetEntityByID(entityId); + + MonoString* name = Mono.mono_string_new_len(ScriptEngine.[Friend]s_AppDomain, entity.Name.Ptr, (.)entity.Name.Length); + + return name; + } + + [RegisterCall("ScriptGlue::Entity_SetName")] + static void Entity_SetName(UUID entityId, MonoString* name) + { + Entity entity = ScriptEngine.Context.GetEntityByID(entityId); + + char8* rawName = Mono.mono_string_to_utf8(name); + + entity.Name = StringView(rawName); + + Mono.mono_free(rawName); + } #endregion diff --git a/GlitchyEngineHelper/src/Mono/Mono.bf b/GlitchyEngineHelper/src/Mono/Mono.bf index 66e746c..b8a9f8c 100644 --- a/GlitchyEngineHelper/src/Mono/Mono.bf +++ b/GlitchyEngineHelper/src/Mono/Mono.bf @@ -76,6 +76,9 @@ static class Mono [LinkName(.C)] public static extern MonoString* mono_string_new(MonoDomain* domain, char8* text); + [LinkName(.C)] + public static extern MonoString* mono_string_new_len(MonoDomain* domain, char8* text, uint32 length); + [LinkName(.C)] public static extern MonoDomain* mono_domain_get(); diff --git a/ScriptCore/Editor/EntityEditor.cs b/ScriptCore/Editor/EntityEditor.cs index 174185d..59d08e0 100644 --- a/ScriptCore/Editor/EntityEditor.cs +++ b/ScriptCore/Editor/EntityEditor.cs @@ -272,13 +272,11 @@ internal class EntityEditor } else if (fieldType.IsSubclassOf(typeof(Entity)) || fieldType == typeof(Entity)) { - // TODO: Show entity drop target - ImGui.Text($"{fieldName} Entity ({fieldType.Name})"); + newValue = ShowEntityDropTarget(fieldName, fieldType, reference); } else if (fieldType.IsSubclassOf(typeof(Component))) { - // TODO: Show component drop target - ImGui.Text($"{fieldName} Component ({fieldType.Name})"); + newValue = ShowComponentDropTarget(fieldName, fieldType, reference); } else if (fieldType == typeof(string)) { @@ -338,6 +336,171 @@ internal class EntityEditor return newValue; } + private static object ShowComponentDropTarget(string fieldName, Type fieldType, object currentValue) + { + object newValue = DidNotChange; + + ImGui.Text($"{fieldName}: "); + ImGui.SameLine(); + + Component component = currentValue as Component; + + string entityName = "None"; + + if (component != null) + { + entityName = component.Entity.Name; + } + + Type actualType = component?.GetType(); + + entityName += $" ({(actualType ?? fieldType).Name})"; + + if (ImGui.Button(entityName)) + { + if (component?.Entity != null) + { + // TODO: Hightlight Entity in hierarchy + // TODO: Highlight Component in editor + } + } + + if (ImGui.BeginDragDropTarget()) + { + ImGuiPayloadPtr peekPayload = ImGui.AcceptDragDropPayload("ENTITY", ImGuiDragDropFlags.AcceptPeekOnly); + + bool allowDrop = false; + + unsafe + { + if (peekPayload.NativePtr != null) + { + UUID draggedId = *(UUID*)peekPayload.Data; + + Entity draggedEntity = new Entity(draggedId); + + allowDrop = draggedEntity.HasComponent(fieldType); + } + + if (allowDrop) + { + ImGuiPayloadPtr payload = ImGui.AcceptDragDropPayload("ENTITY"); + + if (payload.NativePtr != null) + { + UUID droppedId = *(UUID*)payload.Data; + + Entity draggedEntity = new Entity(droppedId); + + try + { + newValue = draggedEntity.GetComponent(fieldType); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + } + + ImGui.EndDragDropTarget(); + } + + return newValue; + } + + private static object ShowEntityDropTarget(string fieldName, Type fieldType, object currentValue) + { + object newValue = DidNotChange; + + ImGui.Text($"{fieldName}: "); + ImGui.SameLine(); + + string entityName = "None"; + + Entity entity = currentValue as Entity; + + if (entity != null) + { + entityName = entity.Name; + } + + Type actualType = entity?.GetType(); + + entityName += $" ({(actualType ?? fieldType).Name})"; + + if (ImGui.Button(entityName)) + { + if (entity != null) + { + // TODO: Hightlight Entity in hierarchy + } + } + + if (ImGui.BeginDragDropTarget()) + { + ImGuiPayloadPtr peekPayload = ImGui.AcceptDragDropPayload("ENTITY", ImGuiDragDropFlags.AcceptPeekOnly); + + bool allowDrop = false; + + unsafe + { + if (peekPayload.NativePtr != null) + { + UUID draggedId = *(UUID*)peekPayload.Data; + + Entity draggedEntity = new Entity(draggedId); + + // TODO: Check if entity is valid? + + if (fieldType == typeof(Entity)) + { + allowDrop = true; + } + else + { + Entity scriptInstance = Entity.GetScriptReference(draggedId, typeof(Entity)); + + allowDrop = fieldType.IsInstanceOfType(scriptInstance); + } + } + + if (allowDrop) + { + ImGuiPayloadPtr payload = ImGui.AcceptDragDropPayload("ENTITY"); + + if (payload.NativePtr != null) + { + UUID droppedId = *(UUID*)payload.Data; + + if (fieldType == typeof(Entity)) + { + newValue = new Entity(droppedId); + } + else + { + newValue = Entity.GetScriptReference(droppedId, typeof(Entity)); + } + } + } + } + + ImGui.EndDragDropTarget(); + } + + ImGui.SameLine(); + + if (ImGui.Button("...")) + { + // TODO: Show entity selector + } + + ImGuiExtension.AttachTooltip("Search entity..."); + + return newValue; + } + private static object ShowStringEditor(object reference, Type fieldType, string fieldName, IEnumerable attributes) { object newValue = DidNotChange; diff --git a/ScriptCore/Entity.cs b/ScriptCore/Entity.cs index af900d0..ced085e 100644 --- a/ScriptCore/Entity.cs +++ b/ScriptCore/Entity.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Reflection; using System.Runtime.CompilerServices; using GlitchyEngine.Core; using GlitchyEngine.Extensions; @@ -11,6 +12,15 @@ namespace GlitchyEngine; /// public class Entity : EngineObject { + /// + /// Gets or sets the name of the entity. + /// + public string Name + { + get => ScriptGlue.Entity_GetName(_uuid); + set => ScriptGlue.Entity_SetName(_uuid, value); + } + /// /// Only to be called by the engine. Don't call this constructor yourself, it will not result in a valid entity. /// If you want to create a new entity use or @@ -102,7 +112,12 @@ public class Entity : EngineObject if (HasComponent(componentType)) { - return Activator.CreateInstance(componentType, true, _uuid) as Component; + Component component = Activator.CreateInstance(componentType, true) as Component; + + if (component != null) + component._uuid = _uuid; + + return component; } return null; @@ -312,13 +327,13 @@ public class Entity : EngineObject /// The id of the entity whose script instance shall be returned. /// The type of the script. /// The script instance or null. - internal static object GetScriptReference(UUID id, Type type) + internal static Entity GetScriptReference(UUID id, Type type) { - Debug.Assert(type.IsSubclassOf(typeof(Entity))); + Debug.Assert(typeof(Entity).IsAssignableFrom(type)); ScriptGlue.Entity_GetScriptInstance(id, out object scriptInstance); - return scriptInstance; + return scriptInstance as Entity; } /// diff --git a/ScriptCore/ScriptGlue.cs b/ScriptCore/ScriptGlue.cs index e49a338..0eb40e8 100644 --- a/ScriptCore/ScriptGlue.cs +++ b/ScriptCore/ScriptGlue.cs @@ -46,6 +46,12 @@ internal static class ScriptGlue [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void Entity_RemoveScript(UUID entityId); + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern string Entity_GetName(UUID entityId); + + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern string Entity_SetName(UUID entityId, string name); + #endregion Entity #region TransformComponent