2 releases

Uses new Rust 2024

new 0.1.2 Apr 16, 2025
0.1.1 Apr 15, 2025
0.1.0 Apr 15, 2025

#1209 in Game dev

Download history 89/week @ 2025-04-09

89 downloads per month

MIT/Apache

60KB
744 lines

jonmo জন্ম

Crates.io Version Docs.rs Following released Bevy versions

in bengali, jonmo means "birth"

jonmo provides a declarative way to define reactive signal chains in Bevy. Signals originate from sources (like component changes, resource changes, or specific entities) and can be transformed (map), combined (combine_with), or filtered (dedupe).

Internally, jonmo builds and maintains a dependency graph of Bevy systems. Each frame, the JonmoPlugin traverses this graph starting from the root systems. It pipes the output (Some(O)) of a parent system as the input (In<O>) to its children. This traversal continues down each branch until a system returns None (often represented by the TERMINATE constant), which halts propagation along that specific path for the current frame.

usage

jonmo allows you to build signal chains declaratively using the Signal builder.

declarative api

Start a chain using Signal::from_* methods, chain transformations with .map(), and activate the chain with .register().

use bevy::prelude::*;
use jonmo::*;

#[derive(Component, Reflect, Clone, Default, PartialEq)]
#[reflect(Component)]
struct Value(i32);

fn setup_ui(world: &mut World) {
    // Assume 'entity' and 'text_entity' are created elsewhere
    let entity = world.spawn(Value(0)).id();
    let text_entity = world.spawn_empty().id();

    // Define the signal chain
    let signal_chain = Signal::from_component::<Value>(entity) // React to Value changes on 'entity'
        .map(dedupe) // Only propagate if the value actually changed
        .map(move |In(value): In<Value>, mut commands: Commands| { // Update text when value changes
            commands.entity(text_entity).insert(Text(value.0.to_string()));
            TERMINATE // End the signal chain here for this update
        });

    // Register the chain to activate it
    let handle = signal_chain.register(world);
    // 'handle' can be used later to clean up the signal chain: handle.cleanup(world);
}

fn main() {
    let mut app = App::new();
    app.add_plugins(DefaultPlugins)
        .add_plugins(JonmoPlugin)
        .register_type::<Value>()
        .add_systems(Startup, setup_ui);
    // ... add systems to change Value ...
    app.run();
}

See the declarative example for a runnable version.

imperative api

For more control or integration with existing systems, you can use the lower-level imperative functions:

  • register_signal: Registers a single system node.
  • mark_signal_root: Marks a system as a starting point for propagation.
  • pipe_signal: Connects the output of one system to the input of another.
  • combine_signal: Creates the necessary nodes to combine two signal branches.
use bevy::prelude::*;
use jonmo::*; // Import imperative functions too

#[derive(Component, Reflect, Clone, Default, PartialEq)]
#[reflect(Component)]
struct Value(i32);

fn setup_ui_imperative(world: &mut World) {
    // Assume 'entity' and 'text_entity' are created elsewhere
    let entity = world.spawn(Value(0)).id();
    let text_entity = world.spawn_empty().id();

    // 1. Create root source
    let system1_id = register_signal(world, entity_root(entity));
    mark_signal_root(world, system1_id.entity());

    // 2. Create component change listener
    let system2_id = register_signal(world, |In(entity): In<Entity>, values: Query<&Value, Changed<Value>>| {
        values.get(entity).ok().cloned()
    });
    pipe_signal(world, system1_id.entity(), system2_id.entity());

    // 3. Create text update system
    let system3_id = register_signal(world, move |In(value): In<Value>, mut commands: Commands| {
        commands.entity(text_entity).insert(Text(value.0.to_string()));
        TERMINATE
    });
    pipe_signal(world, system2_id.entity(), system3_id.entity());
}

See the imperative example for a runnable version.

Bevy compatibility

bevy jonmo
0.15 0.1

license

All code in this repository is dual-licensed under either:

at your option.

your contributions

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~14–25MB
~365K SLoC