add QuerySnapshotWindow

This commit is contained in:
Mikhail 2026-04-16 15:15:08 +08:00
parent edc1190a40
commit f8b414d926
10 changed files with 324 additions and 32 deletions

View File

@ -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();
}
}

View File

@ -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<entlong> _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<QuerySnapshotWindow>("Query Snapshot");
newWin.Setup(entites);
newWin.ShowUtility();
if (UserSettingsPrefs.instance.IsPauseOnSnapshot)
{
Debug.Break();
}
}
private void Setup(EcsSpan entites)
{
_world = entites.World;
_list = new StructList<entlong>(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<EntityMonitor>(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<EntityLinksComponent>().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

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 09884db6f09e654498c72416a199e357
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;

View File

@ -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<char> 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();
//

View File

@ -19,6 +19,9 @@ namespace DCFApixels.DragonECS.Unity.Internal
public void Set(EcsWorld world)
{
_world = world;
#if UNITY_EDITOR
world.Get<DragonGUI.EntityLinksComponent>().SetWorldMonitor(this);
#endif
}
}
@ -130,5 +133,7 @@ namespace DCFApixels.DragonECS.Unity.Internal
_entityMonitorRef.Set(_world.GetEntityLong(entityID));
}
}
public void OnMigrateEntity(int entityID) { }
}
}

View File

@ -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;

View File

@ -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<EntityLinksComponent>().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<int> 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);
}

View File

@ -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;

View File

@ -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;