Skip to content

Game.Net.AggregateSystem

Assembly: Game
Namespace: Game.Net

Type: class

Base: GameSystemBase

Summary:
AggregateSystem is a Unity ECS system used by Cities: Skylines 2 to manage aggregation of net edges (roads/rails/etc.) into "aggregate" prefabs for batching and processing. The system runs a Burst-compiled IJob (UpdateAgregatesJob) that:

  • Scans entities that have the Aggregated component and were Created/Updated/Deleted.
  • Builds and validates aggregate entities (collections of connected edges of the same prefab aggregate type).
  • Creates new aggregates, combines aggregates, or marks aggregates Deleted/Updated as needed.
  • Uses a ModificationBarrier2B to record entity command buffer operations produced by the job.
  • Uses various component lookups and buffer lookups (Edge, Curve, PrefabRef, NetGeometryData, AggregateNetData, AggregateElement buffer, ConnectedEdge buffer) to decide how to group edges and when to merge or split aggregates.

The system is highly optimized for multithreaded execution (Burst, Native collections) and contains a nested UpdateAgregatesJob with many helper methods for the aggregation logic: CreateAggregate, TryCombine, CombineAggregate, ValidateAggregate, GetBestConnectionEdge, AddElements, GetStart/GetEnd, GetAggregateType, and ShouldCombine.


Fields

  • private EntityQuery m_ModifiedQuery
    Documentation: Query used to find entities with the Aggregated component that also have Created, Updated or Deleted — these are the items the system processes.

  • private ModificationBarrier2B m_ModificationBarrier
    Documentation: System-provided barrier used to create an EntityCommandBuffer for safe structural changes from the job and to register the producer JobHandle.

  • private TypeHandle __TypeHandle
    Documentation: Internal struct instance that caches EntityTypeHandle, ComponentTypeHandle and ComponentLookup/BufferLookup handles used by the job. The handles are assigned in OnCreateForCompiler via __AssignHandles to be used in the scheduled job.

  • private struct UpdateAgregatesJob (nested)
    Documentation: Burst-compiled IJob that performs the bulk of the work. It holds read-only and read-write component lookups/handles and native collections passed from the main system. Key responsibilities:

  • Iterate chunks of "modified" aggregated entities and collect edges to validate or create aggregates.
  • Use NativeParallelHashSet/NativeParallelHashMap to manage processing sets (edgeSet, emptySet, updateMap).
  • Create or combine aggregate entities using the command buffer (m_CommandBuffer).
  • Methods include Execute(), CreateAggregate(), TryCombine(), CombineAggregate(), ValidateAggregate(), GetBestConnectionEdge(), AddElements(), GetStart(), GetEnd(), ShouldCombine(), GetAggregateType(), etc.

  • private struct TypeHandle (nested)
    Documentation: Struct that holds all the ECS type handles used by the job. It exposes __AssignHandles to initialize them from a SystemState.

Properties

  • None (this system exposes no public properties).

Constructors

  • public AggregateSystem()
    Documentation: Default constructor. The system is preserved and used by the world; initialization logic is done during OnCreate.

Methods

  • protected override void OnCreate()
    Documentation: Called when the system is created. This method:
  • Calls base.OnCreate().
  • Retrieves/creates the ModificationBarrier2B system used for command buffer creation.
  • Builds m_ModifiedQuery: All = Aggregated, Any = Created/Updated/Deleted.
  • Calls RequireForUpdate(m_ModifiedQuery) to make the system run only when relevant entities exist.

  • protected override void OnUpdate()
    Documentation: Main update that schedules the UpdateAgregatesJob:

  • Gathers archetype chunks matching m_ModifiedQuery asynchronously.
  • Constructs the UpdateAgregatesJob, assigning all required type handles/lookups/buffers and the chunks list plus a command buffer from the modification barrier.
  • Schedules the job (Burst IJob) and chains dependencies (combines base.Dependency with chunk gather handle).
  • Disposes the chunk list when the job completes and registers the job handle with the modification barrier (AddJobHandleForProducer).
  • Stores the job handle into base.Dependency.

  • protected override void OnCreateForCompiler()
    Documentation: Internal initialization used by compiler-generated code / modding framework. It assigns query-related data and calls __AssignHandles on __TypeHandle.

  • private void __AssignQueries(ref SystemState state)
    Documentation: Internal helper used by compiler-generated code. In the decompiled source this creates an EntityQueryBuilder and disposes it (placeholder for query assignment in compile-time pipeline).

  • private struct TypeHandle.__AssignHandles(ref SystemState state)
    Documentation: Initializes all cached type handles (EntityTypeHandle, ComponentTypeHandle, ComponentLookup<...>, BufferLookup<...>, etc.) from the provided SystemState. Called from OnCreateForCompiler.

  • UpdateAgregatesJob.Execute() (in nested struct)
    Documentation: Core job entry point. Describes how it:

  • Builds sets/maps and iterates chunks to populate them.
  • Calls ValidateAggregate for aggregates in edgeSet.
  • Calls CombineAggregate to merge aggregates where appropriate.
  • Iterates emptySet to create new aggregates via CreateAggregate.
  • Applies updates found in updateMap by adding Updated/BatchesUpdated/Deleted/Updated flags to aggregates via the command buffer.

