diff --git a/examples/basic.rs b/examples/basic.rs index eef73d4..e9ac0fa 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,32 +1,34 @@ -use std::time::Duration; - -use bevy::prelude::*; +use bevy::{prelude::*, sprite::MaterialMesh2dBundle}; use corentin::prelude::*; fn main() { App::new() - .add_plugins(DefaultPlugins) - .insert_resource(Executor::new()) - .add_systems(Startup, setup_coroutines) - .add_systems(Update, run_coroutines) + .add_plugins((DefaultPlugins, CorentinPlugin)) + .add_systems(Startup, setup_scene) .run(); } -fn setup_coroutines(mut executor: ResMut) { - executor.add(|mut fib| async move { - let mut i = 0; - loop { - let dt = fib.next_tick().await; - println!("Last frame lasted for {}", dt.as_secs_f32()); - fib.duration(Duration::from_secs(1)).await; - i += 1; - println!("This coroutine has started since {} seconds", i); - } - }); -} +fn setup_scene( + mut meshes: ResMut>, + mut materials: ResMut>, + mut commands: Commands, +) { + commands.spawn(Camera2dBundle::default()); -fn run_coroutines(world: &mut World) { - world.resource_scope(|w, mut exec: Mut| { - exec.tick(w); - }) + // Circle + commands + .spawn(MaterialMesh2dBundle { + mesh: meshes.add(shape::Circle::new(50.).into()).into(), + material: materials.add(ColorMaterial::from(Color::PURPLE)), + transform: Transform::from_translation(Vec3::new(-150., 0., 0.)), + ..default() + }) + .add(coroutine( + |fib: Fib, mut transform: Wr| async move { + loop { + let dt = fib.next_tick().await; + transform.get_mut().translation.x += 100.0 * dt.as_secs_f32(); + } + }, + )); } diff --git a/examples/read_write.rs b/examples/read_write.rs index ebd63cd..d24ade7 100644 --- a/examples/read_write.rs +++ b/examples/read_write.rs @@ -1,46 +1,30 @@ -use std::time::Duration; - -use bevy::prelude::*; -use corentin::{coroutine::PrimitiveVoid, prelude::*}; - -#[derive(Component)] -struct ExampleComponent(u32); - fn main() { - App::new() - .add_plugins(DefaultPlugins) - .insert_resource(Executor::new()) - .add_systems(Startup, setup_access) - .add_systems(Update, run_coroutines) - .run(); + //App::new() + // .add_plugins((DefaultPlugins, CorentinPlugin)) + // .add_systems(Startup, setup_access) + // .run(); } -fn setup_access(world: &mut World) { - world.resource_scope(|w, mut exec: Mut| { - let e = w.spawn(ExampleComponent(0)).id(); - exec.add_to_entity(e, move |mut fib, this| async move { - loop { - let mut b = fib - .duration(Duration::from_secs(1)) - .then_grab::<&mut ExampleComponent>(this) - .await; - b.0 += 1; - } - }); - exec.add(|mut fib| async move { - loop { - let c = fib - .change::(e) - .then_grab::<&ExampleComponent>(e) - .await; - println!("Change detected, value is now {}", c.0); - } - }); - }) -} - -fn run_coroutines(world: &mut World) { - world.resource_scope(|w, mut exec: Mut| { - exec.tick(w); - }) -} +//fn setup_scene( +// mut meshes: ResMut>, +// mut materials: ResMut>, +// mut commands: Commands, +//) { +// commands.spawn(Camera2dBundle::default()); +// let e = world.spawn((IsClicked(0))).id(); +// coroutine(|fib: Fib, mut ex: R| async move { +// loop { +// ex.on_change().await; +// +// } +// }) +// .apply(e, world); +// +// coroutine(|_: Fib, ex: R| async move { +// loop { +// ex.on_change().await; +// println!("Change detected, value is now {}", ex.get().0); +// } +// }) +// .apply(e, world); +//} diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..904a9d8 --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,39 @@ +use bevy::ecs::system::EntityCommand; +use bevy::utils::synccell::SyncCell; +use std::marker::PhantomData; + +use bevy::prelude::{Entity, World}; + +use crate::coroutine::UninitCoroutine; +use crate::executor::Executor; + +pub struct AddCoroutine { + coro: C, + _phantom: PhantomData, +} + +impl EntityCommand for AddCoroutine +where + C: UninitCoroutine, +{ + fn apply(self, id: Entity, world: &mut World) { + world.resource_scope::(|w, mut executor| { + if let Some(coroutine) = self.coro.init(id, w) { + executor.add(SyncCell::new(Box::pin(coroutine))); + } + }); + } +} + +pub fn coroutine(coro: C) -> AddCoroutine { + AddCoroutine::new(coro) +} + +impl AddCoroutine { + pub fn new(coro: C) -> Self { + Self { + coro, + _phantom: PhantomData, + } + } +} diff --git a/src/coroutine/coro_param/component.rs b/src/coroutine/coro_param/component.rs new file mode 100644 index 0000000..2c2687c --- /dev/null +++ b/src/coroutine/coro_param/component.rs @@ -0,0 +1,156 @@ +use std::marker::PhantomData; + +use bevy::prelude::{Component, Entity, World}; + +use crate::coroutine::{ + observable::{ObservableId, OnChange}, + CoroAccess, CoroWrites, SourceId, +}; + +use super::{CoroParam, ParamContext, RdGuard, WrGuard}; + +/// A readonly reference to a [`Component`] from the owning [`Entity`]. +/// +/// Note that a Coroutine with such parameter will be canceled if the entity does not have the +/// relevent component. +pub struct Rd { + context: ParamContext, + _phantom: PhantomData, +} + +impl CoroParam for Rd { + fn init(context: ParamContext, world: &mut World, access: &mut CoroAccess) -> Option { + let id = world + .component_id::() + .unwrap_or(world.init_component::()); + + if !access.add_read(SourceId::Entity(context.owner), id) { + return None; + } + + Some(Self { + context, + _phantom: PhantomData, + }) + } + + fn is_valid(owner: Entity, world: &World) -> bool { + match world.get_entity(owner) { + Some(e) => e.contains::(), + _ => false, + } + } +} + +impl Rd { + /// Return the current value of the [`Component`]. The result ([`InGuard`]) cannot be held + /// accros any await. + pub fn get(&self) -> RdGuard<'_, T> { + unsafe { + RdGuard::new( + self.context + .world_window + .world_cell() + .get_entity(self.context.owner) + .unwrap() + .get::() + .unwrap(), + ) + } + } + + /// Yields and resume when the `Component` is mutated. + /// + /// Note that it integrates with the regular change detection of Bevy, meaning that the + /// coroutine will be resumed, if a [`System`] mutates the value. + pub fn on_change(&self) -> OnChange<'_> { + unsafe { + OnChange::new( + &self.context, + ObservableId::Component( + self.context.owner, + self.context.world_window.component_id::(), + ), + ) + } + } +} + +/// A read-write exclusive reference to a [`Component`] from the owning [`Entity`]. +/// +/// Note that a Coroutine with such parameter will be canceled if the entity does not have the +/// relevent component. +pub struct Wr { + _phantom: PhantomData, + context: ParamContext, +} + +impl CoroParam for Wr { + fn init(context: ParamContext, w: &mut World, access: &mut CoroAccess) -> Option { + let id = w.component_id::().unwrap_or(w.init_component::()); + + if !access.add_write(SourceId::Entity(context.owner), id) { + return None; + } + + Some(Self { + _phantom: PhantomData, + context, + }) + } + + fn is_valid(owner: Entity, world: &World) -> bool { + match world.get_entity(owner) { + Some(e) => e.contains::(), + _ => false, + } + } +} + +impl Wr { + pub fn get(&self) -> RdGuard<'_, T> { + let value = unsafe { + self.context + .world_window + .world_cell() + .get_entity(self.context.owner) + .unwrap() + .get::() + .unwrap() + }; + + RdGuard::new(value) + } + + pub fn get_mut(&mut self) -> WrGuard<'_, T> { + unsafe { + let cell = self.context.world_window.world_cell(); + let c_id = cell.components().component_id::().unwrap(); + cell.get_resource_mut::() + .unwrap() + .0 + // TODO fix write + .push_back((self.context.owner, c_id)); + + let value = cell + .get_entity(self.context.owner) + .unwrap() + .get_mut::() + .unwrap(); + + WrGuard::new(value) + } + } + + pub fn on_change(&self) -> OnChange<'_> { + unsafe { + OnChange::new( + &self.context, + ObservableId::Component( + self.context.owner, + self.context.world_window.component_id::(), + ), + ) + } + } +} diff --git a/src/coroutine/coro_param/mod.rs b/src/coroutine/coro_param/mod.rs new file mode 100644 index 0000000..4e97cdf --- /dev/null +++ b/src/coroutine/coro_param/mod.rs @@ -0,0 +1,223 @@ +use std::{ + cell::Cell, + marker::PhantomData, + ops::{Deref, DerefMut}, + rc::Rc, +}; + +use super::{CoroAccess, WaitingReason}; +use bevy::{ + ecs::{component::ComponentId, world::unsafe_world_cell::UnsafeWorldCell}, + prelude::{Component, Entity, Mut, World}, + utils::all_tuples, +}; + +pub mod component; +pub mod resource; + +/// Any async function that takes only [`CoroParam`] as arguments can +/// automatically be turned into a [`Coroutine`]. +pub trait CoroParam: Sized + Send + 'static { + /// Initialize the parameter and register any access, if it is invalid (for instance + /// conflicting accesses) it will return None. + /// + /// Note: `world_window` is not yet open at that point, `world` should be used instead. + fn init(context: ParamContext, world: &mut World, access: &mut CoroAccess) -> Option; + + /// Returns true if the parameter is still valid. + fn is_valid(owner: Entity, world: &World) -> bool; +} + +/// A shared ref to a [`World`], it is "open" (meaning it points to a valid world with exclusive +/// access) when the Coroutine is resumed. It is shared to all [`CoroParam`]s. +#[derive(Clone)] +pub struct WorldWindow(Rc>); + +// Safety: The window is only shared whith the coroutine itself, As with other similar construct, +// if you start spawning thread inside a coroutine, or sending coroutine parameters via channels, +// you may break the safety, therefore please don't do that. (thanks UwU) +unsafe impl Send for WorldWindow {} +unsafe impl Sync for WorldWindow {} + +impl WorldWindow { + pub fn closed_window() -> Self { + WorldWindow(Rc::new(Cell::new(std::ptr::null_mut()))) + } + + pub fn scope(&mut self, world: &mut World, f: impl FnOnce() -> T) -> T { + self.0.replace(world as *mut _); + let res = f(); + self.0.replace(std::ptr::null_mut()); + res + } + + /// Returns the world as an [`UnsafeWorldCell`]. + /// + /// # Safety + /// The caller must ensure that the pointer points to a valid value. (the window should be + /// "open"). + pub unsafe fn world_cell(&self) -> UnsafeWorldCell<'_> { + (*self.0.get()).as_unsafe_world_cell() + } + + /// Return the appropriate ComponentId, and initialize it if not present in the world + /// + /// # Safety + /// The caller must ensure that the pointer points to a valid value. (the window should be + /// "open"). + pub unsafe fn component_id(&self) -> ComponentId { + let cell = self.world_cell(); + cell.components() + .component_id::() + .unwrap_or_else(|| cell.world_mut().init_component::()) + } +} + +/// The channel throught each [`CoroParam`] can yield back to the coroutine. +/// Which can then return the reason to the caller (the [`Executor`]). +#[derive(Default, Clone)] +pub struct YieldChannel(Rc>>); + +impl YieldChannel { + pub(crate) fn new() -> Self { + Self::default() + } + + pub(crate) fn receive(&self) -> Option { + self.0.replace(None) + } + + pub(crate) fn send(&self, val: WaitingReason) { + self.0.replace(Some(val)); + } +} + +// Safety: Same as [`WorldWindow`]. +unsafe impl Send for YieldChannel {} +unsafe impl Sync for YieldChannel {} + +/// All relevent values a [`CoroParam`] might need. +#[derive(Clone)] +pub struct ParamContext { + pub(crate) owner: Entity, + pub(crate) world_window: WorldWindow, + pub(crate) yield_channel: YieldChannel, +} + +/// A guarded readonly reference, cannot be held accros awaiting points. +pub struct RdGuard<'a, T> { + value: &'a T, + _phantom: PhantomData<*const T>, +} + +impl<'a, T> RdGuard<'a, T> { + pub fn new(value: &'a T) -> Self { + Self { + value, + _phantom: PhantomData, + } + } +} + +impl<'a, T> Deref for RdGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value + } +} + +/// A guarded exclusive write reference, cannot be held accros awaiting points. +pub struct WrGuard<'a, T> { + value: Mut<'a, T>, + _phantom: PhantomData<*const T>, +} + +impl<'a, T> WrGuard<'a, T> { + pub fn new(value: Mut<'a, T>) -> Self { + Self { + value, + _phantom: PhantomData, + } + } +} + +impl<'a, T> Deref for WrGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value.deref() + } +} + +impl<'a, T> DerefMut for WrGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.value.deref_mut() + } +} + +pub struct Opt { + context: ParamContext, + inner: T, +} + +impl Opt { + pub fn get(&self) -> Option<&T> { + unsafe { + if T::is_valid( + self.context.owner, + self.context.world_window.world_cell().world(), + ) { + Some(&self.inner) + } else { + None + } + } + } + + pub fn get_mut(&mut self) -> Option<&mut T> { + unsafe { + if T::is_valid( + self.context.owner, + self.context.world_window.world_cell().world(), + ) { + Some(&mut self.inner) + } else { + None + } + } + } +} + +impl CoroParam for Opt { + fn init(context: ParamContext, world: &mut World, access: &mut CoroAccess) -> Option { + let t = T::init(context.clone(), world, access)?; + + Some(Self { context, inner: t }) + } + + fn is_valid(_owner: Entity, _world: &World) -> bool { + true + } +} + +macro_rules! impl_coro_param { + ($($param: ident),*) => { + #[allow(non_snake_case, unused_parens, unused_variables)] + impl<$($param: CoroParam),*> CoroParam for ($($param,)*) { + fn init(context: ParamContext, world: &mut World, access: &mut CoroAccess) -> Option { + $(let $param = $param::init(context.clone(), world, access)?;)* + + Some(($($param,)*)) + + } + + fn is_valid(owner: Entity, world: &World) -> bool { + true $(&& $param::is_valid(owner, world))* + } + } + + }; +} + +all_tuples!(impl_coro_param, 0, 16, P); diff --git a/src/coroutine/coro_param/resource.rs b/src/coroutine/coro_param/resource.rs new file mode 100644 index 0000000..38869dd --- /dev/null +++ b/src/coroutine/coro_param/resource.rs @@ -0,0 +1,139 @@ +use std::marker::PhantomData; + +use bevy::{ + ecs::component::ComponentId, + prelude::{self, Resource, World}, +}; + +use crate::coroutine::{ + observable::{ObservableId, OnChange}, + SourceId, +}; + +use super::{CoroParam, ParamContext, RdGuard, WrGuard}; + +/// A readonly reference to a [`Resource`] in the [`World`]. +/// +/// Note that a Coroutine with such parameter will be canceled if the resource is removed. +pub struct RdRes { + context: ParamContext, + id: ComponentId, + _phantom: PhantomData, +} + +impl CoroParam for RdRes { + fn init( + context: ParamContext, + world: &mut World, + access: &mut crate::coroutine::CoroAccess, + ) -> Option { + let id = world.components().resource_id::()?; + if access.add_read(SourceId::World, id) { + return None; + } + + Some(RdRes { + context, + id, + _phantom: PhantomData, + }) + } + + fn is_valid(_owner: prelude::Entity, world: &World) -> bool { + world.contains_resource::() + } +} + +impl RdRes { + /// Return the current value of the [`Resource`]. The result ([`RdGuard`]) cannot be held + /// accros any await. + pub fn get(&self) -> RdGuard<'_, R> { + unsafe { + RdGuard::new( + self.context + .world_window + .world_cell() + .get_resource::() + .unwrap(), + ) + } + } + + /// Yields and resume when the [`Resource`] is mutated. + /// + /// Note that it integrates with the regular change detection of Bevy, meaning that the + /// coroutine will be resumed, if a [`System`] mutates the value. + pub fn on_change(&self) -> OnChange<'_> { + OnChange::new(&self.context, ObservableId::Resource(self.id)) + } +} + +/// A read-write exclusive reference to a [`Resource`] in the [`World`]. +/// +/// Note that a Coroutine with such parameter will be canceled if the resource is removed. +pub struct WrRes { + context: ParamContext, + id: ComponentId, + _phantom: PhantomData, +} + +impl CoroParam for WrRes { + fn init( + context: ParamContext, + world: &mut World, + access: &mut crate::coroutine::CoroAccess, + ) -> Option { + let id = world.components().resource_id::()?; + if access.add_write(SourceId::World, id) { + return None; + } + + Some(WrRes { + context, + id, + _phantom: PhantomData, + }) + } + + fn is_valid(_owner: prelude::Entity, world: &World) -> bool { + world.contains_resource::() + } +} + +impl WrRes { + /// Return the current value of the [`Resource`]. The result ([`RdGuard`]) cannot be held + /// accros any await. + pub fn get(&self) -> RdGuard<'_, R> { + unsafe { + RdGuard::new( + self.context + .world_window + .world_cell() + .get_resource::() + .unwrap(), + ) + } + } + + /// Return the current value of the [`Resource`]. The result ([`RdGuard`]) cannot be held + /// accros any await. + pub fn get_mut(&mut self) -> WrGuard<'_, R> { + unsafe { + WrGuard::new( + self.context + .world_window + .world_cell() + .get_resource_mut::() + .unwrap(), + ) + } + } + + /// Yields and resume when the [`Resource`] is mutated. + /// + /// Note that it integrates with the regular change detection of Bevy, meaning that the + /// coroutine will be resumed, if a [`System`] mutates the value. + pub fn on_change(&self) -> OnChange<'_> { + OnChange::new(&self.context, ObservableId::Resource(self.id)) + } +} diff --git a/src/coroutine/duration.rs b/src/coroutine/duration.rs index 1895a60..4fc630f 100644 --- a/src/coroutine/duration.rs +++ b/src/coroutine/duration.rs @@ -1,36 +1,33 @@ -use crate::coroutine::{CoroState, Fib, WaitingReason}; +use crate::coroutine::WaitingReason; use bevy::time::Time; use bevy::time::Timer; use bevy::time::TimerMode; use std::future::Future; -use std::marker::PhantomData; use std::pin::Pin; use std::task::Context; use std::task::Poll; use std::time::Duration; -use super::Primitive; -use super::PrimitiveVoid; +use super::coro_param::ParamContext; +use super::CoroState; #[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct NextTick<'a> { - fib: &'a Fib, +pub struct NextTick { + context: ParamContext, state: CoroState, - _phantom: PhantomData<&'a ()>, } -impl<'a> NextTick<'a> { - pub(crate) fn new(fib: &'a Fib) -> Self { +impl NextTick { + pub(crate) fn new(context: ParamContext) -> Self { NextTick { - fib, + context, state: CoroState::Running, - _phantom: PhantomData, } } } -impl<'a> Future for NextTick<'a> { +impl Future for NextTick { type Output = Duration; fn poll(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll { @@ -41,47 +38,40 @@ impl<'a> Future for NextTick<'a> { // SAFETY: None lmao let dt = unsafe { - (*self.fib.world_window.get().unwrap()) - .resource::