Does my code look okay? Basic spawner script.

This is my first time exploring ECS/DOTS. I am NOT a programmer so I not too sure on this stuff.

Can anyone comment on my code? It seems to run and not log any errors. But I am not sure if I am laying out everything efficiently. Is there any improvements I should make?

I couldn’t find any examples/docs on good practices when it comes to using burst with a Entities.ForEach job as appose to IJobChuck. Is it okay to use burst with Entities.ForEach? I will eventually learn how to use IJobChuck but for learning purposes I want to do it later once I am more comfortable with this new way to coding.

Another question is about public Entity prefab; in SpawnerData.cs. Is Entity considered as a value type or reference type? Is it considered blittable?

Thanks for your help!

SpawnerData.cs

public struct SpawnerData : IComponentData
{
    public Entity prefab;
    public int count;
    public float rate;      // Spawn rate
    public float delay;     // Time delay before the first spawn
    public float next;      // Time until the next spawn
}

SpawnerAuthoring.cs

public class SpawnerAuthoring : MonoBehaviour, IConvertGameObjectToEntity
{
    public GameObject prefab;
    public int count = 10;
    public float rate = 1f;
    public float delay = 2f;
    private float next = 0f;

    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        SpawnerData data = new SpawnerData();

        var conversionSetting = GameObjectConversionSettings.FromWorld(dstManager.World, conversionSystem.BlobAssetStore);
        data.prefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(prefab, conversionSetting);

        data.count = count;
        data.rate = rate;
        data.delay = delay;

        if (delay == 0f)
            data.next = 0f;
        else
            data.next = delay;

        dstManager.AddComponentData<SpawnerData>(entity, data);
    }
}

SpawnerSystem.cs

public class SpawnerSystem : SystemBase
{
    private EndSimulationEntityCommandBufferSystem commandBufferSystem;

    protected override void OnCreate() { commandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>(); }

    protected override void OnUpdate()
    {
        var dT = Time.DeltaTime;
        var commandBuffer = commandBufferSystem.CreateCommandBuffer().ToConcurrent();

        Entities.ForEach((Entity entity, int entityInQueryIndex, ref SpawnerData data) =>
        {
            // Check if it is time to spawn
            if (data.next <= 0f)
            {
                commandBuffer.Instantiate(entityInQueryIndex, data.prefab); // Spawn a prefab entity

                data.count--;   // Reduce the spawn count

                // Check if spawn count has reached 0
                if (data.count == 0)
                {
                    commandBuffer.DestroyEntity(entityInQueryIndex, data.prefab);   // Destroy the reference entity
                    commandBuffer.DestroyEntity(entityInQueryIndex, entity);        // Destroy this entity
                }

                else
                    data.next = data.rate;  // Reset the spawn timer
            }

            else
                data.next -= dT;    // Reduce the time until next spawn

        }).WithBurst().ScheduleParallel();

        commandBufferSystem.AddJobHandleForProducer(Dependency);
    }
}

Seems fair. I not sure how necessary it is to do this in parallel. It depends on how much you are spawning per frame. Are you spawning more than like 50 per frame?

I am not spawning too much. Maybe 1 ai unit per half a second for about 40 - 50 units per spawner.

Holy cow are you the same name on youtube? If so, I actually watched your series to get started with ECS. Amazing series. Your videos are probably the most helpful one I have come across so far.

Thanks for your input!

Looks similar to how I would do it. One thing I am thinking of is the fact that you access and read data.next every frame, however all other data is not being read as often. Therefore moving it to another IComponentData might improve performance by streamlining the memory and read/write. This is my guess.

1 Like

Yes, I was thinking about doing that too but wasn’t sure if would be considered premature optimization.

Thanks for your input!

  1. You shouldn’t need both delay and next fields. Just initialize next to be whatever the delay is during conversion.
  2. You should never need to call GameObjectConversionUtility inside IConvertGameObjectToEntity.Convert. Instead, use IDeclareReferencedPrefabs and ConversionSystem.{Try}GetPrimaryEntity. This will cause the prefab to be shared between spawners rather than create unique instances, which will reduce subscene size and runtime memory requirements. I have an example of what this looks like here: lsss-wip/Assets/_Code/Authoring/FactionAuthoring.cs at master · Dreaming381/lsss-wip · GitHub
  3. Because of (2), you will not want to destroy the prefab entity anymore as it is shared. There are better (less scattered code and more performant) ways to clean up prefab entities. System is otherwise fine. I highly doubt the components and systems involved here are going to be chewing up much of your frame budget.
1 Like
  1. Correct I didn’t need delay in there. I have removed.

  2. and 3) I will study your github to understand.

Thank you!

Then you should use .Run() instead, .ScheduleParalell() has a bit of overhead, and you code will likely execute faster then the schedule code itself.

And yes, I’m that guy on Youtube. Thanks a lot! That is that stuff that makes it really worth it! =) I’m on a sort of vacation right now, so the series will not return until in a little over a week. But it 's really nice to hear that people are finding it helpful!

2 Likes