From cb4ef1c85321da5a1eb039c1653c50d6b430d6d1 Mon Sep 17 00:00:00 2001 From: Mikhail <99481254+DCFApixels@users.noreply.github.com> Date: Thu, 7 Mar 2024 21:22:48 +0800 Subject: [PATCH] add runtime editing --- src/Connectors/EcsWorldProvider.cs | 32 +++++-- .../Editor/EntityTemplateEditor.cs | 36 +++---- src/Internal/Editor/EcsGUI.cs | 34 ++++++- src/Internal/Editor/UnityEditorUtility.cs | 94 ++++++++++++++++++- 4 files changed, 169 insertions(+), 27 deletions(-) diff --git a/src/Connectors/EcsWorldProvider.cs b/src/Connectors/EcsWorldProvider.cs index f01c77c..1f044fa 100644 --- a/src/Connectors/EcsWorldProvider.cs +++ b/src/Connectors/EcsWorldProvider.cs @@ -24,25 +24,45 @@ namespace DCFApixels.DragonECS [Header("Default Configs")] [Header("Entites")] [SerializeField] - private int EntitiesCapacity = EcsWorldConfig.Default.EntitiesCapacity; + private int _entitiesCapacity = EcsWorldConfig.Default.EntitiesCapacity; [Header("Groups")] [SerializeField] - private int GroupCapacity = EcsWorldConfig.Default.GroupCapacity; + private int _groupCapacity = EcsWorldConfig.Default.GroupCapacity; [Header("Pools/Components")] [SerializeField] - private int PoolsCapacity = EcsWorldConfig.Default.PoolsCapacity; + private int _poolsCapacity = EcsWorldConfig.Default.PoolsCapacity; [SerializeField] - private int PoolComponentsCapacity = EcsWorldConfig.Default.PoolComponentsCapacity; + private int _poolComponentsCapacity = EcsWorldConfig.Default.PoolComponentsCapacity; [SerializeField] - private int PoolRecycledComponentsCapacity = EcsWorldConfig.Default.PoolRecycledComponentsCapacity; + private int _poolRecycledComponentsCapacity = EcsWorldConfig.Default.PoolRecycledComponentsCapacity; #region Properties public sealed override bool IsEmpty { get { return _world == null; } } + public int EntitiesCapacity + { + get { return _entitiesCapacity; } + } + public int GroupCapacity + { + get { return _groupCapacity; } + } + public int PoolsCapacity + { + get { return _poolsCapacity; } + } + public int PoolComponentsCapacity + { + get { return _poolComponentsCapacity; } + } + public int PoolRecycledComponentsCapacity + { + get { return _poolRecycledComponentsCapacity; } + } #endregion #region Methods @@ -94,7 +114,7 @@ namespace DCFApixels.DragonECS #region Events protected virtual TWorld BuildWorld() { - EcsWorldConfig config = new EcsWorldConfig(EntitiesCapacity, GroupCapacity, PoolsCapacity, PoolComponentsCapacity, PoolRecycledComponentsCapacity); + EcsWorldConfig config = new EcsWorldConfig(_entitiesCapacity, _groupCapacity, _poolsCapacity, _poolComponentsCapacity, _poolRecycledComponentsCapacity); ConfigContainer configs = new ConfigContainer().Set(config); return (TWorld)Activator.CreateInstance(typeof(TWorld), new object[] { configs, _worldID }); } diff --git a/src/EntityTemplate/Editor/EntityTemplateEditor.cs b/src/EntityTemplate/Editor/EntityTemplateEditor.cs index f517417..054fdba 100644 --- a/src/EntityTemplate/Editor/EntityTemplateEditor.cs +++ b/src/EntityTemplate/Editor/EntityTemplateEditor.cs @@ -12,32 +12,32 @@ namespace DCFApixels.DragonECS.Unity.Editors private static readonly Rect RemoveButtonRect = new Rect(0f, 0f, 17f, 19f); private static readonly Rect TooltipIconRect = new Rect(0f, 0f, 21f, 15f); - private GUIStyle removeButtonStyle; - private GenericMenu genericMenu; + private GUIStyle _removeButtonStyle; + private GenericMenu _genericMenu; private bool _isInit = false; #region Init private void Init() { - if (genericMenu == null) { _isInit = false; } + if (_genericMenu == null) { _isInit = false; } if (_isInit) { return; } var tmpstylebase = UnityEditorUtility.GetStyle(new Color(0.9f, 0f, 0.22f), 0.5f); var tmpStyle = UnityEditorUtility.GetStyle(new Color(1f, 0.5f, 0.7f), 0.5f); - removeButtonStyle = new GUIStyle(EditorStyles.linkLabel); - removeButtonStyle.alignment = TextAnchor.MiddleCenter; + _removeButtonStyle = new GUIStyle(EditorStyles.linkLabel); + _removeButtonStyle.alignment = TextAnchor.MiddleCenter; - removeButtonStyle.normal = tmpstylebase.normal; - removeButtonStyle.hover = tmpStyle.normal; - removeButtonStyle.active = tmpStyle.normal; - removeButtonStyle.focused = tmpStyle.normal; + _removeButtonStyle.normal = tmpstylebase.normal; + _removeButtonStyle.hover = tmpStyle.normal; + _removeButtonStyle.active = tmpStyle.normal; + _removeButtonStyle.focused = tmpStyle.normal; - removeButtonStyle.padding = new RectOffset(0, 0, 0, 0); - removeButtonStyle.margin = new RectOffset(0, 0, 0, 0); - removeButtonStyle.border = new RectOffset(0, 0, 0, 0); + _removeButtonStyle.padding = new RectOffset(0, 0, 0, 0); + _removeButtonStyle.margin = new RectOffset(0, 0, 0, 0); + _removeButtonStyle.border = new RectOffset(0, 0, 0, 0); - genericMenu = new GenericMenu(); + _genericMenu = new GenericMenu(); var componentTemplateDummies = ComponentTemplateTypeCache.Dummies; foreach (var dummy in componentTemplateDummies) @@ -56,7 +56,7 @@ namespace DCFApixels.DragonECS.Unity.Editors { name = $"{name} {EcsUnityConsts.INFO_MARK}"; } - genericMenu.AddItem(new GUIContent(name, description), false, OnAddComponent, dummy); + _genericMenu.AddItem(new GUIContent(name, description), false, OnAddComponent, dummy); } _isInit = true; @@ -122,7 +122,7 @@ namespace DCFApixels.DragonECS.Unity.Editors if (GUILayout.Button("Add Component", GUILayout.Height(24f))) { Init(); - genericMenu.ShowAsContext(); + _genericMenu.ShowAsContext(); } } private void DrawFooter(ITemplateInternal target) @@ -167,8 +167,6 @@ namespace DCFApixels.DragonECS.Unity.Editors string description = meta.Description; Color panelColor = meta.Color.ToUnityColor().Desaturate(EscEditorConsts.COMPONENT_DRAWER_DESATURATE); - Rect removeButtonRect = GUILayoutUtility.GetLastRect(); - //GUIContent label = new GUIContent(name); GUIContent label = UnityEditorUtility.GetLabel(name); bool isEmpty = componentType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Length <= 0; @@ -176,6 +174,8 @@ namespace DCFApixels.DragonECS.Unity.Editors Color alphaPanelColor = panelColor; alphaPanelColor.a = EscEditorConsts.COMPONENT_DRAWER_ALPHA; + Rect removeButtonRect = GUILayoutUtility.GetLastRect(); + EditorGUI.BeginChangeCheck(); GUILayout.BeginVertical(UnityEditorUtility.GetStyle(alphaPanelColor)); @@ -231,7 +231,7 @@ namespace DCFApixels.DragonECS.Unity.Editors removeButtonRect.center = new Vector2(lastrect.xMax + removeButtonRect.width, lastrect.yMin + removeButtonRect.height / 2f); GUILayout.Label("", GUILayout.Width(removeButtonRect.width)); - if (GUI.Button(removeButtonRect, "x", removeButtonStyle)) + if (GUI.Button(removeButtonRect, "x", _removeButtonStyle)) { OnRemoveComponentAt(index); } diff --git a/src/Internal/Editor/EcsGUI.cs b/src/Internal/Editor/EcsGUI.cs index 433a0f8..41d866b 100644 --- a/src/Internal/Editor/EcsGUI.cs +++ b/src/Internal/Editor/EcsGUI.cs @@ -13,6 +13,8 @@ namespace DCFApixels.DragonECS.Unity.Editors internal readonly static Color GreenColor = new Color32(75, 255, 0, 255); internal readonly static Color RedColor = new Color32(255, 0, 75, 255); + private static readonly Rect RemoveButtonRect = new Rect(0f, 0f, 17f, 19f); + private static readonly Rect TooltipIconRect = new Rect(0f, 0f, 21f, 15f); //private static GUILayoutOption[] _defaultParams; //private static bool _isInit = false; //private static void Init() @@ -68,6 +70,13 @@ namespace DCFApixels.DragonECS.Unity.Editors } } GUILayout.EndVertical(); + + if (GUILayout.Button("Add Component", GUILayout.Height(24f))) + { + GenericMenu genericMenu = RuntimeComponentsUtility.GetAddComponentGenericMenu(world); + RuntimeComponentsUtility.CurrentEntityID = entityID; + genericMenu.ShowAsContext(); + } } private static readonly BindingFlags fieldFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static void DrawRuntimeComponent(int entityID, IEcsPool pool) @@ -77,15 +86,36 @@ namespace DCFApixels.DragonECS.Unity.Editors { object data = pool.GetRaw(entityID); Color panelColor = meta.Color.ToUnityColor().Desaturate(EscEditorConsts.COMPONENT_DRAWER_DESATURATE); + + float padding = EditorGUIUtility.standardVerticalSpacing; + Rect removeButtonRect = GUILayoutUtility.GetLastRect(); + GUILayout.BeginVertical(UnityEditorUtility.GetStyle(panelColor, EscEditorConsts.COMPONENT_DRAWER_ALPHA)); EditorGUI.BeginChangeCheck(); + bool isRemoveComponent = false; + removeButtonRect.yMin = removeButtonRect.yMax; + removeButtonRect.yMax += RemoveButtonRect.height; + removeButtonRect.xMin = removeButtonRect.xMax - RemoveButtonRect.width; + removeButtonRect.center += Vector2.up * padding * 2f; + if (GUI.Button(removeButtonRect, "x")) + { + isRemoveComponent = true; + } + Type componentType = pool.ComponentType; ExpandMatrix expandMatrix = ExpandMatrix.Take(componentType); bool changed = DrawRuntimeData(componentType, UnityEditorUtility.GetLabel(meta.Name), expandMatrix, data, out object resultData); - if (changed) + if (changed || isRemoveComponent) { - pool.SetRaw(entityID, resultData); + if (isRemoveComponent) + { + pool.Del(entityID); + } + else + { + pool.SetRaw(entityID, resultData); + } } GUILayout.EndVertical(); diff --git a/src/Internal/Editor/UnityEditorUtility.cs b/src/Internal/Editor/UnityEditorUtility.cs index 2ad3da0..5a4a43b 100644 --- a/src/Internal/Editor/UnityEditorUtility.cs +++ b/src/Internal/Editor/UnityEditorUtility.cs @@ -1,5 +1,7 @@ #if UNITY_EDITOR using DCFApixels.DragonECS.Unity.Internal; +using System; +using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using UnityEditor; @@ -156,5 +158,95 @@ namespace DCFApixels.DragonECS.Unity.Editors } #endregion } + + internal static class RuntimeComponentsUtility + { + public struct WorldData + { + public GenericMenu addComponentGenericMenu; + public int poolsCount; + public WorldData(GenericMenu addComponentGenericMenu, int poolsCount) + { + this.addComponentGenericMenu = addComponentGenericMenu; + this.poolsCount = poolsCount; + } + } + //world id + private static Dictionary _worldDatas = new Dictionary(); + + public static GenericMenu GetAddComponentGenericMenu(EcsWorld world) + { + if (_worldDatas.TryGetValue(world, out WorldData data)) + { + if (data.poolsCount != world.PoolsCount) + { + data = CreateWorldData(world); + _worldDatas[world] = data; + } + } + else + { + data = CreateWorldData(world); + _worldDatas[world] = data; + world.AddListener(new Listener(world)); + } + + return data.addComponentGenericMenu; + } + + private static WorldData CreateWorldData(EcsWorld world) + { + GenericMenu genericMenu = new GenericMenu(); + + var pools = world.AllPools; + for (int i = 0; i < world.PoolsCount; i++) + { + var pool = pools[i]; + if (pool.IsNullOrDummy()) + { + i--; + continue; + } + var meta = pool.ComponentType.ToMeta(); + + genericMenu.AddItem(new GUIContent(meta.Name, meta.Description), false, OnAddComponent, pool); + } + return new WorldData(genericMenu, world.PoolsCount); + } + + public static int CurrentEntityID = 0; + + private static void OnAddComponent(object userData) + { + IEcsPool pool = (IEcsPool)userData; + if (pool.World.IsUsed(CurrentEntityID) == false) + { + return; + } + if (pool.Has(CurrentEntityID) == false) + { + pool.AddRaw(CurrentEntityID, Activator.CreateInstance(pool.ComponentType)); + } + else + { + Debug.LogWarning($"Entity({CurrentEntityID}) already has component {EcsDebugUtility.GetGenericTypeName(pool.ComponentType)}."); + } + } + + private class Listener : IEcsWorldEventListener + { + private EcsWorld _world; + public Listener(EcsWorld world) + { + _world = world; + } + public void OnReleaseDelEntityBuffer(ReadOnlySpan buffer) { } + public void OnWorldDestroy() + { + _worldDatas.Remove(_world); + } + public void OnWorldResize(int newSize) { } + } + } } -#endif +#endif \ No newline at end of file