@carbonenginejs/runtime-resource 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/LICENSE +21 -21
  2. package/NOTICE +29 -29
  3. package/README.md +67 -548
  4. package/dist/CjsMotherLode.js +1271 -1271
  5. package/dist/CjsResMan.js +3005 -3005
  6. package/dist/CjsResManQueue.js +214 -214
  7. package/dist/CjsResource.js +566 -566
  8. package/dist/CjsResourceSource.js +59 -59
  9. package/dist/_virtual/_rollupPluginBabelHelpers.js +153 -153
  10. package/dist/format/CjsBlueReader.js +269 -269
  11. package/dist/format/CjsFormat.js +192 -192
  12. package/dist/format/CjsReader.js +18 -18
  13. package/dist/format/CjsResourceProbe.js +277 -277
  14. package/dist/format/payloadContract.js +173 -173
  15. package/dist/formats/black/CjsBlackFormat.js +278 -278
  16. package/dist/formats/black/core/CjsBlackBinaryReader.js +153 -153
  17. package/dist/formats/black/core/CjsBlackPropertyReaders.js +382 -382
  18. package/dist/formats/black/core/CjsBlackReader.js +639 -639
  19. package/dist/formats/black/core/CjsBlackSchemaRegistry.js +433 -433
  20. package/dist/formats/black/core/black-schema-v1-2026-07-11.json.js +4 -4
  21. package/dist/formats/black/core/blackConstants.js +7 -7
  22. package/dist/formats/black/core/blackDefinitions.js +10 -10
  23. package/dist/formats/black/core/blackEnums.js +6 -6
  24. package/dist/formats/black/core/blackSchema.js +3 -3
  25. package/dist/formats/black/core/blackVersion.js +22 -22
  26. package/dist/formats/black/core/helpers.js +182 -182
  27. package/dist/formats/black/core/schema.js +4 -4
  28. package/dist/formats/black/index.js +2 -2
  29. package/dist/formats/bnk/CjsBnkFormat.js +125 -125
  30. package/dist/formats/bnk/core/graph.js +140 -140
  31. package/dist/formats/bnk/core/helpers.js +409 -409
  32. package/dist/formats/bnk/core/musicNodes.js +489 -489
  33. package/dist/formats/bnk/core/soundbanksInfo.js +246 -246
  34. package/dist/formats/bnk/index.js +2 -2
  35. package/dist/formats/cmf/CjsCmfFormat.js +497 -497
  36. package/dist/formats/cmf/core/binary.js +118 -118
  37. package/dist/formats/cmf/core/buffers.js +233 -233
  38. package/dist/formats/cmf/core/constants.js +47 -47
  39. package/dist/formats/cmf/core/gr2Anim.js +453 -453
  40. package/dist/formats/cmf/core/helpers.js +287 -287
  41. package/dist/formats/cmf/core/pack.js +276 -276
  42. package/dist/formats/cmf/core/schema.js +364 -364
  43. package/dist/formats/cmf/core/shared.js +268 -268
  44. package/dist/formats/cmf/core/writer.js +517 -517
  45. package/dist/formats/cmf/index.js +2 -2
  46. package/dist/formats/dds/CjsDdsFormat.js +193 -193
  47. package/dist/formats/dds/core/bc6h.js +288 -288
  48. package/dist/formats/dds/core/bc7.js +251 -251
  49. package/dist/formats/dds/core/helpers.js +815 -815
  50. package/dist/formats/dds/index.js +2 -2
  51. package/dist/formats/fbx/CjsFbxFormat.js +266 -266
  52. package/dist/formats/fbx/core/helpers.js +3901 -3901
  53. package/dist/formats/fbx/index.js +2 -2
  54. package/dist/formats/flac/CjsFlacFormat.js +87 -87
  55. package/dist/formats/flac/core/helpers.js +295 -295
  56. package/dist/formats/flac/index.js +2 -2
  57. package/dist/formats/gif/CjsGifFormat.js +87 -87
  58. package/dist/formats/gif/core/helpers.js +360 -360
  59. package/dist/formats/gif/index.js +2 -2
  60. package/dist/formats/gltf/CjsGltfFormat.js +290 -290
  61. package/dist/formats/gltf/core/helpers.js +292 -292
  62. package/dist/formats/gltf/core/json.js +79 -79
  63. package/dist/formats/gltf/core/parser.js +666 -666
  64. package/dist/formats/gltf/core/targets.js +163 -163
  65. package/dist/formats/gltf/index.js +2 -2
  66. package/dist/formats/gr2/CjsGr2Format.js +46 -0
  67. package/dist/formats/gr2/CjsGr2Format.js.map +1 -0
  68. package/dist/formats/gr2/core/CjsFormatGr2.js +273 -0
  69. package/dist/formats/gr2/core/CjsFormatGr2.js.map +1 -0
  70. package/dist/formats/gr2/core/bitknit2.js +280 -0
  71. package/dist/formats/gr2/core/bitknit2.js.map +1 -0
  72. package/dist/formats/gr2/core/curves.js +1047 -0
  73. package/dist/formats/gr2/core/curves.js.map +1 -0
  74. package/dist/formats/gr2/core/gsf.js +72 -0
  75. package/dist/formats/gr2/core/gsf.js.map +1 -0
  76. package/dist/formats/gr2/core/helpers.js +332 -0
  77. package/dist/formats/gr2/core/helpers.js.map +1 -0
  78. package/dist/formats/gr2/core/json.js +622 -0
  79. package/dist/formats/gr2/core/json.js.map +1 -0
  80. package/dist/formats/gr2/core/oodle1.js +388 -0
  81. package/dist/formats/gr2/core/oodle1.js.map +1 -0
  82. package/dist/formats/gr2/core/reader.js +617 -0
  83. package/dist/formats/gr2/core/reader.js.map +1 -0
  84. package/dist/formats/gr2/core/tangents.js +48 -0
  85. package/dist/formats/gr2/core/tangents.js.map +1 -0
  86. package/dist/formats/gr2/core/targets.js +351 -0
  87. package/dist/formats/gr2/core/targets.js.map +1 -0
  88. package/dist/formats/gr2/index.js +3 -0
  89. package/dist/formats/gr2/index.js.map +1 -0
  90. package/dist/formats/index.js +30 -23
  91. package/dist/formats/index.js.map +1 -1
  92. package/dist/formats/jpeg/CjsJpegFormat.js +206 -206
  93. package/dist/formats/jpeg/core/helpers.js +350 -350
  94. package/dist/formats/jpeg/core/jpeg.js +377 -377
  95. package/dist/formats/jpeg/index.js +2 -2
  96. package/dist/formats/mp3/CjsMp3Format.js +192 -192
  97. package/dist/formats/mp3/core/helpers.js +338 -338
  98. package/dist/formats/mp3/index.js +2 -2
  99. package/dist/formats/mp4/CjsMp4Format.js +191 -191
  100. package/dist/formats/mp4/core/helpers.js +449 -449
  101. package/dist/formats/mp4/index.js +2 -2
  102. package/dist/formats/obj/CjsObjFormat.js +253 -253
  103. package/dist/formats/obj/core/helpers.js +573 -573
  104. package/dist/formats/obj/core/json.js +64 -64
  105. package/dist/formats/obj/core/parser.js +321 -321
  106. package/dist/formats/obj/index.js +2 -2
  107. package/dist/formats/ogg/CjsOggFormat.js +88 -88
  108. package/dist/formats/ogg/core/helpers.js +387 -387
  109. package/dist/formats/ogg/core/imdct.js +178 -178
  110. package/dist/formats/ogg/core/vorbis.js +999 -999
  111. package/dist/formats/ogg/index.js +2 -2
  112. package/dist/formats/png/CjsPngFormat.js +195 -195
  113. package/dist/formats/png/core/helpers.js +583 -583
  114. package/dist/formats/png/index.js +2 -2
  115. package/dist/formats/red/CjsRedFormat.js +261 -261
  116. package/dist/formats/red/core/CjsRedReader.js +194 -194
  117. package/dist/formats/red/core/blackDefinitions.js +3 -3
  118. package/dist/formats/red/core/helpers.js +139 -139
  119. package/dist/formats/red/core/redGraph.js +68 -68
  120. package/dist/formats/red/core/schema.js +4 -4
  121. package/dist/formats/red/index.js +2 -2
  122. package/dist/formats/stl/CjsStlFormat.js +365 -365
  123. package/dist/formats/stl/core/helpers.js +261 -261
  124. package/dist/formats/stl/core/json.js +51 -51
  125. package/dist/formats/stl/core/stl.js +629 -629
  126. package/dist/formats/stl/core/targets.js +163 -163
  127. package/dist/formats/stl/index.js +2 -2
  128. package/dist/formats/tga/CjsTgaFormat.js +192 -192
  129. package/dist/formats/tga/core/helpers.js +446 -446
  130. package/dist/formats/tga/index.js +2 -2
  131. package/dist/formats/wav/CjsWavFormat.js +192 -192
  132. package/dist/formats/wav/core/helpers.js +328 -328
  133. package/dist/formats/wav/index.js +2 -2
  134. package/dist/formats/webm/CjsWebmFormat.js +191 -191
  135. package/dist/formats/webm/core/helpers.js +537 -537
  136. package/dist/formats/webm/index.js +2 -2
  137. package/dist/formats/webp/CjsWebpFormat.js +86 -86
  138. package/dist/formats/webp/core/helpers.js +214 -214
  139. package/dist/formats/webp/index.js +2 -2
  140. package/dist/formats/wem/CjsWemFormat.js +241 -241
  141. package/dist/formats/wem/core/bitStream.js +259 -259
  142. package/dist/formats/wem/core/codebookLibrary.js +164 -164
  143. package/dist/formats/wem/core/helpers.js +417 -417
  144. package/dist/formats/wem/core/packedCodebooksAotuv603.js +30 -30
  145. package/dist/formats/wem/core/ptadpcm.js +77 -77
  146. package/dist/formats/wem/core/resolve.js +121 -121
  147. package/dist/formats/wem/core/wemToOgg.js +485 -485
  148. package/dist/formats/wem/index.js +2 -2
  149. package/dist/formats/yaml/CjsYamlFormat.js +83 -83
  150. package/dist/formats/yaml/core/CjsYamlReader.js +305 -305
  151. package/dist/formats/yaml/core/helpers.js +160 -160
  152. package/dist/formats/yaml/index.js +2 -2
  153. package/dist/index.js +49 -48
  154. package/dist/index.js.map +1 -1
  155. package/dist/resourcePath.js +18 -18
  156. package/dist/resourceStates.js +9 -9
  157. package/dist/resources/AudioGeometryResData.js +47 -47
  158. package/dist/resources/GStateBindingCallbackData.js +31 -31
  159. package/dist/resources/MeshDecalData.js +37 -37
  160. package/dist/resources/MeshDecalLodData.js +34 -34
  161. package/dist/resources/Tr2EffectRes.js +71 -71
  162. package/dist/resources/Tr2GrannyIntersectionResult.js +60 -60
  163. package/dist/resources/Tr2GrannyStateRes.js +44 -44
  164. package/dist/resources/Tr2ImageRes.js +114 -114
  165. package/dist/resources/Tr2LightProfileRes.js +40 -40
  166. package/dist/resources/Tr2MaterialArea.js +34 -34
  167. package/dist/resources/Tr2MaterialMesh.js +31 -31
  168. package/dist/resources/Tr2MaterialRes.js +34 -34
  169. package/dist/resources/Tr2ShaderPermutation.js +43 -43
  170. package/dist/resources/Tr2TextureLodManager.js +80 -80
  171. package/dist/resources/Tr2TextureLodUpdateRequest.js +37 -37
  172. package/dist/resources/Tr2TexturePackChannel.js +37 -37
  173. package/dist/resources/Tr2TexturePipeline.js +52 -52
  174. package/dist/resources/Tr2TexturePipelineParams.js +34 -34
  175. package/dist/resources/Tr2TexturePipelineStepCompress.js +40 -40
  176. package/dist/resources/Tr2TexturePipelineStepGenerateMips.js +22 -0
  177. package/dist/resources/Tr2TexturePipelineStepGenerateMips.js.map +1 -0
  178. package/dist/resources/Tr2TexturePipelineStepLimitSize.js +34 -34
  179. package/dist/resources/Tr2TexturePipelineStepLoad.js +31 -31
  180. package/dist/resources/Tr2TexturePipelineStepPack.js +43 -43
  181. package/dist/resources/TriGeometryRes.js +239 -239
  182. package/dist/resources/TriGeometryResAreaData.js +59 -59
  183. package/dist/resources/TriGeometryResJointData.js +38 -38
  184. package/dist/resources/TriGeometryResLodData.js +88 -88
  185. package/dist/resources/TriGeometryResMeshData.js +63 -63
  186. package/dist/resources/TriGeometryResSkeletonData.js +34 -34
  187. package/dist/resources/TriGrannyRes.js +43 -43
  188. package/dist/resources/TriJointBinding.js +38 -38
  189. package/dist/resources/TriMorphTargetGeometryConstants.js +46 -46
  190. package/dist/resources/TriRtGeometryConstants.js +88 -88
  191. package/dist/resources/TriTextureRes.js +304 -304
  192. package/dist/resources/enums.js +18 -18
  193. package/dist/resources/resourceBoundary.js +47 -47
  194. package/dist/resources/texturePipelineBehavior.js +308 -308
  195. package/dist/texture/CjsTextureArrayRes.js +406 -406
  196. package/dist/texture/CjsTextureParameterProxy.js +133 -133
  197. package/docs/README.md +69 -0
  198. package/docs/architecture.md +86 -0
  199. package/docs/concepts/resource-lifecycle.md +217 -0
  200. package/docs/formats/README.md +104 -0
  201. package/{FORMAT-PROVENANCE.md → docs/formats/provenance.md} +173 -155
  202. package/docs/formats/stl.md +37 -0
  203. package/docs/formats/wwise.md +44 -0
  204. package/docs/reference/events.md +92 -0
  205. package/docs/reference/motherlode-cache.md +244 -0
  206. package/docs/reference/queues.md +102 -0
  207. package/docs/reference/reload.md +107 -0
  208. package/docs/reference/texture-arrays.md +113 -0
  209. package/docs/reference/texture-pipeline.md +53 -0
  210. package/docs/roadmap.md +104 -0
  211. package/format-notices/black/LICENSE +21 -21
  212. package/format-notices/black/NOTICE +47 -47
  213. package/format-notices/bnk/LICENSE +21 -21
  214. package/format-notices/bnk/NOTICE +20 -20
  215. package/format-notices/cmf/LICENSE +21 -21
  216. package/format-notices/cmf/NOTICE +36 -36
  217. package/format-notices/dds/LICENSE +21 -21
  218. package/format-notices/dds/NOTICE +14 -14
  219. package/format-notices/fbx/LICENSE +21 -21
  220. package/format-notices/fbx/NOTICE +14 -14
  221. package/format-notices/flac/LICENSE +21 -21
  222. package/format-notices/flac/NOTICE +14 -14
  223. package/format-notices/gif/LICENSE +21 -21
  224. package/format-notices/gif/NOTICE +14 -14
  225. package/format-notices/gltf/LICENSE +21 -21
  226. package/format-notices/gltf/NOTICE +27 -27
  227. package/format-notices/gr2/LICENSE +21 -0
  228. package/format-notices/gr2/NOTICE +60 -0
  229. package/format-notices/gr2/THIRD-PARTY-NOTICES.md +93 -0
  230. package/format-notices/jpeg/LICENSE +21 -21
  231. package/format-notices/jpeg/NOTICE +14 -14
  232. package/format-notices/mp3/LICENSE +21 -21
  233. package/format-notices/mp3/NOTICE +14 -14
  234. package/format-notices/mp4/LICENSE +21 -21
  235. package/format-notices/mp4/NOTICE +14 -14
  236. package/format-notices/obj/LICENSE +21 -21
  237. package/format-notices/obj/NOTICE +26 -26
  238. package/format-notices/ogg/LICENSE +21 -21
  239. package/format-notices/ogg/NOTICE +28 -28
  240. package/format-notices/png/LICENSE +21 -21
  241. package/format-notices/png/NOTICE +14 -14
  242. package/format-notices/red/LICENSE +21 -21
  243. package/format-notices/red/NOTICE +31 -31
  244. package/format-notices/stl/LICENSE +21 -21
  245. package/format-notices/stl/NOTICE +21 -21
  246. package/format-notices/tga/LICENSE +21 -21
  247. package/format-notices/tga/NOTICE +14 -14
  248. package/format-notices/wav/LICENSE +21 -21
  249. package/format-notices/wav/NOTICE +14 -14
  250. package/format-notices/webm/LICENSE +21 -21
  251. package/format-notices/webm/NOTICE +14 -14
  252. package/format-notices/webp/LICENSE +21 -21
  253. package/format-notices/webp/NOTICE +14 -14
  254. package/format-notices/wem/LICENSE +57 -57
  255. package/format-notices/wem/NOTICE +33 -33
  256. package/format-notices/yaml/LICENSE +21 -21
  257. package/format-notices/yaml/NOTICE +44 -44
  258. package/package.json +52 -51
  259. package/resource-lifecycle.md +0 -679
