Skip to content

Game.Objects.SubObjectSystem

Assembly:
Game (in-game systems assembly)

Namespace:
Game.Objects

Type:
class

Base:
GameSystemBase

Summary:
A large DOTS-based system responsible for creating, updating, duplicating and removing "sub-objects" (prefab children, decorations, effects, placeholder/spawnable objects, activity locations, nested sub-objects, etc.) for city elements such as buildings, roads (nodes/edges), areas and other owners. The system: - Collects owners whose sub-objects need updating (created/updated/deleted or special events). - Builds requirement and placeholder state (brands, renters, resources, themes). - Creates/duplicates sub-objects deterministically using seeded randoms and handles selection for spawnable groups. - Handles placement rules (edge/node/area/transform), elevation/ground adjustments, interpolation/attached/relative transforms and special placement flags (pillars, anchors, even spacing, course placement, etc.). - Supports editor mode behaviors, temporary objects (Temp), upgrades and deep nested sub-object creation (with a max depth safeguard). - Uses multiple Burst-compiled jobs (IJobChunk, IJob, IJobParallelForDefer) and EntityCommandBuffer.ParallelWriter for safe multithreaded DOTS updates. This system is central to prefab composition and procedural placement in Cities: Skylines 2 and is highly optimized for large-scale updates.


Fields

  • private TerrainSystem m_TerrainSystem
    Reference to the TerrainSystem used to sample heights/adjust positions for sub-objects and to register CPU height readers.

  • private WaterSystem m_WaterSystem
    Reference to the WaterSystem for water-surface queries used when adjusting elevations for objects on/near water.

  • private ToolSystem m_ToolSystem
    Reference to the ToolSystem (used to determine editor mode, editor state and action modes).

  • private CityConfigurationSystem m_CityConfigurationSystem
    Configuration holder used for defaults such as themes and city-wide settings required when evaluating placeholder requirements.

  • private ModificationBarrier2B m_ModificationBarrier
    A modification barrier (command buffer provider) used to queue structural ECS changes (create/remove/component changes) from jobs.

  • private EntityQuery m_UpdateQuery
    EntityQuery used to detect owners that need sub-object updates (those with SubObject buffers + Updated/Deleted or specific Event components).

  • private EntityQuery m_TempQuery
    EntityQuery used for temporary entities (Temp + Object + Owner + Updated, not Deleted) — used when filling ignore sets for duplication logic.

  • private EntityQuery m_ContainerQuery
    EntityQuery used to detect editor container prefabs (Editor container handling in editor mode).

  • private EntityQuery m_BuildingSettingsQuery
    Query to get BuildingConfigurationData (used for construction/collapsed object prefabs etc).

  • private EntityQuery m_HappinessParameterQuery
    Query to get CitizenHappinessParameterData used when deriving placeholder requirement flags from household consumption.

  • private ComponentTypeSet m_AppliedTypes
    A set of component types applied/removed to signal applied/created/updated states for objects; used when cleaning up old sub-objects.

  • private NativeQueue<Entity> m_LoopErrorPrefabs
    Persistent native queue used to accumulate prefabs that caused recursion / exceeded max sub-object depth; used later to log errors.

  • private TypeHandle __TypeHandle
    Internal container of EntityTypeHandle/ComponentTypeHandle/ComponentLookup/BufferLookup values used to assign and pass handles into jobs.

(There are many local/temporary collections and nested job types inside the system — the list above covers the important instance fields used across OnCreate/OnUpdate.)


Properties

  • (No public properties)
    This system does not expose public properties; functionality is internal to the ECS world and the system updates itself each frame via OnUpdate. Modders should interact with ECS components, entities and system-managed APIs rather than internal properties.

Constructors

  • public SubObjectSystem()
    Default constructor. The system relies on OnCreate to initialise system references and queries. The class is annotated with [Preserve] and OnCreate is overridden to set up dependencies and queries.

