Skip to content

Commit 0b81e1f

Browse files
author
DavidQ
committed
BUILD_PR_LEVEL_09_17_ASSET_PERFORMANCE_OPTIMIZATION
1 parent 874847e commit 0b81e1f

10 files changed

Lines changed: 111 additions & 54 deletions

docs/dev/CODEX_COMMANDS.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
MODEL: GPT-5.4
22
REASONING: high
33
COMMAND:
4-
- add debug visibility hooks for asset pipeline
5-
- expose manifest + lookup state
4+
- add caching to asset lookup
5+
- optimize manifest access
6+
- avoid redundant loads
67
- roadmap status only update

docs/dev/COMMIT_COMMENT.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
BUILD_PR_LEVEL_09_16_ASSET_DEBUG_VISIBILITY
1+
BUILD_PR_LEVEL_09_17_ASSET_PERFORMANCE_OPTIMIZATION
22

3-
Adds debug visibility hooks for asset pipeline + runtime lookup, exposes manifest and lookup state snapshots in debug reporting, and applies a roadmap status-only bracket update.
3+
Adds runtime asset lookup caching, optimizes manifest/binding access patterns, avoids redundant resolution work in touched layers, and applies a roadmap status-only bracket update.

docs/dev/NEXT_COMMAND.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
BUILD_PR_LEVEL_09_17_ASSET_PERFORMANCE_OPTIMIZATION
1+
BUILD_PR_LEVEL_09_18_ASSET_BUNDLE_PREPARATION
Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,22 @@
11
Summary
2-
- Added debug visibility hooks for asset pipeline and runtime lookup layers.
3-
- Exposed manifest + lookup state snapshots through shared debug reporting.
4-
- Kept scope surgical with no engine/runtime behavior redesign.
2+
- Added caching to runtime asset lookup resolution to avoid redundant repeated resolution/validation work.
3+
- Optimized manifest and binding access patterns by pre-indexing lookup data and reusing manifest snapshot state.
4+
- Reduced redundant per-call cloning/lookup overhead in touched runtime asset consumers through shared lookup improvements.
55

66
Implementation
77
- Updated `tools/shared/pipeline/runtimeAssetLookup.js`:
8-
- exposes `gameAssetManifest`
9-
- adds `getDebugState()` snapshot with lookup counters, manifest, and propagated errors
10-
- Updated `tools/shared/pipeline/assetPipelineTooling.js`:
11-
- adds `debugState` snapshot in both ready and invalid paths
12-
- includes stage/status/record/error summaries and coordinated manifest reference when available
13-
- Updated `tools/shared/debugVisualizationLayer.js`:
14-
- added sections `Asset Runtime State` and `Asset Pipeline State`
15-
- renders manifest-domain and lookup/pipeline summary lines for debug visibility
16-
- Updated touched runtime consumers to pass lookup debug state into debug visualization:
17-
- `tools/shared/asteroidsPlatformDemo.js`
18-
- `tools/shared/vectorNativeTemplate.js`
19-
- `tools/shared/vectorTemplateSampleGame.js`
20-
- `tools/shared/vectorAssetSystem.js`
21-
22-
Tests
23-
- Updated `tests/tools/DebugVisualizationLayer.test.mjs`
24-
- Updated `tests/tools/RuntimeAssetLookupConsolidation.test.mjs`
25-
- Updated `tests/tools/AssetPipelineTooling.test.mjs`
8+
- adds per-asset resolution cache (`hits/misses/size`) to avoid redundant loads/resolution
9+
- adds static source cache to avoid repeated deep clone work per request
10+
- pre-indexes binding records by domain+asset id for faster access
11+
- reuses manifest snapshot for debug state to avoid repeated manifest recomputation
12+
- exposes cache stats via `getDebugState().lookup.cache`
13+
- Updated `tools/shared/pipeline/runtimeAssetBinding.js`:
14+
- adds WeakMap-backed indexed resolution in `resolveRuntimeAsset` for efficient repeated lookup
15+
- Updated `tests/tools/RuntimeAssetLookupConsolidation.test.mjs`:
16+
- validates cache hit/miss behavior and cache size
17+
- validates optimized lookup still preserves existing error/visibility behavior
2618

