Closed davidhewitt closed 6 years ago
Yes, Signals are fundamentally designed around a 1 producer 1 consumer model, just like Futures and Streams. This is very important to ensure that they are zero-cost. Requiring full broadcast ability for all Signals would incur extra expense.
Most of the time having a 1:1 model works perfectly fine, but as you said sometimes you need a 1:N model, with multiple consumers.
My plan is to create a .shared()
/ .broadcast()
method which can be used on all Signals. It returns a new Signal, and that new Signal can be cloned, and the clones receive the same messages. I just haven't had the time to make it yet.
You're right that it is confusing to have Receiver
implement Clone
, since this behavior is surprising.
A pull request is welcome, especially if it's an initial sketch that we can discuss, since I have some ideas on how to implement it as well.
Agree that keeping to zero-cost is desirable. I think what I have in mind doesn't have too much overhead but it's probably enough that you wouldn't want it for all signals.
I'll try to cook up a PR (probably tomorrow)!
I just now made a breaking change: Receiver
, Sender
, and mutable
no longer exist.
Instead, they are replaced with Mutable
and MutableSignal
:
Old way:
let (sender, receiver) = mutable(5);
sender.set(10);
receiver.for_each(...);
New way:
let x = Mutable::new(5);
x.set(10);
x.signal().for_each(...);
x.signal().for_each(...);
As demonstrated above, it is now possible to call the .signal()
method multiple times, each time it is called it returns a fresh MutableSignal
. When x.set(...)
is called it will broadcast the value to all of the MutableSignal
s.
It will .clone()
the value once per MutableSignal
, so you should use Mutable::new(Rc::new(...))
if you want to avoid the expense of a .clone()
And now I added back in Sender
and Receiver
(and renamed mutable
to channel
).
Now there's two ways to create Signal sources:
let (sender, receiver) = channel(5);
sender.send(10);
receiver.for_each(...);
let x = Mutable::new(5);
x.set(10);
x.signal().for_each(...);
Mutable
is the preferred way of creating Signal sources, since it is much more convenient, it supports retrieving the value (with x.get()
), and it supports broadcasting.
But in the situations where you don't need those extra features, using channel
is faster.
P.S. I still would like to have a generic .broadcast()
method that works on all signals, but I figured that it's more efficient and convenient to have Mutable
support broadcasting by default.
Similarly I've now changed signal_vec::unsync::Sender
and Receiver
to be MutableVec
:
Old way:
let (sender, receiver) = mutable();
sender.push(5);
sender.push(10);
receiver.for_each(...);
New way:
let x = MutableVec::new();
x.push(5);
x.push(10);
x.signal_vec().for_each(...);
After all the various changes I've made in the past couple days, now there is a full implementation of TodoMVC in the examples/todomvc
folder!
This is a big milestone, since it means that rust-dominator
now has enough combinators implemented that it's possible to start making apps!
Thanks! I've had a busy couple of weeks but finally had a chance today to play around with the released dominator on crates.io with this new Mutable. It's really really nice, thanks!
I hope to get to the .broadcast()
impl this week, sorry about the delay. Ping me if it's blocking you and I'll step on it asap.
No worries, it's not blocking anything.
I've given some thought to this, and I think we should have two methods: broadcast_sync_cloned
and broadcast_unsync_cloned
.
Actually... now that I think of it, rather than using methods, I think it would be even better if they were structs, like this:
use futures_signals::signals::unsync::Broadcaster;
let bar = Broadcaster::new(foo);
bar.signal_cloned() // for Clone
bar.signal() // for Copy
And similarly for signals::sync::Broadcaster
(but we don't need to implement sync
right now. I'm prioritizing wasm, which is single-threaded).
But it still needs to solve the problem of only polling once per change. Maybe the Broadcaster
can somehow set up its own internal Task which it uses to keep track of whether the parent Signal has changed or not. I remember seeing some Futures APIs that can do that.
P.S. A reminder that you'll need to make the changes to the rust-signal
repo.
See my broadcaster
branch for a new unsync::Broadcaster
attempt! (Built on top of the futures-0.2
branch, didn't think it wise to open a PR until that one's merged).
Closing since this is resolved.
While I was playing around with creating a store struct for state I noticed that only one signal reciever gets the update (I hadn't really grokked this before). E.g.
The rendered Dom populates the text after First: with "Hello", but then when the button is clicked it's the second text that recieves "World". Ouch 😨
I guess that either:
I have a concept on how I would build that last one, would it be interesting if I made a PR with a signal::mpmc module to discuss?