@carbonenginejs/runtime 0.1.0-alpha.2 → 0.1.0-alpha.3
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.
- package/dist/global/model/CjsModel.js +3 -1
- package/dist/global/model/CjsModel.js.map +1 -1
- package/dist/global/model/document/CjsDocumentHydrator.js +5 -1
- package/dist/global/model/document/CjsDocumentHydrator.js.map +1 -1
- package/dist/global/schema/CjsSchema.js +98 -18
- package/dist/global/schema/CjsSchema.js.map +1 -1
- package/dist/global/schema/hydration.js +10 -8
- package/dist/global/schema/hydration.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/resource/CjsResMan.js +177 -4
- package/dist/resource/CjsResMan.js.map +1 -1
- package/dist/resource/format/CjsBlueReader.js +8 -1
- package/dist/resource/format/CjsBlueReader.js.map +1 -1
- package/dist/resource/formats/webgl/core/glsl/DxbcGlslEmitter.js +7 -1
- package/dist/resource/formats/webgl/core/glsl/DxbcGlslEmitter.js.map +1 -1
- package/dist/resource/index.js +2 -0
- package/dist/resource/index.js.map +1 -1
- package/dist/resource/texture/SolidColorTextureConstructor.js +46 -0
- package/dist/resource/texture/SolidColorTextureConstructor.js.map +1 -0
- package/dist/resource/texture/TriTextureRes.js +43 -0
- package/dist/resource/texture/TriTextureRes.js.map +1 -1
- package/dist/resource/texture/index.js +2 -0
- package/dist/resource/texture/index.js.map +1 -1
- package/dist/resource/texture/solidColorTexture.js +111 -0
- package/dist/resource/texture/solidColorTexture.js.map +1 -0
- package/dist/trinity/eve/attachment/decal/EveSpaceObjectDecal.js +19 -8
- package/dist/trinity/eve/attachment/decal/EveSpaceObjectDecal.js.map +1 -1
- package/dist/trinity/eve/child/EveChildMesh.js +17 -3
- package/dist/trinity/eve/child/EveChildMesh.js.map +1 -1
- package/dist/trinity/eve/locator/EveLocatorSets.js +12 -3
- package/dist/trinity/eve/locator/EveLocatorSets.js.map +1 -1
- package/dist/trinity/eve/spaceObject/EveSpaceObject2.js +15 -2
- package/dist/trinity/eve/spaceObject/EveSpaceObject2.js.map +1 -1
- package/dist/trinity/eve/spaceObject/IEveSpaceObject2ParentData.js +44 -52
- package/dist/trinity/eve/spaceObject/IEveSpaceObject2ParentData.js.map +1 -1
- package/docs/resource/concepts/resource-lifecycle.md +10 -8
- package/docs/resource/formats/geometry-interchange.md +50 -0
- package/docs/resource/formats/webgl/decl-io.md +241 -397
- package/docs/resource/formats/webgl/memory-structured.md +91 -128
- package/docs/resource/formats/webgl/texture-sample.md +306 -469
- package/docs/resource/reference/classes/texture.md +11 -1
- package/docs/resource/reference/motherlode-cache.md +24 -11
- package/docs/resource/reference/queues.md +23 -1
- package/package.json +1 -1
|
@@ -27,12 +27,14 @@ import { CjsSchema } from './CjsSchema.js';
|
|
|
27
27
|
* reader works with only the schema layer loaded; the two are the same path
|
|
28
28
|
* for every class that has the method.
|
|
29
29
|
*
|
|
30
|
-
* A
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
30
|
+
* A schema-declared class without SetValues - one that no longer extends
|
|
31
|
+
* CjsModel - populates through CjsSchema.setValues, which answers without the
|
|
32
|
+
* model layer. The READER says so, as `ctx.declared`: it resolved the kind to
|
|
33
|
+
* a constructor itself and asks CjsSchema whether that class declares fields,
|
|
34
|
+
* so the adapter never inspects the object. Raw assignment stays for what
|
|
35
|
+
* declares nothing - the reader's fallback carrier, and a caller-supplied
|
|
36
|
+
* plain class. Without this arm, taking a class off the base would silently
|
|
37
|
+
* drop it to Object.assign: no coercion, no writability gate, no settle.
|
|
36
38
|
*/
|
|
37
39
|
|
|
38
40
|
|
|
@@ -57,8 +59,8 @@ function resolveHydrationAdapter(options = {}) {
|
|
|
57
59
|
instance.SetValues(values, ctx?.options);
|
|
58
60
|
return instance;
|
|
59
61
|
}
|
|
60
|
-
if (
|
|
61
|
-
CjsSchema.setValues(instance, values, ctx
|
|
62
|
+
if (ctx?.declared === true) {
|
|
63
|
+
CjsSchema.setValues(instance, values, ctx.options);
|
|
62
64
|
return instance;
|
|
63
65
|
}
|
|
64
66
|
return Object.assign(instance, values);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydration.js","sources":["../../../../src/global/schema/hydration.js"],"sourcesContent":["/**\n * Hydration adapter seam - the population contract for readers that build\n * runtime objects incrementally (black/red). The ordering guarantee:\n *\n * 1. construct - build every instance (register refs before values, so\n * cycles/back-references resolve)\n * 2. applyValues - apply each node's field values\n * 3. finalize - run once per instance AFTER the whole graph is built,\n * so references are already resolved\n *\n * DEFAULTS (operator ruling, 2026-09-05; resource-population.md owns the\n * decision): a resolved class is populated through its own validated setter -\n * \"if someone is using Object.assign, they probably should be using\n * SetValues\" - and a class-owned Initialize runs once post-graph, which is\n * the incremental-reader counterpart of the reader+IInitialize handshake\n * Blue performs and CjsModel.from performs for whole-bag JSON. Plain\n * fallback carriers (unresolved kinds) keep raw assignment: that IS their\n * contract, not a bypass.\n *\n * The single escape hatch is `options.adapter`, per-hook optional. The\n * per-kind adapters map and the hydrationAdapter alias are gone - they had\n * zero consumers. Callers wanting raw assignment pass their own adapter.\n *\n * SetValues is called directly rather than through CjsSchema.setValues so a\n * reader works with only the schema layer loaded; the two are the same path\n * for every class that has the method.\n *\n * A
|
|
1
|
+
{"version":3,"file":"hydration.js","sources":["../../../../src/global/schema/hydration.js"],"sourcesContent":["/**\n * Hydration adapter seam - the population contract for readers that build\n * runtime objects incrementally (black/red). The ordering guarantee:\n *\n * 1. construct - build every instance (register refs before values, so\n * cycles/back-references resolve)\n * 2. applyValues - apply each node's field values\n * 3. finalize - run once per instance AFTER the whole graph is built,\n * so references are already resolved\n *\n * DEFAULTS (operator ruling, 2026-09-05; resource-population.md owns the\n * decision): a resolved class is populated through its own validated setter -\n * \"if someone is using Object.assign, they probably should be using\n * SetValues\" - and a class-owned Initialize runs once post-graph, which is\n * the incremental-reader counterpart of the reader+IInitialize handshake\n * Blue performs and CjsModel.from performs for whole-bag JSON. Plain\n * fallback carriers (unresolved kinds) keep raw assignment: that IS their\n * contract, not a bypass.\n *\n * The single escape hatch is `options.adapter`, per-hook optional. The\n * per-kind adapters map and the hydrationAdapter alias are gone - they had\n * zero consumers. Callers wanting raw assignment pass their own adapter.\n *\n * SetValues is called directly rather than through CjsSchema.setValues so a\n * reader works with only the schema layer loaded; the two are the same path\n * for every class that has the method.\n *\n * A schema-declared class without SetValues - one that no longer extends\n * CjsModel - populates through CjsSchema.setValues, which answers without the\n * model layer. The READER says so, as `ctx.declared`: it resolved the kind to\n * a constructor itself and asks CjsSchema whether that class declares fields,\n * so the adapter never inspects the object. Raw assignment stays for what\n * declares nothing - the reader's fallback carrier, and a caller-supplied\n * plain class. Without this arm, taking a class off the base would silently\n * drop it to Object.assign: no coercion, no writability gate, no settle.\n */\n\nimport { CjsSchema } from \"./CjsSchema.js\";\n\n/**\n * Resolves a normalized adapter from hydration options. The returned object\n * always exposes construct/applyValues/finalize with the documented defaults.\n *\n * @param {Object} [options]\n * @param {Object} [options.adapter] Per-hook overrides: construct/applyValues/finalize.\n * @returns {{ construct: Function, applyValues: Function, finalize: Function }}\n */\nexport function resolveHydrationAdapter(options = {})\n{\n const custom = options.adapter || null;\n\n return {\n construct(kind, ctx)\n {\n if (custom && typeof custom.construct === \"function\") return custom.construct(kind, ctx);\n return undefined;\n },\n applyValues(instance, values, ctx)\n {\n if (custom && typeof custom.applyValues === \"function\") return custom.applyValues(instance, values, ctx);\n if (instance && typeof instance.SetValues === \"function\")\n {\n instance.SetValues(values, ctx?.options);\n return instance;\n }\n if (ctx?.declared === true)\n {\n CjsSchema.setValues(instance, values, ctx.options);\n return instance;\n }\n return Object.assign(instance, values);\n },\n finalize(instance, ctx)\n {\n if (custom && typeof custom.finalize === \"function\")\n {\n custom.finalize(instance, ctx);\n return;\n }\n if (instance && typeof instance.Initialize === \"function\") instance.Initialize();\n }\n };\n}\n"],"names":["resolveHydrationAdapter","options","custom","adapter","construct","kind","ctx","undefined","applyValues","instance","values","SetValues","declared","CjsSchema","setValues","Object","assign","finalize","Initialize"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASA,uBAAuBA,CAACC,OAAO,GAAG,EAAE,EACpD;AACI,EAAA,MAAMC,MAAM,GAAGD,OAAO,CAACE,OAAO,IAAI,IAAI;EAEtC,OAAO;AACHC,IAAAA,SAASA,CAACC,IAAI,EAAEC,GAAG,EACnB;AACI,MAAA,IAAIJ,MAAM,IAAI,OAAOA,MAAM,CAACE,SAAS,KAAK,UAAU,EAAE,OAAOF,MAAM,CAACE,SAAS,CAACC,IAAI,EAAEC,GAAG,CAAC;AACxF,MAAA,OAAOC,SAAS;IACpB,CAAC;AACDC,IAAAA,WAAWA,CAACC,QAAQ,EAAEC,MAAM,EAAEJ,GAAG,EACjC;AACI,MAAA,IAAIJ,MAAM,IAAI,OAAOA,MAAM,CAACM,WAAW,KAAK,UAAU,EAAE,OAAON,MAAM,CAACM,WAAW,CAACC,QAAQ,EAAEC,MAAM,EAAEJ,GAAG,CAAC;MACxG,IAAIG,QAAQ,IAAI,OAAOA,QAAQ,CAACE,SAAS,KAAK,UAAU,EACxD;QACIF,QAAQ,CAACE,SAAS,CAACD,MAAM,EAAEJ,GAAG,EAAEL,OAAO,CAAC;AACxC,QAAA,OAAOQ,QAAQ;AACnB,MAAA;AACA,MAAA,IAAIH,GAAG,EAAEM,QAAQ,KAAK,IAAI,EAC1B;QACIC,SAAS,CAACC,SAAS,CAACL,QAAQ,EAAEC,MAAM,EAAEJ,GAAG,CAACL,OAAO,CAAC;AAClD,QAAA,OAAOQ,QAAQ;AACnB,MAAA;AACA,MAAA,OAAOM,MAAM,CAACC,MAAM,CAACP,QAAQ,EAAEC,MAAM,CAAC;IAC1C,CAAC;AACDO,IAAAA,QAAQA,CAACR,QAAQ,EAAEH,GAAG,EACtB;MACI,IAAIJ,MAAM,IAAI,OAAOA,MAAM,CAACe,QAAQ,KAAK,UAAU,EACnD;AACIf,QAAAA,MAAM,CAACe,QAAQ,CAACR,QAAQ,EAAEH,GAAG,CAAC;AAC9B,QAAA;AACJ,MAAA;AACA,MAAA,IAAIG,QAAQ,IAAI,OAAOA,QAAQ,CAACS,UAAU,KAAK,UAAU,EAAET,QAAQ,CAACS,UAAU,EAAE;AACpF,IAAA;GACH;AACL;;;;"}
|
package/dist/index.js
CHANGED
|
@@ -134,6 +134,7 @@ export { Tr2SamplerSetup } from './resource/shader/sampler/Tr2SamplerSetup.js';
|
|
|
134
134
|
export { RegisterShaderResources, ShaderResourceExtensions } from './resource/shader/registerShaderResources.js';
|
|
135
135
|
export { CjsTextureArrayRes } from './resource/texture/CjsTextureArrayRes.js';
|
|
136
136
|
export { CjsTextureArrayResParameterProxy } from './resource/texture/CjsTextureArrayResParameterProxy.js';
|
|
137
|
+
export { RegisterSolidColorTexture, SolidColorTextureConstructor } from './resource/texture/SolidColorTextureConstructor.js';
|
|
137
138
|
export { Tr2ImageRes } from './resource/texture/Tr2ImageRes.js';
|
|
138
139
|
export { Tr2TextureLodManager } from './resource/texture/Tr2TextureLodManager.js';
|
|
139
140
|
export { Tr2TextureLodUpdateRequest } from './resource/texture/Tr2TextureLodUpdateRequest.js';
|
|
@@ -146,6 +147,7 @@ export { Tr2TexturePipelineStepLimitSize } from './resource/texture/Tr2TexturePi
|
|
|
146
147
|
export { Tr2TexturePipelineStepLoad } from './resource/texture/Tr2TexturePipelineStepLoad.js';
|
|
147
148
|
export { Tr2TexturePipelineStepPack } from './resource/texture/Tr2TexturePipelineStepPack.js';
|
|
148
149
|
export { TriTextureRes } from './resource/texture/TriTextureRes.js';
|
|
150
|
+
export { ColorPrefix, IsSolidColorTexturePath, ParseColor, RasterizeSolidColor } from './resource/texture/solidColorTexture.js';
|
|
149
151
|
export { CjsResourceProbe } from './resource/format/CjsResourceProbe.js';
|
|
150
152
|
export { ResourcePayloadType, ResourcePayloadValues, validateAudioPayload, validateRgbaPayload, validateTexturePayload, validateVideoPayload } from './resource/format/payloadContract.js';
|
|
151
153
|
export { CjsFormat, CjsFormatOutputRole, CjsFormatReadMode } from './resource/format/CjsFormat.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -157,6 +157,30 @@ let nextLocalValueIdentity = 1;
|
|
|
157
157
|
* @typedef {CjsResourceOwnership|CjsResourceReloadCandidate} CjsResourceMutationAuthority
|
|
158
158
|
*/
|
|
159
159
|
|
|
160
|
+
// `dynamic:` protocol (BlueResMan.cpp:219-223): the name is the segment after
|
|
161
|
+
// this prefix, and everything after the following slash is the query.
|
|
162
|
+
const DYNAMIC_RESOURCE_PREFIX = "dynamic:/";
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Carbon logs and returns null for an unknown dynamic name or a constructor that
|
|
166
|
+
* yields nothing (BlueResMan.cpp:225-235). There is no log channel here, and a
|
|
167
|
+
* null handle breaks every caller that composes on GetResource, so the same
|
|
168
|
+
* condition is raised, named.
|
|
169
|
+
*
|
|
170
|
+
* @param {string} path Normalized dynamic path.
|
|
171
|
+
* @param {string} name Constructor name.
|
|
172
|
+
* @param {string} code Error code.
|
|
173
|
+
* @param {string} message Error message.
|
|
174
|
+
* @returns {Error}
|
|
175
|
+
*/
|
|
176
|
+
function dynamicResourceError(path, name, code, message) {
|
|
177
|
+
const error = new Error(`CjsResMan ${message} (${path})`);
|
|
178
|
+
error.code = code;
|
|
179
|
+
error.path = path;
|
|
180
|
+
error.name = "CjsResManDynamicResourceError";
|
|
181
|
+
error.constructorName = name;
|
|
182
|
+
return error;
|
|
183
|
+
}
|
|
160
184
|
class CjsResMan {
|
|
161
185
|
// Carbon reaches its manager through the process-wide `BeResMan`
|
|
162
186
|
// (Tr2Effect.cpp:397). Ours needs the same reach for the same reason: a
|
|
@@ -216,6 +240,14 @@ class CjsResMan {
|
|
|
216
240
|
#resourceExtensionRoutes = new WeakMap();
|
|
217
241
|
#resourceHandlerModes = new WeakMap();
|
|
218
242
|
#resourceTypeCandidates = new Map();
|
|
243
|
+
// BlueResMan::m_dynamicConstructors (BlueResMan.h:154-155).
|
|
244
|
+
#dynamicConstructors = new Map();
|
|
245
|
+
#dynamicResources = new WeakSet();
|
|
246
|
+
// Carbon caches a LoadObject BUILDER and creates a new object per call
|
|
247
|
+
// (BlueResMan.cpp:653-795). For an OBJECT-mode route that hydrates, the
|
|
248
|
+
// payload is the decoded plain values - the builder - and this records what
|
|
249
|
+
// builds from them.
|
|
250
|
+
#objectBuilders = new WeakMap();
|
|
219
251
|
|
|
220
252
|
/**
|
|
221
253
|
* Create a GPU-free resource manager and apply its initial registration.
|
|
@@ -997,6 +1029,39 @@ class CjsResMan {
|
|
|
997
1029
|
return this.#extensionRoutes.get(normalizeResourceExtension(extension)) || null;
|
|
998
1030
|
}
|
|
999
1031
|
|
|
1032
|
+
/**
|
|
1033
|
+
* Registers the constructor that builds `dynamic:/<name>/<query>` resources
|
|
1034
|
+
* (BlueResMan.cpp:296-305). The name is lowercased, as Carbon's is; the
|
|
1035
|
+
* constructor's `GetResource(query)` returns a resource for that query.
|
|
1036
|
+
*
|
|
1037
|
+
* @param {string} name Name following `dynamic:/`.
|
|
1038
|
+
* @param {{GetResource: function(string): object}} constructor Resource constructor.
|
|
1039
|
+
* @returns {CjsResMan} This resource manager.
|
|
1040
|
+
* @throws {TypeError} If the name is empty or the constructor has no GetResource.
|
|
1041
|
+
*/
|
|
1042
|
+
RegisterResourceConstructor(name, constructor) {
|
|
1043
|
+
const key = String(name ?? "").toLowerCase();
|
|
1044
|
+
if (!key) {
|
|
1045
|
+
throw new TypeError("CjsResMan.RegisterResourceConstructor requires a name.");
|
|
1046
|
+
}
|
|
1047
|
+
if (typeof constructor?.GetResource !== "function") {
|
|
1048
|
+
throw new TypeError(`CjsResMan dynamic resource constructor "${key}" must implement GetResource.`);
|
|
1049
|
+
}
|
|
1050
|
+
this.#dynamicConstructors.set(key, constructor);
|
|
1051
|
+
return this;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Removes a dynamic resource constructor (BlueResMan.cpp:312-325).
|
|
1056
|
+
*
|
|
1057
|
+
* @param {string} name Name following `dynamic:/`.
|
|
1058
|
+
* @returns {CjsResMan} This resource manager.
|
|
1059
|
+
*/
|
|
1060
|
+
UnregisterResourceConstructor(name) {
|
|
1061
|
+
this.#dynamicConstructors.delete(String(name ?? "").toLowerCase());
|
|
1062
|
+
return this;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1000
1065
|
/**
|
|
1001
1066
|
* Return registered format facades for one normalized input extension.
|
|
1002
1067
|
*
|
|
@@ -1061,6 +1126,11 @@ class CjsResMan {
|
|
|
1061
1126
|
this.motherLode.KeepAlive(cacheKey);
|
|
1062
1127
|
return existing;
|
|
1063
1128
|
}
|
|
1129
|
+
// Below the cache lookup, as in BlueResMan::GetResourceHelper, so identical
|
|
1130
|
+
// queries share one resource.
|
|
1131
|
+
if (key.startsWith(DYNAMIC_RESOURCE_PREFIX)) {
|
|
1132
|
+
return this.#CreateDynamicResource(key, cacheKey);
|
|
1133
|
+
}
|
|
1064
1134
|
if (!existing && options.reload === true) {
|
|
1065
1135
|
this.InvalidateReadCache(key, {
|
|
1066
1136
|
source: options.source || this.source
|
|
@@ -1133,12 +1203,25 @@ class CjsResMan {
|
|
|
1133
1203
|
const ownership = this.#RequireResourceOwnership(resource, "object:begin");
|
|
1134
1204
|
const existing = this.objectOperations.get(resource);
|
|
1135
1205
|
if (existing?.ownership === ownership) {
|
|
1136
|
-
|
|
1206
|
+
// Joining an in-flight load shares its promise - unless the route builds
|
|
1207
|
+
// per caller, when the load's own caller receives the instance built at
|
|
1208
|
+
// publication and a joiner gets its own.
|
|
1209
|
+
const route = this.#resourceExtensionRoutes.get(resource);
|
|
1210
|
+
if (!route?.Target && !route?.Identify) return existing.promise;
|
|
1211
|
+
return existing.promise.then(result => this.#objectBuilders.has(resource) ? this.#BuildObject(resource) : result);
|
|
1137
1212
|
}
|
|
1138
1213
|
if (resource.HasPayload?.()) {
|
|
1139
1214
|
resource.KeepPayloadAlive?.();
|
|
1215
|
+
if (this.#objectBuilders.has(resource)) {
|
|
1216
|
+
return Promise.resolve().then(() => this.#BuildObject(resource));
|
|
1217
|
+
}
|
|
1140
1218
|
return Promise.resolve(getPublishedResourceObject(resource, this.#resourceExtensionRoutes.get(resource) || null, this.#resourceHandlerModes.get(resource) || null));
|
|
1141
1219
|
}
|
|
1220
|
+
if (this.#dynamicResources.has(resource)) {
|
|
1221
|
+
// A dynamic resource is built by its constructor, never read from a
|
|
1222
|
+
// source: without a payload it failed, and there is nothing to fetch.
|
|
1223
|
+
return Promise.reject(resource.error || dynamicResourceError(resource.GetPath(), "", "CJS_RESMAN_DYNAMIC_RESOURCE_UNAVAILABLE", "dynamic resource has no payload"));
|
|
1224
|
+
}
|
|
1142
1225
|
const promise = this.QueueResourceObject(resource, operationOptions);
|
|
1143
1226
|
const operation = {
|
|
1144
1227
|
promise,
|
|
@@ -1754,6 +1837,7 @@ class CjsResMan {
|
|
|
1754
1837
|
* @returns {*} Original or hydrated object value.
|
|
1755
1838
|
*/
|
|
1756
1839
|
#HydrateExtensionObject(resource, values, options, resolved) {
|
|
1840
|
+
this.#objectBuilders.delete(resource);
|
|
1757
1841
|
const route = resolved.route;
|
|
1758
1842
|
if (!route || !route.Target && !route.Identify) return values;
|
|
1759
1843
|
const Format = resolved.descriptor?.Format || null;
|
|
@@ -1781,8 +1865,39 @@ class CjsResMan {
|
|
|
1781
1865
|
throw createExtensionTargetError(resource, "Identify returned an invalid target constructor.", error);
|
|
1782
1866
|
}
|
|
1783
1867
|
}
|
|
1868
|
+
const hydrated = this.#HydrateTarget(resource, Target, values, context);
|
|
1869
|
+
// Publication decides whether this route builds per caller: only an
|
|
1870
|
+
// OBJECT-mode handle hands its object out, so only it keeps the builder.
|
|
1871
|
+
this.#objectBuilders.set(resource, {
|
|
1872
|
+
Target,
|
|
1873
|
+
context,
|
|
1874
|
+
values,
|
|
1875
|
+
hydrated
|
|
1876
|
+
});
|
|
1877
|
+
return hydrated;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
/**
|
|
1881
|
+
* Build one object from decoded values. Every build receives its own
|
|
1882
|
+
* structured copy, so no two callers share a nested array, typed array or
|
|
1883
|
+
* reader carrier however `from` treats its input - Carbon's objects are
|
|
1884
|
+
* likewise independent, each read out of the cached reader's bytes.
|
|
1885
|
+
*
|
|
1886
|
+
* @param {CjsResource} resource Resource whose values are built.
|
|
1887
|
+
* @param {Function} Target Resolved target constructor.
|
|
1888
|
+
* @param {*} values Decoded plain values.
|
|
1889
|
+
* @param {object} context Hydration context captured at publication.
|
|
1890
|
+
* @returns {*} A new hydrated object.
|
|
1891
|
+
*/
|
|
1892
|
+
#HydrateTarget(resource, Target, values, context) {
|
|
1893
|
+
let copy;
|
|
1894
|
+
try {
|
|
1895
|
+
copy = structuredClone(values);
|
|
1896
|
+
} catch (error) {
|
|
1897
|
+
throw createExtensionTargetError(resource, "Decoded values could not be copied for a per-caller build.", error);
|
|
1898
|
+
}
|
|
1784
1899
|
try {
|
|
1785
|
-
const hydrated = typeof Target.fromYAML === "function" ? Target.fromYAML(
|
|
1900
|
+
const hydrated = typeof Target.fromYAML === "function" ? Target.fromYAML(copy, context) : Target.from(copy);
|
|
1786
1901
|
if (hydrated && typeof hydrated.then === "function") {
|
|
1787
1902
|
throw new TypeError("Extension target hydration must be synchronous.");
|
|
1788
1903
|
}
|
|
@@ -1792,6 +1907,53 @@ class CjsResMan {
|
|
|
1792
1907
|
}
|
|
1793
1908
|
}
|
|
1794
1909
|
|
|
1910
|
+
/**
|
|
1911
|
+
* A fresh object from a resident builder: Carbon's
|
|
1912
|
+
* `builder->CreateObjectWithYield` (BlueResMan.cpp:785).
|
|
1913
|
+
*
|
|
1914
|
+
* @param {CjsResource} resource Resource holding the decoded values as payload.
|
|
1915
|
+
* @returns {*} A new hydrated object.
|
|
1916
|
+
*/
|
|
1917
|
+
#BuildObject(resource) {
|
|
1918
|
+
const builder = this.#objectBuilders.get(resource);
|
|
1919
|
+
return this.#HydrateTarget(resource, builder.Target, resource.GetPayload(), builder.context);
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
/**
|
|
1923
|
+
* `dynamic:` branch of BlueResMan::GetResourceHelper (BlueResMan.cpp:219-245).
|
|
1924
|
+
* Inserted not cacheable, Carbon's `CACHING_NOT_ALLOWED`: a dynamic resource is
|
|
1925
|
+
* never admitted to the byte cache.
|
|
1926
|
+
*
|
|
1927
|
+
* @param {string} key Normalized `dynamic:/<name>/<query>` path.
|
|
1928
|
+
* @param {string} cacheKey MotherLode key.
|
|
1929
|
+
* @returns {CjsResource} Canonical dynamic resource.
|
|
1930
|
+
* @throws {Error} If no constructor is registered or it yields no resource.
|
|
1931
|
+
*/
|
|
1932
|
+
#CreateDynamicResource(key, cacheKey) {
|
|
1933
|
+
const rest = key.slice(DYNAMIC_RESOURCE_PREFIX.length);
|
|
1934
|
+
const slash = rest.indexOf("/");
|
|
1935
|
+
const name = slash === -1 ? rest : rest.slice(0, slash);
|
|
1936
|
+
const query = slash === -1 ? "" : rest.slice(slash + 1);
|
|
1937
|
+
const constructor = this.#dynamicConstructors.get(name);
|
|
1938
|
+
if (!constructor) {
|
|
1939
|
+
throw dynamicResourceError(key, name, "CJS_RESMAN_DYNAMIC_CONSTRUCTOR_MISSING", `no dynamic constructor is registered for "${name}"`);
|
|
1940
|
+
}
|
|
1941
|
+
const resource = constructor.GetResource(query);
|
|
1942
|
+
if (!resource || typeof resource.Initialize !== "function" || typeof resource.SetObjectLoader !== "function") {
|
|
1943
|
+
throw dynamicResourceError(key, name, "CJS_RESMAN_DYNAMIC_RESOURCE_UNAVAILABLE", `dynamic constructor "${name}" returned no CjsResource-compatible resource`);
|
|
1944
|
+
}
|
|
1945
|
+
this.#dynamicResources.add(resource);
|
|
1946
|
+
resource.SetObjectLoader(() => this.GetObject(key));
|
|
1947
|
+
const insertion = this.motherLode.Insert(cacheKey, resource, {
|
|
1948
|
+
replace: true,
|
|
1949
|
+
cacheable: false
|
|
1950
|
+
});
|
|
1951
|
+
const canonical = insertion?.resource || resource;
|
|
1952
|
+
this.#BindResourceLifecycle(cacheKey, canonical);
|
|
1953
|
+
this.motherLode.KeepAlive(cacheKey);
|
|
1954
|
+
return canonical;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1795
1957
|
/**
|
|
1796
1958
|
* Publish the final CPU object outcome and loaded state. Every
|
|
1797
1959
|
* CjsResource-compatible handle owns the reader/converter result through
|
|
@@ -1848,12 +2010,23 @@ class CjsResMan {
|
|
|
1848
2010
|
*/
|
|
1849
2011
|
#PublishResourceObjectValue(resource, object, options) {
|
|
1850
2012
|
const mode = resolveResourceHandlerMode(resource, this.#resourceExtensionRoutes.get(resource) ? this.#resourceHandlerModes.get(resource) : null);
|
|
1851
|
-
|
|
2013
|
+
const builder = this.#objectBuilders.get(resource);
|
|
2014
|
+
const buildsPerCaller = mode === ResourceHandlerMode.OBJECT && builder?.hydrated === object;
|
|
2015
|
+
if (builder && !buildsPerCaller) this.#objectBuilders.delete(resource);
|
|
2016
|
+
// A per-caller route retains the decoded values - the builder - and hands
|
|
2017
|
+
// `object` to this publication's caller only. The values then live under the
|
|
2018
|
+
// payload lease, like any payload.
|
|
2019
|
+
const payload = buildsPerCaller ? builder.values : object;
|
|
2020
|
+
if (buildsPerCaller) {
|
|
2021
|
+
delete builder.values;
|
|
2022
|
+
delete builder.hydrated;
|
|
2023
|
+
}
|
|
2024
|
+
resource.SetPayload?.(payload, options);
|
|
1852
2025
|
// The two modes differ in one thing only: what `object` names and what the
|
|
1853
2026
|
// caller is handed back. RESOURCE publishes the stable handle, so both
|
|
1854
2027
|
// point at the resource; OBJECT publishes the reader outcome.
|
|
1855
2028
|
const published = mode === ResourceHandlerMode.RESOURCE ? resource : object;
|
|
1856
|
-
resource.object = published;
|
|
2029
|
+
resource.object = buildsPerCaller ? payload : published;
|
|
1857
2030
|
// PREPARED, not LOADED: `object` is the reader/converter OUTCOME, so the
|
|
1858
2031
|
// bytes have already been turned into whatever they needed to become.
|
|
1859
2032
|
// LOADED means raw source data is in hand and still has to be prepared - a
|