Why does this Job execute 3 times?

Hi all,

I’m very new to ECS / DOTS. Can anyone tell me why this job (IJobEntity) exectues 3 times? The System that schedules the job only runs once as expected, however i’m confused as to why the job itself executes 3 times… effectively creating 3 maps when i’m trying to create 1.

The Singleton “MapGeneratorSingletonData” is used to control if/when the (Map Generator) System runs. The Job is supposed to update the Singleton so that the System then only runs once. It works… kinda. The System DOES only run once however the Job executes 3 times.

Here is the full code…

using Level7.Controller;
using Level7.Model;
using Level7.Utils;
using log4net;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;

[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(MapDestroyerSystem))]
public partial class MapBaseCreatorSystem : SystemBase
{
    private readonly ILog _log = ServiceLocator.Instance.GetLog();

    protected override void OnCreate()
    {
        RequireForUpdate<MapGeneratorSingletonData>();
    }
  
    protected override void OnUpdate()
    {
        if (SystemAPI.GetSingleton<MapGeneratorSingletonData>().State == MapGeneratorSingletonData.MapGeneratorState.BASECREATE)
        {
            _log.Debug($"~~~ {GetType()} ~~~");

            EntityCommandBuffer _entityCommandBuffer = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>().CreateCommandBuffer(World.Unmanaged);

            MapBaseCreatorJob job = new MapBaseCreatorJob()
            {
                MapArchetype = ServiceLocator.Instance.GetService<MainController>().MapManager.Factory.GetMapArchetype(),
                MapCellArchetype = ServiceLocator.Instance.GetService<MainController>().MapManager.Factory.GetMapCellArchetype(),
                MapSettingsData = SystemAPI.GetSingleton<GameSettingsData>().MapSettingsData,
                EntityCommandBuffer = _entityCommandBuffer,
                MapGeneratorSingletonEntity = SystemAPI.GetSingletonEntity<MapGeneratorSingletonData>()
            };

            Dependency = job.Schedule(Dependency);
        }
    }

    [BurstCompile]
    public partial struct MapBaseCreatorJob : IJobEntity
    {
        [ReadOnly] public EntityArchetype MapArchetype;
        [ReadOnly] public EntityArchetype MapCellArchetype;
        [ReadOnly] public MapSettingsData MapSettingsData;
        public EntityCommandBuffer EntityCommandBuffer;
        public Entity MapGeneratorSingletonEntity;

        private void Execute()
        {
            ServiceLocator.Instance.GetLog().Debug($"EXECUTING MapBaseCreatorJob");

            Entity mapEntity = EntityCommandBuffer.CreateEntity(MapArchetype);
            EntityCommandBuffer.SetComponent(mapEntity, new MapData
            {
                Dummy = 0
            });

            EntityCommandBuffer.SetComponent(mapEntity, new MapSizeData
            {
                Width = MapSettingsData.Width,
                Height = MapSettingsData.Height
            });

            DynamicBuffer<MapCellsBuffer> mapCells = EntityCommandBuffer.SetBuffer<MapCellsBuffer>(mapEntity);

            for (int x = 0, i = 0; x < MapSettingsData.Width; x++)
            {
                for (int y = 0; y < MapSettingsData.Height; y++)
                {
                    Entity mapCell = CreateMapCell(mapEntity, x, y, i);
                    mapCells.Add(new MapCellsBuffer { Entity = mapCell });
                    i++;
                }
            }

            EntityCommandBuffer.SetComponent(mapEntity, new RandomData
            {
                Value = new Unity.Mathematics.Random(999)
            });

            EntityCommandBuffer.SetComponent(MapGeneratorSingletonEntity, new MapGeneratorSingletonData { State = MapGeneratorSingletonData.MapGeneratorState.COMPLETE });
        }

