bevy/crates/bevy_core/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

204 lines
6.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#![warn(missing_docs)]
//! This crate provides core functionality for Bevy Engine.
mod name;
#[cfg(feature = "serialize")]
mod serde;
mod task_pool_options;
use bevy_ecs::system::{ResMut, Resource};
pub use bytemuck::{bytes_of, cast_slice, Pod, Zeroable};
pub use name::*;
pub use task_pool_options::*;
pub mod prelude {
//! The Bevy Core Prelude.
#[doc(hidden)]
pub use crate::{
DebugName, FrameCountPlugin, Name, TaskPoolOptions, TaskPoolPlugin, TypeRegistrationPlugin,
};
}
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use bevy_utils::{Duration, HashSet, Instant};
use std::borrow::Cow;
use std::ffi::OsString;
use std::marker::PhantomData;
use std::ops::Range;
use std::path::PathBuf;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm32"))]
use bevy_tasks::tick_global_task_pools_on_main_thread;
/// Registration of default types to the `TypeRegistry` reesource.
#[derive(Default)]
pub struct TypeRegistrationPlugin;
impl Plugin for TypeRegistrationPlugin {
fn build(&self, app: &mut App) {
app.register_type::<Entity>().register_type::<Name>();
register_rust_types(app);
register_math_types(app);
}
}
fn register_rust_types(app: &mut App) {
app.register_type::<Range<f32>>()
.register_type_data::<Range<f32>, ReflectSerialize>()
.register_type_data::<Range<f32>, ReflectDeserialize>()
.register_type::<String>()
.register_type::<PathBuf>()
.register_type::<OsString>()
.register_type::<HashSet<String>>()
.register_type::<Option<String>>()
.register_type::<Cow<'static, str>>()
.register_type::<Duration>()
.register_type::<Instant>();
}
fn register_math_types(app: &mut App) {
app.register_type::<bevy_math::IVec2>()
.register_type::<bevy_math::IVec3>()
.register_type::<bevy_math::IVec4>()
.register_type::<bevy_math::UVec2>()
.register_type::<bevy_math::UVec3>()
.register_type::<bevy_math::UVec4>()
.register_type::<bevy_math::DVec2>()
.register_type::<bevy_math::DVec3>()
.register_type::<bevy_math::DVec4>()
.register_type::<bevy_math::BVec2>()
.register_type::<bevy_math::BVec3>()
.register_type::<bevy_math::BVec3A>()
.register_type::<bevy_math::BVec4>()
.register_type::<bevy_math::BVec4A>()
.register_type::<bevy_math::Vec2>()
.register_type::<bevy_math::Vec3>()
.register_type::<bevy_math::Vec3A>()
.register_type::<bevy_math::Vec4>()
.register_type::<bevy_math::DAffine2>()
.register_type::<bevy_math::DAffine3>()
.register_type::<bevy_math::Affine2>()
.register_type::<bevy_math::Affine3A>()
.register_type::<bevy_math::DMat2>()
.register_type::<bevy_math::DMat3>()
.register_type::<bevy_math::DMat4>()
.register_type::<bevy_math::Mat2>()
.register_type::<bevy_math::Mat3>()
.register_type::<bevy_math::Mat3A>()
.register_type::<bevy_math::Mat4>()
.register_type::<bevy_math::DQuat>()
.register_type::<bevy_math::Quat>();
}
/// Setup of default task pools: `AsyncComputeTaskPool`, `ComputeTaskPool`, `IoTaskPool`.
#[derive(Default)]
pub struct TaskPoolPlugin {
/// Options for the [`TaskPool`](bevy_tasks::TaskPool) created at application start.
pub task_pool_options: TaskPoolOptions,
}
impl Plugin for TaskPoolPlugin {
fn build(&self, app: &mut App) {
// Setup the default bevy task pools
self.task_pool_options.create_default_pools();
#[cfg(not(target_arch = "wasm32"))]
app.add_system(tick_global_task_pools.in_base_set(bevy_app::CoreSet::Last));
}
}
/// A dummy type that is [`!Send`](Send), to force systems to run on the main thread.
pub struct NonSendMarker(PhantomData<*mut ()>);
/// A system used to check and advanced our task pools.
///
/// Calls [`tick_global_task_pools_on_main_thread`],
/// and uses [`NonSendMarker`] to ensure that this system runs on the main thread
#[cfg(not(target_arch = "wasm32"))]
fn tick_global_task_pools(_main_thread_marker: Option<NonSend<NonSendMarker>>) {
tick_global_task_pools_on_main_thread();
}
/// Maintains a count of frames rendered since the start of the application.
///
/// [`FrameCount`] is incremented during [`CoreSet::Last`], providing predictable
/// behaviour: it will be 0 during the first update, 1 during the next, and so forth.
///
/// # Overflows
///
/// [`FrameCount`] will wrap to 0 after exceeding [`u32::MAX`]. Within reasonable
/// assumptions, one may exploit wrapping arithmetic to determine the number of frames
/// that have elapsed between two observations see [`u32::wrapping_sub()`].
#[derive(Default, Resource, Clone, Copy)]
pub struct FrameCount(pub u32);
/// Adds frame counting functionality to Apps.
#[derive(Default)]
pub struct FrameCountPlugin;
impl Plugin for FrameCountPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<FrameCount>();
app.add_system(update_frame_count.in_base_set(CoreSet::Last));
}
}
fn update_frame_count(mut frame_count: ResMut<FrameCount>) {
frame_count.0 = frame_count.0.wrapping_add(1);
}
#[cfg(test)]
mod tests {
use super::*;
use bevy_tasks::prelude::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool};
#[test]
fn runs_spawn_local_tasks() {
let mut app = App::new();
app.add_plugin(TaskPoolPlugin::default());
app.add_plugin(TypeRegistrationPlugin::default());
let (async_tx, async_rx) = crossbeam_channel::unbounded();
AsyncComputeTaskPool::get()
.spawn_local(async move {
async_tx.send(()).unwrap();
})
.detach();
let (compute_tx, compute_rx) = crossbeam_channel::unbounded();
ComputeTaskPool::get()
.spawn_local(async move {
compute_tx.send(()).unwrap();
})
.detach();
let (io_tx, io_rx) = crossbeam_channel::unbounded();
IoTaskPool::get()
.spawn_local(async move {
io_tx.send(()).unwrap();
})
.detach();
app.run();
async_rx.try_recv().unwrap();
compute_rx.try_recv().unwrap();
io_rx.try_recv().unwrap();
}
#[test]
fn frame_counter_update() {
let mut app = App::new();
app.add_plugin(TaskPoolPlugin::default());
app.add_plugin(TypeRegistrationPlugin::default());
app.add_plugin(FrameCountPlugin::default());
app.update();
let frame_count = app.world.resource::<FrameCount>();
assert_eq!(1, frame_count.0);
}
}