Skip to content

Commit c2294aa

Browse files
committed
feat: add IterableWeakSet
1 parent a248b5a commit c2294aa

File tree

4 files changed

+254
-1
lines changed

4 files changed

+254
-1
lines changed

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,8 @@
44
"deno.unstable": true,
55
"editor.formatOnSave": true,
66
"editor.defaultFormatter": "denoland.vscode-deno",
7-
"cSpell.words": []
7+
"cSpell.words": [
8+
"iwset",
9+
"wset"
10+
]
811
}

iterable_weak_set.test.ts

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { assert, assertEquals, assertFalse } from "testing/asserts.ts";
2+
import { IterableWeakSet } from "./iterable_weak_set.ts";
3+
4+
Deno.test("IterableWeakSet, comparison Set, WeakSet", () => {
5+
const set = new Set();
6+
const wset = new WeakSet();
7+
const iwset = new IterableWeakSet();
8+
9+
assertEquals(set.size, 0);
10+
// assertEquals(wset.size, 0);
11+
assertEquals(iwset.size, 0);
12+
13+
const obj1 = {};
14+
const obj2 = {};
15+
16+
assertEquals(set.add(obj1), set);
17+
assertEquals(wset.add(obj1), wset);
18+
assertEquals(iwset.add(obj1), iwset);
19+
20+
assertEquals(set.size, 1);
21+
// assertEquals(wset.size, 0);
22+
assertEquals(iwset.size, 1);
23+
24+
// add the same object again
25+
assertEquals(set.add(obj1), set);
26+
assertEquals(wset.add(obj1), wset);
27+
assertEquals(iwset.add(obj1), iwset);
28+
29+
assertEquals(set.size, 1);
30+
// assertEquals(wset.size, 0);
31+
assertEquals(iwset.size, 1);
32+
33+
assertEquals(set.add(obj2), set);
34+
assertEquals(wset.add(obj2), wset);
35+
assertEquals(iwset.add(obj2), iwset);
36+
37+
assertEquals(set.size, 2);
38+
// assertEquals(wset.size, 0);
39+
assertEquals(iwset.size, 2);
40+
41+
assert(set.has(obj1));
42+
assert(wset.has(obj1));
43+
assert(iwset.has(obj1));
44+
45+
// delete the object
46+
assert(set.delete(obj1));
47+
assert(wset.delete(obj1));
48+
assert(iwset.delete(obj1));
49+
50+
assertEquals(set.size, 1);
51+
// assertEquals(wset.size, 0);
52+
assertEquals(iwset.size, 1);
53+
54+
assertFalse(set.delete(obj1));
55+
assertFalse(wset.delete(obj1));
56+
assertFalse(iwset.delete(obj1));
57+
58+
assertEquals(set.size, 1);
59+
// assertEquals(wset.size, 0);
60+
assertEquals(iwset.size, 1);
61+
62+
assertFalse(set.has(obj1));
63+
assertFalse(wset.has(obj1));
64+
assertFalse(iwset.has(obj1));
65+
66+
assertFalse(set.clear());
67+
// assertFalse(wset.clear());
68+
assertFalse(iwset.clear());
69+
70+
assertEquals(set.size, 0);
71+
// assertEquals(wset.size, 0);
72+
assertEquals(iwset.size, 0);
73+
74+
assertEquals(set.toString(), "[object Set]");
75+
assertEquals(wset.toString(), "[object WeakSet]");
76+
assertEquals(iwset.toString(), "[object IterableWeakSet]");
77+
});
78+
79+
Deno.test("IterableWeakSet, iterable", () => {
80+
const objs = [{}, {}, {}];
81+
82+
const sets = [
83+
new Set(objs),
84+
new IterableWeakSet(objs),
85+
];
86+
87+
for (const set of sets) {
88+
assertEquals(set.size, 3);
89+
90+
{
91+
let i = 0;
92+
set.forEach(function (this: unknown, v1, v2, s) {
93+
assert(this === undefined);
94+
assert(v1 === objs[i]);
95+
assert(v2 === objs[i]);
96+
assert(s === set);
97+
i++;
98+
});
99+
assertFalse(i === 0);
100+
}
101+
{
102+
let i = 0;
103+
set.forEach(function (this: unknown, v1, v2, s) {
104+
assert(this === ":D");
105+
assert(v1 === objs[i]);
106+
assert(v2 === objs[i]);
107+
assert(s === set);
108+
i++;
109+
}, ":D");
110+
assertFalse(i === 0);
111+
}
112+
{
113+
let i = 0;
114+
for (const obj of set) {
115+
assert(obj === objs[i++]);
116+
}
117+
assertFalse(i === 0);
118+
}
119+
{
120+
let i = 0;
121+
for (const obj of set.keys()) {
122+
assert(obj === objs[i++]);
123+
}
124+
assertFalse(i === 0);
125+
}
126+
{
127+
let i = 0;
128+
for (const obj of set.values()) {
129+
assert(obj === objs[i++]);
130+
}
131+
assertFalse(i === 0);
132+
}
133+
{
134+
let i = 0;
135+
for (const tuple of set.entries()) {
136+
assert(tuple[0] === objs[i]);
137+
assert(tuple[1] === objs[i++]);
138+
}
139+
assertFalse(i === 0);
140+
}
141+
}
142+
});
143+
144+
Deno.test("IterableWeakSet, garbage collect", async () => {
145+
let removedCount = 0;
146+
let insertedCount = 0;
147+
const register = new FinalizationRegistry(() => {
148+
removedCount++;
149+
});
150+
151+
const set = new IterableWeakSet();
152+
for (let i = 0; removedCount < 100; i++) {
153+
await new Promise((resolve) => setTimeout(resolve, 16));
154+
const data = {};
155+
set.add(data);
156+
register.register(data, i);
157+
insertedCount++;
158+
}
159+
160+
assertEquals(insertedCount - removedCount, set.size);
161+
assertEquals([...set].length, set.size);
162+
});

