bevy/crates/bevy_window/src/lib.rs
Carter Anderson dcc03724a5 Base Sets (#7466)
# Objective

NOTE: This depends on #7267 and should not be merged until #7267 is merged. If you are reviewing this before that is merged, I highly recommend viewing the Base Sets commit instead of trying to find my changes amongst those from #7267.

"Default sets" as described by the [Stageless RFC](https://github.com/bevyengine/rfcs/pull/45) have some [unfortunate consequences](https://github.com/bevyengine/bevy/discussions/7365).

## Solution

This adds "base sets" as a variant of `SystemSet`:

A set is a "base set" if `SystemSet::is_base` returns `true`. Typically this will be opted-in to using the `SystemSet` derive:

```rust
#[derive(SystemSet, Clone, Hash, Debug, PartialEq, Eq)]
#[system_set(base)]
enum MyBaseSet {
  A,
  B,
}
``` 

**Base sets are exclusive**: a system can belong to at most one "base set". Adding a system to more than one will result in an error. When possible we fail immediately during system-config-time with a nice file + line number. For the more nested graph-ey cases, this will fail at the final schedule build. 

**Base sets cannot belong to other sets**: this is where the word "base" comes from

Systems and Sets can only be added to base sets using `in_base_set`. Calling `in_set` with a base set will fail. As will calling `in_base_set` with a normal set.

```rust
app.add_system(foo.in_base_set(MyBaseSet::A))
       // X must be a normal set ... base sets cannot be added to base sets
       .configure_set(X.in_base_set(MyBaseSet::A))
```

Base sets can still be configured like normal sets:

```rust
app.add_system(MyBaseSet::B.after(MyBaseSet::Ap))
``` 

The primary use case for base sets is enabling a "default base set":

```rust
schedule.set_default_base_set(CoreSet::Update)
  // this will belong to CoreSet::Update by default
  .add_system(foo)
  // this will override the default base set with PostUpdate
  .add_system(bar.in_base_set(CoreSet::PostUpdate))
```

This allows us to build apis that work by default in the standard Bevy style. This is a rough analog to the "default stage" model, but it use the new "stageless sets" model instead, with all of the ordering flexibility (including exclusive systems) that it provides.

---

## Changelog

- Added "base sets" and ported CoreSet to use them.

## Migration Guide

TODO
2023-02-06 03:10:08 +00:00

161 lines
5.8 KiB
Rust

#[warn(missing_docs)]
mod cursor;
mod event;
mod raw_handle;
mod system;
mod window;
pub use crate::raw_handle::*;
pub use cursor::*;
pub use event::*;
pub use system::*;
pub use window::*;
pub mod prelude {
#[doc(hidden)]
pub use crate::{
CursorEntered, CursorIcon, CursorLeft, CursorMoved, FileDragAndDrop, Ime, MonitorSelection,
ReceivedCharacter, Window, WindowMoved, WindowPlugin, WindowPosition,
WindowResizeConstraints,
};
}
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use std::path::PathBuf;
impl Default for WindowPlugin {
fn default() -> Self {
WindowPlugin {
primary_window: Some(Window::default()),
exit_condition: ExitCondition::OnAllClosed,
close_when_requested: true,
}
}
}
/// A [`Plugin`] that defines an interface for windowing support in Bevy.
pub struct WindowPlugin {
/// Settings for the primary window. This will be spawned by
/// default, if you want to run without a primary window you should
/// set this to `None`.
///
/// Note that if there are no windows, by default the App will exit,
/// due to [`exit_on_all_closed`].
pub primary_window: Option<Window>,
/// Whether to exit the app when there are no open windows.
///
/// If disabling this, ensure that you send the [`bevy_app::AppExit`]
/// event when the app should exit. If this does not occur, you will
/// create 'headless' processes (processes without windows), which may
/// surprise your users. It is recommended to leave this setting to
/// either [`ExitCondition::OnAllClosed`] or [`ExitCondition::OnPrimaryClosed`].
///
/// [`ExitCondition::OnAllClosed`] will add [`exit_on_all_closed`] to [`CoreSet::Update`].
/// [`ExitCondition::OnPrimaryClosed`] will add [`exit_on_primary_closed`] to [`CoreSet::Update`].
pub exit_condition: ExitCondition,
/// Whether to close windows when they are requested to be closed (i.e.
/// when the close button is pressed).
///
/// If true, this plugin will add [`close_when_requested`] to [`CoreSet::Update`].
/// If this system (or a replacement) is not running, the close button will have no effect.
/// This may surprise your users. It is recommended to leave this setting as `true`.
pub close_when_requested: bool,
}
impl Plugin for WindowPlugin {
fn build(&self, app: &mut App) {
// User convenience events
app.add_event::<WindowResized>()
.add_event::<WindowCreated>()
.add_event::<WindowClosed>()
.add_event::<WindowCloseRequested>()
.add_event::<RequestRedraw>()
.add_event::<CursorMoved>()
.add_event::<CursorEntered>()
.add_event::<CursorLeft>()
.add_event::<ReceivedCharacter>()
.add_event::<Ime>()
.add_event::<WindowFocused>()
.add_event::<WindowScaleFactorChanged>()
.add_event::<WindowBackendScaleFactorChanged>()
.add_event::<FileDragAndDrop>()
.add_event::<WindowMoved>();
if let Some(primary_window) = &self.primary_window {
app.world
.spawn(primary_window.clone())
.insert(PrimaryWindow);
}
match self.exit_condition {
ExitCondition::OnPrimaryClosed => {
app.add_system(exit_on_primary_closed.in_base_set(CoreSet::PostUpdate));
}
ExitCondition::OnAllClosed => {
app.add_system(exit_on_all_closed.in_base_set(CoreSet::PostUpdate));
}
ExitCondition::DontExit => {}
}
if self.close_when_requested {
app.add_system(close_when_requested.in_base_set(CoreSet::PostUpdate));
}
// Register event types
app.register_type::<WindowResized>()
.register_type::<RequestRedraw>()
.register_type::<WindowCreated>()
.register_type::<WindowCloseRequested>()
.register_type::<WindowClosed>()
.register_type::<CursorMoved>()
.register_type::<CursorEntered>()
.register_type::<CursorLeft>()
.register_type::<ReceivedCharacter>()
.register_type::<WindowFocused>()
.register_type::<WindowScaleFactorChanged>()
.register_type::<WindowBackendScaleFactorChanged>()
.register_type::<FileDragAndDrop>()
.register_type::<WindowMoved>();
// Register window descriptor and related types
app.register_type::<Window>()
.register_type::<Cursor>()
.register_type::<WindowResolution>()
.register_type::<WindowPosition>()
.register_type::<WindowMode>()
.register_type::<PresentMode>()
.register_type::<InternalWindowState>()
.register_type::<MonitorSelection>()
.register_type::<WindowResizeConstraints>();
// Register `PathBuf` as it's used by `FileDragAndDrop`
app.register_type::<PathBuf>();
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub struct ModifiesWindows;
/// Defines the specific conditions the application should exit on
#[derive(Clone)]
pub enum ExitCondition {
/// Close application when the primary window is closed
///
/// The plugin will add [`exit_on_primary_closed`] to [`CoreSet::Update`].
OnPrimaryClosed,
/// Close application when all windows are closed
///
/// The plugin will add [`exit_on_all_closed`] to [`CoreSet::Update`].
OnAllClosed,
/// Keep application running headless even after closing all windows
///
/// If selecting this, ensure that you send the [`bevy_app::AppExit`]
/// event when the app should exit. If this does not occur, you will
/// create 'headless' processes (processes without windows), which may
/// surprise your users.
DontExit,
}