-
Notifications
You must be signed in to change notification settings - Fork 141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Caching stuff #208
base: master
Are you sure you want to change the base?
Caching stuff #208
Changes from 5 commits
bacc9d4
836b4e6
3c940f5
5351733
da98531
5025e2c
28cc547
372f8b7
fe630e9
69e6904
ce555ed
2ea41bb
90b35c6
5aef5eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,11 +102,41 @@ function getStoreDeps(getter) { | |
return storeDeps | ||
} | ||
|
||
/** | ||
* Add override options to a getter | ||
* @param {getter} getter | ||
* @param {object} options | ||
* @param {boolean} options.useCache | ||
* @param {*} options.cacheKey | ||
* @returns {getter} | ||
*/ | ||
function addGetterOptions(getter, options) { | ||
if (!isKeyPath(getter) && !isGetter(getter)) { | ||
throw new Error('createGetter must be passed a keyPath or Getter') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like the error string was copied from |
||
} | ||
|
||
if (getter.hasOwnProperty('__options')) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Storing these options on A newcomer to nuclear-js might think this would change the cache key (globally): addGetterOptions(['items'], {cacheKey: "items_cache"});
reactor.evaluate(['items']); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a |
||
throw new Error('Cannot reassign options to getter') | ||
} | ||
|
||
getter.__options = {} | ||
|
||
if (options.useCache !== undefined) { | ||
getter.__options.useCache = !!options.useCache | ||
} | ||
|
||
if (options.cacheKey !== undefined) { | ||
getter.__options.cacheKey = options.cacheKey | ||
} | ||
return getter | ||
} | ||
|
||
export default { | ||
isGetter, | ||
getComputeFn, | ||
getFlattenedDeps, | ||
getStoreDeps, | ||
getDeps, | ||
fromKeyPath, | ||
addGetterOptions, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,11 +24,16 @@ import { | |
class Reactor { | ||
constructor(config = {}) { | ||
const debug = !!config.debug | ||
const useCache = config.useCache === undefined ? true : !!config.useCache | ||
const itemsToCache = Number(config.maxItemsToCache) | ||
const maxItemsToCache = itemsToCache && itemsToCache > 1 ? itemsToCache : null | ||
const baseOptions = debug ? DEBUG_OPTIONS : PROD_OPTIONS | ||
const initialReactorState = new ReactorState({ | ||
debug: debug, | ||
maxItemsToCache: maxItemsToCache, | ||
// merge config options with the defaults | ||
options: baseOptions.merge(config.options || {}), | ||
useCache: useCache | ||
}) | ||
|
||
this.prevReactorState = initialReactorState | ||
|
@@ -285,6 +290,21 @@ class Reactor { | |
this.__isDispatching = false | ||
} | ||
} | ||
|
||
/** | ||
* Retrieve cache values | ||
* @returns {Immutable.Map} | ||
*/ | ||
getCacheValues() { | ||
return this.reactorState.get('cache') | ||
} | ||
|
||
/** | ||
* Clear all cached values | ||
*/ | ||
clearCacheValues() { | ||
this.reactorState = this.reactorState.set('cache', Immutable.Map()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is the use case of this api method? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added as an afterthought, thought it might be useful in some cases of switching views in a spa. will remove |
||
} | ||
} | ||
|
||
export default toFactory(Reactor) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ import { each } from '../utils' | |
* Immutable Types | ||
*/ | ||
const EvaluateResult = Immutable.Record({ result: null, reactorState: null}) | ||
export const CACHE_CLEAR_RATIO = .8; | ||
|
||
function evaluateResult(result, reactorState) { | ||
return new EvaluateResult({ | ||
|
@@ -335,14 +336,13 @@ export function evaluate(reactorState, keyPathOrGetter) { | |
// Cache hit | ||
return evaluateResult( | ||
getCachedValue(reactorState, keyPathOrGetter), | ||
reactorState | ||
updateCacheRecency(reactorState, keyPathOrGetter) | ||
) | ||
} | ||
|
||
// evaluate dependencies | ||
const args = getDeps(keyPathOrGetter).map(dep => evaluate(reactorState, dep).result) | ||
const evaluatedValue = getComputeFn(keyPathOrGetter).apply(null, args) | ||
|
||
return evaluateResult( | ||
evaluatedValue, | ||
cacheValue(reactorState, keyPathOrGetter, evaluatedValue) | ||
|
@@ -381,6 +381,9 @@ export function resetDirtyStores(reactorState) { | |
* @return {Getter} | ||
*/ | ||
function getCacheKey(getter) { | ||
if (getter.__options && getter.__options.cacheKey !== undefined) { | ||
return getter.__options.cacheKey | ||
} | ||
return getter | ||
} | ||
|
||
|
@@ -424,6 +427,17 @@ function isCached(reactorState, keyPathOrGetter) { | |
* @return {ReactorState} | ||
*/ | ||
function cacheValue(reactorState, getter, value) { | ||
const hasGetterUseCacheSpecified = getter.__options && getter.__options.useCache !== undefined | ||
|
||
// Prefer getter settings over reactor settings | ||
if (hasGetterUseCacheSpecified) { | ||
if (!getter.__options.useCache) { | ||
return reactorState | ||
} | ||
} else if (!reactorState.get('useCache')) { | ||
return reactorState | ||
} | ||
|
||
const cacheKey = getCacheKey(getter) | ||
const dispatchId = reactorState.get('dispatchId') | ||
const storeDeps = getStoreDeps(getter) | ||
|
@@ -434,11 +448,38 @@ function cacheValue(reactorState, getter, value) { | |
}) | ||
}) | ||
|
||
return reactorState.setIn(['cache', cacheKey], Immutable.Map({ | ||
value: value, | ||
storeStates: storeStates, | ||
dispatchId: dispatchId, | ||
})) | ||
const maxItemsToCache = reactorState.get('maxItemsToCache') | ||
const itemsToCache = maxItemsToCache * CACHE_CLEAR_RATIO | ||
|
||
return reactorState.withMutations(state => { | ||
if (maxItemsToCache && maxItemsToCache <= state.get('cache').size) { | ||
do { | ||
let key = state.get('cacheRecency').first() | ||
state.deleteIn(['cache', key]) | ||
state.deleteIn(['cacheRecency', key]) | ||
} while (itemsToCache < state.get('cache').size) | ||
} | ||
|
||
state.setIn(['cacheRecency', cacheKey], cacheKey) | ||
state.setIn(['cache', cacheKey], Immutable.Map({ | ||
value: value, | ||
storeStates: storeStates, | ||
dispatchId: dispatchId, | ||
})) | ||
}) | ||
} | ||
|
||
/** | ||
* Readds the key for the item in cache to update recency | ||
* @param {ReactorState} reactorState | ||
* @param {cacheKey} cacheKey | ||
* @return {ReactorState} | ||
*/ | ||
function updateCacheRecency(reactorState, cacheKey) { | ||
return reactorState.withMutations(state => { | ||
state.deleteIn(['cacheRecency', cacheKey]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This return reactorState.setIn(['cacheRecency', cacheKey], cacheKey) edit: nevermind, I missed that it's an OrderedMap |
||
state.setIn(['cacheRecency', cacheKey], cacheKey) | ||
}) | ||
} | ||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This API change to introduce the concept of "getter options" is a little awkward imo.
Currently, it will replace any previously set options which could lead to unexpected behavior given its name.
An alternative would be to call this
setGetterOptions
to indicate it would replace, not patch, the options.But I feel like the API is being conflated with implementation details of hidden
__options
object. The alternative that I would suggest is two separate methods to configure these options.cc @jordangarcia since this is API design considerations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jordangarcia Another thing to consider is that it's probably natural from an API standpoint that getters are configured where they are defined. It may be time to introduce a formal
Getter()
constructor... :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to see a
Getter
constructor.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jordangarcia Is there an advantage to using a constructor outside of style preference?