com.artromskiy.deltaecs.unity 0.0.64 → 0.0.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.66
4
+
5
+ - Published with the matching DeltaECS NuGet packages.
6
+
7
+ ## 0.0.65
8
+
9
+ - Published with the matching DeltaECS NuGet packages.
10
+
3
11
  ## 0.0.64
4
12
 
5
13
  - Published with the matching DeltaECS NuGet packages.
@@ -19,25 +19,31 @@ namespace Delta.ECS.Unity.Editor
19
19
  internal sealed class HierarchyNodeHandler : HierarchyNodeTypeHandler
20
20
  {
21
21
  private const double RuntimeHierarchyRefreshInterval = 0.5d;
22
- private static readonly ComponentId[] NoRequiredComponents = Array.Empty<ComponentId>();
23
22
  private static readonly SchemaId HiddenEntitySchema = new(ViewComponentSchemaIds.HideFromEntityHierarchy);
24
23
  private static double _nextRuntimeHierarchyRefresh;
25
24
  private static bool _wasPlaying;
25
+ private static bool _authoringsDirty = true;
26
+ private static SceneAuthoring[] _authorings = Array.Empty<SceneAuthoring>();
26
27
  private static readonly HashSet<HierarchyNodeHandler> Instances = new();
27
28
 
28
29
  private readonly Dictionary<EntityKey, EntityNode> _entityNodes = new();
30
+ private readonly Dictionary<SceneAuthoring, RuntimeHierarchyState> _runtimeStates = new();
29
31
  private readonly HashSet<EntityKey> _activeEntityKeys = new();
30
32
  private readonly List<EntityKey> _removedEntityKeys = new();
31
33
  private readonly Dictionary<HierarchyNode, NodeReference> _nodes = new();
32
34
  private readonly HashSet<HierarchyView> _boundViews = new();
33
35
  private readonly Dictionary<HierarchyView, BlockedDrag> _blockedDrags = new();
34
36
  private readonly Dictionary<SceneAuthoring, Action<SceneDataChange>> _sceneDataSubscriptions = new();
37
+ private readonly HashSet<SceneAuthoring> _loadedAuthorings = new();
38
+ private readonly List<SceneAuthoring> _removedAuthorings = new();
35
39
 
36
40
  static HierarchyNodeHandler()
37
41
  {
38
42
  HierarchyWindow.BindView += BindHierarchyView;
39
43
  EditorApplication.delayCall += BindExistingHierarchyViews;
40
44
  EditorApplication.update += UpdateRuntimeHierarchy;
45
+ SceneAuthoring.HierarchyChanged += InvalidateAuthorings;
46
+ SceneWorld.LiveWorldsChanged += InvalidateAuthorings;
41
47
  }
42
48
 
43
49
  public override string GetNodeTypeName() => nameof(HierarchyNodeHandler);
@@ -97,7 +103,14 @@ namespace Delta.ECS.Unity.Editor
97
103
  pair.Key.SceneData.Changed -= pair.Value;
98
104
  }
99
105
  }
106
+ foreach (RuntimeHierarchyState state in _runtimeStates.Values)
107
+ {
108
+ state.Dispose();
109
+ }
110
+ _runtimeStates.Clear();
100
111
  _sceneDataSubscriptions.Clear();
112
+ _loadedAuthorings.Clear();
113
+ _removedAuthorings.Clear();
101
114
  _entityNodes.Clear();
102
115
  _activeEntityKeys.Clear();
103
116
  _removedEntityKeys.Clear();
@@ -147,6 +160,8 @@ namespace Delta.ECS.Unity.Editor
147
160
 
148
161
  internal static void RefreshAll()
