use std::{marker::PhantomData, sync::Arc};
use crate::{CapSet, ImmCapAccessRequests, ImmCapAccessRequestsResource};
use bevy_ecs::{
bundle::Bundle,
change_detection::DetectChanges,
component::{Component, Mutable},
entity::Entity,
event::EntityEvent,
hierarchy::ChildOf,
query::{QueryEntityError, Without},
resource::Resource,
system::{Commands, EntityCommands, IntoObserverSystem, Query},
world::{FilteredEntityRef, Mut, error::ResourceFetchError},
};
pub struct BevyImmediatePlugin<Caps = ()>(PhantomData<Caps>);
impl<Caps> BevyImmediatePlugin<Caps> {
pub fn new() -> Self {
Self(PhantomData)
}
}
impl<Caps> Default for BevyImmediatePlugin<Caps> {
fn default() -> Self {
Self::new()
}
}
impl<Caps> bevy_app::Plugin for BevyImmediatePlugin<Caps>
where
Caps: CapSet,
{
fn build(&self, app: &mut bevy_app::App) {
if app.is_plugin_added::<Self>() {
return;
}
entity_mapping::init::<Caps>(app);
upkeep::init::<Caps>(app);
cached_hash::init::<Caps>(app);
let mut capabilities = ImmCapAccessRequests::<Caps>::default();
Caps::initialize(app, &mut capabilities);
app.insert_resource(ImmCapAccessRequestsResource::new(capabilities));
#[cfg(feature = "hotpatching")]
hotpatching::init(app);
}
fn is_unique(&self) -> bool {
false
}
}
#[cfg(feature = "hotpatching")]
pub mod hotpatching;
mod system_set;
pub use system_set::ImmediateSystemSet;
mod ctx;
pub use ctx::ImmCtx;
mod id;
use crate::utils::ImmTypeMap;
pub use id::{ImmId, ImmIdBuilder, imm_id};
mod cached_hash;
mod entity_mapping;
mod upkeep;
pub type ImmQuery<'w, 's, Caps, D, F = ()> = Query<'w, 's, D, (Without<ImmMarker<Caps>>, F)>;
pub struct Imm<'w, 's, Caps: CapSet> {
ctx: ImmCtx<'w, 's, Caps>,
current: Current,
}
#[derive(Clone, Copy)]
struct Current {
id: ImmId,
entity: Option<CurrentEntity>,
id_pref: ImmId,
auto_id_idx: usize,
}
#[derive(Clone, Copy)]
struct CurrentEntity {
entity: Entity,
will_be_spawned: bool,
}
impl<'w, 's, Caps: CapSet> Imm<'w, 's, Caps> {
pub fn ch(&mut self) -> ImmEntity<'_, 'w, 's, Caps> {
self.ch_with_manual_id(ImmIdBuilder::Auto)
}
pub fn ch_id<T: std::hash::Hash>(&mut self, id: T) -> ImmEntity<'_, 'w, 's, Caps> {
self.ch_with_manual_id(ImmIdBuilder::Hierarchy(ImmId::new(id)))
}
pub fn ch_with_manual_id(&mut self, id: ImmIdBuilder) -> ImmEntity<'_, 'w, 's, Caps> {
let id = id.resolve(self);
let mut will_be_spawned = false;
let entity = 'entity_retrieval: {
'entity_full_reuse: {
let Some(entity) = self.ctx.mapping.id_to_entity.get(&id).copied() else {
break 'entity_full_reuse;
};
let Ok(mut qentity) = self.ctx.entity_query.get_mut(entity) else {
break 'entity_full_reuse;
};
qentity.tracker.iteration = self.ctx.state.iteration;
if qentity.child_of.map(|ch| ch.parent()) != self.current.entity.map(|e| e.entity) {
let mut entity_commands = self.ctx.commands.entity(entity);
match self.current.entity {
Some(entity) => {
entity_commands.insert(ChildOf(entity.entity));
}
None => {
entity_commands.remove::<ChildOf>();
}
}
}
break 'entity_retrieval entity;
}
let mut commands = self.ctx.commands.spawn((
ImmMarker::<Caps> {
id,
iteration: self.ctx.state.iteration,
_ph: PhantomData,
},
));
if let Some(entity) = self.current.entity {
commands.insert(ChildOf(entity.entity));
}
will_be_spawned = true;
commands.id()
};
let access_requests: Arc<_> = self.ctx.access_requests.capabilities.clone();
let mut entity = ImmEntity {
imm: self,
e: EntityParams {
id,
entity,
will_be_spawned,
},
tmp_store: ImmTypeMap::new(),
};
for on_children in access_requests.on_children.iter() {
(on_children)(&mut entity);
}
entity
}
pub fn with_add_id_pref(
&mut self,
id: impl std::hash::Hash,
) -> ImmScopeGuard<'_, 'w, 's, Caps> {
ImmScopeGuard::add_id_pref(self, id)
}
#[deprecated = "Use with_add_id_pref"]
pub fn with_local_auto_id_guard(
&mut self,
id: impl std::hash::Hash,
) -> ImmScopeGuard<'_, 'w, 's, Caps> {
ImmScopeGuard::add_id_pref(self, id)
}
pub fn unrooted<T: std::hash::Hash>(&mut self, id: T, f: impl FnOnce(&mut Imm<'w, 's, Caps>)) {
let id = ImmIdBuilder::Hierarchy(ImmId::new(id)).resolve(self);
let mut imm = ImmScopeGuard::new_scope(
self,
Current {
id,
entity: None,
auto_id_idx: 0,
id_pref: ImmId::new(49382395483011234u64),
},
);
f(&mut imm);
}
#[inline]
pub fn current_entity(&self) -> Option<Entity> {
self.current.entity.map(|e| e.entity)
}
#[inline]
pub fn current_imm_id(&self) -> ImmId {
self.current.id
}
#[inline]
pub fn commands_mut(&mut self) -> &mut Commands<'w, 's> {
&mut self.ctx.commands
}
#[inline]
pub fn ctx(&self) -> &ImmCtx<'w, 's, Caps> {
&self.ctx
}
#[inline]
pub fn ctx_mut(&mut self) -> &mut ImmCtx<'w, 's, Caps> {
&mut self.ctx
}
pub fn deconstruct(self) -> ImmCtx<'w, 's, Caps> {
self.ctx
}
fn add_child_entities_scope(
&mut self,
EntityParams {
id,
entity,
will_be_spawned,
}: EntityParams,
) -> ImmScopeGuard<'_, 'w, 's, Caps> {
ImmScopeGuard::new_scope(
self,
Current {
id,
entity: Some(CurrentEntity {
entity,
will_be_spawned,
}),
auto_id_idx: 0,
id_pref: ImmId::new(49382395483011234u64),
},
)
}
fn add_child_entities<R>(
&mut self,
params: EntityParams,
f: impl FnOnce(&mut Imm<'w, 's, Caps>) -> R,
) -> R {
self.add_child_entities_dyn(params, Box::new(f))
}
#[allow(clippy::type_complexity)]
fn add_child_entities_dyn<R>(
&mut self,
params: EntityParams,
f: Box<dyn FnOnce(&mut Imm<'w, 's, Caps>) -> R + '_>,
) -> R {
let mut imm = self.add_child_entities_scope(params);
f(&mut imm)
}
pub fn reinterpret_as_entity(&mut self) -> Option<ImmEntity<'_, 'w, 's, Caps>> {
if let Some(current_entity) = self.current.entity {
let e = EntityParams {
id: self.current.id,
entity: current_entity.entity,
will_be_spawned: current_entity.will_be_spawned,
};
Some(ImmEntity {
imm: self,
e,
tmp_store: ImmTypeMap::new(),
})
} else {
None
}
}
pub fn has_changed<T>(&self, value: &Mut<'_, T>) -> bool {
self.change_detector().has_changed(value)
}
pub fn change_detector(&self) -> ChangeDetector {
ChangeDetector {
last_run: self.ctx.system_change_tick.last_run(),
}
}
}
pub struct ImmEntity<'r, 'w, 's, Caps: CapSet> {
imm: &'r mut Imm<'w, 's, Caps>,
e: EntityParams,
tmp_store: ImmTypeMap,
}
#[derive(Clone, Copy)]
struct EntityParams {
id: ImmId,
entity: Entity,
will_be_spawned: bool,
}
impl<'r, 'w, 's, Caps: CapSet> ImmEntity<'r, 'w, 's, Caps> {
#[allow(clippy::should_implement_trait)]
pub fn add(self, f: impl FnOnce(&mut Imm<'w, 's, Caps>)) -> Self {
self.imm.add_child_entities(self.e, f);
self
}
pub fn add_with_return<R>(self, f: impl FnOnce(&mut Imm<'w, 's, Caps>) -> R) -> (Self, R) {
let value = self.imm.add_child_entities(self.e, f);
(self, value)
}
pub fn add_scoped(&mut self) -> ImmScopeGuard<'_, 'w, 's, Caps> {
self.imm.add_child_entities_scope(self.e)
}
pub fn unrooted<T: std::hash::Hash>(
self,
id: T,
f: impl FnOnce(&mut Imm<'w, 's, Caps>),
) -> Self {
self.add(|ui| {
ui.unrooted(id, f);
})
}
pub fn ctx(&self) -> &ImmCtx<'w, 's, Caps> {
&self.imm.ctx
}
pub fn ctx_mut(&mut self) -> &mut ImmCtx<'w, 's, Caps> {
&mut self.imm.ctx
}
pub fn cap_get_entity(
&self,
) -> Result<FilteredEntityRef<'_, 's>, bevy_ecs::query::QueryEntityError> {
self.ctx().cap_entities.get(self.entity())
}
pub fn cap_get_entity_mut(
&mut self,
) -> Result<bevy_ecs::world::FilteredEntityMut<'_, 's>, bevy_ecs::query::QueryEntityError> {
let entity = self.entity();
self.ctx_mut().cap_entities.get_mut(entity)
}
pub fn entity(&self) -> Entity {
self.e.entity
}
#[inline]
pub fn imm_id(&self) -> ImmId {
self.e.id
}
pub fn commands(&mut self) -> &mut Commands<'w, 's> {
&mut self.imm.ctx.commands
}
pub fn entity_commands(&mut self) -> EntityCommands<'_> {
self.imm.ctx.commands.entity(self.e.entity)
}
pub fn will_be_spawned(&self) -> bool {
self.e.will_be_spawned
}
pub fn at_this_moment_apply_commands<F>(mut self, f: F) -> Self
where
F: FnOnce(&mut EntityCommands<'_>),
{
let mut entity_commands = self.entity_commands();
f(&mut entity_commands);
self
}
pub fn at_this_moment_apply_commands_if<F>(self, f: F, condition: impl FnOnce() -> bool) -> Self
where
F: FnOnce(&mut EntityCommands<'_>),
{
if condition() {
self.at_this_moment_apply_commands(f)
} else {
self
}
}
pub fn on_spawn_apply_commands<F>(self, f: F) -> Self
where
F: FnOnce(&mut EntityCommands<'_>),
{
if self.e.will_be_spawned {
self.at_this_moment_apply_commands(f)
} else {
self
}
}
pub fn on_spawn_apply_commands_if<F>(self, f: F, condition: impl FnOnce() -> bool) -> Self
where
F: FnOnce(&mut EntityCommands<'_>),
{
if self.e.will_be_spawned {
self.at_this_moment_apply_commands_if(f, condition)
} else {
self
}
}
pub fn on_spawn_insert<F, B>(self, f: F) -> Self
where
F: FnOnce() -> B,
B: Bundle,
{
self.on_spawn_apply_commands(|commands| {
commands.insert(f());
})
}
pub fn on_spawn_insert_if<F, B, Cond>(self, f: F, condition: impl FnOnce() -> bool) -> Self
where
F: FnOnce() -> B,
B: Bundle,
{
self.on_spawn_apply_commands_if(
|commands| {
commands.insert(f());
},
condition,
)
}
pub fn on_spawn_insert_if_new<F, B>(self, f: F) -> Self
where
F: FnOnce() -> B,
B: Bundle,
{
self.on_spawn_apply_commands(|commands| {
commands.insert_if_new(f());
})
}
pub fn on_spawn_insert_if_new_and<F, B>(self, f: F, condition: impl FnOnce() -> bool) -> Self
where
F: FnOnce() -> B,
B: Bundle,
{
self.on_spawn_apply_commands_if(
|commands| {
commands.insert_if_new(f());
},
condition,
)
}
pub fn on_spawn_observe<E: EntityEvent, B: Bundle, M>(
self,
observer: impl IntoObserverSystem<E, B, M>,
) -> Self {
self.on_spawn_apply_commands(|commands| {
commands.observe(observer);
})
}
pub fn on_change_insert<F, B>(mut self, changed: bool, f: F) -> Self
where
F: FnOnce() -> B,
B: Bundle,
{
if self.e.will_be_spawned || changed {
self.entity_commands().insert(f());
self
} else {
self
}
}
pub fn cap_entity_contains<T: Component>(&self) -> bool {
let Ok(entity) = self.cap_get_entity() else {
return false;
};
entity.contains::<T>()
}
pub fn cap_get_component<T: Component>(&self) -> Result<Option<&T>, QueryEntityError> {
let entity = self.cap_get_entity()?;
Ok(entity.get::<T>())
}
pub fn cap_get_component_mut<'a, T: Component<Mutability = Mutable>>(
&'a mut self,
) -> Result<Option<bevy_ecs::world::Mut<'a, T>>, QueryEntityError> {
let entity = self.cap_get_entity_mut()?;
Ok(entity.into_mut::<T>())
}
pub fn cap_get_resource<R: Resource>(
&self,
) -> Result<bevy_ecs::world::Ref<'_, R>, ResourceFetchError> {
self.ctx().cap_resources.get::<R>()
}
pub fn cap_get_resource_mut<R: Resource>(
&mut self,
) -> Result<bevy_ecs::world::Mut<'_, R>, ResourceFetchError> {
self.ctx_mut().cap_resources.get_mut::<R>()
}
pub fn changed_for<T>(&self, value: &Mut<'_, T>) -> bool {
value.is_changed() || value.last_changed() == self.ctx().system_change_tick.last_run()
}
pub fn has_changed<T>(&self, value: &Mut<'_, T>) -> bool {
self.imm.has_changed(value)
}
pub fn cap_entity_tmp_store(&self) -> &ImmTypeMap {
&self.tmp_store
}
pub fn cap_entity_tmp_store_mut(&mut self) -> &mut ImmTypeMap {
&mut self.tmp_store
}
pub fn parent_entity(&self) -> Option<Entity> {
self.imm.current_entity()
}
pub fn hash_update(&mut self, key: ImmId, value: Option<ImmId>) -> bool {
match value {
Some(value) => self.hash_set(key, value),
None => self.hash_remove(key).is_some(),
}
}
pub fn hash_update_typ<UniqueMarker: 'static>(&mut self, value: Option<ImmId>) -> bool {
match value {
Some(value) => self.hash_set_typ::<UniqueMarker>(value),
None => self.hash_remove_typ::<UniqueMarker>().is_some(),
}
}
pub fn hash_set(&mut self, key: ImmId, value: ImmId) -> bool {
let entity = self.entity();
self.imm.ctx.cached_hash.set(entity, key, value)
}
pub fn hash_set_typ<UniqueMarker: 'static>(&mut self, value: ImmId) -> bool {
let entity = self.entity();
self.imm
.ctx
.cached_hash
.set_typ::<UniqueMarker>(entity, value)
}
pub fn hash_get(&self, key: ImmId) -> Option<ImmId> {
let entity = self.entity();
self.imm.ctx.cached_hash.get(entity, key)
}
pub fn hash_get_typ<UniqueMarker: 'static>(&self) -> Option<ImmId> {
let entity = self.entity();
self.imm.ctx.cached_hash.get_typ::<UniqueMarker>(entity)
}
pub fn hash_remove(&mut self, key: ImmId) -> Option<ImmId> {
let entity = self.entity();
self.imm.ctx.cached_hash.remove(entity, key)
}
pub fn hash_remove_typ<UniqueMarker: 'static>(&mut self) -> Option<ImmId> {
let entity = self.entity();
self.imm.ctx.cached_hash.remove_typ::<UniqueMarker>(entity)
}
pub fn on_hash_change_insert<H, F, B>(mut self, key: &str, value: &H, f: F) -> Self
where
H: std::hash::Hash,
F: FnOnce() -> B,
B: Bundle,
{
let key = imm_id(key);
let value = imm_id(value);
let current = self.hash_get(key);
if current != Some(value) {
self.entity_commands().insert(f());
self.hash_set(key, value);
}
self
}
pub fn on_hash_change_typ_insert<Key, H, F, B>(mut self, value: &H, f: F) -> Self
where
Key: 'static,
H: std::hash::Hash,
F: FnOnce() -> B,
B: Bundle,
{
let value = imm_id(value);
let current = self.hash_get_typ::<Key>();
if current != Some(value) {
self.entity_commands().insert(f());
self.hash_set_typ::<Key>(value);
}
self
}
}
#[derive(bevy_ecs::component::Component)]
pub struct ImmMarker<Caps> {
id: ImmId,
iteration: u32,
_ph: PhantomData<Caps>,
}
pub type WithoutImm<Caps = ()> = Without<ImmMarker<Caps>>;
pub struct ChangeDetector {
last_run: bevy_ecs::change_detection::Tick,
}
impl ChangeDetector {
pub fn has_changed<T>(&self, value: &Mut<'_, T>) -> bool {
value.is_changed() || value.last_changed() == self.last_run
}
}
pub struct ImmScopeGuard<'r, 'w, 's, Caps: CapSet> {
imm: &'r mut Imm<'w, 's, Caps>,
stored_current: Current,
}
impl<'r, 'w, 's, Caps: CapSet> std::ops::Deref for ImmScopeGuard<'r, 'w, 's, Caps> {
type Target = Imm<'w, 's, Caps>;
fn deref(&self) -> &Self::Target {
self.imm
}
}
impl<'r, 'w, 's, Caps: CapSet> std::ops::DerefMut for ImmScopeGuard<'r, 'w, 's, Caps> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.imm
}
}
impl<'r, 'w, 's, Caps: CapSet> Drop for ImmScopeGuard<'r, 'w, 's, Caps> {
fn drop(&mut self) {
self.imm.current = self.stored_current;
}
}
impl<'r, 'w, 's, Caps: CapSet> ImmScopeGuard<'r, 'w, 's, Caps> {
pub fn add_id_pref(
imm: &'r mut Imm<'w, 's, Caps>,
additional_auto_id: impl std::hash::Hash,
) -> Self {
let auto_id_pref = imm.current.id_pref.with(additional_auto_id);
Self::new_scope(
imm,
Current {
id: imm.current.id,
entity: imm.current.entity,
auto_id_idx: 0,
id_pref: auto_id_pref,
},
)
}
fn new_scope(imm: &'r mut Imm<'w, 's, Caps>, mut new_current: Current) -> Self {
std::mem::swap(&mut new_current, &mut imm.current);
Self {
imm,
stored_current: new_current,
}
}
}