-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
119 lines (94 loc) · 2.41 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
class CacheEntry {
constructor (key, index, map) {
this.key = key
this.index = index
this.map = map
}
}
class CacheValue {
constructor (entry, value) {
this.entry = entry
this.value = value
}
}
class Rache {
constructor ({ maxSize = 65536, parent = null } = {}) {
this.maxSize = parent?.maxSize || maxSize
this._array = parent?._array || []
this._map = new Map()
}
static from (cache) {
return cache ? new this({ parent: cache }) : new this()
}
get globalSize () {
return this._array.length
}
get size () {
return this._map.size
}
sub () {
return new Rache({ parent: this })
}
set (key, value) { // ~constant time
const existing = this._map.get(key)
if (existing !== undefined) {
existing.value = value
return
}
if (this._array.length >= this.maxSize) this._gc()
const entry = new CacheEntry(key, this._array.length, this._map)
this._array.push(entry)
const cacheValue = new CacheValue(entry, value)
this._map.set(key, cacheValue)
}
delete (key) {
const existing = this._map.get(key)
if (existing === undefined) return false
this._delete(existing.entry.index)
return true
}
get (key) {
const existing = this._map.get(key)
return existing === undefined ? undefined : existing.value
}
* [Symbol.iterator] () {
for (const [key, { value }] of this._map) {
yield [key, value]
}
}
keys () {
return this._map.keys()
}
* values () {
for (const { value } of this._map.values()) {
yield value
}
}
clear () {
// The entries in map linger on in _array,
// so on top of clearing the map, we also kill the ref,
// so that any gc running later on the old map won't interfere
// (in case a new entry was added with the same key as a cleared entry)
this._map.clear()
this._map = new Map()
}
destroy () {
this._map = null
this._array = null
}
_gc () {
this._delete(Math.floor(Math.random() * this._array.length))
}
_delete (index) { // ~constant time
if (index >= this._array.length) throw new Error('Cannot delete unused index (logic bug?)')
const head = this._array.pop()
let removed = head
if (index < this._array.length) {
removed = this._array[index]
head.index = index
this._array[index] = head
}
removed.map.delete(removed.key)
}
}
module.exports = Rache