149
162
  {
163
+ InvalidateAuthorings();
164
+ GetAuthorings();
150
165
  var changed = false;
151
166
  foreach (var instance in Instances)
152
167
  {
@@ -203,7 +218,22 @@ namespace Delta.ECS.Unity.Editor
203
218
  }
204
219
 
205
220
  _nextRuntimeHierarchyRefresh = EditorApplication.timeSinceStartup + RuntimeHierarchyRefreshInterval;
206
- RefreshAll();
221
+ if (_authoringsDirty)
222
+ {
223
+ RefreshAll();
224
+ }
225
+ else
226
+ {
227
+ bool changed = false;
228
+ foreach (HierarchyNodeHandler instance in Instances)
229
+ {
230
+ changed |= instance.RefreshRuntimeChanges();
231
+ }
232
+ if (changed)
233
+ {
234
+ EditorApplication.RepaintHierarchyWindow();
235
+ }
236
+ }
207
237
  return;
208
238
  }
209
239
 
@@ -214,6 +244,18 @@ namespace Delta.ECS.Unity.Editor
214
244
  }
215
245
  }
216
246
 
247
+ private static void InvalidateAuthorings() => _authoringsDirty = true;
248
+
249
+ private static SceneAuthoring[] GetAuthorings()
250
+ {
251
+ if (_authoringsDirty)
252
+ {
253
+ _authorings = Resources.FindObjectsOfTypeAll<SceneAuthoring>();
254
+ _authoringsDirty = false;
255
+ }
256
+ return _authorings;
257
+ }
258
+
217
259
  private static void BindHierarchyView(HierarchyWindow window, HierarchyView view)
218
260
  {
219
261
  if (view == null)
@@ -429,19 +471,46 @@ namespace Delta.ECS.Unity.Editor
429
471
  }
430
472
  }
431
473
 
