From 4e9ac330131cb29b220516a3682b8d949ed0b104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kwa=C5=9Bniak?= Date: Sun, 29 Sep 2024 15:36:38 +0200 Subject: [PATCH 01/11] fix: Remove optimizations from Map iterators --- packages/map/src/index.ts | 64 +++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts index d60889400..239ef4082 100644 --- a/packages/map/src/index.ts +++ b/packages/map/src/index.ts @@ -24,48 +24,59 @@ export class ReactiveMap extends Map { #keyTriggers = new TriggerCache(); #valueTriggers = new TriggerCache(); - constructor(initial?: Iterable | null) { - super(); - if (initial) for (const v of initial) super.set(v[0], v[1]); + [Symbol.iterator](): MapIterator<[K, V]> { + return this.entries(); } - // reads - has(key: K): boolean { - this.#keyTriggers.track(key); - return super.has(key); - } - get(key: K): V | undefined { - this.#valueTriggers.track(key); - return super.get(key); + constructor(entries?: Iterable | null) { + super(); + if (entries) for (const entry of entries) super.set(...entry); } + get size(): number { this.#keyTriggers.track($KEYS); return super.size; } *keys(): MapIterator { + this.#keyTriggers.track($KEYS); + for (const key of super.keys()) { - this.#keyTriggers.track(key); yield key; } - this.#keyTriggers.track($KEYS); } + *values(): MapIterator { - for (const [key, v] of super.entries()) { - this.#valueTriggers.track(key); - yield v; - } this.#keyTriggers.track($KEYS); + + for (const value of super.values()) { + yield value; + } } + *entries(): MapIterator<[K, V]> { + this.#keyTriggers.track($KEYS); + for (const entry of super.entries()) { - this.#valueTriggers.track(entry[0]); yield entry; } + } + + forEach(callbackfn: (value: V, key: K, map: Map) => void, thisArg?: any): void { this.#keyTriggers.track($KEYS); + super.forEach(callbackfn, thisArg); + } + + has(key: K): boolean { + this.#keyTriggers.track(key); + return super.has(key); + } + + get(key: K): V | undefined { + this.#valueTriggers.track(key); + return super.get(key); } - // writes set(key: K, value: V): this { batch(() => { if (super.has(key)) { @@ -79,6 +90,7 @@ export class ReactiveMap extends Map { }); return this; } + delete(key: K): boolean { const r = super.delete(key); if (r) { @@ -90,6 +102,7 @@ export class ReactiveMap extends Map { } return r; } + clear(): void { if (super.size) { batch(() => { @@ -102,19 +115,6 @@ export class ReactiveMap extends Map { }); } } - - // callback - forEach(callbackfn: (value: V, key: K, map: this) => void) { - this.#keyTriggers.track($KEYS); - for (const [key, v] of super.entries()) { - this.#valueTriggers.track(key); - callbackfn(v, key, this); - } - } - - [Symbol.iterator](): MapIterator<[K, V]> { - return this.entries(); - } } /** From fb8dcbcbe21865dde87f40da4d2820c132ca0dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kwa=C5=9Bniak?= Date: Fri, 4 Oct 2024 11:14:18 +0200 Subject: [PATCH 02/11] feat: Optimize Map initialization --- packages/map/src/index.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts index 239ef4082..0b3a30f24 100644 --- a/packages/map/src/index.ts +++ b/packages/map/src/index.ts @@ -30,7 +30,12 @@ export class ReactiveMap extends Map { constructor(entries?: Iterable | null) { super(); - if (entries) for (const entry of entries) super.set(...entry); + + if (entries) { + batch(() => { + for (const entry of entries) super.set(...entry); + }); + } } get size(): number { @@ -137,9 +142,14 @@ export class ReactiveWeakMap extends WeakMap { #keyTriggers = new TriggerCache(WeakMap); #valueTriggers = new TriggerCache(WeakMap); - constructor(initial?: Iterable | null) { + constructor(entries?: Iterable | null) { super(); - if (initial) for (const v of initial) super.set(v[0], v[1]); + + if (entries) { + batch(() => { + for (const entry of entries) super.set(...entry); + }); + } } has(key: K): boolean { From f39dd15fc00d36db81b536cf9259f40ba742be01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kwa=C5=9Bniak?= Date: Fri, 4 Oct 2024 11:14:26 +0200 Subject: [PATCH 03/11] feat: Optimize Set initialization --- packages/set/src/index.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/set/src/index.ts b/packages/set/src/index.ts index 4ae790d73..de10430a9 100644 --- a/packages/set/src/index.ts +++ b/packages/set/src/index.ts @@ -20,7 +20,12 @@ export class ReactiveSet extends Set { constructor(values?: Iterable | null) { super(); - if (values) for (const v of values) super.add(v); + + if (values) { + batch(() => { + for (const value of values) super.add(value); + }); + } } [Symbol.iterator](): SetIterator { @@ -113,7 +118,12 @@ export class ReactiveWeakSet extends WeakSet { constructor(values?: Iterable | null) { super(); - if (values) for (const v of values) super.add(v); + + if (values) { + batch(() => { + for (const value of values) super.add(value); + }); + } } has(value: T): boolean { From 51a28d172ffe3cbef377ef08d1b8b3aa9b54d5ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kwa=C5=9Bniak?= Date: Fri, 4 Oct 2024 11:15:28 +0200 Subject: [PATCH 04/11] feat: Optimize Map set --- packages/map/src/index.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts index 0b3a30f24..65821e49e 100644 --- a/packages/map/src/index.ts +++ b/packages/map/src/index.ts @@ -83,17 +83,19 @@ export class ReactiveMap extends Map { } set(key: K, value: V): this { - batch(() => { - if (super.has(key)) { - if (super.get(key)! === value) return; - } else { - this.#keyTriggers.dirty(key); + const hadNoKey = !super.has(key); + const hasChanged = super.get(key) !== value; + const result = super.set(key, value); + + if (hasChanged || hadNoKey) { + batch(() => { this.#keyTriggers.dirty($KEYS); - } - this.#valueTriggers.dirty(key); - super.set(key, value); - }); - return this; + if (hadNoKey) this.#keyTriggers.dirty(key); + if (hasChanged) this.#valueTriggers.dirty(key); + }); + } + + return result; } delete(key: K): boolean { From 0ef612f1c9af5b98f3f06334a60bf646dc2f1781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kwa=C5=9Bniak?= Date: Fri, 4 Oct 2024 11:15:57 +0200 Subject: [PATCH 05/11] feat: Optimize Map delete --- packages/map/src/index.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts index 65821e49e..ab8c61b49 100644 --- a/packages/map/src/index.ts +++ b/packages/map/src/index.ts @@ -99,15 +99,18 @@ export class ReactiveMap extends Map { } delete(key: K): boolean { - const r = super.delete(key); - if (r) { + const isDefined = super.get(key) !== undefined; + const result = super.delete(key); + + if (result) { batch(() => { - this.#keyTriggers.dirty(key); this.#keyTriggers.dirty($KEYS); - this.#valueTriggers.dirty(key); + this.#keyTriggers.dirty(key); + if (isDefined) this.#valueTriggers.dirty(key); }); } - return r; + + return result; } clear(): void { From e18c60e17304c98904228d83694dd7ab83928e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kwa=C5=9Bniak?= Date: Fri, 4 Oct 2024 11:16:24 +0200 Subject: [PATCH 06/11] feat: Fix and optimize Map clear --- packages/map/src/index.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts index ab8c61b49..deeefc460 100644 --- a/packages/map/src/index.ts +++ b/packages/map/src/index.ts @@ -115,13 +115,11 @@ export class ReactiveMap extends Map { clear(): void { if (super.size) { + super.clear(); + batch(() => { - for (const v of super.keys()) { - this.#keyTriggers.dirty(v); - this.#valueTriggers.dirty(v); - } - super.clear(); - this.#keyTriggers.dirty($KEYS); + this.#keyTriggers.dirtyAll(); + this.#valueTriggers.dirtyAll(); }); } } From 44ca69741be2bbcb051d3656456820d3c0728174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kwa=C5=9Bniak?= Date: Fri, 4 Oct 2024 11:16:43 +0200 Subject: [PATCH 07/11] feat: Optimize WeakMap set --- packages/map/src/index.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts index deeefc460..3fda2d73c 100644 --- a/packages/map/src/index.ts +++ b/packages/map/src/index.ts @@ -164,14 +164,18 @@ export class ReactiveWeakMap extends WeakMap { return super.get(key); } set(key: K, value: V): this { - batch(() => { - if (super.has(key)) { - if (super.get(key)! === value) return; - } else this.#keyTriggers.dirty(key); - this.#valueTriggers.dirty(key); - super.set(key, value); - }); - return this; + const hadNoKey = !super.has(key); + const hasChanged = super.get(key) !== value; + const result = super.set(key, value); + + if (hasChanged || hadNoKey) { + batch(() => { + if (hadNoKey) this.#keyTriggers.dirty(key); + if (hasChanged) this.#valueTriggers.dirty(key); + }); + } + + return result; } delete(key: K): boolean { const r = super.delete(key); From e47d6d0c1069da9d4729fe33b7b1fb57051f6fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kwa=C5=9Bniak?= Date: Fri, 4 Oct 2024 11:16:48 +0200 Subject: [PATCH 08/11] feat: Optimize WeakMap delete --- packages/map/src/index.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts index 3fda2d73c..56fa7b3b3 100644 --- a/packages/map/src/index.ts +++ b/packages/map/src/index.ts @@ -178,14 +178,17 @@ export class ReactiveWeakMap extends WeakMap { return result; } delete(key: K): boolean { - const r = super.delete(key); - if (r) { + const isDefined = super.get(key) !== undefined; + const result = super.delete(key); + + if (result) { batch(() => { this.#keyTriggers.dirty(key); - this.#valueTriggers.dirty(key); + if (isDefined) this.#valueTriggers.dirty(key); }); } - return r; + + return result; } } From 0b1db2d615364eba0ca2cea977c0810d1c2c3bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kwa=C5=9Bniak?= Date: Fri, 4 Oct 2024 12:11:32 +0200 Subject: [PATCH 09/11] chore: Remove init optimization --- packages/map/src/index.ts | 14 ++------------ packages/set/src/index.ts | 14 ++------------ 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts index 56fa7b3b3..610194fbf 100644 --- a/packages/map/src/index.ts +++ b/packages/map/src/index.ts @@ -30,12 +30,7 @@ export class ReactiveMap extends Map { constructor(entries?: Iterable | null) { super(); - - if (entries) { - batch(() => { - for (const entry of entries) super.set(...entry); - }); - } + if (entries) for (const entry of entries) super.set(...entry); } get size(): number { @@ -147,12 +142,7 @@ export class ReactiveWeakMap extends WeakMap { constructor(entries?: Iterable | null) { super(); - - if (entries) { - batch(() => { - for (const entry of entries) super.set(...entry); - }); - } + if (entries) for (const entry of entries) super.set(...entry); } has(key: K): boolean { diff --git a/packages/set/src/index.ts b/packages/set/src/index.ts index de10430a9..26d81653c 100644 --- a/packages/set/src/index.ts +++ b/packages/set/src/index.ts @@ -20,12 +20,7 @@ export class ReactiveSet extends Set { constructor(values?: Iterable | null) { super(); - - if (values) { - batch(() => { - for (const value of values) super.add(value); - }); - } + if (values) for (const value of values) super.add(value); } [Symbol.iterator](): SetIterator { @@ -118,12 +113,7 @@ export class ReactiveWeakSet extends WeakSet { constructor(values?: Iterable | null) { super(); - - if (values) { - batch(() => { - for (const value of values) super.add(value); - }); - } + if (values) for (const value of values) super.add(value); } has(value: T): boolean { From da3985b98866ece6685828a7755ba73c2357b484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kwa=C5=9Bniak?= Date: Fri, 4 Oct 2024 12:12:36 +0200 Subject: [PATCH 10/11] fix: Fix values and keys tracking in Map --- packages/map/src/index.ts | 37 +++++++++++++++--------- packages/map/test/index.test.ts | 50 +++++++++++++-------------------- 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts index 610194fbf..3d4df2f5b 100644 --- a/packages/map/src/index.ts +++ b/packages/map/src/index.ts @@ -1,7 +1,7 @@ import { Accessor, batch } from "solid-js"; import { TriggerCache } from "@solid-primitives/trigger"; -const $KEYS = Symbol("track-keys"); +const $OBJECT = Symbol("track-object"); /** * A reactive version of `Map` data structure. All the reads (like `get` or `has`) are signals, and all the writes (`delete` or `set`) will cause updates to appropriate signals. @@ -21,8 +21,8 @@ const $KEYS = Symbol("track-keys"); * userPoints.set(user1, { foo: "bar" }); */ export class ReactiveMap extends Map { - #keyTriggers = new TriggerCache(); - #valueTriggers = new TriggerCache(); + #keyTriggers = new TriggerCache(); + #valueTriggers = new TriggerCache(); [Symbol.iterator](): MapIterator<[K, V]> { return this.entries(); @@ -34,12 +34,12 @@ export class ReactiveMap extends Map { } get size(): number { - this.#keyTriggers.track($KEYS); + this.#keyTriggers.track($OBJECT); return super.size; } *keys(): MapIterator { - this.#keyTriggers.track($KEYS); + this.#keyTriggers.track($OBJECT); for (const key of super.keys()) { yield key; @@ -47,7 +47,7 @@ export class ReactiveMap extends Map { } *values(): MapIterator { - this.#keyTriggers.track($KEYS); + this.#valueTriggers.track($OBJECT); for (const value of super.values()) { yield value; @@ -55,7 +55,8 @@ export class ReactiveMap extends Map { } *entries(): MapIterator<[K, V]> { - this.#keyTriggers.track($KEYS); + this.#keyTriggers.track($OBJECT); + this.#valueTriggers.track($OBJECT); for (const entry of super.entries()) { yield entry; @@ -63,7 +64,8 @@ export class ReactiveMap extends Map { } forEach(callbackfn: (value: V, key: K, map: Map) => void, thisArg?: any): void { - this.#keyTriggers.track($KEYS); + this.#keyTriggers.track($OBJECT); + this.#valueTriggers.track($OBJECT); super.forEach(callbackfn, thisArg); } @@ -84,9 +86,14 @@ export class ReactiveMap extends Map { if (hasChanged || hadNoKey) { batch(() => { - this.#keyTriggers.dirty($KEYS); - if (hadNoKey) this.#keyTriggers.dirty(key); - if (hasChanged) this.#valueTriggers.dirty(key); + if (hadNoKey) { + this.#keyTriggers.dirty($OBJECT); + this.#keyTriggers.dirty(key); + } + if (hasChanged) { + this.#valueTriggers.dirty($OBJECT); + this.#valueTriggers.dirty(key); + } }); } @@ -99,9 +106,13 @@ export class ReactiveMap extends Map { if (result) { batch(() => { - this.#keyTriggers.dirty($KEYS); + this.#keyTriggers.dirty($OBJECT); this.#keyTriggers.dirty(key); - if (isDefined) this.#valueTriggers.dirty(key); + + if (isDefined) { + this.#valueTriggers.dirty($OBJECT); + this.#valueTriggers.dirty(key); + } }); } diff --git a/packages/map/test/index.test.ts b/packages/map/test/index.test.ts index d70c7d726..ccc5f2898 100644 --- a/packages/map/test/index.test.ts +++ b/packages/map/test/index.test.ts @@ -210,7 +210,6 @@ describe("ReactiveMap", () => { [1, "a"], [2, "b"], [3, "c"], - [4, "d"], ]); const captured: unknown[][] = []; @@ -220,7 +219,6 @@ describe("ReactiveMap", () => { const run: unknown[] = []; for (const key of map.keys()) { run.push(key); - if (key === 3) break; // don't iterate over all keys } captured.push(run); }); @@ -233,12 +231,12 @@ describe("ReactiveMap", () => { map.set(1, "e"); expect(captured, "value change").toHaveLength(1); - map.set(5, "f"); - expect(captured, "not seen key change").toHaveLength(1); + map.set(4, "f"); + expect(captured, "new key added").toHaveLength(2); map.delete(1); - expect(captured, "seen key change").toHaveLength(2); - expect(captured[1]).toEqual([2, 3]); + expect(captured, "seen key change").toHaveLength(3); + expect(captured[2]).toEqual([2, 3, 4]); dispose(); }); @@ -248,7 +246,6 @@ describe("ReactiveMap", () => { [1, "a"], [2, "b"], [3, "c"], - [4, "d"], ]); const captured: unknown[][] = []; @@ -256,11 +253,8 @@ describe("ReactiveMap", () => { const dispose = createRoot(dispose => { createEffect(() => { const run: unknown[] = []; - let i = 0; for (const v of map.values()) { run.push(v); - if (i === 2) break; // don't iterate over all keys - i += 1; } captured.push(run); }); @@ -275,14 +269,12 @@ describe("ReactiveMap", () => { expect(captured[1]).toEqual(["e", "b", "c"]); map.set(4, "f"); - expect(captured, "not seen value change").toHaveLength(2); + expect(captured, "new key added").toHaveLength(3); + expect(captured[2]).toEqual(["e", "b", "c", "f"]); map.delete(4); - expect(captured, "not seen key change").toHaveLength(2); - - map.delete(1); - expect(captured, "seen key change").toHaveLength(3); - expect(captured[2]).toEqual(["b", "c"]); + expect(captured, "key removed").toHaveLength(4); + expect(captured[3]).toEqual(["e", "b", "c"]); dispose(); }); @@ -292,7 +284,6 @@ describe("ReactiveMap", () => { [1, "a"], [2, "b"], [3, "c"], - [4, "d"], ]); const captured: unknown[][] = []; @@ -300,11 +291,8 @@ describe("ReactiveMap", () => { const dispose = createRoot(dispose => { createEffect(() => { const run: unknown[] = []; - let i = 0; for (const e of map.entries()) { run.push(e); - if (i === 2) break; // don't iterate over all keys - i += 1; } captured.push(run); }); @@ -327,14 +315,18 @@ describe("ReactiveMap", () => { ]); map.set(4, "f"); - expect(captured, "not seen value change").toHaveLength(2); + expect(captured, "new key added").toHaveLength(3); + expect(captured[2]).toEqual([ + [1, "e"], + [2, "b"], + [3, "c"], + [4, "f"], + ]); map.delete(4); - expect(captured, "not seen key change").toHaveLength(2); - - map.delete(1); - expect(captured, "seen key change").toHaveLength(3); - expect(captured[2]).toEqual([ + expect(captured, "key removed").toHaveLength(4); + expect(captured[3]).toEqual([ + [1, "e"], [2, "b"], [3, "c"], ]); @@ -347,7 +339,6 @@ describe("ReactiveMap", () => { [1, "a"], [2, "b"], [3, "c"], - [4, "d"], ]); const captured: unknown[][] = []; @@ -368,7 +359,6 @@ describe("ReactiveMap", () => { [1, "a"], [2, "b"], [3, "c"], - [4, "d"], ]); map.set(1, "e"); @@ -377,15 +367,13 @@ describe("ReactiveMap", () => { [1, "e"], [2, "b"], [3, "c"], - [4, "d"], ]); - map.delete(4); + map.delete(3); expect(captured).toHaveLength(3); expect(captured[2]).toEqual([ [1, "e"], [2, "b"], - [3, "c"], ]); dispose(); From 8db3aecb089e24e1982f3105250ecf536efd03ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kwa=C5=9Bniak?= Date: Fri, 4 Oct 2024 12:19:59 +0200 Subject: [PATCH 11/11] fix: Always dirty values on Map delete --- packages/map/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts index 3d4df2f5b..489c5bc76 100644 --- a/packages/map/src/index.ts +++ b/packages/map/src/index.ts @@ -107,10 +107,10 @@ export class ReactiveMap extends Map { if (result) { batch(() => { this.#keyTriggers.dirty($OBJECT); + this.#valueTriggers.dirty($OBJECT); this.#keyTriggers.dirty(key); if (isDefined) { - this.#valueTriggers.dirty($OBJECT); this.#valueTriggers.dirty(key); } });