Skip to content

Commit ec35e71

Browse files
Merge pull request cerebral#175 from cerebral/optimizeProxyRemoval
Optimize proxy removal
2 parents 1d5bfd0 + 2d8a845 commit ec35e71

File tree

5 files changed

+184
-26
lines changed

5 files changed

+184
-26
lines changed

packages/node_modules/overmind/src/index.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,10 @@ export class Overmind<Config extends Configuration> implements Configuration {
246246
this.trackEffects(this.effects, execution)
247247
),
248248
(err, finalContext) => {
249-
this.eventHub.emit(
250-
EventType.ACTION_END,
251-
finalContext.execution
252-
)
249+
this.eventHub.emit(EventType.ACTION_END, {
250+
...finalContext.execution,
251+
operatorId: finalContext.execution.operatorId - 1,
252+
})
253253
if (err) reject(err)
254254
else resolve(finalContext.value)
255255
}
@@ -767,9 +767,21 @@ export function action<Input, Config extends Configuration = TheConfig>(
767767
mutationTree.onMutation((mutation) => {
768768
context.execution.emit(EventType.MUTATIONS, {
769769
...context.execution,
770-
operatorId: context.execution.operatorId,
771770
mutations: makeStringifySafeMutations([mutation]),
772771
})
772+
setTimeout(() => {
773+
const flushData = context.proxyStateTree.flush(true)
774+
if (flushData.mutations.length) {
775+
context.execution.send({
776+
type: 'flush',
777+
data: {
778+
...context.execution,
779+
...flushData,
780+
mutations: makeStringifySafeMutations(flushData.mutations),
781+
},
782+
})
783+
}
784+
})
773785
})
774786
}
775787
const maybePromise: any = operation({

packages/node_modules/proxy-state-tree/src/Proxyfier.ts

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import isPlainObject from 'is-plain-obj'
44
export const IS_PROXY = Symbol('IS_PROXY')
55
export const PATH = Symbol('PATH')
66
export const VALUE = Symbol('VALUE')
7+
export const CACHED_PROXY = Symbol('CACHED_PROXY')
78

89
const arrayMutations = new Set([
910
'push',
@@ -16,22 +17,77 @@ const arrayMutations = new Set([
1617
'copyWithin',
1718
])
1819

19-
const removeProxyMutations = ['set', 'splice', 'shift', 'unshift']
20-
2120
export class Proxifier {
2221
private proxyCache = {}
2322
constructor(private tree: TTree) {
24-
tree.master.onMutation(this.removeProxies)
23+
tree.master.onRemoveProxy(this.removeProxies)
2524
}
2625
private concat(path, prop) {
2726
return path ? path + '.' + prop : prop
2827
}
29-
3028
addProxyToCache(path: string, proxy: any) {
31-
return (this.proxyCache[path] = proxy)
29+
const pathArray = path.split('.')
30+
let currentCache = this.proxyCache
31+
const length = pathArray.length
32+
const keyIndex = length - 1
33+
34+
for (let x = 0; x < length; x++) {
35+
const key = pathArray[x]
36+
37+
if (!currentCache[key]) {
38+
currentCache[key] = {}
39+
}
40+
41+
if (x === keyIndex) {
42+
currentCache[key][CACHED_PROXY] = proxy
43+
} else {
44+
currentCache = currentCache[key]
45+
}
46+
}
47+
48+
return proxy
3249
}
50+
3351
getProxyFromCache(path: string) {
34-
return this.proxyCache[path]
52+
const pathArray = path.split('.')
53+
let currentCache = this.proxyCache
54+
const length = pathArray.length
55+
const keyIndex = length - 1
56+
57+
for (let x = 0; x < length; x++) {
58+
const key = pathArray[x]
59+
60+
if (!currentCache[key]) {
61+
return null
62+
}
63+
64+
if (x === keyIndex) {
65+
return currentCache[key][CACHED_PROXY]
66+
} else {
67+
currentCache = currentCache[key]
68+
}
69+
}
70+
}
71+
72+
removeProxies = (path: string) => {
73+
const pathArray = path.split('.')
74+
let currentCache = this.proxyCache
75+
const length = pathArray.length
76+
const keyIndex = length - 1
77+
78+
for (let x = 0; x < length; x++) {
79+
const key = pathArray[x]
80+
81+
if (!currentCache[key]) {
82+
return null
83+
}
84+
85+
if (x === keyIndex) {
86+
delete currentCache[key]
87+
} else {
88+
currentCache = currentCache[key]
89+
}
90+
}
3591
}
3692

3793
shouldTrackMutations(path) {
@@ -41,16 +97,6 @@ export class Proxifier {
4197
)
4298
}
4399

44-
removeProxies = (mutation: IMutation) => {
45-
if (removeProxyMutations.includes(mutation.method)) {
46-
for (let path in this.proxyCache) {
47-
if (path.indexOf(mutation.path) === 0) {
48-
delete this.proxyCache[path]
49-
}
50-
}
51-
}
52-
}
53-
54100
ensureMutationTrackingIsEnabled(path) {
55101
if (this.tree.master.options.devmode && !this.tree.canMutate()) {
56102
throw new Error(
@@ -142,9 +188,19 @@ export class Proxifier {
142188
proxifier.ensureMutationTrackingIsEnabled(nestedPath)
143189
return (...args) => {
144190
const mutationTree = proxifier.getMutationTree()
191+
const method = String(prop)
192+
193+
// On POP we can optimally remove cached proxy by removing the specific one
194+
// that was removed. If it is a PUSH, we do not have to remove anything, as
195+
// existing proxies stays the same
196+
if (method === 'pop') {
197+
proxifier.tree.master.removeProxy(nestedPath)
198+
} else if (method !== 'push') {
199+
proxifier.tree.master.removeProxy(path)
200+
}
145201

146202
mutationTree.addMutation({
147-
method: String(prop),
203+
method,
148204
path: path,
149205
args: args,
150206
})
@@ -167,6 +223,10 @@ export class Proxifier {
167223

168224
const mutationTree = proxifier.getMutationTree()
169225

226+
if (isPlainObject(target[prop])) {
227+
proxifier.tree.master.removeProxy(nestedPath)
228+
}
229+
170230
mutationTree.addMutation({
171231
method: 'set',
172232
path: nestedPath,
@@ -248,6 +308,10 @@ export class Proxifier {
248308

249309
const mutationTree = proxifier.getMutationTree()
250310

311+
if (isPlainObject(target[prop]) || Array.isArray(target[prop])) {
312+
proxifier.tree.master.removeProxy(nestedPath)
313+
}
314+
251315
mutationTree.addMutation(
252316
{
253317
method: 'set',
@@ -277,6 +341,10 @@ export class Proxifier {
277341

278342
const mutationTree = proxifier.getMutationTree()
279343

344+
if (isPlainObject(target[prop]) || Array.isArray(target[prop])) {
345+
proxifier.tree.master.removeProxy(nestedPath)
346+
}
347+
280348
mutationTree.addMutation(
281349
{
282350
method: 'unset',
@@ -295,9 +363,9 @@ export class Proxifier {
295363
)
296364
)
297365
}
298-
proxify(value, path) {
366+
proxify(value: any, path: string) {
299367
if (value) {
300-
if (value[IS_PROXY] && value[PATH] !== path) {
368+
if (value[IS_PROXY] && String(value[PATH]) !== String(path)) {
301369
return this.proxify(value[VALUE], path)
302370
} else if (value[IS_PROXY]) {
303371
return value

packages/node_modules/proxy-state-tree/src/index.test.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IS_PROXY, ProxyStateTree } from './'
1+
import { IS_PROXY, ProxyStateTree, CACHED_PROXY } from './'
22

33
describe('CREATION', () => {
44
test('should create a ProxyStateTree instance', () => {
@@ -917,3 +917,64 @@ describe('GETTER', () => {
917917
expect(renderCount).toBe(2)
918918
})
919919
})
920+
921+
describe('PROXY CACHE', () => {
922+
it('should cache proxies', () => {
923+
const tree = new ProxyStateTree({
924+
foo: {
925+
bar: 'baz',
926+
},
927+
})
928+
929+
const accessTree = tree.getTrackStateTree()
930+
931+
const proxy = accessTree.state.foo
932+
const proxifier = accessTree.proxifier as any
933+
expect(proxifier.proxyCache.foo[CACHED_PROXY]).toBe(proxy)
934+
})
935+
936+
it('should replace cached proxies when path mutated', () => {
937+
const tree = new ProxyStateTree({
938+
foo: {
939+
bar: 'baz',
940+
},
941+
})
942+
943+
const accessTree = tree.getTrackStateTree()
944+
const mutateTree = tree.getMutationTree()
945+
946+
const proxy = accessTree.state.foo
947+
948+
mutateTree.state.foo = { bar: 'baz2' }
949+
950+
const proxy2 = accessTree.state.foo
951+
952+
const proxifier = accessTree.proxifier as any
953+
expect(proxifier.proxyCache.foo[CACHED_PROXY]).not.toBe(proxy)
954+
expect(proxifier.proxyCache.foo[CACHED_PROXY]).toBe(proxy2)
955+
})
956+
it('should remove cached proxies across proxifiers', () => {
957+
const tree = new ProxyStateTree({
958+
foo: {
959+
bar: 'baz',
960+
},
961+
})
962+
963+
const mutateTree = tree.getMutationTree()
964+
const mutateTree2 = tree.getMutationTree()
965+
966+
const proxy = mutateTree.state.foo
967+
const proxy2 = mutateTree2.state.foo
968+
969+
const proxifier = mutateTree.proxifier as any
970+
const proxifier2 = mutateTree2.proxifier as any
971+
972+
expect(proxifier.proxyCache.foo[CACHED_PROXY]).toBe(proxy)
973+
expect(proxifier2.proxyCache.foo[CACHED_PROXY]).toBe(proxy2)
974+
975+
delete mutateTree.state.foo
976+
977+
expect(proxifier.proxyCache.foo).toBe(undefined)
978+
expect(proxifier2.proxyCache.foo).toBe(undefined)
979+
})
980+
})

packages/node_modules/proxy-state-tree/src/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IS_PROXY, VALUE, PATH, Proxifier } from './Proxyfier'
1+
import { IS_PROXY, VALUE, PATH, Proxifier, CACHED_PROXY } from './Proxyfier'
22
import isPlainObject from 'is-plain-obj'
33
import {
44
IMutation,
@@ -11,6 +11,7 @@ import {
1111
IProxyStateTree,
1212
IFlushCallback,
1313
IProxifier,
14+
IRemoveProxyCallback,
1415
} from './types'
1516
import { TrackMutationTree } from './TrackMutationTree'
1617
import { TrackStateTree } from './TrackStateTree'
@@ -20,6 +21,7 @@ export {
2021
IS_PROXY,
2122
VALUE,
2223
PATH,
24+
CACHED_PROXY,
2325
IMutation,
2426
ITrackCallback,
2527
ITrackStateTree,
@@ -32,6 +34,7 @@ export * from './types'
3234

3335
export class ProxyStateTree<T extends object> implements IProxyStateTree<T> {
3436
private mutationCallbacks: IMutationCallback[] = []
37+
private removeProxyCallbacks: IRemoveProxyCallback[] = []
3538
private flushCallbacks: IFlushCallback[] = []
3639
private cache = {
3740
trackMutationTree: [] as ITrackMutationTree<T>[],
@@ -82,6 +85,14 @@ export class ProxyStateTree<T extends object> implements IProxyStateTree<T> {
8285
''
8386
)
8487
}
88+
onRemoveProxy(cb: IRemoveProxyCallback) {
89+
this.removeProxyCallbacks.push(cb)
90+
}
91+
removeProxy(path: string) {
92+
for (let cb of this.removeProxyCallbacks) {
93+
cb(path)
94+
}
95+
}
8596
getMutationTree(): ITrackMutationTree<T> {
8697
if (IS_PRODUCTION) {
8798
return (this.mutationTree =

packages/node_modules/proxy-state-tree/src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ export interface IFlushCallback {
6767

6868
export type TTree = ITrackMutationTree<any> | ITrackStateTree<any>
6969

70+
export interface IRemoveProxyCallback {
71+
(path: string): void
72+
}
73+
7074
export interface IProxyStateTree<T extends object> {
7175
addMutation(mutation: IMutation, objectChangePath?: string): void
7276
addPathDependency(path: string, callback: ITrackCallback): void
@@ -76,6 +80,8 @@ export interface IProxyStateTree<T extends object> {
7680
changeTrackStateTree(tree: ITrackStateTree<T>): void
7781
disposeTree(proxy: TTree): void
7882
onMutation(cb: IMutationCallback): void
83+
onRemoveProxy(cb: IRemoveProxyCallback): void
84+
removeProxy(path: string): void
7985
onFlush(cb: IFlushCallback): void
8086
rescope(value: any, tree: TTree): any
8187
sourceState: T

0 commit comments

Comments
 (0)