
# Objective - Contributes to #15460 ## Solution - Added the following features: - `std` (default) - `async_executor` (default) - `edge_executor` - `critical-section` - `portable-atomic` - Gated `tracing` in `bevy_utils` to allow compilation on certain platforms - Switched from `tracing` to `log` for simple message logging within `bevy_ecs`. Note that `tracing` supports capturing from `log` so this should be an uncontroversial change. - Fixed imports and added feature gates as required - Made `bevy_tasks` optional within `bevy_ecs`. Turns out it's only needed for parallel operations which are already gated behind `multi_threaded` anyway. ## Testing - Added to `compile-check-no-std` CI command - `cargo check -p bevy_ecs --no-default-features --features edge_executor,critical-section,portable-atomic --target thumbv6m-none-eabi` - `cargo check -p bevy_ecs --no-default-features --features edge_executor,critical-section` - `cargo check -p bevy_ecs --no-default-features` ## Draft Release Notes Bevy's core ECS now supports `no_std` platforms. In prior versions of Bevy, it was not possible to work with embedded or niche platforms due to our reliance on the standard library, `std`. This has blocked a number of novel use-cases for Bevy, such as an embedded database for IoT devices, or for creating games on retro consoles. With this release, `bevy_ecs` no longer requires `std`. To use Bevy on a `no_std` platform, you must disable default features and enable the new `edge_executor` and `critical-section` features. You may also need to enable `portable-atomic` and `critical-section` if your platform does not natively support all atomic types and operations used by Bevy. ```toml [dependencies] bevy_ecs = { version = "0.16", default-features = false, features = [ # Required for platforms with incomplete atomics (e.g., Raspberry Pi Pico) "portable-atomic", "critical-section", # Optional "bevy_reflect", "serialize", "bevy_debug_stepping", "edge_executor" ] } ``` Currently, this has been tested on bare-metal x86 and the Raspberry Pi Pico. If you have trouble using `bevy_ecs` on a particular platform, please reach out either through a GitHub issue or in the `no_std` working group on the Bevy Discord server. Keep an eye out for future `no_std` updates as we continue to improve the parity between `std` and `no_std`. We look forward to seeing what kinds of applications are now possible with Bevy! ## Notes - Creating PR in draft to ensure CI is passing before requesting reviews. - This implementation has no support for multithreading in `no_std`, especially due to `NonSend` being unsound if allowed in multithreading. The reason is we cannot check the `ThreadId` in `no_std`, so we have no mechanism to at-runtime determine if access is sound. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Vic <59878206+Victoronz@users.noreply.github.com>
152 lines
5.2 KiB
Rust
152 lines
5.2 KiB
Rust
use crate::{
|
|
component::{Component, ComponentCloneHandler, ComponentHooks, Mutable, StorageType},
|
|
entity::{Entity, EntityCloneBuilder, EntityCloner},
|
|
observer::ObserverState,
|
|
world::{DeferredWorld, World},
|
|
};
|
|
use alloc::vec::Vec;
|
|
|
|
/// Tracks a list of entity observers for the [`Entity`] [`ObservedBy`] is added to.
|
|
#[derive(Default)]
|
|
pub(crate) struct ObservedBy(pub(crate) Vec<Entity>);
|
|
|
|
impl Component for ObservedBy {
|
|
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
|
|
type Mutability = Mutable;
|
|
|
|
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
|
hooks.on_remove(|mut world, entity, _| {
|
|
let observed_by = {
|
|
let mut component = world.get_mut::<ObservedBy>(entity).unwrap();
|
|
core::mem::take(&mut component.0)
|
|
};
|
|
for e in observed_by {
|
|
let (total_entities, despawned_watched_entities) = {
|
|
let Ok(mut entity_mut) = world.get_entity_mut(e) else {
|
|
continue;
|
|
};
|
|
let Some(mut state) = entity_mut.get_mut::<ObserverState>() else {
|
|
continue;
|
|
};
|
|
state.despawned_watched_entities += 1;
|
|
(
|
|
state.descriptor.entities.len(),
|
|
state.despawned_watched_entities as usize,
|
|
)
|
|
};
|
|
|
|
// Despawn Observer if it has no more active sources.
|
|
if total_entities == despawned_watched_entities {
|
|
world.commands().entity(e).despawn();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
fn get_component_clone_handler() -> ComponentCloneHandler {
|
|
ComponentCloneHandler::Ignore
|
|
}
|
|
}
|
|
|
|
/// Trait that holds functions for configuring interaction with observers during entity cloning.
|
|
pub trait CloneEntityWithObserversExt {
|
|
/// Sets the option to automatically add cloned entities to the obsevers targeting source entity.
|
|
fn add_observers(&mut self, add_observers: bool) -> &mut Self;
|
|
}
|
|
|
|
impl CloneEntityWithObserversExt for EntityCloneBuilder<'_> {
|
|
fn add_observers(&mut self, add_observers: bool) -> &mut Self {
|
|
if add_observers {
|
|
self.override_component_clone_handler::<ObservedBy>(ComponentCloneHandler::Custom(
|
|
component_clone_observed_by,
|
|
))
|
|
} else {
|
|
self.remove_component_clone_handler_override::<ObservedBy>()
|
|
}
|
|
}
|
|
}
|
|
|
|
fn component_clone_observed_by(world: &mut DeferredWorld, entity_cloner: &EntityCloner) {
|
|
let target = entity_cloner.target();
|
|
let source = entity_cloner.source();
|
|
|
|
world.commands().queue(move |world: &mut World| {
|
|
let observed_by = world
|
|
.get::<ObservedBy>(source)
|
|
.map(|observed_by| observed_by.0.clone())
|
|
.expect("Source entity must have ObservedBy");
|
|
|
|
world
|
|
.entity_mut(target)
|
|
.insert(ObservedBy(observed_by.clone()));
|
|
|
|
for observer in &observed_by {
|
|
let mut observer_state = world
|
|
.get_mut::<ObserverState>(*observer)
|
|
.expect("Source observer entity must have ObserverState");
|
|
observer_state.descriptor.entities.push(target);
|
|
let event_types = observer_state.descriptor.events.clone();
|
|
let components = observer_state.descriptor.components.clone();
|
|
for event_type in event_types {
|
|
let observers = world.observers.get_observers(event_type);
|
|
if components.is_empty() {
|
|
if let Some(map) = observers.entity_observers.get(&source).cloned() {
|
|
observers.entity_observers.insert(target, map);
|
|
}
|
|
} else {
|
|
for component in &components {
|
|
let Some(observers) = observers.component_observers.get_mut(component)
|
|
else {
|
|
continue;
|
|
};
|
|
if let Some(map) = observers.entity_map.get(&source).cloned() {
|
|
observers.entity_map.insert(target, map);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{
|
|
self as bevy_ecs,
|
|
entity::EntityCloneBuilder,
|
|
event::Event,
|
|
observer::{CloneEntityWithObserversExt, Trigger},
|
|
system::{ResMut, Resource},
|
|
world::World,
|
|
};
|
|
|
|
#[derive(Resource, Default)]
|
|
struct Num(usize);
|
|
|
|
#[derive(Event)]
|
|
struct E;
|
|
|
|
#[test]
|
|
fn clone_entity_with_observer() {
|
|
let mut world = World::default();
|
|
world.init_resource::<Num>();
|
|
|
|
let e = world
|
|
.spawn_empty()
|
|
.observe(|_: Trigger<E>, mut res: ResMut<Num>| res.0 += 1)
|
|
.id();
|
|
world.flush();
|
|
|
|
world.trigger_targets(E, e);
|
|
|
|
let e_clone = world.spawn_empty().id();
|
|
let mut builder = EntityCloneBuilder::new(&mut world);
|
|
builder.add_observers(true);
|
|
builder.clone_entity(e, e_clone);
|
|
|
|
world.trigger_targets(E, [e, e_clone]);
|
|
|
|
assert_eq!(world.resource::<Num>().0, 3);
|
|
}
|
|
}
|