Skip to content
Open
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
17 changes: 17 additions & 0 deletions guide/src/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ back to the origins.

# Unreleased

- Fixed three issues with primary-window initialization on wasm:

- The 1024×768 bootstrap `WindowResolution` now uses integer dimensions,
restoring `wasm32-unknown-unknown` compilation with Bevy 0.19, which does not
implement `From<(f64, f64)>` for `WindowResolution`.
- Window reads immediately following
`app.new_window().primary().size(...).build()` now return the pending
configuration instead of the stale 1024×768 bootstrap state. Pending state is
preferred only during the frame in which it was recorded, so subsequent
reads continue to reflect live resize, focus, and cursor state. This prevents
application coordinates initialized in `model` from disagreeing with later
mouse and egui input.
- New window builders now inherit the bootstrap window's scale factor before
applying logical dimensions. On high-DPI wasm displays, `.size(...)` therefore
produces the requested logical canvas size while keeping nannou, mouse, and
egui coordinates aligned.

- Added `RunMode::loop_once()` and `RunMode::loop_ntimes(n)`, with matching
`.loop_once()` / `.loop_ntimes(n)` shortcuts on both the app `Builder` and the
`SketchBuilder`. These run `update` and `view` a fixed number of times and then
Expand Down
2 changes: 1 addition & 1 deletion nannou/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ where
// initialization has a canvas to attach to when configuring the surface.
primary_window: Some(Window {
title: "Nannou".to_string(),
resolution: (1024.0, 768.0).into(),
resolution: (1024_u32, 768_u32).into(),
present_mode: crate::window::DEFAULT_PRESENT_MODE,
..default()
}),
Expand Down
76 changes: 50 additions & 26 deletions nannou/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,19 @@ pub struct App<'w, 's> {
// The window whose `view` is currently being run, set by the classic driver systems so that
// `draw()` targets the right window. `None` falls back to the focused window.
current_view: Local<'s, Cell<Option<Entity>>>,
// Windows created this run via `new_window` but not yet spawned (spawns are deferred through the
// command queue). Lets the classic `model` read back a window it just created in the same call,
// e.g. `app.new_window().build(); let r = app.window_rect();`. The `bool` records whether the
// window was requested as primary, so `main_window` can resolve it before it spawns.
// Windows created this frame via `new_window` whose deferred commands have not yet been
// applied. Lets the classic `model` read back a window it just created in the same call, e.g.
// `app.new_window().build(); let r = app.window_rect();`.
pending_windows: Local<'s, RefCell<Vec<PendingWindow>>>,
}

/// A window created this call but not yet spawned: `(entity, primary, component)`.
type PendingWindow = (Entity, bool, bevy::window::Window);
/// A window created or reconfigured this frame whose deferred command has not yet been applied.
struct PendingWindow {
entity: Entity,
primary: bool,
frame_count: u32,
window: bevy::window::Window,
}

impl<'w, 's> App<'w, 's> {
/// The elapsed seconds since startup.
Expand Down Expand Up @@ -150,34 +154,45 @@ impl<'w, 's> App<'w, 's> {
self.mouse_buttons.clone()
}

/// Run `f` with the [`Window`](bevy::window::Window) component for `entity`, from the world or
/// (for a window created this call but not yet spawned) the pending-window cache.
/// Run `f` with the [`Window`](bevy::window::Window) component for `entity`.
///
/// A window created or reconfigured during the current frame takes precedence over the world
/// because its deferred command may not have been applied yet.
pub(crate) fn with_window<R>(
&self,
entity: Entity,
f: impl FnOnce(&bevy::window::Window) -> R,
) -> Option<R> {
if let Ok((_, window)) = self.windows.get(entity) {
return Some(f(window));
}
let pending = self.pending_windows.borrow();
pending
if let Some(window) = pending
.iter()
.rev()
.find(|(e, _, _)| *e == entity)
.map(|(_, _, w)| f(w))
.find(|pending| pending.entity == entity && pending.frame_count == self.frame_count.0)
.map(|pending| &pending.window)
{
return Some(f(window));
}
drop(pending);

if let Ok((_, window)) = self.windows.get(entity) {
return Some(f(window));
}
None
}

/// Record a window created this call but not yet spawned, so it can be read back immediately.
/// Record a window created or reconfigured this frame so it can be read back immediately.
pub(crate) fn record_pending_window(
&self,
entity: Entity,
primary: bool,
window: bevy::window::Window,
) {
self.pending_windows
.borrow_mut()
.push((entity, primary, window));
self.pending_windows.borrow_mut().push(PendingWindow {
entity,
primary,
frame_count: self.frame_count.0,
window,
});
}

/// The current mouse position in points, relative to the centre of the focused window.
Expand Down Expand Up @@ -216,8 +231,14 @@ impl<'w, 's> App<'w, 's> {
return entity;
}
// Then a window created this call but not yet spawned (e.g. just built in `model`).
if let Some((entity, _, _)) = self.pending_windows.borrow().last() {
return *entity;
if let Some(pending) = self
.pending_windows
.borrow()
.iter()
.rev()
.find(|pending| pending.frame_count == self.frame_count.0)
{
return pending.entity;
}
// Finally, any open window (e.g. a freshly-spawned window not yet focused).
self.windows
Expand All @@ -238,7 +259,10 @@ impl<'w, 's> App<'w, 's> {
let pending = self.pending_windows.borrow();
let pending_count = pending
.iter()
.filter(|(e, _, _)| self.windows.get(*e).is_err())
.filter(|pending| {
pending.frame_count == self.frame_count.0
&& self.windows.get(pending.entity).is_err()
})
.count();
query_count + pending_count
}
Expand Down Expand Up @@ -478,9 +502,9 @@ impl<'w, 's> App<'w, 's> {
/// [`Entity`] from [`build`](crate::window::Builder::build).
pub fn new_window<M: 'static>(&self) -> crate::window::Builder<'_, 'w, 's, M> {
// Drop any pending windows that have since been spawned, so the cache stays bounded.
self.pending_windows
.borrow_mut()
.retain(|(e, _, _)| self.windows.get(*e).is_err());
self.pending_windows.borrow_mut().retain(|pending| {
pending.frame_count == self.frame_count.0 && self.windows.get(pending.entity).is_err()
});
crate::window::Builder::new(self)
}

Expand Down Expand Up @@ -538,8 +562,8 @@ impl<'w, 's> App<'w, 's> {
.borrow()
.iter()
.rev()
.find(|(_, primary, _)| *primary)
.map(|(e, _, _)| *e)
.find(|pending| pending.primary && pending.frame_count == self.frame_count.0)
.map(|pending| pending.entity)
})
// No window is explicitly primary: fall back to the current window.
.unwrap_or_else(|| self.window_id());
Expand Down
22 changes: 18 additions & 4 deletions nannou/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,26 @@ where
{
/// Begin building a new window.
pub fn new(app: &'a App<'w, 's>) -> Self {
Builder {
app,
window: bevy::window::Window {
#[cfg(not(target_arch = "wasm32"))]
let window = bevy::window::Window {
present_mode: DEFAULT_PRESENT_MODE,
..bevy::window::Window::default()
};
#[cfg(target_arch = "wasm32")]
let window = {
let mut resolution = bevy::window::WindowResolution::default();
resolution
.set_scale_factor_and_apply_to_physical_size(app.main_window().scale_factor());
bevy::window::Window {
present_mode: DEFAULT_PRESENT_MODE,
resolution,
..bevy::window::Window::default()
},
}
};

Builder {
app,
window,
camera: None,
light: None,
primary: false,
Expand Down