Use Tag components to publish changes to the Bridge layer (Push Data to Managed Layer)¶
What¶
There are some instances of our non-refactored Systems mixing the manipulation of managed and unmanaged code.
public sealed partial class ArmyPathRendererSystem : SystemBase {
protected override void OnUpdate() {
using NativeList<ArmyPathToUpdateContainer> armiesToUpdateLine = new(Allocator.Temp);
using NativeArray<Entity> armyEntities = _query.ToEntityArray(Allocator.Temp);
// Unmanaged part
for (int entityIndex = 0; entityIndex < armyEntities.Length; ++entityIndex) {
// complex unmanaged code filtering entities
armiesToUpdateLine.Add(armyEntities[entityIndex]);
}
// Managed part
foreach (ArmyPathToUpdateContainer armyPathContainer in armiesToUpdateLine) {
GameObject gameObject;
if (EntityManager.HasComponent<ArmyPathRendererComponent>(armyPathContainer.ArmyEntity)) {
gameObject = EntityManager.GetComponentData<ArmyPathRendererComponent>(armyPathContainer.ArmyEntity).Value;
} else {
gameObject = Object.Instantiate(armyPathRendererConfig.LineRendererPrefab);
EntityManager.AddComponent<ArmyPathRendererComponent>(armyPathContainer.ArmyEntity);
EntityManager.SetComponentData(armyPathContainer.ArmyEntity, new ArmyPathRendererComponent { Value = gameObject });
}
ArmyPathLineRenderer armyPathLineRenderer = gameObject.GetComponent<ArmyPathLineRenderer>();
armyPathLineRenderer.UpdateLine(armyPathContainer.MoveCommandPositions, zoomLevel.Value);
}
}
}
Why¶
Besides going against the dependency flow of the architecture, it also prevents the System from being Burst-compatible as it references managed code.
How¶
Instead of updating the managed part directly in the DOTS Layer, we should mark those entities for update somehow. Tag components are an easy way of doing that:
public struct ArmyPathLineRendererUpdateTag : IComponentData { }
public partial struct ArmyPathRendererSystem : ISystem {
[BurstCompile]
public void OnUpdate(ref SystemState state) {
using NativeList<ArmyPathToUpdateContainer> armiesToUpdateLine = new(Allocator.Temp);
// ...
foreach (ArmyPathToUpdateContainer armyPathContainer in armiesToUpdateLine) {
EntityManager.AddComponent<ArmyPathLineRendererUpdateTag>(armyPathContainer.ArmyEntity);
}
}
}
Then we can move the managed part to our Bridge Layer:
public partial class ArmyPathRendererBridgeSystem : SystemBase, IArmyPathRendererUpdateNotifier {
public event IArmyPathRendererUpdateNotifier.PathUpdatedHandler? OnPathUpdated;
protected override void OnCreate() {
RequireForUpdate(
new EntityQueryBuilder(Allocator.Temp)
.WithAny<ArmyPathLineRendererUpdateTag>()
.Build(this)
);
}
protected override void OnUpdate() {
if (OnPathUpdated == null) {
return;
}
foreach ((ArmyPathRendererComponent armyPathRenderer, Entity entity) in SystemAPI
.Query<ArmyPathRendererComponent>()
.WithAny<ArmyPathLineRendererUpdateTag, ArmyPathLineRendererRemoveTag>()
.WithEntityAccess()) {
// ...
// Raise the event to be handled in the Managed Layer
OnPathUpdated.Invoke();
}
// Remove the ArmyPathLineRendererUpdateTag since we've already reacted to the change
EntityQuery pathRendererUpdateTagRemoveQuery =
SystemAPI.QueryBuilder().WithAll<ArmyPathLineRendererUpdateTag>().Build();
entityManager.RemoveComponent<ArmyPathLineRendererUpdateTag>(pathRendererUpdateTagRemoveQuery);
}
}