Last active
April 16, 2021 20:46
-
-
Save mikeapr4/8436ce26952bae0e20072eb6d10ff43a to your computer and use it in GitHub Desktop.
more advanced version of a reactive method-style getter caching, with a size limit on the cache
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This utility is to keep track of a list of keys (entity IDs no doubt) in last access | |
// order. The oldest entities can be popped off the list, and new entities can be added. | |
// The premise is a bidirectional linked list along with an ID hash to an point in the | |
// linked list. The reason behind the complexity is performance. | |
export default () => { | |
let first = null; | |
let last = null; | |
const hash = {}; | |
let size = 0; | |
return { | |
size: () => size, | |
popStale(qty = 1) { | |
if (size < qty) { | |
return null; | |
} | |
const ret = []; | |
let x = first; | |
while (ret.length < qty) { | |
ret.push(x.id); | |
delete hash[x.id]; | |
x = x.n ? hash[x.n] : null; | |
} | |
size -= qty; | |
if (first === last) { | |
last = null; | |
} | |
first = x; | |
if (first) { | |
first.p = null; | |
} | |
return ret; | |
}, | |
pushAccess(id) { | |
if (!id) { | |
return; | |
} | |
const place = hash[id]; | |
if (place) { | |
// If it already exists, remove it | |
const { p, n } = place; | |
if (p && n) { // middle | |
hash[p].n = n; | |
hash[n].p = p; | |
} else if (p) { // last | |
last = hash[p]; | |
hash[p].n = null; | |
} else if (n) { // first | |
first = hash[n]; | |
hash[n].p = null; | |
} else { // first and last | |
first = null; | |
last = null; | |
} | |
} else { | |
size += 1; | |
} | |
hash[id] = { id, p: last ? last.id : null, n: null }; | |
if (last) { | |
last.n = id; | |
} | |
last = hash[id]; | |
first = first || last; | |
}, | |
}; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cacheQueue from 'cache-queue'; | |
describe('Cache Queue Util', () => { | |
it('should not store an ID more than once', () => { | |
const q = cacheQueue(); | |
[1, 2, 3, 4, 5, 4, 3, 2, 1, 1].forEach((n) => q.pushAccess(n)); | |
expect(q.size()).to.equal(5); | |
expect(q.popStale(2)).to.deep.equal([5, 4]); | |
expect(q.size()).to.equal(3); | |
q.pushAccess(3); | |
q.pushAccess(7); | |
expect(q.size()).to.equal(4); | |
expect(q.popStale()).to.deep.equal([2]); | |
expect(q.popStale(3)).to.deep.equal([1, 3, 7]); | |
expect(q.size()).to.equal(0); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cacheQueue from 'cache-queue'; | |
// recreation of a computed property | |
const computed = (vm, func) => { | |
vm.$watch( | |
func, | |
null, // callback not used for lazy | |
{ lazy: true }, // lazy means we can just flag as dirty | |
); | |
// eslint-disable-next-line no-underscore-dangle | |
const watcher = vm._watchers[vm._watchers.length - 1]; | |
// add an accessor | |
watcher.lazyValue = () => { | |
if (watcher.dirty) { | |
watcher.evaluate(); | |
} | |
watcher.depend(); // means any calling computed will have transitive deps | |
return watcher.value; | |
}; | |
return watcher; | |
}; | |
// Cache Wrapper for Method-style Getter | |
// *Parameter: limit | |
const methodStyleCache = (getVM, getter, limit) => { | |
let watchers = {}; | |
let q; | |
return (...args) => { | |
// Remove old watchers if the getter is rebuilt | |
Object.values(watchers).forEach((w) => w.teardown()); | |
watchers = {}; | |
q = cacheQueue(); | |
const innerMethod = getter.call(null, ...args); | |
return (id) => { | |
let watcher = watchers[id]; | |
if (!watcher) { | |
watcher = computed(getVM(), () => innerMethod(id)); | |
watchers[id] = watcher; | |
// *If the size of the cache goes above 150% of the limit, | |
// drop the excess stale entries | |
if (limit && q.size() >= (limit * 1.5)) { | |
q.popStale(limit * 0.5).forEach((staleId) => { | |
const staleWatcher = watchers[staleId]; | |
delete watchers[staleId]; | |
staleWatcher.teardown(); | |
}); | |
} | |
} | |
// *Each access adjusts the queue | |
if (limit) { | |
q.pushAccess(id); | |
} | |
return watcher.lazyValue(); | |
}; | |
}; | |
}; | |
// Cacher that can be attached as a plugin to get a store reference | |
const methodGetterCacher = () => { | |
let store; | |
const getVM = () => store._vm; | |
return { | |
plugin: (st) => { store = st; }, | |
cache: (getter, limit) => methodStyleCache(getVM, getter, limit), | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment