@aelionsdk/render-ir 1.2.0-rc.5 → 1.2.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
@@ -6,10 +6,10 @@ evaluation for AelionSDK.
6
6
  ## Install
7
7
 
8
8
  ```bash
9
- npm install @aelionsdk/render-ir@next
9
+ npm install @aelionsdk/render-ir
10
10
  ```
11
11
 
12
- `next` currently resolves to `1.2.0-rc.5`. Product applications should use
12
+ `latest` currently resolves to `1.2.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,EAAsB,KAAK,aAAa,EAAmB,MAAM,2BAA2B,CAAC;AAGpG,OAAO,KAAK,EAaV,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAscpB,qBAAa,yBAAyB;;IAIpC;;;;OAIG;IACI,IAAI,IAAI,yBAAyB;IAMxC,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;CAwOvB"}
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"}
package/dist/compiler.js CHANGED
@@ -1,7 +1,35 @@
1
- import { canonicalStringify } from '@aelionsdk/project-schema';
1
+ import { canonicalStringify, } from '@aelionsdk/project-schema';
2
+ /**
3
+ * Objects `deepFreezePlain` has already frozen, along with everything beneath
4
+ * them.
5
+ *
6
+ * An incremental compile reuses most of the previous IR verbatim, and those
7
+ * objects were deep-frozen by the compile that produced them. Re-walking them
8
+ * dominated incremental compile time -- roughly three quarters of a no-op
9
+ * compile at a thousand clips. Membership means the whole subtree is already
10
+ * frozen, so the walk can stop at the first reused node.
11
+ *
12
+ * Entries are held weakly, so a released IR is still collectable.
13
+ */
14
+ const deepFrozen = new WeakSet();
15
+ /** Item types the Render IR can compile, as a set so the check is one lookup. */
16
+ const COMPILABLE_ITEM_TYPES = new Set([
17
+ 'video',
18
+ 'image',
19
+ 'audio',
20
+ 'text',
21
+ 'caption',
22
+ 'nested-sequence',
23
+ 'generator',
24
+ 'shape',
25
+ 'material-content',
26
+ 'adjustment',
27
+ ]);
2
28
  function deepFreezePlain(value, seen = new WeakSet()) {
3
29
  if (value === null || typeof value !== 'object')
4
30
  return value;
31
+ if (deepFrozen.has(value))
32
+ return value;
5
33
  if (seen.has(value))
6
34
  return value;
7
35
  const prototype = Object.getPrototypeOf(value);
@@ -10,6 +38,9 @@ function deepFreezePlain(value, seen = new WeakSet()) {
10
38
  seen.add(value);
11
39
  for (const entry of Object.values(value))
12
40
  deepFreezePlain(entry, seen);
41
+ // Recorded only after the whole subtree is frozen, so membership never
42
+ // promises more than has actually been done.
43
+ deepFrozen.add(value);
13
44
  return Object.freeze(value);
14
45
  }
15
46
  function object(value, context) {
@@ -416,6 +447,16 @@ function contentDuration(project, trackIds) {
416
447
  export class IncrementalRenderCompiler {
417
448
  #previous;
418
449
  #compiling = false;
450
+ /**
451
+ * Track fingerprints keyed by the source track object.
452
+ *
453
+ * A track fingerprint is `canonicalStringify(track)`, and a track carries every
454
+ * item id on it, so recomputing it for a thousand-clip track dominated the cost
455
+ * of an incremental compile even when nothing on that track changed. Commits
456
+ * share structure: editing one item leaves every untouched track at the same
457
+ * object identity, so identity is a sound key and an edited track simply misses.
458
+ */
459
+ #trackFingerprints = new WeakMap();
419
460
  /**
420
461
  * Creates an isolated compiler that reuses this compiler's immutable baseline.
421
462
  * Compiling on the fork cannot advance or corrupt the parent baseline; a host
@@ -426,6 +467,14 @@ export class IncrementalRenderCompiler {
426
467
  fork.#previous = this.#previous;
427
468
  return fork;
428
469
  }
470
+ #trackFingerprint(track) {
471
+ const cached = this.#trackFingerprints.get(track);
472
+ if (cached !== undefined)
473
+ return cached;
474
+ const fingerprint = canonicalStringify(track);
475
+ this.#trackFingerprints.set(track, fingerprint);
476
+ return fingerprint;
477
+ }
429
478
  /** Releases the incremental baseline retained for clip/transition reuse. */
430
479
  clear() {
431
480
  if (this.#compiling) {
@@ -465,36 +514,34 @@ export class IncrementalRenderCompiler {
465
514
  const track = project.tracks[trackId];
466
515
  if (track === undefined)
467
516
  throw new RangeError(`Track ${trackId} does not exist`);
468
- const clips = track.itemIds.flatMap(itemId => {
517
+ // A plain loop rather than `flatMap`: the reuse path runs once per item on
518
+ // every commit, and wrapping each result in a throwaway single-element array
519
+ // was the largest remaining cost of an incremental compile.
520
+ const clips = [];
521
+ for (const itemId of track.itemIds) {
469
522
  const item = project.items[itemId];
470
523
  if (item === undefined)
471
524
  throw new RangeError(`Item ${itemId} does not exist`);
472
- if (item.type !== 'video' &&
473
- item.type !== 'image' &&
474
- item.type !== 'audio' &&
475
- item.type !== 'text' &&
476
- item.type !== 'caption' &&
477
- item.type !== 'nested-sequence' &&
478
- item.type !== 'generator' &&
479
- item.type !== 'shape' &&
480
- item.type !== 'material-content' &&
481
- item.type !== 'adjustment')
525
+ if (!COMPILABLE_ITEM_TYPES.has(item.type)) {
482
526
  throw new TypeError(`Render IR cannot compile item type ${item.type}`);
527
+ }
483
528
  const previous = previousClips.get(itemId);
484
529
  if (canReuseByEntity &&
485
530
  previous !== undefined &&
486
531
  !previous.dependencyEntityIds.some(id => affectedEntityIds.has(id))) {
487
532
  reusedClips += 1;
488
- return [previous];
533
+ clips.push(previous);
534
+ continue;
489
535
  }
490
536
  const candidate = compileClip(item, materials, project.assets);
491
537
  if (previous?.fingerprint === candidate.fingerprint) {
492
538
  reusedClips += 1;
493
- return [previous];
539
+ clips.push(previous);
540
+ continue;
494
541
  }
495
542
  compiledClips += 1;
496
- return [candidate];
497
- });
543
+ clips.push(candidate);
544
+ }
498
545
  return {
499
546
  id: track.id,
500
547
  kind: track.kind,
@@ -506,7 +553,7 @@ export class IncrementalRenderCompiler {
506
553
  : {}),
507
554
  clips,
508
555
  materialInstanceIds: [...track.materialInstanceIds],
509
- fingerprint: canonicalStringify(track),
556
+ fingerprint: this.#trackFingerprint(track),
510
557
  };
511
558
  });
512
559
  const transitions = sequence.transitionIds.map(id => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aelionsdk/render-ir",
3
- "version": "1.2.0-rc.5",
3
+ "version": "1.2.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-rc.5",
38
- "@aelionsdk/material-compiler": "1.2.0-rc.5",
39
- "@aelionsdk/project-schema": "1.2.0-rc.5"
37
+ "@aelionsdk/core": "1.2.0",
38
+ "@aelionsdk/material-compiler": "1.2.0",
39
+ "@aelionsdk/project-schema": "1.2.0"
40
40
  },
41
41
  "scripts": {
42
42
  "build": "tsc -b",