432
- private bool Reconcile()
474
+ private bool Reconcile(bool changed = false)
433
475
  {
434
476
  if (!Hierarchy.IsCreated)
435
477
  {
436
478
  return false;
437
479
  }
438
480
 
439
- var changed = false;
440
481
  _activeEntityKeys.Clear();
441
482
 
442
483
  var sceneHandler = Hierarchy.GetNodeTypeHandler<HierarchySceneHandler>();
443
- var authoringsInScenes = Resources.FindObjectsOfTypeAll<SceneAuthoring>();
484
+ var authoringsInScenes = GetAuthorings();
444
485
  SynchronizeSceneSubscriptions(authoringsInScenes);
486
+ foreach (var pair in _runtimeStates)
487
+ {
488
+ if (!EditorApplication.isPlaying || !pair.Value.World.IsBound
489
+ || !SceneWorld.TryFindLiveWorld(pair.Key.SceneData, out SceneWorld liveWorld)
490
+ || !ReferenceEquals(liveWorld, pair.Value.World))
491
+ {
492
+ _removedAuthorings.Add(pair.Key);
493
+ }
494
+ }
495
+ foreach (SceneAuthoring authoring in _removedAuthorings)
496
+ {
497
+ _runtimeStates[authoring].Dispose();
498
+ _runtimeStates.Remove(authoring);
499
+ }
500
+ _removedAuthorings.Clear();
501
+ // Runtime nodes survive authored-data reconciliation. Their own dirty queue
502
+ // removes destroyed, hidden or newly bound entities below.
503
+ foreach (var pair in _entityNodes)
504
+ {
505
+ NodeReference reference = pair.Value.Reference;
506
+ if (reference.IsRuntimeOnly && reference.RuntimeWorld.IsBound
507
+ && _loadedAuthorings.Contains(reference.Authoring)
508
+ && _runtimeStates.TryGetValue(reference.Authoring, out RuntimeHierarchyState state)
509
+ && ReferenceEquals(state.World, reference.RuntimeWorld))
510
+ {
511
+ _activeEntityKeys.Add(pair.Key);
512
+ }
513
+ }
445
514
  for (var i = 0; i < authoringsInScenes.Length; i++)
446
515
  {
447
516
  var authoringHost = authoringsInScenes[i];
@@ -461,6 +530,7 @@ namespace Delta.ECS.Unity.Editor
461
530
  {
462
531
  SceneWorld.TryFindLiveWorld(authoringHost.SceneData, out runtimeWorld);
463
532
  }
533
+ runtimeWorld?.RefreshDestroyedViews();
464
534
 
465
535
  var entities = authoringHost.SceneData.Entities;
466
536
  for (var entityIndex = 0; entityIndex < entities.Count; entityIndex++)
@@ -481,8 +551,7 @@ namespace Delta.ECS.Unity.Editor
481
551
  : default;
482
552
  bool hasView = authoringHost.TryGetView(authoring.StableId, out _)
483
553
  || (runtimeWorld != null
484
- && runtimeWorld.World.IsAlive(runtimeEntity)
485
- && runtimeWorld.TryGetView(runtimeEntity, out _));
554
+ && runtimeWorld.HasRuntimeView(runtimeEntity));
486
555
  if (hasView || IsHiddenFromHierarchy(authoring, runtimeWorld, runtimeEntity))
487
556
  {
488
557
  continue;
@@ -553,27 +622,7 @@ namespace Delta.ECS.Unity.Editor
553
622
 
554
623
  for (var i = 0; i < _removedEntityKeys.Count; i++)
555
624
  {
556
- var key = _removedEntityKeys[i];
557
- var entityNode = _entityNodes[key];
558
- if (entityNode.Reference.IsRuntimeOnly)
559
- {
560
- HierarchySelection.ClearIfSelected(
561
- entityNode.Reference.RuntimeWorld,
562
- entityNode.Reference.RuntimeEntity);
563
- }
564
- else
565
- {
566
- HierarchySelection.ClearIfSelected(this, entityNode.Reference.Authoring, entityNode.Reference.StableId);
567
- }
568
- if (Hierarchy.Exists(entityNode.Node))
569
- {
570
- CommandList.Remove(entityNode.Node);
571
- }
572
-
573
- SelectionProxyBridge.Remove(this, entityNode.Node);
574
- _nodes.Remove(entityNode.Node);
575
- _entityNodes.Remove(key);
576
- changed = true;
625
+ changed |= RemoveEntityNode(_removedEntityKeys[i]);
577
626
  }
578
627
 
579
628
  if (CommandList.Size > 0)
@@ -594,54 +643,108 @@ namespace Delta.ECS.Unity.Editor
594
643
  HierarchyNode sceneNode,
595
644
  SceneWorld runtimeWorld)
596
645
  {
597
- Query query = runtimeWorld.World.WhereNone(runtimeWorld.HiddenEntityHierarchyComponentId)
598
- .WhereAll(NoRequiredComponents);
646
+ if (!_runtimeStates.TryGetValue(authoring, out RuntimeHierarchyState state)
647
+ || !ReferenceEquals(state.World, runtimeWorld))
648
+ {
649
+ state?.Dispose();
650
+ state = new RuntimeHierarchyState(runtimeWorld);
651
+ _runtimeStates[authoring] = state;
652
+ }
653
+ state.PrepareChanges();
654
+ return state.Synchronize(this, authoring, sceneNode, out _);
655
+ }
656
+
657
+ private bool RefreshRuntimeChanges()
658
+ {
659
+ if (!Hierarchy.IsCreated)
660
+ {
661
+ return false;
662
+ }
599
663
  bool changed = false;
600
- runtimeWorld.World.ForEachEntity(in query, entity =>
664
+ bool authoredChanged = false;
665
+ foreach (var pair in _runtimeStates)
601
666
  {
602
- if (runtimeWorld.TryGetStableId(entity, out _) || runtimeWorld.TryGetView(entity, out _))
667
+ RuntimeHierarchyState state = pair.Value;
668
+ if (!state.World.IsBound || !state.PrepareChanges())
603
669
  {
604
- return;
670
+ continue;
605
671
  }
606
-
607
- var key = new EntityKey(runtimeWorld, entity);
608
- _activeEntityKeys.Add(key);
609
- string name = $"ECS Entity {entity.Index}:{entity.Generation}";
610
- var reference = new NodeReference(authoring, null, runtimeWorld, entity);
611
- if (_entityNodes.TryGetValue(key, out EntityNode entityNode))
672
+ var sceneHandler = Hierarchy.GetNodeTypeHandler<HierarchySceneHandler>();
673
+ HierarchyNode sceneNode = sceneHandler.GetOrCreateNode(pair.Key.gameObject.scene);
674
+ if (sceneNode != HierarchyNode.Null)
612
675
  {
613
- if (entityNode.Reference.RuntimeWorld != reference.RuntimeWorld
614
- || entityNode.Reference.RuntimeEntity != reference.RuntimeEntity)
615
- {
616
- entityNode.Reference = reference;
617
- changed = true;
618
- }
619
-
620
- if (!string.Equals(entityNode.Name, name, StringComparison.Ordinal))
621
- {
622
- CommandList.SetName(entityNode.Node, name);
623
- entityNode.Name = name;
624
- changed = true;
625
- }
626
-
627
- _entityNodes[key] = entityNode;
628
- _nodes[entityNode.Node] = reference;
629
- return;
676
+ changed |= state.Synchronize(this, pair.Key, sceneNode, out bool dirtyAuthoring);
677
+ authoredChanged |= dirtyAuthoring;
630
678
  }
679
+ }
680
+ if (authoredChanged)
681
+ {
682
+ return Reconcile(changed);
683
+ }
684
+ if (!changed)
685
+ {
686
+ return false;
687
+ }
688
+ if (CommandList.Size > 0)
689
+ {
690
+ CommandList.Execute();
691
+ }
692
+ Hierarchy.Update();
693
+ return true;
694
+ }
631
695
 
632
- CommandList.Add(sceneNode, out HierarchyNode node);
633
- CommandList.SetName(node, name);
634
- _entityNodes.Add(key, new EntityNode(node, reference, name));
635
- _nodes.Add(node, reference);
636
- changed = true;
637
- }).Invoke();
696
+ private bool SynchronizeRuntimeEntity(SceneAuthoring authoring, HierarchyNode sceneNode,
697
+ SceneWorld runtimeWorld, Entity entity)
698
+ {
699
+ var key = new EntityKey(runtimeWorld, entity);
700
+ if (!runtimeWorld.World.IsAlive(entity)
701
+ || runtimeWorld.HasRuntimeView(entity)
702
+ || runtimeWorld.World.Has(entity, runtimeWorld.HiddenEntityHierarchyComponentId))
703
+ {
704
+ _activeEntityKeys.Remove(key);
705
+ return RemoveEntityNode(key);
706
+ }
707
+ _activeEntityKeys.Add(key);
708
+ if (_entityNodes.TryGetValue(key, out _))
709
+ {
710
+ return false;
711
+ }
712
+ string name = $"ECS Entity {entity.Index}:{entity.Generation}";
713
+ var reference = new NodeReference(authoring, null, runtimeWorld, entity);
714
+ CommandList.Add(sceneNode, out HierarchyNode node);
715
+ CommandList.SetName(node, name);
716
+ _entityNodes.Add(key, new EntityNode(node, reference, name));
717
+ _nodes.Add(node, reference);
718
+ return true;
719
+ }
638
720
 
