Signals
This chapter teaches you the very basics of Signals. See the
futures-signals
tutorial for a more thorough introduction.
Signals and Mutables
A Mutable
is a variable with interior mutability whose changes can be
tracked by a Signal
. Lets go through a simple example.
First we create a new Mutable
initialized with the value 1
:
let x = Mutable::new(1);
assert_eq!(x.get(), 1);
Now lets set up a signal to track changes to x
:
let x_signal = x.signal();
By itself, this won't do anything. We have to tell it what we want to do when
the signal changes. In this case spawn_for_each
will spawn a task on the
microtask queue that logs the value of x
when it changes.
x_signal.spawn_for_each(|x| {
web_log::println!("{x}");
async {}
});
Normally, you won't need to call spawn_for_each
as you'll pass the
Signal
to Silkenweb, and Silkenweb will watch for any changes. We'll
see this in action in the chapter on reactivity.
Push/Pull
Signals are both push and pull.
- They are push, in the sense that the future will notify that it wants to be
woken when there is a change. This uses the normal
Future
polling mechanism. - They are pull, in the sense that the future will pull the value from the signal when it is polled.
Streams
Signals are like streams in some ways, but there are differences:
- Signals are allowed to skip intermediate values for efficiency. In our
example, not every intermediate value of
x
will be printed. - Signals always have at least one value, whereas a stream can be empty.
Differential Signals
More complex data types don't always fit well with the normal, value
based signals that we've seen so far. It wouldn't be very useful to be
notified with a completely new vector every time an element changes, for
example. For vectors, we use signal_vec
. This allows us to see what's
changed in the vector using VecDiff
.
Here's how we create a MutableVec
and push a value onto it:
let v = MutableVec::new();
v.lock_mut().push(1);
and we can listen for changes with:
v.signal_vec().spawn_for_each(|delta| {
let action = match delta {
VecDiff::Replace { .. } => "Replace",
VecDiff::InsertAt { .. } => "InsertAt",
VecDiff::UpdateAt { .. } => "UpdateAt",
VecDiff::RemoveAt { .. } => "RemoveAt",
VecDiff::Move { .. } => "Move",
VecDiff::Push { .. } => "Push",
VecDiff::Pop {} => "Pop",
VecDiff::Clear {} => "Clear",
};
web_log::println!("{action}");
async {}
})
signal_vec
intermediate values can't be discarded like with value
signals, as we need to know all the deltas to reconstruct a vector. They
can however be combined as an optimization. For example, maybe a push
followed by a pop could be discarded. This is implementation defined
though, and not guaranteed.