From f8b414d9262e16c1647c7f659bd9caa3e6d79373 Mon Sep 17 00:00:00 2001 From: Mikhail <99481254+DCFApixels@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:15:08 +0800 Subject: [PATCH] add QuerySnapshotWindow --- src/DebugUtils/Editor/UserSettingsPrefs.cs | 10 +- .../Monitors/Editor/QuerySnapshotWindow.cs | 234 ++++++++++++++++++ .../Editor/QuerySnapshotWindow.cs.meta | 11 + .../Monitors/Editor/WorldMonitorEditor.cs | 2 +- .../Editor/WorldQueriesMonitorEditor.cs | 40 ++- src/DebugUtils/Monitors/WorldMonitor.cs | 5 + src/Internal/Editor/DragonGUI.Layout.cs | 2 +- src/Internal/Editor/DragonGUI.cs | 45 +++- src/Internal/Utils/RectUtility.cs | 6 + .../DragonDocs/Editors/DragonDocsWindow.cs | 1 - 10 files changed, 324 insertions(+), 32 deletions(-) create mode 100644 src/DebugUtils/Monitors/Editor/QuerySnapshotWindow.cs create mode 100644 src/DebugUtils/Monitors/Editor/QuerySnapshotWindow.cs.meta diff --git a/src/DebugUtils/Editor/UserSettingsPrefs.cs b/src/DebugUtils/Editor/UserSettingsPrefs.cs index dbc6a8f..40f4b67 100644 --- a/src/DebugUtils/Editor/UserSettingsPrefs.cs +++ b/src/DebugUtils/Editor/UserSettingsPrefs.cs @@ -84,15 +84,15 @@ namespace DCFApixels.DragonECS.Unity.Editors } } [SerializeField] - private bool _isShowWrappedMemberMeta = true; - public bool IsShowWrappedMemberMeta + private bool _isPauseOnSnapshot = true; + public bool IsPauseOnSnapshot { - get => _isShowWrappedMemberMeta; + get => _isPauseOnSnapshot; set { - if (_isShowWrappedMemberMeta != value) + if (_isPauseOnSnapshot != value) { - _isShowWrappedMemberMeta = value; + _isPauseOnSnapshot = value; AutoSave(); } } diff --git a/src/DebugUtils/Monitors/Editor/QuerySnapshotWindow.cs b/src/DebugUtils/Monitors/Editor/QuerySnapshotWindow.cs new file mode 100644 index 0000000..223ad7c --- /dev/null +++ b/src/DebugUtils/Monitors/Editor/QuerySnapshotWindow.cs @@ -0,0 +1,234 @@ +#if UNITY_EDITOR +using DCFApixels.DragonECS.Core.Unchecked; +using DCFApixels.DragonECS.Unity.Internal; +using UnityEditor; +using UnityEngine; +using static DCFApixels.DragonECS.Unity.Editors.DragonGUI; + +namespace DCFApixels.DragonECS.Unity.Editors +{ + internal class QuerySnapshotWindow : EditorWindow + { + private EcsWorld _world; + private StructList _list; + + private readonly Color _selectionColor = new Color(0.12f, 0.5f, 1f, 0.40f); + private readonly Color _isAliveColor = new Color(0.2f, 0.6f, 1f); + private readonly Color _hoverColor = new Color(1f, 1f, 1f, 0.12f); + + public static void ShowNew(EcsSpan entites) + { + var newWin = CreateWindow("Query Snapshot"); + newWin.Setup(entites); + newWin.ShowUtility(); + + if (UserSettingsPrefs.instance.IsPauseOnSnapshot) + { + Debug.Break(); + } + } + private void Setup(EcsSpan entites) + { + _world = entites.World; + _list = new StructList(entites.Count); + _list._count = entites.Longs.ToArray(ref _list._items); + } + private Vector2 _scrollState; + private void OnGUI() + { + if (_world.IsDestroyed) { _world = null; } + if (_world == null) + { + Close(); + return; + } + int selectedEntity = -1; + var selectedGO = Selection.activeGameObject; + if (selectedGO != null && + selectedGO.TryGetComponent(out var selectedMonitor) && + selectedMonitor.Entity.TryUnpack(_world, out selectedEntity) == false) + { + selectedEntity = -1; + } + SelectEvent selectEvent = default; + + + var line = EditorGUIUtility.singleLineHeight; + var space = EditorGUIUtility.standardVerticalSpacing; + var step = line + space; + Event current = Event.current; + + if (hasFocus && current.type == EventType.KeyUp && current.isKey) + { + if (current.keyCode == KeyCode.DownArrow) + { + selectEvent.Type = SelectEventType.Down; + } + if (current.keyCode == KeyCode.UpArrow) + { + selectEvent.Type = SelectEventType.Up; + } + } + + var rect = position; + rect.x = 0; + rect.y = 0; + + Rect hyperlinkButtonRect; + Rect topLineRect; + (topLineRect, rect) = rect.VerticalSliceTop(line + space); + topLineRect = topLineRect.AddPadding(0, 0, 0, space); + (topLineRect, hyperlinkButtonRect) = topLineRect.HorizontalSliceRight(18f); + EditorGUI.IntField(topLineRect, "World: ", _world.ID); + using (DragonGUI.SetEnable(_world != null)) + { + DragonGUI.WorldHyperlinkButton(hyperlinkButtonRect, _world); + } + + (topLineRect, rect) = rect.VerticalSliceTop(line + space); + UserSettingsPrefs.instance.IsPauseOnSnapshot = EditorGUI.ToggleLeft(topLineRect, "Pause On Snapshot", UserSettingsPrefs.instance.IsPauseOnSnapshot); + + var viewRect = rect; + viewRect.x = 0; + viewRect.y = 0; + viewRect.height = (line + space) * _list.Count; + viewRect.xMax -= GUI.skin.verticalScrollbar.fixedWidth; + + + var lineRect = viewRect; + lineRect.height = line; + Rect statusR; + (statusR, lineRect) = lineRect.HorizontalSliceLeft(3f); + + _scrollState = GUI.BeginScrollView(rect, _scrollState, viewRect, false, true); + var checkRect = rect; + checkRect.position = Vector2.zero; + + bool foundSelected = false; + for (int i = 0; i < _list.Count; i++) + { + EntitySlotInfo entity = (EntitySlotInfo)_list[i]; + bool isAlive = _world.IsAlive(entity.id, entity.gen); + bool selected = selectedEntity == entity.id && isAlive; + foundSelected |= selected; + bool isClick = false; + + + bool visible = lineRect.Overlaps(checkRect.AddOffset(_scrollState)); + if (visible) + { + using (DragonGUI.SetAlpha(0)) { GUI.Label(lineRect, string.Empty, GUI.skin.button); } + if (DragonGUI.HitTest(lineRect)) + { + EditorGUI.DrawRect(lineRect, _hoverColor); + if (current.type == EventType.MouseUp) + { + isClick = true; + } + } + if (selected) + { + DragonGUI.DrawRect(lineRect, _selectionColor); + } + if (isAlive) + { + DragonGUI.DrawRect(statusR, _isAliveColor); + } + var (labelR, infoR) = lineRect.HorizontalSliceLeft(45f); + infoR.width = Mathf.Min(infoR.width, 200f); + var (lR, rR) = infoR.HorizontalSliceLerp(0.5f); + GUI.Label(labelR, "Entity", GUI.skin.label); + EditorGUI.IntField(lR, entity.id, GUI.skin.label); + EditorGUI.IntField(rR, entity.gen, GUI.skin.label); + + if (isClick && isAlive) + { + selectEvent.Type = SelectEventType.Click; + selectEvent.EntityID = entity.id; + selectEvent.Index = i; + } + } + + if (isAlive) + { + switch (selectEvent.Type) + { + case SelectEventType.Up: + { + if (foundSelected == false) + { + selectEvent.EntityID = entity.id; + selectEvent.Index = i; + } + } + break; + case SelectEventType.Down: + { + if (foundSelected && selected == false && selectEvent.EntityID == 0) + { + selectEvent.EntityID = entity.id; + selectEvent.Index = i; + } + } + break; + } + } + + lineRect.y += step; + statusR.y += step; + } + GUI.EndScrollView(); + + + if (selectEvent.IsSelected && selectedEntity != selectEvent.EntityID) + { + float top = selectEvent.Index * step; + float bottom = top + step; + + if (top < _scrollState.y) + { + _scrollState.y = top; + } + else if (bottom > _scrollState.y + checkRect.height) + { + _scrollState.y = bottom - checkRect.height; + } + GUIUtility.keyboardControl = 0; + + SelectEntity(selectEvent.EntityID); + } + + + if (selectEvent.IsSelected) + { + Repaint(); + return; + } + } + + private void SelectEntity(int entityID) + { + var monitor = _world.Get().GetMonitorLink(entityID); + EditorGUIUtility.PingObject(monitor); + Selection.activeObject = monitor; + } + private void OnDestroy() { } + + + private struct SelectEvent + { + public SelectEventType Type; + public int EntityID; + public int Index; + public bool IsSelected { get { return Type != SelectEventType.None && EntityID != 0; } } + } + private enum SelectEventType + { + None, + Click, + Up, + Down, + } + } +} +#endif \ No newline at end of file diff --git a/src/DebugUtils/Monitors/Editor/QuerySnapshotWindow.cs.meta b/src/DebugUtils/Monitors/Editor/QuerySnapshotWindow.cs.meta new file mode 100644 index 0000000..b9a8d0f --- /dev/null +++ b/src/DebugUtils/Monitors/Editor/QuerySnapshotWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 09884db6f09e654498c72416a199e357 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/DebugUtils/Monitors/Editor/WorldMonitorEditor.cs b/src/DebugUtils/Monitors/Editor/WorldMonitorEditor.cs index e2fab01..3cbf1a9 100644 --- a/src/DebugUtils/Monitors/Editor/WorldMonitorEditor.cs +++ b/src/DebugUtils/Monitors/Editor/WorldMonitorEditor.cs @@ -1,5 +1,5 @@ #if UNITY_EDITOR -using DCFApixels.DragonECS.UncheckedCore; +using DCFApixels.DragonECS.Core.Unchecked; using DCFApixels.DragonECS.Unity.Internal; using System.Text; using UnityEditor; diff --git a/src/DebugUtils/Monitors/Editor/WorldQueriesMonitorEditor.cs b/src/DebugUtils/Monitors/Editor/WorldQueriesMonitorEditor.cs index b26e510..f17133c 100644 --- a/src/DebugUtils/Monitors/Editor/WorldQueriesMonitorEditor.cs +++ b/src/DebugUtils/Monitors/Editor/WorldQueriesMonitorEditor.cs @@ -23,7 +23,7 @@ namespace DCFApixels.DragonECS.Unity.Editors _pattern = pattern ?? throw new ArgumentNullException(nameof(pattern)); _separator = separator; } - public Enumerator GetEnumerator() => new Enumerator(_pattern, _separator); + public Enumerator GetEnumerator() { return new Enumerator(_pattern, _separator); } public ref struct Enumerator { private readonly string _pattern; @@ -43,25 +43,24 @@ namespace DCFApixels.DragonECS.Unity.Editors public ReadOnlySpan Current { - get - { - if (_currentStart < 0) - throw new InvalidOperationException("Enumeration not started or already finished"); - return _pattern.AsSpan(_currentStart, _currentLength); - } + get { return _pattern.AsSpan(_currentStart, _currentLength); } } public bool MoveNext() { if (_pattern == null || _start > _pattern.Length) + { return false; + } int len = _pattern.Length; while (_start <= len) { int i = _start; while (i < len && _pattern[i] != _separator) + { i++; + } int subLen = i - _start; if (subLen > 0) // возвращаем только непустые подстроки @@ -143,8 +142,10 @@ namespace DCFApixels.DragonECS.Unity.Editors var incs = query.Mask.Incs; var excs = query.Mask.Excs; + var anys = query.Mask.Anys; var incsI = 0; var excsI = 0; + var anysI = 0; for (int j = 0; j < allpools.Length; j++) { var pool = allpools[j]; @@ -165,6 +166,13 @@ namespace DCFApixels.DragonECS.Unity.Editors excsI++; continue; } + + if (anysI < anys.Length && anys[anysI] == j) + { + sb.Append($"~"); + anysI++; + continue; + } } } sb.Append("\r\n"); @@ -194,8 +202,9 @@ namespace DCFApixels.DragonECS.Unity.Editors } } - EditorGUILayout.IntField("Count: ", executors.Count); + GUILayout.Space(10f); + EditorGUILayout.IntField("Total Count: ", executors.Count); HasSearchPattern = true; if (string.IsNullOrEmpty(Target.SearchPattern)) @@ -203,13 +212,15 @@ namespace DCFApixels.DragonECS.Unity.Editors Target.SearchPattern = string.Empty; HasSearchPattern = false; } + GUILayout.Space(10f); - Target.SearchPattern = EditorGUILayout.TextField("Search: ", Target.SearchPattern); - - + Target.SearchPattern = EditorGUILayout.TextField(Target.SearchPattern, EditorStyles.toolbarSearchField); string searchPattern = Target.SearchPattern; - GUILayout.Space(20); + var r = GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, 3f); + DragonGUI.DrawRect(r, Color.white.SetAlpha(0.5f)); + GUILayout.Space(10f); + //using (EcsGUI.Layout.BeginVertical(UnityEditorUtility.GetStyle(Color.black, 0.2f))) { @@ -273,10 +284,15 @@ namespace DCFApixels.DragonECS.Unity.Editors var mask = executor.Mask; DrawConstraint("+", mask.Incs); DrawConstraint("-", mask.Excs); + DrawConstraint("~", mask.Anys); } EditorGUILayout.LongField("Version: ", executor.Version); EditorGUILayout.IntField("Entites Count: ", executor.LastCachedCount); + if (GUILayout.Button("Snapshot")) + { + QuerySnapshotWindow.ShowNew(executor.Snapshot()); + } //var rect = GUILayoutUtility.GetLastRect(); // diff --git a/src/DebugUtils/Monitors/WorldMonitor.cs b/src/DebugUtils/Monitors/WorldMonitor.cs index 627e5e6..8ac9016 100644 --- a/src/DebugUtils/Monitors/WorldMonitor.cs +++ b/src/DebugUtils/Monitors/WorldMonitor.cs @@ -19,6 +19,9 @@ namespace DCFApixels.DragonECS.Unity.Internal public void Set(EcsWorld world) { _world = world; +#if UNITY_EDITOR + world.Get().SetWorldMonitor(this); +#endif } } @@ -130,5 +133,7 @@ namespace DCFApixels.DragonECS.Unity.Internal _entityMonitorRef.Set(_world.GetEntityLong(entityID)); } } + + public void OnMigrateEntity(int entityID) { } } } diff --git a/src/Internal/Editor/DragonGUI.Layout.cs b/src/Internal/Editor/DragonGUI.Layout.cs index 8e397f9..f2b740e 100644 --- a/src/Internal/Editor/DragonGUI.Layout.cs +++ b/src/Internal/Editor/DragonGUI.Layout.cs @@ -121,7 +121,7 @@ namespace DCFApixels.DragonECS.Unity.Editors { EntityField(UnityEditorUtility.GetLabel(label), entity); } - public static unsafe void EntityField(GUIContent label, entlong entity) + public static void EntityField(GUIContent label, entlong entity) { float width = EditorGUIUtility.currentViewWidth; float height = EntityBarHeight; diff --git a/src/Internal/Editor/DragonGUI.cs b/src/Internal/Editor/DragonGUI.cs index 0fbfc9b..a79ff42 100644 --- a/src/Internal/Editor/DragonGUI.cs +++ b/src/Internal/Editor/DragonGUI.cs @@ -464,7 +464,25 @@ namespace DCFApixels.DragonECS.Unity.Editors DrawIcon(position, Icons.Instance.HelpIcon, 0, description); } } + public static void WorldHyperlinkButton(Rect position, EcsWorld world) + { + var current = Event.current; + var hover = IconHoverScan(position, current); + var click = IconButton(position, Icons.Instance.HyperlinkIcon, 2f, string.Empty); + if (GUI.enabled) + { + if (click) + { + var monitor = world.Get().GetWorldMonitor(); + if (monitor != null) + { + EditorGUIUtility.PingObject(monitor); + Selection.activeObject = monitor; + } + } + } + } public static void EntityHyperlinkButton(Rect position, EcsWorld world, int entityID) { var current = Event.current; @@ -565,13 +583,15 @@ namespace DCFApixels.DragonECS.Unity.Editors { private readonly Storage _storage; private EntityLinksComponent(Storage storage) { _storage = storage; } - public void SetConnectLink(int entityID, EcsEntityConnect link) { _storage.links[entityID].connect = link; } - public void SetMonitorLink(int entityID, EntityMonitor link) { _storage.links[entityID].monitor = link; } - public EcsEntityConnect GetConnectLink(int entityID) { return _storage.links[entityID].connect; } - public EntityMonitor GetMonitorLink(int entityID) { return _storage.links[entityID].monitor; } + public void SetWorldMonitor(WorldMonitor monitor) { _storage.WorldMonitor = monitor; } + public WorldMonitor GetWorldMonitor() { return _storage.WorldMonitor; } + public void SetConnectLink(int entityID, EcsEntityConnect link) { _storage.Links[entityID].connect = link; } + public void SetMonitorLink(int entityID, EntityMonitor link) { _storage.Links[entityID].monitor = link; } + public EcsEntityConnect GetConnectLink(int entityID) { return _storage.Links[entityID].connect; } + public EntityMonitor GetMonitorLink(int entityID) { return _storage.Links[entityID].monitor; } public UnityEngine.Object GetLink(int entityID) { - ref var links = ref _storage.links[entityID]; + ref var links = ref _storage.Links[entityID]; if (links.connect != null) { return links.connect; @@ -588,15 +608,16 @@ namespace DCFApixels.DragonECS.Unity.Editors } private class Storage : IEcsWorldEventListener { - private readonly EcsWorld _world; - public (EcsEntityConnect connect, EntityMonitor monitor)[] links; + public readonly EcsWorld World; + public WorldMonitor WorldMonitor; + public (EcsEntityConnect connect, EntityMonitor monitor)[] Links; public Storage(EcsWorld world) { - _world = world; - _world.AddListener(this); - links = new (EcsEntityConnect, EntityMonitor)[_world.Capacity]; + World = world; + World.AddListener(this); + Links = new (EcsEntityConnect, EntityMonitor)[World.Capacity]; } - public void OnWorldResize(int newSize) { Array.Resize(ref links, newSize); } + public void OnWorldResize(int newSize) { Array.Resize(ref Links, newSize); } public void OnReleaseDelEntityBuffer(ReadOnlySpan buffer) { } public void OnWorldDestroy() { } } @@ -605,7 +626,7 @@ namespace DCFApixels.DragonECS.Unity.Editors { EntityField(position, DragonGUIContent.Empty, entity); } - public static unsafe void EntityField(Rect position, DragonGUIContent label, entlong entity) + public static void EntityField(Rect position, DragonGUIContent label, entlong entity) { EntityField(position, label, (EntitySlotInfo)entity); } diff --git a/src/Internal/Utils/RectUtility.cs b/src/Internal/Utils/RectUtility.cs index 87a16a5..d8e0962 100644 --- a/src/Internal/Utils/RectUtility.cs +++ b/src/Internal/Utils/RectUtility.cs @@ -6,6 +6,12 @@ namespace DCFApixels.DragonECS.Unity.Internal { internal static class RectUtility { + public static Rect AddOffset(in this Rect rect, Vector2 offset) + { + var r = rect; + r.position += offset; + return r; + } public static (Rect, Rect) HorizontalSliceLerp(in this Rect rect, float t) { Rect l = rect; diff --git a/src/Tools/DragonDocs/Editors/DragonDocsWindow.cs b/src/Tools/DragonDocs/Editors/DragonDocsWindow.cs index 4c93499..2144144 100644 --- a/src/Tools/DragonDocs/Editors/DragonDocsWindow.cs +++ b/src/Tools/DragonDocs/Editors/DragonDocsWindow.cs @@ -1,7 +1,6 @@ #if UNITY_EDITOR using DCFApixels.DragonECS.Unity.Editors; using DCFApixels.DragonECS.Unity.Internal; -using DCFApixels.DragonECS.Unity.RefRepairer.Editors; using System; using UnityEditor; using UnityEngine;