Nested job helper methods (summaries): - CreateAggregate(Entity startEdge, NativeParallelHashSet emptySet, NativeList edgeList, NativeParallelHashMap updateMap)
Builds a new aggregate around a start edge by walking connected edges that match the same aggregate type, then creates an aggregate entity and sets Aggregated on member edges, adds Temp component if needed, or tries to combine with an existing aggregate.

  • TryCombine(Entity prefab, NativeList edgeList, bool isTemp, NativeParallelHashMap updateMap)
    Attempts to attach an edgeList to an existing aggregate (combining at start/end). Handles two-sided combination, updates Aggregated values, resizes aggregate buffers, and marks other aggregates for deletion if merged.

  • CombineAggregate(Entity aggregate, NativeParallelHashMap updateMap)
    Attempts to combine the provided aggregate with neighboring aggregates (both start and end sides), moving elements and marking other aggregates Deleted.

  • ValidateAggregate(Entity aggregate, NativeParallelHashSet edgeSet, NativeParallelHashSet emptySet, NativeParallelHashMap updateMap)
    Ensures an existing aggregate still contains valid edges (same prefab and not deleted). If not, clears or rebuilds the aggregate content and marks for Deleted/Updated appropriately.

  • GetBestConnectionEdge and AddElements
    Geometry-based heuristics to select the best connected edge when walking nodes (they use Curve tangents, directions and an ad-hoc scoring function to prefer visually/ geometrically appropriate continuations).

  • GetStart / GetEnd
    Helpers to determine the start/end edge/node/isStart flag for an array of AggregateElement entries.

  • ShouldCombine / GetAggregateType
    Helpers to determine whether an edge should be combined with another aggregate and to map an edge to the aggregate prefab type via PrefabRef -> NetGeometryData.m_AggregateType.

Notes about memory and threading: - Uses NativeParallelHashSet, NativeParallelHashMap and NativeList; allocate with Allocator.Temp inside job and dispose at the end. - Uses ComponentLookup and BufferLookup for thread-safe read/write operations inside the job when allowed. - Structural changes are done via EntityCommandBuffer obtained from m_ModificationBarrier inside the main system and passed into the job; m_ModificationBarrier.AddJobHandleForProducer is used to synchronize.

Usage Example

[Preserve]
protected override void OnCreate()
{
    base.OnCreate();
    // Typical setup performed by the system itself:
    m_ModificationBarrier = base.World.GetOrCreateSystemManaged<ModificationBarrier2B>();
    m_ModifiedQuery = GetEntityQuery(new EntityQueryDesc
    {
        All = new ComponentType[1] { ComponentType.ReadOnly<Aggregated>() },
        Any = new ComponentType[3]
        {
            ComponentType.ReadOnly<Created>(),
            ComponentType.ReadOnly<Updated>(),
            ComponentType.ReadOnly<Deleted>()
        },
        None = new ComponentType[0]
    });
    RequireForUpdate(m_ModifiedQuery);
}

Additional notes for modders: - You normally do not create or call this system directly; it runs as part of the game's ECS world. If you add or modify net edges or Aggregated components, this system is responsible for maintaining aggregate entities and must be allowed to run to keep batching consistent. - When creating custom net prefabs that use aggregation, ensure NetGeometryData.m_AggregateType is configured appropriately so GetAggregateType finds the intended aggregate prefab. - Because this system uses low-level scheduling, modification barrier and burst job, avoid making structural changes to Aggregated/AggregateElement buffers from outside threads without coordinating via the same barrier or appropriate command buffers to prevent race conditions.