How to properly migrate IJobForEach -> IJobChunk (writing to multiple chunks)?

The setup: An Enemy attacks the Player. To effect damage, the Enemy decrements the Player’s health (EnemySystem_ForEach.cs below). I attempted to rewrite this ForEach as an IJobChunk (EnemySystem_IJobChunk.cs below).

Problem: The ForEach writes to both the Enemy and the Player chunks. As a result, I tried to pass the health container* to IJobChunk. However, runtime provides an error that says the container must be marked readonly. *health container being ComponentDataFromEntity.

Question: How do I rewrite ForEach as an IJobChunk, when ForEach is writing to chunks of different archetypes?

Note: This came up after realizing IJobForEach* structs are deprecated. Otherwise, I’d be more than happy to use IJobForEach*.
Note: One possibility is to include both Player and Enemy in the EntityQuery. In Execute, I would then iterate over all queried entities to find the player first, then iterate over all remaining entities, that are enemies. Is this reasonable? (nvm: This wouldn’t work because Enemy and Player have different archetypes, so they wouldn’t live in the same Execute call…)
Note: ECSSamples includes IJobChunk but both exampes as of writing (ex 1, ex 2), afaik, only write to chunks of one archetype at a time.

Original working code EnemySystem_ForEach.cs

public class EnemySystem : SystemBase {
    protected override void OnUpdate()
    {
        var healthGroup = GetComponentDataFromEntity<HealthComponent>();
        var player = GetSingletonEntity<PlayerComponent>();

        Dependency = Entities.ForEach((
            Entity entity,
            int entityInQueryIndex,
            ref EnemyComponent enemy) =>
        {
            enemy.AttackCount += 1;

            var health = healthGroup[player];
            health.Value -= 1;
            healthGroup[player] = health;
        }.Schedule(Dependency);
}

Attempt at IJobChunk EnemySystem_IJobChunk.cs

public class EnemySystem : SystemBase {
    EntityQuery EnemyQuery => GetEntityQuery(typeof(EnemyComponent));

    struct DuckSystemJob2 : IJobChunk
    {
        // breaks down when I add the following line, as it must be readonly
        public ComponentDataFromEntity<HealthComponent> HealthGroup;
        public ArchetypeChunkComponent<EnemyComponent> EnemyType;

        public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
       {
           var chunkEnemies = chunk.GetNativeArray(EnemyType);
           for (var i = 0; i < chunk.Count; i++) {
               var enemy = chunkEnemies[i];
               enemy.AttackCount += 1;
               chunkEnemies[i] = enemy;
           }
        }
    }
    protected override void OnUpdate() {
        Dependency = new EnemySystemJob() {
            HealthGroup = GetComponentDataFromEntity<HealthComponent>(),
        }.Schedule(EnemyQuery, Dependency);
    }
}

This is not an IJobForEach, this is a lambda Entities.ForEach which is not deprecated.

Thanks for the reply. For sure, I understand Entities.ForEach isn’t deprecated.

My goal is to switch to a class-style job instead of a lambda, for code cleanliness. Since IJobForEach isn’t a viable long-term option, I turned to IJobChunk.

Your job chunk is not doing anything in the execute. That’s probably why it tells you it should be read only, because your code is not accutally assigning anything to it.
Also if you are looking to implement an ability system, feel free to look at my public repo on MGM Ability. (still WIP :wink: )

The equivalent of ForEach.Schedule is JobChunk.ScheduleSingle (they seriously need to follow a unique convention here).


In case you wonder why to use ScheduleSingle, you can only use GetComponentDataFromEntity as read-only in parallel jobs.

1 Like