Two questions about Barriers and EntityCommandBuffer

I’m trying to understand command buffers and the job system.

  1. [Inject] will be removed in a future ECS release. How can you access a barrier to create a command buffer without the attribute? All the docs still use [Inject] from what I can see.

  2. Should you call CreateCommandBuffer() every update or can you cache the buffer? Caching seems to work but maybe it will become invalidated/disposed at some point?

Thanks

1 Like

World.Active.GetOrCreateManager(); in OnCreateManager()

For JobComponentSystem I have a concern that inject-less barriers would not work correctly, because in the code I see lines like

            // Notify all injected barrier systems that they will need to sync on any jobs we spawned.
            // This is conservative currently - the barriers will sync on too much if we use more than one.
            for (int i = 0; i < m_BarrierList.Length; ++i)
            {
                m_BarrierList[i].AddJobHandleForProducer(outputJob);
            }

Which it works with injected barriers.

        protected sealed override void OnBeforeCreateManagerInternal(World world)
        {
            base.OnBeforeCreateManagerInternal(world);

            m_BarrierList = ComponentSystemInjection.GetAllInjectedManagers<BarrierSystem>(this, world);
        }

If you add barriers not by injection, then they will update like normal systems and playback commands from ECB but won’t call complete on your scheduled jobs. And then commands which you queued up in-job may execute at the same time while the job is still running? (If the barrier’s update came before the job finished)

Barrier deallocate all of its ECB on its time to update, so you need to remake the ECB. Caching should not work past the barrier’s update point.

Thanks both.

I missed that somehow. I didn’t realise EntityManager, ComponentSystem and BarrierSystem are all actually the same base class ScriptBehaviourManager. The naming is a bit confusing to me.

Good to know.

You are correct, you must use [Inject] with JobComponentSystem, I don’t think there is any workaround for this yet. If you just get/create the system from the World, you will eventually end up with job dependency error (“… you must call complete on … before you can read/write to the … safely”).

never access World.Active from inside systems unless you really mean that.
systems have a World property that points to their world, which may be or not be the active world.

Yes, you are right, just wrote by inertia in my example above:) Systems have their own World property of course, as shown by @Adam-Mechtley in
https://discussions.unity.com/t/713583 page-2#post-3770272

Yeah this is an issue and will cause a dependency issue eventually which I found out the hard way.

I’m using a T GetBarrier<T>(this JobComponentSystem componentSystem) extension to handle this for now so I can remove all injects. This just manually adds it to the m_BarrierList.

When we get proper support I should be able to do a quick search/replace to fix it.

// <copyright file="JobComponentSystemExtensions.cs" company="Timothy Raines">
//     Copyright (c) Timothy Raines. All rights reserved.
// </copyright>

namespace BovineLabs.Common.Utility
{
    using System;
    using System.Reflection;
    using Unity.Entities;

    /// <summary>
    /// Extensions for the <see cref="JobComponentSystem"/>.
    /// </summary>
    public static class JobComponentSystemExtensions
    {
        private static bool setup;
        private static PropertyInfo _worldPropertyInfo;
        private static FieldInfo barrierListFieldInfo;

        /// <summary>
        /// Gets or creates a barrier and correctly adds it to the barrier list.
        /// </summary>
        /// <typeparam name="T">The type of the barrier to get or create.</typeparam>
        /// <param name="componentSystem">The <see cref="JobComponentSystem"/>.</param>
        /// <returns>The barrier.</returns>
        public static T GetBarrier<T>(this JobComponentSystem componentSystem)
            where T : BarrierSystem
        {
            if (!setup)
            {
                Setup();
            }

            var world = (World)_worldPropertyInfo.GetValue(componentSystem);
            var barrierList = (BarrierSystem[])barrierListFieldInfo.GetValue(componentSystem);

            Array.Resize(ref barrierList, barrierList.Length + 1);

            var barrier = world.GetOrCreateManager<T>();
            barrierList[barrierList.Length - 1] = barrier;

            barrierListFieldInfo.SetValue(componentSystem, barrierList);

            return barrier;
        }

        private static void Setup()
        {
            _worldPropertyInfo = CreateWorldGet();
            barrierListFieldInfo = CreateBarrierListGet();

            setup = true;
        }

        private static PropertyInfo CreateWorldGet()
        {
            var propertyInfo = typeof(ComponentSystemBase).GetProperty("World", BindingFlags.NonPublic | BindingFlags.Instance);

            if (propertyInfo == null)
            {
                throw new NullReferenceException("World changed");
            }

            return propertyInfo;
        }

        private static FieldInfo CreateBarrierListGet()
        {
            var fieldInfo = typeof(JobComponentSystem).GetField("m_BarrierList", BindingFlags.NonPublic | BindingFlags.Instance);

            if (fieldInfo == null)
            {
                throw new NullReferenceException("m_BarrierList changed");
            }

            return fieldInfo;
        }
    }
}

May I ask Unity devs (i.e. @Joachim_Ante_1 ) for comment upon that matter?

Is it best to stick with injection for now, or any of above proposed solutions are more appropriate?

6 Likes

Hello!

I’m interested in knowing this as well!

Be well!

  • S
2 Likes