Old entities teleport back when instantiating

I’m trying to learn unity DOTS and every tutorial showed me a particular workflow when it comes to spawning entities. Yet, when I try to replicate it, my the previous spawned entities teleport back when instantiating new ones. Can someone please tell me why it happens? This is the code:

public void OnUpdate(ref SystemState state)
{
    if (!SystemAPI.TryGetSingletonEntity<BoidSpawnerComponent>(out Entity spawnerEntity))
        return;
    RefRW<BoidSpawnerComponent> spawner = SystemAPI.GetComponentRW<BoidSpawnerComponent>(spawnerEntity);

    spawner.ValueRW.ElapsedTime += Time.deltaTime;

    if (spawner.ValueRO.ElapsedTime < spawner.ValueRO.Interval) return;
    
    EntityCommandBuffer ecb = new(Allocator.Temp);
    BoidSpawnerComponent spawnerValuesRO = spawner.ValueRO;
    spawner.ValueRW.ElapsedTime = 0;
    
    for (int i = 0; i <= spawnerValuesRO.BoidsPerInterval; i++)
    {
        if (spawnerValuesRO.TotalSpawnedBoids >= spawnerValuesRO.BoidsToSpawn)
            break;
        Debug.Log("Spawning entity");
        Entity e = ecb.Instantiate(spawner.ValueRO.BoidPrefab);

        ecb.AddComponent(e, new BoidComponent
        {
            velocity = math.normalize(UnityEngine.Random.insideUnitSphere) * spawnerValuesRO.MaxSpeed,
            PersceptionDistance = spawnerValuesRO.PersceptionDistance,
            Speed = spawnerValuesRO.MaxSpeed,
            cellSize = spawnerValuesRO.CellSize,
            alignmentBias = spawnerValuesRO.AlignmentBias,
            separationBias = spawnerValuesRO.SeparationBias,
            cohesionBias = spawnerValuesRO.CohesionBias,
            Step = spawnerValuesRO.Step,

        });

        spawner.ValueRW.TotalSpawnedBoids++;
    }
    ecb.Playback(state.EntityManager);

}

Not enough info, nothing appears to set position data here. Show how you’re initially positioning and moving these things.

Well that’s the thing, I’m not setting any position but the teleportation happens. But, just in case, here’s the code for my boid movement system:

public partial struct BoidSystem : ISystem
{
    private NativeParallelMultiHashMap<int, BoidComponent> cellVsEntityPositions;
    private EntityQuery boidQuery;

    public static int GetUniqueKeyForPosition(float3 position, int cellSize)
    {
        return (int)( (15 * math.floor(position.x / cellSize)) + (17 * math.floor(position.y / cellSize)) );
    }

    public void OnCreate(ref SystemState state)
    {
        cellVsEntityPositions = new NativeParallelMultiHashMap<int, BoidComponent>(0, Allocator.Persistent);
        boidQuery = SystemAPI.QueryBuilder()
                    .WithAll<BoidComponent>()
                    .WithAll<LocalToWorld>()
                    .Build();
        state.RequireForUpdate<BoidComponent>();
    }

    public void OnDestroy(ref SystemState state)
    {
        cellVsEntityPositions.Dispose();
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        cellVsEntityPositions.Clear();
        int entityCount = boidQuery.CalculateEntityCount();
        if (entityCount > cellVsEntityPositions.Capacity)
        {
            cellVsEntityPositions.Capacity = entityCount;
        }

        var cellVsEntityPositionsParallel = cellVsEntityPositions.AsParallelWriter();

        state.Dependency = new PopulateCellJob
        {
            cellVsEntityPositionsParallel = cellVsEntityPositionsParallel
        }.ScheduleParallel(state.Dependency);

        state.Dependency = new BoidMovementJob
        {
            cellVsEntityPositions = cellVsEntityPositions,
            deltaTime = SystemAPI.Time.DeltaTime
        }.ScheduleParallel(state.Dependency);
        
    }

    [BurstCompile]
    public partial struct PopulateCellJob : IJobEntity
    {
        public NativeParallelMultiHashMap<int, BoidComponent>.ParallelWriter cellVsEntityPositionsParallel;

        public void Execute(ref BoidComponent bc, in LocalToWorld trans)
        {
            bc.currentPosition = trans.Position;
            cellVsEntityPositionsParallel.Add(GetUniqueKeyForPosition(trans.Position, bc.cellSize), bc);
        }
    }

    [BurstCompile]
    public partial struct BoidMovementJob : IJobEntity
    {
        [ReadOnly] public NativeParallelMultiHashMap<int, BoidComponent> cellVsEntityPositions;
        public float deltaTime;

