diff --git a/jest/__snapshots__/bundles-snapshot.test.js.snap b/jest/__snapshots__/bundles-snapshot.test.js.snap index c8ffbd89..82cd60af 100644 --- a/jest/__snapshots__/bundles-snapshot.test.js.snap +++ b/jest/__snapshots__/bundles-snapshot.test.js.snap @@ -370,12 +370,67 @@ exports[`Dist bundle is unchanged 1`] = ` return LruMapCache; }(); + var RrMapCache = /*#__PURE__*/function () { + function RrMapCache(_temp) { + var _ref = _temp === void 0 ? {} : _temp, + cacheSize = _ref.cacheSize; + + validateCacheSize(cacheSize); + this.clear(); + this._cacheSize = cacheSize; + } + + var _proto = RrMapCache.prototype; + + _proto.set = function set(key, selectorFn) { + if (this._cache.size >= this._cacheSize) { + this._randomReplace(key, selectorFn); + } else { + this._cache.set(key, selectorFn); + + this._cacheKeys[this._cache.size] = key; + } + }; + + _proto.get = function get(key) { + return this._cache.get(key); + }; + + _proto.remove = function remove(key) { + var index = this._cacheKeys.indexOf(key); // O(1) + + + if (index > -1) { + delete this._cache[\\"delete\\"](key); + this._cacheKeys[index] = this._cacheKeys[this._cache.size]; + } + }; + + _proto.clear = function clear() { + this._cache = new Map(); + this._cacheKeys = []; + }; + + _proto._randomReplace = function _randomReplace(newKey, newValue) { + var index = Math.floor(Math.random() * this._cache.size); + + this._cache[\\"delete\\"](this._cacheKeys[index]); + + this._cacheKeys[index] = newKey; + + this._cache.set(newKey, newValue); + }; + + return RrMapCache; + }(); + exports.FifoMapCache = FifoMapCache; exports.FifoObjectCache = FifoObjectCache; exports.FlatMapCache = FlatMapCache; exports.FlatObjectCache = FlatObjectCache; exports.LruMapCache = LruMapCache; exports.LruObjectCache = LruObjectCache; + exports.RrMapCache = RrMapCache; exports.createCachedSelector = createCachedSelector; exports.createStructuredCachedSelector = createStructuredCachedSelector; exports[\\"default\\"] = createCachedSelector; diff --git a/src/cache/README.md b/src/cache/README.md index 511dece9..fc3e49e4 100644 --- a/src/cache/README.md +++ b/src/cache/README.md @@ -4,7 +4,7 @@ ## Available cache objects -`re-reselect` ships with **6 ready-to-use cache object constructors**: +`re-reselect` ships with **7 ready-to-use cache object constructors**: | name | accepted cacheKey | type | storage | | :---------------------------------------: | :---------------: | :-----------------------------------: | :----------------------------: | @@ -14,6 +14,7 @@ | [`FlatMapCache`](./FlatMapCache.js) | any | flat unlimited | [Map object][docs-mozilla-map] | | [`FifoMapCache`](./FifoMapCache.js) | any | [first in first out][docs-fifo-cache] | [Map object][docs-mozilla-map] | | [`LruMapCache`](./LruMapCache.js) | any | [least recently used][docs-lru-cache] | [Map object][docs-mozilla-map] | +| [`RrMapCache`](./RrMapCache.js) | any | [random replacement][docs-rr-cache] | [Map object][docs-mozilla-map] | ```js @@ -53,4 +54,5 @@ interface ICacheObject { [wiki-strategy-pattern]: https://en.wikipedia.org/wiki/Strategy_pattern [docs-fifo-cache]: https://en.wikipedia.org/wiki/Cache_replacement_policies#First_in_first_out_(FIFO) [docs-lru-cache]: https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU) +[docs-rr-cache]: https://en.wikipedia.org/wiki/Cache_replacement_policies#Random_replacement_(RR) [docs-mozilla-map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map diff --git a/src/cache/RrMapCache.js b/src/cache/RrMapCache.js new file mode 100644 index 00000000..6993eb27 --- /dev/null +++ b/src/cache/RrMapCache.js @@ -0,0 +1,37 @@ +import validateCacheSize from './util/validateCacheSize'; + +export default class RrMapCache { + constructor({cacheSize} = {}) { + validateCacheSize(cacheSize); + this.clear(); + this._cacheSize = cacheSize; + } + set(key, selectorFn) { + if (this._cache.size >= this._cacheSize) { + this._randomReplace(key, selectorFn); + } else { + this._cacheKeys[this._cache.size] = key; + this._cache.set(key, selectorFn); + } + } + get(key) { + return this._cache.get(key); + } + remove(key) { + const index = this._cacheKeys.indexOf(key); // O(1) + if (index > -1) { + delete this._cache.delete(key); + this._cacheKeys[index] = this._cacheKeys[this._cache.size]; + } + } + clear() { + this._cache = new Map(); + this._cacheKeys = []; + } + _randomReplace(newKey, newValue) { + const index = Math.floor(Math.random() * this._cache.size); + this._cache.delete(this._cacheKeys[index]); + this._cacheKeys[index] = newKey; + this._cache.set(newKey, newValue); + } +} diff --git a/src/cache/__tests__/RrMapCache.spec.js b/src/cache/__tests__/RrMapCache.spec.js new file mode 100644 index 00000000..f553b2cc --- /dev/null +++ b/src/cache/__tests__/RrMapCache.spec.js @@ -0,0 +1,12 @@ +import {RrMapCache as CacheObject} from '../../../src/index'; +import testBasicBehavior from '../__util__/testBasicBehavior'; +import testRrBehavior from '../__util__/testRrBehavior'; +import testCacheSizeOptionValidation from '../__util__/testCacheSizeOptionValidation'; +import testMapCacheKeyBehavior from '../__util__/testMapCacheKeyBehavior'; + +describe('RrMapCache', () => { + testBasicBehavior(CacheObject, {cacheSize: 10}); + testRrBehavior(CacheObject); + testCacheSizeOptionValidation(CacheObject); + testMapCacheKeyBehavior(CacheObject, {cacheSize: 10}); +}); diff --git a/src/cache/__util__/testBasicBehavior.js b/src/cache/__util__/testBasicBehavior.js index 34d1284f..020c9c73 100644 --- a/src/cache/__util__/testBasicBehavior.js +++ b/src/cache/__util__/testBasicBehavior.js @@ -36,6 +36,11 @@ function testBasicBehavior(CacheObject, options) { expect(cache.get(entry)).toBe(undefined); }); }); + + it('removes non-existant keys', () => { + const cache = new CacheObject({cacheSize: 5}); + expect(() => cache.remove('foo')).not.toThrow(); + }); }); } diff --git a/src/cache/__util__/testRrBehavior.js b/src/cache/__util__/testRrBehavior.js new file mode 100644 index 00000000..30efc15d --- /dev/null +++ b/src/cache/__util__/testRrBehavior.js @@ -0,0 +1,30 @@ +import fillCacheWith from './fillCacheWith'; + +function testRrBehavior(CacheObject) { + describe('RR cache behavior', () => { + it('limits cache size by removing a random item', () => { + const cache = new CacheObject({cacheSize: 5}); + const entries = [1, 2, 3, 4, 5, 6]; + const get = cache.get.bind(cache); + fillCacheWith(cache, entries); + expect(entries.every(get)).toBe(false); + }); + + it('shrinks when removing existing items manually', () => { + const cache = new CacheObject({cacheSize: 5}); + const entries = [1, 2, 3, 4, 5]; + const get = cache.get.bind(cache); + fillCacheWith(cache, entries); + expect(entries.every(get)).toBe(true); + cache.remove('non-existant'); + cache.remove(5); + expect(cache.get(5)).toBeUndefined(); + cache.set(5, 5); + expect(entries.every(get)).toBe(true); + cache.set(6, 6); + expect(entries.every(get)).toBe(false); + }); + }); +} + +export default testRrBehavior; diff --git a/src/index.d.ts b/src/index.d.ts index 3db31647..749886cf 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -4350,6 +4350,14 @@ export class LruMapCache implements ICacheObject { clear(): void; } +export class RrMapCache implements ICacheObject { + constructor(options: {cacheSize: number}); + set(key: any, selectorFn: any): void; + get(key: any): any; + remove(key: any): void; + clear(): void; +} + /* * Key selector creators */ diff --git a/src/index.js b/src/index.js index f32b037d..4830a675 100644 --- a/src/index.js +++ b/src/index.js @@ -11,3 +11,4 @@ export {default as LruObjectCache} from './cache/LruObjectCache'; export {default as FlatMapCache} from './cache/FlatMapCache'; export {default as FifoMapCache} from './cache/FifoMapCache'; export {default as LruMapCache} from './cache/LruMapCache'; +export {default as RrMapCache} from './cache/RrMapCache';