refactor(M03): extract WMO render build queue
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
extends SceneTree
|
||||
|
||||
## Asset-free lifecycle, ordering, ownership and timing regression for WMO jobs.
|
||||
|
||||
const QUEUE_SCRIPT := preload("res://src/render/wmo/wmo_render_build_queue.gd")
|
||||
const QUEUE_PATH := "res://src/render/wmo/wmo_render_build_queue.gd"
|
||||
const JOB_PATH := "res://src/render/wmo/wmo_render_build_job.gd"
|
||||
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var failures: Array[String] = []
|
||||
_verify_invalid_and_fifo_jobs(failures)
|
||||
_verify_cursor_adoption(failures)
|
||||
_verify_duplicate_and_stale_key_behavior(failures)
|
||||
_verify_cancel_clear_and_engine_lifetime(failures)
|
||||
_verify_detached_sorted_diagnostics(failures)
|
||||
_verify_loader_and_engine_lifetime_boundaries(failures)
|
||||
var elapsed_milliseconds := _verify_bounded_timing(failures)
|
||||
if not failures.is_empty():
|
||||
for failure in failures:
|
||||
push_error("WMO_RENDER_BUILD_QUEUE: %s" % failure)
|
||||
quit(1)
|
||||
return
|
||||
print(
|
||||
"WMO_RENDER_BUILD_QUEUE PASS cases=11 iterations=100 elapsed_ms=%.3f"
|
||||
% elapsed_milliseconds
|
||||
)
|
||||
quit(0)
|
||||
|
||||
|
||||
func _verify_invalid_and_fifo_jobs(failures: Array[String]) -> void:
|
||||
var queue: RefCounted = QUEUE_SCRIPT.new()
|
||||
var root := Node3D.new()
|
||||
var render_resource := Resource.new()
|
||||
_expect_false(queue.call("enqueue", "", root, render_resource), "empty key rejected", failures)
|
||||
_expect_false(queue.call("enqueue", "uid:1", null, render_resource), "null root rejected", failures)
|
||||
_expect_false(queue.call("enqueue", "uid:1", root, null), "null resource rejected", failures)
|
||||
_expect_true(queue.call("enqueue", "uid:2", root, render_resource), "first enqueue", failures)
|
||||
_expect_true(queue.call("enqueue", "uid:3", root, render_resource), "second enqueue", failures)
|
||||
_expect_string_equal(String(queue.call("front_key")), "uid:2", "FIFO front", failures)
|
||||
_expect_equal(int(queue.call("pending_count")), 2, "FIFO pending count", failures)
|
||||
_expect_equal(int(queue.call("active_job_count")), 2, "FIFO active count", failures)
|
||||
var first_job: RefCounted = queue.call("job_for", "uid:2")
|
||||
_expect_string_equal(String(first_job.call("placement_key")), "uid:2", "typed job key", failures)
|
||||
_expect_true(first_job.call("root") == root, "job retains root", failures)
|
||||
_expect_true(first_job.call("render_resource") == render_resource, "job retains resource", failures)
|
||||
queue.call("clear")
|
||||
root.free()
|
||||
|
||||
|
||||
func _verify_cursor_adoption(failures: Array[String]) -> void:
|
||||
var queue: RefCounted = QUEUE_SCRIPT.new()
|
||||
var root := Node3D.new()
|
||||
queue.call("enqueue", "uid:4", root, Resource.new())
|
||||
var job: RefCounted = queue.call("job_for", "uid:4")
|
||||
_expect_equal(int(job.call("mesh_index")), 0, "initial mesh cursor", failures)
|
||||
_expect_equal(int(job.call("multimesh_index")), 0, "initial multimesh cursor", failures)
|
||||
job.call("adopt_cursors", 7, 3)
|
||||
_expect_equal(int(job.call("mesh_index")), 7, "adopted mesh cursor", failures)
|
||||
_expect_equal(int(job.call("multimesh_index")), 3, "adopted multimesh cursor", failures)
|
||||
queue.call("clear")
|
||||
root.free()
|
||||
|
||||
|
||||
func _verify_duplicate_and_stale_key_behavior(failures: Array[String]) -> void:
|
||||
var queue: RefCounted = QUEUE_SCRIPT.new()
|
||||
var first_root := Node3D.new()
|
||||
var second_root := Node3D.new()
|
||||
queue.call("enqueue", "uid:5", first_root, Resource.new())
|
||||
queue.call("enqueue", "uid:5", second_root, Resource.new())
|
||||
_expect_equal(int(queue.call("pending_count")), 2, "duplicate keys append", failures)
|
||||
_expect_equal(int(queue.call("active_job_count")), 1, "duplicate job replaces", failures)
|
||||
var replacement_job: RefCounted = queue.call("job_for", "uid:5")
|
||||
_expect_true(replacement_job.call("root") == second_root, "duplicate latest job wins", failures)
|
||||
_expect_true(queue.call("cancel", "uid:5"), "duplicate cancel finds job", failures)
|
||||
_expect_equal(int(queue.call("pending_count")), 1, "cancel erases first duplicate key", failures)
|
||||
_expect_true(queue.call("job_for", "uid:5") == null, "remaining duplicate key is stale", failures)
|
||||
_expect_string_equal(String(queue.call("pop_front")), "uid:5", "stale front pop", failures)
|
||||
_expect_false(queue.call("has_pending"), "stale queue drained", failures)
|
||||
first_root.free()
|
||||
second_root.free()
|
||||
|
||||
|
||||
func _verify_cancel_clear_and_engine_lifetime(failures: Array[String]) -> void:
|
||||
var queue: RefCounted = QUEUE_SCRIPT.new()
|
||||
var root := Node3D.new()
|
||||
queue.call("enqueue", "uid:6", root, Resource.new())
|
||||
_expect_false(queue.call("cancel", "uid:missing"), "unknown cancel", failures)
|
||||
_expect_true(is_instance_valid(root), "unknown cancel keeps root valid", failures)
|
||||
queue.call("clear")
|
||||
queue.call("clear")
|
||||
_expect_equal(int(queue.call("pending_count")), 0, "clear idempotent", failures)
|
||||
_expect_true(is_instance_valid(root), "clear does not free root", failures)
|
||||
root.free()
|
||||
|
||||
|
||||
func _verify_detached_sorted_diagnostics(failures: Array[String]) -> void:
|
||||
var queue: RefCounted = QUEUE_SCRIPT.new()
|
||||
var root := Node3D.new()
|
||||
queue.call("enqueue", "uid:20", root, Resource.new())
|
||||
queue.call("enqueue", "uid:10", root, Resource.new())
|
||||
var snapshot: Dictionary = queue.call("diagnostic_snapshot")
|
||||
_expect_string_array(
|
||||
snapshot["queued_placement_keys"],
|
||||
["uid:20", "uid:10"],
|
||||
"diagnostic queue order",
|
||||
failures
|
||||
)
|
||||
var jobs: Array = snapshot["jobs"]
|
||||
_expect_string_equal(String(jobs[0]["placement_key"]), "uid:10", "diagnostic job sort", failures)
|
||||
(snapshot["queued_placement_keys"] as Array).append("mutated")
|
||||
jobs[0]["mesh_index"] = 99
|
||||
var fresh_snapshot: Dictionary = queue.call("diagnostic_snapshot")
|
||||
_expect_string_array(
|
||||
fresh_snapshot["queued_placement_keys"],
|
||||
["uid:20", "uid:10"],
|
||||
"diagnostic queue detached",
|
||||
failures
|
||||
)
|
||||
_expect_equal(int((fresh_snapshot["jobs"] as Array)[0]["mesh_index"]), 0, "job diagnostic detached", failures)
|
||||
queue.call("clear")
|
||||
root.free()
|
||||
|
||||
|
||||
func _verify_loader_and_engine_lifetime_boundaries(failures: Array[String]) -> void:
|
||||
var loader_source := _read_text(LOADER_PATH, failures)
|
||||
var queue_source := _read_text(QUEUE_PATH, failures)
|
||||
var job_source := _read_text(JOB_PATH, failures)
|
||||
_expect_true(
|
||||
loader_source.contains("WMO_RENDER_BUILD_QUEUE_SCRIPT.new()"),
|
||||
"loader composes queue",
|
||||
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.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)
|
||||
_expect_true(
|
||||
loader_source.contains("MeshInstance3D.new()")
|
||||
and loader_source.contains("MultiMeshInstance3D.new()")
|
||||
and loader_source.contains("try_consume_permit("),
|
||||
"loader retains materialization and permits",
|
||||
failures
|
||||
)
|
||||
for forbidden_token in ["queue_free", "RenderingServer", "RID", "MeshInstance3D", "MultiMeshInstance3D", "FileAccess", "Thread", "Mutex"]:
|
||||
_expect_false(
|
||||
queue_source.contains(forbidden_token) or job_source.contains(forbidden_token),
|
||||
"queue/job omit %s ownership" % forbidden_token,
|
||||
failures
|
||||
)
|
||||
|
||||
|
||||
func _verify_bounded_timing(failures: Array[String]) -> float:
|
||||
var queue: RefCounted = QUEUE_SCRIPT.new()
|
||||
var root := Node3D.new()
|
||||
var render_resource := Resource.new()
|
||||
var started_microseconds := Time.get_ticks_usec()
|
||||
for _iteration in range(100):
|
||||
for job_index in range(256):
|
||||
queue.call("enqueue", "uid:%d" % job_index, root, render_resource)
|
||||
for job_index in range(256):
|
||||
queue.call("cancel", "uid:%d" % job_index)
|
||||
_expect_equal(int(queue.call("pending_count")), 0, "timing cycle drains", failures)
|
||||
var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
|
||||
_expect_true(elapsed_milliseconds < 1000.0, "100 queue cycles remain bounded", failures)
|
||||
root.free()
|
||||
return elapsed_milliseconds
|
||||
|
||||
|
||||
func _read_text(path: String, failures: Array[String]) -> String:
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
if file == null:
|
||||
failures.append("cannot open %s" % path)
|
||||
return ""
|
||||
return file.get_as_text()
|
||||
|
||||
|
||||
func _expect_equal(actual_value: int, expected_value: int, label: String, failures: Array[String]) -> void:
|
||||
if actual_value != expected_value:
|
||||
failures.append("%s expected %d, got %d" % [label, expected_value, actual_value])
|
||||
|
||||
|
||||
func _expect_string_equal(actual_value: String, expected_value: String, label: String, failures: Array[String]) -> void:
|
||||
if actual_value != expected_value:
|
||||
failures.append("%s expected %s, got %s" % [label, expected_value, actual_value])
|
||||
|
||||
|
||||
func _expect_string_array(actual_values: Array, expected_values: Array, label: String, failures: Array[String]) -> void:
|
||||
if actual_values != expected_values:
|
||||
failures.append("%s expected %s, got %s" % [label, expected_values, actual_values])
|
||||
|
||||
|
||||
func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
|
||||
if not actual_value:
|
||||
failures.append("%s expected true" % label)
|
||||
|
||||
|
||||
func _expect_false(actual_value: bool, label: String, failures: Array[String]) -> void:
|
||||
if actual_value:
|
||||
failures.append("%s expected false" % label)
|
||||
@@ -0,0 +1 @@
|
||||
uid://s6bsyt6bv1uq
|
||||
Reference in New Issue
Block a user