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 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?