I have the following system that is working fine, up until the point at which it “runs out” of points to process. At that point the LaneEntity
field on the VehicleLaneComponent
has the value of Entity.Null
. According to the entity debugger, the AddNextLaneSystem
has never run (makes sense since the required component has not been set, as confirmed in the debugger by the absence of the required tag), and the code to add NeedsNextLaneTag
never executes due to an exception from the aforementioned LaneEntity
field. The obvious work-around is to add handling for that situation, but how did I get there in the first place? VehicleLaneComponent
is marked as ReadOnly
, so what is causing it to be updated? None of my other systems modify that data, other than the AddNextLaneSystem
, which should only process an entity after this system has marked the entity as needing to be processed by that system, which it hasn’t, and the debugger seems to confirm that.
(Yes, the AddNextLaneSystem
is marked to execute before this system, which is correct because if this entity has run out of lane points, I need that system to find and add a reference to the next lane during the next update in order for this system to be able to access the next point the entity should be moving to.)
Also, feel free to point out any other bone-headed decisions I’ve made in this code.
using GeoExplorer.RoadNetwork.DOTS.Components;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
namespace GeoExplorer.RoadNetwork.DOTS.Systems
{
[UpdateAfter(typeof(AddNextLaneSystem))]
public class ER3D_TrafficControlSystem : JobComponentSystem
{
BeginInitializationEntityCommandBufferSystem _entityCommandBufferSystem;
[RequireComponentTag(typeof(ERAutoTag))]
private struct AutoNavigationJob : IJobForEachWithEntity<
VehicleLaneComponent,
VehicleStatsComponent,
VehicleCurrentDataComponent,
Translation,
Rotation>
{
public EntityCommandBuffer.Concurrent CommandBuffer;
public float deltaTime;
// [NativeDisableParallelForRestriction]
[ReadOnly] public BufferFromEntity<LanePointsComponent> VehicleLaneComponentData;
[ReadOnly] public ComponentDataFromEntity<LaneComponent> LaneComponentData;
public void Execute(Entity entity,
int index,
[ReadOnly] ref VehicleLaneComponent vehicleLaneComponent,
[ReadOnly] ref VehicleStatsComponent vehicleStats,
ref VehicleCurrentDataComponent vehicleCurrentData,
ref Translation translation,
ref Rotation rotation)
{
var speed = vehicleCurrentData.Speed;
var laneSpeed = LaneComponentData[vehicleLaneComponent.LaneEntity].Speed;
if (math.abs(laneSpeed - speed) > 0.01)
{
// accelerate to target speed
var acceleration = speed <= math.min(laneSpeed, vehicleStats.MaxSpeed)
? vehicleStats.Acceleration
: -vehicleStats.Deceleration;
vehicleCurrentData.Speed += deltaTime * acceleration;
}
var distance = vehicleCurrentData.Speed * deltaTime;
var currentIndex = vehicleCurrentData.CurrentPositionIndex;
var lanePointsBuffer = VehicleLaneComponentData[vehicleLaneComponent.LaneEntity];
if ((currentIndex + 1) < lanePointsBuffer.Length)
{
var nextPoint = lanePointsBuffer[currentIndex + 1].value;
var distanceToNextPoint = math.distancesq(translation.Value, nextPoint);
float distanceSq = distance * distance;
if (distanceSq > distanceToNextPoint)
{
vehicleCurrentData.CurrentPositionIndex++;
nextPoint = lanePointsBuffer[currentIndex + 1].value;
}
var currentPoint = lanePointsBuffer[currentIndex].value;
var direction = math.normalizesafe(nextPoint - currentPoint);
translation.Value += direction * distance;
rotation.Value = quaternion.LookRotationSafe(direction, math.up());
}
else
{
// Let interested systems know we need the next lane
CommandBuffer.AddComponent(index, entity, typeof(NeedsNextLaneTag));
}
}
}
protected override void OnCreate()
{
// Cache in a field, so we don't have to create every frame
_entityCommandBufferSystem = World.GetOrCreateSystem<BeginInitializationEntityCommandBufferSystem>();
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var lanePointsComponent = GetBufferFromEntity<LanePointsComponent>(true);
var laneComponent = GetComponentDataFromEntity<LaneComponent>(true);
var autoNavigationJob = new AutoNavigationJob
{
deltaTime = Time.DeltaTime,
CommandBuffer = _entityCommandBufferSystem.CreateCommandBuffer().ToConcurrent(),
VehicleLaneComponentData = lanePointsComponent,
LaneComponentData = laneComponent,
}.Schedule(this, inputDeps);
_entityCommandBufferSystem.AddJobHandleForProducer(autoNavigationJob);
return autoNavigationJob;
}
}
public struct VehicleLaneComponent : IComponentData
{
public Entity LaneEntity;
}
}