2719
Roadmap (status-only)
2820
- `docs/dev/roadmaps/MASTER_ROADMAP_HIGH_LEVEL.md`
29-
- `debug tools align with engine/debug maturity` moved from `[.]` to `[x]`
21+
- `content pipeline tools after asset complexity justifies them` moved from `[.]` to `[x]`
3022
- No roadmap prose/text/order edits.

docs/dev/reports/validation_checklist.txt

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
1-
Validation Checklist - BUILD_PR_LEVEL_09_16_ASSET_DEBUG_VISIBILITY
1+
Validation Checklist - BUILD_PR_LEVEL_09_17_ASSET_PERFORMANCE_OPTIMIZATION
22

33
Scope
4-
[x] Debug visibility hooks added for asset pipeline + runtime lookup
5-
[x] Manifest + lookup state exposed through debug layer
4+
[x] Caching added to shared asset lookup path
5+
[x] Manifest access optimized in touched lookup flow
6+
[x] Redundant resolution/load work avoided where touched
67
[x] No engine redesign
7-
[x] No gameplay/runtime feature expansion
8-
[x] No UI redesign work outside debug reporting sections
8+
[x] No UI changes
99

1010
Code
1111
[x] Updated `tools/shared/pipeline/runtimeAssetLookup.js`
12-
[x] Updated `tools/shared/pipeline/assetPipelineTooling.js`
13-
[x] Updated `tools/shared/debugVisualizationLayer.js`
14-
[x] Updated `tools/shared/asteroidsPlatformDemo.js`
15-
[x] Updated `tools/shared/vectorNativeTemplate.js`
16-
[x] Updated `tools/shared/vectorTemplateSampleGame.js`
17-
[x] Updated `tools/shared/vectorAssetSystem.js`
18-
[x] Updated `tests/tools/DebugVisualizationLayer.test.mjs`
12+
[x] Updated `tools/shared/pipeline/runtimeAssetBinding.js`
1913
[x] Updated `tests/tools/RuntimeAssetLookupConsolidation.test.mjs`
20-
[x] Updated `tests/tools/AssetPipelineTooling.test.mjs`
2114

2215
Validation
23-
[x] `node --check` passes on touched files
24-
[x] Focused tests pass: DebugVisualizationLayer, RuntimeAssetLookupConsolidation, AssetPipelineTooling
25-
[x] Existing regression tests pass: RuntimeAssetValidation, RuntimeAssetBinding, GameAssetManifestCoordinator, ProjectToolDataContracts
16+
[x] `node --check tools/shared/pipeline/runtimeAssetLookup.js`
17+
[x] `node --check tools/shared/pipeline/runtimeAssetBinding.js`
18+
[x] `node --check tests/tools/RuntimeAssetLookupConsolidation.test.mjs`
19+
[x] `node --check tests/tools/RuntimeAssetBinding.test.mjs`
20+
[x] `node --check tests/tools/AssetPipelineTooling.test.mjs`
21+
[x] Focused tests pass: RuntimeAssetLookupConsolidation, RuntimeAssetBinding, AssetPipelineTooling
22+
[x] Existing regression tests pass: RuntimeAssetValidation, GameAssetManifestCoordinator, ProjectToolDataContracts
2623
[x] Existing consumer tests pass: AsteroidsAssetReferenceAdoption, AsteroidsPlatformDemo, VectorNativeTemplate, VectorTemplateSampleGame, VectorAssetSystem
2724

2825
Roadmap

docs/dev/roadmaps/MASTER_ROADMAP_HIGH_LEVEL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@
447447
### Tooling Strategy By Need
448448
- [ ] 2D tool stabilization before 3D tool expansion
449449
- [ ] 3D prerequisite samples before advanced 3D tools
450-
- [.] content pipeline tools after asset complexity justifies them
450+
- [x] content pipeline tools after asset complexity justifies them
451451
- [x] debug tools align with engine/debug maturity
452452
- [ ] no standalone showcase-only tool tracks
453453

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# BUILD_PR — LEVEL 09_17 — ASSET PERFORMANCE OPTIMIZATION
2+
3+
Optimize asset loading and lookup performance using caching and efficient access patterns.

tests/tools/RuntimeAssetLookupConsolidation.test.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export async function run() {
3030
true
3131
);
3232

