UNPKG

26.6 kBJavaScriptView Raw
1const {
2 $numberInline,
3 $booleanInline,
4 $stringRef,
5 $numberRef,
6 $expressionRef,
7 $condRef,
8 $root,
9 $topLevel,
10 $loop,
11 $context,
12 $val,
13 $key,
14 $arg0,
15 $arg1,
16 $arg2,
17 $arg3,
18 $arg4,
19 $arg5,
20 $arg6,
21 $arg7,
22 $arg8,
23 $arg9,
24 $null,
25 Verbs,
26 VerbsCount,
27 $setter,
28 $push,
29 $splice,
30 setterTypesCount
31} = require('./bytecode-enums');
32
33const bytecodeFunctions = require('./bytecode-functions');
34
35const LengthMask = (1 << 16) - 1;
36const BUFFERS_COUNT = 6;
37
38const unimplementedVerb = () => {};
39const verbFuncs = new Array(VerbsCount).fill(unimplementedVerb).map((_, id) => () => {throw new Error(`missing impl${id}`)});
40Object.keys(Verbs).forEach(v => {
41 if (bytecodeFunctions[v]) {
42 verbFuncs[Verbs[v]] = bytecodeFunctions[v];
43 }
44});
45
46verbFuncs[Verbs.$parseInt] = function $parseInt($offset, $length) {
47 this.processValue(this.$expressions[++$offset]);
48 let radix = 10;
49 if ($length > 2) {
50 this.processValue(this.$expressions[++$offset]);
51 radix = this.$stack.pop();
52 }
53 this.$stack.push(parseInt(this.$stack.pop(), radix));
54};
55
56verbFuncs[Verbs.$parseFloat] = function $parseInt($offset) {
57 this.processValue(this.$expressions[++$offset]);
58 this.$stack.push(parseFloat(this.$stack.pop()));
59};
60
61verbFuncs[Verbs.$and] = function $and($offset, $length) {
62 for (let i = 1; i < $length; i++) {
63 this.processValue(this.$expressions[$offset + i]);
64 if (i === $length - 1 || !this.$stack[this.$stack.length - 1]) {
65 this.$conds[this.$conds.length - 1].set($offset, i);
66 break;
67 } else {
68 this.$stack.pop();
69 }
70 }
71};
72
73verbFuncs[Verbs.$or] = function $or($offset, $length) {
74 for (let i = 1; i < $length; i++) {
75 this.processValue(this.$expressions[$offset + i]);
76 if (i === $length - 1 || this.$stack[this.$stack.length - 1]) {
77 this.$conds[this.$conds.length - 1].set($offset, i);
78 break;
79 } else {
80 this.$stack.pop();
81 }
82 }
83};
84
85verbFuncs[Verbs.$ternary] = function $ternary($offset, $length) {
86 this.processValue(this.$expressions[$offset + 1]);
87 if (this.$stack.pop()) {
88 this.$conds[this.$conds.length - 1].set($offset, 2);
89 this.processValue(this.$expressions[$offset + 2]);
90 } else {
91 this.$conds[this.$conds.length - 1].set($offset, 3);
92 this.processValue(this.$expressions[$offset + 3]);
93 }
94};
95
96verbFuncs[Verbs.$func] = function $func($offset, $length) {
97 this.$conds.push(new Map());
98 this.processValue(this.$expressions[$offset + 1]);
99 this.processValue(this.$expressions[$offset + 2]);
100 this.$conds.pop();
101 this.$keys.pop();
102};
103
104verbFuncs[Verbs.$effect] = function $effect($offset, $length) {
105 const newVal = [];
106 for (let i = 1; i < $length; i++) {
107 this.processValue(this.$expressions[++$offset]);
108 newVal.push(this.$stack.pop());
109 }
110 this.$funcLib[newVal[0]].apply(this.$res, newVal.slice(1));
111 this.$stack.push(void 0);
112};
113
114const recursiveCacheFunc = () => new Map();
115
116const emptyObj = () => ({});
117const emptyArr = () => [];
118
119verbFuncs[Verbs.$recursiveMapValues] = function $recursiveMapValues($offset, $length) {
120 const func = this.$expressions[++$offset];
121 this.processValue(this.$expressions[++$offset]);
122
123 if ($length === 3) {
124 this.$stack.push(null);
125 } else {
126 this.processValue(this.$expressions[++$offset]);
127 }
128
129 if ($length === 3) {
130 this.$contexts.push(this.$stack.pop());
131 } else {
132 const contextArray = this.getEmptyArray(-$offset);
133
134 if (contextArray.length) {
135 this.setOnArray(contextArray, 0, this.$stack.pop(), false);
136 } else {
137 contextArray[0] = this.$stack.pop();
138 }
139
140 this.$contexts.push(contextArray);
141 }
142
143 const src = this.$stack.pop();
144 this.$collections.push(src);
145 // eslint-disable-next-line no-undef
146 this.$functions.push(func);
147
148 const $storage = this.initOutput($offset - $length, emptyObj, recursiveCacheFunc);
149 const $out = $storage[1];
150 const $invalidatedKeys = $storage[2];
151 const $new = $storage[3];
152 const $dependencyMap = $storage[4];
153 if ($new) {
154 Object.keys(src).forEach(key => $invalidatedKeys.add(key));
155 Object.keys(src).forEach(key => {
156 this.$keys.push(key);
157 this.collectionRecursiveFunction();
158 this.$stack.pop();
159 });
160 } else {
161 this.cascadeRecursiveInvalidations($invalidatedKeys, $dependencyMap);
162 $invalidatedKeys.forEach(key => {
163 this.$keys.push(key);
164 this.collectionRecursiveFunction();
165 this.$stack.pop();
166 });
167 }
168 $invalidatedKeys.clear();
169 this.$stack.push($out);
170 this.$functions.pop();
171 this.$collections.pop();
172 this.$currentSets.pop();
173 this.$contexts.pop();
174};
175
176verbFuncs[Verbs.$recursiveMap] = function $recursiveMap($offset, $length) {
177 const func = this.$expressions[++$offset];
178 this.processValue(this.$expressions[++$offset]);
179
180 if ($length === 3) {
181 this.$stack.push(null);
182 } else {
183 this.processValue(this.$expressions[++$offset]);
184 }
185
186 if ($length === 3) {
187 this.$contexts.push(this.$stack.pop());
188 } else {
189 const contextArray = this.getEmptyArray(-$offset);
190
191 if (contextArray.length) {
192 this.setOnArray(contextArray, 0, this.$stack.pop(), false);
193 } else {
194 contextArray[0] = this.$stack.pop();
195 }
196
197 this.$contexts.push(contextArray);
198 }
199
200 const src = this.$stack.pop();
201 this.$collections.push(src);
202 // eslint-disable-next-line no-undef
203 this.$functions.push(func);
204 const $storage = this.initOutput($offset, emptyArr, recursiveCacheFunc);
205 const $out = $storage[1];
206 const $invalidatedKeys = $storage[2];
207 const $new = $storage[3];
208 const $dependencyMap = $storage[4];
209 if ($new) {
210 for (let key = 0; key < src.length; key++) {
211 $invalidatedKeys.add(key);
212 }
213 for (let key = 0; key < src.length; key++) {
214 this.$keys.push(key);
215 this.collectionRecursiveFunction();
216 this.$stack.pop();
217 }
218 } else {
219 this.cascadeRecursiveInvalidations($invalidatedKeys, $dependencyMap);
220 $invalidatedKeys.forEach(key => {
221 this.$keys.push(key);
222 this.collectionRecursiveFunction();
223 this.$stack.pop();
224 });
225 }
226 $invalidatedKeys.clear();
227 this.$stack.push($out);
228 this.$functions.pop();
229 this.$collections.pop();
230 this.$currentSets.pop();
231 this.$contexts.pop();
232};
233
234verbFuncs[Verbs.$recur] = function $recur($offset, $length) {
235 this.processValue(this.$expressions[++$offset]);
236 this.processValue(this.$expressions[++$offset]);
237 const nextKey = this.$stack.pop();
238 const stackDepth = this.$stack.pop();
239 const $invalidatedKeys = this.$currentSets[stackDepth];
240 const $dependencyMap = $invalidatedKeys.$cache[4];
241 if (!$dependencyMap.has(nextKey)) {
242 $dependencyMap.set(nextKey, []);
243 }
244 $dependencyMap.get(nextKey).push([this.$currentSets[this.$currentSets.length - 1], this.$keys[this.$keys.length - 1]]);
245
246 this.$functions.push(this.$functions[stackDepth - 1]);
247 this.$collections.push(this.$collections[stackDepth - 1]);
248 this.$contexts.push(this.$contexts[stackDepth - 1]);
249 this.$currentSets.push($invalidatedKeys);
250 this.$keys.push(nextKey);
251 this.collectionRecursiveFunction();
252 this.$functions.pop();
253 this.$collections.pop();
254 this.$currentSets.pop();
255 this.$contexts.pop();
256};
257
258verbFuncs[Verbs.$trackPath] = function $trackPath($offset, $length) {
259 let $tracked = null;
260 for (let i = 1; i < $length; i += 2) {
261 if (!$tracked) {
262 $tracked = [this.$currentSets[this.$currentSets.length - 1], this.$keys[this.$keys.length - 1]];
263 }
264 this.processValue(this.$expressions[$offset + i]);
265 const $cond = this.$stack.pop();
266 if ($cond) {
267 let valueAndType = this.$expressions[$offset + i + 1];
268 const path = [];
269 let cnt = 1;
270 while ((valueAndType & 31) === $expressionRef) {
271 cnt++;
272 const getterOffset = valueAndType >> 5;
273 this.processValue(this.$expressions[getterOffset + 1]);
274 valueAndType = this.$expressions[getterOffset + 2]
275 }
276 if ((valueAndType & 31) === $context) { /// PATHS to context have 0 prefix
277 cnt++;
278 this.$stack.push(0);
279 this.$stack.push(this.$contexts[this.$contexts.length - 1])
280 } else {
281 this.processValue(valueAndType);
282 }
283 for (let i = 0; i < cnt; i++) {
284 path.push(this.$stack.pop());
285 }
286
287 // console.log(valueAndType & 31, path);
288 this.trackPath($tracked, path);
289 }
290 }
291}
292
293const settersFuncs = new Array(setterTypesCount).fill();
294settersFuncs[$setter] = function $setter(path, value) {
295 let $target = this.$model;
296 for (let i = 0; i < path.length - 1; i++) {
297 const pathPart = path[i];
298 if (typeof $target[pathPart] !== 'object') {
299 $target[pathPart] = typeof path[i + 1] === 'number' ? [] : {};
300 }
301 this.triggerInvalidations($target, pathPart, false);
302 $target = $target[pathPart];
303 }
304 if (Array.isArray($target)) {
305 this.setOnArray($target, path[path.length - 1], value, false);
306 } else if (typeof value === 'undefined') {
307 this.deleteOnObject($target, path[path.length - 1]);
308 } else {
309 this.setOnObject($target, path[path.length - 1], value, false);
310 }
311};
312
313settersFuncs[$push] = function $push(path, value) {
314 let $target = this.$model;
315 path.push(0);
316 for (let i = 0; i < path.length - 1; i++) {
317 const pathPart = path[i];
318 if (typeof $target[pathPart] !== 'object') {
319 $target[pathPart] = typeof path[i + 1] === 'number' ? [] : {};
320 }
321 if (i !== path.length - 1) {
322 this.triggerInvalidations($target, pathPart, false);
323 $target = $target[pathPart];
324 }
325 }
326 this.setOnArray($target, $target.length, value, false);
327};
328
329settersFuncs[$splice] = function $splice(path, start, len, ...newItems) {
330 let $target = this.$model;
331 path.push(start);
332 for (let i = 0; i < path.length - 1; i++) {
333 const pathPart = path[i];
334 if (typeof $target[pathPart] !== 'object') {
335 $target[pathPart] = typeof path[i + 1] === 'number' ? [] : {};
336 }
337 if (i !== path.length - 1) {
338 this.triggerInvalidations($target, pathPart, false);
339 $target = $target[pathPart];
340 }
341 }
342 const copy = $target.slice(start);
343 copy.splice(0, len, ...newItems);
344 if (copy.length < $target.length - start) {
345 this.truncateArray($target, copy.length + start);
346 }
347 for (let i = 0; i < copy.length; i++) {
348 this.setOnArray($target, i + start, copy[i], false);
349 }
350};
351
352function wrapSetter(func, ...args) {
353 if (this.$inBatch || this.$inRecalculate || this.$batchingStrategy) {
354 this.$batchPending.push({func, args});
355 if (!this.$inBatch && !this.$inRecalculate && this.$batchingStrategy) {
356 this.$inBatch = true;
357 this.$batchingStrategy.call(this.$res);
358 }
359 } else {
360 func.apply(this.$res, args);
361 this.recalculate();
362 }
363}
364
365class VirtualMachineInstance {
366 static getTypedArrayByIndex($bytecode, index, bytesPerItem) {
367 const bytecodeOffsets = new Uint32Array($bytecode, 0, BUFFERS_COUNT * 4 + 4);
368 switch (bytesPerItem) {
369 case 1:
370 return new Uint8Array(
371 $bytecode,
372 bytecodeOffsets[index],
373 (bytecodeOffsets[index + 1] - bytecodeOffsets[index]) / bytesPerItem
374 );
375 case 2:
376 return new Uint16Array(
377 $bytecode,
378 bytecodeOffsets[index],
379 (bytecodeOffsets[index + 1] - bytecodeOffsets[index]) / bytesPerItem
380 );
381 case 4:
382 return new Uint32Array(
383 $bytecode,
384 bytecodeOffsets[index],
385 (bytecodeOffsets[index + 1] - bytecodeOffsets[index]) / bytesPerItem
386 );
387 }
388 }
389
390 constructor($constants, $globals, $bytecode, $model, $funcLib, $batchingStrategy) {
391 this.$strings = $constants.$strings;
392 this.$numbers = $constants.$numbers;
393 this.$globals = $globals;
394 const header = VirtualMachineInstance.getTypedArrayByIndex($bytecode, 0, 4);
395 this.$topLevelsExpressions = VirtualMachineInstance.getTypedArrayByIndex($bytecode, 1, 4);
396 this.$topLevelsNames = VirtualMachineInstance.getTypedArrayByIndex($bytecode, 2, 4);
397 this.$topLevelsTracking = VirtualMachineInstance.getTypedArrayByIndex($bytecode, 3, 4);
398 this.$expressions = VirtualMachineInstance.getTypedArrayByIndex($bytecode, 4, 4);
399 this.$topLevelsCount = header[0];
400 this.$model = $model;
401 this.$funcLib = $funcLib;
402 this.$funcLibRaw = $funcLib;
403 this.$batchingStrategy = $batchingStrategy;
404 this.$listeners = new Set();
405 this.$inBatch = false;
406 this.$inRecalculate = false;
407 this.$batchPending = [];
408 this.$topLevels = [];
409 this.$keys = [];
410 this.$collections = [];
411 this.$contexts = [];
412 this.$functions = [];
413 this.$stack = [];
414 this.$currentSets = [];
415 this.$conds = [];
416 this.$res = {
417 $model,
418 $startBatch: () => {
419 this.$inBatch = true;
420 },
421 $endBatch: () => {
422 if (this.$inRecalculate) {
423 throw new Error('Can not end batch in the middle of a batch');
424 }
425 this.$inBatch = false;
426 if (this.$batchPending.length) {
427 this.$batchPending.forEach(({func, args}) => {
428 func.apply(this, args);
429 });
430 this.$batchPending = [];
431 this.recalculate();
432 }
433 },
434 $runInBatch: func => {
435 this.$res.$startBatch();
436 func();
437 this.$res.$endBatch();
438 },
439 $addListener: func => {
440 this.$listeners.add(func);
441 },
442 $removeListener: func => {
443 this.$listeners.delete(func);
444 },
445 $setBatchingStrategy: func => {
446 $batchingStrategy = func;
447 }
448 };
449 // this.$verbs = verbFuncs.map(f => f.bind(this));
450 this.$trackingMap = new WeakMap();
451 this.$trackingWildcards = new WeakMap();
452 this.$invalidatedMap = new WeakMap();
453 this.$invalidatedRoots = new Set();
454 this.$invalidatedRoots.$subKeys = {};
455 this.$invalidatedRoots.$parentKey = null;
456 this.$invalidatedRoots.$parent = null;
457 this.$invalidatedRoots.$tracked = {};
458 this.$invalidatedRoots.$cache = [null, this.$topLevels, this.$invalidatedRoots, true, null];
459 this.$first = true;
460 this.$tainted = new Set();
461 this.buildSetters(VirtualMachineInstance.getTypedArrayByIndex($bytecode, 5, 4));
462 this.recalculate();
463 }
464
465 processValue(valueAndType) {
466 const type = valueAndType & 31;
467 const value = valueAndType >> 5;
468 switch (type) {
469 case $numberInline:
470 this.$stack.push(value);
471 break;
472 case $booleanInline:
473 this.$stack.push(value === 1);
474 break;
475 case $stringRef:
476 this.$stack.push(this.$strings[value]);
477 break;
478 case $numberRef:
479 this.$stack.push(this.$numbers[value]);
480 break;
481 case $expressionRef:
482 this.processExpression(value);
483 break;
484 case $condRef:
485 this.$stack.push(this.$conds[this.$conds.length - 1].get(value) || 0);
486 break;
487 case $root:
488 this.$stack.push(this.$model);
489 break;
490 case $topLevel:
491 this.$stack.push(this.$topLevels);
492 break;
493 case $loop:
494 this.$stack.push(this.$currentSets.length - 1);
495 break;
496 case $context:
497 this.$stack.push(this.$contexts[this.$contexts.length - 1][0]);
498 break;
499 case $val:
500 this.$stack.push(this.$collections[this.$collections.length - 1][this.$keys[this.$keys.length - 1]]);
501 break;
502 case $key:
503 this.$stack.push(this.$keys[this.$keys.length - 1]);
504 break;
505 case $null:
506 this.$stack.push(null);
507 break;
508 }
509 }
510
511 processExpression(offset) {
512 const verbAndLength = this.$expressions[offset];
513 const length = verbAndLength & LengthMask;
514 const verb = verbAndLength >> 16;
515 verbFuncs[verb].call(this, offset, length);
516 // this.$verbs[verb](offset, length);
517 }
518
519 updateDerived() {
520 this.$inRecalculate = true;
521 this.$currentSets.push(this.$invalidatedRoots);
522 for (let i = 0; i < this.$topLevelsCount; i++) {
523 if (this.$first || this.$invalidatedRoots.has(i)) {
524 this.$keys.push(i);
525 this.$conds.push(new Map());
526 this.processExpression(this.$topLevelsExpressions[i]);
527 this.processExpression(this.$topLevelsTracking[i]);
528 this.$keys.pop();
529 this.$conds.pop();
530 this.setOnArray(this.$topLevels, i, this.$stack.pop(), this.$first);
531 if (!this.$first) {
532 this.$invalidatedRoots.delete(i);
533 }
534 if (this.$topLevelsNames[i]) {
535 this.$res[this.$strings[this.$topLevelsNames[i]]] = this.$topLevels[i];
536 }
537 }
538 }
539 this.$currentSets.pop(this.$invalidatedRoots);
540 this.$first = false;
541 this.$tainted = new Set();
542 }
543
544 recalculate() {
545 if (this.$inBatch) {
546 return;
547 }
548 this.updateDerived();
549 this.$listeners.forEach(callback => callback());
550 this.$inRecalculate = false;
551 if (this.$batchPending.length) {
552 this.$res.$endBatch();
553 }
554 }
555
556 collectionFunction() {
557 this.processExpression(this.$functions[this.$functions.length - 1] >> 5);
558 }
559
560 collectionRecursiveFunction() {
561 const $invalidatedKeys = this.$currentSets[this.$currentSets.length - 1];
562 const key = this.$keys[this.$keys.length - 1];
563 const $cache = $invalidatedKeys.$cache;
564 const $out = $cache[1];
565 const $new = $cache[3];
566 const src = this.$collections[this.$collections.length - 1];
567 if ($invalidatedKeys.has(key)) {
568 if (Array.isArray($out)) {
569 if (key >= src.length) {
570 this.truncateArray($out, src.length);
571 $out.length = src.length;
572 this.$keys.pop();
573 } else {
574 this.processExpression(this.$functions[this.$functions.length - 1] >> 5);
575 this.setOnArray($out, key, this.$stack.pop(), $new);
576 }
577 } else if (!src.hasOwnProperty(key)) {
578 if ($out.hasOwnProperty(key)) {
579 this.deleteOnObject($out, key, $new);
580 }
581 this.$keys.pop();
582 } else {
583 this.processExpression(this.$functions[this.$functions.length - 1] >> 5);
584 this.setOnObject($out, key, this.$stack.pop(), $new);
585 }
586 $invalidatedKeys.delete(key);
587 } else {
588 this.$keys.pop();
589 }
590 this.$stack.push($out[key]);
591 }
592
593 cascadeRecursiveInvalidations($invalidatedKeys, $dependencyMap) {
594 $invalidatedKeys.forEach(key => {
595 if ($dependencyMap.has(key)) {
596 $dependencyMap.get(key).forEach($tracked => {
597 this.invalidate($tracked[0], $tracked[1]);
598 });
599 $dependencyMap.delete(key);
600 }
601 });
602 }
603
604 generateSetter($setters, $offset) {
605 return (...args) => {
606 const $length = $setters[$offset] & LengthMask;
607 const $setterType = $setters[$offset] >> 16;
608 const path = [];
609 let maxArgs = 0;
610 for (let i = 0; i < $length; i++) {
611 const valueAndType = $setters[$offset + i + 2];
612 const type = valueAndType & 31;
613 const value = valueAndType >> 5;
614 switch (type) {
615 case $numberInline:
616 path.push(value);
617 break;
618 case $booleanInline:
619 path.push(value === 1);
620 break;
621 case $stringRef:
622 path.push(this.$strings[value]);
623 break;
624 case $numberRef:
625 path.push(this.$numbers[value]);
626 break;
627 case $arg0:
628 case $arg1:
629 case $arg2:
630 case $arg3:
631 case $arg4:
632 case $arg5:
633 case $arg6:
634 case $arg7:
635 case $arg8:
636 case $arg9:
637 path.push(args[type - $arg0]);
638 maxArgs = Math.max(maxArgs, type - $arg0 + 1);
639 break;
640 }
641 }
642 args = args.slice(maxArgs);
643 settersFuncs[$setterType].apply(this, [path].concat(args));
644 };
645 }
646
647 buildSetters($setters) {
648 let $offset = 0;
649 while ($offset < $setters.length) {
650 this.$res[this.$strings[$setters[$offset + 1]]] = wrapSetter.bind(this, this.generateSetter($setters, $offset));
651 $offset += ($setters[$offset] & LengthMask) + 2;
652 }
653 }
654
655 getAssignableObject(path, index) {
656 return path.slice(0, index).reduce((agg, p) => agg[p], this.$model);
657 }
658
659 //// AUTO-GENERATED
660 untrack($targetKeySet, $targetKey) {
661 const $tracked = $targetKeySet.$tracked;
662 if (!$tracked || !$tracked[$targetKey]) {
663 return;
664 }
665 const $trackedByKey = $tracked[$targetKey];
666 for (let i = 0; i < $trackedByKey.length; i += 3) {
667 const $trackingSource = this.$trackingMap.get($trackedByKey[i]);
668 $trackingSource[$trackedByKey[i + 1]].delete($trackedByKey[i + 2]);
669 }
670 delete $tracked[$targetKey];
671 }
672
673 invalidate($targetKeySet, $targetKey) {
674 if ($targetKeySet.has($targetKey)) {
675 return;
676 }
677 $targetKeySet.add($targetKey);
678 this.untrack($targetKeySet, $targetKey);
679 if ($targetKeySet.$parent) {
680 this.invalidate($targetKeySet.$parent, $targetKeySet.$parentKey);
681 }
682 }
683
684 setOnObject($target, $key, $val, $new) {
685 let $changed = false;
686 let $hard = false;
687 if (!$new) {
688 if (typeof $target[$key] === 'object' && $target[$key] && $target[$key] !== $val) {
689 $hard = true;
690 }
691 if (
692 $hard ||
693 $target[$key] !== $val ||
694 $val && typeof $val === 'object' && this.$tainted.has($val) ||
695 !$target.hasOwnProperty($key) && $target[$key] === undefined
696 ) {
697 $changed = true;
698 this.triggerInvalidations($target, $key, $hard);
699 }
700 }
701 $target[$key] = $val;
702 }
703
704 deleteOnObject($target, $key, $new) {
705 let $hard = false;
706 if (!$new) {
707 if (typeof $target[$key] === 'object' && $target[$key]) {
708 $hard = true;
709 }
710 this.triggerInvalidations($target, $key, $hard);
711 const $invalidatedKeys = this.$invalidatedMap.get($target);
712 if ($invalidatedKeys) {
713 delete $invalidatedKeys.$subKeys[$key];
714 }
715 }
716 delete $target[$key];
717 }
718
719 setOnArray($target, $key, $val, $new) {
720 let $hard = false;
721 if (!$new) {
722 if (typeof $target[$key] === 'object' && $target[$key] && $target[$key] !== $val) {
723 $hard = true;
724 }
725 if (
726 $hard ||
727 $key >= $target.length ||
728 $target[$key] !== $val ||
729 $val && typeof $target[$key] === 'object' && this.$tainted.has($val)
730 ) {
731 this.triggerInvalidations($target, $key, $hard);
732 }
733 }
734
735 $target[$key] = $val;
736 }
737
738 truncateArray($target, newLen) {
739 for (let i = newLen; i < $target.length; i++) {
740 this.triggerInvalidations($target, i, true);
741 }
742
743 $target.length = newLen;
744 }
745
746 track($target, $sourceObj, $sourceKey, $soft) {
747 if (!this.$trackingMap.has($sourceObj)) {
748 this.$trackingMap.set($sourceObj, {});
749 }
750 const $track = this.$trackingMap.get($sourceObj);
751 $track[$sourceKey] = $track[$sourceKey] || new Map();
752 $track[$sourceKey].set($target, $soft);
753 const $tracked = $target[0].$tracked;
754 $tracked[$target[1]] = $tracked[$target[1]] || [];
755 $tracked[$target[1]].push($sourceObj, $sourceKey, $target);
756 }
757
758 trackPath($target, $path) {
759 const $end = $path.length - 2;
760 let $current = $path[0];
761 for (let i = 0; i <= $end; i++) {
762 this.track($target, $current, $path[i + 1], i !== $end);
763 $current = $current[$path[i + 1]];
764 }
765 }
766
767 triggerInvalidations($sourceObj, $sourceKey, $hard) {
768 this.$tainted.add($sourceObj);
769 const $track = this.$trackingMap.get($sourceObj);
770 if ($track && $track.hasOwnProperty($sourceKey)) {
771 $track[$sourceKey].forEach(($soft, $target) => {
772 if (!$soft || $hard) {
773 this.invalidate($target[0], $target[1]);
774 }
775 });
776 }
777 if (this.$trackingWildcards.has($sourceObj)) {
778 this.$trackingWildcards.get($sourceObj).forEach($targetInvalidatedKeys => {
779 this.invalidate($targetInvalidatedKeys, $sourceKey);
780 });
781 }
782 }
783
784 initOutput(func, createDefaultValue, createCacheValue) {
785 const $parent = this.$currentSets[this.$currentSets.length - 1];
786 const $currentKey = this.$keys[this.$keys.length - 1];
787 const src = this.$collections[this.$collections.length - 1];
788 const subKeys = $parent.$subKeys;
789 const $cachePerTargetKey = subKeys[$currentKey] = subKeys[$currentKey] || new Map();
790 let $cachedByFunc = $cachePerTargetKey.get(func); //null; //$cachePerTargetKey.get(func);
791 if (!$cachedByFunc) {
792 const $resultObj = createDefaultValue();
793 const $cacheValue = createCacheValue();
794 const $invalidatedKeys = new Set();
795 $invalidatedKeys.$subKeys = {};
796 $invalidatedKeys.$parentKey = $currentKey;
797 $invalidatedKeys.$parent = $parent;
798 $invalidatedKeys.$tracked = {};
799 this.$invalidatedMap.set($resultObj, $invalidatedKeys);
800 $cachedByFunc = [null, $resultObj, $invalidatedKeys, true, $cacheValue];
801 $invalidatedKeys.$cache = $cachedByFunc;
802 $cachePerTargetKey.set(func, $cachedByFunc);
803 } else {
804 $cachedByFunc[3] = false;
805 }
806 const $invalidatedKeys = $cachedByFunc[2];
807 this.$currentSets.push($invalidatedKeys);
808 const $prevSrc = $cachedByFunc[0];
809 if ($prevSrc !== src) {
810 if ($prevSrc) {
811 // prev mapped to a different collection
812 this.$trackingWildcards.get($prevSrc).delete($invalidatedKeys);
813 if (Array.isArray($prevSrc)) {
814 $prevSrc.forEach((_item, index) => $invalidatedKeys.add(index));
815 } else {
816 Object.keys($prevSrc).forEach(key => $invalidatedKeys.add(key));
817 }
818 if (Array.isArray(src)) {
819 src.forEach((_item, index) => $invalidatedKeys.add(index));
820 } else {
821 Object.keys(src).forEach(key => $invalidatedKeys.add(key));
822 }
823 }
824 if (!this.$trackingWildcards.has(src)) {
825 this.$trackingWildcards.set(src, new Set());
826 }
827 this.$trackingWildcards.get(src).add($invalidatedKeys);
828 $cachedByFunc[0] = src;
829 }
830 return $cachedByFunc;
831 }
832
833 getEmptyArray(token) {
834 const subKeys = this.$currentSets[this.$currentSets.length - 1].$subKeys;
835 const currentKey = this.$keys[this.$keys.length - 1];
836 const $cachePerTargetKey = subKeys[currentKey] = subKeys[currentKey] || new Map();
837
838 if (!$cachePerTargetKey.has(token)) {
839 $cachePerTargetKey.set(token, []);
840 }
841 return $cachePerTargetKey.get(token);
842 // return [];
843 }
844
845 getEmptyObject(token) {
846 const subKeys = this.$currentSets[this.$currentSets.length - 1].$subKeys;
847 const currentKey = this.$keys[this.$keys.length - 1];
848 const $cachePerTargetKey = subKeys[currentKey] = subKeys[currentKey] || new Map();
849 if (!$cachePerTargetKey.has(token)) {
850 $cachePerTargetKey.set(token, {});
851 }
852 return $cachePerTargetKey.get(token);
853 // return {};
854 }
855}
856
857module.exports = VirtualMachineInstance;