Game.AttachPositionSystem
Assembly: Assembly-CSharp
Namespace: Game.Objects
Type: class
Base: GameSystemBase
Summary:
AttachPositionSystem is an ECS system that updates world transforms for entities with an Attached component. It schedules a Burst-compiled IJobChunk (AttachPositionJob) over entities that are both Updated and Attached (excluding Deleted) and adjusts their Transform based on their parent (Node or Composition/edge). The job snaps objects to route lanes or composition areas as appropriate, respects prefab placement flags (e.g., CanOverlap, pillar types, standing geometry), and propagates transform changes to subobjects that are owned by the parent entity. This system is intended for positioning prop/asset instances attached to net elements (nodes/segments) in Cities: Skylines 2.
Fields
-
private Unity.Entities.EntityQuery m_UpdateQuery
{{ The query used to find entities that need processing. It requires components Updated and Attached and excludes Deleted. The system calls RequireForUpdate(m_UpdateQuery) in OnCreate so the system only runs when matching entities exist. }} -
private TypeHandle __TypeHandle
{{ Internal struct instance used to cache Entity/ComponentType and ComponentLookup/BufferLookup handles. __TypeHandle.__AssignHandles(ref state) is called (via OnCreateForCompiler) to initialize these handles before scheduling the job; OnUpdate uses InternalCompilerInterface helpers to obtain runtime handles from the cached ones. }}
Properties
- This class exposes no public properties.
{{ All data access is performed inside the scheduled AttachPositionJob via ComponentTypeHandle/ComponentLookup/BufferLookup obtained from __TypeHandle. }}
Constructors
public AttachPositionSystem()
{{ Default parameterless constructor. The class is marked with [Preserve] on lifecycle methods to avoid stripping; construction itself doesn't set up queries—OnCreate does that work. }}
Methods
-
protected override void OnCreate()
{{ Initializes m_UpdateQuery = GetEntityQuery(ComponentType.ReadOnly(), ComponentType.ReadOnly (), ComponentType.Exclude ()) and calls RequireForUpdate(m_UpdateQuery) so the system only updates when matching entities exist. This prepares the system to schedule the AttachPositionJob in OnUpdate. }} -
protected override void OnUpdate()
{{ Builds an AttachPositionJob with component handles and lookups populated through InternalCompilerInterface.GetComponentTypeHandle / GetComponentLookup / GetBufferLookup and schedules it as a parallel JobChunk via JobChunkExtensions.ScheduleParallel(jobData, m_UpdateQuery, base.Dependency). The job writes to Transform via a ComponentLookup (non-readonly). }} -
protected override void OnCreateForCompiler()
{{ Called by generated code paths to assign cached handles: it calls __AssignQueries and __TypeHandle.__AssignHandles(ref state). This method ensures code-gen/IL2CPP/Burst compile paths have the necessary cached handles set up. }} -
private void __AssignQueries(ref SystemState state)
{{ Internal helper used by OnCreateForCompiler; in this class it currently creates and disposes an EntityQueryBuilder and serves as a placeholder for compiler-driven query assignment. }} -
AttachPositionJob (private nested BurstCompile struct)
{{ The core worker: it implements IJobChunk and iterates over chunks containing Attached + PrefabRef. Key responsibilities: - Execute: iterates entities in the chunk, reads Attached and PrefabRef and current Transform, calls FixAttachedPosition to compute a potentially new Transform, and if changed calls MoveObject to apply it.
- MoveObject(Entity, oldTransform, newTransform): writes the new Transform to the target entity and propagates transform updates to subobjects owned by that entity (if the subobject has Updated and is not itself Attached). Propagation uses ObjectUtils.InverseTransform / LocalToWorld math to preserve local offsets.
- FixAttachedPosition(Attached, PrefabRef, ref Transform): determines the correct world transform based on the Attached.m_Parent. If parent is a Node, it snaps to node position/rotation. If parent is a Composition (edge), it evaluates edge geometry, curve, prefab composition data, object geometry, route connections and composition areas, and computes a best transform using SnapRouteLane / SnapSegmentAreas. It respects PlaceableObjectData flags (CanOverlap) and PillarData types to decide whether to alter placement.
- SnapRouteLane(...): when the prefab has a RouteConnectionData, it searches NetCompositionLane buffers to find the nearest lane matching the requested RouteConnectionType and validates lane-specific prefab data (e.g., TrackLaneData / CarLaneData). It calculates a lane-aligned transform and sets snapEdge = true when snapping to a lane.
- SnapSegmentAreas(...): iterates NetCompositionArea buffer entries for the composition/edge and finds the best area to snap into (buildable areas), clamps along the left/right curves according to area width and snap parameters, and sets position + rotation accordingly. }}
Notes on concurrency and safety: the job uses [NativeDisableParallelForRestriction] on the Transform ComponentLookup to allow concurrent writes to different entities' transforms. Subobject propagation uses ComponentLookup and BufferLookup query methods (TryGetBuffer / TryGetComponent) that are safe inside the job as configured.
Usage Example
[Preserve]
protected override void OnCreate()
{
base.OnCreate();
m_UpdateQuery = GetEntityQuery(
ComponentType.ReadOnly<Updated>(),
ComponentType.ReadOnly<Attached>(),
ComponentType.Exclude<Deleted>());
RequireForUpdate(m_UpdateQuery);
}
[Preserve]
protected override void OnUpdate()
{
var jobData = new AttachPositionJob {
m_EntityType = InternalCompilerInterface.GetEntityTypeHandle(ref __TypeHandle.__Unity_Entities_Entity_TypeHandle, ref base.CheckedStateRef),
m_AttachedType = InternalCompilerInterface.GetComponentTypeHandle(ref __TypeHandle.__Game_Objects_Attached_RO_ComponentTypeHandle, ref base.CheckedStateRef),
m_PrefabRefType = InternalCompilerInterface.GetComponentTypeHandle(ref __TypeHandle.__Game_Prefabs_PrefabRef_RO_ComponentTypeHandle, ref base.CheckedStateRef),
// ... other lookups and buffer lookups initialized similarly ...
m_TransformData = InternalCompilerInterface.GetComponentLookup(ref __TypeHandle.__Game_Objects_Transform_RW_ComponentLookup, ref base.CheckedStateRef)
};
base.Dependency = JobChunkExtensions.ScheduleParallel(jobData, m_UpdateQuery, base.Dependency);
}
{{ YOUR_INFO }} - When modding: if you add or change Attached, PrefabRef, or related prefab data components, ensure the system's query and job lookups reflect those changes; mismatches can lead to missing updates or runtime errors. - To add custom snapping behavior, extend FixAttachedPosition (or add an extra job stage) and ensure access to the necessary ComponentLookup/BufferLookup handles; keep Burst-safety and thread-safety in mind (prefer readonly lookups where possible). - If you need to force a transform update from managed code, set Updated on the entity and update its Attached/PrefabRef fields as needed so AttachPositionSystem will process it on the next tick.