com.artromskiy.deltaecs.unity 0.0.47 → 0.0.52
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 +8 -0
- package/README.md +25 -1
- package/Runtime/EntityContext.cs +121 -0
- package/Runtime/EntityContext.cs.meta +2 -0
- package/Runtime/SceneWorld.cs +1 -0
- package/Runtime/SceneWorldExtensions.cs +33 -0
- package/Runtime/ThrowHelpers.cs +9 -0
- package/Runtime/ViewBindingSystem.cs +119 -0
- package/Runtime/ViewBindingSystem.cs.meta +2 -0
- package/Runtime/ViewComponents.cs +28 -0
- package/Runtime/ViewComponents.cs.meta +2 -0
- package/Runtime/ViewPrefabCatalog.cs +68 -0
- package/Runtime/ViewPrefabCatalog.cs.meta +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
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.
|
|
21
|
+
"com.artromskiy.deltaecs.unity": "0.0.52"
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
```
|
|
@@ -75,5 +75,29 @@ public struct Position
|
|
|
75
75
|
}
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
## Initialize views when they are bound
|
|
79
|
+
|
|
80
|
+
Implement `IEntityView` on a `MonoBehaviour` when a view needs to receive its
|
|
81
|
+
entity after binding. `OnBind` receives an `EntityContext` containing the
|
|
82
|
+
owning world and entity:
|
|
83
|
+
|
|
84
|
+
```csharp
|
|
85
|
+
using Delta.ECS.Unity;
|
|
86
|
+
using UnityEngine;
|
|
87
|
+
|
|
88
|
+
public sealed class UnitView : MonoBehaviour, IEntityView
|
|
89
|
+
{
|
|
90
|
+
private EntityContext _context;
|
|
91
|
+
|
|
92
|
+
public void OnBind(EntityContext context) => _context = context;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The binder calls `OnBind` for implementations on the view and its child
|
|
97
|
+
GameObjects, including inactive children. It runs after the view is associated
|
|
98
|
+
with the entity and after the ECS query has finished, so the callback can use
|
|
99
|
+
the regular `World` API. A stored context can later be checked with
|
|
100
|
+
`context.IsAlive` before using its entity.
|
|
101
|
+
|
|
78
102
|
See the [DeltaECS documentation](https://github.com/Artromskiy/DeltaECS/wiki)
|
|
79
103
|
for the runtime and generator APIs.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#nullable enable
|
|
2
|
+
|
|
3
|
+
using System;
|
|
4
|
+
using Delta.ECS;
|
|
5
|
+
|
|
6
|
+
namespace Delta.ECS.Unity
|
|
7
|
+
{
|
|
8
|
+
/// <summary>A stable entity handle paired with the world that owns it.</summary>
|
|
9
|
+
public readonly struct EntityContext
|
|
10
|
+
{
|
|
11
|
+
/// <summary>Gets the entity handle.</summary>
|
|
12
|
+
public Entity Entity { get; }
|
|
13
|
+
|
|
14
|
+
/// <summary>Gets the owning world, or <see langword="null"/> when this is the default context.</summary>
|
|
15
|
+
public World? World { get; }
|
|
16
|
+
|
|
17
|
+
/// <summary>Gets whether this context refers to a live entity.</summary>
|
|
18
|
+
public bool IsAlive => World != null && World.IsAlive(Entity);
|
|
19
|
+
|
|
20
|
+
/// <summary>Reports whether the entity owns the specified component registration.</summary>
|
|
21
|
+
public bool Has(ComponentId componentId) => World?.Has(Entity, componentId) ?? false;
|
|
22
|
+
|
|
23
|
+
/// <summary>Reports whether the entity owns the primary registration for <typeparamref name="T"/>.</summary>
|
|
24
|
+
public bool Has<T>() => World?.Has<T>(Entity) ?? false;
|
|
25
|
+
|
|
26
|
+
/// <summary>Reports whether the entity owns the specified registration of <typeparamref name="T"/>.</summary>
|
|
27
|
+
public bool Has<T>(ComponentId componentId) => World?.Has<T>(Entity, componentId) ?? false;
|
|
28
|
+
|
|
29
|
+
/// <summary>Attempts to read the primary component for <typeparamref name="T"/>.</summary>
|
|
30
|
+
public bool TryGet<T>(out T value)
|
|
31
|
+
{
|
|
32
|
+
World? world = World;
|
|
33
|
+
if (world is null)
|
|
34
|
+
{
|
|
35
|
+
value = default!;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return world.TryGet(Entity, out value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// <summary>Attempts to read the specified registration of <typeparamref name="T"/>.</summary>
|
|
43
|
+
public bool TryGet<T>(ComponentId componentId, out T value)
|
|
44
|
+
{
|
|
45
|
+
World? world = World;
|
|
46
|
+
if (world is null)
|
|
47
|
+
{
|
|
48
|
+
value = default!;
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return world.TryGet(Entity, componentId, out value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// <summary>Reads the primary component for <typeparamref name="T"/> or throws when it is missing.</summary>
|
|
56
|
+
public T Get<T>() => GetWorld().Get<T>(Entity);
|
|
57
|
+
|
|
58
|
+
/// <summary>Reads the specified component registration as <typeparamref name="T"/> or throws when it is missing.</summary>
|
|
59
|
+
public T Get<T>(ComponentId componentId) => GetWorld().Get<T>(Entity, componentId);
|
|
60
|
+
|
|
61
|
+
/// <summary>Gets a writable reference to the primary component for <typeparamref name="T"/>.</summary>
|
|
62
|
+
public ref T GetRef<T>() => ref GetWorld().GetRef<T>(Entity);
|
|
63
|
+
|
|
64
|
+
/// <summary>Gets a writable reference to the specified registration of <typeparamref name="T"/>.</summary>
|
|
65
|
+
public ref T GetRef<T>(ComponentId componentId) => ref GetWorld().GetRef<T>(Entity, componentId);
|
|
66
|
+
|
|
67
|
+
/// <summary>Gets a read-only reference to the primary component for <typeparamref name="T"/>.</summary>
|
|
68
|
+
public ref readonly T GetReadRef<T>() => ref GetWorld().GetReadRef<T>(Entity);
|
|
69
|
+
|
|
70
|
+
/// <summary>Gets a read-only reference to the specified registration of <typeparamref name="T"/>.</summary>
|
|
71
|
+
public ref readonly T GetReadRef<T>(ComponentId componentId) => ref GetWorld().GetReadRef<T>(Entity, componentId);
|
|
72
|
+
|
|
73
|
+
/// <summary>Attempts to read the stamp for the primary component of <typeparamref name="T"/>.</summary>
|
|
74
|
+
public bool TryGetComponentStamp<T>(out Stamp stamp)
|
|
75
|
+
{
|
|
76
|
+
World? world = World;
|
|
77
|
+
if (world is null)
|
|
78
|
+
{
|
|
79
|
+
stamp = default;
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return world.TryGetComponentStamp<T>(Entity, out stamp);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// <summary>Attempts to read the stamp for the specified component registration.</summary>
|
|
87
|
+
public bool TryGetComponentStamp(ComponentId componentId, out Stamp stamp)
|
|
88
|
+
{
|
|
89
|
+
World? world = World;
|
|
90
|
+
if (world is null)
|
|
91
|
+
{
|
|
92
|
+
stamp = default;
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return world.TryGetComponentStamp(Entity, componentId, out stamp);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// <summary>Attempts to read the stamp for the specified registration of <typeparamref name="T"/>.</summary>
|
|
100
|
+
public bool TryGetComponentStamp<T>(ComponentId componentId, out Stamp stamp)
|
|
101
|
+
{
|
|
102
|
+
World? world = World;
|
|
103
|
+
if (world is null)
|
|
104
|
+
{
|
|
105
|
+
stamp = default;
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return world.TryGetComponentStamp<T>(Entity, componentId, out stamp);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
internal EntityContext(World world, Entity entity)
|
|
113
|
+
{
|
|
114
|
+
World = world;
|
|
115
|
+
Entity = entity;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private World GetWorld()
|
|
119
|
+
=> World ?? UnityThrowHelpers.ThrowMissingEntityContextWorld();
|
|
120
|
+
}
|
|
121
|
+
}
|
package/Runtime/SceneWorld.cs
CHANGED
|
@@ -32,6 +32,7 @@ namespace Delta.ECS.Unity
|
|
|
32
32
|
public World World => _world;
|
|
33
33
|
internal IEcsWorld Integration => _integration;
|
|
34
34
|
internal SceneData SceneData => _sceneData;
|
|
35
|
+
internal Scene UnityScene => _authoring != null ? _authoring.gameObject.scene : default;
|
|
35
36
|
internal bool IsBound => !_disposed;
|
|
36
37
|
|
|
37
38
|
/// <summary>Returns the integration-owned runtime context for a loaded Unity scene.</summary>
|
|
@@ -9,6 +9,23 @@ namespace Delta.ECS.Unity
|
|
|
9
9
|
{
|
|
10
10
|
public static class SceneWorldExtensions
|
|
11
11
|
{
|
|
12
|
+
/// <summary>Returns the live ECS context bound to this GameObject, or the default context when unbound.</summary>
|
|
13
|
+
public static EntityContext GetEntity(this GameObject? view)
|
|
14
|
+
=> TryGetEntity(view, out EntityContext context) ? context : default;
|
|
15
|
+
|
|
16
|
+
/// <summary>Returns whether this GameObject is bound to a live ECS entity.</summary>
|
|
17
|
+
public static bool TryGetEntity(this GameObject? view, out EntityContext context)
|
|
18
|
+
{
|
|
19
|
+
if (view != null && SceneWorld.TryGetBoundEntity(view, out World world, out Entity entity))
|
|
20
|
+
{
|
|
21
|
+
context = new EntityContext(world, entity);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
context = default;
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
12
29
|
/// <summary>Returns the entity bound to this GameObject, or the default entity when no binding exists.</summary>
|
|
13
30
|
/// <param name="world">The bound live ECS world, or <see langword="null"/> when no binding exists.</param>
|
|
14
31
|
public static Entity GetEntity(this GameObject? view, out World? world)
|
|
@@ -40,6 +57,22 @@ namespace Delta.ECS.Unity
|
|
|
40
57
|
return TryGetEntity(behaviour, out Entity entity, out world) ? entity : default;
|
|
41
58
|
}
|
|
42
59
|
|
|
60
|
+
/// <summary>Returns the live ECS context bound to this MonoBehaviour, or the default context when unbound.</summary>
|
|
61
|
+
public static EntityContext GetEntity(this MonoBehaviour? behaviour)
|
|
62
|
+
=> TryGetEntity(behaviour, out EntityContext context) ? context : default;
|
|
63
|
+
|
|
64
|
+
/// <summary>Returns whether this MonoBehaviour is bound to a live ECS entity.</summary>
|
|
65
|
+
public static bool TryGetEntity(this MonoBehaviour? behaviour, out EntityContext context)
|
|
66
|
+
{
|
|
67
|
+
if (behaviour != null)
|
|
68
|
+
{
|
|
69
|
+
return behaviour.gameObject.TryGetEntity(out context);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
context = default;
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
43
76
|
/// <summary>Returns whether this MonoBehaviour is bound to a live ECS entity.</summary>
|
|
44
77
|
public static bool TryGetEntity(
|
|
45
78
|
this MonoBehaviour? behaviour,
|
package/Runtime/ThrowHelpers.cs
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
#nullable enable
|
|
2
|
+
|
|
1
3
|
using System;
|
|
2
4
|
using System.Diagnostics.CodeAnalysis;
|
|
5
|
+
using System.Runtime.CompilerServices;
|
|
3
6
|
using System.Runtime.ExceptionServices;
|
|
7
|
+
using Delta.ECS;
|
|
4
8
|
|
|
5
9
|
namespace Delta.ECS.Unity
|
|
6
10
|
{
|
|
@@ -40,5 +44,10 @@ namespace Delta.ECS.Unity
|
|
|
40
44
|
ExceptionDispatchInfo.Capture(exception).Throw();
|
|
41
45
|
return default;
|
|
42
46
|
}
|
|
47
|
+
|
|
48
|
+
[DoesNotReturn]
|
|
49
|
+
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
50
|
+
internal static World ThrowMissingEntityContextWorld()
|
|
51
|
+
=> throw new InvalidOperationException("The default entity context does not reference a world.");
|
|
43
52
|
}
|
|
44
53
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#nullable enable
|
|
2
|
+
|
|
3
|
+
using System.Collections.Generic;
|
|
4
|
+
using Delta.ECS;
|
|
5
|
+
using UnityEngine;
|
|
6
|
+
using UnityEngine.SceneManagement;
|
|
7
|
+
|
|
8
|
+
namespace Delta.ECS.Unity
|
|
9
|
+
{
|
|
10
|
+
/// <summary>Creates and binds prefab views for entities carrying an unbound <see cref="ViewToken"/>.</summary>
|
|
11
|
+
public sealed class ViewBindingSystem
|
|
12
|
+
{
|
|
13
|
+
private readonly SceneWorld _sceneWorld;
|
|
14
|
+
private readonly World _world;
|
|
15
|
+
private readonly ViewPrefabCatalog _catalog;
|
|
16
|
+
private readonly ComponentId _viewBoundId;
|
|
17
|
+
private readonly EcsOperation<ForEachEntityOperationInvoker> _bindOperation;
|
|
18
|
+
private readonly List<Entity> _entitiesToBind = new();
|
|
19
|
+
private readonly List<MonoBehaviour> _viewBehaviours = new();
|
|
20
|
+
|
|
21
|
+
/// <summary>Creates a view binder for one scene world and prefab catalog.</summary>
|
|
22
|
+
public ViewBindingSystem(SceneWorld sceneWorld, ViewPrefabCatalog catalog)
|
|
23
|
+
{
|
|
24
|
+
_sceneWorld = sceneWorld ?? UnityThrowHelpers.ThrowArgumentNull<SceneWorld>(nameof(sceneWorld));
|
|
25
|
+
_catalog = catalog ?? UnityThrowHelpers.ThrowArgumentNull<ViewPrefabCatalog>(nameof(catalog));
|
|
26
|
+
_world = _sceneWorld.World;
|
|
27
|
+
|
|
28
|
+
ComponentId viewTokenId = _world.Layouts.GetPrimary<ViewToken>();
|
|
29
|
+
_viewBoundId = _world.Layouts.GetPrimary<ViewBound>();
|
|
30
|
+
Query query = _world.WhereAll(stackalloc ComponentId[1] { viewTokenId })
|
|
31
|
+
.WhereNone(stackalloc ComponentId[1] { _viewBoundId });
|
|
32
|
+
_bindOperation = _world.ForEachEntity(in query, QueueViewBinding);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// <summary>Binds views for currently unbound entities with a matching token.</summary>
|
|
36
|
+
public void BindPendingViews()
|
|
37
|
+
{
|
|
38
|
+
_entitiesToBind.Clear();
|
|
39
|
+
_bindOperation.Invoke();
|
|
40
|
+
|
|
41
|
+
for (int i = 0; i < _entitiesToBind.Count; i++)
|
|
42
|
+
{
|
|
43
|
+
BindView(_entitiesToBind[i]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_entitiesToBind.Clear();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private void QueueViewBinding(EntityRef entityRef) => _entitiesToBind.Add(entityRef);
|
|
50
|
+
|
|
51
|
+
private void BindView(Entity entity)
|
|
52
|
+
{
|
|
53
|
+
if (!_world.IsAlive(entity) || _world.Has(entity, _viewBoundId))
|
|
54
|
+
{
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (_sceneWorld.TryGetView(entity, out GameObject existingView))
|
|
59
|
+
{
|
|
60
|
+
_world.Add(entity, _viewBoundId);
|
|
61
|
+
NotifyViewBound(existingView, entity);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
ViewToken token = _world.Get<ViewToken>(entity);
|
|
66
|
+
if (!_catalog.TryGetPrefab(token.Id, out GameObject? prefab))
|
|
67
|
+
{
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
Object instance = Object.Instantiate((Object)prefab);
|
|
72
|
+
GameObject? view = instance as GameObject;
|
|
73
|
+
if (view == null)
|
|
74
|
+
{
|
|
75
|
+
string instanceType = instance == null ? "null" : instance.GetType().ToString();
|
|
76
|
+
Debug.LogError(
|
|
77
|
+
$"View prefab '{token.Id}' ({prefab.GetType()}) instantiated as '{instanceType}'.",
|
|
78
|
+
prefab);
|
|
79
|
+
if (instance != null)
|
|
80
|
+
{
|
|
81
|
+
Object.Destroy(instance);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Scene scene = _sceneWorld.UnityScene;
|
|
88
|
+
if (scene.IsValid() && scene.isLoaded && view.scene != scene)
|
|
89
|
+
{
|
|
90
|
+
SceneManager.MoveGameObjectToScene(view, scene);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!_sceneWorld.BindView(entity, view))
|
|
94
|
+
{
|
|
95
|
+
Object.Destroy(view);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_world.Add(entity, _viewBoundId);
|
|
100
|
+
NotifyViewBound(view, entity);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private void NotifyViewBound(GameObject view, Entity entity)
|
|
104
|
+
{
|
|
105
|
+
_viewBehaviours.Clear();
|
|
106
|
+
view.GetComponentsInChildren(true, _viewBehaviours);
|
|
107
|
+
EntityContext context = new EntityContext(_world, entity);
|
|
108
|
+
for (int i = 0; i < _viewBehaviours.Count; i++)
|
|
109
|
+
{
|
|
110
|
+
if (_viewBehaviours[i] != null && _viewBehaviours[i] is IEntityView entityView)
|
|
111
|
+
{
|
|
112
|
+
entityView.OnBind(context);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
_viewBehaviours.Clear();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
using Delta.ECS;
|
|
2
|
+
|
|
3
|
+
namespace Delta.ECS.Unity
|
|
4
|
+
{
|
|
5
|
+
/// <summary>Receives the entity context after a Unity view is bound to an entity.</summary>
|
|
6
|
+
public interface IEntityView
|
|
7
|
+
{
|
|
8
|
+
/// <summary>Initializes this view for its bound entity.</summary>
|
|
9
|
+
void OnBind(EntityContext context);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/// <summary>Selects the Unity prefab used to represent an entity.</summary>
|
|
13
|
+
[DeltaEcsComponent(SchemaId = 0x405B11F5A17E14A1UL)]
|
|
14
|
+
public readonly struct ViewToken
|
|
15
|
+
{
|
|
16
|
+
/// <summary>Gets the identifier of the view prefab in a <see cref="ViewPrefabCatalog"/>.</summary>
|
|
17
|
+
public string Id { get; }
|
|
18
|
+
|
|
19
|
+
/// <summary>Creates a token that selects a view prefab by identifier.</summary>
|
|
20
|
+
public ViewToken(string id) => Id = id;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// <summary>Marks an entity whose Unity view has already been bound.</summary>
|
|
24
|
+
[DeltaEcsComponent(SchemaId = 0xB31CB42A81D3B164UL)]
|
|
25
|
+
public struct ViewBound
|
|
26
|
+
{
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#nullable enable
|
|
2
|
+
|
|
3
|
+
using System;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.Diagnostics.CodeAnalysis;
|
|
6
|
+
using UnityEngine;
|
|
7
|
+
|
|
8
|
+
namespace Delta.ECS.Unity
|
|
9
|
+
{
|
|
10
|
+
/// <summary>Maps <see cref="ViewToken.Id"/> values to Unity view prefabs.</summary>
|
|
11
|
+
[CreateAssetMenu(fileName = "ViewPrefabCatalog", menuName = "Delta ECS/View Prefab Catalog")]
|
|
12
|
+
public sealed class ViewPrefabCatalog : ScriptableObject
|
|
13
|
+
{
|
|
14
|
+
[SerializeField] private List<ViewPrefabEntry> _entries = new();
|
|
15
|
+
[NonSerialized] private Dictionary<string, GameObject>? _prefabsById;
|
|
16
|
+
|
|
17
|
+
/// <summary>Resolves a token identifier to its configured prefab.</summary>
|
|
18
|
+
public bool TryGetPrefab(string id, [NotNullWhen(true)] out GameObject? prefab)
|
|
19
|
+
{
|
|
20
|
+
EnsureIndex();
|
|
21
|
+
if (!string.IsNullOrEmpty(id)
|
|
22
|
+
&& _prefabsById!.TryGetValue(id, out prefab)
|
|
23
|
+
&& prefab != null)
|
|
24
|
+
{
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
prefab = null;
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private void OnValidate() => _prefabsById = null;
|
|
33
|
+
|
|
34
|
+
private void EnsureIndex()
|
|
35
|
+
{
|
|
36
|
+
if (_prefabsById != null)
|
|
37
|
+
{
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
_prefabsById = new Dictionary<string, GameObject>(StringComparer.Ordinal);
|
|
42
|
+
_entries ??= new List<ViewPrefabEntry>();
|
|
43
|
+
for (int i = 0; i < _entries.Count; i++)
|
|
44
|
+
{
|
|
45
|
+
ViewPrefabEntry entry = _entries[i];
|
|
46
|
+
if (entry == null || string.IsNullOrEmpty(entry.Id) || entry.Prefab == null)
|
|
47
|
+
{
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!_prefabsById.TryAdd(entry.Id, entry.Prefab))
|
|
52
|
+
{
|
|
53
|
+
Debug.LogError($"Duplicate view prefab identifier '{entry.Id}' in '{name}'.", this);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
[Serializable]
|
|
59
|
+
private sealed class ViewPrefabEntry
|
|
60
|
+
{
|
|
61
|
+
[SerializeField] private string _id = string.Empty;
|
|
62
|
+
[SerializeField] private GameObject? _prefab;
|
|
63
|
+
|
|
64
|
+
internal string Id => _id;
|
|
65
|
+
internal GameObject? Prefab => _prefab;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|