Methods

  • protected override void OnCreate()
    Initializes references to other systems (TerrainSystem, WaterSystem, ToolSystem, CityConfigurationSystem, and ModificationBarrier2B), prepares EntityQueries, initialises component-type sets used for applied changes, registers the system to require updates for owners with sub-objects, and allocates the persistent m_LoopErrorPrefabs queue.

  • protected override void OnDestroy()
    Disposes the persistent m_LoopErrorPrefabs queue and performs base cleanup.

  • protected override void OnUpdate()
    Core orchestration method. Steps performed each update:

  • Call ShowLoopErrors() to log deep nesting errors.
  • Prepare temporary native containers: owner queue/list, ignore set, owner map.
  • Schedule CheckSubObjectOwnersJob (IJobChunk) to collect candidate owners that need sub-object changes (based on Updated/Deleted/Tmp/SubObjectsUpdated/RentersUpdated).
  • Schedule CollectSubObjectOwnersJob (IJob) to aggregate owner data into a list and map.
  • Schedule FillIgnoreSetJob (IJobChunk) to fill a parallel ignore set (for duplicates) from temporary entities.
  • Schedule UpdateSubObjectsJob (IJobParallelForDefer) using the collected owner list — this job performs the heavy work: computing requirements, placeholders, duplicating or creating sub-objects, relocating area objects, clearing unused old sub-objects, recursive creation of nested sub-objects and writing command buffer operations.
  • Properly chain job dependencies, dispose temporary containers when safe, register terrain/water readers and the produced job handle with the modification barrier.

  • private void ShowLoopErrors()
    Logs errors for prefabs that caused sub-object nesting deeper than the allowed max (kMaxSubObjectDepth = 7). It drains m_LoopErrorPrefabs, deduplicates prefabs and logs a message indicating the prefab name that caused deep nesting.

  • private void __AssignQueries(ref SystemState state)
    Internal helper used by the generated code path/compiler; currently a no-op (creates and disposes an EntityQueryBuilder).

  • protected override void OnCreateForCompiler()
    Internal method used to call __AssignQueries and assign type handles for the job structs at compile-time.

  • Nested/Burst job structs (internal, important):

  • CheckSubObjectOwnersJob : IJobChunk
    Scans archetype chunks to enqueue owners needing sub-object updates; handles event-driven updates and special cases (deleted owners, temporary entities referencing originals).
  • CollectSubObjectOwnersJob : IJob
    Consumes the owner queue produced by CheckSubObjectOwnersJob and builds a deduplicated list and map of owners to process.
  • FillIgnoreSetJob : IJobChunk
    Builds a set of temporary entities that should be ignored when matching duplicates (to avoid referencing the temp itself).
  • UpdateSubObjectsJob : IJobParallelForDefer
    The main worker that, for each owner:

    • Determines owner type (transform/node/edge/area).
    • Prepares pseudo-random seeds (top-level and sub-random).
    • Gathers old sub-objects and original buffers and placeholder requirements.
    • Handles duplication vs creation logic.
    • Performs CreateSubObjects / DuplicateSubObjects / RelocateSubObjects depending on owner type and flags.
    • Manages placeholder/spawnable selection, requirement checks, and affiliate brand selection.
    • Calls CreateSubObject / CreateContainerObject which either update an existing (old) sub-object entity or instantiate a new entity using a prefab's ObjectData archetype and adds the appropriate components (Owner, PrefabRef, Transform, Temp, Elevation, Relative, InterpolatedTransform, UnderConstruction, Destroyed, Overridden, etc.).
    • Enqueues deeper owners for nested sub-object creation (up to a depth limit).
    • Uses command buffer to schedule component changes/creates from jobs safely.
  • Many private helper methods inside UpdateSubObjectsJob (summarised):

  • FillOldSubObjectsBuffer, FillOriginalSubObjectsBuffer, FillClearAreas
  • RemoveUnusedOldSubObjects
  • DuplicateSubObjects, RelocateSubObjects, CreateSubObjects
  • CreateSubObject (overloaded variants: handling placeholders vs direct creation)
  • CreateContainerObject (for editor container/editor-only prefabs)
  • EnsurePlaceholderRequirements, AddAffiliatedBrands (builds placeholder requirements map and flags)
  • FindOldSubObject, FindOriginalSubObject (matching logic between old/existing objects and new placements)
  • Utility checks for placement constraints: HasSubRequirements, CheckRequirements, IsDeadEnd, IsAbruptEnd, IsOrphan, IsWaterwayCrossing, IsContinuous, GetFixedRange, GetFixedRange etc.

