DynamicBuffer cannot use Contain<>()

Hello. I’m new to DOTS and ECS. I’m trying to implement this mechanic: a ball can move through multiple zones, and it will trigger some events every time it enters a new zone. To do that, when the ball enters a new zone, I stored that zone’s id in a dynamic buffer component attached to the ball entity. So the next time it enters that zone again, I check if the zone’s id is in the list to know if it is new or not. I have implemented all required physic components and collision/trigger detecting scripts as the Physics Example. However, when I try to check and add a new element to the dynamic buffer list component of the ball, an error says
"NotImplementedException: The method or operation is not implemented.
Unity.Entities.DynamicBuffer`1[T].System.Collections.Generic.IEnumerable.GetEnumerator () (at ./Library/PackageCache/com.unity.entities@1.3.5/Unity.Entities/Iterators/DynamicBuffer.cs:569)"
Here is my code:

[UpdateInGroup(typeof(PhysicsSystemGroup))]
[UpdateAfter(typeof(StatefulTriggerEventBufferSystem))]
public partial class TriggerMultiplySystem : SystemBase
{
    private EndFixedStepSimulationEntityCommandBufferSystem m_CommandBufferSystem;
    private EntityQuery m_NonTriggerQuery;
    private EntityQueryMask m_NonTriggerMask;
    private EntityManager enMa;

    protected override void OnCreate()
    {
        m_CommandBufferSystem = World.GetOrCreateSystemManaged<EndFixedStepSimulationEntityCommandBufferSystem>();
        enMa = World.DefaultGameObjectInjectionWorld.EntityManager;

        m_NonTriggerQuery =
            GetEntityQuery(new EntityQueryDesc
            {
                None = new ComponentType[]
                {
                    typeof(StatefulTriggerEvent)
                }
            });
        Assert.IsFalse(m_NonTriggerQuery.HasFilter(), "The use of EntityQueryMask in this system will not respect the query's active filter settings.");
        m_NonTriggerMask = m_NonTriggerQuery.GetEntityQueryMask();

        RequireForUpdate<MultipleZone>();
    }

    protected override void OnUpdate()
    {
        EntityCommandBuffer commandBuffer = m_CommandBufferSystem.CreateCommandBuffer();

        // Need this extra variable here so that it can be captured by Entities.ForEach loop below
        var nonTriggerMask = m_NonTriggerMask;
        // get a list of all ContactedZone component
        BufferLookup<ContactedZone> contactedZoneLookup = GetBufferLookup<ContactedZone>();

        foreach (var (triggerEventBuffer, mul, entity) in SystemAPI.Query<DynamicBuffer<StatefulTriggerEvent>, RefRO<MultipleZone>>().WithEntityAccess())
        {
            for (int i = 0; i < triggerEventBuffer.Length; i++)
            {
                var triggerEvent = triggerEventBuffer[i];
                var ball = triggerEvent.GetOtherEntity(entity);

                if (triggerEvent.State == StatefulEventState.Stay || triggerEvent.State == StatefulEventState.Exit || !nonTriggerMask.MatchesIgnoreFilter(ball))
                {
                    continue;
                }

                if (triggerEvent.State == StatefulEventState.Enter)
                {
                    Debug.Log("Ball trigger on zone x" + mul.ValueRO.multiplier);
                    // get the list "contactedZone" of "ball" entity
                    var l = contactedZoneLookup[ball];
                    var l1 = l.Reinterpret<int>();
                    if (!l1.Contains (mul.ValueRO.id))
                    {
                        Debug.Log("trigger events");
                        l.Add(new ContactedZone { zoneId = mul.ValueRO.id });
                    }
                }
            }
        }
        m_CommandBufferSystem.AddJobHandleForProducer(Dependency);
    }
}

It seems the line 56 if (!l1.Contains (mul.ValueRO.id)) throws the error, but I don’t know how to fix it. Other tutorials just call Add method normally and it works, but the system scripts they use are implemented from ISystem interface, not a subclass of SystemBase. I also consider to use ISystem, but the physics example only uses SystemBase.

I think its the use of Contains which DynamicBuffers don’t implement. If you reinterpret or get the buffer as a NativeArray there are NativeArrayExtensions which implement Contains(). Side note entitymanager is a property of any SystemBase derived system.

1 Like

As a general rule, avoid LINQ with all DOTS types. It is very easy to accidentally start using LINQ, because for some reason Unity decided to inherit some of those interfaces on their collection types but not implement them.

1 Like

Thank you all @DreamingImLatios and @thelebaron! I have removed the use of Contain<>() and use a foreach loop to check it manually. The Add() method works now, but there is another problem. Unity keeps throwing this error every update when I start playing:
System.InvalidOperationException: Adding/removing components or changing position/rotation/velocity/collider ECS data on dynamic entities during physics step
This Exception was thrown from a job compiled with Burst, which has limited exception support.

So basically I should not add new element to a dynamic buffer in my trigger detection update. Is there any alternative way? I’m considering to add all the zones’ id to the list from the start, and add a bool field to ContactedZone component to check if a zone is new or not, but this is not good if the zones are spawned in runtime.
My code now looks like this:

if (triggerEvent.State == StatefulEventState.Enter)
{
				Debug.Log("Ball trigger on zone x" + mul.ValueRO.multiplier);
				// get the list of zones those have triggered to entity "ball"
				DynamicBuffer<ContactedZone> triggeredZone = EntityManager.GetBuffer<ContactedZone>(ball);
				// check if the current zone is in the list
				bool exist = false;
				foreach (ContactedZone contactedZone in triggeredZone) {
					if (contactedZone.zoneId == mul.ValueRO.id) {
						exist = true; break;
					}
				}
    // if not => first time trigger
    if (!exist) {
        // add this zone id to the list
        triggeredZone.Add(new ContactedZone { zoneId = mul.ValueRO.id });
        Debug.Log("add new zone id to the list");
    }
}

That is not your issue at all. Adding elements to a DynamicBuffer is not considered a “Structural Change”. Something else is triggering your error.

1 Like

While that works, you may be interested in trying the fix by thelebaron which is to use dynamicBuffer.AsNativeArray().Contains(value) (DynamicBuffer.AsNativeArray, NativeArrayExtensions.Contains).

1 Like