render: add M2 build resource snapshot
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
extends SceneTree
|
||||
|
||||
## Asset-free M2 resource observation identity, adoption, lifetime, boundary and
|
||||
## bounded-timing regression.
|
||||
|
||||
const SNAPSHOT_SCRIPT := preload(
|
||||
"res://src/render/m2/m2_build_resource_snapshot.gd"
|
||||
)
|
||||
const SNAPSHOT_PATH := "res://src/render/m2/m2_build_resource_snapshot.gd"
|
||||
const DISPATCH_PATH := "res://src/render/m2/m2_build_dispatch_planner.gd"
|
||||
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var failures: Array[String] = []
|
||||
_verify_initial_observation(failures)
|
||||
_verify_static_adoption_and_replacement(failures)
|
||||
_verify_detached_diagnostics(failures)
|
||||
_verify_engine_lifetime(failures)
|
||||
_verify_loader_and_dependency_boundaries(failures)
|
||||
var elapsed_milliseconds := _verify_bounded_timing(failures)
|
||||
if not failures.is_empty():
|
||||
for failure in failures:
|
||||
push_error("M2_BUILD_RESOURCE_SNAPSHOT: %s" % failure)
|
||||
quit(1)
|
||||
return
|
||||
print(
|
||||
"M2_BUILD_RESOURCE_SNAPSHOT PASS cases=12 iterations=20000 elapsed_ms=%.3f"
|
||||
% elapsed_milliseconds
|
||||
)
|
||||
quit(0)
|
||||
|
||||
|
||||
func _verify_initial_observation(failures: Array[String]) -> void:
|
||||
var prototype := Node3D.new()
|
||||
var snapshot: RefCounted = SNAPSHOT_SCRIPT.new(
|
||||
"world/example.m2",
|
||||
prototype,
|
||||
true
|
||||
)
|
||||
_expect_string(
|
||||
String(snapshot.call("normalized_relative_path")),
|
||||
"world/example.m2",
|
||||
"normalized path retained",
|
||||
failures
|
||||
)
|
||||
_expect_same(
|
||||
snapshot.call("animated_prototype"),
|
||||
prototype,
|
||||
"animated prototype identity retained",
|
||||
failures
|
||||
)
|
||||
_expect_true(
|
||||
bool(snapshot.call("has_animated_prototype")),
|
||||
"animated availability",
|
||||
failures
|
||||
)
|
||||
_expect_true(
|
||||
bool(snapshot.call("animation_request_pending")),
|
||||
"animation pending retained",
|
||||
failures
|
||||
)
|
||||
_expect_false(bool(snapshot.call("has_static_mesh")), "static defaults empty", failures)
|
||||
_expect_false(
|
||||
bool(snapshot.call("static_model_missing")),
|
||||
"missing defaults false",
|
||||
failures
|
||||
)
|
||||
prototype.free()
|
||||
|
||||
|
||||
func _verify_static_adoption_and_replacement(failures: Array[String]) -> void:
|
||||
var snapshot: RefCounted = SNAPSHOT_SCRIPT.new("world/static.m2", null, false)
|
||||
var first_mesh := ArrayMesh.new()
|
||||
var second_mesh := ArrayMesh.new()
|
||||
snapshot.call("adopt_static_observation", first_mesh, true)
|
||||
_expect_same(snapshot.call("static_mesh"), first_mesh, "first Mesh identity", failures)
|
||||
_expect_true(bool(snapshot.call("has_static_mesh")), "static available", failures)
|
||||
_expect_true(
|
||||
bool(snapshot.call("static_model_missing")),
|
||||
"contradictory missing flag retained",
|
||||
failures
|
||||
)
|
||||
snapshot.call("adopt_static_observation", second_mesh, false)
|
||||
_expect_same(
|
||||
snapshot.call("static_mesh"),
|
||||
second_mesh,
|
||||
"replacement Mesh identity",
|
||||
failures
|
||||
)
|
||||
_expect_false(
|
||||
bool(snapshot.call("static_model_missing")),
|
||||
"replacement missing flag",
|
||||
failures
|
||||
)
|
||||
snapshot.call("adopt_static_observation", null, true)
|
||||
_expect_false(bool(snapshot.call("has_static_mesh")), "Mesh may clear", failures)
|
||||
_expect_true(bool(snapshot.call("static_model_missing")), "terminal missing adopted", failures)
|
||||
|
||||
|
||||
func _verify_detached_diagnostics(failures: Array[String]) -> void:
|
||||
var prototype := Node3D.new()
|
||||
var mesh := ArrayMesh.new()
|
||||
var snapshot: RefCounted = SNAPSHOT_SCRIPT.new("world/diag.m2", prototype, false)
|
||||
snapshot.call("adopt_static_observation", mesh, true)
|
||||
var diagnostics: Dictionary = snapshot.call("diagnostic_snapshot")
|
||||
_expect_true(
|
||||
diagnostics == {
|
||||
"normalized_relative_path": "world/diag.m2",
|
||||
"has_animated_prototype": true,
|
||||
"animation_request_pending": false,
|
||||
"has_static_mesh": true,
|
||||
"static_model_missing": true,
|
||||
},
|
||||
"diagnostic scalar contract",
|
||||
failures
|
||||
)
|
||||
_expect_false(diagnostics.values().has(prototype), "diagnostics omit prototype", failures)
|
||||
_expect_false(diagnostics.values().has(mesh), "diagnostics omit Mesh", failures)
|
||||
diagnostics["has_static_mesh"] = false
|
||||
var fresh: Dictionary = snapshot.call("diagnostic_snapshot")
|
||||
_expect_true(bool(fresh["has_static_mesh"]), "diagnostics detached", failures)
|
||||
prototype.free()
|
||||
|
||||
|
||||
func _verify_engine_lifetime(failures: Array[String]) -> void:
|
||||
var prototype := Node3D.new()
|
||||
var snapshot: RefCounted = SNAPSHOT_SCRIPT.new("world/lifetime.m2", prototype, false)
|
||||
snapshot = null
|
||||
_expect_true(is_instance_valid(prototype), "snapshot release keeps Node valid", failures)
|
||||
prototype.free()
|
||||
|
||||
|
||||
func _verify_loader_and_dependency_boundaries(failures: Array[String]) -> void:
|
||||
var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
|
||||
var dispatch_source := FileAccess.get_file_as_string(DISPATCH_PATH)
|
||||
var snapshot_source := FileAccess.get_file_as_string(SNAPSHOT_PATH)
|
||||
for delegated_token in [
|
||||
"M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(",
|
||||
"\"adopt_static_observation\"",
|
||||
"resource_snapshot.call(\"animated_prototype\")",
|
||||
"resource_snapshot.call(\"static_mesh\")",
|
||||
]:
|
||||
_expect_true(
|
||||
loader_source.contains(delegated_token),
|
||||
"loader uses %s" % delegated_token,
|
||||
failures
|
||||
)
|
||||
_expect_true(
|
||||
dispatch_source.contains("resource_snapshot.call(\"animation_request_pending\")")
|
||||
and dispatch_source.contains("resource_snapshot.call(\"has_static_mesh\")"),
|
||||
"dispatch planner consumes typed snapshot",
|
||||
failures
|
||||
)
|
||||
for forbidden_token in [
|
||||
"queue_free",
|
||||
"free(",
|
||||
"ResourceLoader",
|
||||
"RenderingServer",
|
||||
"FileAccess",
|
||||
"WorkerThreadPool",
|
||||
"Thread",
|
||||
"Mutex",
|
||||
]:
|
||||
_expect_false(
|
||||
snapshot_source.contains(forbidden_token),
|
||||
"snapshot omits %s ownership" % forbidden_token,
|
||||
failures
|
||||
)
|
||||
|
||||
|
||||
func _verify_bounded_timing(failures: Array[String]) -> float:
|
||||
var mesh := ArrayMesh.new()
|
||||
var started_microseconds := Time.get_ticks_usec()
|
||||
for iteration in range(20000):
|
||||
var snapshot: RefCounted = SNAPSHOT_SCRIPT.new(
|
||||
"world/%d.m2" % (iteration % 64),
|
||||
null,
|
||||
iteration % 5 == 0
|
||||
)
|
||||
snapshot.call(
|
||||
"adopt_static_observation",
|
||||
mesh if iteration % 3 == 0 else null,
|
||||
iteration % 7 == 0
|
||||
)
|
||||
snapshot.call("diagnostic_snapshot")
|
||||
var elapsed_milliseconds := (
|
||||
float(Time.get_ticks_usec() - started_microseconds) / 1000.0
|
||||
)
|
||||
_expect_true(
|
||||
elapsed_milliseconds < 1000.0,
|
||||
"20,000 snapshot cycles remain bounded",
|
||||
failures
|
||||
)
|
||||
return elapsed_milliseconds
|
||||
|
||||
|
||||
func _expect_string(
|
||||
actual: String,
|
||||
expected: String,
|
||||
label: String,
|
||||
failures: Array[String]
|
||||
) -> void:
|
||||
if actual != expected:
|
||||
failures.append("%s expected=%s actual=%s" % [label, expected, actual])
|
||||
|
||||
|
||||
func _expect_same(
|
||||
actual: Variant,
|
||||
expected: Variant,
|
||||
label: String,
|
||||
failures: Array[String]
|
||||
) -> void:
|
||||
if not is_same(actual, expected):
|
||||
failures.append(label)
|
||||
|
||||
|
||||
func _expect_true(condition: bool, label: String, failures: Array[String]) -> void:
|
||||
if not condition:
|
||||
failures.append(label)
|
||||
|
||||
|
||||
func _expect_false(condition: bool, label: String, failures: Array[String]) -> void:
|
||||
_expect_true(not condition, label, failures)
|
||||
Reference in New Issue
Block a user