Schrödinger's Entity. Has both no data and data

EDIT: The problem was that I have a List of game objects, that I convert into entities and then when I need to place that auto/entity on the road I make a new instance of the original entity. Those Entity-Prefabs are the ones that caused my problem. NOT Unity at all!


Ok, I have an odd situation, and I can’t think of a short way to state this so…

I have an road asset (Easy Roads 3D, HIGHLY recommend, good asset, FANTASTIC support) and it has lane data (a series of Vector3 points)

So, when my game starts I create a series of entities:

  • Autos and their current set of lane points
  • Roads and their lanes

In my System it moves the Auto from point to point, and when it reaches the end of the Auto’s current set of points, it gets the next set.

So simple right? So as you look at the code below you’ll see that each Auto is now assigned an ‘identity’ (this is for debugging) and only one Auto is not being created (the issue ONLY happens with the first Auto, I have tested it with all roads get an Auto, and yea, the output showed only Identity=0 has the issue)

First we make the roads and each road gets the opportunity to get an auto (percentageLanesPopulated is set to 100)

private void CreateRoadEntities()
    {
        var roadEntityArchetype = entityManager.CreateArchetype(
            typeof(ERRoadTag),
            typeof(RoadDetails),
            typeof(LanePoints)
            );

        var connectionEntityArchetype = entityManager.CreateArchetype(
            typeof(ERConnectionTag),
            typeof(ConnectionDetails),
            typeof(LanePoints)
        );

        int nextRoadIdentity = 0;
        int nextLaneIndex = 0;
        int connectionIndexEnd;
        int connectionIndexStart;
        int connectionIdentityEnd = 0;
        int connectionIdentityStart = 0;
        ERConnection erConnectionEnd;
        ERConnection erConnectionStart;
        ERRoadNetwork roadNetwork = new ERRoadNetwork();
        ERRoad[] roads = roadNetwork.GetRoadObjects(); // .GetRoads();
        List<Vector3> allLanePoints = new List<Vector3>();
        List<Vector3> connectionLanePoints = new List<Vector3>();
        ConnectedTo connectedTo;


        //Make entities for each of these objects
        //Roads first.  They need a collection of Lanes and each lane has an input and output connection identity.
        //This collection of Road Identity & Lane Identity, or Connection Identity & Lane Identity
        //will be used in the JOB to get the next load of data
        foreach (ERRoad road in roads)
        {
            int roadIdentity = road.gameObject.GetComponent<ERRoadConnectionIdentity>().value;
            int laneCount = road.GetLaneCount();
          
            for (int lane = 0; lane < laneCount; lane++)
            {
                ERLaneData erLaneData = road.GetLaneData(lane);
                if (erLaneData.direction == ERLaneDirection.Right)
                {
                    erConnectionEnd = road.GetConnectionAtEnd(out connectionIndexEnd);
                    erConnectionStart = road.GetConnectionAtStart(out connectionIndexStart);
                }
                else
                {
                    erConnectionEnd = road.GetConnectionAtStart(out connectionIndexEnd);
                    erConnectionStart = road.GetConnectionAtEnd(out connectionIndexStart);                  
                }

                connectionIdentityStart = RoadConnectionIdentity(erConnectionStart.gameObject);
                connectionIdentityEnd = RoadConnectionIdentity(erConnectionEnd.gameObject);
 
                //Create the Road/Lane entity
                Entity roadEntity = entityManager.CreateEntity(roadEntityArchetype);
                entityManager.SetComponentData(
                    roadEntity,
                    new RoadDetails
                    {
                        RoadIdentity = roadIdentity,
                        LaneIndex = erLaneData.laneIndex,
                        ConnectionIdentityEnd = connectionIdentityEnd,
                        ConnectionIdentityStart = connectionIdentityStart,
                        ConnectionIndexEnd = connectionIndexEnd,
                        ConnectionIndexStart = connectionIndexStart
                    });


                //Get and store the road's lane points
                Vector3[] roadLanePoints = erLaneData.points;
                var roadLanePointsBuffer = entityManager.GetBuffer<LanePoints>(roadEntity);
                for (int point = 0; point < roadLanePoints.Length; point++)
                {
                    roadLanePointsBuffer.Add(new LanePoints { value = roadLanePoints[point] });
                }
                //---------------------------------------------------

                //Create this Road/Lane/Connection combo
                ERLaneConnector[] laneConnectors = erConnectionEnd.GetLaneData(connectionIndexEnd, erLaneData.laneIndex);
           
                for (int i = 0; i < laneConnectors.Length; i++)
                {
                    var laneConnector = laneConnectors[i];
                  
                    var endRoad = erConnectionEnd.GetConnectedRoad(laneConnector.endConnectionIndex, out connectedTo);
                    var roadIdentityEnd = RoadConnectionIdentity(endRoad.gameObject);

                    Entity connectionEntity = entityManager.CreateEntity(connectionEntityArchetype);
                    entityManager.SetComponentData(
                        connectionEntity,
                        new ConnectionDetails
                        {
                            ConnectionIdentity = connectionIdentityEnd,
                            LaneIndexStart = erLaneData.laneIndex,
                            LaneIndexEnd = laneConnector.endLaneIndex,
                            RoadIdentityStart = roadIdentity,
                            RoadIdentityEnd = roadIdentityEnd,
                            ConnectionIndexEnd = laneConnector.endConnectionIndex,
                            ConnectionIndexStart = connectionIndexStart
                        });


                    var connectionLanePointsBuffer = entityManager.GetBuffer<LanePoints>(connectionEntity);
                    for (int x = 0; x < laneConnector.points.Length; x++)
                    {
                        connectionLanePointsBuffer.Add(new LanePoints { value = laneConnector.points[x] });
                    }
                }
                //---------------------------------------------------
                              
                //Pick a connection and one of the output values to give to the cars that are about to be created
                int randomValue = UnityEngine.Random.Range(0, laneConnectors.Length);
                var laneConnect = laneConnectors[randomValue];
                var exitRoad = erConnectionEnd.GetConnectedRoad(laneConnect.endConnectionIndex, out connectedTo);

                nextLaneIndex = laneConnect.endLaneIndex;
                nextRoadIdentity = RoadConnectionIdentity(exitRoad.gameObject);
                connectionIndexEnd = laneConnect.endConnectionIndex;

                connectionLanePoints = laneConnect.points.ToList();

                allLanePoints.Clear();
                allLanePoints.AddRange(roadLanePoints);
                allLanePoints.AddRange(connectionLanePoints);

                Array.Clear(roadLanePoints, 0, roadLanePoints.Length);
                connectionLanePoints.Clear();

                //Option to not fill every lane
                int random = UnityEngine.Random.Range(0, 100);

                if (random <= percentageLanesPopulated)
                {
                    //Temp to ensure only one auto is made for this test
                    if (autoIdentity < 1)
                    {
                        Debug.Log("Lane count: " + allLanePoints.Count);
                        CreateAutoEntity(
                            allLanePoints,
                            nextLaneIndex,
                            nextRoadIdentity,
                            connectionIdentityEnd,
                            connectionIndexEnd);
                    }
                }
            }
        }

        allLanePoints.Clear();
        connectionLanePoints.Clear();
    }