@@ -1,679 +0,0 @@
1
- # Runtime Resource Lifecycle
2
-
3
- This note records how Carbon/ccpwgl resource loading maps to
4
- `runtime-resource`.
5
-
6
- ## Carbon and ccpwgl
7
-
8
- Carbon's resource model separates load from prepare. The source schemas expose
9
- resource classes through `BlueAsyncRes`, and Carbon notes distinguish background
10
- load work from main-thread/device prepare work.
11
-
12
- ccpwgl makes that split visible in `Tw2Resource`:
13
-
14
- ```text
15
- NO_INIT -> REQUESTED -> LOADED -> PREPARED
16
- ```
17
-
18
- Additional terminal or cleanup states include `ERROR`, `UNLOADED`, and `PURGED`.
19
-
20
- The important behavior is:
21
-
22
- - `Tw2ResMan.LoadResource()` requests a resource.
23
- - The raw fetch resolves.
24
- - `Tw2Resource.OnLoaded()` marks bytes or source data as loaded.
25
- - The resource is queued for prepare.
26
- - The manager tick later calls `res.Prepare(data)`.
27
- - The concrete resource calls `OnPrepared()` after prepare work succeeds.
28
-
29
- Some ccpwgl concrete `Prepare()` implementations also create WebGL objects.
30
- That is a historical engine/runtime coupling, not the boundary we want in
31
- CarbonEngineJS.
32
-
33
- ## CarbonEngineJS
34
-
35
- `runtime-resource` owns the GPU-free half:
36
-
37
- ```text
38
- EMPTY -> REQUESTED/LOADING -> LOADED
39
- ```
40
-
41
- Engine adapters own device realization:
42
-
43
- ```text
44
- LOADED -> PREPARING -> PREPARED
45
- ```
46
-
47
- Current state meanings:
48
-
49
- - `EMPTY`: resource identity exists, but no payload has been read.
50
- - `REQUESTED`: the resource is waiting on a queued or shared source load.
51
- - `LOADING`: source bytes are available and CPU reader/format work is active.
52
- - `LOADED`: CPU payload or hydrated object graph exists.
53
- - `PREPARING`: an engine adapter is realizing backend-owned resources.
54
- - `PREPARED`: preparation completed successfully and the resource is usable.
55
- - `FAILED`: CPU loading, conversion, validation, or publication failed before a valid payload was published.
56
- - `UNLOADED`: resource payload was released.
57
- - `PURGED`: an inactivity or recorded-byte cache policy evicted the resource
58
- from active ownership. Ordinary replacement, `Delete()`, `Clear()`,
59
- `ClearCached()`, and shutdown clean owned payloads/adapters but preserve the
60
- detached handle's last valid state.
61
-
62
- `CjsResMan.LoadObject()` now queues one deduplicated background source operation
63
- per source/path and limits active source operations with `maxConcurrentLoads`.
64
- After bytes arrive, object construction is split into separate main-queue
65
- items:
66
-
67
- ```text
68
- reader/format conversion -> publish
69
- ```
70
-
71
- `maxPrepareTime` limits seconds spent starting synchronous main-queue work in
72
- one pump, and `maxPrepareItemsPerTick` can add a count limit. Promise-returning
73
- format work remains in flight without blocking the JavaScript event loop.
74
- Publication moves the resource to `LOADED`, then stops. ResMan never performs
75
- backend realization or marks the resource `PREPARED`/`GOOD`.
76
-
77
- Format classes own conversion to the promised CPU output. Backend realization
78
- is a separate explicit engine operation after publication. A realization
79
- failure destroys its candidate and returns a current resource to `LOADED`
80
- without discarding the valid CPU payload; an existing usable adapter may remain
81
- `PREPARED`.
82
-
83
- Every requested handle uses a normalized source path plus one promised output
84
- tag. `variant` is explicit; `emit`, `requirement`, and `payload` are fallbacks.
85
- The selected constructor, reader, and format defaults/options are setup-time
86
- execution details, not MotherLode identity. A changed
87
- registration does not create a hidden second resource; reset the affected
88
- identity or create a new manager. A changed output contract uses a new tag.
89
-
90
- Output matching is case-insensitive, then the canonical declaration spelling
91
- is passed to the format reader. Direct object loaders expose only their
92
- unforced default; multiple named outputs belong on a format class. Unsupported
93
- output is rejected before a MotherLode cache hit can return.
94
-
95
- Releasing an unlocked CPU payload keeps the lightweight handle and a small
96
- reconstruction request containing its path/output and source provenance. Its
97
- promised-output fields and retained source provenance remain pinned during
98
- `Ready()`/`GetObject()` so another output/source cannot silently replace the
99
- old identity. `Ready()` may re-read and rebuild it through current
100
- registrations. Queries, lease renewal, sweeps, and adapter cleanup never
101
- trigger that read. Engine adapters own their backend allocations and may
102
- release those independently.
103
-
104
- The Blue method names remain the public queue vocabulary: `AddToQueue`,
105
- `CancelFromQueue`, `GetNextIdForQueue`, `PumpMainThreadQueue`, `PauseQueue`,
106
- `ResumeQueue`, `GetPendingLoads`, and `GetPendingPrepares`. `Update()`/`Tick()`
107
- pump queues. `Wait()` snapshots queued resource-operation roots and already
108
- submitted low-level queue tasks before its first await. Captured roots remain
109
- open through publication enqueued after an asynchronous read, failure, and
110
- lock release; later unrelated roots/tasks are excluded. Failure and queued
111
- cancellation cross the fence without making `Wait()` reject.
112
-
113
- The default wait pumps only the two queues, never automatic retention sweeps,
114
- and honors existing pause state and budgets. `{ pump: false }` requires an
115
- external driver. A standalone canonical `PrepareResourceObjectQueued()` call
116
- opens a queued root. Direct `LoadResourceObject()`, direct
117
- `PrepareResourceObject()`, and standalone source/format reads bypass both
118
- queues and are outside the fence. They are still tracked as active mutations
119
- so synchronous MotherLode replacement cannot detach them. `WaitUrgent()`
120
- remains open until real per-item priority and urgent membership exist.
121
-
122
- ## CjsLibrary resource-path workflow
123
-
124
- The normal direct resource-path flow is:
125
-
126
- ```text
127
- Application / runtime object
128
- |
129
- | requests "res:/model/ship.gr2"
130
- | with optional per-request overrides
131
- v
132
- +-----------------------------------------------+
133
- | CjsLibrary |
134
- | |
135
- | - starts from registered default behavior |
136
- | - considers registered capability reports |
137
- | - chooses requirement / emit / format |
138
- | - applies explicit request overrides |
139
- +-----------------------------------------------+
140
- |
141
- | path + resolved request options
142
- v
143
- +-----------------------------------------------+
144
- | CjsResMan.GetResource(path, options) |
145
- | |
146
- | - normalize source path and extension |
147
- | - resolve the promised output tag |
148
- +-----------------------------------------------+
149
- |
150
- v
151
- +-----------------------------------------------+
152
- | CjsMotherLode.Lookup(resolved key) |
153
- +-----------------------------------------------+
154
- |
155
- +--- cache hit ------------------------------+
156
- | |
157
- | reuse the CjsResource and any active |
158
- | load/build operation |
159
- | |
160
- `--- cache miss -----------------------------+
161
- | |
162
- | resolve class from requirement |
163
- | construct + Initialize() |
164
- | insert into CjsMotherLode |
165
- v |
166
- new CjsResource -------------------------------+
167
- |
168
- | Ready() / GetObject()
169
- v
170
- +===============================================+
171
- | BACKGROUND LOAD QUEUE |
172
- | |
173
- | - mark resource REQUESTED |
174
- | - share one source operation per source/path |
175
- | - obey maxConcurrentLoads |
176
- | - source.Read(path) |
177
- +===============================================+
178
- |
179
- | source bytes
180
- v
181
- +===============================================+
182
- | MAIN PREPARE QUEUE |
183
- | |
184
- | every box below is a separately budgeted item |
185
- +===============================================+
186
- |
187
- v
188
- +-----------------------------------------------+
189
- | Read stage |
190
- | |
191
- | current object loader for extension? |
192
- | yes -> call it |
193
- | no -> resolve registered formats by |
194
- | bytes + request options |
195
- +-----------------------------------------------+
196
- |
197
- | plain payload / hydrated object
198
- v
199
- +-----------------------------------------------+
200
- | Publish stage |
201
- | |
202
- | semantic resource -> SetPayload(payload) |
203
- | generic resource -> SetPayload(value) |
204
- | object aliases payload |
205
- +-----------------------------------------------+
206
- |
207
- +--- validation failure --------------------+
208
- | |
209
- | resource -> FAILED |
210
- | Ready() rejects |
211
- | |
212
- `--- publication succeeds -----------------+
213
- |
214
- | resource -> LOADED
215
- v
216
- CjsLibrary returns
217
- the built CjsResource/object
218
- ```
219
-
220
- Device realization is a separate continuation selected outside ResMan. It can
221
- run again after adapter eviction or device loss while the CPU payload remains
222
- resident:
223
-
224
- ```text
225
- CjsResource LOADED
226
- |
227
- | selected engine adapter
228
- v
229
- PREPARING
230
- |
231
- | create candidate -> verify current target -> synchronous attach
232
- v
233
- PREPARED
234
-
235
- failure: destroy candidate -> LOADED (CPU payload retained)
236
- ```
237
-
238
- ## Texture Array Generations
239
-
240
- `CjsTextureArrayRes` is a derived multi-source resource with an explicit
241
- requested/prepared revision boundary:
242
-
243
- ```text
244
- proxy/source change
245
- -> requested revision + dirty layer
246
- -> one scheduled next-frame snapshot
247
- -> consumed/in-flight request
248
- -> adapter candidate preparation
249
- -> guarded adapter + prepared-revision publication
250
- ```
251
-
252
- `ConsumeUpdateRequest()` produces an immutable snapshot and marks that revision
253
- in flight. A current consumed revision may be completed through
254
- `CommitPreparedAdapterRevision()`, failed through `FailUpdateRequest()`, or
255
- returned to the queue through `RetryUpdateRequest()`. Commit-before-consume and
256
- stale commits are rejected; rejected candidate allocations are destroyed by
257
- default.
258
-
259
- Publication installs the adapter allocation and prepared revision before
260
- completion events run. The result returns the displaced allocation to the
261
- adapter owner for post-publication destruction. A reentrant source change may
262
- therefore request a newer revision without allowing stale completion to replace
263
- it. The previous prepared allocation and `IsGood()` remain usable while a
264
- replacement is pending or if replacement preparation fails.
265
-
266
- `Ready()` is specialized for this derived resource: it resolves when the
267
- generation requested at call time has been published, rather than delegating
268
- to a single-source object loader. Initial preparation failure rejects it.
269
-
270
- Logical paths and resolved sources are independent. Attaching a redirected or
271
- LOD-specific source does not rewrite persistence. `TouchLayer()` represents an
272
- in-place source revision, and `HandleAdapterLoss()` invalidates the complete
273
- topology after destroying an unusable adapter allocation. Topology snapshots
274
- report an explicit `topologyChanged` flag and contain only valid current dirty
275
- layer indices.
276
-
277
- ## Optional Runtime Manager Follow-up
278
-
279
- The runtime-resource 0.6.0 ResMan/MotherLode contract is complete. Future
280
- work should be driven by measured application needs rather than another
281
- resource preparation abstraction:
282
-
283
- - main-queue priority and starvation policy
284
- - cancellation/abort propagation for work that has already started
285
- - `WaitUrgent()` after real priority and bounded fairness
286
- - queue-time and reader/format-time telemetry
287
- - application-level default retention policy selection
288
- - automatic resource/payload byte estimation and separate CPU/adapter budgets
289
- - browser-aware source behavior such as fetch response type selection
290
-
291
- Explicit `Ready()`/`GetObject()` reconstructs released CPU payloads. Backend
292
- device-loss recovery belongs to the engine's realization operation.
293
-
294
- ## Memory Retention and Purging
295
-
296
- ccpwgl keeps every resource in `Tw2MotherLode` until it is explicitly cleared or
297
- auto-purged. Each resource has:
298
-
299
- - `activeFrame`: last frame the resource was considered in use.
300
- - `doNotPurge`: a lock counter/flag for resources that must stay resident.
301
- - `KeepAlive()`: updates `activeFrame` and reloads an unloaded/purged resource
302
- if it is touched again.
303
-
304
- The manager advances an `activeFrame` counter on a cadence, and
305
- `Tw2MotherLode.PurgeInactive()` unloads resources whose `activeFrame` is old
306
- enough. It then marks them purged and removes them from the cache. In ccpwgl,
307
- `IsGood()` also calls `KeepAlive()`, so many read/check paths implicitly keep
308
- resources resident.
309
-
310
- That model works for ccpwgl, but CarbonEngineJS is deliberately more explicit:
311
-
312
- - `IsGood()`, `IsPrepared()`, and `HasLoaded()` remain pure state checks.
313
- - `KeepAlive()` and `KeepPayloadAlive()` are the explicit liveness operations.
314
- - `Lock()` / `Unlock()` maintain a non-underflowing count and prevent identity
315
- and payload eviction during a sweep.
316
- - MotherLode tracks separate identity and CPU-payload frame/time observations.
317
- - `CjsMotherLode.PurgeInactive()` itself scans only when explicitly requested
318
- and never infers JavaScript reachability. `CjsResMan.Update()` may request it
319
- only under an explicitly configured automatic policy.
320
- - `Unload()` should release engine adapter resources and optionally CPU payloads.
321
- - A resource-level `Purge()`/`Reload()` vocabulary remains future policy work.
322
-
323
- ### Recorded-Byte Cache Contract
324
-
325
- Carbon can infer when only its cache retains a resource through weak-reference
326
- and refcount transitions. JavaScript cannot reproduce that ownership test
327
- reliably, so `CjsMotherLode` budgets only entries that a caller explicitly
328
- classifies with `{ cached: true, bytes }`. The byte value is an exact
329
- caller-supplied safe-integer eviction weight; runtime-resource does not walk
330
- arbitrary cyclic/shared object graphs or invoke payload getters to guess size.
331
-
332
- Explicit cached entries receive a monotonic admission sequence.
333
- With its default cleanup, `TrimCache(options)` destroys adapters, releases
334
- payloads, detaches lifecycle callbacks, marks compatible handles `PURGED`, and
335
- removes positive-byte entries oldest-first until `cacheBytes <= cacheSize`.
336
- Live, locked, non-cacheable, and zero-byte entries do not create byte pressure.
337
- `KeepAlive()` and `Lock()` promote a cached record to live; `Unlock()` does not
338
- silently re-admit it.
339
-
340
- `SetCacheSize(bytes, options)` installs and immediately enforces the new budget.
341
- `CjsResMan.Update()` / `Tick()` also run cache housekeeping after queue pumping;
342
- `{ cache: false }` skips one update without changing policy. Cleanup is
343
- transactional per identity: a failed candidate remains canonical, later
344
- candidates are still attempted, and the aggregate
345
- `CJS_MOTHERLODE_CACHE_TRIM_FAILED` error carries successful evictions and any
346
- remaining over-budget state. Trimming never reads, prepares, or reloads data.
347
-
348
- The JS/browser split adds one more axis that ccpwgl blurs: CPU payload memory and
349
- GPU/device memory are different budgets. A large decoded image, geometry buffer,
350
- or shader graph can be expensive even before an engine adapter prepares it.
351
- Runtime-resource should therefore support separate retention policy for:
352
-
353
- - resource identity: path, extension, state, error summary, lightweight metadata.
354
- - CPU payload: plain reader/converter objects, hydrated object graphs, decoded
355
- typed arrays.
356
- - adapter payload: WebGL/WebGPU textures, buffers, shader modules, pipelines.
357
-
358
- A sane default would keep resource identity and lightweight metadata while
359
- allowing CPU payloads and adapter payloads to be released independently. Engine
360
- adapters should own adapter-resource destruction, but `runtime-resource` can
361
- provide lifecycle hooks and opaque adapter slots so the adapter has a consistent
362
- place to clean up.
363
-
364
- ### Payload Retention Contract
365
-
366
- Reader and converter outputs are plain transient payload objects, not resource
367
- classes or DTO models. A payload may contain more decoded data than a particular
368
- resource or engine adapter needs. Each concrete resource validates the fields
369
- it requires before publishing the payload and retains the scalars and references
370
- it needs. An adapter may retain additional references in adapter-owned state.
371
- Referencing payload-owned typed arrays is valid and preferable to copying them
372
- merely to change ownership.
373
-
374
- The lifecycle treats resource residency and payload residency independently:
375
-
376
- ```text
377
- resource.KeepAlive()
378
- -> renew resource/cache residency
379
-
380
- resource.KeepPayloadAlive()
381
- -> renew the attached payload lease
382
-
383
- resource.ReleasePayload()
384
- -> explicitly release the full payload reference
385
- ```
386
-
387
- `CjsResMan` binds resource-facing `KeepAlive()`, `KeepPayloadAlive()`, `Lock()`,
388
- and `Unlock()` to the resource's canonical MotherLode key. `SetPayload()` renews
389
- both identity and payload activity when it publishes a non-null payload.
390
- `GetPayload()` and `HasPayload()` are pure queries; reading the payload does not
391
- implicitly renew its lease. Detached and purged handles retain deterministic
392
- no-op liveness methods rather than silently starting work.
393
-
394
- Both semantic and generic/base resource results use that payload slot. A base
395
- resource also mirrors its payload on the compatibility `object` property;
396
- `ReleasePayload()` clears the alias only when it still references that exact
397
- payload. Semantic resources continue to expose `object === resource` while
398
- holding their validated plain payload privately.
399
-
400
- Object-operation promises are retained only while in flight. Concurrent
401
- `GetObject()`/`Ready()` calls share one operation; a resident result is returned
402
- without source work and renews the explicit payload lease. Settlement removes
403
- the operation record so its result graph can be reclaimed and a failure can be
404
- retried. After payload release, only a new explicit object/readiness call
405
- reconstructs it; queries, lease calls, and purge sweeps do not.
406
-
407
- Read-cache provenance is explicit and separate from path/output resource identity.
408
- For a selected source object and normalized path, `sourceRevision` is an opaque
409
- caller/source-supplied string or finite-number content token. Source and format
410
- records do not share across revisions. Format records are additionally isolated
411
- by source object, frozen registration descriptor, and effective format options,
412
- so another source or a re-registered default cannot reuse a stale parse.
413
- Registered defaults are deeply snapshotted for supported plain-object/array
414
- configuration. Requests containing option values that cannot be represented
415
- safely bypass parsed-format sharing instead of accepting an ambiguous key.
416
-
417
- `cacheSource` and `cacheFormat` use the same per-call tri-state contract:
418
- omitted shares existing in-flight/retained work but drops a newly completed
419
- record; `true` retains success and upgrades joined work; `false` bypasses both
420
- sharing and retention. Failures are never retained. Resource loaders capture
421
- the effective selected source (including the creation-time manager default)
422
- and revision for later reconstruction, but do not capture cache flags or
423
- one-shot reload.
424
-
425
- `reload: true` detaches all queued/source/format records for the selected
426
- source/path when fresh candidate work begins. `InvalidateReadCache()` exposes
427
- explicit path invalidation, optionally restricted to one revision. Detachment
428
- never aborts or rejects existing consumers and never touches MotherLode or the
429
- already-published canonical payload. `Delete()` remains resource-identity-only;
430
- `Clear()` resets all read ledgers. A failed reload preserves the former payload
431
- but does not restore reusable read-cache entries detached by its explicit
432
- freshness request.
433
-
434
- ### Candidate-First Atomic Reload
435
-
436
- When an owner already exists, `GetResource(path, { reload: true })` constructs
437
- and initializes a distinct off-registry candidate. Ordinary `Lookup()` remains
438
- on the former resource. Calling `Ready()` on the candidate, using
439
- `GetObject()` / `FetchResource()` with `reload: true`, or calling the explicit
440
- `ReloadObject()` / `ReloadResource()` helpers starts one shared candidate
441
- operation.
442
-
443
- The manager captures the exact MotherLode, key, former handle, former ownership
444
- generation, and a newest-request token. It purge-locks the former owner and
445
- tracks the candidate as a normal `Wait()` root. Reader, prepare, and publication
446
- stages mutate only the detached candidate and validate candidate authority
447
- before and after asynchronous boundaries. A fully loaded CPU candidate commits
448
- through `CjsMotherLode.ReplaceExpected()` only if the exact former owner and
449
- newest token still match. The final authority callback and exact-record check
450
- run immediately before the synchronous map switch, with no user cleanup or
451
- `await` between the comparison and publication.
452
-
453
- After the switch, the displaced ownership generation is invalidated, the
454
- candidate receives ordinary lifecycle/reconstruction callbacks, and the former
455
- handle is cleaned exactly once. Existing JavaScript references are not
456
- retargeted; fresh lookup returns the new handle. Source, format, or publication
457
- failure leaves the exact former state, payload, and adapters canonical, retains
458
- its original error, and cleans payload/adapters attached to the never-canonical
459
- candidate. An otherwise-successful candidate that was superseded, deleted,
460
- cleared, or replaced rejects with `CJS_RESMAN_STALE_RELOAD_CANDIDATE` and cannot
461
- resurrect the key. Constructors that return the former singleton are rejected
462
- before `Initialize()` can mutate it because staging requires a distinct handle.
463
-
464
- Cleanup errors have explicit sides. Candidate cleanup failure aggregates with
465
- the original preparation/stale error as
466
- `CJS_RESMAN_RELOAD_CANDIDATE_CLEANUP_FAILED` while the former owner remains
467
- canonical. A displaced-owner cleanup failure occurs after publication and
468
- rejects as `CJS_MOTHERLODE_REPLACE_CLEANUP_FAILED` with a result where
469
- `committed === true`; the already-good candidate remains canonical.
470
- Started source/format work abort remains separate work.
471
-
472
- This availability contract intentionally differs from Carbon.
473
- `BlueAsyncRes::Reload` cancels/joins work, releases dependent cached data, and
474
- reloads the same canonical object in place; failure can therefore leave that
475
- stable handle bad. Carbon MotherLode replacement also switches immediately and
476
- has no prepare-success gate or rollback. Runtime-resource instead preserves the
477
- last published good handle until a distinct candidate has succeeded.
478
-
479
- Every canonical queued/direct/standalone preparation captures an immutable
480
- publication authority: exact MotherLode, canonical key, resource handle, and a
481
- manager-local ownership generation. The manager validates that authority
482
- before and after state changes, asynchronous reader/format work, and publication.
483
- Delete, Clear, successful reload commit, or exact-handle reinsertion therefore makes
484
- older work reject with `CJS_RESMAN_STALE_RESOURCE_OPERATION` before it can
485
- publish. If stale work independently rejects, its original source/format error
486
- is preserved and `SetError()` is suppressed on the detached handle.
487
-
488
- `Register({ motherLode })` rejects with
489
- `CJS_RESMAN_ACTIVE_RESOURCE_OPERATIONS` while queued or direct mutations are
490
- active, including a reload candidate. Normal `Wait()` drains queued roots and
491
- candidate lineages; a direct caller must await its own load/prepare promise
492
- before retrying replacement. Canonical and candidate authority prevent late
493
- publication, but started-work abort remains separate. Backend candidate
494
- cleanup belongs to the explicit engine realization operation.
495
-
496
- An explicit `PurgeInactive()` sweep accepts separate frame/time limits for
497
- identity and payload residency. Identity expiry destroys adapter resources,
498
- releases the payload, detaches lifecycle callbacks, marks compatible handles
499
- `PURGED`, and removes the canonical key. Payload expiry calls
500
- `ReleasePayload()` while retaining identity and adapter allocations. Locks skip
501
- both operations, and candidate failures are aggregated after the sweep has
502
- continued over other entries.
503
-
504
- `CjsResMan` also exposes opt-in automatic scheduling through
505
- `SetAutoPurgePolicy()` or the `autoPurgePolicy` registration option. It is
506
- disabled by default. Automatic policy is time-only because MotherLode's current
507
- activity frame counts observations rather than renderer frames. A policy must
508
- set at least one of `maxIdleMilliseconds` or
509
- `payloadMaxIdleMilliseconds`; `intervalMilliseconds` defaults to 1000. The
510
- first `PumpAutoPurge()`/`Update()` after configuration sweeps immediately, then
511
- the interval sets the minimum cadence. `Update({ purge: false })` suppresses a
512
- sweep for one update without changing cadence. A regressing clock rebases and
513
- skips one pump; custom deterministic clocks should be shared with MotherLode.
514
- Recorded-byte cache trimming is separate from this opt-in inactivity policy and
515
- runs on ordinary updates unless `{ cache: false }` is supplied.
516
-
517
- Both queued `QueueResourceObject()` work and direct `LoadResourceObject()` work
518
- hold one manager-owned lock from request/loading publication through success or
519
- failure. The lock is balanced independently of caller locks, so automatic or
520
- manual sweeps cannot detach a handle while its read/prepare operation is still
521
- active. Lock release is conditional on the same captured ownership generation,
522
- so stale work cannot decrement a newly rebound handle's lock. Scheduling and
523
- active-work protection do not fetch or reload data.
524
-
525
- The processor preparing a resource decides when the full payload can be
526
- released:
527
-
528
- ```text
529
- format reader -> plain payload -> resource validation + adapter prepare
530
- |
531
- +-> resource retains required values/references
532
- +-> adapter retains adapter-specific state/references
533
- +-> release payload after successful preparation
534
- `-> or renew its lease for deferred/further work
535
- ```
536
-
537
- A time- or frame-based lease is a fallback against abandoned payloads. An owner
538
- performing deferred work can renew the lease. If an expired payload is required
539
- again, the caller must explicitly request reconstruction; lease renewal and
540
- purging never fetch or reload source data. Dynamic or non-reloadable resources
541
- must remain locked, retain the required payload, or be able to recreate it.
542
-
543
- Payload references are shared read-only by default. Preparing WebGL and WebGPU
544
- adapters side by side should normally pass the same payload to both consumers
545
- and retain it until both have finished. Copying is an explicit consumer
546
- operation, justified when a consumer must mutate data, transfer and detach an
547
- `ArrayBuffer`, or retain an independently writable snapshot. The consumer
548
- should copy only the fields it requires; runtime-resource should not
549
- automatically deep-clone entire payload or typed-array bundles. Any full-copy
550
- operation should be format-aware rather than a generic resource-side clone.
551
-
552
- Open design questions:
553
-
554
- - Should `Unload()` drop only adapter payloads by default, or CPU payloads too?
555
- - Should there be explicit `UnloadAdapterResources()`, `UnloadPayload()`, and
556
- `Purge()` phases?
557
- - Which format/resource-specific estimators should supply separate identity,
558
- CPU-payload, and adapter byte weights without double-counting shared buffers?
559
- - Should manually attached/dynamic resources default to locked, like ccpwgl's
560
- manual shader resources use `doNotPurge`?
561
- - What explicit `Reload()`/reconstruction API should restore purged resources
562
- without introducing surprising browser or network work?
563
-
564
- ccpwgl's raw event emitter shape is useful. The part we should not copy is the
565
- separate resource notification/callback compatibility layer that sits beside
566
- events. CarbonEngineJS exposes one small event-emitter API and avoids short
567
- generic names such as `On`, `Once`, `Off`, and `Emit` on Carbon-shaped classes.
568
- `CjsEventEmitter` is a separate base class so non-model runtime services can
569
- extend it without extending `CjsModel`; `CjsResMan` already uses that path.
570
-
571
- The API stays deliberately direct:
572
-
573
- - `AddEvents(events)`
574
- - `OnEvent(eventName, listener, source?)`
575
- - `OnceEvent(eventName, listener, source?)`
576
- - `OffEvent(eventName = "*", listener?, source?)`
577
- - `EmitEvent(eventName, ...args)`
578
- - `HasEvent(eventName = "*", listener?, source?)`
579
- - `ClearEvent(eventName = "*")`
580
- - `GetEventNames()` and `GetEventListenerCount(eventName = "*")`
581
-
582
- The optional `source` is the callback's `this` value and an explicit matching
583
- identity for removal. Mutating event methods return the emitter for chaining.
584
- There are no listener scopes, subscription handles, owner-side `ListenTo()`
585
- helpers, or parallel resource notification callbacks.
586
-
587
- Event names are normalized to lowercase and dispatched by exact match. Resource
588
- classes may emit their own state or domain events, but the emitter does not
589
- invent a resource lifecycle contract.
590
-
591
- Event memory rules matter as much as event names. Event storage is the optional
592
- `events` member of the emitter's non-enumerable `__state` object. It is created
593
- only when the first listener is registered and deleted when the last record is
594
- removed. That does not make listeners weak. As long as an emitter is reachable,
595
- its event map strongly references listener functions and sources, and those
596
- listeners can keep whole scene/resource graphs alive.
597
-
598
- CarbonEngineJS event emitters should therefore follow these rules:
599
-
600
- - `OnceEvent()` should remove the listener before or immediately after the first
601
- callback, even if the callback throws.
602
- - `OffEvent(eventName, listener, source)` must remove the exact listener/source
603
- entry.
604
- - an external party that no longer observes a target must call `OffEvent()`;
605
- `target.OffEvent("*", null, source)` removes all of that source's records.
606
- - `Unload()` and `Purge()` are resource state/cache operations, not an implied
607
- listener-destruction lifecycle.
608
- - `OnEvent()` returns the emitter, ccpwgl-style. It does not return unsubscribe
609
- closures because those closures create another reference path.
610
- - event payload history is not stored.
611
- - we should prefer deterministic cleanup over `WeakRef`/`FinalizationRegistry`;
612
- those can help diagnostics, but they are not a lifecycle contract.
613
-
614
- The target is "easy to debug, hard to leak": clear ownership of who subscribed,
615
- who unsubscribes, and which cleanup phase clears all remaining listeners.
616
-
617
- The event data model is deliberately small:
618
-
619
- ```text
620
- emitter.__state (non-enumerable, allocated only when some subsystem needs it)
621
- events -> eventName -> Set<listenerRecord>
622
-
623
- listenerRecord
624
- emitter
625
- eventName
626
- listener
627
- source
628
- once
629
- ```
630
-
631
- The event map is allocated only when the first listener is registered. If the
632
- emitter becomes unreachable, its state and event records can be collected with
633
- it. While the emitter remains reachable, listener functions and sources are
634
- strongly retained until `OffEvent()`, `ClearEvent()`, once dispatch, or emitter
635
- collection. Multiple listeners on the same event are allowed because each event
636
- bucket is a set of records. A raw `CjsEventEmitter` does not gain model-owned
637
- `dirty` or `rebuild` state.
638
-
639
- This is as far as the event system should go for now:
640
-
641
- - lowercase exact event names only.
642
- - no wildcard listener dispatch.
643
- - wildcard names are accepted only by lookup and cleanup methods.
644
- - no listener scope or subscription-object layer.
645
- - no separate resource notification/callback compatibility layer.
646
- - no `family.event` or ancestor routing.
647
- - no event history by default.
648
- - no global master event manager.
649
- - debugging introspection should stay limited to counts and names unless a real
650
- use case appears.
651
-
652
- `CjsModel` has dirty-state helpers (`MarkDirty`, `ClearDirty`, `ConsumeDirty`,
653
- `GetDirtyNotifications`) for model invalidation. `SetValues()` compares incoming
654
- values with the current field values and only marks dirty when a value actually
655
- changes. A plain `MarkDirty()` means broad dirty invalidation; it does not request
656
- a rebuild. Deferred rebuild reasons belong to the independent
657
- `model.__state.rebuild` set. This is not a resource lifecycle event system.
658
- Resource lifecycle events should therefore remain a resource/resman concern.
659
-
660
- ## Why We Diverge
661
-
662
- The Carbon and ccpwgl resource classes live inside an engine that can prepare
663
- GPU objects directly. CarbonEngineJS keeps the format/resource layer reusable by
664
- stopping before GPU work:
665
-
666
- - `runtime-resource` selects and runs registered non-shader readers, then can
667
- directly hydrate the requested runtime class or return another requested
668
- outcome.
669
- - `runtime-resource` stores lifecycle state, cache entries, and loaded object
670
- payloads. Plain reader results are normally transient prepare inputs; the
671
- resource or adapter retains only what it requires, by reference or by
672
- explicit copy.
673
- - Frozen standalone non-shader `format-*` packages remain compatibility
674
- distributions. GR2 and all shader formats remain separate packages for now.
675
- - engine packages create WebGL/WebGPU textures, buffers, shader modules,
676
- pipelines, and bind groups from loaded resources.
677
-
678
- This gives us the Carbon lifecycle shape without forcing WebGL/WebGPU imports or
679
- device decisions into `runtime-resource`.