-
-
Notifications
You must be signed in to change notification settings - Fork 93
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
Proposal: Multi-value Signal using bitmaps (bit arrays) #217
base: main
Are you sure you want to change the base?
Conversation
|
✅ Deploy Preview for preact-signals-demo ready!
To edit notification comments on pull requests, go to your Netlify site settings. |
Hi Eddy! I have a hunch that this may be a bit out-of-scope for signals, since we're focused on providing a single "box" primitive that folks can build interesting things like this on top of. Perhaps it would make sense to explore this in one of the object/wrapper libraries folks have been working on that use Signals under the hood? |
I think this is a fantastic prototype and well worth exploring further! Agree with @developit that it's currently out of scope for signals as we're trying to keep the API as minimal as possible. The PR here would be perfect as a separate package that users can install and try out 👍 |
In the current tracking scheme (based on versions numbers) each of the 32 different values probably need their own version numbers. That's because a signal can have multiple listeners (effects, computeds) that run their computations in different times. For example in the following example the last const a = reader({ x: 0, y: 0 });
const c1 = computed(() => {
console.log("computing c1, a.x is", a.x);
});
const c2 = computed(() => {
console.log("computing c2, a.y is", a.y);
});
a.x = 1; // Set _fields bit 0.
a.y = 1; // Set _fields bit 1.
c1.value; // Console: computing c1, a.x is 1
c2.value; // Console: computing c2, a.y is 1
a.x = 2; // _fields bits 0 and 1 stay set.
c1.value; // Console: computing c1, a.x is 2
c2.value; // Console: computing c2, a.y is 1 One workaround that comes to mind would be to set const a = reader({ x: 0, y: 0, z: 0 });
const c1 = computed(() => {
console.log("computing c1, a.x + a.y is", a.x + a.y);
});
const c2 = computed(() => {
console.log("computing c2, a.y + a.z is", a.y + a.z);
});
c1.value; // should recompute
c2.value; // should recompute
a.y = 2;
c1.value; // should recompute
c2.value; // should recompute |
Hi 👋, this is a PoC of #213 😄 (all tests still passing 😅)
Description
The basic idea of this PR is demonstrate how Signals could be improved to keep track of multiple values (up to 32) using 1 single node to reduce memory footprint and possibly improve performance when multiple (related) values are accessed within a
computed
oreffect
.In this PoC, assume a Signal can have - instead of value - properties
signal[0] (alias for signal.value)
tosignal[31]
. Just because numeric properties seem to be accessed faster than string properties (it could have beensignal.value0 .. signal.value31
but it's not relevant).For every
get
, we keep track of the index - in a bit array - of the accessed property:And every
set
operation checks whether the node is watching the property whose value is changed:With this as base, we can create signals that have multiple values (up to 32) and create a lot less nodes if all the accessed properties within a
computed
belong to the same Signal, for example:In this particular example, all properties in both
computed
belong to the same signal, so a single node should be created with bitmap set tofields = (1 << 0) | (1 << 1) | (1 << 2)
(for the first). This means that, for example, ifvalue[0]
changes, then the secondcomputed
won't run even though both use the same signal.Now, the DX is awkward so this PoC doesn't attempt to be a final version but rather to suggest creating an even lower primitive than Signal. A Signal could be an abstraction for this new primitive where it only uses one field
value[0]
.An example for such abstraction is the
reader
function which I included in the code. Which allows creating signals in this manner:( Please check the test cases for
reader
😄 )A Signal could look like:
Of course, this example is just an illustration since better optimizations can be made but I think it conveys the point 😅
But Why?
I want to create a factory which creates classes, sort of like this:
Where
t.String
is a config object which is converted to a property descriptor with getter/setter which would use a signal. However, this consumes a lot of memory and it's (relatively) slow if multiple related properties are accessed within acomputed
.I think this could be improved by using a single Signal to watch multiple related properties (well, up to 32). As in the
reader
PoC example, in one of the tests, you can see that 93 properties can be used with just 3 signals instead of defining 93 signals.My implementation is just a PoC, at its current state I just hope to spawn some discussion around this if it's interesting enough to move forward within preact/signals (Please check the new test cases).
I think this could also be interesting for #4 . I don't think all this logic should belong to
Signal
😅 to be honest, it was just easier to implement it there for a PoC PR.