Skip to content

Game.CityBoundaryMeshSystem

Assembly:
Namespace: Game.Rendering

Type: class

Base: GameSystemBase, IPreDeserialize

Summary:
CityBoundaryMeshSystem is an ECS system responsible for generating and providing the renderable mesh and material for city/map boundary lines. It scans area/map-tile data using a quad-tree search, collects boundary segments (city vs map border), tessellates those segments into a mesh (vertices, UVs, indices, colors) using multithreaded Jobs and Burst where applicable, and exposes the resulting Mesh and Material via GetBoundaryMesh. The system coordinates with PrefabSystem (for settings/material), ToolSystem (editor mode), MapTileSystem, TerrainSystem (height scaling), and the area SearchSystem. It manages job dependencies and temporary native containers to avoid main-thread stalls and disposes resources on unload.


Fields

  • private PrefabSystem m_PrefabSystem
    Holds a reference to the game's PrefabSystem used to obtain CityBoundaryPrefab settings (material, colors, width, tiling).

  • private ToolSystem m_ToolSystem
    Reference to the ToolSystem to determine editor vs gameplay mode (affects which tiles are treated as native vs city).

  • private MapTileSystem m_MapTileSystem
    Used to query start tiles and map-tile related data.

  • private TerrainSystem m_TerrainSystem
    Used to obtain height scale/offset to set mesh bounds in world space.

  • private SearchSystem m_AreaSearchSystem
    Provides a read-only quad-tree search tree for finding overlapping areas/tiles when deciding which edges are exposed boundaries.

  • private EntityQuery m_UpdatedQuery
    Query that matches MapTile + Area entities with Updated or Deleted tags (ignores Temp). Used to decide when to rebuild boundary data.

  • private EntityQuery m_MapTileQuery
    Query that selects MapTile + Area + Node buffers for iterating all relevant tiles/areas to build boundaries.

  • private EntityQuery m_SettingsQuery
    Query that ensures a CityBoundaryData singleton exists — used for requiring the system to run and to fetch prefab settings.

  • private Mesh m_BoundaryMesh
    Unity Mesh instance used to store the generated boundary geometry. Created on demand and destroyed when empty or on clear.

  • private Material m_BoundaryMaterial
    Material reference obtained from the CityBoundaryPrefab; returned alongside the mesh.

  • private JobHandle m_MeshDependencies
    JobHandle tracking jobs that produce the mesh data (FillBoundaryMeshDataJob). Completed before the mesh is converted to a Unity Mesh on the main thread.

  • private NativeList<float3> m_Vertices
    Native container (job-allocated) holding vertex positions produced by the job pipeline.

  • private NativeList<float2> m_UVs
    Native container holding UVs for the mesh.

  • private NativeList<Color32> m_Colors
    Native container holding vertex colors for the mesh (boundary color per-vertex).

  • private NativeList<int> m_Indices
    Native container holding triangle indices.

  • private NativeValue<Bounds3> m_Bounds
    NativeValue used to accumulate bounds for the generated geometry (converted to Unity Bounds later).

  • private bool m_Loaded
    Flag set during deserialization (PreDeserialize) to force an initial rebuild on the next update.

  • private TypeHandle __TypeHandle
    Internal struct caching ECS TypeHandles/Lookups used by the jobs (EntityTypeHandle, ComponentTypeHandle, BufferTypeHandle, and various ComponentLookup/BufferLookup entries).

  • private struct Boundary (nested)
    Small POD representing a boundary segment (Line3.Segment m_Line) and its Color32 m_Color. Used to enqueue segments discovered by FillBoundaryQueueJob to be consumed by FillBoundaryMeshDataJob.

  • private struct TypeHandle (nested)
    Contains readonly ECS handle fields and an __AssignHandles method that captures the required type handles from a SystemState.

  • private struct FillBoundaryQueueJob (nested)
    IJobChunk that iterates the map-tile/area archetype chunks and determines which edges are exposed (i.e., boundary edges) by querying the area search tree. It enqueues Boundary entries into a NativeQueue (parallel writer). Burst-compiled.

  • The job contains a nested Iterator implementing INativeQuadTreeIterator/ IUnsafeQuadTreeIterator used to test whether a candidate line segment lies adjacent to another tile/area; if no adjacent tile contains the same line, the edge is treated as a boundary and enqueued.

  • private struct FillBoundaryMeshDataJob (nested)
    IJob (Burst-compiled) that reads the NativeQueue, tessellates segments into quads (triangles), computes UVs and vertex colors, populates m_Vertices, m_UVs, m_Colors, m_Indices and accumulates bounds in m_Bounds. Tiling is controlled by prefab settings (m_TilingLength, m_Width). Runs after FillBoundaryQueueJob.


