render: extract M2 Mesh resource finalizer
This commit is contained in:
@@ -47,6 +47,7 @@ Paired run 2026-07-11 подтвердил крупный coordinate/placement g
|
|||||||
- `src/render/m2/m2_cached_animation_resource_observer.gd` - cached animated GLB eligibility, threaded request admission and snapshot production.
|
- `src/render/m2/m2_cached_animation_resource_observer.gd` - cached animated GLB eligibility, threaded request admission and snapshot production.
|
||||||
- `src/render/m2/m2_animation_load_pipeline_state.gd` - animated M2 threaded-load request records and completion-order finalize FIFO without I/O or Node ownership.
|
- `src/render/m2/m2_animation_load_pipeline_state.gd` - animated M2 threaded-load request records and completion-order finalize FIFO without I/O or Node ownership.
|
||||||
- `src/render/m2/m2_animation_resource_finalizer.gd` - cached animated M2 terminal status polling, Resource load, scene finalization and prototype/static-only outcome.
|
- `src/render/m2/m2_animation_resource_finalizer.gd` - cached animated M2 terminal status polling, Resource load, scene finalization and prototype/static-only outcome.
|
||||||
|
- `src/render/m2/m2_mesh_resource_finalizer.gd` - static M2 terminal status polling, Mesh extraction/preparation and cache/missing outcome.
|
||||||
- `src/render/m2/m2_mesh_load_pipeline_state.gd` - static M2 threaded-load request records, terminal statuses and completion-order finalize FIFO without I/O or Mesh ownership.
|
- `src/render/m2/m2_mesh_load_pipeline_state.gd` - static M2 threaded-load request records, terminal statuses and completion-order finalize FIFO without I/O or Mesh ownership.
|
||||||
- `src/render/m2/m2_mesh_resource_cache_state.gd` - normalized-path prepared static M2 Mesh references with final-shutdown lifetime.
|
- `src/render/m2/m2_mesh_resource_cache_state.gd` - normalized-path prepared static M2 Mesh references with final-shutdown lifetime.
|
||||||
- `src/render/m2/m2_mesh_resource_extractor.gd` - first-Mesh selection from direct/PackedScene/Node inputs with temporary instance cleanup.
|
- `src/render/m2/m2_mesh_resource_extractor.gd` - first-Mesh selection from direct/PackedScene/Node inputs with temporary instance cleanup.
|
||||||
@@ -1169,6 +1170,22 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
|||||||
- No request order, status rule, cache format, profile or visible behavior changed.
|
- No request order, status rule, cache format, profile or visible behavior changed.
|
||||||
Synthetic fixtures are not asset-backed animation/leak/p95/p99 evidence.
|
Synthetic fixtures are not asset-backed animation/leak/p95/p99 evidence.
|
||||||
|
|
||||||
|
## 2026-07-18 M2 Mesh Resource Finalizer Extraction
|
||||||
|
|
||||||
|
- `M2MeshResourceFinalizer` now owns static M2 terminal status polling,
|
||||||
|
completion-FIFO pops, terminal Resource retrieval, first-Mesh extraction,
|
||||||
|
stale/current runtime preparation and Mesh/missing cache adoption.
|
||||||
|
- Pending paths are still polled in insertion order. One
|
||||||
|
`M2_MESH_FINALIZE` permit still pops at most one terminal record; cached Mesh
|
||||||
|
outcomes skip terminal retrieval exactly as before.
|
||||||
|
- Current Meshes retain exact identity and skip raw reads. Stale Meshes still
|
||||||
|
request raw data only when `M2RuntimeMeshFinalizer` requires it, preserving
|
||||||
|
refresh-version, rebuild and original-Mesh fallback rules.
|
||||||
|
- `StreamingWorldLoader` retains request admission, scheduler permits,
|
||||||
|
composition, static materialization and shutdown drain ordering. Cache paths,
|
||||||
|
profiles and visible behavior are unchanged; synthetic fixtures are not
|
||||||
|
asset-backed visual/leak/p95/p99 evidence.
|
||||||
|
|
||||||
## 2026-07-18 M2 Native Animation Resource Observer Extraction
|
## 2026-07-18 M2 Native Animation Resource Observer Extraction
|
||||||
|
|
||||||
- `M2NativeAnimationResourceObserver` now owns the exact case-insensitive
|
- `M2NativeAnimationResourceObserver` now owns the exact case-insensitive
|
||||||
@@ -1244,12 +1261,14 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
|||||||
|
|
||||||
- `M2RuntimeMeshFinalizer` now owns material refresh version `2`, stale-Mesh
|
- `M2RuntimeMeshFinalizer` now owns material refresh version `2`, stale-Mesh
|
||||||
rebuild classification, M2Builder rebuild and original-Mesh fallback.
|
rebuild classification, M2Builder rebuild and original-Mesh fallback.
|
||||||
- Current Meshes still skip raw `.m2` loading. `M2RawModelRepository` now owns
|
- Current Meshes still skip raw `.m2` loading. `M2RawModelRepository` owns
|
||||||
FileAccess/ClassDB M2Loader I/O and supplies raw data through the loader only
|
FileAccess/ClassDB M2Loader I/O and supplies raw data through
|
||||||
when the finalizer reports a stale Mesh; both historical clear sites persist.
|
`M2MeshResourceFinalizer` only when the runtime finalizer reports a stale Mesh;
|
||||||
|
both historical clear sites persist.
|
||||||
- Billboard/UV-rotation predicates, rebuild extraction, metadata key and failure
|
- Billboard/UV-rotation predicates, rebuild extraction, metadata key and failure
|
||||||
fallback are unchanged. Cache adoption decisions, permits and MultiMesh
|
fallback are unchanged. Mesh resource finalization owns cache adoption;
|
||||||
materialization remain loader-owned; negative outcomes belong to prototype state.
|
permits and MultiMesh materialization remain loader-owned, while negative
|
||||||
|
outcomes belong to prototype state.
|
||||||
- Synthetic triangle rebuild/fallback timing is not asset-backed material,
|
- Synthetic triangle rebuild/fallback timing is not asset-backed material,
|
||||||
descriptor-pressure/leak or p95/p99 evidence.
|
descriptor-pressure/leak or p95/p99 evidence.
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
| M2 mesh load pipeline state | Implemented extraction | [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md) |
|
| M2 mesh load pipeline state | Implemented extraction | [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md) |
|
||||||
| M2 mesh resource cache state | Implemented extraction | [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md) |
|
| M2 mesh resource cache state | Implemented extraction | [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md) |
|
||||||
| M2 mesh resource extractor | Implemented extraction | [`m2-mesh-resource-extractor.md`](m2-mesh-resource-extractor.md) |
|
| M2 mesh resource extractor | Implemented extraction | [`m2-mesh-resource-extractor.md`](m2-mesh-resource-extractor.md) |
|
||||||
|
| M2 mesh resource finalizer | Implemented extraction | [`m2-mesh-resource-finalizer.md`](m2-mesh-resource-finalizer.md) |
|
||||||
| M2 runtime mesh finalizer | Implemented extraction | [`m2-runtime-mesh-finalizer.md`](m2-runtime-mesh-finalizer.md) |
|
| M2 runtime mesh finalizer | Implemented extraction | [`m2-runtime-mesh-finalizer.md`](m2-runtime-mesh-finalizer.md) |
|
||||||
| M2 raw model repository | Implemented extraction | [`m2-raw-model-repository.md`](m2-raw-model-repository.md) |
|
| M2 raw model repository | Implemented extraction | [`m2-raw-model-repository.md`](m2-raw-model-repository.md) |
|
||||||
| M2 prototype cache state | Implemented extraction | [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md) |
|
| M2 prototype cache state | Implemented extraction | [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md) |
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ Side effects are limited to collection mutation and retaining String/integer val
|
|||||||
flowchart TD
|
flowchart TD
|
||||||
Start[Successful ResourceLoader request] --> Remember[remember request]
|
Start[Successful ResourceLoader request] --> Remember[remember request]
|
||||||
Remember --> Snapshot[request records snapshot]
|
Remember --> Snapshot[request records snapshot]
|
||||||
Snapshot --> Poll[Loader polls status]
|
Snapshot --> Poll[Mesh resource finalizer polls status]
|
||||||
Poll --> Active{In progress?}
|
Poll --> Active{In progress?}
|
||||||
Active -->|yes| Snapshot
|
Active -->|yes| Snapshot
|
||||||
Active -->|no| Complete[complete request with terminal status]
|
Active -->|no| Complete[complete request with terminal status]
|
||||||
@@ -86,7 +86,7 @@ flowchart TD
|
|||||||
FIFO --> Permit{Loader permit available?}
|
FIFO --> Permit{Loader permit available?}
|
||||||
Permit -->|no| FIFO
|
Permit -->|no| FIFO
|
||||||
Permit -->|yes| Pop[Pop oldest terminal record]
|
Permit -->|yes| Pop[Pop oldest terminal record]
|
||||||
Pop --> Finalize[Loader gets Resource and delegates first-Mesh extraction]
|
Pop --> Finalize[Mesh resource finalizer gets Resource and extracts first Mesh]
|
||||||
Finalize --> Prepare[M2RuntimeMeshFinalizer prepares Mesh]
|
Finalize --> Prepare[M2RuntimeMeshFinalizer prepares Mesh]
|
||||||
Prepare --> Adopt[Loader adopts Mesh or marks prototype outcome state]
|
Prepare --> Adopt[Loader adopts Mesh or marks prototype outcome state]
|
||||||
```
|
```
|
||||||
@@ -113,6 +113,7 @@ again only if loader cache/missing rules permit it.
|
|||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant Loader as StreamingWorldLoader
|
participant Loader as StreamingWorldLoader
|
||||||
|
participant Finalizer as M2MeshResourceFinalizer
|
||||||
participant Resource as ResourceLoader
|
participant Resource as ResourceLoader
|
||||||
participant State as M2MeshLoadPipelineState
|
participant State as M2MeshLoadPipelineState
|
||||||
participant Budget as RenderBudgetScheduler
|
participant Budget as RenderBudgetScheduler
|
||||||
@@ -120,14 +121,16 @@ sequenceDiagram
|
|||||||
Resource-->>Loader: OK or ERR_BUSY
|
Resource-->>Loader: OK or ERR_BUSY
|
||||||
Loader->>State: remember_request(normalized, cache path)
|
Loader->>State: remember_request(normalized, cache path)
|
||||||
loop frames
|
loop frames
|
||||||
Loader->>State: request_records_snapshot()
|
Loader->>Finalizer: poll_terminal_requests(state, prototype cache)
|
||||||
Loader->>Resource: load_threaded_get_status(path)
|
Finalizer->>State: request_records_snapshot()
|
||||||
|
Finalizer->>Resource: load_threaded_get_status(path)
|
||||||
end
|
end
|
||||||
Loader->>State: complete_request(normalized, terminal status)
|
Finalizer->>State: complete_request(normalized, terminal status)
|
||||||
Loader->>Budget: try_consume_permit(M2_MESH_FINALIZE)
|
Loader->>Budget: try_consume_permit(M2_MESH_FINALIZE)
|
||||||
Loader->>State: pop_finalize_record()
|
Loader->>Finalizer: finalize_next_resource(state, caches, directory)
|
||||||
Loader->>Resource: load_threaded_get(path)
|
Finalizer->>State: pop_finalize_record()
|
||||||
Loader->>Loader: extract/refresh/adopt Mesh or mark missing
|
Finalizer->>Resource: load_threaded_get(path)
|
||||||
|
Finalizer->>Finalizer: extract/refresh/adopt Mesh or mark missing
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dependency diagram
|
## Dependency diagram
|
||||||
@@ -135,7 +138,9 @@ sequenceDiagram
|
|||||||
```mermaid
|
```mermaid
|
||||||
flowchart TB
|
flowchart TB
|
||||||
Loader[StreamingWorldLoader] --> State[M2MeshLoadPipelineState]
|
Loader[StreamingWorldLoader] --> State[M2MeshLoadPipelineState]
|
||||||
Loader --> Resource[ResourceLoader]
|
Loader --> MeshFinalizer[M2MeshResourceFinalizer]
|
||||||
|
MeshFinalizer --> State
|
||||||
|
MeshFinalizer --> Resource[ResourceLoader]
|
||||||
Loader --> Budget[RenderBudgetScheduler]
|
Loader --> Budget[RenderBudgetScheduler]
|
||||||
Loader --> MeshCache[M2 Mesh resource and prototype outcome cache states]
|
Loader --> MeshCache[M2 Mesh resource and prototype outcome cache states]
|
||||||
Loader --> Finalizer[M2RuntimeMeshFinalizer]
|
Loader --> Finalizer[M2RuntimeMeshFinalizer]
|
||||||
@@ -148,7 +153,9 @@ flowchart TB
|
|||||||
|
|
||||||
- Main thread serializes all state mutation and snapshots.
|
- Main thread serializes all state mutation and snapshots.
|
||||||
- State owns only request/finalize Dictionaries containing Strings and status integers.
|
- State owns only request/finalize Dictionaries containing Strings and status integers.
|
||||||
- Loader owns ResourceLoader request lifetime and drains active paths before shutdown clear.
|
- `M2MeshResourceFinalizer` owns terminal status polling, terminal Resource
|
||||||
|
retrieval, first-Mesh extraction, runtime preparation and cache/missing adoption.
|
||||||
|
- Loader owns request admission, scheduler permits and shutdown drain ordering.
|
||||||
- `M2MeshResourceCacheState` owns prepared static Mesh references and
|
- `M2MeshResourceCacheState` owns prepared static Mesh references and
|
||||||
`M2RuntimeMeshFinalizer` owns refresh/rebuild/fallback. The loader owns shared
|
`M2RuntimeMeshFinalizer` owns refresh/rebuild/fallback. The loader owns shared
|
||||||
adoption decisions, raw I/O and remaining engine resources; the prototype
|
adoption decisions, raw I/O and remaining engine resources; the prototype
|
||||||
@@ -161,9 +168,9 @@ flowchart TB
|
|||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| Empty/duplicate request | State guard | Reject unchanged | Contract verifier | Correct caller/request later |
|
| Empty/duplicate request | State guard | Reject unchanged | Contract verifier | Correct caller/request later |
|
||||||
| Request start failure | Loader return code | No state insert; mark missing | Existing loader behavior | Cache/source correction |
|
| Request start failure | Loader return code | No state insert; mark missing | Existing loader behavior | Cache/source correction |
|
||||||
| Non-terminal status | Loader poll | Keep pending | Existing queue metric | Poll next frame |
|
| Non-terminal status | Finalizer poll | Keep pending | Existing queue metric | Poll next frame |
|
||||||
| Terminal load failure | Status in popped record | Loader marks missing | Existing missing behavior | World/cache reload |
|
| Terminal load failure | Status in popped record | Finalizer marks missing | Existing missing behavior | World/cache reload |
|
||||||
| Empty defensive path | Loader before poll | Discard and mark missing | Source regression | Correct request producer |
|
| Empty defensive path | Finalizer before poll | Discard and mark missing | Source regression | Correct request producer |
|
||||||
| Shutdown | Loader drains pending Resource paths | Clear state | Shutdown verifier | New loader starts empty |
|
| Shutdown | Loader drains pending Resource paths | Clear state | Shutdown verifier | New loader starts empty |
|
||||||
|
|
||||||
## Configuration and capabilities
|
## Configuration and capabilities
|
||||||
@@ -207,7 +214,7 @@ rebuild policy are unchanged; no migration or rebake is required.
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Static M2 request/finalize state | Implemented extraction | Contract/source/timing verifier | Asset-backed long traversal pending |
|
| Static M2 request/finalize state | Implemented extraction | Contract/source/timing verifier | Asset-backed long traversal pending |
|
||||||
| Static request admission I/O | Implemented in observer | Observer/shutdown regressions | Asset-backed traversal pending |
|
| Static request admission I/O | Implemented in observer | Observer/shutdown regressions | Asset-backed traversal pending |
|
||||||
| Static request polling/finalize I/O | Existing loader-owned | Shutdown/material regressions | Finalize extraction optional |
|
| Static request polling/finalize I/O | Implemented extraction | Finalizer contract/source/timing verifier | Asset-backed traversal pending |
|
||||||
| Mesh cache | Implemented extraction | Mesh resource cache state verifier | Asset-backed memory/leak run pending |
|
| Mesh cache | Implemented extraction | Mesh resource cache state verifier | Asset-backed memory/leak run pending |
|
||||||
| First-Mesh extraction | Implemented extraction | Resource/order/lifetime verifier | Asset-backed corrupt-scene fixture pending |
|
| First-Mesh extraction | Implemented extraction | Resource/order/lifetime verifier | Asset-backed corrupt-scene fixture pending |
|
||||||
| Mesh preparation | Implemented extraction | Runtime finalizer transition/rebuild verifier | Asset-backed material comparison pending |
|
| Mesh preparation | Implemented extraction | Runtime finalizer transition/rebuild verifier | Asset-backed material comparison pending |
|
||||||
@@ -225,11 +232,12 @@ rebuild policy are unchanged; no migration or rebake is required.
|
|||||||
| Path | Responsibility |
|
| Path | Responsibility |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `src/render/m2/m2_mesh_load_pipeline_state.gd` | Pending records, terminal FIFO and metrics |
|
| `src/render/m2/m2_mesh_load_pipeline_state.gd` | Pending records, terminal FIFO and metrics |
|
||||||
|
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Terminal polling, Resource extraction, preparation and adoption |
|
||||||
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared static Mesh references and final clear |
|
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared static Mesh references and final clear |
|
||||||
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh selection and temporary PackedScene lifetime |
|
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh selection and temporary PackedScene lifetime |
|
||||||
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation |
|
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation |
|
||||||
| `src/render/m2/m2_prototype_cache_state.gd` | Missing-model outcome retention |
|
| `src/render/m2/m2_prototype_cache_state.gd` | Missing-model outcome retention |
|
||||||
| `src/scenes/streaming/streaming_world_loader.gd` | Cache path choice, I/O polling, permits and adoption decisions |
|
| `src/scenes/streaming/streaming_world_loader.gd` | Cache path choice, request admission, permits and composition |
|
||||||
| `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Downstream stale Mesh rebuild decision |
|
| `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Downstream stale Mesh rebuild decision |
|
||||||
| `src/tools/verify_m2_mesh_load_pipeline_state.gd` | Lifecycle/boundary/timing regression |
|
| `src/tools/verify_m2_mesh_load_pipeline_state.gd` | Lifecycle/boundary/timing regression |
|
||||||
|
|
||||||
|
|||||||
@@ -103,9 +103,9 @@ sequenceDiagram
|
|||||||
participant Pipeline as M2MeshLoadPipelineState
|
participant Pipeline as M2MeshLoadPipelineState
|
||||||
Loader->>Cache: find/has normalized path
|
Loader->>Cache: find/has normalized path
|
||||||
alt cache miss
|
alt cache miss
|
||||||
Loader->>Pipeline: request/poll/finalize record
|
Loader->>Pipeline: request record
|
||||||
Loader->>Loader: ResourceLoader get; delegate extract + prepare
|
Loader->>Finalizer: poll/finalize one permitted record
|
||||||
Loader->>Cache: store_mesh(path, prepared Mesh)
|
Finalizer->>Cache: store_mesh(path, prepared Mesh)
|
||||||
end
|
end
|
||||||
Cache-->>Loader: exact retained Mesh
|
Cache-->>Loader: exact retained Mesh
|
||||||
Loader->>Loader: materialize static M2 batch
|
Loader->>Loader: materialize static M2 batch
|
||||||
@@ -133,8 +133,9 @@ flowchart TB
|
|||||||
- Borrowed Mesh lookups do not transfer ownership or duplicate resources.
|
- Borrowed Mesh lookups do not transfer ownership or duplicate resources.
|
||||||
- `M2MeshResourceExtractor` owns first-Mesh selection and temporary PackedScene
|
- `M2MeshResourceExtractor` owns first-Mesh selection and temporary PackedScene
|
||||||
instances. `M2PrototypeCacheState` owns missing/prototype/animated state; the
|
instances. `M2PrototypeCacheState` owns missing/prototype/animated state; the
|
||||||
static materializer owns MultiMesh construction/attachment; the loader owns
|
static materializer owns MultiMesh construction/attachment;
|
||||||
resource adoption and build-job decisions.
|
`M2MeshResourceFinalizer` owns resource adoption, while the loader owns
|
||||||
|
build-job and scheduler-permit decisions.
|
||||||
- The loader drains asynchronous work before the final cache clear.
|
- The loader drains asynchronous work before the final cache clear.
|
||||||
|
|
||||||
## Errors, cancellation and recovery
|
## Errors, cancellation and recovery
|
||||||
@@ -186,7 +187,7 @@ the historical Mesh cache had no queue contribution or log site.
|
|||||||
| Capability | Status | Evidence | Gap/next step |
|
| Capability | Status | Evidence | Gap/next step |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Prepared static M2 Mesh cache | Implemented extraction | Contract/source/timing verifier | Asset-backed memory/leak run pending |
|
| Prepared static M2 Mesh cache | Implemented extraction | Contract/source/timing verifier | Asset-backed memory/leak run pending |
|
||||||
| M2 Mesh request lifecycle | Implemented extraction | Pipeline state verifier | Finalize polling remains loader-owned |
|
| M2 Mesh request lifecycle | Implemented extraction | Pipeline/finalizer verifiers | Asset-backed traversal pending |
|
||||||
| M2 Mesh extraction | Implemented extraction | Resource/order/lifetime verifier | Asset-backed corrupt-scene fixture pending |
|
| M2 Mesh extraction | Implemented extraction | Resource/order/lifetime verifier | Asset-backed corrupt-scene fixture pending |
|
||||||
| M2 Mesh preparation | Implemented extraction | Runtime finalizer transition/rebuild verifier | Asset-backed material comparison pending |
|
| M2 Mesh preparation | Implemented extraction | Runtime finalizer transition/rebuild verifier | Asset-backed material comparison pending |
|
||||||
|
|
||||||
@@ -203,8 +204,9 @@ the historical Mesh cache had no queue contribution or log site.
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh references and final clear |
|
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh references and final clear |
|
||||||
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh selection and temporary PackedScene lifetime |
|
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh selection and temporary PackedScene lifetime |
|
||||||
|
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Prepared Mesh producer and cache adoption |
|
||||||
| `src/render/m2/m2_prototype_cache_state.gd` | Prototype references and negative lookup outcomes |
|
| `src/render/m2/m2_prototype_cache_state.gd` | Prototype references and negative lookup outcomes |
|
||||||
| `src/scenes/streaming/streaming_world_loader.gd` | Normalization, raw/resource I/O and materialization |
|
| `src/scenes/streaming/streaming_world_loader.gd` | Normalization, request admission, permits and materialization |
|
||||||
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation |
|
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation |
|
||||||
| `src/render/m2/m2_mesh_load_pipeline_state.gd` | Pending request and terminal finalize records |
|
| `src/render/m2/m2_mesh_load_pipeline_state.gd` | Pending request and terminal finalize records |
|
||||||
| `src/tools/verify_m2_mesh_resource_cache_state.gd` | Cache ownership/lifetime/boundary/timing regression |
|
| `src/tools/verify_m2_mesh_resource_cache_state.gd` | Cache ownership/lifetime/boundary/timing regression |
|
||||||
|
|||||||
@@ -92,27 +92,27 @@ No state or Resource reference is retained between calls.
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant Loader as StreamingWorldLoader
|
participant Finalizer as M2MeshResourceFinalizer
|
||||||
participant Extractor as M2MeshResourceExtractor
|
participant Extractor as M2MeshResourceExtractor
|
||||||
participant Scene as PackedScene
|
participant Scene as PackedScene
|
||||||
Loader->>Extractor: extract_first_mesh(loaded Resource)
|
Finalizer->>Extractor: extract_first_mesh(loaded Resource)
|
||||||
alt direct Mesh
|
alt direct Mesh
|
||||||
Extractor-->>Loader: same Mesh reference
|
Extractor-->>Finalizer: same Mesh reference
|
||||||
else PackedScene
|
else PackedScene
|
||||||
Extractor->>Scene: instantiate()
|
Extractor->>Scene: instantiate()
|
||||||
Scene-->>Extractor: temporary root
|
Scene-->>Extractor: temporary root
|
||||||
Extractor->>Extractor: depth-first first-Mesh search
|
Extractor->>Extractor: depth-first first-Mesh search
|
||||||
Extractor->>Extractor: temporary_root.free()
|
Extractor->>Extractor: temporary_root.free()
|
||||||
Extractor-->>Loader: Mesh or null
|
Extractor-->>Finalizer: Mesh or null
|
||||||
end
|
end
|
||||||
Loader->>Loader: prepare and cache Mesh or mark missing
|
Finalizer->>Finalizer: prepare and cache Mesh or mark missing
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dependency diagram
|
## Dependency diagram
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TB
|
flowchart TB
|
||||||
Loader[StreamingWorldLoader] --> Extractor[M2MeshResourceExtractor]
|
Finalizer[M2MeshResourceFinalizer] --> Extractor[M2MeshResourceExtractor]
|
||||||
Extractor --> Types[Resource / PackedScene / Node / Mesh]
|
Extractor --> Types[Resource / PackedScene / Node / Mesh]
|
||||||
Loader --> Pipeline[M2MeshLoadPipelineState]
|
Loader --> Pipeline[M2MeshLoadPipelineState]
|
||||||
Loader --> Cache[M2MeshResourceCacheState]
|
Loader --> Cache[M2MeshResourceCacheState]
|
||||||
@@ -168,8 +168,8 @@ normalized M2 path. Its verifier measures traversal time and temporary Node coun
|
|||||||
|
|
||||||
## Extension points
|
## Extension points
|
||||||
|
|
||||||
- `M2RuntimeMeshFinalizer` consumes the Mesh returned here and the loader stores
|
- `M2MeshResourceFinalizer` passes the Mesh to `M2RuntimeMeshFinalizer` and
|
||||||
its result in `M2MeshResourceCacheState`.
|
stores its result in `M2MeshResourceCacheState`.
|
||||||
- Broader generic scene traversal is intentionally excluded until another real
|
- Broader generic scene traversal is intentionally excluded until another real
|
||||||
consumer requires the same exact contract.
|
consumer requires the same exact contract.
|
||||||
|
|
||||||
@@ -194,8 +194,9 @@ normalized M2 path. Its verifier measures traversal time and temporary Node coun
|
|||||||
| Path | Responsibility |
|
| Path | Responsibility |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh traversal and temporary PackedScene lifetime |
|
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh traversal and temporary PackedScene lifetime |
|
||||||
|
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Terminal Resource consumer and extraction caller |
|
||||||
| `src/render/m2/m2_prototype_cache_state.gd` | Prototype and missing/static-only lookup state |
|
| `src/render/m2/m2_prototype_cache_state.gd` | Prototype and missing/static-only lookup state |
|
||||||
| `src/scenes/streaming/streaming_world_loader.gd` | ResourceLoader/raw I/O, cache decisions and materialization |
|
| `src/scenes/streaming/streaming_world_loader.gd` | Request admission, permits and materialization |
|
||||||
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation |
|
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation |
|
||||||
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh retention |
|
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh retention |
|
||||||
| `src/tools/verify_m2_mesh_resource_extractor.gd` | Resource/order/lifetime/boundary/timing regression |
|
| `src/tools/verify_m2_mesh_resource_extractor.gd` | Resource/order/lifetime/boundary/timing regression |
|
||||||
|
|||||||
@@ -0,0 +1,251 @@
|
|||||||
|
# M2 Mesh Resource Finalizer
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|---|---|
|
||||||
|
| Status | Implemented extraction |
|
||||||
|
| Target/work package | M03 / `M03-RND-M2-MESH-RESOURCE-FINALIZER-001` |
|
||||||
|
| Owners | Static M2 terminal Resource I/O, Mesh preparation and cache outcome |
|
||||||
|
| Last verified | Worktree `work/sindo-main-codex/m03-m2-mesh-resource-finalizer`, 2026-07-18 |
|
||||||
|
| Profiles/capabilities | Existing cached/static MultiMesh M2 path |
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Drain static M2 ResourceLoader work and publish a prepared Mesh or missing-model
|
||||||
|
outcome without keeping terminal I/O and refresh logic in `StreamingWorldLoader`.
|
||||||
|
The same preparation method serves threaded and synchronous Mesh consumers.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Select cache candidates or admit threaded ResourceLoader requests.
|
||||||
|
- Own scheduler permits, prototype Nodes, MultiMeshes or SceneTree roots.
|
||||||
|
- Change first-Mesh traversal, refresh version, rebuild rules or raw parser.
|
||||||
|
- Merge static and animated terminal services through callbacks/frameworks.
|
||||||
|
- Change cache formats, profiles or visible output.
|
||||||
|
|
||||||
|
## Context and boundaries
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
Observer[M2StaticBuildResourceObserver] --> Pipeline[M2MeshLoadPipelineState]
|
||||||
|
Loader[StreamingWorldLoader] --> Finalizer[M2MeshResourceFinalizer]
|
||||||
|
Pipeline --> Finalizer
|
||||||
|
ResourceLoader --> Finalizer
|
||||||
|
Finalizer --> Extractor[M2MeshResourceExtractor]
|
||||||
|
Finalizer --> Runtime[M2RuntimeMeshFinalizer]
|
||||||
|
Finalizer --> Raw[M2RawModelRepository]
|
||||||
|
Finalizer --> MeshCache[M2MeshResourceCacheState]
|
||||||
|
Finalizer --> Prototype[M2PrototypeCacheState]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Public API
|
||||||
|
|
||||||
|
| Symbol | Kind | Purpose | Failure behavior |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `poll_terminal_requests(pipeline, prototype_cache)` | I/O command | Move LOADED/FAILED records into completion FIFO | Invalid composition returns zero |
|
||||||
|
| `finalize_next_resource(pipeline, mesh_cache, prototype_cache, extracted_dir)` | I/O command | Pop one record and publish Mesh/missing outcome | Empty/invalid composition returns false |
|
||||||
|
| `prepare_mesh_for_runtime(path, mesh, extracted_dir)` | I/O preparation | Preserve current Mesh or load raw data and refresh stale Mesh | Null returns null |
|
||||||
|
| `load_threaded_get_status(path)` | I/O adapter | Production status query with injectable test seam | Returns ResourceLoader status |
|
||||||
|
| `load_threaded_get(path)` | I/O adapter | Production terminal Resource retrieval with injectable test seam | May return null |
|
||||||
|
|
||||||
|
## Inputs and outputs
|
||||||
|
|
||||||
|
| Direction | Data | Producer | Consumer | Ownership/lifetime |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Input | Pending/finalize records | Mesh pipeline | Finalizer | Pipeline-owned copied Dictionaries |
|
||||||
|
| Input | Terminal Resource | ResourceLoader | Mesh extractor | Borrowed one operation |
|
||||||
|
| Input | Extracted/cached Mesh | Mesh extractor/loader consumer | Runtime preparation | Borrowed Resource reference |
|
||||||
|
| Input | Raw M2 Dictionary | Raw repository | Runtime finalizer | Call-local value |
|
||||||
|
| Output | Prepared Mesh | Runtime finalizer | Mesh cache/loader consumer | Resource reference |
|
||||||
|
| Output | Missing-model outcome | Finalizer | Prototype cache | Copied path; loader session |
|
||||||
|
| Output | Processed-record flag | Finalizer | Loader permit loop/tests | Boolean value |
|
||||||
|
|
||||||
|
## Data flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Poll[Poll pending records in insertion order] --> Path{Resource path empty?}
|
||||||
|
Path -->|yes| Discard[Discard and mark missing]
|
||||||
|
Path -->|no| Status{LOADED or FAILED?}
|
||||||
|
Status -->|no| Pending[Keep pending]
|
||||||
|
Status -->|yes| Complete[Append completion FIFO]
|
||||||
|
Complete --> Permit[Loader consumes M2_MESH_FINALIZE permit]
|
||||||
|
Permit --> Pop[Pop oldest terminal record]
|
||||||
|
Pop --> Cached{Empty path or Mesh cached?}
|
||||||
|
Cached -->|yes| Skip[Finish one permit operation]
|
||||||
|
Cached -->|no| Loaded{Status LOADED?}
|
||||||
|
Loaded -->|no| Missing[Mark model missing]
|
||||||
|
Loaded -->|yes| Get[Get terminal Resource]
|
||||||
|
Get --> Extract[Extract first Mesh]
|
||||||
|
Extract --> Found{Mesh found?}
|
||||||
|
Found -->|no| Missing
|
||||||
|
Found -->|yes| Current{Refresh version current?}
|
||||||
|
Current -->|yes| Store[Store exact Mesh]
|
||||||
|
Current -->|no| Raw[Read raw M2 data]
|
||||||
|
Raw --> Refresh[Finalize/rebuild/fallback Mesh]
|
||||||
|
Refresh --> Store
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lifecycle/state
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> Polling
|
||||||
|
Polling --> Pending: nonterminal status
|
||||||
|
Polling --> TerminalQueued: LOADED or FAILED
|
||||||
|
Polling --> Missing: empty Resource path
|
||||||
|
TerminalQueued --> Skipped: empty/cached record
|
||||||
|
TerminalQueued --> Missing: failed load or no Mesh
|
||||||
|
TerminalQueued --> Extracted: Mesh found
|
||||||
|
Extracted --> Current: refresh version current
|
||||||
|
Extracted --> Refreshing: stale Mesh
|
||||||
|
Refreshing --> Prepared: rebuild or fallback
|
||||||
|
Current --> Cached
|
||||||
|
Prepared --> Cached
|
||||||
|
Pending --> [*]
|
||||||
|
Skipped --> [*]
|
||||||
|
Missing --> [*]
|
||||||
|
Cached --> [*]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Main sequence
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant L as StreamingWorldLoader
|
||||||
|
participant F as M2MeshResourceFinalizer
|
||||||
|
participant P as M2MeshLoadPipelineState
|
||||||
|
participant R as ResourceLoader
|
||||||
|
participant E as M2MeshResourceExtractor
|
||||||
|
participant N as M2RuntimeMeshFinalizer
|
||||||
|
participant C as M2MeshResourceCacheState
|
||||||
|
L->>F: poll_terminal_requests(P, prototype cache)
|
||||||
|
F->>R: load_threaded_get_status(path)
|
||||||
|
F->>P: complete terminal request
|
||||||
|
L->>L: consume M2_MESH_FINALIZE permit
|
||||||
|
L->>F: finalize_next_resource(...)
|
||||||
|
F->>P: pop oldest record
|
||||||
|
F->>R: load_threaded_get(path)
|
||||||
|
F->>E: extract_first_mesh(Resource)
|
||||||
|
F->>N: requires_raw_data_for_refresh(Mesh)
|
||||||
|
opt stale Mesh
|
||||||
|
F->>F: read raw M2 data
|
||||||
|
F->>N: finalize_mesh(...)
|
||||||
|
end
|
||||||
|
F->>C: store prepared Mesh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependency diagram
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
Finalizer[M2MeshResourceFinalizer] --> ResourceLoader
|
||||||
|
Finalizer --> Pipeline[M2MeshLoadPipelineState]
|
||||||
|
Finalizer --> Extractor[M2MeshResourceExtractor]
|
||||||
|
Finalizer --> Runtime[M2RuntimeMeshFinalizer]
|
||||||
|
Finalizer --> Raw[M2RawModelRepository]
|
||||||
|
Finalizer --> MeshCache[M2MeshResourceCacheState]
|
||||||
|
Finalizer --> Prototype[M2PrototypeCacheState]
|
||||||
|
Loader[StreamingWorldLoader] --> Finalizer
|
||||||
|
Loader --> Scheduler[RenderBudgetScheduler]
|
||||||
|
Finalizer -. no dependency .-> Scheduler
|
||||||
|
Finalizer -. no dependency .-> MultiMesh
|
||||||
|
Finalizer -. no dependency .-> SceneTree
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ownership, threading and resources
|
||||||
|
|
||||||
|
- All operations run synchronously on the renderer main thread.
|
||||||
|
- Pipeline owns pending/completion records; one permit pops one terminal record.
|
||||||
|
- Extractor owns temporary PackedScene instance cleanup and returns a Mesh reference.
|
||||||
|
- Runtime finalizer owns stale/current/rebuild/fallback rules; raw repository owns
|
||||||
|
only call-local native I/O.
|
||||||
|
- Mesh cache retains prepared Mesh references until final shutdown; prototype
|
||||||
|
cache retains missing-model paths.
|
||||||
|
- Service retains dependency references but no pending record, Resource, Mesh or
|
||||||
|
raw Dictionary between calls. Loader retains permits and materialization.
|
||||||
|
|
||||||
|
## Errors, cancellation and recovery
|
||||||
|
|
||||||
|
| Failure/state | Behavior | Recovery |
|
||||||
|
|---|---|---|
|
||||||
|
| Missing composition dependency | Return zero/false; preparation keeps Mesh where possible | Correct composition |
|
||||||
|
| Empty Resource path | Discard request and mark missing | Repair cache and restart session |
|
||||||
|
| Nonterminal status | Keep pending | Poll next tick |
|
||||||
|
| FAILED status | Pop and mark missing | Repair cache and restart session |
|
||||||
|
| Existing cached Mesh | Pop and skip terminal Resource get | None |
|
||||||
|
| Null/unsupported/no-Mesh Resource | Mark missing | Repair imported cache |
|
||||||
|
| Raw read/rebuild unavailable | Runtime finalizer marks/reuses original Mesh | Existing fallback |
|
||||||
|
| Tile cancellation | Shared cache work continues | Loader queue remains authoritative |
|
||||||
|
| Shutdown | Loader drains ResourceLoader before pipeline/cache clear | New loader starts empty |
|
||||||
|
|
||||||
|
## Configuration and capabilities
|
||||||
|
|
||||||
|
The service consumes the existing extracted directory. The scheduler retains
|
||||||
|
`m2_mesh_finalize_ops_per_tick`; material refresh version remains `2`.
|
||||||
|
|
||||||
|
## Persistence, cache and migration
|
||||||
|
|
||||||
|
No cache path, schema or version changes are introduced. Existing `.tscn/.glb`
|
||||||
|
Resources and in-memory refresh metadata remain compatible; no rebake is required.
|
||||||
|
|
||||||
|
## Diagnostics and observability
|
||||||
|
|
||||||
|
The service adds no log. Existing pending-plus-finalize metrics and refresh
|
||||||
|
metadata remain unchanged. Normalized relative path remains the correlation key.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- Dedicated verifier covers status insertion order, empty-path discard, FIFO,
|
||||||
|
failed/cached/null outcomes, terminal get, exact Mesh identity, stale-only raw
|
||||||
|
read, exact finalizer arguments, cache adoption, source ownership and timing.
|
||||||
|
- Pipeline/cache/extractor/runtime-finalizer/raw-repository/prototype/shutdown,
|
||||||
|
facade, internal-access and baseline regressions protect adjacent behavior.
|
||||||
|
- Fidelity evidence is exact lifecycle/I/O extraction only; no private asset,
|
||||||
|
original-client visual, leak-pressure or p95/p99 claim is made.
|
||||||
|
|
||||||
|
## Extension points
|
||||||
|
|
||||||
|
Further extraction can move static prototype/material-source construction without
|
||||||
|
changing this terminal Resource contract. Animated finalization remains separate
|
||||||
|
because its candidate-before-material ordering and Node ownership differ.
|
||||||
|
|
||||||
|
## Capability status
|
||||||
|
|
||||||
|
| Capability | Status | Evidence | Gap/next step |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Static terminal polling/load | Implemented extraction | Status/FIFO/load/source verifier | Asset-backed traversal pending |
|
||||||
|
| Mesh extraction/preparation/cache outcome | Implemented extraction | Identity/raw/finalizer/store verifier | Private leak/p95/p99 pending |
|
||||||
|
| Static MultiMesh materialization | Separate implemented service | Materializer verifier | GPU/asset-backed evidence pending |
|
||||||
|
|
||||||
|
## Known gaps and risks
|
||||||
|
|
||||||
|
- Terminal Resource get, extraction and stale rebuild remain synchronous main-thread work.
|
||||||
|
- Prepared Mesh cache remains unbounded until final shutdown.
|
||||||
|
- Private asset traversal, descriptor pressure, leak and p95/p99 evidence is absent.
|
||||||
|
|
||||||
|
## Source map
|
||||||
|
|
||||||
|
| Path | Responsibility |
|
||||||
|
|---|---|
|
||||||
|
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Poll/load/extract/prepare/cache/missing service |
|
||||||
|
| `src/render/m2/m2_mesh_load_pipeline_state.gd` | Pending and completion FIFO ownership |
|
||||||
|
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh and temporary-instance ownership |
|
||||||
|
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback rules |
|
||||||
|
| `src/render/m2/m2_raw_model_repository.gd` | Optional native raw M2 boundary |
|
||||||
|
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh ownership |
|
||||||
|
| `src/render/m2/m2_prototype_cache_state.gd` | Missing-model outcome ownership |
|
||||||
|
| `src/scenes/streaming/streaming_world_loader.gd` | Permit loop and materialization composition |
|
||||||
|
| `src/tools/verify_m2_mesh_resource_finalizer.gd` | Polling/preparation/boundary/timing regression |
|
||||||
|
|
||||||
|
## Related decisions and references
|
||||||
|
|
||||||
|
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md)
|
||||||
|
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
|
||||||
|
- [`m2-mesh-resource-extractor.md`](m2-mesh-resource-extractor.md)
|
||||||
|
- [`m2-runtime-mesh-finalizer.md`](m2-runtime-mesh-finalizer.md)
|
||||||
|
- [`m2-raw-model-repository.md`](m2-raw-model-repository.md)
|
||||||
|
- [`world-renderer.md`](world-renderer.md)
|
||||||
|
- [`../../RENDER.md`](../../RENDER.md)
|
||||||
|
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||||
@@ -193,7 +193,8 @@ and native M2 formats are unchanged; no migration or rebake is introduced.
|
|||||||
- `diagnostic_snapshot` exposes four sorted path arrays without Node references.
|
- `diagnostic_snapshot` exposes four sorted path arrays without Node references.
|
||||||
- Cached eligibility rejection logging belongs to the cached observer; native
|
- Cached eligibility rejection logging belongs to the cached observer; native
|
||||||
success logging belongs to the native observer and cached terminal success
|
success logging belongs to the native observer and cached terminal success
|
||||||
logging belongs to the animation resource finalizer.
|
logging belongs to the animation resource finalizer. Static terminal missing
|
||||||
|
outcomes are produced by `M2MeshResourceFinalizer`.
|
||||||
- Existing renderer queue metrics remain unchanged because these tables never
|
- Existing renderer queue metrics remain unchanged because these tables never
|
||||||
contributed work counts.
|
contributed work counts.
|
||||||
- Normalized relative path remains the correlation key.
|
- Normalized relative path remains the correlation key.
|
||||||
@@ -240,6 +241,7 @@ this state without moving ResourceLoader or builder ownership into cache state.
|
|||||||
| `src/render/m2/m2_prototype_cache_state.gd` | Positive Node ownership, negative path state and shutdown release |
|
| `src/render/m2/m2_prototype_cache_state.gd` | Positive Node ownership, negative path state and shutdown release |
|
||||||
| `src/render/m2/m2_native_animation_resource_observer.gd` | Animated raw/build and positive/static-only transitions |
|
| `src/render/m2/m2_native_animation_resource_observer.gd` | Animated raw/build and positive/static-only transitions |
|
||||||
| `src/render/m2/m2_animation_resource_finalizer.gd` | Cached terminal positive/static-only transitions |
|
| `src/render/m2/m2_animation_resource_finalizer.gd` | Cached terminal positive/static-only transitions |
|
||||||
|
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Static terminal missing transitions |
|
||||||
| `src/scenes/streaming/streaming_world_loader.gd` | Normalization, observer order, remaining I/O/fallback adapters |
|
| `src/scenes/streaming/streaming_world_loader.gd` | Normalization, observer order, remaining I/O/fallback adapters |
|
||||||
| `src/tools/verify_m2_prototype_cache_state.gd` | Admission/identity/lifecycle/source/timing regression |
|
| `src/tools/verify_m2_prototype_cache_state.gd` | Admission/identity/lifecycle/source/timing regression |
|
||||||
| `src/tools/verify_render_runtime_cache_shutdown.gd` | Integrated final-shutdown release regression |
|
| `src/tools/verify_render_runtime_cache_shutdown.gd` | Integrated final-shutdown release regression |
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ static or animated Dictionary consumed by existing builders and classifiers.
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
Loader[StreamingWorldLoader] --> Repository[M2RawModelRepository]
|
MeshFinalizer[M2MeshResourceFinalizer] --> Repository[M2RawModelRepository]
|
||||||
|
Loader[StreamingWorldLoader] --> Repository
|
||||||
Observer[M2NativeAnimationResourceObserver] --> Repository
|
Observer[M2NativeAnimationResourceObserver] --> Repository
|
||||||
Repository --> File[Extracted M2 file]
|
Repository --> File[Extracted M2 file]
|
||||||
Repository --> Native[ClassDB M2Loader]
|
Repository --> Native[ClassDB M2Loader]
|
||||||
Native --> Raw[Raw Dictionary]
|
Native --> Raw[Raw Dictionary]
|
||||||
|
Raw --> MeshFinalizer
|
||||||
Raw --> Loader
|
Raw --> Loader
|
||||||
Raw --> Observer
|
Raw --> Observer
|
||||||
Loader --> Builder[Existing M2 builders/finalizer]
|
Loader --> Builder[Existing M2 builders/finalizer]
|
||||||
@@ -55,7 +57,7 @@ renderer policy and other application layers are forbidden.
|
|||||||
| Input | Extracted directory String | Loader configuration | Repository path resolution | Copied value | One call |
|
| Input | Extracted directory String | Loader configuration | Repository path resolution | Copied value | One call |
|
||||||
| Input | Already-normalized relative M2 path String | Loader normalization | Repository | Copied value | One call |
|
| Input | Already-normalized relative M2 path String | Loader normalization | Repository | Copied value | One call |
|
||||||
| Input | Extracted `.m2` bytes | Local legal extraction | Native M2Loader | File-owned | Native call |
|
| Input | Extracted `.m2` bytes | Local legal extraction | Native M2Loader | File-owned | Native call |
|
||||||
| Output | Static raw M2 Dictionary | Native `load_m2` | Loader/finalizer/M2Builder | Fresh native result | One caller operation |
|
| Output | Static raw M2 Dictionary | Native `load_m2` | Mesh resource finalizer/M2Builder | Fresh native result | One caller operation |
|
||||||
| Output | Animated raw M2 Dictionary | Native `load_m2_animated` | Native animation observer/builder | Fresh native result | One caller operation |
|
| Output | Animated raw M2 Dictionary | Native `load_m2_animated` | Native animation observer/builder | Fresh native result | One caller operation |
|
||||||
| Output | Empty Dictionary | Repository guards | Loader fallback and prototype outcome adapter | Fresh value | One failed call |
|
| Output | Empty Dictionary | Repository guards | Loader fallback and prototype outcome adapter | Fresh value | One failed call |
|
||||||
|
|
||||||
@@ -136,8 +138,9 @@ flowchart TB
|
|||||||
## Ownership, threading and resources
|
## Ownership, threading and resources
|
||||||
|
|
||||||
- The repository owns only call-local path, native instance and result values.
|
- The repository owns only call-local path, native instance and result values.
|
||||||
- The loader owns path normalization and fallback selection; the native observer
|
- The loader owns path normalization and fallback selection; the Mesh resource
|
||||||
owns the animated raw-read/build decision;
|
finalizer owns static refresh reads, while the native observer owns the
|
||||||
|
animated raw-read/build decision;
|
||||||
`M2PrototypeCacheState` owns prototype/negative adoption.
|
`M2PrototypeCacheState` owns prototype/negative adoption.
|
||||||
- Native `M2Loader` owns parsing behavior and returns a new Dictionary value.
|
- Native `M2Loader` owns parsing behavior and returns a new Dictionary value.
|
||||||
- Calls are synchronous on the caller's thread; current renderer callers use the
|
- Calls are synchronous on the caller's thread; current renderer callers use the
|
||||||
@@ -215,6 +218,7 @@ measured work packages rather than expansion of this repository.
|
|||||||
| Path | Responsibility |
|
| Path | Responsibility |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `src/render/m2/m2_raw_model_repository.gd` | Stateless native class/file/method boundary |
|
| `src/render/m2/m2_raw_model_repository.gd` | Stateless native class/file/method boundary |
|
||||||
|
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Static refresh repository consumer |
|
||||||
| `src/render/m2/m2_native_animation_resource_observer.gd` | Animated raw-data consumer and builder adapter |
|
| `src/render/m2/m2_native_animation_resource_observer.gd` | Animated raw-data consumer and builder adapter |
|
||||||
| `src/render/m2/m2_prototype_cache_state.gd` | Prototype and negative-result retention |
|
| `src/render/m2/m2_prototype_cache_state.gd` | Prototype and negative-result retention |
|
||||||
| `src/scenes/streaming/streaming_world_loader.gd` | Path normalization, fallback decisions and result consumers |
|
| `src/scenes/streaming/streaming_world_loader.gd` | Path normalization, fallback decisions and result consumers |
|
||||||
|
|||||||
@@ -27,9 +27,8 @@ UV-rotation cases and returning the historical original-Mesh fallback.
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
Extractor[M2MeshResourceExtractor] --> Loader[StreamingWorldLoader]
|
ResourceFinalizer[M2MeshResourceFinalizer] -->|is raw data required?| Finalizer[M2RuntimeMeshFinalizer]
|
||||||
Loader -->|is raw data required?| Finalizer[M2RuntimeMeshFinalizer]
|
ResourceFinalizer --> Raw[M2RawModelRepository]
|
||||||
Loader --> Raw[M2RawModelRepository]
|
|
||||||
Raw --> Finalizer
|
Raw --> Finalizer
|
||||||
Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier]
|
Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier]
|
||||||
Finalizer --> Builder[M2Builder]
|
Finalizer --> Builder[M2Builder]
|
||||||
@@ -54,8 +53,8 @@ Nodes outside temporary rebuild roots and other application layers are forbidden
|
|||||||
|
|
||||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| Input | Extracted Mesh and normalized M2 path | Loader/extractor adapter | Finalizer | Borrowed Mesh reference/String copy | One call |
|
| Input | Extracted Mesh and normalized M2 path | Mesh resource finalizer | Finalizer | Borrowed Mesh reference/String copy | One call |
|
||||||
| Input | Already-loaded raw M2 Dictionary | `M2RawModelRepository` through loader adapter | Classifier/M2Builder | Caller-owned value container | One call |
|
| Input | Already-loaded raw M2 Dictionary | `M2RawModelRepository` through resource finalizer | Classifier/M2Builder | Caller-owned value container | One call |
|
||||||
| Input | Extracted directory path | Loader configuration | M2Builder texture resolution | Copied String | One rebuild |
|
| Input | Extracted directory path | Loader configuration | M2Builder texture resolution | Copied String | One rebuild |
|
||||||
| Output | Original or rebuilt current Mesh | Finalizer | Cache/prototype adapter | Borrowed/new Resource reference | Cache may retain |
|
| Output | Original or rebuilt current Mesh | Finalizer | Cache/prototype adapter | Borrowed/new Resource reference | Cache may retain |
|
||||||
|
|
||||||
@@ -101,24 +100,24 @@ map/reset clear site calls `clear()`.
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant Loader as StreamingWorldLoader
|
participant ResourceFinalizer as M2MeshResourceFinalizer
|
||||||
participant Finalizer as M2RuntimeMeshFinalizer
|
participant Finalizer as M2RuntimeMeshFinalizer
|
||||||
participant Raw as M2Loader boundary
|
participant Raw as M2Loader boundary
|
||||||
participant Classifier as RebuildClassifier
|
participant Classifier as RebuildClassifier
|
||||||
participant Builder as M2Builder
|
participant Builder as M2Builder
|
||||||
Loader->>Finalizer: requires_raw_data_for_refresh(mesh)
|
ResourceFinalizer->>Finalizer: requires_raw_data_for_refresh(mesh)
|
||||||
alt current Mesh
|
alt current Mesh
|
||||||
Finalizer-->>Loader: false; reuse Mesh
|
Finalizer-->>ResourceFinalizer: false; reuse Mesh
|
||||||
else stale Mesh
|
else stale Mesh
|
||||||
Finalizer-->>Loader: true
|
Finalizer-->>ResourceFinalizer: true
|
||||||
Loader->>Raw: load static raw Dictionary
|
ResourceFinalizer->>Raw: load static raw Dictionary
|
||||||
Loader->>Finalizer: finalize_mesh(path, mesh, raw, extracted dir)
|
ResourceFinalizer->>Finalizer: finalize_mesh(path, mesh, raw, extracted dir)
|
||||||
Finalizer->>Classifier: needs_runtime_mesh_rebuild(path, raw)
|
Finalizer->>Classifier: needs_runtime_mesh_rebuild(path, raw)
|
||||||
opt rebuild required
|
opt rebuild required
|
||||||
Finalizer->>Builder: build(raw, extracted dir)
|
Finalizer->>Builder: build(raw, extracted dir)
|
||||||
Finalizer->>Finalizer: extract first Mesh; free prototype
|
Finalizer->>Finalizer: extract first Mesh; free prototype
|
||||||
end
|
end
|
||||||
Finalizer-->>Loader: rebuilt or marked fallback Mesh
|
Finalizer-->>ResourceFinalizer: rebuilt or marked fallback Mesh
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -126,12 +125,12 @@ sequenceDiagram
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TB
|
flowchart TB
|
||||||
Loader[StreamingWorldLoader] --> Finalizer[M2RuntimeMeshFinalizer]
|
ResourceFinalizer[M2MeshResourceFinalizer] --> Finalizer[M2RuntimeMeshFinalizer]
|
||||||
Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier]
|
Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier]
|
||||||
Finalizer --> Extractor[M2MeshResourceExtractor]
|
Finalizer --> Extractor[M2MeshResourceExtractor]
|
||||||
Finalizer --> Builder[M2Builder]
|
Finalizer --> Builder[M2Builder]
|
||||||
Loader --> Raw[M2RawModelRepository]
|
ResourceFinalizer --> Raw[M2RawModelRepository]
|
||||||
Loader --> Cache[M2MeshResourceCacheState]
|
ResourceFinalizer --> Cache[M2MeshResourceCacheState]
|
||||||
Finalizer -. no dependency .-> Raw
|
Finalizer -. no dependency .-> Raw
|
||||||
Finalizer -. no dependency .-> Cache
|
Finalizer -. no dependency .-> Cache
|
||||||
```
|
```
|
||||||
@@ -139,7 +138,8 @@ flowchart TB
|
|||||||
## Ownership, threading and resources
|
## Ownership, threading and resources
|
||||||
|
|
||||||
- Renderer main thread executes metadata changes and M2Builder work.
|
- Renderer main thread executes metadata changes and M2Builder work.
|
||||||
- `M2RawModelRepository` owns raw M2 file/native calls; loader supplies its value result.
|
- `M2RawModelRepository` owns raw M2 file/native calls; the Mesh resource
|
||||||
|
finalizer supplies its value result.
|
||||||
- Finalizer owns classifier memoization and temporary rebuild prototype lifetime.
|
- Finalizer owns classifier memoization and temporary rebuild prototype lifetime.
|
||||||
- M2Builder owns construction rules; extractor selects the first rebuilt Mesh.
|
- M2Builder owns construction rules; extractor selects the first rebuilt Mesh.
|
||||||
- Cache/prototype adapters decide where the returned Mesh reference is retained.
|
- Cache/prototype adapters decide where the returned Mesh reference is retained.
|
||||||
@@ -208,7 +208,8 @@ logs/queue metrics are unchanged. Normalized M2 path remains the correlation key
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh version, classification, rebuild and fallback |
|
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh version, classification, rebuild and fallback |
|
||||||
| `src/render/m2/m2_raw_model_repository.gd` | Raw-file/native I/O and Dictionary result |
|
| `src/render/m2/m2_raw_model_repository.gd` | Raw-file/native I/O and Dictionary result |
|
||||||
| `src/scenes/streaming/streaming_world_loader.gd` | Repository call and returned-Mesh adoption |
|
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Repository call and returned-Mesh adoption |
|
||||||
|
| `src/scenes/streaming/streaming_world_loader.gd` | Service composition, permits and materialization |
|
||||||
| `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Memoized billboard/UV-rotation predicate |
|
| `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Memoized billboard/UV-rotation predicate |
|
||||||
| `src/render/m2/m2_mesh_resource_extractor.gd` | First rebuilt-Mesh selection |
|
| `src/render/m2/m2_mesh_resource_extractor.gd` | First rebuilt-Mesh selection |
|
||||||
| `src/tools/verify_m2_runtime_mesh_finalizer.gd` | Transition/rebuild/boundary/timing regression |
|
| `src/tools/verify_m2_runtime_mesh_finalizer.gd` | Transition/rebuild/boundary/timing regression |
|
||||||
|
|||||||
@@ -134,7 +134,8 @@ flowchart TB
|
|||||||
- Cache/pipeline services retain their existing state and resource ownership.
|
- Cache/pipeline services retain their existing state and resource ownership.
|
||||||
- Snapshot borrows the exact cached Mesh; observer never frees it.
|
- Snapshot borrows the exact cached Mesh; observer never frees it.
|
||||||
- Resource existence/request and GLB inspection run on renderer main thread.
|
- Resource existence/request and GLB inspection run on renderer main thread.
|
||||||
- Finalization and Mesh preparation remain in the loader drain path.
|
- `M2MeshResourceFinalizer` performs terminal polling, Resource extraction,
|
||||||
|
Mesh preparation and cache/missing adoption from the loader permit loop.
|
||||||
|
|
||||||
## Errors, cancellation and recovery
|
## Errors, cancellation and recovery
|
||||||
|
|
||||||
@@ -181,7 +182,7 @@ a generic resource framework.
|
|||||||
| Capability | Status | Evidence | Gap |
|
| Capability | Status | Evidence | Gap |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Static build lookup/request production | Implemented extraction | Lifecycle/path/source/timing verifier | Asset-backed traversal pending |
|
| Static build lookup/request production | Implemented extraction | Lifecycle/path/source/timing verifier | Asset-backed traversal pending |
|
||||||
| Static finalize/preparation | Existing loader-owned | Pipeline/finalizer regressions | Later extraction |
|
| Static finalize/preparation | Implemented extraction | Mesh resource finalizer verifier | Asset-backed traversal pending |
|
||||||
| Animated observation | Existing loader-owned | Animation regressions | Separate producer pending |
|
| Animated observation | Existing loader-owned | Animation regressions | Separate producer pending |
|
||||||
|
|
||||||
## Known gaps and risks
|
## Known gaps and risks
|
||||||
@@ -197,7 +198,8 @@ a generic resource framework.
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `src/render/m2/m2_static_build_resource_observer.gd` | Static cache/request/missing observation |
|
| `src/render/m2/m2_static_build_resource_observer.gd` | Static cache/request/missing observation |
|
||||||
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed observation result |
|
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed observation result |
|
||||||
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, finalize drain and action execution |
|
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Terminal polling, preparation and adoption |
|
||||||
|
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, permits and action execution |
|
||||||
| `src/tools/verify_m2_static_build_resource_observer.gd` | Lifecycle/path/boundary/timing regression |
|
| `src/tools/verify_m2_static_build_resource_observer.gd` | Lifecycle/path/boundary/timing regression |
|
||||||
|
|
||||||
## Related decisions and references
|
## Related decisions and references
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ from externally reading/writing loader-private queue, task, cache and tile-state
|
|||||||
| `M2MeshLoadPipelineState` | Internal M2 async-state service | Owns static Mesh pending request records and terminal finalize FIFO | Renderer main thread; map/session | Empty/duplicate/unknown transitions rejected |
|
| `M2MeshLoadPipelineState` | Internal M2 async-state service | Owns static Mesh pending request records and terminal finalize FIFO | Renderer main thread; map/session | Empty/duplicate/unknown transitions rejected |
|
||||||
| `M2MeshResourceCacheState` | Internal M2 Resource cache | Owns prepared static Mesh references by normalized path | Renderer main thread; final shutdown | Empty path/null Mesh rejected; same path replaces |
|
| `M2MeshResourceCacheState` | Internal M2 Resource cache | Owns prepared static Mesh references by normalized path | Renderer main thread; final shutdown | Empty path/null Mesh rejected; same path replaces |
|
||||||
| `M2MeshResourceExtractor` | Internal M2 scene/resource service | Selects first Mesh from direct Resource, PackedScene or Node subtree | Renderer main thread; stateless except temporary instance | Invalid/no Mesh returns null; temporary PackedScene root freed |
|
| `M2MeshResourceExtractor` | Internal M2 scene/resource service | Selects first Mesh from direct Resource, PackedScene or Node subtree | Renderer main thread; stateless except temporary instance | Invalid/no Mesh returns null; temporary PackedScene root freed |
|
||||||
|
| `M2MeshResourceFinalizer` | Internal M2 terminal-I/O service | Polls static requests, extracts/prepares terminal Meshes and publishes cache/missing outcomes | Renderer main thread; stateless across calls | Invalid/failed Resources mark missing; one call pops at most one terminal record |
|
||||||
| `M2RuntimeMeshFinalizer` | Internal M2 preparation service | Owns refresh version, rebuild classification, M2Builder rebuild and fallback | Renderer main thread; decisions cached until reset | Null returns null; missing/failed rebuild marks and reuses original Mesh |
|
| `M2RuntimeMeshFinalizer` | Internal M2 preparation service | Owns refresh version, rebuild classification, M2Builder rebuild and fallback | Renderer main thread; decisions cached until reset | Null returns null; missing/failed rebuild marks and reuses original Mesh |
|
||||||
| `M2RawModelRepository` | Internal M2 native repository | Reads static/animated raw Dictionaries through exact M2Loader methods | Synchronous; stateless | Invalid/unavailable/non-Dictionary result returns empty Dictionary |
|
| `M2RawModelRepository` | Internal M2 native repository | Reads static/animated raw Dictionaries through exact M2Loader methods | Synchronous; stateless | Invalid/unavailable/non-Dictionary result returns empty Dictionary |
|
||||||
| `M2PrototypeCacheState` | Internal M2 prototype cache | Owns detached static/animated Nodes and missing/static-only outcomes | Renderer main thread; final shutdown | Invalid admission rejected; first valid prototype wins |
|
| `M2PrototypeCacheState` | Internal M2 prototype cache | Owns detached static/animated Nodes and missing/static-only outcomes | Renderer main thread; final shutdown | Invalid admission rejected; first valid prototype wins |
|
||||||
@@ -427,8 +428,9 @@ sequenceDiagram
|
|||||||
- `M2MeshLoadPipelineState` owns static M2 pending Resource paths, opaque
|
- `M2MeshLoadPipelineState` owns static M2 pending Resource paths, opaque
|
||||||
terminal statuses and completion-order finalize FIFO. The static observer owns
|
terminal statuses and completion-order finalize FIFO. The static observer owns
|
||||||
cache path selection, request admission and initial snapshot adoption. The
|
cache path selection, request admission and initial snapshot adoption. The
|
||||||
loader retains ResourceLoader polling/finalize, permits and terminal adoption;
|
Mesh resource finalizer owns ResourceLoader polling/finalize and terminal
|
||||||
prototype cache state owns shared missing outcomes.
|
adoption; loader retains permits and composition, while prototype cache state
|
||||||
|
owns shared missing outcomes.
|
||||||
- `M2AnimationLoadPipelineState` owns animated M2 pending Resource paths, opaque
|
- `M2AnimationLoadPipelineState` owns animated M2 pending Resource paths, opaque
|
||||||
terminal statuses and completion-order finalize FIFO. The cached animation
|
terminal statuses and completion-order finalize FIFO. The cached animation
|
||||||
observer owns allow/deny/path/GLB selection, request admission and initial
|
observer owns allow/deny/path/GLB selection, request admission and initial
|
||||||
@@ -440,11 +442,11 @@ sequenceDiagram
|
|||||||
belong to the sibling cache service and loader respectively.
|
belong to the sibling cache service and loader respectively.
|
||||||
- `M2MeshResourceExtractor` owns depth-first first-Mesh selection and temporary
|
- `M2MeshResourceExtractor` owns depth-first first-Mesh selection and temporary
|
||||||
PackedScene instance destruction. The static observer admits ResourceLoader
|
PackedScene instance destruction. The static observer admits ResourceLoader
|
||||||
requests and initial cache/missing adoption; the loader retains polling,
|
requests and initial cache/missing adoption; the Mesh resource finalizer owns
|
||||||
terminal adoption and materialization.
|
terminal extraction/adoption, while the loader retains materialization.
|
||||||
- `M2RuntimeMeshFinalizer` owns refresh version `2`, classifier lifetime,
|
- `M2RuntimeMeshFinalizer` owns refresh version `2`, classifier lifetime,
|
||||||
M2Builder rebuild and original-Mesh fallback. The loader loads raw data only
|
M2Builder rebuild and original-Mesh fallback. `M2MeshResourceFinalizer` loads
|
||||||
after the finalizer reports that a cached Mesh is stale.
|
raw data only after the runtime finalizer reports that a cached Mesh is stale.
|
||||||
- `M2RawModelRepository` owns FileAccess/ClassDB availability and the exact
|
- `M2RawModelRepository` owns FileAccess/ClassDB availability and the exact
|
||||||
`load_m2`/`load_m2_animated` calls. The loader retains normalization, fallback
|
`load_m2`/`load_m2_animated` calls. The loader retains normalization, fallback
|
||||||
order and every result consumer; `M2PrototypeCacheState` retains outcomes.
|
order and every result consumer; `M2PrototypeCacheState` retains outcomes.
|
||||||
@@ -596,10 +598,11 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
|||||||
| M2 build batch planner | Implemented extraction | Scene-free limit/count/cursor/source/timing contract | Spatial cells and asset-backed p95/p99 remain pending |
|
| M2 build batch planner | Implemented extraction | Scene-free limit/count/cursor/source/timing contract | Spatial cells and asset-backed p95/p99 remain pending |
|
||||||
| M2 build dispatch planner | Implemented extraction | Scene-free priority/action/transition/source/timing contract | Resource observation/orchestration and asset-backed traversal remain pending |
|
| M2 build dispatch planner | Implemented extraction | Scene-free priority/action/transition/source/timing contract | Resource observation/orchestration and asset-backed traversal remain pending |
|
||||||
| M2 build resource snapshot | Implemented extraction | Typed identity/adoption/lifetime/source/timing contract | Resource observation service and asset-backed traversal remain pending |
|
| M2 build resource snapshot | Implemented extraction | Typed identity/adoption/lifetime/source/timing contract | Resource observation service and asset-backed traversal remain pending |
|
||||||
| M2 static build resource observer | Implemented extraction | Cache/pending/path/request/source/timing contract | Asset-backed traversal and finalize extraction pending |
|
| M2 static build resource observer | Implemented extraction | Cache/pending/path/request/source/timing contract | Asset-backed traversal pending |
|
||||||
| M2 cached animation resource observer | Implemented extraction | Cache/pending/policy/GLB/request/source/timing contract | Asset-backed traversal pending |
|
| M2 cached animation resource observer | Implemented extraction | Cache/pending/policy/GLB/request/source/timing contract | Asset-backed traversal pending |
|
||||||
| M2 native animation resource observer | Implemented extraction | Candidate/cache/raw/build/adoption/source/timing contract | Asset-backed traversal/leak/p95/p99 pending |
|
| M2 native animation resource observer | Implemented extraction | Candidate/cache/raw/build/adoption/source/timing contract | Asset-backed traversal/leak/p95/p99 pending |
|
||||||
| M2 animation resource finalizer | Implemented extraction | Status/FIFO/load/candidate/repair/adoption/source/timing contract | Asset-backed traversal/leak/p95/p99 pending |
|
| M2 animation resource finalizer | Implemented extraction | Status/FIFO/load/candidate/repair/adoption/source/timing contract | Asset-backed traversal/leak/p95/p99 pending |
|
||||||
|
| M2 mesh resource finalizer | Implemented extraction | Status/FIFO/load/extract/prepare/adoption/source/timing contract | Asset-backed traversal/leak/p95/p99 pending |
|
||||||
| M2 build queue | Implemented extraction | Typed lifecycle/FIFO/progress/lifetime/source/timing contract | Resource dispatch and asset-backed traversal remain pending |
|
| M2 build queue | Implemented extraction | Typed lifecycle/FIFO/progress/lifetime/source/timing contract | Resource dispatch and asset-backed traversal remain pending |
|
||||||
| M2 animation load pipeline state | Implemented extraction | Synthetic lifecycle/FIFO/source/timing contract | Asset-backed traversal/leak/animation-fidelity/p95/p99 pending |
|
| M2 animation load pipeline state | Implemented extraction | Synthetic lifecycle/FIFO/source/timing contract | Asset-backed traversal/leak/animation-fidelity/p95/p99 pending |
|
||||||
| M2 animated scene finalizer | Implemented extraction | Synthetic type/lifetime/material/player/source/timing contract | Asset-backed GLB traversal/material/animation comparison pending |
|
| M2 animated scene finalizer | Implemented extraction | Synthetic type/lifetime/material/player/source/timing contract | Asset-backed GLB traversal/material/animation comparison pending |
|
||||||
@@ -681,6 +684,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
|||||||
| `src/render/m2/m2_mesh_load_pipeline_state.gd` | Static M2 pending Resource paths, terminal statuses and finalize FIFO |
|
| `src/render/m2/m2_mesh_load_pipeline_state.gd` | Static M2 pending Resource paths, terminal statuses and finalize FIFO |
|
||||||
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared static M2 Mesh references and final-shutdown release |
|
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared static M2 Mesh references and final-shutdown release |
|
||||||
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh selection and temporary PackedScene instance lifetime |
|
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh selection and temporary PackedScene instance lifetime |
|
||||||
|
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Static terminal polling, Mesh preparation and cache/missing adoption |
|
||||||
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Runtime refresh version, rebuild classification/build and fallback |
|
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Runtime refresh version, rebuild classification/build and fallback |
|
||||||
| `src/render/m2/m2_raw_model_repository.gd` | Stateless static/animated native M2 file boundary |
|
| `src/render/m2/m2_raw_model_repository.gd` | Stateless static/animated native M2 file boundary |
|
||||||
| `src/render/m2/m2_native_animation_resource_observer.gd` | Native candidate/read/build/cache observation |
|
| `src/render/m2/m2_native_animation_resource_observer.gd` | Native candidate/read/build/cache observation |
|
||||||
@@ -702,6 +706,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
|||||||
| `src/tools/verify_m2_runtime_mesh_rebuild_classifier.gd` | M2 rebuild predicate/cache/boundary/timing regression |
|
| `src/tools/verify_m2_runtime_mesh_rebuild_classifier.gd` | M2 rebuild predicate/cache/boundary/timing regression |
|
||||||
| `src/tools/verify_m2_animation_load_pipeline_state.gd` | Animated M2 request/terminal/FIFO/boundary/timing regression |
|
| `src/tools/verify_m2_animation_load_pipeline_state.gd` | Animated M2 request/terminal/FIFO/boundary/timing regression |
|
||||||
| `src/tools/verify_m2_animation_resource_finalizer.gd` | Animated M2 polling/load/finalize/adoption/boundary/timing regression |
|
| `src/tools/verify_m2_animation_resource_finalizer.gd` | Animated M2 polling/load/finalize/adoption/boundary/timing regression |
|
||||||
|
| `src/tools/verify_m2_mesh_resource_finalizer.gd` | Static M2 polling/extraction/preparation/adoption/boundary/timing regression |
|
||||||
| `src/tools/verify_m2_animated_scene_finalizer.gd` | Animated M2 candidate/material/player/lifetime/boundary/timing regression |
|
| `src/tools/verify_m2_animated_scene_finalizer.gd` | Animated M2 candidate/material/player/lifetime/boundary/timing regression |
|
||||||
| `src/tools/verify_m2_animation_playback_controller.gd` | Animated M2 phase/selection/playback/native/boundary/timing regression |
|
| `src/tools/verify_m2_animation_playback_controller.gd` | Animated M2 phase/selection/playback/native/boundary/timing regression |
|
||||||
| `src/tools/verify_m2_animated_instance_materializer.gd` | Animated M2 order/render/playback/attachment/boundary/timing regression |
|
| `src/tools/verify_m2_animated_instance_materializer.gd` | Animated M2 order/render/playback/attachment/boundary/timing regression |
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -108,6 +108,9 @@ const M2_MESH_RESOURCE_CACHE_STATE_SCRIPT := preload(
|
|||||||
const M2_MESH_RESOURCE_EXTRACTOR_SCRIPT := preload(
|
const M2_MESH_RESOURCE_EXTRACTOR_SCRIPT := preload(
|
||||||
"res://src/render/m2/m2_mesh_resource_extractor.gd"
|
"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_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 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")
|
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_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_cache_state := M2_MESH_RESOURCE_CACHE_STATE_SCRIPT.new()
|
||||||
var _m2_mesh_resource_extractor := M2_MESH_RESOURCE_EXTRACTOR_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_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_animation_load_pipeline_state := M2_ANIMATION_LOAD_PIPELINE_STATE_SCRIPT.new()
|
||||||
var _m2_animated_scene_finalizer := M2_ANIMATED_SCENE_FINALIZER_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:
|
func _drain_m2_mesh_loads() -> void:
|
||||||
for pending in _m2_mesh_load_pipeline_state.request_records_snapshot():
|
_m2_mesh_resource_finalizer.poll_terminal_requests(
|
||||||
var normalized_rel := String(pending.get("normalized", ""))
|
_m2_mesh_load_pipeline_state,
|
||||||
var path: String = String(pending.get("path", ""))
|
_m2_prototype_cache_state
|
||||||
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)
|
|
||||||
|
|
||||||
while _m2_mesh_load_pipeline_state.has_finalize_record() and _render_budget_scheduler.try_consume_permit(
|
while _m2_mesh_load_pipeline_state.has_finalize_record() and _render_budget_scheduler.try_consume_permit(
|
||||||
RENDER_BUDGET_SCHEDULER_SCRIPT.M2_MESH_FINALIZE):
|
RENDER_BUDGET_SCHEDULER_SCRIPT.M2_MESH_FINALIZE):
|
||||||
var pending: Dictionary = _m2_mesh_load_pipeline_state.pop_finalize_record()
|
_m2_mesh_resource_finalizer.finalize_next_resource(
|
||||||
var normalized_rel := String(pending.get("normalized", ""))
|
_m2_mesh_load_pipeline_state,
|
||||||
if (
|
_m2_mesh_resource_cache_state,
|
||||||
normalized_rel.is_empty()
|
_m2_prototype_cache_state,
|
||||||
or _m2_mesh_resource_cache_state.has_mesh(normalized_rel)
|
extracted_dir
|
||||||
):
|
)
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
func _process_m2_build_jobs() -> void:
|
func _process_m2_build_jobs() -> void:
|
||||||
@@ -4606,30 +4592,17 @@ func _normalize_m2_rel_path(rel_path: String) -> String:
|
|||||||
return normalized_rel
|
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:
|
func _prepare_m2_node_for_runtime(normalized_rel: String, root: Node3D) -> void:
|
||||||
if root == null:
|
if root == null:
|
||||||
return
|
return
|
||||||
for mesh_instance in _m2_animated_scene_finalizer.mesh_instances_in_subtree(root):
|
for mesh_instance in _m2_animated_scene_finalizer.mesh_instances_in_subtree(root):
|
||||||
if mesh_instance == null or mesh_instance.mesh == null:
|
if mesh_instance == null or mesh_instance.mesh == null:
|
||||||
continue
|
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:
|
if prepared != null:
|
||||||
mesh_instance.mesh = prepared
|
mesh_instance.mesh = prepared
|
||||||
|
|
||||||
@@ -4643,7 +4616,11 @@ func _get_or_load_m2_mesh(rel_path: String) -> Mesh:
|
|||||||
return null
|
return null
|
||||||
var mesh := _m2_mesh_resource_extractor.find_first_mesh_in_subtree(prototype)
|
var mesh := _m2_mesh_resource_extractor.find_first_mesh_in_subtree(prototype)
|
||||||
if mesh != null and not normalized_rel.is_empty():
|
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)
|
_m2_mesh_resource_cache_state.store_mesh(normalized_rel, mesh)
|
||||||
return 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_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 PIPELINE_PATH := "res://src/render/m2/m2_mesh_load_pipeline_state.gd"
|
||||||
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.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:
|
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:
|
func _verify_ownership_boundaries(failures: Array[String]) -> void:
|
||||||
var pipeline_source := FileAccess.get_file_as_string(PIPELINE_PATH)
|
var pipeline_source := FileAccess.get_file_as_string(PIPELINE_PATH)
|
||||||
var loader_source := FileAccess.get_file_as_string(LOADER_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(
|
_expect_true(
|
||||||
loader_source.contains("M2_MESH_LOAD_PIPELINE_STATE_SCRIPT.new()"),
|
loader_source.contains("M2_MESH_LOAD_PIPELINE_STATE_SCRIPT.new()"),
|
||||||
"loader composes pipeline state",
|
"loader composes pipeline state",
|
||||||
@@ -105,15 +109,19 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
|
|||||||
"two existing clear sites delegate",
|
"two existing clear sites delegate",
|
||||||
failures
|
failures
|
||||||
)
|
)
|
||||||
for retained_loader_rule in [
|
for retained_renderer_rule in [
|
||||||
"ResourceLoader.load_threaded_request(",
|
"ResourceLoader.load_threaded_request(",
|
||||||
"ResourceLoader.load_threaded_get_status(path)",
|
"ResourceLoader.load_threaded_get_status(resource_path)",
|
||||||
"ResourceLoader.load_threaded_get(path)",
|
"ResourceLoader.load_threaded_get(resource_path)",
|
||||||
"RENDER_BUDGET_SCHEDULER_SCRIPT.M2_MESH_FINALIZE",
|
"RENDER_BUDGET_SCHEDULER_SCRIPT.M2_MESH_FINALIZE",
|
||||||
"_m2_mesh_resource_cache_state.store_mesh(",
|
"\"store_mesh\"",
|
||||||
"_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 [
|
for forbidden_dependency in [
|
||||||
"ResourceLoader.",
|
"ResourceLoader.",
|
||||||
"WorkerThreadPool.",
|
"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 CACHE_PATH := "res://src/render/m2/m2_mesh_resource_cache_state.gd"
|
||||||
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.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 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:
|
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 cache_source := FileAccess.get_file_as_string(CACHE_PATH)
|
||||||
var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
|
var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
|
||||||
var observer_source := FileAccess.get_file_as_string(STATIC_OBSERVER_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(
|
_expect_true(
|
||||||
loader_source.contains("M2_MESH_RESOURCE_CACHE_STATE_SCRIPT.new()"),
|
loader_source.contains("M2_MESH_RESOURCE_CACHE_STATE_SCRIPT.new()"),
|
||||||
"loader composes Mesh cache state",
|
"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_false(loader_source.contains("var _m2_mesh_cache:"), "legacy Mesh cache removed", failures)
|
||||||
_expect_equal(
|
_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,
|
2,
|
||||||
"two existing stores delegate",
|
"two existing stores delegate",
|
||||||
failures
|
failures
|
||||||
@@ -95,14 +100,18 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
|
|||||||
"final shutdown clear delegates",
|
"final shutdown clear delegates",
|
||||||
failures
|
failures
|
||||||
)
|
)
|
||||||
for retained_loader_rule in [
|
for retained_renderer_rule in [
|
||||||
"ResourceLoader.load_threaded_get(path)",
|
"ResourceLoader.load_threaded_get(resource_path)",
|
||||||
"_m2_mesh_resource_extractor.extract_first_mesh(resource)",
|
"\"extract_first_mesh\"",
|
||||||
"_prepare_m2_mesh_for_runtime(normalized_rel, mesh)",
|
"prepare_mesh_for_runtime(",
|
||||||
"_m2_prototype_cache_state.mark_model_missing(normalized_rel)",
|
"\"mark_model_missing\"",
|
||||||
"_m2_prototype_cache_state.adopt_static_prototype(",
|
"_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 [
|
for forbidden_dependency in [
|
||||||
"ResourceLoader.",
|
"ResourceLoader.",
|
||||||
"FileAccess.",
|
"FileAccess.",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ extends SceneTree
|
|||||||
const EXTRACTOR_SCRIPT := preload("res://src/render/m2/m2_mesh_resource_extractor.gd")
|
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 EXTRACTOR_PATH := "res://src/render/m2/m2_mesh_resource_extractor.gd"
|
||||||
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.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:
|
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:
|
func _verify_ownership_boundaries(failures: Array[String]) -> void:
|
||||||
var extractor_source := FileAccess.get_file_as_string(EXTRACTOR_PATH)
|
var extractor_source := FileAccess.get_file_as_string(EXTRACTOR_PATH)
|
||||||
var loader_source := FileAccess.get_file_as_string(LOADER_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(
|
_expect_true(
|
||||||
loader_source.contains("M2_MESH_RESOURCE_EXTRACTOR_SCRIPT.new()"),
|
loader_source.contains("M2_MESH_RESOURCE_EXTRACTOR_SCRIPT.new()"),
|
||||||
"loader composes Mesh resource extractor",
|
"loader composes Mesh resource extractor",
|
||||||
@@ -110,7 +114,7 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
|
|||||||
failures
|
failures
|
||||||
)
|
)
|
||||||
_expect_equal(
|
_expect_equal(
|
||||||
loader_source.count("_m2_mesh_resource_extractor.extract_first_mesh("),
|
resource_finalizer_source.count("\"extract_first_mesh\""),
|
||||||
1,
|
1,
|
||||||
"threaded Resource finalization delegates",
|
"threaded Resource finalization delegates",
|
||||||
failures
|
failures
|
||||||
@@ -121,14 +125,18 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
|
|||||||
"synchronous fallback delegates",
|
"synchronous fallback delegates",
|
||||||
failures
|
failures
|
||||||
)
|
)
|
||||||
for retained_loader_rule in [
|
for retained_renderer_rule in [
|
||||||
"ResourceLoader.load_threaded_get(path)",
|
"ResourceLoader.load_threaded_get(resource_path)",
|
||||||
"_prepare_m2_mesh_for_runtime(normalized_rel, mesh)",
|
"prepare_mesh_for_runtime(",
|
||||||
"_m2_mesh_resource_cache_state.store_mesh(",
|
"\"store_mesh\"",
|
||||||
"M2_BUILDER_SCRIPT.build(data, extracted_dir)",
|
"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 [
|
for forbidden_dependency in [
|
||||||
"ResourceLoader.",
|
"ResourceLoader.",
|
||||||
"FileAccess.",
|
"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
|
||||||
@@ -7,6 +7,7 @@ const REPOSITORY_PATH := "res://src/render/m2/m2_raw_model_repository.gd"
|
|||||||
const NATIVE_OBSERVER_PATH := (
|
const NATIVE_OBSERVER_PATH := (
|
||||||
"res://src/render/m2/m2_native_animation_resource_observer.gd"
|
"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"
|
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:
|
func _verify_ownership_boundaries(failures: Array[String]) -> void:
|
||||||
var repository_source := FileAccess.get_file_as_string(REPOSITORY_PATH)
|
var repository_source := FileAccess.get_file_as_string(REPOSITORY_PATH)
|
||||||
var native_observer_source := FileAccess.get_file_as_string(NATIVE_OBSERVER_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)
|
var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
|
||||||
_expect_true(
|
_expect_true(
|
||||||
loader_source.contains("M2_RAW_MODEL_REPOSITORY_SCRIPT.new()"),
|
loader_source.contains("M2_RAW_MODEL_REPOSITORY_SCRIPT.new()"),
|
||||||
@@ -67,7 +69,8 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
|
|||||||
failures
|
failures
|
||||||
)
|
)
|
||||||
_expect_equal(
|
_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,
|
2,
|
||||||
"two static callers delegate",
|
"two static callers delegate",
|
||||||
failures
|
failures
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
`M03-RND-M2-STATIC-BUILD-RESOURCE-OBSERVER-001`,
|
`M03-RND-M2-STATIC-BUILD-RESOURCE-OBSERVER-001`,
|
||||||
`M03-RND-M2-CACHED-ANIMATION-RESOURCE-OBSERVER-001`,
|
`M03-RND-M2-CACHED-ANIMATION-RESOURCE-OBSERVER-001`,
|
||||||
`M03-RND-M2-NATIVE-ANIMATION-RESOURCE-OBSERVER-001`,
|
`M03-RND-M2-NATIVE-ANIMATION-RESOURCE-OBSERVER-001`,
|
||||||
`M03-RND-M2-ANIMATION-RESOURCE-FINALIZER-001`
|
`M03-RND-M2-ANIMATION-RESOURCE-FINALIZER-001`,
|
||||||
|
`M03-RND-M2-MESH-RESOURCE-FINALIZER-001`
|
||||||
- Commands: dedicated scheduler/planner/facade/focus/coordinate/manifest/shutdown
|
- Commands: dedicated scheduler/planner/facade/focus/coordinate/manifest/shutdown
|
||||||
headless verifiers; Godot cold editor parse; coordination, documentation and diff gates.
|
headless verifiers; Godot cold editor parse; coordination, documentation and diff gates.
|
||||||
- Results: scheduler `cases=6 iterations=20000 elapsed_ms=10.216`; planner
|
- Results: scheduler `cases=6 iterations=20000 elapsed_ms=10.216`; planner
|
||||||
@@ -317,6 +318,13 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
All 58 autonomous headless regressions passed; internal-access remained `30`,
|
All 58 autonomous headless regressions passed; internal-access remained `30`,
|
||||||
documentation covered `45` module specifications, coordination retained `34`
|
documentation covered `45` module specifications, coordination retained `34`
|
||||||
historical expired-claim warnings and checkpoint dry-run kept all `7/7` plans.
|
historical expired-claim warnings and checkpoint dry-run kept all `7/7` plans.
|
||||||
|
M2 Mesh resource finalizer passed `cases=31 iterations=1000
|
||||||
|
elapsed_ms=0.767` with insertion-order status polling, loaded/failed FIFO,
|
||||||
|
empty-path discard, cached/null/current/stale Mesh outcomes, exact dependency
|
||||||
|
arguments, raw-read suppression and loader source boundaries. All 59 autonomous
|
||||||
|
headless regressions passed; internal-access remained `30`, documentation
|
||||||
|
covered `46` module specifications, coordination retained `34` historical
|
||||||
|
expired-claim warnings and checkpoint dry-run kept all `7/7` plans.
|
||||||
M02 terrain-query regression
|
M02 terrain-query regression
|
||||||
remained green (13 pre-existing expired M00 claim warnings).
|
remained green (13 pre-existing expired M00 claim warnings).
|
||||||
- Fidelity comparison: all 16 historical operation limits and drain sites were
|
- Fidelity comparison: all 16 historical operation limits and drain sites were
|
||||||
@@ -400,14 +408,16 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
M2 stale-cache rebuild selection retains the same positive billboard flag/
|
M2 stale-cache rebuild selection retains the same positive billboard flag/
|
||||||
vertex-count tests, referenced non-identity UV rotation, exclusive
|
vertex-count tests, referenced non-identity UV rotation, exclusive
|
||||||
`0.000001` speed threshold, four-stage cap and first-decision-per-path cache
|
`0.000001` speed threshold, four-stage cap and first-decision-per-path cache
|
||||||
lifetime. Raw loading, material refresh version, M2Builder rebuild/fallback and
|
lifetime. Material refresh version and M2Builder rebuild/fallback remain in
|
||||||
Mesh adoption remain loader-owned.
|
the runtime finalizer; static raw loading and Mesh adoption now belong to the
|
||||||
|
Mesh resource finalizer.
|
||||||
Static M2 mesh pending requests preserve normalized-path dedupe and Dictionary
|
Static M2 mesh pending requests preserve normalized-path dedupe and Dictionary
|
||||||
insertion polling order; loaded/failed transitions preserve completion-order
|
insertion polling order; loaded/failed transitions preserve completion-order
|
||||||
finalization FIFO and opaque statuses. All three pending-plus-finalize metrics,
|
finalization FIFO and opaque statuses. All three pending-plus-finalize metrics,
|
||||||
both clear sites and shutdown ResourceLoader drain order are unchanged; cache
|
both clear sites and shutdown ResourceLoader drain order are unchanged. Cache
|
||||||
path selection, I/O, permits and adoption decisions remain loader-owned;
|
path selection/request admission and permits remain loader-owned; terminal
|
||||||
missing outcomes now belong to the prototype cache state.
|
I/O and Mesh/missing adoption belong to the Mesh resource finalizer, with
|
||||||
|
missing outcomes retained by prototype cache state.
|
||||||
Animated M2 pending requests preserve normalized-path dedupe and Dictionary
|
Animated M2 pending requests preserve normalized-path dedupe and Dictionary
|
||||||
insertion polling order; loaded/failed transitions preserve completion-order
|
insertion polling order; loaded/failed transitions preserve completion-order
|
||||||
finalization FIFO and opaque statuses. All three pending-plus-finalize metrics,
|
finalization FIFO and opaque statuses. All three pending-plus-finalize metrics,
|
||||||
@@ -489,6 +499,8 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
adapters plus synthetic lifecycle and exact-dependency regression fixtures,
|
adapters plus synthetic lifecycle and exact-dependency regression fixtures,
|
||||||
cached M2 animation resource finalizer and loader/pipeline/scene-finalizer/
|
cached M2 animation resource finalizer and loader/pipeline/scene-finalizer/
|
||||||
prototype-cache adapters plus synthetic terminal-I/O regression fixtures,
|
prototype-cache adapters plus synthetic terminal-I/O regression fixtures,
|
||||||
|
static M2 Mesh resource finalizer and loader/pipeline/extractor/runtime-finalizer/
|
||||||
|
raw-repository/cache adapters plus synthetic terminal-I/O regression fixtures,
|
||||||
expanded facade/internal-access/coordinate verifiers; work-package claims and this evidence.
|
expanded facade/internal-access/coordinate verifiers; work-package claims and this evidence.
|
||||||
- Remaining risks: worker concurrency and stale-result validation remain
|
- Remaining risks: worker concurrency and stale-result validation remain
|
||||||
streamer-owned; cancellation stops new permits but does not interrupt
|
streamer-owned; cancellation stops new permits but does not interrupt
|
||||||
@@ -505,8 +517,9 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
culling-driven spatial-cell grouping; M2 rebuild classification and static
|
culling-driven spatial-cell grouping; M2 rebuild classification and static
|
||||||
request/finalize bookkeeping, prepared Mesh cache ownership and first-Mesh
|
request/finalize bookkeeping, prepared Mesh cache ownership and first-Mesh
|
||||||
traversal plus refresh/rebuild/fallback, raw native M2 I/O and prototype/
|
traversal plus refresh/rebuild/fallback, raw native M2 I/O and prototype/
|
||||||
negative cache state are separated, but synchronous native parsing/build and
|
negative cache state are separated; static Mesh terminal polling/adoption is
|
||||||
Mesh adoption remain in the loader call path; cached animation eligibility/
|
also separated, but synchronous native parsing/build remains in its
|
||||||
|
main-thread service call path; cached animation eligibility/
|
||||||
request admission is extracted while terminal ResourceLoader polling remains;
|
request admission is extracted while terminal ResourceLoader polling remains;
|
||||||
raw
|
raw
|
||||||
animated-instance materialization is now separated but remains synchronous
|
animated-instance materialization is now separated but remains synchronous
|
||||||
@@ -518,8 +531,8 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
batching still needs culling/performance evidence; typed M2 pending state is
|
batching still needs culling/performance evidence; typed M2 pending state is
|
||||||
separated and dispatch decisions are explicit; static and cached-animation
|
separated and dispatch decisions are explicit; static and cached-animation
|
||||||
observers produce typed per-operation state, including native observation;
|
observers produce typed per-operation state, including native observation;
|
||||||
animation terminal ResourceLoader drain is extracted, while action execution,
|
animation and static Mesh terminal ResourceLoader drains are extracted, while
|
||||||
material-prototype lookup, root cleanup and static Mesh finalize drain remain loader-owned;
|
action execution, material-prototype lookup and root cleanup remain loader-owned;
|
||||||
WMO ResourceLoader/FileAccess I/O, live fallback and group materialization
|
WMO ResourceLoader/FileAccess I/O, live fallback and group materialization
|
||||||
remain in the loader;
|
remain in the loader;
|
||||||
asset-backed WMO placement/portal/material/leak/p95/p99 evidence remains pending;
|
asset-backed WMO placement/portal/material/leak/p95/p99 evidence remains pending;
|
||||||
|
|||||||
Reference in New Issue
Block a user