• Jump To … +
    magazine.js readme.t.js
  • magazine.js

  • ¶
    const assert = require('assert')
    
    const Keyify = require('keyify')
    
    const NULL = Symbol('null')
    
    class Magazine {
        static Map = class {
            constructor (cache) {
                this.cache = cache
            }
    
            get (key, ...vargs) {
                let got
                if (vargs.length == 0) {
                    const cartridge = this.cache.hold(key)
                    if (cartridge == null) {
                        return got
                    }
                    got = cartridge.value
                    cartridge.release()
                } else {
                    const cartridge = this.cache.hold.apply(this.cache, [ key ].concat(vargs))
                    got = cartridge.value
                    cartridge.release()
                }
                return got
            }
    
            put (key, value) {
                let got
                const cartridge = this.cache.hold(key, NULL)
                if (cartridge.value !== NULL) {
                    got = cartridge.value
                }
                cartridge.value = value
                cartridge.release()
                return got
            }
    
            remove (key) {
                let got
                const cartridge = this.cache.hold(key, NULL)
                if (cartridge.value !== NULL) {
                    got = cartridge.value
                }
                cartridge.remove()
                return got
            }
        }
    
        constructor (parent = null, key = []) {
            const path = [ this ]
            let iterator = parent
            while (iterator != null) {
                path.push(iterator)
                iterator = iterator.parent
            }
            this._index = path.length - 1
            this._path = path.reverse()
            this.parent = parent
            this._key = key
            this._instance = 0
            this.count = 0
            this._head = new Cartridge(this, null, [], null)
            this._heft = 0
            this._cache = parent != null ? parent._cache : {}
        }
    
        get heft () {
            return this._heft
        }
    
        magazine (key) {
            return new Magazine(this, this._key.concat(key))
        }
    
        cache (key) {
            this.magazine(key)
        }
    
        hold (key, ...vargs) {
            const qualified = this._key.concat(key)
            const keyified = Keyify.stringify(qualified)
            let cartridge = this._cache[keyified]
            if (cartridge == null) {
                if (vargs.length == 0) {
                    return null
                }
                this._cache[keyified] = cartridge = new Cartridge(this, keyified, qualified, vargs[0])
                cartridge._link()
                if (vargs.length == 2) {
                    cartridge.heft = vargs[1]
                }
            } else {
                cartridge._unlink()
                cartridge._link()
            }
            cartridge._references++
            return cartridge
        }
    
        purge (heft) {
            this._evict(() => this._heft > heft)
        }
    
        shrink (count) {
            this._evict(() => this.count > count)
        }
    
        evict (evict) {
            this._evict(entry => evict({ cache: this, value: entry.value, heft: entry.heft }))
        }
    
        _evict (evict) {
            let iterator = this._head._links[this._index].previous
            while (
                iterator.cartridge != null &&
                iterator.cartridge._references == 0 &&
                evict(iterator.cartridge)
            ) {
                iterator.cartridge._unlink()
                delete this._cache[iterator.cartridge._keyified]
                iterator = iterator.previous
            }
        }
    
        [Symbol.iterator] () {
            let iterator = this._head._links[this._index].previous
            return {
                next () {
                    if (iterator.cartridge == null || iterator.cartridge._references != 0) {
                        return { done: true, value: null }
                    }
                    const cartridge = iterator.cartridge
                    iterator = iterator.previous
                    cartridge._references++
                    return { done: false, value: cartridge }
                }
            }
        }
    
        least () {
            const entry = this._head._links[this._index].previous
            if (previous.cartridge == null || previous.cartridge._references != 0) {
                return null
            }
            previous.cartridge._references++
            return previous.cartridge
        }
    
        every (f) {
            let iterator = this._head._links[this._index].previous
            while (iterator.cartridge != null) {
                if (!f(iterator.cartridge.value)) {
                    return false
                }
                iterator = iterator.next
            }
            return true
        }
    
        reduce (f, value) {
            let iterator = this._head._links[this._index].previous
            while (iterator.cartridge != null) {
                value = f(value, iterator.cartridge.value)
            }
            return value
        }
    }
    
    class Cartridge {
        constructor (magazine, keyified, qualified, value) {
            this.magazine = magazine
            this._keyified = keyified
            this.qualified = qualified
            this.value = value
            this._references = 0
            this._links = []
            this._heft = 0
            for (let i = 0; i < magazine._path.length; i++) {
                const node = { previous: null, next: null, cartridge: null }
                this._links[i] = node.previous = node.next = node
            }
        }
    
        set heft (value) {
            for (const magazine of this.magazine._path) {
                magazine._heft -= this._heft
            }
            this._heft = value
            for (const magazine of this.magazine._path) {
                magazine._heft += this._heft
            }
        }
    
        get heft () {
            return this._heft
        }
    
        _link () {
            for (let i = 0, I = this._links.length; i < I; i++) {
                const magazine = this.magazine._path[i]
                const node = this._links[i]
                node.cartridge = this
                node.next = magazine._head._links[i].next
                node.previous = magazine._head._links[i]
                node.next.previous = node
                node.previous.next = node
                magazine.count++
                magazine._heft += this._heft
            }
        }
    
        _unlink () {
            for (let i = 0, I = this._links.length; i < I; i++) {
                this._links[i].previous.next = this._links[i].next
                this._links[i].next.previous = this._links[i].previous
                const magazine = this.magazine._path[i]
                magazine.count--
                magazine._heft -= this._heft
            }
        }
    
        remove () {
            assert(this._references == 1, 'multiple references at delete')
            this._unlink()
            delete this.magazine._cache[this._keyified]
        }
    
        release () {
            this._references--
        }
    }
    
    module.exports = Magazine