        private Entity CreateMapCell(Entity mapEntity, int x, int y, int i)
        {
            Entity mapCellEntity = EntityCommandBuffer.CreateEntity(MapCellArchetype);
            EntityCommandBuffer.SetComponent(mapCellEntity, new MapCellData
            {
                Index = i,
                Coordinates = HexCoordinates.FromOffsetCoordinates(x, y)
            });
            EntityCommandBuffer.SetComponent(mapCellEntity, new MapCellTerrainData
            {
                Type = ETerrainType.PLAINS
            });
            EntityCommandBuffer.SetComponent(mapCellEntity, new ParentData
            {
                Entity = Entity.Null
            });

            DynamicBuffer<MapCellNeighboursBuffer> mapCellNeighbours = EntityCommandBuffer.SetBuffer<MapCellNeighboursBuffer>(mapCellEntity);
            for (int n = 0; n < 7; n++)
            {
                mapCellNeighbours.Add(new MapCellNeighboursBuffer { Entity = Entity.Null });
            }

            return mapCellEntity;
        }
    }

}

And here is the snippit of the log where it cleary shows the System executing 1 time the job 3 times…

2023-05-09 22:51:27,792 | DEBUG | MapDestroyerSystem | OnUpdate | 26 | ~~~ MapDestroyerSystem ~~~
2023-05-09 22:51:27,794 | DEBUG | MapBaseCreatorSystem | OnUpdate | 25 | ~~~ MapBaseCreatorSystem ~~~
2023-05-09 22:51:27,800 | DEBUG | MapBaseCreatorSystem+MapBaseCreatorJob | Execute | 53 | EXECUTING MapBaseCreatorJob
2023-05-09 22:51:27,808 | DEBUG | MapBaseCreatorSystem+MapBaseCreatorJob | Execute | 53 | EXECUTING MapBaseCreatorJob
2023-05-09 22:51:27,809 | DEBUG | MapBaseCreatorSystem+MapBaseCreatorJob | Execute | 53 | EXECUTING MapBaseCreatorJob
2023-05-09 22:51:27,837 | DEBUG | OnMapGeneratorCompleteSystem | OnUpdate | 29 | ~~~ OnMapGeneratorCompleteSystem ~~~
2023-05-09 22:51:27,864 | DEBUG | Level7.Utils.MessageBus | Publish | 35 | Publish --) MapGenerationCompleteMsg

I have tried using .Run to exectute the job and the same thing happens, it Execute 3 times.

I’m stumped :face_with_spiral_eyes:

Thanks

Assuming I’m not blind and that you’ve posted all of the self-written (i.e. not source generated) parts of MapBaseCreatorJob (edit: I say this because I haven’t seen definitions mark the Execute method as private - that’s a first for me), your Execute signature is odd for an IJobEntity. See, IJobEntity is a front for heavy source generation of an underlying IJobChunk. The parameters to the IJobEntity.Execute method, in addition to the extra configuration afforded by attributes placed on the struct like [WithAll(typeof(sometype))], make it filter to run the Execute method once per matched entity. As you have no such filter attribute and have not qualified your Execute method with any component parameters, I suspect your job’s Execute is being run once per entity in the world. Queries by default ignore prefabs and system entities, so there are probably 3 “normal” entities in total at that point.

https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/iterating-data-ijobentity.html

You might be under the impression that you need to use IJobEntity to interact with the ECS world. That is not necessarily the case; you can use any appropriate job interface. Here, it would be most appropriate to use IJob if you really want this job’s Execute method to be run once.

To boil it down:
IJob: run Execute once somewhere.
IJobEntity: run Execute once per entity the job’s generated or manually passed query matches.

You should switch that job interface implementation to just IJob.

2 Likes

Oh dear, you’re right and I’m an idiot.

This is from an old project prior to 1.0 and i’m trying to convert it to 1.0. I’m in the process of trying to convert my IJobChunk jobs to IJob Entity jobs (which I believe is a sensible thing to do).

However this one was an IJob not an IJobChunk. So yes you’re correct this should be staying as an IJob. I’m getting confused coming back to DOTS and trying to update to 1.0.

Thanks very much.