com.artromskiy.deltaecs.unity 0.0.65 → 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,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.66
4
+
5
+ - Published with the matching DeltaECS NuGet packages.
6
+
3
7
  ## 0.0.65
4
8
 
5
9
  - Published with the matching DeltaECS NuGet packages.
@@ -22,9 +22,12 @@ namespace Delta.ECS.Unity.Editor
22
22
  private static readonly SchemaId HiddenEntitySchema = new(ViewComponentSchemaIds.HideFromEntityHierarchy);
23
23
  private static double _nextRuntimeHierarchyRefresh;
24
24
  private static bool _wasPlaying;
25
+ private static bool _authoringsDirty = true;
26
+ private static SceneAuthoring[] _authorings = Array.Empty<SceneAuthoring>();
25
27
  private static readonly HashSet<HierarchyNodeHandler> Instances = new();
26
28
 
27
29
  private readonly Dictionary<EntityKey, EntityNode> _entityNodes = new();
30
+ private readonly Dictionary<SceneAuthoring, RuntimeHierarchyState> _runtimeStates = new();
28
31
  private readonly HashSet<EntityKey> _activeEntityKeys = new();
29
32
  private readonly List<EntityKey> _removedEntityKeys = new();
30
33
  private readonly Dictionary<HierarchyNode, NodeReference> _nodes = new();
@@ -39,6 +42,8 @@ namespace Delta.ECS.Unity.Editor
39
42
  HierarchyWindow.BindView += BindHierarchyView;
40
43
  EditorApplication.delayCall += BindExistingHierarchyViews;
41
44
  EditorApplication.update += UpdateRuntimeHierarchy;
45
+ SceneAuthoring.HierarchyChanged += InvalidateAuthorings;
46
+ SceneWorld.LiveWorldsChanged += InvalidateAuthorings;
42
47
  }
43
48
 
44
49
  public override string GetNodeTypeName() => nameof(HierarchyNodeHandler);
@@ -98,6 +103,11 @@ namespace Delta.ECS.Unity.Editor
98
103
  pair.Key.SceneData.Changed -= pair.Value;
99
104
  }
100
105
  }
106
+ foreach (RuntimeHierarchyState state in _runtimeStates.Values)
107
+ {
108
+ state.Dispose();
109
+ }
110
+ _runtimeStates.Clear();
101
111
  _sceneDataSubscriptions.Clear();
102
112
  _loadedAuthorings.Clear();
103
113
  _removedAuthorings.Clear();
@@ -150,6 +160,8 @@ namespace Delta.ECS.Unity.Editor
150
160
 
151
161
  internal static void RefreshAll()
152
162
  {
163
+ InvalidateAuthorings();
164
+ GetAuthorings();
153
165
  var changed = false;
154
166
  foreach (var instance in Instances)
155
167
  {
@@ -206,7 +218,22 @@ namespace Delta.ECS.Unity.Editor
206
218
  }
207
219
 
208
220
  _nextRuntimeHierarchyRefresh = EditorApplication.timeSinceStartup + RuntimeHierarchyRefreshInterval;
209
- 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
+ }
210
237
  return;
211
238
  }
212
239
 
@@ -217,6 +244,18 @@ namespace Delta.ECS.Unity.Editor
217
244
  }
218
245
  }
219
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
+
220
259
  private static void BindHierarchyView(HierarchyWindow window, HierarchyView view)
221
260
  {
222
261
  if (view == null)
@@ -432,19 +471,46 @@ namespace Delta.ECS.Unity.Editor
432
471
  }
433
472
  }
434
473
 
435
- private bool Reconcile()
474
+ private bool Reconcile(bool changed = false)
436
475
  {
437
476
  if (!Hierarchy.IsCreated)
438
477
  {
439
478
  return false;
440
479
  }
441
480
 
442
- var changed = false;
443
481
  _activeEntityKeys.Clear();
444
482
 
445
483
  var sceneHandler = Hierarchy.GetNodeTypeHandler<HierarchySceneHandler>();
446
- var authoringsInScenes = Resources.FindObjectsOfTypeAll<SceneAuthoring>();
484
+ var authoringsInScenes = GetAuthorings();
447
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
+ }
448
514
  for (var i = 0; i < authoringsInScenes.Length; i++)
