Skip to content

(WinitPlugin) Allow control of game tick #2872

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions crates/bevy_asset/src/asset_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ pub enum AssetServerError {
AssetIoError(#[from] AssetIoError),
}

pub trait AssetNotify: Send + Sync + 'static {
fn notify(&self);
}

fn format_missing_asset_ext(exts: &[String]) -> String {
if !exts.is_empty() {
format!(
Expand All @@ -53,6 +57,7 @@ pub struct AssetServerInternal {
pub(crate) asset_ref_counter: AssetRefCounter,
pub(crate) asset_sources: Arc<RwLock<HashMap<SourcePathId, SourceInfo>>>,
pub(crate) asset_lifecycles: Arc<RwLock<HashMap<Uuid, Box<dyn AssetLifecycle>>>>,
asset_notify: RwLock<Option<Box<dyn AssetNotify>>>,
loaders: RwLock<Vec<Arc<dyn AssetLoader>>>,
extension_to_loader_index: RwLock<HashMap<String, usize>>,
handle_to_path: Arc<RwLock<HashMap<HandleId, AssetPath<'static>>>>,
Expand Down Expand Up @@ -86,6 +91,7 @@ impl AssetServer {
asset_ref_counter: Default::default(),
handle_to_path: Default::default(),
asset_lifecycles: Default::default(),
asset_notify: Default::default(),
task_pool,
asset_io,
}),
Expand Down Expand Up @@ -115,6 +121,10 @@ impl AssetServer {
loaders.push(Arc::new(loader));
}

pub fn replace_notify(&self, asset_notify: Box<dyn AssetNotify>) {
*self.server.asset_notify.write() = Some(asset_notify);
}

pub fn watch_for_changes(&self) -> Result<(), AssetServerError> {
self.server.asset_io.watch_for_changes()?;
Ok(())
Expand Down Expand Up @@ -371,6 +381,8 @@ impl AssetServer {
.spawn(async move {
if let Err(err) = server.load_async(owned_path, force).await {
warn!("{}", err);
} else if let Some(asset_notify) = &*server.server.asset_notify.read() {
asset_notify.notify();
}
})
.detach();
Expand Down Expand Up @@ -617,6 +629,7 @@ mod test {
asset_ref_counter: Default::default(),
handle_to_path: Default::default(),
asset_lifecycles: Default::default(),
asset_notify: Default::default(),
task_pool: Default::default(),
asset_io: Box::new(FileAssetIo::new(asset_path)),
}),
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_winit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ x11 = ["winit/x11"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.5.0" }
bevy_asset = { path = "../bevy_asset", version = "0.5.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" }
bevy_input = { path = "../bevy_input", version = "0.5.0" }
bevy_math = { path = "../bevy_math", version = "0.5.0" }
Expand All @@ -24,6 +25,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
# other
winit = { version = "0.25.0", default-features = false }
approx = { version = "0.5.0", default-features = false }
parking_lot = "0.11.0"

[target.'cfg(target_arch = "wasm32")'.dependencies]
winit = { version = "0.25.0", features = ["web-sys"], default-features = false }
Expand Down
50 changes: 47 additions & 3 deletions crates/bevy_winit/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod converters;
mod winit_config;
mod winit_tick;
mod winit_windows;

use bevy_input::{
Expand All @@ -8,17 +9,25 @@ use bevy_input::{
touch::TouchInput,
};
pub use winit_config::*;
pub use winit_tick::*;
pub use winit_windows::*;

use bevy_app::{App, AppExit, CoreStage, Events, ManualEventReader, Plugin};
use bevy_ecs::{system::IntoExclusiveSystem, world::World};
use bevy_asset::{AssetNotify, AssetServer};
use bevy_ecs::{
prelude::NonSend,
system::{IntoExclusiveSystem, Res},
world::World,
};
use bevy_math::{ivec2, Vec2};
use bevy_utils::tracing::{error, trace, warn};
use bevy_window::{
CreateWindow, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, ReceivedCharacter,
WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, WindowFocused,
WindowMoved, WindowResized, WindowScaleFactorChanged, Windows,
};
use parking_lot::Mutex;
use winit::event_loop::EventLoopProxy;
use winit::{
dpi::PhysicalPosition,
event::{self, DeviceEvent, Event, WindowEvent},
Expand All @@ -35,14 +44,49 @@ use winit::dpi::LogicalSize;
))]
use winit::platform::unix::EventLoopExtUnix;

struct WinitNotify {
event_loop_proxy: Mutex<EventLoopProxy<()>>,
}

impl AssetNotify for WinitNotify {
fn notify(&self) {
let _ = self.event_loop_proxy.lock().send_event(());
}
}

// SAFETY: We clone the EventLoopProxy in exclusive system, and never destroy it.
#[cfg(target_arch = "wasm32")]
unsafe impl Send for WinitNotify {}

#[cfg(target_arch = "wasm32")]
unsafe impl Sync for WinitNotify {}

#[derive(Default)]
pub struct WinitPlugin;

impl Plugin for WinitPlugin {
fn build(&self, app: &mut App) {
if app.world.get_resource::<WinitTick>().is_none() {
app.init_resource::<WinitTick>();
}

app.init_resource::<WinitWindows>()
.set_runner(winit_runner)
.add_system_to_stage(CoreStage::PostUpdate, change_window.exclusive_system());
.add_startup_system(setup.exclusive_system())
.add_system_to_stage(CoreStage::PostUpdate, change_window.exclusive_system())
.add_system_to_stage(CoreStage::Last, tick.exclusive_system());
}
}

fn setup(asset_server: Res<AssetServer>, event_loop_proxy: NonSend<EventLoopProxy<()>>) {
asset_server.replace_notify(Box::new(WinitNotify {
event_loop_proxy: Mutex::new(event_loop_proxy.clone()),
}));
}

fn tick(winit_tick: Res<WinitTick>, event_loop_proxy: NonSend<EventLoopProxy<()>>) {
if winit_tick.finish() {
let _ = event_loop_proxy.send_event(());
}
}

Expand Down Expand Up @@ -238,7 +282,7 @@ pub fn winit_runner_with(mut app: App, mut event_loop: EventLoop<()>) {
let event_handler = move |event: Event<()>,
event_loop: &EventLoopWindowTarget<()>,
control_flow: &mut ControlFlow| {
*control_flow = ControlFlow::Poll;
*control_flow = ControlFlow::Wait;

if let Some(app_exit_events) = app.world.get_resource_mut::<Events<AppExit>>() {
if app_exit_event_reader
Expand Down
36 changes: 36 additions & 0 deletions crates/bevy_winit/src/winit_tick.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use std::sync::atomic::{AtomicBool, Ordering};

/// A resource for dynamically control of game tick.
pub struct WinitTick {
default_poll_tick: bool,
should_poll_tick: AtomicBool,
}

impl Default for WinitTick {
fn default() -> Self {
Self::new(true)
}
}

impl WinitTick {
/// Create a `WinitTick` resource with the specified `default_poll_tick`.
/// `false` is recommended for GUI applications, animation systems can call
/// `poll_tick` with `WinitTick` resource to poll game tick for the next frame.
///
/// Default value of `default_poll_tick` parameter is `true`.
pub fn new(default_poll_tick: bool) -> Self {
Self {
default_poll_tick,
should_poll_tick: AtomicBool::new(default_poll_tick),
}
}

pub fn poll_tick(&self) {
self.should_poll_tick.store(true, Ordering::Relaxed);
}

pub fn finish(&self) -> bool {
self.should_poll_tick
.swap(self.default_poll_tick, Ordering::Relaxed)
}
}
2 changes: 2 additions & 0 deletions examples/ui/ui.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use bevy::prelude::*;
use bevy::winit::WinitTick;

/// This example illustrates the various features of Bevy UI.
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(WinitTick::new(false))
.add_startup_system(setup)
.run();
}
Expand Down