Properties

  • (No public properties are exposed by this system)

Constructors

  • public CityBoundaryMeshSystem()
    Default constructor. Marked with [Preserve] to ensure construction across managed/native boundaries and when asm stripping occurs. The system relies on OnCreate to initialize references and queries.

Methods

  • protected override void OnCreate()
    Initializes system references and entity queries. Grabs or creates required managed system references:
  • m_PrefabSystem, m_ToolSystem, m_MapTileSystem, m_TerrainSystem, m_AreaSearchSystem.
  • Sets up EntityQuery instances:

    • m_UpdatedQuery: MapTile + Area with Updated/Deleted (ignores Temp) — used as change trigger.
    • m_MapTileQuery: MapTile + Area + Node, excludes Deleted/Temp — used to schedule the chunk job.
    • m_SettingsQuery: CityBoundaryData singleton; RequireForUpdate is called so system only runs when settings exist. Note: Marked [Preserve].
  • protected override void OnDestroy()
    Calls Clear() to dispose native resources and destroy the mesh, then calls base.OnDestroy(). Marked [Preserve].

  • public void PreDeserialize(Context context)
    IPreDeserialize implementation. Called during deserialization/load to clear current mesh data and set m_Loaded = true, which forces a rebuild on next update.

  • private void Clear()
    High-level resource cleanup: Dispose mesh native data, destroy Unity Mesh, clear material reference. Safely frees job resources if they exist.

  • private void DestroyMesh()
    Destroys the Unity Mesh (m_BoundaryMesh) if it exists and sets the reference to null. Uses Object.Destroy.

  • private void DisposeMeshData()
    Disposes native containers (m_Vertices, m_UVs, m_Colors, m_Indices, m_Bounds) if they were created. Waits on m_MeshDependencies (Complete) before disposing to ensure no jobs are still using them.

  • private bool GetLoaded()
    Internal helper: returns true if m_Loaded flag was set (and resets it). Used to trigger an immediate rebuild on load/deserialization.

  • protected override void OnUpdate()
    Main update logic:

  • If GetLoaded() is true (first frame after load) or if m_UpdatedQuery is not empty (tiles changed), it rebuilds boundary data.
  • Disposes any previous mesh/native data.
  • Obtains CityBoundaryPrefab from PrefabSystem (via m_SettingsQuery singleton entity) and copies its material, colors, width, tiling length.
  • Allocates temporary NativeQueue and NativeList containers (Vertices, UVs, Colors, Indices, Bounds) with Allocator.TempJob.
  • Prepares FillBoundaryQueueJob with required Component/Buffer lookups/handles, search-tree reader and other settings. FillBoundaryQueueJob schedules as a parallel chunk job across m_MapTileQuery (ScheduleParallel).
  • Schedules FillBoundaryMeshDataJob (IJob) dependent on the FillBoundaryQueueJob handle.
  • Registers the search-tree reader handle with m_AreaSearchSystem (AddSearchTreeReader) to ensure dependencies are tracked.
  • Stores job handles in m_MeshDependencies and base.Dependency accordingly.
  • Ensures the NativeQueue is disposed with boundaryQueue.Dispose(jobHandle2) (queued disposal).

