-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathfast-getter.ts
114 lines (101 loc) · 4.05 KB
/
fast-getter.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { snapshotProcessor } from "mobx-state-tree/dist/internal";
import type { PropertyMetadata, SnapshottedViewMetadata, ViewMetadata } from "./class-model";
import { getPropertyDescriptor } from "./class-model";
import { RegistrationError } from "./errors";
import { $notYetMemoized, $readOnly } from "./symbols";
/** Assemble a function for getting the value of a readonly instance very quickly with static dispatch to properties */
export class FastGetBuilder {
memoizableProperties: string[];
constructor(
metadatas: PropertyMetadata[],
readonly klass: { new (...args: any[]): any },
) {
this.memoizableProperties = metadatas
.filter((metadata): metadata is ViewMetadata => {
if (metadata.type !== "view" && metadata.type !== "snapshotted-view") return false;
const property = metadata.property;
const descriptor = getPropertyDescriptor(klass.prototype, property);
if (!descriptor) {
throw new RegistrationError(`Property ${property} not found on ${klass} prototype, can't register view for class model`);
}
return descriptor.get !== undefined;
})
.map((metadata) => metadata.property);
}
memoSymbolName(property: string) {
return `mqt/${property}-memo`;
}
snapshottedViewInputSymbolName(property: string) {
return `mqt/${property}-svi-memo`;
}
outerClosureStatements(className: string) {
return this.memoizableProperties
.map(
(property) => `
const ${property}Memo = Symbol.for("${this.memoSymbolName(property)}");
${className}.prototype[${property}Memo] = $notYetMemoized;
`,
)
.join("\n");
}
buildViewGetter(metadata: ViewMetadata | SnapshottedViewMetadata, descriptor: PropertyDescriptor) {
const property = metadata.property;
const $memo = Symbol.for(this.memoSymbolName(property));
let source;
let args;
if (metadata.type === "snapshotted-view" && metadata.options.createReadOnly) {
const $snapshotValue = Symbol.for(this.snapshottedViewInputSymbolName(property));
// this snapshotted view has a hydrator, so we need a special view function for readonly instances that lazily hydrates the snapshotted value
source = `
(
function build({ $readOnly, $memo, $notYetMemoized, $snapshotValue, getValue, hydrate }) {
return function get${property}(model, imports) {
if (!this[$readOnly]) return getValue.call(this);
let value = this[$memo];
if (value !== $notYetMemoized) {
return value;
}
const dehydratedValue = this[$snapshotValue];
if (typeof dehydratedValue !== "undefined") {
value = hydrate(dehydratedValue, this);
} else {
value = getValue.call(this);
}
this[$memo] = value;
return value;
}
}
)
//# sourceURL=mqt-eval/dynamic/${this.klass.name}-${property}-get.js
`;
args = { $readOnly, $memo, $snapshotValue, $notYetMemoized, hydrate: metadata.options.createReadOnly, getValue: descriptor.get };
} else {
source = `
(
function build({ $readOnly, $memo, $notYetMemoized, getValue }) {
return function get${property}(model, imports) {
if (!this[$readOnly]) return getValue.call(this);
let value = this[$memo];
if (value !== $notYetMemoized) {
return value;
}
value = getValue.call(this);
this[$memo] = value;
return value;
}
}
)
//# sourceURL=mqt-eval/dynamic/${this.klass.name}-${property}-get.js
`;
args = { $readOnly, $memo, $notYetMemoized, getValue: descriptor.get };
}
try {
const builder = eval(source);
return builder(args);
} catch (error) {
console.error(`Error building getter for ${this.klass.name}#${property}`);
console.error(`Compiled source:\n${source}`);
throw error;
}
}
}