Then the Auto entity and it’s road points is created:

private int autoIdentity = 0;
    private void CreateAutoEntity(
        List<Vector3> lanePoints,
        int nextLaneIdentity,
        int nextRoadIdentity,
        int nextConnectionIdentity,
        int connectionIndex)
    {
        Entity prefab = autoEntities[UnityEngine.Random.Range(0, autoEntities.Count)];
        var entity = entityManager.Instantiate(prefab);
     
        float speed = UnityEngine.Random.Range(speedMinimum, speedMaximum);
        int currentIndex = UnityEngine.Random.Range(0, lanePoints.Count - vehicleLength);

        Vector3 translation = lanePoints[currentIndex];
        translation.y += heightOffset;

        Vector3 destination = lanePoints[currentIndex + 1];
        destination.y += heightOffset;

        //float3 lookVector = destination - translation;
        Vector3 lookVector = destination - translation;
        Quaternion rotation = new Quaternion();

        if (lookVector.sqrMagnitude > 0.01f)
        {
            rotation = Quaternion.LookRotation(lookVector);
        }

        entityManager.SetComponentData(entity,
            new AutoDetails {
            yOffset = heightOffset,
            speed = speed });

        entityManager.SetComponentData(entity,
            new AutoPosition
            {
            CurrentPositionIndex = currentIndex,
            Destination = destination,
            LaneIndex = nextLaneIdentity,
            RoadIdentity = nextRoadIdentity,
            ConnectionIdentity = nextConnectionIdentity,
            ConnectionIndex = connectionIndex
            });

        entityManager.SetComponentData(entity, new Translation { Value = translation });
        entityManager.SetComponentData(entity, new Rotation { Value = rotation });

        //TEMP for debugging
        entityManager.SetComponentData(entity, new AutoIdentity { value = autoIdentity });
        autoIdentity++;
          Debug.Log("Lane points count: " + lanePoints.Count);

        var lanePointsBuffer = entityManager.GetBuffer<AutoLanePoints>(entity);
        for (int i = 0; i < lanePoints.Count; i++)
        {
            lanePointsBuffer.Add(new AutoLanePoints { value = lanePoints[i] });
        }
        Debug.Log("Buffer Count: " + lanePointsBuffer.Length);
    }

