-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
231 lines (209 loc) · 6.37 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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
class WeakRefMap extends Map {
// Delete the corresponding key when object is collected
// Need to manually deregister if deleting or setting new value
// to avoid "finalizing" the new value
#registry = new FinalizationRegistry(key => {
super.delete(key)
})
// When generating with new iterable, use the modified set
// so that we generate weakrefs
// TODO: Allow non object values? Just act as a normal map?
constructor (iterable) {
super()
if (iterable) {
for (const [key, value] of iterable) {
this.set(key, value)
}
}
}
// When setting wrap in a weakref instead
// Remember to first degister the old ref and
// register the new one for finalization
set (key, value) {
const oldValue = super.get(key)
if (oldValue instanceof WeakRef) {
this.#registry.unregister(oldValue)
}
// If its an object wrap it in a weakref
if (typeof value === 'object' && value !== null) {
const ref = new WeakRef(value)
this.#registry.register(value, key, ref)
return super.set(key, ref)
// If its not an object just set it directly
} else {
return super.set(key, value)
}
}
get (key) {
let value = super.get(key)
// If its a weakRef then unwrap it first
// No need to check for GCd stuff because its meant to be undefined anyway
if (value instanceof WeakRef) value = super.get(key)?.deref()
return value
}
has (key) {
let value = super.get(key)
// If its a weakRef then unwrap it first
if (value instanceof WeakRef) {
value = super.get(key)?.deref()
// If its been GC'd then return false
if (typeof value === 'undefined') return false
return true
}
// If it's a normal object use the super
// Do this to account for the edge case of setting a key with value undefined
return super.has(key)
}
delete (key) {
const value = super.get(key)
// If there is a ref then unregister first to avoid
// finalization deleting any new values later
if (value instanceof WeakRef) {
this.#registry.unregister(value)
super.delete(key)
// Only return a successful delete if ref was still live
if (typeof value.deref() === 'undefined') return false
else return true
// Getting here means it is a valid primitive
// return the super.delete call to account for
// edge case of valid undefined value
} else {
return super.delete(key)
}
}
clear () {
this.#registry = new FinalizationRegistry(key => {
super.delete(key)
})
return super.clear()
}
forEach (callback, context) {
for (const [key, value] of this) { callback.call(context, value, key, this) }
}
// Default iterator
// Iterates but only yields live references
* [Symbol.iterator] () {
for (let [key, value] of super[Symbol.iterator]()) {
if (value instanceof WeakRef) {
value = value.deref()
if (typeof value !== 'undefined') yield [key, value]
} else {
yield [key, value]
}
}
}
// Pass through to the default iterator
* entries () {
yield * this
}
// Use default iterator but only return the value
* values () {
for (const keyValuePair of this) {
yield keyValuePair[1]
}
}
}
// Custom Set with weakly held values (WeakSet does something else)
class WeakRefSet extends Set {
// Used to check existing membership of the underlying target
// Maps the target to its ref
#membership = new WeakMap()
// Delete the corresponding ref when object is collected
// No need to remove from membership because it would already be gone from there
// By the time we hit finalization
#registry = new FinalizationRegistry(ref => {
super.delete(ref)
})
// When generating with an iterable, use the modified add
// so that we generate weakrefs
constructor (iterable) {
super()
if (iterable) {
for (const value of iterable) {
this.add(value)
}
}
}
// When add wrap the target in a weakref instead
add (value) {
// If it is already contained then skip
if (this.#membership.has(value)) return this
// Otherwise mark the membership
// mark for clean up
// and store the reference
if (typeof value === 'object' && value !== null) {
const ref = new WeakRef(value)
this.#membership.set(value, ref)
this.#registry.register(value, ref, ref)
return super.add(ref)
// For primitives then just process it normally
} else {
return super.add(value)
}
}
has (value) {
// If its an object then check the refs
if (typeof value === 'object' && value !== null) {
const ref = this.#membership.get(value)
if (typeof ref === 'undefined') return false
if (typeof ref.deref() === 'undefined') return false
return true
// If it's a primitive then do a normal has check
} else {
return super.has(value)
}
}
delete (value) {
if (typeof value === 'object' && value !== null) {
const ref = this.#membership.get(value)
// Early return if nothing defined
if (typeof ref === 'undefined') return false
// Otherwise an entry was found
this.#membership.delete(value)
this.#registry.unregister(ref)
super.delete(ref)
// Only return a successful delete if ref was still live
if (typeof ref.deref() === 'undefined') return false
return true
} else {
return super.delete(value)
}
}
clear () {
this.#membership = new WeakMap()
this.#registry = new FinalizationRegistry(ref => {
super.delete(ref)
})
return super.clear()
}
// Follows the map API convention but passes value twice instead of
// value and key
forEach (callback, context) {
for (const value of this) {
callback.call(context, value, value, this)
}
}
// Default iterator
// Iterates but only yields live references
* [Symbol.iterator] () {
for (let value of super[Symbol.iterator]()) {
if (value instanceof WeakRef) {
value = value.deref()
if (typeof value !== 'undefined') yield value
} else { yield value }
}
}
// The Set API follows a similar structure to Map despite lack of keys
// Returns an array of [value, value] pairs
* entries () {
for (const value of this) {
yield [value, value]
}
}
* keys () { yield * this }
* values () { yield * this }
}
export {
WeakRefMap,
WeakRefSet
}