render: extract M2 Mesh resource finalizer

This commit is contained in:
2026-07-18 13:51:08 +04:00
parent c328d86554
commit ddeb708c08
21 changed files with 1147 additions and 150 deletions
+182
View File
@@ -0,0 +1,182 @@
class_name M2MeshResourceFinalizer
extends RefCounted
## Polls static M2 ResourceLoader requests and publishes prepared Mesh outcomes.
## The caller owns permits, pipeline/cache lifetime and MultiMesh materialization.
var _mesh_resource_extractor: RefCounted
var _runtime_mesh_finalizer: RefCounted
var _raw_model_repository: RefCounted
var _resource_loader_adapter: Object
func _init(
mesh_resource_extractor: RefCounted,
runtime_mesh_finalizer: RefCounted,
raw_model_repository: RefCounted,
resource_loader_adapter: Object = null
) -> void:
_mesh_resource_extractor = mesh_resource_extractor
_runtime_mesh_finalizer = runtime_mesh_finalizer
_raw_model_repository = raw_model_repository
_resource_loader_adapter = resource_loader_adapter
## Moves terminal threaded requests into the pipeline finalize FIFO.
## Empty resource paths retain the historical immediate missing-model outcome.
func poll_terminal_requests(
load_pipeline_state: RefCounted,
prototype_cache_state: RefCounted
) -> int:
if load_pipeline_state == null or prototype_cache_state == null:
return 0
var completed_request_count := 0
var request_records: Array = load_pipeline_state.call(
"request_records_snapshot"
)
for request_variant in request_records:
if not (request_variant is Dictionary):
continue
var request: Dictionary = request_variant
var normalized_relative_path := String(request.get("normalized", ""))
var resource_path := String(request.get("path", ""))
if resource_path.is_empty():
load_pipeline_state.call(
"discard_request",
normalized_relative_path
)
prototype_cache_state.call(
"mark_model_missing",
normalized_relative_path
)
completed_request_count += 1
continue
var load_status := load_threaded_get_status(resource_path)
if (
load_status != ResourceLoader.THREAD_LOAD_LOADED
and load_status != ResourceLoader.THREAD_LOAD_FAILED
):
continue
load_pipeline_state.call(
"complete_request",
normalized_relative_path,
load_status
)
completed_request_count += 1
return completed_request_count
## Pops and finalizes one terminal record. A true result means a record was
## consumed, including cached, failed and invalid outcomes.
func finalize_next_resource(
load_pipeline_state: RefCounted,
mesh_resource_cache_state: RefCounted,
prototype_cache_state: RefCounted,
extracted_directory: String
) -> bool:
if (
load_pipeline_state == null
or mesh_resource_cache_state == null
or prototype_cache_state == null
):
return false
var terminal_record: Dictionary = load_pipeline_state.call(
"pop_finalize_record"
)
if terminal_record.is_empty():
return false
var normalized_relative_path := String(
terminal_record.get("normalized", "")
)
if (
normalized_relative_path.is_empty()
or bool(mesh_resource_cache_state.call(
"has_mesh",
normalized_relative_path
))
):
return true
if int(terminal_record.get(
"status",
ResourceLoader.THREAD_LOAD_FAILED
)) != ResourceLoader.THREAD_LOAD_LOADED:
prototype_cache_state.call(
"mark_model_missing",
normalized_relative_path
)
return true
var resource_path := String(terminal_record.get("path", ""))
var loaded_resource := load_threaded_get(resource_path)
var mesh := _mesh_resource_extractor.call(
"extract_first_mesh",
loaded_resource
) as Mesh
if mesh == null:
prototype_cache_state.call(
"mark_model_missing",
normalized_relative_path
)
return true
mesh_resource_cache_state.call(
"store_mesh",
normalized_relative_path,
prepare_mesh_for_runtime(
normalized_relative_path,
mesh,
extracted_directory
)
)
return true
## Applies the existing refresh-version/raw-data/rebuild path to one Mesh.
func prepare_mesh_for_runtime(
normalized_relative_path: String,
mesh: Mesh,
extracted_directory: String
) -> Mesh:
if mesh == null:
return null
if (
_runtime_mesh_finalizer == null
or not bool(_runtime_mesh_finalizer.call(
"requires_raw_data_for_refresh",
mesh
))
):
return mesh
var raw_model_data: Dictionary = {}
if _raw_model_repository != null:
raw_model_data = _raw_model_repository.call(
"load_static_model_data",
extracted_directory,
normalized_relative_path
)
return _runtime_mesh_finalizer.call(
"finalize_mesh",
normalized_relative_path,
mesh,
raw_model_data,
extracted_directory
) as Mesh
## Production ResourceLoader status adapter; injectable in synthetic tests.
func load_threaded_get_status(resource_path: String) -> int:
if _resource_loader_adapter != null:
return int(_resource_loader_adapter.call(
"load_threaded_get_status",
resource_path
))
return ResourceLoader.load_threaded_get_status(resource_path)
## Production ResourceLoader terminal-result adapter; injectable in tests.
func load_threaded_get(resource_path: String) -> Resource:
if _resource_loader_adapter != null:
return _resource_loader_adapter.call(
"load_threaded_get",
resource_path
) as Resource
return ResourceLoader.load_threaded_get(resource_path)
@@ -0,0 +1 @@
uid://de4xl1fywvqfw
+28 -51
View File
@@ -108,6 +108,9 @@ const M2_MESH_RESOURCE_CACHE_STATE_SCRIPT := preload(
const M2_MESH_RESOURCE_EXTRACTOR_SCRIPT := preload(
"res://src/render/m2/m2_mesh_resource_extractor.gd"
)
const M2_MESH_RESOURCE_FINALIZER_SCRIPT := preload(
"res://src/render/m2/m2_mesh_resource_finalizer.gd"
)
const STREAMING_TARGET_PLANNER_SCRIPT := preload("res://src/render/streaming/streaming_target_planner.gd")
const STREAMING_TARGET_POLICY_SCRIPT := preload("res://src/render/streaming/streaming_target_policy.gd")
const RENDER_BUDGET_SCHEDULER_SCRIPT := preload("res://src/render/streaming/render_budget_scheduler.gd")
@@ -309,6 +312,11 @@ var _m2_raw_model_repository := M2_RAW_MODEL_REPOSITORY_SCRIPT.new()
var _m2_prototype_cache_state := M2_PROTOTYPE_CACHE_STATE_SCRIPT.new()
var _m2_mesh_resource_cache_state := M2_MESH_RESOURCE_CACHE_STATE_SCRIPT.new()
var _m2_mesh_resource_extractor := M2_MESH_RESOURCE_EXTRACTOR_SCRIPT.new()
var _m2_mesh_resource_finalizer := M2_MESH_RESOURCE_FINALIZER_SCRIPT.new(
_m2_mesh_resource_extractor,
_m2_runtime_mesh_finalizer,
_m2_raw_model_repository
)
var _m2_mesh_load_pipeline_state := M2_MESH_LOAD_PIPELINE_STATE_SCRIPT.new()
var _m2_animation_load_pipeline_state := M2_ANIMATION_LOAD_PIPELINE_STATE_SCRIPT.new()
var _m2_animated_scene_finalizer := M2_ANIMATED_SCENE_FINALIZER_SCRIPT.new()
@@ -4268,41 +4276,19 @@ func _drain_m2_animation_loads() -> void:
func _drain_m2_mesh_loads() -> void:
for pending in _m2_mesh_load_pipeline_state.request_records_snapshot():
var normalized_rel := String(pending.get("normalized", ""))
var path: String = String(pending.get("path", ""))
if path.is_empty():
_m2_mesh_load_pipeline_state.discard_request(normalized_rel)
_m2_prototype_cache_state.mark_model_missing(normalized_rel)
continue
var status := ResourceLoader.load_threaded_get_status(path)
if status != ResourceLoader.THREAD_LOAD_LOADED and status != ResourceLoader.THREAD_LOAD_FAILED:
continue
_m2_mesh_load_pipeline_state.complete_request(normalized_rel, status)
_m2_mesh_resource_finalizer.poll_terminal_requests(
_m2_mesh_load_pipeline_state,
_m2_prototype_cache_state
)
while _m2_mesh_load_pipeline_state.has_finalize_record() and _render_budget_scheduler.try_consume_permit(
RENDER_BUDGET_SCHEDULER_SCRIPT.M2_MESH_FINALIZE):
var pending: Dictionary = _m2_mesh_load_pipeline_state.pop_finalize_record()
var normalized_rel := String(pending.get("normalized", ""))
if (
normalized_rel.is_empty()
or _m2_mesh_resource_cache_state.has_mesh(normalized_rel)
):
continue
if int(pending.get("status", ResourceLoader.THREAD_LOAD_FAILED)) != ResourceLoader.THREAD_LOAD_LOADED:
_m2_prototype_cache_state.mark_model_missing(normalized_rel)
continue
var path := String(pending.get("path", ""))
var resource: Resource = ResourceLoader.load_threaded_get(path)
var mesh := _m2_mesh_resource_extractor.extract_first_mesh(resource)
if mesh != null:
_m2_mesh_resource_cache_state.store_mesh(
normalized_rel,
_prepare_m2_mesh_for_runtime(normalized_rel, mesh)
)
else:
_m2_prototype_cache_state.mark_model_missing(normalized_rel)
_m2_mesh_resource_finalizer.finalize_next_resource(
_m2_mesh_load_pipeline_state,
_m2_mesh_resource_cache_state,
_m2_prototype_cache_state,
extracted_dir
)
func _process_m2_build_jobs() -> void:
@@ -4606,30 +4592,17 @@ func _normalize_m2_rel_path(rel_path: String) -> String:
return normalized_rel
func _prepare_m2_mesh_for_runtime(normalized_rel: String, mesh: Mesh) -> Mesh:
if mesh == null:
return null
if not _m2_runtime_mesh_finalizer.requires_raw_data_for_refresh(mesh):
return mesh
var data := _m2_raw_model_repository.load_static_model_data(
extracted_dir,
normalized_rel
)
return _m2_runtime_mesh_finalizer.finalize_mesh(
normalized_rel,
mesh,
data,
extracted_dir
)
func _prepare_m2_node_for_runtime(normalized_rel: String, root: Node3D) -> void:
if root == null:
return
for mesh_instance in _m2_animated_scene_finalizer.mesh_instances_in_subtree(root):
if mesh_instance == null or mesh_instance.mesh == null:
continue
var prepared := _prepare_m2_mesh_for_runtime(normalized_rel, mesh_instance.mesh)
var prepared := _m2_mesh_resource_finalizer.prepare_mesh_for_runtime(
normalized_rel,
mesh_instance.mesh,
extracted_dir
)
if prepared != null:
mesh_instance.mesh = prepared
@@ -4643,7 +4616,11 @@ func _get_or_load_m2_mesh(rel_path: String) -> Mesh:
return null
var mesh := _m2_mesh_resource_extractor.find_first_mesh_in_subtree(prototype)
if mesh != null and not normalized_rel.is_empty():
mesh = _prepare_m2_mesh_for_runtime(normalized_rel, mesh)
mesh = _m2_mesh_resource_finalizer.prepare_mesh_for_runtime(
normalized_rel,
mesh,
extracted_dir
)
_m2_mesh_resource_cache_state.store_mesh(normalized_rel, mesh)
return mesh
@@ -6,6 +6,7 @@ extends SceneTree
const PIPELINE_SCRIPT := preload("res://src/render/m2/m2_mesh_load_pipeline_state.gd")
const PIPELINE_PATH := "res://src/render/m2/m2_mesh_load_pipeline_state.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
const RESOURCE_FINALIZER_PATH := "res://src/render/m2/m2_mesh_resource_finalizer.gd"
func _initialize() -> void:
@@ -86,6 +87,9 @@ func _verify_discard_metrics_clear_and_diagnostics(failures: Array[String]) -> v
func _verify_ownership_boundaries(failures: Array[String]) -> void:
var pipeline_source := FileAccess.get_file_as_string(PIPELINE_PATH)
var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
var resource_finalizer_source := FileAccess.get_file_as_string(
RESOURCE_FINALIZER_PATH
)
_expect_true(
loader_source.contains("M2_MESH_LOAD_PIPELINE_STATE_SCRIPT.new()"),
"loader composes pipeline state",
@@ -105,15 +109,19 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
"two existing clear sites delegate",
failures
)
for retained_loader_rule in [
for retained_renderer_rule in [
"ResourceLoader.load_threaded_request(",
"ResourceLoader.load_threaded_get_status(path)",
"ResourceLoader.load_threaded_get(path)",
"ResourceLoader.load_threaded_get_status(resource_path)",
"ResourceLoader.load_threaded_get(resource_path)",
"RENDER_BUDGET_SCHEDULER_SCRIPT.M2_MESH_FINALIZE",
"_m2_mesh_resource_cache_state.store_mesh(",
"_m2_prototype_cache_state.mark_model_missing(normalized_rel)",
"\"store_mesh\"",
"\"mark_model_missing\"",
]:
_expect_true(loader_source.contains(retained_loader_rule), "loader retains %s" % retained_loader_rule, failures)
_expect_true(
(loader_source + resource_finalizer_source).contains(retained_renderer_rule),
"renderer retains %s" % retained_renderer_rule,
failures
)
for forbidden_dependency in [
"ResourceLoader.",
"WorkerThreadPool.",
@@ -7,6 +7,7 @@ const CACHE_SCRIPT := preload("res://src/render/m2/m2_mesh_resource_cache_state.
const CACHE_PATH := "res://src/render/m2/m2_mesh_resource_cache_state.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
const STATIC_OBSERVER_PATH := "res://src/render/m2/m2_static_build_resource_observer.gd"
const RESOURCE_FINALIZER_PATH := "res://src/render/m2/m2_mesh_resource_finalizer.gd"
func _initialize() -> void:
@@ -70,6 +71,9 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
var cache_source := FileAccess.get_file_as_string(CACHE_PATH)
var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
var observer_source := FileAccess.get_file_as_string(STATIC_OBSERVER_PATH)
var resource_finalizer_source := FileAccess.get_file_as_string(
RESOURCE_FINALIZER_PATH
)
_expect_true(
loader_source.contains("M2_MESH_RESOURCE_CACHE_STATE_SCRIPT.new()"),
"loader composes Mesh cache state",
@@ -77,7 +81,8 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
)
_expect_false(loader_source.contains("var _m2_mesh_cache:"), "legacy Mesh cache removed", failures)
_expect_equal(
loader_source.count("_m2_mesh_resource_cache_state.store_mesh("),
loader_source.count("_m2_mesh_resource_cache_state.store_mesh(")
+ resource_finalizer_source.count("\"store_mesh\""),
2,
"two existing stores delegate",
failures
@@ -95,14 +100,18 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
"final shutdown clear delegates",
failures
)
for retained_loader_rule in [
"ResourceLoader.load_threaded_get(path)",
"_m2_mesh_resource_extractor.extract_first_mesh(resource)",
"_prepare_m2_mesh_for_runtime(normalized_rel, mesh)",
"_m2_prototype_cache_state.mark_model_missing(normalized_rel)",
for retained_renderer_rule in [
"ResourceLoader.load_threaded_get(resource_path)",
"\"extract_first_mesh\"",
"prepare_mesh_for_runtime(",
"\"mark_model_missing\"",
"_m2_prototype_cache_state.adopt_static_prototype(",
]:
_expect_true(loader_source.contains(retained_loader_rule), "loader retains %s" % retained_loader_rule, failures)
_expect_true(
(loader_source + resource_finalizer_source).contains(retained_renderer_rule),
"renderer retains %s" % retained_renderer_rule,
failures
)
for forbidden_dependency in [
"ResourceLoader.",
"FileAccess.",
+15 -7
View File
@@ -6,6 +6,7 @@ extends SceneTree
const EXTRACTOR_SCRIPT := preload("res://src/render/m2/m2_mesh_resource_extractor.gd")
const EXTRACTOR_PATH := "res://src/render/m2/m2_mesh_resource_extractor.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
const RESOURCE_FINALIZER_PATH := "res://src/render/m2/m2_mesh_resource_finalizer.gd"
func _initialize() -> void:
@@ -94,6 +95,9 @@ func _verify_packed_scene_mesh_and_temporary_lifetime(failures: Array[String]) -
func _verify_ownership_boundaries(failures: Array[String]) -> void:
var extractor_source := FileAccess.get_file_as_string(EXTRACTOR_PATH)
var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
var resource_finalizer_source := FileAccess.get_file_as_string(
RESOURCE_FINALIZER_PATH
)
_expect_true(
loader_source.contains("M2_MESH_RESOURCE_EXTRACTOR_SCRIPT.new()"),
"loader composes Mesh resource extractor",
@@ -110,7 +114,7 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
failures
)
_expect_equal(
loader_source.count("_m2_mesh_resource_extractor.extract_first_mesh("),
resource_finalizer_source.count("\"extract_first_mesh\""),
1,
"threaded Resource finalization delegates",
failures
@@ -121,14 +125,18 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
"synchronous fallback delegates",
failures
)
for retained_loader_rule in [
"ResourceLoader.load_threaded_get(path)",
"_prepare_m2_mesh_for_runtime(normalized_rel, mesh)",
"_m2_mesh_resource_cache_state.store_mesh(",
for retained_renderer_rule in [
"ResourceLoader.load_threaded_get(resource_path)",
"prepare_mesh_for_runtime(",
"\"store_mesh\"",
"M2_BUILDER_SCRIPT.build(data, extracted_dir)",
"_m2_prototype_cache_state.mark_model_missing(normalized_rel)",
"\"mark_model_missing\"",
]:
_expect_true(loader_source.contains(retained_loader_rule), "loader retains %s" % retained_loader_rule, failures)
_expect_true(
(loader_source + resource_finalizer_source).contains(retained_renderer_rule),
"renderer retains %s" % retained_renderer_rule,
failures
)
for forbidden_dependency in [
"ResourceLoader.",
"FileAccess.",
@@ -0,0 +1,499 @@
extends SceneTree
const FINALIZER_SCRIPT := preload(
"res://src/render/m2/m2_mesh_resource_finalizer.gd"
)
const PIPELINE_SCRIPT := preload(
"res://src/render/m2/m2_mesh_load_pipeline_state.gd"
)
const MESH_CACHE_SCRIPT := preload(
"res://src/render/m2/m2_mesh_resource_cache_state.gd"
)
const PROTOTYPE_CACHE_SCRIPT := preload(
"res://src/render/m2/m2_prototype_cache_state.gd"
)
const FINALIZER_PATH := "res://src/render/m2/m2_mesh_resource_finalizer.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
class FakeResourceLoaderAdapter extends RefCounted:
var statuses: Dictionary = {}
var resources: Dictionary = {}
var status_paths: Array[String] = []
var load_paths: Array[String] = []
func load_threaded_get_status(resource_path: String) -> int:
status_paths.append(resource_path)
return int(statuses.get(
resource_path,
ResourceLoader.THREAD_LOAD_IN_PROGRESS
))
func load_threaded_get(resource_path: String) -> Resource:
load_paths.append(resource_path)
return resources.get(resource_path, null) as Resource
class FakeMeshResourceExtractor extends RefCounted:
var mesh_by_resource: Dictionary = {}
var resources: Array[Resource] = []
func extract_first_mesh(resource: Resource) -> Mesh:
resources.append(resource)
return mesh_by_resource.get(resource, null) as Mesh
class FakeRuntimeMeshFinalizer extends RefCounted:
var raw_data_required: bool = false
var finalized_mesh: Mesh = null
var requirement_meshes: Array[Mesh] = []
var finalize_calls: Array[Dictionary] = []
func requires_raw_data_for_refresh(mesh: Mesh) -> bool:
requirement_meshes.append(mesh)
return raw_data_required
func finalize_mesh(
normalized_relative_path: String,
mesh: Mesh,
raw_model_data: Dictionary,
extracted_directory: String
) -> Mesh:
finalize_calls.append({
"normalized": normalized_relative_path,
"mesh": mesh,
"raw_model_data": raw_model_data,
"extracted_directory": extracted_directory,
})
return finalized_mesh
class FakeRawModelRepository extends RefCounted:
var result: Dictionary = {}
var calls: Array[Dictionary] = []
func load_static_model_data(
extracted_directory: String,
normalized_relative_path: String
) -> Dictionary:
calls.append({
"extracted_directory": extracted_directory,
"normalized": normalized_relative_path,
})
return result
class EmptyPathPipeline extends RefCounted:
var discarded_path: String = ""
func request_records_snapshot() -> Array[Dictionary]:
return [{"normalized": "world/empty.m2", "path": ""}]
func discard_request(normalized_relative_path: String) -> bool:
discarded_path = normalized_relative_path
return true
func _initialize() -> void:
var failures: Array[String] = []
_verify_polling(failures)
_verify_empty_path_polling(failures)
_verify_terminal_rejections(failures)
_verify_current_mesh_adoption(failures)
_verify_stale_mesh_preparation(failures)
_verify_source_boundaries(failures)
var elapsed_milliseconds := _verify_bounded_timing(failures)
if not failures.is_empty():
for failure in failures:
push_error("M2_MESH_RESOURCE_FINALIZER: %s" % failure)
quit(1)
return
print(
"M2_MESH_RESOURCE_FINALIZER PASS "
+ "cases=31 iterations=1000 elapsed_ms=%.3f" % elapsed_milliseconds
)
quit(0)
func _verify_polling(failures: Array[String]) -> void:
var dependencies := _dependencies()
var service: RefCounted = dependencies.service
var resource_loader: FakeResourceLoaderAdapter = dependencies.resource_loader
var pipeline: RefCounted = PIPELINE_SCRIPT.new()
var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
for request in [
["world/pending.m2", "res://pending.tres"],
["world/loaded.m2", "res://loaded.tres"],
["world/failed.m2", "res://failed.tres"],
]:
pipeline.call("remember_request", request[0], request[1])
resource_loader.statuses = {
"res://pending.tres": ResourceLoader.THREAD_LOAD_IN_PROGRESS,
"res://loaded.tres": ResourceLoader.THREAD_LOAD_LOADED,
"res://failed.tres": ResourceLoader.THREAD_LOAD_FAILED,
}
_expect_equal(
int(service.call("poll_terminal_requests", pipeline, prototype_cache)),
2,
"two terminal requests completed",
failures
)
_expect_equal(int(pipeline.call("pending_request_count")), 1, "pending retained", failures)
_expect_equal(int(pipeline.call("finalize_record_count")), 2, "two finalize records", failures)
_expect_string_array(
resource_loader.status_paths,
["res://pending.tres", "res://loaded.tres", "res://failed.tres"],
"status insertion order",
failures
)
prototype_cache.call("clear_and_release")
func _verify_empty_path_polling(failures: Array[String]) -> void:
var dependencies := _dependencies()
var pipeline := EmptyPathPipeline.new()
var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
_expect_equal(
int(dependencies.service.call(
"poll_terminal_requests",
pipeline,
prototype_cache
)),
1,
"empty resource path completed",
failures
)
_expect_string_equal(
pipeline.discarded_path,
"world/empty.m2",
"empty resource path discarded",
failures
)
_expect_true(
bool(prototype_cache.call("is_model_missing", "world/empty.m2")),
"empty resource path marks missing",
failures
)
_expect_equal(
dependencies.resource_loader.status_paths.size(),
0,
"empty path skips I/O",
failures
)
prototype_cache.call("clear_and_release")
func _verify_terminal_rejections(failures: Array[String]) -> void:
var dependencies := _dependencies()
var service: RefCounted = dependencies.service
var pipeline: RefCounted = PIPELINE_SCRIPT.new()
var mesh_cache: RefCounted = MESH_CACHE_SCRIPT.new()
var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
_expect_false(
bool(service.call(
"finalize_next_resource",
pipeline,
mesh_cache,
prototype_cache,
"res://data/extracted"
)),
"empty finalize FIFO",
failures
)
_enqueue_terminal(
pipeline,
"world/failed.m2",
"res://failed.tres",
ResourceLoader.THREAD_LOAD_FAILED
)
_expect_true(
bool(service.call(
"finalize_next_resource", pipeline, mesh_cache,
prototype_cache, "res://data/extracted"
)),
"failed record consumed",
failures
)
_expect_true(
bool(prototype_cache.call("is_model_missing", "world/failed.m2")),
"failed record marks missing",
failures
)
var cached_mesh := ArrayMesh.new()
mesh_cache.call("store_mesh", "world/cached.m2", cached_mesh)
_enqueue_terminal(
pipeline,
"world/cached.m2",
"res://cached.tres",
ResourceLoader.THREAD_LOAD_LOADED
)
service.call(
"finalize_next_resource", pipeline, mesh_cache,
prototype_cache, "res://data/extracted"
)
_enqueue_terminal(
pipeline,
"world/null.m2",
"res://null.tres",
ResourceLoader.THREAD_LOAD_LOADED
)
service.call(
"finalize_next_resource", pipeline, mesh_cache,
prototype_cache, "res://data/extracted"
)
_expect_true(
bool(prototype_cache.call("is_model_missing", "world/null.m2")),
"null Resource marks missing",
failures
)
_expect_string_array(
dependencies.resource_loader.load_paths,
["res://null.tres"],
"cached record skips terminal get",
failures
)
mesh_cache.call("clear")
prototype_cache.call("clear_and_release")
func _verify_current_mesh_adoption(failures: Array[String]) -> void:
var dependencies := _dependencies()
var resource := Resource.new()
var mesh := ArrayMesh.new()
dependencies.resource_loader.resources["res://current.tres"] = resource
dependencies.extractor.mesh_by_resource[resource] = mesh
var pipeline: RefCounted = PIPELINE_SCRIPT.new()
var mesh_cache: RefCounted = MESH_CACHE_SCRIPT.new()
var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
_enqueue_terminal(
pipeline,
"world/current.m2",
"res://current.tres",
ResourceLoader.THREAD_LOAD_LOADED
)
_expect_true(
bool(dependencies.service.call(
"finalize_next_resource", pipeline, mesh_cache,
prototype_cache, "res://custom/extracted"
)),
"current Mesh record consumed",
failures
)
_expect_same(
mesh_cache.call("find_mesh", "world/current.m2"),
mesh,
"current Mesh exact adoption",
failures
)
_expect_equal(dependencies.raw_repository.calls.size(), 0, "current Mesh skips raw", failures)
_expect_equal(
dependencies.runtime_finalizer.finalize_calls.size(),
0,
"current Mesh skips finalize",
failures
)
mesh_cache.call("clear")
prototype_cache.call("clear_and_release")
func _verify_stale_mesh_preparation(failures: Array[String]) -> void:
var dependencies := _dependencies()
dependencies.runtime_finalizer.raw_data_required = true
dependencies.raw_repository.result = {"vertices": [1]}
var resource := Resource.new()
var stale_mesh := ArrayMesh.new()
var prepared_mesh := ArrayMesh.new()
dependencies.runtime_finalizer.finalized_mesh = prepared_mesh
dependencies.resource_loader.resources["res://stale.tres"] = resource
dependencies.extractor.mesh_by_resource[resource] = stale_mesh
var pipeline: RefCounted = PIPELINE_SCRIPT.new()
var mesh_cache: RefCounted = MESH_CACHE_SCRIPT.new()
var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
_enqueue_terminal(
pipeline,
"world/stale.m2",
"res://stale.tres",
ResourceLoader.THREAD_LOAD_LOADED
)
dependencies.service.call(
"finalize_next_resource", pipeline, mesh_cache,
prototype_cache, "res://custom/extracted"
)
_expect_same(
mesh_cache.call("find_mesh", "world/stale.m2"),
prepared_mesh,
"prepared Mesh adoption",
failures
)
_expect_equal(dependencies.raw_repository.calls.size(), 1, "one raw read", failures)
_expect_string_equal(
String(dependencies.raw_repository.calls[0].normalized),
"world/stale.m2",
"raw normalized path",
failures
)
_expect_string_equal(
String(dependencies.raw_repository.calls[0].extracted_directory),
"res://custom/extracted",
"raw extracted directory",
failures
)
_expect_same(
dependencies.runtime_finalizer.finalize_calls[0].mesh,
stale_mesh,
"runtime finalizer source Mesh",
failures
)
_expect_true(
dependencies.runtime_finalizer.finalize_calls[0].raw_model_data
== dependencies.raw_repository.result,
"runtime finalizer raw data",
failures
)
mesh_cache.call("clear")
prototype_cache.call("clear_and_release")
func _verify_source_boundaries(failures: Array[String]) -> void:
var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
var service_source := FileAccess.get_file_as_string(FINALIZER_PATH)
var drain_start := loader_source.find("func _drain_m2_mesh_loads()")
var drain_end := loader_source.find("func _process_m2_build_jobs()", drain_start)
var drain_source := loader_source.substr(drain_start, drain_end - drain_start)
_expect_true(
drain_source.contains("_m2_mesh_resource_finalizer.poll_terminal_requests("),
"loader delegates polling",
failures
)
_expect_true(
drain_source.contains("_m2_mesh_resource_finalizer.finalize_next_resource("),
"loader delegates terminal finalization",
failures
)
for removed_token in [
"ResourceLoader.load_threaded_get_status",
"ResourceLoader.load_threaded_get(",
"_m2_mesh_resource_extractor.extract_first_mesh",
"_m2_mesh_resource_cache_state.store_mesh",
"_m2_prototype_cache_state.mark_model_missing",
]:
_expect_false(
drain_source.contains(removed_token),
"loader drain omits %s" % removed_token,
failures
)
_expect_false(
loader_source.contains("func _prepare_m2_mesh_for_runtime("),
"legacy loader preparation removed",
failures
)
for required_token in [
"ResourceLoader.load_threaded_get_status",
"ResourceLoader.load_threaded_get(",
"\"extract_first_mesh\"",
"\"store_mesh\"",
"\"mark_model_missing\"",
"\"load_static_model_data\"",
"\"finalize_mesh\"",
]:
_expect_true(
service_source.contains(required_token),
"service owns %s" % required_token,
failures
)
func _verify_bounded_timing(failures: Array[String]) -> float:
var dependencies := _dependencies()
var pipeline: RefCounted = PIPELINE_SCRIPT.new()
var prototype_cache: RefCounted = PROTOTYPE_CACHE_SCRIPT.new()
var started_microseconds := Time.get_ticks_usec()
for iteration in range(1000):
dependencies.service.call(
"poll_terminal_requests",
pipeline,
prototype_cache
)
var elapsed_milliseconds := float(
Time.get_ticks_usec() - started_microseconds
) / 1000.0
_expect_true(elapsed_milliseconds < 1000.0, "empty polling bounded", failures)
prototype_cache.call("clear_and_release")
return elapsed_milliseconds
func _dependencies() -> Dictionary:
var resource_loader := FakeResourceLoaderAdapter.new()
var extractor := FakeMeshResourceExtractor.new()
var runtime_finalizer := FakeRuntimeMeshFinalizer.new()
var raw_repository := FakeRawModelRepository.new()
return {
"resource_loader": resource_loader,
"extractor": extractor,
"runtime_finalizer": runtime_finalizer,
"raw_repository": raw_repository,
"service": FINALIZER_SCRIPT.new(
extractor,
runtime_finalizer,
raw_repository,
resource_loader
),
}
func _enqueue_terminal(
pipeline: RefCounted,
normalized_relative_path: String,
resource_path: String,
status: int
) -> void:
pipeline.call("remember_request", normalized_relative_path, resource_path)
pipeline.call("complete_request", normalized_relative_path, status)
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)
func _expect_same(
actual: Variant,
expected: Variant,
label: String,
failures: Array[String]
) -> void:
_expect_true(is_same(actual, expected), label, failures)
func _expect_equal(
actual: int,
expected: int,
label: String,
failures: Array[String]
) -> void:
if actual != expected:
failures.append("%s expected=%d actual=%d" % [label, expected, actual])
func _expect_string_equal(
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_string_array(
actual: Array[String],
expected: Array[String],
label: String,
failures: Array[String]
) -> void:
if actual != expected:
failures.append("%s expected=%s actual=%s" % [label, expected, actual])
@@ -0,0 +1 @@
uid://by0rlnw5g8hjr
+4 -1
View File
@@ -7,6 +7,7 @@ const REPOSITORY_PATH := "res://src/render/m2/m2_raw_model_repository.gd"
const NATIVE_OBSERVER_PATH := (
"res://src/render/m2/m2_native_animation_resource_observer.gd"
)
const MESH_FINALIZER_PATH := "res://src/render/m2/m2_mesh_resource_finalizer.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
@@ -60,6 +61,7 @@ func _verify_rejected_inputs(failures: Array[String]) -> void:
func _verify_ownership_boundaries(failures: Array[String]) -> void:
var repository_source := FileAccess.get_file_as_string(REPOSITORY_PATH)
var native_observer_source := FileAccess.get_file_as_string(NATIVE_OBSERVER_PATH)
var mesh_finalizer_source := FileAccess.get_file_as_string(MESH_FINALIZER_PATH)
var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
_expect_true(
loader_source.contains("M2_RAW_MODEL_REPOSITORY_SCRIPT.new()"),
@@ -67,7 +69,8 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
failures
)
_expect_equal(
loader_source.count("_m2_raw_model_repository.load_static_model_data("),
loader_source.count("_m2_raw_model_repository.load_static_model_data(")
+ mesh_finalizer_source.count("\"load_static_model_data\""),
2,
"two static callers delegate",
failures