1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
|
#if MODULE_ENTITIES
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
namespace Pathfinding.ECS {
/// <summary>Helper for EntityAccess</summary>
static class EntityAccessHelper {
public static readonly int GlobalSystemVersionOffset = UnsafeUtility.GetFieldOffset(typeof(ComponentTypeHandle<int>).GetField("m_GlobalSystemVersion", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance));
}
/// <summary>
/// Wrapper for a pointer.
///
/// Very similar to the entities package RefRW<T> struct. But unfortunately that one cannot be used because the required constructor is not exposed.
/// </summary>
public ref struct ComponentRef<T> where T : unmanaged {
unsafe byte* ptr;
public unsafe ComponentRef(byte* ptr) {
this.ptr = ptr;
}
public ref T value {
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
get {
unsafe {
return ref *(T*)ptr;
}
}
}
}
/// <summary>Utility for efficient random access to entity storage data from the main thread</summary>
public struct EntityStorageCache {
EntityStorageInfo storage;
Entity entity;
int lastWorldHash;
/// <summary>
/// Retrieves the storage for a given entity.
///
/// This method is very fast if the entity is the same as the last call to this method.
/// If the entity is different, it will be slower.
///
/// Returns: True if the entity exists, and false if it does not.
/// </summary>
// Inlining makes this method about 20% faster. It's hot when accessing properties on the FollowerEntity component.
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public bool Update (World world, Entity entity, out EntityManager entityManager, out EntityStorageInfo storage) {
entityManager = default;
storage = this.storage;
if (world == null) return false;
entityManager = world.EntityManager;
// We must use entityManager.EntityOrderVersion here, not GlobalSystemVersion, because
// the GlobalSystemVersion does not necessarily update when structural changes happen.
var worldHash = entityManager.EntityOrderVersion ^ ((int)world.SequenceNumber << 8);
if (worldHash != lastWorldHash || entity != this.entity) {
if (!entityManager.Exists(entity)) return false;
this.storage = storage = entityManager.GetStorageInfo(entity);
this.entity = entity;
lastWorldHash = worldHash;
}
return true;
}
/// <summary>
/// Retrieves a component for a given entity.
///
/// This is a convenience method to call <see cref="Update"/> on this object and update on the access object, and then retrieve the component data.
///
/// This method is very fast if the entity is the same as the last call to this method.
/// If the entity is different, it will be slower.
///
/// Warning: You must not store the returned reference past a structural change in the ECS world.
///
/// Returns: True if the entity exists, and false if it does not.
/// Throws: An exception if the entity does not have the given component.
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public bool GetComponentData<A>(Entity entity, ref EntityAccess<A> access, out ComponentRef<A> value) where A : unmanaged, IComponentData {
if (Update(World.DefaultGameObjectInjectionWorld, entity, out var entityManager, out var storage)) {
access.Update(entityManager);
unsafe {
value = new ComponentRef<A>((byte*)UnsafeUtility.AddressOf(ref access[storage]));
}
return true;
} else {
value = default;
return false;
}
}
}
/// <summary>
/// Utility for efficient random access to entity component data from the main thread.
///
/// Since this struct returns a reference to the component data, it is faster than using EntityManager.GetComponentData,
/// in particular for larger component types.
///
/// Warning: Some checks are not enforced by this API. It is the user's responsibility to ensure that
/// this struct does not survive past an ECS system update. If you only use this struct from the main thread
/// and only store it locally on the stack, this should not be a problem.
/// This struct also does not enforce that you only read to the component data if the readOnly flag is set.
/// </summary>
public struct EntityAccess<T> where T : unmanaged, IComponentData {
public ComponentTypeHandle<T> handle;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
SystemHandle systemHandle;
#endif
uint lastSystemVersion;
ulong worldSequenceNumber;
bool readOnly;
public EntityAccess(bool readOnly) {
handle = default;
this.readOnly = readOnly;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
systemHandle = default;
#endif
// Version 0 is never used by EntityManager.GlobalSystemVersion
lastSystemVersion = 0;
worldSequenceNumber = 0;
}
/// <summary>
/// Update the component type handle if necessary.
///
/// This must be called if any jobs or system might have been scheduled since the struct was created or since the last call to Update.
/// </summary>
public void Update (EntityManager entityManager) {
// If the global system version has changed, jobs may have been scheduled which writes
// to the component data. Therefore we need to complete all dependencies before we can
// safely read or write to the component data.
var systemVersion = entityManager.GlobalSystemVersion;
var sequenceNumber = entityManager.WorldUnmanaged.SequenceNumber;
if (systemVersion != lastSystemVersion || worldSequenceNumber != sequenceNumber) {
if (lastSystemVersion == 0 || worldSequenceNumber != sequenceNumber) {
handle = entityManager.GetComponentTypeHandle<T>(readOnly);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
entityManager.WorldUnmanaged.IsSystemValid(systemHandle);
systemHandle = entityManager.WorldUnmanaged.GetExistingUnmanagedSystem<AIMoveSystem>();
#endif
}
lastSystemVersion = systemVersion;
worldSequenceNumber = sequenceNumber;
if (readOnly) entityManager.CompleteDependencyBeforeRO<T>();
else entityManager.CompleteDependencyBeforeRW<T>();
}
#if ENABLE_UNITY_COLLECTIONS_CHECKS
handle.Update(ref entityManager.WorldUnmanaged.ResolveSystemStateRef(systemHandle));
#else
// handle.Update just does the same thing as this unsafe code, but in a much more roundabout way
unsafe {
var ptr = (byte*)UnsafeUtility.AddressOf(ref handle);
*(uint*)(ptr + EntityAccessHelper.GlobalSystemVersionOffset) = systemVersion;
}
#endif
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public bool HasComponent (EntityStorageInfo storage) {
return storage.Chunk.Has<T>(ref handle);
}
public ref T this[EntityStorageInfo storage] {
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
get {
unsafe {
var ptr = readOnly ? ((T*)storage.Chunk.GetRequiredComponentDataPtrRO(ref handle) + storage.IndexInChunk) : ((T*)storage.Chunk.GetRequiredComponentDataPtrRW(ref handle) + storage.IndexInChunk);
return ref *ptr;
}
}
}
}
/// <summary>
/// Utility for efficient random access to managed entity component data from the main thread.
///
/// Warning: Some checks are not enforced by this API. It is the user's responsibility to ensure that
/// this struct does not survive past an ECS system update. If you only use this struct from the main thread
/// and only store it locally on the stack, this should not be a problem.
/// This struct also does not enforce that you only read to the component data if the readOnly flag is set.
/// </summary>
public struct ManagedEntityAccess<T> where T : class, IComponentData {
EntityManager entityManager;
ComponentTypeHandle<T> handle;
bool readOnly;
public ManagedEntityAccess(bool readOnly) {
entityManager = default;
handle = default;
this.readOnly = readOnly;
}
public ManagedEntityAccess(EntityManager entityManager, bool readOnly) : this(readOnly) {
Update(entityManager);
}
public void Update (EntityManager entityManager) {
if (readOnly) entityManager.CompleteDependencyBeforeRO<T>();
else entityManager.CompleteDependencyBeforeRW<T>();
handle = entityManager.GetComponentTypeHandle<T>(readOnly);
this.entityManager = entityManager;
}
public T this[EntityStorageInfo storage] {
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
get {
return storage.Chunk.GetManagedComponentAccessor<T>(ref handle, entityManager)[storage.IndexInChunk];
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
set {
var accessor = storage.Chunk.GetManagedComponentAccessor<T>(ref handle, entityManager);
accessor[storage.IndexInChunk] = value;
}
}
}
}
#endif
|