Skip to content

Commit

Permalink
that's a lot of files
Browse files Browse the repository at this point in the history
  • Loading branch information
maniwani committed Jul 12, 2023
1 parent 7c70fc1 commit ed449db
Show file tree
Hide file tree
Showing 34 changed files with 2,528 additions and 2,609 deletions.
944 changes: 342 additions & 602 deletions crates/bevy_app/src/app.rs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions crates/bevy_app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand All @@ -28,6 +30,7 @@ pub mod prelude {
First, FixedUpdate, Last, Main, PostStartup, PostUpdate, PreStartup, PreUpdate,
Startup, StateTransition, Update,
},
sub_app::SubApp,
DynamicPlugin, Plugin, PluginGroup,
};
}
9 changes: 8 additions & 1 deletion crates/bevy_app/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
///
Expand Down
156 changes: 88 additions & 68 deletions crates/bevy_app/src/schedule_runner.rs
Original file line number Diff line number Diff line change
@@ -1,68 +1,65 @@
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")]
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<Duration>,
/// 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 },
}
}
}
Expand All @@ -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::<AppExit>::default();
let mut exit_event_reader = ManualEventReader::<AppExit>::default();
match run_mode {
RunMode::Once => {
app.update();
}
RunMode::Loop { wait } => {
let mut tick = move |app: &mut App,
wait: Option<Duration>|
-> Result<Option<Duration>, AppExit> {
let mut update = move |sub_apps: &mut SubApps| -> Result<Duration, AppExit> {
let start_time = Instant::now();
sub_apps.update();
let end_time = Instant::now();

if let Some(app_exit_events) =
app.world.get_resource_mut::<Events<AppExit>>()
if let Some(exit_events) =
sub_apps.main.world.get_resource_mut::<Events<AppExit>>()
{
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::<Events<AppExit>>()
{
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;
}
}
}
}
Expand All @@ -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<dyn FnMut()>));
set_timeout(g.borrow().as_ref().unwrap(), asap);

*g.borrow_mut() =
Some(Closure::wrap(Box::new(closure) as Box<dyn FnMut()>));

set_timeout(g.borrow().as_ref().unwrap(), min_sleep);
};

// remove TLS accessor
sub_apps
.for_each(|sub_app| sub_app.world.remove_resource::<ThreadLocalAccessor>());
}
}
});
Expand Down
Loading

0 comments on commit ed449db

Please sign in to comment.