From 05c3568ad1f3261381f49cef13c5e244701a2ed8 Mon Sep 17 00:00:00 2001 From: ramon-bernardo Date: Wed, 28 Feb 2024 20:34:37 -0300 Subject: [PATCH 1/3] Async events improve --- Cargo.lock | 5 ++-- ryot/Cargo.toml | 9 ++++--- ryot/src/bevy_ryot/async_events.rs | 38 +++++++++++++--------------- ryot_compass/src/bevy_compass/gui.rs | 4 +-- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49c69c2a..114db27e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2048,9 +2048,9 @@ checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" [[package]] name = "crossbeam-channel" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" dependencies = [ "crossbeam-utils", ] @@ -5031,6 +5031,7 @@ dependencies = [ "bevy_common_assets", "bytes", "color-eyre", + "crossbeam-channel", "derive_more", "egui", "glam", diff --git a/ryot/Cargo.toml b/ryot/Cargo.toml index b9152263..24962700 100644 --- a/ryot/Cargo.toml +++ b/ryot/Cargo.toml @@ -16,6 +16,7 @@ readme = "../README.md" async-std = "1.12.0" derive_more = "0.99.17" serde_repr = "0.1" +crossbeam-channel = "0.5.12" bevy = { workspace = true, optional = true } bevy_asset_loader = { workspace = true, optional = true } @@ -54,10 +55,10 @@ default = ["bevy"] lmdb = ["dep:heed", "dep:postcard"] compression = ["dep:zstd"] bevy = [ - "dep:bevy", - "dep:bevy_common_assets", - "dep:bevy_asset_loader", - "dep:leafwing-input-manager", + "dep:bevy", + "dep:bevy_common_assets", + "dep:bevy_asset_loader", + "dep:leafwing-input-manager", ] egui = ["dep:egui"] diff --git a/ryot/src/bevy_ryot/async_events.rs b/ryot/src/bevy_ryot/async_events.rs index c43165ab..0305c4ac 100644 --- a/ryot/src/bevy_ryot/async_events.rs +++ b/ryot/src/bevy_ryot/async_events.rs @@ -3,19 +3,21 @@ //! content from disk or loading sprites from a sprite sheet before rendering. use bevy::app::App; use bevy::prelude::*; -use std::sync::mpsc::{channel, Receiver, Sender}; -use std::sync::Mutex; +use crossbeam_channel::{Receiver, Sender}; + +// TODO: doc. +#[derive(SystemSet, Clone, Copy, Eq, PartialEq, Hash, Debug)] +pub struct AsyncEventSet(); /// A resource that emits asynchronous events of a given type. -/// It's a bevy friendly wrapper around a `std::sync::mpsc::Sender`. +/// It's a bevy friendly wrapper around a `crossbeam_channel::Sender`. #[derive(Resource, Deref, DerefMut)] -pub struct EventSender(pub Sender); +pub struct AsyncEventSender(pub Sender); /// A resource that receives asynchronous events of a given type. -/// It's a bevy friendly wrapper around a `std::sync::mpsc::Receiver`. -/// It's wrapped in a `Mutex` to allow multiple systems to safely access it. +/// It's a bevy friendly wrapper around a `crossbeam_channel::Receiver`. #[derive(Resource, Deref, DerefMut)] -struct EventReceiver(Mutex>); +struct AsyncEventReceiver(Receiver); /// A trait to add an asynchronous event to an App. pub trait AsyncEventApp { @@ -27,27 +29,23 @@ pub trait AsyncEventApp { /// the system that sends events from the receiver to Bevy's event system. impl AsyncEventApp for App { fn add_async_event(&mut self) -> &mut Self { - if self.world.contains_resource::>() { + if self.world.contains_resource::>() { return self; } - let (sender, receiver) = channel::(); + let (sender, receiver) = crossbeam_channel::unbounded::(); - self.add_event::() - .add_systems(Update, channel_to_event::) - .insert_resource(EventSender(sender)) - .insert_resource(EventReceiver(Mutex::new(receiver))); + self.add_event::() + .add_systems(PreUpdate, unbounded_channel_to_event::.in_set(AsyncEventSet())) + .insert_resource(AsyncEventSender(sender)) + .insert_resource(AsyncEventReceiver(receiver)); - self + self } } /// A system that sends events from the receiver to Bevy's event system. /// Converts the asynchronous event into a bevy event and sends it to the event system. -fn channel_to_event(receiver: Res>, mut writer: EventWriter) { - // this should be the only system working with the receiver, - // thus we always expect to get this lock - let events = receiver.lock().expect("unable to acquire mutex lock"); - - writer.send_batch(events.try_iter()); +fn unbounded_channel_to_event(receiver: Res>, mut writer: EventWriter) { + writer.send_batch(receiver.try_iter()); } diff --git a/ryot_compass/src/bevy_compass/gui.rs b/ryot_compass/src/bevy_compass/gui.rs index 01c3afc0..0412e95f 100644 --- a/ryot_compass/src/bevy_compass/gui.rs +++ b/ryot_compass/src/bevy_compass/gui.rs @@ -10,7 +10,7 @@ use crate::{ExportMap, LoadMap}; #[cfg(not(target_arch = "wasm32"))] use bevy::app::AppExit; #[cfg(not(target_arch = "wasm32"))] -use ryot::bevy_ryot::{AsyncEventApp, EventSender}; +use ryot::bevy_ryot::{AsyncEventApp, AsyncEventSender}; use bevy::{prelude::*, render::camera::Viewport, winit::WinitWindows}; use bevy_egui::{EguiContext, EguiContexts, EguiPlugin, EguiUserTextures}; @@ -81,7 +81,7 @@ fn ui_menu_system( #[cfg(not(target_arch = "wasm32"))] content_assets: Res, #[cfg(not(target_arch = "wasm32"))] mut exit: EventWriter, #[cfg(not(target_arch = "wasm32"))] mut map_export_sender: EventWriter, - #[cfg(not(target_arch = "wasm32"))] load_map_sender: Res>, + #[cfg(not(target_arch = "wasm32"))] load_map_sender: Res>, _windows: NonSend, ) { let Ok(mut cursor) = cursor_query.get_single_mut() else { From 1eb3446151ef6091371a2dc95cc0efba5a873729 Mon Sep 17 00:00:00 2001 From: ramon-bernardo Date: Thu, 29 Feb 2024 00:30:02 -0300 Subject: [PATCH 2/3] Use phantom data and impl manual eq, partialEq, copy, clone and debug --- ryot/src/bevy_ryot/async_events.rs | 56 +++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/ryot/src/bevy_ryot/async_events.rs b/ryot/src/bevy_ryot/async_events.rs index 0305c4ac..1d49c206 100644 --- a/ryot/src/bevy_ryot/async_events.rs +++ b/ryot/src/bevy_ryot/async_events.rs @@ -1,23 +1,57 @@ //! This module provides a way to send events between systems asynchronously. //! It's useful to send events between threads that perform asynchronous tasks, such as loading //! content from disk or loading sprites from a sprite sheet before rendering. +use std::{hash::Hasher, marker::PhantomData}; + use bevy::app::App; use bevy::prelude::*; use crossbeam_channel::{Receiver, Sender}; // TODO: doc. -#[derive(SystemSet, Clone, Copy, Eq, PartialEq, Hash, Debug)] -pub struct AsyncEventSet(); +#[derive(SystemSet, Default)] +pub struct AsyncEventSet(PhantomData); + +impl std::fmt::Debug for AsyncEventSet { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("AsyncEventSet") + .field(&format_args!("fn {}()", &std::any::type_name::())) + .finish() + } +} + +impl std::hash::Hash for AsyncEventSet { + fn hash(&self, _state: &mut H) { + // all systems of a given type are the same + } +} + +impl Clone for AsyncEventSet { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for AsyncEventSet {} + +impl PartialEq for AsyncEventSet { + #[inline] + fn eq(&self, _other: &Self) -> bool { + // all systems of a given type are the same + true + } +} + +impl Eq for AsyncEventSet {} /// A resource that emits asynchronous events of a given type. /// It's a bevy friendly wrapper around a `crossbeam_channel::Sender`. #[derive(Resource, Deref, DerefMut)] -pub struct AsyncEventSender(pub Sender); +pub struct AsyncEventSender(pub Sender); /// A resource that receives asynchronous events of a given type. /// It's a bevy friendly wrapper around a `crossbeam_channel::Receiver`. #[derive(Resource, Deref, DerefMut)] -struct AsyncEventReceiver(Receiver); +struct AsyncEventReceiver(Receiver); /// A trait to add an asynchronous event to an App. pub trait AsyncEventApp { @@ -35,17 +69,23 @@ impl AsyncEventApp for App { let (sender, receiver) = crossbeam_channel::unbounded::(); - self.add_event::() - .add_systems(PreUpdate, unbounded_channel_to_event::.in_set(AsyncEventSet())) + self.add_event::() + .add_systems( + PreUpdate, + unbounded_channel_to_event::.in_set(AsyncEventSet::(PhantomData)), + ) .insert_resource(AsyncEventSender(sender)) .insert_resource(AsyncEventReceiver(receiver)); - self + self } } /// A system that sends events from the receiver to Bevy's event system. /// Converts the asynchronous event into a bevy event and sends it to the event system. -fn unbounded_channel_to_event(receiver: Res>, mut writer: EventWriter) { +fn unbounded_channel_to_event( + receiver: Res>, + mut writer: EventWriter, +) { writer.send_batch(receiver.try_iter()); } From ee3eec62d5dc9e12f959fa0ff52b31276f6ccde4 Mon Sep 17 00:00:00 2001 From: ramon-bernardo Date: Thu, 29 Feb 2024 00:37:31 -0300 Subject: [PATCH 3/3] Fix AsyncEventSet impl --- ryot/src/bevy_ryot/async_events.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ryot/src/bevy_ryot/async_events.rs b/ryot/src/bevy_ryot/async_events.rs index 1d49c206..bd004a5d 100644 --- a/ryot/src/bevy_ryot/async_events.rs +++ b/ryot/src/bevy_ryot/async_events.rs @@ -8,14 +8,12 @@ use bevy::prelude::*; use crossbeam_channel::{Receiver, Sender}; // TODO: doc. -#[derive(SystemSet, Default)] +#[derive(SystemSet)] pub struct AsyncEventSet(PhantomData); impl std::fmt::Debug for AsyncEventSet { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("AsyncEventSet") - .field(&format_args!("fn {}()", &std::any::type_name::())) - .finish() + write!(f, "AsyncEventSet<{}>", &std::any::type_name::()) } }