Skip to content

Commit 132f511

Browse files
committed
Release 8
1 parent 4af3b83 commit 132f511

File tree

1,074 files changed

+7231
-66543
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,074 files changed

+7231
-66543
lines changed

.gitignore

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,16 @@
2323
build/*
2424
TwoStickShooter/Pure/Library/AnnotationManager
2525
*.rsp
26+
*.pyc
2627

2728
#generated by performance framework
2829
*/PerformanceTestRunInfo.json
30+
Performance/Assets/StreamingAssets.meta
31+
Performance/Assets/StreamingAssets
2932

30-
# Files to unignore that would otherwise have been ignored
31-
!PackageRepos/ECSJobDemos/UnstablePrototypes/Asteriods/Assets/Packages/Unity.Multiplayer/Plugins/gamesocket.native.pdb
32-
33-
*.pyc
3433
StreamingAssets.meta
34+
PerformanceTestRunInfo.json
35+
PerformanceTestRunInfo.json.meta
36+
37+
**/InitTestScene*
38+
Samples/Logs/*
Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
# Chunk Iteration
2+
3+
## Motivation
4+
5+
If, for example, there are three components Position, Rotation, and Scale and the output of any combination of these three components should write to a LocalToWorld component. The approach using component group injection might look something like:
6+
7+
```
8+
struct PositionToLocalToWorld
9+
{
10+
ComponentDataArray<Position> Position;
11+
SubtractiveComponent<Rotation> Rotation;
12+
SubtractiveComponent<Scale> Rotation;
13+
ComponentDataArray<LocalToWorld> LocalToWorld;
14+
}
15+
[Inject] PositionToLocalToWorld positionToLocalToWorld;
16+
17+
struct PositionRotationToLocalToWorld
18+
{
19+
ComponentDataArray<Position> Position;
20+
ComponentDataArray<Rotation> Rotation;
21+
SubtractiveComponent<Scale> Rotation;
22+
ComponentDataArray<LocalToWorld> LocalToWorld;
23+
}
24+
[Inject] PositionRotationToLocalToWorld positionRotationToLocalToWorld;
25+
26+
struct PositionRotationScaleToLocalToWorld
27+
{
28+
ComponentDataArray<Position> Position;
29+
ComponentDataArray<Rotation> Rotation;
30+
ComponentDataArray<Scale> Rotation;
31+
ComponentDataArray<LocalToWorld> LocalToWorld;
32+
}
33+
[Inject] PositionRotationScaleToLocalToWorld positionRotationScaleToLocalToWorld;
34+
35+
struct PositionScaleToLocalToWorld
36+
{
37+
ComponentDataArray<Position> Position;
38+
SubtractiveComponent<Rotation> Rotation;
39+
ComponentDataArray<Scale> Rotation;
40+
ComponentDataArray<LocalToWorld> LocalToWorld;
41+
}
42+
[Inject] PositionScaleToLocalToWorld positionScaleToLocalToWorld;
43+
44+
struct RotationToLocalToWorld
45+
{
46+
SubtractiveComponent<Position> Position;
47+
ComponentDataArray<Rotation> Rotation;
48+
SubtractiveComponent<Scale> Rotation;
49+
ComponentDataArray<LocalToWorld> LocalToWorld;
50+
}
51+
[Inject] RotationToLocalToWorld rotationToLocalToWorld;
52+
53+
struct RotationScaleToLocalToWorld
54+
{
55+
SubtractiveComponent<Position> Position;
56+
ComponentDataArray<Rotation> Rotation;
57+
ComponentDataArray<Scale> Rotation;
58+
ComponentDataArray<LocalToWorld> LocalToWorld;
59+
}
60+
[Inject] RotationScaleToLocalToWorld rotationScaleToLocalToWorld;
61+
62+
struct ScaleToLocalToWorld
63+
{
64+
SubtractiveComponent<Position> Position;
65+
SubtractiveComponent<Rotation> Rotation;
66+
ComponentDataArray<Scale> Rotation;
67+
ComponentDataArray<LocalToWorld> LocalToWorld;
68+
}
69+
[Inject] ScaleToLocalToWorld scaleToLocalToWorld;
70+
```
71+
72+
ComponentGroup is a utility which simplifies iteration over same component type values independent of the archetypes those components belong to. ComponentGroup accomplishes this by contraining the types of archetypes that are queried: Either components which must exist in all matching archetypes or components which exist in none of the matching archetypes (subtractive). By contrast, chunks can be iterated in a way that matches how the data is layed out in memory without those same contraints. (At the cost of foregoing ComponentGroup, automatic injection and and other associated utilities.)
73+
74+
Direct chunk iteration allows for "optional" components or managing component combinations more directly.
75+
76+
Another alternative might be to use ComponentDataFromEntity and check for the existence of the components on a per-entity basis, as in:
77+
```
78+
[Inject] [ReadOnly] ComponentDataFromEntity<Position> positions;
79+
[Inject] [ReadOnly] ComponentDataFromEntity<Rotation> rotations;
80+
[Inject] [ReadOnly] ComponentDataFromEntity<Scale> scales;
81+
82+
struct LocalToWorldGroup
83+
{
84+
EntityArray Entities;
85+
ComponentDataArray<LocalToWorld> LocalToWorld;
86+
}
87+
[Inject] LocalToWorldGroup localToWorldGroup;
88+
```
89+
90+
An advantage of direct chunk iteration is that any branching that needs to be done based on the existance of a particular component type can be done on a per-chunk basis rather than a per-entity basis.
91+
92+
## Querying matching archetypes
93+
94+
Each Chunk belongs to a specific Archetype. In order to iterate Chunks, a set of archetypes must be selected. This is an `EntityArchetypeQuery`.
95+
96+
```
97+
public class EntityArchetypeQuery
98+
{
99+
public ComponentType[] Any;
100+
public ComponentType[] None;
101+
public ComponentType[] All;
102+
}
103+
```
104+
105+
An example might look like:
106+
```
107+
var RootLocalToWorldQuery = new EntityArchetypeQuery
108+
{
109+
Any = new ComponentType[] {typeof(Rotation), typeof(Position), typeof(Scale)},
110+
None = new ComponentType[] {typeof(Frozen), typeof(Parent)},
111+
All = new ComponentType[] {typeof(LocalToWorld)},
112+
};
113+
```
114+
115+
Which means RootLocalToWorldQuery will request all archetypes which meet the conditions:
116+
1. Archetype has at least one of Rotation, Position, or Scale component tyoe.
117+
2. Archetype does not have Frozen or Parent component types.
118+
3. Archetype must have LocalToWorld component tyoe.
119+
120+
The query can be resolved by a call to `EntityManager.AddMatchingArchetypes(EntityArchetypeQuery query, NativeList<EntityArchetype> foundArchetypes)`
121+
122+
Additional calls to `AddMatchingArchetypes` passing in the results of previous calls to foundArchetypes will append additional results to the NativeList. (i.e. The logical-or of multiple queries.)
123+
124+
Note that EntityArchetypeQuery uses managed arrays so they should not be created per frame. (OnCreate in a ComponentSystem or JobComponentSystem is more appropriate.)
125+
126+
## Getting array of Chunks
127+
128+
From a `NativeList<EntityArchetype>` the list of Chunks in those archetypes can be retrieved.
129+
130+
This is done by a call to `EntityManager.CreateArchetypeChunkArray(NativeList<EntityArchetype> archetypes, Allocator allocator)` which will return a `NativeArray<ArchetypeChunk>`.
131+
132+
There is also a utility function `EntityManager.CreateArchetypeChunkArray(EntityArchetypeQuery query, Allocator allocator)` which takes a single EntityArchetypeQuery directly and will return a NativeArray<ArchetypeChunk>. (Simplifying the case where no logical-or between multiple EntityArchetypeQuery is needed.)
133+
134+
The caller is responsible for calling Dispose() on the `NativeArray<ArchetypeChunk>`.
135+
136+
An ArchetypeChunk type and by extension a `NativeArray<ArchetypeChunk>` is always read-only. (And should be marked [ReadOnly] when used in jobs.)
137+
138+
However the arrays of component data within those chunks can be retrieved as either read-write or read-only, as needed.
139+
140+
## Accessing component data in chunks
141+
142+
To access data within a chunk, a `ChunkComponentType` is required which represents the specific component type and read-only attribute requested. From within a ComponentSystem or JobComponentSystem, this is retrieved by a call to `GetArchetypeChunkComponentType<T>(bool isReadOnly = false)` which returns a `ArchetypeChunkComponentType<T>`.
143+
144+
For instance, in order to gain read-only access to the Position component data in the Chunks matching the archetypes above:
145+
```
146+
var RotationTypeRO = GetArchetypeChunkComponentType<Rotation>(true);
147+
```
148+
Or read-write access to the LocalToWorld component data:
149+
```
150+
var LocalToWorldTypeRW = GetArchetypeChunkComponentType<LocalToWorld>(false);
151+
```
152+
153+
When used in a Job, the \[ReadOnly\] attribute must match the type. e.g.
154+
```
155+
[ReadOnly] public ArchetypeChunkComponentType<Rotation> rotationType;
156+
public ArchetypeChunkComponentType<LocalToWorld> localToWorldType;
157+
```
158+
159+
To retrieve the actual component data for reading or editing, `ArchetypeChunk.GetNativeSlice<T>(ArchetypeChunkComponentType<T> chunkComponentType)` is used, which returns `NativeSlice<T>`
160+
161+
e.g. For a given ArchetypeChunk (chunk), the Position and LocalToWorld data can be retrieved as:
162+
```
163+
var chunkPositions = chunk.GetNativeSlice(positionType);
164+
var chunkLocalToWorlds = chunk.GetNativeSlice(localToWorldType);
165+
```
166+
167+
Implicit to an `EntityArchetypeQuery` is that every chunk may not have the same components available. In this case, for instance, chunks coming from different archetypes may or may not have Position components. In that case, the length of the returned array will be zero. e.g. Existance of Position component data in a chunk can be confirmed by:
168+
```
169+
var chunkPositionsExist = chunkPositions.Length > 0;
170+
```
171+
172+
For iteration, the number of instances in a chunk can be retrieved with `ArchetypeChunk.Count`.
173+
174+
After confirming existance of the component data for the chunk, the data can be read/written as expected. e.g.
175+
```
176+
for (int i = 0; i < chunk.Count; i++)
177+
{
178+
chunkLocalToWorlds[i] = new LocalToWorld
179+
{
180+
Value = float4x4.translate(chunkPositions[i].Value)
181+
};
182+
}
183+
```
184+
185+
## Accessing entity data in chunks
186+
187+
Iterating Entity values in chunks is very similar to Components.
188+
189+
The Entity type is requested from within a ComponentSystem or JobComponentSystem by `GetArchetypeChunkEntityType()` with returns a `ArchetypeChunkEntityType`. Entity type is always read-only.
190+
191+
e.g.
192+
```
193+
var EntityTypeRO = GetArchetypeChunkEntityType();
194+
```
195+
196+
Similarly ArchetypeChunkEntityType should always include the \[ReadOnly\] attribute when used in a Job. e.g.
197+
```
198+
[ReadOnly] public ArchetypeChunkEntityType entityType;
199+
```
200+
201+
To retrieve the Entity values given a specific ArchetypeChunk, GetNativeSlice is used as with component data:
202+
```
203+
var chunkEntities = chunk.GetNativeSlice(entityType);
204+
```
205+
206+
## Accessing SharedComponent (index) data in chunks
207+
208+
SharedComponent data cannot be directly accessed in a chunk. SharedComponent data is not stored in the chunks. However, each archetype contains the index of the specific value of the SharedComponent which is part of its definition, and indexes into the global list of SharedComponent values.
209+
210+
Retrieving the shared component index works very much like retrieving component data. An `ArchetypeChunkSharedComponentType<T>` is returned by a call to `GetArchetypeChunkSharedComponentType<T>()` within a ComponentSystem or JobComponentSystem.
211+
212+
Like `ArchetypeChunkEntityType`, `ArchetypeChunkSharedComponentType` is always read-only.
213+
214+
The index of the shared component is returned by `ArchetypeChunk.GetSharedComponentIndex<T>(ArchetypeChunkSharedComponentType<T> chunkSharedComponentData)`
215+
216+
e.g.
217+
```
218+
var chunkDepthSharedIndex = chunk.GetSharedComponentIndex(depthType);
219+
```
220+
221+
Where depthType is:
222+
```
223+
[ReadOnly] public ArchetypeChunkSharedComponentType<Depth> depthType;
224+
```
225+
226+
The total number of shared component instances the archetype the chunk belongs to includes can also be retrieved by: `ArchetypeChunk.NumSharedComponents()`
227+
228+
## Accessing SharedComponent data from ArchetypeChunk.GetSharedComponentIndex
229+
230+
Shared component values can be retrieved from EntityManager via `EntityManager.GetAllUniqueSharedComponentData<T>(List<T> sharedComponentValues)`. However, this list is per SharedComponent type. The indices returned by `ArchetypeChunk.GetSharedComponentIndex` refer to the global shared component list and are not per-type.
231+
232+
In order to resolve these indices, a mapping from global to per-type index is needed.
233+
234+
Both the shared component values and the remapping can be retrieved from EntityManager via `EntityManager.GetAllUniqueSharedComponentData<T>(List<T> sharedComponentValues, List<int> sharedComponentIndices)`
235+
236+
For each `sharedComponentValue[i]`, the `sharedComponentIndices[i]` stores the global index of the shared component value.
237+
238+
e.g.
239+
```
240+
var sharedDepths = new List<Depth>();
241+
var sharedDepthIndices = new List<int>();
242+
EntityManager.GetAllUniqueSharedComponentData(sharedDepths, sharedDepthIndices);
243+
244+
...
245+
246+
var chunkDepthSharedIndex = chunk.GetSharedComponentIndex(depthType);
247+
var chunkDepthIndex = sharedDepthIndices.IndexOf(chunkDepthSharedIndex);
248+
var chunkDepth = sharedDepths[chunkDepthIndex];
249+
```
250+
251+
Additionally, the total count of all shared components in the global list can be retrieved with `EntityManager.GetSharedComponentCount()`
252+
253+
## Common use in Jobs
254+
255+
It's expected to iterate over `NativeArray<ArchetypeChunk>` in an IJobParallelFor. Then within the Job Execute, for each ArchetypeChunk, based on what Components exist, the code would loop over all the appropriate components. i.e. It's a batch operation.
256+
257+
Additionally:
258+
259+
- `ArchetypeChunkArray.CalculateEntityCount(NativeArray<ArchetypeChunk> chunks)` is a utility which returns the complete entity count for all chunks in the array.
260+
- Each ArchetypeChunk contains a StartIndex value which is the entity count offset within the NativeArray<ArchetypeChunk>.
261+
262+
e.g.
263+
```
264+
struct CollectValues : IJobParallelFor
265+
{
266+
[ReadOnly] public NativeArray<ArchetypeChunk> chunks;
267+
[ReadOnly] public ArchetypeChunkComponentType<EcsTestData> ecsTestData;
268+
269+
[NativeDisableParallelForRestriction] public NativeArray<int> values;
270+
271+
public void Execute(int chunkIndex)
272+
{
273+
var chunk = chunks[chunkIndex];
274+
var chunkStartIndex = chunk.StartIndex;
275+
var chunkCount = chunk.Count;
276+
var chunkEcsTestData = chunk.GetNativeSlice(ecsTestData);
277+
278+
for (int i = 0; i < chunkCount; i++)
279+
{
280+
values[chunkStartIndex + i] = chunkEcsTestData[i].value;
281+
}
282+
}
283+
}
284+
285+
public void TestCollectValues()
286+
{
287+
var query = new EntityArchetypeQuery
288+
{
289+
Any = Array.Empty<ComponentType>(),
290+
None = Array.Empty<ComponentType>(),
291+
All = new ComponentType[] {typeof(EcsTestData)}
292+
};
293+
var chunks = m_Manager.CreateArchetypeChunkArray(query, Allocator.Temp);
294+
var ecsTestData = m_Manager.GetArchetypeChunkComponentType<EcsTestData>(true);
295+
var entityCount = ArchetypeChunkArray.CalculateEntityCount(chunks);
296+
var values = new NativeArray<int>(entityCount, Allocator.TempJob);
297+
var collectValuesJob = new CollectValues
298+
{
299+
chunks = chunks,
300+
ecsTestData = ecsTestData,
301+
values = values
302+
};
303+
var collectValuesJobHandle = collectValuesJob.Schedule(chunks.Length, 64);
304+
collectValuesJobHandle.Complete();
305+
chunks.Dispose();
306+
307+
// Use values here...
308+
309+
values.Dispose();
310+
}
311+
```
312+
313+
## Change Versions
314+
315+
When a type is changed within a chunk, the version for that type within the chunk is assigned to the EntityManager.GlobalSystemVersion. By comparing the version number of a type within a chunk to the current GlobalSystemVersion in a ComponentSystem or JobComponentSystem, it can be inferred whether or not the type values have changed.
316+
317+
Special value: If the version number is zero, the chunk is new.
318+
319+
The version number is returned by `ArchetypeChunk.GetComponentVersion<T>(ArchetypeChunkComponentType<T> chunkComponentType)` e.g. `chunk.GetComponentVersion(positionType)`
320+
321+
Utilities are provided to compare version numbers and determine change:
322+
323+
`ChangeVersionUtility.DidChange(uint changeVersion, uint requiredVersion)` given the chunk type version number and the expected system version, respectively, will return whether or not the specified type in the chunk has been changed. New chunks will return false.
324+
325+
`ChangeVersionUtility.DidAddOrChange(uint changeVersion, uint requiredVersion)` given the chunk type version number and the expected system version, respectively, will return whether or not the specified type in the chunk has been changed or is new.
326+
327+
By way of example, in this case a chunk is skipped if no change was made to the specified types since the last iteration:
328+
```
329+
var chunkRotationsChanged = ChangeVersionUtility.DidAddOrChange(chunk.GetComponentVersion(rotationType), lastSystemVersion);
330+
var chunkPositionsChanged = ChangeVersionUtility.DidAddOrChange(chunk.GetComponentVersion(positionType), lastSystemVersion);
331+
var chunkScalesChanged = ChangeVersionUtility.DidAddOrChange(chunk.GetComponentVersion(scaleType), lastSystemVersion);
332+
var chunkAnyChanged = chunkRotationsChanged || chunkPositionsChanged || chunkScalesChanged;
333+
334+
if (!chunkAnyChanged)
335+
return;
336+
```

0 commit comments

Comments
 (0)