refactor(M03): extract M2 prototype cache state

This commit is contained in:
2026-07-17 16:29:11 +04:00
parent 87bf067493
commit cd664eb4cb
19 changed files with 715 additions and 89 deletions
+62 -43
View File
@@ -66,6 +66,9 @@ const M2_RUNTIME_MESH_FINALIZER_SCRIPT := preload(
const M2_RAW_MODEL_REPOSITORY_SCRIPT := preload(
"res://src/render/m2/m2_raw_model_repository.gd"
)
const M2_PROTOTYPE_CACHE_STATE_SCRIPT := preload(
"res://src/render/m2/m2_prototype_cache_state.gd"
)
const M2_MESH_LOAD_PIPELINE_STATE_SCRIPT := preload(
"res://src/render/m2/m2_mesh_load_pipeline_state.gd"
)
@@ -263,6 +266,7 @@ var _m2_placement_grouper := M2_PLACEMENT_GROUPER_SCRIPT.new()
var _m2_build_batch_planner := M2_BUILD_BATCH_PLANNER_SCRIPT.new()
var _m2_runtime_mesh_finalizer := M2_RUNTIME_MESH_FINALIZER_SCRIPT.new()
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_load_pipeline_state := M2_MESH_LOAD_PIPELINE_STATE_SCRIPT.new()
@@ -309,10 +313,6 @@ var _wmo_render_build_queue := WMO_RENDER_BUILD_QUEUE_SCRIPT.new()
var _wmo_scene_resource_cache_state := WMO_SCENE_RESOURCE_CACHE_STATE_SCRIPT.new()
var _wmo_missing_cache: Dictionary = {}
var _wmo_placement_resolver := WMO_PLACEMENT_RESOLVER_SCRIPT.new()
var _m2_scene_cache: Dictionary = {}
var _m2_animated_scene_cache: Dictionary = {}
var _m2_static_animation_cache: Dictionary = {}
var _m2_missing_cache: Dictionary = {}
var _world_wmo_root: Node3D
var _wmo_placement_registry := WMO_PLACEMENT_REGISTRY_SCRIPT.new()
var _wmo_render_build_step_planner := WMO_RENDER_BUILD_STEP_PLANNER_SCRIPT.new()
@@ -481,12 +481,9 @@ func _render_operation_limits_for_frame() -> Dictionary:
## loader after all asynchronous work has completed. Map refreshes retain these
## caches; only scene shutdown destroys them.
func _release_runtime_caches_for_shutdown() -> void:
_free_detached_node_cache(_m2_scene_cache)
_free_detached_node_cache(_m2_animated_scene_cache)
_m2_prototype_cache_state.clear_and_release()
_free_detached_node_cache(_wmo_prototype_cache)
_m2_mesh_resource_cache_state.clear()
_m2_static_animation_cache.clear()
_m2_missing_cache.clear()
_wmo_render_resource_cache_state.clear_all()
_wmo_scene_resource_cache_state.clear_all()
_wmo_missing_cache.clear()
@@ -4219,7 +4216,7 @@ func _drain_m2_animation_loads() -> void:
var path := String(pending.get("path", ""))
if path.is_empty():
_m2_animation_load_requests.erase(normalized_rel)
_m2_static_animation_cache[normalized_rel] = true
_m2_prototype_cache_state.mark_animation_static(normalized_rel)
continue
var status := ResourceLoader.load_threaded_get_status(path)
if status != ResourceLoader.THREAD_LOAD_LOADED and status != ResourceLoader.THREAD_LOAD_FAILED:
@@ -4232,10 +4229,14 @@ func _drain_m2_animation_loads() -> void:
RENDER_BUDGET_SCHEDULER_SCRIPT.M2_ANIMATION_FINALIZE):
var pending: Dictionary = _m2_animation_finalize_queue.pop_front()
var normalized_rel := String(pending.get("normalized", ""))
if normalized_rel.is_empty() or _m2_animated_scene_cache.has(normalized_rel) or _m2_static_animation_cache.has(normalized_rel):
if (
normalized_rel.is_empty()
or _m2_prototype_cache_state.has_animated_prototype(normalized_rel)
or _m2_prototype_cache_state.is_animation_static(normalized_rel)
):
continue
if int(pending.get("status", ResourceLoader.THREAD_LOAD_FAILED)) != ResourceLoader.THREAD_LOAD_LOADED:
_m2_static_animation_cache[normalized_rel] = true
_m2_prototype_cache_state.mark_animation_static(normalized_rel)
continue
var path := String(pending.get("path", ""))
@@ -4246,12 +4247,15 @@ func _drain_m2_animation_loads() -> void:
_repair_m2_animated_materials(normalized_rel, node as Node3D)
var players := _find_animation_players_recursive(node)
if not players.is_empty():
_m2_animated_scene_cache[normalized_rel] = node as Node3D
_m2_prototype_cache_state.adopt_animated_prototype(
normalized_rel,
node as Node3D
)
if debug_streaming:
print("M2_ANIM_CACHE path=%s cache=%s players=%d" % [normalized_rel, path, players.size()])
continue
node.free()
_m2_static_animation_cache[normalized_rel] = true
_m2_prototype_cache_state.mark_animation_static(normalized_rel)
func _drain_m2_mesh_loads() -> void:
@@ -4260,7 +4264,7 @@ func _drain_m2_mesh_loads() -> void:
var path: String = String(pending.get("path", ""))
if path.is_empty():
_m2_mesh_load_pipeline_state.discard_request(normalized_rel)
_m2_missing_cache[normalized_rel] = true
_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:
@@ -4277,7 +4281,7 @@ func _drain_m2_mesh_loads() -> void:
):
continue
if int(pending.get("status", ResourceLoader.THREAD_LOAD_FAILED)) != ResourceLoader.THREAD_LOAD_LOADED:
_m2_missing_cache[normalized_rel] = true
_m2_prototype_cache_state.mark_model_missing(normalized_rel)
continue
var path := String(pending.get("path", ""))
@@ -4289,7 +4293,7 @@ func _drain_m2_mesh_loads() -> void:
_prepare_m2_mesh_for_runtime(normalized_rel, mesh)
)
else:
_m2_missing_cache[normalized_rel] = true
_m2_prototype_cache_state.mark_model_missing(normalized_rel)
func _process_m2_build_jobs() -> void:
@@ -4357,7 +4361,7 @@ func _process_m2_build_jobs() -> void:
else:
var mesh := _get_m2_mesh_or_request(rel_path)
if mesh == null:
if not _m2_missing_cache.has(normalized_rel):
if not _m2_prototype_cache_state.is_model_missing(normalized_rel):
_m2_build_queue.pop_front()
_m2_build_queue.append(key)
_render_budget_scheduler.try_consume_permit(RENDER_BUDGET_SCHEDULER_SCRIPT.M2_BUILD)
@@ -4624,7 +4628,7 @@ func _get_m2_mesh_or_request(rel_path: String) -> Mesh:
return null
if _m2_mesh_resource_cache_state.has_mesh(normalized_rel):
return _m2_mesh_resource_cache_state.find_mesh(normalized_rel)
if _m2_missing_cache.has(normalized_rel):
if _m2_prototype_cache_state.is_model_missing(normalized_rel):
return null
_request_m2_mesh_load(normalized_rel)
return null
@@ -4649,7 +4653,7 @@ func _request_m2_mesh_load(normalized_rel: String) -> void:
return
break
_m2_missing_cache[normalized_rel] = true
_m2_prototype_cache_state.mark_model_missing(normalized_rel)
func _prepare_m2_mesh_for_runtime(normalized_rel: String, mesh: Mesh) -> Mesh:
@@ -4840,11 +4844,11 @@ func _get_or_load_m2_prototype(rel_path: String) -> Node3D:
var normalized_rel := _normalize_m2_rel_path(rel_path)
if normalized_rel.is_empty():
return null
if _m2_scene_cache.has(normalized_rel):
var cached: Node3D = _m2_scene_cache[normalized_rel]
var cached := _m2_prototype_cache_state.find_static_prototype(normalized_rel)
if cached != null:
_prepare_m2_node_for_runtime(normalized_rel, cached)
return cached
if _m2_missing_cache.has(normalized_rel):
if _m2_prototype_cache_state.is_model_missing(normalized_rel):
return null
# Try pre-baked cache (.tscn first, then legacy .glb)
@@ -4858,8 +4862,10 @@ func _get_or_load_m2_prototype(rel_path: String) -> Node3D:
var node = (resource as PackedScene).instantiate()
if node is Node3D:
_prepare_m2_node_for_runtime(normalized_rel, node as Node3D)
_m2_scene_cache[normalized_rel] = node as Node3D
return node as Node3D
return _m2_prototype_cache_state.adopt_static_prototype(
normalized_rel,
node as Node3D
)
break
# Fall back to the raw native M2 repository.
@@ -4868,25 +4874,30 @@ func _get_or_load_m2_prototype(rel_path: String) -> Node3D:
normalized_rel
)
if data.is_empty():
_m2_missing_cache[normalized_rel] = true
_m2_prototype_cache_state.mark_model_missing(normalized_rel)
return null
var prototype: Node3D = M2_BUILDER_SCRIPT.build(data, extracted_dir)
if prototype == null:
_m2_missing_cache[normalized_rel] = true
_m2_prototype_cache_state.mark_model_missing(normalized_rel)
return null
_m2_scene_cache[normalized_rel] = prototype
return prototype
return _m2_prototype_cache_state.adopt_static_prototype(
normalized_rel,
prototype
)
func _get_or_load_m2_native_animated_prototype(rel_path: String) -> Node3D:
var normalized_rel := _normalize_m2_rel_path(rel_path)
if normalized_rel.is_empty() or not _is_m2_native_animation_candidate(normalized_rel):
return null
if _m2_animated_scene_cache.has(normalized_rel):
return _m2_animated_scene_cache[normalized_rel]
if _m2_static_animation_cache.has(normalized_rel):
var cached_animated := _m2_prototype_cache_state.find_animated_prototype(
normalized_rel
)
if cached_animated != null:
return cached_animated
if _m2_prototype_cache_state.is_animation_static(normalized_rel):
return null
var data := _m2_raw_model_repository.load_animated_model_data(
extracted_dir,
@@ -4894,15 +4905,18 @@ func _get_or_load_m2_native_animated_prototype(rel_path: String) -> Node3D:
)
var animated_surfaces: Array = data.get("animated_surfaces", [])
if data.is_empty() or animated_surfaces.is_empty():
_m2_static_animation_cache[normalized_rel] = true
_m2_prototype_cache_state.mark_animation_static(normalized_rel)
return null
var prototype: Node3D = M2_NATIVE_ANIMATED_BUILDER_SCRIPT.build(data, extracted_dir)
if prototype == null or prototype.get_child_count() <= 0:
_m2_static_animation_cache[normalized_rel] = true
_m2_prototype_cache_state.mark_animation_static(normalized_rel)
return null
_m2_animated_scene_cache[normalized_rel] = prototype
prototype = _m2_prototype_cache_state.adopt_animated_prototype(
normalized_rel,
prototype
)
if debug_streaming:
print("M2_NATIVE_ANIM_CACHE path=%s surfaces=%d bones=%d anim_id=%d seq=%d score=%d length=%.2f" % [
normalized_rel,
@@ -4924,9 +4938,12 @@ func _get_or_load_m2_animated_prototype(rel_path: String) -> Node3D:
var normalized_rel := _normalize_m2_rel_path(rel_path)
if normalized_rel.is_empty():
return null
if _m2_animated_scene_cache.has(normalized_rel):
return _m2_animated_scene_cache[normalized_rel]
if _m2_static_animation_cache.has(normalized_rel):
var cached_animated := _m2_prototype_cache_state.find_animated_prototype(
normalized_rel
)
if cached_animated != null:
return cached_animated
if _m2_prototype_cache_state.is_animation_static(normalized_rel):
return null
if _m2_animation_load_requests.has(normalized_rel):
return null
@@ -4940,7 +4957,7 @@ func _request_m2_animation_load(normalized_rel: String) -> void:
return
var cache_res_path := _find_m2_animated_glb_cache_path(normalized_rel)
if cache_res_path.is_empty():
_m2_static_animation_cache[normalized_rel] = true
_m2_prototype_cache_state.mark_animation_static(normalized_rel)
return
var err := ResourceLoader.load_threaded_request(
cache_res_path,
@@ -4953,7 +4970,7 @@ func _request_m2_animation_load(normalized_rel: String) -> void:
"path": cache_res_path,
}
else:
_m2_static_animation_cache[normalized_rel] = true
_m2_prototype_cache_state.mark_animation_static(normalized_rel)
func _find_m2_animated_glb_cache_path(normalized_rel: String) -> String:
@@ -5018,8 +5035,8 @@ func _repair_m2_animated_materials(normalized_rel: String, animated_root: Node3D
func _get_or_load_m2_material_prototype(normalized_rel: String) -> Node3D:
if normalized_rel.is_empty():
return null
if _m2_scene_cache.has(normalized_rel):
var cached: Node3D = _m2_scene_cache[normalized_rel]
var cached := _m2_prototype_cache_state.find_static_prototype(normalized_rel)
if cached != null:
_prepare_m2_node_for_runtime(normalized_rel, cached)
return cached
@@ -5031,8 +5048,10 @@ func _get_or_load_m2_material_prototype(normalized_rel: String) -> Node3D:
var node = (resource as PackedScene).instantiate()
if node is Node3D:
_prepare_m2_node_for_runtime(normalized_rel, node as Node3D)
_m2_scene_cache[normalized_rel] = node as Node3D
return node as Node3D
return _m2_prototype_cache_state.adopt_static_prototype(
normalized_rel,
node as Node3D
)
return null