From 0d8c05136a6809977ab04f9f53c4f5015e8eacb0 Mon Sep 17 00:00:00 2001 From: Zacharie Tevaearai Date: Sat, 16 Sep 2023 16:02:11 +0200 Subject: [PATCH 01/11] WIP --- examples/basic.rs | 44 +- examples/read_write.rs | 88 ++-- src/commands.rs | 39 ++ src/coroutine/coro_param.rs | 253 ++++++++++ src/coroutine/duration.rs | 32 +- src/coroutine/function_coroutine.rs | 247 ++++++++++ src/coroutine/grab.rs | 466 ------------------ src/coroutine/mod.rs | 361 ++++---------- src/coroutine/observable.rs | 59 +++ src/coroutine/on.rs | 94 ++-- src/coroutine/par_and.rs | 53 +-- src/coroutine/par_or.rs | 53 +-- src/coroutine/waker.rs | 15 + src/coroutine/when.rs | 143 +----- src/executor/mod.rs | 395 ++++++--------- src/executor/run_ctx.rs | 31 +- src/lib.rs | 712 ++++++++++++++-------------- src/plugin.rs | 19 + 18 files changed, 1444 insertions(+), 1660 deletions(-) create mode 100644 src/commands.rs create mode 100644 src/coroutine/coro_param.rs create mode 100644 src/coroutine/function_coroutine.rs delete mode 100644 src/coroutine/grab.rs create mode 100644 src/coroutine/observable.rs create mode 100644 src/coroutine/waker.rs create mode 100644 src/plugin.rs diff --git a/examples/basic.rs b/examples/basic.rs index eef73d4..f45f587 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: W| 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..8822c9d 100644 --- a/examples/read_write.rs +++ b/examples/read_write.rs @@ -1,46 +1,42 @@ -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(); -} - -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); - }) -} +//use std::time::Duration; +// +//use bevy::ecs::system::EntityCommand; +//use bevy::prelude::*; +//use corentin::{ +// coroutine::coro_param::{R, W}, +// prelude::*, +//}; +// +//#[derive(Component)] +//struct IsClicked(u32); +// +//fn main() { +// App::new() +// .add_plugins((DefaultPlugins, CorentinPlugin)) +// .add_systems(Startup, setup_access) +// .run(); +//} +// +//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.rs b/src/coroutine/coro_param.rs new file mode 100644 index 0000000..dad14eb --- /dev/null +++ b/src/coroutine/coro_param.rs @@ -0,0 +1,253 @@ +use std::{ + cell::Cell, + marker::PhantomData, + ops::{Deref, DerefMut}, + rc::Rc, +}; + +use crate::executor::msg_channel::Sender; + +use super::{ + observable::{ObservableId, OnChange}, + CoroMeta, CoroWrites, WaitingReason, +}; +use bevy::{ + ecs::{component::ComponentId, world::unsafe_world_cell::UnsafeWorldCell}, + prelude::{Component, Entity, Mut, World}, + utils::all_tuples, +}; + +/// 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, meta: &mut CoroMeta) -> Option; +} + +/// A shared ref to a [`World`], it is "open" (meaning it points to a valid world) when the +/// Coroutine is resumed. +#[derive(Clone)] +pub struct WorldWindow(pub(crate) Rc>); + +// Safety: The window is only shared whith the coroutine itself +unsafe impl Send for WorldWindow {} + +impl WorldWindow { + /// #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"). And that it is called with exclusive world. + pub unsafe fn component_id(&self) -> ComponentId { + let cell = self.world_cell(); + cell.components() + .component_id::() + .unwrap_or_else(|| cell.world_mut().init_component::()) + } +} + +/// All relevent values a [`CoroParam`] might need. +#[derive(Clone)] +pub struct ParamContext { + pub(crate) owner: Entity, + pub(crate) world_window: WorldWindow, + pub(crate) yield_sender: Sender, +} + +/// Safety ? Who knows... +unsafe impl Send for ParamContext {} +unsafe impl Sync for ParamContext {} + +/// 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 R { + context: ParamContext, + _phantom: PhantomData, +} + +unsafe impl Send for R {} + +impl CoroParam for R { + fn init(context: ParamContext, world: &mut World, meta: &mut CoroMeta) -> Option { + let id = world + .component_id::() + .unwrap_or(world.init_component::()); + + if meta.this_writes.contains(id.index()) { + return None; + } + + meta.this_reads.insert(id.index()); + + Some(Self { + context, + _phantom: PhantomData, + }) + } +} + +/// A guarded readonly reference, cannot be hold accros awaiting points. +pub struct InGuard<'a, T> { + value: &'a T, + _phantom: PhantomData<*const T>, +} + +impl<'a, T: Component> Deref for InGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value + } +} + +impl R { + pub fn get<'a>(&'a self) -> InGuard<'a, T> { + unsafe { + InGuard { + value: self + .context + .world_window + .world_cell() + .get_entity(self.context.owner) + .unwrap() + .get::() + .unwrap(), + _phantom: PhantomData, + } + } + } + + pub fn on_change(&self) -> OnChange<'_> { + unsafe { + OnChange::new( + &self.context, + ObservableId::Component( + self.context.owner, + self.context.world_window.component_id::(), + ), + ) + } + } +} + +pub struct W { + _phantom: PhantomData, + context: ParamContext, +} + +pub struct InOutGuard<'a, T> { + value: Mut<'a, T>, + _phantom: PhantomData<*const T>, +} + +impl<'a, T: Component> Deref for InOutGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value.deref() + } +} + +impl<'a, T: Component> DerefMut for InOutGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.value.deref_mut() + } +} + +impl W { + pub fn get<'a>(&'a self) -> InGuard<'a, T> { + let value = unsafe { + self.context.world_window + .world_cell() + .get_entity(self.context.owner) + .unwrap() + .get::() + .unwrap() + }; + + InGuard { + value, + _phantom: PhantomData, + } + } + + pub fn get_mut<'a>(&'a mut self) -> InOutGuard<'a, T> { + unsafe { + let cell = self.context.world_window.world_cell(); + let c_id = cell.components().component_id::().unwrap(); + cell.get_resource_mut::() + .unwrap() + .0 + .push_back((self.context.owner, c_id)); + + let value = cell.get_entity(self.context.owner).unwrap().get_mut::().unwrap(); + InOutGuard { + value, + _phantom: PhantomData, + } + } + } + + pub fn on_change(&self) -> OnChange<'_> { + unsafe { + OnChange::new( + &self.context, + ObservableId::Component( + self.context.owner, + self.context.world_window.component_id::(), + ), + ) + } + } +} + +impl CoroParam for W { + fn init(context: ParamContext, w: &mut World, meta: &mut CoroMeta) -> Option { + let id = w.component_id::().unwrap_or(w.init_component::()); + + if !meta.this_reads.insert(id.index()) { + return None; + } + + meta.this_writes.insert(id.index()); + + Some(Self { + _phantom: PhantomData, + context, + }) + } +} + +// TODO: Later +//impl CoroParam for Option> { +// +//} + +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, meta: &mut CoroMeta) -> Option { + $(let $param = $param::init(context.clone(), world, meta)?;)* + + Some(($($param,)*)) + + } + } + + }; +} + +all_tuples!(impl_coro_param, 0, 16, P); diff --git a/src/coroutine/duration.rs b/src/coroutine/duration.rs index 1895a60..fbb2ce7 100644 --- a/src/coroutine/duration.rs +++ b/src/coroutine/duration.rs @@ -1,23 +1,21 @@ -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::CoroState; +use crate::coroutine::function_coroutine::Fib; #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct NextTick<'a> { fib: &'a Fib, state: CoroState, - _phantom: PhantomData<&'a ()>, } impl<'a> NextTick<'a> { @@ -25,7 +23,6 @@ impl<'a> NextTick<'a> { NextTick { fib, state: CoroState::Running, - _phantom: PhantomData, } } } @@ -41,33 +38,27 @@ impl<'a> Future for NextTick<'a> { // SAFETY: None lmao let dt = unsafe { - (*self.fib.world_window.get().unwrap()) - .resource::