iterable_weak_set.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// deno-lint-ignore-file ban-types
2+
3+
export class IterableWeakSet<T extends object> implements WeakSet<T>, Set<T> {
4+
#weakMap = new WeakMap<T, WeakRef<T>>();
5+
#set = new Set<WeakRef<T>>();
6+
#registry = new FinalizationRegistry<WeakRef<T>>(
7+
this.#set.delete.bind(this.#set),
8+
);
9+
10+
constructor(values?: readonly T[] | null);
11+
constructor(iterable: Iterable<T>);
12+
constructor(iterable: Iterable<T> | readonly T[] | null = null) {
13+
for (const value of iterable ?? []) {
14+
this.add(value);
15+
}
16+
}
17+
18+
get size(): number {
19+
return this.#set.size;
20+
}
21+
22+
add(value: T): this {
23+
if (this.has(value)) {
24+
return this;
25+
}
26+
const ref = new WeakRef(value);
27+
this.#weakMap.set(value, ref);
28+
this.#set.add(ref);
29+
this.#registry.register(value, ref, ref);
30+
return this;
31+
}
32+
33+
clear(): void {
34+
for (const value of this.#set) {
35+
this.delete(value.deref()!);
36+
}
37+
}
38+
39+
delete(value: T): boolean {
40+
const ref = this.#weakMap.get(value);
41+
if (ref) {
42+
this.#set.delete(ref);
43+
this.#weakMap.delete(value);
44+
this.#registry.unregister(ref);
45+
return true;
46+
}
47+
return false;
48+
}
49+
50+
has(value: T): boolean {
51+
return this.#weakMap.has(value);
52+
}
53+
54+
forEach(
55+
callbackfn: (value: T, value2: T, set: Set<T>) => void,
56+
thisArg?: unknown,
57+
): void {
58+
for (const tuple of this.entries()) {
59+
callbackfn.call(thisArg, tuple[0], tuple[1], this);
60+
}
61+
}
62+
63+
get [Symbol.toStringTag]() {
64+
return "IterableWeakSet";
65+
}
66+
67+
*[Symbol.iterator](): IterableIterator<T> {
68+
for (const ref of this.#set) {
69+
yield ref.deref()!;
70+
}
71+
}
72+
73+
*entries(): IterableIterator<[T, T]> {
74+
for (const ref of this.#set) {
75+
const value = ref.deref()!;
76+
yield [value, value];
77+
}
78+
}
79+
80+
keys(): IterableIterator<T> {
81+
return this[Symbol.iterator]();
82+
}
83+
84+
values(): IterableIterator<T> {
85+
return this[Symbol.iterator]();
86+
}
87+
}

mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { IterableWeakSet } from "./iterable_weak_set.ts";

0 commit comments

Comments
 (0)