Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: improve reactive Map and Set implementations #11827

Merged
merged 4 commits into from
May 31, 2024
Merged

Conversation

trueadm
Copy link
Contributor

@trueadm trueadm commented May 29, 2024

Closes #11727.

This PR aims to tackle issues around our reactive Map/Set implementations. Notably:

  • We now store the values on the backing Map/Set, allowing for much better introspection in console/dev tools
  • We no longer store the values inside the source signals, instead we use Symbols and booleans only as markers

Copy link

changeset-bot bot commented May 29, 2024

🦋 Changeset detected

Latest commit: ac921d8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
svelte Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@fznhq
Copy link

fznhq commented May 30, 2024

Sorry, but this still doesn't fix the problem on firefox console (still no values). Here my example from #11680 REPL

@trueadm
Copy link
Contributor Author

trueadm commented May 30, 2024

@fznhq There's nothing we can do about Firefox unfortunately. It doesn't like having a get size prototype accessor on the class and we need that to ensure size is reactive. It's something the FF folks should likely fix as it works fine in other browsers.

@fznhq
Copy link

fznhq commented May 30, 2024

If there is nothing svelte can do about it, just leave a note in the documentation about this behavior. Thanks @trueadm

@@ -28,7 +27,8 @@ export class ReactiveMap extends Map {
var sources = this.#sources;

for (var [key, v] of value) {
sources.set(key, source(v));
sources.set(key, source(Symbol()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we check if the key is an object here as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, I'm not sure if we need to fill the sources Map here, since we now generate signals in has and get

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, fixed.


/**
* @template K
* @template V
* @extends {Map<K, V>}
*/
export class ReactiveMap extends Map {
/** @type {Map<K, import('#client').Source<V>>} */
/** @type {Map<K, import('#client').Source<symbol>>} */
#sources = new Map();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could use a WeakMap here to allow for object keys?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah WeakMaps don't give us anything here really. It means we'd have to duplicate data as we need to store the value on the super and in the WeakMap, which means the WeakMap never does anything as the value is strongly held. Also WeakMaps are very slow.

// If we're working with a non-primitive then we can generate a signal for it
if (typeof key !== 'object' || key === null) {
s = source(UNINITIALIZED);
sources.set(key, s);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still has the same problem as #11504 and #11287. We need a way to remove these signals when we are no longer listening to them. Otherwise it would create a memory leak (see #11504 (comment))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Unfortunately, this means that we can't retain primitives either. So they won't be fine-grain, as there's no way to do so without leaking.

@Azarattum
Copy link
Contributor

There are still two major problems with this:

  • Initializing all the sources in the constructor/set/add means that the number of signals is proportional to the size of the Map/Set instead of being proportional to a subset a user listens to (which is generally much smaller)
  • Generating signals in get/has still needs some kind of a cleanup when an effect is done. And the generation doesn't need to happen when we are not in effect at all

@trueadm
Copy link
Contributor Author

trueadm commented May 31, 2024

There are still two major problems with this:

  • Initializing all the sources in the constructor/set/add means that the number of signals is proportional to the size of the Map/Set instead of being proportional to a subset a user listens to (which is generally much smaller)
  • Generating signals in get/has still needs some kind of a cleanup when an effect is done. And the generation doesn't need to happen when we are not in effect at all

Fixed these issues. Unfortunately, checks for values that aren't added will not be fine-grain till they are added. There's nothing we can do about this without retaining too much memory.

If you do a loop and does a has check for all ints from 0 to a very large int, it causes my Chrome tab to retain almost 200mb. That's just not acceptable for something that isn't that big a dead.

@dummdidumm dummdidumm merged commit 2382eb0 into main May 31, 2024
8 checks passed
@dummdidumm dummdidumm deleted the improve-map-set branch May 31, 2024 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

svelte5: reactivity package classes are not fine-grained
4 participants