render: extract M2 animation resource finalizer
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
class_name M2AnimationResourceFinalizer
|
||||
extends RefCounted
|
||||
|
||||
## Polls cached animated M2 ResourceLoader requests and finalizes terminal scenes.
|
||||
## The caller owns permits, material-source lookup and SceneTree materialization.
|
||||
|
||||
const M2_ANIMATED_SCENE_FINALIZER_SCRIPT := preload(
|
||||
"res://src/render/m2/m2_animated_scene_finalizer.gd"
|
||||
)
|
||||
|
||||
var _animated_scene_finalizer: RefCounted
|
||||
var _resource_loader_adapter: Object
|
||||
|
||||
|
||||
func _init(
|
||||
animated_scene_finalizer: RefCounted = null,
|
||||
resource_loader_adapter: Object = null
|
||||
) -> void:
|
||||
_animated_scene_finalizer = animated_scene_finalizer
|
||||
if _animated_scene_finalizer == null:
|
||||
_animated_scene_finalizer = M2_ANIMATED_SCENE_FINALIZER_SCRIPT.new()
|
||||
_resource_loader_adapter = resource_loader_adapter
|
||||
|
||||
|
||||
## Moves terminal threaded requests into the pipeline finalize FIFO.
|
||||
## Empty resource paths retain the historical immediate static-only 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_animation_static",
|
||||
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 one terminal record and prepares a detached animated scene candidate.
|
||||
## An empty result means the record was skipped or resolved as static-only.
|
||||
func prepare_next_candidate(
|
||||
load_pipeline_state: RefCounted,
|
||||
prototype_cache_state: RefCounted
|
||||
) -> Dictionary:
|
||||
if load_pipeline_state == null or prototype_cache_state == null:
|
||||
return {}
|
||||
var terminal_record: Dictionary = load_pipeline_state.call(
|
||||
"pop_finalize_record"
|
||||
)
|
||||
if terminal_record.is_empty():
|
||||
return {}
|
||||
var normalized_relative_path := String(
|
||||
terminal_record.get("normalized", "")
|
||||
)
|
||||
if (
|
||||
normalized_relative_path.is_empty()
|
||||
or bool(prototype_cache_state.call(
|
||||
"has_animated_prototype",
|
||||
normalized_relative_path
|
||||
))
|
||||
or bool(prototype_cache_state.call(
|
||||
"is_animation_static",
|
||||
normalized_relative_path
|
||||
))
|
||||
):
|
||||
return {}
|
||||
if int(terminal_record.get(
|
||||
"status",
|
||||
ResourceLoader.THREAD_LOAD_FAILED
|
||||
)) != ResourceLoader.THREAD_LOAD_LOADED:
|
||||
prototype_cache_state.call(
|
||||
"mark_animation_static",
|
||||
normalized_relative_path
|
||||
)
|
||||
return {}
|
||||
|
||||
var resource_path := String(terminal_record.get("path", ""))
|
||||
var loaded_resource := load_threaded_get(resource_path)
|
||||
var candidate := _animated_scene_finalizer.call(
|
||||
"instantiate_candidate",
|
||||
loaded_resource
|
||||
) as Node3D
|
||||
if candidate == null:
|
||||
prototype_cache_state.call(
|
||||
"mark_animation_static",
|
||||
normalized_relative_path
|
||||
)
|
||||
return {}
|
||||
return {
|
||||
"normalized": normalized_relative_path,
|
||||
"path": resource_path,
|
||||
"candidate": candidate,
|
||||
}
|
||||
|
||||
|
||||
## Repairs, validates and adopts one prepared candidate. The preparation must be
|
||||
## completed synchronously so its detached candidate always receives an owner.
|
||||
func finalize_prepared_candidate(
|
||||
preparation: Dictionary,
|
||||
material_source_root: Node3D,
|
||||
prototype_cache_state: RefCounted,
|
||||
debug_logging_enabled: bool = false
|
||||
) -> Node3D:
|
||||
if prototype_cache_state == null:
|
||||
return null
|
||||
var normalized_relative_path := String(preparation.get("normalized", ""))
|
||||
var candidate := preparation.get("candidate", null) as Node3D
|
||||
if normalized_relative_path.is_empty() or candidate == null:
|
||||
return null
|
||||
_animated_scene_finalizer.call(
|
||||
"repair_materials",
|
||||
candidate,
|
||||
material_source_root
|
||||
)
|
||||
var finalization: Dictionary = _animated_scene_finalizer.call(
|
||||
"finalize_candidate",
|
||||
candidate
|
||||
)
|
||||
var prototype := finalization.get("prototype", null) as Node3D
|
||||
if prototype == null:
|
||||
prototype_cache_state.call(
|
||||
"mark_animation_static",
|
||||
normalized_relative_path
|
||||
)
|
||||
return null
|
||||
var canonical_prototype := prototype_cache_state.call(
|
||||
"adopt_animated_prototype",
|
||||
normalized_relative_path,
|
||||
prototype
|
||||
) as Node3D
|
||||
if debug_logging_enabled:
|
||||
print("M2_ANIM_CACHE path=%s cache=%s players=%d" % [
|
||||
normalized_relative_path,
|
||||
String(preparation.get("path", "")),
|
||||
int(finalization.get("animation_player_count", 0)),
|
||||
])
|
||||
return canonical_prototype
|
||||
|
||||
|
||||
## 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)
|
||||
Reference in New Issue
Block a user