Important: This method does not block on jobs to keep frame time low. The mesh is finalized on demand when GetBoundaryMesh is called (or the next consumer requires it), at which point jobs are completed.

  • public bool GetBoundaryMesh(out Mesh mesh, out Material material)
    Main consumer API: If the job-produced native lists exist, completes m_MeshDependencies, converts native arrays into a Unity Mesh (creating or clearing m_BoundaryMesh), assigns vertices, UVs, colors, indices (MeshTopology.Triangles), and sets mesh.bounds using TerrainSystem height scale/offset. Disposes native containers after conversion. Returns true if m_BoundaryMesh != null. Outputs mesh and material (material is the prefab's material previously stored).

Notes: - This is the main thread operation and must be called from the main thread (Unity API usage). - Completes jobs before accessing native data. - If no vertices were produced, the mesh is destroyed and returns null mesh.

  • private void __AssignQueries(ref SystemState state)
    Internal method executed by OnCreateForCompiler — appears to be for compiler-time generation/assignment of queries. Minimal implementation in this file creates and disposes a temporary EntityQueryBuilder. Marked MethodImpl(AggressiveInlining) on TypeHandle assignments.

  • protected override void OnCreateForCompiler()
    Compiler-time helper which calls __AssignQueries and assigns TypeHandle handles. Kept for generated/compiled workflows.

  • private void CheckLine(...) (inside FillBoundaryQueueJob.Invoke)
    Within the FillBoundaryQueueJob, there is a CheckLine method that builds an Iterator over the area search tree to test whether a candidate edge is adjacent to any other tile/area that would make it not a visible boundary. If the iterator finds no adjacent tile, the line is enqueued as a Boundary with color chosen depending on whether the area is native or city.

  • FillBoundaryQueueJob.Execute(in ArchetypeChunk ...)
    Chunk job entry point: iterates chunk entities and their Node buffers to enumerate closed polylines of nodes. For each segment between consecutive nodes, calls CheckLine(...) above. Handles special behavior for m_EditorMode (start tile set) and for native vs non-native tiles.

  • Iterator.Intersect / Iterate / CheckLine (nested inside FillBoundaryQueueJob.Iterator)
    Iterator used by the quad-tree. It checks if the search tree cell intersects the tested Line2.Segment and if the cell contains a candidate area item that might share the line. It inspects triangle buffers and nodes to detect matching edge coordinates (using distance squared checks with threshold). Sets m_TileFound when a matching adjacent tile is present.

  • FillBoundaryMeshDataJob.Execute()
    Dequeues boundaries from m_BoundaryQueue, for each segment:

  • Skips very short segments (length < 1.0).
  • Computes a perpendicular offset (half width scaled by segment length).
  • Splits the segment into num2 tiles (based on tiling length) and for each tile:
    • Generates 4 vertices (quad) with positions, UVs and colors.
    • Adds two triangles (6 indices).
  • Accumulates bounds into m_Bounds.
  • Writes results into m_Vertices, m_UVs, m_Colors, m_Indices. This job runs on a single thread (IJob) and is Burst-compiled.

Usage Example

// Example of retrieving the generated boundary mesh & material from another system or renderer.
public void RenderBoundaries(CityBoundaryMeshSystem boundarySystem)
{
    if (boundarySystem.GetBoundaryMesh(out Mesh mesh, out Material mat))
    {
        // Use mesh & mat for rendering (e.g., Graphics.DrawMesh)
        // Note: GetBoundaryMesh completes jobs and returns Mesh on main thread.
        Graphics.DrawMesh(mesh, Matrix4x4.identity, mat, 0);
    }
}

Additional notes: - The system is designed to avoid blocking the main thread during boundary detection and tessellation by using parallel chunk jobs + a follow-up IJob to build mesh buffers. Only the final mesh creation uses UnityEngine.Mesh API and runs on the main thread when GetBoundaryMesh is called. - When modding, change CityBoundaryPrefab settings (material, width, tiling, colors) to affect output. Ensure to respect Burst-compatibility for any custom job changes and keep native containers properly disposed. - Be mindful that coordinates comparisons for edges use small distance-squared thresholds, so exact vertex positions matter; modifications in upstream area/node generation may affect detection.