merge M03 WMO scene resource cache state
This commit is contained in:
@@ -32,6 +32,7 @@ Paired run 2026-07-11 подтвердил крупный coordinate/placement g
|
|||||||
- `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/render/wmo/wmo_render_resource_cache_state.gd` - validated lightweight WMO render Resources, negative cache and pending cache paths without ResourceLoader I/O.
|
||||||
|
- `src/render/wmo/wmo_scene_resource_cache_state.gd` - validated cached-WMO PackedScenes, negative cache and pending `.tscn` paths without file/I/O/Node ownership.
|
||||||
- `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.
|
||||||
@@ -1062,6 +1063,20 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
|||||||
- Asset-backed corrupt-cache, traversal/leak p95/p99 and paired fidelity evidence
|
- Asset-backed corrupt-cache, traversal/leak p95/p99 and paired fidelity evidence
|
||||||
remain pending.
|
remain pending.
|
||||||
|
|
||||||
|
## 2026-07-17 WMO Scene Resource Cache State Extraction
|
||||||
|
|
||||||
|
- `WmoSceneResourceCacheState` now owns validated cached-WMO PackedScenes,
|
||||||
|
negative entries and normalized-path to pending-`.tscn` records.
|
||||||
|
- `StreamingWorldLoader` still checks file existence and
|
||||||
|
`wmo_max_runtime_scene_mb`, calls `ResourceLoader`, instantiates a validation
|
||||||
|
probe, checks WMOBuilder cache metadata and frees the probe before adoption.
|
||||||
|
- Missing files, oversize scenes, request errors, load failures and stale scenes
|
||||||
|
retain their prior negative-cache and live-prototype fallback behavior.
|
||||||
|
- Map reset clears pending/negative state while retaining accepted scenes; final
|
||||||
|
shutdown releases all scene state. Formats, profiles and visuals are unchanged.
|
||||||
|
- Asset-backed oversize/stale fixtures, 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,
|
||||||
|
|||||||
@@ -82,14 +82,30 @@ version validation.
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
- State: claimed
|
- State: ready for integration
|
||||||
- Done: lifecycle and four negative-cache sources identified
|
- Done: state holder implemented; loader lookup/request/drain/reset/metrics
|
||||||
- Next: implement state holder, migrate loader adapters, verify and document
|
adapters migrated; shutdown regression and required documentation updated
|
||||||
|
- Next: integrate and run post-merge smoke on master
|
||||||
- Blocked by:
|
- Blocked by:
|
||||||
|
|
||||||
## Handoff
|
## Handoff
|
||||||
|
|
||||||
- Commit:
|
- Commit: `c839294`
|
||||||
- Results:
|
- Results: scene cache-state PASS (`cases=11`, `iterations=100`,
|
||||||
- Remaining risks:
|
`elapsed_ms=40.768`; initial direct cold run `43.636ms`); render cache-state,
|
||||||
- Documentation updated:
|
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 PackedScenes remain strongly referenced until full
|
||||||
|
shutdown release; ResourceLoader/FileAccess I/O, cache-validation probes and
|
||||||
|
live fallback remain in loader; no proprietary stale/oversize fixture,
|
||||||
|
asset-backed traversal/leak run, p95/p99 or original-client paired evidence.
|
||||||
|
The extracted scene state passed cold; full loader cold execution still needs
|
||||||
|
editor class registration for pre-existing focus/coordinate global classes.
|
||||||
|
- Documentation updated: inline public API; new
|
||||||
|
`docs/modules/wmo-scene-resource-cache-state.md`; render-cache spec, module
|
||||||
|
registry, world renderer and `RENDER.md`; data-flow/state/sequence diagrams,
|
||||||
|
ownership, threading, file-size/cache-version boundary, failure/recovery and
|
||||||
|
source map included.
|
||||||
|
|
||||||
|
<!-- OPENWC_HANDOFF:READY:M03-RND-WMO-SCENE-RESOURCE-CACHE-001:c839294 -->
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
| 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) |
|
| WMO render Resource cache state | Implemented extraction | [`wmo-render-resource-cache-state.md`](wmo-render-resource-cache-state.md) |
|
||||||
|
| WMO scene Resource cache state | Implemented extraction | [`wmo-scene-resource-cache-state.md`](wmo-scene-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) |
|
||||||
|
|||||||
@@ -179,8 +179,8 @@ states; Resource references and cache file paths are not exposed. No logs emit.
|
|||||||
|
|
||||||
## Extension points
|
## Extension points
|
||||||
|
|
||||||
- Cached WMO scene request state can use a separate typed module because its
|
- Cached WMO scene request state is a separate typed module because its size
|
||||||
size limit, PackedScene validation and missing-file behavior differ.
|
limit, PackedScene validation and missing-file behavior differ.
|
||||||
- A later loader I/O adapter may consume the detached request snapshot while
|
- A later loader I/O adapter may consume the detached request snapshot while
|
||||||
preserving this state contract and cache-version boundary.
|
preserving this state contract and cache-version boundary.
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ states; Resource references and cache file paths are not exposed. No logs emit.
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Lightweight WMO render Resource state | Implemented extraction | Lifecycle/source/timing and shutdown verifiers | Asset-backed traversal/leak evidence pending |
|
| 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 |
|
| 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 |
|
| Cached WMO scene state | Implemented extraction | Scene-cache lifecycle/source/timing verifier | Asset-backed traversal/leak evidence pending |
|
||||||
| WMO materialization | Partial/loader-owned | Queue/planner regressions | Further safe extraction |
|
| WMO materialization | Partial/loader-owned | Queue/planner regressions | Further safe extraction |
|
||||||
|
|
||||||
## Known gaps and risks
|
## Known gaps and risks
|
||||||
@@ -213,6 +213,7 @@ states; Resource references and cache file paths are not exposed. No logs emit.
|
|||||||
## Related decisions and references
|
## Related decisions and references
|
||||||
|
|
||||||
- [`wmo-render-build-queue.md`](wmo-render-build-queue.md)
|
- [`wmo-render-build-queue.md`](wmo-render-build-queue.md)
|
||||||
|
- [`wmo-scene-resource-cache-state.md`](wmo-scene-resource-cache-state.md)
|
||||||
- [`wmo-placement-resolver.md`](wmo-placement-resolver.md)
|
- [`wmo-placement-resolver.md`](wmo-placement-resolver.md)
|
||||||
- [`world-renderer.md`](world-renderer.md)
|
- [`world-renderer.md`](world-renderer.md)
|
||||||
- [`../../RENDER.md`](../../RENDER.md)
|
- [`../../RENDER.md`](../../RENDER.md)
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
# WMO Scene Resource Cache State
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|---|---|
|
||||||
|
| Status | Implemented |
|
||||||
|
| Target/work package | M03 / `M03-RND-WMO-SCENE-RESOURCE-CACHE-001` |
|
||||||
|
| Owners | Validated cached-WMO PackedScenes, negative cache and pending request paths |
|
||||||
|
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-scene-resource-cache`, 2026-07-17 |
|
||||||
|
| Profiles/capabilities | Cached WMO `.tscn` fallback; profile-independent state |
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Own mutually exclusive cached, missing and pending states for normalized WMO
|
||||||
|
scene paths. The state accepts only caller-validated `PackedScene` references and
|
||||||
|
records `.tscn` paths for threaded requests whose I/O, size limit and cache
|
||||||
|
metadata validation remain in `StreamingWorldLoader`.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Normalize WMO paths, build `.tscn` paths or measure files.
|
||||||
|
- Call ResourceLoader/FileAccess, instantiate scenes or validate cache metadata.
|
||||||
|
- Own live prototypes, placement/build jobs, instantiated Nodes or RIDs.
|
||||||
|
- Change missing, oversize, request-error, retry or fallback behavior.
|
||||||
|
- Persist, migrate or build WMO caches.
|
||||||
|
|
||||||
|
## Context and boundaries
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
Lookup[Normalized WMO scene lookup] --> Loader[StreamingWorldLoader]
|
||||||
|
Loader --> State[WmoSceneResourceCacheState]
|
||||||
|
Loader --> Size[File existence and size limit]
|
||||||
|
Size --> ResourceLoader[Godot ResourceLoader]
|
||||||
|
ResourceLoader --> Loader
|
||||||
|
Loader --> Probe[Instantiate, metadata/version check, free]
|
||||||
|
Probe -->|accepted PackedScene or missing| State
|
||||||
|
Loader --> Live[Live-prototype fallback]
|
||||||
|
```
|
||||||
|
|
||||||
|
The module retains validated PackedScenes and copied paths only. ResourceLoader,
|
||||||
|
FileAccess, WMOBuilder, Node, RID, worker, gameplay, network and editor concerns
|
||||||
|
remain outside it.
|
||||||
|
|
||||||
|
## Public API
|
||||||
|
|
||||||
|
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `scene_for(path)` / `contains_scene(path)` | Query | Borrow/test validated PackedScene | Renderer main thread; until full clear | Empty/unknown returns null/false |
|
||||||
|
| `is_missing(path)` / `has_request(path)` | Query | Test negative/pending state | Renderer main thread | Empty returns false |
|
||||||
|
| `remember_request(path, cache_path)` | Command/query | Record loader-started request | Until terminal/reset | Invalid/occupied returns false |
|
||||||
|
| `mark_missing(path)` | Command/query | Record absent/oversize/request-start failure without pending state | Until transient/full clear | Invalid/occupied returns false |
|
||||||
|
| `request_paths_snapshot()` | Query | Copy pending normalized/cache-path mapping | Poll/shutdown drain | Detached Dictionary |
|
||||||
|
| `complete_request_with_scene(path, scene)` | Command/query | Remove pending and adopt validated PackedScene | Terminal poll | Unknown/null returns false |
|
||||||
|
| `complete_request_as_missing(path)` | Command/query | Remove pending and adopt negative state | Terminal poll | Unknown returns false |
|
||||||
|
| `clear_transient_state()` | Command | Clear pending/missing and retain scenes | Map reset/request drain | Idempotent |
|
||||||
|
| `clear_all()` | Command | Release scenes and transient state | Final runtime-cache release | Idempotent |
|
||||||
|
| `pending_request_count()` | Query | Preserve renderer queue metric | Diagnostics | None |
|
||||||
|
| `diagnostic_snapshot()` | Query | Return sorted detached scalar keys | Tests/diagnostics | No PackedScene exposure |
|
||||||
|
|
||||||
|
## Inputs and outputs
|
||||||
|
|
||||||
|
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| Input | Normalized WMO path | Placement resolver through loader | State | Copied String | Cache lifetime |
|
||||||
|
| Input | `.tscn` path after accepted request start | Loader | Pending state | Copied String | Until terminal/reset |
|
||||||
|
| Input | Metadata/version-validated PackedScene | Loader probe adapter | Scene cache | Strong reference | Until full clear |
|
||||||
|
| Input | Missing decision | Loader file/size/request/load/validation adapters | Negative cache | Boolean membership | Until transient/full clear |
|
||||||
|
| Output | Borrowed PackedScene | State | Loader scene instantiation | State retains reference | Lookup/instantiate call |
|
||||||
|
| Output | Detached request snapshot | State | Loader polling/shutdown | Caller-owned Dictionary | One drain pass |
|
||||||
|
|
||||||
|
## Data flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Lookup --> Known{Cached, missing or pending?}
|
||||||
|
Known -->|cached| Return[Return PackedScene]
|
||||||
|
Known -->|missing/pending| Null[Return null]
|
||||||
|
Known -->|absent| Check[Loader checks file and size]
|
||||||
|
Check -->|missing/oversize| Mark[mark_missing]
|
||||||
|
Check -->|allowed| Request[Start threaded request]
|
||||||
|
Request -->|error| Mark
|
||||||
|
Request -->|accepted| Remember[remember_request]
|
||||||
|
Remember --> Poll[Loader polls snapshot]
|
||||||
|
Poll -->|failure| CompleteMissing[complete as missing]
|
||||||
|
Poll -->|loaded| Validate[Instantiate and validate cache metadata]
|
||||||
|
Validate -->|valid| CompleteScene[complete with scene]
|
||||||
|
Validate -->|invalid| CompleteMissing
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lifecycle/state
|
||||||
|
|
||||||
|
Each normalized path is `Absent`, `Pending`, `Cached` or `Missing`. Direct file,
|
||||||
|
size and request-start rejection moves `Absent` directly to `Missing`; terminal
|
||||||
|
loads move `Pending` to `Cached` or `Missing`. Transient clear retains `Cached`,
|
||||||
|
while full clear returns every path to `Absent`.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> Absent
|
||||||
|
Absent --> Pending: accepted request
|
||||||
|
Absent --> Missing: absent, oversize or request error
|
||||||
|
Pending --> Cached: validated PackedScene
|
||||||
|
Pending --> Missing: load or validation failure
|
||||||
|
Pending --> Absent: transient/full clear
|
||||||
|
Missing --> Absent: transient/full clear
|
||||||
|
Cached --> Absent: full clear
|
||||||
|
```
|
||||||
|
|
||||||
|
## Main sequence
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Loader as StreamingWorldLoader
|
||||||
|
participant State as WmoSceneResourceCacheState
|
||||||
|
participant RL as ResourceLoader
|
||||||
|
Loader->>State: cached/missing/pending queries
|
||||||
|
Loader->>Loader: exists and wmo_max_runtime_scene_mb check
|
||||||
|
Loader->>RL: load_threaded_request(.tscn)
|
||||||
|
Loader->>State: remember_request(normalized, path)
|
||||||
|
loop renderer tick
|
||||||
|
Loader->>State: request_paths_snapshot()
|
||||||
|
Loader->>RL: load_threaded_get_status(path)
|
||||||
|
end
|
||||||
|
Loader->>RL: load_threaded_get(path)
|
||||||
|
Loader->>Loader: instantiate, validate WMO metadata, free probe
|
||||||
|
Loader->>State: complete with scene or as missing
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ownership, threading and resources
|
||||||
|
|
||||||
|
- State owns three Dictionaries and strong references to accepted PackedScenes.
|
||||||
|
- Loader owns paths, file measurement, requests, validation and all Node lifetime.
|
||||||
|
- All mutation is serialized on the renderer main thread; no mutex is required.
|
||||||
|
- Detached request snapshots allow terminal removal while polling.
|
||||||
|
- Scene instantiation borrows the PackedScene and does not transfer cache ownership.
|
||||||
|
|
||||||
|
## Errors, cancellation and recovery
|
||||||
|
|
||||||
|
| Failure/state | Detection | Behavior | Recovery |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Missing `.tscn` | Loader ResourceLoader existence check | Direct missing state | Transient reset permits later retry |
|
||||||
|
| Oversize `.tscn` | Loader byte limit | Missing plus unchanged debug log | Raise limit/rebuild, then reset |
|
||||||
|
| Request-start/load failure | Loader error/status | Direct or terminal missing | Reset and retry later |
|
||||||
|
| Wrong type/stale metadata | Loader PackedScene/probe validation | Terminal missing; probe freed | Rebuild cache and reset |
|
||||||
|
| Shutdown while pending | Loader drains snapshot | Clear transient, then full cache release | New loader starts absent |
|
||||||
|
|
||||||
|
## Configuration and capabilities
|
||||||
|
|
||||||
|
No setting is added. `wmo_cache_dir`, `wmo_max_runtime_scene_mb`, request timing,
|
||||||
|
quality profiles and WMO build budgets stay loader-owned.
|
||||||
|
|
||||||
|
## Persistence, cache and migration
|
||||||
|
|
||||||
|
The module is runtime-only. Existing WMOBuilder cache metadata/version checks
|
||||||
|
remain unchanged and execute before state adoption. No cache format, migration or
|
||||||
|
rebuild is introduced.
|
||||||
|
|
||||||
|
## Diagnostics and observability
|
||||||
|
|
||||||
|
Pending count preserves the existing `wmobuild` metric contribution. The snapshot
|
||||||
|
exposes sorted scene/missing/request keys only. Existing oversize logging remains
|
||||||
|
in the loader; the state emits no logs.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `verify_wmo_scene_resource_cache_state.gd`: invalid input, direct missing,
|
||||||
|
exclusivity, request completion, partial/full reset, diagnostics, boundaries
|
||||||
|
and bounded timing.
|
||||||
|
- `verify_render_runtime_cache_shutdown.gd`: loader full release of scenes,
|
||||||
|
negative and pending state.
|
||||||
|
- Adjacent WMO/renderer regressions verify unchanged lookup/fallback behavior.
|
||||||
|
This extraction makes no visual or build-12340 parity claim.
|
||||||
|
|
||||||
|
## Extension points
|
||||||
|
|
||||||
|
- A later I/O adapter may consume request snapshots while retaining this contract.
|
||||||
|
- Scene instantiation/material preparation can be extracted separately because it
|
||||||
|
owns Nodes and main-thread side effects rather than cache state.
|
||||||
|
|
||||||
|
## Capability status
|
||||||
|
|
||||||
|
| Capability | Status | Evidence | Gap/next step |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Cached WMO PackedScene state | Implemented extraction | Lifecycle/source/timing and shutdown verifiers | Asset-backed traversal/leak evidence pending |
|
||||||
|
| Size and cache metadata validation | Preserved in loader | Source boundary and WMO regressions | Oversize/stale asset fixtures could follow |
|
||||||
|
| ResourceLoader I/O and live fallback | Partial/loader-owned | Existing runtime behavior | Separate extraction if justified |
|
||||||
|
|
||||||
|
## Known gaps and risks
|
||||||
|
|
||||||
|
- PackedScenes remain strongly referenced until final shutdown cache release.
|
||||||
|
- Negative caching suppresses retries until transient reset, matching prior behavior.
|
||||||
|
- No proprietary WMO corpus, stale/oversize asset fixture, leak run, traversal
|
||||||
|
p95/p99 or paired original-client capture is included.
|
||||||
|
|
||||||
|
## Source map
|
||||||
|
|
||||||
|
| Path | Responsibility |
|
||||||
|
|---|---|
|
||||||
|
| `src/render/wmo/wmo_scene_resource_cache_state.gd` | Scene/missing/request state and resets |
|
||||||
|
| `src/scenes/streaming/streaming_world_loader.gd` | Paths, file limit, requests, validation, fallback and Node lifetime |
|
||||||
|
| `src/tools/verify_wmo_scene_resource_cache_state.gd` | State, boundary and timing regression |
|
||||||
|
| `src/tools/verify_render_runtime_cache_shutdown.gd` | Loader final cache ownership regression |
|
||||||
|
|
||||||
|
## Related decisions and references
|
||||||
|
|
||||||
|
- [`wmo-render-resource-cache-state.md`](wmo-render-resource-cache-state.md)
|
||||||
|
- [`wmo-render-build-queue.md`](wmo-render-build-queue.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-resource-cache`, 2026-07-17 |
|
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-scene-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
|
||||||
@@ -137,6 +137,7 @@ from externally reading/writing loader-private queue, task, cache and tile-state
|
|||||||
| `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 |
|
| `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 |
|
||||||
|
| `WmoSceneResourceCacheState` | Internal WMO cache-state service | Owns validated PackedScenes, negative entries and pending `.tscn` paths | Renderer main thread; map/cache session | Direct missing and terminal request transitions remain distinct |
|
||||||
|
|
||||||
Публичным 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
|
||||||
@@ -170,6 +171,7 @@ loader configuration remains transitional composition data, not a caller API.
|
|||||||
| 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 |
|
| 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 |
|
||||||
|
| Internal WMO scene cache | Normalized path, `.tscn` path and validated PackedScene | Loader / `WmoSceneResourceCacheState` | Loader lookup, request poll and scene instantiation | State-owned PackedScene/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 |
|
||||||
@@ -350,9 +352,10 @@ sequenceDiagram
|
|||||||
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.
|
strong root/resource references without freeing engine objects.
|
||||||
`WmoRenderResourceCacheState` owns validated render Resources, negative entries
|
`WmoRenderResourceCacheState` owns validated render Resources, negative entries
|
||||||
and pending cache paths. The loader retains ResourceLoader I/O, script/version
|
and pending cache paths. `WmoSceneResourceCacheState` similarly owns validated
|
||||||
validation, cached-scene/live fallback, materialization, permits, validity
|
PackedScenes, negative entries and pending `.tscn` paths. The loader retains
|
||||||
reactions and every Node lifecycle action.
|
ResourceLoader/FileAccess I/O, size and cache-version validation, 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
|
||||||
@@ -455,6 +458,9 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
|||||||
- WMO render Resource cache contract: invalid/duplicate request rejection,
|
- WMO render Resource cache contract: invalid/duplicate request rejection,
|
||||||
validated/missing terminal transitions, transient/full reset, detached sorted
|
validated/missing terminal transitions, transient/full reset, detached sorted
|
||||||
diagnostics, loader-owned version validation and bounded timing.
|
diagnostics, loader-owned version validation and bounded timing.
|
||||||
|
- WMO scene Resource cache contract: direct missing sources, pending terminal
|
||||||
|
transitions, transient/full reset, detached diagnostics, loader-owned size/
|
||||||
|
metadata 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.
|
||||||
@@ -492,7 +498,8 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
|||||||
| 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 | Materialization 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 render Resource cache state | Implemented extraction | Scene-free lifecycle/exclusivity/source/timing plus shutdown contract | ResourceLoader I/O and asset-backed traversal/leak evidence pending |
|
||||||
|
| WMO scene Resource cache state | Implemented extraction | Scene-free lifecycle/direct-missing/source/timing plus shutdown contract | ResourceLoader/live-fallback 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,134 @@
|
|||||||
|
class_name WmoSceneResourceCacheState
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
## Owns validated cached-WMO PackedScenes and their load state. File limits,
|
||||||
|
## ResourceLoader I/O and scene-cache validation remain caller responsibilities.
|
||||||
|
|
||||||
|
var _scenes_by_normalized_path: Dictionary = {}
|
||||||
|
var _missing_normalized_paths: Dictionary = {}
|
||||||
|
var _request_cache_paths_by_normalized_path: Dictionary = {}
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the validated PackedScene for a normalized WMO path, or null.
|
||||||
|
func scene_for(normalized_relative_path: String) -> PackedScene:
|
||||||
|
if normalized_relative_path.is_empty():
|
||||||
|
return null
|
||||||
|
return _scenes_by_normalized_path.get(normalized_relative_path) as PackedScene
|
||||||
|
|
||||||
|
|
||||||
|
## Returns whether a validated PackedScene is cached for the normalized path.
|
||||||
|
func contains_scene(normalized_relative_path: String) -> bool:
|
||||||
|
return (
|
||||||
|
not normalized_relative_path.is_empty()
|
||||||
|
and _scenes_by_normalized_path.has(normalized_relative_path)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
## Returns whether the normalized 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 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-started request. Occupied or empty paths are rejected.
|
||||||
|
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 _has_any_state(normalized_relative_path):
|
||||||
|
return false
|
||||||
|
_request_cache_paths_by_normalized_path[normalized_relative_path] = cache_path
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
## Records an absent, oversize or request-start failure without requiring a
|
||||||
|
## pending request. Occupied and empty paths are rejected.
|
||||||
|
func mark_missing(normalized_relative_path: String) -> bool:
|
||||||
|
if normalized_relative_path.is_empty() or _has_any_state(normalized_relative_path):
|
||||||
|
return false
|
||||||
|
_missing_normalized_paths[normalized_relative_path] = true
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
## Returns a detached normalized-path to cache-path snapshot for loader polling.
|
||||||
|
func request_paths_snapshot() -> Dictionary:
|
||||||
|
return _request_cache_paths_by_normalized_path.duplicate()
|
||||||
|
|
||||||
|
|
||||||
|
## Completes a pending request with a caller-validated PackedScene.
|
||||||
|
func complete_request_with_scene(
|
||||||
|
normalized_relative_path: String,
|
||||||
|
validated_scene: PackedScene
|
||||||
|
) -> bool:
|
||||||
|
if (
|
||||||
|
normalized_relative_path.is_empty()
|
||||||
|
or validated_scene == 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)
|
||||||
|
_scenes_by_normalized_path[normalized_relative_path] = validated_scene
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
## Completes a pending request as missing after load or validation failure.
|
||||||
|
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)
|
||||||
|
_scenes_by_normalized_path.erase(normalized_relative_path)
|
||||||
|
_missing_normalized_paths[normalized_relative_path] = true
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
## Clears pending and negative state while retaining validated PackedScenes.
|
||||||
|
func clear_transient_state() -> void:
|
||||||
|
_request_cache_paths_by_normalized_path.clear()
|
||||||
|
_missing_normalized_paths.clear()
|
||||||
|
|
||||||
|
|
||||||
|
## Releases every cached scene and transient entry. Safe to call repeatedly.
|
||||||
|
func clear_all() -> void:
|
||||||
|
_scenes_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 PackedScene references.
|
||||||
|
func diagnostic_snapshot() -> Dictionary:
|
||||||
|
var scene_paths: Array = _scenes_by_normalized_path.keys()
|
||||||
|
var missing_paths: Array = _missing_normalized_paths.keys()
|
||||||
|
var request_paths: Array = _request_cache_paths_by_normalized_path.keys()
|
||||||
|
scene_paths.sort()
|
||||||
|
missing_paths.sort()
|
||||||
|
request_paths.sort()
|
||||||
|
return {
|
||||||
|
"scene_paths": scene_paths.duplicate(),
|
||||||
|
"missing_paths": missing_paths.duplicate(),
|
||||||
|
"request_paths": request_paths.duplicate(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func _has_any_state(normalized_relative_path: String) -> bool:
|
||||||
|
return (
|
||||||
|
_scenes_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)
|
||||||
|
)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b4geepupdscu7
|
||||||
@@ -24,6 +24,9 @@ const WMO_RENDER_BUILD_QUEUE_SCRIPT := preload(
|
|||||||
const WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT := preload(
|
const WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT := preload(
|
||||||
"res://src/render/wmo/wmo_render_resource_cache_state.gd"
|
"res://src/render/wmo/wmo_render_resource_cache_state.gd"
|
||||||
)
|
)
|
||||||
|
const WMO_SCENE_RESOURCE_CACHE_STATE_SCRIPT := preload(
|
||||||
|
"res://src/render/wmo/wmo_scene_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")
|
||||||
@@ -285,9 +288,7 @@ var _terrain_quality_mesh_cache: RefCounted = TERRAIN_QUALITY_MESH_CACHE_SCRIPT.
|
|||||||
var _wmo_prototype_cache: Dictionary = {}
|
var _wmo_prototype_cache: Dictionary = {}
|
||||||
var _wmo_render_resource_cache_state := WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT.new()
|
var _wmo_render_resource_cache_state := WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT.new()
|
||||||
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_state := WMO_SCENE_RESOURCE_CACHE_STATE_SCRIPT.new()
|
||||||
var _wmo_scene_cache_missing: Dictionary = {}
|
|
||||||
var _wmo_scene_load_requests: Dictionary = {}
|
|
||||||
var _wmo_missing_cache: Dictionary = {}
|
var _wmo_missing_cache: Dictionary = {}
|
||||||
var _wmo_placement_resolver := WMO_PLACEMENT_RESOLVER_SCRIPT.new()
|
var _wmo_placement_resolver := WMO_PLACEMENT_RESOLVER_SCRIPT.new()
|
||||||
var _m2_scene_cache: Dictionary = {}
|
var _m2_scene_cache: Dictionary = {}
|
||||||
@@ -469,8 +470,7 @@ func _release_runtime_caches_for_shutdown() -> void:
|
|||||||
_m2_static_animation_cache.clear()
|
_m2_static_animation_cache.clear()
|
||||||
_m2_missing_cache.clear()
|
_m2_missing_cache.clear()
|
||||||
_wmo_render_resource_cache_state.clear_all()
|
_wmo_render_resource_cache_state.clear_all()
|
||||||
_wmo_scene_resource_cache.clear()
|
_wmo_scene_resource_cache_state.clear_all()
|
||||||
_wmo_scene_cache_missing.clear()
|
|
||||||
_wmo_missing_cache.clear()
|
_wmo_missing_cache.clear()
|
||||||
_shared_tex_cache.clear()
|
_shared_tex_cache.clear()
|
||||||
|
|
||||||
@@ -1007,7 +1007,7 @@ func _log_hitch_profile(start_usec: int, timings: Array[String], did_refresh: bo
|
|||||||
_m2_build_queue.size(),
|
_m2_build_queue.size(),
|
||||||
(
|
(
|
||||||
_wmo_build_queue.size()
|
_wmo_build_queue.size()
|
||||||
+ _wmo_scene_load_requests.size()
|
+ _wmo_scene_resource_cache_state.pending_request_count()
|
||||||
+ _wmo_render_resource_cache_state.pending_request_count()
|
+ _wmo_render_resource_cache_state.pending_request_count()
|
||||||
),
|
),
|
||||||
_wmo_render_build_queue.pending_count(),
|
_wmo_render_build_queue.pending_count(),
|
||||||
@@ -1038,7 +1038,7 @@ func render_baseline_snapshot() -> Dictionary:
|
|||||||
"m2_build": _m2_build_queue.size(),
|
"m2_build": _m2_build_queue.size(),
|
||||||
"wmo_build": (
|
"wmo_build": (
|
||||||
_wmo_build_queue.size()
|
_wmo_build_queue.size()
|
||||||
+ _wmo_scene_load_requests.size()
|
+ _wmo_scene_resource_cache_state.pending_request_count()
|
||||||
+ _wmo_render_resource_cache_state.pending_request_count()
|
+ _wmo_render_resource_cache_state.pending_request_count()
|
||||||
),
|
),
|
||||||
"wmo_groups": _wmo_render_build_queue.pending_count(),
|
"wmo_groups": _wmo_render_build_queue.pending_count(),
|
||||||
@@ -1324,7 +1324,7 @@ func _tick_runtime_stats(delta: float) -> void:
|
|||||||
_m2_build_queue.size(),
|
_m2_build_queue.size(),
|
||||||
(
|
(
|
||||||
_wmo_build_queue.size()
|
_wmo_build_queue.size()
|
||||||
+ _wmo_scene_load_requests.size()
|
+ _wmo_scene_resource_cache_state.pending_request_count()
|
||||||
+ _wmo_render_resource_cache_state.pending_request_count()
|
+ _wmo_render_resource_cache_state.pending_request_count()
|
||||||
),
|
),
|
||||||
_wmo_render_build_queue.pending_count(),
|
_wmo_render_build_queue.pending_count(),
|
||||||
@@ -2359,14 +2359,15 @@ func _wait_for_tile_tasks() -> void:
|
|||||||
_m2_animation_load_requests.clear()
|
_m2_animation_load_requests.clear()
|
||||||
_m2_animation_finalize_queue.clear()
|
_m2_animation_finalize_queue.clear()
|
||||||
|
|
||||||
for path_variant in _wmo_scene_load_requests.values():
|
var wmo_scene_request_paths := _wmo_scene_resource_cache_state.request_paths_snapshot()
|
||||||
|
for path_variant in wmo_scene_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_scene_load_requests.clear()
|
_wmo_scene_resource_cache_state.clear_transient_state()
|
||||||
|
|
||||||
var wmo_render_request_paths := (
|
var wmo_render_request_paths := (
|
||||||
_wmo_render_resource_cache_state.request_paths_snapshot()
|
_wmo_render_resource_cache_state.request_paths_snapshot()
|
||||||
@@ -2379,7 +2380,6 @@ func _wait_for_tile_tasks() -> void:
|
|||||||
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_resource_cache_state.clear_transient_state()
|
_wmo_render_resource_cache_state.clear_transient_state()
|
||||||
_wmo_scene_cache_missing.clear()
|
|
||||||
_wmo_render_build_queue.clear()
|
_wmo_render_build_queue.clear()
|
||||||
|
|
||||||
for pending in _terrain_upgrade_tasks.values():
|
for pending in _terrain_upgrade_tasks.values():
|
||||||
@@ -3015,9 +3015,8 @@ func _clear_streamed_world() -> void:
|
|||||||
_m2_animation_finalize_queue.clear()
|
_m2_animation_finalize_queue.clear()
|
||||||
_wmo_build_jobs.clear()
|
_wmo_build_jobs.clear()
|
||||||
_wmo_build_queue.clear()
|
_wmo_build_queue.clear()
|
||||||
_wmo_scene_load_requests.clear()
|
_wmo_scene_resource_cache_state.clear_transient_state()
|
||||||
_wmo_render_resource_cache_state.clear_transient_state()
|
_wmo_render_resource_cache_state.clear_transient_state()
|
||||||
_wmo_scene_cache_missing.clear()
|
|
||||||
_wmo_render_build_queue.clear()
|
_wmo_render_build_queue.clear()
|
||||||
|
|
||||||
_tile_result_mutex.lock()
|
_tile_result_mutex.lock()
|
||||||
@@ -3607,7 +3606,7 @@ func _process_wmo_build_jobs() -> void:
|
|||||||
else:
|
else:
|
||||||
var scene := _get_wmo_scene_or_request(rel_path)
|
var scene := _get_wmo_scene_or_request(rel_path)
|
||||||
if scene == null:
|
if scene == null:
|
||||||
if _wmo_scene_load_requests.has(normalized_rel):
|
if _wmo_scene_resource_cache_state.has_request(normalized_rel):
|
||||||
advance_job = false
|
advance_job = false
|
||||||
else:
|
else:
|
||||||
var live_instance := _instantiate_wmo_world(rel_path, placement)
|
var live_instance := _instantiate_wmo_world(rel_path, placement)
|
||||||
@@ -3662,21 +3661,24 @@ func _process_wmo_build_jobs() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _drain_wmo_scene_loads() -> void:
|
func _drain_wmo_scene_loads() -> void:
|
||||||
for normalized_rel_variant in _wmo_scene_load_requests.keys():
|
var request_paths := _wmo_scene_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_scene_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_scene_load_requests.erase(normalized_rel)
|
|
||||||
if status != ResourceLoader.THREAD_LOAD_LOADED:
|
if status != ResourceLoader.THREAD_LOAD_LOADED:
|
||||||
_wmo_scene_cache_missing[normalized_rel] = true
|
_wmo_scene_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 resource is PackedScene and _is_wmo_scene_cache_current(resource as PackedScene):
|
if resource is PackedScene and _is_wmo_scene_cache_current(resource as PackedScene):
|
||||||
_wmo_scene_resource_cache[normalized_rel] = resource as PackedScene
|
_wmo_scene_resource_cache_state.complete_request_with_scene(
|
||||||
|
normalized_rel,
|
||||||
|
resource as PackedScene
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
_wmo_scene_cache_missing[normalized_rel] = true
|
_wmo_scene_resource_cache_state.complete_request_as_missing(normalized_rel)
|
||||||
|
|
||||||
|
|
||||||
func _drain_wmo_render_loads() -> void:
|
func _drain_wmo_render_loads() -> void:
|
||||||
@@ -3733,23 +3735,23 @@ func _get_wmo_scene_or_request(rel_path: String) -> PackedScene:
|
|||||||
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_scene_resource_cache.has(normalized_rel):
|
if _wmo_scene_resource_cache_state.contains_scene(normalized_rel):
|
||||||
return _wmo_scene_resource_cache[normalized_rel]
|
return _wmo_scene_resource_cache_state.scene_for(normalized_rel)
|
||||||
if _wmo_scene_cache_missing.has(normalized_rel):
|
if _wmo_scene_resource_cache_state.is_missing(normalized_rel):
|
||||||
return null
|
return null
|
||||||
if _wmo_scene_load_requests.has(normalized_rel):
|
if _wmo_scene_resource_cache_state.has_request(normalized_rel):
|
||||||
return null
|
return null
|
||||||
|
|
||||||
var cache_path := wmo_cache_dir.path_join(normalized_rel.get_basename() + ".tscn")
|
var cache_path := wmo_cache_dir.path_join(normalized_rel.get_basename() + ".tscn")
|
||||||
if not ResourceLoader.exists(cache_path):
|
if not ResourceLoader.exists(cache_path):
|
||||||
_wmo_scene_cache_missing[normalized_rel] = true
|
_wmo_scene_resource_cache_state.mark_missing(normalized_rel)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
if wmo_max_runtime_scene_mb > 0.0:
|
if wmo_max_runtime_scene_mb > 0.0:
|
||||||
var size_bytes := _get_resource_file_size(cache_path)
|
var size_bytes := _get_resource_file_size(cache_path)
|
||||||
var max_bytes := int(wmo_max_runtime_scene_mb * 1024.0 * 1024.0)
|
var max_bytes := int(wmo_max_runtime_scene_mb * 1024.0 * 1024.0)
|
||||||
if size_bytes > max_bytes:
|
if size_bytes > max_bytes:
|
||||||
_wmo_scene_cache_missing[normalized_rel] = true
|
_wmo_scene_resource_cache_state.mark_missing(normalized_rel)
|
||||||
if debug_streaming:
|
if debug_streaming:
|
||||||
print("StreamingWorld: skipping large WMO %.2f MB: %s" % [
|
print("StreamingWorld: skipping large WMO %.2f MB: %s" % [
|
||||||
float(size_bytes) / (1024.0 * 1024.0),
|
float(size_bytes) / (1024.0 * 1024.0),
|
||||||
@@ -3763,9 +3765,9 @@ func _get_wmo_scene_or_request(rel_path: String) -> PackedScene:
|
|||||||
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_scene_load_requests[normalized_rel] = cache_path
|
_wmo_scene_resource_cache_state.remember_request(normalized_rel, cache_path)
|
||||||
else:
|
else:
|
||||||
_wmo_scene_cache_missing[normalized_rel] = true
|
_wmo_scene_resource_cache_state.mark_missing(normalized_rel)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,10 @@ func _verify_runtime_cache_ownership() -> void:
|
|||||||
wmo_render_cache_state.call("complete_request_with_resource", "render", Resource.new())
|
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("remember_request", "missing", "res://missing.res")
|
||||||
wmo_render_cache_state.call("complete_request_as_missing", "missing")
|
wmo_render_cache_state.call("complete_request_as_missing", "missing")
|
||||||
loader.set("_wmo_scene_resource_cache", {"scene": PackedScene.new()})
|
var wmo_scene_cache_state: RefCounted = loader.get("_wmo_scene_resource_cache_state")
|
||||||
loader.set("_wmo_scene_cache_missing", {"missing": true})
|
wmo_scene_cache_state.call("remember_request", "scene", "res://scene.tscn")
|
||||||
|
wmo_scene_cache_state.call("complete_request_with_scene", "scene", PackedScene.new())
|
||||||
|
wmo_scene_cache_state.call("mark_missing", "missing")
|
||||||
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()})
|
||||||
|
|
||||||
@@ -60,8 +62,6 @@ 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_scene_resource_cache",
|
|
||||||
"_wmo_scene_cache_missing",
|
|
||||||
"_wmo_missing_cache",
|
"_wmo_missing_cache",
|
||||||
"_shared_tex_cache",
|
"_shared_tex_cache",
|
||||||
]:
|
]:
|
||||||
@@ -80,6 +80,12 @@ func _verify_runtime_cache_ownership() -> void:
|
|||||||
(wmo_render_snapshot["request_paths"] as Array).is_empty(),
|
(wmo_render_snapshot["request_paths"] as Array).is_empty(),
|
||||||
"WMO render requests should be empty after shutdown"
|
"WMO render requests should be empty after shutdown"
|
||||||
)
|
)
|
||||||
|
var wmo_scene_snapshot: Dictionary = wmo_scene_cache_state.call("diagnostic_snapshot")
|
||||||
|
for state_key in ["scene_paths", "missing_paths", "request_paths"]:
|
||||||
|
_expect(
|
||||||
|
(wmo_scene_snapshot[state_key] as Array).is_empty(),
|
||||||
|
"WMO scene %s should be empty after shutdown" % state_key
|
||||||
|
)
|
||||||
|
|
||||||
loader.free()
|
loader.free()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,363 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
## Asset-free state, ownership, dependency and timing regression for cached WMO scenes.
|
||||||
|
|
||||||
|
const CACHE_STATE_SCRIPT := preload(
|
||||||
|
"res://src/render/wmo/wmo_scene_resource_cache_state.gd"
|
||||||
|
)
|
||||||
|
const CACHE_STATE_PATH := "res://src/render/wmo/wmo_scene_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_direct_missing_and_exclusivity(failures)
|
||||||
|
_verify_validated_scene_completion(failures)
|
||||||
|
_verify_failed_request_completion(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_SCENE_RESOURCE_CACHE_STATE: %s" % failure)
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
print(
|
||||||
|
"WMO_SCENE_RESOURCE_CACHE_STATE PASS cases=11 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/a.tscn"),
|
||||||
|
"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.tscn"),
|
||||||
|
"valid request accepted",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("has_request", "world/a.wmo"),
|
||||||
|
"pending request observable",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_equal(
|
||||||
|
int(cache_state.call("pending_request_count")),
|
||||||
|
1,
|
||||||
|
"pending request count",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("remember_request", "world/a.wmo", "res://cache/other.tscn"),
|
||||||
|
"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.tscn",
|
||||||
|
"request cache path preserved",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
request_snapshot.clear()
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("has_request", "world/a.wmo"),
|
||||||
|
"request snapshot detached",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_direct_missing_and_exclusivity(failures: Array[String]) -> void:
|
||||||
|
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
|
||||||
|
_expect_false(cache_state.call("mark_missing", ""), "empty missing rejected", failures)
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("mark_missing", "world/missing.wmo"),
|
||||||
|
"direct missing accepted",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("is_missing", "world/missing.wmo"),
|
||||||
|
"direct missing observable",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("mark_missing", "world/missing.wmo"),
|
||||||
|
"duplicate missing rejected",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call(
|
||||||
|
"remember_request",
|
||||||
|
"world/missing.wmo",
|
||||||
|
"res://cache/retry.tscn"
|
||||||
|
),
|
||||||
|
"missing path rejects request",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_validated_scene_completion(failures: Array[String]) -> void:
|
||||||
|
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
|
||||||
|
var validated_scene := PackedScene.new()
|
||||||
|
cache_state.call("remember_request", "world/b.wmo", "res://cache/b.tscn")
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("complete_request_with_scene", "world/b.wmo", null),
|
||||||
|
"null completion rejected",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("complete_request_with_scene", "world/b.wmo", validated_scene),
|
||||||
|
"validated PackedScene completes request",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("has_request", "world/b.wmo"),
|
||||||
|
"successful completion removes request",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("contains_scene", "world/b.wmo"),
|
||||||
|
"successful completion cached",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("scene_for", "world/b.wmo") == validated_scene,
|
||||||
|
"cached PackedScene identity preserved",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("mark_missing", "world/b.wmo"),
|
||||||
|
"cached scene rejects missing state",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_failed_request_completion(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 request completion rejected",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
cache_state.call("remember_request", "world/failed.wmo", "res://cache/failed.tscn")
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("complete_request_as_missing", "world/failed.wmo"),
|
||||||
|
"failed request completes as missing",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("is_missing", "world/failed.wmo"),
|
||||||
|
"failed request negative-cached",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("has_request", "world/failed.wmo"),
|
||||||
|
"failed completion removes request",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_transient_and_full_clear(failures: Array[String]) -> void:
|
||||||
|
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
|
||||||
|
var retained_scene := PackedScene.new()
|
||||||
|
cache_state.call("remember_request", "world/retained.wmo", "res://retained.tscn")
|
||||||
|
cache_state.call("complete_request_with_scene", "world/retained.wmo", retained_scene)
|
||||||
|
cache_state.call("remember_request", "world/pending.wmo", "res://pending.tscn")
|
||||||
|
cache_state.call("mark_missing", "world/missing.wmo")
|
||||||
|
cache_state.call("clear_transient_state")
|
||||||
|
_expect_true(
|
||||||
|
cache_state.call("scene_for", "world/retained.wmo") == retained_scene,
|
||||||
|
"transient clear retains PackedScene",
|
||||||
|
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 missing state",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
cache_state.call("clear_all")
|
||||||
|
cache_state.call("clear_all")
|
||||||
|
_expect_false(
|
||||||
|
cache_state.call("contains_scene", "world/retained.wmo"),
|
||||||
|
"full clear idempotently releases PackedScene",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_detached_sorted_diagnostics(failures: Array[String]) -> void:
|
||||||
|
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
|
||||||
|
cache_state.call("remember_request", "world/z.wmo", "res://z.tscn")
|
||||||
|
cache_state.call("remember_request", "world/a.wmo", "res://a.tscn")
|
||||||
|
cache_state.call("mark_missing", "world/m.wmo")
|
||||||
|
var snapshot: Dictionary = cache_state.call("diagnostic_snapshot")
|
||||||
|
_expect_string_array(
|
||||||
|
snapshot["request_paths"],
|
||||||
|
["world/a.wmo", "world/z.wmo"],
|
||||||
|
"request diagnostics sorted",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_string_array(
|
||||||
|
snapshot["missing_paths"],
|
||||||
|
["world/m.wmo"],
|
||||||
|
"missing diagnostics",
|
||||||
|
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_SCENE_RESOURCE_CACHE_STATE_SCRIPT.new()"),
|
||||||
|
"loader composes scene cache state",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
for legacy_field in [
|
||||||
|
"var _wmo_scene_resource_cache:",
|
||||||
|
"var _wmo_scene_cache_missing:",
|
||||||
|
"var _wmo_scene_load_requests:",
|
||||||
|
]:
|
||||||
|
_expect_false(
|
||||||
|
loader_source.contains(legacy_field),
|
||||||
|
"loader removes %s" % legacy_field,
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
for loader_owned_token in [
|
||||||
|
"ResourceLoader.",
|
||||||
|
"FileAccess.",
|
||||||
|
"WMO_BUILDER_SCRIPT",
|
||||||
|
"instantiate()",
|
||||||
|
"Node3D",
|
||||||
|
"RID",
|
||||||
|
"Mutex",
|
||||||
|
"Semaphore",
|
||||||
|
]:
|
||||||
|
_expect_false(
|
||||||
|
cache_state_source.contains(loader_owned_token),
|
||||||
|
"scene cache state omits %s dependency" % loader_owned_token,
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
for retained_loader_rule in [
|
||||||
|
"wmo_max_runtime_scene_mb",
|
||||||
|
"_get_resource_file_size(cache_path)",
|
||||||
|
"_is_wmo_scene_cache_current(resource as PackedScene)",
|
||||||
|
"scene.instantiate() as Node3D",
|
||||||
|
"WMO_BUILDER_SCRIPT.is_scene_cache_current(node)",
|
||||||
|
]:
|
||||||
|
_expect_true(
|
||||||
|
loader_source.contains(retained_loader_rule),
|
||||||
|
"loader retains %s" % retained_loader_rule,
|
||||||
|
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 scene_index in range(256):
|
||||||
|
var normalized_path := "world/object_%d.wmo" % scene_index
|
||||||
|
if scene_index % 3 == 0:
|
||||||
|
cache_state.call("mark_missing", normalized_path)
|
||||||
|
else:
|
||||||
|
cache_state.call(
|
||||||
|
"remember_request",
|
||||||
|
normalized_path,
|
||||||
|
"res://cache/object_%d.tscn" % scene_index
|
||||||
|
)
|
||||||
|
if scene_index % 2 == 0:
|
||||||
|
cache_state.call(
|
||||||
|
"complete_request_with_scene",
|
||||||
|
normalized_path,
|
||||||
|
PackedScene.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 scene/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://ccuuoqli83f6f
|
||||||
Reference in New Issue
Block a user