@aelionsdk/render-ir 1.2.0 → 2.0.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.
package/README.md CHANGED
@@ -9,7 +9,7 @@ evaluation for AelionSDK.
9
9
  npm install @aelionsdk/render-ir
10
10
  ```
11
11
 
12
- `latest` currently resolves to `1.2.0`. Product applications should use
12
+ `latest` currently resolves to `2.0.0`. Product applications should use
13
13
  `@aelionsdk/sdk`; direct use is for custom renderers, exporters and engine
14
14
  instrumentation.
15
15
 
@@ -1 +1 @@
1
- {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAGnB,MAAM,2BAA2B,CAAC;AAGnC,OAAO,KAAK,EAaV,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAsepB,qBAAa,yBAAyB;;IAcpC;;;;OAIG;IACI,IAAI,IAAI,yBAAyB;IAcxC,4EAA4E;IACrE,KAAK,IAAI,IAAI;IAOb,OAAO,CACZ,OAAO,EAAE,aAAa,EACtB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,uBAAuB,GACnB,oBAAoB,GACpB,mBAAmB,CAAC,OAAO,CAAC,CAAC,gBAAgB,CAAM,GACtD,mBAAmB;CAoOvB"}
1
+ {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAqC,MAAM,2BAA2B,CAAC;AAGlG,OAAO,KAAK,EAaV,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAkgBpB,qBAAa,yBAAyB;;IAcpC;;;;OAIG;IACI,IAAI,IAAI,yBAAyB;IAcxC,4EAA4E;IACrE,KAAK,IAAI,IAAI;IAOb,OAAO,CACZ,OAAO,EAAE,aAAa,EACtB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,uBAAuB,GACnB,oBAAoB,GACpB,mBAAmB,CAAC,OAAO,CAAC,CAAC,gBAAgB,CAAM,GACtD,mBAAmB;CAyOvB"}
package/dist/compiler.js CHANGED
@@ -1,4 +1,4 @@
1
- import { canonicalStringify, } from '@aelionsdk/project-schema';
1
+ import {} from '@aelionsdk/project-schema';
2
2
  /**
3
3
  * Objects `deepFreezePlain` has already frozen, along with everything beneath
4
4
  * them.
@@ -24,6 +24,8 @@ const COMPILABLE_ITEM_TYPES = new Set([
24
24
  'shape',
25
25
  'material-content',
26
26
  'adjustment',
27
+ // Compiles to no clip at all; see the Gap branch in the Track loop below.
28
+ 'gap',
27
29
  ]);
28
30
  function deepFreezePlain(value, seen = new WeakSet()) {
29
31
  if (value === null || typeof value !== 'object')
@@ -32,12 +34,23 @@ function deepFreezePlain(value, seen = new WeakSet()) {
32
34
  return value;
33
35
  if (seen.has(value))
34
36
  return value;
35
- const prototype = Object.getPrototypeOf(value);
36
- if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null)
37
- return value;
38
- seen.add(value);
39
- for (const entry of Object.values(value))
40
- deepFreezePlain(entry, seen);
37
+ if (Array.isArray(value)) {
38
+ seen.add(value);
39
+ for (const entry of value)
40
+ deepFreezePlain(entry, seen);
41
+ }
42
+ else {
43
+ const prototype = Object.getPrototypeOf(value);
44
+ if (prototype !== Object.prototype && prototype !== null)
45
+ return value;
46
+ seen.add(value);
47
+ // `for...in` over a plain object walks V8's enumeration cache without
48
+ // materializing a key or value array for every node in the IR, which at a
49
+ // thousand clips is tens of thousands of throwaway arrays per compile.
50
+ for (const key in value) {
51
+ deepFreezePlain(value[key], seen);
52
+ }
53
+ }
41
54
  // Recorded only after the whole subtree is frozen, so membership never
42
55
  // promises more than has actually been done.
43
56
  deepFrozen.add(value);
@@ -216,22 +229,37 @@ function nestedSequenceSource(item) {
216
229
  boundary: boundary,
217
230
  };
218
231
  }
232
+ /**
233
+ * Serializes a value for fingerprint comparison.
234
+ *
235
+ * A fingerprint is only ever compared against the fingerprint the previous
236
+ * compile recorded for the same entity, so it needs one property: two entities
237
+ * that differ must serialize differently. Canonical ordering buys the stronger
238
+ * property that two *equal* entities always agree even if their keys were
239
+ * written in a different order -- which here would at worst cause a recompile
240
+ * that produces the same clip, and costs a hand-written walk over every value
241
+ * in the Sequence to avoid. `JSON.stringify` is exact for the plain JSON an
242
+ * admitted Project contains, and the engine runs it natively.
243
+ */
244
+ function fingerprintOf(value) {
245
+ return JSON.stringify(value);
246
+ }
219
247
  function clipFingerprint(item, materials, assets) {
220
248
  const source = object(item.source ?? {}, `item ${item.id}.source`);
221
249
  const sourceAssetId = typeof source.assetId === 'string' ? source.assetId : undefined;
222
250
  const sourceAsset = sourceAssetId === undefined ? undefined : assets[sourceAssetId];
223
251
  return [
224
- canonicalStringify(item),
252
+ fingerprintOf(item),
225
253
  ...(sourceAsset?.kind === 'image-sequence'
226
- ? [canonicalStringify(sourceAsset.imageSequence ?? null)]
254
+ ? [fingerprintOf(sourceAsset.imageSequence ?? null)]
227
255
  : []),
228
256
  ...item.materialInstanceIds.map(id => materialFingerprint(materials[id])),
229
257
  ].join('|');
230
258
  }
231
259
  function materialFingerprint(instance) {
232
260
  if (instance === undefined)
233
- return canonicalStringify(null);
234
- return canonicalStringify({
261
+ return 'null';
262
+ return fingerprintOf({
235
263
  id: instance.id,
236
264
  definition: {
237
265
  packageId: instance.definition.packageId,
@@ -450,7 +478,7 @@ export class IncrementalRenderCompiler {
450
478
  /**
451
479
  * Track fingerprints keyed by the source track object.
452
480
  *
453
- * A track fingerprint is `canonicalStringify(track)`, and a track carries every
481
+ * A track fingerprint serializes the whole Track, and a Track carries every
454
482
  * item id on it, so recomputing it for a thousand-clip track dominated the cost
455
483
  * of an incremental compile even when nothing on that track changed. Commits
456
484
  * share structure: editing one item leaves every untouched track at the same
@@ -471,7 +499,7 @@ export class IncrementalRenderCompiler {
471
499
  const cached = this.#trackFingerprints.get(track);
472
500
  if (cached !== undefined)
473
501
  return cached;
474
- const fingerprint = canonicalStringify(track);
502
+ const fingerprint = fingerprintOf(track);
475
503
  this.#trackFingerprints.set(track, fingerprint);
476
504
  return fingerprint;
477
505
  }
@@ -525,6 +553,12 @@ export class IncrementalRenderCompiler {
525
553
  if (!COMPILABLE_ITEM_TYPES.has(item.type)) {
526
554
  throw new TypeError(`Render IR cannot compile item type ${item.type}`);
527
555
  }
556
+ // A Gap is time and nothing else. It still lengthens the Sequence,
557
+ // because `contentDuration` reads the Project rather than the clip
558
+ // list, but it produces no clip for the renderer or the mixer to
559
+ // consider.
560
+ if (item.type === 'gap')
561
+ continue;
528
562
  const previous = previousClips.get(itemId);
529
563
  if (canReuseByEntity &&
530
564
  previous !== undefined &&
@@ -576,7 +610,7 @@ export class IncrementalRenderCompiler {
576
610
  materialInstanceId: value.materialInstanceId,
577
611
  dependencyEntityIds: [id, value.fromItemId, value.toItemId, value.materialInstanceId],
578
612
  fingerprint: [
579
- canonicalStringify(value),
613
+ fingerprintOf(value),
580
614
  materialFingerprint(materials[value.materialInstanceId]),
581
615
  ].join('|'),
582
616
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aelionsdk/render-ir",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "Incremental render intermediate representation compiler for AelionSDK",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,9 +34,9 @@
34
34
  "provenance": true
35
35
  },
36
36
  "dependencies": {
37
- "@aelionsdk/core": "1.2.0",
38
- "@aelionsdk/material-compiler": "1.2.0",
39
- "@aelionsdk/project-schema": "1.2.0"
37
+ "@aelionsdk/core": "2.0.0",
38
+ "@aelionsdk/material-compiler": "2.0.0",
39
+ "@aelionsdk/project-schema": "2.0.0"
40
40
  },
41
41
  "scripts": {
42
42
  "build": "tsc -b",