449
515
  {
450
516
  var authoringHost = authoringsInScenes[i];
@@ -464,6 +530,7 @@ namespace Delta.ECS.Unity.Editor
464
530
  {
465
531
  SceneWorld.TryFindLiveWorld(authoringHost.SceneData, out runtimeWorld);
466
532
  }
533
+ runtimeWorld?.RefreshDestroyedViews();
467
534
 
468
535
  var entities = authoringHost.SceneData.Entities;
469
536
  for (var entityIndex = 0; entityIndex < entities.Count; entityIndex++)
@@ -484,8 +551,7 @@ namespace Delta.ECS.Unity.Editor
484
551
  : default;
485
552
  bool hasView = authoringHost.TryGetView(authoring.StableId, out _)
486
553
  || (runtimeWorld != null
487
- && runtimeWorld.World.IsAlive(runtimeEntity)
488
- && runtimeWorld.TryGetView(runtimeEntity, out _));
554
+ && runtimeWorld.HasRuntimeView(runtimeEntity));
489
555
  if (hasView || IsHiddenFromHierarchy(authoring, runtimeWorld, runtimeEntity))
490
556
  {
491
557
  continue;
@@ -556,27 +622,7 @@ namespace Delta.ECS.Unity.Editor
556
622
 
557
623
  for (var i = 0; i < _removedEntityKeys.Count; i++)
558
624
  {
559
- var key = _removedEntityKeys[i];
560
- var entityNode = _entityNodes[key];
561
- if (entityNode.Reference.IsRuntimeOnly)
562
- {
563
- HierarchySelection.ClearIfSelected(
564
- entityNode.Reference.RuntimeWorld,
565
- entityNode.Reference.RuntimeEntity);
566
- }
567
- else
568
- {
569
- HierarchySelection.ClearIfSelected(this, entityNode.Reference.Authoring, entityNode.Reference.StableId);
570
- }
571
- if (Hierarchy.Exists(entityNode.Node))
572
- {
573
- CommandList.Remove(entityNode.Node);
574
- }
575
-
576
- SelectionProxyBridge.Remove(this, entityNode.Node);
577
- _nodes.Remove(entityNode.Node);
578
- _entityNodes.Remove(key);
579
- changed = true;
625
+ changed |= RemoveEntityNode(_removedEntityKeys[i]);
580
626
  }
581
627
 
582
628
  if (CommandList.Size > 0)
@@ -597,33 +643,103 @@ namespace Delta.ECS.Unity.Editor
597
643
  HierarchyNode sceneNode,
598
644
  SceneWorld runtimeWorld)
599
645
  {
600
- Query query = runtimeWorld.World.WhereNone(runtimeWorld.HiddenEntityHierarchyComponentId);
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
+ }
601
663
  bool changed = false;
602
- runtimeWorld.World.ForEachEntity(in query, entityRef =>
664
+ bool authoredChanged = false;
665
+ foreach (var pair in _runtimeStates)
603
666
  {
604
- Entity entity = entityRef;
605
- if (runtimeWorld.TryGetStableId(entity, out _) || runtimeWorld.TryGetView(entity, out _))
667
+ RuntimeHierarchyState state = pair.Value;
668
+ if (!state.World.IsBound || !state.PrepareChanges())
606
669
  {
607
- return;
670
+ continue;
608
671
  }
609
-
610
- var key = new EntityKey(runtimeWorld, entity);
611
- _activeEntityKeys.Add(key);
612
- if (_entityNodes.ContainsKey(key))
672
+ var sceneHandler = Hierarchy.GetNodeTypeHandler<HierarchySceneHandler>();
673
+ HierarchyNode sceneNode = sceneHandler.GetOrCreateNode(pair.Key.gameObject.scene);
674
+ if (sceneNode != HierarchyNode.Null)
613
675
  {
614
- return;
676
+ changed |= state.Synchronize(this, pair.Key, sceneNode, out bool dirtyAuthoring);
677
+ authoredChanged |= dirtyAuthoring;
615
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
+ }
616
695
 
617
- string name = $"ECS Entity {entity.Index}:{entity.Generation}";
618
- var reference = new NodeReference(authoring, null, runtimeWorld, entity);
619
- CommandList.Add(sceneNode, out HierarchyNode node);
620
- CommandList.SetName(node, name);
621
- _entityNodes.Add(key, new EntityNode(node, reference, name));
622
- _nodes.Add(node, reference);
623
- changed = true;
624
- }).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
+ }
625
720
 
626
- 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;
627
743
  }
628
744
 
629
745
  private void SynchronizeSceneSubscriptions(SceneAuthoring[] authorings)
@@ -669,6 +785,10 @@ namespace Delta.ECS.Unity.Editor
669
785
  authoring.SceneData.Changed -= _sceneDataSubscriptions[authoring];
670
786
  }
671
787
  _sceneDataSubscriptions.Remove(authoring);
788
+ if (_runtimeStates.Remove(authoring, out RuntimeHierarchyState state))
789
+ {
790
+ state.Dispose();
791
+ }
672
792
  }
673
793
 
674
794
  _removedAuthorings.Clear();
@@ -869,6 +989,64 @@ namespace Delta.ECS.Unity.Editor
869
989
  }
870
990
  }
871
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
+
872
1050
  private struct BlockedDrag
873
1051
  {
874
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.65"
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.65",
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",