639
- return changed;
721
+ private bool RemoveEntityNode(EntityKey key)
722
+ {
723
+ if (!_entityNodes.Remove(key, out EntityNode entityNode))
724
+ {
725
+ return false;
726
+ }
727
+ NodeReference reference = entityNode.Reference;
728
+ if (reference.IsRuntimeOnly)
729
+ {
730
+ HierarchySelection.ClearIfSelected(reference.RuntimeWorld, reference.RuntimeEntity);
731
+ }
732
+ else
733
+ {
734
+ HierarchySelection.ClearIfSelected(this, reference.Authoring, reference.StableId);
735
+ }
736
+ if (Hierarchy.Exists(entityNode.Node))
737
+ {
738
+ CommandList.Remove(entityNode.Node);
739
+ }
740
+ SelectionProxyBridge.Remove(this, entityNode.Node);
741
+ _nodes.Remove(entityNode.Node);
742
+ return true;
640
743
  }
641
744
 
642
745
  private void SynchronizeSceneSubscriptions(SceneAuthoring[] authorings)
643
746
  {
644
- var loadedAuthorings = new HashSet<SceneAuthoring>();
747
+ _loadedAuthorings.Clear();
645
748
  for (var i = 0; i < authorings.Length; i++)
646
749
  {
647
750
  var authoring = authorings[i];
@@ -650,7 +753,7 @@ namespace Delta.ECS.Unity.Editor
650
753
  continue;
651
754
  }
652
755
 
653
- loadedAuthorings.Add(authoring);
756
+ _loadedAuthorings.Add(authoring);
654
757
  if (!_sceneDataSubscriptions.ContainsKey(authoring))
655
758
  {
656
759
  var capturedAuthoring = authoring;
@@ -660,29 +763,35 @@ namespace Delta.ECS.Unity.Editor
660
763
  }
661
764
  }
