@swf2es/player-hosts 0.1.5 → 0.1.6

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.
@@ -25,6 +25,7 @@ export function ownBounds(d, lines) {
25
25
  : null;
26
26
  }
27
27
  if (d instanceof TextObject) {
28
+ d.settle();
28
29
  return { xMin: d.left, yMin: d.top, xMax: d.left + d.width, yMax: d.top + d.height };
29
30
  }
30
31
  if (d instanceof VideoObject) {
@@ -95,12 +96,116 @@ export function placedRect(d, r, drawn = false) {
95
96
  const box = transformRect(r, own);
96
97
  return back ? boxThrough(box, back) : box;
97
98
  }
99
+ // Scratch matrices for boundsIn's flat case: getRect and getBounds are asked of every moved
100
+ // sprite, every frame, by some movies, and these leave nothing behind for the collector.
101
+ const scratchUp = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
102
+ const scratchDown = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
103
+ const scratchBoth = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
104
+ /** `m` set to `p`'s six numbers. */
105
+ function copyInto(m, p) {
106
+ m.a = p.a;
107
+ m.b = p.b;
108
+ m.c = p.c;
109
+ m.d = p.d;
110
+ m.tx = p.tx;
111
+ m.ty = p.ty;
112
+ }
113
+ /** `m` then `p` into `out`, as concat multiplies two flat matrices; `out` may be `m`. */
114
+ function concatInto(out, m, p) {
115
+ const a = m.a * p.a + m.b * p.c;
116
+ const b = m.a * p.b + m.b * p.d;
117
+ const c = m.c * p.a + m.d * p.c;
118
+ const d = m.c * p.b + m.d * p.d;
119
+ const tx = m.tx * p.a + m.ty * p.c + p.tx;
120
+ const ty = m.tx * p.b + m.ty * p.d + p.ty;
121
+ out.a = a;
122
+ out.b = b;
123
+ out.c = c;
124
+ out.d = d;
125
+ out.tx = tx;
126
+ out.ty = ty;
127
+ }
128
+ // `placed` of one with a scrollRect, shifted as geometry's shifted shifts it, without a new matrix.
129
+ const scratchPlaced = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
130
+ /** `d.placed` if flat, into scratchPlaced where a scroll shifts it; null for a projective one. */
131
+ function flatPlaced(d) {
132
+ if (d.space) {
133
+ const m = d.placed;
134
+ return isProjective(m) ? null : m;
135
+ }
136
+ const m = d.matrix;
137
+ if (isProjective(m)) {
138
+ return null;
139
+ }
140
+ const r = d.scroll;
141
+ if (!r) {
142
+ return m;
143
+ }
144
+ copyInto(scratchPlaced, m);
145
+ scratchPlaced.tx = m.tx - m.a * r.xMin - m.c * r.yMin;
146
+ scratchPlaced.ty = m.ty - m.b * r.xMin - m.d * r.yMin;
147
+ return scratchPlaced;
148
+ }
149
+ /** toStage into `out`, if every matrix on the way is flat; false, `out` spoilt, otherwise. */
150
+ function flatToStage(d, stage, out) {
151
+ const own = flatPlaced(d);
152
+ if (!own) {
153
+ return false;
154
+ }
155
+ copyInto(out, own);
156
+ for (let p = d.parent; p && p !== stage; p = p.parent) {
157
+ const m = flatPlaced(p);
158
+ if (!m) {
159
+ return false;
160
+ }
161
+ concatInto(out, out, m);
162
+ }
163
+ return true;
164
+ }
165
+ /** `m` inverted in place, as invert does; left as it is, as invert's null is, when flat. */
166
+ function invertFlat(m) {
167
+ const det = m.a * m.d - m.b * m.c;
168
+ if (det === 0) {
169
+ return false;
170
+ }
171
+ const a = m.d / det;
172
+ const b = -m.b / det;
173
+ const c = -m.c / det;
174
+ const d = m.a / det;
175
+ const tx = (m.c * m.ty - m.d * m.tx) / det;
176
+ const ty = (m.b * m.tx - m.a * m.ty) / det;
177
+ m.a = a;
178
+ m.b = b;
179
+ m.c = c;
180
+ m.d = d;
181
+ m.tx = tx;
182
+ m.ty = ty;
183
+ return true;
184
+ }
98
185
  /** `d`'s bounds in `target`'s space (its own for null), through the stage. */
99
186
  export function boundsIn(d, target, stage, lines) {
100
187
  const own = bounds(d, lines) ?? { xMin: 0, yMin: 0, xMax: 0, yMax: 0 };
101
188
  if (!target || target === d) {
102
189
  return own;
103
190
  }
191
+ // Flat all the way, the common case, the same arithmetic as below's in scratch matrices.
192
+ if (flatToStage(d, stage, scratchUp)) {
193
+ if (target === stage) {
194
+ copyInto(scratchDown, IDENTITY);
195
+ }
196
+ else if (!flatToStage(target, stage, scratchDown)) {
197
+ return boundsInThrough(d, target, stage, own);
198
+ }
199
+ else if (!invertFlat(scratchDown)) {
200
+ copyInto(scratchDown, IDENTITY);
201
+ }
202
+ concatInto(scratchBoth, scratchUp, scratchDown);
203
+ return transformRect(own, scratchBoth);
204
+ }
205
+ return boundsInThrough(d, target, stage, own);
206
+ }
207
+ /** boundsIn where a plane is on the way: up or down through a projective matrix. */
208
+ function boundsInThrough(d, target, stage, own) {
104
209
  const up = toStage(d, stage);
105
210
  const down = target === stage ? IDENTITY : (invert(toStage(target, stage)) ?? IDENTITY);
106
211
  if (!isProjective(up)) {
@@ -20,6 +20,13 @@ function fillsOnly(layer) {
20
20
  const LINE_POINTS = [0, 0];
21
21
  const CURVE_POINTS = [0, 0, 0, 0];
22
22
  const CUBIC_POINTS = [0, 0, 0, 0, 0, 0];
23
+ // Each quarter's start and end directions, (sx, sy) and (ex, ey), for arc.
24
+ const QUARTERS = [
25
+ [1, 0, 0, 1],
26
+ [0, 1, -1, 0],
27
+ [-1, 0, 0, -1],
28
+ [0, -1, 1, 0],
29
+ ];
23
30
  /** `kind` and `points` onto the last of `paths`. */
24
31
  function append(paths, kind, points) {
25
32
  const path = paths[paths.length - 1];
@@ -40,8 +47,13 @@ export class Drawing {
40
47
  /** Where the open fill's entries begin in its layer, which fills before it may share. */
41
48
  fillStart = 0;
42
49
  stroke = null;
43
- /** The line closing the open contour, or the one endFill closed, while a line drawn on may take it away. */
50
+ /**
51
+ * The stroke whose last closing line closes the open contour, or the one
52
+ * endFill closed, while a line drawn on may take it away.
53
+ */
44
54
  closing = null;
55
+ /** The closing line last taken away, for the next: a filled, lined pen makes one a segment. */
56
+ spareClose = null;
45
57
  /**
46
58
  * A point of the open contour off its start, for whether it leaves the
47
59
  * line through them: its offset from the start, while `heading`.
@@ -205,12 +217,11 @@ export class Drawing {
205
217
  if (rx === 0 && ry === 0) {
206
218
  return;
207
219
  }
208
- const [sx, sy, ex, ey] = [
209
- [1, 0, 0, 1],
210
- [0, 1, -1, 0],
211
- [-1, 0, 0, -1],
212
- [0, -1, 1, 0],
213
- ][quarter];
220
+ const turn = QUARTERS[quarter];
221
+ const sx = turn[0];
222
+ const sy = turn[1];
223
+ const ex = turn[2];
224
+ const ey = turn[3];
214
225
  this.curveTo(cx + (sx + ex * TAN_22_5) * rx, cy + (sy + ey * TAN_22_5) * ry, cx + (sx + ex) * COS_45 * rx, cy + (sy + ey) * COS_45 * ry);
215
226
  this.curveTo(cx + (ex + sx * TAN_22_5) * rx, cy + (ey + sy * TAN_22_5) * ry, cx + ex * rx, cy + ey * ry);
216
227
  }
@@ -591,7 +602,7 @@ export class Drawing {
591
602
  unclose() {
592
603
  if (this.closing) {
593
604
  this.version++;
594
- this.closing.stroke.closes?.pop();
605
+ this.spareClose = this.closing.closes?.pop() ?? null;
595
606
  this.closing = null;
596
607
  }
597
608
  }
@@ -603,10 +614,14 @@ export class Drawing {
603
614
  return;
604
615
  }
605
616
  if (contour[1] !== this.x || contour[2] !== this.y) {
606
- const close = { at: stroke.paths.length - 1, x: contour[1], y: contour[2] };
617
+ const close = this.spareClose ?? { at: 0, x: 0, y: 0 };
618
+ this.spareClose = null;
619
+ close.at = stroke.paths.length - 1;
620
+ close.x = contour[1];
621
+ close.y = contour[2];
607
622
  stroke.closes = stroke.closes ?? [];
608
623
  stroke.closes.push(close);
609
- this.closing = { stroke, close };
624
+ this.closing = stroke;
610
625
  this.version++;
611
626
  }
612
627
  }
@@ -58,21 +58,60 @@ export function inside(points, px, py) {
58
58
  }
59
59
  return hit;
60
60
  }
61
+ // The running box of extent: one for every call, as bounds are asked of every drawn shape.
62
+ const box = new Float64Array(4);
63
+ function take(x, y) {
64
+ box[0] = Math.min(box[0], x);
65
+ box[1] = Math.min(box[1], y);
66
+ box[2] = Math.max(box[2], x);
67
+ box[3] = Math.max(box[3], y);
68
+ }
69
+ /** The quadratic curve from (x, y) through (cx, cy) to (ax, ay), at `t`, into the box. */
70
+ function takeCurveAt(t, x, y, cx, cy, ax, ay) {
71
+ const u = 1 - t;
72
+ take(u * u * x + 2 * u * t * cx + t * t * ax, u * u * y + 2 * u * t * cy + t * t * ay);
73
+ }
74
+ /** The cubic curve from (x, y) by (c1x, c1y) and (c2x, c2y) to (ax, ay), at `t`, into the box. */
75
+ function takeCubicAt(t, x, y, c1x, c1y, c2x, c2y, ax, ay) {
76
+ if (!(t > 0 && t < 1)) {
77
+ return;
78
+ }
79
+ const u = 1 - t;
80
+ take(u * u * u * x + 3 * u * u * t * c1x + 3 * u * t * t * c2x + t * t * t * ax, u * u * u * y + 3 * u * u * t * c1y + 3 * u * t * t * c2y + t * t * t * ay);
81
+ }
82
+ /**
83
+ * Where the cubic's derivative on one axis, from p0 by c1 and c2 to p3, is
84
+ * zero within it: the roots of a t² + b t + c, each into the box.
85
+ */
86
+ function takeCubicTurns(p0, c1, c2, p3, x, y, c1x, c1y, c2x, c2y, ax, ay) {
87
+ const a = -p0 + 3 * c1 - 3 * c2 + p3;
88
+ const b = 2 * (p0 - 2 * c1 + c2);
89
+ const c = c1 - p0;
90
+ if (a === 0) {
91
+ if (b !== 0) {
92
+ takeCubicAt(-c / b, x, y, c1x, c1y, c2x, c2y, ax, ay);
93
+ }
94
+ return;
95
+ }
96
+ const d = b * b - 4 * a * c;
97
+ if (d < 0) {
98
+ return;
99
+ }
100
+ const s = Math.sqrt(d);
101
+ takeCubicAt((-b + s) / (2 * a), x, y, c1x, c1y, c2x, c2y, ax, ay);
102
+ takeCubicAt((-b - s) / (2 * a), x, y, c1x, c1y, c2x, c2y, ax, ay);
103
+ }
61
104
  /**
62
105
  * The path's extent, each curve at its true extremes, as [xMin, yMin,
63
- * xMax, yMax]; null for a path with no points.
106
+ * xMax, yMax]; null for a path with no points. It allocates only its
107
+ * answer: the bounds of every shape drawn, a pen's redrawn each frame
108
+ * among them, are asked again and again.
64
109
  */
65
110
  export function extent(path) {
66
- let xMin = Number.POSITIVE_INFINITY;
67
- let yMin = Number.POSITIVE_INFINITY;
68
- let xMax = Number.NEGATIVE_INFINITY;
69
- let yMax = Number.NEGATIVE_INFINITY;
70
- const take = (x, y) => {
71
- xMin = Math.min(xMin, x);
72
- yMin = Math.min(yMin, y);
73
- xMax = Math.max(xMax, x);
74
- yMax = Math.max(yMax, y);
75
- };
111
+ box[0] = Number.POSITIVE_INFINITY;
112
+ box[1] = Number.POSITIVE_INFINITY;
113
+ box[2] = Number.NEGATIVE_INFINITY;
114
+ box[3] = Number.NEGATIVE_INFINITY;
76
115
  let x = 0;
77
116
  let y = 0;
78
117
  for (let i = 0; i < path.length;) {
@@ -86,49 +125,36 @@ export function extent(path) {
86
125
  }
87
126
  take(x, y);
88
127
  if (command === CURVE) {
89
- const [cx, cy, ax, ay] = path.slice(i + 1, i + 5);
128
+ const cx = path[i + 1];
129
+ const cy = path[i + 2];
130
+ const ax = path[i + 3];
131
+ const ay = path[i + 4];
90
132
  take(ax, ay);
91
133
  // Where the derivative is zero on either axis, if within the curve.
92
- const at = (t) => {
93
- const u = 1 - t;
94
- take(u * u * x + 2 * u * t * cx + t * t * ax, u * u * y + 2 * u * t * cy + t * t * ay);
95
- };
96
- for (const [p0, c, p2] of [
97
- [x, cx, ax],
98
- [y, cy, ay],
99
- ]) {
100
- const denominator = p0 - 2 * c + p2;
101
- const t = denominator === 0 ? -1 : (p0 - c) / denominator;
102
- if (t > 0 && t < 1) {
103
- at(t);
104
- }
134
+ const dx = x - 2 * cx + ax;
135
+ const tx = dx === 0 ? -1 : (x - cx) / dx;
136
+ if (tx > 0 && tx < 1) {
137
+ takeCurveAt(tx, x, y, cx, cy, ax, ay);
138
+ }
139
+ const dy = y - 2 * cy + ay;
140
+ const ty = dy === 0 ? -1 : (y - cy) / dy;
141
+ if (ty > 0 && ty < 1) {
142
+ takeCurveAt(ty, x, y, cx, cy, ax, ay);
105
143
  }
106
144
  x = ax;
107
145
  y = ay;
108
146
  i += 5;
109
147
  }
110
148
  else if (command === CUBIC) {
111
- const [c1x, c1y, c2x, c2y, ax, ay] = path.slice(i + 1, i + 7);
149
+ const c1x = path[i + 1];
150
+ const c1y = path[i + 2];
151
+ const c2x = path[i + 3];
152
+ const c2y = path[i + 4];
153
+ const ax = path[i + 5];
154
+ const ay = path[i + 6];
112
155
  take(ax, ay);
113
- const at = (t) => {
114
- const u = 1 - t;
115
- take(u * u * u * x + 3 * u * u * t * c1x + 3 * u * t * t * c2x + t * t * t * ax, u * u * u * y + 3 * u * u * t * c1y + 3 * u * t * t * c2y + t * t * t * ay);
116
- };
117
- for (const [p0, c1, c2, p3] of [
118
- [x, c1x, c2x, ax],
119
- [y, c1y, c2y, ay],
120
- ]) {
121
- // The derivative's roots: a t² + b t + c = 0.
122
- const a = -p0 + 3 * c1 - 3 * c2 + p3;
123
- const b = 2 * (p0 - 2 * c1 + c2);
124
- const c = c1 - p0;
125
- const roots = a === 0 ? (b === 0 ? [] : [-c / b]) : quadraticRoots(a, b, c);
126
- for (const t of roots) {
127
- if (t > 0 && t < 1) {
128
- at(t);
129
- }
130
- }
131
- }
156
+ takeCubicTurns(x, c1x, c2x, ax, x, y, c1x, c1y, c2x, c2y, ax, ay);
157
+ takeCubicTurns(y, c1y, c2y, ay, x, y, c1x, c1y, c2x, c2y, ax, ay);
132
158
  x = ax;
133
159
  y = ay;
134
160
  i += 7;
@@ -140,15 +166,7 @@ export function extent(path) {
140
166
  i += 3;
141
167
  }
142
168
  }
143
- return xMin <= xMax ? [xMin, yMin, xMax, yMax] : null;
144
- }
145
- function quadraticRoots(a, b, c) {
146
- const d = b * b - 4 * a * c;
147
- if (d < 0) {
148
- return [];
149
- }
150
- const s = Math.sqrt(d);
151
- return [(-b + s) / (2 * a), (-b - s) / (2 * a)];
169
+ return box[0] <= box[2] ? [box[0], box[1], box[2], box[3]] : null;
152
170
  }
153
171
  /** Layers as they draw mirrored, as of a version: the renderer and the hit tests ask on each redraw. */
154
172
  const mirroredLayers = new WeakMap();
@@ -30,6 +30,15 @@ export class StaticTextObject extends DisplayObject {
30
30
  return this.laidGlyphs;
31
31
  }
32
32
  }
33
+ /** The fields whose fit a set size left pending, for the frame to settle before it is drawn. */
34
+ const pendingFits = new Set();
35
+ /** Every pending fit applied, as Flash applies them before it draws. */
36
+ export function settleFits() {
37
+ for (const field of pendingFits) {
38
+ field.settle();
39
+ }
40
+ pendingFits.clear();
41
+ }
33
42
  /**
34
43
  * A TextField: placed by DefineEditText, or made by a script. Its text and
35
44
  * formats are a TextModel; a timeline's starts with its tag's text, read
@@ -55,6 +64,12 @@ export class TextObject extends DisplayObject {
55
64
  type = "dynamic";
56
65
  embedFonts = false;
57
66
  autoSize = "none";
67
+ /**
68
+ * A script set the size of a field that fits itself: as in Flash, the fit
69
+ * waits for its size to be asked, its transform to change or the frame to
70
+ * be drawn, and a wordWrap set meanwhile wraps at the size set (adl).
71
+ */
72
+ fitPending = false;
58
73
  selectable = true;
59
74
  maxChars = 0;
60
75
  displayAsPassword = false;
@@ -165,7 +180,7 @@ export class TextObject extends DisplayObject {
165
180
  * centre or right edge where it was.
166
181
  */
167
182
  fit() {
168
- if (this.autoSize === "none") {
183
+ if (this.autoSize === "none" || this.fitPending) {
169
184
  return;
170
185
  }
171
186
  const layout = this.layout;
@@ -186,6 +201,24 @@ export class TextObject extends DisplayObject {
186
201
  this.height = height;
187
202
  this.invalidate(CONTENT);
188
203
  }
204
+ /** A script set its size: under autoSize, the fit waits (fitPending). */
205
+ sizeSet() {
206
+ if (this.autoSize !== "none") {
207
+ this.fitPending = true;
208
+ pendingFits.add(this);
209
+ }
210
+ }
211
+ /** The fit a set size left pending, applied: its size asked, its transform changed, the frame drawn. */
212
+ settle() {
213
+ if (this.fitPending) {
214
+ this.fitPending = false;
215
+ this.fit();
216
+ }
217
+ }
218
+ touch() {
219
+ super.touch();
220
+ this.settle();
221
+ }
189
222
  /** The text, \r between its lines. */
190
223
  get text() {
191
224
  return this.model.text;
@@ -10,6 +10,7 @@ import { decodeImages, decodeInBrowser } from "./bitmap/images.js";
10
10
  import { drawKernelFrame, drawKernelLetGo, drawKernelUsed } from "./bitmap/kernel.js";
11
11
  import { BUTTON, CLIP, Container, MovieClip, OTHER, unloads, walkList, } from "./display/display.js";
12
12
  import { setStageSize } from "./display/perspective.js";
13
+ import { settleFits } from "./display/textobjects.js";
13
14
  import { readLibrary } from "./display/timeline.js";
14
15
  import { customContextMenu } from "./input/context-menu.js";
15
16
  import { KeyboardInput } from "./input/keyboard.js";
@@ -607,9 +608,7 @@ export class Player {
607
608
  }
608
609
  };
609
610
  collect(this.stage);
610
- for (const orphan of this.scripting?.lifecycle.orphanRoots() ?? []) {
611
- collect(orphan);
612
- }
611
+ this.scripting?.lifecycle.eachOrphanRoot(collect);
613
612
  // Let go of once advanced, so that the kept list holds none past the tick.
614
613
  try {
615
614
  for (let i = 0; i < count; i++) {
@@ -624,5 +623,7 @@ export class Player {
624
623
  this.scripting.frame(this.stage, true, render);
625
624
  this.frameRate = this.scripting.frameRate;
626
625
  }
626
+ // The fits a set size left pending happen before the frame is drawn, as in Flash.
627
+ settleFits();
627
628
  }
628
629
  }
@@ -340,7 +340,7 @@ export function displayObjectNatives(s) {
340
340
  if (d instanceof TextObject) {
341
341
  d.width = value / (Math.abs(d.scaleX) || 1);
342
342
  d.invalidate(CONTENT);
343
- d.fit();
343
+ d.sizeSet();
344
344
  return;
345
345
  }
346
346
  const r = bounds(d, true);
@@ -375,7 +375,7 @@ export function displayObjectNatives(s) {
375
375
  if (d instanceof TextObject) {
376
376
  d.height = value / (Math.abs(d.scaleY) || 1);
377
377
  d.invalidate(CONTENT);
378
- d.fit();
378
+ d.sizeSet();
379
379
  return;
380
380
  }
381
381
  const r = bounds(d, true);
@@ -45,7 +45,7 @@ export class Lifecycle {
45
45
  * order, as Flash dispatches them.
46
46
  */
47
47
  added(display) {
48
- this.orphans.delete(display.serial);
48
+ this.dropOrphan(display.serial);
49
49
  this.dispatch(display.object, "added", true);
50
50
  if (this.onStage(display)) {
51
51
  this.eachObject(display, (o) => this.dispatch(o, "addedToStage", false));
@@ -136,6 +136,7 @@ export class Lifecycle {
136
136
  orphan(display, keep = true) {
137
137
  if (display.object && !this.orphans.has(display.serial)) {
138
138
  this.orphans.set(display.serial, { ref: display.ref, keep, since: this.s.frames });
139
+ this.sortedOrphans = null;
139
140
  scriptWork.changes++;
140
141
  }
141
142
  }
@@ -152,24 +153,42 @@ export class Lifecycle {
152
153
  display.fresh = true;
153
154
  }
154
155
  }
155
- /** The orphans still there, newest first, as Flash runs their frames. */
156
- orphanRoots() {
157
- const roots = [];
158
- for (const [serial, orphan] of this.orphans) {
159
- const display = orphan.ref.deref();
156
+ /**
157
+ * The orphans, newest first, as Flash runs their frames: kept sorted
158
+ * until one comes or goes, as the player walks them twice a frame.
159
+ */
160
+ sortedOrphans = null;
161
+ dropOrphan(serial) {
162
+ if (this.orphans.delete(serial)) {
163
+ this.sortedOrphans = null;
164
+ }
165
+ }
166
+ /** Each orphan still there, newest first, to `f`, which must leave the orphans as they are. */
167
+ eachOrphanRoot(f) {
168
+ let sorted = this.sortedOrphans;
169
+ if (!sorted) {
170
+ sorted = [];
171
+ for (const [serial, orphan] of this.orphans) {
172
+ sorted.push({ serial, ref: orphan.ref });
173
+ }
174
+ sorted.sort((a, b) => b.serial - a.serial);
175
+ this.sortedOrphans = sorted;
176
+ }
177
+ for (let i = 0; i < sorted.length; i++) {
178
+ const display = sorted[i].ref.deref();
160
179
  if (!display) {
161
- this.orphans.delete(serial);
180
+ this.dropOrphan(sorted[i].serial);
162
181
  continue;
163
182
  }
164
- roots.push(display);
183
+ f(display);
165
184
  }
166
- return roots.sort((a, b) => b.serial - a.serial);
167
185
  }
168
186
  /** Whether `display` or anything under it listens for a frame's broadcast events. */
169
187
  hearsFrames(display) {
170
188
  if (display.object) {
171
- for (const targets of this.s.broadcasts.values()) {
172
- if (targets.has(display.object)) {
189
+ const sets = this.s.broadcastSets;
190
+ for (let i = 0; i < sets.length; i++) {
191
+ if (sets[i].has(display.object)) {
173
192
  return true;
174
193
  }
175
194
  }
@@ -187,7 +206,7 @@ export class Lifecycle {
187
206
  * does: timelines stopped, frame broadcasts no longer heard, no orphan.
188
207
  */
189
208
  stopAll(display) {
190
- this.orphans.delete(display.serial);
209
+ this.dropOrphan(display.serial);
191
210
  stopTimelineSoundsUnder(this.s, display);
192
211
  const stop = (o) => {
193
212
  if (o instanceof MovieClip) {
@@ -230,24 +249,25 @@ export class Lifecycle {
230
249
  // What the timeline took off this frame has had its frame; it stops here. So
231
250
  // does one that has played ORPHAN_FRAMES off the list, but for one that
232
251
  // listens for a frame's events, which hold it in Flash too.
233
- for (const [serial, orphan] of this.orphans) {
252
+ // forEach, as for-of makes an entry for each of what may be thousands, every frame.
253
+ this.orphans.forEach((orphan, serial) => {
234
254
  if (!orphan.keep) {
235
- this.orphans.delete(serial);
236
- continue;
255
+ this.dropOrphan(serial);
256
+ return;
237
257
  }
238
258
  if (this.s.frames - orphan.since < ORPHAN_FRAMES) {
239
- continue;
259
+ return;
240
260
  }
241
261
  const display = orphan.ref.deref();
242
262
  if (display && this.hearsFrames(display)) {
243
263
  orphan.since = this.s.frames;
244
- continue;
264
+ return;
245
265
  }
246
- this.orphans.delete(serial);
266
+ this.dropOrphan(serial);
247
267
  if (display) {
248
268
  stopTimelineSoundsUnder(this.s, display);
249
269
  }
250
- }
270
+ });
251
271
  // What scripts made this frame and left off the display list plays on as an orphan.
252
272
  const made = this.madeThisFrame.splice(0);
253
273
  if (made.length > 0) {
@@ -203,6 +203,8 @@ export class Scripting {
203
203
  inFrameScript = null;
204
204
  /** The display objects listening for each frame event, in the order they first listened; a broadcast reaches these. */
205
205
  broadcasts = new Map();
206
+ /** The same sets, for a walk by index that makes no iterator; the map never lets one go. */
207
+ broadcastSets = [];
206
208
  /** The dispatchers listening for ACTIVATE or DEACTIVATE, held weakly. */
207
209
  activation = new Map();
208
210
  /** What flash.display.Stage reports and sets; the player copies the frame rate back each frame. */
@@ -579,6 +581,7 @@ export class Scripting {
579
581
  if (!targets) {
580
582
  targets = new Set();
581
583
  this.broadcasts.set(type, targets);
584
+ this.broadcastSets.push(targets);
582
585
  }
583
586
  return targets;
584
587
  }
@@ -792,9 +795,7 @@ export class Scripting {
792
795
  }
793
796
  }
794
797
  };
795
- for (const orphan of this.lifecycle.orphanRoots()) {
796
- visit(orphan);
797
- }
798
+ this.lifecycle.eachOrphanRoot(visit);
798
799
  visit(root);
799
800
  for (const display of this.lifecycle.fresh) {
800
801
  visit(display);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swf2es/player-hosts",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Optional Node and WebSocket transports, worker threads, an IndexedDB module cache and precompiled modules for the swf2es player",
5
5
  "keywords": [
6
6
  "swf",
@@ -60,6 +60,6 @@
60
60
  "LICENSE.third-party"
61
61
  ],
62
62
  "dependencies": {
63
- "@swf2es/player": "0.1.5"
63
+ "@swf2es/player": "0.1.6"
64
64
  }
65
65
  }