33+
assert.equal(strictLookup.resolvePackagedAsset({ id: "vector.ship", type: "vector" }).file, "games/asteroids/assets/vectors/ship.vector.json");
3334
assert.equal(strictLookup.resolvePackagedAsset({ id: "vector.ship", type: "vector" }).file, "games/asteroids/assets/vectors/ship.vector.json");
3435
assert.equal(strictLookup.resolvePackagedAsset({ id: "vector.bad", type: "vector" }), null);
3536
assert.equal(strictLookup.resolvePackagedAsset({ id: "vector.tool-only", type: "vector" }), null);
@@ -43,6 +44,9 @@ export async function run() {
4344
assert.equal(strictDebug.lookup.recordCount, 5);
4445
assert.equal(strictDebug.lookup.domainCounts.vectors, 2);
4546
assert.equal(Object.keys(strictDebug.manifest.domains).length, 4);
47+
assert.equal(strictDebug.lookup.cache.misses, 4);
48+
assert.equal(strictDebug.lookup.cache.hits, 1);
49+
assert.equal(strictDebug.lookup.cache.size, 4);
4650

4751
const fallbackLookup = createRuntimeManifestAssetLookup({
4852
gameId: "TemplateGame",
@@ -60,5 +64,7 @@ export async function run() {
6064
assert.equal(fallbackResolved.file, "tools/templates/vector-native-arcade/assets/data/vectors/template-player.vector.json");
6165
assert.equal(fallbackLookup.binding.domains.vectors.length, 0);
6266
assert.equal(fallbackLookup.getErrors().some((entry) => entry.code === "RUNTIME_BINDING_REJECTED"), true);
63-
assert.equal(fallbackLookup.getDebugState().lookup.rejectedCount > 0, true);
67+
const fallbackDebug = fallbackLookup.getDebugState();
68+
assert.equal(fallbackDebug.lookup.rejectedCount > 0, true);
69+
assert.equal(fallbackDebug.lookup.cache.misses, 1);
6470
}

tools/shared/pipeline/runtimeAssetBinding.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export const RUNTIME_ASSET_BINDING_SCHEMA = "html-js-gaming.runtime-asset-bindin
55
export const RUNTIME_ASSET_BINDING_VERSION = 1;
66

77
export const RUNTIME_ACTIVE_DOMAINS = Object.freeze(["sprites", "tilemaps", "parallax", "vectors"]);
8+
const RUNTIME_BINDING_INDEX_CACHE = new WeakMap();
89

910
function asObject(value) {
1011
return value && typeof value === "object" ? value : {};
@@ -120,6 +121,20 @@ export function resolveRuntimeAsset(bindingInput, options = {}) {
120121
if (!domain || !assetId) {
121122
return null;
122123
}
123-
const entries = asArray(asObject(binding.domains)[domain]);
124-
return entries.find((entry) => toSlug(entry.assetId, "") === assetId) || null;
124+
125+
let index = RUNTIME_BINDING_INDEX_CACHE.get(binding);
126+
if (!index) {
127+
index = {};
128+
RUNTIME_ACTIVE_DOMAINS.forEach((activeDomain) => {
129+
const domainMap = new Map();
130+
asArray(asObject(binding.domains)[activeDomain]).forEach((entry) => {
131+
domainMap.set(toSlug(entry?.assetId, ""), entry);
132+
});
133+
index[activeDomain] = domainMap;
134+
});
135+
RUNTIME_BINDING_INDEX_CACHE.set(binding, index);
136+
}
137+
138+
const domainMap = index[domain];
139+
return domainMap ? domainMap.get(assetId) || null : null;
125140
}

tools/shared/pipeline/runtimeAssetLookup.js

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ function toSlug(value, fallback = "asset") {
2525
return text || fallback;
2626
}
2727

28+
function createDomainIndex(binding) {
29+
const index = {};
30+
Object.entries(binding?.domains || {}).forEach(([domain, entries]) => {
31+
const domainMap = new Map();
32+
(Array.isArray(entries) ? entries : []).forEach((entry) => {
33+
domainMap.set(safeString(entry?.assetId, ""), entry);
34+
});
35+
index[domain] = domainMap;
36+
});
37+
return index;
38+
}
39+
2840
export function getRuntimeBindingDomain(assetId) {
2941
const normalizedId = safeString(assetId, "").toLowerCase();
3042
const matchedPrefix = Object.keys(RUNTIME_BINDING_PREFIXES)
@@ -75,6 +87,14 @@ export function createRuntimeManifestAssetLookup(options = {}) {
7587
records
7688
});
7789
const binding = createRuntimeAssetBinding(coordinatedManifest.manifest);
90+
const bindingIndex = createDomainIndex(binding);
91+
const manifestSnapshot = cloneJson(coordinatedManifest.manifest);
92+
const staticSourceCache = new Map();
93+
const resolutionCache = new Map();
94+
const cacheStats = {
95+
hits: 0,
96+
misses: 0
97+
};
7898
const missingBindingBehavior = options.missingBindingBehavior === "null" ? "null" : "static";
7999
const errors = [];
80100

@@ -105,27 +125,41 @@ export function createRuntimeManifestAssetLookup(options = {}) {
105125
return null;
106126
}
107127

128+
if (resolutionCache.has(assetId)) {
129+
cacheStats.hits += 1;
130+
return resolutionCache.get(assetId);
131+
}
132+
cacheStats.misses += 1;
133+
108134
if (safeString(asset?.type, "") === "image") {
109135
if (typeof options.resolveImageAsset === "function") {
110-
return options.resolveImageAsset(asset);
136+
const resolvedImage = options.resolveImageAsset(asset);
137+
resolutionCache.set(assetId, resolvedImage);
138+
return resolvedImage;
111139
}
112-
return {
140+
const defaultImage = {
113141
image: {
114142
width: 960,
115143
height: 720,
116144
src: safeString(asset?.path, "")
117145
},
118146
status: "provided-loaded"
119147
};
148+
resolutionCache.set(assetId, defaultImage);
149+
return defaultImage;
120150
}
121151

122-
const staticSource = runtimeAssetSources[assetId] ? cloneJson(runtimeAssetSources[assetId]) : null;
152+
if (!staticSourceCache.has(assetId)) {
153+
staticSourceCache.set(assetId, runtimeAssetSources[assetId] ? cloneJson(runtimeAssetSources[assetId]) : null);
154+
}
155+
const staticSource = staticSourceCache.get(assetId);
123156
const domain = getRuntimeBindingDomain(assetId);
124157
if (!domain) {
158+
resolutionCache.set(assetId, staticSource);
125159
return staticSource;
126160
}
127161

128-
const runtimeRecord = resolveRuntimeAsset(binding, { domain, assetId });
162+
const runtimeRecord = bindingIndex[domain]?.get(assetId) || resolveRuntimeAsset(binding, { domain, assetId });
129163
if (!runtimeRecord) {
130164
appendAssetError(errors, {
131165
code: "RUNTIME_LOOKUP_MISSING_BINDING",
@@ -134,7 +168,9 @@ export function createRuntimeManifestAssetLookup(options = {}) {
134168
assetId,
135169
message: `Missing runtime binding for ${assetId}.`
136170
});
137-
return missingBindingBehavior === "null" ? null : staticSource;
171+
const missingResolution = missingBindingBehavior === "null" ? null : staticSource;
172+
resolutionCache.set(assetId, missingResolution);
173+
return missingResolution;
138174
}
139175

140176
const mergedSource = staticSource || {};
@@ -151,9 +187,11 @@ export function createRuntimeManifestAssetLookup(options = {}) {
151187
});
152188
if (!validation.valid) {
153189
appendAssetErrors(errors, validation.errors);
190+
resolutionCache.set(assetId, null);
154191
return null;
155192
}
156193

194+
resolutionCache.set(assetId, mergedSource);
157195
return mergedSource;
158196
}
159197

@@ -175,9 +213,14 @@ export function createRuntimeManifestAssetLookup(options = {}) {
175213
Object.entries(binding.domains || {}).map(([domain, entries]) => [domain, Array.isArray(entries) ? entries.length : 0])
176214
),
177215
rejectedCount: Array.isArray(binding.rejected) ? binding.rejected.length : 0,
178-
errorCount: errors.length
216+
errorCount: errors.length,
217+
cache: {
218+
hits: cacheStats.hits,
219+
misses: cacheStats.misses,
220+
size: resolutionCache.size
221+
}
179222
},
180-
manifest: cloneJson(coordinatedManifest.manifest),
223+
manifest: manifestSnapshot,
181224
errors: errors.slice()
182225
};
183226
},

0 commit comments

Comments
 (0)