use crate::arena::signal_arena::add_signal_writer;
use crate::arena::{
EffectId, SignalId, SignalMetadata, current_effect, mark_effect_pending, signal_arena_insert,
signal_arena_remove,
};
use crate::effect::schedule_effect_processing;
use crate::transaction::is_transaction_active;
pub struct Signal {
node_id: SignalId,
}
impl Signal {
pub fn new() -> Self {
let metadata = SignalMetadata::new();
let node_id = signal_arena_insert(metadata);
Signal { node_id }
}
pub(crate) fn id(&self) -> SignalId {
self.node_id
}
pub(crate) fn from_raw(id: SignalId) -> Self {
Self { node_id: id }
}
pub fn track_dependency(&self) {
if let Some(effect_id) = current_effect() {
effect_id.add_source(self.node_id);
self.node_id.add_subscriber(effect_id);
}
}
pub fn emit(&self) {
self.emit_from_api();
}
pub fn emit_from_api(&self) {
self.emit_with_origin(false);
}
pub fn emit_from_ui(&self) {
self.emit_with_origin(true);
}
fn emit_with_origin(&self, is_from_ui: bool) {
if let Some(effect_id) = current_effect() {
let is_source = effect_id.has_source(self.node_id);
if is_source {
eprintln!(
"Warning: Effect {:?} both reads and writes signal {:?}. \
This would cause an infinite loop. \
The read is being treated as untracked.",
effect_id, self.node_id
);
self.node_id.remove_subscriber(effect_id);
effect_id.remove_source(self.node_id);
}
effect_id.add_output(self.node_id);
add_signal_writer(self.node_id, effect_id);
}
self.notify_subscribers(is_from_ui);
}
fn notify_subscribers(&self, is_from_ui: bool) {
self.node_id.with_subscribers(|subscribers| {
for effect_id in subscribers {
if is_from_ui && effect_id.is_ui_updates_only() {
continue;
}
mark_effect_pending(*effect_id);
effect_id.with_outputs(|outputs| {
for output in outputs {
output.mark_subscribers_check();
}
});
}
});
if !is_transaction_active() && current_effect().is_none() {
schedule_effect_processing();
}
}
pub fn subscribe(&self, effect_id: EffectId) {
self.node_id.add_subscriber(effect_id);
}
pub fn unsubscribe(&self, effect_id: EffectId) {
self.node_id.remove_subscriber(effect_id);
}
}
impl Drop for Signal {
fn drop(&mut self) {
self.node_id.with_subscribers(|subscribers| {
for effect_id in subscribers {
effect_id.remove_source(self.node_id);
}
});
signal_arena_remove(self.node_id);
}
}
impl Default for Signal {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Effect;
#[test]
fn signal_tracks_multiple_accesses_deduplicated() {
let signal = Signal::new();
let signal_id = signal.id();
let effect = Effect::new(move || {
signal_id.track_dependency();
signal_id.track_dependency();
signal_id.track_dependency();
});
let source_count = effect
.id()
.with_sources(|sources| sources.count())
.unwrap_or(0);
assert_eq!(source_count, 1);
}
}