Skip to content

Commit

Permalink
More implementation work
Browse files Browse the repository at this point in the history
  • Loading branch information
kriszyp committed Oct 1, 2020
1 parent 016b0a5 commit 9d948e6
Show file tree
Hide file tree
Showing 7 changed files with 337 additions and 30 deletions.
67 changes: 67 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
dist

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

package-lock.json
# Typescript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
tests/samples

# Visual Studio Code directory
.vscode
.vs

build
5 changes: 5 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Dependency directories
node_modules/
tests/samples
.vs
build/
199 changes: 176 additions & 23 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,62 +1,213 @@
//import { ExpirationStrategy } from './ExpirationStrategy'
const defaultExpirer = new ExpirationStrategy()

export class WeakCache extends Map {
constructor(entries, expirer) {
class WeakLRUCache extends Map {
constructor(entries, options) {
super(entries)
this.expirer = expirer || defaultExpirer
expirer.register(this)
this.expirer = (options ? options.expirer === false ? new NoLRUStrategy() : options.expirer : null) || defaultExpirer
this.registry = new FinalizationRegistry(key => {
let entry = super.get(key)
if (entry && entry.deref && entry.deref() === undefined)
super.delete(key)
})
this.expirer.onRemove = entry => {
let target = entry.deref && entry.deref()
if (target) {
// remove strong reference, so only a weak reference, wait until it is finalized to remove
entry.value = undefined
} else if (entry.key) {
let currentEntry = super.get(entry.key)
if (currentEntry === entry)
super.delete(entry.key)
}
}
}
getValue(id) {
let entry = this.get(id)
get(key, mode) {
let entry = super.get(key)
let value
if (entry) {
this.expirer.used(entry)
let value = entry.value
return value === undefined ? entry.deref() : value
value = entry.value
if (value === undefined) {
value = entry.deref && entry.deref()
if (value === undefined)
super.delete(key)
else {
if (mode !== 1)
this.expirer.used(entry)
return mode === 2 ? value : entry
}
}
else {
if (mode !== 1)
this.expirer.used(entry)
return mode === 2 ? value : entry
}
}
}
setValue(id, value) {
getValue(key) {
return this.get(key, 2)
}

setValue(key, value) {
let entry
if (value && typeof value == 'object') {
entry = new WeakRef(value)
entry.value = value
}
else
entry = { value }
this.expirer.add(entry)
return this.set(id, entry)
else if (value === undefined)
return
else
entry = { key, value }
this.set(key, entry)
}
set(key, entry) {
let oldEntry = this.get(key)
if (oldEntry)
this.expirer.delete(oldEntry)
return this.insert(key, entry)
}
insert(key, entry) {
if (entry) {
let target = entry.deref && entry.deref()
if (target)
this.registry.register(target, key)
if (entry.value !== undefined)
this.expirer.add(entry)
}
return super.set(key, entry)
}
setManually(key, entry){
super.set(key, entry)
}
delete(key) {
let oldEntry = this.get(key)
if (oldEntry) {
this.expirer.delete(oldEntry)
}
return super.delete(key)
}
used(entry) {
this.expirer.used(entry)
}
}

class ExpirationStrategy {
class LRUStrategy {
constructor() {
this.cache = []
this.index = 0
}
add(entry, map, key, value) {
let previous = this.cache[this.index]
entry.index = this.index
if (previous && previous.value !== undefined)
previous.value = undefined
this.cache[this.index++] = entry
if (this.index == 0x1000)
this.index = 0
}
delete(entry) {
entry.position = -1
}
used() {

}
}

class LRFUStrategy {
constructor() {
this.cache = []
for (let i = 0; i < 4; i++) {
this.cache[i] = new Array(0x2000)
this.cache[i].position = i << 24
}
this.index = 0
}
add(entry) {
this.used(entry)
}
delete(entry) {
if (entry.position > -1) {
this.cache[entry.position >> 24][entry.position & 0xffff] = null
}
entry.position = -1
}
used(entry) {
let originalPosition = entry.position
let nextCache
let cachePosition
let cacheIndex
if (originalPosition > -1) {
let cacheIndex = originalPosition >> 24
if (cacheIndex >= 3)
return // can't get any higher than this, don't do anything
let cache = this.cache[cacheIndex]
// check to see if it is in the same generation
if ((originalPosition & 0xff0000) === (cache.position & 0xff0000))
return // still in same generation, don't move
cache[originalPosition & 0xffff] = null // remove it, we are going to move it
nextCache = this.cache[++cacheIndex]
} else
nextCache = this.cache[cacheIndex = 0]
do {
// put it in the next cache
let cachePosition = nextCache.position++
let previousEntry = nextCache[cachePosition & 0xffff]
nextCache[cachePosition & 0xffff] = entry
entry.position = cachePosition
if (cachePosition && 0xfff) {
// next generation
cachePosition += 0x10000
if (cachePosition & 0x400000)
generation &= 0xf00ffff // reset the generations
if (cachePosition & 0x2000)
cachePosition &= 0xfff0000 // reset the inner position
}
entry = previousEntry
nextCache = this.cache[--cacheIndex]
} while (entry && nextCache)
if (entry) {// this one was removed
entry.position = -1
if (entry.deref)
entry.value === undefined // clear out the self value so the weak reference can be collected (and then removed from the map)
else
this.onRemove(entry)
}
}
}
class NoLRUStrategy {
add(entry) {
this.onRemove(entry)
}
used(entry) {
this.onRemove(entry)
}
}
class MapSweepExpirationStrategy {
constructor() {
this.entryUseCount = 0
this.instances = []
}
register(map) {
this.instances.push(new WeakRef(this))
this.instances.push(new WeakRef(map))
}
add(entry) {
entry.usage = 0x10000
if (this.entryUseCount++ > 1000) {
if (this.entryUseCount++ > 2000) {
if (!this.cleanTimer) {
cleanTimer = setImmediate(clean)
} else if (uses > 2000) {
clearImmediate(cleanTimer)
this.cleanTimer = setImmediate(() => this.clean)
} else if (this.entryUseCount > 3000) {
this.clean()
}
}
}
use(entry) {
entry.usage |= 0x10000
used(entry) {
entry.usage |= 0x10000 // | entryUseCount
if (entry.value === undefined && entry.deref) {
entry.value = entry.deref() // make it a strong reference again
}
}
clean() {
this.entryUseCount = 0
clearImmediate(this.cleanTimer)
this.cleanTimer = null
let instances = this.instances
for (let i = 0, l = instances.length; i < l; i++) {
Expand All @@ -79,3 +230,5 @@ class ExpirationStrategy {
}
}
}
const defaultExpirer = new LRFUStrategy()
exports.WeakLRUCache = WeakLRUCache
2 changes: 2 additions & 0 deletions index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import wrcModule from './index.js'
export const WeakLRUCache = wrcModule.WeakLRUCache
28 changes: 21 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
{
"name": "weak-cache",
"name": "weak-lru-cache",
"author": "Kris Zyp",
"version": "0.1.0",
"description": "weak cache",
"version": "0.2.0",
"description": "An LRU cache of weak references",
"license": "MIT",
"types": "./index.d.ts",
"keywords": [
"cache",
"weak",
"references"
"references",
"LRU",
"LRFU"
],
"repository": {
"type": "git",
"url": "http://github.com/kriszyp/weak-cache"
"url": "http://github.com/kriszyp/weak-lru-cache"
},
"type": "module",
"main": "./index.js"
"type": "commonjs",
"main": "./index.js",
"exports": {
"import": "./index.mjs",
"require": "./index.js"
},
"scripts": {
"test": "./node_modules/.bin/mocha tests/test*.js -u tdd"
},
"devDependencies": {
"benchmark": "^2.1.4",
"chai": "^4",
"mocha": "^8"
}
}
Loading

0 comments on commit 9d948e6

Please sign in to comment.