| Crates.io | rx_bevy |
| lib.rs | rx_bevy |
| version | 0.3.1 |
| created_at | 2026-01-19 09:19:08.564636+00 |
| updated_at | 2026-01-24 17:58:57.06866+00 |
| description | Reactive Extensions for the Bevy game engine |
| homepage | https://github.com/AlexAegis/rx_bevy |
| repository | https://github.com/AlexAegis/rx_bevy |
| max_upload_size | |
| id | 2054072 |
| size | 393,269 |
Reactive Extensions for the Bevy Game Engine!
rx_bevy abstracts away common event orchestration patterns under observables
and operators, so you can focus on building your logic, instead of boilerplate.
rx_bevy is a fairly low-level library, in the sense that it isn't a solution
to a specific problem, but a toolbox to implement solutions. Feel free to
build on top of rx_bevy and publish it as a library like extra
operators and observables!
Please be mindful of the crate name you choose to not block me from adding new features! Please refer to the external crate naming guide.
If you want to jump straight to using rx_bevy check out the numbered examples
that go though how observables can be used within Bevy:
Other examples on observables, operators and subjects can be found at
crates/rx_core/examples/. I recommend cloning the repository to check them
out!
Change the virtual time speed with keyboard input!
use bevy::prelude::*;
use rx_bevy::prelude::*;
fn main() -> AppExit {
App::new()
.add_plugins((
DefaultPlugins,
RxPlugin,
RxSchedulerPlugin::<Update, Virtual>::default(),
))
.init_resource::<ExampleSubscriptions>()
.add_systems(Startup, setup)
.run()
}
#[derive(Resource, Default, Deref, DerefMut)]
struct ExampleSubscriptions {
subscriptions: SharedSubscription,
}
fn setup(rx_schedule: RxSchedule<Update, Virtual>, mut example_subscriptions: ResMut<ExampleSubscriptions>) {
let subscription = KeyboardObservable::new(KeyboardObservableOptions::default(), rx_schedule.handle())
.filter(|key_code, _| matches!(key_code, KeyCode::Digit1 | KeyCode::Digit2 | KeyCode::Digit3))
.subscribe(ResourceDestination::new(
|mut virtual_time: Mut<'_, Time<Virtual>>, signal| {
let speed = match signal {
ObserverNotification::Next(key_code) => match key_code {
KeyCode::Digit1 => 0.5,
KeyCode::Digit2 => 1.0,
KeyCode::Digit3 => 1.5,
_ => unreachable!(),
},
ObserverNotification::Complete | ObserverNotification::Error(_) => 1.0,
};
virtual_time.set_relative_speed(speed);
},
rx_schedule.handle(),
));
example_subscriptions.add(subscription);
}
Observables define a stream of emissions that is instantiated upon subscription.
Warning: you need to handle subscriptions made to this yourself!
() once the timer elapses!usize's every time the Duration of the interval rolls
over.connect function is called on it. Subscribers of
will subscribe to this internal connector.RxObservers (Not to be confused with Bevy's Observers!) are the destinations of subscriptions! They are the last stations of a signal.
println!.Subjects are both Observers and Observables at the same time. Subjects multicast the signals they observe across all subscribers.
N values and replays them to late subscribers.BehaviorSubject that also stores an additional value that can be used
for filtering. Useful to track the origin of a value as some subscribers may
only be interested in certain origins while some are interested in all values
regardless of origin.Operators take an observable as input and return a new observable as output, enhancing the original observable with new behavior.
Into.Never next/error channels into concrete types as they are always !unreachable().Option and keep only the Some values.n values, then complete.n values.None and forward Some values.next without touching errors or completion.Result values.Result values into next and error signals.Never as the error type to guard pipelines at compile time.CompositeOperator.For every primitive, there is a derive macro available to ease implementation.
They mostly implement traits defining associated types like Out and
OutError. They may also provide default, trivial implementations for when it
is applicable.
See the individual macros for more information:
RxExecutor -
Derive macro for Executors.RxObservable -
Derive macro for Observables.RxObserver -
Derive macro for RxObservers.RxOperator -
Derive macro for Operators.RxScheduler -
Derive macro for Schedulers.RxSubject -
Derive macro for Subjects.RxSubscriber -
Derive macro for Subscribers.RxSubscription -
Derive macro for Subscriptions.RxWork -
Derive macro for schedulable work.The rx_core_testing crate provides utilities to test your Observables and
Operators.
Not everything needs to be an Observable!
rx_bevyis meant to orchestrate events, if something isn't meant to be an event don't make it one without good reason! This doesn't mean you can't express most of your game logic with observable, go ahead, it's fun! But performance critical parts should prioritize performance over a choice of API. And this doesn't meanrx_bevyisn't performant either, but everything comes at a cost!
Observables does not necessarily have to fully live inside the ECS to be used with Bevy:
RxObserver, or an entity with that can
observe RxSignals using an actual Bevy Observer.And you can mix and match these aspects however you like! Whatever is more comfortable in a given situation!
All subscriptions unsubscribe when dropped! Make sure you put them somewhere safe.
"Shared" types - like all Subjects - are actually bundles of Arcs
internally, so you can just Clone them. (This isn't like this because of
convenience, the implementation relies on having multiple locks)
If a behavior of an operator or observable isn't clear, and the provided documentation doesn help, check out the implementation!
For example, you're not sure if the
delayoperator is delaying errors too or just regular signals. (Besides reading delay's readme) Jumping to theDelaySubscriberanswers that at a glance as theerrorimpl just simply calls error on the destination!
Pipelines don't have to be one big pile of operators, feel free to separate them into segments into different variables.
Using the share operator you can "cache" upstream calculations if you use
the ReplaySubject as its connector!
Be careful with filtering operators. If you filter out a signal, nothing will trigger anything downstream! Which can be a problem if you need some "reset" logic there!
Let's say you have an observable pipeline that sets the color of a light based on an upstream signal
Color. Then you introduce the concept of a power outage so you add aCombineLatestObservableand combine thecolorandhas_powerstates. You may instinctively reach for thefilteroperator to prevent the color to be set if there is no power. But that would mean you can't turn it off and after a power outage the lamp stay on its last observed color! In these situations instead offilter, you actually need amapand you need to represent an entirely new state, in this caseoff.
It's very easy tangle yourself up in a web of pipelines. Try to keep things simple!
While
Rxintroduces an entirely new dimension to programming (time), that also comes with complexity!
Only the latest version is maintained. Please do not submit issues for older versions!
| Bevy | rx_bevy |
|---|---|
| 0.18 | 0.3 |
| 0.17 | 0.2 |
| 0.16 | 0.1 |
rx_bevyhas been in closed development in one form or another since the release of Bevy0.16, and first released with the release of Bevy0.18.rx_bevyversions0.1and0.2therefore had no users and received no post-release bugfixes. They are there so you can tryrx_bevyout even if you're a little behind on updates!
See contributing.md