diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index a761eb151eb301..6b17fcea64615a 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1,18 +1,15 @@ -use crate::{First, Main, MainSchedulePlugin, Plugin, Plugins, Startup, StateTransition, Update}; +use crate::{Main, MainSchedulePlugin, Plugin, Plugins, SubApp, SubApps}; pub use bevy_derive::AppLabel; use bevy_ecs::{ prelude::*, - schedule::{ - apply_state_transition, common_conditions::run_once as run_once_condition, - run_enter_schedule, BoxedScheduleLabel, IntoSystemConfigs, IntoSystemSetConfigs, - ScheduleLabel, + schedule::{IntoSystemConfigs, IntoSystemSetConfigs, ScheduleLabel}, + storage::{ + ThreadLocalAccessor, ThreadLocalTask, ThreadLocalTaskSendError, ThreadLocalTaskSender, + ThreadLocals, }, }; -use bevy_utils::{tracing::debug, HashMap, HashSet}; -use std::{ - fmt::Debug, - panic::{catch_unwind, resume_unwind, AssertUnwindSafe}, -}; +use bevy_utils::HashMap; +use std::fmt::Debug; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; @@ -24,17 +21,63 @@ bevy_utils::define_label!( AppLabelId, ); +/// The [`Resource`] that stores the [`App`]'s [`TypeRegistry`](bevy_reflect::TypeRegistry). +#[cfg(feature = "bevy_reflect")] +#[derive(Resource, Clone, bevy_derive::Deref, bevy_derive::DerefMut, Default)] +pub struct AppTypeRegistry(pub bevy_reflect::TypeRegistryArc); + pub(crate) enum AppError { DuplicatePlugin { plugin_name: String }, } +/// An event that indicates the [`App`] should exit. If one or more of these are present at the +/// end of an update, the [runner](App::set_runner) will end and ([maybe](App::run)) return +/// control to the caller. +/// +/// This event can be used to detect when an exit is requested. Make sure that systems listening +/// for this event run before the current update ends. +#[derive(Event, Debug, Clone, Default)] +pub struct AppExit; + +#[cfg(not(target_arch = "wasm32"))] +/// Events that multi-threaded [`App`] runners can send back to the main thread. +pub enum AppThreadEvent { + /// The app has sent a task with access to [`ThreadLocals`](bevy_ecs::prelude::ThreadLocals). + RunTask(ThreadLocalTask), + /// The app has exited. + Exit(SubApps), +} + +// TODO: move ThreadLocals over to bevy_app? +impl ThreadLocalTaskSender for std::sync::mpsc::Sender { + fn send_task( + &mut self, + task: ThreadLocalTask, + ) -> Result<(), ThreadLocalTaskSendError> { + self.send(AppThreadEvent::RunTask(task)) + .map_err(|error| ThreadLocalTaskSendError(error.0)) + } +} + +pub type RunnerFn = Box; + +fn run_once(mut app: App) { + while !app.ready() { + #[cfg(not(target_arch = "wasm32"))] + bevy_tasks::tick_global_task_pools_on_main_thread(); + } + app.finish(); + app.cleanup(); + app.update(); + + Some(app) +} + #[allow(clippy::needless_doctest_main)] -/// A container of app logic and data. +/// [`App`] is the primary API for writing user applications. /// -/// Bundles together the necessary elements like [`World`] and [`Schedule`] to create -/// an ECS-based application. It also stores a pointer to a [runner function](Self::set_runner). -/// The runner is responsible for managing the application's event loop and applying the -/// [`Schedule`] to the [`World`] to drive application logic. +/// It automates the setup of the [standard game lifecycle](CoreSet) and +/// provides interface glue for integrating [plugins](`Plugin`). /// /// # Examples /// @@ -55,122 +98,23 @@ pub(crate) enum AppError { /// } /// ``` pub struct App { - /// The main ECS [`World`] of the [`App`]. - /// This stores and provides access to all the main data of the application. - /// The systems of the [`App`] will run using this [`World`]. - /// If additional separate [`World`]-[`Schedule`] pairs are needed, you can use [`sub_app`](App::insert_sub_app)s. - pub world: World, - /// The [runner function](Self::set_runner) is primarily responsible for managing - /// the application's event loop and advancing the [`Schedule`]. - /// Typically, it is not configured manually, but set by one of Bevy's built-in plugins. - /// See `bevy::winit::WinitPlugin` and [`ScheduleRunnerPlugin`](crate::schedule_runner::ScheduleRunnerPlugin). - pub runner: Box, // Send bound is required to make App Send - /// The schedule that systems are added to by default. - /// - /// The schedule that runs the main loop of schedule execution. - /// - /// This is initially set to [`Main`]. - pub main_schedule_label: BoxedScheduleLabel, - sub_apps: HashMap, - plugin_registry: Vec>, - plugin_name_added: HashSet, - /// A private counter to prevent incorrect calls to `App::run()` from `Plugin::build()` - building_plugin_depth: usize, + sub_apps: SubApps, + tls: ThreadLocals, + /// The function that will manage the app's lifecycle. + /// + /// Bevy provides the [`WinitPlugin`] and [`HeadlessPlugin`] for windowed and headless + /// applications, respectively. + /// + /// [`WinitPlugin`]: https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html + /// [`HeadlessPlugin`]: https://docs.rs/bevy/latest/bevy/app/struct.HeadlessPlugin.html + runner: Option, } impl Debug for App { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "App {{ sub_apps: ")?; f.debug_map() - .entries(self.sub_apps.iter().map(|(k, v)| (k, v))) - .finish()?; - write!(f, "}}") - } -} - -/// A [`SubApp`] contains its own [`Schedule`] and [`World`] separate from the main [`App`]. -/// This is useful for situations where data and data processing should be kept completely separate -/// from the main application. The primary use of this feature in bevy is to enable pipelined rendering. -/// -/// # Example -/// -/// ```rust -/// # use bevy_app::{App, AppLabel, SubApp, Main}; -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::schedule::ScheduleLabel; -/// -/// #[derive(Resource, Default)] -/// struct Val(pub i32); -/// -/// #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] -/// struct ExampleApp; -/// -/// let mut app = App::new(); -/// -/// // initialize the main app with a value of 0; -/// app.insert_resource(Val(10)); -/// -/// // create a app with a resource and a single schedule -/// let mut sub_app = App::empty(); -/// // add an outer schedule that runs the main schedule -/// sub_app.insert_resource(Val(100)); -/// -/// // initialize main schedule -/// sub_app.add_systems(Main, |counter: Res| { -/// // since we assigned the value from the main world in extract -/// // we see that value instead of 100 -/// assert_eq!(counter.0, 10); -/// }); -/// -/// // add the sub_app to the app -/// app.insert_sub_app(ExampleApp, SubApp::new(sub_app, |main_world, sub_app| { -/// // extract the value from the main app to the sub app -/// sub_app.world.resource_mut::().0 = main_world.resource::().0; -/// })); -/// -/// // This will run the schedules once, since we're using the default runner -/// app.run(); -/// ``` -pub struct SubApp { - /// The [`SubApp`]'s instance of [`App`] - pub app: App, - - /// A function that allows access to both the main [`App`] [`World`] and the [`SubApp`]. This is - /// useful for moving data between the sub app and the main app. - extract: Box, -} - -impl SubApp { - /// Creates a new [`SubApp`]. - /// - /// The provided function `extract` is normally called by the [`update`](App::update) method. - /// After extract is called, the [`Schedule`] of the sub app is run. The [`World`] - /// parameter represents the main app world, while the [`App`] parameter is just a mutable - /// reference to the `SubApp` itself. - pub fn new(app: App, extract: impl Fn(&mut World, &mut App) + Send + 'static) -> Self { - Self { - app, - extract: Box::new(extract), - } - } - - /// Runs the [`SubApp`]'s default schedule. - pub fn run(&mut self) { - self.app.world.run_schedule(&*self.app.main_schedule_label); - self.app.world.clear_trackers(); - } - - /// Extracts data from main world to this sub-app. - pub fn extract(&mut self, main_world: &mut World) { - (self.extract)(main_world, &mut self.app); - } -} - -impl Debug for SubApp { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "SubApp {{ app: ")?; - f.debug_map() - .entries(self.app.sub_apps.iter().map(|(k, v)| (k, v))) + .entries(self.sub_apps.sub_apps.iter().map(|(k, v)| (k, v))) .finish()?; write!(f, "}}") } @@ -179,9 +123,10 @@ impl Debug for SubApp { impl Default for App { fn default() -> Self { let mut app = App::empty(); + app.sub_apps.main.update_schedule = Some(Box::new(Main)); + #[cfg(feature = "bevy_reflect")] app.init_resource::(); - app.add_plugins(MainSchedulePlugin); app.add_event::(); @@ -194,12 +139,6 @@ impl Default for App { } } -// Dummy plugin used to temporary hold the place in the plugin registry -struct PlaceholderPlugin; -impl Plugin for PlaceholderPlugin { - fn build(&self, _app: &mut App) {} -} - impl App { /// Creates a new [`App`] with some default structure to enable core engine features. /// This is the preferred constructor for most use cases. @@ -211,121 +150,121 @@ impl App { /// /// This constructor should be used if you wish to provide custom scheduling, exit handling, cleanup, etc. pub fn empty() -> App { - let mut world = World::new(); - world.init_resource::(); Self { - world, - runner: Box::new(run_once), - sub_apps: HashMap::default(), - plugin_registry: Vec::default(), - plugin_name_added: Default::default(), - main_schedule_label: Box::new(Main), - building_plugin_depth: 0, + sub_apps: SubApps { + main: SubApp::new(), + sub_apps: HashMap::new(), + }, + tls: ThreadLocals::new(), + runner: Some(Box::new(run_once)), } } - /// Advances the execution of the [`Schedule`] by one cycle. + pub fn into_parts(self) -> (SubApps, ThreadLocals, Option) { + let App { + sub_apps, + tls, + runner, + } = self; + + (sub_apps, tls, runner) + } + + pub fn from_parts(sub_apps: SubApps, tls: ThreadLocals, runner: Option) -> Self { + App { + sub_apps, + tls, + runner, + } + } + + /// Updates all sub-apps starting with the main app. /// - /// This method also updates sub apps. /// See [`insert_sub_app`](Self::insert_sub_app) for more details. - /// - /// The schedule run by this method is determined by the [`main_schedule_label`](App) field. - /// By default this is [`Main`]. - /// - /// # Panics - /// - /// The active schedule of the app must be set before this method is called. pub fn update(&mut self) { - #[cfg(feature = "trace")] - let _bevy_update_span = info_span!("update").entered(); + let (mut sub_apps, mut tls, runner) = std::mem::take(self).into_parts(); + + // create event loop channel + let (send, recv) = std::sync::mpsc::channel(); + + // insert TLS accessor + sub_apps.for_each(|sub_app| { + // SAFETY: `tls` is not moved or dropped until `access` has been dropped. + let access = + unsafe { ThreadLocalAccessor::new(std::ptr::addr_of_mut!(tls), send.clone()) }; + sub_app.world.insert_resource(access); + }); + + #[cfg(not(target_arch = "wasm32"))] { - #[cfg(feature = "trace")] - let _bevy_main_update_span = info_span!("main app").entered(); - self.world.run_schedule(&*self.main_schedule_label); + // Move sub-apps to another thread and run an event loop in this thread. + let (send, recv) = std::sync::mpsc::channel(); + let handle = std::thread::spawn(move || { + sub_apps.update(); + send.send(AppThreadEvent::Exit(sub_apps)).unwrap(); + }); + + loop { + let event = recv.recv().unwrap(); + match event { + AppThreadEvent::RunTask(f) => { + f(&mut tls); + } + AppThreadEvent::Exit(sub_apps) => { + handle.join(); + break; + } + } + } } - for (_label, sub_app) in self.sub_apps.iter_mut() { - #[cfg(feature = "trace")] - let _sub_app_span = info_span!("sub app", name = ?_label).entered(); - sub_app.extract(&mut self.world); - sub_app.run(); + + #[cfg(target_arch = "wasm32")] + { + sub_apps.update(); } - self.world.clear_trackers(); + // remove TLS accessor + sub_apps.for_each(|sub_app| sub_app.world.remove_resource::()); + + *self = App::from_parts(sub_apps, tls, runner); } - /// Starts the application by calling the app's [runner function](Self::set_runner). + /// Runs the [`App`] by calling its [runner](Self::set_runner). /// - /// Finalizes the [`App`] configuration. For general usage, see the example on the item + /// This will (re)build the [`App`] first. For general usage, see the example on the item /// level documentation. /// - /// # `run()` might not return - /// - /// Calls to [`App::run()`] might never return. + /// # Caveats /// - /// In simple and *headless* applications, one can expect that execution will - /// proceed, normally, after calling [`run()`](App::run()) but this is not the case for - /// windowed applications. + /// **This method is not required to return.** /// - /// Windowed apps are typically driven by an *event loop* or *message loop* and - /// some window-manager APIs expect programs to terminate when their primary - /// window is closed and that event loop terminates – behavior of processes that - /// do not is often platform dependent or undocumented. + /// Headless apps can generally expect this method to return control to the caller when + /// it completes, but that is not the case for windowed apps. Windowed apps are typically + /// driven by an event loop and some platforms expect the program to terminate when the + /// event loop ends. See [`WinitSettings::return_from_run`] for more details. /// - /// By default, *Bevy* uses the `winit` crate for window creation. See - /// [`WinitSettings::return_from_run`](https://docs.rs/bevy/latest/bevy/winit/struct.WinitSettings.html#structfield.return_from_run) - /// for further discussion of this topic and for a mechanism to require that [`App::run()`] - /// *does* return – albeit one that carries its own caveats and disclaimers. + /// Bevy uses `winit` as its default window manager. See [`WinitSettings::return_from_run`] + /// for a mechanism (with many additional caveats) that will guarantee [`App::run()`] returns + /// to the caller. /// /// # Panics /// - /// Panics if called from `Plugin::build()`, because it would prevent other plugins to properly build. + /// Panics if not all plugins have been built. + /// + /// [`WinitSettings::return_from_run`]: https://docs.rs/bevy/latest/bevy/winit/struct.WinitSettings.html#structfield.return_from_run pub fn run(&mut self) { #[cfg(feature = "trace")] let _bevy_app_run_span = info_span!("bevy_app").entered(); - let mut app = std::mem::replace(self, App::empty()); - if app.building_plugin_depth > 0 { + if !self.sub_apps.sub_apps.values().all(|s| s.can_update()) { panic!("App::run() was called from within Plugin::build(), which is not allowed."); } - let runner = std::mem::replace(&mut app.runner, Box::new(run_once)); + let mut app = std::mem::replace(self, App::empty()); + let runner = app.runner.take().unwrap(); (runner)(app); } - /// Check that [`Plugin::ready`] of all plugins returns true. This is usually called by the - /// event loop, but can be useful for situations where you want to use [`App::update`] - pub fn ready(&self) -> bool { - for plugin in &self.plugin_registry { - if !plugin.ready(self) { - return false; - } - } - true - } - - /// Run [`Plugin::finish`] for each plugin. This is usually called by the event loop once all - /// plugins are [`App::ready`], but can be useful for situations where you want to use - /// [`App::update`]. - pub fn finish(&mut self) { - // temporarily remove the plugin registry to run each plugin's setup function on app. - let plugin_registry = std::mem::take(&mut self.plugin_registry); - for plugin in &plugin_registry { - plugin.finish(self); - } - self.plugin_registry = plugin_registry; - } - - /// Run [`Plugin::cleanup`] for each plugin. This is usually called by the event loop after - /// [`App::finish`], but can be useful for situations where you want to use [`App::update`]. - pub fn cleanup(&mut self) { - // temporarily remove the plugin registry to run each plugin's setup function on app. - let plugin_registry = std::mem::take(&mut self.plugin_registry); - for plugin in &plugin_registry { - plugin.cleanup(self); - } - self.plugin_registry = plugin_registry; - } - /// Adds [`State`] and [`NextState`] resources, [`OnEnter`] and [`OnExit`] schedules /// for each state variant (if they don't already exist), an instance of [`apply_state_transition::`] in /// [`StateTransition`] so that transitions happen before [`Update`] and @@ -339,48 +278,10 @@ impl App { /// Note that you can also apply state transitions at other points in the schedule /// by adding the [`apply_state_transition`] system manually. pub fn add_state(&mut self) -> &mut Self { - self.init_resource::>() - .init_resource::>() - .add_systems( - StateTransition, - ( - run_enter_schedule::.run_if(run_once_condition()), - apply_state_transition::, - ) - .chain(), - ); - - // The OnEnter, OnExit, and OnTransition schedules are lazily initialized - // (i.e. when the first system is added to them), and World::try_run_schedule is used to fail - // gracefully if they aren't present. - + self.sub_apps.main.add_state::(); self } - /// Adds a system to the default system set and schedule of the app's [`Schedules`]. - /// - /// Refer to the [system module documentation](bevy_ecs::system) to see how a system - /// can be defined. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # fn my_system() {} - /// # let mut app = App::new(); - /// # - /// app.add_system(my_system); - /// ``` - #[deprecated( - since = "0.11.0", - note = "Please use `add_systems` instead. If you didn't change the default base set, you should use `add_systems(Update, your_system).`" - )] - pub fn add_system(&mut self, system: impl IntoSystemConfigs) -> &mut Self { - self.add_systems(Update, system) - } - /// Adds a system to the given schedule in this app's [`Schedules`]. /// /// # Examples @@ -403,103 +304,67 @@ impl App { schedule: impl ScheduleLabel, systems: impl IntoSystemConfigs, ) -> &mut Self { - let mut schedules = self.world.resource_mut::(); - - if let Some(schedule) = schedules.get_mut(&schedule) { - schedule.add_systems(systems); - } else { - let mut new_schedule = Schedule::new(); - new_schedule.add_systems(systems); - schedules.insert(schedule, new_schedule); - } - + self.sub_apps.main.add_systems(schedule, systems); self } - /// Adds a system to [`Startup`]. - /// - /// These systems will run exactly once, at the start of the [`App`]'s lifecycle. - /// To add a system that runs every frame, see [`add_system`](Self::add_system). - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// fn my_startup_system(_commands: Commands) { - /// println!("My startup system"); - /// } - /// - /// App::new() - /// .add_systems(Startup, my_startup_system); - /// ``` - #[deprecated( - since = "0.11.0", - note = "Please use `add_systems` instead. If you didn't change the default base set, you should use `add_systems(Startup, your_system).`" - )] - pub fn add_startup_system(&mut self, system: impl IntoSystemConfigs) -> &mut Self { - self.add_systems(Startup, system) - } - - /// Adds a collection of systems to [`Startup`]. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut app = App::new(); - /// # fn startup_system_a() {} - /// # fn startup_system_b() {} - /// # fn startup_system_c() {} - /// # - /// app.add_systems(Startup, ( - /// startup_system_a, - /// startup_system_b, - /// startup_system_c, - /// )); - /// ``` - #[deprecated( - since = "0.11.0", - note = "Please use `add_systems` instead. If you didn't change the default base set, you should use `add_systems(Startup, your_system).`" - )] - pub fn add_startup_systems(&mut self, systems: impl IntoSystemConfigs) -> &mut Self { - self.add_systems(Startup, systems.into_configs()) - } - - /// Configures a system set in the default schedule, adding the set if it does not exist. + /// Configures a system set in the given schedule, adding it if it doesn't exist. pub fn configure_set( &mut self, schedule: impl ScheduleLabel, set: impl IntoSystemSetConfig, ) -> &mut Self { - let mut schedules = self.world.resource_mut::(); - if let Some(schedule) = schedules.get_mut(&schedule) { - schedule.configure_set(set); - } else { - let mut new_schedule = Schedule::new(); - new_schedule.configure_set(set); - schedules.insert(schedule, new_schedule); - } + self.sub_apps.main.configure_set(schedule, set); self } - /// Configures a collection of system sets in the default schedule, adding any sets that do not exist. + /// Configures a collection of system sets in the given schedule, adding any sets that do not exist. pub fn configure_sets( &mut self, schedule: impl ScheduleLabel, sets: impl IntoSystemSetConfigs, ) -> &mut Self { - let mut schedules = self.world.resource_mut::(); - if let Some(schedule) = schedules.get_mut(&schedule) { - schedule.configure_sets(sets); - } else { - let mut new_schedule = Schedule::new(); - new_schedule.configure_sets(sets); - schedules.insert(schedule, new_schedule); - } + self.sub_apps.main.configure_sets(schedule, sets); + self + } + + /// Adds a new `schedule` to the [`App`] under the provided `label`. + /// + /// # Warning + /// This method will overwrite any existing schedule at that label. + /// To avoid this behavior, use the `init_schedule` method instead. + pub fn add_schedule(&mut self, label: impl ScheduleLabel, schedule: Schedule) -> &mut Self { + self.sub_apps.main.add_schedule(label, schedule); + self + } + + /// Adds an empty `schedule` to the [`App`] under the provided `label` if it does not exist. + /// + /// See [`App::add_schedule`] to pass in a pre-constructed schedule. + pub fn init_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { + self.sub_apps.main.init_schedule(label); + self + } + + /// Returns a reference to the [`Schedule`] with the provided `label` if it exists. + pub fn get_schedule(&self, label: impl ScheduleLabel) -> Option<&Schedule> { + self.sub_apps.main.get_schedule(label) + } + + /// Returns a mutable reference to the [`Schedule`] with the provided `label` if it exists. + pub fn get_schedule_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> { + self.sub_apps.main.get_schedule_mut(label) + } + + /// Runs function `f` with the [`Schedule`] associated with `label`. + /// + /// **Note:** This will create the schedule if it does not already exist. + pub fn edit_schedule( + &mut self, + label: impl ScheduleLabel, + mut f: impl FnMut(&mut Schedule), + ) -> &mut Self { + self.sub_apps.main.edit_schedule(label, f); self } @@ -526,10 +391,7 @@ impl App { where T: Event, { - if !self.world.contains_resource::>() { - self.init_resource::>() - .add_systems(First, Events::::update_system); - } + self.sub_apps.main.add_event::(); self } @@ -555,30 +417,7 @@ impl App { /// .insert_resource(MyCounter { counter: 0 }); /// ``` pub fn insert_resource(&mut self, resource: R) -> &mut Self { - self.world.insert_resource(resource); - self - } - - /// Inserts a non-send resource to the app. - /// - /// You usually want to use [`insert_resource`](Self::insert_resource), - /// but there are some special cases when a resource cannot be sent across threads. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// struct MyCounter { - /// counter: usize, - /// } - /// - /// App::new() - /// .insert_non_send_resource(MyCounter { counter: 0 }); - /// ``` - pub fn insert_non_send_resource(&mut self, resource: R) -> &mut Self { - self.world.insert_non_send_resource(resource); + self.sub_apps.main.insert_resource(resource); self } @@ -613,7 +452,30 @@ impl App { /// .init_resource::(); /// ``` pub fn init_resource(&mut self) -> &mut Self { - self.world.init_resource::(); + self.sub_apps.main.init_resource::(); + self + } + + /// Inserts a non-send resource to the app. + /// + /// You usually want to use [`insert_resource`](Self::insert_resource), + /// but there are some special cases when a resource cannot be sent across threads. + /// + /// # Examples + /// + /// ``` + /// # use bevy_app::prelude::*; + /// # use bevy_ecs::prelude::*; + /// # + /// struct MyCounter { + /// counter: usize, + /// } + /// + /// App::new() + /// .insert_non_send_resource(MyCounter { counter: 0 }); + /// ``` + pub fn insert_non_send_resource(&mut self, resource: R) -> &mut Self { + self.tls.insert_resource(resource); self } @@ -623,7 +485,7 @@ impl App { /// If the [`Default`] trait is implemented, the [`FromWorld`] trait will use /// the [`Default::default`] method to initialize the [`Resource`]. pub fn init_non_send_resource(&mut self) -> &mut Self { - self.world.init_non_send_resource::(); + self.tls.init_resource::(); self } @@ -651,91 +513,66 @@ impl App { /// App::new() /// .set_runner(my_runner); /// ``` - pub fn set_runner(&mut self, run_fn: impl FnOnce(App) + 'static + Send) -> &mut Self { - self.runner = Box::new(run_fn); + pub fn set_runner(&mut self, f: impl FnOnce(App) + 'static) -> &mut Self { + self.runner = Box::new(f); self } - /// Adds a single [`Plugin`]. + /// Adds a [`Plugin`] collection to the app. /// /// One of Bevy's core principles is modularity. All Bevy engine features are implemented /// as [`Plugin`]s. This includes internal features like the renderer. /// - /// Bevy also provides a few sets of default [`Plugin`]s. See [`add_plugins`](Self::add_plugins). + /// [`Plugin`]s can be grouped into a set by using a [`PluginGroup`]. /// - /// # Examples + /// There are built-in [`PluginGroup`]s that provide core engine functionality. + /// The [`PluginGroup`]s available by default are `DefaultPlugins` and `MinimalPlugins`. + /// + /// To customize the plugins in the group (reorder, disable a plugin, add a new plugin + /// before / after another plugin), call [`build()`](super::PluginGroup::build) on the group, + /// which will convert it to a [`PluginGroupBuilder`](crate::PluginGroupBuilder). + /// + /// You can also specify a group of [`Plugin`]s by using a tuple over [`Plugin`]s and + /// [`PluginGroup`]s. See [`Plugins`] for more details. /// + /// ## Examples /// ``` - /// # use bevy_app::prelude::*; + /// # use bevy_app::{prelude::*, PluginGroupBuilder, NoopPluginGroup as MinimalPlugins}; /// # /// # // Dummies created to avoid using `bevy_log`, /// # // which pulls in too many dependencies and breaks rust-analyzer - /// # pub mod bevy_log { - /// # use bevy_app::prelude::*; - /// # #[derive(Default)] - /// # pub struct LogPlugin; - /// # impl Plugin for LogPlugin{ - /// # fn build(&self, app: &mut App) {} - /// # } + /// # pub struct LogPlugin; + /// # impl Plugin for LogPlugin { + /// # fn build(&self, app: &mut App) {} /// # } - /// App::new().add_plugin(bevy_log::LogPlugin::default()); + /// App::new() + /// .add_plugins(MinimalPlugins); + /// App::new() + /// .add_plugins((MinimalPlugins, LogPlugin)); /// ``` /// /// # Panics /// - /// Panics if the plugin was already added to the application. - #[deprecated(since = "0.11.0", note = "Please use `add_plugins` instead.")] - pub fn add_plugin(&mut self, plugin: T) -> &mut Self - where - T: Plugin, - { - self.add_plugins(plugin) - } - - /// Boxed variant of [`add_plugin`](App::add_plugin) that can be used from a - /// [`PluginGroup`](super::PluginGroup) - pub(crate) fn add_boxed_plugin( - &mut self, - plugin: Box, - ) -> Result<&mut Self, AppError> { - debug!("added plugin: {}", plugin.name()); - if plugin.is_unique() && !self.plugin_name_added.insert(plugin.name().to_string()) { - Err(AppError::DuplicatePlugin { - plugin_name: plugin.name().to_string(), - })?; - } - - // Reserve that position in the plugin registry. if a plugin adds plugins, they will be correctly ordered - let plugin_position_in_registry = self.plugin_registry.len(); - self.plugin_registry.push(Box::new(PlaceholderPlugin)); - - self.building_plugin_depth += 1; - let result = catch_unwind(AssertUnwindSafe(|| plugin.build(self))); - self.building_plugin_depth -= 1; - if let Err(payload) = result { - resume_unwind(payload); - } - self.plugin_registry[plugin_position_in_registry] = plugin; - Ok(self) + /// Panics if one of the plugins was already added to the application. + /// + /// [`PluginGroup`]:super::PluginGroup + pub fn add_plugins(&mut self, plugins: impl Plugins) -> &mut Self { + plugins.add_to_app(self); + self } - /// Checks if a [`Plugin`] has already been added. - /// - /// This can be used by plugins to check if a plugin they depend upon has already been - /// added. + /// Returns `true` if the [`Plugin`] has already been added. pub fn is_plugin_added(&self) -> bool where T: Plugin, { - self.plugin_registry - .iter() - .any(|p| p.downcast_ref::().is_some()) + self.sub_apps.main.is_plugin_added::() } - /// Returns a vector of references to any plugins of type `T` that have been added. + /// Returns a vector of references to all plugins of type `T` that have been added. /// - /// This can be used to read the settings of any already added plugins. - /// This vector will be length zero if no plugins of that type have been added. + /// This can be used to read the settings of any existing plugins. + /// This vector will be empty if no plugins of that type have been added. /// If multiple copies of the same plugin are added to the [`App`], they will be listed in insertion order in this vector. /// /// ```rust @@ -755,53 +592,93 @@ impl App { where T: Plugin, { - self.plugin_registry - .iter() - .filter_map(|p| p.downcast_ref()) - .collect() + self.sub_apps.main.get_added_plugins::() } - /// Adds one or more [`Plugin`]s. - /// - /// One of Bevy's core principles is modularity. All Bevy engine features are implemented - /// as [`Plugin`]s. This includes internal features like the renderer. - /// - /// [`Plugin`]s can be grouped into a set by using a [`PluginGroup`]. - /// - /// There are built-in [`PluginGroup`]s that provide core engine functionality. - /// The [`PluginGroup`]s available by default are `DefaultPlugins` and `MinimalPlugins`. - /// - /// To customize the plugins in the group (reorder, disable a plugin, add a new plugin - /// before / after another plugin), call [`build()`](super::PluginGroup::build) on the group, - /// which will convert it to a [`PluginGroupBuilder`](crate::PluginGroupBuilder). + /// Returns `true` if [`Plugin::ready`] returns `true` for all plugins. This is usually called by the + /// event loop, but can be useful for situations where you want to use [`App::update`] + pub fn is_ready(&self) -> bool { + self.sub_apps.main.is_ready() && self.sub_apps.sub_apps.values().all(|s| s.is_ready()) + } + + /// Runs [`Plugin::finish`] for each plugin. This is usually called by the event loop once all + /// plugins are [`App::ready`], but can be useful for situations where you want to use + /// [`App::update`]. + pub fn finish(&mut self) { + self.sub_apps.main.finish(); + self.sub_apps.sub_apps.value_mut().for_each(|s| s.finish()); + } + + /// Runs [`Plugin::cleanup`] for each plugin. This is usually called by the event loop after + /// [`App::finish`], but can be useful for situations where you want to use [`App::update`]. + pub fn cleanup(&mut self) { + self.sub_apps.main.cleanup(); + self.sub_apps.sub_apps.value_mut().for_each(|s| s.cleanup()); + } + + /// Returns a reference to the [`SubApp`] with the given label. /// - /// You can also specify a group of [`Plugin`]s by using a tuple over [`Plugin`]s and - /// [`PluginGroup`]s. See [`Plugins`] for more details. + /// # Panics /// - /// ## Examples - /// ``` - /// # use bevy_app::{prelude::*, PluginGroupBuilder, NoopPluginGroup as MinimalPlugins}; - /// # - /// # // Dummies created to avoid using `bevy_log`, - /// # // which pulls in too many dependencies and breaks rust-analyzer - /// # pub struct LogPlugin; - /// # impl Plugin for LogPlugin { - /// # fn build(&self, app: &mut App) {} - /// # } - /// App::new() - /// .add_plugins(MinimalPlugins); - /// App::new() - /// .add_plugins((MinimalPlugins, LogPlugin)); - /// ``` + /// Panics if the sub-app doesn't exist. + pub fn sub_app(&self, label: dyn AsRef) -> &SubApp { + self.get_sub_app(label).unwrap_or_else(|| { + panic!("No sub-app with label '{:?}' exists.", label.as_str()); + }) + } + + /// Returns a reference to the [`SubApp`] with the given label. /// /// # Panics /// - /// Panics if one of the plugins was already added to the application. - /// - /// [`PluginGroup`]:super::PluginGroup - pub fn add_plugins(&mut self, plugins: impl Plugins) -> &mut Self { - plugins.add_to_app(self); - self + /// Panics if the reference to a sub-app doesn't exist. + pub fn sub_app_mut(&mut self, label: dyn AsRef) -> &mut SubApp { + self.get_sub_app_mut(label).unwrap_or_else(|| { + panic!("No sub-app with label '{:?}' exists.", label.as_str()); + }) + } + + /// Returns a reference to the [`SubApp`] with the given label, if it exists. + /// Otherwise, returns the label. + pub fn get_sub_app(&self, label: dyn AsRef) -> Option<&SubApp> { + self.sub_apps.sub_apps.get(&label.as_label()) + } + + /// Returns a mutable reference to the [`SubApp`] with the given label, if it exists. + /// Otherwise, returns the label. + pub fn get_sub_app_mut(&mut self, label: dyn AsRef) -> Option<&mut SubApp> { + let label_id = label.as_label(); + self.sub_apps.sub_apps.get_mut(&label_id) + } + + /// Inserts a [`SubApp`] with the given label. + pub fn insert_sub_app(&mut self, label: impl AppLabel, sub_app: SubApp) { + let label_id = label.as_label(); + self.sub_apps.sub_apps.insert(label_id, sub_app); + } + + /// Removes the [`SubApp`] with the given label, if it exists. + pub fn remove_sub_app(&mut self, label: dyn AsRef) -> Option { + let label_id = label.as_label(); + self.sub_apps.sub_apps.remove(&label_id) + } + + /// Run a function `f` with the specified sub-app as the default "main" one. + pub fn with_default_sub_app(&mut self, label: dyn AsRef, mut f: F) + where + F: FnMut(&mut App), + { + let label_id = label.as_label(); + // Remove sub-app. + let sub = self.sub_apps.sub_apps.remove(&label_id).unwrap_or_default(); + // Replace main with sub-app. + let main = std::mem::replace(&mut self.sub_apps.main, sub); + // Run function. + f(self); + // Put main back. + let sub = std::mem::replace(&mut self.sub_apps.main, main); + // Put sub-app back. + self.sub_apps.sub_apps.insert(label_id, sub); } /// Registers the type `T` in the [`TypeRegistry`](bevy_reflect::TypeRegistry) resource, @@ -814,8 +691,7 @@ impl App { /// See [`bevy_reflect::TypeRegistry::register`]. #[cfg(feature = "bevy_reflect")] pub fn register_type(&mut self) -> &mut Self { - let registry = self.world.resource_mut::(); - registry.write().register::(); + self.sub_apps.main.register_type::(); self } @@ -845,147 +721,11 @@ impl App { >( &mut self, ) -> &mut Self { - let registry = self.world.resource_mut::(); - registry.write().register_type_data::(); - self - } - - /// Retrieves a `SubApp` stored inside this [`App`]. - /// - /// # Panics - /// - /// Panics if the `SubApp` doesn't exist. - pub fn sub_app_mut(&mut self, label: impl AppLabel) -> &mut App { - match self.get_sub_app_mut(label) { - Ok(app) => app, - Err(label) => panic!("Sub-App with label '{:?}' does not exist", label.as_str()), - } - } - - /// Retrieves a `SubApp` inside this [`App`] with the given label, if it exists. Otherwise returns - /// an [`Err`] containing the given label. - pub fn get_sub_app_mut(&mut self, label: impl AppLabel) -> Result<&mut App, AppLabelId> { - let label = label.as_label(); - self.sub_apps - .get_mut(&label) - .map(|sub_app| &mut sub_app.app) - .ok_or(label) - } - - /// Retrieves a `SubApp` stored inside this [`App`]. - /// - /// # Panics - /// - /// Panics if the `SubApp` doesn't exist. - pub fn sub_app(&self, label: impl AppLabel) -> &App { - match self.get_sub_app(label) { - Ok(app) => app, - Err(label) => panic!("Sub-App with label '{:?}' does not exist", label.as_str()), - } - } - - /// Inserts an existing sub app into the app - pub fn insert_sub_app(&mut self, label: impl AppLabel, sub_app: SubApp) { - self.sub_apps.insert(label.as_label(), sub_app); - } - - /// Removes a sub app from the app. Returns [`None`] if the label doesn't exist. - pub fn remove_sub_app(&mut self, label: impl AppLabel) -> Option { - self.sub_apps.remove(&label.as_label()) - } - - /// Retrieves a `SubApp` inside this [`App`] with the given label, if it exists. Otherwise returns - /// an [`Err`] containing the given label. - pub fn get_sub_app(&self, label: impl AppLabel) -> Result<&App, impl AppLabel> { - self.sub_apps - .get(&label.as_label()) - .map(|sub_app| &sub_app.app) - .ok_or(label) - } - - /// Adds a new `schedule` to the [`App`] under the provided `label`. - /// - /// # Warning - /// This method will overwrite any existing schedule at that label. - /// To avoid this behavior, use the `init_schedule` method instead. - pub fn add_schedule(&mut self, label: impl ScheduleLabel, schedule: Schedule) -> &mut Self { - let mut schedules = self.world.resource_mut::(); - schedules.insert(label, schedule); - - self - } - - /// Initializes a new empty `schedule` to the [`App`] under the provided `label` if it does not exists. - /// - /// See [`App::add_schedule`] to pass in a pre-constructed schedule. - pub fn init_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { - let mut schedules = self.world.resource_mut::(); - if !schedules.contains(&label) { - schedules.insert(label, Schedule::new()); - } - self - } - - /// Gets read-only access to the [`Schedule`] with the provided `label` if it exists. - pub fn get_schedule(&self, label: impl ScheduleLabel) -> Option<&Schedule> { - let schedules = self.world.get_resource::()?; - schedules.get(&label) - } - - /// Gets read-write access to a [`Schedule`] with the provided `label` if it exists. - pub fn get_schedule_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> { - let schedules = self.world.get_resource_mut::()?; - // We need to call .into_inner here to satisfy the borrow checker: - // it can reason about reborrows using ordinary references but not the `Mut` smart pointer. - schedules.into_inner().get_mut(&label) - } - - /// Applies the function to the [`Schedule`] associated with `label`. - /// - /// **Note:** This will create the schedule if it does not already exist. - pub fn edit_schedule( - &mut self, - label: impl ScheduleLabel, - f: impl FnOnce(&mut Schedule), - ) -> &mut Self { - let mut schedules = self.world.resource_mut::(); - - if schedules.get(&label).is_none() { - schedules.insert(label.dyn_clone(), Schedule::new()); - } - - let schedule = schedules.get_mut(&label).unwrap(); - // Call the function f, passing in the schedule retrieved - f(schedule); - + self.sub_apps.main.register_type_data::(); self } } -fn run_once(mut app: App) { - while !app.ready() { - #[cfg(not(target_arch = "wasm32"))] - bevy_tasks::tick_global_task_pools_on_main_thread(); - } - app.finish(); - app.cleanup(); - - app.update(); -} - -/// An event that indicates the [`App`] should exit. This will fully exit the app process at the -/// start of the next tick of the schedule. -/// -/// You can also use this event to detect that an exit was requested. In order to receive it, systems -/// subscribing to this event should run after it was emitted and before the schedule of the same -/// frame is over. This is important since [`App::run()`] might never return. -/// -/// If you don't require access to other components or resources, consider implementing the [`Drop`] -/// trait on components/resources for code that runs on exit. That saves you from worrying about -/// system schedule ordering, and is idiomatic Rust. -#[derive(Event, Debug, Clone, Default)] -pub struct AppExit; - #[cfg(test)] mod tests { use bevy_ecs::{ diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 1941392ee01942..4cd295c950471d 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -8,6 +8,7 @@ mod main_schedule; mod plugin; mod plugin_group; mod schedule_runner; +mod sub_app; #[cfg(feature = "bevy_ci_testing")] pub mod ci_testing; @@ -18,6 +19,7 @@ pub use main_schedule::*; pub use plugin::*; pub use plugin_group::*; pub use schedule_runner::*; +pub use sub_app::*; #[allow(missing_docs)] pub mod prelude { @@ -28,6 +30,7 @@ pub mod prelude { First, FixedUpdate, Last, Main, PostStartup, PostUpdate, PreStartup, PreUpdate, Startup, StateTransition, Update, }, + sub_app::SubApp, DynamicPlugin, Plugin, PluginGroup, }; } diff --git a/crates/bevy_app/src/plugin.rs b/crates/bevy_app/src/plugin.rs index 12e7d1f1c8d66f..c63b50139afb04 100644 --- a/crates/bevy_app/src/plugin.rs +++ b/crates/bevy_app/src/plugin.rs @@ -25,7 +25,7 @@ pub trait Plugin: Downcast + Any + Send + Sync { /// Configures the [`App`] to which this plugin is added. fn build(&self, app: &mut App); - /// Has the plugin finished it's setup? This can be useful for plugins that needs something + /// Has the plugin finished its setup? This can be useful for plugins that needs something /// asynchronous to happen before they can finish their setup, like renderer initialization. /// Once the plugin is ready, [`finish`](Plugin::finish) should be called. fn ready(&self, _app: &App) -> bool { @@ -60,6 +60,13 @@ pub trait Plugin: Downcast + Any + Send + Sync { impl_downcast!(Plugin); +/// A dummy plugin that's to temporarily occupy an entry in an app's plugin registry. +pub(crate) struct PlaceholderPlugin; + +impl Plugin for PlaceholderPlugin { + fn build(&self, _app: &mut App) {} +} + /// A type representing an unsafe function that returns a mutable pointer to a [`Plugin`]. /// It is used for dynamically loading plugins. /// diff --git a/crates/bevy_app/src/schedule_runner.rs b/crates/bevy_app/src/schedule_runner.rs index d303769f926960..09bda8eb1950ea 100644 --- a/crates/bevy_app/src/schedule_runner.rs +++ b/crates/bevy_app/src/schedule_runner.rs @@ -1,8 +1,9 @@ use crate::{ - app::{App, AppExit}, + app::{App, AppExit, AppThreadEvent, SubApps}, plugin::Plugin, }; use bevy_ecs::event::{Events, ManualEventReader}; +use bevy_ecs::storage::ThreadLocalAccessor; use bevy_utils::{Duration, Instant}; #[cfg(target_arch = "wasm32")] @@ -10,59 +11,55 @@ use std::{cell::RefCell, rc::Rc}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::{prelude::*, JsCast}; -/// Determines the method used to run an [`App`]'s [`Schedule`](bevy_ecs::schedule::Schedule). -/// -/// It is used in the [`ScheduleRunnerPlugin`]. +/// Determines how frequently the [`App`] should be updated by the [`ScheduleRunnerPlugin`]. #[derive(Copy, Clone, Debug)] pub enum RunMode { - /// Indicates that the [`App`]'s schedule should run repeatedly. + /// The [`App`] will update once. + Once, + /// The [`App`] will update over and over, until an [`AppExit`] event appears. Loop { - /// The minimum [`Duration`] to wait after a [`Schedule`](bevy_ecs::schedule::Schedule) - /// has completed before repeating. A value of [`None`] will not wait. - wait: Option, + /// The minimum time from the start of one update to the next. + /// + /// **Note:** This has no upper limit, but the [`App`] will hang if you set this too high. + wait: Duration, }, - /// Indicates that the [`App`]'s schedule should run only once. - Once, } impl Default for RunMode { fn default() -> Self { - RunMode::Loop { wait: None } + RunMode::Loop { + wait: Duration::ZERO, + } } } -/// Configures an [`App`] to run its [`Schedule`](bevy_ecs::schedule::Schedule) according to a given -/// [`RunMode`]. +/// Runs an [`App`] according to the selected [`RunMode`]. /// -/// [`ScheduleRunnerPlugin`] is included in the -/// [`MinimalPlugins`](https://docs.rs/bevy/latest/bevy/struct.MinimalPlugins.html) plugin group. +/// This plugin is included in the [`MinimalPlugins`] group, but **not** included in the +/// [`DefaultPlugins`] group. [`DefaultPlugins`] assumes the [`App`] will render to a window, +/// so it comes with the [`WinitPlugin`] instead. /// -/// [`ScheduleRunnerPlugin`] is *not* included in the -/// [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html) plugin group -/// which assumes that the [`Schedule`](bevy_ecs::schedule::Schedule) will be executed by other means: -/// typically, the `winit` event loop -/// (see [`WinitPlugin`](https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html)) -/// executes the schedule making [`ScheduleRunnerPlugin`] unnecessary. +/// [`DefaultPlugins`]: https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html +/// [`MinimalPlugins`]: https://docs.rs/bevy/latest/bevy/struct.MinimalPlugins.html +/// [`WinitPlugin`]: https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html #[derive(Default)] pub struct ScheduleRunnerPlugin { - /// Determines whether the [`Schedule`](bevy_ecs::schedule::Schedule) is run once or repeatedly. + /// Determines how frequently the [`App`] should update. pub run_mode: RunMode, } impl ScheduleRunnerPlugin { /// See [`RunMode::Once`]. pub fn run_once() -> Self { - ScheduleRunnerPlugin { + Self { run_mode: RunMode::Once, } } /// See [`RunMode::Loop`]. - pub fn run_loop(wait_duration: Duration) -> Self { - ScheduleRunnerPlugin { - run_mode: RunMode::Loop { - wait: Some(wait_duration), - }, + pub fn run_loop(wait: Duration) -> Self { + Self { + run_mode: RunMode::Loop { wait }, } } } @@ -71,61 +68,77 @@ impl Plugin for ScheduleRunnerPlugin { fn build(&self, app: &mut App) { let run_mode = self.run_mode; app.set_runner(move |mut app: App| { - while !app.ready() { + while !app.is_ready() { #[cfg(not(target_arch = "wasm32"))] bevy_tasks::tick_global_task_pools_on_main_thread(); } app.finish(); app.cleanup(); - let mut app_exit_event_reader = ManualEventReader::::default(); + let mut exit_event_reader = ManualEventReader::::default(); match run_mode { RunMode::Once => { app.update(); } RunMode::Loop { wait } => { - let mut tick = move |app: &mut App, - wait: Option| - -> Result, AppExit> { + let mut update = move |sub_apps: &mut SubApps| -> Result { let start_time = Instant::now(); + sub_apps.update(); + let end_time = Instant::now(); - if let Some(app_exit_events) = - app.world.get_resource_mut::>() + if let Some(exit_events) = + sub_apps.main.world.get_resource_mut::>() { - if let Some(exit) = app_exit_event_reader.iter(&app_exit_events).last() - { + if let Some(exit) = exit_event_reader.iter(&exit_events).last() { return Err(exit.clone()); } } - app.update(); - - if let Some(app_exit_events) = - app.world.get_resource_mut::>() - { - if let Some(exit) = app_exit_event_reader.iter(&app_exit_events).last() - { - return Err(exit.clone()); - } + let elapsed = end_time - start_time; + if elapsed < wait { + return Ok(wait - elapsed); } - let end_time = Instant::now(); + Ok(Duration::ZERO) + }; - if let Some(wait) = wait { - let exe_time = end_time - start_time; - if exe_time < wait { - return Ok(Some(wait - exe_time)); - } - } + let (mut sub_apps, mut tls, _) = app.into_parts(); - Ok(None) - }; + // create event loop channel + let (send, recv) = std::sync::mpsc::channel(); + + // insert TLS accessor + sub_apps.for_each(|sub_app| { + // SAFETY: `tls` is not moved or dropped until `access` has been dropped. + let access = unsafe { + ThreadLocalAccessor::new(std::ptr::addr_of_mut!(tls), send.clone()) + }; + sub_app.world.insert_resource(access); + }); #[cfg(not(target_arch = "wasm32"))] { - while let Ok(delay) = tick(&mut app, wait) { - if let Some(delay) = delay { - std::thread::sleep(delay); + // Move sub-apps to another thread and run an event loop in this thread. + let handle = std::thread::spawn(move || { + while let Ok(sleep) = update(&mut sub_apps) { + if !sleep.is_zero() { + std::thread::sleep(sleep); + } + } + + send.send(AppThreadEvent::Exit(sub_apps)); + }); + + loop { + let event = recv.recv().unwrap(); + match event { + AppThreadEvent::RunTask(f) => { + f(&mut tls); + } + AppThreadEvent::Exit(sub_apps) => { + handle.join(); + break; + } } } } @@ -141,25 +154,32 @@ impl Plugin for ScheduleRunnerPlugin { ) .expect("Should register `setTimeout`."); } - let asap = Duration::from_millis(1); - let mut rc = Rc::new(app); + let min_sleep = Duration::from_millis(1); + + let mut rc = Rc::new(sub_apps); let f = Rc::new(RefCell::new(None)); let g = f.clone(); - let c = move || { - let mut app = Rc::get_mut(&mut rc).unwrap(); - let delay = tick(&mut app, wait); - match delay { - Ok(delay) => { - set_timeout(f.borrow().as_ref().unwrap(), delay.unwrap_or(asap)) + let closure = move || { + let mut sub_apps = Rc::get_mut(&mut rc).unwrap(); + match update(&mut sub_apps) { + Ok(sleep) => { + set_timeout(f.borrow().as_ref().unwrap(), sleep.max(min_sleep)) } Err(_) => {} } }; - *g.borrow_mut() = Some(Closure::wrap(Box::new(c) as Box)); - set_timeout(g.borrow().as_ref().unwrap(), asap); + + *g.borrow_mut() = + Some(Closure::wrap(Box::new(closure) as Box)); + + set_timeout(g.borrow().as_ref().unwrap(), min_sleep); }; + + // remove TLS accessor + sub_apps + .for_each(|sub_app| sub_app.world.remove_resource::()); } } }); diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs new file mode 100644 index 00000000000000..294db6c6aff32b --- /dev/null +++ b/crates/bevy_app/src/sub_app.rs @@ -0,0 +1,418 @@ +use crate::{ + app::AppTypeRegistry, App, AppError, AppLabelId, First, PlaceholderPlugin, Plugin, PluginGroup, + StateTransition, +}; +use bevy_ecs::{ + prelude::*, + schedule::{ + common_conditions::run_once as run_once_condition, run_enter_schedule, BoxedScheduleLabel, + IntoSystemConfigs, IntoSystemSetConfigs, ScheduleLabel, + }, +}; +use bevy_utils::{default, tracing::debug, HashMap, HashSet}; + +use std::fmt::Debug; +use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe}; + +// pub type SetupFn = Box Option + Send>; +pub type ExtractFn = Box; + +#[derive(Default)] +struct PluginStore { + registry: Vec>, + names: HashSet, +} + +/// A secondary application with its own [`World`]. These can run independently of each other. +/// +/// These are useful for situations where certain processes (e.g. a render thread) need to be kept +/// separate from the main application. +pub struct SubApp { + /// + // pub label: AppLabelId, + /// The [`World`] + pub world: World, + /// Metadata for installed plugins. + plugins: PluginStore, + /// Panics if an update is attempted before plugins have been built. + plugin_build_depth: usize, + /// The schedule that will be run by [`update`](SubApp::update). + pub update_schedule: Option, + /// A function that gives mutable access to two app worlds. This is primarily + /// intended for copying data from the main world to secondary worlds. + extract: Option, +} + +impl Debug for SubApp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "SubApp") + } +} + +impl Default for SubApp { + fn default() -> Self { + let mut world = World::new(); + world.init_resource::(); + Self { + world, + plugins: default(), + plugin_build_depth: 0, + update_schedule: None, + extract: None, + } + } +} + +impl SubApp { + pub fn new() -> Self { + Self::default() + } + + pub fn set_extract(&mut self, extract: F) + where + F: Fn(&mut World, &mut World) + Send + 'static, + { + self.extract = Some(Box::new(extract)) + } + + /// Runs the `SubApp`'s default schedule. + pub fn update(&mut self) { + assert!(self.can_update()); + if let Some(label) = self.update_schedule.as_ref() { + self.world.run_schedule(&*label); + } + self.world.clear_trackers(); + } + + /// Extracts data from the world into this sub-app. + pub fn extract(&mut self, world: &mut World) { + if let Some(f) = self.extract.as_mut() { + f(world, &mut self.world); + } + } + + /// Adds a collection of systems to the schedule (stored in the [`Schedules`] of this sub-app). + pub fn add_systems( + &mut self, + schedule: impl ScheduleLabel, + systems: impl IntoSystemConfigs, + ) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + if let Some(schedule) = schedules.get_mut(&schedule) { + schedule.add_systems(systems); + } else { + let mut new_schedule = Schedule::new(); + new_schedule.add_systems(systems); + schedules.insert(schedule, new_schedule); + } + + self + } + + /// Configures a system set in the default schedule, adding the set if it does not exist. + pub fn configure_set( + &mut self, + schedule: impl ScheduleLabel, + set: impl IntoSystemSetConfig, + ) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + if let Some(schedule) = schedules.get_mut(&schedule) { + schedule.configure_set(set); + } else { + let mut new_schedule = Schedule::new(); + new_schedule.configure_set(set); + schedules.insert(schedule, new_schedule); + } + self + } + + /// Configures a collection of system sets in the default schedule, adding any sets that do not exist. + pub fn configure_sets( + &mut self, + schedule: impl ScheduleLabel, + sets: impl IntoSystemSetConfigs, + ) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + if let Some(schedule) = schedules.get_mut(&schedule) { + schedule.configure_sets(sets); + } else { + let mut new_schedule = Schedule::new(); + new_schedule.configure_sets(sets); + schedules.insert(schedule, new_schedule); + } + self + } + + pub fn init_resource(&mut self) -> &mut Self { + self.world.init_resource::(); + self + } + + pub fn insert_resource(&mut self, resource: R) -> &mut Self { + self.world.insert_resource(resource); + self + } + + pub fn add_schedule(&mut self, label: impl ScheduleLabel, schedule: Schedule) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + schedules.insert(label, schedule); + self + } + + pub fn init_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + if !schedules.contains(&label) { + schedules.insert(label, Schedule::new()); + } + self + } + + pub fn get_schedule(&self, label: impl ScheduleLabel) -> Option<&Schedule> { + let schedules = self.world.get_resource::()?; + schedules.get(&label) + } + + pub fn get_schedule_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> { + let schedules = self.world.get_resource_mut::()?; + // We must call `.into_inner` here because the borrow checker only understands reborrows + // using ordinary references, not our `Mut` smart pointers. + schedules.into_inner().get_mut(&label) + } + + pub fn edit_schedule( + &mut self, + label: impl ScheduleLabel, + mut f: impl FnMut(&mut Schedule), + ) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + if !schedules.contains(&label) { + schedules.insert(label.dyn_clone(), Schedule::new()); + } + + let schedule = schedules.get_mut(&label).unwrap(); + f(schedule); + + self + } + + pub(crate) fn run_as_app(&mut self, mut f: F) -> &mut Self + where + F: FnMut(&mut App), + { + let mut app = App { + sub_apps: SubApps { + main: std::mem::take(self), + ..default() + }, + ..default() + }; + + f(&mut app); + + let (mut sub_apps, _, _) = app.into_parts(); + *self = std::mem::take(&mut sub_apps.main); + + self + } + + pub fn add_plugin(&mut self, plugin: T) -> &mut Self + where + T: Plugin, + { + match self.add_boxed_plugin(Box::new(plugin)) { + Ok(app) => app, + Err(AppError::DuplicatePlugin { plugin_name }) => panic!( + "Error adding plugin {plugin_name}: : plugin was already added in application" + ), + } + } + + pub(crate) fn add_boxed_plugin( + &mut self, + plugin: Box, + ) -> Result<&mut Self, AppError> { + debug!("added plugin: {}", plugin.name()); + if plugin.is_unique() && !self.plugins.names.insert(plugin.name().to_string()) { + Err(AppError::DuplicatePlugin { + plugin_name: plugin.name().to_string(), + })?; + } + + // Reserve position in the plugin registry. If the plugin adds more plugins, + // they'll all end up in insertion order. + let index = self.plugin_registry.len(); + self.plugin_registry.push(Box::new(PlaceholderPlugin)); + + self.plugin_build_depth += 1; + let result = catch_unwind(AssertUnwindSafe(|| { + self.run_as_app(|app| plugin.build(app)) + })); + self.plugin_build_depth -= 1; + + if let Err(payload) = result { + resume_unwind(payload); + } + + self.plugins.registry[index] = plugin; + Ok(self) + } + + pub fn is_plugin_added(&self) -> bool + where + T: Plugin, + { + self.plugins + .registry + .iter() + .any(|p| p.downcast_ref::().is_some()) + } + + pub fn get_added_plugins(&self) -> Vec<&T> + where + T: Plugin, + { + self.plugins + .registry + .iter() + .filter_map(|p| p.downcast_ref()) + .collect() + } + + pub fn add_plugins(&mut self, group: T) -> &mut Self { + let builder = group.build(); + self.run_as_app(|app| { + builder.finish(app); + }); + self + } + + /// Adds [`State`] and [`NextState`] resources, [`OnEnter`] and [`OnExit`] schedules + /// for each state variant (if they don't already exist), an instance of [`apply_state_transition::`] in + /// [`StateTransition`] so that transitions happen before [`Update`] and + /// a instance of [`run_enter_schedule::`] in [`StateTransition`] with a + /// [`run_once`](`run_once_condition`) condition to run the on enter schedule of the + /// initial state. + /// + /// If you would like to control how other systems run based on the current state, + /// you can emulate this behavior using the [`in_state`] [`Condition`](bevy_ecs::schedule::Condition). + /// + /// Note that you can also apply state transitions at other points in the schedule + /// by adding the [`apply_state_transition`] system manually. + pub fn add_state(&mut self) -> &mut Self { + self.init_resource::>() + .init_resource::>() + .add_systems( + StateTransition, + ( + run_enter_schedule::.run_if(run_once_condition()), + apply_state_transition::, + ) + .chain(), + ); + + // The OnEnter, OnExit, and OnTransition schedules are lazily initialized + // (i.e. when the first system is added to them), and World::try_run_schedule is used to fail + // gracefully if they aren't present. + + self + } + + /// Returns `true` if there is no plugin in the middle of being built. + pub(crate) fn can_update(&self) -> bool { + self.plugin_build_depth == 0 + } + + /// Return `true` if [`Plugin::ready`] returns `true` for all plugins. + pub fn is_ready(&self) -> bool { + for plugin in &self.plugins.registry { + if !plugin.ready(self) { + return false; + } + } + true + } + + /// Runs [`Plugin::finish`] for each plugin. + pub fn finish(&mut self) { + // temporarily remove the plugin registry to run each plugin's setup function on app. + let plugins = std::mem::take(&mut self.plugins); + for plugin in &plugins.registry { + self.run_as_app(|app| plugin.finish(app)); + } + self.plugins = plugins; + } + + /// Runs [`Plugin::cleanup`] for each plugin. + pub fn cleanup(&mut self) { + let plugins = std::mem::take(&mut self.plugins); + for plugin in &plugins.registry { + self.run_as_app(|app| plugin.cleanup(app)); + } + self.plugins = plugins; + } + + pub fn add_event(&mut self) -> &mut Self + where + T: Event, + { + if !self.world.contains_resource::>() { + self.init_resource::>() + .add_systems(First, Events::::update_system); + } + self + } + + #[cfg(feature = "bevy_reflect")] + pub fn register_type(&mut self) -> &mut Self { + let registry = self.world.resource_mut::(); + registry.write().register::(); + self + } + + #[cfg(feature = "bevy_reflect")] + pub fn register_type_data< + T: bevy_reflect::Reflect + 'static, + D: bevy_reflect::TypeData + bevy_reflect::FromType, + >( + &mut self, + ) -> &mut Self { + let registry = self.world.resource_mut::(); + registry.write().register_type_data::(); + + self + } +} + +#[derive(Default)] +pub struct SubApps { + pub main: SubApp, + pub sub_apps: HashMap, +} + +impl SubApps { + pub fn update(&mut self) { + { + #[cfg(feature = "trace")] + let _bevy_frame_update_span = info_span!("main app").entered(); + self.main.update(); + } + for (_label, sub_app) in self.sub_apps.iter_mut() { + #[cfg(feature = "trace")] + let _sub_app_span = info_span!("sub app", name = ?_label).entered(); + sub_app.extract(&mut self.main.world); + sub_app.update(); + } + + self.main.world.clear_trackers(); + } + + pub fn for_each(&mut self, mut f: F) + where + F: FnMut(&mut SubApp), + { + f(&mut self.main); + for sub_app in self.sub_apps.values_mut() { + f(sub_app); + } + } +} diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 38ccbef7b50bbe..9dbd2364b24f89 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -501,39 +501,6 @@ impl<'a, T: Resource> From> for Mut<'a, T> { } } -/// Unique borrow of a non-[`Send`] resource. -/// -/// Only [`Send`] resources may be accessed with the [`ResMut`] [`SystemParam`](crate::system::SystemParam). In case that the -/// resource does not implement `Send`, this `SystemParam` wrapper can be used. This will instruct -/// the scheduler to instead run the system on the main thread so that it doesn't send the resource -/// over to another thread. -/// -/// # Panics -/// -/// Panics when used as a `SystemParameter` if the resource does not exist. -/// -/// Use `Option>` instead if the resource might not always exist. -pub struct NonSendMut<'a, T: ?Sized + 'static> { - pub(crate) value: &'a mut T, - pub(crate) ticks: TicksMut<'a>, -} - -change_detection_impl!(NonSendMut<'a, T>, T,); -change_detection_mut_impl!(NonSendMut<'a, T>, T,); -impl_methods!(NonSendMut<'a, T>, T,); -impl_debug!(NonSendMut<'a, T>,); - -impl<'a, T: 'static> From> for Mut<'a, T> { - /// Convert this `NonSendMut` into a `Mut`. This allows keeping the change-detection feature of `Mut` - /// while losing the specificity of `NonSendMut`. - fn from(other: NonSendMut<'a, T>) -> Mut<'a, T> { - Mut { - value: other.value, - ticks: other.ticks, - } - } -} - /// Shared borrow of an entity's component with access to change detection. /// Similar to [`Mut`] but is immutable and so doesn't require unique access. pub struct Ref<'a, T: ?Sized> { @@ -801,9 +768,7 @@ mod tests { use crate::{ self as bevy_ecs, - change_detection::{ - Mut, NonSendMut, Ref, ResMut, TicksMut, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE, - }, + change_detection::{Mut, Ref, ResMut, TicksMut, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE}, component::{Component, ComponentTicks, Tick}, system::{IntoSystem, Query, System}, world::World, @@ -948,31 +913,6 @@ mod tests { assert!(val.is_changed()); } - #[test] - fn mut_from_non_send_mut() { - let mut component_ticks = ComponentTicks { - added: Tick::new(1), - changed: Tick::new(2), - }; - let ticks = TicksMut { - added: &mut component_ticks.added, - changed: &mut component_ticks.changed, - last_run: Tick::new(3), - this_run: Tick::new(4), - }; - let mut res = R {}; - let non_send_mut = NonSendMut { - value: &mut res, - ticks, - }; - - let into_mut: Mut = non_send_mut.into(); - assert_eq!(1, into_mut.ticks.added.get()); - assert_eq!(2, into_mut.ticks.changed.get()); - assert_eq!(3, into_mut.ticks.last_run.get()); - assert_eq!(4, into_mut.ticks.this_run.get()); - } - #[test] fn map_mut() { use super::*; diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 4bdc7572e02e02..be07d120fb7733 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1,5 +1,6 @@ //! Types for declaring and storing [`Component`]s. +use crate::storage::ThreadLocalResource; use crate::{ self as bevy_ecs, change_detection::MAX_CHANGE_AGE, @@ -608,7 +609,7 @@ impl Components { /// If a resource of this type has already been initialized, this will return /// the ID of the pre-existing resource. #[inline] - pub fn init_non_send(&mut self) -> ComponentId { + pub fn init_non_send(&mut self) -> ComponentId { // SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`] unsafe { self.get_or_insert_resource_with(TypeId::of::(), || { diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index c23963444014e0..ef6ad99bdfec3e 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -44,11 +44,12 @@ pub mod prelude { IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig, IntoSystemSetConfigs, NextState, OnEnter, OnExit, OnTransition, Schedule, Schedules, State, States, SystemSet, }, + storage::{ThreadLocal, ThreadLocalResource, ThreadLocals}, system::{ adapter as system_adapter, adapter::{dbg, error, ignore, info, unwrap, warn}, - Commands, Deferred, In, IntoSystem, Local, NonSend, NonSendMut, ParallelCommands, - ParamSet, Query, ReadOnlySystem, Res, ResMut, Resource, System, SystemParamFunction, + Commands, Deferred, In, IntoSystem, Local, ParallelCommands, ParamSet, Query, + ReadOnlySystem, Res, ResMut, Resource, System, SystemParamFunction, }, world::{EntityRef, FromWorld, World}, }; @@ -75,7 +76,6 @@ mod tests { use bevy_tasks::{ComputeTaskPool, TaskPool}; use std::{ any::TypeId, - marker::PhantomData, sync::{ atomic::{AtomicUsize, Ordering}, Arc, Mutex, @@ -89,9 +89,6 @@ mod tests { #[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] struct C; - #[derive(Default)] - struct NonSendA(usize, PhantomData<*mut ()>); - #[derive(Component, Clone, Debug)] struct DropCk(Arc); impl DropCk { @@ -1267,36 +1264,6 @@ mod tests { ); } - #[test] - fn non_send_resource() { - let mut world = World::default(); - world.insert_non_send_resource(123i32); - world.insert_non_send_resource(456i64); - assert_eq!(*world.non_send_resource::(), 123); - assert_eq!(*world.non_send_resource_mut::(), 456); - } - - #[test] - fn non_send_resource_points_to_distinct_data() { - let mut world = World::default(); - world.insert_resource(A(123)); - world.insert_non_send_resource(A(456)); - assert_eq!(*world.resource::(), A(123)); - assert_eq!(*world.non_send_resource::(), A(456)); - } - - #[test] - #[should_panic] - fn non_send_resource_panic() { - let mut world = World::default(); - world.insert_non_send_resource(0i32); - std::thread::spawn(move || { - let _ = world.non_send_resource_mut::(); - }) - .join() - .unwrap(); - } - #[test] fn exact_size_query() { let mut world = World::default(); @@ -1414,32 +1381,6 @@ mod tests { assert_eq!(world.resource::().0, 1); } - #[test] - #[should_panic( - expected = "Attempted to access or drop non-send resource bevy_ecs::tests::NonSendA from thread" - )] - fn non_send_resource_drop_from_different_thread() { - let mut world = World::default(); - world.insert_non_send_resource(NonSendA::default()); - - let thread = std::thread::spawn(move || { - // Dropping the non-send resource on a different thread - // Should result in a panic - drop(world); - }); - - if let Err(err) = thread.join() { - std::panic::resume_unwind(err); - } - } - - #[test] - fn non_send_resource_drop_from_same_thread() { - let mut world = World::default(); - world.insert_non_send_resource(NonSendA::default()); - drop(world); - } - #[test] fn insert_overwrite_drop() { let (dropck1, dropped1) = DropCk::new_pair(); diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index abaf9bbf62c2d6..99e6d5c416d7cd 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -1028,10 +1028,6 @@ where self.condition.archetype_component_access() } - fn is_send(&self) -> bool { - self.condition.is_send() - } - fn is_exclusive(&self) -> bool { self.condition.is_exclusive() } diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index bc9a687811cfab..230a937b9b5dd3 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -7,17 +7,11 @@ use crate::{ set::{BoxedSystemSet, IntoSystemSet, SystemSet}, ScheduleLabel, }, - system::{BoxedSystem, IntoSystem, System}, + system::{BoxedSystem, IntoSystem}, }; fn new_condition(condition: impl Condition) -> BoxedCondition { let condition_system = IntoSystem::into_system(condition); - assert!( - condition_system.is_send(), - "Condition `{}` accesses `NonSend` resources. This is not currently supported.", - condition_system.name() - ); - Box::new(condition_system) } diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index cb206143eb5c5e..1130b180b9c37b 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -1,5 +1,6 @@ use std::{ any::Any, + panic::AssertUnwindSafe, sync::{Arc, Mutex}, }; @@ -8,7 +9,6 @@ use bevy_utils::default; use bevy_utils::syncunsafecell::SyncUnsafeCell; #[cfg(feature = "trace")] use bevy_utils::tracing::{info_span, Instrument}; -use std::panic::AssertUnwindSafe; use async_channel::{Receiver, Sender}; use fixedbitset::FixedBitSet; diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index 2cabf44303040e..968fd1fdd95e40 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -22,10 +22,12 @@ mod blob_vec; mod resource; +mod resource_non_send; mod sparse_set; mod table; pub use resource::*; +pub use resource_non_send::*; pub use sparse_set::*; pub use table::*; @@ -38,6 +40,4 @@ pub struct Storages { pub tables: Tables, /// Backing storage for resources. pub resources: Resources, - /// Backing storage for `!Send` resources. - pub non_send_resources: Resources, } diff --git a/crates/bevy_ecs/src/storage/resource_non_send.rs b/crates/bevy_ecs/src/storage/resource_non_send.rs new file mode 100644 index 00000000000000..0d9cfbb0a16f37 --- /dev/null +++ b/crates/bevy_ecs/src/storage/resource_non_send.rs @@ -0,0 +1,525 @@ +use std::any::TypeId; +use std::marker::PhantomData; + +use bevy_ptr::{OwningPtr, Ptr}; +use bevy_utils::default; + +use crate::archetype::ArchetypeComponentId; +use crate::change_detection::{Mut, MutUntyped, TicksMut}; +use crate::component::{ComponentId, Components, Tick, TickCells}; +use crate::storage::{ResourceData, Resources}; + +/// A type that can be inserted into [`ThreadLocals`]. +/// Unlike [`Resource`](crate::system::Resource), this does not require [`Send`] or [`Sync`]. +pub trait ThreadLocalResource: 'static {} + +pub use param::*; + +mod param { + use std::thread::ThreadId; + + use bevy_utils::dyn_clone::{clone_trait_object, DynClone}; + + use super::ThreadLocals; + use crate as bevy_ecs; + use crate::component::{ComponentId, Tick}; + use crate::system::{Resource, SystemParam}; + use crate::world::unsafe_world_cell::UnsafeWorldCell; + + /// Type alias for tasks that access thread-local data. + pub type ThreadLocalTask = Box; + + /// An error returned from the [`ThreadLocalTaskSender::send`] function. + /// + /// A send operation can only fail if the receiving end of a channel is disconnected, + /// implying that the data could never be received. The error contains the data that + /// was sent as a payload so it can be recovered. + #[derive(Debug)] + pub struct ThreadLocalTaskSendError(pub T); + + /// Channel for sending [`ThreadLocalTask`] instances. + pub trait ThreadLocalTaskSender: DynClone + Send + 'static { + /// Attempts to send a task over this channel, returning it back if it could not be sent. + fn send_task( + &mut self, + task: ThreadLocalTask, + ) -> Result<(), ThreadLocalTaskSendError>; + } + + clone_trait_object!(ThreadLocalTaskSender); + + /// Grants access to thread-local data. + /// + /// # Safety + /// + /// The runner context is responsible for inserting and removing this resource. This resource + /// must be dropped before the [`ThreadLocals`] it points to can be moved or dropped. + #[derive(Resource)] + pub struct ThreadLocalAccessor { + owning_thread: ThreadId, + direct: *mut ThreadLocals, + indirect: Box, + } + + impl ThreadLocalAccessor { + /// Constructs a [`ThreadLocalAccessor`] that has direct access to `tls` when on the same thread + /// and indirect access when on other threads (through `event_loop`). + /// + /// # Safety + /// + /// - The caller must ensure that `tls` remains valid until this accessor is dropped. + pub unsafe fn new(tls: *mut ThreadLocals, event_loop: impl ThreadLocalTaskSender) -> Self { + Self { + owning_thread: std::thread::current().id(), + direct: tls, + indirect: Box::new(event_loop), + } + } + } + + // SAFETY: The pointer to the thread-local storage is only dereferenced on its owning thread. + unsafe impl Send for ThreadLocalAccessor {} + + // SAFETY: The pointer to the thread-local storage is only dereferenced on its owning thread. + // Likewise, all operations require an exclusive reference, so there can be no races. + unsafe impl Sync for ThreadLocalAccessor {} + + enum ThreadLocalAccess<'a> { + Direct(&'a mut ThreadLocals), + Indirect(&'a mut dyn ThreadLocalTaskSender), + } + + #[doc(hidden)] + pub struct ThreadLocalState { + component_id: ComponentId, + last_run: Tick, + } + + /// A [`SystemParam`] that grants scoped access to the thread-local data of the main thread. + pub struct ThreadLocal<'w, 's> { + access: ThreadLocalAccess<'w>, + last_run: &'s mut Tick, + } + + impl ThreadLocal<'_, '_> { + /// Runs `f` in a scope that has access to the thread-local resources. + pub fn run(&mut self, f: F) -> T + where + F: FnOnce(&mut ThreadLocals) -> T + Send, + T: Send + 'static, + { + match self.access { + ThreadLocalAccess::Direct(_) => self.run_direct(f), + ThreadLocalAccess::Indirect(_) => self.run_indirect(f), + } + } + + fn run_direct(&mut self, f: F) -> T + where + F: FnOnce(&mut ThreadLocals) -> T + Send, + T: Send + 'static, + { + let ThreadLocalAccess::Direct(ref mut tls) = self.access else { unreachable!() }; + + tls.update_change_tick(); + let saved = std::mem::replace(&mut tls.last_tick, *self.last_run); + let result = f(*tls); + tls.last_tick = saved; + + *self.last_run = tls.last_tick; + + result + } + + fn run_indirect(&mut self, f: F) -> T + where + F: FnOnce(&mut ThreadLocals) -> T + Send, + T: Send + 'static, + { + let ThreadLocalAccess::Indirect(ref mut sender) = self.access else { unreachable!() }; + + let (result_tx, result_rx) = std::sync::mpsc::sync_channel(1); + let f = |tls: &mut ThreadLocals| { + tls.update_change_tick(); + let saved = std::mem::replace(&mut tls.last_tick, *self.last_run); + // we want to propagate to caller instead of panicking in the main thread + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(tls))); + tls.last_tick = saved; + + *self.last_run = tls.last_tick; + result_tx.send(result).unwrap(); + }; + + let f: Box = Box::new(f); + + // SAFETY: `scope` blocks the calling thread until `f` completes, + // so any borrows within `f` will remain valid until then. + let f: Box = + unsafe { std::mem::transmute(f) }; + + // Send task to the main thread. + sender + .send_task(f) + .unwrap_or_else(|_| panic!("receiver missing")); + + // Wait to receive result back from the main thread. + result_rx.recv().unwrap().unwrap() + } + } + + // SAFETY: This system param does not borrow any data from the world. + unsafe impl SystemParam for ThreadLocal<'_, '_> { + type State = ThreadLocalState; + type Item<'w, 's> = ThreadLocal<'w, 's>; + + fn init_state( + world: &mut crate::prelude::World, + system_meta: &mut crate::system::SystemMeta, + ) -> Self::State { + let component_id = + crate::system::ResMut::::init_state(world, system_meta); + + ThreadLocalState { + component_id, + last_run: Tick::new(0), + } + } + + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + system_meta: &crate::system::SystemMeta, + world: UnsafeWorldCell<'world>, + curr_tick: Tick, + ) -> Self::Item<'world, 'state> { + let mut accessor = crate::system::ResMut::::get_param( + &mut state.component_id, + system_meta, + world, + curr_tick, + ); + + let access = if std::thread::current().id() == accessor.owning_thread { + // SAFETY: We are on the owning thread and pointer is valid. + let ptr = accessor.direct; + unsafe { ThreadLocalAccess::Direct(&mut *ptr) } + } else { + let ptr: *mut dyn ThreadLocalTaskSender = &mut *accessor.indirect; + ThreadLocalAccess::Indirect(&mut *ptr) + }; + + ThreadLocal { + access, + last_run: &mut state.last_run, + } + } + } +} + +/// Storage for thread-local (`!Send`) data. +pub struct ThreadLocals { + info: Components, + storage: Resources, + curr_tick: Tick, + last_tick: Tick, + // !Send + !Sync + _marker: PhantomData<*const ()>, +} + +impl Default for ThreadLocals { + fn default() -> Self { + Self { + info: Components::default(), + storage: Resources::default(), + curr_tick: Tick::new(0), + last_tick: Tick::new(0), + _marker: PhantomData, + } + } +} + +impl ThreadLocals { + /// Constructs a new instance of [`ThreadLocals`]. + pub fn new() -> Self { + Self { ..default() } + } + + /// Initializes a new [`ThreadLocalResource`] type and returns the [`ComponentId`] created for it. + pub fn init_resource_type(&mut self) -> ComponentId { + self.info.init_non_send::() + } + + /// Returns the [`ComponentId`] of the [`ThreadLocalResource`], if it exists. + /// + /// **Note:** The returned `ComponentId` is specific to this `ThreadLocals` instance. + /// You should not use it with another `ThreadLocals` instance. + #[inline] + pub fn resource_id(&self) -> Option { + self.info.get_resource_id(TypeId::of::()) + } + + /// Inserts a new resource with its default value. + /// + /// If the resource already exists, nothing happens. + #[inline] + pub fn init_resource(&mut self) { + if !self.contains_resource::() { + self.insert_resource::(Default::default()); + } + } + + /// Inserts a new resource with the given `value`. + /// + /// Resources are "unique" data of a given type. + /// If you insert a resource of a type that already exists, + /// you will overwrite any existing data. + #[inline] + pub fn insert_resource(&mut self, value: R) { + let id = self.info.init_non_send::(); + OwningPtr::make(value, |ptr| { + // SAFETY: id was just initialized and corresponds to resource of type R + unsafe { + self.insert_resource_by_id(id, ptr); + } + }); + } + + /// Removes the resource of a given type and returns it, if it exists. Otherwise returns [None]. + #[inline] + pub fn remove_resource(&mut self) -> Option { + let id = self.info.get_resource_id(TypeId::of::())?; + let (ptr, _) = self.storage.get_mut(id)?.remove()?; + // SAFETY: `id` came directly from `R`, so this has to be the `R` data + unsafe { Some(ptr.read::()) } + } + + /// Returns `true` if a resource of type `R` exists. + #[inline] + pub fn contains_resource(&self) -> bool { + self.info + .get_resource_id(TypeId::of::()) + .and_then(|id| self.storage.get(id)) + .map(|info| info.is_present()) + .unwrap_or(false) + } + + pub fn is_resource_added(&self) -> bool { + self.info + .get_resource_id(TypeId::of::()) + .and_then(|id| self.storage.get(id)?.get_ticks()) + .map(|ticks| ticks.is_added(self.last_tick, self.curr_tick)) + .unwrap_or(false) + } + + pub fn is_resource_changed(&self) -> bool { + self.info + .get_resource_id(TypeId::of::()) + .and_then(|id| self.storage.get(id)?.get_ticks()) + .map(|ticks| ticks.is_changed(self.last_tick, self.curr_tick)) + .unwrap_or(false) + } + + /// Returns a reference to the resource. + /// + /// # Panics + /// + /// Panics if the resource does not exist. + /// Use [`get_resource`](ThreadLocals::get_resource) instead if you want to handle this case. + /// + /// If you want to instead insert a value if the resource does not exist, + /// use [`get_resource_or_insert_with`](ThreadLocals::get_resource_or_insert_with). + #[inline] + #[track_caller] + pub fn resource(&self) -> &R { + match self.get_resource() { + Some(x) => x, + None => panic!( + "Requested resource {} does not exist. Did you insert it?", + std::any::type_name::() + ), + } + } + + /// Returns a mutable reference to the resource. + /// + /// # Panics + /// + /// Panics if the resource does not exist. + /// Use [`get_resource_mut`](ThreadLocals::get_resource_mut) instead if you want to handle this case. + /// + /// If you want to instead insert a value if the resource does not exist, + /// use [`get_resource_or_insert_with`](ThreadLocals::get_resource_or_insert_with). + #[inline] + #[track_caller] + pub fn resource_mut(&mut self) -> Mut<'_, R> { + match self.get_resource_mut() { + Some(x) => x, + None => panic!( + "Requested resource {} does not exist. Did you insert it?", + std::any::type_name::() + ), + } + } + + /// Returns a reference to the resource, if it exists. + #[inline] + pub fn get_resource(&self) -> Option<&R> { + let id = self.info.get_resource_id(TypeId::of::())?; + // SAFETY: `id` was derived from `R` directly + unsafe { self.get_resource_by_id(id).map(|ptr| ptr.deref()) } + } + + /// Returns a mutable reference to the resource, if it exists. + #[inline] + pub fn get_resource_mut(&mut self) -> Option> { + // SAFETY: exclusive access is enforced + unsafe { self.get_resource_unchecked_mut() } + } + + /// Returns a mutable reference to the resource. + /// If the resource does not exist, calls `f` and inserts its result first. + #[inline] + pub fn get_resource_or_insert_with( + &mut self, + f: impl FnOnce() -> R, + ) -> Mut<'_, R> { + if !self.contains_resource::() { + self.insert_resource(f()); + } + self.resource_mut() + } + + /// Returns mutable reference to the resource of the given type, if it exists. + /// + /// # Safety + /// + /// The caller must ensure that this reference is unique. + #[inline] + pub unsafe fn get_resource_unchecked_mut(&self) -> Option> { + let id = self.info.get_resource_id(TypeId::of::())?; + // SAFETY: `id` was derived from `R` directly + unsafe { + self.get_resource_mut_by_id(id) + .map(|ptr| ptr.with_type::()) + } + } + + /// # Safety + /// + /// The caller must ensure that `id` is assigned to type `R`. + #[inline] + pub(crate) unsafe fn get_resource_by_id(&self, id: ComponentId) -> Option { + self.storage.get(id)?.get_data() + } + + /// # Safety + /// + /// The caller must ensure that `id` is assigned to type `R` and that this reference is unique. + #[inline] + pub(crate) unsafe fn get_resource_mut_by_id(&self, id: ComponentId) -> Option> { + // SAFETY: caller ensures unaliased access + let (ptr, ticks) = unsafe { self.get_resource_with_ticks(id)? }; + + // SAFETY: caller ensures unaliased access + let ticks = unsafe { TicksMut::from_tick_cells(ticks, self.last_tick, self.curr_tick) }; + + Some(MutUntyped { + // SAFETY: caller ensures unaliased access + value: unsafe { ptr.assert_unique() }, + ticks, + }) + } + + /// Returns untyped references to the data and change ticks of a resource. + /// + /// # Safety + /// + /// The caller must ensure that mutable references have no aliases. + #[inline] + pub(crate) unsafe fn get_resource_with_ticks( + &self, + id: ComponentId, + ) -> Option<(Ptr<'_>, TickCells)> { + self.storage.get(id)?.get_with_ticks() + } + + /// Inserts a new resource with the given `value`. Will replace the value if it already existed. + /// + /// **Prefer the typed API [`ThreadLocals::insert_resource`] when possible. + /// Only use this in cases where the actual types are not known at compile time.** + /// + /// # Safety + /// - `id` must already exist in [`ThreadLocals`] + /// - `value` must be a valid value of the type represented by `id` + #[inline] + pub unsafe fn insert_resource_by_id(&mut self, id: ComponentId, value: OwningPtr<'_>) { + let curr_tick = self.curr_tick; + // SAFETY: caller ensures the value is a valid value of the type given by `id` + unsafe { + self.initialize_resource_internal(id) + .insert(value, curr_tick) + }; + } + + /// # Safety + /// `id` must be valid for this world + #[inline] + unsafe fn initialize_resource_internal(&mut self, id: ComponentId) -> &mut ResourceData { + self.storage + .initialize_with(id, &self.info, || ArchetypeComponentId::new(id.index())) + } + + /// Temporarily removes `R` from the [`ThreadLocals`], then re-inserts it before returning. + pub fn resource_scope( + &mut self, + f: impl FnOnce(&mut ThreadLocals, Mut) -> T, + ) -> T { + let id = self + .info + .get_resource_id(TypeId::of::()) + .unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::())); + + let (ptr, mut ticks) = self + .storage + .get_mut(id) + .and_then(|info| info.remove()) + .unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::())); + + // Read the value onto the stack to avoid potential &mut aliasing. + // SAFETY: pointer is of type R + let mut value = unsafe { ptr.read::() }; + let value_mut = Mut { + value: &mut value, + ticks: TicksMut { + added: &mut ticks.added, + changed: &mut ticks.changed, + last_run: self.last_tick, + this_run: self.curr_tick, + }, + }; + + let result = f(self, value_mut); + assert!( + !self.contains_resource::(), + "Resource `{}` was inserted during a call to `resource_scope`.\n\ + This is not allowed as the original resource is re-inserted after `f` is invoked.", + std::any::type_name::() + ); + + OwningPtr::make(value, |ptr| { + // SAFETY: pointer is of type R + unsafe { + self.storage + .get_mut(id) + .map(|info| info.insert_with_ticks(ptr, ticks)) + .unwrap(); + } + }); + + result + } + + pub(crate) fn update_change_tick(&mut self) { + let tick = self.curr_tick.get(); + self.curr_tick.set(tick.wrapping_add(1)); + } +} diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 13c2b7869303d4..aabd8b33131531 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -153,10 +153,6 @@ where &self.archetype_component_access } - fn is_send(&self) -> bool { - self.a.is_send() && self.b.is_send() - } - fn is_exclusive(&self) -> bool { self.a.is_exclusive() || self.b.is_exclusive() } diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 6d2a31f5542013..841b00d0c82ef9 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -79,14 +79,6 @@ where &self.system_meta.archetype_component_access } - #[inline] - fn is_send(&self) -> bool { - // exclusive systems should have access to non-send resources - // the executor runs exclusive systems on the main thread, so this - // field reflects that constraint - false - } - #[inline] unsafe fn run_unsafe(&mut self, _input: Self::In, _world: UnsafeWorldCell) -> Self::Out { panic!("Cannot run exclusive systems with a shared World reference"); diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index e245f93716d782..787ee7d499e8dd 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -18,9 +18,6 @@ pub struct SystemMeta { pub(crate) name: Cow<'static, str>, pub(crate) component_access_set: FilteredAccessSet, pub(crate) archetype_component_access: Access, - // NOTE: this must be kept private. making a SystemMeta non-send is irreversible to prevent - // SystemParams from overriding each other - is_send: bool, pub(crate) last_run: Tick, } @@ -30,7 +27,6 @@ impl SystemMeta { name: std::any::type_name::().into(), archetype_component_access: Access::default(), component_access_set: FilteredAccessSet::default(), - is_send: true, last_run: Tick::new(0), } } @@ -40,20 +36,6 @@ impl SystemMeta { pub fn name(&self) -> &str { &self.name } - - /// Returns true if the system is [`Send`]. - #[inline] - pub fn is_send(&self) -> bool { - self.is_send - } - - /// Sets the system to be not [`Send`]. - /// - /// This is irreversible. - #[inline] - pub fn set_non_send(&mut self) { - self.is_send = false; - } } // TODO: Actually use this in FunctionSystem. We should probably only do this once Systems are constructed using a World reference @@ -432,11 +414,6 @@ where &self.system_meta.archetype_component_access } - #[inline] - fn is_send(&self) -> bool { - self.system_meta.is_send - } - #[inline] fn is_exclusive(&self) -> bool { false diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 3900dd68ac6ba2..5eabe2ce3ecf31 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -89,8 +89,6 @@ //! - [`Local`] //! - [`EventReader`](crate::event::EventReader) //! - [`EventWriter`](crate::event::EventWriter) -//! - [`NonSend`] and `Option` -//! - [`NonSendMut`] and `Option` //! - [`&World`](crate::world::World) //! - [`RemovedComponents`](crate::removal_detection::RemovedComponents) //! - [`SystemName`] @@ -510,8 +508,8 @@ mod tests { Schedule, }, system::{ - adapter::new, Commands, In, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, - QueryComponentError, Res, ResMut, Resource, System, SystemState, + adapter::new, Commands, In, IntoSystem, Local, ParamSet, Query, QueryComponentError, + Res, ResMut, Resource, System, SystemState, }, world::{FromWorld, World}, }; @@ -1056,52 +1054,6 @@ mod tests { assert_eq!(*world.resource::(), SystemRan::Yes); } - #[test] - fn non_send_option_system() { - let mut world = World::default(); - - world.insert_resource(SystemRan::No); - struct NotSend1(std::rc::Rc); - struct NotSend2(std::rc::Rc); - world.insert_non_send_resource(NotSend1(std::rc::Rc::new(0))); - - fn sys( - op: Option>, - mut _op2: Option>, - mut system_ran: ResMut, - ) { - op.expect("NonSend should exist"); - *system_ran = SystemRan::Yes; - } - - run_system(&mut world, sys); - // ensure the system actually ran - assert_eq!(*world.resource::(), SystemRan::Yes); - } - - #[test] - fn non_send_system() { - let mut world = World::default(); - - world.insert_resource(SystemRan::No); - struct NotSend1(std::rc::Rc); - struct NotSend2(std::rc::Rc); - - world.insert_non_send_resource(NotSend1(std::rc::Rc::new(1))); - world.insert_non_send_resource(NotSend2(std::rc::Rc::new(2))); - - fn sys( - _op: NonSend, - mut _op2: NonSendMut, - mut system_ran: ResMut, - ) { - *system_ran = SystemRan::Yes; - } - - run_system(&mut world, sys); - assert_eq!(*world.resource::(), SystemRan::Yes); - } - #[test] fn removal_tracking() { let mut world = World::new(); diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 124aa4582fb008..307aaec06144c0 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -33,12 +33,8 @@ pub trait System: Send + Sync + 'static { fn component_access(&self) -> &Access; /// Returns the system's archetype component [`Access`]. fn archetype_component_access(&self) -> &Access; - /// Returns true if the system is [`Send`]. - fn is_send(&self) -> bool; - /// Returns true if the system must be run exclusively. fn is_exclusive(&self) -> bool; - /// Runs the system with the given input in the world. Unlike [`System::run`], this function /// can be called in parallel with other systems and may break Rust's aliasing rules /// if used incorrectly, making it unsafe to call. @@ -150,13 +146,7 @@ pub(crate) fn check_system_change_tick(last_run: &mut Tick, this_run: Tick, syst impl Debug for dyn System { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "System {}: {{{}}}", self.name(), { - if self.is_send() { - if self.is_exclusive() { - "is_send is_exclusive" - } else { - "is_send" - } - } else if self.is_exclusive() { + if self.is_exclusive() { "is_exclusive" } else { "" diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index ff4321ee6cd50d..a90b046a5efc9e 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -1,9 +1,9 @@ -pub use crate::change_detection::{NonSendMut, Res, ResMut}; +pub use crate::change_detection::{Res, ResMut}; use crate::{ archetype::{Archetype, Archetypes}, bundle::Bundles, change_detection::{Ticks, TicksMut}, - component::{ComponentId, ComponentTicks, Components, Tick}, + component::{ComponentId, Components, Tick}, entity::Entities, query::{ Access, FilteredAccess, FilteredAccessSet, QueryState, ReadOnlyWorldQuery, WorldQuery, @@ -927,239 +927,6 @@ unsafe impl SystemParam for Deferred<'_, T> { } } -/// Shared borrow of a non-[`Send`] resource. -/// -/// Only `Send` resources may be accessed with the [`Res`] [`SystemParam`]. In case that the -/// resource does not implement `Send`, this `SystemParam` wrapper can be used. This will instruct -/// the scheduler to instead run the system on the main thread so that it doesn't send the resource -/// over to another thread. -/// -/// # Panics -/// -/// Panics when used as a `SystemParameter` if the resource does not exist. -/// -/// Use `Option>` instead if the resource might not always exist. -pub struct NonSend<'w, T: 'static> { - pub(crate) value: &'w T, - ticks: ComponentTicks, - last_run: Tick, - this_run: Tick, -} - -// SAFETY: Only reads a single World non-send resource -unsafe impl<'w, T> ReadOnlySystemParam for NonSend<'w, T> {} - -impl<'w, T> Debug for NonSend<'w, T> -where - T: Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("NonSend").field(&self.value).finish() - } -} - -impl<'w, T: 'static> NonSend<'w, T> { - /// Returns `true` if the resource was added after the system last ran. - pub fn is_added(&self) -> bool { - self.ticks.is_added(self.last_run, self.this_run) - } - - /// Returns `true` if the resource was added or mutably dereferenced after the system last ran. - pub fn is_changed(&self) -> bool { - self.ticks.is_changed(self.last_run, self.this_run) - } -} - -impl<'w, T> Deref for NonSend<'w, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.value - } -} -impl<'a, T> From> for NonSend<'a, T> { - fn from(nsm: NonSendMut<'a, T>) -> Self { - Self { - value: nsm.value, - ticks: ComponentTicks { - added: nsm.ticks.added.to_owned(), - changed: nsm.ticks.changed.to_owned(), - }, - this_run: nsm.ticks.this_run, - last_run: nsm.ticks.last_run, - } - } -} - -// SAFETY: NonSendComponentId and ArchetypeComponentId access is applied to SystemMeta. If this -// NonSend conflicts with any prior access, a panic will occur. -unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { - type State = ComponentId; - type Item<'w, 's> = NonSend<'w, T>; - - fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - system_meta.set_non_send(); - - let component_id = world.initialize_non_send_resource::(); - let combined_access = system_meta.component_access_set.combined_access(); - assert!( - !combined_access.has_write(component_id), - "error[B0002]: NonSend<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access.", - std::any::type_name::(), - system_meta.name, - ); - system_meta - .component_access_set - .add_unfiltered_read(component_id); - - let archetype_component_id = world - .get_non_send_archetype_component_id(component_id) - .unwrap(); - system_meta - .archetype_component_access - .add_read(archetype_component_id); - - component_id - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - let (ptr, ticks) = world - .get_non_send_with_ticks(component_id) - .unwrap_or_else(|| { - panic!( - "Non-send resource requested by {} does not exist: {}", - system_meta.name, - std::any::type_name::() - ) - }); - - NonSend { - value: ptr.deref(), - ticks: ticks.read(), - last_run: system_meta.last_run, - this_run: change_tick, - } - } -} - -// SAFETY: Only reads a single World non-send resource -unsafe impl ReadOnlySystemParam for Option> {} - -// SAFETY: this impl defers to `NonSend`, which initializes and validates the correct world access. -unsafe impl SystemParam for Option> { - type State = ComponentId; - type Item<'w, 's> = Option>; - - fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - NonSend::::init_state(world, system_meta) - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - world - .get_non_send_with_ticks(component_id) - .map(|(ptr, ticks)| NonSend { - value: ptr.deref(), - ticks: ticks.read(), - last_run: system_meta.last_run, - this_run: change_tick, - }) - } -} - -// SAFETY: NonSendMut ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this -// NonSendMut conflicts with any prior access, a panic will occur. -unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { - type State = ComponentId; - type Item<'w, 's> = NonSendMut<'w, T>; - - fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - system_meta.set_non_send(); - - let component_id = world.initialize_non_send_resource::(); - let combined_access = system_meta.component_access_set.combined_access(); - if combined_access.has_write(component_id) { - panic!( - "error[B0002]: NonSendMut<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access.", - std::any::type_name::(), system_meta.name); - } else if combined_access.has_read(component_id) { - panic!( - "error[B0002]: NonSendMut<{}> in system {} conflicts with a previous immutable resource access ({0}). Consider removing the duplicate access.", - std::any::type_name::(), system_meta.name); - } - system_meta - .component_access_set - .add_unfiltered_write(component_id); - - let archetype_component_id = world - .get_non_send_archetype_component_id(component_id) - .unwrap(); - system_meta - .archetype_component_access - .add_write(archetype_component_id); - - component_id - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - let (ptr, ticks) = world - .get_non_send_with_ticks(component_id) - .unwrap_or_else(|| { - panic!( - "Non-send resource requested by {} does not exist: {}", - system_meta.name, - std::any::type_name::() - ) - }); - NonSendMut { - value: ptr.assert_unique().deref_mut(), - ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick), - } - } -} - -// SAFETY: this impl defers to `NonSendMut`, which initializes and validates the correct world access. -unsafe impl<'a, T: 'static> SystemParam for Option> { - type State = ComponentId; - type Item<'w, 's> = Option>; - - fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - NonSendMut::::init_state(world, system_meta) - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - world - .get_non_send_with_ticks(component_id) - .map(|(ptr, ticks)| NonSendMut { - value: ptr.assert_unique().deref_mut(), - ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick), - }) - } -} - // SAFETY: Only reads World archetypes unsafe impl<'a> ReadOnlySystemParam for &'a Archetypes {} diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index a5d1e5fb14e7bc..1cb16cbc473e4f 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -850,57 +850,6 @@ impl World { }); } - /// Initializes a new non-send resource and returns the [`ComponentId`] created for it. - /// - /// If the resource already exists, nothing happens. - /// - /// The value given by the [`FromWorld::from_world`] method will be used. - /// Note that any resource with the `Default` trait automatically implements `FromWorld`, - /// and those default values will be here instead. - /// - /// # Panics - /// - /// Panics if called from a thread other than the main thread. - #[inline] - pub fn init_non_send_resource(&mut self) -> ComponentId { - let component_id = self.components.init_non_send::(); - if self - .storages - .non_send_resources - .get(component_id) - .map_or(true, |data| !data.is_present()) - { - let value = R::from_world(self); - OwningPtr::make(value, |ptr| { - // SAFETY: component_id was just initialized and corresponds to resource of type R. - unsafe { - self.insert_non_send_by_id(component_id, ptr); - } - }); - } - component_id - } - - /// Inserts a new non-send resource with the given `value`. - /// - /// `NonSend` resources cannot be sent across threads, - /// and do not need the `Send + Sync` bounds. - /// Systems with `NonSend` resources are always scheduled on the main thread. - /// - /// # Panics - /// If a value is already present, this function will panic if called - /// from a different thread than where the original value was inserted from. - #[inline] - pub fn insert_non_send_resource(&mut self, value: R) { - let component_id = self.components.init_non_send::(); - OwningPtr::make(value, |ptr| { - // SAFETY: component_id was just initialized and corresponds to resource of type R. - unsafe { - self.insert_non_send_by_id(component_id, ptr); - } - }); - } - /// Removes the resource of a given type and returns it, if it exists. Otherwise returns [None]. #[inline] pub fn remove_resource(&mut self) -> Option { @@ -910,29 +859,6 @@ impl World { unsafe { Some(ptr.read::()) } } - /// Removes a `!Send` resource from the world and returns it, if present. - /// - /// `NonSend` resources cannot be sent across threads, - /// and do not need the `Send + Sync` bounds. - /// Systems with `NonSend` resources are always scheduled on the main thread. - /// - /// Returns `None` if a value was not previously present. - /// - /// # Panics - /// If a value is present, this function will panic if called from a different - /// thread than where the value was inserted from. - #[inline] - pub fn remove_non_send_resource(&mut self) -> Option { - let component_id = self.components.get_resource_id(TypeId::of::())?; - let (ptr, _) = self - .storages - .non_send_resources - .get_mut(component_id)? - .remove()?; - // SAFETY: `component_id` was gotten via looking up the `R` type - unsafe { Some(ptr.read::()) } - } - /// Returns `true` if a resource of type `R` exists. Otherwise returns `false`. #[inline] pub fn contains_resource(&self) -> bool { @@ -943,23 +869,6 @@ impl World { .unwrap_or(false) } - /// Returns `true` if a resource of type `R` exists. Otherwise returns `false`. - #[inline] - pub fn contains_non_send(&self) -> bool { - self.components - .get_resource_id(TypeId::of::()) - .and_then(|component_id| self.storages.non_send_resources.get(component_id)) - .map(|info| info.is_present()) - .unwrap_or(false) - } - - /// Return's `true` if a resource of type `R` exists and was added since the world's - /// [`last_change_tick`](World::last_change_tick()). Otherwise, this return's `false`. - /// - /// This means that: - /// - When called from an exclusive system, this will check for additions since the system last ran. - /// - When called elsewhere, this will check for additions since the last time that [`World::clear_trackers`] - /// was called. pub fn is_resource_added(&self) -> bool { self.components .get_resource_id(TypeId::of::()) @@ -1079,76 +988,6 @@ impl World { unsafe { data.with_type::() } } - /// Gets an immutable reference to the non-send resource of the given type, if it exists. - /// - /// # Panics - /// - /// Panics if the resource does not exist. - /// Use [`get_non_send_resource`](World::get_non_send_resource) instead if you want to handle this case. - /// - /// This function will panic if it isn't called from the same thread that the resource was inserted from. - #[inline] - #[track_caller] - pub fn non_send_resource(&self) -> &R { - match self.get_non_send_resource() { - Some(x) => x, - None => panic!( - "Requested non-send resource {} does not exist in the `World`. - Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? - Non-send resources can also be be added by plugins.", - std::any::type_name::() - ), - } - } - - /// Gets a mutable reference to the non-send resource of the given type, if it exists. - /// - /// # Panics - /// - /// Panics if the resource does not exist. - /// Use [`get_non_send_resource_mut`](World::get_non_send_resource_mut) instead if you want to handle this case. - /// - /// This function will panic if it isn't called from the same thread that the resource was inserted from. - #[inline] - #[track_caller] - pub fn non_send_resource_mut(&mut self) -> Mut<'_, R> { - match self.get_non_send_resource_mut() { - Some(x) => x, - None => panic!( - "Requested non-send resource {} does not exist in the `World`. - Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? - Non-send resources can also be be added by plugins.", - std::any::type_name::() - ), - } - } - - /// Gets a reference to the non-send resource of the given type, if it exists. - /// Otherwise returns [None]. - /// - /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. - #[inline] - pub fn get_non_send_resource(&self) -> Option<&R> { - // SAFETY: - // - `as_unsafe_world_cell_readonly` gives permission to access the entire world immutably - // - `&self` ensures that there are no mutable borrows of world data - unsafe { self.as_unsafe_world_cell_readonly().get_non_send_resource() } - } - - /// Gets a mutable reference to the non-send resource of the given type, if it exists. - /// Otherwise returns [None] - /// - /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. - #[inline] - pub fn get_non_send_resource_mut(&mut self) -> Option> { - // SAFETY: - // - `as_unsafe_world_cell` gives permission to access the entire world mutably - // - `&mut self` ensures that there are no borrows of world data - unsafe { self.as_unsafe_world_cell().get_non_send_resource_mut() } - } - // Shorthand helper function for getting the [`ArchetypeComponentId`] for a resource. #[inline] pub(crate) fn get_resource_archetype_component_id( @@ -1159,16 +998,6 @@ impl World { Some(resource.id()) } - // Shorthand helper function for getting the [`ArchetypeComponentId`] for a resource. - #[inline] - pub(crate) fn get_non_send_archetype_component_id( - &self, - component_id: ComponentId, - ) -> Option { - let resource = self.storages.non_send_resources.get(component_id)?; - Some(resource.id()) - } - /// For a given batch of ([Entity], [Bundle]) pairs, either spawns each [Entity] with the given /// bundle (if the entity does not exist), or inserts the [Bundle] (if the entity already exists). /// This is faster than doing equivalent operations one-by-one. @@ -1410,31 +1239,6 @@ impl World { .insert(value, change_tick); } - /// Inserts a new `!Send` resource with the given `value`. Will replace the value if it already - /// existed. - /// - /// **You should prefer to use the typed API [`World::insert_non_send_resource`] where possible and only - /// use this in cases where the actual types are not known at compile time.** - /// - /// # Panics - /// If a value is already present, this function will panic if not called from the same - /// thread that the original value was inserted from. - /// - /// # Safety - /// The value referenced by `value` must be valid for the given [`ComponentId`] of this world. - #[inline] - pub unsafe fn insert_non_send_by_id( - &mut self, - component_id: ComponentId, - value: OwningPtr<'_>, - ) { - let change_tick = self.change_tick(); - - // SAFETY: value is valid for component_id, ensured by caller - self.initialize_non_send_internal(component_id) - .insert(value, change_tick); - } - /// # Panics /// Panics if `component_id` is not registered as a `Send` component type in this `World` #[inline] @@ -1452,35 +1256,12 @@ impl World { }) } - /// # Panics - /// panics if `component_id` is not registered in this world - #[inline] - fn initialize_non_send_internal( - &mut self, - component_id: ComponentId, - ) -> &mut ResourceData { - let archetype_component_count = &mut self.archetypes.archetype_component_count; - self.storages - .non_send_resources - .initialize_with(component_id, &self.components, || { - let id = ArchetypeComponentId::new(*archetype_component_count); - *archetype_component_count += 1; - id - }) - } - pub(crate) fn initialize_resource(&mut self) -> ComponentId { let component_id = self.components.init_resource::(); self.initialize_resource_internal(component_id); component_id } - pub(crate) fn initialize_non_send_resource(&mut self) -> ComponentId { - let component_id = self.components.init_non_send::(); - self.initialize_non_send_internal(component_id); - component_id - } - /// Empties queued entities and adds them to the empty [Archetype](crate::archetype::Archetype). /// This should be called before doing operations that might operate on queued entities, /// such as inserting a [Component]. @@ -1552,7 +1333,6 @@ impl World { ref mut tables, ref mut sparse_sets, ref mut resources, - ref mut non_send_resources, } = self.storages; #[cfg(feature = "trace")] @@ -1560,7 +1340,6 @@ impl World { tables.check_change_ticks(change_tick); sparse_sets.check_change_ticks(change_tick); resources.check_change_ticks(change_tick); - non_send_resources.check_change_ticks(change_tick); if let Some(mut schedules) = self.get_resource_mut::() { schedules.check_change_ticks(change_tick); @@ -1593,7 +1372,6 @@ impl World { /// Use with caution. pub fn clear_resources(&mut self) { self.storages.resources.clear(); - self.storages.non_send_resources.clear(); } } @@ -1632,46 +1410,6 @@ impl World { } } - /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. - /// The returned pointer must not be used to modify the resource, and must not be - /// dereferenced after the immutable borrow of the [`World`] ends. - /// - /// **You should prefer to use the typed API [`World::get_resource`] where possible and only - /// use this in cases where the actual types are not known at compile time.** - /// - /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. - #[inline] - pub fn get_non_send_by_id(&self, component_id: ComponentId) -> Option> { - // SAFETY: - // - `as_unsafe_world_cell_readonly` gives permission to access the whole world immutably - // - `&self` ensures there are no mutable borrows on world data - unsafe { - self.as_unsafe_world_cell_readonly() - .get_non_send_resource_by_id(component_id) - } - } - - /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. - /// The returned pointer may be used to modify the resource, as long as the mutable borrow - /// of the [`World`] is still valid. - /// - /// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only - /// use this in cases where the actual types are not known at compile time.** - /// - /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. - #[inline] - pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - // SAFETY: - // - `&mut self` ensures that all accessed data is unaliased - // - `as_unsafe_world_cell` provides mutable permission to the whole world - unsafe { - self.as_unsafe_world_cell() - .get_non_send_resource_mut_by_id(component_id) - } - } - /// Removes the resource of a given type, if it exists. Otherwise returns [None]. /// /// **You should prefer to use the typed API [`World::remove_resource`] where possible and only @@ -1684,21 +1422,6 @@ impl World { Some(()) } - /// Removes the resource of a given type, if it exists. Otherwise returns [None]. - /// - /// **You should prefer to use the typed API [`World::remove_resource`] where possible and only - /// use this in cases where the actual types are not known at compile time.** - /// - /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. - pub fn remove_non_send_by_id(&mut self, component_id: ComponentId) -> Option<()> { - self.storages - .non_send_resources - .get_mut(component_id)? - .remove_and_drop(); - Some(()) - } - /// Retrieves an immutable untyped reference to the given `entity`'s [Component] of the given [`ComponentId`]. /// Returns [None] if the `entity` does not have a [Component] of the given type. /// @@ -2161,19 +1884,6 @@ mod tests { assert_eq!(resource.0, 0); } - #[test] - fn init_non_send_resource_does_not_overwrite() { - let mut world = World::new(); - world.insert_resource(TestResource(0)); - world.init_non_send_resource::(); - world.insert_resource(TestResource(1)); - world.init_non_send_resource::(); - - let resource = world.non_send_resource::(); - - assert_eq!(resource.0, 0); - } - #[derive(Component)] struct Foo; diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 9c3ec47406df2e..7655d905bb529f 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -300,22 +300,7 @@ impl<'w> UnsafeWorldCell<'w> { Some(resource.id()) } - /// Shorthand helper function for getting the [`ArchetypeComponentId`] for a resource. - #[inline] - pub(crate) fn get_non_send_archetype_component_id( - self, - component_id: ComponentId, - ) -> Option { - // SAFETY: - // - we only access world metadata - let resource = unsafe { self.world_metadata() } - .storages - .non_send_resources - .get(component_id)?; - Some(resource.id()) - } - - /// Retrieves an [`UnsafeEntityCell`] that exposes read and write operations for the given `entity`. + /// Retrieves an [`UnsafeWorldCellEntityRef`] that exposes read and write operations for the given `entity`. /// Similar to the [`UnsafeWorldCell`], you are in charge of making sure that no aliasing rules are violated. #[inline] pub fn get_entity(self, entity: Entity) -> Option> { @@ -362,48 +347,6 @@ impl<'w> UnsafeWorldCell<'w> { .get_data() } - /// Gets a reference to the non-send resource of the given type if it exists - /// - /// # Safety - /// It is the callers responsibility to ensure that - /// - the [`UnsafeWorldCell`] has permission to access the resource - /// - no mutable reference to the resource exists at the same time - #[inline] - pub unsafe fn get_non_send_resource(self) -> Option<&'w R> { - let component_id = self.components().get_resource_id(TypeId::of::())?; - // SAFETY: caller ensures that `self` has permission to access `R` - // caller ensures that no mutable reference exists to `R` - unsafe { - self.get_non_send_resource_by_id(component_id) - // SAFETY: `component_id` was obtained from `TypeId::of::()` - .map(|ptr| ptr.deref::()) - } - } - - /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. - /// The returned pointer must not be used to modify the resource, and must not be - /// dereferenced after the immutable borrow of the [`World`] ends. - /// - /// **You should prefer to use the typed API [`UnsafeWorldCell::get_non_send_resource`] where possible and only - /// use this in cases where the actual types are not known at compile time.** - /// - /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. - /// - /// # Safety - /// It is the callers responsibility to ensure that - /// - the [`UnsafeWorldCell`] has permission to access the resource - /// - no mutable reference to the resource exists at the same time - #[inline] - pub unsafe fn get_non_send_resource_by_id(self, component_id: ComponentId) -> Option> { - // SAFETY: we only access data on world that the caller has ensured is unaliased and we have - // permission to access. - unsafe { self.storages() } - .non_send_resources - .get(component_id)? - .get_data() - } - /// Gets a mutable reference to the resource of the given type if it exists /// /// # Safety @@ -462,65 +405,6 @@ impl<'w> UnsafeWorldCell<'w> { }) } - /// Gets a mutable reference to the non-send resource of the given type if it exists - /// - /// # Safety - /// It is the callers responsibility to ensure that - /// - the [`UnsafeWorldCell`] has permission to access the resource mutably - /// - no other references to the resource exist at the same time - #[inline] - pub unsafe fn get_non_send_resource_mut(self) -> Option> { - let component_id = self.components().get_resource_id(TypeId::of::())?; - // SAFETY: - // - caller ensures that `self` has permission to access the resource - // - caller ensures that the resource is unaliased - unsafe { - self.get_non_send_resource_mut_by_id(component_id) - // SAFETY: `component_id` was gotten by `TypeId::of::()` - .map(|ptr| ptr.with_type::()) - } - } - - /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. - /// The returned pointer may be used to modify the resource, as long as the mutable borrow - /// of the [`World`] is still valid. - /// - /// **You should prefer to use the typed API [`UnsafeWorldCell::get_non_send_resource_mut`] where possible and only - /// use this in cases where the actual types are not known at compile time.** - /// - /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. - /// - /// # Safety - /// It is the callers responsibility to ensure that - /// - the [`UnsafeWorldCell`] has permission to access the resource mutably - /// - no other references to the resource exist at the same time - #[inline] - pub unsafe fn get_non_send_resource_mut_by_id( - self, - component_id: ComponentId, - ) -> Option> { - let change_tick = self.change_tick(); - // SAFETY: we only access data that the caller has ensured is unaliased and `self` - // has permission to access. - let (ptr, ticks) = unsafe { self.storages() } - .non_send_resources - .get(component_id)? - .get_with_ticks()?; - - let ticks = - // SAFETY: This function has exclusive access to the world so nothing aliases `ticks`. - // - index is in-bounds because the column is initialized and non-empty - // - no other reference to the ticks of the same row can exist at the same time - unsafe { TicksMut::from_tick_cells(ticks, self.last_change_tick(), change_tick) }; - - Some(MutUntyped { - // SAFETY: This function has exclusive access to the world so nothing aliases `ptr`. - value: unsafe { ptr.assert_unique() }, - ticks, - }) - } - // Shorthand helper function for getting the data and change ticks for a resource. /// /// # Safety @@ -541,30 +425,6 @@ impl<'w> UnsafeWorldCell<'w> { .get(component_id)? .get_with_ticks() } - - // Shorthand helper function for getting the data and change ticks for a resource. - /// - /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. - /// - /// # Safety - /// It is the callers responsibility to ensure that - /// - the [`UnsafeWorldCell`] has permission to access the resource mutably - /// - no mutable references to the resource exist at the same time - #[inline] - pub(crate) unsafe fn get_non_send_with_ticks( - self, - component_id: ComponentId, - ) -> Option<(Ptr<'w>, TickCells<'w>)> { - // SAFETY: - // - caller ensures there is no `&mut World` - // - caller ensures there are no mutable borrows of this resource - // - caller ensures that we have permission to access this resource - unsafe { self.storages() } - .non_send_resources - .get(component_id)? - .get_with_ticks() - } } /// A interior-mutable reference to a particular [`Entity`] and all of its components diff --git a/crates/bevy_ecs/src/world/world_cell.rs b/crates/bevy_ecs/src/world/world_cell.rs index d97cdcac9f6608..885193a8c25f72 100644 --- a/crates/bevy_ecs/src/world/world_cell.rs +++ b/crates/bevy_ecs/src/world/world_cell.rs @@ -268,74 +268,6 @@ impl<'w> WorldCell<'w> { } } - /// Gets an immutable reference to the non-send resource of the given type, if it exists. - pub fn get_non_send_resource(&self) -> Option> { - let component_id = self.world.components().get_resource_id(TypeId::of::())?; - - let archetype_component_id = self - .world - .get_non_send_archetype_component_id(component_id)?; - WorldBorrow::try_new( - // SAFETY: access is checked by WorldBorrowMut - || unsafe { self.world.get_non_send_resource::() }, - archetype_component_id, - self.access.clone(), - ) - } - - /// Gets an immutable reference to the non-send resource of the given type, if it exists. - /// - /// # Panics - /// - /// Panics if the resource does not exist. Use - /// [`get_non_send_resource`](WorldCell::get_non_send_resource) instead if you want to handle - /// this case. - pub fn non_send_resource(&self) -> WorldBorrow<'_, T> { - match self.get_non_send_resource() { - Some(x) => x, - None => panic!( - "Requested non-send resource {} does not exist in the `World`. - Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? - Non-send resources can also be be added by plugins.", - std::any::type_name::() - ), - } - } - - /// Gets a mutable reference to the non-send resource of the given type, if it exists. - pub fn get_non_send_resource_mut(&self) -> Option> { - let component_id = self.world.components().get_resource_id(TypeId::of::())?; - - let archetype_component_id = self - .world - .get_non_send_archetype_component_id(component_id)?; - WorldBorrowMut::try_new( - // SAFETY: access is checked by WorldBorrowMut - || unsafe { self.world.get_non_send_resource_mut::() }, - archetype_component_id, - self.access.clone(), - ) - } - - /// Gets a mutable reference to the non-send resource of the given type, if it exists. - /// - /// # Panics - /// - /// Panics if the resource does not exist. Use - /// [`get_non_send_resource_mut`](WorldCell::get_non_send_resource_mut) instead if you want to - /// handle this case. - pub fn non_send_resource_mut(&self) -> WorldBorrowMut<'_, T> { - match self.get_non_send_resource_mut() { - Some(x) => x, - None => panic!( - "Requested non-send resource {} does not exist in the `World`. - Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? - Non-send resources can also be be added by plugins.", - std::any::type_name::() - ), - } - } - /// Sends an [`Event`](crate::event::Event). #[inline] pub fn send_event(&self, event: E) { diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index de1d0afb3c733c..dbcfc399f6fca9 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -1,6 +1,7 @@ use crate::converter::{convert_axis, convert_button, convert_gamepad_id}; use bevy_ecs::event::EventWriter; -use bevy_ecs::system::{NonSend, NonSendMut, Res}; +use bevy_ecs::storage::ThreadLocal; +use bevy_ecs::system::Res; use bevy_input::gamepad::{ GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadConnection, GamepadConnectionEvent, GamepadSettings, @@ -11,79 +12,92 @@ use bevy_input::Axis; use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter, Gilrs}; pub fn gilrs_event_startup_system( - gilrs: NonSend, + local_thread: ThreadLocal, mut connection_events: EventWriter, ) { - for (id, gamepad) in gilrs.gamepads() { - let info = GamepadInfo { - name: gamepad.name().into(), - }; + local_thread.run(|tls| { + let gilrs = tls.resource::(); + for (id, gamepad) in gilrs.gamepads() { + let info = GamepadInfo { + name: gamepad.name().into(), + }; - connection_events.send(GamepadConnectionEvent { - gamepad: convert_gamepad_id(id), - connection: GamepadConnection::Connected(info), - }); - } + connection_events.send(GamepadConnectionEvent { + gamepad: convert_gamepad_id(id), + connection: GamepadConnection::Connected(info), + }); + } + }); } pub fn gilrs_event_system( - mut gilrs: NonSendMut, + local_thread: ThreadLocal, mut events: EventWriter, gamepad_axis: Res>, gamepad_buttons: Res>, gamepad_settings: Res, ) { - while let Some(gilrs_event) = gilrs - .next_event() - .filter_ev(&axis_dpad_to_button, &mut gilrs) - { - gilrs.update(&gilrs_event); + local_thread.run(|tls| { + let mut gilrs = tls.resource_mut::(); + while let Some(gilrs_event) = gilrs + .next_event() + .filter_ev(&axis_dpad_to_button, &mut gilrs) + { + gilrs.update(&gilrs_event); - let gamepad = convert_gamepad_id(gilrs_event.id); - match gilrs_event.event { - EventType::Connected => { - let pad = gilrs.gamepad(gilrs_event.id); - let info = GamepadInfo { - name: pad.name().into(), - }; + let gamepad = convert_gamepad_id(gilrs_event.id); + match gilrs_event.event { + EventType::Connected => { + let pad = gilrs.gamepad(gilrs_event.id); + let info = GamepadInfo { + name: pad.name().into(), + }; - events.send( - GamepadConnectionEvent::new(gamepad, GamepadConnection::Connected(info)).into(), - ); - } - EventType::Disconnected => events - .send(GamepadConnectionEvent::new(gamepad, GamepadConnection::Disconnected).into()), - EventType::ButtonChanged(gilrs_button, raw_value, _) => { - if let Some(button_type) = convert_button(gilrs_button) { - let button = GamepadButton::new(gamepad, button_type); - let old_value = gamepad_buttons.get(button); - let button_settings = gamepad_settings.get_button_axis_settings(button); + events.send( + GamepadConnectionEvent::new(gamepad, GamepadConnection::Connected(info)) + .into(), + ); + } + EventType::Disconnected => events.send( + GamepadConnectionEvent::new(gamepad, GamepadConnection::Disconnected).into(), + ), + EventType::ButtonChanged(gilrs_button, raw_value, _) => { + if let Some(button_type) = convert_button(gilrs_button) { + let button = GamepadButton::new(gamepad, button_type); + let old_value = gamepad_buttons.get(button); + let button_settings = gamepad_settings.get_button_axis_settings(button); - // Only send events that pass the user-defined change threshold - if let Some(filtered_value) = button_settings.filter(raw_value, old_value) { - events.send( - GamepadButtonChangedEvent::new(gamepad, button_type, filtered_value) + // Only send events that pass the user-defined change threshold + if let Some(filtered_value) = button_settings.filter(raw_value, old_value) { + events.send( + GamepadButtonChangedEvent::new( + gamepad, + button_type, + filtered_value, + ) .into(), - ); + ); + } } } - } - EventType::AxisChanged(gilrs_axis, raw_value, _) => { - if let Some(axis_type) = convert_axis(gilrs_axis) { - let axis = GamepadAxis::new(gamepad, axis_type); - let old_value = gamepad_axis.get(axis); - let axis_settings = gamepad_settings.get_axis_settings(axis); + EventType::AxisChanged(gilrs_axis, raw_value, _) => { + if let Some(axis_type) = convert_axis(gilrs_axis) { + let axis = GamepadAxis::new(gamepad, axis_type); + let old_value = gamepad_axis.get(axis); + let axis_settings = gamepad_settings.get_axis_settings(axis); - // Only send events that pass the user-defined change threshold - if let Some(filtered_value) = axis_settings.filter(raw_value, old_value) { - events.send( - GamepadAxisChangedEvent::new(gamepad, axis_type, filtered_value).into(), - ); + // Only send events that pass the user-defined change threshold + if let Some(filtered_value) = axis_settings.filter(raw_value, old_value) { + events.send( + GamepadAxisChangedEvent::new(gamepad, axis_type, filtered_value) + .into(), + ); + } } } - } - _ => (), - }; - } - gilrs.inc(); + _ => (), + }; + } + gilrs.inc(); + }); } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 61e814e9bc2d9e..7f98dd40483cab 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -69,7 +69,7 @@ pub struct RenderPlugin { pub wgpu_settings: WgpuSettings, } -/// The labels of the default App rendering sets. +/// The systems sets of the default [`App`] rendering schedule. /// /// The sets run in the order listed, with [`apply_deferred`] inserted between each set. /// @@ -203,7 +203,7 @@ struct FutureRendererResources( >, ); -/// A Label for the rendering sub-app. +/// A label for the rendering sub-app. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] pub struct RenderApp; @@ -264,8 +264,8 @@ impl Plugin for RenderPlugin { app.init_resource::(); - let mut render_app = App::empty(); - render_app.main_schedule_label = Box::new(Render); + let mut render_app = SubApp::new(); + render_app.update_schedule = Box::new(Render); let mut extract_schedule = Schedule::new(); extract_schedule.set_apply_final_deferred(false); @@ -291,11 +291,7 @@ impl Plugin for RenderPlugin { ), ); - let (sender, receiver) = bevy_time::create_time_channels(); - app.insert_resource(receiver); - render_app.insert_resource(sender); - - app.insert_sub_app(RenderApp, SubApp::new(render_app, move |main_world, render_app| { + render_app.set_extract(move |main_world, render_world| { #[cfg(feature = "trace")] let _render_span = bevy_utils::tracing::info_span!("extract main app to render subapp").entered(); { @@ -309,15 +305,14 @@ impl Plugin for RenderPlugin { let total_count = main_world.entities().total_count(); assert_eq!( - render_app.world.entities().len(), + render_world.entities().len(), 0, "An entity was spawned after the entity list was cleared last frame and before the extract schedule began. This is not supported", ); // This is safe given the clear_entities call in the past frame and the assert above unsafe { - render_app - .world + render_world .entities_mut() .flush_and_reserve_invalid_assuming_no_entities(total_count); } @@ -325,7 +320,12 @@ impl Plugin for RenderPlugin { // run extract schedule extract(main_world, render_app); - })); + }); + + let (sender, receiver) = bevy_time::create_time_channels(); + render_app.insert_resource(sender); + app.insert_resource(receiver); + app.insert_sub_app(RenderApp, render_app); } app.add_plugins(( diff --git a/crates/bevy_render/src/pipelined_rendering.rs b/crates/bevy_render/src/pipelined_rendering.rs index c96d936b85bbe4..bd9d2039797b0a 100644 --- a/crates/bevy_render/src/pipelined_rendering.rs +++ b/crates/bevy_render/src/pipelined_rendering.rs @@ -10,35 +10,34 @@ use bevy_tasks::ComputeTaskPool; use crate::RenderApp; -/// A Label for the sub app that runs the parts of pipelined rendering that need to run on the main thread. +/// A label for the [`SubApp`] that runs the "in the app thread" parts of the pipelined rendering logic. /// -/// The Main schedule of this app can be used to run logic after the render schedule starts, but -/// before I/O processing. This can be useful for something like frame pacing. +/// The main schedule of this [`SubApp`] runs after the render schedule starts, but before +/// input events are processed. This can be useful for something like framepacing. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] pub struct RenderExtractApp; -/// Channel to send the render app from the main thread to the rendering thread +/// A channel (sender) for sending the renderer [`SubApp`] between the app and render threads. #[derive(Resource)] -pub struct MainToRenderAppSender(pub Sender); +pub struct RendererSend(pub Sender); -/// Channel to send the render app from the render thread to the main thread +/// A channel (receiver) for sending the renderer [`SubApp`] between the app and render threads. #[derive(Resource)] -pub struct RenderToMainAppReceiver(pub Receiver); +pub struct RendererRecv(pub Receiver); -/// The [`PipelinedRenderingPlugin`] can be added to your application to enable pipelined rendering. -/// This moves rendering into a different thread, so that the Nth frame's rendering can -/// be run at the same time as the N + 1 frame's simulation. +/// The [`PipelinedRenderingPlugin`] enables asynchronous rendering. This plugin moves rendering +/// into a separate thread, so `frame N` can be rendered while `frame N+1` is being simulated. /// /// ```text -/// |--------------------|--------------------|--------------------|--------------------| -/// | simulation thread | frame 1 simulation | frame 2 simulation | frame 3 simulation | -/// |--------------------|--------------------|--------------------|--------------------| -/// | rendering thread | | frame 1 rendering | frame 2 rendering | -/// |--------------------|--------------------|--------------------|--------------------| +/// |--------------------|----------------|----------------|----------------| +/// | simulation thread | frame 1 update | frame 2 update | frame 3 update | +/// |--------------------|----------------|----------------|----------------| +/// | rendering thread | | frame 1 render | frame 2 render | +/// |--------------------|----------------|----------------|----------------| /// ``` /// -/// The plugin is dependent on the [`crate::RenderApp`] added by [`crate::RenderPlugin`] and so must -/// be added after that plugin. If it is not added after, the plugin will do nothing. +/// This plugin requires the [`RenderApp`](crate::RenderApp) added by [`RenderPlugin`](crate::RenderPlugin) +/// so the `RenderPlugin` must be added first. Otherwise, this plugin will fail to initialize. /// /// A single frame of execution looks something like below /// @@ -65,49 +64,46 @@ pub struct PipelinedRenderingPlugin; impl Plugin for PipelinedRenderingPlugin { fn build(&self, app: &mut App) { - // Don't add RenderExtractApp if RenderApp isn't initialized. + // headless apps do not render if app.get_sub_app(RenderApp).is_err() { return; } - app.insert_resource(MainThreadExecutor::new()); - let mut sub_app = App::empty(); - sub_app.init_schedule(Main); - app.insert_sub_app(RenderExtractApp, SubApp::new(sub_app, update_rendering)); + let mut sub_app = SubApp::new(); + sub_app.set_extract(renderer_extract); + app.insert_sub_app(RenderExtractApp, sub_app); } - // Sets up the render thread and inserts resources into the main app used for controlling the render thread. + // Spawns render thread and inserts resources that can control it into the main app world. + // TODO: this needs to run *after* TLS accessor inserted fn cleanup(&self, app: &mut App) { - // skip setting up when headless + // headless apps do not render if app.get_sub_app(RenderExtractApp).is_err() { return; } - let (app_to_render_sender, app_to_render_receiver) = async_channel::bounded::(1); - let (render_to_app_sender, render_to_app_receiver) = async_channel::bounded::(1); + let (send_to_renderer, recv_from_app) = async_channel::bounded::(1); + let (send_to_app, recv_from_renderer) = async_channel::bounded::(1); let mut render_app = app .remove_sub_app(RenderApp) .expect("Unable to get RenderApp. Another plugin may have removed the RenderApp before PipelinedRenderingPlugin"); - // clone main thread executor to render world - let executor = app.world.get_resource::().unwrap(); - render_app.app.world.insert_resource(executor.clone()); + send_to_app.send_blocking(render_app).unwrap(); - render_to_app_sender.send_blocking(render_app).unwrap(); - - app.insert_resource(MainToRenderAppSender(app_to_render_sender)); - app.insert_resource(RenderToMainAppReceiver(render_to_app_receiver)); + app.insert_resource(RendererSend(send_to_renderer)); + app.insert_resource(RendererRecv(recv_from_renderer)); std::thread::spawn(move || { #[cfg(feature = "trace")] let _span = bevy_utils::tracing::info_span!("render thread").entered(); loop { - // run a scope here to allow main world to use this thread while it's waiting for the render app - let mut render_app = ComputeTaskPool::get() + // wait to receive render world + // make this thread available to help run async tasks while we wait + let mut renderer = ComputeTaskPool::get() .scope(|s| { - s.spawn(async { app_to_render_receiver.recv().await.unwrap() }); + s.spawn(async { recv_from_app.recv().await.unwrap() }); }) .pop() .unwrap(); @@ -115,32 +111,38 @@ impl Plugin for PipelinedRenderingPlugin { #[cfg(feature = "trace")] let _sub_app_span = bevy_utils::tracing::info_span!("sub app", name = ?RenderApp).entered(); - render_app.run(); - render_to_app_sender.send_blocking(render_app).unwrap(); + + // run the render schedule + renderer.update(); + + // send the render world to app thread (to run extraction) + send_to_app.send_blocking(renderer).unwrap(); } }); } } -// This function waits for the rendering world to be received, -// runs extract, and then sends the rendering world back to the render thread. -fn update_rendering(app_world: &mut World, _sub_app: &mut App) { - app_world.resource_scope(|world, main_thread_executor: Mut| { - // we use a scope here to run any main thread tasks that the render world still needs to run - // while we wait for the render world to be received. - let mut render_app = ComputeTaskPool::get() +/// Waits to receive the render world, runs the [`ExtractSchedule`](crate::ExtractSchedule), +/// then sends the render world back to the render thread. +fn renderer_extract(main_world: &mut World, _world: &mut World) { + main_world.resource_scope(|world, main_thread_executor: Mut| { + // wait to receive render world + // make this thread available to help run async tasks while we wait + let mut renderer = ComputeTaskPool::get() .scope_with_executor(true, Some(&*main_thread_executor.0), |s| { s.spawn(async { - let receiver = world.get_resource::().unwrap(); - receiver.0.recv().await.unwrap() + let recv_from_renderer = app_world.resource::(); + recv_from_renderer.0.recv().await.unwrap() }); }) .pop() .unwrap(); - render_app.extract(world); + // run the extract schedule + renderer.extract(app_world); - let sender = world.resource::(); - sender.0.send_blocking(render_app).unwrap(); + // send the render world to render thread (to run pipeline) + let send_to_renderer = app_world.resource::(); + send_to_renderer.0.send_blocking(renderer).unwrap(); }); } diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index e76097004a7bc0..2f45553cf8be79 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -21,10 +21,6 @@ use screenshot::{ use super::Msaa; -/// Token to ensure a system runs on the main thread. -#[derive(Resource, Default)] -pub struct NonSendMarker; - pub struct WindowRenderPlugin; #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] @@ -195,30 +191,27 @@ pub struct WindowSurfaces { /// Creates and (re)configures window surfaces, and obtains a swapchain texture for rendering. /// -/// NOTE: `get_current_texture` in `prepare_windows` can take a long time if the GPU workload is -/// the performance bottleneck. This can be seen in profiles as multiple prepare-set systems all -/// taking an unusually long time to complete, and all finishing at about the same time as the -/// `prepare_windows` system. Improvements in bevy are planned to avoid this happening when it -/// should not but it will still happen as it is easy for a user to create a large GPU workload -/// relative to the GPU performance and/or CPU workload. -/// This can be caused by many reasons, but several of them are: -/// - GPU workload is more than your current GPU can manage -/// - Error / performance bug in your custom shaders -/// - wgpu was unable to detect a proper GPU hardware-accelerated device given the chosen +/// **NOTE:** `get_current_texture` (acquiring the next framebuffer) in `prepare_windows` can take +/// a long time if the GPU workload is heavy. This can be seen in profiling views with many prepare +/// systems taking an unusually long time to complete, but all finishing at around the same time +/// `prepare_windows` does. Performance improvements are planned to reduce how often this happens, +/// but it will still be possible, since it's easy to create a heavy GPU workload. +/// +/// These are some contributing factors: +/// - The GPU workload is more than your GPU can handle. +/// - There are custom shaders with an error / performance bug. +/// - wgpu could not detect a proper GPU hardware-accelerated device given the chosen /// [`Backends`](crate::settings::Backends), [`WgpuLimits`](crate::settings::WgpuLimits), -/// and/or [`WgpuFeatures`](crate::settings::WgpuFeatures). For example, on Windows currently -/// `DirectX 11` is not supported by wgpu 0.12 and so if your GPU/drivers do not support Vulkan, -/// it may be that a software renderer called "Microsoft Basic Render Driver" using `DirectX 12` -/// will be chosen and performance will be very poor. This is visible in a log message that is -/// output during renderer initialization. Future versions of wgpu will support `DirectX 11`, but -/// another alternative is to try to use [`ANGLE`](https://github.com/gfx-rs/wgpu#angle) and -/// [`Backends::GL`](crate::settings::Backends::GL) if your GPU/drivers support `OpenGL 4.3` / `OpenGL ES 3.0` or -/// later. -#[allow(clippy::too_many_arguments)] +/// and/or [`WgpuFeatures`](crate::settings::WgpuFeatures). +/// - On Windows, DirectX 11 is not supported by wgpu 0.12, and if your GPU/drivers do not +/// support Vulkan, a software renderer called "Microsoft Basic Render Driver" using DirectX 12 +/// may be used and performance will be very poor. This will be logged as a message when the +/// renderer is initialized. Future versions of wgpu will support DirectX 11, but an +/// alternative is to try to use [`ANGLE`](https://github.com/gfx-rs/wgpu#angle) and +/// [`Backends::GL`](crate::settings::Backends::GL) if your GPU/drivers support OpenGL 4.3, +/// OpenGL ES 3.0, or later. pub fn prepare_windows( - // By accessing a NonSend resource, we tell the scheduler to put this system on the main thread, - // which is necessary for some OS s - _marker: NonSend, + mut main_thread: ThreadLocal, mut windows: ResMut, mut window_surfaces: ResMut, render_device: Res, @@ -234,20 +227,28 @@ pub fn prepare_windows( let surface_data = window_surfaces .surfaces .entry(window.entity) - .or_insert_with(|| unsafe { - // NOTE: On some OSes this MUST be called from the main thread. - // As of wgpu 0.15, only failable if the given window is a HTML canvas and obtaining a WebGPU or WebGL2 context fails. - let surface = render_instance - .create_surface(&window.handle.get_handle()) - .expect("Failed to create wgpu surface"); + .or_insert_with(|| { + let surface = main_thread.run(|_| { + // SAFETY: raw window handle is valid + unsafe { + render_instance + // Some operating systems only allow dereferencing window handles in + // the *main* thread (and may panic if done in another thread). + .create_surface(&window.handle.get_handle()) + // As of wgpu 0.15, this can only fail if the window is a HTML canvas + // and obtaining a WebGPU/WebGL2 context fails. + .expect("failed to create wgpu surface") + } + }); let caps = surface.get_capabilities(&render_adapter); let formats = caps.formats; - // For future HDR output support, we'll need to request a format that supports HDR, - // but as of wgpu 0.15 that is not yet supported. - // Prefer sRGB formats for surfaces, but fall back to first available format if no sRGB formats are available. - let mut format = *formats.get(0).expect("No supported formats for surface"); + // Prefer sRGB formats, but fall back to first available format if none available. + // NOTE: To support HDR output in the future, we'll need to request a format that + // supports HDR, but as of wgpu 0.15 that is still unsupported. + let mut format = *formats.get(0).expect("no supported formats for surface"); for available_format in formats { - // Rgba8UnormSrgb and Bgra8UnormSrgb and the only sRGB formats wgpu exposes that we can use for surfaces. + // Rgba8UnormSrgb and Bgra8UnormSrgb and the only sRGB formats wgpu exposes + // that we can use for surfaces. if available_format == TextureFormat::Rgba8UnormSrgb || available_format == TextureFormat::Bgra8UnormSrgb { diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index ba887e8057220b..b31ea3a52311c6 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -13,6 +13,7 @@ detailed_trace = [] [dependencies] ahash = "0.8.3" +dyn-clone = "1.0" tracing = { version = "0.1", default-features = false, features = ["std"] } instant = { version = "0.1", features = ["wasm-bindgen"] } uuid = { version = "1.1", features = ["v4", "serde"] } diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 3dc32e81d89045..885808b43442ff 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -24,6 +24,7 @@ mod float_ord; pub use ahash::{AHasher, RandomState}; pub use bevy_utils_proc_macros::*; pub use default::default; +pub use dyn_clone; pub use float_ord::*; pub use hashbrown; pub use instant::{Duration, Instant}; diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 46b3e3e195f599..c89b100f26b4da 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -7,6 +7,17 @@ //! The app's [runner](bevy_app::App::runner) is set by `WinitPlugin` and handles the `winit` [`EventLoop`](winit::event_loop::EventLoop). //! See `winit_runner` for details. +use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender}; +use std::thread::JoinHandle; + +#[cfg(target_os = "android")] +pub use winit::platform::android::activity::AndroidApp; + +use winit::{ + event::{self, DeviceEvent, Event, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopWindowTarget}, +}; + pub mod accessibility; mod converters; mod system; @@ -15,18 +26,17 @@ mod web_resize; mod winit_config; mod winit_windows; -use bevy_a11y::AccessibilityRequested; -use bevy_ecs::system::{SystemParam, SystemState}; -#[cfg(not(target_arch = "wasm32"))] -use bevy_tasks::tick_global_task_pools_on_main_thread; -use system::{changed_window, create_window, despawn_window, CachedWindow}; - +use system::{changed_windows, create_windows, despawn_windows}; pub use winit_config::*; pub use winit_windows::*; -use bevy_app::{App, AppExit, Last, Plugin}; +use bevy_a11y::AccessibilityRequested; +#[cfg(not(target_arch = "wasm32"))] +use bevy_app::AppThreadEvent; +use bevy_app::{App, AppExit, First, Last, Plugin, SubApps}; use bevy_ecs::event::{Events, ManualEventReader}; use bevy_ecs::prelude::*; +use bevy_ecs::system::{SystemParam, SystemState}; use bevy_input::{ keyboard::KeyboardInput, mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel}, @@ -35,9 +45,11 @@ use bevy_input::{ }; use bevy_math::{ivec2, DVec2, Vec2}; use bevy_utils::{ + synccell::SyncCell, tracing::{trace, warn}, - Instant, + Duration, Instant, }; + use bevy_window::{ exit_on_all_closed, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter, RequestRedraw, Window, WindowBackendScaleFactorChanged, @@ -45,17 +57,10 @@ use bevy_window::{ WindowResized, WindowScaleFactorChanged, WindowThemeChanged, }; -#[cfg(target_os = "android")] -pub use winit::platform::android::activity::AndroidApp; - -use winit::{ - event::{self, DeviceEvent, Event, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopWindowTarget}, -}; - use crate::accessibility::{AccessKitAdapters, AccessibilityPlugin, WinitActionHandlers}; - use crate::converters::convert_winit_theme; +use crate::system::CachedWindow; + #[cfg(target_arch = "wasm32")] use crate::web_resize::{CanvasParentResizeEventChannel, CanvasParentResizePlugin}; @@ -63,14 +68,153 @@ use crate::web_resize::{CanvasParentResizeEventChannel, CanvasParentResizePlugin #[cfg(target_os = "android")] pub static ANDROID_APP: std::sync::OnceLock = std::sync::OnceLock::new(); -/// A [`Plugin`] that utilizes [`winit`] for window creation and event loop management. +#[cfg(not(target_arch = "wasm32"))] +pub use platform::*; + +#[cfg(not(target_arch = "wasm32"))] +mod platform { + use std::ops::Deref; + use std::sync::mpsc::{channel, Receiver, Sender}; + + use bevy_app::AppThreadEvent; + use bevy_ecs::prelude::*; + use bevy_ecs::storage::{ThreadLocalTask, ThreadLocalTaskSendError, ThreadLocalTaskSender}; + use bevy_utils::default; + + use crate::WinitAppRunnerState; + + /// [`EventLoopProxy`](winit::event_loop::EventLoopProxy) wrapped in a [`SyncCell`]. + /// Allows other threads to wake the [`winit`] event loop. + #[derive(Resource, Clone)] + pub struct EventLoopProxy(pub SyncCell>); + + /// [`EventLoopWindowTarget`](winit::event_loop::EventLoopWindowTarget) wrapper type. + pub struct EventLoopWindowTarget( + pub(crate) &'static winit::event_loop::EventLoopWindowTarget, + ); + + impl ThreadLocalResource for EventLoopWindowTarget {} + + impl Deref for EventLoopWindowTarget { + type Target = winit::event_loop::EventLoopWindowTarget; + + fn deref(&self) -> &Self::Target { + self.0 + } + } + + impl ThreadLocalTaskSender for EventLoopProxy { + fn send_task( + &mut self, + task: ThreadLocalTask, + ) -> Result<(), ThreadLocalTaskSendError> { + self.0 + .get() + .send_event(AppThreadEvent::Task(task)) + .map_err(|error| ThreadLocalTaskSendError(error.0)) + } + } + + pub struct WinitEventSender { + pub(crate) unfiltered_send: Sender>, + pub(crate) clear_send: Sender, + pub(crate) last_sent: u64, + } + + #[derive(Resource)] + pub struct WinitEventReceiver { + // TODO: reduce allocations + pub(crate) unfiltered_recv: Receiver>, + pub(crate) filtered_send: Sender>, + pub(crate) filtered_recv: Receiver>, + pub(crate) clear_recv: Receiver, + pub(crate) last_processed: u64, + pub(crate) state: WinitAppRunnerState, + } + + pub fn winit_channel() -> (WinitEventSender, WinitEventReceiver) { + let (clear_send, clear_recv) = channel(); + let (unfiltered_send, unfiltered_recv) = channel(); + let (filtered_send, filtered_recv) = channel(); + + let sender = WinitEventSender { + clear_send, + unfiltered_send, + last_sent: 0, + }; + + let receiver = WinitEventReceiver { + unfiltered_recv, + filtered_send, + filtered_recv, + clear_recv, + last_processed: 0, + state: default(), + }; + + (sender, receiver) + } + + impl WinitEventSender { + pub(crate) fn send(&mut self, event: winit::event::Event) { + self.last_sent = self.last_sent.checked_add(1).unwrap(); + self.unfiltered_send.send(event).unwrap(); + } + + /// Informs the receiver that there is a new batch of events to be read. + pub(crate) fn send_clear(&mut self, event: winit::event::Event) { + assert!(matches!(event, winit::event::Event::RedrawEventsCleared)); + self.send(event); + self.clear_send.send(self.last_sent).unwrap(); + } + } + + impl WinitEventReceiver { + fn process_event(&mut self, event: winit::event::Event) { + self.last_processed = self.last_processed.checked_add(1).unwrap(); + match event { + winit::event::Event::WindowEvent { .. } => { + self.state.window_or_device_event_received = true; + self.state.window_event_received = true; + self.send.send(event).unwrap(); + } + winit::event::Event::DeviceEvent { .. } => { + self.state.window_or_device_event_received = true; + self.send.send(event).unwrap(); + } + winit::event::Event::Suspended => { + self.state.is_active = false; + } + winit::event::Event::Resumed => { + self.state.is_active = true; + } + winit::event::Event::RedrawRequested(_) => { + self.state.redraw_requested = true; + } + _ => (), + } + } + + pub(crate) fn process_events_until(&mut self, clear_event: u64) { + while self.last_processed < clear_event { + let event = self.unfiltered_recv.try_recv().unwrap(); + self.process_event(event); + } + } + } +} + +/// Integrates [`winit`], extending an [`App`] with capabilities for managing windows and +/// receiving window and input devicewriter. +/// +/// **NOTE:** This plugin will replace the existing application runner function. #[derive(Default)] pub struct WinitPlugin; impl Plugin for WinitPlugin { fn build(&self, app: &mut App) { - let mut event_loop_builder = EventLoopBuilder::<()>::with_user_event(); - + // setup event loop + let mut event_loop_builder = EventLoopBuilder::::with_user_event(); #[cfg(target_os = "android")] { use winit::platform::android::EventLoopBuilderExtAndroid; @@ -85,18 +229,23 @@ impl Plugin for WinitPlugin { let event_loop = event_loop_builder.build(); app.insert_non_send_resource(event_loop); + // setup app app.init_non_send_resource::() .init_resource::() .set_runner(winit_runner) - // exit_on_all_closed only uses the query to determine if the query is empty, - // and so doesn't care about ordering relative to changed_window + .add_systems(First, flush_winit_events::) .add_systems( Last, ( - changed_window.ambiguous_with(exit_on_all_closed), - // Update the state of the window before attempting to despawn to ensure consistent event ordering - despawn_window.after(changed_window), - ), + // `exit_on_all_closed` seemingly conflicts with `changed_windows` + // but does not actually access any data that would alias (only metadata) + changed_windows.ambiguous_with(exit_on_all_closed), + create_windows::, + despawn_windows, + ) + // apply all changes before despawning windows for consistent event ordering + .chain() + .in_set(ModifiesWindows), ); app.add_plugins(AccessibilityPlugin); @@ -104,126 +253,27 @@ impl Plugin for WinitPlugin { #[cfg(target_arch = "wasm32")] app.add_plugins(CanvasParentResizePlugin); - #[cfg(not(target_arch = "wasm32"))] - let mut create_window_system_state: SystemState<( - Commands, - NonSendMut>, - Query<(Entity, &mut Window)>, - EventWriter, - NonSendMut, - NonSendMut, - ResMut, - ResMut, - )> = SystemState::from_world(&mut app.world); - - #[cfg(target_arch = "wasm32")] - let mut create_window_system_state: SystemState<( - Commands, - NonSendMut>, - Query<(Entity, &mut Window)>, - EventWriter, - NonSendMut, - NonSendMut, - ResMut, - ResMut, - ResMut, - )> = SystemState::from_world(&mut app.world); - - // And for ios and macos, we should not create window early, all ui related code should be executed inside - // UIApplicationMain/NSApplicationMain. + // iOS and macOS do not like it when you create windows outside of the event loop. + // See: + // - https://github.com/rust-windowing/winit/blob/master/README.md#macos + // - https://github.com/rust-windowing/winit/blob/master/README.md#ios + // + // And we just make Android match the iOS config. #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "macos")))] { - #[cfg(not(target_arch = "wasm32"))] - let ( - commands, - event_loop, - mut new_windows, - event_writer, - winit_windows, - adapters, - handlers, - accessibility_requested, - ) = create_window_system_state.get_mut(&mut app.world); - - #[cfg(target_arch = "wasm32")] - let ( - commands, - event_loop, - mut new_windows, - event_writer, - winit_windows, - adapters, - handlers, - accessibility_requested, - event_channel, - ) = create_window_system_state.get_mut(&mut app.world); - - // Here we need to create a winit-window and give it a WindowHandle which the renderer can use. - // It needs to be spawned before the start of the startup schedule, so we cannot use a regular system. - // Instead we need to create the window and spawn it using direct world access - create_window( - commands, - &event_loop, - new_windows.iter_mut(), - event_writer, - winit_windows, - adapters, - handlers, - accessibility_requested, - #[cfg(target_arch = "wasm32")] - event_channel, - ); + // Otherwise, we try to create a window before `bevy_render` initializes + // the renderer, so that we have a surface to use as a hint. + // This improves compatibility with wgpu backends, especially WASM/WebGL2. + let create_windows = IntoSystem::into_system(create_windows::); + create_windows.run((), &mut app.world); + create_windows.apply_deferred(&mut app.world); } - - create_window_system_state.apply(&mut app.world); } } -fn run(event_loop: EventLoop<()>, event_handler: F) -> ! -where - F: 'static + FnMut(Event<'_, ()>, &EventLoopWindowTarget<()>, &mut ControlFlow), -{ - event_loop.run(event_handler) -} - -// TODO: It may be worth moving this cfg into a procedural macro so that it can be referenced by -// a single name instead of being copied around. -// https://gist.github.com/jakerr/231dee4a138f7a5f25148ea8f39b382e seems to work. -#[cfg(any( - target_os = "windows", - target_os = "macos", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" -))] -fn run_return(event_loop: &mut EventLoop<()>, event_handler: F) -where - F: FnMut(Event<'_, ()>, &EventLoopWindowTarget<()>, &mut ControlFlow), -{ - use winit::platform::run_return::EventLoopExtRunReturn; - event_loop.run_return(event_handler); -} - -#[cfg(not(any( - target_os = "windows", - target_os = "macos", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" -)))] -fn run_return(_event_loop: &mut EventLoop<()>, _event_handler: F) -where - F: FnMut(Event<'_, ()>, &EventLoopWindowTarget<()>, &mut ControlFlow), -{ - panic!("Run return is not supported on this platform!") -} - #[derive(SystemParam)] -struct WindowEvents<'w> { +struct WinitEventWriters<'w> { + // window events window_resized: EventWriter<'w, WindowResized>, window_close_requested: EventWriter<'w, WindowCloseRequested>, window_scale_factor_changed: EventWriter<'w, WindowScaleFactorChanged>, @@ -232,10 +282,7 @@ struct WindowEvents<'w> { window_moved: EventWriter<'w, WindowMoved>, window_theme_changed: EventWriter<'w, WindowThemeChanged>, window_destroyed: EventWriter<'w, WindowDestroyed>, -} -#[derive(SystemParam)] -struct InputEvents<'w> { keyboard_input: EventWriter<'w, KeyboardInput>, character_input: EventWriter<'w, ReceivedCharacter>, mouse_button_input: EventWriter<'w, MouseButtonInput>, @@ -244,194 +291,48 @@ struct InputEvents<'w> { mouse_wheel_input: EventWriter<'w, MouseWheel>, touch_input: EventWriter<'w, TouchInput>, ime_input: EventWriter<'w, Ime>, -} + file_drag_and_drop: EventWriter<'w, FileDragAndDrop>, -#[derive(SystemParam)] -struct CursorEvents<'w> { cursor_moved: EventWriter<'w, CursorMoved>, cursor_entered: EventWriter<'w, CursorEntered>, cursor_left: EventWriter<'w, CursorLeft>, -} -// #[cfg(any( -// target_os = "linux", -// target_os = "dragonfly", -// target_os = "freebsd", -// target_os = "netbsd", -// target_os = "openbsd" -// ))] -// pub fn winit_runner_any_thread(app: App) { -// winit_runner_with(app, EventLoop::new_any_thread()); -// } - -/// Stores state that must persist between frames. -struct WinitPersistentState { - /// Tracks whether or not the application is active or suspended. - active: bool, - /// Tracks whether or not an event has occurred this frame that would trigger an update in low - /// power mode. Should be reset at the end of every frame. - low_power_event: bool, - /// Tracks whether the event loop was started this frame because of a redraw request. - redraw_request_sent: bool, - /// Tracks if the event loop was started this frame because of a [`ControlFlow::WaitUntil`] - /// timeout. - timeout_reached: bool, - last_update: Instant, -} -impl Default for WinitPersistentState { - fn default() -> Self { - Self { - active: false, - low_power_event: false, - redraw_request_sent: false, - timeout_reached: false, - last_update: Instant::now(), - } - } + // device events + mouse_motion: EventWriter<'w, MouseMotion>, } -/// The default [`App::runner`] for the [`WinitPlugin`] plugin. -/// -/// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the `EventLoop`. -pub fn winit_runner(mut app: App) { - // We remove this so that we have ownership over it. - let mut event_loop = app - .world - .remove_non_send_resource::>() - .unwrap(); - - let mut app_exit_event_reader = ManualEventReader::::default(); - let mut redraw_event_reader = ManualEventReader::::default(); - let mut winit_state = WinitPersistentState::default(); - app.world - .insert_non_send_resource(event_loop.create_proxy()); - - let return_from_run = app.world.resource::().return_from_run; - - trace!("Entering winit event loop"); - - let mut focused_window_state: SystemState<(Res, Query<&Window>)> = - SystemState::from_world(&mut app.world); - - #[cfg(not(target_arch = "wasm32"))] - let mut create_window_system_state: SystemState<( - Commands, - Query<(Entity, &mut Window), Added>, - EventWriter, - NonSendMut, - NonSendMut, - ResMut, - ResMut, - )> = SystemState::from_world(&mut app.world); - - #[cfg(target_arch = "wasm32")] - let mut create_window_system_state: SystemState<( - Commands, - Query<(Entity, &mut Window), Added>, - EventWriter, - NonSendMut, - NonSendMut, - ResMut, - ResMut, - ResMut, - )> = SystemState::from_world(&mut app.world); - - let mut finished_and_setup_done = false; - - let event_handler = move |event: Event<()>, - event_loop: &EventLoopWindowTarget<()>, - control_flow: &mut ControlFlow| { - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("winit event_handler").entered(); - - if !finished_and_setup_done { - if !app.ready() { - #[cfg(not(target_arch = "wasm32"))] - tick_global_task_pools_on_main_thread(); - } else { - app.finish(); - app.cleanup(); - finished_and_setup_done = true; - } - } - - if let Some(app_exit_events) = app.world.get_resource::>() { - if app_exit_event_reader.iter(app_exit_events).last().is_some() { - *control_flow = ControlFlow::Exit; - return; - } - } - +pub(crate) fn flush_winit_events( + queue: WinitEventReceiver, + mut writers: WinitEventWriters, + local_thread: ThreadLocal, + windows: Query<(&mut Window, &mut CachedWindow)>, +) { + while let Ok(event) = queue.filtered_recv.try_recv() { match event { - event::Event::NewEvents(start) => { - let (winit_config, window_focused_query) = focused_window_state.get(&app.world); - - let app_focused = window_focused_query.iter().any(|window| window.focused); - - // Check if either the `WaitUntil` timeout was triggered by winit, or that same - // amount of time has elapsed since the last app update. This manual check is needed - // because we don't know if the criteria for an app update were met until the end of - // the frame. - let auto_timeout_reached = matches!(start, StartCause::ResumeTimeReached { .. }); - let now = Instant::now(); - let manual_timeout_reached = match winit_config.update_mode(app_focused) { - UpdateMode::Continuous => false, - UpdateMode::Reactive { max_wait } - | UpdateMode::ReactiveLowPower { max_wait } => { - now.duration_since(winit_state.last_update) >= *max_wait - } - }; - // The low_power_event state and timeout must be reset at the start of every frame. - winit_state.low_power_event = false; - winit_state.timeout_reached = auto_timeout_reached || manual_timeout_reached; - } event::Event::WindowEvent { - event, - window_id: winit_window_id, - .. + window_id, event, .. } => { - // Fetch and prepare details from the world - let mut system_state: SystemState<( - NonSend, - Query<(&mut Window, &mut CachedWindow)>, - WindowEvents, - InputEvents, - CursorEvents, - EventWriter, - )> = SystemState::new(&mut app.world); - let ( - winit_windows, - mut window_query, - mut window_events, - mut input_events, - mut cursor_events, - mut file_drag_and_drop_events, - ) = system_state.get_mut(&mut app.world); - - // Entity of this window - let window_entity = - if let Some(entity) = winit_windows.get_window_entity(winit_window_id) { - entity - } else { - warn!( - "Skipped event {:?} for unknown winit Window Id {:?}", - event, winit_window_id - ); - return; - }; - - let (mut window, mut cache) = - if let Ok((window, info)) = window_query.get_mut(window_entity) { - (window, info) - } else { - warn!( - "Window {:?} is missing `Window` component, skipping event {:?}", - window_entity, event - ); - return; - }; + // TODO: store id->entity index in world instead of accessing local thread here + let window_entity = local_thread.run(|tls| { + let winit_windows = tls.resource::(); + winit_windows + .get_window_entity(window_id) + .unwrap_or_else(|| { + warn!( + "Skipped event {:?} for unknown winit Window Id {:?}", + event, winit_window_id + ); + return; + }) + }); - winit_state.low_power_event = true; + let (mut window, mut cache) = windows.get_mut(window_entity).unwrap_or_else(|| { + warn!( + "Window {:?} is missing `Window` component, skipping event {:?}", + window_entity, event + ); + return; + }); match event { WindowEvent::Resized(size) => { @@ -439,67 +340,60 @@ pub fn winit_runner(mut app: App) { .resolution .set_physical_resolution(size.width, size.height); - window_events.window_resized.send(WindowResized { + writers.window_resized.send(WindowResized { window: window_entity, width: window.width(), height: window.height(), }); } WindowEvent::CloseRequested => { - window_events - .window_close_requested - .send(WindowCloseRequested { - window: window_entity, - }); + writers.window_close_requested.send(WindowCloseRequested { + window: window_entity, + }); } WindowEvent::KeyboardInput { ref input, .. } => { - input_events + writers .keyboard_input .send(converters::convert_keyboard_input(input, window_entity)); } WindowEvent::CursorMoved { position, .. } => { let physical_position = DVec2::new(position.x, position.y); - window.set_physical_cursor_position(Some(physical_position)); - - cursor_events.cursor_moved.send(CursorMoved { + writers.cursor_moved.send(CursorMoved { window: window_entity, position: (physical_position / window.resolution.scale_factor()) .as_vec2(), }); } WindowEvent::CursorEntered { .. } => { - cursor_events.cursor_entered.send(CursorEntered { + writers.cursor_entered.send(CursorEntered { window: window_entity, }); } WindowEvent::CursorLeft { .. } => { window.set_physical_cursor_position(None); - - cursor_events.cursor_left.send(CursorLeft { + writers.cursor_left.send(CursorLeft { window: window_entity, }); } WindowEvent::MouseInput { state, button, .. } => { - input_events.mouse_button_input.send(MouseButtonInput { + writers.mouse_button_input.send(MouseButtonInput { button: converters::convert_mouse_button(button), state: converters::convert_element_state(state), window: window_entity, }); } WindowEvent::TouchpadMagnify { delta, .. } => { - input_events + writers .touchpad_magnify_input .send(TouchpadMagnify(delta as f32)); } WindowEvent::TouchpadRotate { delta, .. } => { - input_events - .touchpad_rotate_input - .send(TouchpadRotate(delta)); + writers.touchpad_rotate_input.send(TouchpadRotate(delta)); } WindowEvent::MouseWheel { delta, .. } => match delta { event::MouseScrollDelta::LineDelta(x, y) => { - input_events.mouse_wheel_input.send(MouseWheel { + writers.mouse_wheel_input.send(MouseWheel { unit: MouseScrollUnit::Line, x, y, @@ -507,7 +401,7 @@ pub fn winit_runner(mut app: App) { }); } event::MouseScrollDelta::PixelDelta(p) => { - input_events.mouse_wheel_input.send(MouseWheel { + writers.mouse_wheel_input.send(MouseWheel { unit: MouseScrollUnit::Pixel, x: p.x as f32, y: p.y as f32, @@ -517,23 +411,21 @@ pub fn winit_runner(mut app: App) { }, WindowEvent::Touch(touch) => { let location = touch.location.to_logical(window.resolution.scale_factor()); - - // Event - input_events + writers .touch_input .send(converters::convert_touch_input(touch, location)); } - WindowEvent::ReceivedCharacter(c) => { - input_events.character_input.send(ReceivedCharacter { + WindowEvent::ReceivedCharacter(char) => { + writers.character_input.send(ReceivedCharacter { window: window_entity, - char: c, + char, }); } WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size, } => { - window_events.window_backend_scale_factor_changed.send( + writers.window_backend_scale_factor_changed.send( WindowBackendScaleFactorChanged { window: window_entity, scale_factor, @@ -545,22 +437,23 @@ pub fn winit_runner(mut app: App) { let new_factor = window.resolution.scale_factor(); if let Some(forced_factor) = window.resolution.scale_factor_override() { - // If there is a scale factor override, then force that to be used - // Otherwise, use the OS suggested size - // We have already told the OS about our resize constraints, so - // the new_inner_size should take those into account + // TODO: should this branch send a WindowsScaleFactorChanged event too? + // TODO: word this comment better + // if there is a scale factor override, apply it + // otherwise, apply what the backend suggests + // we've already applied entity resize constraints to the backend, + // so new_inner_size should take those into account *new_inner_size = winit::dpi::LogicalSize::new(window.width(), window.height()) .to_physical::(forced_factor); - // TODO: Should this not trigger a WindowsScaleFactorChanged? } else if approx::relative_ne!(new_factor, prior_factor) { - // Trigger a change event if they are approximately different - window_events.window_scale_factor_changed.send( - WindowScaleFactorChanged { + // send a change event if these are different enough + writers + .window_scale_factor_changed + .send(WindowScaleFactorChanged { window: window_entity, scale_factor, - }, - ); + }); } let new_logical_width = (new_inner_size.width as f64 / new_factor) as f32; @@ -568,7 +461,7 @@ pub fn winit_runner(mut app: App) { if approx::relative_ne!(window.width(), new_logical_width) || approx::relative_ne!(window.height(), new_logical_height) { - window_events.window_resized.send(WindowResized { + writers.window_resized.send(WindowResized { window: window_entity, width: new_logical_width, height: new_logical_height, @@ -579,68 +472,70 @@ pub fn winit_runner(mut app: App) { .set_physical_resolution(new_inner_size.width, new_inner_size.height); } WindowEvent::Focused(focused) => { - // Component window.focused = focused; - - window_events.window_focused.send(WindowFocused { + writers.window_focused.send(WindowFocused { window: window_entity, focused, }); } WindowEvent::DroppedFile(path_buf) => { - file_drag_and_drop_events.send(FileDragAndDrop::DroppedFile { - window: window_entity, - path_buf, - }); + writers + .file_drag_and_drop + .send(FileDragAndDrop::DroppedFile { + window: window_entity, + path_buf, + }); } WindowEvent::HoveredFile(path_buf) => { - file_drag_and_drop_events.send(FileDragAndDrop::HoveredFile { - window: window_entity, - path_buf, - }); + writers + .file_drag_and_drop + .send(FileDragAndDrop::HoveredFile { + window: window_entity, + path_buf, + }); } WindowEvent::HoveredFileCancelled => { - file_drag_and_drop_events.send(FileDragAndDrop::HoveredFileCanceled { - window: window_entity, - }); + writers + .file_drag_and_drop + .send(FileDragAndDrop::HoveredFileCancelled { + window: window_entity, + }); } WindowEvent::Moved(position) => { let position = ivec2(position.x, position.y); - window.position.set(position); - - window_events.window_moved.send(WindowMoved { + writers.window_moved.send(WindowMoved { entity: window_entity, position, }); } WindowEvent::Ime(event) => match event { event::Ime::Preedit(value, cursor) => { - input_events.ime_input.send(Ime::Preedit { + writers.ime_input.send(Ime::Preedit { window: window_entity, value, cursor, }); } - event::Ime::Commit(value) => input_events.ime_input.send(Ime::Commit { + event::Ime::Commit(value) => writers.ime_input.send(Ime::Commit { window: window_entity, value, }), - event::Ime::Enabled => input_events.ime_input.send(Ime::Enabled { + event::Ime::Enabled => writers.ime_input.send(Ime::Enabled { window: window_entity, }), - event::Ime::Disabled => input_events.ime_input.send(Ime::Disabled { + event::Ime::Disabled => writers.ime_input.send(Ime::Disabled { window: window_entity, }), }, WindowEvent::ThemeChanged(theme) => { - window_events.window_theme_changed.send(WindowThemeChanged { + writers.window_theme_changed.send(WindowThemeChanged { window: window_entity, theme: convert_winit_theme(theme), }); } WindowEvent::Destroyed => { - window_events.window_destroyed.send(WindowDestroyed { + writers.window_destroyed.send(WindowDestroyed { window: window_entity, }); } @@ -655,133 +550,309 @@ pub fn winit_runner(mut app: App) { event: DeviceEvent::MouseMotion { delta: (x, y) }, .. } => { - let mut system_state: SystemState> = - SystemState::new(&mut app.world); - let mut mouse_motion = system_state.get_mut(&mut app.world); - - mouse_motion.send(MouseMotion { + writers.mouse_motion.send(MouseMotion { delta: Vec2::new(x as f32, y as f32), }); } - event::Event::Suspended => { - winit_state.active = false; - #[cfg(target_os = "android")] - { - // Bevy doesn't support suspend/resume so we just exit - // and Android will restart the application on resume - // TODO: Save save some state and load on resume - *control_flow = ControlFlow::Exit; + _ => (), + } + } +} + +struct WinitAppRunnerState { + /// Is `true` if the app is running and not suspended. + is_active: bool, + /// Is `true` if a new window or input device event has been received. + window_or_device_event_received: bool, + /// Is `true` if a new window event has been received. + window_event_received: bool, + /// Is `true` if the app has requested a redraw. + redraw_requested: bool, + /// Is `true` if enough time has elapsed since `last_update`. + timeout_elapsed: bool, + /// The time the most recent update started. + last_update: Instant, +} + +impl Default for WinitAppRunnerState { + fn default() -> Self { + Self { + is_active: true, + window_or_device_event_received: false, + window_event_received: false, + redraw_requested: false, + timeout_elapsed: false, + last_update: Instant::now(), + } + } +} + +fn spawn_app_thread(sub_apps: SubApps) -> std::thread::JoinHandle<()> { + use bevy_app::AppThreadEvent; + use winit::event_loop::ControlFlow; + + std::thread::spawn(|| { + let mut focused_windows_state: SystemState<(Query<&Window>, Res)> = + SystemState::from_world(&mut sub_apps.main.world); + let mut redraw_event_reader = ManualEventReader::::default(); + let mut exit_event_reader = ManualEventReader::::default(); + + let mut rx = sub_apps + .main + .world + .remove_resource::>() + .unwrap(); + + let mut control_flow = ControlFlow::Poll; + loop { + let now = Instant::now(); + match control_flow { + ControlFlow::Poll => { + // check for RedrawEventsCleared + match rx.clear_recv.try_recv() { + Ok(clear_event) => { + rx.process_events_until(clear_event); + while let Ok(clear_event) = rx.clear_recv.try_recv() { + rx.process_events_until(clear_event); + } + } + Err(TryRecvError::Empty) => {} + Err(TryRecvError::Disconnected) => { + trace!("terminating app because event loop disconnected"); + return; + } + } } - } - event::Event::Resumed => { - winit_state.active = true; - } - event::Event::MainEventsCleared => { - let (winit_config, window_focused_query) = focused_window_state.get(&app.world); - - let update = if winit_state.active { - // True if _any_ windows are currently being focused - let app_focused = window_focused_query.iter().any(|window| window.focused); - match winit_config.update_mode(app_focused) { - UpdateMode::Continuous | UpdateMode::Reactive { .. } => true, - UpdateMode::ReactiveLowPower { .. } => { - winit_state.low_power_event - || winit_state.redraw_request_sent - || winit_state.timeout_reached + ControlFlow::Wait => { + // wait until we receive RedrawEventsCleared + if let Ok(clear_event) = rx.clear_recv.recv() { + rx.process_events_until(clear_event); + while let Ok(clear_event) = rx.clear_recv.try_recv() { + rx.process_events_until(clear_event); } + } else { + trace!("terminating app because event loop disconnected"); + return; } - } else { - false - }; + } + ControlFlow::WaitUntil(next) => { + // wait until we receive RedrawEventsCleared or enough time passes + let timeout = next.checked_duration_since(now).unwrap_or(Duration::ZERO); + match rx.clear_recv.recv_timeout(timeout) { + Ok(clear_event) => { + rx.process_events_until(clear_event); + while let Ok(clear_event) = rx.clear_recv.try_recv() { + rx.process_events_until(clear_event); + } - if update && finished_and_setup_done { - winit_state.last_update = Instant::now(); - app.update(); + let (windows, config) = focused_windows_state.get(&world); + let focused = windows.iter().any(|window| window.focused); + rx.state.timeout_elapsed = match config.update_mode(focused) { + UpdateMode::Continuous => unreachable!(), + UpdateMode::RateLimited { .. } + | UpdateMode::Reactive { .. } + | UpdateMode::ReactiveLowPower { .. } => timeout.is_zero(), + }; + } + Err(RecvTimeoutError::Timeout) => { + rx.state.timeout_elapsed = true; + } + Err(RecvTimeoutError::Disconnected) => { + trace!("terminating app because event loop disconnected"); + return; + } + } + } + ControlFlow::ExitWithCode(_) => { + trace!("exiting app"); + // return sub-apps to the main thread + event_loop_proxy + .send_event(AppThreadEvent::Exit(sub_apps)) + .unwrap(); + return; } } - Event::RedrawEventsCleared => { - { - // Fetch from world - let (winit_config, window_focused_query) = focused_window_state.get(&app.world); - - // True if _any_ windows are currently being focused - let app_focused = window_focused_query.iter().any(|window| window.focused); - - let now = Instant::now(); - use UpdateMode::*; - *control_flow = match winit_config.update_mode(app_focused) { - Continuous => ControlFlow::Poll, - Reactive { max_wait } | ReactiveLowPower { max_wait } => { - if let Some(instant) = now.checked_add(*max_wait) { - ControlFlow::WaitUntil(instant) + + if rx.state.is_active { + let (windows, config) = focused_windows_state.get(&sub_apps.main.world); + let focused = windows.iter().any(|window| window.focused); + let should_update = match config.update_mode(focused) { + UpdateMode::Continuous => true, + UpdateMode::RateLimited { .. } => { + rx.state.timeout_elapsed || rx.state.redraw_requested + } + UpdateMode::Reactive { .. } => { + rx.state.timeout_elapsed + || rx.state.redraw_requested + || rx.state.window_or_device_event_received + } + UpdateMode::ReactiveLowPower { .. } => { + rx.state.timeout_elapsed + || rx.state.redraw_requested + || rx.state.window_event_received + } + }; + + if should_update { + // reset these flags + rx.state.timeout_elapsed = false; + rx.state.window_or_device_event_received = false; + rx.state.window_event_received = false; + rx.state.redraw_requested = false; + rx.state.last_update = now; + + sub_apps.main.world.insert_resource(rx); + sub_apps.update(); + rx = sub_apps + .main + .world + .remove_resource::>() + .unwrap(); + + // decide when to run the next update + let (windows, config) = focused_windows_state.get(&sub_apps.main.world); + let focused = windows.iter().any(|window| window.focused); + match config.update_mode(focused) { + UpdateMode::Continuous => control_flow = ControlFlow::Poll, + UpdateMode::RateLimited { wait } + | UpdateMode::Reactive { wait } + | UpdateMode::ReactiveLowPower { wait } => { + if let Some(next) = events.state.last_update.checked_add(wait) { + control_flow = ControlFlow::WaitUntil(next); } else { - ControlFlow::Wait + control_flow = ControlFlow::Wait; } } - }; - } + } - // This block needs to run after `app.update()` in `MainEventsCleared`. Otherwise, - // we won't be able to see redraw requests until the next event, defeating the - // purpose of a redraw request! - let mut redraw = false; - if let Some(app_redraw_events) = app.world.get_resource::>() { - if redraw_event_reader.iter(app_redraw_events).last().is_some() { - *control_flow = ControlFlow::Poll; - redraw = true; + if let Some(redraw_events) = + sub_apps.main.world.get_resource::>() + { + if redraw_event_reader.iter(redraw_events).last().is_some() { + rx.state.redraw_requested = true; + control_flow = ControlFlow::Poll; + } } - } - winit_state.redraw_request_sent = redraw; + if let Some(exit_events) = sub_apps.main.world.get_resource::>() + { + if exit_event_reader.iter(exit_events).last().is_some() { + control_flow = ControlFlow::Exit; + } + } + } + } else { + #[cfg(target_os = "android")] + { + // Android sending this event invalidates all render surfaces. + // TODO + // Upon resume, check if the new render surfaces are compatible with the + // existing render device. If not (which should basically never happen), + // *then* try to rebuild the renderer. + control_flow = ControlFlow::Exit; + } } - - _ => (), } + }) +} - if winit_state.active { - #[cfg(not(target_arch = "wasm32"))] - let ( - commands, - mut new_windows, - created_window_writer, - winit_windows, - adapters, - handlers, - accessibility_requested, - ) = create_window_system_state.get_mut(&mut app.world); - - #[cfg(target_arch = "wasm32")] - let ( - commands, - mut new_windows, - created_window_writer, - winit_windows, - adapters, - handlers, - accessibility_requested, - canvas_parent_resize_channel, - ) = create_window_system_state.get_mut(&mut app.world); - - // Responsible for creating new windows - create_window( - commands, - event_loop, - new_windows.iter_mut(), - created_window_writer, - winit_windows, - adapters, - handlers, - accessibility_requested, - #[cfg(target_arch = "wasm32")] - canvas_parent_resize_channel, - ); +pub fn winit_runner(mut app: App) { + let (mut sub_apps, mut tls, _) = app.into_parts(); + let return_on_loop_exit = sub_apps + .main + .world + .resource::() + .return_on_loop_exit; + let mut event_loop = tls.remove_resource::>().unwrap(); + let app_event_send = EventLoopProxy(SyncCell::new(event_loop.create_proxy())); + let (winit_event_send, winit_event_recv) = winit_channel::(); + + let thread: std::thread::JoinHandle<()>; + + let event_handler = move |event: Event, + event_loop: &EventLoopWindowTarget, + control_flow: &mut ControlFlow| { + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!("winit event_handler").entered(); + + // this thread should sleep when it isn't processing events + *control_flow = ControlFlow::Wait; + + match event { + Event::NewEvents(StartCause::Init) => { + // insert `winit` event channel + sub_apps.main.world.insert_resource(receiver); + + // SAFETY: + // - This event loop lives at least as long as the app itself. + // - This reference is valid for the lifetime of the event loop. + // - This reference cannot be cloned or copied. + // - This reference is removed and dropped before the event loop returns. + let window_target: &'static EventLoopWindowTarget = + unsafe { core::mem::transmute(event_loop) }; + tls.insert_resource(crate::platform::EventLoopWindowTarget(window_target)); + + #[cfg(any(target_os = "android", target_os = "ios", target_os = "macos"))] + { + let create_windows = IntoSystem::into_system(create_windows::); + create_windows.run((), &mut world); + create_windows.apply_deferred(&mut world); + } + + // insert TLS accessor + sub_apps.for_each(|sub_app| { + // SAFETY: `tls` is not moved or dropped until `access` has been dropped. + let access = unsafe { + ThreadLocalAccessor::new( + std::ptr::addr_of_mut!(tls), + app_event_send.clone(), + ) + }; + sub_app.world.insert_resource(access); + }); + + // send the sub-apps to a different thread + thread = spawn_app_thread(sub_apps); + } + Event::UserEvent(event) => { + match event { + AppThreadEvent::RunTask(f) => { + // running `f` here is the only time the event loop is "blocked" + f(&mut tls); + } + AppThreadEvent::Exit(sub_apps) => { + // remove TLS accessor + sub_apps.main.world.remove_resource::(); + for sub_app in sub_apps.sub_apps.values_mut() { + sub_app.world.remove_resource::(); + } + + // remove channel + sub_apps + .main + .world + .remove_resource::>(); - create_window_system_state.apply(&mut app.world); + *control_flow = ControlFlow::Exit; + } + } + } + Event::RedrawEventsCleared => { + winit_event_send.send_clear(event).unwrap(); + } + Event::LoopDestroyed => { + assert!(thread.is_finished()); + // SAFETY: Resource must be dropped before returning. + tls.remove_resource::>(); + } + _ => { + winit_event_send.send(event).unwrap(); + } } }; - // If true, returns control from Winit back to the main Bevy loop - if return_from_run { + trace!("starting winit event loop"); + if return_on_loop_exit { run_return(&mut event_loop, event_handler); } else { run(event_loop, event_handler); diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 92a3ec2ca96b05..3f15e1c2fd1e68 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -1,12 +1,5 @@ use bevy_a11y::AccessibilityRequested; -use bevy_ecs::{ - entity::Entity, - event::EventWriter, - prelude::{Changed, Component, Resource}, - removal_detection::RemovedComponents, - system::{Commands, NonSendMut, Query, ResMut}, - world::Mut, -}; +use bevy_ecs::prelude::*; use bevy_utils::{ tracing::{error, info, warn}, HashMap, @@ -14,104 +7,109 @@ use bevy_utils::{ use bevy_window::{RawHandleWrapper, Window, WindowClosed, WindowCreated}; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; -use winit::{ - dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, - event_loop::EventLoopWindowTarget, -}; +use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; #[cfg(target_arch = "wasm32")] use crate::web_resize::{CanvasParentResizeEventChannel, WINIT_CANVAS_SELECTOR}; use crate::{ accessibility::{AccessKitAdapters, WinitActionHandlers}, + attempt_grab, converters::{self, convert_window_level, convert_window_theme, convert_winit_theme}, - get_best_videomode, get_fitting_videomode, WinitWindows, + get_best_videomode, get_fitting_videomode, EventLoopWindowTarget, WinitWindows, }; -/// System responsible for creating new windows whenever a [`Window`] component is added -/// to an entity. +/// Creates a new window on the [`winit`] backend for each entity with a newly-added +/// [`Window`] component. /// -/// This will default any necessary components if they are not already added. +/// If any of these entities are missing required components, those will be added with their +/// default values. #[allow(clippy::too_many_arguments)] -pub(crate) fn create_window<'a>( +pub(crate) fn create_windows<'a, T>( mut commands: Commands, - event_loop: &EventLoopWindowTarget<()>, - created_windows: impl Iterator)>, + created_windows: Query<(Entity, &mut Window)>, mut event_writer: EventWriter, - mut winit_windows: NonSendMut, - mut adapters: NonSendMut, + mut local_thread: ThreadLocal, mut handlers: ResMut, mut accessibility_requested: ResMut, #[cfg(target_arch = "wasm32")] event_channel: ResMut, ) { - for (entity, mut window) in created_windows { - if winit_windows.get_window(entity).is_some() { - continue; - } + local_thread.scope(|tls| { + tls.resource_scope(|tls, mut winit_windows: Mut| { + tls.resource_scope(|tls, mut adapters: Mut| { + for (entity, mut window) in created_windows { + if winit_windows.get_window(entity).is_some() { + continue; + } - info!( - "Creating new window {:?} ({:?})", - window.title.as_str(), - entity - ); + info!( + "Creating new window {:?} ({:?})", + window.title.as_str(), + entity + ); - let winit_window = winit_windows.create_window( - event_loop, - entity, - &window, - &mut adapters, - &mut handlers, - &mut accessibility_requested, - ); + let event_loop = tls.resource::>(); - if let Some(theme) = winit_window.theme() { - window.window_theme = Some(convert_winit_theme(theme)); - } + let winit_window = winit_windows.create_window( + event_loop, + entity, + &window, + &mut adapters, + &mut handlers, + &mut accessibility_requested, + ); - window - .resolution - .set_scale_factor(winit_window.scale_factor()); - commands - .entity(entity) - .insert(RawHandleWrapper { - window_handle: winit_window.raw_window_handle(), - display_handle: winit_window.raw_display_handle(), - }) - .insert(CachedWindow { - window: window.clone(), - }); + if let Some(theme) = winit_window.theme() { + window.window_theme = Some(convert_winit_theme(theme)); + } - #[cfg(target_arch = "wasm32")] - { - if window.fit_canvas_to_parent { - let selector = if let Some(selector) = &window.canvas { - selector - } else { - WINIT_CANVAS_SELECTOR - }; - event_channel.listen_to_selector(entity, selector); - } - } + window + .resolution + .set_scale_factor(winit_window.scale_factor()); + commands + .entity(entity) + .insert(RawHandleWrapper { + window_handle: winit_window.raw_window_handle(), + display_handle: winit_window.raw_display_handle(), + }) + .insert(CachedWindow { + window: window.clone(), + }); + + #[cfg(target_arch = "wasm32")] + { + if window.fit_canvas_to_parent { + let selector = if let Some(selector) = &window.canvas { + selector + } else { + WINIT_CANVAS_SELECTOR + }; + event_channel.listen_to_selector(entity, selector); + } + } - event_writer.send(WindowCreated { window: entity }); - } + event_writer.send(WindowCreated { window: entity }); + } + }); + }); + }); } /// Cache for closing windows so we can get better debug information. #[derive(Debug, Clone, Resource)] pub struct WindowTitleCache(HashMap); -pub(crate) fn despawn_window( +pub(crate) fn despawn_windows( mut closed: RemovedComponents, window_entities: Query<&Window>, mut close_events: EventWriter, - mut winit_windows: NonSendMut, + mut backend: ThreadLocalMut, ) { for window in closed.iter() { info!("Closing window {:?}", window); // Guard to verify that the window is in fact actually gone, // rather than having the component added and removed in the same frame. if !window_entities.contains(window) { - winit_windows.remove_window(window); + backend.remove_window(window); close_events.send(WindowClosed { window }); } } @@ -123,189 +121,191 @@ pub struct CachedWindow { pub window: Window, } -// Detect changes to the window and update the winit window accordingly. -// -// Notes: -// - [`Window::present_mode`] and [`Window::composite_alpha_mode`] updating should be handled in the bevy render crate. -// - [`Window::transparent`] currently cannot be updated after startup for winit. -// - [`Window::canvas`] currently cannot be updated after startup, not entirely sure if it would work well with the -// event channel stuff. -pub(crate) fn changed_window( +/// Propagates changes from window entities to the [`winit`] backend. +/// +/// # Notes +/// +/// - [`Window::present_mode`] and [`Window::composite_alpha_mode`] changes are handled by the `bevy_render` crate. +/// - [`Window::transparent`] cannot be changed after the window is created. +/// - [`Window::canvas`] cannot be changed after the window is created. +pub(crate) fn changed_windows( mut changed_windows: Query<(Entity, &mut Window, &mut CachedWindow), Changed>, - winit_windows: NonSendMut, + mut local_thread: ThreadLocal, ) { - for (entity, mut window, mut cache) in &mut changed_windows { - if let Some(winit_window) = winit_windows.get_window(entity) { - if window.title != cache.window.title { - winit_window.set_title(window.title.as_str()); - } - - if window.mode != cache.window.mode { - let new_mode = match window.mode { - bevy_window::WindowMode::BorderlessFullscreen => { - Some(winit::window::Fullscreen::Borderless(None)) + local_thread.scope(|tls| { + let winit_windows = tls.resource_mut::(); + for (entity, mut window, mut cache) in &mut changed_windows { + if let Some(winit_window) = winit_windows.get_window(entity) { + if window.title != cache.window.title { + winit_window.set_title(window.title.as_str()); + } + + if window.mode != cache.window.mode { + let new_mode = match window.mode { + bevy_window::WindowMode::BorderlessFullscreen => { + Some(winit::window::Fullscreen::Borderless(None)) + } + bevy_window::WindowMode::Fullscreen => { + Some(winit::window::Fullscreen::Exclusive(get_best_videomode( + &winit_window.current_monitor().unwrap(), + ))) + } + bevy_window::WindowMode::SizedFullscreen => { + Some(winit::window::Fullscreen::Exclusive(get_fitting_videomode( + &winit_window.current_monitor().unwrap(), + window.width() as u32, + window.height() as u32, + ))) + } + bevy_window::WindowMode::Windowed => None, + }; + + if winit_window.fullscreen() != new_mode { + winit_window.set_fullscreen(new_mode); } - bevy_window::WindowMode::Fullscreen => { - Some(winit::window::Fullscreen::Exclusive(get_best_videomode( - &winit_window.current_monitor().unwrap(), - ))) + } + if window.resolution != cache.window.resolution { + let physical_size = PhysicalSize::new( + window.resolution.physical_width(), + window.resolution.physical_height(), + ); + winit_window.set_inner_size(physical_size); + } + + if window.physical_cursor_position() != cache.window.physical_cursor_position() { + if let Some(physical_position) = window.physical_cursor_position() { + let inner_size = winit_window.inner_size(); + + let position = PhysicalPosition::new( + physical_position.x, + // Flip the coordinate space back to winit's context. + inner_size.height as f32 - physical_position.y, + ); + + if let Err(err) = winit_window.set_cursor_position(position) { + error!("could not set cursor position: {:?}", err); + } } - bevy_window::WindowMode::SizedFullscreen => { - Some(winit::window::Fullscreen::Exclusive(get_fitting_videomode( - &winit_window.current_monitor().unwrap(), - window.width() as u32, - window.height() as u32, - ))) + } + + if window.cursor.icon != cache.window.cursor.icon { + winit_window.set_cursor_icon(converters::convert_cursor_icon(window.cursor.icon)); + } + + if window.cursor.grab_mode != cache.window.cursor.grab_mode { + attempt_grab(winit_window, window.cursor.grab_mode); + } + + if window.cursor.visible != cache.window.cursor.visible { + winit_window.set_cursor_visible(window.cursor.visible); + } + + if window.cursor.hit_test != cache.window.cursor.hit_test { + if let Err(err) = winit_window.set_cursor_hittest(window.cursor.hit_test) { + window.cursor.hit_test = cache.window.cursor.hit_test; + warn!( + "Could not set cursor hit test for window {:?}: {:?}", + window.title, err + ); } - bevy_window::WindowMode::Windowed => None, - }; - - if winit_window.fullscreen() != new_mode { - winit_window.set_fullscreen(new_mode); } - } - if window.resolution != cache.window.resolution { - let physical_size = PhysicalSize::new( - window.resolution.physical_width(), - window.resolution.physical_height(), - ); - winit_window.set_inner_size(physical_size); - } - - if window.physical_cursor_position() != cache.window.physical_cursor_position() { - if let Some(physical_position) = window.physical_cursor_position() { - let inner_size = winit_window.inner_size(); - - let position = PhysicalPosition::new( - physical_position.x, - // Flip the coordinate space back to winit's context. - inner_size.height as f32 - physical_position.y, - ); - - if let Err(err) = winit_window.set_cursor_position(position) { - error!("could not set cursor position: {:?}", err); + + if window.decorations != cache.window.decorations + && window.decorations != winit_window.is_decorated() + { + winit_window.set_decorations(window.decorations); + } + + if window.resizable != cache.window.resizable + && window.resizable != winit_window.is_resizable() + { + winit_window.set_resizable(window.resizable); + } + + if window.resize_constraints != cache.window.resize_constraints { + let constraints = window.resize_constraints.check_constraints(); + let min_inner_size = LogicalSize { + width: constraints.min_width, + height: constraints.min_height, + }; + let max_inner_size = LogicalSize { + width: constraints.max_width, + height: constraints.max_height, + }; + + winit_window.set_min_inner_size(Some(min_inner_size)); + if constraints.max_width.is_finite() && constraints.max_height.is_finite() { + winit_window.set_max_inner_size(Some(max_inner_size)); } } - } - - if window.cursor.icon != cache.window.cursor.icon { - winit_window.set_cursor_icon(converters::convert_cursor_icon(window.cursor.icon)); - } - - if window.cursor.grab_mode != cache.window.cursor.grab_mode { - crate::winit_windows::attempt_grab(winit_window, window.cursor.grab_mode); - } - - if window.cursor.visible != cache.window.cursor.visible { - winit_window.set_cursor_visible(window.cursor.visible); - } - - if window.cursor.hit_test != cache.window.cursor.hit_test { - if let Err(err) = winit_window.set_cursor_hittest(window.cursor.hit_test) { - window.cursor.hit_test = cache.window.cursor.hit_test; + + if window.position != cache.window.position { + if let Some(position) = crate::winit_window_position( + &window.position, + &window.resolution, + winit_window.available_monitors(), + winit_window.primary_monitor(), + winit_window.current_monitor(), + ) { + let should_set = match winit_window.outer_position() { + Ok(current_position) => current_position != position, + _ => true, + }; + + if should_set { + winit_window.set_outer_position(position); + } + } + } + + if let Some(maximized) = window.internal.take_maximize_request() { + winit_window.set_maximized(maximized); + } + + if let Some(minimized) = window.internal.take_minimize_request() { + winit_window.set_minimized(minimized); + } + + if window.focused != cache.window.focused && window.focused { + winit_window.focus_window(); + } + + if window.window_level != cache.window.window_level { + winit_window.set_window_level(convert_window_level(window.window_level)); + } + + if window.transparent != cache.window.transparent { + window.transparent = cache.window.transparent; warn!( - "Could not set cursor hit test for window {:?}: {:?}", - window.title, err + "Currently, `winit` does not support changing a window's transparency after it's been created." ); } - } - - if window.decorations != cache.window.decorations - && window.decorations != winit_window.is_decorated() - { - winit_window.set_decorations(window.decorations); - } - - if window.resizable != cache.window.resizable - && window.resizable != winit_window.is_resizable() - { - winit_window.set_resizable(window.resizable); - } - - if window.resize_constraints != cache.window.resize_constraints { - let constraints = window.resize_constraints.check_constraints(); - let min_inner_size = LogicalSize { - width: constraints.min_width, - height: constraints.min_height, - }; - let max_inner_size = LogicalSize { - width: constraints.max_width, - height: constraints.max_height, - }; - - winit_window.set_min_inner_size(Some(min_inner_size)); - if constraints.max_width.is_finite() && constraints.max_height.is_finite() { - winit_window.set_max_inner_size(Some(max_inner_size)); + + #[cfg(target_arch = "wasm32")] + if window.canvas != cache.window.canvas { + window.canvas = cache.window.canvas.clone(); + warn!( + "Currently, `winit` does not support changing a window's canvas after it's been created." + ); } - } - - if window.position != cache.window.position { - if let Some(position) = crate::winit_window_position( - &window.position, - &window.resolution, - winit_window.available_monitors(), - winit_window.primary_monitor(), - winit_window.current_monitor(), - ) { - let should_set = match winit_window.outer_position() { - Ok(current_position) => current_position != position, - _ => true, - }; - - if should_set { - winit_window.set_outer_position(position); - } + + if window.ime_enabled != cache.window.ime_enabled { + winit_window.set_ime_allowed(window.ime_enabled); } + + if window.ime_position != cache.window.ime_position { + winit_window.set_ime_position(LogicalPosition::new( + window.ime_position.x, + window.ime_position.y, + )); + } + + if window.window_theme != cache.window.window_theme { + winit_window.set_theme(window.window_theme.map(convert_window_theme)); + } + + cache.window = window.clone(); } - - if let Some(maximized) = window.internal.take_maximize_request() { - winit_window.set_maximized(maximized); - } - - if let Some(minimized) = window.internal.take_minimize_request() { - winit_window.set_minimized(minimized); - } - - if window.focused != cache.window.focused && window.focused { - winit_window.focus_window(); - } - - if window.window_level != cache.window.window_level { - winit_window.set_window_level(convert_window_level(window.window_level)); - } - - // Currently unsupported changes - if window.transparent != cache.window.transparent { - window.transparent = cache.window.transparent; - warn!( - "Winit does not currently support updating transparency after window creation." - ); - } - - #[cfg(target_arch = "wasm32")] - if window.canvas != cache.window.canvas { - window.canvas = cache.window.canvas.clone(); - warn!( - "Bevy currently doesn't support modifying the window canvas after initialization." - ); - } - - if window.ime_enabled != cache.window.ime_enabled { - winit_window.set_ime_allowed(window.ime_enabled); - } - - if window.ime_position != cache.window.ime_position { - winit_window.set_ime_position(LogicalPosition::new( - window.ime_position.x, - window.ime_position.y, - )); - } - - if window.window_theme != cache.window.window_theme { - winit_window.set_theme(window.window_theme.map(convert_window_theme)); - } - - cache.window = window.clone(); } - } + }); } diff --git a/crates/bevy_winit/src/web_resize.rs b/crates/bevy_winit/src/web_resize.rs index a53075dea38837..e2acf22111fe71 100644 --- a/crates/bevy_winit/src/web_resize.rs +++ b/crates/bevy_winit/src/web_resize.rs @@ -26,14 +26,17 @@ pub(crate) struct CanvasParentResizeEventChannel { } fn canvas_parent_resize_event_handler( - winit_windows: NonSend, + local_thread: ThreadLocal, resize_events: Res, ) { - for event in resize_events.receiver.try_iter() { - if let Some(window) = winit_windows.get_window(event.window) { - window.set_inner_size(event.size); + local_thread.run(|tls| { + let winit_windows = tls.resource::(); + for event in resize_events.receiver.try_iter() { + if let Some(window) = winit_windows.get_window(event.window) { + window.set_inner_size(event.size); + } } - } + }); } fn get_size(selector: &str) -> Option> { diff --git a/crates/bevy_winit/src/winit_config.rs b/crates/bevy_winit/src/winit_config.rs index ec2ff83127488c..bb3869304eeb4b 100644 --- a/crates/bevy_winit/src/winit_config.rs +++ b/crates/bevy_winit/src/winit_config.rs @@ -1,113 +1,180 @@ use bevy_ecs::system::Resource; use bevy_utils::Duration; -/// A resource for configuring usage of the [`winit`] library. +use winit::event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}; + +/// Determines how frequently an [`App`](bevy_app::App) should update. +/// +/// **NOTE:** This setting is independent of VSync. VSync is controlled by a window's +/// [`PresentMode`](bevy_window::PresentMode) setting. If an app can update faster than +/// the refresh rate, but VSync is enabled, the update rate will be indirectly limited +/// by the renderer. +#[derive(Debug, Clone, Copy)] +pub enum UpdateMode { + /// The [`App`](bevy_app::App) will update over and over, as fast as it possibly can, + /// until an [`AppExit`](bevy_app::AppExit) event appears. + Continuous, + /// The [`App`](bevy_app::App) will update in response to the following, + /// until an [`AppExit`](bevy_app::AppExit) event appears: + /// - enough time has elapsed since the previous update + /// - a redraw is requested + RateLimited { + /// The minimum time from the start of one update to the next. + /// + /// **Note:** This has no upper limit. + /// The [`App`](bevy_app) wait forever if you set this to [`Duration::MAX`]. + wait: Duration, + }, + /// The [`App`](bevy_app::App) will update in response to the following, + /// until an [`AppExit`](bevy_app::AppExit) event appears: + /// - enough time has elapsed since the previous update + /// - a redraw is requested + /// - new window or device events have appeared + Reactive { + /// The minimum time from the start of one update to the next. + /// + /// **Note:** This has no upper limit. + /// The [`App`](bevy_app) wait forever if you set this to [`Duration::MAX`]. + wait: Duration, + }, + /// The [`App`](bevy_app::App) will update in response to the following, + /// until an [`AppExit`](bevy_app::AppExit) event appears: + /// - enough time has elapsed since the previous update + /// - a redraw is requested + /// - new window events have appeared + /// + /// **Note:** Unlike [`Reactive`](`UpdateMode::Reactive`), this mode ignores device events. + /// Use this mode if, for example, you only want your app to update when the mouse cursor is + /// moving over a window, not just moving in general. This can greatly reduce power consumption. + ReactiveLowPower { + /// The minimum time from the start of one update to the next. + /// + /// **Note:** This has no upper limit. + /// The [`App`](bevy_app) wait forever if you set this to [`Duration::MAX`]. + wait: Duration, + }, +} + +/// Settings for the [`WinitPlugin`](super::WinitPlugin) app runner. #[derive(Debug, Resource)] pub struct WinitSettings { - /// Configures `winit` to return control to the caller after exiting the - /// event loop, enabling [`App::run()`](bevy_app::App::run()) to return. + /// Controls how the [`EventLoop`](winit::event_loop::EventLoop) is deployed. /// - /// By default, [`return_from_run`](Self::return_from_run) is `false` and *Bevy* - /// will use `winit`'s - /// [`EventLoop::run()`](https://docs.rs/winit/latest/winit/event_loop/struct.EventLoop.html#method.run) - /// to initiate the event loop. - /// [`EventLoop::run()`](https://docs.rs/winit/latest/winit/event_loop/struct.EventLoop.html#method.run) - /// will never return but will terminate the process after the event loop exits. + /// - If this value is set to `false` (default), [`run`] is called, and exiting the loop will + /// terminate the program. + /// - If this value is set to `true`, [`run_return`] is called, and exiting the loop will + /// return control to the caller. /// - /// Setting [`return_from_run`](Self::return_from_run) to `true` will cause *Bevy* - /// to use `winit`'s - /// [`EventLoopExtRunReturn::run_return()`](https://docs.rs/winit/latest/winit/platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return) - /// instead which is strongly discouraged by the `winit` authors. + /// **NOTE:** This cannot be changed while the loop is running. `winit` discourages use of `run_return`. /// /// # Supported platforms /// - /// This feature is only available on the following desktop `target_os` configurations: - /// `windows`, `macos`, `linux`, `dragonfly`, `freebsd`, `netbsd`, and `openbsd`. + /// `run_return` is only available on the following `target_os` environments: + /// - `windows` + /// - `macos` + /// - `linux` + /// - `freebsd` + /// - `openbsd` + /// - `netbsd` + /// - `dragonfly` /// - /// Setting [`return_from_run`](Self::return_from_run) to `true` on - /// unsupported platforms will cause [`App::run()`](bevy_app::App::run()) to panic! - pub return_from_run: bool, - /// Configures how the winit event loop updates while the window is focused. + /// The runner will panic if this is set to `true` on other platforms. + /// + /// [`run`]: https://docs.rs/winit/latest/winit/event_loop/struct.EventLoop.html#method.run + /// [`run_return`]: https://docs.rs/winit/latest/winit/platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return + pub return_on_loop_exit: bool, + /// Determines how frequently the [`App`](bevy_app::App) can update when it has focus. pub focused_mode: UpdateMode, - /// Configures how the winit event loop updates while the window is *not* focused. + /// Determines how frequently the [`App`](bevy_app::App) can update when it's out of focus. pub unfocused_mode: UpdateMode, } + impl WinitSettings { - /// Configure winit with common settings for a game. + /// Default settings for games. + /// + /// [`Continuous`](UpdateMode::Continuous) if windows have focus, + /// [`RateLimited`](UpdateMode::RateLimited) otherwise. pub fn game() -> Self { - WinitSettings::default() + WinitSettings { + focused_mode: UpdateMode::Continuous, + unfocused_mode: UpdateMode::RateLimited { + wait: Duration::from_millis(50), // 20Hz + }, + ..Default::default() + } } - /// Configure winit with common settings for a desktop application. + /// Default settings for desktop applications. + /// + /// [`Reactive`](UpdateMode::Reactive) if windows have focus, + /// [`ReactiveLowPower`](UpdateMode::ReactiveLowPower) otherwise. pub fn desktop_app() -> Self { WinitSettings { focused_mode: UpdateMode::Reactive { - max_wait: Duration::from_secs(5), + wait: Duration::from_secs(5), }, unfocused_mode: UpdateMode::ReactiveLowPower { - max_wait: Duration::from_secs(60), + wait: Duration::from_secs(60), }, ..Default::default() } } - /// Gets the configured [`UpdateMode`] depending on whether the window is focused or not - pub fn update_mode(&self, focused: bool) -> &UpdateMode { + /// Returns the focused or unfocused [`UpdateMode`]. + pub fn update_mode(&self, focused: bool) -> UpdateMode { match focused { - true => &self.focused_mode, - false => &self.unfocused_mode, + true => self.focused_mode, + false => self.unfocused_mode, } } } + impl Default for WinitSettings { fn default() -> Self { WinitSettings { - return_from_run: false, + return_on_loop_exit: false, focused_mode: UpdateMode::Continuous, unfocused_mode: UpdateMode::Continuous, } } } -/// Configure how the winit event loop should update. -#[derive(Debug)] -pub enum UpdateMode { - /// The event loop will update continuously, running as fast as possible. - Continuous, - /// The event loop will only update if there is a winit event, a redraw is requested, or the - /// maximum wait time has elapsed. - /// - /// ## Note - /// - /// Once the app has executed all bevy systems and reaches the end of the event loop, there is - /// no way to force the app to wake and update again, unless a `winit` event (such as user - /// input, or the window being resized) is received or the time limit is reached. - Reactive { - /// The maximum time to wait before the event loop runs again. - /// - /// Note that Bevy will wait indefinitely if the duration is too high (such as [`Duration::MAX`]). - max_wait: Duration, - }, - /// The event loop will only update if there is a winit event from direct interaction with the - /// window (e.g. mouseover), a redraw is requested, or the maximum wait time has elapsed. - /// - /// ## Note - /// - /// Once the app has executed all bevy systems and reaches the end of the event loop, there is - /// no way to force the app to wake and update again, unless a `winit` event (such as user - /// input, or the window being resized) is received or the time limit is reached. - /// - /// ## Differences from [`UpdateMode::Reactive`] - /// - /// Unlike [`UpdateMode::Reactive`], this mode will ignore winit events that aren't directly - /// caused by interaction with the window. For example, you might want to use this mode when the - /// window is not focused, to only re-draw your bevy app when the cursor is over the window, but - /// not when the mouse moves somewhere else on the screen. This helps to significantly reduce - /// power consumption by only updated the app when absolutely necessary. - ReactiveLowPower { - /// The maximum time to wait before the event loop runs again. - /// - /// Note that Bevy will wait indefinitely if the duration is too high (such as [`Duration::MAX`]). - max_wait: Duration, - }, +fn run(event_loop: EventLoop, event_handler: F) -> ! +where + F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget, &mut ControlFlow), +{ + event_loop.run(event_handler) +} + +#[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +fn run_return(event_loop: &mut EventLoop, event_handler: F) +where + F: FnMut(Event<'_, T>, &EventLoopWindowTarget, &mut ControlFlow), +{ + use winit::platform::run_return::EventLoopExtRunReturn; + event_loop.run_return(event_handler); +} + +#[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +)))] +fn run_return(_event_loop: &mut EventLoop, _event_handler: F) +where + F: FnMut(Event<'_, T>, &EventLoopWindowTarget, &mut ControlFlow), +{ + panic!("Run return is not supported on this platform!") } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 00c58706680a35..7cf5731f46baa0 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -1,4 +1,5 @@ #![warn(missing_docs)] +use std::marker::PhantomData; use std::sync::atomic::Ordering; use accesskit_winit::Adapter; @@ -7,12 +8,12 @@ use bevy_a11y::{ AccessKitEntityExt, AccessibilityRequested, }; use bevy_ecs::entity::Entity; - use bevy_utils::{tracing::warn, HashMap}; use bevy_window::{CursorGrabMode, Window, WindowMode, WindowPosition, WindowResolution}; use winit::{ dpi::{LogicalSize, PhysicalPosition}, + event_loop::EventLoopWindowTarget, monitor::MonitorHandle, }; @@ -21,7 +22,7 @@ use crate::{ converters::{convert_window_level, convert_window_theme}, }; -/// A resource which maps window entities to [`winit`] library windows. +/// A resource mapping [`Window`] entities to [`winit`] library [`Window`](winit::window::Window) instances. #[derive(Debug, Default)] pub struct WinitWindows { /// Stores [`winit`] windows by window identifier. @@ -30,18 +31,16 @@ pub struct WinitWindows { pub entity_to_winit: HashMap, /// Maps `winit` window identifiers to entities. pub winit_to_entity: HashMap, - - // Some winit functions, such as `set_window_icon` can only be used from the main thread. If - // they are used in another thread, the app will hang. This marker ensures `WinitWindows` is - // only ever accessed with bevy's non-send functions and in NonSend systems. - _not_send_sync: core::marker::PhantomData<*const ()>, + // Many `winit` window functions (e.g. `set_window_icon`) can only be called on the main thread. + // If they're called on other threads, the program might hang. This marker indicates that + // this type is not thread-safe and will be `!Send` and `!Sync`. + _not_send_sync: PhantomData<*const ()>, } impl WinitWindows { - /// Creates a `winit` window and associates it with our entity. - pub fn create_window( + pub fn create_window( &mut self, - event_loop: &winit::event_loop::EventLoopWindowTarget<()>, + event_loop: &EventLoopWindowTarget, entity: Entity, window: &Window, adapters: &mut AccessKitAdapters, @@ -315,8 +314,7 @@ pub(crate) fn attempt_grab(winit_window: &winit::window::Window, grab_mode: Curs } /// Compute the physical window position for a given [`WindowPosition`]. -// Ideally we could generify this across window backends, but we only really have winit atm -// so whatever. +// TODO: Ideally, this function is backend-generic, but right now we only internally support winit. pub fn winit_window_position( position: &WindowPosition, resolution: &WindowResolution, @@ -374,7 +372,7 @@ pub fn winit_window_position( } } -// WARNING: this only works under the assumption that wasm runtime is single threaded +// WARNING: This only works assuming the wasm runtime is single-threaded. #[cfg(target_arch = "wasm32")] unsafe impl Send for WinitWindows {} #[cfg(target_arch = "wasm32")] diff --git a/errors/B0002.md b/errors/B0002.md index cfba45ba32578f..29160aa75fd778 100644 --- a/errors/B0002.md +++ b/errors/B0002.md @@ -1,6 +1,6 @@ # B0002 -To keep [Rust rules on references](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#the-rules-of-references) (either one mutable reference or any number of immutable references) on a resource, it is not possible to have more than one resource of a kind if one is mutable in the same system. This can happen between [`Res`](https://docs.rs/bevy/*/bevy/ecs/system/struct.Res.html) and [`ResMut`](https://docs.rs/bevy/*/bevy/ecs/system/struct.ResMut.html) for the same `T`, or between [`NonSend`](https://docs.rs/bevy/*/bevy/ecs/system/struct.NonSend.html) and [`NonSendMut`](https://docs.rs/bevy/*/bevy/ecs/system/struct.NonSendMut.html) for the same `T`. +To keep [Rust rules on references](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#the-rules-of-references) (either one mutable reference or any number of immutable references) on a resource, it is not possible to have more than one resource of a kind if one is mutable in the same system. This can happen between [`Res`](https://docs.rs/bevy/*/bevy/ecs/system/struct.Res.html) and [`ResMut`](https://docs.rs/bevy/*/bevy/ecs/system/struct.ResMut.html) for the same `T`. Erroneous code example: