Skip to content

Commit 284b003

Browse files
committed
Improve cleaning phase of keypath tracker cache
Instead of doing a full recursive traversal of the keypathStates tree, keep track of all changed paths as use that as an indicator of the paths the need to be cleaned
1 parent e6338d9 commit 284b003

File tree

4 files changed

+156
-54
lines changed

4 files changed

+156
-54
lines changed

src/reactor/keypath-tracker.js

Lines changed: 105 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* k
99
*
1010
*/
11-
import { Map, Record } from 'immutable'
11+
import { Map, Record, Set } from 'immutable'
1212
import { toImmutable, toJS } from '../immutable-helpers'
1313

1414
export const status = {
@@ -17,12 +17,26 @@ export const status = {
1717
UNKNOWN: 2,
1818
}
1919

20-
export const Node = Record({
20+
export const RootNode = Record({
2121
status: status.CLEAN,
2222
state: 1,
2323
children: Map(),
24+
changedPaths: Set(),
2425
})
2526

27+
const Node = Record({
28+
status: status.CLEAN,
29+
state: 1,
30+
children: Map(),
31+
})
32+
33+
/**
34+
* Denotes that a keypath hasn't changed
35+
* Makes the Node at the keypath as CLEAN and recursively marks the children as CLEAN
36+
* @param {Immutable.Map} map
37+
* @param {Keypath} keypath
38+
* @return {Status}
39+
*/
2640
export function unchanged(map, keypath) {
2741
const childKeypath = getChildKeypath(keypath)
2842
if (!map.hasIn(childKeypath)) {
@@ -36,14 +50,24 @@ export function unchanged(map, keypath) {
3650
})
3751
}
3852

53+
/**
54+
* Denotes that a keypath has changed
55+
* Traverses to the Node at the keypath and marks as DIRTY, marks all children as UNKNOWN
56+
* @param {Immutable.Map} map
57+
* @param {Keypath} keypath
58+
* @return {Status}
59+
*/
3960
export function changed(map, keypath) {
4061
const childrenKeypath = getChildKeypath(keypath).concat('children')
41-
return map
42-
.update('children', children => recursiveIncrement(children, keypath))
62+
// TODO(jordan): can this be optimized
63+
return map.withMutations(m => {
64+
m.update('changedPaths', p => p.add(toImmutable(keypath)))
65+
m.update('children', children => recursiveIncrement(children, keypath))
4366
// handle the root node
44-
.update('state', val => val + 1)
45-
.set('status', status.DIRTY)
46-
.updateIn(childrenKeypath, entry => recursiveSetStatus(entry, status.UNKNOWN))
67+
m.update('state', val => val + 1)
68+
m.set('status', status.DIRTY)
69+
m.updateIn(childrenKeypath, entry => recursiveSetStatus(entry, status.UNKNOWN))
70+
})
4771
}
4872

4973
/**
@@ -63,12 +87,7 @@ export function isEqual(map, keypath, value) {
6387
return entry.get('state') === value;
6488
}
6589

66-
/**
67-
* Increments all unknown states and sets everything to CLEAN
68-
* @param {Immutable.Map} map
69-
* @return {Status}
70-
*/
71-
export function incrementAndClean(map) {
90+
function recursiveClean(map) {
7291
if (map.size === 0) {
7392
return map
7493
}
@@ -82,11 +101,37 @@ export function incrementAndClean(map) {
82101
return map
83102
.update('children', c => c.withMutations(m => {
84103
m.keySeq().forEach(key => {
85-
m.update(key, incrementAndClean)
104+
m.update(key, recursiveClean)
86105
})
87106
}))
88107
}
89108

109+
/**
110+
* Increments all unknown states and sets everything to CLEAN
111+
* @param {Immutable.Map} map
112+
* @return {Status}
113+
*/
114+
export function incrementAndClean(map) {
115+
if (map.size === 0) {
116+
return map
117+
}
118+
const changedPaths = map.get('changedPaths')
119+
// TODO(jordan): can this be optimized
120+
return map.withMutations(m => {
121+
changedPaths.forEach(path => {
122+
m.update('children', c => traverseAndMarkClean(c, path))
123+
})
124+
125+
m.set('changedPaths', Set())
126+
const rootStatus = m.get('status')
127+
if (rootStatus === status.DIRTY) {
128+
setClean(m)
129+
} else if (rootStatus === status.UNKNOWN) {
130+
setClean(increment(m))
131+
}
132+
})
133+
}
134+
90135
export function get(map, keypath) {
91136
return map.getIn(getChildKeypath(keypath).concat('state'))
92137
}
@@ -129,6 +174,52 @@ function recursiveIncrement(map, keypath) {
129174
})
130175
}
131176

177+
/**
178+
* Traverses up to a keypath and marks all entries as clean along the way, then recursively traverses over all children
179+
* @param {Immutable.Map} map
180+
* @param {Immutable.List} keypath
181+
* @return {Status}
182+
*/
183+
function traverseAndMarkClean(map, keypath) {
184+
if (keypath.size === 0) {
185+
return recursiveCleanChildren(map)
186+
}
187+
return map.withMutations(map => {
188+
const key = keypath.first()
189+
190+
map.update(key, incrementAndCleanNode)
191+
map.updateIn([key, 'children'], children => traverseAndMarkClean(children, keypath.rest()))
192+
})
193+
}
194+
195+
function recursiveCleanChildren(children) {
196+
if (children.size === 0) {
197+
return children
198+
}
199+
200+
return children.withMutations(c => {
201+
c.keySeq().forEach(key => {
202+
c.update(key, incrementAndCleanNode)
203+
c.updateIn([key, 'children'], recursiveCleanChildren)
204+
})
205+
})
206+
}
207+
208+
/**
209+
* Takes a node, marks it CLEAN, if it was UNKNOWN it increments
210+
* @param {Node} node
211+
* @return {Status}
212+
*/
213+
function incrementAndCleanNode(node) {
214+
const nodeStatus = node.get('status')
215+
if (nodeStatus === status.DIRTY) {
216+
return setClean(node)
217+
} else if (nodeStatus === status.UNKNOWN) {
218+
return setClean(increment(node))
219+
}
220+
return node
221+
}
222+
132223
function recursiveRegister(map, keypath) {
133224
keypath = toImmutable(keypath)
134225
if (keypath.size === 0) {

src/reactor/records.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Map, Set, Record } from 'immutable'
22
import { DefaultCache } from './cache'
3-
import { Node as KeypathTrackerNode } from './keypath-tracker'
3+
import { RootNode as KeypathTrackerNode } from './keypath-tracker'
44

55
export const PROD_OPTIONS = Map({
66
// logs information for each dispatch

0 commit comments

Comments
 (0)