662
765
 
663
- if (_sceneDataSubscriptions.Count == loadedAuthorings.Count)
766
+ if (_sceneDataSubscriptions.Count == _loadedAuthorings.Count)
664
767
  {
665
768
  return;
666
769
  }
667
770
 
668
- var removed = new List<SceneAuthoring>();
771
+ _removedAuthorings.Clear();
669
772
  foreach (var authoring in _sceneDataSubscriptions.Keys)
670
773
  {
671
- if (!loadedAuthorings.Contains(authoring))
774
+ if (!_loadedAuthorings.Contains(authoring))
672
775
  {
673
- removed.Add(authoring);
776
+ _removedAuthorings.Add(authoring);
674
777
  }
675
778
  }
676
779
 
677
- for (var i = 0; i < removed.Count; i++)
780
+ for (var i = 0; i < _removedAuthorings.Count; i++)
678
781
  {
679
- var authoring = removed[i];
782
+ var authoring = _removedAuthorings[i];
680
783
  if (authoring != null)
681
784
  {
682
785
  authoring.SceneData.Changed -= _sceneDataSubscriptions[authoring];
683
786
  }
684
787
  _sceneDataSubscriptions.Remove(authoring);
788
+ if (_runtimeStates.Remove(authoring, out RuntimeHierarchyState state))
789
+ {
790
+ state.Dispose();
791
+ }
685
792
  }
793
+
794
+ _removedAuthorings.Clear();
686
795
  }
687
796
 
688
797
  private void OnSceneDataChanged(SceneAuthoring authoring, SceneDataChange change)
@@ -880,6 +989,64 @@ namespace Delta.ECS.Unity.Editor
880
989
  }
881
990
  }
882
991
 
992
+ private sealed class RuntimeHierarchyState : IDisposable
993
+ {
994
+ private readonly HashSet<Entity> _dirtyEntities = new();
995
+ private readonly ComponentChangeOperation<HideFromEntityHierarchy> _hiddenChanges;
996
+ internal SceneWorld World { get; }
997
+
998
+ internal RuntimeHierarchyState(SceneWorld world)
999
+ {
1000
+ World = world;
1001
+ world.HierarchyEntitiesChanged += Queue;
1002
+ _hiddenChanges = world.World.ForEachChange<HideFromEntityHierarchy>(
1003
+ world.HiddenEntityHierarchyComponentId, OnHiddenChanged);
1004
+ // One snapshot, without tag-slot cache or repeated per-refresh operations.
1005
+ Query query = world.World.WhereNone(ReadOnlySpan<ComponentId>.Empty);
1006
+ world.World.ForEachEntity(in query, QueueInitialEntity).Invoke();
1007
+ }
1008
+
1009
+ public void Dispose() => World.HierarchyEntitiesChanged -= Queue;
1010
+
1011
+ internal bool PrepareChanges()
1012
+ {
1013
+ World.RefreshDestroyedViews();
1014
+ _hiddenChanges.Invoke();
1015
+ return _dirtyEntities.Count != 0;
1016
+ }
1017
+
1018
+ internal bool Synchronize(HierarchyNodeHandler handler, SceneAuthoring authoring,
1019
+ HierarchyNode sceneNode, out bool authoredChanged)
1020
+ {
1021
+ bool changed = false;
1022
+ authoredChanged = false;
1023
+ foreach (Entity entity in _dirtyEntities)
1024
+ {
1025
+ if (World.TryGetStableId(entity, out _))
1026
+ {
1027
+ authoredChanged = true;
1028
+ }
1029
+ else
1030
+ {
1031
+ changed |= handler.SynchronizeRuntimeEntity(authoring, sceneNode, World, entity);
1032
+ }
1033
+ }
1034
+ _dirtyEntities.Clear();
1035
+ return changed;
1036
+ }
1037
+
1038
+ private void QueueInitialEntity(EntityRef entity) => _dirtyEntities.Add(entity);
1039
+ private void OnHiddenChanged(Entity entity, ChangeKind kind, ref HideFromEntityHierarchy value)
1040
+ => _dirtyEntities.Add(entity);
1041
+ private void Queue(ReadOnlySpan<Entity> entities)
1042
+ {
1043
+ foreach (Entity entity in entities)
1044
+ {
1045
+ _dirtyEntities.Add(entity);
1046
+ }
1047
+ }
1048
+ }
1049
+
883
1050
  private struct BlockedDrag