Notes: - This class is highly complex — most of the logic happens inside UpdateSubObjectsJob and its helper methods (many placement rules, random selection strategies and editor-specific behavior). - The system relies on several Prefab and Object component data structures (ObjectData, ObjectGeometryData, PrefabRef, PrefabData, PlaceholderObjectData, etc.) to decide how to create and place sub-objects. - It uses deterministic Random seeds (RandomSeed.Next(), PseudoRandomSeed) so spawnables can be consistently reproduced.

Usage Example

A mod typically does not subclass SubObjectSystem. The following is the actual OnCreate implementation from the system — it shows how it obtains references to other systems and sets up queries (useful if you want to inspect or interact with these subsystems when writing mods):

[Preserve]
protected override void OnCreate()
{
    base.OnCreate();
    m_TerrainSystem = base.World.GetOrCreateSystemManaged<TerrainSystem>();
    m_WaterSystem = base.World.GetOrCreateSystemManaged<WaterSystem>();
    m_ToolSystem = base.World.GetOrCreateSystemManaged<ToolSystem>();
    m_CityConfigurationSystem = base.World.GetOrCreateSystemManaged<CityConfigurationSystem>();
    m_ModificationBarrier = base.World.GetOrCreateSystemManaged<ModificationBarrier2B>();
    m_UpdateQuery = GetEntityQuery(new EntityQueryDesc
    {
        All = new ComponentType[1] { ComponentType.ReadOnly<SubObject>() },
        Any = new ComponentType[2]
        {
            ComponentType.ReadOnly<Updated>(),
            ComponentType.ReadOnly<Deleted>()
        }
    }, new EntityQueryDesc
    {
        All = new ComponentType[1] { ComponentType.ReadOnly<Event>() },
        Any = new ComponentType[2]
        {
            ComponentType.ReadOnly<RentersUpdated>(),
            ComponentType.ReadOnly<SubObjectsUpdated>()
        }
    });
    m_TempQuery = GetEntityQuery(new EntityQueryDesc
    {
        All = new ComponentType[4]
        {
            ComponentType.ReadOnly<Temp>(),
            ComponentType.ReadOnly<Object>(),
            ComponentType.ReadOnly<Owner>(),
            ComponentType.ReadOnly<Updated>()
        },
        None = new ComponentType[1] { ComponentType.ReadOnly<Deleted>() }
    });
    m_ContainerQuery = GetEntityQuery(ComponentType.ReadOnly<EditorContainerData>(), ComponentType.ReadOnly<ObjectData>());
    m_BuildingSettingsQuery = GetEntityQuery(ComponentType.ReadOnly<BuildingConfigurationData>());
    m_HappinessParameterQuery = GetEntityQuery(ComponentType.ReadOnly<CitizenHappinessParameterData>());
    m_AppliedTypes = new ComponentTypeSet(ComponentType.ReadWrite<Applied>(), ComponentType.ReadWrite<Created>(), ComponentType.ReadWrite<Updated>());
    RequireForUpdate(m_UpdateQuery);
    m_LoopErrorPrefabs = new NativeQueue<Entity>(Allocator.Persistent);
}

If you need to interact with sub-object creation behavior from a mod: - Modify prefab component data (PlaceholderObjectData, SpawnableObjectData, ObjectRequirementElement buffers) to change how placeholders/spawnables are selected. - Add/modify components on owner entities (e.g., Temp, UnderConstruction, Overridden) to affect creation of nested sub-objects. - Access the SubObjectSystem (World.GetExistingSystemManaged()) if you need to inspect its state (read-only) — but avoid directly calling internal job methods; instead modify ECS data and let the system run its next OnUpdate.


If you want, I can generate a shorter quick-reference of the main job flows, list the most relevant component types the system reads/writes, or extract the placement flags/pillar types/constants used by this system for easier modding reference. Which would you prefer?