Skip to content

Commit f9ed988

Browse files
committed
fix(cache): simplified resolving algorhythm with suspense feature
1 parent 58bf782 commit f9ed988

File tree

4 files changed

+167
-104
lines changed

4 files changed

+167
-104
lines changed

src/cache.js

+82-101
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as emitter from "./emitter.js";
22

33
const entries = new WeakMap();
4+
const suspense = new WeakSet();
5+
46
export function getEntry(target, key) {
57
let targetMap = entries.get(target);
68
if (!targetMap) {
@@ -15,11 +17,9 @@ export function getEntry(target, key) {
1517
target,
1618
key,
1719
value: undefined,
18-
contexts: undefined,
19-
deps: undefined,
20-
state: 0,
21-
checksum: 0,
22-
observed: false,
20+
contexts: new Set(),
21+
deps: new Set(),
22+
resolved: false,
2323
};
2424
targetMap.set(key, entry);
2525
}
@@ -38,100 +38,63 @@ export function getEntries(target) {
3838
return result;
3939
}
4040

41-
function calculateChecksum(entry) {
42-
let checksum = entry.state;
43-
if (entry.deps) {
44-
entry.deps.forEach(depEntry => {
45-
checksum += depEntry.state;
46-
});
47-
}
48-
49-
return checksum;
50-
}
51-
5241
function dispatchDeep(entry) {
53-
if (entry.observed) emitter.dispatch(entry);
54-
if (entry.contexts) entry.contexts.forEach(dispatchDeep);
55-
}
56-
57-
function restoreDeepDeps(entry, deps) {
58-
if (deps) {
59-
deps.forEach(depEntry => {
60-
entry.deps.add(depEntry);
61-
62-
if (entry.observed) {
63-
/* istanbul ignore if */
64-
if (!depEntry.contexts) depEntry.contexts = new Set();
65-
depEntry.contexts.add(entry);
66-
}
42+
entry.resolved = false;
6743

68-
restoreDeepDeps(entry, depEntry.deps);
69-
});
70-
}
44+
emitter.dispatch(entry);
45+
entry.contexts.forEach(dispatchDeep);
7146
}
7247

73-
const contextStack = new Set();
48+
const contexts = [];
7449
export function get(target, key, getter, validate) {
7550
const entry = getEntry(target, key);
7651

77-
if (contextStack.size && contextStack.has(entry)) {
52+
if (contexts.includes(entry)) {
7853
throw Error(`Circular get invocation is forbidden: '${key}'`);
7954
}
8055

81-
contextStack.forEach(context => {
82-
if (!context.deps) context.deps = new Set();
83-
context.deps.add(entry);
56+
const context = contexts[0];
8457

85-
if (context.observed) {
86-
if (!entry.contexts) entry.contexts = new Set();
87-
entry.contexts.add(context);
88-
}
89-
});
58+
if (context && !suspense.has(context)) {
59+
context.deps.add(entry);
60+
entry.contexts.add(context);
61+
}
9062

9163
if (
92-
((validate && validate(entry.value)) || !validate) &&
93-
entry.checksum &&
94-
entry.checksum === calculateChecksum(entry)
64+
!suspense.has(target) &&
65+
entry.resolved &&
66+
((validate && validate(entry.value)) || !validate)
9567
) {
9668
return entry.value;
9769
}
9870

9971
try {
100-
contextStack.add(entry);
72+
contexts.unshift(entry);
10173

102-
if (entry.observed && entry.deps && entry.deps.size) {
103-
entry.deps.forEach(depEntry => {
104-
/* istanbul ignore else */
105-
if (depEntry.contexts) depEntry.contexts.delete(entry);
106-
});
107-
}
74+
entry.deps.forEach(depEntry => {
75+
depEntry.contexts.delete(entry);
76+
});
77+
entry.deps.clear();
10878

109-
entry.deps = undefined;
11079
const nextValue = getter(target, entry.value);
11180

112-
if (entry.deps) {
113-
entry.deps.forEach(depEntry => {
114-
restoreDeepDeps(entry, depEntry.deps);
115-
});
116-
}
117-
11881
if (nextValue !== entry.value) {
119-
entry.state += 1;
12082
entry.value = nextValue;
121-
12283
dispatchDeep(entry);
12384
}
12485

125-
entry.checksum = calculateChecksum(entry);
126-
contextStack.delete(entry);
86+
entry.resolved = !suspense.has(target);
87+
88+
contexts.shift();
12789
} catch (e) {
128-
entry.checksum = 0;
90+
contexts.shift();
91+
92+
entry.resolved = false;
12993

130-
contextStack.delete(entry);
131-
contextStack.forEach(context => {
94+
if (context && !suspense.has(context)) {
13295
context.deps.delete(entry);
133-
if (context.observed) entry.contexts.delete(context);
134-
});
96+
entry.contexts.delete(context);
97+
}
13598

13699
throw e;
137100
}
@@ -144,10 +107,7 @@ export function set(target, key, setter, value) {
144107
const newValue = setter(target, value, entry.value);
145108

146109
if (newValue !== entry.value) {
147-
entry.checksum = 0;
148-
entry.state += 1;
149110
entry.value = newValue;
150-
151111
dispatchDeep(entry);
152112
}
153113
}
@@ -157,7 +117,11 @@ function deleteEntry(entry) {
157117
if (!gcList.size) {
158118
requestAnimationFrame(() => {
159119
gcList.forEach(e => {
160-
if (!e.contexts || (e.contexts && e.contexts.size === 0)) {
120+
if (e.contexts.size === 0) {
121+
e.deps.forEach(depEntry => {
122+
depEntry.contexts.delete(e);
123+
});
124+
161125
const targetMap = entries.get(e.target);
162126
targetMap.delete(e.key);
163127
}
@@ -170,19 +134,19 @@ function deleteEntry(entry) {
170134
}
171135

172136
function invalidateEntry(entry, clearValue, deleteValue) {
173-
entry.checksum = 0;
174-
entry.state += 1;
175-
176137
dispatchDeep(entry);
177-
if (deleteValue) deleteEntry(entry);
178138

179139
if (clearValue) {
180140
entry.value = undefined;
181141
}
142+
143+
if (deleteValue) {
144+
deleteEntry(entry);
145+
}
182146
}
183147

184148
export function invalidate(target, key, clearValue, deleteValue) {
185-
if (contextStack.size) {
149+
if (contexts.length) {
186150
throw Error(
187151
`Invalidating property in chain of get calls is forbidden: '${key}'`,
188152
);
@@ -193,7 +157,7 @@ export function invalidate(target, key, clearValue, deleteValue) {
193157
}
194158

195159
export function invalidateAll(target, clearValue, deleteValue) {
196-
if (contextStack.size) {
160+
if (contexts.length) {
197161
throw Error(
198162
"Invalidating all properties in chain of get calls is forbidden",
199163
);
@@ -209,33 +173,50 @@ export function invalidateAll(target, clearValue, deleteValue) {
209173

210174
export function observe(target, key, getter, fn) {
211175
const entry = getEntry(target, key);
212-
entry.observed = true;
213-
214176
let lastValue;
215-
const unsubscribe = emitter.subscribe(entry, () => {
216-
const value = get(target, key, getter);
217-
if (value !== lastValue) {
218-
fn(target, value, lastValue);
219-
lastValue = value;
177+
178+
return emitter.subscribe(entry, () => {
179+
if (!suspense.has(target)) {
180+
const value = get(target, key, getter);
181+
if (value !== lastValue) {
182+
fn(target, value, lastValue);
183+
lastValue = value;
184+
}
220185
}
221186
});
187+
}
222188

223-
if (entry.deps) {
224-
entry.deps.forEach(depEntry => {
225-
/* istanbul ignore else */
226-
if (!depEntry.contexts) depEntry.contexts = new Set();
227-
depEntry.contexts.add(entry);
189+
const clearTargets = new Set();
190+
export function clear(target) {
191+
if (clearTargets.size === 0) {
192+
requestAnimationFrame(() => {
193+
clearTargets.forEach(t => {
194+
const targetMap = entries.get(t);
195+
if (targetMap) {
196+
targetMap.forEach(entry => {
197+
entry.resolved = false;
198+
199+
entry.deps.forEach(depEntry => {
200+
depEntry.contexts.delete(entry);
201+
});
202+
203+
entry.deps.clear();
204+
entry.contexts.clear();
205+
});
206+
}
207+
});
208+
209+
clearTargets.clear();
228210
});
229211
}
212+
clearTargets.add(target);
213+
}
230214

231-
return function unobserve() {
232-
unsubscribe();
233-
entry.observed = false;
234-
if (entry.deps && entry.deps.size) {
235-
entry.deps.forEach(depEntry => {
236-
/* istanbul ignore else */
237-
if (depEntry.contexts) depEntry.contexts.delete(entry);
238-
});
239-
}
240-
};
215+
export function suspend(target) {
216+
suspense.add(target);
217+
}
218+
219+
export function unsuspend(target) {
220+
suspense.delete(target);
221+
clearTargets.delete(target);
241222
}

src/define.js

+7
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,13 @@ function defineElement(tagName, hybridsOrConstructor) {
155155
this[key] = value;
156156
}
157157
}
158+
159+
cache.suspend(this);
158160
}
159161

160162
connectedCallback() {
163+
cache.unsuspend(this);
164+
161165
const callbacks = callbacksMap.get(Hybrid);
162166
const list = [];
163167

@@ -174,6 +178,9 @@ function defineElement(tagName, hybridsOrConstructor) {
174178
for (let index = 0; index < list.length; index += 1) {
175179
list[index]();
176180
}
181+
182+
cache.suspend(this);
183+
cache.clear(this);
177184
}
178185
}
179186

src/emitter.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ function execute() {
1919
}
2020

2121
export function dispatch(target) {
22-
if (!queue.size) {
23-
requestAnimationFrame(execute);
22+
if (callbacks.has(target)) {
23+
if (!queue.size) {
24+
requestAnimationFrame(execute);
25+
}
26+
queue.add(target);
2427
}
25-
queue.add(target);
2628
}
2729

2830
export function subscribe(target, cb) {

0 commit comments

Comments
 (0)