@carbonenginejs/runtime-utils 0.1.1 → 0.1.2
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/package.json +1 -1
- package/src/is.js +71 -9
- package/src/model/CjsModel.js +61 -16
- package/src/schema/CjsSchema.js +416 -297
- package/src/vec3.js +16 -0
package/package.json
CHANGED
package/src/is.js
CHANGED
|
@@ -143,15 +143,77 @@ export function isError(a)
|
|
|
143
143
|
* @param {*} a
|
|
144
144
|
* @returns {Boolean}
|
|
145
145
|
*/
|
|
146
|
-
export function isNumber(a)
|
|
147
|
-
{
|
|
148
|
-
return isTag(a, "[object Number]");
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Checks if a value
|
|
153
|
-
* @param {*}
|
|
154
|
-
* @returns {Boolean}
|
|
146
|
+
export function isNumber(a)
|
|
147
|
+
{
|
|
148
|
+
return isTag(a, "[object Number]");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Checks if a value fits a signed 8-bit integer.
|
|
153
|
+
* @param {*} value
|
|
154
|
+
* @returns {Boolean}
|
|
155
|
+
*/
|
|
156
|
+
export function isInt8(value)
|
|
157
|
+
{
|
|
158
|
+
return Number.isInteger(value) && value >= -0x80 && value <= 0x7f;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Checks if a value fits an unsigned 8-bit integer.
|
|
163
|
+
* @param {*} value
|
|
164
|
+
* @returns {Boolean}
|
|
165
|
+
*/
|
|
166
|
+
export function isUint8(value)
|
|
167
|
+
{
|
|
168
|
+
return Number.isInteger(value) && value >= 0 && value <= 0xff;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Checks if a value fits a signed 16-bit integer.
|
|
173
|
+
* @param {*} value
|
|
174
|
+
* @returns {Boolean}
|
|
175
|
+
*/
|
|
176
|
+
export function isInt16(value)
|
|
177
|
+
{
|
|
178
|
+
return Number.isInteger(value) && value >= -0x8000 && value <= 0x7fff;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Checks if a value fits an unsigned 16-bit integer.
|
|
183
|
+
* @param {*} value
|
|
184
|
+
* @returns {Boolean}
|
|
185
|
+
*/
|
|
186
|
+
export function isUint16(value)
|
|
187
|
+
{
|
|
188
|
+
return Number.isInteger(value) && value >= 0 && value <= 0xffff;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Checks if a value fits a signed 32-bit integer.
|
|
193
|
+
* @param {*} value
|
|
194
|
+
* @returns {Boolean}
|
|
195
|
+
*/
|
|
196
|
+
export function isInt32(value)
|
|
197
|
+
{
|
|
198
|
+
return Number.isInteger(value)
|
|
199
|
+
&& value >= -0x80000000
|
|
200
|
+
&& value <= 0x7fffffff;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Checks if a value fits an unsigned 32-bit integer.
|
|
205
|
+
* @param {*} value
|
|
206
|
+
* @returns {Boolean}
|
|
207
|
+
*/
|
|
208
|
+
export function isUint32(value)
|
|
209
|
+
{
|
|
210
|
+
return Number.isInteger(value) && value >= 0 && value <= 0xffffffff;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Checks if a value is a function
|
|
215
|
+
* @param {*} a
|
|
216
|
+
* @returns {Boolean}
|
|
155
217
|
*/
|
|
156
218
|
export function isFunction(a)
|
|
157
219
|
{
|
package/src/model/CjsModel.js
CHANGED
|
@@ -13,6 +13,15 @@ const MAX_UPDATE_PASSES = 32;
|
|
|
13
13
|
*/
|
|
14
14
|
export class CjsModel extends CjsEventEmitter
|
|
15
15
|
{
|
|
16
|
+
/**
|
|
17
|
+
* Identifies this class as a schema-backed model.
|
|
18
|
+
*
|
|
19
|
+
* Declared statically so CjsSchema can recognise a model class from a field
|
|
20
|
+
* declaration alone, without importing CjsModel - which it cannot do, since
|
|
21
|
+
* this module already imports CjsSchema. Mirrors `CjsResource.isResource`.
|
|
22
|
+
*/
|
|
23
|
+
static isModel = true;
|
|
24
|
+
|
|
16
25
|
/**
|
|
17
26
|
* Creates a schema-backed model with initialized runtime state.
|
|
18
27
|
*/
|
|
@@ -208,16 +217,18 @@ export class CjsModel extends CjsEventEmitter
|
|
|
208
217
|
|
|
209
218
|
if (descend)
|
|
210
219
|
{
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
220
|
+
// Only the fields declared to hold child models, precomputed per
|
|
221
|
+
// class - not every field, type-tested per value per visit.
|
|
222
|
+
const children = CjsSchema.getSchema(model.constructor).children;
|
|
223
|
+
const start = reverse ? children.length - 1 : 0;
|
|
224
|
+
const end = reverse ? -1 : children.length;
|
|
214
225
|
const step = reverse ? -1 : 1;
|
|
215
226
|
|
|
216
227
|
for (let i = start; i !== end; i += step)
|
|
217
228
|
{
|
|
218
|
-
const
|
|
219
|
-
if (options.ownedOnly === true &&
|
|
220
|
-
const value = model[
|
|
229
|
+
const child = children[i];
|
|
230
|
+
if (options.ownedOnly === true && !child.owned) continue;
|
|
231
|
+
const value = model[child.name];
|
|
221
232
|
|
|
222
233
|
if (Array.isArray(value))
|
|
223
234
|
{
|
|
@@ -242,19 +253,47 @@ export class CjsModel extends CjsEventEmitter
|
|
|
242
253
|
/**
|
|
243
254
|
* Collects unique resources reported by this model graph into an array.
|
|
244
255
|
*
|
|
256
|
+
* Every model in the graph is visited: reporting resources does not hide a
|
|
257
|
+
* model's descendants, because an under-reported dependency set would let
|
|
258
|
+
* readiness checks pass while a child's resources were still loading.
|
|
259
|
+
*
|
|
260
|
+
* Resources held in schema fields are collected automatically - they are
|
|
261
|
+
* already declared, as `@type.objectRef("TriGeometryRes")` and friends, so
|
|
262
|
+
* restating them in a hook would be the hand-written relay chain this
|
|
263
|
+
* traversal exists to replace.
|
|
264
|
+
*
|
|
265
|
+
* `OnGetResources()` is the escape hatch for resources a model holds
|
|
266
|
+
* outside its schema, such as private fields. It takes no arguments and
|
|
267
|
+
* always returns an iterable of resources - never a bare resource and never
|
|
268
|
+
* nothing. Most models do not implement it.
|
|
269
|
+
*
|
|
245
270
|
* @param {Array<*>} [out=[]] Output array, whose contents are replaced.
|
|
246
271
|
* @returns {Array<*>} The supplied output array.
|
|
247
272
|
*/
|
|
248
273
|
GetResources(out = [])
|
|
249
274
|
{
|
|
250
275
|
const resources = new Set();
|
|
251
|
-
AddResources(resources, out);
|
|
252
276
|
|
|
253
277
|
this.Traverse(model =>
|
|
254
278
|
{
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
279
|
+
for (const field of CjsSchema.getSchema(model.constructor).resources)
|
|
280
|
+
{
|
|
281
|
+
const value = model[field.name];
|
|
282
|
+
if (Array.isArray(value))
|
|
283
|
+
{
|
|
284
|
+
for (const item of value) AddResource(resources, item);
|
|
285
|
+
}
|
|
286
|
+
else
|
|
287
|
+
{
|
|
288
|
+
AddResource(resources, value);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (typeof model.OnGetResources === "function")
|
|
293
|
+
{
|
|
294
|
+
AddResources(resources, model.OnGetResources());
|
|
295
|
+
}
|
|
296
|
+
return true;
|
|
258
297
|
});
|
|
259
298
|
|
|
260
299
|
out.length = 0;
|
|
@@ -776,17 +815,23 @@ function initializeOwnedGraph(root, options = {})
|
|
|
776
815
|
return root;
|
|
777
816
|
}
|
|
778
817
|
|
|
818
|
+
function AddResource(target, value)
|
|
819
|
+
{
|
|
820
|
+
if (value?.isResource === true) target.add(value);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
|
|
779
824
|
function AddResources(target, values)
|
|
780
825
|
{
|
|
781
|
-
if (values ===
|
|
782
|
-
if (values?.isResource === true)
|
|
826
|
+
if (typeof values === "string" || typeof values?.[Symbol.iterator] !== "function")
|
|
783
827
|
{
|
|
784
|
-
|
|
785
|
-
return;
|
|
828
|
+
throw new TypeError("CjsModel.OnGetResources must return an iterable of resources.");
|
|
786
829
|
}
|
|
787
|
-
|
|
830
|
+
|
|
831
|
+
// Empty slots are the model's own unset fields, not a contract violation.
|
|
832
|
+
for (const value of values)
|
|
788
833
|
{
|
|
789
|
-
|
|
834
|
+
if (value !== null && value !== undefined) target.add(value);
|
|
790
835
|
}
|
|
791
836
|
}
|
|
792
837
|
|
package/src/schema/CjsSchema.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
const CLASS_SCHEMA = new WeakMap();
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
const CLASS_SCHEMA = new WeakMap();
|
|
2
|
+
|
|
3
|
+
// Exported schemas, memoized per class. SCHEMA_GENERATION is bumped by every
|
|
4
|
+
// metadata definition (see getOrCreateClassSchema), which is what makes a stale
|
|
5
|
+
// memo detectable without tracking which subclasses a base class change reaches.
|
|
6
|
+
const SCHEMA_EXPORTS = new WeakMap();
|
|
7
|
+
let SCHEMA_GENERATION = 0;
|
|
8
|
+
|
|
9
|
+
const CONSTRUCTOR_BY_NAME = new Map();
|
|
10
|
+
const ENUM_SCHEMA_BY_NAME = new Map();
|
|
11
|
+
const ENUM_SCHEMA_BY_OBJECT = new WeakMap();
|
|
12
|
+
const STAGE3_FIELD_METADATA = Symbol("carbonenginejs.schema.stage3Fields");
|
|
6
13
|
|
|
7
14
|
export const CJS_ENUM_NAME = Symbol.for("carbonenginejs.enum.name");
|
|
8
15
|
|
|
@@ -56,32 +63,32 @@ export class CjsSchema
|
|
|
56
63
|
return this;
|
|
57
64
|
}
|
|
58
65
|
|
|
59
|
-
static defineMethod(Constructor, methodName, namespace, value)
|
|
60
|
-
{
|
|
61
|
-
defineMethodMetadata(Constructor, methodName, namespace, value);
|
|
62
|
-
return this;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
static getField(Constructor, fieldName)
|
|
66
|
-
{
|
|
67
|
-
return getEffectiveFields(Constructor).find(field => field.name === fieldName) || null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Excludes named inherited fields from the decorated class's schema surface.
|
|
72
|
-
*/
|
|
73
|
-
static hideInherited(fieldNames)
|
|
74
|
-
{
|
|
75
|
-
return hiddenInheritedFieldsDecorator(normalizeHiddenInheritedFields(fieldNames));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Checks whether a field is hidden from a class by its inheritance chain.
|
|
80
|
-
*/
|
|
81
|
-
static isFieldHidden(Constructor, fieldName)
|
|
82
|
-
{
|
|
83
|
-
return getHiddenInheritedFieldNames(Constructor).has(fieldName);
|
|
84
|
-
}
|
|
66
|
+
static defineMethod(Constructor, methodName, namespace, value)
|
|
67
|
+
{
|
|
68
|
+
defineMethodMetadata(Constructor, methodName, namespace, value);
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static getField(Constructor, fieldName)
|
|
73
|
+
{
|
|
74
|
+
return getEffectiveFields(Constructor).find(field => field.name === fieldName) || null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Excludes named inherited fields from the decorated class's schema surface.
|
|
79
|
+
*/
|
|
80
|
+
static hideInherited(fieldNames)
|
|
81
|
+
{
|
|
82
|
+
return hiddenInheritedFieldsDecorator(normalizeHiddenInheritedFields(fieldNames));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Checks whether a field is hidden from a class by its inheritance chain.
|
|
87
|
+
*/
|
|
88
|
+
static isFieldHidden(Constructor, fieldName)
|
|
89
|
+
{
|
|
90
|
+
return getHiddenInheritedFieldNames(Constructor).has(fieldName);
|
|
91
|
+
}
|
|
85
92
|
|
|
86
93
|
static getMethod(Constructor, methodName)
|
|
87
94
|
{
|
|
@@ -128,6 +135,9 @@ export class CjsSchema
|
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
CONSTRUCTOR_BY_NAME.set(name.trim(), Constructor);
|
|
138
|
+
// Buckets resolve class references by name, so a late registration
|
|
139
|
+
// changes how already-built schemas should have been bucketed.
|
|
140
|
+
SCHEMA_GENERATION += 1;
|
|
131
141
|
return this;
|
|
132
142
|
}
|
|
133
143
|
|
|
@@ -151,49 +161,40 @@ export class CjsSchema
|
|
|
151
161
|
return name ? ENUM_SCHEMA_BY_NAME.get(name) || null : null;
|
|
152
162
|
}
|
|
153
163
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
if (
|
|
178
|
-
{
|
|
179
|
-
result.family = family;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (schema?.sourceClass && schema.sourceClass !== result.className)
|
|
183
|
-
{
|
|
184
|
-
result.sourceClass = schema.sourceClass;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (schema?.aliases?.length)
|
|
188
|
-
{
|
|
189
|
-
result.aliases = Object.freeze([...schema.aliases]);
|
|
190
|
-
}
|
|
164
|
+
/**
|
|
165
|
+
* Return the exported schema for a class.
|
|
166
|
+
*
|
|
167
|
+
* The schema is the precomputed answer - collapsing the inheritance
|
|
168
|
+
* lineage and merging metadata - so building it per call would defeat its
|
|
169
|
+
* purpose. Callers traverse model graphs and ask once per node, so this is
|
|
170
|
+
* memoized per class and rebuilt only when class metadata is defined.
|
|
171
|
+
*
|
|
172
|
+
* Namespace-filtered exports are not memoized: they are a projection of the
|
|
173
|
+
* full schema requested by tooling, not the hot read path.
|
|
174
|
+
*
|
|
175
|
+
* The result is shared, not copied - treat it as read-only. It is not
|
|
176
|
+
* frozen: deep-cloning and freezing every field on the way out cost far
|
|
177
|
+
* more than the mistakes it guarded against.
|
|
178
|
+
*
|
|
179
|
+
* @param {Function} Constructor
|
|
180
|
+
* @param {object} [options={}]
|
|
181
|
+
* @param {string|Array<string>} [options.namespaces] Restricts exported metadata namespaces.
|
|
182
|
+
* @returns {object} Shared schema export.
|
|
183
|
+
*/
|
|
184
|
+
static getSchema(Constructor, options = {})
|
|
185
|
+
{
|
|
186
|
+
const namespaces = normalizeNamespaces(options.namespaces);
|
|
187
|
+
if (namespaces) return buildSchema(Constructor, namespaces);
|
|
191
188
|
|
|
192
|
-
|
|
189
|
+
const memo = SCHEMA_EXPORTS.get(Constructor);
|
|
190
|
+
if (memo && memo.generation === SCHEMA_GENERATION) return memo.schema;
|
|
193
191
|
|
|
194
|
-
|
|
192
|
+
const schema = buildSchema(Constructor, null);
|
|
193
|
+
SCHEMA_EXPORTS.set(Constructor, { generation: SCHEMA_GENERATION, schema });
|
|
194
|
+
return schema;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
|
|
197
198
|
static type = Object.freeze({
|
|
198
199
|
array: itemType => fieldDecorator("type", { kind: "array", itemType }),
|
|
199
200
|
boolean: fieldDecorator("type", { kind: "boolean" }),
|
|
@@ -338,17 +339,17 @@ function createComponentsNamespace()
|
|
|
338
339
|
return Object.freeze(components);
|
|
339
340
|
}
|
|
340
341
|
|
|
341
|
-
function fieldDecorator(namespace, value)
|
|
342
|
-
{
|
|
343
|
-
return function schemaFieldDecorator(targetOrValue, contextOrFieldName)
|
|
344
|
-
{
|
|
345
|
-
if (contextOrFieldName && typeof contextOrFieldName === "object")
|
|
346
|
-
{
|
|
347
|
-
const context = contextOrFieldName;
|
|
348
|
-
if (context.kind !== "field") throw new TypeError("CjsSchema decorators only support class fields.");
|
|
349
|
-
recordStage3FieldMetadata(context, namespace, value);
|
|
350
|
-
|
|
351
|
-
// Register field metadata on instance construction. addInitializer covers
|
|
342
|
+
function fieldDecorator(namespace, value)
|
|
343
|
+
{
|
|
344
|
+
return function schemaFieldDecorator(targetOrValue, contextOrFieldName)
|
|
345
|
+
{
|
|
346
|
+
if (contextOrFieldName && typeof contextOrFieldName === "object")
|
|
347
|
+
{
|
|
348
|
+
const context = contextOrFieldName;
|
|
349
|
+
if (context.kind !== "field") throw new TypeError("CjsSchema decorators only support class fields.");
|
|
350
|
+
recordStage3FieldMetadata(context, namespace, value);
|
|
351
|
+
|
|
352
|
+
// Register field metadata on instance construction. addInitializer covers
|
|
352
353
|
// spec-compliant runtimes; the returned field initializer covers runtimes (e.g.
|
|
353
354
|
// Deno/SWC) that do NOT fire field-decorator addInitializer. Both register the same
|
|
354
355
|
// metadata (idempotent via mergeNamespace), so whichever the runtime honours, the
|
|
@@ -376,44 +377,44 @@ function fieldDecorator(namespace, value)
|
|
|
376
377
|
};
|
|
377
378
|
}
|
|
378
379
|
|
|
379
|
-
function classDefinitionDecorator(definition)
|
|
380
|
-
{
|
|
381
|
-
return function schemaClassDefinitionDecorator(value, context)
|
|
382
|
-
{
|
|
383
|
-
if (context && typeof context === "object")
|
|
384
|
-
{
|
|
385
|
-
if (context.kind !== "class") throw new TypeError("CjsSchema type.define only supports classes.");
|
|
386
|
-
registerStage3FieldMetadata(value, context.metadata);
|
|
387
|
-
defineClassMetadata(value, normalizeClassDefinition(value, definition));
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
380
|
+
function classDefinitionDecorator(definition)
|
|
381
|
+
{
|
|
382
|
+
return function schemaClassDefinitionDecorator(value, context)
|
|
383
|
+
{
|
|
384
|
+
if (context && typeof context === "object")
|
|
385
|
+
{
|
|
386
|
+
if (context.kind !== "class") throw new TypeError("CjsSchema type.define only supports classes.");
|
|
387
|
+
registerStage3FieldMetadata(value, context.metadata);
|
|
388
|
+
defineClassMetadata(value, normalizeClassDefinition(value, definition));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
390
391
|
|
|
391
392
|
if (typeof value !== "function")
|
|
392
393
|
{
|
|
393
394
|
throw new TypeError("CjsSchema type.define requires a class constructor.");
|
|
394
395
|
}
|
|
395
396
|
|
|
396
|
-
defineClassMetadata(value, normalizeClassDefinition(value, definition));
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function hiddenInheritedFieldsDecorator(fieldNames)
|
|
401
|
-
{
|
|
402
|
-
return function schemaHiddenInheritedFieldsDecorator(value, context)
|
|
403
|
-
{
|
|
404
|
-
if (context && typeof context === "object")
|
|
405
|
-
{
|
|
406
|
-
if (context.kind !== "class") throw new TypeError("CjsSchema.hideInherited only supports classes.");
|
|
407
|
-
registerStage3FieldMetadata(value, context.metadata);
|
|
408
|
-
}
|
|
409
|
-
else if (typeof value !== "function")
|
|
410
|
-
{
|
|
411
|
-
throw new TypeError("CjsSchema.hideInherited requires a class constructor.");
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
defineHiddenInheritedFields(value, fieldNames);
|
|
415
|
-
};
|
|
416
|
-
}
|
|
397
|
+
defineClassMetadata(value, normalizeClassDefinition(value, definition));
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function hiddenInheritedFieldsDecorator(fieldNames)
|
|
402
|
+
{
|
|
403
|
+
return function schemaHiddenInheritedFieldsDecorator(value, context)
|
|
404
|
+
{
|
|
405
|
+
if (context && typeof context === "object")
|
|
406
|
+
{
|
|
407
|
+
if (context.kind !== "class") throw new TypeError("CjsSchema.hideInherited only supports classes.");
|
|
408
|
+
registerStage3FieldMetadata(value, context.metadata);
|
|
409
|
+
}
|
|
410
|
+
else if (typeof value !== "function")
|
|
411
|
+
{
|
|
412
|
+
throw new TypeError("CjsSchema.hideInherited requires a class constructor.");
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
defineHiddenInheritedFields(value, fieldNames);
|
|
416
|
+
};
|
|
417
|
+
}
|
|
417
418
|
|
|
418
419
|
const CONTEXT_FIRST_PARAMETER = /^\(?\s*_?(context|updateContext)\b/;
|
|
419
420
|
|
|
@@ -523,10 +524,10 @@ function defineMethodMetadata(Constructor, methodName, namespace, value)
|
|
|
523
524
|
defineMemberMetadata(Constructor, "methods", "methodsByName", methodName, namespace, value);
|
|
524
525
|
}
|
|
525
526
|
|
|
526
|
-
function defineMemberMetadata(Constructor, listKey, mapKey, name, namespace, value)
|
|
527
|
-
{
|
|
528
|
-
const schema = getOrCreateClassSchema(Constructor);
|
|
529
|
-
let item = schema[mapKey].get(name);
|
|
527
|
+
function defineMemberMetadata(Constructor, listKey, mapKey, name, namespace, value)
|
|
528
|
+
{
|
|
529
|
+
const schema = getOrCreateClassSchema(Constructor);
|
|
530
|
+
let item = schema[mapKey].get(name);
|
|
530
531
|
|
|
531
532
|
if (!item)
|
|
532
533
|
{
|
|
@@ -534,188 +535,307 @@ function defineMemberMetadata(Constructor, listKey, mapKey, name, namespace, val
|
|
|
534
535
|
schema[listKey].push(item);
|
|
535
536
|
schema[mapKey].set(name, item);
|
|
536
537
|
}
|
|
537
|
-
|
|
538
|
-
item[namespace] = mergeNamespace(item[namespace], value);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
function defineHiddenInheritedFields(Constructor, fieldNames)
|
|
542
|
-
{
|
|
543
|
-
if (typeof Constructor !== "function")
|
|
544
|
-
{
|
|
545
|
-
throw new TypeError("CjsSchema.hideInherited requires a class constructor.");
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const Parent = Object.getPrototypeOf(Constructor);
|
|
549
|
-
const inheritedFields = new Set(getEffectiveFields(Parent).map(field => field.name));
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
538
|
+
|
|
539
|
+
item[namespace] = mergeNamespace(item[namespace], value);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function defineHiddenInheritedFields(Constructor, fieldNames)
|
|
543
|
+
{
|
|
544
|
+
if (typeof Constructor !== "function")
|
|
545
|
+
{
|
|
546
|
+
throw new TypeError("CjsSchema.hideInherited requires a class constructor.");
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const Parent = Object.getPrototypeOf(Constructor);
|
|
550
|
+
const inheritedFields = new Set(getEffectiveFields(Parent).map(field => field.name));
|
|
551
|
+
// Declared name only: Constructor.name does not survive minification, and a
|
|
552
|
+
// mangled name in an error reads as a real one and sends you chasing it.
|
|
553
|
+
const className = CLASS_SCHEMA.get(Constructor)?.className || "<undeclared>";
|
|
554
|
+
|
|
555
|
+
for (const fieldName of fieldNames)
|
|
556
|
+
{
|
|
557
|
+
if (!inheritedFields.has(fieldName))
|
|
558
|
+
{
|
|
559
|
+
throw new TypeError(
|
|
560
|
+
`CjsSchema.hideInherited cannot hide "${fieldName}" on ${className}: ` +
|
|
561
|
+
"the parent schema does not expose that field."
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const schema = getOrCreateClassSchema(Constructor);
|
|
567
|
+
for (const fieldName of fieldNames)
|
|
568
|
+
{
|
|
569
|
+
schema.hiddenInherited.add(fieldName);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function getEffectiveFields(Constructor)
|
|
574
|
+
{
|
|
575
|
+
const ordered = [];
|
|
576
|
+
const byName = new Map();
|
|
577
|
+
const hidden = new Set();
|
|
578
|
+
|
|
579
|
+
for (const current of getSchemaLineage(Constructor))
|
|
580
|
+
{
|
|
581
|
+
const schema = CLASS_SCHEMA.get(current);
|
|
582
|
+
for (const field of schema?.fields || [])
|
|
583
|
+
{
|
|
584
|
+
const existing = byName.get(field.name);
|
|
585
|
+
if (existing)
|
|
586
|
+
{
|
|
587
|
+
mergeMemberMetadata(existing, field);
|
|
588
|
+
}
|
|
589
|
+
else
|
|
590
|
+
{
|
|
591
|
+
const merged = mergeMemberMetadata({ name: field.name }, field);
|
|
592
|
+
ordered.push(merged);
|
|
593
|
+
byName.set(field.name, merged);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
for (const fieldName of schema?.hiddenInherited || [])
|
|
598
|
+
{
|
|
599
|
+
hidden.add(fieldName);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return ordered.filter(field => !hidden.has(field.name));
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function getHiddenInheritedFieldNames(Constructor)
|
|
607
|
+
{
|
|
608
|
+
const hidden = new Set();
|
|
609
|
+
for (const current of getSchemaLineage(Constructor))
|
|
610
|
+
{
|
|
611
|
+
for (const fieldName of CLASS_SCHEMA.get(current)?.hiddenInherited || [])
|
|
612
|
+
{
|
|
613
|
+
hidden.add(fieldName);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return hidden;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function getSchemaLineage(Constructor)
|
|
620
|
+
{
|
|
621
|
+
const lineage = [];
|
|
622
|
+
let current = Constructor;
|
|
623
|
+
while (typeof current === "function")
|
|
624
|
+
{
|
|
625
|
+
if (CLASS_SCHEMA.has(current)) lineage.push(current);
|
|
626
|
+
current = Object.getPrototypeOf(current);
|
|
627
|
+
}
|
|
628
|
+
return lineage.reverse();
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function mergeMemberMetadata(target, source)
|
|
632
|
+
{
|
|
633
|
+
for (const [namespace, value] of Object.entries(source))
|
|
634
|
+
{
|
|
635
|
+
if (namespace === "name") continue;
|
|
636
|
+
target[namespace] = mergeNamespace(target[namespace], value);
|
|
637
|
+
}
|
|
638
|
+
return target;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function buildSchema(Constructor, namespaces)
|
|
642
|
+
{
|
|
643
|
+
const schema = CLASS_SCHEMA.get(Constructor);
|
|
644
|
+
const fields = [];
|
|
645
|
+
const methods = [];
|
|
646
|
+
|
|
647
|
+
for (const field of getEffectiveFields(Constructor))
|
|
648
|
+
{
|
|
649
|
+
fields.push(enrichEnumField(exportField(field, namespaces), Constructor));
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
for (const method of schema?.methods || [])
|
|
653
|
+
{
|
|
654
|
+
methods.push(exportField(method, namespaces));
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const result = {
|
|
658
|
+
className: CjsSchema.getClassName(Constructor),
|
|
659
|
+
fields
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
const family = schema?.family || CjsSchema.getClassFamily(Constructor);
|
|
663
|
+
if (family)
|
|
664
|
+
{
|
|
665
|
+
result.family = family;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (schema?.sourceClass && schema.sourceClass !== result.className)
|
|
669
|
+
{
|
|
670
|
+
result.sourceClass = schema.sourceClass;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (schema?.aliases?.length)
|
|
674
|
+
{
|
|
675
|
+
result.aliases = [ ...schema.aliases ];
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (methods.length) result.methods = methods;
|
|
679
|
+
|
|
680
|
+
addSchemaBuckets(result);
|
|
681
|
+
return result;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
// Kinds that hold many values rather than one. `map` and `set` are included
|
|
686
|
+
// because they are iterable collections of the referenced class, same as a list.
|
|
687
|
+
const MANY_KINDS = new Set([ "list", "array", "set", "map" ]);
|
|
688
|
+
|
|
689
|
+
// Kinds that can hold a child model. Bucketing on the KIND rather than on a
|
|
690
|
+
// resolvable class reference keeps traversal conservative: `type.struct(Class)`
|
|
691
|
+
// drops the reference during normalization, and a raw defineField may omit the
|
|
692
|
+
// item type, so requiring a reference would silently stop visiting those.
|
|
693
|
+
// Scalar and math kinds are excluded, which is where the saving comes from.
|
|
694
|
+
const MODEL_KINDS = new Set([
|
|
695
|
+
"struct", "model", "rawStruct", "objectRef", "unknown",
|
|
696
|
+
"list", "array", "set", "map"
|
|
697
|
+
]);
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Precompute the answers consumers would otherwise recompute per traversal.
|
|
702
|
+
*
|
|
703
|
+
* Graph walks ask the same two questions of every node - which fields hold
|
|
704
|
+
* child models, which hold resources - and answering them by scanning the field
|
|
705
|
+
* list and type-testing each value costs more than the walk itself. The class
|
|
706
|
+
* cannot change without rebuilding its schema, so this is solved once.
|
|
707
|
+
*/
|
|
708
|
+
function addSchemaBuckets(schema)
|
|
709
|
+
{
|
|
710
|
+
const byName = new Map();
|
|
711
|
+
const children = [];
|
|
712
|
+
const resources = [];
|
|
713
|
+
|
|
714
|
+
for (const field of schema.fields)
|
|
715
|
+
{
|
|
716
|
+
byName.set(field.name, field);
|
|
717
|
+
|
|
718
|
+
const type = field.type;
|
|
719
|
+
// An undeclared field could hold anything, so it stays traversable.
|
|
720
|
+
const kind = type?.kind;
|
|
721
|
+
if (kind && !MODEL_KINDS.has(kind)) continue;
|
|
722
|
+
|
|
723
|
+
const entry = { name: field.name, many: MANY_KINDS.has(kind) };
|
|
724
|
+
|
|
725
|
+
if (type && resolveFieldClass(type)?.isResource === true)
|
|
726
|
+
{
|
|
727
|
+
resources.push(entry);
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
entry.owned = field.io?.ownership === "owned";
|
|
732
|
+
children.push(entry);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
schema.byName = byName;
|
|
736
|
+
schema.children = children;
|
|
737
|
+
schema.resources = resources;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
// Only string references survive normalization - type.struct(SomeClass) drops
|
|
742
|
+
// the reference entirely - so a field can only be bucketed when it names a
|
|
743
|
+
// class. The one field in the tree that named nothing was a missing
|
|
744
|
+
// declaration, not a deliberate escape.
|
|
745
|
+
function resolveFieldClass(type)
|
|
746
|
+
{
|
|
747
|
+
const ref = type.className || type.itemType || type.valueType;
|
|
748
|
+
return typeof ref === "string" && ref ? CONSTRUCTOR_BY_NAME.get(ref) || null : null;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
function getOrCreateClassSchema(Constructor)
|
|
753
|
+
{
|
|
754
|
+
// The only route by which class metadata is mutated, and therefore the only
|
|
755
|
+
// place exported schemas can go stale. A single global counter rather than
|
|
756
|
+
// per-class invalidation because a base class change invalidates every
|
|
757
|
+
// subclass, and lineage is not tracked in reverse.
|
|
758
|
+
SCHEMA_GENERATION += 1;
|
|
759
|
+
|
|
760
|
+
let schema = CLASS_SCHEMA.get(Constructor);
|
|
761
|
+
if (!schema)
|
|
642
762
|
{
|
|
643
763
|
schema = {
|
|
644
764
|
className: null,
|
|
645
765
|
family: null,
|
|
646
766
|
sourceClass: null,
|
|
647
|
-
aliases: null,
|
|
648
|
-
fields: [],
|
|
649
|
-
fieldsByName: new Map(),
|
|
650
|
-
hiddenInherited: new Set(),
|
|
651
|
-
methods: [],
|
|
652
|
-
methodsByName: new Map()
|
|
653
|
-
};
|
|
767
|
+
aliases: null,
|
|
768
|
+
fields: [],
|
|
769
|
+
fieldsByName: new Map(),
|
|
770
|
+
hiddenInherited: new Set(),
|
|
771
|
+
methods: [],
|
|
772
|
+
methodsByName: new Map()
|
|
773
|
+
};
|
|
654
774
|
CLASS_SCHEMA.set(Constructor, schema);
|
|
655
775
|
}
|
|
656
|
-
return schema;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
function normalizeHiddenInheritedFields(fieldNames)
|
|
660
|
-
{
|
|
661
|
-
if (!Array.isArray(fieldNames) || !fieldNames.length)
|
|
662
|
-
{
|
|
663
|
-
throw new TypeError("CjsSchema.hideInherited requires a non-empty array of field names.");
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
const normalized = fieldNames.map((fieldName, index) =>
|
|
667
|
-
{
|
|
668
|
-
if (typeof fieldName !== "string" || !fieldName.trim())
|
|
669
|
-
{
|
|
670
|
-
throw new TypeError(`CjsSchema.hideInherited fieldNames[${index}] must be a non-empty string.`);
|
|
671
|
-
}
|
|
672
|
-
return fieldName.trim();
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
return Object.freeze([...new Set(normalized)]);
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
function recordStage3FieldMetadata(context, namespace, value)
|
|
679
|
-
{
|
|
680
|
-
const metadata = context?.metadata;
|
|
681
|
-
if (!metadata || typeof metadata !== "object") return;
|
|
682
|
-
|
|
683
|
-
let fields;
|
|
684
|
-
if (Object.prototype.hasOwnProperty.call(metadata, STAGE3_FIELD_METADATA))
|
|
685
|
-
{
|
|
686
|
-
fields = metadata[STAGE3_FIELD_METADATA];
|
|
687
|
-
}
|
|
688
|
-
else
|
|
689
|
-
{
|
|
690
|
-
fields = [];
|
|
691
|
-
Object.defineProperty(metadata, STAGE3_FIELD_METADATA, {
|
|
692
|
-
configurable: false,
|
|
693
|
-
enumerable: false,
|
|
694
|
-
value: fields,
|
|
695
|
-
writable: false
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
fields.push({
|
|
700
|
-
name: context.name,
|
|
701
|
-
namespace,
|
|
702
|
-
value
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
function registerStage3FieldMetadata(Constructor, metadata)
|
|
707
|
-
{
|
|
708
|
-
if (!metadata || typeof metadata !== "object") return;
|
|
709
|
-
if (!Object.prototype.hasOwnProperty.call(metadata, STAGE3_FIELD_METADATA)) return;
|
|
710
|
-
|
|
711
|
-
for (const field of metadata[STAGE3_FIELD_METADATA])
|
|
712
|
-
{
|
|
713
|
-
defineFieldMetadata(Constructor, field.name, field.namespace, field.value);
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
function normalizeClassDefinition(Constructor, definition)
|
|
718
|
-
{
|
|
776
|
+
return schema;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function normalizeHiddenInheritedFields(fieldNames)
|
|
780
|
+
{
|
|
781
|
+
if (!Array.isArray(fieldNames) || !fieldNames.length)
|
|
782
|
+
{
|
|
783
|
+
throw new TypeError("CjsSchema.hideInherited requires a non-empty array of field names.");
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const normalized = fieldNames.map((fieldName, index) =>
|
|
787
|
+
{
|
|
788
|
+
if (typeof fieldName !== "string" || !fieldName.trim())
|
|
789
|
+
{
|
|
790
|
+
throw new TypeError(`CjsSchema.hideInherited fieldNames[${index}] must be a non-empty string.`);
|
|
791
|
+
}
|
|
792
|
+
return fieldName.trim();
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
return Object.freeze([...new Set(normalized)]);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function recordStage3FieldMetadata(context, namespace, value)
|
|
799
|
+
{
|
|
800
|
+
const metadata = context?.metadata;
|
|
801
|
+
if (!metadata || typeof metadata !== "object") return;
|
|
802
|
+
|
|
803
|
+
let fields;
|
|
804
|
+
if (Object.prototype.hasOwnProperty.call(metadata, STAGE3_FIELD_METADATA))
|
|
805
|
+
{
|
|
806
|
+
fields = metadata[STAGE3_FIELD_METADATA];
|
|
807
|
+
}
|
|
808
|
+
else
|
|
809
|
+
{
|
|
810
|
+
fields = [];
|
|
811
|
+
Object.defineProperty(metadata, STAGE3_FIELD_METADATA, {
|
|
812
|
+
configurable: false,
|
|
813
|
+
enumerable: false,
|
|
814
|
+
value: fields,
|
|
815
|
+
writable: false
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
fields.push({
|
|
820
|
+
name: context.name,
|
|
821
|
+
namespace,
|
|
822
|
+
value
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function registerStage3FieldMetadata(Constructor, metadata)
|
|
827
|
+
{
|
|
828
|
+
if (!metadata || typeof metadata !== "object") return;
|
|
829
|
+
if (!Object.prototype.hasOwnProperty.call(metadata, STAGE3_FIELD_METADATA)) return;
|
|
830
|
+
|
|
831
|
+
for (const field of metadata[STAGE3_FIELD_METADATA])
|
|
832
|
+
{
|
|
833
|
+
defineFieldMetadata(Constructor, field.name, field.namespace, field.value);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function normalizeClassDefinition(Constructor, definition)
|
|
838
|
+
{
|
|
719
839
|
if (typeof definition === "string")
|
|
720
840
|
{
|
|
721
841
|
definition = { className: definition };
|
|
@@ -937,9 +1057,8 @@ function componentIndex(char)
|
|
|
937
1057
|
|
|
938
1058
|
function mergeNamespace(existing, value)
|
|
939
1059
|
{
|
|
940
|
-
if (
|
|
941
|
-
|
|
942
|
-
return cloneSchemaValue(value);
|
|
1060
|
+
if (isPlainObject(existing) && isPlainObject(value)) return { ...existing, ...value };
|
|
1061
|
+
return value;
|
|
943
1062
|
}
|
|
944
1063
|
|
|
945
1064
|
// Resolves @schema.enum("X") through the owning class's PascalCase static so
|
|
@@ -984,10 +1103,10 @@ function exportField(field, namespaces)
|
|
|
984
1103
|
{
|
|
985
1104
|
if (key === "name") continue;
|
|
986
1105
|
if (namespaces && !namespaces.has(key)) continue;
|
|
987
|
-
result[key] =
|
|
1106
|
+
result[key] = value;
|
|
988
1107
|
}
|
|
989
1108
|
|
|
990
|
-
return
|
|
1109
|
+
return result;
|
|
991
1110
|
}
|
|
992
1111
|
|
|
993
1112
|
function normalizeNamespaces(namespaces)
|
package/src/vec3.js
CHANGED
|
@@ -559,6 +559,21 @@ vec3.isEmpty = function (a)
|
|
|
559
559
|
return a[0] === 0 && a[1] === 0 && a[2] === 0;
|
|
560
560
|
};
|
|
561
561
|
|
|
562
|
+
/**
|
|
563
|
+
* The largest of the three components.
|
|
564
|
+
*
|
|
565
|
+
* A reduction to one number, unlike `max`, which is the component-wise maximum
|
|
566
|
+
* of two vectors. Carbon spells it `MaxVectorComponent`, and uses it to reduce a
|
|
567
|
+
* colour to the single value that decides how bright it counts as.
|
|
568
|
+
*
|
|
569
|
+
* @param {vec3} a
|
|
570
|
+
* @returns {Number}
|
|
571
|
+
*/
|
|
572
|
+
vec3.maxComponent = function (a)
|
|
573
|
+
{
|
|
574
|
+
return Math.max(a[0], a[1], a[2]);
|
|
575
|
+
};
|
|
576
|
+
|
|
562
577
|
/**
|
|
563
578
|
* Multiplies a vec3 by a scalar
|
|
564
579
|
*
|
|
@@ -1097,6 +1112,7 @@ export const {
|
|
|
1097
1112
|
length,
|
|
1098
1113
|
lerp,
|
|
1099
1114
|
max,
|
|
1115
|
+
maxComponent,
|
|
1100
1116
|
min,
|
|
1101
1117
|
mul,
|
|
1102
1118
|
multiply,
|