I don’t believe there is a ComponentArray equivalent in chunk iteration. However, i tend to simply grab the chunk’s entity array and just use EntityManager.GetComponentObject(entity).
This system uses [Inject] to retrieve GameObject entities with both a PlayerIdEntity MonoBehavior and Text MonoBehavior. It then finds ECS entities with both a PlayerId component and PlayerInput component.
After the entities have been collected, it looks through the MonoBehavior values, and when it finds a PlayerIdEntity.Value MonoBehavior and PlayerID.Value ECS entity that match, it appends the content of the ECS entity’s PlayerInput.Value to that GameObject entity’s Text.text value and destroys the ECS entity.
using JetBrains.Annotations;
using System;
using Unity.Burst;
using Unity.Entities;
using Unity.Collections;
using UnityEngine.UI;
[UsedImplicitly]
public sealed class PlayerInputToTextSystem : ComponentSystem {
private ComponentGroup playerInputGroup;
private struct TextData {
[UsedImplicitly] public Text Text;
[UsedImplicitly] public PlayerIdEntity PlayerId;
}
protected override void OnCreateManager() {
playerInputGroup = GetComponentGroup(
new EntityArchetypeQuery {
All = new[] {
ComponentType.Create<PlayerId>(),
ComponentType.Create<PlayerInput>()
},
Any = Array.Empty<ComponentType>(),
None = Array.Empty<ComponentType>()
}
);
}
[BurstCompile]
protected override void OnUpdate() {
var playerInputChunks = playerInputGroup.CreateArchetypeChunkArray(Allocator.TempJob);
var chunkEntityType = GetArchetypeChunkEntityType();
var chunkPlayerIdType = GetArchetypeChunkComponentType<PlayerId>();
var chunkPlayerInputType = GetArchetypeChunkComponentType<PlayerInput>();
foreach (var entity in GetEntities<TextData>()) {
foreach (var chunk in playerInputChunks) {
var entities = chunk.GetNativeArray(chunkEntityType);
var playerIds = chunk.GetNativeArray(chunkPlayerIdType);
var playerInputs = chunk.GetNativeArray(chunkPlayerInputType);
for (var i = 0; i < playerIds.Length; i++) {
if (playerIds[i].Value != entity.PlayerId.Value) {
continue;
}
entity.Text.text += (char)playerInputs[i].Value;
EntityManager.DestroyEntity(entities[i]);
}
}
}
playerInputChunks.Dispose();
}
}
As mentioned in my original post, I’m quite new to chunk iteration, so any helpful suggestions on how to improve my approach are much appreciated.
Do you have an example of how ‘EntityManager.GetComponentObject(entity)’ can be used to find entities with given MonoBehavior components (eg: Text and PlayerIdEntity)?
Text inherits from MonoBehavior (it’s in UnityEngine.UI). It doesn’t throw any exceptions when I find it as you mention above, but when I try to use “GetArchetypeChunkComponentType()”, it throws an exception stating that Text must be a non-nullable type.
Is there a way to obtain and iterate through components and entities aside from the method that I’m using above that would be more applicable here?
Below is an example of how I use chunk iteration with Unity’s CharacterController. Notice that I am not using GetArchetypeChunkComponentType with CharacterController. Rather, I am getting the relevant entity and calling EntityManager.GetComponentObject().
public class CharacterControllerSystem : ComponentSystem
{
private ComponentGroup _componentGroup;
protected override void OnCreateManager()
{
_componentGroup = GetComponentGroup(new EntityArchetypeQuery
{
Any = Array.Empty<ComponentType>(),
None = Array.Empty<ComponentType>(),
All = new ComponentType[] { typeof(Rotation), typeof(CharacterMovement), typeof(CharacterSpeed), typeof(CharacterController) }
});
}
protected override void OnUpdate()
{
var chunks = _componentGroup.CreateArchetypeChunkArray(Allocator.TempJob);
if (chunks.Length == 0)
{
chunks.Dispose();
return;
}
var entityTypeRO = GetArchetypeChunkEntityType();
var rotationTypeRO = GetArchetypeChunkComponentType<Rotation>(true);
var characterMovementTypeRO = GetArchetypeChunkComponentType<CharacterMovement>(true);
var characterSpeedTypeRO = GetArchetypeChunkComponentType<CharacterSpeed>(true);
var dt = Time.deltaTime;
for (var chunkIndex = 0; chunkIndex < chunks.Length; chunkIndex++)
{
var chunk = chunks[chunkIndex];
var entities = chunk.GetNativeArray(entityTypeRO);
var rotations = chunk.GetNativeArray(rotationTypeRO);
var movements = chunk.GetNativeArray(characterMovementTypeRO);
var speeds = chunk.GetNativeArray(characterSpeedTypeRO);
for (var i = 0; i < chunk.Count; i++)
{
var controller = EntityManager.GetComponentObject<CharacterController>(entities[i]);
var movement = movements[i].Value * speeds[i].Value * dt;
var motion = math.rotate(rotations[i].Value, movement);
controller.Move(motion);
}
}
chunks.Dispose();
}
}
You can use ComponentGroup.GetComponentArray<>() to get an array of MonoBehaviour components. Calling EntityManager.GetComponentObject() on every entity is slow.
Yes, you’re right. It doesn’t. For these cases, I’m willing to forego chunk iteration and use GetComponentArray() and GetComponentDataArray() if I really need both struct components and MonoBehaviour components. Hopefully, a new API that works for MonoBehaviour components in chunk iteration would be introduced.
I used GetComponentObject in one of my in development lib so I have done some research about this.
The indexer/iterator indeed is faster (and “feel faster” since you are using int to access) but it is good to know that GetComponentObject is not so bad as it feel. Entity is actually a key to get the managed data.
CA
Baked type index with generic trick.
Getting the correct chunk : if the same chunk with previous access it is almost free (just if and some booleans). If the index goes over to the new chunk, there are heavy calculation to turn the new index to a new chunk which I guess is expensive.
Inside of line 53 contains one line of code equals to right side’s line 50.
EM
Type create static access cost to get the type index.
Assert does not count in real build.
Getting the correct chunk : 2x pointer access + 2x int copy via out (chunk + chunk index) with Entity as an indexer, no searching.
GetManagedObject : 1x pointer access + cast.
CA wins by small margin since accessing static will be on heap but baked local int should be a stack access and so closer? (not sure)
EM has consistent 2 int data copy access cost. Entity speed up access like dictionary.
CA is expensive on chunk boundary change but cheaper on the same chunk. I am not sure how cheaper the if condition compared to what EM did, but I guess my lib should process less enough entities that using chunk iteration with EM gives me better gains from chunk iteration to offset this CA advantage. (skips chunks, NativeArray Burst advantage, etc)
3. This is a draw.