diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cfc4b9..8d680e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,45 @@ the format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) ## unreleased +### changed + +- signal graph processed in deterministic topological order instead of the previous nondeterministic depth first order +- `SignalBuilder::*` methods moved to free functions in the `signal` module (e.g. `SignalBuilder::always` -> `signal::always`) +- `JonmoBuilder` renamed to `jonmo::Builder` +- `jonmo::Builder` is now lock-free +- `jonmo::Builder::hold_signals` renamed to `jonmo::Builder::hold_tasks` and takes `Box`s instead of `SignalHandles` +- renamed `SignalExt::combine` to `SignalExt::zip` +- `MutableVecBuilder`/`MutableBTreeMapBuilder` changed to `MutableVec::builder()`/`MutableBTreeMap::builder()` with simple chainable `.values` and `.with_values` methods +- removed self item `Clone` bound from `SignalVecExt::map_signal` and `SignalVecExt::filter_map` +- `.get_mut`s unwrapped and `.get_entity_mut`s changed to `.entity_mut` in cases where they were silently ignoring invariant violations + +### added +- `signal::once` +- `signal::from_component_changed` +- `signal::from_resource_changed` +- `signal::zip!`, a variadic flattened version of `SignalExt::zip` +- `SignalExt::take` +- `SignalVecExt::flatten` +- `track_caller` derive for panicking `LazyEntity` methods +- panic (debug only) or error log that cloning `jonmo::Builder`s at runtime is a bug + +### fixed + +- deadlock when despawning `MutableVec/BTreeMap`s during another `MutableVec/BTreeMap` despawn +- initially empty `MutableVec/BTreeMap`s work as expected when output to `.switch_signal_vec/map` +- `SignalVecExt::debug` and `SignalMapExt::debug` now log correct code location + +### removed +- `*_lazy` signal builder functions, the non-`lazy` versions now take both `Entity` and `LazyEntity` +- `jonmo::Builder::signal_from_*` methods, use corresponding `signal` building functions with `.task()` and `jonmo::Builder::hold_tasks` instead +- `jonmo::Builder::component_signal_from_*` methods, use corresponding `signal` building functions with `jonmo::Builder::component_signal` instead + # 0.5.0 (2025-12-19) ### changed - `.entity_sync` renamed to `.lazy_entity` -- `SignalExt::combine` always `.clone`s its latest upstream outputs instead of `.take`-ing them +- `SignalExt::combine` emits its latest upstream outputs every frame, unlike previously, when it only emitted on frames where the latest output pair was yet to be emitted ### added diff --git a/Cargo.lock b/Cargo.lock index 3df3779..745cf79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2770,6 +2770,7 @@ dependencies = [ "bevy", "bevy_app", "bevy_derive", + "bevy_diagnostic", "bevy_ecs", "bevy_log", "bevy_platform", diff --git a/Cargo.toml b/Cargo.toml index 691cb44..5e4e03a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ documentation = "https://docs.rs/jonmo" [dependencies] bevy_app = { version = "0.17", default-features = false } bevy_derive = { version = "0.17", default-features = false } +bevy_diagnostic = { version = "0.17", default-features = false } bevy_ecs = { version = "0.17", default-features = false } bevy_platform = { version = "0.17", default-features = false } bevy_log = { version = "0.17", default-features = false, optional = true } @@ -26,7 +27,7 @@ document-features = { version = "0.2", optional = true } [features] default = ["builder", "tracing", "time", "std"] -## Enables access to jonmo's high-level entity builder, [`JonmoBuilder`](https://docs.rs/jonmo/latest/jonmo/builder/struct.JonmoBuilder.html). +## Enables access to jonmo's high-level entity builder, [`jonmo::Builder`](https://docs.rs/jonmo/latest/jonmo/builder/struct.Builder.html). builder = [] ## Enables access to signal ext `.debug` methods, which conveniently logs signal outputs at any step. diff --git a/README.md b/README.md index 13291e8..1870c6a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ in bengali, jonmo means "birth" ``` -[jonmo](https://github.com/databasedav/jonmo) provides an ergonomic, functional, and declarative API for specifying Bevy [system](https://docs.rs/bevy/latest/bevy/ecs/system/index.html) dependency graphs, where "output" handles to nodes of the graph are canonically referred to as "signals". Building upon these signals, jonmo offers a high level [entity builder](https://docs.rs/jonmo/latest/jonmo/builder/struct.JonmoBuilder.html) which enables one to declare reactive entities, components, and children using a familiar fluent syntax with semantics and API ported from the incredible [FRP](https://en.wikipedia.org/wiki/Functional_reactive_programming) signals of [futures-signals](https://github.com/Pauan/rust-signals) and its web UI dependents [MoonZoon](https://github.com/MoonZoon/MoonZoon) and [Dominator](https://github.com/Pauan/rust-dominator). +[jonmo](https://github.com/databasedav/jonmo) provides an ergonomic, functional, and declarative API for specifying Bevy [system](https://docs.rs/bevy/latest/bevy/ecs/system/index.html) dependency graphs, where "output" handles to nodes of the graph are canonically referred to as "signals". Building upon these signals, jonmo offers a high level [entity builder](https://docs.rs/jonmo/latest/jonmo/builder/struct.Builder.html) which enables one to declare reactive entities, components, and children using a familiar fluent syntax with semantics and API ported from the incredible [FRP](https://en.wikipedia.org/wiki/Functional_reactive_programming) signals of [futures-signals](https://github.com/Pauan/rust-signals) and its web UI dependents [MoonZoon](https://github.com/MoonZoon/MoonZoon) and [Dominator](https://github.com/Pauan/rust-dominator). The runtime of jonmo is quite simple; every frame, the outputs of systems are forwarded to their dependants, recursively. The complexity and power of jonmo really emerges from its monadic signal combinators, defined within the [`SignalExt`](https://docs.rs/jonmo/latest/jonmo/signal/trait.SignalExt.html), [`SignalVecExt`](https://docs.rs/jonmo/latest/jonmo/signal_vec/trait.SignalVecExt.html), and [`SignalMapExt`](https://docs.rs/jonmo/latest/jonmo/signal_map/trait.SignalMapExt.html) traits (ported from futures-signals' traits of the same name), which internally manage special Bevy systems that allow for the declarative composition of complex data flows with minimalistic, high-level, signals-oriented methods. @@ -51,10 +51,10 @@ fn main() { #[derive(Component, Clone, Deref, DerefMut)] struct Counter(i32); -fn ui_root() -> JonmoBuilder { +fn ui_root() -> jonmo::Builder { let counter_holder = LazyEntity::new(); - JonmoBuilder::from(Node { + jonmo::Builder::from(Node { width: Val::Percent(100.0), height: Val::Percent(100.0), justify_content: JustifyContent::Center, @@ -62,7 +62,7 @@ fn ui_root() -> JonmoBuilder { ..default() }) .child( - JonmoBuilder::from(Node { + jonmo::Builder::from(Node { flex_direction: FlexDirection::Row, column_gap: Val::Px(15.0), align_items: AlignItems::Center, @@ -73,11 +73,10 @@ fn ui_root() -> JonmoBuilder { .lazy_entity(counter_holder.clone()) .child(counter_button(counter_holder.clone(), PINK, "-", -1)) .child( - JonmoBuilder::from((Node::default(), TextFont::from_font_size(25.))) + jonmo::Builder::from((Node::default(), TextFont::from_font_size(25.))) .component_signal( - SignalBuilder::from_component_lazy(counter_holder.clone()) - .map_in(|counter: Counter| *counter) - .dedupe() + signal::from_component_changed::(counter_holder.clone()) + .map_in(deref_copied) .map_in_ref(ToString::to_string) .map_in(Text) .map_in(Some), @@ -87,8 +86,8 @@ fn ui_root() -> JonmoBuilder { ) } -fn counter_button(counter_holder: LazyEntity, color: Color, label: &'static str, step: i32) -> JonmoBuilder { - JonmoBuilder::from(( +fn counter_button(counter_holder: LazyEntity, color: Color, label: &'static str, step: i32) -> jonmo::Builder { + jonmo::Builder::from(( Node { width: Val::Px(45.0), justify_content: JustifyContent::Center, @@ -99,11 +98,9 @@ fn counter_button(counter_holder: LazyEntity, color: Color, label: &'static str, BackgroundColor(color), )) .observe(move |_: On>, mut counters: Query<&mut Counter>| { - if let Ok(mut counter) = counters.get_mut(*counter_holder) { - **counter += step; - } + **counters.get_mut(*counter_holder).unwrap() += step; }) - .child(JonmoBuilder::from((Text::from(label), TextFont::from_font_size(25.)))) + .child(jonmo::Builder::from((Text::from(label), TextFont::from_font_size(25.)))) } fn camera(mut commands: Commands) { diff --git a/agents.md b/agents.md new file mode 100644 index 0000000..b0b9951 --- /dev/null +++ b/agents.md @@ -0,0 +1,5 @@ +# Agent Instructions + +## Testing + +Use `just test` for running tests. diff --git a/examples/basic.rs b/examples/basic.rs index 8fc732a..ede6dee 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -26,8 +26,7 @@ fn ui(world: &mut World) { let text = world .spawn((Node::default(), TextFont::from_font_size(100.), Value(0))) .id(); - let signal = SignalBuilder::from_component(text) - .dedupe() + let signal = signal::from_component_changed::(text) .map(move |In(value): In, mut commands: Commands| { commands.entity(text).insert(Text(value.0.to_string())); }) diff --git a/examples/basic_builder.rs b/examples/basic_builder.rs index 798b8cd..a308b1e 100644 --- a/examples/basic_builder.rs +++ b/examples/basic_builder.rs @@ -26,27 +26,30 @@ fn main() { #[derive(Resource, Deref, DerefMut)] struct ValueTicker(Timer); -#[derive(Component, Clone, Default, PartialEq)] +#[derive(Component, Clone, Default, PartialEq, Deref)] struct Value(i32); -fn ui() -> JonmoBuilder { - JonmoBuilder::from(Node { +fn ui() -> jonmo::Builder { + jonmo::Builder::from(Node { justify_content: JustifyContent::Center, align_items: AlignItems::Center, height: Val::Percent(100.), width: Val::Percent(100.), ..default() }) - .child( - JonmoBuilder::from((Node::default(), TextFont::from_font_size(100.))) + .child({ + let lazy_entity = LazyEntity::new(); + jonmo::Builder::from((Node::default(), TextFont::from_font_size(100.))) + .lazy_entity(lazy_entity.clone()) .insert(Value(0)) - .component_signal_from_component(|signal| { - signal - .dedupe() - .map(|In(value): In| Text(value.0.to_string())) - .map_in(Some) - }), - ) + .component_signal( + signal::from_component_changed::(lazy_entity) + .map_in(deref_copied) + .map_in_ref(ToString::to_string) + .map_in(Text) + .map_in(Some), + ) + }) } fn incr_value(mut ticker: ResMut, time: Res