diff --git a/docs/modules/wmo-render-build-queue.md b/docs/modules/wmo-render-build-queue.md index 5b8a8ad..6c4a018 100644 --- a/docs/modules/wmo-render-build-queue.md +++ b/docs/modules/wmo-render-build-queue.md @@ -69,7 +69,11 @@ editor UI are forbidden dependencies. | `has_pending()` / `pending_count()` | Query | Observe FIFO entries including duplicate/stale keys | Renderer main thread | None | | `active_job_count()` | Query | Count current keyed jobs | Renderer main thread | None | | `front_key()` / `pop_front()` | Queue operation | Inspect/remove one front FIFO key | Renderer main thread | Empty queue returns empty String | -| `job_for(placement_key)` | Query | Borrow current typed job or null for stale key | Renderer main thread | No ownership transfer | +| `has_job(placement_key)` | Query | Distinguish a live job from a stale FIFO key | Renderer main thread | Unknown key returns false | +| `job_for(placement_key)` | Diagnostic/test query | Borrow current job through its `RefCounted` base or return null | Renderer main thread | Runtime loader uses typed accessors instead | +| `root_for` / `render_resource_for` | Typed query | Borrow current engine references without exposing job implementation type | Renderer main thread | Stale key returns null | +| `mesh_index_for` / `multimesh_index_for` | Typed query | Return current cursors | Renderer main thread | Stale key returns zero | +| `adopt_cursors(placement_key, next_mesh_index, next_multimesh_index)` | Command/query | Atomically update current job cursors | Renderer main thread | Stale key returns false | | `cancel(placement_key)` | Command/query | Remove keyed job and first matching FIFO key | Renderer main thread | Unknown key returns false; stale duplicates remain | | `clear()` | Command | Release all job/key references idempotently | Reset/shutdown | Does not free engine objects | | `diagnostic_snapshot()` | Query | Return detached FIFO order and key-sorted scalar jobs | Caller-owned result | None | @@ -134,15 +138,15 @@ sequenceDiagram participant Planner as WmoRenderBuildStepPlanner Loader->>Queue: enqueue(key, root, resource) loop while permit and pending key - Loader->>Queue: front_key + job_for + Loader->>Queue: front_key + has_job alt stale key Loader->>Queue: pop_front else live job - Queue-->>Loader: borrowed Job + Loader->>Queue: typed root/resource/cursor accessors Loader->>Planner: plan_step(counts, job cursors) Planner-->>Loader: operation and next cursors Loader->>Loader: materialize one group on main thread - Loader->>Job: adopt_cursors(next mesh, next MultiMesh) + Loader->>Queue: adopt_cursors(key, next mesh, next MultiMesh) Loader->>Loader: consume one permit end end diff --git a/src/render/wmo/wmo_render_build_queue.gd b/src/render/wmo/wmo_render_build_queue.gd index f902cd5..bace2cd 100644 --- a/src/render/wmo/wmo_render_build_queue.gd +++ b/src/render/wmo/wmo_render_build_queue.gd @@ -35,9 +35,60 @@ func front_key() -> String: return _queued_placement_keys.front() -## Returns the current typed job for a placement key, or null for a stale key. -func job_for(placement_key: String) -> WmoRenderBuildJob: - return _jobs_by_placement_key.get(placement_key, null) as WmoRenderBuildJob +## Returns whether a FIFO placement key still has a current job record. +func has_job(placement_key: String) -> bool: + return _jobs_by_placement_key.has(placement_key) + + +## Returns the current job record as its engine base type, or null for a stale +## key. Runtime consumers should prefer the typed accessors below. +func job_for(placement_key: String) -> RefCounted: + return _jobs_by_placement_key.get(placement_key, null) as RefCounted + + +## Returns the borrowed root for a current job, or null for a stale key. +func root_for(placement_key: String) -> Node3D: + var job := job_for(placement_key) + if job == null: + return null + return job.call("root") as Node3D + + +## Returns the borrowed render resource for a current job, or null when stale. +func render_resource_for(placement_key: String) -> Resource: + var job := job_for(placement_key) + if job == null: + return null + return job.call("render_resource") as Resource + + +## Returns the current mesh cursor, or zero for a stale key. +func mesh_index_for(placement_key: String) -> int: + var job := job_for(placement_key) + if job == null: + return 0 + return int(job.call("mesh_index")) + + +## Returns the current MultiMesh cursor, or zero for a stale key. +func multimesh_index_for(placement_key: String) -> int: + var job := job_for(placement_key) + if job == null: + return 0 + return int(job.call("multimesh_index")) + + +## Adopts both cursors for a current job and returns whether it existed. +func adopt_cursors( + placement_key: String, + next_mesh_index: int, + next_multimesh_index: int +) -> bool: + var job := job_for(placement_key) + if job == null: + return false + job.call("adopt_cursors", next_mesh_index, next_multimesh_index) + return true ## Pops and returns the front FIFO key, or an empty String when already empty. @@ -79,9 +130,9 @@ func diagnostic_snapshot() -> Dictionary: var jobs: Array[Dictionary] = [] for placement_key_variant in placement_keys: var placement_key := String(placement_key_variant) - var job := job_for(placement_key) + var job: RefCounted = job_for(placement_key) if job != null: - jobs.append(job.diagnostic_snapshot()) + jobs.append(job.call("diagnostic_snapshot") as Dictionary) return { "queued_placement_keys": _queued_placement_keys.duplicate(), "jobs": jobs, diff --git a/src/scenes/streaming/streaming_world_loader.gd b/src/scenes/streaming/streaming_world_loader.gd index 02ed54b..824fe2d 100644 --- a/src/scenes/streaming/streaming_world_loader.gd +++ b/src/scenes/streaming/streaming_world_loader.gd @@ -3768,17 +3768,16 @@ func _process_wmo_render_build_jobs() -> void: while _render_budget_scheduler.has_remaining_permit( RENDER_BUDGET_SCHEDULER_SCRIPT.WMO_RENDER_GROUP_BUILD) and _wmo_render_build_queue.has_pending(): var unique_key := _wmo_render_build_queue.front_key() - var job := _wmo_render_build_queue.job_for(unique_key) - if job == null: + if not _wmo_render_build_queue.has_job(unique_key): _wmo_render_build_queue.pop_front() continue - var root: Node = job.root() + var root: Node = _wmo_render_build_queue.root_for(unique_key) if root == null or not is_instance_valid(root): _cancel_wmo_render_build_job(unique_key) continue - var render_resource: Resource = job.render_resource() + var render_resource: Resource = _wmo_render_build_queue.render_resource_for(unique_key) if render_resource == null: _cancel_wmo_render_build_job(unique_key) continue @@ -3786,11 +3785,11 @@ func _process_wmo_render_build_jobs() -> void: var meshes: Array = render_resource.get("meshes") var mesh_transforms: Array = render_resource.get("mesh_transforms") var mesh_names: PackedStringArray = render_resource.get("mesh_names") - var mesh_index := job.mesh_index() + var mesh_index := _wmo_render_build_queue.mesh_index_for(unique_key) var multimeshes: Array = render_resource.get("multimeshes") var multimesh_transforms: Array = render_resource.get("multimesh_transforms") var multimesh_names: PackedStringArray = render_resource.get("multimesh_names") - var multimesh_index := job.multimesh_index() + var multimesh_index := _wmo_render_build_queue.multimesh_index_for(unique_key) var build_step: Dictionary = _wmo_render_build_step_planner.plan_step( meshes.size(), mesh_index, @@ -3821,7 +3820,8 @@ func _process_wmo_render_build_jobs() -> void: mesh_instance.visibility_range_end_margin = CHUNK_SIZE (root as Node3D).add_child(mesh_instance) _set_editor_owner_recursive(mesh_instance) - job.adopt_cursors( + _wmo_render_build_queue.adopt_cursors( + unique_key, int(build_step["next_mesh_index"]), int(build_step["next_multimesh_index"]) ) @@ -3851,7 +3851,8 @@ func _process_wmo_render_build_jobs() -> void: multimesh_instance.visibility_range_end_margin = CHUNK_SIZE (root as Node3D).add_child(multimesh_instance) _set_editor_owner_recursive(multimesh_instance) - job.adopt_cursors( + _wmo_render_build_queue.adopt_cursors( + unique_key, int(build_step["next_mesh_index"]), int(build_step["next_multimesh_index"]) ) diff --git a/src/tools/verify_wmo_render_build_queue.gd b/src/tools/verify_wmo_render_build_queue.gd index 849b436..728286a 100644 --- a/src/tools/verify_wmo_render_build_queue.gd +++ b/src/tools/verify_wmo_render_build_queue.gd @@ -133,7 +133,10 @@ func _verify_loader_and_engine_lifetime_boundaries(failures: Array[String]) -> v failures ) _expect_true(loader_source.contains("_wmo_render_build_queue.enqueue("), "loader delegates enqueue", failures) - _expect_true(loader_source.contains("_wmo_render_build_queue.job_for("), "loader reads typed job", failures) + _expect_true(loader_source.contains("_wmo_render_build_queue.has_job("), "loader checks stale keys", failures) + _expect_true(loader_source.contains("_wmo_render_build_queue.root_for("), "loader borrows root", failures) + _expect_true(loader_source.contains("_wmo_render_build_queue.render_resource_for("), "loader borrows resource", failures) + _expect_true(loader_source.contains("_wmo_render_build_queue.adopt_cursors("), "loader delegates cursors", failures) _expect_true(loader_source.contains("_wmo_render_build_queue.cancel("), "loader delegates cancel", failures) _expect_true(loader_source.contains("_wmo_render_build_queue.clear()"), "loader delegates clear", failures) _expect_false(loader_source.contains("var _wmo_render_build_jobs"), "legacy job dictionary removed", failures)