Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e690756301 | |||
| b4955d6162 | |||
| b4b94b3cbb | |||
| d39f591e50 | |||
| 4314f961f6 |
@@ -31,6 +31,7 @@ Paired run 2026-07-11 подтвердил крупный coordinate/placement g
|
|||||||
- `src/render/wmo/wmo_placement_registry.gd` - WMO placement-key to tile/global reference sets; Nodes and render jobs remain in the streamer.
|
- `src/render/wmo/wmo_placement_registry.gd` - WMO placement-key to tile/global reference sets; Nodes and render jobs remain in the streamer.
|
||||||
- `src/render/wmo/wmo_render_build_step_planner.gd` - mesh-first lightweight WMO group operation and cursor planning without Nodes or Resources.
|
- `src/render/wmo/wmo_render_build_step_planner.gd` - mesh-first lightweight WMO group operation and cursor planning without Nodes or Resources.
|
||||||
- `src/render/wmo/wmo_render_build_queue.gd` / `wmo_render_build_job.gd` - typed pending group jobs, FIFO placement keys and strong root/resource references without engine destruction.
|
- `src/render/wmo/wmo_render_build_queue.gd` / `wmo_render_build_job.gd` - typed pending group jobs, FIFO placement keys and strong root/resource references without engine destruction.
|
||||||
|
- `src/render/wmo/wmo_render_resource_cache_state.gd` - validated lightweight WMO render Resources, negative cache and pending cache paths without ResourceLoader I/O.
|
||||||
- `src/scenes/streaming/eastern_kingdoms_streaming.tscn` - текущая сцена для проверки Azeroth/Eastern Kingdoms.
|
- `src/scenes/streaming/eastern_kingdoms_streaming.tscn` - текущая сцена для проверки Azeroth/Eastern Kingdoms.
|
||||||
- `addons/mpq_extractor/loaders/adt_builder.gd` - сборка ADT terrain, control splat материалов и MH2O liquids.
|
- `addons/mpq_extractor/loaders/adt_builder.gd` - сборка ADT terrain, control splat материалов и MH2O liquids.
|
||||||
- `addons/mpq_extractor/loaders/m2_builder.gd` - сборка статического M2 mesh/material.
|
- `addons/mpq_extractor/loaders/m2_builder.gd` - сборка статического M2 mesh/material.
|
||||||
@@ -1047,6 +1048,20 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
|||||||
- Cache formats, placement values, profiles and visible rules are unchanged.
|
- Cache formats, placement values, profiles and visible rules are unchanged.
|
||||||
Asset-backed placement/p95/p99 and general WMO parity remain pending.
|
Asset-backed placement/p95/p99 and general WMO parity remain pending.
|
||||||
|
|
||||||
|
## 2026-07-17 WMO Render Resource Cache State Extraction
|
||||||
|
|
||||||
|
- `WmoRenderResourceCacheState` now owns validated lightweight-WMO render
|
||||||
|
Resources, negative entries and normalized-path to pending-cache-path records.
|
||||||
|
- `StreamingWorldLoader` still constructs cache paths, calls `ResourceLoader`,
|
||||||
|
polls requests and validates `WMOStreamingResource` script identity plus
|
||||||
|
`FORMAT_VERSION` before completing cache state.
|
||||||
|
- Map reset and orderly request draining clear pending/negative state while
|
||||||
|
retaining accepted Resources; final runtime cache release clears all state.
|
||||||
|
- Missing render-cache files still are not negatively cached, preserving retry
|
||||||
|
and cached-scene/live fallback behavior. Formats, profiles and visuals are unchanged.
|
||||||
|
- Asset-backed corrupt-cache, traversal/leak p95/p99 and paired fidelity evidence
|
||||||
|
remain pending.
|
||||||
|
|
||||||
## 2026-07-16 World Environment Snapshot Facade
|
## 2026-07-16 World Environment Snapshot Facade
|
||||||
|
|
||||||
- `WorldEnvironmentSnapshot` carries one immutable finite time-of-day value,
|
- `WorldEnvironmentSnapshot` carries one immutable finite time-of-day value,
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# M03-RND-WMO-RENDER-RESOURCE-CACHE-001 — WMO render resource cache state
|
||||||
|
|
||||||
|
<!-- OPENWC_CLAIM:M03-RND-WMO-RENDER-RESOURCE-CACHE-001:sindo-main-codex:2026-07-20 -->
|
||||||
|
|
||||||
|
## Ownership
|
||||||
|
|
||||||
|
- Target: M03
|
||||||
|
- Program: RND
|
||||||
|
- Owner/Agent ID: sindo-main-codex
|
||||||
|
- Branch: `work/sindo-main-codex/m03-wmo-render-resource-cache`
|
||||||
|
- Lease expires UTC: 2026-07-20
|
||||||
|
- Integrator: M03 milestone integrator
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
Extract lightweight-WMO render Resource cache, negative cache and asynchronous
|
||||||
|
request bookkeeping from the loader while preserving loader-owned I/O and
|
||||||
|
cache-format validation.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Call `ResourceLoader`, construct cache paths or inspect files.
|
||||||
|
- Validate `WMOStreamingResource` script identity or format version.
|
||||||
|
- Own cached WMO scenes, live prototypes, placement/build jobs or Nodes/RIDs.
|
||||||
|
- Change fallback order, retry behavior, quality profiles or visual output.
|
||||||
|
- Claim asset-backed WMO fidelity, leak acceptance or traversal p95/p99.
|
||||||
|
|
||||||
|
## Paths
|
||||||
|
|
||||||
|
- Exclusive: `src/render/wmo/wmo_render_resource_cache_state.gd`,
|
||||||
|
`src/tools/verify_wmo_render_resource_cache_state.gd`,
|
||||||
|
`docs/modules/wmo-render-resource-cache-state.md`, this claim
|
||||||
|
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
|
||||||
|
`src/tools/verify_render_runtime_cache_shutdown.gd`,
|
||||||
|
`docs/modules/wmo-render-build-queue.md`, `docs/modules/world-renderer.md`,
|
||||||
|
`docs/modules/README.md`, `RENDER.md`, `targets/03-renderer-facade.md`
|
||||||
|
- Generated/ignored: `.godot`, native DLL, generated ADT/WMO resources, caches
|
||||||
|
and proprietary renderer corpus
|
||||||
|
|
||||||
|
## Contracts and data
|
||||||
|
|
||||||
|
- State keys are loader-normalized relative WMO paths copied as `String`.
|
||||||
|
- Cached values are already validated `Resource` references.
|
||||||
|
- Pending values are cache paths for loader-owned `ResourceLoader` requests.
|
||||||
|
- Terminal request removal precedes validated-resource or missing-state adoption.
|
||||||
|
- Request/missing reset preserves accepted resources; full clear releases all.
|
||||||
|
- Cache/schema/coordinate/profile versions and validation rules remain unchanged.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Requires: current master `c954704`, accepted WMO render build queue
|
||||||
|
- Blocks: further WMO cached-scene/load and materialization decomposition
|
||||||
|
- External state: none; verification uses synthetic Resources and paths
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- Commands: dedicated lifecycle/request/cache/reset/diagnostics/source/timing
|
||||||
|
verifier; WMO queue/planner/registry/resolver/material/shutdown plus adjacent
|
||||||
|
renderer/streaming/coordinate/documentation/coordination gates
|
||||||
|
- Fixtures: synthetic Resource instances and normalized relative/cache paths
|
||||||
|
- Fidelity evidence: exact current cache/missing/request transitions preserved
|
||||||
|
- Performance budget: 100 cycles over 256 resource/request keys under 1 second
|
||||||
|
|
||||||
|
## Documentation deliverables
|
||||||
|
|
||||||
|
- Inline public API docs
|
||||||
|
- Module specification with inputs/outputs, ownership and source map
|
||||||
|
- Data-flow, lifecycle/state and loader interaction sequence diagrams
|
||||||
|
- World-renderer, module registry, WMO queue and RENDER status updates
|
||||||
|
|
||||||
|
## Simplicity and naming
|
||||||
|
|
||||||
|
- Important names introduced: `WmoRenderResourceCacheState`
|
||||||
|
- Simplest considered solution: one RefCounted around three Dictionaries
|
||||||
|
- Rejected complexity/abstractions: generic cache, callbacks, signals, mutexes,
|
||||||
|
ResourceLoader wrapper, persistence layer or shared WMO/M2 cache hierarchy
|
||||||
|
- Unavoidable complexity and justification: partial reset must retain validated
|
||||||
|
Resources while discarding pending/negative state at map reset and shutdown
|
||||||
|
- Measured optimization evidence: bounded synthetic store/request/reset loop
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
- State: accepted
|
||||||
|
- Done: state holder implemented; loader cache/request/reset/metrics adapters
|
||||||
|
migrated; shutdown regression and required documentation updated
|
||||||
|
- Next: cached WMO scene/load state or liquid service extraction
|
||||||
|
- Blocked by:
|
||||||
|
|
||||||
|
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-WMO-RENDER-RESOURCE-CACHE-001:b4955d6 -->
|
||||||
|
|
||||||
|
## Handoff
|
||||||
|
|
||||||
|
- Commit: `d39f591`
|
||||||
|
- Results: cache-state PASS (`cases=10`, `iterations=100`,
|
||||||
|
`elapsed_ms=58.727`); direct no-`.godot` cache-state run PASS (`45.361ms`);
|
||||||
|
WMO queue/planner/registry/resolver/material/shutdown plus 18 adjacent
|
||||||
|
M2/terrain/facade/scheduler/streaming/coordinate regressions PASS;
|
||||||
|
documentation and coordination gates PASS; checkpoint dry-run PASS `7/7`.
|
||||||
|
- Remaining risks: accepted Resources remain strongly referenced until full
|
||||||
|
shutdown release; cached-scene and ResourceLoader I/O seams remain in loader;
|
||||||
|
no proprietary corrupt-cache fixture, asset-backed WMO traversal/leak run,
|
||||||
|
p95/p99 or original-client paired evidence. Full loader cannot be used as a
|
||||||
|
direct empty-class-cache smoke because pre-existing typed global coordinate/
|
||||||
|
focus dependencies require editor class registration; the extracted service
|
||||||
|
itself passed cold and normal editor parse passed.
|
||||||
|
- Documentation updated: inline public API; new
|
||||||
|
`docs/modules/wmo-render-resource-cache-state.md`; module registry, WMO queue,
|
||||||
|
world renderer and `RENDER.md`; data-flow/state/sequence diagrams, ownership,
|
||||||
|
threading, cache-version boundary, failure/recovery and source map included.
|
||||||
|
- Integration: merged as `b4955d6`; post-merge cache state (`cases=10`,
|
||||||
|
`iterations=100`, `elapsed_ms=51.949`), shutdown, WMO queue/resolver, facade,
|
||||||
|
internal-access (`private_symbols=41`), `7/7` manifest, documentation and
|
||||||
|
coordination gates passed.
|
||||||
|
|
||||||
|
<!-- OPENWC_HANDOFF:READY:M03-RND-WMO-RENDER-RESOURCE-CACHE-001:d39f591 -->
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
| WMO placement registry | Implemented extraction | [`wmo-placement-registry.md`](wmo-placement-registry.md) |
|
| WMO placement registry | Implemented extraction | [`wmo-placement-registry.md`](wmo-placement-registry.md) |
|
||||||
| WMO render build step planner | Implemented extraction | [`wmo-render-build-step-planner.md`](wmo-render-build-step-planner.md) |
|
| WMO render build step planner | Implemented extraction | [`wmo-render-build-step-planner.md`](wmo-render-build-step-planner.md) |
|
||||||
| WMO render build queue | Implemented extraction | [`wmo-render-build-queue.md`](wmo-render-build-queue.md) |
|
| WMO render build queue | Implemented extraction | [`wmo-render-build-queue.md`](wmo-render-build-queue.md) |
|
||||||
|
| WMO render Resource cache state | Implemented extraction | [`wmo-render-resource-cache-state.md`](wmo-render-resource-cache-state.md) |
|
||||||
| Third-person camera | Implemented | [`third-person-camera.md`](third-person-camera.md) |
|
| Third-person camera | Implemented | [`third-person-camera.md`](third-person-camera.md) |
|
||||||
| Character presentation | Implemented boundary / Partial fidelity | [`character-presentation.md`](character-presentation.md) |
|
| Character presentation | Implemented boundary / Partial fidelity | [`character-presentation.md`](character-presentation.md) |
|
||||||
| Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) |
|
| Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) |
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ all validation, planning, materialization, permit consumption and destruction.
|
|||||||
|
|
||||||
- Free or queue-free Nodes, Resources or RIDs.
|
- Free or queue-free Nodes, Resources or RIDs.
|
||||||
- Create MeshInstance3D/MultiMeshInstance3D or refresh materials.
|
- Create MeshInstance3D/MultiMeshInstance3D or refresh materials.
|
||||||
- Own frame permits, placement references, caches or load requests.
|
- Own frame permits, placement references, render cache state or load I/O.
|
||||||
- Change duplicate enqueue, stale-key cleanup, cancellation or ordering.
|
- Change duplicate enqueue, stale-key cleanup, cancellation or ordering.
|
||||||
- Change cache versions, WMO transforms, shadows, visibility or fidelity rules.
|
- Change cache versions, WMO transforms, shadows, visibility or fidelity rules.
|
||||||
|
|
||||||
@@ -213,7 +213,8 @@ without exposing mutable jobs or engine objects. No runtime logs are emitted.
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| WMO render build pending-state ownership | Implemented | Lifecycle/source/timing verifier | Asset-backed traversal/leak evidence pending |
|
| WMO render build pending-state ownership | Implemented | Lifecycle/source/timing verifier | Asset-backed traversal/leak evidence pending |
|
||||||
| WMO group planning | Implemented | Build-step planner verifier | Asset-backed p95/p99 pending |
|
| WMO group planning | Implemented | Build-step planner verifier | Asset-backed p95/p99 pending |
|
||||||
| WMO materialization/cache loading | Partial | Existing runtime regressions | Separate loader seams remain |
|
| WMO render Resource state | Implemented extraction | Cache-state lifecycle/source/timing verifier | Asset-backed traversal/leak evidence pending |
|
||||||
|
| WMO materialization/cache I/O | Partial | Existing runtime regressions | Cached-scene and I/O seams remain |
|
||||||
|
|
||||||
## Known gaps and risks
|
## Known gaps and risks
|
||||||
|
|
||||||
@@ -236,6 +237,7 @@ without exposing mutable jobs or engine objects. No runtime logs are emitted.
|
|||||||
## Related decisions and references
|
## Related decisions and references
|
||||||
|
|
||||||
- [`wmo-render-build-step-planner.md`](wmo-render-build-step-planner.md)
|
- [`wmo-render-build-step-planner.md`](wmo-render-build-step-planner.md)
|
||||||
|
- [`wmo-render-resource-cache-state.md`](wmo-render-resource-cache-state.md)
|
||||||
- [`wmo-placement-registry.md`](wmo-placement-registry.md)
|
- [`wmo-placement-registry.md`](wmo-placement-registry.md)
|
||||||
- [`world-renderer.md`](world-renderer.md)
|
- [`world-renderer.md`](world-renderer.md)
|
||||||
- [`../../RENDER.md`](../../RENDER.md)
|
- [`../../RENDER.md`](../../RENDER.md)
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
# WMO Render Resource Cache State
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|---|---|
|
||||||
|
| Status | Implemented |
|
||||||
|
| Target/work package | M03 / `M03-RND-WMO-RENDER-RESOURCE-CACHE-001` |
|
||||||
|
| Owners | Validated lightweight-WMO render Resources, negative cache and pending request paths |
|
||||||
|
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-render-resource-cache`, 2026-07-17 |
|
||||||
|
| Profiles/capabilities | Lightweight WMO render-cache path; profile-independent |
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Own the mutually exclusive cached, missing and pending states for normalized
|
||||||
|
lightweight-WMO render paths outside the monolithic streamer. The state holder
|
||||||
|
accepts only caller-validated `Resource` references and records cache paths for
|
||||||
|
threaded requests whose I/O lifecycle remains in `StreamingWorldLoader`.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Normalize WMO paths or construct `.res` cache paths.
|
||||||
|
- Call `ResourceLoader`, inspect files or validate script/format versions.
|
||||||
|
- Own WMO cached scenes, live prototypes, placements, build jobs or engine Nodes.
|
||||||
|
- Change fallback order, retry timing, quality profiles or visible output.
|
||||||
|
- Persist, migrate or build WMO caches.
|
||||||
|
|
||||||
|
## Context and boundaries
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
Placement[Normalized WMO render lookup] --> Loader[StreamingWorldLoader]
|
||||||
|
Loader --> State[WmoRenderResourceCacheState]
|
||||||
|
State -->|cached Resource| Loader
|
||||||
|
Loader -->|cache path| ResourceLoader[Godot ResourceLoader]
|
||||||
|
ResourceLoader -->|status and loaded Resource| Loader
|
||||||
|
Loader --> Validate[Script and FORMAT_VERSION validation]
|
||||||
|
Validate -->|accepted Resource or missing| State
|
||||||
|
Loader --> Fallback[Cached scene or live-prototype fallback]
|
||||||
|
```
|
||||||
|
|
||||||
|
The module may retain validated `Resource` references and copied path Strings.
|
||||||
|
It has no FileAccess, ResourceLoader, Node, RID, worker, mutex, gameplay, network
|
||||||
|
or editor dependency. Cache validation stays at the I/O boundary in the loader.
|
||||||
|
|
||||||
|
## Public API
|
||||||
|
|
||||||
|
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `resource_for(path)` | Query | Borrow validated cached Resource | Renderer main thread; until full clear | Empty/unknown returns null |
|
||||||
|
| `contains_resource(path)` | Query | Test accepted cache state | Renderer main thread | Empty returns false |
|
||||||
|
| `is_missing(path)` | Query | Test negative cache state | Renderer main thread | Empty returns false |
|
||||||
|
| `has_request(path)` | Query | Test pending threaded-request state | Renderer main thread | Empty returns false |
|
||||||
|
| `remember_request(path, cache_path)` | Command/query | Record one loader-started request | Renderer main thread; until terminal/reset | Invalid or occupied state returns false |
|
||||||
|
| `request_paths_snapshot()` | Query | Copy pending normalized/cache-path mapping | Poll or shutdown drain | Detached Dictionary |
|
||||||
|
| `complete_request_with_resource(path, resource)` | Command/query | Remove pending request and adopt caller-validated Resource | Terminal loader poll | Unknown/null returns false |
|
||||||
|
| `complete_request_as_missing(path)` | Command/query | Remove pending request and adopt negative state | Terminal loader poll | Unknown returns false |
|
||||||
|
| `clear_transient_state()` | Command | Clear pending and missing while retaining accepted Resources | Map reset/request drain | Idempotent |
|
||||||
|
| `clear_all()` | Command | Release Resources, pending and missing | Final runtime cache release | Idempotent |
|
||||||
|
| `pending_request_count()` | Query | Preserve renderer queue metric contribution | Renderer diagnostics | None |
|
||||||
|
| `diagnostic_snapshot()` | Query | Return sorted detached scalar keys | Tests/diagnostics | Never exposes Resources |
|
||||||
|
|
||||||
|
## Inputs and outputs
|
||||||
|
|
||||||
|
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| Input | Normalized relative WMO path | Placement resolver through loader | Cache state | Copied String | Map/cache lifetime |
|
||||||
|
| Input | `.res` cache path after accepted threaded-request result | Loader | Pending state | Copied String | Until terminal/reset |
|
||||||
|
| Input | Script/version-validated WMO Resource | Loader validation adapter | Resource cache | Strong reference | Until full clear |
|
||||||
|
| Output | Borrowed validated Resource | Cache state | Loader render-root/build adapters | State retains reference | Lookup/build start |
|
||||||
|
| Output | Detached pending-path snapshot | Cache state | Loader ResourceLoader polling/shutdown | Caller-owned Dictionary | One drain pass |
|
||||||
|
| Output | Sorted scalar diagnostic keys | Cache state | Verifier/future metrics | Caller-owned values | Snapshot lifetime |
|
||||||
|
|
||||||
|
## Data flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Lookup[Lookup normalized path] --> Cached{Cached?}
|
||||||
|
Cached -->|yes| Return[Return validated Resource]
|
||||||
|
Cached -->|no| Blocked{Missing or pending?}
|
||||||
|
Blocked -->|yes| Null[Return null; loader waits/falls back]
|
||||||
|
Blocked -->|no| Start[Loader starts threaded request]
|
||||||
|
Start --> Remember[remember_request]
|
||||||
|
Remember --> Poll[Loader polls detached request snapshot]
|
||||||
|
Poll --> Terminal{Loaded or failed?}
|
||||||
|
Terminal -->|no| Poll
|
||||||
|
Terminal -->|failed| Missing[complete as missing]
|
||||||
|
Terminal -->|loaded| Validate[Loader validates script/version]
|
||||||
|
Validate -->|valid| Adopt[complete with Resource]
|
||||||
|
Validate -->|invalid| Missing
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lifecycle/state
|
||||||
|
|
||||||
|
Each normalized path is `Absent`, `Pending`, `Cached` or `Missing`. Valid request
|
||||||
|
completion leaves exactly one terminal state. Transient clear moves `Pending` and
|
||||||
|
`Missing` to `Absent` but retains `Cached`; full clear moves every state to
|
||||||
|
`Absent` and releases retained Resources.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> Absent
|
||||||
|
Absent --> Pending: remember valid request
|
||||||
|
Pending --> Cached: validated Resource completion
|
||||||
|
Pending --> Missing: load/validation failure
|
||||||
|
Pending --> Absent: clear transient/full
|
||||||
|
Missing --> Absent: clear transient/full
|
||||||
|
Cached --> Absent: full clear
|
||||||
|
```
|
||||||
|
|
||||||
|
## Main sequence
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Loader as StreamingWorldLoader
|
||||||
|
participant State as WmoRenderResourceCacheState
|
||||||
|
participant RL as ResourceLoader
|
||||||
|
Loader->>State: cached/missing/pending queries
|
||||||
|
Loader->>RL: exists + load_threaded_request(cache path)
|
||||||
|
Loader->>State: remember_request(normalized, cache path)
|
||||||
|
loop renderer tick
|
||||||
|
Loader->>State: request_paths_snapshot()
|
||||||
|
Loader->>RL: load_threaded_get_status(cache path)
|
||||||
|
end
|
||||||
|
alt load failed
|
||||||
|
Loader->>State: complete_request_as_missing(normalized)
|
||||||
|
else loaded
|
||||||
|
Loader->>RL: load_threaded_get(cache path)
|
||||||
|
Loader->>Loader: validate script and FORMAT_VERSION
|
||||||
|
Loader->>State: complete with Resource or as missing
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ownership, threading and resources
|
||||||
|
|
||||||
|
- The state owns three Dictionaries and strong references to accepted Resources.
|
||||||
|
- The loader owns normalization, cache paths, ResourceLoader calls and validation.
|
||||||
|
- All mutation is serialized by the renderer main-thread lookup/drain lifecycle.
|
||||||
|
- No mutex or callback is needed; detached request snapshots permit safe removal.
|
||||||
|
- The loader/build queue borrow Resources without transferring ownership.
|
||||||
|
|
||||||
|
## Errors, cancellation and recovery
|
||||||
|
|
||||||
|
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Empty path/cache path | Request guard | Reject unchanged | Contract verifier | Repair caller input |
|
||||||
|
| Duplicate or occupied path | State membership | Reject request unchanged | Contract/source verifier | Await terminal/reset |
|
||||||
|
| Threaded load failed | Loader status | Complete as missing | Existing renderer metrics | Transient reset permits retry |
|
||||||
|
| Wrong script/old format | Loader validation | Complete as missing | Cache-version source gate | Rebuild cache, then reset |
|
||||||
|
| Shutdown while pending | Loader drains ResourceLoader snapshot | Clear transient afterward | Shutdown verifier | Next runtime starts absent |
|
||||||
|
| Final cache release | Loader shutdown lifecycle | Full clear releases Resources | Shutdown verifier | Recreated with loader |
|
||||||
|
|
||||||
|
## Configuration and capabilities
|
||||||
|
|
||||||
|
The module adds no setting or profile branch. `wmo_render_cache_dir`, resource
|
||||||
|
format version, request scheduling and WMO build budgets remain loader-owned.
|
||||||
|
|
||||||
|
## Persistence, cache and migration
|
||||||
|
|
||||||
|
The state is runtime-only and serializes nothing. `WMOStreamingResource` script
|
||||||
|
identity and `FORMAT_VERSION` validation remain unchanged in the loader; no cache
|
||||||
|
migration or rebuild is introduced by this extraction.
|
||||||
|
|
||||||
|
## Diagnostics and observability
|
||||||
|
|
||||||
|
`pending_request_count` preserves the existing `wmobuild` metric contribution.
|
||||||
|
Diagnostics expose only sorted normalized-path keys for cached/missing/pending
|
||||||
|
states; Resource references and cache file paths are not exposed. No logs emit.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `verify_wmo_render_resource_cache_state.gd`: invalid input, detached requests,
|
||||||
|
successful/missing terminal transitions, exclusivity, partial/full reset,
|
||||||
|
sorted diagnostics, ownership/source boundary and bounded timing.
|
||||||
|
- `verify_render_runtime_cache_shutdown.gd`: full release of accepted Resources
|
||||||
|
and negative state through the loader lifecycle.
|
||||||
|
- WMO queue/planner/registry/resolver and surrounding renderer regressions verify
|
||||||
|
unchanged fallback/build behavior. This package makes no visual parity claim.
|
||||||
|
|
||||||
|
## Extension points
|
||||||
|
|
||||||
|
- Cached WMO scene request state can use a separate typed module because its
|
||||||
|
size limit, PackedScene validation and missing-file behavior differ.
|
||||||
|
- A later loader I/O adapter may consume the detached request snapshot while
|
||||||
|
preserving this state contract and cache-version boundary.
|
||||||
|
|
||||||
|
## Capability status
|
||||||
|
|
||||||
|
| Capability | Status | Evidence | Gap/next step |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Lightweight WMO render Resource state | Implemented extraction | Lifecycle/source/timing and shutdown verifiers | Asset-backed traversal/leak evidence pending |
|
||||||
|
| Cache script/version validation | Preserved in loader | Source boundary and WMO regressions | Dedicated corrupt-cache fixture could follow |
|
||||||
|
| Cached WMO scene state | Partial/loader-owned | Existing runtime behavior | Separate extraction |
|
||||||
|
| WMO materialization | Partial/loader-owned | Queue/planner regressions | Further safe extraction |
|
||||||
|
|
||||||
|
## Known gaps and risks
|
||||||
|
|
||||||
|
- Strong Resource references live until final cache release, matching prior state.
|
||||||
|
- Missing cache files remain unrecorded for render Resources, so lookup can retry;
|
||||||
|
this intentionally differs from the cached-scene path.
|
||||||
|
- No proprietary WMO corpus, corrupt-version fixture, leak run, traversal p95/p99
|
||||||
|
or paired original-client capture is included.
|
||||||
|
|
||||||
|
## Source map
|
||||||
|
|
||||||
|
| Path | Responsibility |
|
||||||
|
|---|---|
|
||||||
|
| `src/render/wmo/wmo_render_resource_cache_state.gd` | Resource/missing/request state and resets |
|
||||||
|
| `src/scenes/streaming/streaming_world_loader.gd` | Normalize, request, poll, validate, fallback and shutdown I/O |
|
||||||
|
| `src/tools/verify_wmo_render_resource_cache_state.gd` | State, boundary and timing regression |
|
||||||
|
| `src/tools/verify_render_runtime_cache_shutdown.gd` | Loader final Resource ownership regression |
|
||||||
|
|
||||||
|
## Related decisions and references
|
||||||
|
|
||||||
|
- [`wmo-render-build-queue.md`](wmo-render-build-queue.md)
|
||||||
|
- [`wmo-placement-resolver.md`](wmo-placement-resolver.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)
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
| Status | Partial |
|
| Status | Partial |
|
||||||
| Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; `M01-QAR-SERVER-SPAWN-RENDERER-001`; M03 facade/planner/scheduler/internal-access/ground/environment/entity packages; M03 terrain packages; M03 M2 packages; M03 WMO placement package |
|
| Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; `M01-QAR-SERVER-SPAWN-RENDERER-001`; M03 facade/planner/scheduler/internal-access/ground/environment/entity packages; M03 terrain packages; M03 M2 packages; M03 WMO placement package |
|
||||||
| Owners | Renderer workstream / milestone integrator |
|
| Owners | Renderer workstream / milestone integrator |
|
||||||
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-render-build-queue`, 2026-07-17 |
|
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-render-resource-cache`, 2026-07-17 |
|
||||||
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
|
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
@@ -136,6 +136,7 @@ from externally reading/writing loader-private queue, task, cache and tile-state
|
|||||||
| `WmoPlacementRegistry.add_reference/release_reference/contains/active_count/diagnostic_snapshot/clear` | Internal WMO service | Owns placement-key to tile/global reference sets | Renderer main thread; map session | Empty/unknown/non-owner input is rejected without mutation |
|
| `WmoPlacementRegistry.add_reference/release_reference/contains/active_count/diagnostic_snapshot/clear` | Internal WMO service | Owns placement-key to tile/global reference sets | Renderer main thread; map session | Empty/unknown/non-owner input is rejected without mutation |
|
||||||
| `WmoRenderBuildStepPlanner.plan_step` | Internal pure WMO service | Selects one mesh-first lightweight render-group operation and next cursors | Main/any thread; stateless | Raw integer comparisons are preserved without clamping |
|
| `WmoRenderBuildStepPlanner.plan_step` | Internal pure WMO service | Selects one mesh-first lightweight render-group operation and next cursors | Main/any thread; stateless | Raw integer comparisons are preserved without clamping |
|
||||||
| `WmoRenderBuildQueue` / `WmoRenderBuildJob` | Internal WMO pending-state service | Owns typed root/resource/cursor jobs and FIFO placement keys | Renderer main thread; map session | Invalid enqueue rejected; duplicate/stale behavior preserved |
|
| `WmoRenderBuildQueue` / `WmoRenderBuildJob` | Internal WMO pending-state service | Owns typed root/resource/cursor jobs and FIFO placement keys | Renderer main thread; map session | Invalid enqueue rejected; duplicate/stale behavior preserved |
|
||||||
|
| `WmoRenderResourceCacheState` | Internal WMO cache-state service | Owns validated Resources, negative entries and pending cache paths | Renderer main thread; map/cache session | Invalid/occupied request and unknown completion are rejected |
|
||||||
|
|
||||||
Публичным contract не считаются `StreamingWorldLoader` methods/properties,
|
Публичным contract не считаются `StreamingWorldLoader` methods/properties,
|
||||||
внутренние dictionaries, queues, job records и generated node names. Scene-owned
|
внутренние dictionaries, queues, job records и generated node names. Scene-owned
|
||||||
@@ -168,6 +169,7 @@ loader configuration remains transitional composition data, not a caller API.
|
|||||||
| Internal WMO ownership | Resolved placement key and tile/global reference key | Loader / `WmoPlacementRegistry` | Loader create/retain/final-free decisions | Registry-owned String sets; detached diagnostics | Map session or final release |
|
| Internal WMO ownership | Resolved placement key and tile/global reference key | Loader / `WmoPlacementRegistry` | Loader create/retain/final-free decisions | Registry-owned String sets; detached diagnostics | Map session or final release |
|
||||||
| Internal WMO build step | Mesh/MultiMesh counts and job cursors | Loader / `WmoRenderBuildStepPlanner` | Loader materialization/cursor adapter | Fresh scalar Dictionary | One group operation |
|
| Internal WMO build step | Mesh/MultiMesh counts and job cursors | Loader / `WmoRenderBuildStepPlanner` | Loader materialization/cursor adapter | Fresh scalar Dictionary | One group operation |
|
||||||
| Internal WMO pending build | Placement key, Node3D root, WMO Resource and cursors | Loader / `WmoRenderBuildQueue` | Loader drain and step planner adapter | Queue-owned job and strong references | Until cancel/clear/replacement |
|
| Internal WMO pending build | Placement key, Node3D root, WMO Resource and cursors | Loader / `WmoRenderBuildQueue` | Loader drain and step planner adapter | Queue-owned job and strong references | Until cancel/clear/replacement |
|
||||||
|
| Internal WMO render cache | Normalized path, cache path and validated Resource | Loader / `WmoRenderResourceCacheState` | Loader lookup, ResourceLoader poll and build queue | State-owned Resource/path references; detached request snapshots | Until transient/full clear |
|
||||||
| Output | Desired tile/detail operations | Streamer plan application | Finalize queues | Loader-owned | Cross-frame |
|
| Output | Desired tile/detail operations | Streamer plan application | Finalize queues | Loader-owned | Cross-frame |
|
||||||
| Output | Terrain/M2/WMO/liquid instances | Loader/builders | Godot world/renderer | Loader/world owner | Main-thread attach |
|
| Output | Terrain/M2/WMO/liquid instances | Loader/builders | Godot world/renderer | Loader/world owner | Main-thread attach |
|
||||||
| Output | Detached renderer metrics | `WorldRenderFacade` | Capture/baseline tools | Caller-owned deep copy | On demand |
|
| Output | Detached renderer metrics | `WorldRenderFacade` | Capture/baseline tools | Caller-owned deep copy | On demand |
|
||||||
@@ -346,9 +348,11 @@ sequenceDiagram
|
|||||||
identity and transform values. `WmoPlacementRegistry` owns only placement-key
|
identity and transform values. `WmoPlacementRegistry` owns only placement-key
|
||||||
reference sets. `WmoRenderBuildStepPlanner` owns only a call-local operation
|
reference sets. `WmoRenderBuildStepPlanner` owns only a call-local operation
|
||||||
and cursor plan. `WmoRenderBuildQueue` owns typed pending jobs, FIFO keys and
|
and cursor plan. `WmoRenderBuildQueue` owns typed pending jobs, FIFO keys and
|
||||||
strong root/resource references without freeing engine objects. The loader
|
strong root/resource references without freeing engine objects.
|
||||||
retains the placement key-to-Node map, caches/loads, materialization, permits,
|
`WmoRenderResourceCacheState` owns validated render Resources, negative entries
|
||||||
validity reactions and every Node lifecycle action.
|
and pending cache paths. The loader retains ResourceLoader I/O, script/version
|
||||||
|
validation, cached-scene/live fallback, materialization, permits, validity
|
||||||
|
reactions and every Node lifecycle action.
|
||||||
- Rendered-ground query results and diagnostic snapshots are caller-owned values;
|
- Rendered-ground query results and diagnostic snapshots are caller-owned values;
|
||||||
the facade never returns Mesh, Node, tile-state or queue references.
|
the facade never returns Mesh, Node, tile-state or queue references.
|
||||||
- Loaded-mesh ground sampling is renderer diagnostics, not authoritative gameplay
|
- Loaded-mesh ground sampling is renderer diagnostics, not authoritative gameplay
|
||||||
@@ -448,6 +452,9 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
|||||||
transition, completion/raw integer behavior, source ownership and bounded timing.
|
transition, completion/raw integer behavior, source ownership and bounded timing.
|
||||||
- WMO render build queue contract: typed references/cursors, FIFO, duplicate
|
- WMO render build queue contract: typed references/cursors, FIFO, duplicate
|
||||||
replacement, stale-front cleanup, cancel/clear engine lifetime and bounded timing.
|
replacement, stale-front cleanup, cancel/clear engine lifetime and bounded timing.
|
||||||
|
- WMO render Resource cache contract: invalid/duplicate request rejection,
|
||||||
|
validated/missing terminal transitions, transient/full reset, detached sorted
|
||||||
|
diagnostics, loader-owned version validation and bounded timing.
|
||||||
- Budget scheduler contract: exact lane exhaustion, shared chunk removal/create
|
- Budget scheduler contract: exact lane exhaustion, shared chunk removal/create
|
||||||
priority, independent lanes, frame reset, invalid limits, terminal cancellation,
|
priority, independent lanes, frame reset, invalid limits, terminal cancellation,
|
||||||
dependency boundaries and bounded permit timing without loading a world scene.
|
dependency boundaries and bounded permit timing without loading a world scene.
|
||||||
@@ -484,7 +491,8 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
|||||||
| WMO placement resolver | Implemented extraction | Scene-free path/identity/transform/source/timing contract | Asset-backed comparison pending |
|
| WMO placement resolver | Implemented extraction | Scene-free path/identity/transform/source/timing contract | Asset-backed comparison pending |
|
||||||
| WMO placement registry | Implemented extraction | Scene-free ownership/lifecycle/source/timing contract | Build/resource state and asset-backed cross-tile corpus pending |
|
| WMO placement registry | Implemented extraction | Scene-free ownership/lifecycle/source/timing contract | Build/resource state and asset-backed cross-tile corpus pending |
|
||||||
| WMO render build step planner | Implemented extraction | Scene-free order/cursor/source/timing contract | Asset-backed traversal p95/p99 pending |
|
| WMO render build step planner | Implemented extraction | Scene-free order/cursor/source/timing contract | Asset-backed traversal p95/p99 pending |
|
||||||
| WMO render build queue | Implemented extraction | Typed lifecycle/order/ownership/source/timing contract | Cache/load state and asset-backed traversal/leak evidence pending |
|
| WMO render build queue | Implemented extraction | Typed lifecycle/order/ownership/source/timing contract | Materialization and asset-backed traversal/leak evidence pending |
|
||||||
|
| WMO render Resource cache state | Implemented extraction | Scene-free lifecycle/exclusivity/source/timing plus shutdown contract | Cached-scene/I/O extraction and asset-backed traversal/leak evidence pending |
|
||||||
| WMO rendering | Partial | Cached group rendering | Portals/rooms/material parity |
|
| WMO rendering | Partial | Cached group rendering | Portals/rooms/material parity |
|
||||||
| Liquids | Partial | MH2O/MLIQ paths | LiquidType/depth/shore fidelity |
|
| Liquids | Partial | MH2O/MLIQ paths | LiquidType/depth/shore fidelity |
|
||||||
| Sky/light | Partial | DBC controller/logs | Zone transition paired validation |
|
| Sky/light | Partial | DBC controller/logs | Zone transition paired validation |
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
class_name WmoRenderResourceCacheState
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
## Owns validated lightweight-WMO render resources and their load state.
|
||||||
|
## ResourceLoader I/O and cache-format validation remain caller responsibilities.
|
||||||
|
|
||||||
|
var _resources_by_normalized_path: Dictionary = {}
|
||||||
|
var _missing_normalized_paths: Dictionary = {}
|
||||||
|
var _request_cache_paths_by_normalized_path: Dictionary = {}
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the validated cached Resource for a normalized WMO path, or null.
|
||||||
|
func resource_for(normalized_relative_path: String) -> Resource:
|
||||||
|
if normalized_relative_path.is_empty():
|
||||||
|
return null
|
||||||
|
return _resources_by_normalized_path.get(normalized_relative_path) as Resource
|
||||||
|
|
||||||
|
|
||||||
|
## Returns whether a validated Resource is cached for the normalized WMO path.
|
||||||
|
func contains_resource(normalized_relative_path: String) -> bool:
|
||||||
|
return (
|
||||||
|
not normalized_relative_path.is_empty()
|
||||||
|
and _resources_by_normalized_path.has(normalized_relative_path)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
## Returns whether the normalized WMO path is in the negative cache.
|
||||||
|
func is_missing(normalized_relative_path: String) -> bool:
|
||||||
|
return (
|
||||||
|
not normalized_relative_path.is_empty()
|
||||||
|
and _missing_normalized_paths.has(normalized_relative_path)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
## Returns whether the normalized WMO path has a pending threaded load request.
|
||||||
|
func has_request(normalized_relative_path: String) -> bool:
|
||||||
|
return (
|
||||||
|
not normalized_relative_path.is_empty()
|
||||||
|
and _request_cache_paths_by_normalized_path.has(normalized_relative_path)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
## Records one loader-owned threaded request. Cached, missing, duplicate or empty
|
||||||
|
## paths are rejected so each normalized WMO path has exactly one state.
|
||||||
|
func remember_request(normalized_relative_path: String, cache_path: String) -> bool:
|
||||||
|
if normalized_relative_path.is_empty() or cache_path.is_empty():
|
||||||
|
return false
|
||||||
|
if (
|
||||||
|
_resources_by_normalized_path.has(normalized_relative_path)
|
||||||
|
or _missing_normalized_paths.has(normalized_relative_path)
|
||||||
|
or _request_cache_paths_by_normalized_path.has(normalized_relative_path)
|
||||||
|
):
|
||||||
|
return false
|
||||||
|
_request_cache_paths_by_normalized_path[normalized_relative_path] = cache_path
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
## Returns a detached normalized-path to cache-path snapshot for loader-owned
|
||||||
|
## ResourceLoader polling or shutdown draining.
|
||||||
|
func request_paths_snapshot() -> Dictionary:
|
||||||
|
return _request_cache_paths_by_normalized_path.duplicate()
|
||||||
|
|
||||||
|
|
||||||
|
## Completes a pending request with a caller-validated Resource. Returns false
|
||||||
|
## for unknown requests or null Resources and otherwise adopts the reference.
|
||||||
|
func complete_request_with_resource(
|
||||||
|
normalized_relative_path: String,
|
||||||
|
validated_resource: Resource
|
||||||
|
) -> bool:
|
||||||
|
if (
|
||||||
|
normalized_relative_path.is_empty()
|
||||||
|
or validated_resource == null
|
||||||
|
or not _request_cache_paths_by_normalized_path.has(normalized_relative_path)
|
||||||
|
):
|
||||||
|
return false
|
||||||
|
_request_cache_paths_by_normalized_path.erase(normalized_relative_path)
|
||||||
|
_missing_normalized_paths.erase(normalized_relative_path)
|
||||||
|
_resources_by_normalized_path[normalized_relative_path] = validated_resource
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
## Completes a pending request as missing after load failure or validation
|
||||||
|
## rejection. Returns false for an unknown request.
|
||||||
|
func complete_request_as_missing(normalized_relative_path: String) -> bool:
|
||||||
|
if (
|
||||||
|
normalized_relative_path.is_empty()
|
||||||
|
or not _request_cache_paths_by_normalized_path.has(normalized_relative_path)
|
||||||
|
):
|
||||||
|
return false
|
||||||
|
_request_cache_paths_by_normalized_path.erase(normalized_relative_path)
|
||||||
|
_resources_by_normalized_path.erase(normalized_relative_path)
|
||||||
|
_missing_normalized_paths[normalized_relative_path] = true
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
## Clears pending requests and negative entries while retaining validated
|
||||||
|
## Resources for map reset or orderly shutdown request draining.
|
||||||
|
func clear_transient_state() -> void:
|
||||||
|
_request_cache_paths_by_normalized_path.clear()
|
||||||
|
_missing_normalized_paths.clear()
|
||||||
|
|
||||||
|
|
||||||
|
## Releases every cached Resource and transient entry. Safe to call repeatedly.
|
||||||
|
func clear_all() -> void:
|
||||||
|
_resources_by_normalized_path.clear()
|
||||||
|
clear_transient_state()
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the number of pending threaded load requests.
|
||||||
|
func pending_request_count() -> int:
|
||||||
|
return _request_cache_paths_by_normalized_path.size()
|
||||||
|
|
||||||
|
|
||||||
|
## Returns detached, sorted scalar keys without exposing Resource references.
|
||||||
|
func diagnostic_snapshot() -> Dictionary:
|
||||||
|
var resource_paths: Array = _resources_by_normalized_path.keys()
|
||||||
|
var missing_paths: Array = _missing_normalized_paths.keys()
|
||||||
|
var request_paths: Array = _request_cache_paths_by_normalized_path.keys()
|
||||||
|
resource_paths.sort()
|
||||||
|
missing_paths.sort()
|
||||||
|
request_paths.sort()
|
||||||
|
return {
|
||||||
|
"resource_paths": resource_paths.duplicate(),
|
||||||
|
"missing_paths": missing_paths.duplicate(),
|
||||||
|
"request_paths": request_paths.duplicate(),
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b1g4s1rqrptsw
|
||||||
@@ -21,6 +21,9 @@ const WMO_RENDER_BUILD_STEP_PLANNER_SCRIPT := preload(
|
|||||||
const WMO_RENDER_BUILD_QUEUE_SCRIPT := preload(
|
const WMO_RENDER_BUILD_QUEUE_SCRIPT := preload(
|
||||||
"res://src/render/wmo/wmo_render_build_queue.gd"
|
"res://src/render/wmo/wmo_render_build_queue.gd"
|
||||||
)
|
)
|
||||||
|
const WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT := preload(
|
||||||
|
"res://src/render/wmo/wmo_render_resource_cache_state.gd"
|
||||||
|
)
|
||||||
const M2_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_builder.gd")
|
const M2_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_builder.gd")
|
||||||
const M2_NATIVE_ANIMATED_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_native_animated_builder.gd")
|
const M2_NATIVE_ANIMATED_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_native_animated_builder.gd")
|
||||||
const M2_NATIVE_ANIMATOR_SCRIPT := preload("res://src/scenes/streaming/m2_native_animator.gd")
|
const M2_NATIVE_ANIMATOR_SCRIPT := preload("res://src/scenes/streaming/m2_native_animator.gd")
|
||||||
@@ -280,9 +283,7 @@ var _tile_mesh_cache: Dictionary = {}
|
|||||||
var _tile_mesh_cache_order: Array = []
|
var _tile_mesh_cache_order: Array = []
|
||||||
var _terrain_quality_mesh_cache: RefCounted = TERRAIN_QUALITY_MESH_CACHE_SCRIPT.new()
|
var _terrain_quality_mesh_cache: RefCounted = TERRAIN_QUALITY_MESH_CACHE_SCRIPT.new()
|
||||||
var _wmo_prototype_cache: Dictionary = {}
|
var _wmo_prototype_cache: Dictionary = {}
|
||||||
var _wmo_render_cache: Dictionary = {}
|
var _wmo_render_resource_cache_state := WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT.new()
|
||||||
var _wmo_render_missing_cache: Dictionary = {}
|
|
||||||
var _wmo_render_load_requests: Dictionary = {}
|
|
||||||
var _wmo_render_build_queue := WMO_RENDER_BUILD_QUEUE_SCRIPT.new()
|
var _wmo_render_build_queue := WMO_RENDER_BUILD_QUEUE_SCRIPT.new()
|
||||||
var _wmo_scene_resource_cache: Dictionary = {}
|
var _wmo_scene_resource_cache: Dictionary = {}
|
||||||
var _wmo_scene_cache_missing: Dictionary = {}
|
var _wmo_scene_cache_missing: Dictionary = {}
|
||||||
@@ -467,9 +468,8 @@ func _release_runtime_caches_for_shutdown() -> void:
|
|||||||
_m2_mesh_cache.clear()
|
_m2_mesh_cache.clear()
|
||||||
_m2_static_animation_cache.clear()
|
_m2_static_animation_cache.clear()
|
||||||
_m2_missing_cache.clear()
|
_m2_missing_cache.clear()
|
||||||
_wmo_render_cache.clear()
|
_wmo_render_resource_cache_state.clear_all()
|
||||||
_wmo_scene_resource_cache.clear()
|
_wmo_scene_resource_cache.clear()
|
||||||
_wmo_render_missing_cache.clear()
|
|
||||||
_wmo_scene_cache_missing.clear()
|
_wmo_scene_cache_missing.clear()
|
||||||
_wmo_missing_cache.clear()
|
_wmo_missing_cache.clear()
|
||||||
_shared_tex_cache.clear()
|
_shared_tex_cache.clear()
|
||||||
@@ -1005,7 +1005,11 @@ func _log_hitch_profile(start_usec: int, timings: Array[String], did_refresh: bo
|
|||||||
_m2_animation_finalize_queue.size() + _m2_animation_load_requests.size(),
|
_m2_animation_finalize_queue.size() + _m2_animation_load_requests.size(),
|
||||||
_m2_mesh_finalize_queue.size() + _m2_mesh_load_requests.size(),
|
_m2_mesh_finalize_queue.size() + _m2_mesh_load_requests.size(),
|
||||||
_m2_build_queue.size(),
|
_m2_build_queue.size(),
|
||||||
_wmo_build_queue.size() + _wmo_scene_load_requests.size() + _wmo_render_load_requests.size(),
|
(
|
||||||
|
_wmo_build_queue.size()
|
||||||
|
+ _wmo_scene_load_requests.size()
|
||||||
|
+ _wmo_render_resource_cache_state.pending_request_count()
|
||||||
|
),
|
||||||
_wmo_render_build_queue.pending_count(),
|
_wmo_render_build_queue.pending_count(),
|
||||||
_tile_lod_create_queue.size(),
|
_tile_lod_create_queue.size(),
|
||||||
_tile_lod_remove_queue.size(),
|
_tile_lod_remove_queue.size(),
|
||||||
@@ -1032,7 +1036,11 @@ func render_baseline_snapshot() -> Dictionary:
|
|||||||
"m2_animation": _m2_animation_finalize_queue.size() + _m2_animation_load_requests.size(),
|
"m2_animation": _m2_animation_finalize_queue.size() + _m2_animation_load_requests.size(),
|
||||||
"m2_mesh": _m2_mesh_finalize_queue.size() + _m2_mesh_load_requests.size(),
|
"m2_mesh": _m2_mesh_finalize_queue.size() + _m2_mesh_load_requests.size(),
|
||||||
"m2_build": _m2_build_queue.size(),
|
"m2_build": _m2_build_queue.size(),
|
||||||
"wmo_build": _wmo_build_queue.size() + _wmo_scene_load_requests.size() + _wmo_render_load_requests.size(),
|
"wmo_build": (
|
||||||
|
_wmo_build_queue.size()
|
||||||
|
+ _wmo_scene_load_requests.size()
|
||||||
|
+ _wmo_render_resource_cache_state.pending_request_count()
|
||||||
|
),
|
||||||
"wmo_groups": _wmo_render_build_queue.pending_count(),
|
"wmo_groups": _wmo_render_build_queue.pending_count(),
|
||||||
"lod_create": _tile_lod_create_queue.size(),
|
"lod_create": _tile_lod_create_queue.size(),
|
||||||
"lod_remove": _tile_lod_remove_queue.size(),
|
"lod_remove": _tile_lod_remove_queue.size(),
|
||||||
@@ -1314,7 +1322,11 @@ func _tick_runtime_stats(delta: float) -> void:
|
|||||||
_m2_animation_finalize_queue.size() + _m2_animation_load_requests.size(),
|
_m2_animation_finalize_queue.size() + _m2_animation_load_requests.size(),
|
||||||
_m2_mesh_finalize_queue.size() + _m2_mesh_load_requests.size(),
|
_m2_mesh_finalize_queue.size() + _m2_mesh_load_requests.size(),
|
||||||
_m2_build_queue.size(),
|
_m2_build_queue.size(),
|
||||||
_wmo_build_queue.size() + _wmo_scene_load_requests.size() + _wmo_render_load_requests.size(),
|
(
|
||||||
|
_wmo_build_queue.size()
|
||||||
|
+ _wmo_scene_load_requests.size()
|
||||||
|
+ _wmo_render_resource_cache_state.pending_request_count()
|
||||||
|
),
|
||||||
_wmo_render_build_queue.pending_count(),
|
_wmo_render_build_queue.pending_count(),
|
||||||
_tile_lod_create_queue.size(),
|
_tile_lod_create_queue.size(),
|
||||||
_tile_lod_remove_queue.size(),
|
_tile_lod_remove_queue.size(),
|
||||||
@@ -2356,15 +2368,17 @@ func _wait_for_tile_tasks() -> void:
|
|||||||
ResourceLoader.load_threaded_get(path)
|
ResourceLoader.load_threaded_get(path)
|
||||||
_wmo_scene_load_requests.clear()
|
_wmo_scene_load_requests.clear()
|
||||||
|
|
||||||
for path_variant in _wmo_render_load_requests.values():
|
var wmo_render_request_paths := (
|
||||||
|
_wmo_render_resource_cache_state.request_paths_snapshot()
|
||||||
|
)
|
||||||
|
for path_variant in wmo_render_request_paths.values():
|
||||||
var path := String(path_variant)
|
var path := String(path_variant)
|
||||||
if path.is_empty():
|
if path.is_empty():
|
||||||
continue
|
continue
|
||||||
var status := ResourceLoader.load_threaded_get_status(path)
|
var status := ResourceLoader.load_threaded_get_status(path)
|
||||||
if status == ResourceLoader.THREAD_LOAD_IN_PROGRESS or status == ResourceLoader.THREAD_LOAD_LOADED:
|
if status == ResourceLoader.THREAD_LOAD_IN_PROGRESS or status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||||
ResourceLoader.load_threaded_get(path)
|
ResourceLoader.load_threaded_get(path)
|
||||||
_wmo_render_load_requests.clear()
|
_wmo_render_resource_cache_state.clear_transient_state()
|
||||||
_wmo_render_missing_cache.clear()
|
|
||||||
_wmo_scene_cache_missing.clear()
|
_wmo_scene_cache_missing.clear()
|
||||||
_wmo_render_build_queue.clear()
|
_wmo_render_build_queue.clear()
|
||||||
|
|
||||||
@@ -3002,8 +3016,7 @@ func _clear_streamed_world() -> void:
|
|||||||
_wmo_build_jobs.clear()
|
_wmo_build_jobs.clear()
|
||||||
_wmo_build_queue.clear()
|
_wmo_build_queue.clear()
|
||||||
_wmo_scene_load_requests.clear()
|
_wmo_scene_load_requests.clear()
|
||||||
_wmo_render_load_requests.clear()
|
_wmo_render_resource_cache_state.clear_transient_state()
|
||||||
_wmo_render_missing_cache.clear()
|
|
||||||
_wmo_scene_cache_missing.clear()
|
_wmo_scene_cache_missing.clear()
|
||||||
_wmo_render_build_queue.clear()
|
_wmo_render_build_queue.clear()
|
||||||
|
|
||||||
@@ -3589,7 +3602,7 @@ func _process_wmo_build_jobs() -> void:
|
|||||||
var normalized_rel := _wmo_placement_resolver.normalize_relative_path(
|
var normalized_rel := _wmo_placement_resolver.normalize_relative_path(
|
||||||
rel_path
|
rel_path
|
||||||
)
|
)
|
||||||
if _wmo_render_load_requests.has(normalized_rel):
|
if _wmo_render_resource_cache_state.has_request(normalized_rel):
|
||||||
advance_job = false
|
advance_job = false
|
||||||
else:
|
else:
|
||||||
var scene := _get_wmo_scene_or_request(rel_path)
|
var scene := _get_wmo_scene_or_request(rel_path)
|
||||||
@@ -3667,15 +3680,15 @@ func _drain_wmo_scene_loads() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _drain_wmo_render_loads() -> void:
|
func _drain_wmo_render_loads() -> void:
|
||||||
for normalized_rel_variant in _wmo_render_load_requests.keys():
|
var request_paths := _wmo_render_resource_cache_state.request_paths_snapshot()
|
||||||
|
for normalized_rel_variant in request_paths.keys():
|
||||||
var normalized_rel := String(normalized_rel_variant)
|
var normalized_rel := String(normalized_rel_variant)
|
||||||
var path := String(_wmo_render_load_requests[normalized_rel])
|
var path := String(request_paths[normalized_rel])
|
||||||
var status := ResourceLoader.load_threaded_get_status(path)
|
var status := ResourceLoader.load_threaded_get_status(path)
|
||||||
if status != ResourceLoader.THREAD_LOAD_LOADED and status != ResourceLoader.THREAD_LOAD_FAILED:
|
if status != ResourceLoader.THREAD_LOAD_LOADED and status != ResourceLoader.THREAD_LOAD_FAILED:
|
||||||
continue
|
continue
|
||||||
_wmo_render_load_requests.erase(normalized_rel)
|
|
||||||
if status != ResourceLoader.THREAD_LOAD_LOADED:
|
if status != ResourceLoader.THREAD_LOAD_LOADED:
|
||||||
_wmo_render_missing_cache[normalized_rel] = true
|
_wmo_render_resource_cache_state.complete_request_as_missing(normalized_rel)
|
||||||
continue
|
continue
|
||||||
var resource: Resource = ResourceLoader.load_threaded_get(path)
|
var resource: Resource = ResourceLoader.load_threaded_get(path)
|
||||||
if (
|
if (
|
||||||
@@ -3683,20 +3696,23 @@ func _drain_wmo_render_loads() -> void:
|
|||||||
and resource.get_script() == WMO_STREAMING_SCRIPT
|
and resource.get_script() == WMO_STREAMING_SCRIPT
|
||||||
and int(resource.get("format_version")) >= WMO_STREAMING_SCRIPT.FORMAT_VERSION
|
and int(resource.get("format_version")) >= WMO_STREAMING_SCRIPT.FORMAT_VERSION
|
||||||
):
|
):
|
||||||
_wmo_render_cache[normalized_rel] = resource
|
_wmo_render_resource_cache_state.complete_request_with_resource(
|
||||||
|
normalized_rel,
|
||||||
|
resource
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
_wmo_render_missing_cache[normalized_rel] = true
|
_wmo_render_resource_cache_state.complete_request_as_missing(normalized_rel)
|
||||||
|
|
||||||
|
|
||||||
func _get_wmo_render_or_request(rel_path: String) -> Resource:
|
func _get_wmo_render_or_request(rel_path: String) -> Resource:
|
||||||
var normalized_rel := _wmo_placement_resolver.normalize_relative_path(rel_path)
|
var normalized_rel := _wmo_placement_resolver.normalize_relative_path(rel_path)
|
||||||
if normalized_rel.is_empty():
|
if normalized_rel.is_empty():
|
||||||
return null
|
return null
|
||||||
if _wmo_render_cache.has(normalized_rel):
|
if _wmo_render_resource_cache_state.contains_resource(normalized_rel):
|
||||||
return _wmo_render_cache[normalized_rel]
|
return _wmo_render_resource_cache_state.resource_for(normalized_rel)
|
||||||
if _wmo_render_missing_cache.has(normalized_rel):
|
if _wmo_render_resource_cache_state.is_missing(normalized_rel):
|
||||||
return null
|
return null
|
||||||
if _wmo_render_load_requests.has(normalized_rel):
|
if _wmo_render_resource_cache_state.has_request(normalized_rel):
|
||||||
return null
|
return null
|
||||||
|
|
||||||
var cache_path := wmo_render_cache_dir.path_join(normalized_rel.get_basename() + ".res")
|
var cache_path := wmo_render_cache_dir.path_join(normalized_rel.get_basename() + ".res")
|
||||||
@@ -3709,7 +3725,7 @@ func _get_wmo_render_or_request(rel_path: String) -> Resource:
|
|||||||
false,
|
false,
|
||||||
ResourceLoader.CACHE_MODE_REUSE)
|
ResourceLoader.CACHE_MODE_REUSE)
|
||||||
if err == OK or err == ERR_BUSY:
|
if err == OK or err == ERR_BUSY:
|
||||||
_wmo_render_load_requests[normalized_rel] = cache_path
|
_wmo_render_resource_cache_state.remember_request(normalized_rel, cache_path)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,12 @@ func _verify_runtime_cache_ownership() -> void:
|
|||||||
loader.set("_m2_mesh_cache", {"mesh": ArrayMesh.new()})
|
loader.set("_m2_mesh_cache", {"mesh": ArrayMesh.new()})
|
||||||
loader.set("_m2_static_animation_cache", {"static": true})
|
loader.set("_m2_static_animation_cache", {"static": true})
|
||||||
loader.set("_m2_missing_cache", {"missing": true})
|
loader.set("_m2_missing_cache", {"missing": true})
|
||||||
loader.set("_wmo_render_cache", {"render": Resource.new()})
|
var wmo_render_cache_state: RefCounted = loader.get("_wmo_render_resource_cache_state")
|
||||||
|
wmo_render_cache_state.call("remember_request", "render", "res://render.res")
|
||||||
|
wmo_render_cache_state.call("complete_request_with_resource", "render", Resource.new())
|
||||||
|
wmo_render_cache_state.call("remember_request", "missing", "res://missing.res")
|
||||||
|
wmo_render_cache_state.call("complete_request_as_missing", "missing")
|
||||||
loader.set("_wmo_scene_resource_cache", {"scene": PackedScene.new()})
|
loader.set("_wmo_scene_resource_cache", {"scene": PackedScene.new()})
|
||||||
loader.set("_wmo_render_missing_cache", {"missing": true})
|
|
||||||
loader.set("_wmo_scene_cache_missing", {"missing": true})
|
loader.set("_wmo_scene_cache_missing", {"missing": true})
|
||||||
loader.set("_wmo_missing_cache", {"missing": true})
|
loader.set("_wmo_missing_cache", {"missing": true})
|
||||||
loader.set("_shared_tex_cache", {"texture": ImageTexture.new()})
|
loader.set("_shared_tex_cache", {"texture": ImageTexture.new()})
|
||||||
@@ -57,15 +60,26 @@ func _verify_runtime_cache_ownership() -> void:
|
|||||||
"_m2_mesh_cache",
|
"_m2_mesh_cache",
|
||||||
"_m2_static_animation_cache",
|
"_m2_static_animation_cache",
|
||||||
"_m2_missing_cache",
|
"_m2_missing_cache",
|
||||||
"_wmo_render_cache",
|
|
||||||
"_wmo_scene_resource_cache",
|
"_wmo_scene_resource_cache",
|
||||||
"_wmo_render_missing_cache",
|
|
||||||
"_wmo_scene_cache_missing",
|
"_wmo_scene_cache_missing",
|
||||||
"_wmo_missing_cache",
|
"_wmo_missing_cache",
|
||||||
"_shared_tex_cache",
|
"_shared_tex_cache",
|
||||||
]:
|
]:
|
||||||
var cache: Dictionary = loader.get(cache_name)
|
var cache: Dictionary = loader.get(cache_name)
|
||||||
_expect(cache.is_empty(), "%s should be empty after shutdown" % cache_name)
|
_expect(cache.is_empty(), "%s should be empty after shutdown" % cache_name)
|
||||||
|
var wmo_render_snapshot: Dictionary = wmo_render_cache_state.call("diagnostic_snapshot")
|
||||||
|
_expect(
|
||||||
|
(wmo_render_snapshot["resource_paths"] as Array).is_empty(),
|
||||||
|
"WMO render Resources should be empty after shutdown"
|
||||||
|
)
|
||||||
|
_expect(
|
||||||
|
(wmo_render_snapshot["missing_paths"] as Array).is_empty(),
|
||||||
|
"WMO render negative cache should be empty after shutdown"
|
||||||
|
)
|
||||||
|
_expect(
|
||||||
|
(wmo_render_snapshot["request_paths"] as Array).is_empty(),
|
||||||
|
"WMO render requests should be empty after shutdown"
|
||||||
|
)
|
||||||
|
|
||||||
loader.free()
|
loader.free()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,335 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
## Asset-free lifecycle, ownership, dependency and timing regression for the
|
||||||
|
## lightweight-WMO render Resource cache state.
|
||||||
|
|
||||||
|
const CACHE_STATE_SCRIPT := preload(
|
||||||
|
"res://src/render/wmo/wmo_render_resource_cache_state.gd"
|
||||||
|
)
|
||||||
|
const CACHE_STATE_PATH := "res://src/render/wmo/wmo_render_resource_cache_state.gd"
|
||||||
|
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
var failures: Array[String] = []
|
||||||
|
_verify_invalid_and_request_state(failures)
|
||||||
|
_verify_validated_resource_completion(failures)
|
||||||
|
_verify_missing_completion_and_exclusivity(failures)
|
||||||
|
_verify_transient_and_full_clear(failures)
|
||||||
|
_verify_detached_sorted_diagnostics(failures)
|
||||||
|
_verify_ownership_boundaries(failures)
|
||||||
|
var elapsed_milliseconds := _verify_bounded_timing(failures)
|
||||||
|
if not failures.is_empty():
|
||||||
|
for failure in failures:
|
||||||
|
push_error("WMO_RENDER_RESOURCE_CACHE_STATE: %s" % failure)
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
print(
|
||||||
|
"WMO_RENDER_RESOURCE_CACHE_STATE PASS cases=10 iterations=100 elapsed_ms=%.3f"
|
||||||
|
% elapsed_milliseconds
|
||||||
|
)
|
||||||
|
quit(0)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_invalid_and_request_state(failures: Array[String]) -> void:
|
||||||
|
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("remember_request", "", "res://cache.res"),
|
||||||
|
"empty normalized path rejected",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("remember_request", "world/a.wmo", ""),
|
||||||
|
"empty cache path rejected",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("remember_request", "world/a.wmo", "res://cache/a.res"),
|
||||||
|
"valid request accepted",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("has_request", "world/a.wmo"),
|
||||||
|
"pending request observable",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("remember_request", "world/a.wmo", "res://cache/other.res"),
|
||||||
|
"duplicate request rejected",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
var request_snapshot: Dictionary = cache_state.call("request_paths_snapshot")
|
||||||
|
_expect_string_equal(
|
||||||
|
String(request_snapshot.get("world/a.wmo", "")),
|
||||||
|
"res://cache/a.res",
|
||||||
|
"request cache path preserved",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
request_snapshot.clear()
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("has_request", "world/a.wmo"),
|
||||||
|
"request snapshot detached",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_validated_resource_completion(failures: Array[String]) -> void:
|
||||||
|
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
|
||||||
|
var validated_resource := Resource.new()
|
||||||
|
cache_state.call("remember_request", "world/b.wmo", "res://cache/b.res")
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("complete_request_with_resource", "world/b.wmo", null),
|
||||||
|
"null completion rejected",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call(
|
||||||
|
"complete_request_with_resource",
|
||||||
|
"world/b.wmo",
|
||||||
|
validated_resource
|
||||||
|
),
|
||||||
|
"validated Resource completes request",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("has_request", "world/b.wmo"),
|
||||||
|
"successful completion removes request",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("contains_resource", "world/b.wmo"),
|
||||||
|
"successful completion cached",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("resource_for", "world/b.wmo") == validated_resource,
|
||||||
|
"cached Resource identity preserved",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("remember_request", "world/b.wmo", "res://cache/b2.res"),
|
||||||
|
"cached path rejects request",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_missing_completion_and_exclusivity(failures: Array[String]) -> void:
|
||||||
|
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("complete_request_as_missing", "world/unknown.wmo"),
|
||||||
|
"unknown completion rejected",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
cache_state.call("remember_request", "world/missing.wmo", "res://cache/missing.res")
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("complete_request_as_missing", "world/missing.wmo"),
|
||||||
|
"failed request completes as missing",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("is_missing", "world/missing.wmo"),
|
||||||
|
"negative cache observable",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("has_request", "world/missing.wmo"),
|
||||||
|
"missing completion removes request",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call(
|
||||||
|
"remember_request",
|
||||||
|
"world/missing.wmo",
|
||||||
|
"res://cache/retry.res"
|
||||||
|
),
|
||||||
|
"negative cache rejects request",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_transient_and_full_clear(failures: Array[String]) -> void:
|
||||||
|
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
|
||||||
|
var retained_resource := Resource.new()
|
||||||
|
cache_state.call("remember_request", "world/retained.wmo", "res://cache/retained.res")
|
||||||
|
cache_state.call(
|
||||||
|
"complete_request_with_resource",
|
||||||
|
"world/retained.wmo",
|
||||||
|
retained_resource
|
||||||
|
)
|
||||||
|
cache_state.call("remember_request", "world/pending.wmo", "res://cache/pending.res")
|
||||||
|
cache_state.call("remember_request", "world/missing.wmo", "res://cache/missing.res")
|
||||||
|
cache_state.call("complete_request_as_missing", "world/missing.wmo")
|
||||||
|
cache_state.call("clear_transient_state")
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("resource_for", "world/retained.wmo") == retained_resource,
|
||||||
|
"transient clear retains validated Resource",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_equal(
|
||||||
|
int(cache_state.call("pending_request_count")),
|
||||||
|
0,
|
||||||
|
"transient clear removes requests",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("is_missing", "world/missing.wmo"),
|
||||||
|
"transient clear removes negative cache",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
cache_state.call("clear_all")
|
||||||
|
cache_state.call("clear_all")
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("contains_resource", "world/retained.wmo"),
|
||||||
|
"full clear idempotently releases Resource",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_detached_sorted_diagnostics(failures: Array[String]) -> void:
|
||||||
|
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
|
||||||
|
for normalized_path in ["world/z.wmo", "world/a.wmo"]:
|
||||||
|
cache_state.call("remember_request", normalized_path, "res://cache/%s.res" % normalized_path)
|
||||||
|
var snapshot: Dictionary = cache_state.call("diagnostic_snapshot")
|
||||||
|
_expect_string_array(
|
||||||
|
snapshot["request_paths"],
|
||||||
|
["world/a.wmo", "world/z.wmo"],
|
||||||
|
"request diagnostics sorted",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
(snapshot["request_paths"] as Array).clear()
|
||||||
|
var fresh_snapshot: Dictionary = cache_state.call("diagnostic_snapshot")
|
||||||
|
_expect_equal(
|
||||||
|
(fresh_snapshot["request_paths"] as Array).size(),
|
||||||
|
2,
|
||||||
|
"diagnostics detached",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_ownership_boundaries(failures: Array[String]) -> void:
|
||||||
|
var loader_source := _read_text(LOADER_PATH, failures)
|
||||||
|
var cache_state_source := _read_text(CACHE_STATE_PATH, failures)
|
||||||
|
_expect_true(
|
||||||
|
loader_source.contains("WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT.new()"),
|
||||||
|
"loader composes cache state",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
for legacy_field in [
|
||||||
|
"var _wmo_render_cache:",
|
||||||
|
"var _wmo_render_missing_cache:",
|
||||||
|
"var _wmo_render_load_requests:",
|
||||||
|
]:
|
||||||
|
_expect_false(
|
||||||
|
loader_source.contains(legacy_field),
|
||||||
|
"loader removes %s" % legacy_field,
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
for loader_owned_token in [
|
||||||
|
"ResourceLoader.",
|
||||||
|
"FileAccess.",
|
||||||
|
"WMO_STREAMING_SCRIPT",
|
||||||
|
"format_version",
|
||||||
|
"Node3D",
|
||||||
|
"RID",
|
||||||
|
"Thread",
|
||||||
|
"Mutex",
|
||||||
|
"Semaphore",
|
||||||
|
]:
|
||||||
|
_expect_false(
|
||||||
|
cache_state_source.contains(loader_owned_token),
|
||||||
|
"cache state omits %s dependency" % loader_owned_token,
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
loader_source.contains("resource.get_script() == WMO_STREAMING_SCRIPT"),
|
||||||
|
"loader retains script validation",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
loader_source.contains("WMO_STREAMING_SCRIPT.FORMAT_VERSION"),
|
||||||
|
"loader retains cache format validation",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_bounded_timing(failures: Array[String]) -> float:
|
||||||
|
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
|
||||||
|
var started_microseconds := Time.get_ticks_usec()
|
||||||
|
for iteration in range(100):
|
||||||
|
for resource_index in range(256):
|
||||||
|
var normalized_path := "world/object_%d.wmo" % resource_index
|
||||||
|
cache_state.call(
|
||||||
|
"remember_request",
|
||||||
|
normalized_path,
|
||||||
|
"res://cache/object_%d.res" % resource_index
|
||||||
|
)
|
||||||
|
if resource_index % 2 == 0:
|
||||||
|
cache_state.call(
|
||||||
|
"complete_request_with_resource",
|
||||||
|
normalized_path,
|
||||||
|
Resource.new()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cache_state.call("complete_request_as_missing", normalized_path)
|
||||||
|
cache_state.call("clear_all")
|
||||||
|
_expect_equal(
|
||||||
|
int(cache_state.call("pending_request_count")),
|
||||||
|
0,
|
||||||
|
"timing cycle %d clears" % iteration,
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
|
||||||
|
_expect_true(
|
||||||
|
elapsed_milliseconds < 1000.0,
|
||||||
|
"100 resource/request cycles remain bounded",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
return elapsed_milliseconds
|
||||||
|
|
||||||
|
|
||||||
|
func _read_text(path: String, failures: Array[String]) -> String:
|
||||||
|
var file := FileAccess.open(path, FileAccess.READ)
|
||||||
|
if file == null:
|
||||||
|
failures.append("cannot open %s" % path)
|
||||||
|
return ""
|
||||||
|
return file.get_as_text()
|
||||||
|
|
||||||
|
|
||||||
|
func _expect_equal(
|
||||||
|
actual_value: int,
|
||||||
|
expected_value: int,
|
||||||
|
label: String,
|
||||||
|
failures: Array[String]
|
||||||
|
) -> void:
|
||||||
|
if actual_value != expected_value:
|
||||||
|
failures.append("%s expected %d, got %d" % [label, expected_value, actual_value])
|
||||||
|
|
||||||
|
|
||||||
|
func _expect_string_equal(
|
||||||
|
actual_value: String,
|
||||||
|
expected_value: String,
|
||||||
|
label: String,
|
||||||
|
failures: Array[String]
|
||||||
|
) -> void:
|
||||||
|
if actual_value != expected_value:
|
||||||
|
failures.append("%s expected %s, got %s" % [label, expected_value, actual_value])
|
||||||
|
|
||||||
|
|
||||||
|
func _expect_string_array(
|
||||||
|
actual_values: Array,
|
||||||
|
expected_values: Array,
|
||||||
|
label: String,
|
||||||
|
failures: Array[String]
|
||||||
|
) -> void:
|
||||||
|
if actual_values != expected_values:
|
||||||
|
failures.append("%s expected %s, got %s" % [label, expected_values, actual_values])
|
||||||
|
|
||||||
|
|
||||||
|
func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
|
||||||
|
if not actual_value:
|
||||||
|
failures.append("%s expected true" % label)
|
||||||
|
|
||||||
|
|
||||||
|
func _expect_false(actual_value: bool, label: String, failures: Array[String]) -> void:
|
||||||
|
if actual_value:
|
||||||
|
failures.append("%s expected false" % label)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b5kuqdm8au7o1
|
||||||
@@ -27,7 +27,7 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
## Evidence
|
## Evidence
|
||||||
|
|
||||||
- Date: 2026-07-17
|
- Date: 2026-07-17
|
||||||
- Revision/worktree: master merges `7ece2ab`, `7e35de7`, `80cb084`, `d6e5b53`, `c69abd6`, `606770c`, `630a0c1`, `f36fabb`, `24aef13`, `2342430`, `396be5e`, `f88bf97`, `3f84717`, `d2cb52f`, `f9e5a4f`, `c06aed5`, `a878e7c`, `60603e1`;
|
- Revision/worktree: master merges `7ece2ab`, `7e35de7`, `80cb084`, `d6e5b53`, `c69abd6`, `606770c`, `630a0c1`, `f36fabb`, `24aef13`, `2342430`, `396be5e`, `f88bf97`, `3f84717`, `d2cb52f`, `f9e5a4f`, `c06aed5`, `a878e7c`, `60603e1`, `b4955d6`;
|
||||||
packages `M03-RND-SCHEDULER-001`, `M03-RND-INTERNAL-ACCESS-GATE-001`,
|
packages `M03-RND-SCHEDULER-001`, `M03-RND-INTERNAL-ACCESS-GATE-001`,
|
||||||
`M03-RND-FACADE-GROUND-QUERY-001`, `M03-RND-FACADE-ENVIRONMENT-001`,
|
`M03-RND-FACADE-GROUND-QUERY-001`, `M03-RND-FACADE-ENVIRONMENT-001`,
|
||||||
`M03-RND-FACADE-ENTITY-001`, `M03-RND-TERRAIN-CACHE-SERVICE-001`,
|
`M03-RND-FACADE-ENTITY-001`, `M03-RND-TERRAIN-CACHE-SERVICE-001`,
|
||||||
@@ -39,7 +39,8 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
`M03-RND-WMO-PLACEMENT-RESOLVER-001`,
|
`M03-RND-WMO-PLACEMENT-RESOLVER-001`,
|
||||||
`M03-RND-WMO-PLACEMENT-REGISTRY-001`,
|
`M03-RND-WMO-PLACEMENT-REGISTRY-001`,
|
||||||
`M03-RND-WMO-RENDER-BUILD-PLANNER-001`,
|
`M03-RND-WMO-RENDER-BUILD-PLANNER-001`,
|
||||||
`M03-RND-WMO-RENDER-BUILD-QUEUE-001`
|
`M03-RND-WMO-RENDER-BUILD-QUEUE-001`,
|
||||||
|
`M03-RND-WMO-RENDER-RESOURCE-CACHE-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
|
||||||
@@ -93,6 +94,13 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
(`57.835ms`), and post-fix master passed queue (`56.454ms`), planner,
|
(`57.835ms`), and post-fix master passed queue (`56.454ms`), planner,
|
||||||
registry, resolver, shutdown, facade, documentation and coordination gates
|
registry, resolver, shutdown, facade, documentation and coordination gates
|
||||||
without parse diagnostics.
|
without parse diagnostics.
|
||||||
|
WMO render Resource cache state passed `cases=10 iterations=100
|
||||||
|
elapsed_ms=58.727`; direct no-`.godot` service verification passed
|
||||||
|
(`45.361ms`), WMO queue/planner/registry/resolver/material/shutdown and 18
|
||||||
|
adjacent regressions passed, and the checkpoint dry-run retained `7/7` plans.
|
||||||
|
Post-merge cache state (`51.949ms`), shutdown, WMO queue/resolver, facade,
|
||||||
|
internal-access `41`, `7/7` manifest, documentation and coordination gates
|
||||||
|
remained green.
|
||||||
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
|
||||||
@@ -134,6 +142,10 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
duplicate replacement, stale-front pop, first-occurrence cancel and three
|
duplicate replacement, stale-front pop, first-occurrence cancel and three
|
||||||
idempotent clear sites. Queue release never frees engine objects; the loader
|
idempotent clear sites. Queue release never frees engine objects; the loader
|
||||||
retains validity checks, materialization, permits and Node destruction.
|
retains validity checks, materialization, permits and Node destruction.
|
||||||
|
Lightweight WMO render cache paths retain the same cached/pending/missing
|
||||||
|
transitions, retry behavior for absent files and map-reset versus final-shutdown
|
||||||
|
lifetime. `ResourceLoader`, script identity and `FORMAT_VERSION` validation
|
||||||
|
remain loader-owned before the extracted state accepts a Resource.
|
||||||
No new build-12340 parity claim is made; asset-backed visual/p95/p99 comparison remains pending.
|
No new build-12340 parity claim is made; asset-backed visual/p95/p99 comparison remains pending.
|
||||||
- Changed files: scene-free scheduler and verifier; streamer budget composition/
|
- Changed files: scene-free scheduler and verifier; streamer budget composition/
|
||||||
permit delegation; scheduler module spec; world renderer/module registry/RENDER notes;
|
permit delegation; scheduler module spec; world renderer/module registry/RENDER notes;
|
||||||
@@ -151,6 +163,8 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
stateful WMO placement registry and global/tile add/release/clear adapters,
|
stateful WMO placement registry and global/tile add/release/clear adapters,
|
||||||
stateless WMO render build step planner and loader cursor-adoption adapter,
|
stateless WMO render build step planner and loader cursor-adoption adapter,
|
||||||
typed WMO render build job/queue and loader enqueue/drain/cancel/clear adapters,
|
typed WMO render build job/queue and loader enqueue/drain/cancel/clear adapters,
|
||||||
|
lightweight WMO render Resource/missing/request state and loader I/O/validation/
|
||||||
|
reset adapters,
|
||||||
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: queue storage length and worker concurrency/stale-result logic
|
- Remaining risks: queue storage length and worker concurrency/stale-result logic
|
||||||
remain streamer-owned; cancellation stops new permits but does not interrupt
|
remain streamer-owned; cancellation stops new permits but does not interrupt
|
||||||
@@ -165,7 +179,8 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
asset-backed visual transform recheck, grouping/build/tasks/caches/finalization
|
asset-backed visual transform recheck, grouping/build/tasks/caches/finalization
|
||||||
and boundary traversal p95/p99 remain pending; path grouping is not yet
|
and boundary traversal p95/p99 remain pending; path grouping is not yet
|
||||||
culling-driven spatial-cell grouping; M2 resource/queue transitions and raw
|
culling-driven spatial-cell grouping; M2 resource/queue transitions and raw
|
||||||
WMO cache/load requests and group materialization remain in the loader;
|
cached-scene WMO cache/load requests, ResourceLoader I/O and group
|
||||||
|
materialization 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;
|
||||||
M03 still needs further terrain/M2/WMO/liquid service extraction
|
M03 still needs further terrain/M2/WMO/liquid service extraction
|
||||||
and dependency/performance acceptance.
|
and dependency/performance acceptance.
|
||||||
|
|||||||
Reference in New Issue
Block a user