Note: In the last lines above, the “lanePoints” AND “lanePointsBuffer” both show a count of 161, until the System runs, then it returns a value of zero.

    protected override void OnUpdate()
    {
  
        Entities.WithAll<ERAutoTag>()
            .ForEach((
            DynamicBuffer<AutoLanePoints> autoLanePoints,
            ref AutoDetails autoDetails,
            ref AutoPosition autoPosition,
            ref Translation translation,
            ref Rotation rotation,
            ref AutoIdentity ident) =>
            {
                   Debug.Log("Ident: " + ident.value + "  Points: " + autoLanePoints.Length);

                        //THE REST OF THE CODE HAS BEEN REMOVED FOR TESTING
            }).Schedule();
    }

When I run the code I get this: Imgur: The magic of the Internet

Notice that at first there are 161 values in the buffer, but as soon as the System runs it reports BOTH 0 and 160 values, and if you notice it shows exactly 3 times as many without values as with values. At no time am I clearing or change that buffer.

Again, ONLY the first Auto entity has this issue. If I remove the IF statement in limiting the number of autos created, the debug log only shows IDENTITY 0 with the issue.

FEELS so odd. So what other dumbass thing am i doing wrong?

Thanks for reading all this and if you have any, ANY thoughts, let me know.

Peace,
James

oh, and one more thing: In the DOTS Hierarchy the entity shows the correct number of data points. Again, if anyone has ANY ideas, I am open to being really, REALLY stupid here, so don’t hesitate to show me where I am wrong.

Thanks

Admittedly, it is a little difficult for me to make sense of what you are describing. Perhaps it would help to insert comments in your code stating what values you are getting for specific lines of code. And also use comments to highlight where the values don’t match your expectations.

One thing I did notice is that you are missing ref in your Entities.ForEach on your DynamicBuffer.

Thank you kindly for responding.

I had not noticed the lack of “ref” onUpdate within ER3D_TrafficSystem.cs, so thank you. Sadly, that changed nothing. I am still getting a debug of the entity having, and not having values in the Dynamic Buffer.

As for exactly what is going on, I hear, after 20+ years as a dev I have never found happiness in reading other people’s code.

Basically within CreateAutoEntity(), lines 54 to 59 points that the road’s segment are added to the Auto’s Entity. The Debug line there shows that there are values in entity buffer.

The line within the Entities.ForEach show that this entity and ONLY this entity is showing to have both values, and no values. The Entity debugger always shows values. I’m losing my f’in mind. So in any case, thank you again for the thought. If you have others, great! If not, have a great week

Sounds like execution order issues. Honestly, my suggestion would be to just add more logging and don’t look at the “collapsed” view in the console, but instead focus on the order things are happening and what values they are reporting.

It isn’t so much that the buffer has two values at once as much as it goes through a sequence of transformations in which after each transformation it has one of two values. That sequence is crucial, because then when it matches expectations, you can rule out code that may be the culprit.

So, what is that sequence? What is the order of execution between the two functions and the system? Don’t answer what you intended for it to be. Log it and find out what is really happening.

I hear you about the sequence, but it’s really very basic

  • ER3D_Traffic.cs - creates the road and auto entities
  • ER3D_TrafficSystem.cs - moves the auto

Within the System there is only the one line, and it is logging that the Dynamic Buffer has 0 and 160 (as shown in the OnUpdate). That is the ENTIRE code in the system. So nothing is affecting that buffer. (That’s why I mentioned that I am watching the values within the Entity Debugger (DOTS Hierarchy) and they always show a value of 160.

I’m hoping someone within Unity sees this, as it seems a deeper bug. (or maybe I’m the Deeper Bug)

Anyway, thanks again

There is only one debug line in that system, yet you are seeing multiple outputs. Therefore, you either have multiple entities or multiple updates. In the case of the latter, that means you have a sequence of results. And something between those updates is changing those results. What is the sequence? Is it 0, 0, 0, 161? Is it 161, 0, 0, 0? Or is it something weirder? Do either of the two functions run between the system updates where the value changes?

DOTS is not a quantum simulation. Everything happens sequentially. And some things happen more than once, occurring multiple times in the sequence. You need to start breaking this sequence apart, and identify which parts of the sequence do exactly what you expect. Eventually, you should be able to pin it down to an exact line of code. If that line of code happens to be a Unity function, then it might be a Unity bug. Most likely, it isn’t.

@DreamingImLatios

I have gotten closer… Thing is, I have a list of GameObjects that I create into a list of entities. Then when I add an Auto to a road, a create new instance of one of those entities. (Saves times to create one set of entities, instead of converting a GameObject for each new instance. It’s a LARGE time saving… I currently have 3 GameObjects… AND when I run the code I get 3x as many objects without a values in it’s Dynamic Buffer… you see where I am now, right? Basically my time savings idea screwed me. I need to insure that I delete those. (oops)

Thanks again.