UNPKG

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