        public void Execute(ref BoidComponent boid, ref LocalToWorld trans)
        {
            int key = GetUniqueKeyForPosition(trans.Position, boid.cellSize);
            int total = 0;
            float3 separation = float3.zero;
            float3 alignment = float3.zero;
            float3 cohesion = float3.zero;
            float3 wallRepelant = float3.zero;
            if (!cellVsEntityPositions.TryGetFirstValue(key, out BoidComponent neighbour, out NativeParallelMultiHashMapIterator<int> nmhKeyIterator)) return;

            do
            {
                if (trans.Position.Equals(neighbour.currentPosition) || //neighbour is this boid
                    math.distance(trans.Position, neighbour.currentPosition) > boid.PersceptionDistance) continue;
                
                float3 distanceFromTo = trans.Position - neighbour.currentPosition;
                separation += (distanceFromTo / math.distance(trans.Position, neighbour.currentPosition));
                cohesion += neighbour.currentPosition;
                alignment += neighbour.velocity;
                total++;
                
            }
            while (cellVsEntityPositions.TryGetNextValue(out neighbour, ref nmhKeyIterator));

            if (total > 0)
            {
                cohesion /= total;
                cohesion -= (trans.Position + boid.velocity);
                cohesion = math.normalize(new float3(cohesion.x, cohesion.y,0)) * boid.cohesionBias;

                separation /= total;
                separation -= boid.velocity;
                separation = math.normalize(new float3(separation.x, separation.y, 0)) * boid.separationBias;

                alignment /= total;
                alignment -= boid.velocity;
                alignment = math.normalize(new float3(alignment.x, alignment.y, 0)) * boid.alignmentBias;

            }
            float wall = 10;//hardcoded for now
            
            if (trans.Position.x < -wall)
                wallRepelant += new float3(5, 0, 0);
            else if (trans.Position.x > wall)
                wallRepelant += new float3(-5, 0, 0);
            if (trans.Position.y < -wall)
                wallRepelant += new float3(0, 5, 0);
            else if (trans.Position.y > wall)
                wallRepelant += new float3(0, -5, 0);

            boid.acceleration += (cohesion + alignment + separation + wallRepelant);
            boid.velocity += boid.acceleration;
            boid.velocity = math.normalize(boid.velocity) * boid.Speed;
            boid.acceleration = float3.zero;

            trans.Value = float4x4.TRS(
                math.lerp(trans.Position, (trans.Position + boid.velocity), deltaTime * boid.Step),
                quaternion.identity,
                new float3(1f)
            );
        }
    }
}

Considering that you move things with LocalToWorld and you see issues when new objects are added, I suspect that you also have LocalTransform on the prefab (as is expected), and your instantiations add entities to existing chunks, causing change filters to trigger and have jobs do things like update the entire chunk’s LocalToWorlds based on the LocalTransforms, because change filters only work on a chunk level granularity. Since you’d not modified LocalTransform, entities in the chunk end up snapping back to their starting point.

LocalToWorld on its own does represent an object’s transform, but if LocalTransform is also present (and / or PostTransformMatrix), LocalToWorld is generated based on that.

You’re more or less supposed to use LocalTransform to perform main transform modifications if it’s present, and optionally apply non-uniform scale etc. with PostTransformMatrix. Try changing your code to only modify LocalTransform.

I don’t really understand what you’re asking cuz I think I already am only modifying LocalTransform. This is the only spot where I am doing that:

// in BoidMovementJob of BoidSystem line 124
trans.Value = float4x4.TRS(
                math.lerp(trans.Position, (trans.Position + boid.velocity), deltaTime * boid.Step),
                quaternion.identity,
                new float3(1f)
            );

Additionally, I tried setting an initial value when Instantiating, but it doesn’t solve the teleportation problem:

//I added this in the BoidSpawnSystem right after adding the BoidComponent (line 34)
ecb.SetComponent(e, new LocalTransform
{
    Position = new float3(randomX, randomY, 0),
    Rotation = quaternion.identity,
    Scale = 1f
});

You’re writing to LocalToWorld there. Not LocalTransform. Your execute parameter was a LocalToWorld. float4x4.TRS returns a float4x4.

Given that you were writing to LocalToWorld, my previous post applies. Whatever value LocalTransform has, whether it be from the prefab’s stored value or from a SetComponent when it’s created, will be applied to LocalToWorld again when a new entity is added to the same chunk because a change filter (used for performance reasons, to skip updating chunks where components haven’t had write access to them or adds/removes) is triggered via component changes in a chunk. The behaviour is completely expected because LocalToWorld is derived from LocalTransform (and PostTransformMatrix) when present, and thus should not be modified directly on its own. The other components should be written to, and you can optionally compute the LocalToWorld yourself if you need an up-to-date value immediately.

Ok, I did it and it works, they don’t teleport anymore. However, the scene lags a bit everytime I instatiate a new entity

//in BoidSpawnSystem
ecb.SetComponent(e, new LocalTransform
{
    Position = new float3(randomX, randomY, 0),
    Rotation = quaternion.identity,
    Scale = 1f
});

//in boidSystem
 public void Execute(ref BoidComponent boid, ref LocalTransform trans)
 {
      // unchanged code...

      // now I'm changing the localTransform component
     trans.Position = math.lerp(trans.Position, (trans.Position + boid.velocity), deltaTime * boid.Step);
 }

Take a look at the profiler to see what’s going on.

Sorry for the late reply. It seems that most of it is from the editor loop for some reason: