Cache map improvements (#856)
* Update CacheMap API; get=>getOrCreate; add get; add set; add has * Update tests * Add more tests
This commit is contained in:
parent
d395a2a6bf
commit
7d78e8737f
@ -125,7 +125,7 @@ class JsonSchemaProxyHandler {
|
|||||||
|
|
||||||
class JsonSchemaValidator {
|
class JsonSchemaValidator {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._regexCache = new CacheMap(100, (pattern, flags) => new RegExp(pattern, flags));
|
this._regexCache = new CacheMap(100, ([pattern, flags]) => new RegExp(pattern, flags));
|
||||||
}
|
}
|
||||||
|
|
||||||
createProxy(target, schema) {
|
createProxy(target, schema) {
|
||||||
@ -676,7 +676,7 @@ class JsonSchemaValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_getRegex(pattern, flags) {
|
_getRegex(pattern, flags) {
|
||||||
const regex = this._regexCache.get(pattern, flags);
|
const regex = this._regexCache.getOrCreate([pattern, flags]);
|
||||||
regex.lastIndex = 0;
|
regex.lastIndex = 0;
|
||||||
return regex;
|
return regex;
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ class CacheMap {
|
|||||||
* Creates a new CacheMap.
|
* Creates a new CacheMap.
|
||||||
* @param maxCount The maximum number of entries able to be stored in the cache.
|
* @param maxCount The maximum number of entries able to be stored in the cache.
|
||||||
* @param create A function to create a value for the corresponding path.
|
* @param create A function to create a value for the corresponding path.
|
||||||
* The signature is: create(...path)
|
* The signature is: create(path)
|
||||||
*/
|
*/
|
||||||
constructor(maxCount, create) {
|
constructor(maxCount, create) {
|
||||||
if (!(
|
if (!(
|
||||||
@ -59,12 +59,57 @@ class CacheMap {
|
|||||||
return this._maxCount;
|
return this._maxCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not an item exists at the given path.
|
||||||
|
* @param path Array corresponding to the key of the cache.
|
||||||
|
* @returns A boolean indicating whether ot not the item exists.
|
||||||
|
*/
|
||||||
|
has(path) {
|
||||||
|
const node = this._accessNode(false, false, null, false, path);
|
||||||
|
return (node !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an item at the given path, if it exists. Otherwise, returns undefined.
|
||||||
|
* @param path Array corresponding to the key of the cache.
|
||||||
|
* @returns The existing value at the path, if any; otherwise, undefined.
|
||||||
|
*/
|
||||||
|
get(path) {
|
||||||
|
const node = this._accessNode(false, false, null, true, path);
|
||||||
|
return (node !== null ? node.value : void 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets an item at the given path, if it exists. Otherwise, creates a new item
|
* Gets an item at the given path, if it exists. Otherwise, creates a new item
|
||||||
* and adds it to the cache. If the count exceeds the maximum count, items will be removed.
|
* and adds it to the cache. If the count exceeds the maximum count, items will be removed.
|
||||||
* @param path Arguments corresponding to the key of the cache.
|
* @param path Array corresponding to the key of the cache.
|
||||||
|
* @returns The existing value at the path, if any; otherwise, a newly created value.
|
||||||
*/
|
*/
|
||||||
get(...path) {
|
getOrCreate(path) {
|
||||||
|
return this._accessNode(true, false, null, true, path).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a value at a given path.
|
||||||
|
* @param path Array corresponding to the key of the cache.
|
||||||
|
* @param value The value to store in the cache.
|
||||||
|
*/
|
||||||
|
set(path, value) {
|
||||||
|
this._accessNode(false, true, value, true, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the cache.
|
||||||
|
*/
|
||||||
|
clear() {
|
||||||
|
this._map.clear();
|
||||||
|
this._count = 0;
|
||||||
|
this._resetEndNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private
|
||||||
|
|
||||||
|
_accessNode(create, set, value, updateRecency, path) {
|
||||||
let ii = path.length;
|
let ii = path.length;
|
||||||
if (ii === 0) { throw new Error('Invalid path'); }
|
if (ii === 0) { throw new Error('Invalid path'); }
|
||||||
|
|
||||||
@ -77,13 +122,18 @@ class CacheMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (i === ii) {
|
if (i === ii) {
|
||||||
// Found (map is now a node)
|
// Found (map should now be a node)
|
||||||
this._updateRecency(map);
|
if (updateRecency) { this._updateRecency(map); }
|
||||||
return map.value;
|
if (set) { map.value = value; }
|
||||||
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new
|
// Create new
|
||||||
const value = this._create(...path);
|
if (create) {
|
||||||
|
value = this._create(path);
|
||||||
|
} else if (!set) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Create mapping
|
// Create mapping
|
||||||
--ii;
|
--ii;
|
||||||
@ -101,20 +151,9 @@ class CacheMap {
|
|||||||
|
|
||||||
this._updateCount();
|
this._updateCount();
|
||||||
|
|
||||||
return value;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the cache.
|
|
||||||
*/
|
|
||||||
clear() {
|
|
||||||
this._map.clear();
|
|
||||||
this._count = 0;
|
|
||||||
this._resetEndNodes();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private
|
|
||||||
|
|
||||||
_updateRecency(node) {
|
_updateRecency(node) {
|
||||||
this._removeNode(node);
|
this._removeNode(node);
|
||||||
this._addNode(node, this._listFirst);
|
this._addNode(node, this._listFirst);
|
||||||
|
@ -57,101 +57,154 @@ function testApi() {
|
|||||||
maxCount: 1,
|
maxCount: 1,
|
||||||
expectedCount: 1,
|
expectedCount: 1,
|
||||||
calls: [
|
calls: [
|
||||||
['get', 'a', 'b', 'c']
|
{func: 'getOrCreate', args: [['a', 'b', 'c']]}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
maxCount: 10,
|
maxCount: 10,
|
||||||
expectedCount: 1,
|
expectedCount: 1,
|
||||||
calls: [
|
calls: [
|
||||||
['get', 'a', 'b', 'c'],
|
{func: 'getOrCreate', args: [['a', 'b', 'c']]},
|
||||||
['get', 'a', 'b', 'c'],
|
{func: 'getOrCreate', args: [['a', 'b', 'c']]},
|
||||||
['get', 'a', 'b', 'c']
|
{func: 'getOrCreate', args: [['a', 'b', 'c']]}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
maxCount: 10,
|
maxCount: 10,
|
||||||
expectedCount: 3,
|
expectedCount: 3,
|
||||||
calls: [
|
calls: [
|
||||||
['get', 'a1', 'b', 'c'],
|
{func: 'getOrCreate', args: [['a1', 'b', 'c']]},
|
||||||
['get', 'a2', 'b', 'c'],
|
{func: 'getOrCreate', args: [['a2', 'b', 'c']]},
|
||||||
['get', 'a3', 'b', 'c']
|
{func: 'getOrCreate', args: [['a3', 'b', 'c']]}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
maxCount: 10,
|
maxCount: 10,
|
||||||
expectedCount: 3,
|
expectedCount: 3,
|
||||||
calls: [
|
calls: [
|
||||||
['get', 'a', 'b1', 'c'],
|
{func: 'getOrCreate', args: [['a', 'b1', 'c']]},
|
||||||
['get', 'a', 'b2', 'c'],
|
{func: 'getOrCreate', args: [['a', 'b2', 'c']]},
|
||||||
['get', 'a', 'b3', 'c']
|
{func: 'getOrCreate', args: [['a', 'b3', 'c']]}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
maxCount: 10,
|
maxCount: 10,
|
||||||
expectedCount: 3,
|
expectedCount: 3,
|
||||||
calls: [
|
calls: [
|
||||||
['get', 'a', 'b', 'c1'],
|
{func: 'getOrCreate', args: [['a', 'b', 'c1']]},
|
||||||
['get', 'a', 'b', 'c2'],
|
{func: 'getOrCreate', args: [['a', 'b', 'c2']]},
|
||||||
['get', 'a', 'b', 'c3']
|
{func: 'getOrCreate', args: [['a', 'b', 'c3']]}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
maxCount: 1,
|
maxCount: 1,
|
||||||
expectedCount: 1,
|
expectedCount: 1,
|
||||||
calls: [
|
calls: [
|
||||||
['get', 'a1', 'b', 'c'],
|
{func: 'getOrCreate', args: [['a1', 'b', 'c']]},
|
||||||
['get', 'a2', 'b', 'c'],
|
{func: 'getOrCreate', args: [['a2', 'b', 'c']]},
|
||||||
['get', 'a3', 'b', 'c']
|
{func: 'getOrCreate', args: [['a3', 'b', 'c']]}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
maxCount: 1,
|
maxCount: 1,
|
||||||
expectedCount: 1,
|
expectedCount: 1,
|
||||||
calls: [
|
calls: [
|
||||||
['get', 'a', 'b1', 'c'],
|
{func: 'getOrCreate', args: [['a', 'b1', 'c']]},
|
||||||
['get', 'a', 'b2', 'c'],
|
{func: 'getOrCreate', args: [['a', 'b2', 'c']]},
|
||||||
['get', 'a', 'b3', 'c']
|
{func: 'getOrCreate', args: [['a', 'b3', 'c']]}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
maxCount: 1,
|
maxCount: 1,
|
||||||
expectedCount: 1,
|
expectedCount: 1,
|
||||||
calls: [
|
calls: [
|
||||||
['get', 'a', 'b', 'c1'],
|
{func: 'getOrCreate', args: [['a', 'b', 'c1']]},
|
||||||
['get', 'a', 'b', 'c2'],
|
{func: 'getOrCreate', args: [['a', 'b', 'c2']]},
|
||||||
['get', 'a', 'b', 'c3']
|
{func: 'getOrCreate', args: [['a', 'b', 'c3']]}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
maxCount: 10,
|
maxCount: 10,
|
||||||
expectedCount: 0,
|
expectedCount: 0,
|
||||||
calls: [
|
calls: [
|
||||||
['get', 'a', 'b', 'c1'],
|
{func: 'getOrCreate', args: [['a', 'b', 'c1']]},
|
||||||
['get', 'a', 'b', 'c2'],
|
{func: 'getOrCreate', args: [['a', 'b', 'c2']]},
|
||||||
['get', 'a', 'b', 'c3'],
|
{func: 'getOrCreate', args: [['a', 'b', 'c3']]},
|
||||||
['clear']
|
{func: 'clear', args: []}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
maxCount: 0,
|
maxCount: 0,
|
||||||
expectedCount: 0,
|
expectedCount: 0,
|
||||||
calls: [
|
calls: [
|
||||||
['get', 'a1', 'b', 'c'],
|
{func: 'getOrCreate', args: [['a1', 'b', 'c']]},
|
||||||
['get', 'a', 'b2', 'c'],
|
{func: 'getOrCreate', args: [['a', 'b2', 'c']]},
|
||||||
['get', 'a', 'b', 'c3']
|
{func: 'getOrCreate', args: [['a', 'b', 'c3']]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
maxCount: 10,
|
||||||
|
expectedCount: 1,
|
||||||
|
calls: [
|
||||||
|
{func: 'get', args: [['a1', 'b', 'c']], returnValue: void 0},
|
||||||
|
{func: 'has', args: [['a1', 'b', 'c']], returnValue: false},
|
||||||
|
{func: 'set', args: [['a1', 'b', 'c'], 32], returnValue: void 0},
|
||||||
|
{func: 'get', args: [['a1', 'b', 'c']], returnValue: 32},
|
||||||
|
{func: 'has', args: [['a1', 'b', 'c']], returnValue: true}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
maxCount: 10,
|
||||||
|
expectedCount: 2,
|
||||||
|
calls: [
|
||||||
|
{func: 'set', args: [['a1', 'b', 'c'], 32], returnValue: void 0},
|
||||||
|
{func: 'get', args: [['a1', 'b', 'c']], returnValue: 32},
|
||||||
|
{func: 'set', args: [['a1', 'b', 'c'], 64], returnValue: void 0},
|
||||||
|
{func: 'get', args: [['a1', 'b', 'c']], returnValue: 64},
|
||||||
|
{func: 'set', args: [['a2', 'b', 'c'], 96], returnValue: void 0},
|
||||||
|
{func: 'get', args: [['a2', 'b', 'c']], returnValue: 96}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
maxCount: 2,
|
||||||
|
expectedCount: 2,
|
||||||
|
calls: [
|
||||||
|
{func: 'has', args: [['a1', 'b', 'c']], returnValue: false},
|
||||||
|
{func: 'has', args: [['a2', 'b', 'c']], returnValue: false},
|
||||||
|
{func: 'has', args: [['a3', 'b', 'c']], returnValue: false},
|
||||||
|
{func: 'set', args: [['a1', 'b', 'c'], 1], returnValue: void 0},
|
||||||
|
{func: 'has', args: [['a1', 'b', 'c']], returnValue: true},
|
||||||
|
{func: 'has', args: [['a2', 'b', 'c']], returnValue: false},
|
||||||
|
{func: 'has', args: [['a3', 'b', 'c']], returnValue: false},
|
||||||
|
{func: 'set', args: [['a2', 'b', 'c'], 2], returnValue: void 0},
|
||||||
|
{func: 'has', args: [['a1', 'b', 'c']], returnValue: true},
|
||||||
|
{func: 'has', args: [['a2', 'b', 'c']], returnValue: true},
|
||||||
|
{func: 'has', args: [['a3', 'b', 'c']], returnValue: false},
|
||||||
|
{func: 'set', args: [['a3', 'b', 'c'], 3], returnValue: void 0},
|
||||||
|
{func: 'has', args: [['a1', 'b', 'c']], returnValue: false},
|
||||||
|
{func: 'has', args: [['a2', 'b', 'c']], returnValue: true},
|
||||||
|
{func: 'has', args: [['a3', 'b', 'c']], returnValue: true}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const create = (...args) => args.join(',');
|
const create = (args) => args.join(',');
|
||||||
for (const {maxCount, expectedCount, calls} of data) {
|
for (const {maxCount, expectedCount, calls} of data) {
|
||||||
const cache = new CacheMap(maxCount, create);
|
const cache = new CacheMap(maxCount, create);
|
||||||
assert.strictEqual(cache.maxCount, maxCount);
|
assert.strictEqual(cache.maxCount, maxCount);
|
||||||
for (const [name, ...args] of calls) {
|
for (const call of calls) {
|
||||||
switch (name) {
|
const {func, args} = call;
|
||||||
case 'get': cache.get(...args); break;
|
let returnValue;
|
||||||
case 'clear': cache.clear(); break;
|
switch (func) {
|
||||||
|
case 'get': returnValue = cache.get(...args); break;
|
||||||
|
case 'getOrCreate': returnValue = cache.getOrCreate(...args); break;
|
||||||
|
case 'set': returnValue = cache.set(...args); break;
|
||||||
|
case 'has': returnValue = cache.has(...args); break;
|
||||||
|
case 'clear': returnValue = cache.clear(...args); break;
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(call, 'returnValue')) {
|
||||||
|
const {returnValue: expectedReturnValue} = call;
|
||||||
|
assert.deepStrictEqual(returnValue, expectedReturnValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert.strictEqual(cache.count, expectedCount);
|
assert.strictEqual(cache.count, expectedCount);
|
||||||
|
Loading…
Reference in New Issue
Block a user