884
1051
  {
885
1052
  public readonly int PointerId;
package/README.md CHANGED
@@ -18,7 +18,7 @@ Add the npm-compatible registry and package to the Unity project's
18
18
  }
19
19
  ],
20
20
  "dependencies": {
21
- "com.artromskiy.deltaecs.unity": "0.0.64"
21
+ "com.artromskiy.deltaecs.unity": "0.0.66"
22
22
  }
23
23
  }
24
24
  ```
@@ -24,6 +24,8 @@ namespace Delta.ECS.Unity
24
24
  [NonSerialized] private Dictionary<string, GameObject> _viewsByStableId;
25
25
  [NonSerialized] private Dictionary<UnityEngine.EntityId, string> _stableIdsByView;
26
26
 
27
+ internal static event Action HierarchyChanged;
28
+
27
29
  internal SceneData SceneData => _sceneData ??= new SceneData();
28
30
 
29
31
  internal static SceneAuthoring FindForScene(Scene scene)
@@ -120,15 +122,36 @@ namespace Delta.ECS.Unity
120
122
 
121
123
  InvalidateBindingIndex();
122
124
  EnsureBindingIndex();
125
+ HierarchyChanged?.Invoke();
123
126
  return true;
124
127
  }
125
128
 
126
129
  internal bool RemoveView(string stableId) => SetView(stableId, null);
127
130
 
131
+ internal void CollectDestroyedViews(List<string> stableIds)
132
+ {
133
+ EnsureBindingIndex();
134
+ foreach (var pair in _viewsByStableId)
135
+ {
136
+ if (pair.Value == null)
137
+ {
138
+ stableIds.Add(pair.Key);
139
+ }
140
+ }
141
+ foreach (string stableId in stableIds)
142
+ {
143
+ if (_viewsByStableId.Remove(stableId, out GameObject view) && view is not null)
144
+ {
145
+ _stableIdsByView.Remove(view.GetEntityId());
146
+ }
147
+ }
148
+ }
149
+
128
150
  private void OnValidate()
129
151
  {
130
152
  SceneData.EnsureStableIds();
131
153
  InvalidateBindingIndex();
154
+ HierarchyChanged?.Invoke();
132
155
  }
133
156
 
134
157
  private void OnEnable()
@@ -136,6 +159,7 @@ namespace Delta.ECS.Unity
136
159
  SceneData.Changed -= OnSceneDataChanged;
137
160
  SceneData.Changed += OnSceneDataChanged;
138
161
  InvalidateBindingIndex();
162
+ HierarchyChanged?.Invoke();
139
163
  }
140
164
 
141
165
  private void OnDisable()
@@ -144,6 +168,7 @@ namespace Delta.ECS.Unity
144
168
  {
145
169
  _sceneData.Changed -= OnSceneDataChanged;
146
170
  }
171
+ HierarchyChanged?.Invoke();
147
172
  }
148
173
 
149
174
  private void OnSceneDataChanged(SceneDataChange change)
@@ -28,6 +28,9 @@ namespace Delta.ECS.Unity
28
28
  private readonly SceneAuthoring _authoring;
29
29
  private readonly bool _ownsWorld;
30
30
  private readonly ComponentId _hiddenEntityHierarchyComponentId;
31
+ private readonly List<Entity> _destroyedViews = new();
32
+ private readonly List<string> _destroyedAuthoredViews = new();
33
+ private RuntimeEntityChangeHandler _hierarchyEntitiesChanged;
31
34
  private bool _disposed;
32
35
 
33
36
  public World World => _world;
@@ -41,6 +44,26 @@ namespace Delta.ECS.Unity
41
44
  return entities;
42
45
  }
43
46
  }
47
+ internal static event Action LiveWorldsChanged;
48
+ internal event RuntimeEntityChangeHandler HierarchyEntitiesChanged
49
+ {
50
+ add
51
+ {
52
+ if (_hierarchyEntitiesChanged == null)
53
+ {
54
+ _world.EntityMembershipChanged += OnEntityMembershipChanged;
55
+ }
56
+ _hierarchyEntitiesChanged += value;
57
+ }
58
+ remove
59
+ {
60
+ _hierarchyEntitiesChanged -= value;
61
+ if (_hierarchyEntitiesChanged == null)
62
+ {
63
+ _world.EntityMembershipChanged -= OnEntityMembershipChanged;
64
+ }
65
+ }
66
+ }
44
67
  internal ComponentId HiddenEntityHierarchyComponentId => _hiddenEntityHierarchyComponentId;
45
68
  internal SceneData SceneData => _sceneData;
46
69
  internal Scene UnityScene => _authoring != null ? _authoring.gameObject.scene : default;
@@ -60,6 +83,7 @@ namespace Delta.ECS.Unity
60
83
  if (registerLiveWorld)
61
84
  {
62
85
  LiveWorlds.Add(this);
86
+ LiveWorldsChanged?.Invoke();
63
87
  }
64
88
  }
65
89
 
@@ -91,24 +115,35 @@ namespace Delta.ECS.Unity
91
115
  UnityEngine.EntityId viewId = view.GetEntityId();
92
116
  if (_entitiesByView.TryGetValue(viewId, out Entity previousEntity))
93
117
  {
118
+ if (previousEntity == entity)
119
+ {
120
+ return true;
121
+ }
94
122
  _viewsByEntity.Remove(previousEntity);
123
+ NotifyHierarchyEntityChanged(previousEntity);
95
124
  }
96
125
 
97
- if (_viewsByEntity.TryGetValue(entity, out GameObject previousView) && previousView != null)
126
+ if (_viewsByEntity.TryGetValue(entity, out GameObject previousView) && previousView is not null)
98
127
  {
99
128
  _entitiesByView.Remove(previousView.GetEntityId());
100
129
  }
101
130
 
102
131
  _viewsByEntity[entity] = view;
103
132
  _entitiesByView[viewId] = entity;
133
+ NotifyHierarchyEntityChanged(entity);
104
134
  return true;
105
135
  }
106
136
 
107
137
  public void UnbindView(Entity entity)
108
138
  {
109
- if (_viewsByEntity.Remove(entity, out GameObject view) && view != null)
139
+ if (_viewsByEntity.Remove(entity, out GameObject view))
110
140
  {
111
- _entitiesByView.Remove(view.GetEntityId());
141
+ // The managed wrapper retains its ID even after Unity destroys the native object.
142
+ if (view is not null)
143
+ {
144
+ _entitiesByView.Remove(view.GetEntityId());
145
+ }
146
+ NotifyHierarchyEntityChanged(entity);
112
147
  }
113
148
  }
114
149
 
@@ -143,7 +178,12 @@ namespace Delta.ECS.Unity
143
178
  }
144
179
 
145
180
  _disposed = true;
146
- LiveWorlds.Remove(this);
181
+ _world.EntityMembershipChanged -= OnEntityMembershipChanged;
182
+ if (LiveWorlds.Remove(this))
183
+ {
184
+ LiveWorldsChanged?.Invoke();
185
+ }
186
+ _hierarchyEntitiesChanged = null;
147
187
  DestroyCurrentAuthoringEntities();
148
188
  _entitiesByStableId.Clear();
149
189
  _stableIdsByEntity.Clear();
@@ -159,14 +199,13 @@ namespace Delta.ECS.Unity
159
199
  /// <summary>Resolves the runtime view first, then the scene-authored stable-ID binding.</summary>
160
200
  public bool TryGetView(Entity entity, [NotNullWhen(true)] out GameObject view)
161
201
  {
162
- if (_viewsByEntity.TryGetValue(entity, out view) && view != null)
202
+ if (_viewsByEntity.TryGetValue(entity, out view))
163
203
  {
164
- return true;
165
- }
166
-
167
- if (_viewsByEntity.ContainsKey(entity))
168
- {
169
- _viewsByEntity.Remove(entity);
204
+ if (view != null)
205
+ {
206
+ return true;
207
+ }
208
+ UnbindView(entity);
170
209
  }
171
210
 
172
211
  return _authoring != null
@@ -262,6 +301,36 @@ namespace Delta.ECS.Unity
262
301
  }
263
302
  }
264
303
 
304
+ internal bool HasRuntimeView(Entity entity) => _viewsByEntity.ContainsKey(entity);
305
+
306
+ internal void RefreshDestroyedViews()
307
+ {
308
+ foreach (var pair in _viewsByEntity)
309
+ {
310
+ if (pair.Value == null || !_world.IsAlive(pair.Key))
311
+ {
312
+ _destroyedViews.Add(pair.Key);
313
+ }
314
+ }
315
+ foreach (Entity entity in _destroyedViews)
316
+ {
317
+ UnbindView(entity);
318
+ }
319
+ _destroyedViews.Clear();
320
+ if (_authoring != null)
321
+ {
322
+ _authoring.CollectDestroyedViews(_destroyedAuthoredViews);
323
+ foreach (string stableId in _destroyedAuthoredViews)
324
+ {
325
+ if (_entitiesByStableId.TryGetValue(stableId, out Entity entity))
326
+ {
327
+ NotifyHierarchyEntityChanged(entity);
328
+ }
329
+ }
330
+ _destroyedAuthoredViews.Clear();
331
+ }
332
+ }
333
+
265
334
  internal void RebuildAuthoringEntities()
266
335
  {
267
336
  ThrowIfDisposed();
@@ -521,6 +590,15 @@ namespace Delta.ECS.Unity
521
590
  return new World(layouts, initialEntityCapacity);
522
591
  }
523
592
 
593
+ private void OnEntityMembershipChanged(ReadOnlySpan<Entity> entities)
594
+ => _hierarchyEntitiesChanged?.Invoke(entities);
595
+
596
+ private void NotifyHierarchyEntityChanged(Entity entity)
597
+ {
598
+ ReadOnlySpan<Entity> entities = stackalloc Entity[] { entity };
599
+ _hierarchyEntitiesChanged?.Invoke(entities);
600
+ }
601
+
524
602
  private void DestroyCurrentAuthoringEntities()
525
603
  {
526
604
  foreach (Entity entity in _entitiesByStableId.Values)
@@ -544,6 +622,8 @@ namespace Delta.ECS.Unity
544
622
  }
545
623
  }
546
624
 
625
+ internal delegate void RuntimeEntityChangeHandler(ReadOnlySpan<Entity> entities);
626
+
547
627
  /// <summary>Owns runtime world creation and teardown for loaded Unity scenes.</summary>
548
628
  internal static class SceneWorldRuntime
549
629
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.artromskiy.deltaecs.unity",
3
- "version": "0.0.64",
3
+ "version": "0.0.66",
4
4
  "displayName": "DeltaECS Unity",
5
5
  "description": "Unity scene authoring and editor integration for DeltaECS.",
6
6
  "unity": "6000.7",