From 36d604b6bd414fd1fbf1e344c6c725bd4a816798 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Thu, 23 Jan 2025 21:50:25 +0800 Subject: [PATCH 01/37] Introducing the concept of a buffer map Signed-off-by: Michael X. Grey --- src/buffer.rs | 38 +++++++++++++++++- src/buffer/buffer_map.rs | 87 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/buffer/buffer_map.rs diff --git a/src/buffer.rs b/src/buffer.rs index bb0fe2b8..2285d074 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -22,7 +22,7 @@ use bevy_ecs::{ system::SystemParam, }; -use std::{ops::RangeBounds, sync::Arc}; +use std::{ops::RangeBounds, sync::Arc, any::TypeId}; use crate::{ Builder, Chain, Gate, GateState, InputSlot, NotifyBufferUpdate, OnNewBufferValue, UnusedTarget, @@ -34,6 +34,9 @@ pub(crate) use buffer_access_lifecycle::*; mod buffer_key_builder; pub(crate) use buffer_key_builder::*; +mod buffer_map; +pub use buffer_map::*; + mod buffer_storage; pub(crate) use buffer_storage::*; @@ -95,6 +98,39 @@ pub struct CloneFromBuffer { pub(crate) _ignore: std::marker::PhantomData, } +/// A [`Buffer`] whose type has been anonymized. +#[derive(Clone, Copy, Debug)] +pub struct DynBuffer { + pub(crate) scope: Entity, + pub(crate) source: Entity, + pub(crate) type_id: TypeId, +} + +impl DynBuffer { + pub fn into_buffer(&self) -> Option> { + if TypeId::of::() == self.type_id { + Some(Buffer { + scope: self.scope, + source: self.source, + _ignore: Default::default(), + }) + } else { + None + } + } +} + +impl From> for DynBuffer { + fn from(value: Buffer) -> Self { + let type_id = TypeId::of::(); + DynBuffer { + scope: value.scope, + source: value.source, + type_id, + } + } +} + /// Settings to describe the behavior of a buffer. #[derive(Default, Clone, Copy)] pub struct BufferSettings { diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs new file mode 100644 index 00000000..2aa75d5d --- /dev/null +++ b/src/buffer/buffer_map.rs @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2025 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +use std::{ + collections::HashMap, + borrow::Cow, +}; + +use bevy_ecs::prelude::{Entity, World}; + +use crate::{DynBuffer, OperationError, OperationResult, OperationRoster, Buffered, Gate}; + +#[derive(Clone)] +pub struct BufferMap { + inner: HashMap, DynBuffer>, +} + +pub trait BufferKeyMap: Sized { + + fn buffered_count( + buffers: &BufferMap, + session: Entity, + world: &World + ) -> Result; + + fn pull( + buffers: &BufferMap, + session: Entity, + world: &mut World, + ) -> Result; + + fn add_listener( + buffers: &BufferMap, + listener: Entity, + world: &mut World, + ) -> OperationResult; + + fn gate_action( + buffers: &BufferMap, + session: Entity, + action: Gate, + world: &mut World, + roster: &mut OperationRoster, + ) -> OperationResult; +} + +struct BufferedMap { + map: BufferMap, + _ignore: std::marker::PhantomData, +} + +impl Buffered for BufferedMap { + + type Item = K; + + fn verify_scope(&self, scope: Entity) { + for buffer in self.map.inner.values() { + assert_eq!(scope, buffer.scope); + } + } + + fn buffered_count(&self, session: Entity, world: &World) -> Result { + K::buffered_count(&self.map, session, world) + } + + fn pull(&self, session: Entity, world: &mut World) -> Result { + K::pull(&self.map, session, world) + } + + fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { + + } +} From d9b9be1492fc59841102fdc1d531b99c365db327 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Fri, 24 Jan 2025 03:07:49 +0000 Subject: [PATCH 02/37] Refactoring Buffered trait Signed-off-by: Michael X. Grey --- src/buffer/buffer_map.rs | 76 +++++++-- src/buffer/bufferable.rs | 21 +-- src/buffer/buffered.rs | 219 ++++++++++++++----------- src/builder.rs | 6 +- src/operation/cleanup.rs | 4 +- src/operation/join.rs | 4 +- src/operation/listen.rs | 4 +- src/operation/operate_buffer_access.rs | 16 +- src/operation/scope.rs | 4 +- 9 files changed, 211 insertions(+), 143 deletions(-) diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 2aa75d5d..039b6c10 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -20,29 +20,27 @@ use std::{ borrow::Cow, }; +use smallvec::SmallVec; + use bevy_ecs::prelude::{Entity, World}; -use crate::{DynBuffer, OperationError, OperationResult, OperationRoster, Buffered, Gate}; +use crate::{ + DynBuffer, OperationError, OperationResult, OperationRoster, Buffered, Gate, + Joined, Accessed, +}; #[derive(Clone)] pub struct BufferMap { inner: HashMap, DynBuffer>, } -pub trait BufferKeyMap: Sized { - +pub trait BufferMapLayout: Sized { fn buffered_count( buffers: &BufferMap, session: Entity, - world: &World + world: &World, ) -> Result; - fn pull( - buffers: &BufferMap, - session: Entity, - world: &mut World, - ) -> Result; - fn add_listener( buffers: &BufferMap, listener: Entity, @@ -56,6 +54,25 @@ pub trait BufferKeyMap: Sized { world: &mut World, roster: &mut OperationRoster, ) -> OperationResult; + + fn as_input(buffers: &BufferMap) -> SmallVec<[Entity; 8]>; + + fn ensure_active_session( + buffers: &BufferMap, + session: Entity, + world: &mut World, + ) -> OperationResult; +} + +pub trait JoinedValue: BufferMapLayout { + fn buffered_count( + buffers: &BufferMap, + session: Entity, + world: &World, + ) -> Result; +} + +pub trait BufferKeyMap: BufferMapLayout { } struct BufferedMap { @@ -63,10 +80,13 @@ struct BufferedMap { _ignore: std::marker::PhantomData, } -impl Buffered for BufferedMap { - - type Item = K; +impl Clone for BufferedMap { + fn clone(&self) -> Self { + Self { map: self.map.clone(), _ignore: Default::default() } + } +} +impl Buffered for BufferedMap { fn verify_scope(&self, scope: Entity) { for buffer in self.map.inner.values() { assert_eq!(scope, buffer.scope); @@ -74,14 +94,36 @@ impl Buffered for BufferedMap { } fn buffered_count(&self, session: Entity, world: &World) -> Result { - K::buffered_count(&self.map, session, world) + L::buffered_count(&self.map, session, world) } - fn pull(&self, session: Entity, world: &mut World) -> Result { - K::pull(&self.map, session, world) + fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { + L::add_listener(&self.map, listener, world) } - fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { + fn gate_action( + &self, + session: Entity, + action: Gate, + world: &mut World, + roster: &mut OperationRoster, + ) -> OperationResult { + L::gate_action(&self.map, session, action, world, roster) + } + + fn as_input(&self) -> SmallVec<[Entity; 8]> { + L::as_input(&self.map) + } + + fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { + L::ensure_active_session(&self.map, session, world) + } +} + +impl Joined for BufferedMap { + type Item = V; + + fn pull(&self, session: Entity, world: &mut World) -> Result { } } diff --git a/src/buffer/bufferable.rs b/src/buffer/bufferable.rs index 17f8b367..6b4292ec 100644 --- a/src/buffer/bufferable.rs +++ b/src/buffer/bufferable.rs @@ -19,15 +19,16 @@ use bevy_utils::all_tuples; use smallvec::SmallVec; use crate::{ - AddOperation, Buffer, BufferSettings, Buffered, Builder, Chain, CleanupWorkflowConditions, - CloneFromBuffer, Join, Listen, Output, Scope, ScopeSettings, UnusedTarget, + Accessed, AddOperation, Buffer, BufferSettings, Buffered, Builder, Chain, + CleanupWorkflowConditions, CloneFromBuffer, Join, Joined, Listen, Output, Scope, + ScopeSettings, UnusedTarget, }; -pub type BufferKeys = <::BufferType as Buffered>::Key; -pub type BufferItem = <::BufferType as Buffered>::Item; +pub type BufferKeys = <::BufferType as Accessed>::Key; +pub type JoinedItem = <::BufferType as Joined>::Item; pub trait Bufferable { - type BufferType: Buffered; + type BufferType: Joined + Accessed; /// Convert these bufferable workflow elements into buffers if they are not /// buffers already. @@ -42,11 +43,11 @@ pub trait Bufferable { fn join<'w, 's, 'a, 'b>( self, builder: &'b mut Builder<'w, 's, 'a>, - ) -> Chain<'w, 's, 'a, 'b, BufferItem> + ) -> Chain<'w, 's, 'a, 'b, JoinedItem> where Self: Sized, Self::BufferType: 'static + Send + Sync, - BufferItem: 'static + Send + Sync, + JoinedItem: 'static + Send + Sync, { let scope = builder.scope(); let buffers = self.into_buffer(builder); @@ -207,7 +208,7 @@ impl Bufferable for [T; N] { } pub trait IterBufferable { - type BufferElement: Buffered; + type BufferElement: Buffered + Joined; /// Convert an iterable collection of bufferable workflow elements into /// buffers if they are not buffers already. @@ -224,11 +225,11 @@ pub trait IterBufferable { fn join_vec<'w, 's, 'a, 'b, const N: usize>( self, builder: &'b mut Builder<'w, 's, 'a>, - ) -> Chain<'w, 's, 'a, 'b, SmallVec<[::Item; N]>> + ) -> Chain<'w, 's, 'a, 'b, SmallVec<[::Item; N]>> where Self: Sized, Self::BufferElement: 'static + Send + Sync, - ::Item: 'static + Send + Sync, + ::Item: 'static + Send + Sync, { let buffers = self.into_buffer_vec::(builder); let join = builder.commands.spawn(()).id(); diff --git a/src/buffer/buffered.rs b/src/buffer/buffered.rs index 084acb13..a6552552 100644 --- a/src/buffer/buffered.rs +++ b/src/buffer/buffered.rs @@ -31,9 +31,6 @@ pub trait Buffered: Clone { fn buffered_count(&self, session: Entity, world: &World) -> Result; - type Item; - fn pull(&self, session: Entity, world: &mut World) -> Result; - fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult; fn gate_action( @@ -46,15 +43,19 @@ pub trait Buffered: Clone { fn as_input(&self) -> SmallVec<[Entity; 8]>; + fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult; +} + +pub trait Joined: Buffered { + type Item; + fn pull(&self, session: Entity, world: &mut World) -> Result; +} + +pub trait Accessed: Buffered { type Key: Clone; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult; - fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key; - - fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult; - fn deep_clone_key(key: &Self::Key) -> Self::Key; - fn is_key_in_use(key: &Self::Key) -> bool; } @@ -70,14 +71,6 @@ impl Buffered for Buffer { .buffered_count::(session) } - type Item = T; - fn pull(&self, session: Entity, world: &mut World) -> Result { - world - .get_entity_mut(self.source) - .or_broken()? - .pull_from_buffer::(session) - } - fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { let mut targets = world .get_mut::(self.source) @@ -112,6 +105,26 @@ impl Buffered for Buffer { SmallVec::from_iter([self.source]) } + fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { + world + .get_mut::>(self.source) + .or_broken()? + .ensure_session(session); + Ok(()) + } +} + +impl Joined for Buffer { + type Item = T; + fn pull(&self, session: Entity, world: &mut World) -> Result { + world + .get_entity_mut(self.source) + .or_broken()? + .pull_from_buffer::(session) + } +} + +impl Accessed for Buffer { type Key = BufferKey; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { let mut accessors = world.get_mut::(self.source).or_broken()?; @@ -126,14 +139,6 @@ impl Buffered for Buffer { builder.build(self.source) } - fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { - world - .get_mut::>(self.source) - .or_broken()? - .ensure_session(session); - Ok(()) - } - fn deep_clone_key(key: &Self::Key) -> Self::Key { key.deep_clone() } @@ -155,15 +160,6 @@ impl Buffered for CloneFromBuffer { .buffered_count::(session) } - type Item = T; - fn pull(&self, session: Entity, world: &mut World) -> Result { - world - .get_entity(self.source) - .or_broken()? - .try_clone_from_buffer(session) - .and_then(|r| r.or_broken()) - } - fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { let mut targets = world .get_mut::(self.source) @@ -197,6 +193,27 @@ impl Buffered for CloneFromBuffer { SmallVec::from_iter([self.source]) } + fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { + world + .get_mut::>(self.source) + .or_broken()? + .ensure_session(session); + Ok(()) + } +} + +impl Joined for CloneFromBuffer { + type Item = T; + fn pull(&self, session: Entity, world: &mut World) -> Result { + world + .get_entity(self.source) + .or_broken()? + .try_clone_from_buffer(session) + .and_then(|r| r.or_broken()) + } +} + +impl Accessed for CloneFromBuffer { type Key = BufferKey; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { let mut accessors = world.get_mut::(self.source).or_broken()?; @@ -211,14 +228,6 @@ impl Buffered for CloneFromBuffer { builder.build(self.source) } - fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { - world - .get_mut::>(self.source) - .or_broken()? - .ensure_session(session); - Ok(()) - } - fn deep_clone_key(key: &Self::Key) -> Self::Key { key.deep_clone() } @@ -253,18 +262,6 @@ macro_rules! impl_buffered_for_tuple { ].iter().copied().min().unwrap_or(0)) } - type Item = ($($T::Item),*); - fn pull( - &self, - session: Entity, - world: &mut World, - ) -> Result { - let ($($T,)*) = self; - Ok(($( - $T.pull(session, world)?, - )*)) - } - fn add_listener( &self, listener: Entity, @@ -300,6 +297,38 @@ macro_rules! impl_buffered_for_tuple { inputs } + fn ensure_active_session( + &self, + session: Entity, + world: &mut World, + ) -> OperationResult { + let ($($T,)*) = self; + $( + $T.ensure_active_session(session, world)?; + )* + Ok(()) + } + } + + #[allow(non_snake_case)] + impl<$($T: Joined),*> Joined for ($($T,)*) + { + type Item = ($($T::Item),*); + fn pull( + &self, + session: Entity, + world: &mut World, + ) -> Result { + let ($($T,)*) = self; + Ok(($( + $T.pull(session, world)?, + )*)) + } + } + + #[allow(non_snake_case)] + impl<$($T: Accessed),*> Accessed for ($($T,)*) + { type Key = ($($T::Key), *); fn add_accessor( &self, @@ -323,18 +352,6 @@ macro_rules! impl_buffered_for_tuple { )*) } - fn ensure_active_session( - &self, - session: Entity, - world: &mut World, - ) -> OperationResult { - let ($($T,)*) = self; - $( - $T.ensure_active_session(session, world)?; - )* - Ok(()) - } - fn deep_clone_key(key: &Self::Key) -> Self::Key { let ($($K,)*) = key; ($( @@ -375,15 +392,6 @@ impl Buffered for [T; N] { Ok(min_count.unwrap_or(0)) } - // TODO(@mxgrey) We may be able to use [T::Item; N] here instead of SmallVec - // when try_map is stabilized: https://github.com/rust-lang/rust/issues/79711 - type Item = SmallVec<[T::Item; N]>; - fn pull(&self, session: Entity, world: &mut World) -> Result { - self.iter() - .map(|buffer| buffer.pull(session, world)) - .collect() - } - fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { for buffer in self { buffer.add_listener(listener, world)?; @@ -408,6 +416,27 @@ impl Buffered for [T; N] { self.iter().flat_map(|buffer| buffer.as_input()).collect() } + fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { + for buffer in self { + buffer.ensure_active_session(session, world)?; + } + + Ok(()) + } +} + +impl Joined for [T; N] { + // TODO(@mxgrey) We may be able to use [T::Item; N] here instead of SmallVec + // when try_map is stabilized: https://github.com/rust-lang/rust/issues/79711 + type Item = SmallVec<[T::Item; N]>; + fn pull(&self, session: Entity, world: &mut World) -> Result { + self.iter() + .map(|buffer| buffer.pull(session, world)) + .collect() + } +} + +impl Accessed for [T; N] { type Key = SmallVec<[T::Key; N]>; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { for buffer in self { @@ -424,14 +453,6 @@ impl Buffered for [T; N] { keys } - fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { - for buffer in self { - buffer.ensure_active_session(session, world)?; - } - - Ok(()) - } - fn deep_clone_key(key: &Self::Key) -> Self::Key { let mut keys = SmallVec::new(); for k in key { @@ -470,13 +491,6 @@ impl Buffered for SmallVec<[T; N]> { Ok(min_count.unwrap_or(0)) } - type Item = SmallVec<[T::Item; N]>; - fn pull(&self, session: Entity, world: &mut World) -> Result { - self.iter() - .map(|buffer| buffer.pull(session, world)) - .collect() - } - fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { for buffer in self { buffer.add_listener(listener, world)?; @@ -501,6 +515,25 @@ impl Buffered for SmallVec<[T; N]> { self.iter().flat_map(|buffer| buffer.as_input()).collect() } + fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { + for buffer in self { + buffer.ensure_active_session(session, world)?; + } + + Ok(()) + } +} + +impl Joined for SmallVec<[T; N]> { + type Item = SmallVec<[T::Item; N]>; + fn pull(&self, session: Entity, world: &mut World) -> Result { + self.iter() + .map(|buffer| buffer.pull(session, world)) + .collect() + } +} + +impl Accessed for SmallVec<[T; N]> { type Key = SmallVec<[T::Key; N]>; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { for buffer in self { @@ -517,14 +550,6 @@ impl Buffered for SmallVec<[T; N]> { keys } - fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { - for buffer in self { - buffer.ensure_active_session(session, world)?; - } - - Ok(()) - } - fn deep_clone_key(key: &Self::Key) -> Self::Key { let mut keys = SmallVec::new(); for k in key { diff --git a/src/builder.rs b/src/builder.rs index 0661023a..eae97efa 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -23,7 +23,7 @@ use std::future::Future; use smallvec::SmallVec; use crate::{ - AddOperation, AsMap, BeginCleanupWorkflow, Buffer, BufferItem, BufferKeys, BufferSettings, + AddOperation, AsMap, BeginCleanupWorkflow, Buffer, JoinedItem, BufferKeys, BufferSettings, Bufferable, Buffered, Chain, Collect, ForkClone, ForkCloneOutput, ForkTargetStorage, Gate, GateRequest, Injection, InputSlot, IntoAsyncMap, IntoBlockingMap, Node, OperateBuffer, OperateBufferAccess, OperateDynamicGate, OperateScope, OperateSplit, OperateStaticGate, Output, @@ -231,10 +231,10 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { } /// Alternative way of calling [`Bufferable::join`] - pub fn join<'b, B: Bufferable>(&'b mut self, buffers: B) -> Chain<'w, 's, 'a, 'b, BufferItem> + pub fn join<'b, B: Bufferable>(&'b mut self, buffers: B) -> Chain<'w, 's, 'a, 'b, JoinedItem> where B::BufferType: 'static + Send + Sync, - BufferItem: 'static + Send + Sync, + JoinedItem: 'static + Send + Sync, { buffers.join(self) } diff --git a/src/operation/cleanup.rs b/src/operation/cleanup.rs index f4231825..6c11c72b 100644 --- a/src/operation/cleanup.rs +++ b/src/operation/cleanup.rs @@ -16,7 +16,7 @@ */ use crate::{ - BufferAccessStorage, Buffered, ManageDisposal, ManageInput, MiscellaneousFailure, + Accessed, BufferAccessStorage, ManageDisposal, ManageInput, MiscellaneousFailure, OperationError, OperationResult, OperationRoster, OrBroken, ScopeStorage, UnhandledErrors, }; @@ -98,7 +98,7 @@ impl<'a> OperationCleanup<'a> { pub fn cleanup_buffer_access(&mut self) -> OperationResult where - B: Buffered + 'static + Send + Sync, + B: Accessed + 'static + Send + Sync, B::Key: 'static + Send + Sync, { let scope = self diff --git a/src/operation/join.rs b/src/operation/join.rs index 91d5f3e9..3bd8bafd 100644 --- a/src/operation/join.rs +++ b/src/operation/join.rs @@ -18,7 +18,7 @@ use bevy_ecs::prelude::{Component, Entity}; use crate::{ - Buffered, FunnelInputStorage, Input, InputBundle, ManageInput, Operation, OperationCleanup, + FunnelInputStorage, Input, InputBundle, Joined, ManageInput, Operation, OperationCleanup, OperationError, OperationReachability, OperationRequest, OperationResult, OperationSetup, OrBroken, ReachabilityResult, SingleInputStorage, SingleTargetStorage, }; @@ -37,7 +37,7 @@ impl Join { #[derive(Component)] struct BufferStorage(Buffers); -impl Operation for Join +impl Operation for Join where Buffers::Item: 'static + Send + Sync, { diff --git a/src/operation/listen.rs b/src/operation/listen.rs index 3a15ca82..43f7a22e 100644 --- a/src/operation/listen.rs +++ b/src/operation/listen.rs @@ -18,7 +18,7 @@ use bevy_ecs::prelude::Entity; use crate::{ - buffer_key_usage, get_access_keys, BufferAccessStorage, BufferKeyUsage, Buffered, + Accessed, buffer_key_usage, get_access_keys, BufferAccessStorage, BufferKeyUsage, FunnelInputStorage, Input, InputBundle, ManageInput, Operation, OperationCleanup, OperationReachability, OperationRequest, OperationResult, OperationSetup, OrBroken, ReachabilityResult, SingleInputStorage, SingleTargetStorage, @@ -37,7 +37,7 @@ impl Listen { impl Operation for Listen where - B: Buffered + 'static + Send + Sync, + B: Accessed + 'static + Send + Sync, B::Key: 'static + Send + Sync, { fn setup(self, OperationSetup { source, world }: OperationSetup) -> OperationResult { diff --git a/src/operation/operate_buffer_access.rs b/src/operation/operate_buffer_access.rs index b2936f01..e0b44c33 100644 --- a/src/operation/operate_buffer_access.rs +++ b/src/operation/operate_buffer_access.rs @@ -25,7 +25,7 @@ use std::{ use smallvec::SmallVec; use crate::{ - BufferKeyBuilder, Buffered, ChannelQueue, Input, InputBundle, ManageInput, Operation, + Accessed, BufferKeyBuilder, ChannelQueue, Input, InputBundle, ManageInput, Operation, OperationCleanup, OperationError, OperationReachability, OperationRequest, OperationResult, OperationSetup, OrBroken, ReachabilityResult, ScopeStorage, SingleInputStorage, SingleTargetStorage, @@ -34,7 +34,7 @@ use crate::{ pub(crate) struct OperateBufferAccess where T: 'static + Send + Sync, - B: Buffered, + B: Accessed, { buffers: B, target: Entity, @@ -44,7 +44,7 @@ where impl OperateBufferAccess where T: 'static + Send + Sync, - B: Buffered, + B: Accessed, { pub(crate) fn new(buffers: B, target: Entity) -> Self { Self { @@ -59,12 +59,12 @@ where pub struct BufferKeyUsage(pub(crate) fn(Entity, Entity, &World) -> ReachabilityResult); #[derive(Component)] -pub(crate) struct BufferAccessStorage { +pub(crate) struct BufferAccessStorage { pub(crate) buffers: B, pub(crate) keys: HashMap, } -impl BufferAccessStorage { +impl BufferAccessStorage { pub(crate) fn new(buffers: B) -> Self { Self { buffers, @@ -76,7 +76,7 @@ impl BufferAccessStorage { impl Operation for OperateBufferAccess where T: 'static + Send + Sync, - B: Buffered + 'static + Send + Sync, + B: Accessed + 'static + Send + Sync, B::Key: 'static + Send + Sync, { fn setup(self, OperationSetup { source, world }: OperationSetup) -> OperationResult { @@ -138,7 +138,7 @@ pub(crate) fn get_access_keys( world: &mut World, ) -> Result where - B: Buffered + 'static + Send + Sync, + B: Accessed + 'static + Send + Sync, B::Key: 'static + Send + Sync, { let scope = world.get::(source).or_broken()?.get(); @@ -180,7 +180,7 @@ pub(crate) fn buffer_key_usage( world: &World, ) -> ReachabilityResult where - B: Buffered + 'static + Send + Sync, + B: Accessed + 'static + Send + Sync, B::Key: 'static + Send + Sync, { let key = world diff --git a/src/operation/scope.rs b/src/operation/scope.rs index fe18821f..9e43b8e2 100644 --- a/src/operation/scope.rs +++ b/src/operation/scope.rs @@ -16,7 +16,7 @@ */ use crate::{ - check_reachability, execute_operation, is_downstream_of, AddOperation, Blocker, + check_reachability, execute_operation, is_downstream_of, Accessed, AddOperation, Blocker, BufferKeyBuilder, Buffered, Cancel, Cancellable, Cancellation, Cleanup, CleanupContents, ClearBufferFn, CollectMarker, DisposalListener, DisposalUpdate, FinalizeCleanup, FinalizeCleanupRequest, Input, InputBundle, InspectDisposals, ManageCancellation, ManageInput, @@ -1125,7 +1125,7 @@ impl BeginCleanupWorkflow { impl Operation for BeginCleanupWorkflow where - B: Buffered + 'static + Send + Sync, + B: Accessed + 'static + Send + Sync, B::Key: 'static + Send + Sync, { fn setup(self, OperationSetup { source, world }: OperationSetup) -> OperationResult { From 763244b89038cf40f4e5b88711d2fc2982da1f1a Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Sat, 25 Jan 2025 01:04:49 +0800 Subject: [PATCH 03/37] Figuring out how to dynamically access buffers Signed-off-by: Michael X. Grey --- src/buffer.rs | 81 ++++++++-- src/buffer/any_buffer.rs | 71 ++++++++ src/buffer/buffer_map.rs | 153 +++++++++++++++++- src/buffer/buffer_storage.rs | 278 +++++++++++++++++++++++++------- src/buffer/buffered.rs | 104 +++++++----- src/buffer/manage_buffer.rs | 33 +++- src/operation/operate_buffer.rs | 4 +- src/operation/scope.rs | 2 +- 8 files changed, 611 insertions(+), 115 deletions(-) create mode 100644 src/buffer/any_buffer.rs diff --git a/src/buffer.rs b/src/buffer.rs index 2285d074..b28f7046 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -28,6 +28,9 @@ use crate::{ Builder, Chain, Gate, GateState, InputSlot, NotifyBufferUpdate, OnNewBufferValue, UnusedTarget, }; +mod any_buffer; +pub use any_buffer::*; + mod buffer_access_lifecycle; pub(crate) use buffer_access_lifecycle::*; @@ -92,6 +95,15 @@ impl Buffer { } } +impl Clone for Buffer { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Buffer {} + +#[derive(Clone, Copy)] pub struct CloneFromBuffer { pub(crate) scope: Entity, pub(crate) source: Entity, @@ -107,6 +119,7 @@ pub struct DynBuffer { } impl DynBuffer { + /// Downcast this into a concrete [`Buffer`] type. pub fn into_buffer(&self) -> Option> { if TypeId::of::() == self.type_id { Some(Buffer { @@ -193,22 +206,6 @@ impl Default for RetentionPolicy { } } -impl Clone for Buffer { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Buffer {} - -impl Clone for CloneFromBuffer { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for CloneFromBuffer {} - /// This key can unlock access to the contents of a buffer by passing it into /// [`BufferAccess`] or [`BufferAccessMut`]. /// @@ -269,6 +266,58 @@ impl BufferKey { } } +#[derive(Clone)] +pub struct DynBufferKey { + buffer: Entity, + session: Entity, + accessor: Entity, + lifecycle: Option>, + type_id: TypeId, +} + +impl std::fmt::Debug for DynBufferKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f + .debug_struct("DynBufferKey") + .field("buffer", &self.buffer) + .field("session", &self.session) + .field("accessor", &self.accessor) + .field("in_use", &self.lifecycle.as_ref().is_some_and(|l| l.is_in_use())) + .field("type_id", &self.type_id) + .finish() + } +} + +impl DynBufferKey { + /// Downcast this into a concrete [`BufferKey`] type. + pub fn into_buffer_key(&self) -> Option> { + if TypeId::of::() == self.type_id { + Some(BufferKey { + buffer: self.buffer, + session: self.session, + accessor: self.accessor, + lifecycle: self.lifecycle.clone(), + _ignore: Default::default(), + }) + } else { + None + } + } +} + +impl From> for DynBufferKey { + fn from(value: BufferKey) -> Self { + let type_id = TypeId::of::(); + DynBufferKey { + buffer: value.buffer, + session: value.session, + accessor: value.accessor, + lifecycle: value.lifecycle.clone(), + type_id, + } + } +} + /// This system parameter lets you get read-only access to a buffer that exists /// within a workflow. Use a [`BufferKey`] to unlock the access. /// diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs new file mode 100644 index 00000000..b4e36b92 --- /dev/null +++ b/src/buffer/any_buffer.rs @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2025 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +use std::any::Any; + +use bevy_ecs::{ + prelude::{Entity, Commands, Mut, World, Query}, + system::SystemState, +}; + +use crate::{ + AnyMessageRef, AnyMessageMut, BoxedMessage, BoxedMessageError, BufferAccessMut, + DynBufferViewer, DynBufferKey, DynBufferManagement, GateState, BufferStorage, +}; + +use thiserror::Error as ThisError; + +/// Similar to [`BufferMut`][crate::BufferMut], but this can be unlocked with a +/// [`DynBufferKey`], so it can work for any buffer regardless of the data type +/// inside. +pub struct AnyBufferMut<'w, 's, 'a> { + storage: Box, AnyMessageMut<'a>, BoxedMessage, BoxedMessageError> + 'a>, + gate: Mut<'a, GateState>, + buffer: Entity, + session: Entity, + accessor: Option, + commands: Commands<'w, 's>, + modified: bool, +} + +#[derive(ThisError, Debug, Clone)] +pub enum AnyBufferError { + #[error("The key was unable to identify a buffer")] + BufferMissing, +} + +pub(crate) fn access_any_buffer_mut( + key: DynBufferKey, + f: Box, + world: &mut World, +) -> Result<(), AnyBufferError> { + let mut state: SystemState> = SystemState::new(world); + let BufferAccessMut { mut query, commands } = state.get_mut(world); + let (storage, gate) = query.get_mut(key.buffer).map_err(|_| AnyBufferError::BufferMissing)?; + let buffer_mut = AnyBufferMut { + storage: Box::new(storage), + gate, + buffer: key.buffer, + session: key.session, + accessor: Some(key.accessor), + commands, + modified: false, + }; + + f(buffer_mut); + Ok(()) +} diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 039b6c10..a6fb60e6 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -16,17 +16,20 @@ */ use std::{ - collections::HashMap, + any::TypeId, borrow::Cow, + collections::HashMap, }; +use thiserror::Error as ThisError; + use smallvec::SmallVec; use bevy_ecs::prelude::{Entity, World}; use crate::{ DynBuffer, OperationError, OperationResult, OperationRoster, Buffered, Gate, - Joined, Accessed, + Joined, Accessed, BufferKeyBuilder, DynBufferKey, }; #[derive(Clone)] @@ -34,7 +37,32 @@ pub struct BufferMap { inner: HashMap, DynBuffer>, } +/// This error is used when the buffers provided for an input are not compatible +/// with the layout. +#[derive(ThisError, Debug, Clone)] +#[error("the incoming buffer map is incompatible with the layout")] +pub struct IncompatibleLayout { + /// Names of buffers that were missing from the incoming buffer map. + pub missing_buffers: Vec>, + /// Buffers whose expected type did not match the received type. + pub incompatible_buffers: Vec, +} + +/// Difference between the expected and received types of a named buffer. +#[derive(Debug, Clone)] +pub struct BufferIncompatibility { + /// Name of the expected buffer + pub name: Cow<'static, str>, + /// The type that was expected for this buffer + pub expected: TypeId, + /// The type that was received for this buffer + pub received: TypeId, + // TODO(@mxgrey): Replace TypeId with TypeInfo +} + pub trait BufferMapLayout: Sized { + fn is_compatible(buffers: &BufferMap) -> Result<(), IncompatibleLayout>; + fn buffered_count( buffers: &BufferMap, session: Entity, @@ -70,9 +98,27 @@ pub trait JoinedValue: BufferMapLayout { session: Entity, world: &World, ) -> Result; + + fn pull( + buffers: &BufferMap, + session: Entity, + world: &World, + ) -> Result; } -pub trait BufferKeyMap: BufferMapLayout { +/// Trait to describe a layout of buffer keys +pub trait BufferKeyMap: BufferMapLayout + Clone { + fn add_accessor( + buffers: &BufferMap, + accessor: Entity, + world: &mut World, + ) -> OperationResult; + + fn create_key(buffers: &BufferMap, builder: &BufferKeyBuilder) -> Self; + + fn deep_clone_key(&self) -> Self; + + fn is_key_in_use(&self) -> bool; } struct BufferedMap { @@ -124,6 +170,107 @@ impl Joined for BufferedMap { type Item = V; fn pull(&self, session: Entity, world: &mut World) -> Result { + V::pull(&self.map, session, world) + } +} + +impl Accessed for BufferedMap { + type Key = K; + + fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { + K::add_accessor(&self.map, accessor, world) + } + + fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key { + K::create_key(&self.map, builder) + } + + fn deep_clone_key(key: &Self::Key) -> Self::Key { + K::deep_clone_key(key) + } + + fn is_key_in_use(key: &Self::Key) -> bool { + K::is_key_in_use(key) + } +} + +/// Container to represent any layout of buffer keys. +#[derive(Clone, Debug)] +pub struct AnyBufferKeyMap { + pub keys: HashMap, DynBufferKey>, +} + +impl BufferMapLayout for AnyBufferKeyMap { + fn is_compatible(_: &BufferMap) -> Result<(), IncompatibleLayout> { + // AnyBufferKeyMap is always compatible with BufferMap + Ok(()) + } + + fn buffered_count( + buffers: &BufferMap, + session: Entity, + world: &World, + ) -> Result { + let mut min_count = None; + for buffer in buffers.inner.values() { + let count = buffer.buffered_count(session, world)?; + + min_count = if min_count.is_some_and(|m| m < count) { + min_count + } else { + Some(count) + }; + } + + Ok(min_count.unwrap_or(0)) + } + + fn add_listener( + buffers: &BufferMap, + listener: Entity, + world: &mut World, + ) -> OperationResult { + for buffer in buffers.inner.values() { + buffer.add_listener(listener, world)?; + } + + Ok(()) + } + + fn gate_action( + buffers: &BufferMap, + session: Entity, + action: Gate, + world: &mut World, + roster: &mut OperationRoster, + ) -> OperationResult { + for buffer in buffers.inner.values() { + buffer.gate_action(session, action, world, roster)?; + } + + Ok(()) + } + + fn as_input(buffers: &BufferMap) -> SmallVec<[Entity; 8]> { + let mut input = SmallVec::new(); + input.reserve(buffers.inner.len()); + + for buffer in buffers.inner.values() { + input.extend(buffer.as_input()); + } + + input + } + + fn ensure_active_session( + buffers: &BufferMap, + session: Entity, + world: &mut World, + ) -> OperationResult { + for buffer in buffers.inner.values() { + buffer.ensure_active_session(session, world)?; + } + Ok(()) } } diff --git a/src/buffer/buffer_storage.rs b/src/buffer/buffer_storage.rs index c7465415..28a0f670 100644 --- a/src/buffer/buffer_storage.rs +++ b/src/buffer/buffer_storage.rs @@ -15,19 +15,56 @@ * */ -use bevy_ecs::prelude::{Component, Entity}; +use bevy_ecs::prelude::{Component, Entity, EntityRef, EntityWorldMut, World, Mut}; use smallvec::{Drain, SmallVec}; use std::collections::HashMap; use std::{ + any::{Any, TypeId}, iter::Rev, ops::RangeBounds, slice::{Iter, IterMut}, }; -use crate::{BufferSettings, RetentionPolicy}; +use thiserror::Error as ThisError; + +use crate::{ + AnyBufferMut, BufferSettings, DynBufferKey, InspectBuffer, ManageBuffer, + RetentionPolicy, OperationError, OperationResult, +}; + +pub(crate) trait BufferInspection { + fn count(&self, session: Entity) -> usize; + fn active_sessions(&self) -> SmallVec<[Entity; 16]>; +} + +pub(crate) trait DynBufferViewer<'a, R> { + fn dyn_oldest(&'a self, session: Entity) -> Option; + fn dyn_newest(&'a self, session: Entity) -> Option; + fn dyn_get(&'a self, session: Entity, index: usize) -> Option; +} + +pub(crate) trait DynBufferMutator<'a, R, M>: DynBufferViewer<'a, R> { + fn dyn_oldest_mut(&'a mut self, session: Entity) -> Option; + fn dyn_newest_mut(&'a mut self, session: Entity) -> Option; + fn dyn_get_mut(&'a mut self, session: Entity, index: usize) -> Option; +} + +pub(crate) trait DynBufferManagement<'a, R, M, T, E>: DynBufferMutator<'a, R, M> { + fn dyn_force_push(&mut self, session: Entity, value: T) -> Result, E>; + fn dyn_push(&mut self, session: Entity, value: T) -> Result, E>; + fn dyn_push_as_oldest(&mut self, session: Entity, value: T) -> Result, E>; + fn dyn_pull(&mut self, session: Entity) -> Option; + fn dyn_pull_newest(&mut self, session: Entity) -> Option; + fn dyn_consume(&mut self, session: Entity) -> SmallVec<[T; 16]>; +} + +pub(crate) trait BufferSessionManagement { + fn clear_session(&mut self, session: Entity); + fn ensure_session(&mut self, session: Entity); +} #[derive(Component)] pub(crate) struct BufferStorage { @@ -44,23 +81,7 @@ pub(crate) struct BufferStorage { } impl BufferStorage { - pub(crate) fn force_push(&mut self, session: Entity, value: T) -> Option { - Self::impl_push( - self.reverse_queues.entry(session).or_default(), - self.settings.retention(), - value, - ) - } - - pub(crate) fn push(&mut self, session: Entity, value: T) -> Option { - let Some(reverse_queue) = self.reverse_queues.get_mut(&session) else { - return Some(value); - }; - - Self::impl_push(reverse_queue, self.settings.retention(), value) - } - - pub(crate) fn impl_push( + fn impl_push( reverse_queue: &mut SmallVec<[T; 16]>, retention: RetentionPolicy, value: T, @@ -92,6 +113,22 @@ impl BufferStorage { replaced } + pub(crate) fn force_push(&mut self, session: Entity, value: T) -> Option { + Self::impl_push( + self.reverse_queues.entry(session).or_default(), + self.settings.retention(), + value, + ) + } + + pub(crate) fn push(&mut self, session: Entity, value: T) -> Option { + let Some(reverse_queue) = self.reverse_queues.get_mut(&session) else { + return Some(value); + }; + + Self::impl_push(reverse_queue, self.settings.retention(), value) + } + pub(crate) fn push_as_oldest(&mut self, session: Entity, value: T) -> Option { let Some(reverse_queue) = self.reverse_queues.get_mut(&session) else { return Some(value); @@ -147,20 +184,8 @@ impl BufferStorage { self.reverse_queues.remove(&session); } - pub(crate) fn count(&self, session: Entity) -> usize { - self.reverse_queues - .get(&session) - .map(|q| q.len()) - .unwrap_or(0) - } - - pub(crate) fn iter(&self, session: Entity) -> IterBufferView<'_, T> - where - T: 'static + Send + Sync, - { - IterBufferView { - iter: self.reverse_queues.get(&session).map(|q| q.iter().rev()), - } + pub(crate) fn ensure_session(&mut self, session: Entity) { + self.reverse_queues.entry(session).or_default(); } pub(crate) fn iter_mut(&mut self, session: Entity) -> IterBufferMut<'_, T> @@ -175,6 +200,28 @@ impl BufferStorage { } } + pub(crate) fn oldest_mut(&mut self, session: Entity) -> Option<&mut T> { + self.reverse_queues + .get_mut(&session) + .and_then(|q| q.last_mut()) + } + + pub(crate) fn newest_mut(&mut self, session: Entity) -> Option<&mut T> { + self.reverse_queues + .get_mut(&session) + .and_then(|q| q.first_mut()) + } + + pub(crate) fn get_mut(&mut self, session: Entity, index: usize) -> Option<&mut T> { + let reverse_queue = self.reverse_queues.get_mut(&session)?; + let len = reverse_queue.len(); + if len >= index { + return None; + } + + reverse_queue.get_mut(len - index - 1) + } + pub(crate) fn drain(&mut self, session: Entity, range: R) -> DrainBuffer<'_, T> where T: 'static + Send + Sync, @@ -188,6 +235,15 @@ impl BufferStorage { } } + pub(crate) fn iter(&self, session: Entity) -> IterBufferView<'_, T> + where + T: 'static + Send + Sync, + { + IterBufferView { + iter: self.reverse_queues.get(&session).map(|q| q.iter().rev()), + } + } + pub(crate) fn oldest(&self, session: Entity) -> Option<&T> { self.reverse_queues.get(&session).and_then(|q| q.last()) } @@ -206,42 +262,123 @@ impl BufferStorage { reverse_queue.get(len - index - 1) } - pub(crate) fn oldest_mut(&mut self, session: Entity) -> Option<&mut T> { - self.reverse_queues - .get_mut(&session) - .and_then(|q| q.last_mut()) + pub(crate) fn new(settings: BufferSettings) -> Self { + Self { + settings, + reverse_queues: Default::default(), + } } +} - pub(crate) fn newest_mut(&mut self, session: Entity) -> Option<&mut T> { +impl BufferInspection for BufferStorage { + fn count(&self, session: Entity) -> usize { self.reverse_queues - .get_mut(&session) - .and_then(|q| q.first_mut()) + .get(&session) + .map(|q| q.len()) + .unwrap_or(0) } - pub(crate) fn get_mut(&mut self, session: Entity, index: usize) -> Option<&mut T> { - let reverse_queue = self.reverse_queues.get_mut(&session)?; - let len = reverse_queue.len(); - if len >= index { - return None; - } + fn active_sessions(&self) -> SmallVec<[Entity; 16]> { + self.reverse_queues.keys().copied().collect() + } +} - reverse_queue.get_mut(len - index - 1) +pub type AnyMessageRef<'a> = &'a (dyn Any + 'static + Send + Sync); + +impl<'a, T: 'static + Send + Sync + Any> DynBufferViewer<'a, AnyMessageRef<'a>> for Mut<'a, BufferStorage> { + fn dyn_oldest(&'a self, session: Entity) -> Option> { + self.oldest(session).map(to_any_ref) } - pub(crate) fn active_sessions(&self) -> SmallVec<[Entity; 16]> { - self.reverse_queues.keys().copied().collect() + fn dyn_newest(&'a self, session: Entity) -> Option> { + self.newest(session).map(to_any_ref) } - pub(crate) fn ensure_session(&mut self, session: Entity) { - self.reverse_queues.entry(session).or_default(); + fn dyn_get(&'a self, session: Entity, index: usize) -> Option> { + self.get(session, index).map(to_any_ref) } +} - pub(crate) fn new(settings: BufferSettings) -> Self { - Self { - settings, - reverse_queues: Default::default(), - } +pub type AnyMessageMut<'a> = &'a mut (dyn Any + 'static + Send + Sync); + +impl<'a, T: 'static + Send + Sync + Any> DynBufferMutator<'a, AnyMessageRef<'a>, AnyMessageMut<'a>> for Mut<'a, BufferStorage> { + fn dyn_oldest_mut(&'a mut self, session: Entity) -> Option> { + self.oldest_mut(session).map(to_any_mut) + } + + fn dyn_newest_mut(&'a mut self, session: Entity) -> Option> { + self.newest_mut(session).map(to_any_mut) } + + fn dyn_get_mut(&'a mut self, session: Entity, index: usize) -> Option> { + self.get_mut(session, index).map(to_any_mut) + } +} + +pub type BoxedMessage = Box; + +#[derive(ThisError, Debug)] +#[error("failed to convert a message")] +pub struct BoxedMessageError { + /// The original value provided + pub value: BoxedMessage, + /// The type expected by the buffer + pub type_id: TypeId, +} + +impl<'a, T: 'static + Send + Sync + Any> DynBufferManagement<'a, AnyMessageRef<'a>, AnyMessageMut<'a>, BoxedMessage, BoxedMessageError> for Mut<'a, BufferStorage> { + fn dyn_force_push(&mut self, session: Entity, value: BoxedMessage) -> Result, BoxedMessageError> { + let value = from_boxed_message::(value)?; + Ok(self.force_push(session, value).map(to_boxed_message)) + } + + fn dyn_push(&mut self, session: Entity, value: BoxedMessage) -> Result, BoxedMessageError> { + let value = from_boxed_message::(value)?; + Ok(self.push(session, value).map(to_boxed_message)) + } + + fn dyn_push_as_oldest(&mut self, session: Entity, value: BoxedMessage) -> Result, BoxedMessageError> { + let value = from_boxed_message::(value)?; + Ok(self.push_as_oldest(session, value).map(to_boxed_message)) + } + + fn dyn_pull(&mut self, session: Entity) -> Option { + self.pull(session).map(to_boxed_message) + } + + fn dyn_pull_newest(&mut self, session: Entity) -> Option { + self.pull_newest(session).map(to_boxed_message) + } + + fn dyn_consume(&mut self, session: Entity) -> SmallVec<[BoxedMessage; 16]> { + self.consume(session).into_iter().map(to_boxed_message).collect() + } +} + +fn to_any_ref<'a, T: 'static + Send + Sync + Any>(x: &'a T) -> AnyMessageRef<'a> { + x +} + +fn to_any_mut<'a, T: 'static + Send + Sync + Any>(x: &'a mut T) -> AnyMessageMut<'a> { + x +} + +fn to_boxed_message(x: T) -> BoxedMessage { + Box::new(x) +} + +fn from_boxed_message(value: BoxedMessage) -> Result +where + T: 'static, +{ + let value = value.downcast::().map_err(|value| { + BoxedMessageError { + value, + type_id: TypeId::of::(), + } + })?; + + Ok(*value) } pub struct IterBufferView<'b, T> @@ -309,3 +446,32 @@ where } } } + +/// A component that lets us inspect buffer properties without knowing the +/// message type of the buffer. +#[derive(Component, Clone, Copy)] +pub(crate) struct DynBufferStorage { + access_any_buffer_mut: fn(DynBufferKey, Box, &mut World), +} + +impl DynBufferStorage { + pub(crate) fn new() -> Self { + Self { + access_any_buffer_mut: crate::access_any_buffer_mut::, + } + } +} + +fn buffered_count( + entity: &EntityRef, + session: Entity, +) -> Result { + entity.buffered_count::(session) +} + +fn ensure_session( + entity_mut: &mut EntityWorldMut, + session: Entity, +) -> OperationResult { + entity_mut.ensure_session::(session) +} diff --git a/src/buffer/buffered.rs b/src/buffer/buffered.rs index a6552552..74b4072e 100644 --- a/src/buffer/buffered.rs +++ b/src/buffer/buffered.rs @@ -23,7 +23,7 @@ use smallvec::SmallVec; use crate::{ Buffer, BufferAccessors, BufferKey, BufferKeyBuilder, BufferStorage, CloneFromBuffer, ForkTargetStorage, Gate, GateState, InspectBuffer, ManageBuffer, OperationError, - OperationResult, OperationRoster, OrBroken, SingleInputStorage, + OperationResult, OperationRoster, OrBroken, SingleInputStorage, DynBuffer, }; pub trait Buffered: Clone { @@ -72,23 +72,7 @@ impl Buffered for Buffer { } fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { - let mut targets = world - .get_mut::(self.source) - .or_broken()?; - if !targets.0.contains(&listener) { - targets.0.push(listener); - } - - if let Some(mut input_storage) = world.get_mut::(listener) { - input_storage.add(self.source); - } else { - world - .get_entity_mut(listener) - .or_broken()? - .insert(SingleInputStorage::new(self.source)); - } - - Ok(()) + add_listener_to_source(self.source, listener, world) } fn gate_action( @@ -161,22 +145,7 @@ impl Buffered for CloneFromBuffer { } fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { - let mut targets = world - .get_mut::(self.source) - .or_broken()?; - if !targets.0.contains(&listener) { - targets.0.push(listener); - } - - if let Some(mut input_storage) = world.get_mut::(listener) { - input_storage.add(self.source); - } else { - world - .get_entity_mut(listener) - .or_broken()? - .insert(SingleInputStorage::new(self.source)); - } - Ok(()) + add_listener_to_source(self.source, listener, world) } fn gate_action( @@ -195,10 +164,9 @@ impl Buffered for CloneFromBuffer { fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { world - .get_mut::>(self.source) + .get_entity_mut(self.source) .or_broken()? - .ensure_session(session); - Ok(()) + .ensure_session::(session) } } @@ -237,6 +205,44 @@ impl Accessed for CloneFromBuffer { } } +impl Buffered for DynBuffer { + fn verify_scope(&self, scope: Entity) { + assert_eq!(scope, self.scope); + } + + fn buffered_count(&self, session: Entity, world: &World) -> Result { + world + .get_entity(self.source) + .or_broken()? + .dyn_buffered_count(session) + } + + fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { + add_listener_to_source(self.source, listener, world) + } + + fn gate_action( + &self, + session: Entity, + action: Gate, + world: &mut World, + roster: &mut OperationRoster, + ) -> OperationResult { + GateState::apply(self.source, session, action, world, roster) + } + + fn as_input(&self) -> SmallVec<[Entity; 8]> { + SmallVec::from_iter([self.source]) + } + + fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { + world + .get_entity_mut(self.source) + .or_broken()? + .dyn_ensure_session(session) + } +} + macro_rules! impl_buffered_for_tuple { ($(($T:ident, $K:ident)),*) => { #[allow(non_snake_case)] @@ -568,3 +574,27 @@ impl Accessed for SmallVec<[T; N]> { false } } + +fn add_listener_to_source( + source: Entity, + listener: Entity, + world: &mut World, +) -> OperationResult { + let mut targets = world + .get_mut::(source) + .or_broken()?; + if !targets.0.contains(&listener) { + targets.0.push(listener); + } + + if let Some(mut input_storage) = world.get_mut::(listener) { + input_storage.add(source); + } else { + world + .get_entity_mut(listener) + .or_broken()? + .insert(SingleInputStorage::new(source)); + } + + Ok(()) +} diff --git a/src/buffer/manage_buffer.rs b/src/buffer/manage_buffer.rs index f0a6705a..07768c0f 100644 --- a/src/buffer/manage_buffer.rs +++ b/src/buffer/manage_buffer.rs @@ -22,7 +22,10 @@ use bevy_ecs::{ use smallvec::SmallVec; -use crate::{BufferStorage, OperationError, OperationResult, OrBroken}; +use crate::{ + BufferStorage, DynBufferStorage, OperationError, OperationResult, OrBroken, + BufferInspection, +}; pub trait InspectBuffer { fn buffered_count( @@ -30,6 +33,8 @@ pub trait InspectBuffer { session: Entity, ) -> Result; + fn dyn_buffered_count(&self, session: Entity) -> Result; + fn try_clone_from_buffer( &self, session: Entity, @@ -49,6 +54,11 @@ impl<'w> InspectBuffer for EntityRef<'w> { Ok(buffer.count(session)) } + fn dyn_buffered_count(&self, session: Entity) -> Result { + let count = self.get::().or_broken()?.count; + count(self, session) + } + fn try_clone_from_buffer( &self, session: Entity, @@ -89,6 +99,10 @@ pub trait ManageBuffer { ) -> Result, OperationError>; fn clear_buffer(&mut self, session: Entity) -> OperationResult; + + fn ensure_session(&mut self, session: Entity) -> OperationResult; + + fn dyn_ensure_session(&mut self, session: Entity) -> OperationResult; } impl<'w> ManageBuffer for EntityWorldMut<'w> { @@ -114,4 +128,21 @@ impl<'w> ManageBuffer for EntityWorldMut<'w> { .clear_session(session); Ok(()) } + + fn ensure_session(&mut self, session: Entity) -> OperationResult { + self + .get_mut::>() + .or_broken()? + .ensure_session(session); + Ok(()) + } + + fn dyn_ensure_session(&mut self, session: Entity) -> OperationResult { + let ensure_session = self + .get_mut::() + .or_broken()? + .ensure_session; + + ensure_session(self, session) + } } diff --git a/src/operation/operate_buffer.rs b/src/operation/operate_buffer.rs index 95d1d467..6fc50d9f 100644 --- a/src/operation/operate_buffer.rs +++ b/src/operation/operate_buffer.rs @@ -29,7 +29,7 @@ use backtrace::Backtrace; use smallvec::SmallVec; use crate::{ - Broken, BufferAccessors, BufferSettings, BufferStorage, DeferredRoster, ForkTargetStorage, + Broken, BufferAccessors, BufferSettings, BufferStorage, DynBufferStorage, DeferredRoster, ForkTargetStorage, Gate, GateActionStorage, Input, InputBundle, InspectBuffer, ManageBuffer, ManageInput, MiscellaneousFailure, Operation, OperationCleanup, OperationError, OperationReachability, OperationRequest, OperationResult, OperationRoster, OperationSetup, OrBroken, @@ -39,12 +39,14 @@ use crate::{ #[derive(Bundle)] pub(crate) struct OperateBuffer { storage: BufferStorage, + inspector: DynBufferStorage, } impl OperateBuffer { pub(crate) fn new(settings: BufferSettings) -> Self { Self { storage: BufferStorage::new(settings), + inspector: DynBufferStorage::new::(), } } } diff --git a/src/operation/scope.rs b/src/operation/scope.rs index 9e43b8e2..d5ac0d92 100644 --- a/src/operation/scope.rs +++ b/src/operation/scope.rs @@ -17,7 +17,7 @@ use crate::{ check_reachability, execute_operation, is_downstream_of, Accessed, AddOperation, Blocker, - BufferKeyBuilder, Buffered, Cancel, Cancellable, Cancellation, Cleanup, CleanupContents, + BufferKeyBuilder, Cancel, Cancellable, Cancellation, Cleanup, CleanupContents, ClearBufferFn, CollectMarker, DisposalListener, DisposalUpdate, FinalizeCleanup, FinalizeCleanupRequest, Input, InputBundle, InspectDisposals, ManageCancellation, ManageInput, Operation, OperationCancel, OperationCleanup, OperationError, OperationReachability, From 96953c55155e0fb9d09697829398f766499422ac Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 27 Jan 2025 09:11:05 +0000 Subject: [PATCH 04/37] Prototype of AnyBufferMut Signed-off-by: Michael X. Grey --- src/buffer.rs | 10 ++-- src/buffer/any_buffer.rs | 95 +++++++++++++++++++++++++-------- src/buffer/buffer_map.rs | 4 +- src/buffer/buffer_storage.rs | 74 ++++++++++++++++--------- src/buffer/manage_buffer.rs | 7 ++- src/operation/operate_buffer.rs | 6 +-- 6 files changed, 136 insertions(+), 60 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index b28f7046..e9c8f1e7 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -267,7 +267,7 @@ impl BufferKey { } #[derive(Clone)] -pub struct DynBufferKey { +pub struct AnyBufferKey { buffer: Entity, session: Entity, accessor: Entity, @@ -275,7 +275,7 @@ pub struct DynBufferKey { type_id: TypeId, } -impl std::fmt::Debug for DynBufferKey { +impl std::fmt::Debug for AnyBufferKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f .debug_struct("DynBufferKey") @@ -288,7 +288,7 @@ impl std::fmt::Debug for DynBufferKey { } } -impl DynBufferKey { +impl AnyBufferKey { /// Downcast this into a concrete [`BufferKey`] type. pub fn into_buffer_key(&self) -> Option> { if TypeId::of::() == self.type_id { @@ -305,10 +305,10 @@ impl DynBufferKey { } } -impl From> for DynBufferKey { +impl From> for AnyBufferKey { fn from(value: BufferKey) -> Self { let type_id = TypeId::of::(); - DynBufferKey { + AnyBufferKey { buffer: value.buffer, session: value.session, accessor: value.accessor, diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index b4e36b92..842e4807 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -23,8 +23,9 @@ use bevy_ecs::{ }; use crate::{ - AnyMessageRef, AnyMessageMut, BoxedMessage, BoxedMessageError, BufferAccessMut, - DynBufferViewer, DynBufferKey, DynBufferManagement, GateState, BufferStorage, + AnyBufferStorageAccess, AnyMessageRef, AnyMessageMut, BoxedMessage, + BoxedMessageError, BufferAccessMut, DynBufferViewer, AnyBufferKey, + DynBufferManagement, GateState, BufferStorage, }; use thiserror::Error as ThisError; @@ -38,34 +39,84 @@ pub struct AnyBufferMut<'w, 's, 'a> { buffer: Entity, session: Entity, accessor: Option, - commands: Commands<'w, 's>, + commands: &'a mut Commands<'w, 's>, modified: bool, } +impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { + /// Same as [BufferMut::allow_closed_loops][1]. + /// + /// [1]: crate::BufferMut::allow_closed_loops + pub fn allow_closed_loops(mut self) -> Self { + self.accessor = None; + self + } + + /// Get how many messages are in this buffer. + pub fn len(&self) -> usize { + self.storage.dyn_count(self.session) + } +} + #[derive(ThisError, Debug, Clone)] pub enum AnyBufferError { #[error("The key was unable to identify a buffer")] BufferMissing, } -pub(crate) fn access_any_buffer_mut( - key: DynBufferKey, - f: Box, - world: &mut World, -) -> Result<(), AnyBufferError> { - let mut state: SystemState> = SystemState::new(world); - let BufferAccessMut { mut query, commands } = state.get_mut(world); - let (storage, gate) = query.get_mut(key.buffer).map_err(|_| AnyBufferError::BufferMissing)?; - let buffer_mut = AnyBufferMut { - storage: Box::new(storage), - gate, - buffer: key.buffer, - session: key.session, - accessor: Some(key.accessor), - commands, - modified: false, - }; +/// This trait allows [`World`] to give you access to any buffer using an +/// [`AnyBufferKey`]. +pub trait AnyBufferWorldAccess { + fn any_buffer_mut( + &mut self, + key: AnyBufferKey, + f: impl FnOnce(AnyBufferMut) -> U, + ) -> Result; +} + +impl AnyBufferWorldAccess for World { + fn any_buffer_mut( + &mut self, + key: AnyBufferKey, + f: impl FnOnce(AnyBufferMut) -> U, + ) -> Result { + let create_state = self.get::(key.buffer) + .ok_or(AnyBufferError::BufferMissing)? + .create_any_buffer_access_mut_state; + + let mut state = create_state(self); + let mut access = state.get_buffer_access_mut(self); + let buffer_mut = access.as_any_buffer_mut(key)?; + Ok(f(buffer_mut)) + } +} + +pub(crate) trait AnyBufferAccessMutState { + fn get_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's>; +} + +impl AnyBufferAccessMutState for SystemState> { + fn get_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's> { + Box::new(self.get_mut(world)) + } +} + +trait AnyBufferAccessMut<'w, 's> { + fn as_any_buffer_mut<'a>(&'a mut self, key: AnyBufferKey) -> Result, AnyBufferError>; +} - f(buffer_mut); - Ok(()) +impl<'w, 's, T: 'static + Send + Sync + Any> AnyBufferAccessMut<'w, 's> for BufferAccessMut<'w, 's, T> { + fn as_any_buffer_mut<'a>(&'a mut self, key: AnyBufferKey) -> Result, AnyBufferError> { + let BufferAccessMut { query, commands } = self; + let (storage, gate) = query.get_mut(key.buffer).map_err(|_| AnyBufferError::BufferMissing)?; + Ok(AnyBufferMut { + storage: Box::new(storage), + gate, + buffer: key.buffer, + session: key.session, + accessor: Some(key.accessor), + commands, + modified: false, + }) + } } diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index a6fb60e6..a8f2e31d 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -29,7 +29,7 @@ use bevy_ecs::prelude::{Entity, World}; use crate::{ DynBuffer, OperationError, OperationResult, OperationRoster, Buffered, Gate, - Joined, Accessed, BufferKeyBuilder, DynBufferKey, + Joined, Accessed, BufferKeyBuilder, AnyBufferKey, }; #[derive(Clone)] @@ -197,7 +197,7 @@ impl Accessed for BufferedMap { /// Container to represent any layout of buffer keys. #[derive(Clone, Debug)] pub struct AnyBufferKeyMap { - pub keys: HashMap, DynBufferKey>, + pub keys: HashMap, AnyBufferKey>, } impl BufferMapLayout for AnyBufferKeyMap { diff --git a/src/buffer/buffer_storage.rs b/src/buffer/buffer_storage.rs index 28a0f670..6d7a6f5e 100644 --- a/src/buffer/buffer_storage.rs +++ b/src/buffer/buffer_storage.rs @@ -15,7 +15,10 @@ * */ -use bevy_ecs::prelude::{Component, Entity, EntityRef, EntityWorldMut, World, Mut}; +use bevy_ecs::{ + prelude::{Component, Entity, EntityRef, EntityWorldMut, World, Mut}, + system::SystemState, +}; use smallvec::{Drain, SmallVec}; @@ -31,16 +34,13 @@ use std::{ use thiserror::Error as ThisError; use crate::{ - AnyBufferMut, BufferSettings, DynBufferKey, InspectBuffer, ManageBuffer, - RetentionPolicy, OperationError, OperationResult, + AnyBufferAccessMutState, BufferAccessMut, BufferSettings, AnyBufferKey, + InspectBuffer, ManageBuffer, RetentionPolicy, OperationError, OperationResult, }; -pub(crate) trait BufferInspection { - fn count(&self, session: Entity) -> usize; - fn active_sessions(&self) -> SmallVec<[Entity; 16]>; -} - pub(crate) trait DynBufferViewer<'a, R> { + fn dyn_count(&self, session: Entity) -> usize; + fn dyn_active_sessions(&self) -> SmallVec<[Entity; 16]>; fn dyn_oldest(&'a self, session: Entity) -> Option; fn dyn_newest(&'a self, session: Entity) -> Option; fn dyn_get(&'a self, session: Entity, index: usize) -> Option; @@ -81,6 +81,17 @@ pub(crate) struct BufferStorage { } impl BufferStorage { + pub(crate) fn count(&self, session: Entity) -> usize { + self.reverse_queues + .get(&session) + .map(|q| q.len()) + .unwrap_or(0) + } + + pub(crate) fn active_sessions(&self) -> SmallVec<[Entity; 16]> { + self.reverse_queues.keys().copied().collect() + } + fn impl_push( reverse_queue: &mut SmallVec<[T; 16]>, retention: RetentionPolicy, @@ -270,22 +281,17 @@ impl BufferStorage { } } -impl BufferInspection for BufferStorage { - fn count(&self, session: Entity) -> usize { - self.reverse_queues - .get(&session) - .map(|q| q.len()) - .unwrap_or(0) - } +pub type AnyMessageRef<'a> = &'a (dyn Any + 'static + Send + Sync); - fn active_sessions(&self) -> SmallVec<[Entity; 16]> { - self.reverse_queues.keys().copied().collect() +impl<'a, T: 'static + Send + Sync + Any> DynBufferViewer<'a, AnyMessageRef<'a>> for Mut<'a, BufferStorage> { + fn dyn_count(&self, session: Entity) -> usize { + self.count(session) } -} -pub type AnyMessageRef<'a> = &'a (dyn Any + 'static + Send + Sync); + fn dyn_active_sessions(&self) -> SmallVec<[Entity; 16]> { + self.active_sessions() + } -impl<'a, T: 'static + Send + Sync + Any> DynBufferViewer<'a, AnyMessageRef<'a>> for Mut<'a, BufferStorage> { fn dyn_oldest(&'a self, session: Entity) -> Option> { self.oldest(session).map(to_any_ref) } @@ -450,14 +456,19 @@ where /// A component that lets us inspect buffer properties without knowing the /// message type of the buffer. #[derive(Component, Clone, Copy)] -pub(crate) struct DynBufferStorage { - access_any_buffer_mut: fn(DynBufferKey, Box, &mut World), +pub(crate) struct AnyBufferStorageAccess { + pub(crate) buffered_count: fn(&EntityRef, Entity) -> Result, + pub(crate) ensure_session: fn(&mut EntityWorldMut, Entity) -> OperationResult, + pub(crate) create_any_buffer_access_mut_state: fn(&mut World) -> Box, } -impl DynBufferStorage { +impl AnyBufferStorageAccess { pub(crate) fn new() -> Self { Self { - access_any_buffer_mut: crate::access_any_buffer_mut::, + buffered_count: buffered_count::, + ensure_session: ensure_session::, + create_any_buffer_access_mut_state: create_any_buffer_access_mut_state::, + } } } @@ -475,3 +486,18 @@ fn ensure_session( ) -> OperationResult { entity_mut.ensure_session::(session) } + + + +fn create_any_buffer_access_mut_state( + world: &mut World, +) -> Box { + // Box::new(SystemState::>::new(world)) + Box::new(SystemState::>::new(world)) +} + +// fn create_buffer_access_mut_state( +// world: &mut World, +// ) -> SystemState> { +// SystemState::new(world) +// } diff --git a/src/buffer/manage_buffer.rs b/src/buffer/manage_buffer.rs index 07768c0f..0f6a8e98 100644 --- a/src/buffer/manage_buffer.rs +++ b/src/buffer/manage_buffer.rs @@ -23,8 +23,7 @@ use bevy_ecs::{ use smallvec::SmallVec; use crate::{ - BufferStorage, DynBufferStorage, OperationError, OperationResult, OrBroken, - BufferInspection, + BufferStorage, AnyBufferStorageAccess, OperationError, OperationResult, OrBroken, }; pub trait InspectBuffer { @@ -55,7 +54,7 @@ impl<'w> InspectBuffer for EntityRef<'w> { } fn dyn_buffered_count(&self, session: Entity) -> Result { - let count = self.get::().or_broken()?.count; + let count = self.get::().or_broken()?.buffered_count; count(self, session) } @@ -139,7 +138,7 @@ impl<'w> ManageBuffer for EntityWorldMut<'w> { fn dyn_ensure_session(&mut self, session: Entity) -> OperationResult { let ensure_session = self - .get_mut::() + .get_mut::() .or_broken()? .ensure_session; diff --git a/src/operation/operate_buffer.rs b/src/operation/operate_buffer.rs index 6fc50d9f..b8efc2e9 100644 --- a/src/operation/operate_buffer.rs +++ b/src/operation/operate_buffer.rs @@ -29,7 +29,7 @@ use backtrace::Backtrace; use smallvec::SmallVec; use crate::{ - Broken, BufferAccessors, BufferSettings, BufferStorage, DynBufferStorage, DeferredRoster, ForkTargetStorage, + Broken, BufferAccessors, BufferSettings, BufferStorage, AnyBufferStorageAccess, DeferredRoster, ForkTargetStorage, Gate, GateActionStorage, Input, InputBundle, InspectBuffer, ManageBuffer, ManageInput, MiscellaneousFailure, Operation, OperationCleanup, OperationError, OperationReachability, OperationRequest, OperationResult, OperationRoster, OperationSetup, OrBroken, @@ -39,14 +39,14 @@ use crate::{ #[derive(Bundle)] pub(crate) struct OperateBuffer { storage: BufferStorage, - inspector: DynBufferStorage, + inspector: AnyBufferStorageAccess, } impl OperateBuffer { pub(crate) fn new(settings: BufferSettings) -> Self { Self { storage: BufferStorage::new(settings), - inspector: DynBufferStorage::new::(), + inspector: AnyBufferStorageAccess::new::(), } } } From f3ddbf5ac816b8246d6bce956888c60cefaeac20 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Wed, 29 Jan 2025 12:39:02 +0000 Subject: [PATCH 05/37] Any buffer access is working Signed-off-by: Michael X. Grey --- src/buffer.rs | 10 +- src/buffer/any_buffer.rs | 323 +++++++++++++++++++++++++++++++++-- src/buffer/buffer_storage.rs | 118 +++++++------ src/lib.rs | 4 +- 4 files changed, 382 insertions(+), 73 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index e9c8f1e7..d03d7763 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -509,7 +509,7 @@ where self.len() == 0 } - /// Check whether the gate of this buffer is open or closed + /// Check whether the gate of this buffer is open or closed. pub fn gate(&self) -> Gate { self.gate .map @@ -552,7 +552,7 @@ where self.storage.drain(self.session, range) } - /// Pull the oldest item from the buffer + /// Pull the oldest item from the buffer. pub fn pull(&mut self) -> Option { self.modified = true; self.storage.pull(self.session) @@ -585,7 +585,7 @@ where // continuous systems with BufferAccessMut from running at the same time no // matter what the buffer type is. - /// Tell the buffer [`Gate`] to open + /// Tell the buffer [`Gate`] to open. pub fn open_gate(&mut self) { if let Some(gate) = self.gate.map.get_mut(&self.session) { if *gate != Gate::Open { @@ -595,7 +595,7 @@ where } } - /// Tell the buffer [`Gate`] to close + /// Tell the buffer [`Gate`] to close. pub fn close_gate(&mut self) { if let Some(gate) = self.gate.map.get_mut(&self.session) { *gate = Gate::Closed; @@ -604,7 +604,7 @@ where } } - /// Perform an action on the gate of the buffer + /// Perform an action on the gate of the buffer. pub fn gate_action(&mut self, action: Gate) { match action { Gate::Open => self.open_gate(), diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 842e4807..c288900d 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -15,17 +15,16 @@ * */ -use std::any::Any; +use std::any::{Any, TypeId}; use bevy_ecs::{ - prelude::{Entity, Commands, Mut, World, Query}, + prelude::{Entity, Commands, Mut, World}, system::SystemState, }; use crate::{ - AnyBufferStorageAccess, AnyMessageRef, AnyMessageMut, BoxedMessage, - BoxedMessageError, BufferAccessMut, DynBufferViewer, AnyBufferKey, - DynBufferManagement, GateState, BufferStorage, + AnyBufferStorageAccess, AnyMessageError, AnyMessageRef, AnyMessageMut, AnyMessage, + BufferAccessMut, AnyBufferKey, NotifyBufferUpdate, AnyBufferManagement, GateState, Gate, }; use thiserror::Error as ThisError; @@ -34,7 +33,7 @@ use thiserror::Error as ThisError; /// [`DynBufferKey`], so it can work for any buffer regardless of the data type /// inside. pub struct AnyBufferMut<'w, 's, 'a> { - storage: Box, AnyMessageMut<'a>, BoxedMessage, BoxedMessageError> + 'a>, + storage: Box, gate: Mut<'a, GateState>, buffer: Entity, session: Entity, @@ -52,9 +51,186 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { self } + /// Look at the oldest item in the buffer. + pub fn oldest(&'a self) -> Option> { + self.storage.any_oldest(self.session) + } + + /// Look at the newest item in the buffer. + pub fn newest(&'a self) -> Option> { + self.storage.any_newest(self.session) + } + + /// Borrow an item from the buffer. Index 0 is the oldest item in the buffer + /// with the highest index being the newest item in the buffer. + pub fn get(&'a self, index: usize) -> Option> { + self.storage.any_get(self.session, index) + } + /// Get how many messages are in this buffer. pub fn len(&self) -> usize { - self.storage.dyn_count(self.session) + self.storage.any_count(self.session) + } + + /// Check if the buffer is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Check whether the gate of this buffer is open or closed. + pub fn gate(&self) -> Gate { + self.gate + .map + .get(&self.session) + .copied() + .unwrap_or(Gate::Open) + } + + /// Modify the oldest item in the buffer. + pub fn oldest_mut(&'a mut self) -> Option> { + self.modified = true; + self.storage.any_oldest_mut(self.session) + } + + /// Modify the newest item in the buffer. + pub fn newest_mut(&'a mut self) -> Option> { + self.modified = true; + self.storage.any_newest_mut(self.session) + } + + /// Modify an item in the buffer. Index 0 is the oldest item in the buffer + /// with the highest index being the newest item in the buffer. + pub fn get_mut(&mut self, index: usize) -> Option> { + self.modified = true; + self.storage.any_get_mut(self.session, index) + } + + /// Pull the oldest item from the buffer. + pub fn pull(&mut self) -> Option { + self.modified = true; + self.storage.any_pull(self.session) + } + + /// Pull the item that was most recently put into the buffer (instead of the + /// oldest, which is what [`Self::pull`] gives). + pub fn pull_newest(&mut self) -> Option { + self.modified = true; + self.storage.any_pull_newest(self.session) + } + + /// Attempt to push a new value into the buffer. + /// + /// If the input value matches the message type of the buffer, this will + /// return [`Ok`]. If the buffer is at its limit before a successful push, this + /// will return the value that needed to be removed. + /// + /// If the input value does not match the message type of the buffer, this + /// will return [`Err`] and give back the message that you tried to push. + pub fn push(&mut self, value: T) -> Result, T> { + if TypeId::of::() != self.storage.any_message_type() { + return Err(value); + } + + self.modified = true; + + // SAFETY: We checked that T matches the message type for this buffer, + // so pushing and downcasting should not exhibit any errors. + let removed = self + .storage + .any_push(self.session, Box::new(value)) + .unwrap() + .map(|value| *value.downcast::().unwrap()); + + Ok(removed) + } + + /// Attempt to push a new value of any message type into the buffer. + /// + /// If the input value matches the message type of the buffer, this will + /// return [`Ok`]. If the buffer is at its limit before a successful push, this + /// will return the value that needed to be removed. + /// + /// If the input value does not match the message type of the buffer, this + /// will return [`Err`] and give back an error with the message that you + /// tried to push and the type information for the expected message type. + pub fn push_any(&mut self, value: AnyMessage) -> Result, AnyMessageError> { + self.storage.any_push(self.session, value) + } + + /// Attempt to push a value into the buffer as if it is the oldest value of + /// the buffer. + /// + /// The result follows the same rules as [`Self::push`]. + pub fn push_as_oldest(&mut self, value: T) -> Result, T> { + if TypeId::of::() != self.storage.any_message_type() { + return Err(value); + } + + self.modified = true; + + // SAFETY: We checked that T matches the message type for this buffer, + // so pushing and downcasting should not exhibit any errors. + let removed = self + .storage + .any_push_as_oldest(self.session, Box::new(value)) + .unwrap() + .map(|value| *value.downcast::().unwrap()); + + Ok(removed) + } + + /// Attempt to push a value into the buffer as if it is the oldest value of + /// the buffer. + /// + /// The result follows the same rules as [`Self::push_any`]. + pub fn push_any_as_oldest(&mut self, value: AnyMessage) -> Result, AnyMessageError> { + self.storage.any_push_as_oldest(self.session, value) + } + + /// Tell the buffer [`Gate`] to open. + pub fn open_gate(&mut self) { + if let Some(gate) = self.gate.map.get_mut(&self.session) { + if *gate != Gate::Open { + *gate = Gate::Open; + self.modified = true; + } + } + } + + /// Tell the buffer [`Gate`] to close. + pub fn close_gate(&mut self) { + if let Some(gate) = self.gate.map.get_mut(&self.session) { + *gate = Gate::Closed; + // There is no need to to indicate that a modification happened + // because listeners do not get notified about gates closing. + } + } + + /// Perform an action on the gate of the buffer. + pub fn gate_action(&mut self, action: Gate) { + match action { + Gate::Open => self.open_gate(), + Gate::Closed => self.close_gate(), + } + } + + /// Trigger the listeners for this buffer to wake up even if nothing in the + /// buffer has changed. This could be used for timers or timeout elements + /// in a workflow. + pub fn pulse(&mut self) { + self.modified = true; + } +} + +impl<'w, 's, 'a> Drop for AnyBufferMut<'w, 's, 'a> { + fn drop(&mut self) { + if self.modified { + self.commands.add(NotifyBufferUpdate::new( + self.buffer, + self.session, + self.accessor, + )); + } } } @@ -69,7 +245,7 @@ pub enum AnyBufferError { pub trait AnyBufferWorldAccess { fn any_buffer_mut( &mut self, - key: AnyBufferKey, + key: &AnyBufferKey, f: impl FnOnce(AnyBufferMut) -> U, ) -> Result; } @@ -77,7 +253,7 @@ pub trait AnyBufferWorldAccess { impl AnyBufferWorldAccess for World { fn any_buffer_mut( &mut self, - key: AnyBufferKey, + key: &AnyBufferKey, f: impl FnOnce(AnyBufferMut) -> U, ) -> Result { let create_state = self.get::(key.buffer) @@ -101,12 +277,12 @@ impl AnyBufferAccessMutState for SystemState { - fn as_any_buffer_mut<'a>(&'a mut self, key: AnyBufferKey) -> Result, AnyBufferError>; +pub(crate) trait AnyBufferAccessMut<'w, 's> { + fn as_any_buffer_mut<'a>(&'a mut self, key: &AnyBufferKey) -> Result, AnyBufferError>; } impl<'w, 's, T: 'static + Send + Sync + Any> AnyBufferAccessMut<'w, 's> for BufferAccessMut<'w, 's, T> { - fn as_any_buffer_mut<'a>(&'a mut self, key: AnyBufferKey) -> Result, AnyBufferError> { + fn as_any_buffer_mut<'a>(&'a mut self, key: &AnyBufferKey) -> Result, AnyBufferError> { let BufferAccessMut { query, commands } = self; let (storage, gate) = query.get_mut(key.buffer).map_err(|_| AnyBufferError::BufferMissing)?; Ok(AnyBufferMut { @@ -120,3 +296,126 @@ impl<'w, 's, T: 'static + Send + Sync + Any> AnyBufferAccessMut<'w, 's> for Buff }) } } + +#[cfg(test)] +mod tests { + use bevy_ecs::prelude::World; + use crate::{prelude::*, testing::*}; + + #[test] + fn test_any_count() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + let buffer = builder.create_buffer(BufferSettings::keep_all()); + let push_multiple_times = builder.commands().spawn_service( + push_multiple_times_into_buffer.into_blocking_service() + ); + let count = builder.commands().spawn_service( + get_buffer_count.into_blocking_service() + ); + + scope + .input + .chain(builder) + .with_access(buffer) + .then(push_multiple_times) + .then(count) + .connect(scope.terminate); + }); + + let mut promise = context.command( + |commands| commands.request(1, workflow).take_response() + ); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let count = promise.take().available().unwrap(); + assert_eq!(count, 5); + assert!(context.no_unhandled_errors()); + } + + fn push_multiple_times_into_buffer( + In((value, key)): In<(usize, BufferKey)>, + mut access: BufferAccessMut, + ) -> AnyBufferKey { + let mut buffer = access.get_mut(&key).unwrap(); + for _ in 0..5 { + buffer.push(value); + } + + key.into() + } + + fn get_buffer_count( + In(key): In, + world: &mut World, + ) -> usize { + world.any_buffer_mut(&key, |access| { + access.len() + }).unwrap() + } + + #[test] + fn test_modify_any_message() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + let buffer = builder.create_buffer(BufferSettings::keep_all()); + let push_multiple_times = builder.commands().spawn_service( + push_multiple_times_into_buffer.into_blocking_service() + ); + let modify_content = builder.commands().spawn_service( + modify_buffer_content.into_blocking_service() + ); + let drain_content = builder.commands().spawn_service( + drain_buffer_content.into_blocking_service() + ); + + scope + .input + .chain(builder) + .with_access(buffer) + .then(push_multiple_times) + .then(modify_content) + .then(drain_content) + .connect(scope.terminate); + }); + + let mut promise = context.command( + |commands| commands.request(3, workflow).take_response() + ); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let values = promise.take().available().unwrap(); + assert_eq!(values, vec![30, 30, 30, 30, 30]); + assert!(context.no_unhandled_errors()); + } + + fn modify_buffer_content( + In(key): In, + world: &mut World, + ) -> AnyBufferKey { + world.any_buffer_mut(&key, |mut access| { + for i in 0..access.len() { + access.get_mut(i).map(|value| { + *value.downcast_mut::().unwrap() *= 10; + }); + } + }).unwrap(); + + key + } + + fn drain_buffer_content( + In(key): In, + world: &mut World, + ) -> Vec { + world.any_buffer_mut(&key, |mut access| { + let mut values = Vec::new(); + while let Some(value) = access.pull() { + values.push(*value.downcast::().unwrap()); + } + values + }).unwrap() + } +} diff --git a/src/buffer/buffer_storage.rs b/src/buffer/buffer_storage.rs index 6d7a6f5e..0d211a6c 100644 --- a/src/buffer/buffer_storage.rs +++ b/src/buffer/buffer_storage.rs @@ -38,27 +38,25 @@ use crate::{ InspectBuffer, ManageBuffer, RetentionPolicy, OperationError, OperationResult, }; -pub(crate) trait DynBufferViewer<'a, R> { - fn dyn_count(&self, session: Entity) -> usize; - fn dyn_active_sessions(&self) -> SmallVec<[Entity; 16]>; - fn dyn_oldest(&'a self, session: Entity) -> Option; - fn dyn_newest(&'a self, session: Entity) -> Option; - fn dyn_get(&'a self, session: Entity, index: usize) -> Option; +pub(crate) trait AnyBufferViewing { + fn any_count(&self, session: Entity) -> usize; + fn any_active_sessions(&self) -> SmallVec<[Entity; 16]>; + fn any_oldest<'a>(&'a self, session: Entity) -> Option>; + fn any_newest<'a>(&'a self, session: Entity) -> Option>; + fn any_get<'a>(&'a self, session: Entity, index: usize) -> Option>; + fn any_message_type(&self) -> TypeId; } -pub(crate) trait DynBufferMutator<'a, R, M>: DynBufferViewer<'a, R> { - fn dyn_oldest_mut(&'a mut self, session: Entity) -> Option; - fn dyn_newest_mut(&'a mut self, session: Entity) -> Option; - fn dyn_get_mut(&'a mut self, session: Entity, index: usize) -> Option; -} - -pub(crate) trait DynBufferManagement<'a, R, M, T, E>: DynBufferMutator<'a, R, M> { - fn dyn_force_push(&mut self, session: Entity, value: T) -> Result, E>; - fn dyn_push(&mut self, session: Entity, value: T) -> Result, E>; - fn dyn_push_as_oldest(&mut self, session: Entity, value: T) -> Result, E>; - fn dyn_pull(&mut self, session: Entity) -> Option; - fn dyn_pull_newest(&mut self, session: Entity) -> Option; - fn dyn_consume(&mut self, session: Entity) -> SmallVec<[T; 16]>; +pub(crate) trait AnyBufferManagement: AnyBufferViewing { + fn any_force_push<'a>(&'a mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult; + fn any_push(&mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult; + fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult; + fn any_pull(&mut self, session: Entity) -> Option; + fn any_pull_newest(&mut self, session: Entity) -> Option; + fn any_consume(&mut self, session: Entity) -> SmallVec<[AnyMessage; 16]>; + fn any_oldest_mut<'a>(&'a mut self, session: Entity) -> Option>; + fn any_newest_mut<'a>(&'a mut self, session: Entity) -> Option>; + fn any_get_mut<'a>(&'a mut self, session: Entity, index: usize) -> Option>; } pub(crate) trait BufferSessionManagement { @@ -226,7 +224,7 @@ impl BufferStorage { pub(crate) fn get_mut(&mut self, session: Entity, index: usize) -> Option<&mut T> { let reverse_queue = self.reverse_queues.get_mut(&session)?; let len = reverse_queue.len(); - if len >= index { + if len <= index { return None; } @@ -266,7 +264,7 @@ impl BufferStorage { pub(crate) fn get(&self, session: Entity, index: usize) -> Option<&T> { let reverse_queue = self.reverse_queues.get(&session)?; let len = reverse_queue.len(); - if len >= index { + if len <= index { return None; } @@ -283,82 +281,86 @@ impl BufferStorage { pub type AnyMessageRef<'a> = &'a (dyn Any + 'static + Send + Sync); -impl<'a, T: 'static + Send + Sync + Any> DynBufferViewer<'a, AnyMessageRef<'a>> for Mut<'a, BufferStorage> { - fn dyn_count(&self, session: Entity) -> usize { +impl AnyBufferViewing for Mut<'_, BufferStorage> { + fn any_count(&self, session: Entity) -> usize { self.count(session) } - fn dyn_active_sessions(&self) -> SmallVec<[Entity; 16]> { + fn any_active_sessions(&self) -> SmallVec<[Entity; 16]> { self.active_sessions() } - fn dyn_oldest(&'a self, session: Entity) -> Option> { + fn any_oldest<'a>(&'a self, session: Entity) -> Option> { self.oldest(session).map(to_any_ref) } - fn dyn_newest(&'a self, session: Entity) -> Option> { + fn any_newest<'a>(&'a self, session: Entity) -> Option> { self.newest(session).map(to_any_ref) } - fn dyn_get(&'a self, session: Entity, index: usize) -> Option> { + fn any_get<'a>(&'a self, session: Entity, index: usize) -> Option> { self.get(session, index).map(to_any_ref) } -} - -pub type AnyMessageMut<'a> = &'a mut (dyn Any + 'static + Send + Sync); -impl<'a, T: 'static + Send + Sync + Any> DynBufferMutator<'a, AnyMessageRef<'a>, AnyMessageMut<'a>> for Mut<'a, BufferStorage> { - fn dyn_oldest_mut(&'a mut self, session: Entity) -> Option> { - self.oldest_mut(session).map(to_any_mut) - } - - fn dyn_newest_mut(&'a mut self, session: Entity) -> Option> { - self.newest_mut(session).map(to_any_mut) - } - - fn dyn_get_mut(&'a mut self, session: Entity, index: usize) -> Option> { - self.get_mut(session, index).map(to_any_mut) + fn any_message_type(&self) -> TypeId { + TypeId::of::() } } -pub type BoxedMessage = Box; +pub type AnyMessageMut<'a> = &'a mut (dyn Any + 'static + Send + Sync); + +pub type AnyMessage = Box; #[derive(ThisError, Debug)] #[error("failed to convert a message")] -pub struct BoxedMessageError { +pub struct AnyMessageError { /// The original value provided - pub value: BoxedMessage, + pub value: AnyMessage, /// The type expected by the buffer pub type_id: TypeId, } -impl<'a, T: 'static + Send + Sync + Any> DynBufferManagement<'a, AnyMessageRef<'a>, AnyMessageMut<'a>, BoxedMessage, BoxedMessageError> for Mut<'a, BufferStorage> { - fn dyn_force_push(&mut self, session: Entity, value: BoxedMessage) -> Result, BoxedMessageError> { +pub type AnyMessagePushResult = Result, AnyMessageError>; + +impl AnyBufferManagement for Mut<'_, BufferStorage> { + fn any_force_push(&mut self, session: Entity, value: AnyMessage) -> Result, AnyMessageError> { let value = from_boxed_message::(value)?; Ok(self.force_push(session, value).map(to_boxed_message)) } - fn dyn_push(&mut self, session: Entity, value: BoxedMessage) -> Result, BoxedMessageError> { + fn any_push(&mut self, session: Entity, value: AnyMessage) -> Result, AnyMessageError> { let value = from_boxed_message::(value)?; Ok(self.push(session, value).map(to_boxed_message)) } - fn dyn_push_as_oldest(&mut self, session: Entity, value: BoxedMessage) -> Result, BoxedMessageError> { + fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessage) -> Result, AnyMessageError> { let value = from_boxed_message::(value)?; Ok(self.push_as_oldest(session, value).map(to_boxed_message)) } - fn dyn_pull(&mut self, session: Entity) -> Option { + fn any_pull(&mut self, session: Entity) -> Option { self.pull(session).map(to_boxed_message) } - fn dyn_pull_newest(&mut self, session: Entity) -> Option { + fn any_pull_newest(&mut self, session: Entity) -> Option { self.pull_newest(session).map(to_boxed_message) } - fn dyn_consume(&mut self, session: Entity) -> SmallVec<[BoxedMessage; 16]> { + fn any_consume(&mut self, session: Entity) -> SmallVec<[AnyMessage; 16]> { self.consume(session).into_iter().map(to_boxed_message).collect() } + + fn any_oldest_mut<'a>(&'a mut self, session: Entity) -> Option> { + self.oldest_mut(session).map(to_any_mut) + } + + fn any_newest_mut<'a>(&'a mut self, session: Entity) -> Option> { + self.newest_mut(session).map(to_any_mut) + } + + fn any_get_mut<'a>(&'a mut self, session: Entity, index: usize) -> Option> { + self.get_mut(session, index).map(to_any_mut) + } } fn to_any_ref<'a, T: 'static + Send + Sync + Any>(x: &'a T) -> AnyMessageRef<'a> { @@ -369,16 +371,16 @@ fn to_any_mut<'a, T: 'static + Send + Sync + Any>(x: &'a mut T) -> AnyMessageMut x } -fn to_boxed_message(x: T) -> BoxedMessage { +fn to_boxed_message(x: T) -> AnyMessage { Box::new(x) } -fn from_boxed_message(value: BoxedMessage) -> Result +fn from_boxed_message(value: AnyMessage) -> Result where T: 'static, { let value = value.downcast::().map_err(|value| { - BoxedMessageError { + AnyMessageError { value, type_id: TypeId::of::(), } @@ -453,6 +455,14 @@ where } } +// pub trait DrainAnyBuffer { +// fn any_next(&mut self) -> Option; +// } + +// impl DrainAnyBuffer for DrainBuffer<'a> { + +// } + /// A component that lets us inspect buffer properties without knowing the /// message type of the buffer. #[derive(Component, Clone, Copy)] diff --git a/src/lib.rs b/src/lib.rs index d7e27ab8..a57d37f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -336,8 +336,8 @@ impl Plugin for ImpulsePlugin { pub mod prelude { pub use crate::{ buffer::{ - Buffer, BufferAccess, BufferAccessMut, BufferKey, BufferSettings, Bufferable, Buffered, - IterBufferable, RetentionPolicy, + AnyBufferKey, AnyBufferWorldAccess, Buffer, BufferAccess, BufferAccessMut, + BufferKey, BufferSettings, Bufferable, Buffered, IterBufferable, RetentionPolicy, }, builder::Builder, callback::{AsCallback, Callback, IntoAsyncCallback, IntoBlockingCallback}, From 9935817a0756cde8e2d78d8193be58577c46a661 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Wed, 29 Jan 2025 14:33:53 +0000 Subject: [PATCH 06/37] Add AnyBuffer drain and restructure any_buffer module Signed-off-by: Michael X. Grey --- src/buffer.rs | 86 ------- src/buffer/any_buffer.rs | 441 +++++++++++++++++++++++++++++++++-- src/buffer/buffer_map.rs | 4 +- src/buffer/buffer_storage.rs | 191 +-------------- src/buffer/buffered.rs | 4 +- 5 files changed, 432 insertions(+), 294 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index d03d7763..f579bd73 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -110,40 +110,6 @@ pub struct CloneFromBuffer { pub(crate) _ignore: std::marker::PhantomData, } -/// A [`Buffer`] whose type has been anonymized. -#[derive(Clone, Copy, Debug)] -pub struct DynBuffer { - pub(crate) scope: Entity, - pub(crate) source: Entity, - pub(crate) type_id: TypeId, -} - -impl DynBuffer { - /// Downcast this into a concrete [`Buffer`] type. - pub fn into_buffer(&self) -> Option> { - if TypeId::of::() == self.type_id { - Some(Buffer { - scope: self.scope, - source: self.source, - _ignore: Default::default(), - }) - } else { - None - } - } -} - -impl From> for DynBuffer { - fn from(value: Buffer) -> Self { - let type_id = TypeId::of::(); - DynBuffer { - scope: value.scope, - source: value.source, - type_id, - } - } -} - /// Settings to describe the behavior of a buffer. #[derive(Default, Clone, Copy)] pub struct BufferSettings { @@ -266,58 +232,6 @@ impl BufferKey { } } -#[derive(Clone)] -pub struct AnyBufferKey { - buffer: Entity, - session: Entity, - accessor: Entity, - lifecycle: Option>, - type_id: TypeId, -} - -impl std::fmt::Debug for AnyBufferKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f - .debug_struct("DynBufferKey") - .field("buffer", &self.buffer) - .field("session", &self.session) - .field("accessor", &self.accessor) - .field("in_use", &self.lifecycle.as_ref().is_some_and(|l| l.is_in_use())) - .field("type_id", &self.type_id) - .finish() - } -} - -impl AnyBufferKey { - /// Downcast this into a concrete [`BufferKey`] type. - pub fn into_buffer_key(&self) -> Option> { - if TypeId::of::() == self.type_id { - Some(BufferKey { - buffer: self.buffer, - session: self.session, - accessor: self.accessor, - lifecycle: self.lifecycle.clone(), - _ignore: Default::default(), - }) - } else { - None - } - } -} - -impl From> for AnyBufferKey { - fn from(value: BufferKey) -> Self { - let type_id = TypeId::of::(); - AnyBufferKey { - buffer: value.buffer, - session: value.session, - accessor: value.accessor, - lifecycle: value.lifecycle.clone(), - type_id, - } - } -} - /// This system parameter lets you get read-only access to a buffer that exists /// within a workflow. Use a [`BufferKey`] to unlock the access. /// diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index c288900d..3dd895e3 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -15,19 +15,118 @@ * */ -use std::any::{Any, TypeId}; +use std::{ + any::{Any, TypeId}, + ops::RangeBounds, + sync::Arc, +}; use bevy_ecs::{ - prelude::{Entity, Commands, Mut, World}, + prelude::{Component, Entity, EntityRef, EntityWorldMut, Commands, Mut, World}, system::SystemState, }; +use smallvec::SmallVec; + +use thiserror::Error as ThisError; + use crate::{ - AnyBufferStorageAccess, AnyMessageError, AnyMessageRef, AnyMessageMut, AnyMessage, - BufferAccessMut, AnyBufferKey, NotifyBufferUpdate, AnyBufferManagement, GateState, Gate, + Buffer, BufferAccessLifecycle, BufferAccessMut, BufferKey, BufferStorage, + DrainBuffer, NotifyBufferUpdate, GateState, Gate, OperationResult, OperationError, + InspectBuffer, ManageBuffer, }; -use thiserror::Error as ThisError; +/// A [`Buffer`] whose type has been anonymized. Joining with this buffer type +/// will yield an [`AnyMessage`]. +#[derive(Clone, Copy, Debug)] +pub struct AnyBuffer { + pub(crate) scope: Entity, + pub(crate) source: Entity, + pub(crate) type_id: TypeId, +} + +impl AnyBuffer { + /// Downcast this into a concrete [`Buffer`] type. + pub fn into_buffer(&self) -> Option> { + if TypeId::of::() == self.type_id { + Some(Buffer { + scope: self.scope, + source: self.source, + _ignore: Default::default(), + }) + } else { + None + } + } +} + +impl From> for AnyBuffer { + fn from(value: Buffer) -> Self { + let type_id = TypeId::of::(); + AnyBuffer { + scope: value.scope, + source: value.source, + type_id, + } + } +} + +/// Similar to a [`BufferKey`][crate::BufferKey] except it can be used for any +/// buffer without knowing the buffer's type ahead of time. +/// +/// Use this with [`AnyBufferAccess`] to directly view or manipulate the contents +/// of a buffer. +#[derive(Clone)] +pub struct AnyBufferKey { + buffer: Entity, + session: Entity, + accessor: Entity, + lifecycle: Option>, + type_id: TypeId, +} + +impl std::fmt::Debug for AnyBufferKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f + .debug_struct("DynBufferKey") + .field("buffer", &self.buffer) + .field("session", &self.session) + .field("accessor", &self.accessor) + .field("in_use", &self.lifecycle.as_ref().is_some_and(|l| l.is_in_use())) + .field("type_id", &self.type_id) + .finish() + } +} + +impl AnyBufferKey { + /// Downcast this into a concrete [`BufferKey`] type. + pub fn into_buffer_key(&self) -> Option> { + if TypeId::of::() == self.type_id { + Some(BufferKey { + buffer: self.buffer, + session: self.session, + accessor: self.accessor, + lifecycle: self.lifecycle.clone(), + _ignore: Default::default(), + }) + } else { + None + } + } +} + +impl From> for AnyBufferKey { + fn from(value: BufferKey) -> Self { + let type_id = TypeId::of::(); + AnyBufferKey { + buffer: value.buffer, + session: value.session, + accessor: value.accessor, + lifecycle: value.lifecycle.clone(), + type_id, + } + } +} /// Similar to [`BufferMut`][crate::BufferMut], but this can be unlocked with a /// [`DynBufferKey`], so it can work for any buffer regardless of the data type @@ -52,18 +151,18 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { } /// Look at the oldest item in the buffer. - pub fn oldest(&'a self) -> Option> { + pub fn oldest(&self) -> Option> { self.storage.any_oldest(self.session) } /// Look at the newest item in the buffer. - pub fn newest(&'a self) -> Option> { + pub fn newest(&self) -> Option> { self.storage.any_newest(self.session) } /// Borrow an item from the buffer. Index 0 is the oldest item in the buffer /// with the highest index being the newest item in the buffer. - pub fn get(&'a self, index: usize) -> Option> { + pub fn get(&self, index: usize) -> Option> { self.storage.any_get(self.session, index) } @@ -87,13 +186,13 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { } /// Modify the oldest item in the buffer. - pub fn oldest_mut(&'a mut self) -> Option> { + pub fn oldest_mut(&mut self) -> Option> { self.modified = true; self.storage.any_oldest_mut(self.session) } /// Modify the newest item in the buffer. - pub fn newest_mut(&'a mut self) -> Option> { + pub fn newest_mut(&mut self) -> Option> { self.modified = true; self.storage.any_newest_mut(self.session) } @@ -105,6 +204,13 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { self.storage.any_get_mut(self.session, index) } + pub fn drain>(&mut self, range: R) -> DrainAnyBuffer<'_> { + self.modified = true; + DrainAnyBuffer { + inner: self.storage.any_drain(self.session, AnyRange::new(range)) + } + } + /// Pull the oldest item from the buffer. pub fn pull(&mut self) -> Option { self.modified = true; @@ -267,6 +373,203 @@ impl AnyBufferWorldAccess for World { } } +trait AnyBufferViewing { + fn any_count(&self, session: Entity) -> usize; + fn any_active_sessions(&self) -> SmallVec<[Entity; 16]>; + fn any_oldest<'a>(&'a self, session: Entity) -> Option>; + fn any_newest<'a>(&'a self, session: Entity) -> Option>; + fn any_get<'a>(&'a self, session: Entity, index: usize) -> Option>; + fn any_message_type(&self) -> TypeId; +} + +trait AnyBufferManagement: AnyBufferViewing { + fn any_force_push<'a>(&'a mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult; + fn any_push(&mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult; + fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult; + fn any_pull(&mut self, session: Entity) -> Option; + fn any_pull_newest(&mut self, session: Entity) -> Option; + fn any_consume(&mut self, session: Entity) -> SmallVec<[AnyMessage; 16]>; + fn any_oldest_mut<'a>(&'a mut self, session: Entity) -> Option>; + fn any_newest_mut<'a>(&'a mut self, session: Entity) -> Option>; + fn any_get_mut<'a>(&'a mut self, session: Entity, index: usize) -> Option>; + fn any_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box; +} + +struct AnyRange { + start_bound: std::ops::Bound, + end_bound: std::ops::Bound, +} + +impl AnyRange { + fn new>(range: T) -> Self { + AnyRange { + start_bound: range.start_bound().map(|x| *x), + end_bound: range.end_bound().map(|x| *x), + } + } +} + +impl std::ops::RangeBounds for AnyRange { + fn start_bound(&self) -> std::ops::Bound<&usize> { + self.start_bound.as_ref() + } + + fn end_bound(&self) -> std::ops::Bound<&usize> { + self.end_bound.as_ref() + } + + fn contains(&self, item: &U) -> bool + where + usize: PartialOrd, + U: ?Sized + PartialOrd, + { + match self.start_bound { + std::ops::Bound::Excluded(lower) => { + if *item <= lower { + return false; + } + } + std::ops::Bound::Included(lower) => { + if *item < lower { + return false; + } + } + _ => {} + } + + match self.end_bound { + std::ops::Bound::Excluded(upper) => { + if upper <= *item { + return false; + } + } + std::ops::Bound::Included(upper) => { + if upper < *item { + return false; + } + } + _ => {} + } + + return true; + } +} + +pub type AnyMessageRef<'a> = &'a (dyn Any + 'static + Send + Sync); + +impl AnyBufferViewing for Mut<'_, BufferStorage> { + fn any_count(&self, session: Entity) -> usize { + self.count(session) + } + + fn any_active_sessions(&self) -> SmallVec<[Entity; 16]> { + self.active_sessions() + } + + fn any_oldest<'a>(&'a self, session: Entity) -> Option> { + self.oldest(session).map(to_any_ref) + } + + fn any_newest<'a>(&'a self, session: Entity) -> Option> { + self.newest(session).map(to_any_ref) + } + + fn any_get<'a>(&'a self, session: Entity, index: usize) -> Option> { + self.get(session, index).map(to_any_ref) + } + + fn any_message_type(&self) -> TypeId { + TypeId::of::() + } +} + + +pub type AnyMessageMut<'a> = &'a mut (dyn Any + 'static + Send + Sync); + +pub type AnyMessage = Box; + +#[derive(ThisError, Debug)] +#[error("failed to convert a message")] +pub struct AnyMessageError { + /// The original value provided + pub value: AnyMessage, + /// The type expected by the buffer + pub type_id: TypeId, +} + +pub type AnyMessagePushResult = Result, AnyMessageError>; + +impl AnyBufferManagement for Mut<'_, BufferStorage> { + fn any_force_push(&mut self, session: Entity, value: AnyMessage) -> Result, AnyMessageError> { + let value = from_boxed_message::(value)?; + Ok(self.force_push(session, value).map(to_any_message)) + } + + fn any_push(&mut self, session: Entity, value: AnyMessage) -> Result, AnyMessageError> { + let value = from_boxed_message::(value)?; + Ok(self.push(session, value).map(to_any_message)) + } + + fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessage) -> Result, AnyMessageError> { + let value = from_boxed_message::(value)?; + Ok(self.push_as_oldest(session, value).map(to_any_message)) + } + + fn any_pull(&mut self, session: Entity) -> Option { + self.pull(session).map(to_any_message) + } + + fn any_pull_newest(&mut self, session: Entity) -> Option { + self.pull_newest(session).map(to_any_message) + } + + fn any_consume(&mut self, session: Entity) -> SmallVec<[AnyMessage; 16]> { + self.consume(session).into_iter().map(to_any_message).collect() + } + + fn any_oldest_mut<'a>(&'a mut self, session: Entity) -> Option> { + self.oldest_mut(session).map(to_any_mut) + } + + fn any_newest_mut<'a>(&'a mut self, session: Entity) -> Option> { + self.newest_mut(session).map(to_any_mut) + } + + fn any_get_mut<'a>(&'a mut self, session: Entity, index: usize) -> Option> { + self.get_mut(session, index).map(to_any_mut) + } + + fn any_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box { + Box::new(self.drain(session, range)) + } +} + +fn to_any_ref<'a, T: 'static + Send + Sync + Any>(x: &'a T) -> AnyMessageRef<'a> { + x +} + +fn to_any_mut<'a, T: 'static + Send + Sync + Any>(x: &'a mut T) -> AnyMessageMut<'a> { + x +} + +fn to_any_message(x: T) -> AnyMessage { + Box::new(x) +} + +fn from_boxed_message(value: AnyMessage) -> Result +where + T: 'static, +{ + let value = value.downcast::().map_err(|value| { + AnyMessageError { + value, + type_id: TypeId::of::(), + } + })?; + + Ok(*value) +} + pub(crate) trait AnyBufferAccessMutState { fn get_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's>; } @@ -297,6 +600,68 @@ impl<'w, 's, T: 'static + Send + Sync + Any> AnyBufferAccessMut<'w, 's> for Buff } } +/// A component that lets us inspect buffer properties without knowing the +/// message type of the buffer. +#[derive(Component, Clone, Copy)] +pub(crate) struct AnyBufferStorageAccess { + pub(crate) buffered_count: fn(&EntityRef, Entity) -> Result, + pub(crate) ensure_session: fn(&mut EntityWorldMut, Entity) -> OperationResult, + pub(crate) create_any_buffer_access_mut_state: fn(&mut World) -> Box, +} + +impl AnyBufferStorageAccess { + pub(crate) fn new() -> Self { + Self { + buffered_count: buffered_count::, + ensure_session: ensure_session::, + create_any_buffer_access_mut_state: create_any_buffer_access_mut_state::, + + } + } +} + +fn buffered_count( + entity: &EntityRef, + session: Entity, +) -> Result { + entity.buffered_count::(session) +} + +fn ensure_session( + entity_mut: &mut EntityWorldMut, + session: Entity, +) -> OperationResult { + entity_mut.ensure_session::(session) +} + +fn create_any_buffer_access_mut_state( + world: &mut World, +) -> Box { + Box::new(SystemState::>::new(world)) +} + +pub struct DrainAnyBuffer<'a> { + inner: Box, +} + +impl<'a> Iterator for DrainAnyBuffer<'a> { + type Item = AnyMessage; + + fn next(&mut self) -> Option { + self.inner.any_next() + } +} + +trait DrainAnyBufferImpl { + fn any_next(&mut self) -> Option; +} + +impl DrainAnyBufferImpl for DrainBuffer<'_, T> { + fn any_next(&mut self) -> Option { + self.next().map(to_any_message) + } +} + #[cfg(test)] mod tests { use bevy_ecs::prelude::World; @@ -368,7 +733,7 @@ mod tests { modify_buffer_content.into_blocking_service() ); let drain_content = builder.commands().spawn_service( - drain_buffer_content.into_blocking_service() + pull_each_buffer_item.into_blocking_service() ); scope @@ -387,7 +752,7 @@ mod tests { context.run_with_conditions(&mut promise, Duration::from_secs(2)); let values = promise.take().available().unwrap(); - assert_eq!(values, vec![30, 30, 30, 30, 30]); + assert_eq!(values, vec![0, 3, 6, 9, 12]); assert!(context.no_unhandled_errors()); } @@ -398,7 +763,7 @@ mod tests { world.any_buffer_mut(&key, |mut access| { for i in 0..access.len() { access.get_mut(i).map(|value| { - *value.downcast_mut::().unwrap() *= 10; + *value.downcast_mut::().unwrap() *= i; }); } }).unwrap(); @@ -406,7 +771,7 @@ mod tests { key } - fn drain_buffer_content( + fn pull_each_buffer_item( In(key): In, world: &mut World, ) -> Vec { @@ -418,4 +783,52 @@ mod tests { values }).unwrap() } + + #[test] + fn test_drain_any_message() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + let buffer = builder.create_buffer(BufferSettings::keep_all()); + let push_multiple_times = builder.commands().spawn_service( + push_multiple_times_into_buffer.into_blocking_service() + ); + let modify_content = builder.commands().spawn_service( + modify_buffer_content.into_blocking_service() + ); + let drain_content = builder.commands().spawn_service( + drain_buffer_contents.into_blocking_service() + ); + + scope + .input + .chain(builder) + .with_access(buffer) + .then(push_multiple_times) + .then(modify_content) + .then(drain_content) + .connect(scope.terminate); + }); + + let mut promise = context.command( + |commands| commands.request(3, workflow).take_response() + ); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let values = promise.take().available().unwrap(); + assert_eq!(values, vec![0, 3, 6, 9, 12]); + assert!(context.no_unhandled_errors()); + } + + fn drain_buffer_contents( + In(key): In, + world: &mut World, + ) -> Vec { + world.any_buffer_mut(&key, |mut access| { + access + .drain(..) + .map(|value| *value.downcast::().unwrap()) + .collect() + }).unwrap() + } } diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index a8f2e31d..bf94bafe 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -28,13 +28,13 @@ use smallvec::SmallVec; use bevy_ecs::prelude::{Entity, World}; use crate::{ - DynBuffer, OperationError, OperationResult, OperationRoster, Buffered, Gate, + AnyBuffer, OperationError, OperationResult, OperationRoster, Buffered, Gate, Joined, Accessed, BufferKeyBuilder, AnyBufferKey, }; #[derive(Clone)] pub struct BufferMap { - inner: HashMap, DynBuffer>, + inner: HashMap, AnyBuffer>, } /// This error is used when the buffers provided for an input are not compatible diff --git a/src/buffer/buffer_storage.rs b/src/buffer/buffer_storage.rs index 0d211a6c..09060642 100644 --- a/src/buffer/buffer_storage.rs +++ b/src/buffer/buffer_storage.rs @@ -31,33 +31,11 @@ use std::{ slice::{Iter, IterMut}, }; -use thiserror::Error as ThisError; - use crate::{ - AnyBufferAccessMutState, BufferAccessMut, BufferSettings, AnyBufferKey, + AnyBufferAccessMutState, BufferAccessMut, BufferSettings, InspectBuffer, ManageBuffer, RetentionPolicy, OperationError, OperationResult, }; -pub(crate) trait AnyBufferViewing { - fn any_count(&self, session: Entity) -> usize; - fn any_active_sessions(&self) -> SmallVec<[Entity; 16]>; - fn any_oldest<'a>(&'a self, session: Entity) -> Option>; - fn any_newest<'a>(&'a self, session: Entity) -> Option>; - fn any_get<'a>(&'a self, session: Entity, index: usize) -> Option>; - fn any_message_type(&self) -> TypeId; -} - -pub(crate) trait AnyBufferManagement: AnyBufferViewing { - fn any_force_push<'a>(&'a mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult; - fn any_push(&mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult; - fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult; - fn any_pull(&mut self, session: Entity) -> Option; - fn any_pull_newest(&mut self, session: Entity) -> Option; - fn any_consume(&mut self, session: Entity) -> SmallVec<[AnyMessage; 16]>; - fn any_oldest_mut<'a>(&'a mut self, session: Entity) -> Option>; - fn any_newest_mut<'a>(&'a mut self, session: Entity) -> Option>; - fn any_get_mut<'a>(&'a mut self, session: Entity, index: usize) -> Option>; -} pub(crate) trait BufferSessionManagement { fn clear_session(&mut self, session: Entity); @@ -279,116 +257,6 @@ impl BufferStorage { } } -pub type AnyMessageRef<'a> = &'a (dyn Any + 'static + Send + Sync); - -impl AnyBufferViewing for Mut<'_, BufferStorage> { - fn any_count(&self, session: Entity) -> usize { - self.count(session) - } - - fn any_active_sessions(&self) -> SmallVec<[Entity; 16]> { - self.active_sessions() - } - - fn any_oldest<'a>(&'a self, session: Entity) -> Option> { - self.oldest(session).map(to_any_ref) - } - - fn any_newest<'a>(&'a self, session: Entity) -> Option> { - self.newest(session).map(to_any_ref) - } - - fn any_get<'a>(&'a self, session: Entity, index: usize) -> Option> { - self.get(session, index).map(to_any_ref) - } - - fn any_message_type(&self) -> TypeId { - TypeId::of::() - } -} - -pub type AnyMessageMut<'a> = &'a mut (dyn Any + 'static + Send + Sync); - -pub type AnyMessage = Box; - -#[derive(ThisError, Debug)] -#[error("failed to convert a message")] -pub struct AnyMessageError { - /// The original value provided - pub value: AnyMessage, - /// The type expected by the buffer - pub type_id: TypeId, -} - -pub type AnyMessagePushResult = Result, AnyMessageError>; - -impl AnyBufferManagement for Mut<'_, BufferStorage> { - fn any_force_push(&mut self, session: Entity, value: AnyMessage) -> Result, AnyMessageError> { - let value = from_boxed_message::(value)?; - Ok(self.force_push(session, value).map(to_boxed_message)) - } - - fn any_push(&mut self, session: Entity, value: AnyMessage) -> Result, AnyMessageError> { - let value = from_boxed_message::(value)?; - Ok(self.push(session, value).map(to_boxed_message)) - } - - fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessage) -> Result, AnyMessageError> { - let value = from_boxed_message::(value)?; - Ok(self.push_as_oldest(session, value).map(to_boxed_message)) - } - - fn any_pull(&mut self, session: Entity) -> Option { - self.pull(session).map(to_boxed_message) - } - - fn any_pull_newest(&mut self, session: Entity) -> Option { - self.pull_newest(session).map(to_boxed_message) - } - - fn any_consume(&mut self, session: Entity) -> SmallVec<[AnyMessage; 16]> { - self.consume(session).into_iter().map(to_boxed_message).collect() - } - - fn any_oldest_mut<'a>(&'a mut self, session: Entity) -> Option> { - self.oldest_mut(session).map(to_any_mut) - } - - fn any_newest_mut<'a>(&'a mut self, session: Entity) -> Option> { - self.newest_mut(session).map(to_any_mut) - } - - fn any_get_mut<'a>(&'a mut self, session: Entity, index: usize) -> Option> { - self.get_mut(session, index).map(to_any_mut) - } -} - -fn to_any_ref<'a, T: 'static + Send + Sync + Any>(x: &'a T) -> AnyMessageRef<'a> { - x -} - -fn to_any_mut<'a, T: 'static + Send + Sync + Any>(x: &'a mut T) -> AnyMessageMut<'a> { - x -} - -fn to_boxed_message(x: T) -> AnyMessage { - Box::new(x) -} - -fn from_boxed_message(value: AnyMessage) -> Result -where - T: 'static, -{ - let value = value.downcast::().map_err(|value| { - AnyMessageError { - value, - type_id: TypeId::of::(), - } - })?; - - Ok(*value) -} - pub struct IterBufferView<'b, T> where T: 'static + Send + Sync, @@ -454,60 +322,3 @@ where } } } - -// pub trait DrainAnyBuffer { -// fn any_next(&mut self) -> Option; -// } - -// impl DrainAnyBuffer for DrainBuffer<'a> { - -// } - -/// A component that lets us inspect buffer properties without knowing the -/// message type of the buffer. -#[derive(Component, Clone, Copy)] -pub(crate) struct AnyBufferStorageAccess { - pub(crate) buffered_count: fn(&EntityRef, Entity) -> Result, - pub(crate) ensure_session: fn(&mut EntityWorldMut, Entity) -> OperationResult, - pub(crate) create_any_buffer_access_mut_state: fn(&mut World) -> Box, -} - -impl AnyBufferStorageAccess { - pub(crate) fn new() -> Self { - Self { - buffered_count: buffered_count::, - ensure_session: ensure_session::, - create_any_buffer_access_mut_state: create_any_buffer_access_mut_state::, - - } - } -} - -fn buffered_count( - entity: &EntityRef, - session: Entity, -) -> Result { - entity.buffered_count::(session) -} - -fn ensure_session( - entity_mut: &mut EntityWorldMut, - session: Entity, -) -> OperationResult { - entity_mut.ensure_session::(session) -} - - - -fn create_any_buffer_access_mut_state( - world: &mut World, -) -> Box { - // Box::new(SystemState::>::new(world)) - Box::new(SystemState::>::new(world)) -} - -// fn create_buffer_access_mut_state( -// world: &mut World, -// ) -> SystemState> { -// SystemState::new(world) -// } diff --git a/src/buffer/buffered.rs b/src/buffer/buffered.rs index 74b4072e..cc69bbc4 100644 --- a/src/buffer/buffered.rs +++ b/src/buffer/buffered.rs @@ -23,7 +23,7 @@ use smallvec::SmallVec; use crate::{ Buffer, BufferAccessors, BufferKey, BufferKeyBuilder, BufferStorage, CloneFromBuffer, ForkTargetStorage, Gate, GateState, InspectBuffer, ManageBuffer, OperationError, - OperationResult, OperationRoster, OrBroken, SingleInputStorage, DynBuffer, + OperationResult, OperationRoster, OrBroken, SingleInputStorage, AnyBuffer, }; pub trait Buffered: Clone { @@ -205,7 +205,7 @@ impl Accessed for CloneFromBuffer { } } -impl Buffered for DynBuffer { +impl Buffered for AnyBuffer { fn verify_scope(&self, scope: Entity) { assert_eq!(scope, self.scope); } From 442c67641e441e293988da6205d55a5238fe5421 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Wed, 29 Jan 2025 14:56:29 +0000 Subject: [PATCH 07/37] Clean up any_buffer implementation Signed-off-by: Michael X. Grey --- src/buffer.rs | 2 +- src/buffer/any_buffer.rs | 104 ++++++++++++++++++----------------- src/buffer/buffer_storage.rs | 17 +----- src/buffer/manage_buffer.rs | 10 ++-- 4 files changed, 63 insertions(+), 70 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index f579bd73..6d09143d 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -22,7 +22,7 @@ use bevy_ecs::{ system::SystemParam, }; -use std::{ops::RangeBounds, sync::Arc, any::TypeId}; +use std::{ops::RangeBounds, sync::Arc}; use crate::{ Builder, Chain, Gate, GateState, InputSlot, NotifyBufferUpdate, OnNewBufferValue, UnusedTarget, diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 3dd895e3..dd3c362b 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -18,7 +18,7 @@ use std::{ any::{Any, TypeId}, ops::RangeBounds, - sync::Arc, + sync::{Arc, OnceLock}, }; use bevy_ecs::{ @@ -26,8 +26,6 @@ use bevy_ecs::{ system::SystemState, }; -use smallvec::SmallVec; - use thiserror::Error as ThisError; use crate::{ @@ -362,11 +360,11 @@ impl AnyBufferWorldAccess for World { key: &AnyBufferKey, f: impl FnOnce(AnyBufferMut) -> U, ) -> Result { - let create_state = self.get::(key.buffer) + let interface = self.get::(key.buffer) .ok_or(AnyBufferError::BufferMissing)? - .create_any_buffer_access_mut_state; + .interface; - let mut state = create_state(self); + let mut state = interface.create_any_buffer_access_mut_state(self); let mut access = state.get_buffer_access_mut(self); let buffer_mut = access.as_any_buffer_mut(key)?; Ok(f(buffer_mut)) @@ -375,7 +373,6 @@ impl AnyBufferWorldAccess for World { trait AnyBufferViewing { fn any_count(&self, session: Entity) -> usize; - fn any_active_sessions(&self) -> SmallVec<[Entity; 16]>; fn any_oldest<'a>(&'a self, session: Entity) -> Option>; fn any_newest<'a>(&'a self, session: Entity) -> Option>; fn any_get<'a>(&'a self, session: Entity, index: usize) -> Option>; @@ -383,12 +380,10 @@ trait AnyBufferViewing { } trait AnyBufferManagement: AnyBufferViewing { - fn any_force_push<'a>(&'a mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult; fn any_push(&mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult; fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult; fn any_pull(&mut self, session: Entity) -> Option; fn any_pull_newest(&mut self, session: Entity) -> Option; - fn any_consume(&mut self, session: Entity) -> SmallVec<[AnyMessage; 16]>; fn any_oldest_mut<'a>(&'a mut self, session: Entity) -> Option>; fn any_newest_mut<'a>(&'a mut self, session: Entity) -> Option>; fn any_get_mut<'a>(&'a mut self, session: Entity, index: usize) -> Option>; @@ -462,10 +457,6 @@ impl AnyBufferViewing for Mut<'_, BufferStorage< self.count(session) } - fn any_active_sessions(&self) -> SmallVec<[Entity; 16]> { - self.active_sessions() - } - fn any_oldest<'a>(&'a self, session: Entity) -> Option> { self.oldest(session).map(to_any_ref) } @@ -500,18 +491,13 @@ pub struct AnyMessageError { pub type AnyMessagePushResult = Result, AnyMessageError>; impl AnyBufferManagement for Mut<'_, BufferStorage> { - fn any_force_push(&mut self, session: Entity, value: AnyMessage) -> Result, AnyMessageError> { - let value = from_boxed_message::(value)?; - Ok(self.force_push(session, value).map(to_any_message)) - } - fn any_push(&mut self, session: Entity, value: AnyMessage) -> Result, AnyMessageError> { - let value = from_boxed_message::(value)?; + let value = from_any_message::(value)?; Ok(self.push(session, value).map(to_any_message)) } fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessage) -> Result, AnyMessageError> { - let value = from_boxed_message::(value)?; + let value = from_any_message::(value)?; Ok(self.push_as_oldest(session, value).map(to_any_message)) } @@ -523,10 +509,6 @@ impl AnyBufferManagement for Mut<'_, BufferStora self.pull_newest(session).map(to_any_message) } - fn any_consume(&mut self, session: Entity) -> SmallVec<[AnyMessage; 16]> { - self.consume(session).into_iter().map(to_any_message).collect() - } - fn any_oldest_mut<'a>(&'a mut self, session: Entity) -> Option> { self.oldest_mut(session).map(to_any_mut) } @@ -556,7 +538,7 @@ fn to_any_message(x: T) -> AnyMessage { Box::new(x) } -fn from_boxed_message(value: AnyMessage) -> Result +fn from_any_message(value: AnyMessage) -> Result where T: 'static, { @@ -604,40 +586,64 @@ impl<'w, 's, T: 'static + Send + Sync + Any> AnyBufferAccessMut<'w, 's> for Buff /// message type of the buffer. #[derive(Component, Clone, Copy)] pub(crate) struct AnyBufferStorageAccess { - pub(crate) buffered_count: fn(&EntityRef, Entity) -> Result, - pub(crate) ensure_session: fn(&mut EntityWorldMut, Entity) -> OperationResult, - pub(crate) create_any_buffer_access_mut_state: fn(&mut World) -> Box, + pub(crate) interface: &'static (dyn AnyBufferStorageAccessInterface + Send + Sync), } impl AnyBufferStorageAccess { - pub(crate) fn new() -> Self { - Self { - buffered_count: buffered_count::, - ensure_session: ensure_session::, - create_any_buffer_access_mut_state: create_any_buffer_access_mut_state::, + pub(crate) fn new() -> Self { + let once: OnceLock<&'static AnyBufferStorageAccessImpl> = OnceLock::new(); + let interface = *once.get_or_init(|| { + Box::leak(Box::new(AnyBufferStorageAccessImpl(Default::default()))) + }); - } + Self { interface } } } -fn buffered_count( - entity: &EntityRef, - session: Entity, -) -> Result { - entity.buffered_count::(session) -} +pub(crate) trait AnyBufferStorageAccessInterface { + fn buffered_count( + &self, + entity: &EntityRef, + session: Entity, + ) -> Result; -fn ensure_session( - entity_mut: &mut EntityWorldMut, - session: Entity, -) -> OperationResult { - entity_mut.ensure_session::(session) + fn ensure_session( + &self, + entity_mut: &mut EntityWorldMut, + session: Entity, + ) -> OperationResult; + + fn create_any_buffer_access_mut_state( + &self, + world: &mut World, + ) -> Box; } -fn create_any_buffer_access_mut_state( - world: &mut World, -) -> Box { - Box::new(SystemState::>::new(world)) +struct AnyBufferStorageAccessImpl(std::marker::PhantomData); + +impl AnyBufferStorageAccessInterface for AnyBufferStorageAccessImpl { + fn buffered_count( + &self, + entity: &EntityRef, + session: Entity, + ) -> Result { + entity.buffered_count::(session) + } + + fn ensure_session( + &self, + entity_mut: &mut EntityWorldMut, + session: Entity, + ) -> OperationResult { + entity_mut.ensure_session::(session) + } + + fn create_any_buffer_access_mut_state( + &self, + world: &mut World, + ) -> Box { + Box::new(SystemState::>::new(world)) + } } pub struct DrainAnyBuffer<'a> { diff --git a/src/buffer/buffer_storage.rs b/src/buffer/buffer_storage.rs index 09060642..6967e473 100644 --- a/src/buffer/buffer_storage.rs +++ b/src/buffer/buffer_storage.rs @@ -15,32 +15,19 @@ * */ -use bevy_ecs::{ - prelude::{Component, Entity, EntityRef, EntityWorldMut, World, Mut}, - system::SystemState, -}; +use bevy_ecs::prelude::{Component, Entity}; use smallvec::{Drain, SmallVec}; use std::collections::HashMap; use std::{ - any::{Any, TypeId}, iter::Rev, ops::RangeBounds, slice::{Iter, IterMut}, }; -use crate::{ - AnyBufferAccessMutState, BufferAccessMut, BufferSettings, - InspectBuffer, ManageBuffer, RetentionPolicy, OperationError, OperationResult, -}; - - -pub(crate) trait BufferSessionManagement { - fn clear_session(&mut self, session: Entity); - fn ensure_session(&mut self, session: Entity); -} +use crate::{BufferSettings, RetentionPolicy}; #[derive(Component)] pub(crate) struct BufferStorage { diff --git a/src/buffer/manage_buffer.rs b/src/buffer/manage_buffer.rs index 0f6a8e98..c1036da7 100644 --- a/src/buffer/manage_buffer.rs +++ b/src/buffer/manage_buffer.rs @@ -54,8 +54,8 @@ impl<'w> InspectBuffer for EntityRef<'w> { } fn dyn_buffered_count(&self, session: Entity) -> Result { - let count = self.get::().or_broken()?.buffered_count; - count(self, session) + let interface = self.get::().or_broken()?.interface; + interface.buffered_count(self, session) } fn try_clone_from_buffer( @@ -137,11 +137,11 @@ impl<'w> ManageBuffer for EntityWorldMut<'w> { } fn dyn_ensure_session(&mut self, session: Entity) -> OperationResult { - let ensure_session = self + let interface = self .get_mut::() .or_broken()? - .ensure_session; + .interface; - ensure_session(self, session) + interface.ensure_session(self, session) } } From d44e383f5e1802f595337d60399d1a211c307803 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Wed, 29 Jan 2025 15:28:46 +0000 Subject: [PATCH 08/37] Introducing JsonBuffer type Signed-off-by: Michael X. Grey --- src/buffer.rs | 3 +++ src/buffer/json_buffer.rs | 42 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/buffer/json_buffer.rs diff --git a/src/buffer.rs b/src/buffer.rs index 6d09143d..96648717 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -52,6 +52,9 @@ pub use bufferable::*; mod manage_buffer; pub use manage_buffer::*; +#[cfg(feature = "diagram")] +mod json_buffer; + /// A buffer is a special type of node within a workflow that is able to store /// and release data. When a session is finished, the buffered data from the /// session will be automatically cleared. diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs new file mode 100644 index 00000000..9d2fd50e --- /dev/null +++ b/src/buffer/json_buffer.rs @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2025 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +use bevy_ecs::prelude::Entity; + +use schemars::schema::Schema; + +#[derive(Clone, Copy, Debug)] +pub struct JsonBuffer { + pub(crate) scope: Entity, + pub(crate) source: Entity, + pub(crate) interface: &'static dyn JsonInterface, +} + +pub(crate) trait JsonInterface { + fn message_type(&self) -> &str; + fn schema(&self) -> &Schema; +} + +impl<'a> std::fmt::Debug for &'a dyn JsonInterface { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f + .debug_struct("Message Properties") + .field("type", &self.message_type()) + .field("schema", self.schema()) + .finish() + } +} From 70cec2cbd551b0dc8fdced62d130697d57d52cac Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Thu, 30 Jan 2025 03:29:20 +0000 Subject: [PATCH 09/37] Refactoring the use of AnyBufferStorageAccessInterface Signed-off-by: Michael X. Grey --- src/buffer/any_buffer.rs | 78 +++++++++++++++++++-------------- src/buffer/buffered.rs | 12 ++--- src/buffer/manage_buffer.rs | 20 +-------- src/operation/operate_buffer.rs | 4 +- 4 files changed, 50 insertions(+), 64 deletions(-) diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index dd3c362b..a48b1281 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -22,7 +22,7 @@ use std::{ }; use bevy_ecs::{ - prelude::{Component, Entity, EntityRef, EntityWorldMut, Commands, Mut, World}, + prelude::{Entity, EntityRef, EntityWorldMut, Commands, Mut, World}, system::SystemState, }; @@ -36,17 +36,27 @@ use crate::{ /// A [`Buffer`] whose type has been anonymized. Joining with this buffer type /// will yield an [`AnyMessage`]. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub struct AnyBuffer { pub(crate) scope: Entity, pub(crate) source: Entity, - pub(crate) type_id: TypeId, + pub(crate) interface: &'static (dyn AnyBufferStorageAccessInterface + Send + Sync) +} + +impl std::fmt::Debug for AnyBuffer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AnyBuffer") + .field("scope", &self.scope) + .field("source", &self.source) + .field("message_type_name", &self.interface.message_type_name()) + .finish() + } } impl AnyBuffer { /// Downcast this into a concrete [`Buffer`] type. pub fn into_buffer(&self) -> Option> { - if TypeId::of::() == self.type_id { + if TypeId::of::() == self.interface.message_type_id() { Some(Buffer { scope: self.scope, source: self.source, @@ -58,13 +68,13 @@ impl AnyBuffer { } } -impl From> for AnyBuffer { +impl From> for AnyBuffer { fn from(value: Buffer) -> Self { - let type_id = TypeId::of::(); + let interface = AnyBufferStorageAccessImpl::::get_interface(); AnyBuffer { scope: value.scope, source: value.source, - type_id, + interface, } } } @@ -80,7 +90,7 @@ pub struct AnyBufferKey { session: Entity, accessor: Entity, lifecycle: Option>, - type_id: TypeId, + interface: &'static (dyn AnyBufferStorageAccessInterface + Send + Sync), } impl std::fmt::Debug for AnyBufferKey { @@ -91,7 +101,7 @@ impl std::fmt::Debug for AnyBufferKey { .field("session", &self.session) .field("accessor", &self.accessor) .field("in_use", &self.lifecycle.as_ref().is_some_and(|l| l.is_in_use())) - .field("type_id", &self.type_id) + .field("message_type_name", &self.interface.message_type_name()) .finish() } } @@ -99,7 +109,7 @@ impl std::fmt::Debug for AnyBufferKey { impl AnyBufferKey { /// Downcast this into a concrete [`BufferKey`] type. pub fn into_buffer_key(&self) -> Option> { - if TypeId::of::() == self.type_id { + if TypeId::of::() == self.interface.message_type_id() { Some(BufferKey { buffer: self.buffer, session: self.session, @@ -113,15 +123,15 @@ impl AnyBufferKey { } } -impl From> for AnyBufferKey { +impl From> for AnyBufferKey { fn from(value: BufferKey) -> Self { - let type_id = TypeId::of::(); + let interface = AnyBufferStorageAccessImpl::::get_interface(); AnyBufferKey { buffer: value.buffer, session: value.session, accessor: value.accessor, lifecycle: value.lifecycle.clone(), - type_id, + interface, } } } @@ -360,10 +370,7 @@ impl AnyBufferWorldAccess for World { key: &AnyBufferKey, f: impl FnOnce(AnyBufferMut) -> U, ) -> Result { - let interface = self.get::(key.buffer) - .ok_or(AnyBufferError::BufferMissing)? - .interface; - + let interface = key.interface; let mut state = interface.create_any_buffer_access_mut_state(self); let mut access = state.get_buffer_access_mut(self); let buffer_mut = access.as_any_buffer_mut(key)?; @@ -582,25 +589,11 @@ impl<'w, 's, T: 'static + Send + Sync + Any> AnyBufferAccessMut<'w, 's> for Buff } } -/// A component that lets us inspect buffer properties without knowing the -/// message type of the buffer. -#[derive(Component, Clone, Copy)] -pub(crate) struct AnyBufferStorageAccess { - pub(crate) interface: &'static (dyn AnyBufferStorageAccessInterface + Send + Sync), -} - -impl AnyBufferStorageAccess { - pub(crate) fn new() -> Self { - let once: OnceLock<&'static AnyBufferStorageAccessImpl> = OnceLock::new(); - let interface = *once.get_or_init(|| { - Box::leak(Box::new(AnyBufferStorageAccessImpl(Default::default()))) - }); +pub(crate) trait AnyBufferStorageAccessInterface { + fn message_type_id(&self) -> TypeId; - Self { interface } - } -} + fn message_type_name(&self) -> &'static str; -pub(crate) trait AnyBufferStorageAccessInterface { fn buffered_count( &self, entity: &EntityRef, @@ -621,7 +614,24 @@ pub(crate) trait AnyBufferStorageAccessInterface { struct AnyBufferStorageAccessImpl(std::marker::PhantomData); +impl AnyBufferStorageAccessImpl { + fn get_interface() -> &'static (dyn AnyBufferStorageAccessInterface + Send + Sync) { + let once: OnceLock<&'static AnyBufferStorageAccessImpl> = OnceLock::new(); + *once.get_or_init(|| { + Box::leak(Box::new(AnyBufferStorageAccessImpl(Default::default()))) + }) + } +} + impl AnyBufferStorageAccessInterface for AnyBufferStorageAccessImpl { + fn message_type_id(&self) -> TypeId { + TypeId::of::() + } + + fn message_type_name(&self) -> &'static str { + std::any::type_name::() + } + fn buffered_count( &self, entity: &EntityRef, diff --git a/src/buffer/buffered.rs b/src/buffer/buffered.rs index cc69bbc4..56d969b4 100644 --- a/src/buffer/buffered.rs +++ b/src/buffer/buffered.rs @@ -211,10 +211,8 @@ impl Buffered for AnyBuffer { } fn buffered_count(&self, session: Entity, world: &World) -> Result { - world - .get_entity(self.source) - .or_broken()? - .dyn_buffered_count(session) + let entity_ref = world.get_entity(self.source).or_broken()?; + self.interface.buffered_count(&entity_ref, session) } fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { @@ -236,10 +234,8 @@ impl Buffered for AnyBuffer { } fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { - world - .get_entity_mut(self.source) - .or_broken()? - .dyn_ensure_session(session) + let mut entity_mut = world.get_entity_mut(self.source).or_broken()?; + self.interface.ensure_session(&mut entity_mut, session) } } diff --git a/src/buffer/manage_buffer.rs b/src/buffer/manage_buffer.rs index c1036da7..1248362c 100644 --- a/src/buffer/manage_buffer.rs +++ b/src/buffer/manage_buffer.rs @@ -23,7 +23,7 @@ use bevy_ecs::{ use smallvec::SmallVec; use crate::{ - BufferStorage, AnyBufferStorageAccess, OperationError, OperationResult, OrBroken, + BufferStorage, OperationError, OperationResult, OrBroken, }; pub trait InspectBuffer { @@ -32,8 +32,6 @@ pub trait InspectBuffer { session: Entity, ) -> Result; - fn dyn_buffered_count(&self, session: Entity) -> Result; - fn try_clone_from_buffer( &self, session: Entity, @@ -53,11 +51,6 @@ impl<'w> InspectBuffer for EntityRef<'w> { Ok(buffer.count(session)) } - fn dyn_buffered_count(&self, session: Entity) -> Result { - let interface = self.get::().or_broken()?.interface; - interface.buffered_count(self, session) - } - fn try_clone_from_buffer( &self, session: Entity, @@ -100,8 +93,6 @@ pub trait ManageBuffer { fn clear_buffer(&mut self, session: Entity) -> OperationResult; fn ensure_session(&mut self, session: Entity) -> OperationResult; - - fn dyn_ensure_session(&mut self, session: Entity) -> OperationResult; } impl<'w> ManageBuffer for EntityWorldMut<'w> { @@ -135,13 +126,4 @@ impl<'w> ManageBuffer for EntityWorldMut<'w> { .ensure_session(session); Ok(()) } - - fn dyn_ensure_session(&mut self, session: Entity) -> OperationResult { - let interface = self - .get_mut::() - .or_broken()? - .interface; - - interface.ensure_session(self, session) - } } diff --git a/src/operation/operate_buffer.rs b/src/operation/operate_buffer.rs index b8efc2e9..95d1d467 100644 --- a/src/operation/operate_buffer.rs +++ b/src/operation/operate_buffer.rs @@ -29,7 +29,7 @@ use backtrace::Backtrace; use smallvec::SmallVec; use crate::{ - Broken, BufferAccessors, BufferSettings, BufferStorage, AnyBufferStorageAccess, DeferredRoster, ForkTargetStorage, + Broken, BufferAccessors, BufferSettings, BufferStorage, DeferredRoster, ForkTargetStorage, Gate, GateActionStorage, Input, InputBundle, InspectBuffer, ManageBuffer, ManageInput, MiscellaneousFailure, Operation, OperationCleanup, OperationError, OperationReachability, OperationRequest, OperationResult, OperationRoster, OperationSetup, OrBroken, @@ -39,14 +39,12 @@ use crate::{ #[derive(Bundle)] pub(crate) struct OperateBuffer { storage: BufferStorage, - inspector: AnyBufferStorageAccess, } impl OperateBuffer { pub(crate) fn new(settings: BufferSettings) -> Self { Self { storage: BufferStorage::new(settings), - inspector: AnyBufferStorageAccess::new::(), } } } From 8f79d53df581f19657fc5f79ce95e27c7c757444 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Thu, 30 Jan 2025 04:22:39 +0000 Subject: [PATCH 10/37] Fix global retention of AnyBufferAccessInterface Signed-off-by: Michael X. Grey --- src/buffer/any_buffer.rs | 35 ++++++++++++++--------- src/buffer/json_buffer.rs | 58 +++++++++++++++++++++++++++++++++------ 2 files changed, 72 insertions(+), 21 deletions(-) diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index a48b1281..e3279a00 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -17,8 +17,9 @@ use std::{ any::{Any, TypeId}, + collections::HashMap, ops::RangeBounds, - sync::{Arc, OnceLock}, + sync::{Arc, OnceLock, Mutex}, }; use bevy_ecs::{ @@ -40,7 +41,7 @@ use crate::{ pub struct AnyBuffer { pub(crate) scope: Entity, pub(crate) source: Entity, - pub(crate) interface: &'static (dyn AnyBufferStorageAccessInterface + Send + Sync) + pub(crate) interface: &'static (dyn AnyBufferAccessInterface + Send + Sync) } impl std::fmt::Debug for AnyBuffer { @@ -70,7 +71,7 @@ impl AnyBuffer { impl From> for AnyBuffer { fn from(value: Buffer) -> Self { - let interface = AnyBufferStorageAccessImpl::::get_interface(); + let interface = AnyBufferAccessImpl::::get_interface(); AnyBuffer { scope: value.scope, source: value.source, @@ -90,7 +91,7 @@ pub struct AnyBufferKey { session: Entity, accessor: Entity, lifecycle: Option>, - interface: &'static (dyn AnyBufferStorageAccessInterface + Send + Sync), + interface: &'static (dyn AnyBufferAccessInterface + Send + Sync), } impl std::fmt::Debug for AnyBufferKey { @@ -125,7 +126,7 @@ impl AnyBufferKey { impl From> for AnyBufferKey { fn from(value: BufferKey) -> Self { - let interface = AnyBufferStorageAccessImpl::::get_interface(); + let interface = AnyBufferAccessImpl::::get_interface(); AnyBufferKey { buffer: value.buffer, session: value.session, @@ -589,7 +590,7 @@ impl<'w, 's, T: 'static + Send + Sync + Any> AnyBufferAccessMut<'w, 's> for Buff } } -pub(crate) trait AnyBufferStorageAccessInterface { +pub(crate) trait AnyBufferAccessInterface { fn message_type_id(&self) -> TypeId; fn message_type_name(&self) -> &'static str; @@ -612,18 +613,26 @@ pub(crate) trait AnyBufferStorageAccessInterface { ) -> Box; } -struct AnyBufferStorageAccessImpl(std::marker::PhantomData); +struct AnyBufferAccessImpl(std::marker::PhantomData); -impl AnyBufferStorageAccessImpl { - fn get_interface() -> &'static (dyn AnyBufferStorageAccessInterface + Send + Sync) { - let once: OnceLock<&'static AnyBufferStorageAccessImpl> = OnceLock::new(); - *once.get_or_init(|| { - Box::leak(Box::new(AnyBufferStorageAccessImpl(Default::default()))) +impl AnyBufferAccessImpl { + fn get_interface() -> &'static (dyn AnyBufferAccessInterface + Send + Sync) { + const INTERFACE_MAP: OnceLock>> = OnceLock::new(); + let binding = INTERFACE_MAP; + + let interfaces = binding.get_or_init(|| Mutex::default()); + + let mut interfaces_mut = interfaces.lock().unwrap(); + *interfaces_mut.entry(TypeId::of::()).or_insert_with(|| { + Box::leak(Box::new(AnyBufferAccessImpl::(Default::default()))) }) } } -impl AnyBufferStorageAccessInterface for AnyBufferStorageAccessImpl { +impl AnyBufferAccessInterface for AnyBufferAccessImpl { fn message_type_id(&self) -> TypeId { TypeId::of::() } diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 9d2fd50e..78fd5d86 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -15,28 +15,70 @@ * */ +use std::{ + any::TypeId, + collections::HashMap, + sync::{Mutex, OnceLock}, +}; + use bevy_ecs::prelude::Entity; -use schemars::schema::Schema; +use schemars::{ + gen::SchemaGenerator, + schema::Schema, + JsonSchema, +}; + +use serde::{de::DeserializeOwned, Serialize}; #[derive(Clone, Copy, Debug)] pub struct JsonBuffer { pub(crate) scope: Entity, pub(crate) source: Entity, - pub(crate) interface: &'static dyn JsonInterface, + pub(crate) interface: &'static (dyn JsonBufferAccessInterface + Send + Sync), } -pub(crate) trait JsonInterface { - fn message_type(&self) -> &str; - fn schema(&self) -> &Schema; +pub(crate) trait JsonBufferAccessInterface { + fn message_type_name(&self) -> &str; + fn message_type_id(&self) -> TypeId; + fn schema(&self, generator: &mut SchemaGenerator) -> &'static Schema; } -impl<'a> std::fmt::Debug for &'a dyn JsonInterface { +impl<'a> std::fmt::Debug for &'a (dyn JsonBufferAccessInterface + Send + Sync) { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f .debug_struct("Message Properties") - .field("type", &self.message_type()) - .field("schema", self.schema()) + .field("type", &self.message_type_name()) .finish() } } + +struct JsonBufferAccessImpl(std::marker::PhantomData); + +impl JsonBufferAccessImpl { + +} + +impl JsonBufferAccessInterface for JsonBufferAccessImpl { + fn message_type_name(&self) -> &str { + std::any::type_name::() + } + + fn message_type_id(&self) -> TypeId { + TypeId::of::() + } + + fn schema(&self, generator: &mut SchemaGenerator) -> &'static Schema { + // We use a const map so that we only need to generate the schema once + // per message type. + const SCHEMA_MAP: OnceLock>> = OnceLock::new(); + let binding = SCHEMA_MAP; + + let schemas = binding.get_or_init(|| Mutex::default()); + + let mut schemas_mut = schemas.lock().unwrap(); + *schemas_mut.entry(TypeId::of::()).or_insert_with(|| { + Box::leak(Box::new(generator.subschema_for::())) + }) + } +} From b21c218ab1586e518559dcff38a2e9af5357d47b Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Thu, 30 Jan 2025 18:05:37 +0000 Subject: [PATCH 11/37] Fleshing out implementation of JsonBuffer Signed-off-by: Michael X. Grey --- src/buffer.rs | 8 + src/buffer/any_buffer.rs | 57 ++++--- src/buffer/json_buffer.rs | 317 +++++++++++++++++++++++++++++++++++--- 3 files changed, 331 insertions(+), 51 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 96648717..cbb774d3 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -24,6 +24,8 @@ use bevy_ecs::{ use std::{ops::RangeBounds, sync::Arc}; +use thiserror::Error as ThisError; + use crate::{ Builder, Chain, Gate, GateState, InputSlot, NotifyBufferUpdate, OnNewBufferValue, UnusedTarget, }; @@ -571,6 +573,12 @@ where } } +#[derive(ThisError, Debug, Clone)] +pub enum BufferError { + #[error("The key was unable to identify a buffer")] + BufferMissing, +} + #[cfg(test)] mod tests { use crate::{prelude::*, testing::*, Gate}; diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index e3279a00..396fda34 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -30,7 +30,7 @@ use bevy_ecs::{ use thiserror::Error as ThisError; use crate::{ - Buffer, BufferAccessLifecycle, BufferAccessMut, BufferKey, BufferStorage, + Buffer, BufferAccessLifecycle, BufferAccessMut, BufferError, BufferKey, BufferStorage, DrainBuffer, NotifyBufferUpdate, GateState, Gate, OperationResult, OperationError, InspectBuffer, ManageBuffer, }; @@ -80,8 +80,8 @@ impl From> for AnyBuffer { } } -/// Similar to a [`BufferKey`][crate::BufferKey] except it can be used for any -/// buffer without knowing the buffer's type ahead of time. +/// Similar to a [`BufferKey`] except it can be used for any buffer without +/// knowing the buffer's message type at compile time. /// /// Use this with [`AnyBufferAccess`] to directly view or manipulate the contents /// of a buffer. @@ -97,7 +97,7 @@ pub struct AnyBufferKey { impl std::fmt::Debug for AnyBufferKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f - .debug_struct("DynBufferKey") + .debug_struct("AnyBufferKey") .field("buffer", &self.buffer) .field("session", &self.session) .field("accessor", &self.accessor) @@ -216,7 +216,7 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { pub fn drain>(&mut self, range: R) -> DrainAnyBuffer<'_> { self.modified = true; DrainAnyBuffer { - inner: self.storage.any_drain(self.session, AnyRange::new(range)) + interface: self.storage.any_drain(self.session, AnyRange::new(range)) } } @@ -349,12 +349,6 @@ impl<'w, 's, 'a> Drop for AnyBufferMut<'w, 's, 'a> { } } -#[derive(ThisError, Debug, Clone)] -pub enum AnyBufferError { - #[error("The key was unable to identify a buffer")] - BufferMissing, -} - /// This trait allows [`World`] to give you access to any buffer using an /// [`AnyBufferKey`]. pub trait AnyBufferWorldAccess { @@ -362,7 +356,7 @@ pub trait AnyBufferWorldAccess { &mut self, key: &AnyBufferKey, f: impl FnOnce(AnyBufferMut) -> U, - ) -> Result; + ) -> Result; } impl AnyBufferWorldAccess for World { @@ -370,10 +364,10 @@ impl AnyBufferWorldAccess for World { &mut self, key: &AnyBufferKey, f: impl FnOnce(AnyBufferMut) -> U, - ) -> Result { + ) -> Result { let interface = key.interface; let mut state = interface.create_any_buffer_access_mut_state(self); - let mut access = state.get_buffer_access_mut(self); + let mut access = state.get_any_buffer_access_mut(self); let buffer_mut = access.as_any_buffer_mut(key)?; Ok(f(buffer_mut)) } @@ -395,10 +389,10 @@ trait AnyBufferManagement: AnyBufferViewing { fn any_oldest_mut<'a>(&'a mut self, session: Entity) -> Option>; fn any_newest_mut<'a>(&'a mut self, session: Entity) -> Option>; fn any_get_mut<'a>(&'a mut self, session: Entity, index: usize) -> Option>; - fn any_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box; + fn any_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box; } -struct AnyRange { +pub(crate) struct AnyRange { start_bound: std::ops::Bound, end_bound: std::ops::Bound, } @@ -492,19 +486,21 @@ pub type AnyMessage = Box; pub struct AnyMessageError { /// The original value provided pub value: AnyMessage, - /// The type expected by the buffer + /// The ID of the type expected by the buffer pub type_id: TypeId, + /// The name of the type expected by the buffer + pub type_name: &'static str, } pub type AnyMessagePushResult = Result, AnyMessageError>; impl AnyBufferManagement for Mut<'_, BufferStorage> { - fn any_push(&mut self, session: Entity, value: AnyMessage) -> Result, AnyMessageError> { + fn any_push(&mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult { let value = from_any_message::(value)?; Ok(self.push(session, value).map(to_any_message)) } - fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessage) -> Result, AnyMessageError> { + fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult { let value = from_any_message::(value)?; Ok(self.push_as_oldest(session, value).map(to_any_message)) } @@ -529,7 +525,7 @@ impl AnyBufferManagement for Mut<'_, BufferStora self.get_mut(session, index).map(to_any_mut) } - fn any_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box { + fn any_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box { Box::new(self.drain(session, range)) } } @@ -554,6 +550,7 @@ where AnyMessageError { value, type_id: TypeId::of::(), + type_name: std::any::type_name::(), } })?; @@ -561,23 +558,23 @@ where } pub(crate) trait AnyBufferAccessMutState { - fn get_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's>; + fn get_any_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's>; } impl AnyBufferAccessMutState for SystemState> { - fn get_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's> { + fn get_any_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's> { Box::new(self.get_mut(world)) } } -pub(crate) trait AnyBufferAccessMut<'w, 's> { - fn as_any_buffer_mut<'a>(&'a mut self, key: &AnyBufferKey) -> Result, AnyBufferError>; +trait AnyBufferAccessMut<'w, 's> { + fn as_any_buffer_mut<'a>(&'a mut self, key: &AnyBufferKey) -> Result, BufferError>; } impl<'w, 's, T: 'static + Send + Sync + Any> AnyBufferAccessMut<'w, 's> for BufferAccessMut<'w, 's, T> { - fn as_any_buffer_mut<'a>(&'a mut self, key: &AnyBufferKey) -> Result, AnyBufferError> { + fn as_any_buffer_mut<'a>(&'a mut self, key: &AnyBufferKey) -> Result, BufferError> { let BufferAccessMut { query, commands } = self; - let (storage, gate) = query.get_mut(key.buffer).map_err(|_| AnyBufferError::BufferMissing)?; + let (storage, gate) = query.get_mut(key.buffer).map_err(|_| BufferError::BufferMissing)?; Ok(AnyBufferMut { storage: Box::new(storage), gate, @@ -666,22 +663,22 @@ impl AnyBufferAccessInterface for AnyBufferAcces } pub struct DrainAnyBuffer<'a> { - inner: Box, + interface: Box, } impl<'a> Iterator for DrainAnyBuffer<'a> { type Item = AnyMessage; fn next(&mut self) -> Option { - self.inner.any_next() + self.interface.any_next() } } -trait DrainAnyBufferImpl { +trait DrainAnyBufferInterface { fn any_next(&mut self) -> Option; } -impl DrainAnyBufferImpl for DrainBuffer<'_, T> { +impl DrainAnyBufferInterface for DrainBuffer<'_, T> { fn any_next(&mut self) -> Option { self.next().map(to_any_message) } diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 78fd5d86..e7cd3ea2 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -18,19 +18,25 @@ use std::{ any::TypeId, collections::HashMap, - sync::{Mutex, OnceLock}, + sync::{Arc, Mutex, OnceLock}, }; -use bevy_ecs::prelude::Entity; - -use schemars::{ - gen::SchemaGenerator, - schema::Schema, - JsonSchema, +use bevy_ecs::{ + prelude::{Commands, Entity, EntityRef, EntityWorldMut, Mut, World}, + system::SystemState, }; use serde::{de::DeserializeOwned, Serialize}; +use serde_json::Value as JsonMessage; + +use thiserror::Error as ThisError; + +use crate::{ + AnyRange, BufferKey, BufferAccessLifecycle, BufferAccessMut, BufferError, BufferStorage, + DrainBuffer, OperationError, OperationResult, InspectBuffer, ManageBuffer, GateState, +}; + #[derive(Clone, Copy, Debug)] pub struct JsonBuffer { pub(crate) scope: Entity, @@ -38,10 +44,220 @@ pub struct JsonBuffer { pub(crate) interface: &'static (dyn JsonBufferAccessInterface + Send + Sync), } +/// Similar to a [`BufferKey`] except it can be used for any buffer that supports +/// serialization and deserialization without knowing the buffer's specific +/// message type at compile time. +/// +/// Use this with [`JsonBufferAccess`] to directly view or manipulate the contents +/// of a buffer. +#[derive(Clone)] +pub struct JsonBufferKey { + buffer: Entity, + session: Entity, + accessor: Entity, + lifecycle: Option>, + interface: &'static (dyn JsonBufferAccessInterface + Send + Sync), +} + +impl std::fmt::Debug for JsonBufferKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f + .debug_struct("JsonBufferKey") + .field("buffer", &self.buffer) + .field("session", &self.session) + .field("accessor", &self.accessor) + .field("in_use", &self.lifecycle.as_ref().is_some_and(|l| l.is_in_use())) + .field("message_type_name", &self.interface.message_type_name()) + .finish() + } +} + +pub struct JsonBufferMut<'w, 's, 'a> { + storage: Box, + gate: Mut<'a, GateState>, + buffer: Entity, + session: Entity, + accessor: Option, + commands: &'a mut Commands<'w, 's>, + modified: bool, +} + +/// View or modify a buffer message in terms of JSON values. +pub struct JsonMut<'a> { + interface: &'a mut dyn JsonMutInterface, +} + +impl<'a> JsonMut<'a> { + /// Serialize the message within the buffer into JSON. + /// + /// This new [`JsonMessage`] will be a duplicate of the data of the message + /// inside the buffer, effectively meaning this function clones the data. + pub fn serialize(&self) -> Result { + self.interface.serialize() + } + + /// This will first serialize the message within the buffer into JSON and + /// then attempt to deserialize it into the target type. + /// + /// The target type does not need to match the message type inside the buffer, + /// as long as the target type can be deserialized from a serialized value + /// of the buffer's message type. + /// + /// The returned value will duplicate the data of the message inside the + /// buffer, effectively meaning this function clones the data. + pub fn deserialize_into(&self) -> Result { + serde_json::from_value::(self.serialize()?) + } + + /// Replace the underlying message with new data, and receive its original + /// data as JSON. + #[must_use = "if you are going to discard the returned message, use insert instead"] + pub fn replace(&mut self, message: JsonMessage) -> JsonMessageReplaceResult { + self.interface.replace(message) + } + + /// Insert new data into the underyling message. This is the same as replace + /// except it is more efficient if you don't care about the original data, + /// because it will discard the original data instead of serializing it. + pub fn insert(&mut self, message: JsonMessage) -> Result<(), serde_json::Error> { + self.interface.insert(message) + } +} + +pub type JsonMessageFetchResult = Result, serde_json::Error>; +pub type JsonMessagePushResult = Result, serde_json::Error>; +pub type JsonMessageReplaceResult = Result; + +trait JsonBufferViewing { + fn json_count(&self, session: Entity) -> usize; + fn json_oldest<'a>(&'a self, session: Entity) -> JsonMessageFetchResult; + fn json_newest<'a>(&'a self, session: Entity) -> JsonMessageFetchResult; + fn json_get<'a>(&'a self, session: Entity, index: usize) ->JsonMessageFetchResult; +} + +trait JsonBufferManagement: JsonBufferViewing { + fn json_push(&mut self, session: Entity, value: JsonMessage) -> JsonMessagePushResult; + fn json_push_as_oldest(&mut self, session: Entity, value: JsonMessage) -> JsonMessagePushResult; + fn json_pull(&mut self, session: Entity) -> JsonMessageFetchResult; + fn json_pull_newest(&mut self, session: Entity) -> JsonMessageFetchResult; + fn json_oldest_mut<'a>(&'a mut self, session: Entity) -> Option>; + fn json_newest_mut<'a>(&'a mut self, session: Entity) -> Option>; + fn json_get_mut<'a>(&'a mut self, session: Entity, index: usize) -> Option>; + fn json_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box; +} + +impl JsonBufferViewing for Mut<'_, BufferStorage> +where + T: 'static + Send + Sync + Serialize + DeserializeOwned, +{ + fn json_count(&self, session: Entity) -> usize { + self.count(session) + } + + fn json_oldest<'a>(&'a self, session: Entity) -> JsonMessageFetchResult { + self.oldest(session).map(serde_json::to_value).transpose() + } + + fn json_newest<'a>(&'a self, session: Entity) -> JsonMessageFetchResult { + self.newest(session).map(serde_json::to_value).transpose() + } + + fn json_get<'a>(&'a self, session: Entity, index: usize) ->JsonMessageFetchResult { + self.get(session, index).map(serde_json::to_value).transpose() + } +} + +impl JsonBufferManagement for Mut<'_, BufferStorage> +where + T: 'static + Send + Sync + Serialize + DeserializeOwned, +{ + fn json_push(&mut self, session: Entity, value: JsonMessage) -> JsonMessagePushResult { + let value: T = serde_json::from_value(value)?; + self.push(session, value).map(serde_json::to_value).transpose() + } + + fn json_push_as_oldest(&mut self, session: Entity, value: JsonMessage) -> JsonMessagePushResult { + let value: T = serde_json::from_value(value)?; + self.push(session, value).map(serde_json::to_value).transpose() + } + + fn json_pull(&mut self, session: Entity) -> JsonMessageFetchResult { + self.pull(session).map(serde_json::to_value).transpose() + } + + fn json_pull_newest(&mut self, session: Entity) -> JsonMessageFetchResult { + self.pull_newest(session).map(serde_json::to_value).transpose() + } + + fn json_oldest_mut<'a>(&'a mut self, session: Entity) -> Option> { + self.oldest_mut(session).map(|interface| JsonMut { interface }) + } + + fn json_newest_mut<'a>(&'a mut self, session: Entity) -> Option> { + self.newest_mut(session).map(|interface| JsonMut { interface }) + } + + fn json_get_mut<'a>(&'a mut self, session: Entity, index: usize) -> Option> { + self.get_mut(session, index).map(|interface| JsonMut { interface }) + } + + fn json_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box { + Box::new(self.drain(session, range)) + } +} + +trait JsonMutInterface { + /// Serialize the underlying message into JSON + fn serialize(&self) -> Result; + /// Replace the underlying message with new data, and receive its original + /// data as JSON + fn replace(&mut self, message: JsonMessage) -> JsonMessageReplaceResult; + /// Insert new data into the underyling message. This is the same as replace + /// except it is more efficient if you don't care about the original data, + /// because it will discard the original data instead of serializing it. + fn insert(&mut self, message: JsonMessage) -> Result<(), serde_json::Error>; +} + +impl JsonMutInterface for T { + fn serialize(&self) -> Result { + serde_json::to_value(self) + } + + fn replace(&mut self, message: JsonMessage) -> JsonMessageReplaceResult { + let new_message: T = serde_json::from_value(message)?; + let old_message = serde_json::to_value(&self)?; + *self = new_message; + Ok(old_message) + } + + fn insert(&mut self, message: JsonMessage) -> Result<(), serde_json::Error> { + let new_message: T = serde_json::from_value(message)?; + *self = new_message; + Ok(()) + } +} + pub(crate) trait JsonBufferAccessInterface { - fn message_type_name(&self) -> &str; fn message_type_id(&self) -> TypeId; - fn schema(&self, generator: &mut SchemaGenerator) -> &'static Schema; + + fn message_type_name(&self) -> &'static str; + + fn buffered_count( + &self, + buffer_ref: &EntityRef, + session: Entity, + ) -> Result; + + fn ensure_session( + &self, + buffer_mut: &mut EntityWorldMut, + session: Entity, + ) -> OperationResult; + + fn create_json_buffer_access_mut_state( + &self, + world: &mut World, + ) -> Box; } impl<'a> std::fmt::Debug for &'a (dyn JsonBufferAccessInterface + Send + Sync) { @@ -55,12 +271,12 @@ impl<'a> std::fmt::Debug for &'a (dyn JsonBufferAccessInterface + Send + Sync) { struct JsonBufferAccessImpl(std::marker::PhantomData); -impl JsonBufferAccessImpl { +impl JsonBufferAccessImpl { } -impl JsonBufferAccessInterface for JsonBufferAccessImpl { - fn message_type_name(&self) -> &str { +impl JsonBufferAccessInterface for JsonBufferAccessImpl { + fn message_type_name(&self) -> &'static str { std::any::type_name::() } @@ -68,17 +284,76 @@ impl JsonB TypeId::of::() } - fn schema(&self, generator: &mut SchemaGenerator) -> &'static Schema { - // We use a const map so that we only need to generate the schema once - // per message type. - const SCHEMA_MAP: OnceLock>> = OnceLock::new(); - let binding = SCHEMA_MAP; + fn buffered_count( + &self, + buffer_ref: &EntityRef, + session: Entity, + ) -> Result { + buffer_ref.buffered_count::(session) + } + + fn ensure_session( + &self, + buffer_mut: &mut EntityWorldMut, + session: Entity, + ) -> OperationResult { + buffer_mut.ensure_session::(session) + } + + fn create_json_buffer_access_mut_state( + &self, + world: &mut World, + ) -> Box { + Box::new(SystemState::>::new(world)) + } +} + +trait JsonBufferAccessMutState { + fn get_json_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's>; +} + +impl JsonBufferAccessMutState for SystemState> +where + T: 'static + Send + Sync + Serialize + DeserializeOwned, +{ + fn get_json_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's> { + Box::new(self.get_mut(world)) + } +} - let schemas = binding.get_or_init(|| Mutex::default()); +trait JsonBufferAccessMut<'w, 's> { + fn as_json_buffer_mut<'a>(&'a mut self, key: &JsonBufferKey) -> Result, BufferError>; +} - let mut schemas_mut = schemas.lock().unwrap(); - *schemas_mut.entry(TypeId::of::()).or_insert_with(|| { - Box::leak(Box::new(generator.subschema_for::())) +impl<'w, 's, T> JsonBufferAccessMut<'w, 's> for BufferAccessMut<'w, 's, T> +where + T: 'static + Send + Sync + Serialize + DeserializeOwned, +{ + fn as_json_buffer_mut<'a>(&'a mut self, key: &JsonBufferKey) -> Result, BufferError> { + let BufferAccessMut { query, commands } = self; + let (storage, gate) = query.get_mut(key.buffer).map_err(|_| BufferError::BufferMissing)?; + Ok(JsonBufferMut { + storage: Box::new(storage), + gate, + buffer: key.buffer, + session: key.session, + accessor: Some(key.accessor), + commands, + modified: false, }) } } + +pub struct DrainJsonBuffer<'a> { + interface: Box, +} + +trait DrainJsonBufferInterface { + fn json_next(&mut self) -> Option>; +} + +impl DrainJsonBufferInterface for DrainBuffer<'_, T> { + fn json_next(&mut self) -> Option> { + self.next().map(serde_json::to_value) + } +} From 792e0b6d01e907173a6058f106319a4420dbb168 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Fri, 31 Jan 2025 05:31:24 +0000 Subject: [PATCH 12/37] Almost done with JsonBuffer Signed-off-by: Michael X. Grey --- src/buffer.rs | 2 + src/buffer/any_buffer.rs | 27 ++-- src/buffer/json_buffer.rs | 272 +++++++++++++++++++++++++++++++++++--- 3 files changed, 267 insertions(+), 34 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index cbb774d3..eb7facd8 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -56,6 +56,8 @@ pub use manage_buffer::*; #[cfg(feature = "diagram")] mod json_buffer; +#[cfg(feature = "diagram")] +pub use json_buffer::*; /// A buffer is a special type of node within a workflow that is able to store /// and release data. When a session is finished, the buffered data from the diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 396fda34..f97674fd 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -137,8 +137,8 @@ impl From> for AnyBufferKey { } } -/// Similar to [`BufferMut`][crate::BufferMut], but this can be unlocked with a -/// [`DynBufferKey`], so it can work for any buffer regardless of the data type +/// Similar to [`BufferMut`][crate::BufferMut], but this can be unlocked with an +/// [`AnyBufferKey`], so it can work for any buffer regardless of the data type /// inside. pub struct AnyBufferMut<'w, 's, 'a> { storage: Box, @@ -159,18 +159,18 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { self } - /// Look at the oldest item in the buffer. + /// Look at the oldest message in the buffer. pub fn oldest(&self) -> Option> { self.storage.any_oldest(self.session) } - /// Look at the newest item in the buffer. + /// Look at the newest message in the buffer. pub fn newest(&self) -> Option> { self.storage.any_newest(self.session) } - /// Borrow an item from the buffer. Index 0 is the oldest item in the buffer - /// with the highest index being the newest item in the buffer. + /// Borrow a message from the buffer. Index 0 is the oldest message in the buffer + /// with the highest index being the newest message in the buffer. pub fn get(&self, index: usize) -> Option> { self.storage.any_get(self.session, index) } @@ -194,25 +194,26 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { .unwrap_or(Gate::Open) } - /// Modify the oldest item in the buffer. + /// Modify the oldest message in the buffer. pub fn oldest_mut(&mut self) -> Option> { self.modified = true; self.storage.any_oldest_mut(self.session) } - /// Modify the newest item in the buffer. + /// Modify the newest message in the buffer. pub fn newest_mut(&mut self) -> Option> { self.modified = true; self.storage.any_newest_mut(self.session) } - /// Modify an item in the buffer. Index 0 is the oldest item in the buffer - /// with the highest index being the newest item in the buffer. + /// Modify a message in the buffer. Index 0 is the oldest message in the buffer + /// with the highest index being the newest message in the buffer. pub fn get_mut(&mut self, index: usize) -> Option> { self.modified = true; self.storage.any_get_mut(self.session, index) } + /// Drain a range of messages out of the buffer. pub fn drain>(&mut self, range: R) -> DrainAnyBuffer<'_> { self.modified = true; DrainAnyBuffer { @@ -220,13 +221,13 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { } } - /// Pull the oldest item from the buffer. + /// Pull the oldest message from the buffer. pub fn pull(&mut self) -> Option { self.modified = true; self.storage.any_pull(self.session) } - /// Pull the item that was most recently put into the buffer (instead of the + /// Pull the message that was most recently put into the buffer (instead of the /// oldest, which is what [`Self::pull`] gives). pub fn pull_newest(&mut self) -> Option { self.modified = true; @@ -398,7 +399,7 @@ pub(crate) struct AnyRange { } impl AnyRange { - fn new>(range: T) -> Self { + pub(crate) fn new>(range: T) -> Self { AnyRange { start_bound: range.start_bound().map(|x| *x), end_bound: range.end_bound().map(|x| *x), diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index e7cd3ea2..266620da 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -18,6 +18,7 @@ use std::{ any::TypeId, collections::HashMap, + ops::RangeBounds, sync::{Arc, Mutex, OnceLock}, }; @@ -34,7 +35,8 @@ use thiserror::Error as ThisError; use crate::{ AnyRange, BufferKey, BufferAccessLifecycle, BufferAccessMut, BufferError, BufferStorage, - DrainBuffer, OperationError, OperationResult, InspectBuffer, ManageBuffer, GateState, + DrainBuffer, OperationError, OperationResult, InspectBuffer, ManageBuffer, Gate, GateState, + NotifyBufferUpdate, }; #[derive(Clone, Copy, Debug)] @@ -72,6 +74,9 @@ impl std::fmt::Debug for JsonBufferKey { } } +/// Similar to [`BufferMut`][crate::BufferMut], but this can be unlocked with a +/// [`JsonBufferKey`], so it can work for any buffer whose message types support +/// serialization and deserialization. pub struct JsonBufferMut<'w, 's, 'a> { storage: Box, gate: Mut<'a, GateState>, @@ -82,9 +87,210 @@ pub struct JsonBufferMut<'w, 's, 'a> { modified: bool, } +impl<'w, 's, 'a> JsonBufferMut<'w, 's, 'a> { + /// Same as [BufferMut::allow_closed_loops][1]. + /// + /// [1]: crate::BufferMut::allow_closed_loops + pub fn allow_closed_loops(mut self) -> Self { + self.accessor = None; + self + } + + /// Get a serialized copy of the oldest message in the buffer. + pub fn oldest(&self) -> JsonMessageViewResult { + self.storage.json_oldest(self.session) + } + + /// Get a serialized copy of the newest message in the buffer. + pub fn newest(&self) -> JsonMessageViewResult { + self.storage.json_newest(self.session) + } + + /// Get a serialized copy of a message in the buffer. + pub fn get(&self, index: usize) -> JsonMessageViewResult { + self.storage.json_get(self.session, index) + } + + /// Get how many messages are in this buffer. + pub fn len(&self) -> usize { + self.storage.json_count(self.session) + } + + /// Check if the buffer is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Check whether the gate of this buffer is open or closed. + pub fn gate(&self) -> Gate { + self.gate + .map + .get(&self.session) + .copied() + .unwrap_or(Gate::Open) + } + + /// Modify the oldest message in the buffer. + pub fn oldest_mut(&mut self) -> Option> { + self.storage.json_oldest_mut(self.session, &mut self.modified) + } + + /// Modify the newest message in the buffer. + pub fn newest_mut(&mut self) -> Option> { + self.storage.json_newest_mut(self.session, &mut self.modified) + } + + /// Modify a message in the buffer. + pub fn get_mut(&mut self, index: usize) -> Option> { + self.storage.json_get_mut(self.session, index, &mut self.modified) + } + + /// Drain a range of messages out of the buffer. + pub fn drain>(&mut self, range: R) -> DrainJsonBuffer<'_> { + self.modified = true; + DrainJsonBuffer { + interface: self.storage.json_drain(self.session, AnyRange::new(range)) + } + } + + /// Pull the oldest message from the buffer as a JSON value. Unlike + /// [`Self::oldest`] this will remove the message from the buffer. + pub fn pull(&mut self) -> JsonMessageViewResult { + self.modified = true; + self.storage.json_pull(self.session) + } + + /// Pull the oldest message from the buffer and attempt to deserialize it + /// into the target type. + pub fn pull_as(&mut self) -> Result, serde_json::Error> { + self.pull()?.map(|m| serde_json::from_value(m)).transpose() + } + + /// Pull the newest message from the buffer as a JSON value. Unlike + /// [`Self::newest`] this will remove the message from the buffer. + pub fn pull_newest(&mut self) -> JsonMessageViewResult { + self.modified = true; + self.storage.json_pull_newest(self.session) + } + + /// Pull the newest message from the buffer and attempt to deserialize it + /// into the target type. + pub fn pull_newest_as(&mut self) -> Result, serde_json::Error> { + self.pull_newest()?.map(|m| serde_json::from_value(m)).transpose() + } + + /// Attempt to push a new value into the buffer. + /// + /// If the input value is compatible with the message type of the buffer, + /// this will return [`Ok`]. If the buffer is at its limit before a successful + /// push, this will return the value that needed to be removed. + /// + /// If the input value does not match the message type of the buffer, this + /// will return [`Err`]. This may also return [`Err`] if the message coming + /// out of the buffer failed to serialize. + // TODO(@mxgrey): Consider having an error type that differentiates the + // various possible error modes. + pub fn push(&mut self, value: T) -> Result, serde_json::Error> { + let message = serde_json::to_value(&value)?; + self.modified = true; + self.storage.json_push(self.session, message) + } + + /// Same as [`Self::push`] but no serialization step is needed for the incoming + /// message. + pub fn push_json(&mut self, message: JsonMessage) -> Result, serde_json::Error> { + self.modified = true; + self.storage.json_push(self.session, message) + } + + /// Same as [`Self::push`] but the message will be interpreted as the oldest + /// message in the buffer. + pub fn push_as_oldest(&mut self, value: T) -> Result, serde_json::Error> { + let message = serde_json::to_value(&value)?; + self.modified = true; + self.storage.json_push_as_oldest(self.session, message) + } + + /// Same as [`Self:push_as_oldest`] but no serialization step is needed for + /// the incoming message. + pub fn push_json_as_oldest(&mut self, message: JsonMessage) -> Result, serde_json::Error> { + self.modified = true; + self.storage.json_push_as_oldest(self.session, message) + } + + /// Tell the buffer [`Gate`] to open. + pub fn open_gate(&mut self) { + if let Some(gate) = self.gate.map.get_mut(&self.session) { + if *gate != Gate::Open { + *gate = Gate::Open; + self.modified = true; + } + } + } + + /// Tell the buffer [`Gate`] to close. + pub fn close_gate(&mut self) { + if let Some(gate) = self.gate.map.get_mut(&self.session) { + *gate = Gate::Closed; + // There is no need to to indicate that a modification happened + // because listeners do not get notified about gates closing. + } + } + + /// Perform an action on the gate of the buffer. + pub fn gate_action(&mut self, action: Gate) { + match action { + Gate::Open => self.open_gate(), + Gate::Closed => self.close_gate(), + } + } + + /// Trigger the listeners for this buffer to wake up even if nothing in the + /// buffer has changed. This could be used for timers or timeout elements + /// in a workflow. + pub fn pulse(&mut self) { + self.modified = true; + } +} + +impl<'w, 's, 'a> Drop for JsonBufferMut<'w, 's, 'a> { + fn drop(&mut self) { + if self.modified { + self.commands.add(NotifyBufferUpdate::new( + self.buffer, + self.session, + self.accessor, + )); + } + } +} + +pub trait JsonBufferWorldAccess { + fn json_buffer_mut( + &mut self, + key: &JsonBufferKey, + f: impl FnOnce(JsonBufferMut) -> U, + ) -> Result; +} + +impl JsonBufferWorldAccess for World { + fn json_buffer_mut( + &mut self, + key: &JsonBufferKey, + f: impl FnOnce(JsonBufferMut) -> U, + ) -> Result { + let interface = key.interface; + let mut state = interface.create_json_buffer_access_mut_state(self); + let mut access = state.get_json_buffer_access_mut(self); + let buffer_mut = access.as_json_buffer_mut(key)?; + Ok(f(buffer_mut)) + } +} + /// View or modify a buffer message in terms of JSON values. pub struct JsonMut<'a> { interface: &'a mut dyn JsonMutInterface, + modified: &'a mut bool, } impl<'a> JsonMut<'a> { @@ -113,6 +319,7 @@ impl<'a> JsonMut<'a> { /// data as JSON. #[must_use = "if you are going to discard the returned message, use insert instead"] pub fn replace(&mut self, message: JsonMessage) -> JsonMessageReplaceResult { + *self.modified = true; self.interface.replace(message) } @@ -120,29 +327,52 @@ impl<'a> JsonMut<'a> { /// except it is more efficient if you don't care about the original data, /// because it will discard the original data instead of serializing it. pub fn insert(&mut self, message: JsonMessage) -> Result<(), serde_json::Error> { + *self.modified = true; self.interface.insert(message) } } -pub type JsonMessageFetchResult = Result, serde_json::Error>; +/// The return type for functions that give a JSON view of a message in a buffer. +/// If an error occurs while attempting to serialize the message, this will return +/// [`Err`]. +/// +/// If this returns [`Ok`] then [`None`] means there was no message available at +/// the requested location while [`Some`] will contain a serialized copy of the +/// message. +pub type JsonMessageViewResult = Result, serde_json::Error>; + +/// The return type for functions that push a new message into a buffer. If an +/// error occurs while deserializing the message into the buffer's message type +/// then this will return [`Err`]. +/// +/// If this returns [`Ok`] then [`None`] means the new message was added and all +/// prior messages have been retained in the buffer. [`Some`] will contain an +/// old message which has now been removed from the buffer. pub type JsonMessagePushResult = Result, serde_json::Error>; + +/// The return type for functions that replace (swap out) one message with +/// another. If an error occurs while serializing or deserializing either +/// message to/from the buffer's message type then this will return [`Err`]. +/// +/// If this returns [`Ok`] then the message was successfully replaced, and the +/// value inside [`Ok`] is the message that was previously in the buffer. pub type JsonMessageReplaceResult = Result; trait JsonBufferViewing { fn json_count(&self, session: Entity) -> usize; - fn json_oldest<'a>(&'a self, session: Entity) -> JsonMessageFetchResult; - fn json_newest<'a>(&'a self, session: Entity) -> JsonMessageFetchResult; - fn json_get<'a>(&'a self, session: Entity, index: usize) ->JsonMessageFetchResult; + fn json_oldest<'a>(&'a self, session: Entity) -> JsonMessageViewResult; + fn json_newest<'a>(&'a self, session: Entity) -> JsonMessageViewResult; + fn json_get<'a>(&'a self, session: Entity, index: usize) ->JsonMessageViewResult; } trait JsonBufferManagement: JsonBufferViewing { fn json_push(&mut self, session: Entity, value: JsonMessage) -> JsonMessagePushResult; fn json_push_as_oldest(&mut self, session: Entity, value: JsonMessage) -> JsonMessagePushResult; - fn json_pull(&mut self, session: Entity) -> JsonMessageFetchResult; - fn json_pull_newest(&mut self, session: Entity) -> JsonMessageFetchResult; - fn json_oldest_mut<'a>(&'a mut self, session: Entity) -> Option>; - fn json_newest_mut<'a>(&'a mut self, session: Entity) -> Option>; - fn json_get_mut<'a>(&'a mut self, session: Entity, index: usize) -> Option>; + fn json_pull(&mut self, session: Entity) -> JsonMessageViewResult; + fn json_pull_newest(&mut self, session: Entity) -> JsonMessageViewResult; + fn json_oldest_mut<'a>(&'a mut self, session: Entity, modified: &'a mut bool) -> Option>; + fn json_newest_mut<'a>(&'a mut self, session: Entity, modified: &'a mut bool) -> Option>; + fn json_get_mut<'a>(&'a mut self, session: Entity, index: usize, modified: &'a mut bool) -> Option>; fn json_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box; } @@ -154,15 +384,15 @@ where self.count(session) } - fn json_oldest<'a>(&'a self, session: Entity) -> JsonMessageFetchResult { + fn json_oldest<'a>(&'a self, session: Entity) -> JsonMessageViewResult { self.oldest(session).map(serde_json::to_value).transpose() } - fn json_newest<'a>(&'a self, session: Entity) -> JsonMessageFetchResult { + fn json_newest<'a>(&'a self, session: Entity) -> JsonMessageViewResult { self.newest(session).map(serde_json::to_value).transpose() } - fn json_get<'a>(&'a self, session: Entity, index: usize) ->JsonMessageFetchResult { + fn json_get<'a>(&'a self, session: Entity, index: usize) ->JsonMessageViewResult { self.get(session, index).map(serde_json::to_value).transpose() } } @@ -181,24 +411,24 @@ where self.push(session, value).map(serde_json::to_value).transpose() } - fn json_pull(&mut self, session: Entity) -> JsonMessageFetchResult { + fn json_pull(&mut self, session: Entity) -> JsonMessageViewResult { self.pull(session).map(serde_json::to_value).transpose() } - fn json_pull_newest(&mut self, session: Entity) -> JsonMessageFetchResult { + fn json_pull_newest(&mut self, session: Entity) -> JsonMessageViewResult { self.pull_newest(session).map(serde_json::to_value).transpose() } - fn json_oldest_mut<'a>(&'a mut self, session: Entity) -> Option> { - self.oldest_mut(session).map(|interface| JsonMut { interface }) + fn json_oldest_mut<'a>(&'a mut self, session: Entity, modified: &'a mut bool) -> Option> { + self.oldest_mut(session).map(|interface| JsonMut { interface, modified }) } - fn json_newest_mut<'a>(&'a mut self, session: Entity) -> Option> { - self.newest_mut(session).map(|interface| JsonMut { interface }) + fn json_newest_mut<'a>(&'a mut self, session: Entity, modified: &'a mut bool) -> Option> { + self.newest_mut(session).map(|interface| JsonMut { interface, modified }) } - fn json_get_mut<'a>(&'a mut self, session: Entity, index: usize) -> Option> { - self.get_mut(session, index).map(|interface| JsonMut { interface }) + fn json_get_mut<'a>(&'a mut self, session: Entity, index: usize, modified: &'a mut bool) -> Option> { + self.get_mut(session, index).map(|interface| JsonMut { interface, modified }) } fn json_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box { From ae65f248ecfcb6c79bfddbbb2940386c471c1bf8 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Sun, 2 Feb 2025 10:14:38 +0000 Subject: [PATCH 13/37] Implemented Joined and Accessed for AnyBuffer and JsonBuffer Signed-off-by: Michael X. Grey --- src/buffer/any_buffer.rs | 227 ++++++++++++++++++++--- src/buffer/buffer_key_builder.rs | 27 +++ src/buffer/buffered.rs | 56 +----- src/buffer/json_buffer.rs | 247 +++++++++++++++++++++++-- src/lib.rs | 2 +- src/operation/operate_buffer_access.rs | 6 + 6 files changed, 470 insertions(+), 95 deletions(-) diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index f97674fd..0285cb12 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -15,6 +15,8 @@ * */ +// TODO(@mxgrey): Add module-level documentation describing how to use AnyBuffer + use std::{ any::{Any, TypeId}, collections::HashMap, @@ -29,14 +31,17 @@ use bevy_ecs::{ use thiserror::Error as ThisError; +use smallvec::SmallVec; + use crate::{ - Buffer, BufferAccessLifecycle, BufferAccessMut, BufferError, BufferKey, BufferStorage, - DrainBuffer, NotifyBufferUpdate, GateState, Gate, OperationResult, OperationError, - InspectBuffer, ManageBuffer, + Buffer, Buffered, Bufferable, BufferAccessLifecycle, BufferAccessMut, BufferAccessors, + BufferError, BufferKey, BufferStorage, Builder, DrainBuffer, NotifyBufferUpdate, + GateState, Gate, OperationResult, OperationError, OperationRoster, OrBroken, InspectBuffer, + ManageBuffer, Joined, Accessed, add_listener_to_source, }; -/// A [`Buffer`] whose type has been anonymized. Joining with this buffer type -/// will yield an [`AnyMessage`]. +/// A [`Buffer`] whose message type has been anonymized. Joining with this buffer +/// type will yield an [`AnyMessage`]. #[derive(Clone, Copy)] pub struct AnyBuffer { pub(crate) scope: Entity, @@ -56,7 +61,7 @@ impl std::fmt::Debug for AnyBuffer { impl AnyBuffer { /// Downcast this into a concrete [`Buffer`] type. - pub fn into_buffer(&self) -> Option> { + pub fn downcast(&self) -> Option> { if TypeId::of::() == self.interface.message_type_id() { Some(Buffer { scope: self.scope, @@ -87,29 +92,16 @@ impl From> for AnyBuffer { /// of a buffer. #[derive(Clone)] pub struct AnyBufferKey { - buffer: Entity, - session: Entity, - accessor: Entity, - lifecycle: Option>, - interface: &'static (dyn AnyBufferAccessInterface + Send + Sync), -} - -impl std::fmt::Debug for AnyBufferKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f - .debug_struct("AnyBufferKey") - .field("buffer", &self.buffer) - .field("session", &self.session) - .field("accessor", &self.accessor) - .field("in_use", &self.lifecycle.as_ref().is_some_and(|l| l.is_in_use())) - .field("message_type_name", &self.interface.message_type_name()) - .finish() - } + pub(crate) buffer: Entity, + pub(crate) session: Entity, + pub(crate) accessor: Entity, + pub(crate) lifecycle: Option>, + pub(crate) interface: &'static (dyn AnyBufferAccessInterface + Send + Sync), } impl AnyBufferKey { /// Downcast this into a concrete [`BufferKey`] type. - pub fn into_buffer_key(&self) -> Option> { + pub fn downcast(&self) -> Option> { if TypeId::of::() == self.interface.message_type_id() { Some(BufferKey { buffer: self.buffer, @@ -122,6 +114,42 @@ impl AnyBufferKey { None } } + + /// The buffer ID of this key. + pub fn id(&self) -> Entity { + self.buffer + } + + /// The session that this key belongs to. + pub fn session(&self) -> Entity { + self.session + } + + fn deep_clone(&self) -> Self { + let mut deep = self.clone(); + deep.lifecycle = self + .lifecycle + .as_ref() + .map(|l| Arc::new(l.as_ref().clone())); + deep + } + + fn is_in_use(&self) -> bool { + self.lifecycle.as_ref().is_some_and(|l| l.is_in_use()) + } +} + +impl std::fmt::Debug for AnyBufferKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f + .debug_struct("AnyBufferKey") + .field("buffer", &self.buffer) + .field("session", &self.session) + .field("accessor", &self.accessor) + .field("in_use", &self.lifecycle.as_ref().is_some_and(|l| l.is_in_use())) + .field("message_type_name", &self.interface.message_type_name()) + .finish() + } } impl From> for AnyBufferKey { @@ -568,7 +596,7 @@ impl AnyBufferAccessMutState for SystemState { +pub(crate) trait AnyBufferAccessMut<'w, 's> { fn as_any_buffer_mut<'a>(&'a mut self, key: &AnyBufferKey) -> Result, BufferError>; } @@ -605,16 +633,22 @@ pub(crate) trait AnyBufferAccessInterface { session: Entity, ) -> OperationResult; + fn pull( + &self, + entity_mut: &mut EntityWorldMut, + session: Entity, + ) -> Result; + fn create_any_buffer_access_mut_state( &self, world: &mut World, ) -> Box; } -struct AnyBufferAccessImpl(std::marker::PhantomData); +pub(crate) struct AnyBufferAccessImpl(std::marker::PhantomData); impl AnyBufferAccessImpl { - fn get_interface() -> &'static (dyn AnyBufferAccessInterface + Send + Sync) { + pub(crate) fn get_interface() -> &'static (dyn AnyBufferAccessInterface + Send + Sync) { const INTERFACE_MAP: OnceLock AnyBufferAccessInterface for AnyBufferAcces entity_mut.ensure_session::(session) } + fn pull( + &self, + entity_mut: &mut EntityWorldMut, + session: Entity, + ) -> Result { + entity_mut.pull_from_buffer::(session).map(to_any_message) + } + fn create_any_buffer_access_mut_state( &self, world: &mut World, @@ -685,6 +727,86 @@ impl DrainAnyBufferInterface for DrainBuffer<'_, } } +impl Bufferable for AnyBuffer { + type BufferType = Self; + fn into_buffer(self, builder: &mut Builder) -> Self::BufferType { + assert_eq!(self.scope, builder.scope()); + self + } +} + +impl Buffered for AnyBuffer { + fn verify_scope(&self, scope: Entity) { + assert_eq!(scope, self.scope); + } + + fn buffered_count(&self, session: Entity, world: &World) -> Result { + let entity_ref = world.get_entity(self.source).or_broken()?; + self.interface.buffered_count(&entity_ref, session) + } + + fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { + add_listener_to_source(self.source, listener, world) + } + + fn gate_action( + &self, + session: Entity, + action: Gate, + world: &mut World, + roster: &mut OperationRoster, + ) -> OperationResult { + GateState::apply(self.source, session, action, world, roster) + } + + fn as_input(&self) -> SmallVec<[Entity; 8]> { + SmallVec::from_iter([self.source]) + } + + fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { + let mut entity_mut = world.get_entity_mut(self.source).or_broken()?; + self.interface.ensure_session(&mut entity_mut, session) + } +} + +impl Joined for AnyBuffer { + type Item = AnyMessage; + fn pull(&self, session: Entity, world: &mut World) -> Result { + let mut buffer_mut = world.get_entity_mut(self.source).or_broken()?; + self.interface.pull(&mut buffer_mut, session) + } +} + +impl Accessed for AnyBuffer { + type Key = AnyBufferKey; + fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { + world + .get_mut::(self.source) + .or_broken()? + .add_accessor(accessor); + Ok(()) + } + + fn create_key(&self, builder: &super::BufferKeyBuilder) -> Self::Key { + let components = builder.as_components(self.source); + AnyBufferKey { + buffer: components.buffer, + session: components.session, + accessor: components.accessor, + lifecycle: components.lifecycle, + interface: self.interface, + } + } + + fn deep_clone_key(key: &Self::Key) -> Self::Key { + key.deep_clone() + } + + fn is_key_in_use(key: &Self::Key) -> bool { + key.is_in_use() + } +} + #[cfg(test)] mod tests { use bevy_ecs::prelude::World; @@ -854,4 +976,53 @@ mod tests { .collect() }).unwrap() } + + #[test] + fn double_any_messages() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope: Scope<(u32, i32, f32), (u32, i32, f32)>, builder| { + let buffer_u32: AnyBuffer = builder.create_buffer::(BufferSettings::default()).into(); + let buffer_i32: AnyBuffer = builder.create_buffer::(BufferSettings::default()).into(); + let buffer_f32: AnyBuffer = builder.create_buffer::(BufferSettings::default()).into(); + + let (input_u32, input_i32, input_f32) = scope.input.chain(builder).unzip(); + input_u32 + .chain(builder) + .map_block(|v| 2*v) + .connect(buffer_u32.downcast::().unwrap().input_slot()); + + input_i32 + .chain(builder) + .map_block(|v| 2*v) + .connect(buffer_i32.downcast::().unwrap().input_slot()); + + input_f32 + .chain(builder) + .map_block(|v| 2.0*v) + .connect(buffer_f32.downcast::().unwrap().input_slot()); + + (buffer_u32, buffer_i32, buffer_f32) + .join(builder) + .map_block(|(value_u32, value_i32, value_f32)| { + ( + *value_u32.downcast::().unwrap(), + *value_i32.downcast::().unwrap(), + *value_f32.downcast::().unwrap(), + ) + }) + .connect(scope.terminate); + }); + + let mut promise = context.command( + |commands| commands.request((1u32, 2i32, 3f32), workflow).take_response() + ); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let (v_u32, v_i32, v_f32) = promise.take().available().unwrap(); + assert_eq!(v_u32, 2); + assert_eq!(v_i32, 4); + assert_eq!(v_f32, 6.0); + assert!(context.no_unhandled_errors()); + } } diff --git a/src/buffer/buffer_key_builder.rs b/src/buffer/buffer_key_builder.rs index 02e4664d..cf9ef6a1 100644 --- a/src/buffer/buffer_key_builder.rs +++ b/src/buffer/buffer_key_builder.rs @@ -28,6 +28,13 @@ pub struct BufferKeyBuilder { lifecycle: Option<(ChannelSender, Arc<()>)>, } +pub struct BufferKeyComponents { + pub buffer: Entity, + pub session: Entity, + pub accessor: Entity, + pub lifecycle: Option>, +} + impl BufferKeyBuilder { pub(crate) fn build(&self, buffer: Entity) -> BufferKey { BufferKey { @@ -48,6 +55,26 @@ impl BufferKeyBuilder { } } + // TODO(@mxgrey): Consider refactoring all the buffer key structs to use a + // single inner struct like BufferKeyComponents + pub(crate) fn as_components(&self, buffer: Entity) -> BufferKeyComponents { + BufferKeyComponents { + buffer, + session: self.session, + accessor: self.accessor, + lifecycle: self.lifecycle.as_ref().map(|(sender, tracker)| { + Arc::new(BufferAccessLifecycle::new( + self.scope, + buffer, + self.session, + self.accessor, + sender.clone(), + tracker.clone(), + )) + }), + } + } + pub(crate) fn with_tracking( scope: Entity, session: Entity, diff --git a/src/buffer/buffered.rs b/src/buffer/buffered.rs index 56d969b4..2841dde5 100644 --- a/src/buffer/buffered.rs +++ b/src/buffer/buffered.rs @@ -23,7 +23,7 @@ use smallvec::SmallVec; use crate::{ Buffer, BufferAccessors, BufferKey, BufferKeyBuilder, BufferStorage, CloneFromBuffer, ForkTargetStorage, Gate, GateState, InspectBuffer, ManageBuffer, OperationError, - OperationResult, OperationRoster, OrBroken, SingleInputStorage, AnyBuffer, + OperationResult, OperationRoster, OrBroken, SingleInputStorage, }; pub trait Buffered: Clone { @@ -111,11 +111,10 @@ impl Joined for Buffer { impl Accessed for Buffer { type Key = BufferKey; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { - let mut accessors = world.get_mut::(self.source).or_broken()?; - - accessors.0.push(accessor); - accessors.0.sort(); - accessors.0.dedup(); + world + .get_mut::(self.source) + .or_broken()? + .add_accessor(accessor); Ok(()) } @@ -184,11 +183,10 @@ impl Joined for CloneFromBuffer { impl Accessed for CloneFromBuffer { type Key = BufferKey; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { - let mut accessors = world.get_mut::(self.source).or_broken()?; - - accessors.0.push(accessor); - accessors.0.sort(); - accessors.0.dedup(); + world + .get_mut::(self.source) + .or_broken()? + .add_accessor(accessor); Ok(()) } @@ -205,40 +203,6 @@ impl Accessed for CloneFromBuffer { } } -impl Buffered for AnyBuffer { - fn verify_scope(&self, scope: Entity) { - assert_eq!(scope, self.scope); - } - - fn buffered_count(&self, session: Entity, world: &World) -> Result { - let entity_ref = world.get_entity(self.source).or_broken()?; - self.interface.buffered_count(&entity_ref, session) - } - - fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { - add_listener_to_source(self.source, listener, world) - } - - fn gate_action( - &self, - session: Entity, - action: Gate, - world: &mut World, - roster: &mut OperationRoster, - ) -> OperationResult { - GateState::apply(self.source, session, action, world, roster) - } - - fn as_input(&self) -> SmallVec<[Entity; 8]> { - SmallVec::from_iter([self.source]) - } - - fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { - let mut entity_mut = world.get_entity_mut(self.source).or_broken()?; - self.interface.ensure_session(&mut entity_mut, session) - } -} - macro_rules! impl_buffered_for_tuple { ($(($T:ident, $K:ident)),*) => { #[allow(non_snake_case)] @@ -571,7 +535,7 @@ impl Accessed for SmallVec<[T; N]> { } } -fn add_listener_to_source( +pub(crate) fn add_listener_to_source( source: Entity, listener: Entity, world: &mut World, diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 266620da..0e9acd6a 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -15,6 +15,8 @@ * */ +// TODO(@mxgrey): Add module-level documentation describing how to use JsonBuffer + use std::{ any::TypeId, collections::HashMap, @@ -31,19 +33,60 @@ use serde::{de::DeserializeOwned, Serialize}; use serde_json::Value as JsonMessage; -use thiserror::Error as ThisError; +use smallvec::SmallVec; use crate::{ - AnyRange, BufferKey, BufferAccessLifecycle, BufferAccessMut, BufferError, BufferStorage, - DrainBuffer, OperationError, OperationResult, InspectBuffer, ManageBuffer, Gate, GateState, - NotifyBufferUpdate, + AnyBufferAccessImpl, AnyBufferAccessInterface, AnyBuffer, AnyBufferKey, + AnyRange, Buffer, BufferAccessors, BufferKey, BufferKeyBuilder, BufferAccessLifecycle, + BufferAccessMut, BufferError, BufferStorage, Builder, DrainBuffer, OperationError, + OperationResult, InspectBuffer, ManageBuffer, Gate, GateState, + NotifyBufferUpdate, Bufferable, Buffered, OrBroken, Joined, Accessed, + add_listener_to_source, }; +/// A [`Buffer`] whose message type has been anonymized, but which is known to +/// support serialization and deserialization. Joining this buffer type will +/// yield a [`JsonMessage`]. #[derive(Clone, Copy, Debug)] pub struct JsonBuffer { - pub(crate) scope: Entity, - pub(crate) source: Entity, - pub(crate) interface: &'static (dyn JsonBufferAccessInterface + Send + Sync), + scope: Entity, + source: Entity, + interface: &'static (dyn JsonBufferAccessInterface + Send + Sync), +} + +impl JsonBuffer { + /// Downcast this into a concerete [`Buffer`] type. + pub fn downcast(&self) -> Option> { + if TypeId::of::() == self.interface.any_access_interface().message_type_id() { + Some(Buffer { + scope: self.scope, + source: self.source, + _ignore: Default::default(), + }) + } else { + None + } + } +} + +impl From> for JsonBuffer { + fn from(value: Buffer) -> Self { + Self { + scope: value.scope, + source: value.source, + interface: JsonBufferAccessImpl::::get_interface(), + } + } +} + +impl From for AnyBuffer { + fn from(value: JsonBuffer) -> Self { + Self { + scope: value.scope, + source: value.source, + interface: value.interface.any_access_interface(), + } + } } /// Similar to a [`BufferKey`] except it can be used for any buffer that supports @@ -61,6 +104,35 @@ pub struct JsonBufferKey { interface: &'static (dyn JsonBufferAccessInterface + Send + Sync), } +impl JsonBufferKey { + pub fn downcast(&self) -> Option> { + if TypeId::of::() == self.interface.any_access_interface().message_type_id() { + Some(BufferKey { + buffer: self.buffer, + session: self.session, + accessor: self.accessor, + lifecycle: self.lifecycle.clone(), + _ignore: Default::default() + }) + } else { + None + } + } + + fn deep_clone(&self) -> Self { + let mut deep = self.clone(); + deep.lifecycle = self + .lifecycle + .as_ref() + .map(|l| Arc::new(l.as_ref().clone())); + deep + } + + fn is_in_use(&self) -> bool { + self.lifecycle.as_ref().is_some_and(|l| l.is_in_use()) + } +} + impl std::fmt::Debug for JsonBufferKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f @@ -69,11 +141,36 @@ impl std::fmt::Debug for JsonBufferKey { .field("session", &self.session) .field("accessor", &self.accessor) .field("in_use", &self.lifecycle.as_ref().is_some_and(|l| l.is_in_use())) - .field("message_type_name", &self.interface.message_type_name()) + .field("message_type_name", &self.interface.any_access_interface().message_type_name()) .finish() } } +impl From> for JsonBufferKey { + fn from(value: BufferKey) -> Self { + let interface = JsonBufferAccessImpl::::get_interface(); + JsonBufferKey { + buffer: value.buffer, + session: value.session, + accessor: value.accessor, + lifecycle: value.lifecycle, + interface + } + } +} + +impl From for AnyBufferKey { + fn from(value: JsonBufferKey) -> Self { + AnyBufferKey { + buffer: value.buffer, + session: value.session, + accessor: value.accessor, + lifecycle: value.lifecycle, + interface: value.interface.any_access_interface(), + } + } +} + /// Similar to [`BufferMut`][crate::BufferMut], but this can be unlocked with a /// [`JsonBufferKey`], so it can work for any buffer whose message types support /// serialization and deserialization. @@ -467,10 +564,8 @@ impl JsonMutInterface f } } -pub(crate) trait JsonBufferAccessInterface { - fn message_type_id(&self) -> TypeId; - - fn message_type_name(&self) -> &'static str; +trait JsonBufferAccessInterface { + fn any_access_interface(&self) -> &'static (dyn AnyBufferAccessInterface + Send + Sync); fn buffered_count( &self, @@ -484,6 +579,12 @@ pub(crate) trait JsonBufferAccessInterface { session: Entity, ) -> OperationResult; + fn pull( + &self, + buffer_mut: &mut EntityWorldMut, + session: Entity, + ) -> Result; + fn create_json_buffer_access_mut_state( &self, world: &mut World, @@ -494,7 +595,7 @@ impl<'a> std::fmt::Debug for &'a (dyn JsonBufferAccessInterface + Send + Sync) { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f .debug_struct("Message Properties") - .field("type", &self.message_type_name()) + .field("type", &self.any_access_interface().message_type_name()) .finish() } } @@ -502,16 +603,25 @@ impl<'a> std::fmt::Debug for &'a (dyn JsonBufferAccessInterface + Send + Sync) { struct JsonBufferAccessImpl(std::marker::PhantomData); impl JsonBufferAccessImpl { - + pub(crate) fn get_interface() -> &'static (dyn JsonBufferAccessInterface + Send + Sync) { + const INTERFACE_MAP: OnceLock>> = OnceLock::new(); + let binding = INTERFACE_MAP; + + let interfaces = binding.get_or_init(|| Mutex::default()); + + let mut interfaces_mut = interfaces.lock().unwrap(); + *interfaces_mut.entry(TypeId::of::()).or_insert_with(|| { + Box::leak(Box::new(JsonBufferAccessImpl::(Default::default()))) + }) + } } impl JsonBufferAccessInterface for JsonBufferAccessImpl { - fn message_type_name(&self) -> &'static str { - std::any::type_name::() - } - - fn message_type_id(&self) -> TypeId { - TypeId::of::() + fn any_access_interface(&self) -> &'static (dyn AnyBufferAccessInterface + Send + Sync) { + AnyBufferAccessImpl::::get_interface() } fn buffered_count( @@ -530,6 +640,15 @@ impl JsonBufferAccessIn buffer_mut.ensure_session::(session) } + fn pull( + &self, + buffer_mut: &mut EntityWorldMut, + session: Entity, + ) -> Result { + let value = buffer_mut.pull_from_buffer::(session)?; + serde_json::to_value(value).or_broken() + } + fn create_json_buffer_access_mut_state( &self, world: &mut World, @@ -578,6 +697,14 @@ pub struct DrainJsonBuffer<'a> { interface: Box, } +impl<'a> Iterator for DrainJsonBuffer<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + self.interface.json_next() + } +} + trait DrainJsonBufferInterface { fn json_next(&mut self) -> Option>; } @@ -587,3 +714,83 @@ impl DrainJsonBufferInterface for DrainBuf self.next().map(serde_json::to_value) } } + +impl Bufferable for JsonBuffer { + type BufferType = Self; + fn into_buffer(self, builder: &mut Builder) -> Self::BufferType { + assert_eq!(self.scope, builder.scope()); + self + } +} + +impl Buffered for JsonBuffer { + fn verify_scope(&self, scope: Entity) { + assert_eq!(scope, self.scope); + } + + fn buffered_count(&self, session: Entity, world: &World) -> Result { + let buffer_ref = world.get_entity(self.source).or_broken()?; + self.interface.buffered_count(&buffer_ref, session) + } + + fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { + add_listener_to_source(self.source, listener, world) + } + + fn gate_action( + &self, + session: Entity, + action: Gate, + world: &mut World, + roster: &mut crate::OperationRoster, + ) -> OperationResult { + GateState::apply(self.source, session, action, world, roster) + } + + fn as_input(&self) -> smallvec::SmallVec<[Entity; 8]> { + SmallVec::from_iter([self.source]) + } + + fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { + let mut buffer_mut = world.get_entity_mut(self.source).or_broken()?; + self.interface.ensure_session(&mut buffer_mut, session) + } +} + +impl Joined for JsonBuffer { + type Item = JsonMessage; + fn pull(&self, session: Entity, world: &mut World) -> Result { + let mut buffer_mut = world.get_entity_mut(self.source).or_broken()?; + self.interface.pull(&mut buffer_mut, session) + } +} + +impl Accessed for JsonBuffer { + type Key = JsonBufferKey; + fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { + world + .get_mut::(self.source) + .or_broken()? + .add_accessor(accessor); + Ok(()) + } + + fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key { + let components = builder.as_components(self.source); + JsonBufferKey { + buffer: components.buffer, + session: components.session, + accessor: components.accessor, + lifecycle: components.lifecycle, + interface: self.interface, + } + } + + fn deep_clone_key(key: &Self::Key) -> Self::Key { + key.deep_clone() + } + + fn is_key_in_use(key: &Self::Key) -> bool { + key.is_in_use() + } +} diff --git a/src/lib.rs b/src/lib.rs index a57d37f4..b9105b54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -336,7 +336,7 @@ impl Plugin for ImpulsePlugin { pub mod prelude { pub use crate::{ buffer::{ - AnyBufferKey, AnyBufferWorldAccess, Buffer, BufferAccess, BufferAccessMut, + AnyBuffer, AnyBufferKey, AnyBufferWorldAccess, Buffer, BufferAccess, BufferAccessMut, BufferKey, BufferSettings, Bufferable, Buffered, IterBufferable, RetentionPolicy, }, builder::Builder, diff --git a/src/operation/operate_buffer_access.rs b/src/operation/operate_buffer_access.rs index e0b44c33..c4cf0cb3 100644 --- a/src/operation/operate_buffer_access.rs +++ b/src/operation/operate_buffer_access.rs @@ -206,6 +206,12 @@ where pub(crate) struct BufferAccessors(pub(crate) SmallVec<[Entity; 8]>); impl BufferAccessors { + pub(crate) fn add_accessor(&mut self, accessor: Entity) { + self.0.push(accessor); + self.0.sort(); + self.0.dedup(); + } + pub(crate) fn is_reachable(r: &mut OperationReachability) -> ReachabilityResult { let Some(accessors) = r.world.get::(r.source) else { return Ok(false); From 7c9d4902b91e490c8aeec788da8bcd3796df3085 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Sun, 2 Feb 2025 13:28:40 +0000 Subject: [PATCH 14/37] Add tests for JsonBuffer Signed-off-by: Michael X. Grey --- src/buffer/json_buffer.rs | 269 +++++++++++++++++++++++++++++++++++++- src/lib.rs | 10 +- 2 files changed, 276 insertions(+), 3 deletions(-) diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 0e9acd6a..f3b43507 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -31,7 +31,7 @@ use bevy_ecs::{ use serde::{de::DeserializeOwned, Serialize}; -use serde_json::Value as JsonMessage; +pub use serde_json::Value as JsonMessage; use smallvec::SmallVec; @@ -427,6 +427,19 @@ impl<'a> JsonMut<'a> { *self.modified = true; self.interface.insert(message) } + + /// Modify the data of the underlying message. This is equivalent to calling + /// [`Self::serialize`], modifying the value, and then calling [`Self::insert`]. + /// The benefit of this function is that you do not need to remember to + /// insert after you have finished your modifications. + pub fn modify( + &mut self, + f: impl FnOnce(&mut JsonMessage), + ) -> Result<(), serde_json::Error> { + let mut message = self.serialize()?; + f(&mut message); + self.insert(message) + } } /// The return type for functions that give a JSON view of a message in a buffer. @@ -794,3 +807,257 @@ impl Accessed for JsonBuffer { key.is_in_use() } } + +#[cfg(test)] +mod tests { + use bevy_ecs::prelude::World; + use crate::{prelude::*, testing::*}; + use serde::{Serialize, Deserialize}; + + #[derive(Serialize, Deserialize, Clone)] + struct TestMessage { + v_i32: i32, + v_u32: u32, + v_string: String, + } + + impl TestMessage { + fn new() -> Self { + Self { + v_i32: 1, + v_u32: 2, + v_string: "hello".to_string(), + } + } + } + + #[test] + fn test_json_count() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + let buffer = builder.create_buffer(BufferSettings::keep_all()); + let push_multiple_times = builder.commands().spawn_service( + push_multiple_times_into_buffer.into_blocking_service() + ); + let count = builder.commands().spawn_service( + get_buffer_count.into_blocking_service() + ); + + scope + .input + .chain(builder) + .with_access(buffer) + .then(push_multiple_times) + .then(count) + .connect(scope.terminate); + }); + + let msg = TestMessage::new(); + let mut promise = context.command( + |commands| commands.request(msg, workflow).take_response() + ); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let count = promise.take().available().unwrap(); + assert_eq!(count, 5); + assert!(context.no_unhandled_errors()); + } + + fn push_multiple_times_into_buffer( + In((value, key)): In<(TestMessage, BufferKey)>, + mut access: BufferAccessMut, + ) -> JsonBufferKey { + let mut buffer = access.get_mut(&key).unwrap(); + for _ in 0..5 { + buffer.push(value.clone()); + } + + key.into() + } + + fn get_buffer_count( + In(key): In, + world: &mut World, + ) -> usize { + world.json_buffer_mut(&key, |access| { + access.len() + }).unwrap() + } + + #[test] + fn test_modify_json_message() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + let buffer = builder.create_buffer(BufferSettings::keep_all()); + let push_multiple_times = builder.commands().spawn_service( + push_multiple_times_into_buffer.into_blocking_service() + ); + let modify_content = builder.commands().spawn_service( + modify_buffer_content.into_blocking_service() + ); + let drain_content = builder.commands().spawn_service( + pull_each_buffer_item.into_blocking_service() + ); + + scope + .input + .chain(builder) + .with_access(buffer) + .then(push_multiple_times) + .then(modify_content) + .then(drain_content) + .connect(scope.terminate); + }); + + let msg = TestMessage::new(); + let mut promise = context.command( + |commands| commands.request(msg, workflow).take_response() + ); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let values = promise.take().available().unwrap(); + assert_eq!(values.len(), 5); + for i in 0..values.len() { + let v_i32 = values[i].get("v_i32").unwrap().as_i64().unwrap(); + assert_eq!(v_i32, i as i64); + } + assert!(context.no_unhandled_errors()); + } + + fn modify_buffer_content( + In(key): In, + world: &mut World, + ) -> JsonBufferKey { + world.json_buffer_mut(&key, |mut access| { + for i in 0..access.len() { + access.get_mut(i).unwrap().modify(|value| { + let v_i32 = value.get_mut("v_i32").unwrap(); + let modified_v_i32 = i as i64 * v_i32.as_i64().unwrap(); + *v_i32 = modified_v_i32.into(); + }).unwrap(); + } + }).unwrap(); + + key + } + + fn pull_each_buffer_item( + In(key): In, + world: &mut World, + ) -> Vec { + world.json_buffer_mut(&key, |mut access| { + let mut values = Vec::new(); + while let Ok(Some(value)) = access.pull() { + values.push(value); + } + values + }).unwrap() + } + + #[test] + fn test_drain_json_message() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + let buffer = builder.create_buffer(BufferSettings::keep_all()); + let push_multiple_times = builder.commands().spawn_service( + push_multiple_times_into_buffer.into_blocking_service() + ); + let modify_content = builder.commands().spawn_service( + modify_buffer_content.into_blocking_service() + ); + let drain_content = builder.commands().spawn_service( + drain_buffer_contents.into_blocking_service() + ); + + scope + .input + .chain(builder) + .with_access(buffer) + .then(push_multiple_times) + .then(modify_content) + .then(drain_content) + .connect(scope.terminate); + }); + + let msg = TestMessage::new(); + let mut promise = context.command( + |commands| commands.request(msg, workflow).take_response() + ); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let values = promise.take().available().unwrap(); + assert_eq!(values.len(), 5); + for i in 0..values.len() { + let v_i32 = values[i].get("v_i32").unwrap().as_i64().unwrap(); + assert_eq!(v_i32, i as i64); + } + assert!(context.no_unhandled_errors()); + } + + fn drain_buffer_contents( + In(key): In, + world: &mut World, + ) -> Vec { + world.json_buffer_mut(&key, |mut access| { + access + .drain(..) + .collect::, _>>() + }) + .unwrap() + .unwrap() + } + + #[test] + fn double_json_messages() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + let buffer_double_u32: JsonBuffer = builder.create_buffer::(BufferSettings::default()).into(); + let buffer_double_i32: JsonBuffer = builder.create_buffer::(BufferSettings::default()).into(); + let buffer_double_string: JsonBuffer = builder.create_buffer::(BufferSettings::default()).into(); + + scope + .input + .chain(builder) + .fork_clone(( + |chain: Chain<_>| chain + .map_block(|mut msg: TestMessage| { + msg.v_u32 *= 2; + msg + }) + .connect(buffer_double_u32.downcast::().unwrap().input_slot()), + |chain: Chain<_>| chain + .map_block(|mut msg: TestMessage| { + msg.v_i32 *= 2; + msg + }) + .connect(buffer_double_i32.downcast::().unwrap().input_slot()), + |chain: Chain<_>| chain + .map_block(|mut msg: TestMessage| { + msg.v_string = msg.v_string.clone() + &msg.v_string; + msg + }) + .connect(buffer_double_string.downcast::().unwrap().input_slot()), + )); + + (buffer_double_u32, buffer_double_i32, buffer_double_string) + .join(builder) + .connect(scope.terminate); + }); + + let msg = TestMessage::new(); + let mut promise = context.command( + |commands| commands.request(msg, workflow).take_response() + ); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let (double_u32, double_i32, double_string) = promise.take().available().unwrap(); + assert_eq!(4, double_u32.get("v_u32").unwrap().as_i64().unwrap()); + assert_eq!(2, double_i32.get("v_i32").unwrap().as_i64().unwrap()); + assert_eq!("hellohello", double_string.get("v_string").unwrap().as_str().unwrap()); + assert!(context.no_unhandled_errors()); + } +} diff --git a/src/lib.rs b/src/lib.rs index b9105b54..d94c7ba4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -336,8 +336,9 @@ impl Plugin for ImpulsePlugin { pub mod prelude { pub use crate::{ buffer::{ - AnyBuffer, AnyBufferKey, AnyBufferWorldAccess, Buffer, BufferAccess, BufferAccessMut, - BufferKey, BufferSettings, Bufferable, Buffered, IterBufferable, RetentionPolicy, + AnyBuffer, AnyBufferKey, AnyBufferMut, AnyBufferWorldAccess, AnyMessage, + Buffer, BufferAccess, BufferAccessMut, BufferKey, BufferSettings, Bufferable, + Buffered, IterBufferable, RetentionPolicy, }, builder::Builder, callback::{AsCallback, Callback, IntoAsyncCallback, IntoBlockingCallback}, @@ -362,4 +363,9 @@ pub mod prelude { BlockingCallback, BlockingCallbackInput, BlockingMap, BlockingService, BlockingServiceInput, ContinuousQuery, ContinuousService, ContinuousServiceInput, }; + + #[cfg(feature = "diagram")] + pub use crate::buffer::{ + JsonBuffer, JsonBufferKey, JsonBufferMut, JsonBufferWorldAccess, JsonMessage, + }; } From 0d1224faff63c3d5a10565637ae27ba4ee63a224 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Sun, 2 Feb 2025 16:11:00 +0000 Subject: [PATCH 15/37] Implement JsonBufferView Signed-off-by: Michael X. Grey --- src/buffer/any_buffer.rs | 5 ++ src/buffer/json_buffer.rs | 156 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 154 insertions(+), 7 deletions(-) diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 0285cb12..667b9246 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -381,6 +381,11 @@ impl<'w, 's, 'a> Drop for AnyBufferMut<'w, 's, 'a> { /// This trait allows [`World`] to give you access to any buffer using an /// [`AnyBufferKey`]. pub trait AnyBufferWorldAccess { + + /// Call this to get mutable access to any buffer. + /// + /// Pass in a callback that will receive a [`AnyBufferMut`], allowing it to + /// view and modify the contents of the buffer. fn any_buffer_mut( &mut self, key: &AnyBufferKey, diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index f3b43507..67e8eb74 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -37,11 +37,11 @@ use smallvec::SmallVec; use crate::{ AnyBufferAccessImpl, AnyBufferAccessInterface, AnyBuffer, AnyBufferKey, - AnyRange, Buffer, BufferAccessors, BufferKey, BufferKeyBuilder, BufferAccessLifecycle, - BufferAccessMut, BufferError, BufferStorage, Builder, DrainBuffer, OperationError, - OperationResult, InspectBuffer, ManageBuffer, Gate, GateState, - NotifyBufferUpdate, Bufferable, Buffered, OrBroken, Joined, Accessed, - add_listener_to_source, + AnyRange, Buffer, BufferAccessors, BufferAccess, BufferKey, BufferKeyBuilder, + BufferAccessLifecycle, BufferAccessMut, BufferError, BufferStorage, Builder, + DrainBuffer, OperationError, OperationResult, InspectBuffer, ManageBuffer, + Gate, GateState, NotifyBufferUpdate, Bufferable, Buffered, OrBroken, Joined, + Accessed, add_listener_to_source, }; /// A [`Buffer`] whose message type has been anonymized, but which is known to @@ -171,6 +171,52 @@ impl From for AnyBufferKey { } } +/// Similar to [`BufferView`][crate::BufferView], but this can be unlocked with +/// a [`JsonBufferKey`], so it can work for any buffer whose message types +/// support serialization and deserialization. +pub struct JsonBufferView<'a> { + storage: Box, + gate: &'a GateState, + buffer: Entity, + session: Entity, +} + +impl<'a> JsonBufferView<'a> { + /// Get a serialized copy of the oldest message in the buffer. + pub fn oldest(&self) -> JsonMessageViewResult { + self.storage.json_oldest(self.session) + } + + /// Get a serialized copy of the newest message in the buffer. + pub fn newest(&self) -> JsonMessageViewResult { + self.storage.json_newest(self.session) + } + + /// Get a serialized copy of a message in the buffer. + pub fn get(&self, index: usize) -> JsonMessageViewResult { + self.storage.json_get(self.session, index) + } + + /// Get how many messages are in this buffer. + pub fn len(&self) -> usize { + self.storage.json_count(self.session) + } + + /// Check if the buffer is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Check whether the gate of this buffer is open or closed. + pub fn gate(&self) -> Gate { + self.gate + .map + .get(&self.session) + .copied() + .unwrap_or(Gate::Open) + } +} + /// Similar to [`BufferMut`][crate::BufferMut], but this can be unlocked with a /// [`JsonBufferKey`], so it can work for any buffer whose message types support /// serialization and deserialization. @@ -184,7 +230,7 @@ pub struct JsonBufferMut<'w, 's, 'a> { modified: bool, } -impl<'w, 's, 'a> JsonBufferMut<'w, 's, 'a> { +impl<'w, 's, 'a> JsonBufferMut<'w, 's, 'a> { /// Same as [BufferMut::allow_closed_loops][1]. /// /// [1]: crate::BufferMut::allow_closed_loops @@ -363,6 +409,24 @@ impl<'w, 's, 'a> Drop for JsonBufferMut<'w, 's, 'a> { } pub trait JsonBufferWorldAccess { + /// Call this to get read-only access to any buffer whose message type is + /// serializable and deserializable. + /// + /// Pass in a callback that will receive a [`JsonBufferView`] alongside a + /// shared borrow of the [`World`]. Due to technical reasons this function + /// needs to be called on a `&mut World`, but you can still view the world + /// from inside the callback using the second argument. + fn json_buffer_view( + &mut self, + key: &JsonBufferKey, + f: impl FnOnce(JsonBufferView, &World) -> U, + ) -> Result; + + /// Call this to get mutable access to any buffer whose message type is + /// serializable and deserializable. + /// + /// Pass in a callback that will receive a [`JsonBufferMut`], allowing it to + /// view and modify the contents of the buffer. fn json_buffer_mut( &mut self, key: &JsonBufferKey, @@ -371,6 +435,18 @@ pub trait JsonBufferWorldAccess { } impl JsonBufferWorldAccess for World { + fn json_buffer_view( + &mut self, + key: &JsonBufferKey, + f: impl FnOnce(JsonBufferView, &World) -> U, + ) -> Result { + let interface = key.interface; + let mut state = interface.create_json_buffer_access_state(self); + let access = state.get_json_buffer_access(self); + let buffer_view = access.as_json_buffer_view(key)?; + Ok(f(buffer_view, &self)) + } + fn json_buffer_mut( &mut self, key: &JsonBufferKey, @@ -486,6 +562,27 @@ trait JsonBufferManagement: JsonBufferViewing { fn json_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box; } +impl JsonBufferViewing for &'_ BufferStorage +where + T: 'static + Send + Sync + Serialize + DeserializeOwned, +{ + fn json_count(&self, session: Entity) -> usize { + self.count(session) + } + + fn json_oldest<'a>(&'a self, session: Entity) -> JsonMessageViewResult { + self.oldest(session).map(serde_json::to_value).transpose() + } + + fn json_newest<'a>(&'a self, session: Entity) -> JsonMessageViewResult { + self.newest(session).map(serde_json::to_value).transpose() + } + + fn json_get<'a>(&'a self, session: Entity, index: usize) ->JsonMessageViewResult { + self.get(session, index).map(serde_json::to_value).transpose() + } +} + impl JsonBufferViewing for Mut<'_, BufferStorage> where T: 'static + Send + Sync + Serialize + DeserializeOwned, @@ -598,6 +695,11 @@ trait JsonBufferAccessInterface { session: Entity, ) -> Result; + fn create_json_buffer_access_state( + &self, + world: &mut World, + ) -> Box; + fn create_json_buffer_access_mut_state( &self, world: &mut World, @@ -662,6 +764,13 @@ impl JsonBufferAccessIn serde_json::to_value(value).or_broken() } + fn create_json_buffer_access_state( + &self, + world: &mut World, + ) -> Box { + Box::new(SystemState::>::new(world)) + } + fn create_json_buffer_access_mut_state( &self, world: &mut World, @@ -670,6 +779,19 @@ impl JsonBufferAccessIn } } +trait JsonBufferAccessState { + fn get_json_buffer_access<'s, 'w: 's>(&'s mut self, world: &'w World) -> Box + 's>; +} + +impl JsonBufferAccessState for SystemState> +where + T: 'static + Send + Sync + Serialize + DeserializeOwned, +{ + fn get_json_buffer_access<'s, 'w: 's>(&'s mut self, world: &'w World) -> Box + 's> { + Box::new(self.get(world)) + } +} + trait JsonBufferAccessMutState { fn get_json_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's>; } @@ -683,6 +805,26 @@ where } } +trait JsonBufferAccess<'w, 's> { + fn as_json_buffer_view<'a>(&'a self, key: &JsonBufferKey) -> Result, BufferError>; +} + +impl<'w, 's, T> JsonBufferAccess<'w, 's> for BufferAccess<'w, 's, T> +where + T: 'static + Send + Sync + Serialize + DeserializeOwned, +{ + fn as_json_buffer_view<'a>(&'a self, key: &JsonBufferKey) -> Result, BufferError> { + let BufferAccess { query } = self; + let (storage, gate) = query.get(key.buffer).map_err(|_| BufferError::BufferMissing)?; + Ok(JsonBufferView { + storage: Box::new(storage), + gate, + buffer: key.buffer, + session: key.session, + }) + } +} + trait JsonBufferAccessMut<'w, 's> { fn as_json_buffer_mut<'a>(&'a mut self, key: &JsonBufferKey) -> Result, BufferError>; } @@ -880,7 +1022,7 @@ mod tests { In(key): In, world: &mut World, ) -> usize { - world.json_buffer_mut(&key, |access| { + world.json_buffer_view(&key, |access, _| { access.len() }).unwrap() } From 5dcae458034eaffb531be79ed4e42b58e767a8ad Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Sun, 2 Feb 2025 16:37:17 +0000 Subject: [PATCH 16/37] Implement AnyBufferView Signed-off-by: Michael X. Grey --- src/buffer/any_buffer.rs | 111 ++++++++++++++++++++++++++++++++++++-- src/buffer/json_buffer.rs | 90 ++++++++++--------------------- 2 files changed, 135 insertions(+), 66 deletions(-) diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 667b9246..f9424d69 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -165,6 +165,52 @@ impl From> for AnyBufferKey { } } +/// Similar to [`BufferView`][crate::BufferView], but this can be unlocked with +/// an [`AnyBufferKey`], so it can work for any buffer whose message types +/// support serialization and deserialization. +pub struct AnyBufferView<'a> { + storage: Box, + gate: &'a GateState, + session: Entity, +} + +impl<'a> AnyBufferView<'a> { + /// Look at the oldest message in the buffer. + pub fn oldest(&self) -> Option> { + self.storage.any_oldest(self.session) + } + + /// Look at the newest message in the buffer. + pub fn newest(&self) -> Option> { + self.storage.any_newest(self.session) + } + + /// Borrow a message from the buffer. Index 0 is the oldest message in the buffer + /// while the highest index is the newest message in the buffer. + pub fn get(&self, index: usize) -> Option> { + self.storage.any_get(self.session, index) + } + + /// Get how many messages are in this buffer. + pub fn len(&self) -> usize { + self.storage.any_count(self.session) + } + + /// Check if the buffer is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Check whether the gate of this buffer is open or closed. + pub fn gate(&self) -> Gate { + self.gate + .map + .get(&self.session) + .copied() + .unwrap_or(Gate::Open) + } +} + /// Similar to [`BufferMut`][crate::BufferMut], but this can be unlocked with an /// [`AnyBufferKey`], so it can work for any buffer regardless of the data type /// inside. @@ -198,7 +244,7 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { } /// Borrow a message from the buffer. Index 0 is the oldest message in the buffer - /// with the highest index being the newest message in the buffer. + /// while the highest index is the newest message in the buffer. pub fn get(&self, index: usize) -> Option> { self.storage.any_get(self.session, index) } @@ -381,6 +427,15 @@ impl<'w, 's, 'a> Drop for AnyBufferMut<'w, 's, 'a> { /// This trait allows [`World`] to give you access to any buffer using an /// [`AnyBufferKey`]. pub trait AnyBufferWorldAccess { + /// Call this to get read-only access to any buffer. + /// + /// For technical reasons this requires direct [`World`] access, but you can + /// do other read-only queries on the world while holding onto the + /// [`AnyBufferView`]. + fn any_buffer_view<'a>( + &self, + key: &AnyBufferKey, + ) -> Result, BufferError>; /// Call this to get mutable access to any buffer. /// @@ -394,6 +449,13 @@ pub trait AnyBufferWorldAccess { } impl AnyBufferWorldAccess for World { + fn any_buffer_view<'a>( + &self, + key: &AnyBufferKey, + ) -> Result, BufferError> { + key.interface.create_any_buffer_view(key, self) + } + fn any_buffer_mut( &mut self, key: &AnyBufferKey, @@ -488,6 +550,28 @@ impl std::ops::RangeBounds for AnyRange { pub type AnyMessageRef<'a> = &'a (dyn Any + 'static + Send + Sync); +impl AnyBufferViewing for &'_ BufferStorage { + fn any_count(&self, session: Entity) -> usize { + self.count(session) + } + + fn any_oldest<'a>(&'a self, session: Entity) -> Option> { + self.oldest(session).map(to_any_ref) + } + + fn any_newest<'a>(&'a self, session: Entity) -> Option> { + self.newest(session).map(to_any_ref) + } + + fn any_get<'a>(&'a self, session: Entity, index: usize) -> Option> { + self.get(session, index).map(to_any_ref) + } + + fn any_message_type(&self) -> TypeId { + TypeId::of::() + } +} + impl AnyBufferViewing for Mut<'_, BufferStorage> { fn any_count(&self, session: Entity) -> usize { self.count(session) @@ -644,6 +728,12 @@ pub(crate) trait AnyBufferAccessInterface { session: Entity, ) -> Result; + fn create_any_buffer_view<'a>( + &self, + key: &AnyBufferKey, + world: &'a World, + ) -> Result, BufferError>; + fn create_any_buffer_access_mut_state( &self, world: &mut World, @@ -702,6 +792,21 @@ impl AnyBufferAccessInterface for AnyBufferAcces entity_mut.pull_from_buffer::(session).map(to_any_message) } + fn create_any_buffer_view<'a>( + &self, + key: &AnyBufferKey, + world: &'a World, + ) -> Result, BufferError> { + let buffer_ref = world.get_entity(key.buffer).ok_or(BufferError::BufferMissing)?; + let storage = buffer_ref.get::>().ok_or(BufferError::BufferMissing)?; + let gate = buffer_ref.get::().ok_or(BufferError::BufferMissing)?; + Ok(AnyBufferView { + storage: Box::new(storage), + gate, + session: key.session + }) + } + fn create_any_buffer_access_mut_state( &self, world: &mut World, @@ -865,9 +970,7 @@ mod tests { In(key): In, world: &mut World, ) -> usize { - world.any_buffer_mut(&key, |access| { - access.len() - }).unwrap() + world.any_buffer_view(&key).unwrap().len() } #[test] diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 67e8eb74..c759d8bd 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -37,7 +37,7 @@ use smallvec::SmallVec; use crate::{ AnyBufferAccessImpl, AnyBufferAccessInterface, AnyBuffer, AnyBufferKey, - AnyRange, Buffer, BufferAccessors, BufferAccess, BufferKey, BufferKeyBuilder, + AnyRange, Buffer, BufferAccessors, BufferKey, BufferKeyBuilder, BufferAccessLifecycle, BufferAccessMut, BufferError, BufferStorage, Builder, DrainBuffer, OperationError, OperationResult, InspectBuffer, ManageBuffer, Gate, GateState, NotifyBufferUpdate, Bufferable, Buffered, OrBroken, Joined, @@ -177,7 +177,6 @@ impl From for AnyBufferKey { pub struct JsonBufferView<'a> { storage: Box, gate: &'a GateState, - buffer: Entity, session: Entity, } @@ -412,15 +411,13 @@ pub trait JsonBufferWorldAccess { /// Call this to get read-only access to any buffer whose message type is /// serializable and deserializable. /// - /// Pass in a callback that will receive a [`JsonBufferView`] alongside a - /// shared borrow of the [`World`]. Due to technical reasons this function - /// needs to be called on a `&mut World`, but you can still view the world - /// from inside the callback using the second argument. - fn json_buffer_view( - &mut self, + /// For technical reasons this requires direct [`World`] access, but you can + /// do other read-only queries on the world while holding onto the + /// [`JsonBufferView`]. + fn json_buffer_view( + &self, key: &JsonBufferKey, - f: impl FnOnce(JsonBufferView, &World) -> U, - ) -> Result; + ) -> Result, BufferError>; /// Call this to get mutable access to any buffer whose message type is /// serializable and deserializable. @@ -435,16 +432,11 @@ pub trait JsonBufferWorldAccess { } impl JsonBufferWorldAccess for World { - fn json_buffer_view( - &mut self, + fn json_buffer_view( + &self, key: &JsonBufferKey, - f: impl FnOnce(JsonBufferView, &World) -> U, - ) -> Result { - let interface = key.interface; - let mut state = interface.create_json_buffer_access_state(self); - let access = state.get_json_buffer_access(self); - let buffer_view = access.as_json_buffer_view(key)?; - Ok(f(buffer_view, &self)) + ) -> Result, BufferError> { + key.interface.create_json_buffer_view(key, self) } fn json_buffer_mut( @@ -695,10 +687,11 @@ trait JsonBufferAccessInterface { session: Entity, ) -> Result; - fn create_json_buffer_access_state( + fn create_json_buffer_view<'a>( &self, - world: &mut World, - ) -> Box; + key: &JsonBufferKey, + world: &'a World, + ) -> Result, BufferError>; fn create_json_buffer_access_mut_state( &self, @@ -764,11 +757,19 @@ impl JsonBufferAccessIn serde_json::to_value(value).or_broken() } - fn create_json_buffer_access_state( + fn create_json_buffer_view<'a>( &self, - world: &mut World, - ) -> Box { - Box::new(SystemState::>::new(world)) + key: &JsonBufferKey, + world: &'a World, + ) -> Result, BufferError> { + let buffer_ref = world.get_entity(key.buffer).ok_or(BufferError::BufferMissing)?; + let storage = buffer_ref.get::>().ok_or(BufferError::BufferMissing)?; + let gate = buffer_ref.get::().ok_or(BufferError::BufferMissing)?; + Ok(JsonBufferView { + storage: Box::new(storage), + gate, + session: key.session, + }) } fn create_json_buffer_access_mut_state( @@ -779,19 +780,6 @@ impl JsonBufferAccessIn } } -trait JsonBufferAccessState { - fn get_json_buffer_access<'s, 'w: 's>(&'s mut self, world: &'w World) -> Box + 's>; -} - -impl JsonBufferAccessState for SystemState> -where - T: 'static + Send + Sync + Serialize + DeserializeOwned, -{ - fn get_json_buffer_access<'s, 'w: 's>(&'s mut self, world: &'w World) -> Box + 's> { - Box::new(self.get(world)) - } -} - trait JsonBufferAccessMutState { fn get_json_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's>; } @@ -805,26 +793,6 @@ where } } -trait JsonBufferAccess<'w, 's> { - fn as_json_buffer_view<'a>(&'a self, key: &JsonBufferKey) -> Result, BufferError>; -} - -impl<'w, 's, T> JsonBufferAccess<'w, 's> for BufferAccess<'w, 's, T> -where - T: 'static + Send + Sync + Serialize + DeserializeOwned, -{ - fn as_json_buffer_view<'a>(&'a self, key: &JsonBufferKey) -> Result, BufferError> { - let BufferAccess { query } = self; - let (storage, gate) = query.get(key.buffer).map_err(|_| BufferError::BufferMissing)?; - Ok(JsonBufferView { - storage: Box::new(storage), - gate, - buffer: key.buffer, - session: key.session, - }) - } -} - trait JsonBufferAccessMut<'w, 's> { fn as_json_buffer_mut<'a>(&'a mut self, key: &JsonBufferKey) -> Result, BufferError>; } @@ -1022,9 +990,7 @@ mod tests { In(key): In, world: &mut World, ) -> usize { - world.json_buffer_view(&key, |access, _| { - access.len() - }).unwrap() + world.json_buffer_view(&key).unwrap().len() } #[test] From ca7a226ed8ac7fb6346e2c353688708129f1d18d Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Sun, 2 Feb 2025 18:15:34 +0000 Subject: [PATCH 17/37] Draft an example implementation of JoinedValue Signed-off-by: Michael X. Grey --- src/buffer/any_buffer.rs | 12 ++ src/buffer/buffer_map.rs | 308 ++++++++++++++++++++++++++++++++++++--- src/builder.rs | 18 ++- src/lib.rs | 3 +- 4 files changed, 319 insertions(+), 22 deletions(-) diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index f9424d69..7d895c8d 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -49,6 +49,18 @@ pub struct AnyBuffer { pub(crate) interface: &'static (dyn AnyBufferAccessInterface + Send + Sync) } +impl AnyBuffer { + /// The buffer ID for this key. + pub fn id(&self) -> Entity { + self.source + } + + /// Get the type ID of the messages that this buffer supports. + pub fn message_type_id(&self) -> TypeId { + self.interface.message_type_id() + } +} + impl std::fmt::Debug for AnyBuffer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("AnyBuffer") diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index bf94bafe..6583aea1 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -28,18 +28,35 @@ use smallvec::SmallVec; use bevy_ecs::prelude::{Entity, World}; use crate::{ - AnyBuffer, OperationError, OperationResult, OperationRoster, Buffered, Gate, - Joined, Accessed, BufferKeyBuilder, AnyBufferKey, + AnyBuffer, AddOperation, Chain, OperationError, OperationResult, OperationRoster, Buffered, Gate, + Join, Joined, Accessed, BufferKeyBuilder, AnyBufferKey, Builder, Output, UnusedTarget, GateState, + add_listener_to_source, }; -#[derive(Clone)] +#[derive(Clone, Default)] pub struct BufferMap { inner: HashMap, AnyBuffer>, } +impl BufferMap { + /// Insert a named buffer into the map. + pub fn insert( + &mut self, + name: Cow<'static, str>, + buffer: impl Into, + ) { + self.inner.insert(name, buffer.into()); + } + + /// Get one of the buffers from the map by its name. + pub fn get(&self, name: &str) -> Option<&AnyBuffer> { + self.inner.get(name) + } +} + /// This error is used when the buffers provided for an input are not compatible /// with the layout. -#[derive(ThisError, Debug, Clone)] +#[derive(ThisError, Debug, Clone, Default)] #[error("the incoming buffer map is incompatible with the layout")] pub struct IncompatibleLayout { /// Names of buffers that were missing from the incoming buffer map. @@ -48,6 +65,38 @@ pub struct IncompatibleLayout { pub incompatible_buffers: Vec, } +impl IncompatibleLayout { + /// Check whether a named buffer is compatible with a specific type. + pub fn require_buffer( + &mut self, + expected_name: &str, + buffers: &BufferMap, + ) { + if let Some((name, buffer)) = buffers.inner.get_key_value(expected_name) { + if buffer.message_type_id() != TypeId::of::() { + self.incompatible_buffers.push(BufferIncompatibility { + name: name.clone(), + expected: TypeId::of::(), + received: buffer.message_type_id(), + }); + } + } else { + self.missing_buffers.push(Cow::Owned(expected_name.to_owned())); + } + } + + /// Convert the instance into a result. If any field has content in it, then + /// this will produce an [`Err`]. Otherwise if no incompatibilities are + /// present, this will produce an [`Ok`]. + pub fn into_result(self) -> Result<(), IncompatibleLayout> { + if self.missing_buffers.is_empty() && self.incompatible_buffers.is_empty() { + Ok(()) + } else { + Err(self) + } + } +} + /// Difference between the expected and received types of a named buffer. #[derive(Debug, Clone)] pub struct BufferIncompatibility { @@ -69,11 +118,22 @@ pub trait BufferMapLayout: Sized { world: &World, ) -> Result; + fn ensure_active_session( + buffers: &BufferMap, + session: Entity, + world: &mut World, + ) -> OperationResult; + fn add_listener( buffers: &BufferMap, listener: Entity, world: &mut World, - ) -> OperationResult; + ) -> OperationResult { + for buffer in buffers.inner.values() { + add_listener_to_source(buffer.id(), listener, world)?; + } + Ok(()) + } fn gate_action( buffers: &BufferMap, @@ -81,29 +141,59 @@ pub trait BufferMapLayout: Sized { action: Gate, world: &mut World, roster: &mut OperationRoster, - ) -> OperationResult; - - fn as_input(buffers: &BufferMap) -> SmallVec<[Entity; 8]>; + ) -> OperationResult { + for buffer in buffers.inner.values() { + GateState::apply(buffer.id(), session, action, world, roster)?; + } + Ok(()) + } - fn ensure_active_session( - buffers: &BufferMap, - session: Entity, - world: &mut World, - ) -> OperationResult; + fn as_input(buffers: &BufferMap) -> SmallVec<[Entity; 8]> { + let mut inputs = SmallVec::new(); + for buffer in buffers.inner.values() { + inputs.push(buffer.id()); + } + inputs + } } -pub trait JoinedValue: BufferMapLayout { - fn buffered_count( - buffers: &BufferMap, - session: Entity, - world: &World, - ) -> Result; +pub trait JoinedValue: 'static + BufferMapLayout + Send + Sync{ + /// This associated type must represent a buffer map layout that is + /// guaranteed to be compatible for this JoinedValue. Failure to implement + /// this trait accordingly will result in panics. + type Buffers: Into; fn pull( buffers: &BufferMap, session: Entity, - world: &World, + world: &mut World, ) -> Result; + + fn join_into<'w, 's, 'a, 'b>( + buffers: Self::Buffers, + builder: &'b mut Builder<'w, 's, 'a>, + ) -> Chain<'w, 's, 'a, 'b, Self> { + Self::try_join_into(buffers.into(), builder).unwrap() + } + + fn try_join_into<'w, 's, 'a, 'b>( + buffers: BufferMap, + builder: &'b mut Builder<'w, 's, 'a>, + ) -> Result, IncompatibleLayout> { + let scope = builder.scope(); + let buffers = BufferedMap::::new(buffers)?; + buffers.verify_scope(scope); + + let join = builder.commands.spawn(()).id(); + let target = builder.commands.spawn(UnusedTarget).id(); + builder.commands.add(AddOperation::new( + Some(scope), + join, + Join::new(buffers, target), + )); + + Ok(Output::new(scope, target).chain(builder)) + } } /// Trait to describe a layout of buffer keys @@ -126,6 +216,13 @@ struct BufferedMap { _ignore: std::marker::PhantomData, } +impl BufferedMap { + fn new(map: BufferMap) -> Result { + K::is_compatible(&map)?; + Ok(Self { map, _ignore: Default::default() }) + } +} + impl Clone for BufferedMap { fn clone(&self) -> Self { Self { map: self.map.clone(), _ignore: Default::default() } @@ -274,3 +371,174 @@ impl BufferMapLayout for AnyBufferKeyMap { Ok(()) } } + +#[cfg(test)] +mod tests { + use std::borrow::Cow; + + use crate::{ + prelude::*, + testing::*, + OperationResult, OperationError, OrBroken, InspectBuffer, ManageBuffer, BufferMap, + }; + + use bevy_ecs::prelude::World; + + #[derive(Clone)] + struct TestJoinedValue { + integer: i64, + float: f64, + string: String, + } + + impl BufferMapLayout for TestJoinedValue { + fn is_compatible(buffers: &BufferMap) -> Result<(), IncompatibleLayout> { + let mut compatibility = IncompatibleLayout::default(); + compatibility.require_buffer::("integer", buffers); + compatibility.require_buffer::("float", buffers); + compatibility.require_buffer::("string", buffers); + compatibility.into_result() + } + + fn buffered_count( + buffers: &BufferMap, + session: Entity, + world: &World, + ) -> Result { + let integer_count = world + .get_entity(buffers.get("integer").unwrap().id()) + .or_broken()? + .buffered_count::(session)?; + + let float_count = world + .get_entity(buffers.get("float").unwrap().id()) + .or_broken()? + .buffered_count::(session)?; + + let string_count = world + .get_entity(buffers.get("string").unwrap().id()) + .or_broken()? + .buffered_count::(session)?; + + Ok( + [ + integer_count, + float_count, + string_count, + ] + .iter() + .min() + .copied() + .unwrap_or(0) + ) + } + + fn ensure_active_session( + buffers: &BufferMap, + session: Entity, + world: &mut World, + ) -> OperationResult { + world + .get_entity_mut(buffers.get("integer").unwrap().id()) + .or_broken()? + .ensure_session::(session)?; + + world + .get_entity_mut(buffers.get("float").unwrap().id()) + .or_broken()? + .ensure_session::(session)?; + + world + .get_entity_mut(buffers.get("string").unwrap().id()) + .or_broken()? + .ensure_session::(session)?; + + Ok(()) + } + } + + impl JoinedValue for TestJoinedValue { + type Buffers = TestJoinedValueBuffers; + + fn pull( + buffers: &BufferMap, + session: Entity, + world: &mut World, + ) -> Result { + let integer = world + .get_entity_mut(buffers.get("integer").unwrap().id()) + .or_broken()? + .pull_from_buffer::(session)?; + + let float = world + .get_entity_mut(buffers.get("float").unwrap().id()) + .or_broken()? + .pull_from_buffer::(session)?; + + let string = world + .get_entity_mut(buffers.get("string").unwrap().id()) + .or_broken()? + .pull_from_buffer::(session)?; + + Ok(Self { integer, float, string }) + } + } + + struct TestJoinedValueBuffers { + integer: Buffer, + float: Buffer, + string: Buffer, + } + + impl From for BufferMap { + fn from(value: TestJoinedValueBuffers) -> Self { + let mut buffers = BufferMap::default(); + buffers.insert(Cow::Borrowed("integer"), value.integer); + buffers.insert(Cow::Borrowed("float"), value.float); + buffers.insert(Cow::Borrowed("string"), value.string); + buffers + } + } + + #[test] + fn test_joined_value() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + let buffer_i64 = builder.create_buffer(BufferSettings::default()); + let buffer_f64 = builder.create_buffer(BufferSettings::default()); + let buffer_string = builder.create_buffer(BufferSettings::default()); + + let mut buffers = BufferMap::default(); + buffers.insert(Cow::Borrowed("integer"), buffer_i64); + buffers.insert(Cow::Borrowed("float"), buffer_f64); + buffers.insert(Cow::Borrowed("string"), buffer_string); + + scope + .input + .chain(builder) + .fork_unzip(( + |chain: Chain<_>| chain.connect(buffer_i64.input_slot()), + |chain: Chain<_>| chain.connect(buffer_f64.input_slot()), + |chain: Chain<_>| chain.connect(buffer_string.input_slot()), + )); + + builder.try_join_into(buffers).unwrap().connect(scope.terminate); + }); + + let mut promise = context.command( + |commands| commands.request( + (5_i64, 3.14_f64, "hello".to_string()), + workflow, + ) + .take_response() + ); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let value: TestJoinedValue = promise.take().available().unwrap(); + assert_eq!(value.integer, 5); + assert_eq!(value.float, 3.14); + assert_eq!(value.string, "hello"); + assert!(context.no_unhandled_errors()); + } +} diff --git a/src/builder.rs b/src/builder.rs index eae97efa..9b953750 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -29,7 +29,7 @@ use crate::{ OperateBufferAccess, OperateDynamicGate, OperateScope, OperateSplit, OperateStaticGate, Output, Provider, RequestOfMap, ResponseOfMap, Scope, ScopeEndpoints, ScopeSettings, ScopeSettingsStorage, Sendish, Service, SplitOutputs, Splittable, StreamPack, StreamTargetMap, - StreamsOfMap, Trim, TrimBranch, UnusedTarget, + StreamsOfMap, Trim, TrimBranch, UnusedTarget, JoinedValue, BufferMap, IncompatibleLayout, }; pub(crate) mod connect; @@ -239,6 +239,22 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { buffers.join(self) } + /// Try joining a map of buffers into a single value. + pub fn try_join_into<'b, Joined: JoinedValue>( + &'b mut self, + buffers: impl Into, + ) -> Result, IncompatibleLayout> { + Joined::try_join_into(buffers.into(), self) + } + + /// Join an appropriate layout of buffers into a single value. + pub fn join_into<'b, Joined: JoinedValue>( + &'b mut self, + buffers: Joined::Buffers, + ) -> Chain<'w, 's, 'a, 'b, Joined> { + Joined::join_into(buffers, self) + } + /// Alternative way of calling [`Bufferable::listen`]. pub fn listen<'b, B: Bufferable>( &'b mut self, diff --git a/src/lib.rs b/src/lib.rs index d94c7ba4..4c11e693 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -338,7 +338,8 @@ pub mod prelude { buffer::{ AnyBuffer, AnyBufferKey, AnyBufferMut, AnyBufferWorldAccess, AnyMessage, Buffer, BufferAccess, BufferAccessMut, BufferKey, BufferSettings, Bufferable, - Buffered, IterBufferable, RetentionPolicy, + Buffered, IterBufferable, RetentionPolicy, BufferMapLayout, JoinedValue, BufferKeyMap, + IncompatibleLayout, }, builder::Builder, callback::{AsCallback, Callback, IntoAsyncCallback, IntoBlockingCallback}, From 096ccf4eef665f3b50774274b97e151196ad1ade Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Sun, 2 Feb 2025 18:30:44 +0000 Subject: [PATCH 18/37] Fix formatting Signed-off-by: Michael X. Grey --- src/buffer/any_buffer.rs | 361 ++++++++++++++-------------- src/buffer/buffer_map.rs | 108 ++++----- src/buffer/buffer_storage.rs | 2 +- src/buffer/bufferable.rs | 4 +- src/buffer/buffered.rs | 4 +- src/buffer/json_buffer.rs | 450 +++++++++++++++++++++-------------- src/buffer/manage_buffer.rs | 7 +- src/builder.rs | 12 +- src/lib.rs | 8 +- src/operation/listen.rs | 2 +- src/operation/scope.rs | 15 +- 11 files changed, 529 insertions(+), 444 deletions(-) diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 7d895c8d..3e098a1d 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -21,11 +21,11 @@ use std::{ any::{Any, TypeId}, collections::HashMap, ops::RangeBounds, - sync::{Arc, OnceLock, Mutex}, + sync::{Arc, Mutex, OnceLock}, }; use bevy_ecs::{ - prelude::{Entity, EntityRef, EntityWorldMut, Commands, Mut, World}, + prelude::{Commands, Entity, EntityRef, EntityWorldMut, Mut, World}, system::SystemState, }; @@ -34,10 +34,10 @@ use thiserror::Error as ThisError; use smallvec::SmallVec; use crate::{ - Buffer, Buffered, Bufferable, BufferAccessLifecycle, BufferAccessMut, BufferAccessors, - BufferError, BufferKey, BufferStorage, Builder, DrainBuffer, NotifyBufferUpdate, - GateState, Gate, OperationResult, OperationError, OperationRoster, OrBroken, InspectBuffer, - ManageBuffer, Joined, Accessed, add_listener_to_source, + add_listener_to_source, Accessed, Buffer, BufferAccessLifecycle, BufferAccessMut, + BufferAccessors, BufferError, BufferKey, BufferStorage, Bufferable, Buffered, Builder, + DrainBuffer, Gate, GateState, InspectBuffer, Joined, ManageBuffer, NotifyBufferUpdate, + OperationError, OperationResult, OperationRoster, OrBroken, }; /// A [`Buffer`] whose message type has been anonymized. Joining with this buffer @@ -46,7 +46,7 @@ use crate::{ pub struct AnyBuffer { pub(crate) scope: Entity, pub(crate) source: Entity, - pub(crate) interface: &'static (dyn AnyBufferAccessInterface + Send + Sync) + pub(crate) interface: &'static (dyn AnyBufferAccessInterface + Send + Sync), } impl AnyBuffer { @@ -153,12 +153,14 @@ impl AnyBufferKey { impl std::fmt::Debug for AnyBufferKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f - .debug_struct("AnyBufferKey") + f.debug_struct("AnyBufferKey") .field("buffer", &self.buffer) .field("session", &self.session) .field("accessor", &self.accessor) - .field("in_use", &self.lifecycle.as_ref().is_some_and(|l| l.is_in_use())) + .field( + "in_use", + &self.lifecycle.as_ref().is_some_and(|l| l.is_in_use()), + ) .field("message_type_name", &self.interface.message_type_name()) .finish() } @@ -303,7 +305,7 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { pub fn drain>(&mut self, range: R) -> DrainAnyBuffer<'_> { self.modified = true; DrainAnyBuffer { - interface: self.storage.any_drain(self.session, AnyRange::new(range)) + interface: self.storage.any_drain(self.session, AnyRange::new(range)), } } @@ -363,7 +365,10 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { /// the buffer. /// /// The result follows the same rules as [`Self::push`]. - pub fn push_as_oldest(&mut self, value: T) -> Result, T> { + pub fn push_as_oldest( + &mut self, + value: T, + ) -> Result, T> { if TypeId::of::() != self.storage.any_message_type() { return Err(value); } @@ -385,7 +390,10 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { /// the buffer. /// /// The result follows the same rules as [`Self::push_any`]. - pub fn push_any_as_oldest(&mut self, value: AnyMessage) -> Result, AnyMessageError> { + pub fn push_any_as_oldest( + &mut self, + value: AnyMessage, + ) -> Result, AnyMessageError> { self.storage.any_push_as_oldest(self.session, value) } @@ -444,10 +452,7 @@ pub trait AnyBufferWorldAccess { /// For technical reasons this requires direct [`World`] access, but you can /// do other read-only queries on the world while holding onto the /// [`AnyBufferView`]. - fn any_buffer_view<'a>( - &self, - key: &AnyBufferKey, - ) -> Result, BufferError>; + fn any_buffer_view<'a>(&self, key: &AnyBufferKey) -> Result, BufferError>; /// Call this to get mutable access to any buffer. /// @@ -461,10 +466,7 @@ pub trait AnyBufferWorldAccess { } impl AnyBufferWorldAccess for World { - fn any_buffer_view<'a>( - &self, - key: &AnyBufferKey, - ) -> Result, BufferError> { + fn any_buffer_view<'a>(&self, key: &AnyBufferKey) -> Result, BufferError> { key.interface.create_any_buffer_view(key, self) } @@ -497,7 +499,11 @@ trait AnyBufferManagement: AnyBufferViewing { fn any_oldest_mut<'a>(&'a mut self, session: Entity) -> Option>; fn any_newest_mut<'a>(&'a mut self, session: Entity) -> Option>; fn any_get_mut<'a>(&'a mut self, session: Entity, index: usize) -> Option>; - fn any_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box; + fn any_drain<'a>( + &'a mut self, + session: Entity, + range: AnyRange, + ) -> Box; } pub(crate) struct AnyRange { @@ -606,7 +612,6 @@ impl AnyBufferViewing for Mut<'_, BufferStorage< } } - pub type AnyMessageMut<'a> = &'a mut (dyn Any + 'static + Send + Sync); pub type AnyMessage = Box; @@ -655,7 +660,11 @@ impl AnyBufferManagement for Mut<'_, BufferStora self.get_mut(session, index).map(to_any_mut) } - fn any_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box { + fn any_drain<'a>( + &'a mut self, + session: Entity, + range: AnyRange, + ) -> Box { Box::new(self.drain(session, range)) } } @@ -676,35 +685,51 @@ fn from_any_message(value: AnyMessage) -> Result where T: 'static, { - let value = value.downcast::().map_err(|value| { - AnyMessageError { - value, - type_id: TypeId::of::(), - type_name: std::any::type_name::(), - } + let value = value.downcast::().map_err(|value| AnyMessageError { + value, + type_id: TypeId::of::(), + type_name: std::any::type_name::(), })?; Ok(*value) } pub(crate) trait AnyBufferAccessMutState { - fn get_any_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's>; + fn get_any_buffer_access_mut<'s, 'w: 's>( + &'s mut self, + world: &'w mut World, + ) -> Box + 's>; } -impl AnyBufferAccessMutState for SystemState> { - fn get_any_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's> { +impl AnyBufferAccessMutState + for SystemState> +{ + fn get_any_buffer_access_mut<'s, 'w: 's>( + &'s mut self, + world: &'w mut World, + ) -> Box + 's> { Box::new(self.get_mut(world)) } } pub(crate) trait AnyBufferAccessMut<'w, 's> { - fn as_any_buffer_mut<'a>(&'a mut self, key: &AnyBufferKey) -> Result, BufferError>; + fn as_any_buffer_mut<'a>( + &'a mut self, + key: &AnyBufferKey, + ) -> Result, BufferError>; } -impl<'w, 's, T: 'static + Send + Sync + Any> AnyBufferAccessMut<'w, 's> for BufferAccessMut<'w, 's, T> { - fn as_any_buffer_mut<'a>(&'a mut self, key: &AnyBufferKey) -> Result, BufferError> { +impl<'w, 's, T: 'static + Send + Sync + Any> AnyBufferAccessMut<'w, 's> + for BufferAccessMut<'w, 's, T> +{ + fn as_any_buffer_mut<'a>( + &'a mut self, + key: &AnyBufferKey, + ) -> Result, BufferError> { let BufferAccessMut { query, commands } = self; - let (storage, gate) = query.get_mut(key.buffer).map_err(|_| BufferError::BufferMissing)?; + let (storage, gate) = query + .get_mut(key.buffer) + .map_err(|_| BufferError::BufferMissing)?; Ok(AnyBufferMut { storage: Box::new(storage), gate, @@ -722,17 +747,9 @@ pub(crate) trait AnyBufferAccessInterface { fn message_type_name(&self) -> &'static str; - fn buffered_count( - &self, - entity: &EntityRef, - session: Entity, - ) -> Result; + fn buffered_count(&self, entity: &EntityRef, session: Entity) -> Result; - fn ensure_session( - &self, - entity_mut: &mut EntityWorldMut, - session: Entity, - ) -> OperationResult; + fn ensure_session(&self, entity_mut: &mut EntityWorldMut, session: Entity) -> OperationResult; fn pull( &self, @@ -756,18 +773,17 @@ pub(crate) struct AnyBufferAccessImpl(std::marker::PhantomData); impl AnyBufferAccessImpl { pub(crate) fn get_interface() -> &'static (dyn AnyBufferAccessInterface + Send + Sync) { - const INTERFACE_MAP: OnceLock>> = OnceLock::new(); + const INTERFACE_MAP: OnceLock< + Mutex>, + > = OnceLock::new(); let binding = INTERFACE_MAP; let interfaces = binding.get_or_init(|| Mutex::default()); let mut interfaces_mut = interfaces.lock().unwrap(); - *interfaces_mut.entry(TypeId::of::()).or_insert_with(|| { - Box::leak(Box::new(AnyBufferAccessImpl::(Default::default()))) - }) + *interfaces_mut + .entry(TypeId::of::()) + .or_insert_with(|| Box::leak(Box::new(AnyBufferAccessImpl::(Default::default())))) } } @@ -780,19 +796,11 @@ impl AnyBufferAccessInterface for AnyBufferAcces std::any::type_name::() } - fn buffered_count( - &self, - entity: &EntityRef, - session: Entity, - ) -> Result { + fn buffered_count(&self, entity: &EntityRef, session: Entity) -> Result { entity.buffered_count::(session) } - fn ensure_session( - &self, - entity_mut: &mut EntityWorldMut, - session: Entity, - ) -> OperationResult { + fn ensure_session(&self, entity_mut: &mut EntityWorldMut, session: Entity) -> OperationResult { entity_mut.ensure_session::(session) } @@ -801,7 +809,9 @@ impl AnyBufferAccessInterface for AnyBufferAcces entity_mut: &mut EntityWorldMut, session: Entity, ) -> Result { - entity_mut.pull_from_buffer::(session).map(to_any_message) + entity_mut + .pull_from_buffer::(session) + .map(to_any_message) } fn create_any_buffer_view<'a>( @@ -809,13 +819,19 @@ impl AnyBufferAccessInterface for AnyBufferAcces key: &AnyBufferKey, world: &'a World, ) -> Result, BufferError> { - let buffer_ref = world.get_entity(key.buffer).ok_or(BufferError::BufferMissing)?; - let storage = buffer_ref.get::>().ok_or(BufferError::BufferMissing)?; - let gate = buffer_ref.get::().ok_or(BufferError::BufferMissing)?; + let buffer_ref = world + .get_entity(key.buffer) + .ok_or(BufferError::BufferMissing)?; + let storage = buffer_ref + .get::>() + .ok_or(BufferError::BufferMissing)?; + let gate = buffer_ref + .get::() + .ok_or(BufferError::BufferMissing)?; Ok(AnyBufferView { storage: Box::new(storage), gate, - session: key.session + session: key.session, }) } @@ -931,8 +947,8 @@ impl Accessed for AnyBuffer { #[cfg(test)] mod tests { - use bevy_ecs::prelude::World; use crate::{prelude::*, testing::*}; + use bevy_ecs::prelude::World; #[test] fn test_any_count() { @@ -940,12 +956,12 @@ mod tests { let workflow = context.spawn_io_workflow(|scope, builder| { let buffer = builder.create_buffer(BufferSettings::keep_all()); - let push_multiple_times = builder.commands().spawn_service( - push_multiple_times_into_buffer.into_blocking_service() - ); - let count = builder.commands().spawn_service( - get_buffer_count.into_blocking_service() - ); + let push_multiple_times = builder + .commands() + .spawn_service(push_multiple_times_into_buffer.into_blocking_service()); + let count = builder + .commands() + .spawn_service(get_buffer_count.into_blocking_service()); scope .input @@ -956,9 +972,7 @@ mod tests { .connect(scope.terminate); }); - let mut promise = context.command( - |commands| commands.request(1, workflow).take_response() - ); + let mut promise = context.command(|commands| commands.request(1, workflow).take_response()); context.run_with_conditions(&mut promise, Duration::from_secs(2)); let count = promise.take().available().unwrap(); @@ -978,10 +992,7 @@ mod tests { key.into() } - fn get_buffer_count( - In(key): In, - world: &mut World, - ) -> usize { + fn get_buffer_count(In(key): In, world: &mut World) -> usize { world.any_buffer_view(&key).unwrap().len() } @@ -991,15 +1002,15 @@ mod tests { let workflow = context.spawn_io_workflow(|scope, builder| { let buffer = builder.create_buffer(BufferSettings::keep_all()); - let push_multiple_times = builder.commands().spawn_service( - push_multiple_times_into_buffer.into_blocking_service() - ); - let modify_content = builder.commands().spawn_service( - modify_buffer_content.into_blocking_service() - ); - let drain_content = builder.commands().spawn_service( - pull_each_buffer_item.into_blocking_service() - ); + let push_multiple_times = builder + .commands() + .spawn_service(push_multiple_times_into_buffer.into_blocking_service()); + let modify_content = builder + .commands() + .spawn_service(modify_buffer_content.into_blocking_service()); + let drain_content = builder + .commands() + .spawn_service(pull_each_buffer_item.into_blocking_service()); scope .input @@ -1011,9 +1022,7 @@ mod tests { .connect(scope.terminate); }); - let mut promise = context.command( - |commands| commands.request(3, workflow).take_response() - ); + let mut promise = context.command(|commands| commands.request(3, workflow).take_response()); context.run_with_conditions(&mut promise, Duration::from_secs(2)); let values = promise.take().available().unwrap(); @@ -1021,32 +1030,30 @@ mod tests { assert!(context.no_unhandled_errors()); } - fn modify_buffer_content( - In(key): In, - world: &mut World, - ) -> AnyBufferKey { - world.any_buffer_mut(&key, |mut access| { - for i in 0..access.len() { - access.get_mut(i).map(|value| { - *value.downcast_mut::().unwrap() *= i; - }); - } - }).unwrap(); + fn modify_buffer_content(In(key): In, world: &mut World) -> AnyBufferKey { + world + .any_buffer_mut(&key, |mut access| { + for i in 0..access.len() { + access.get_mut(i).map(|value| { + *value.downcast_mut::().unwrap() *= i; + }); + } + }) + .unwrap(); key } - fn pull_each_buffer_item( - In(key): In, - world: &mut World, - ) -> Vec { - world.any_buffer_mut(&key, |mut access| { - let mut values = Vec::new(); - while let Some(value) = access.pull() { - values.push(*value.downcast::().unwrap()); - } - values - }).unwrap() + fn pull_each_buffer_item(In(key): In, world: &mut World) -> Vec { + world + .any_buffer_mut(&key, |mut access| { + let mut values = Vec::new(); + while let Some(value) = access.pull() { + values.push(*value.downcast::().unwrap()); + } + values + }) + .unwrap() } #[test] @@ -1055,15 +1062,15 @@ mod tests { let workflow = context.spawn_io_workflow(|scope, builder| { let buffer = builder.create_buffer(BufferSettings::keep_all()); - let push_multiple_times = builder.commands().spawn_service( - push_multiple_times_into_buffer.into_blocking_service() - ); - let modify_content = builder.commands().spawn_service( - modify_buffer_content.into_blocking_service() - ); - let drain_content = builder.commands().spawn_service( - drain_buffer_contents.into_blocking_service() - ); + let push_multiple_times = builder + .commands() + .spawn_service(push_multiple_times_into_buffer.into_blocking_service()); + let modify_content = builder + .commands() + .spawn_service(modify_buffer_content.into_blocking_service()); + let drain_content = builder + .commands() + .spawn_service(drain_buffer_contents.into_blocking_service()); scope .input @@ -1075,9 +1082,7 @@ mod tests { .connect(scope.terminate); }); - let mut promise = context.command( - |commands| commands.request(3, workflow).take_response() - ); + let mut promise = context.command(|commands| commands.request(3, workflow).take_response()); context.run_with_conditions(&mut promise, Duration::from_secs(2)); let values = promise.take().available().unwrap(); @@ -1085,59 +1090,67 @@ mod tests { assert!(context.no_unhandled_errors()); } - fn drain_buffer_contents( - In(key): In, - world: &mut World, - ) -> Vec { - world.any_buffer_mut(&key, |mut access| { - access - .drain(..) - .map(|value| *value.downcast::().unwrap()) - .collect() - }).unwrap() + fn drain_buffer_contents(In(key): In, world: &mut World) -> Vec { + world + .any_buffer_mut(&key, |mut access| { + access + .drain(..) + .map(|value| *value.downcast::().unwrap()) + .collect() + }) + .unwrap() } #[test] fn double_any_messages() { let mut context = TestingContext::minimal_plugins(); - let workflow = context.spawn_io_workflow(|scope: Scope<(u32, i32, f32), (u32, i32, f32)>, builder| { - let buffer_u32: AnyBuffer = builder.create_buffer::(BufferSettings::default()).into(); - let buffer_i32: AnyBuffer = builder.create_buffer::(BufferSettings::default()).into(); - let buffer_f32: AnyBuffer = builder.create_buffer::(BufferSettings::default()).into(); - - let (input_u32, input_i32, input_f32) = scope.input.chain(builder).unzip(); - input_u32 - .chain(builder) - .map_block(|v| 2*v) - .connect(buffer_u32.downcast::().unwrap().input_slot()); - - input_i32 - .chain(builder) - .map_block(|v| 2*v) - .connect(buffer_i32.downcast::().unwrap().input_slot()); - - input_f32 - .chain(builder) - .map_block(|v| 2.0*v) - .connect(buffer_f32.downcast::().unwrap().input_slot()); - - (buffer_u32, buffer_i32, buffer_f32) - .join(builder) - .map_block(|(value_u32, value_i32, value_f32)| { - ( - *value_u32.downcast::().unwrap(), - *value_i32.downcast::().unwrap(), - *value_f32.downcast::().unwrap(), - ) - }) - .connect(scope.terminate); + let workflow = + context.spawn_io_workflow(|scope: Scope<(u32, i32, f32), (u32, i32, f32)>, builder| { + let buffer_u32: AnyBuffer = builder + .create_buffer::(BufferSettings::default()) + .into(); + let buffer_i32: AnyBuffer = builder + .create_buffer::(BufferSettings::default()) + .into(); + let buffer_f32: AnyBuffer = builder + .create_buffer::(BufferSettings::default()) + .into(); + + let (input_u32, input_i32, input_f32) = scope.input.chain(builder).unzip(); + input_u32 + .chain(builder) + .map_block(|v| 2 * v) + .connect(buffer_u32.downcast::().unwrap().input_slot()); + + input_i32 + .chain(builder) + .map_block(|v| 2 * v) + .connect(buffer_i32.downcast::().unwrap().input_slot()); + + input_f32 + .chain(builder) + .map_block(|v| 2.0 * v) + .connect(buffer_f32.downcast::().unwrap().input_slot()); + + (buffer_u32, buffer_i32, buffer_f32) + .join(builder) + .map_block(|(value_u32, value_i32, value_f32)| { + ( + *value_u32.downcast::().unwrap(), + *value_i32.downcast::().unwrap(), + *value_f32.downcast::().unwrap(), + ) + }) + .connect(scope.terminate); + }); + + let mut promise = context.command(|commands| { + commands + .request((1u32, 2i32, 3f32), workflow) + .take_response() }); - let mut promise = context.command( - |commands| commands.request((1u32, 2i32, 3f32), workflow).take_response() - ); - context.run_with_conditions(&mut promise, Duration::from_secs(2)); let (v_u32, v_i32, v_f32) = promise.take().available().unwrap(); assert_eq!(v_u32, 2); diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 6583aea1..d2902a1e 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -15,11 +15,7 @@ * */ -use std::{ - any::TypeId, - borrow::Cow, - collections::HashMap, -}; +use std::{any::TypeId, borrow::Cow, collections::HashMap}; use thiserror::Error as ThisError; @@ -28,9 +24,9 @@ use smallvec::SmallVec; use bevy_ecs::prelude::{Entity, World}; use crate::{ - AnyBuffer, AddOperation, Chain, OperationError, OperationResult, OperationRoster, Buffered, Gate, - Join, Joined, Accessed, BufferKeyBuilder, AnyBufferKey, Builder, Output, UnusedTarget, GateState, - add_listener_to_source, + add_listener_to_source, Accessed, AddOperation, AnyBuffer, AnyBufferKey, BufferKeyBuilder, + Buffered, Builder, Chain, Gate, GateState, Join, Joined, OperationError, OperationResult, + OperationRoster, Output, UnusedTarget, }; #[derive(Clone, Default)] @@ -40,11 +36,7 @@ pub struct BufferMap { impl BufferMap { /// Insert a named buffer into the map. - pub fn insert( - &mut self, - name: Cow<'static, str>, - buffer: impl Into, - ) { + pub fn insert(&mut self, name: Cow<'static, str>, buffer: impl Into) { self.inner.insert(name, buffer.into()); } @@ -67,11 +59,7 @@ pub struct IncompatibleLayout { impl IncompatibleLayout { /// Check whether a named buffer is compatible with a specific type. - pub fn require_buffer( - &mut self, - expected_name: &str, - buffers: &BufferMap, - ) { + pub fn require_buffer(&mut self, expected_name: &str, buffers: &BufferMap) { if let Some((name, buffer)) = buffers.inner.get_key_value(expected_name) { if buffer.message_type_id() != TypeId::of::() { self.incompatible_buffers.push(BufferIncompatibility { @@ -81,7 +69,8 @@ impl IncompatibleLayout { }); } } else { - self.missing_buffers.push(Cow::Owned(expected_name.to_owned())); + self.missing_buffers + .push(Cow::Owned(expected_name.to_owned())); } } @@ -124,11 +113,7 @@ pub trait BufferMapLayout: Sized { world: &mut World, ) -> OperationResult; - fn add_listener( - buffers: &BufferMap, - listener: Entity, - world: &mut World, - ) -> OperationResult { + fn add_listener(buffers: &BufferMap, listener: Entity, world: &mut World) -> OperationResult { for buffer in buffers.inner.values() { add_listener_to_source(buffer.id(), listener, world)?; } @@ -157,7 +142,7 @@ pub trait BufferMapLayout: Sized { } } -pub trait JoinedValue: 'static + BufferMapLayout + Send + Sync{ +pub trait JoinedValue: 'static + BufferMapLayout + Send + Sync { /// This associated type must represent a buffer map layout that is /// guaranteed to be compatible for this JoinedValue. Failure to implement /// this trait accordingly will result in panics. @@ -198,11 +183,7 @@ pub trait JoinedValue: 'static + BufferMapLayout + Send + Sync{ /// Trait to describe a layout of buffer keys pub trait BufferKeyMap: BufferMapLayout + Clone { - fn add_accessor( - buffers: &BufferMap, - accessor: Entity, - world: &mut World, - ) -> OperationResult; + fn add_accessor(buffers: &BufferMap, accessor: Entity, world: &mut World) -> OperationResult; fn create_key(buffers: &BufferMap, builder: &BufferKeyBuilder) -> Self; @@ -219,13 +200,19 @@ struct BufferedMap { impl BufferedMap { fn new(map: BufferMap) -> Result { K::is_compatible(&map)?; - Ok(Self { map, _ignore: Default::default() }) + Ok(Self { + map, + _ignore: Default::default(), + }) } } impl Clone for BufferedMap { fn clone(&self) -> Self { - Self { map: self.map.clone(), _ignore: Default::default() } + Self { + map: self.map.clone(), + _ignore: Default::default(), + } } } @@ -322,11 +309,7 @@ impl BufferMapLayout for AnyBufferKeyMap { Ok(min_count.unwrap_or(0)) } - fn add_listener( - buffers: &BufferMap, - listener: Entity, - world: &mut World, - ) -> OperationResult { + fn add_listener(buffers: &BufferMap, listener: Entity, world: &mut World) -> OperationResult { for buffer in buffers.inner.values() { buffer.add_listener(listener, world)?; } @@ -377,9 +360,8 @@ mod tests { use std::borrow::Cow; use crate::{ - prelude::*, - testing::*, - OperationResult, OperationError, OrBroken, InspectBuffer, ManageBuffer, BufferMap, + prelude::*, testing::*, BufferMap, InspectBuffer, ManageBuffer, OperationError, + OperationResult, OrBroken, }; use bevy_ecs::prelude::World; @@ -420,17 +402,11 @@ mod tests { .or_broken()? .buffered_count::(session)?; - Ok( - [ - integer_count, - float_count, - string_count, - ] + Ok([integer_count, float_count, string_count] .iter() .min() .copied() - .unwrap_or(0) - ) + .unwrap_or(0)) } fn ensure_active_session( @@ -480,7 +456,11 @@ mod tests { .or_broken()? .pull_from_buffer::(session)?; - Ok(Self { integer, float, string }) + Ok(Self { + integer, + float, + string, + }) } } @@ -514,25 +494,23 @@ mod tests { buffers.insert(Cow::Borrowed("float"), buffer_f64); buffers.insert(Cow::Borrowed("string"), buffer_string); - scope - .input - .chain(builder) - .fork_unzip(( - |chain: Chain<_>| chain.connect(buffer_i64.input_slot()), - |chain: Chain<_>| chain.connect(buffer_f64.input_slot()), - |chain: Chain<_>| chain.connect(buffer_string.input_slot()), - )); + scope.input.chain(builder).fork_unzip(( + |chain: Chain<_>| chain.connect(buffer_i64.input_slot()), + |chain: Chain<_>| chain.connect(buffer_f64.input_slot()), + |chain: Chain<_>| chain.connect(buffer_string.input_slot()), + )); - builder.try_join_into(buffers).unwrap().connect(scope.terminate); + builder + .try_join_into(buffers) + .unwrap() + .connect(scope.terminate); }); - let mut promise = context.command( - |commands| commands.request( - (5_i64, 3.14_f64, "hello".to_string()), - workflow, - ) - .take_response() - ); + let mut promise = context.command(|commands| { + commands + .request((5_i64, 3.14_f64, "hello".to_string()), workflow) + .take_response() + }); context.run_with_conditions(&mut promise, Duration::from_secs(2)); let value: TestJoinedValue = promise.take().available().unwrap(); diff --git a/src/buffer/buffer_storage.rs b/src/buffer/buffer_storage.rs index 6967e473..e3d9bc88 100644 --- a/src/buffer/buffer_storage.rs +++ b/src/buffer/buffer_storage.rs @@ -44,7 +44,7 @@ pub(crate) struct BufferStorage { } impl BufferStorage { - pub(crate) fn count(&self, session: Entity) -> usize { + pub(crate) fn count(&self, session: Entity) -> usize { self.reverse_queues .get(&session) .map(|q| q.len()) diff --git a/src/buffer/bufferable.rs b/src/buffer/bufferable.rs index 6b4292ec..b6eaa704 100644 --- a/src/buffer/bufferable.rs +++ b/src/buffer/bufferable.rs @@ -20,8 +20,8 @@ use smallvec::SmallVec; use crate::{ Accessed, AddOperation, Buffer, BufferSettings, Buffered, Builder, Chain, - CleanupWorkflowConditions, CloneFromBuffer, Join, Joined, Listen, Output, Scope, - ScopeSettings, UnusedTarget, + CleanupWorkflowConditions, CloneFromBuffer, Join, Joined, Listen, Output, Scope, ScopeSettings, + UnusedTarget, }; pub type BufferKeys = <::BufferType as Accessed>::Key; diff --git a/src/buffer/buffered.rs b/src/buffer/buffered.rs index 2841dde5..c9201ef1 100644 --- a/src/buffer/buffered.rs +++ b/src/buffer/buffered.rs @@ -540,9 +540,7 @@ pub(crate) fn add_listener_to_source( listener: Entity, world: &mut World, ) -> OperationResult { - let mut targets = world - .get_mut::(source) - .or_broken()?; + let mut targets = world.get_mut::(source).or_broken()?; if !targets.0.contains(&listener) { targets.0.push(listener); } diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index c759d8bd..a427e636 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -36,12 +36,11 @@ pub use serde_json::Value as JsonMessage; use smallvec::SmallVec; use crate::{ - AnyBufferAccessImpl, AnyBufferAccessInterface, AnyBuffer, AnyBufferKey, - AnyRange, Buffer, BufferAccessors, BufferKey, BufferKeyBuilder, - BufferAccessLifecycle, BufferAccessMut, BufferError, BufferStorage, Builder, - DrainBuffer, OperationError, OperationResult, InspectBuffer, ManageBuffer, - Gate, GateState, NotifyBufferUpdate, Bufferable, Buffered, OrBroken, Joined, - Accessed, add_listener_to_source, + add_listener_to_source, Accessed, AnyBuffer, AnyBufferAccessImpl, AnyBufferAccessInterface, + AnyBufferKey, AnyRange, Buffer, BufferAccessLifecycle, BufferAccessMut, BufferAccessors, + BufferError, BufferKey, BufferKeyBuilder, BufferStorage, Bufferable, Buffered, Builder, + DrainBuffer, Gate, GateState, InspectBuffer, Joined, ManageBuffer, NotifyBufferUpdate, + OperationError, OperationResult, OrBroken, }; /// A [`Buffer`] whose message type has been anonymized, but which is known to @@ -112,7 +111,7 @@ impl JsonBufferKey { session: self.session, accessor: self.accessor, lifecycle: self.lifecycle.clone(), - _ignore: Default::default() + _ignore: Default::default(), }) } else { None @@ -135,13 +134,18 @@ impl JsonBufferKey { impl std::fmt::Debug for JsonBufferKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f - .debug_struct("JsonBufferKey") + f.debug_struct("JsonBufferKey") .field("buffer", &self.buffer) .field("session", &self.session) .field("accessor", &self.accessor) - .field("in_use", &self.lifecycle.as_ref().is_some_and(|l| l.is_in_use())) - .field("message_type_name", &self.interface.any_access_interface().message_type_name()) + .field( + "in_use", + &self.lifecycle.as_ref().is_some_and(|l| l.is_in_use()), + ) + .field( + "message_type_name", + &self.interface.any_access_interface().message_type_name(), + ) .finish() } } @@ -154,7 +158,7 @@ impl From> session: value.session, accessor: value.accessor, lifecycle: value.lifecycle, - interface + interface, } } } @@ -274,24 +278,27 @@ impl<'w, 's, 'a> JsonBufferMut<'w, 's, 'a> { /// Modify the oldest message in the buffer. pub fn oldest_mut(&mut self) -> Option> { - self.storage.json_oldest_mut(self.session, &mut self.modified) + self.storage + .json_oldest_mut(self.session, &mut self.modified) } /// Modify the newest message in the buffer. pub fn newest_mut(&mut self) -> Option> { - self.storage.json_newest_mut(self.session, &mut self.modified) + self.storage + .json_newest_mut(self.session, &mut self.modified) } /// Modify a message in the buffer. pub fn get_mut(&mut self, index: usize) -> Option> { - self.storage.json_get_mut(self.session, index, &mut self.modified) + self.storage + .json_get_mut(self.session, index, &mut self.modified) } /// Drain a range of messages out of the buffer. pub fn drain>(&mut self, range: R) -> DrainJsonBuffer<'_> { self.modified = true; DrainJsonBuffer { - interface: self.storage.json_drain(self.session, AnyRange::new(range)) + interface: self.storage.json_drain(self.session, AnyRange::new(range)), } } @@ -318,7 +325,9 @@ impl<'w, 's, 'a> JsonBufferMut<'w, 's, 'a> { /// Pull the newest message from the buffer and attempt to deserialize it /// into the target type. pub fn pull_newest_as(&mut self) -> Result, serde_json::Error> { - self.pull_newest()?.map(|m| serde_json::from_value(m)).transpose() + self.pull_newest()? + .map(|m| serde_json::from_value(m)) + .transpose() } /// Attempt to push a new value into the buffer. @@ -332,7 +341,10 @@ impl<'w, 's, 'a> JsonBufferMut<'w, 's, 'a> { /// out of the buffer failed to serialize. // TODO(@mxgrey): Consider having an error type that differentiates the // various possible error modes. - pub fn push(&mut self, value: T) -> Result, serde_json::Error> { + pub fn push( + &mut self, + value: T, + ) -> Result, serde_json::Error> { let message = serde_json::to_value(&value)?; self.modified = true; self.storage.json_push(self.session, message) @@ -340,14 +352,20 @@ impl<'w, 's, 'a> JsonBufferMut<'w, 's, 'a> { /// Same as [`Self::push`] but no serialization step is needed for the incoming /// message. - pub fn push_json(&mut self, message: JsonMessage) -> Result, serde_json::Error> { + pub fn push_json( + &mut self, + message: JsonMessage, + ) -> Result, serde_json::Error> { self.modified = true; self.storage.json_push(self.session, message) } /// Same as [`Self::push`] but the message will be interpreted as the oldest /// message in the buffer. - pub fn push_as_oldest(&mut self, value: T) -> Result, serde_json::Error> { + pub fn push_as_oldest( + &mut self, + value: T, + ) -> Result, serde_json::Error> { let message = serde_json::to_value(&value)?; self.modified = true; self.storage.json_push_as_oldest(self.session, message) @@ -355,7 +373,10 @@ impl<'w, 's, 'a> JsonBufferMut<'w, 's, 'a> { /// Same as [`Self:push_as_oldest`] but no serialization step is needed for /// the incoming message. - pub fn push_json_as_oldest(&mut self, message: JsonMessage) -> Result, serde_json::Error> { + pub fn push_json_as_oldest( + &mut self, + message: JsonMessage, + ) -> Result, serde_json::Error> { self.modified = true; self.storage.json_push_as_oldest(self.session, message) } @@ -414,10 +435,7 @@ pub trait JsonBufferWorldAccess { /// For technical reasons this requires direct [`World`] access, but you can /// do other read-only queries on the world while holding onto the /// [`JsonBufferView`]. - fn json_buffer_view( - &self, - key: &JsonBufferKey, - ) -> Result, BufferError>; + fn json_buffer_view(&self, key: &JsonBufferKey) -> Result, BufferError>; /// Call this to get mutable access to any buffer whose message type is /// serializable and deserializable. @@ -432,10 +450,7 @@ pub trait JsonBufferWorldAccess { } impl JsonBufferWorldAccess for World { - fn json_buffer_view( - &self, - key: &JsonBufferKey, - ) -> Result, BufferError> { + fn json_buffer_view(&self, key: &JsonBufferKey) -> Result, BufferError> { key.interface.create_json_buffer_view(key, self) } @@ -500,10 +515,7 @@ impl<'a> JsonMut<'a> { /// [`Self::serialize`], modifying the value, and then calling [`Self::insert`]. /// The benefit of this function is that you do not need to remember to /// insert after you have finished your modifications. - pub fn modify( - &mut self, - f: impl FnOnce(&mut JsonMessage), - ) -> Result<(), serde_json::Error> { + pub fn modify(&mut self, f: impl FnOnce(&mut JsonMessage)) -> Result<(), serde_json::Error> { let mut message = self.serialize()?; f(&mut message); self.insert(message) @@ -540,18 +552,36 @@ trait JsonBufferViewing { fn json_count(&self, session: Entity) -> usize; fn json_oldest<'a>(&'a self, session: Entity) -> JsonMessageViewResult; fn json_newest<'a>(&'a self, session: Entity) -> JsonMessageViewResult; - fn json_get<'a>(&'a self, session: Entity, index: usize) ->JsonMessageViewResult; + fn json_get<'a>(&'a self, session: Entity, index: usize) -> JsonMessageViewResult; } trait JsonBufferManagement: JsonBufferViewing { fn json_push(&mut self, session: Entity, value: JsonMessage) -> JsonMessagePushResult; - fn json_push_as_oldest(&mut self, session: Entity, value: JsonMessage) -> JsonMessagePushResult; + fn json_push_as_oldest(&mut self, session: Entity, value: JsonMessage) + -> JsonMessagePushResult; fn json_pull(&mut self, session: Entity) -> JsonMessageViewResult; fn json_pull_newest(&mut self, session: Entity) -> JsonMessageViewResult; - fn json_oldest_mut<'a>(&'a mut self, session: Entity, modified: &'a mut bool) -> Option>; - fn json_newest_mut<'a>(&'a mut self, session: Entity, modified: &'a mut bool) -> Option>; - fn json_get_mut<'a>(&'a mut self, session: Entity, index: usize, modified: &'a mut bool) -> Option>; - fn json_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box; + fn json_oldest_mut<'a>( + &'a mut self, + session: Entity, + modified: &'a mut bool, + ) -> Option>; + fn json_newest_mut<'a>( + &'a mut self, + session: Entity, + modified: &'a mut bool, + ) -> Option>; + fn json_get_mut<'a>( + &'a mut self, + session: Entity, + index: usize, + modified: &'a mut bool, + ) -> Option>; + fn json_drain<'a>( + &'a mut self, + session: Entity, + range: AnyRange, + ) -> Box; } impl JsonBufferViewing for &'_ BufferStorage @@ -570,8 +600,10 @@ where self.newest(session).map(serde_json::to_value).transpose() } - fn json_get<'a>(&'a self, session: Entity, index: usize) ->JsonMessageViewResult { - self.get(session, index).map(serde_json::to_value).transpose() + fn json_get<'a>(&'a self, session: Entity, index: usize) -> JsonMessageViewResult { + self.get(session, index) + .map(serde_json::to_value) + .transpose() } } @@ -591,8 +623,10 @@ where self.newest(session).map(serde_json::to_value).transpose() } - fn json_get<'a>(&'a self, session: Entity, index: usize) ->JsonMessageViewResult { - self.get(session, index).map(serde_json::to_value).transpose() + fn json_get<'a>(&'a self, session: Entity, index: usize) -> JsonMessageViewResult { + self.get(session, index) + .map(serde_json::to_value) + .transpose() } } @@ -602,12 +636,20 @@ where { fn json_push(&mut self, session: Entity, value: JsonMessage) -> JsonMessagePushResult { let value: T = serde_json::from_value(value)?; - self.push(session, value).map(serde_json::to_value).transpose() + self.push(session, value) + .map(serde_json::to_value) + .transpose() } - fn json_push_as_oldest(&mut self, session: Entity, value: JsonMessage) -> JsonMessagePushResult { + fn json_push_as_oldest( + &mut self, + session: Entity, + value: JsonMessage, + ) -> JsonMessagePushResult { let value: T = serde_json::from_value(value)?; - self.push(session, value).map(serde_json::to_value).transpose() + self.push(session, value) + .map(serde_json::to_value) + .transpose() } fn json_pull(&mut self, session: Entity) -> JsonMessageViewResult { @@ -615,22 +657,50 @@ where } fn json_pull_newest(&mut self, session: Entity) -> JsonMessageViewResult { - self.pull_newest(session).map(serde_json::to_value).transpose() + self.pull_newest(session) + .map(serde_json::to_value) + .transpose() } - fn json_oldest_mut<'a>(&'a mut self, session: Entity, modified: &'a mut bool) -> Option> { - self.oldest_mut(session).map(|interface| JsonMut { interface, modified }) + fn json_oldest_mut<'a>( + &'a mut self, + session: Entity, + modified: &'a mut bool, + ) -> Option> { + self.oldest_mut(session).map(|interface| JsonMut { + interface, + modified, + }) } - fn json_newest_mut<'a>(&'a mut self, session: Entity, modified: &'a mut bool) -> Option> { - self.newest_mut(session).map(|interface| JsonMut { interface, modified }) + fn json_newest_mut<'a>( + &'a mut self, + session: Entity, + modified: &'a mut bool, + ) -> Option> { + self.newest_mut(session).map(|interface| JsonMut { + interface, + modified, + }) } - fn json_get_mut<'a>(&'a mut self, session: Entity, index: usize, modified: &'a mut bool) -> Option> { - self.get_mut(session, index).map(|interface| JsonMut { interface, modified }) + fn json_get_mut<'a>( + &'a mut self, + session: Entity, + index: usize, + modified: &'a mut bool, + ) -> Option> { + self.get_mut(session, index).map(|interface| JsonMut { + interface, + modified, + }) } - fn json_drain<'a>(&'a mut self, session: Entity, range: AnyRange) -> Box { + fn json_drain<'a>( + &'a mut self, + session: Entity, + range: AnyRange, + ) -> Box { Box::new(self.drain(session, range)) } } @@ -675,11 +745,7 @@ trait JsonBufferAccessInterface { session: Entity, ) -> Result; - fn ensure_session( - &self, - buffer_mut: &mut EntityWorldMut, - session: Entity, - ) -> OperationResult; + fn ensure_session(&self, buffer_mut: &mut EntityWorldMut, session: Entity) -> OperationResult; fn pull( &self, @@ -701,8 +767,7 @@ trait JsonBufferAccessInterface { impl<'a> std::fmt::Debug for &'a (dyn JsonBufferAccessInterface + Send + Sync) { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f - .debug_struct("Message Properties") + f.debug_struct("Message Properties") .field("type", &self.any_access_interface().message_type_name()) .finish() } @@ -712,22 +777,23 @@ struct JsonBufferAccessImpl(std::marker::PhantomData); impl JsonBufferAccessImpl { pub(crate) fn get_interface() -> &'static (dyn JsonBufferAccessInterface + Send + Sync) { - const INTERFACE_MAP: OnceLock>> = OnceLock::new(); + const INTERFACE_MAP: OnceLock< + Mutex>, + > = OnceLock::new(); let binding = INTERFACE_MAP; let interfaces = binding.get_or_init(|| Mutex::default()); let mut interfaces_mut = interfaces.lock().unwrap(); - *interfaces_mut.entry(TypeId::of::()).or_insert_with(|| { - Box::leak(Box::new(JsonBufferAccessImpl::(Default::default()))) - }) + *interfaces_mut + .entry(TypeId::of::()) + .or_insert_with(|| Box::leak(Box::new(JsonBufferAccessImpl::(Default::default())))) } } -impl JsonBufferAccessInterface for JsonBufferAccessImpl { +impl JsonBufferAccessInterface + for JsonBufferAccessImpl +{ fn any_access_interface(&self) -> &'static (dyn AnyBufferAccessInterface + Send + Sync) { AnyBufferAccessImpl::::get_interface() } @@ -740,11 +806,7 @@ impl JsonBufferAccessIn buffer_ref.buffered_count::(session) } - fn ensure_session( - &self, - buffer_mut: &mut EntityWorldMut, - session: Entity, - ) -> OperationResult { + fn ensure_session(&self, buffer_mut: &mut EntityWorldMut, session: Entity) -> OperationResult { buffer_mut.ensure_session::(session) } @@ -762,9 +824,15 @@ impl JsonBufferAccessIn key: &JsonBufferKey, world: &'a World, ) -> Result, BufferError> { - let buffer_ref = world.get_entity(key.buffer).ok_or(BufferError::BufferMissing)?; - let storage = buffer_ref.get::>().ok_or(BufferError::BufferMissing)?; - let gate = buffer_ref.get::().ok_or(BufferError::BufferMissing)?; + let buffer_ref = world + .get_entity(key.buffer) + .ok_or(BufferError::BufferMissing)?; + let storage = buffer_ref + .get::>() + .ok_or(BufferError::BufferMissing)?; + let gate = buffer_ref + .get::() + .ok_or(BufferError::BufferMissing)?; Ok(JsonBufferView { storage: Box::new(storage), gate, @@ -781,29 +849,43 @@ impl JsonBufferAccessIn } trait JsonBufferAccessMutState { - fn get_json_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's>; + fn get_json_buffer_access_mut<'s, 'w: 's>( + &'s mut self, + world: &'w mut World, + ) -> Box + 's>; } impl JsonBufferAccessMutState for SystemState> where T: 'static + Send + Sync + Serialize + DeserializeOwned, { - fn get_json_buffer_access_mut<'s, 'w: 's>(&'s mut self, world: &'w mut World) -> Box + 's> { + fn get_json_buffer_access_mut<'s, 'w: 's>( + &'s mut self, + world: &'w mut World, + ) -> Box + 's> { Box::new(self.get_mut(world)) } } trait JsonBufferAccessMut<'w, 's> { - fn as_json_buffer_mut<'a>(&'a mut self, key: &JsonBufferKey) -> Result, BufferError>; + fn as_json_buffer_mut<'a>( + &'a mut self, + key: &JsonBufferKey, + ) -> Result, BufferError>; } impl<'w, 's, T> JsonBufferAccessMut<'w, 's> for BufferAccessMut<'w, 's, T> where T: 'static + Send + Sync + Serialize + DeserializeOwned, { - fn as_json_buffer_mut<'a>(&'a mut self, key: &JsonBufferKey) -> Result, BufferError> { + fn as_json_buffer_mut<'a>( + &'a mut self, + key: &JsonBufferKey, + ) -> Result, BufferError> { let BufferAccessMut { query, commands } = self; - let (storage, gate) = query.get_mut(key.buffer).map_err(|_| BufferError::BufferMissing)?; + let (storage, gate) = query + .get_mut(key.buffer) + .map_err(|_| BufferError::BufferMissing)?; Ok(JsonBufferMut { storage: Box::new(storage), gate, @@ -920,9 +1002,9 @@ impl Accessed for JsonBuffer { #[cfg(test)] mod tests { - use bevy_ecs::prelude::World; use crate::{prelude::*, testing::*}; - use serde::{Serialize, Deserialize}; + use bevy_ecs::prelude::World; + use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone)] struct TestMessage { @@ -947,12 +1029,12 @@ mod tests { let workflow = context.spawn_io_workflow(|scope, builder| { let buffer = builder.create_buffer(BufferSettings::keep_all()); - let push_multiple_times = builder.commands().spawn_service( - push_multiple_times_into_buffer.into_blocking_service() - ); - let count = builder.commands().spawn_service( - get_buffer_count.into_blocking_service() - ); + let push_multiple_times = builder + .commands() + .spawn_service(push_multiple_times_into_buffer.into_blocking_service()); + let count = builder + .commands() + .spawn_service(get_buffer_count.into_blocking_service()); scope .input @@ -964,9 +1046,8 @@ mod tests { }); let msg = TestMessage::new(); - let mut promise = context.command( - |commands| commands.request(msg, workflow).take_response() - ); + let mut promise = + context.command(|commands| commands.request(msg, workflow).take_response()); context.run_with_conditions(&mut promise, Duration::from_secs(2)); let count = promise.take().available().unwrap(); @@ -986,10 +1067,7 @@ mod tests { key.into() } - fn get_buffer_count( - In(key): In, - world: &mut World, - ) -> usize { + fn get_buffer_count(In(key): In, world: &mut World) -> usize { world.json_buffer_view(&key).unwrap().len() } @@ -999,15 +1077,15 @@ mod tests { let workflow = context.spawn_io_workflow(|scope, builder| { let buffer = builder.create_buffer(BufferSettings::keep_all()); - let push_multiple_times = builder.commands().spawn_service( - push_multiple_times_into_buffer.into_blocking_service() - ); - let modify_content = builder.commands().spawn_service( - modify_buffer_content.into_blocking_service() - ); - let drain_content = builder.commands().spawn_service( - pull_each_buffer_item.into_blocking_service() - ); + let push_multiple_times = builder + .commands() + .spawn_service(push_multiple_times_into_buffer.into_blocking_service()); + let modify_content = builder + .commands() + .spawn_service(modify_buffer_content.into_blocking_service()); + let drain_content = builder + .commands() + .spawn_service(pull_each_buffer_item.into_blocking_service()); scope .input @@ -1020,9 +1098,8 @@ mod tests { }); let msg = TestMessage::new(); - let mut promise = context.command( - |commands| commands.request(msg, workflow).take_response() - ); + let mut promise = + context.command(|commands| commands.request(msg, workflow).take_response()); context.run_with_conditions(&mut promise, Duration::from_secs(2)); let values = promise.take().available().unwrap(); @@ -1034,34 +1111,36 @@ mod tests { assert!(context.no_unhandled_errors()); } - fn modify_buffer_content( - In(key): In, - world: &mut World, - ) -> JsonBufferKey { - world.json_buffer_mut(&key, |mut access| { - for i in 0..access.len() { - access.get_mut(i).unwrap().modify(|value| { - let v_i32 = value.get_mut("v_i32").unwrap(); - let modified_v_i32 = i as i64 * v_i32.as_i64().unwrap(); - *v_i32 = modified_v_i32.into(); - }).unwrap(); - } - }).unwrap(); + fn modify_buffer_content(In(key): In, world: &mut World) -> JsonBufferKey { + world + .json_buffer_mut(&key, |mut access| { + for i in 0..access.len() { + access + .get_mut(i) + .unwrap() + .modify(|value| { + let v_i32 = value.get_mut("v_i32").unwrap(); + let modified_v_i32 = i as i64 * v_i32.as_i64().unwrap(); + *v_i32 = modified_v_i32.into(); + }) + .unwrap(); + } + }) + .unwrap(); key } - fn pull_each_buffer_item( - In(key): In, - world: &mut World, - ) -> Vec { - world.json_buffer_mut(&key, |mut access| { - let mut values = Vec::new(); - while let Ok(Some(value)) = access.pull() { - values.push(value); - } - values - }).unwrap() + fn pull_each_buffer_item(In(key): In, world: &mut World) -> Vec { + world + .json_buffer_mut(&key, |mut access| { + let mut values = Vec::new(); + while let Ok(Some(value)) = access.pull() { + values.push(value); + } + values + }) + .unwrap() } #[test] @@ -1070,15 +1149,15 @@ mod tests { let workflow = context.spawn_io_workflow(|scope, builder| { let buffer = builder.create_buffer(BufferSettings::keep_all()); - let push_multiple_times = builder.commands().spawn_service( - push_multiple_times_into_buffer.into_blocking_service() - ); - let modify_content = builder.commands().spawn_service( - modify_buffer_content.into_blocking_service() - ); - let drain_content = builder.commands().spawn_service( - drain_buffer_contents.into_blocking_service() - ); + let push_multiple_times = builder + .commands() + .spawn_service(push_multiple_times_into_buffer.into_blocking_service()); + let modify_content = builder + .commands() + .spawn_service(modify_buffer_content.into_blocking_service()); + let drain_content = builder + .commands() + .spawn_service(drain_buffer_contents.into_blocking_service()); scope .input @@ -1091,9 +1170,8 @@ mod tests { }); let msg = TestMessage::new(); - let mut promise = context.command( - |commands| commands.request(msg, workflow).take_response() - ); + let mut promise = + context.command(|commands| commands.request(msg, workflow).take_response()); context.run_with_conditions(&mut promise, Duration::from_secs(2)); let values = promise.take().available().unwrap(); @@ -1105,17 +1183,13 @@ mod tests { assert!(context.no_unhandled_errors()); } - fn drain_buffer_contents( - In(key): In, - world: &mut World, - ) -> Vec { - world.json_buffer_mut(&key, |mut access| { - access - .drain(..) - .collect::, _>>() - }) - .unwrap() - .unwrap() + fn drain_buffer_contents(In(key): In, world: &mut World) -> Vec { + world + .json_buffer_mut(&key, |mut access| { + access.drain(..).collect::, _>>() + }) + .unwrap() + .unwrap() } #[test] @@ -1123,33 +1197,57 @@ mod tests { let mut context = TestingContext::minimal_plugins(); let workflow = context.spawn_io_workflow(|scope, builder| { - let buffer_double_u32: JsonBuffer = builder.create_buffer::(BufferSettings::default()).into(); - let buffer_double_i32: JsonBuffer = builder.create_buffer::(BufferSettings::default()).into(); - let buffer_double_string: JsonBuffer = builder.create_buffer::(BufferSettings::default()).into(); - - scope - .input - .chain(builder) - .fork_clone(( - |chain: Chain<_>| chain + let buffer_double_u32: JsonBuffer = builder + .create_buffer::(BufferSettings::default()) + .into(); + let buffer_double_i32: JsonBuffer = builder + .create_buffer::(BufferSettings::default()) + .into(); + let buffer_double_string: JsonBuffer = builder + .create_buffer::(BufferSettings::default()) + .into(); + + scope.input.chain(builder).fork_clone(( + |chain: Chain<_>| { + chain .map_block(|mut msg: TestMessage| { msg.v_u32 *= 2; msg }) - .connect(buffer_double_u32.downcast::().unwrap().input_slot()), - |chain: Chain<_>| chain + .connect( + buffer_double_u32 + .downcast::() + .unwrap() + .input_slot(), + ) + }, + |chain: Chain<_>| { + chain .map_block(|mut msg: TestMessage| { msg.v_i32 *= 2; msg }) - .connect(buffer_double_i32.downcast::().unwrap().input_slot()), - |chain: Chain<_>| chain + .connect( + buffer_double_i32 + .downcast::() + .unwrap() + .input_slot(), + ) + }, + |chain: Chain<_>| { + chain .map_block(|mut msg: TestMessage| { msg.v_string = msg.v_string.clone() + &msg.v_string; msg }) - .connect(buffer_double_string.downcast::().unwrap().input_slot()), - )); + .connect( + buffer_double_string + .downcast::() + .unwrap() + .input_slot(), + ) + }, + )); (buffer_double_u32, buffer_double_i32, buffer_double_string) .join(builder) @@ -1157,15 +1255,17 @@ mod tests { }); let msg = TestMessage::new(); - let mut promise = context.command( - |commands| commands.request(msg, workflow).take_response() - ); + let mut promise = + context.command(|commands| commands.request(msg, workflow).take_response()); context.run_with_conditions(&mut promise, Duration::from_secs(2)); let (double_u32, double_i32, double_string) = promise.take().available().unwrap(); assert_eq!(4, double_u32.get("v_u32").unwrap().as_i64().unwrap()); assert_eq!(2, double_i32.get("v_i32").unwrap().as_i64().unwrap()); - assert_eq!("hellohello", double_string.get("v_string").unwrap().as_str().unwrap()); + assert_eq!( + "hellohello", + double_string.get("v_string").unwrap().as_str().unwrap() + ); assert!(context.no_unhandled_errors()); } } diff --git a/src/buffer/manage_buffer.rs b/src/buffer/manage_buffer.rs index 1248362c..af9cf65b 100644 --- a/src/buffer/manage_buffer.rs +++ b/src/buffer/manage_buffer.rs @@ -22,9 +22,7 @@ use bevy_ecs::{ use smallvec::SmallVec; -use crate::{ - BufferStorage, OperationError, OperationResult, OrBroken, -}; +use crate::{BufferStorage, OperationError, OperationResult, OrBroken}; pub trait InspectBuffer { fn buffered_count( @@ -120,8 +118,7 @@ impl<'w> ManageBuffer for EntityWorldMut<'w> { } fn ensure_session(&mut self, session: Entity) -> OperationResult { - self - .get_mut::>() + self.get_mut::>() .or_broken()? .ensure_session(session); Ok(()) diff --git a/src/builder.rs b/src/builder.rs index 9b953750..28ac6787 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -23,13 +23,13 @@ use std::future::Future; use smallvec::SmallVec; use crate::{ - AddOperation, AsMap, BeginCleanupWorkflow, Buffer, JoinedItem, BufferKeys, BufferSettings, + AddOperation, AsMap, BeginCleanupWorkflow, Buffer, BufferKeys, BufferMap, BufferSettings, Bufferable, Buffered, Chain, Collect, ForkClone, ForkCloneOutput, ForkTargetStorage, Gate, - GateRequest, Injection, InputSlot, IntoAsyncMap, IntoBlockingMap, Node, OperateBuffer, - OperateBufferAccess, OperateDynamicGate, OperateScope, OperateSplit, OperateStaticGate, Output, - Provider, RequestOfMap, ResponseOfMap, Scope, ScopeEndpoints, ScopeSettings, - ScopeSettingsStorage, Sendish, Service, SplitOutputs, Splittable, StreamPack, StreamTargetMap, - StreamsOfMap, Trim, TrimBranch, UnusedTarget, JoinedValue, BufferMap, IncompatibleLayout, + GateRequest, IncompatibleLayout, Injection, InputSlot, IntoAsyncMap, IntoBlockingMap, + JoinedItem, JoinedValue, Node, OperateBuffer, OperateBufferAccess, OperateDynamicGate, + OperateScope, OperateSplit, OperateStaticGate, Output, Provider, RequestOfMap, ResponseOfMap, + Scope, ScopeEndpoints, ScopeSettings, ScopeSettingsStorage, Sendish, Service, SplitOutputs, + Splittable, StreamPack, StreamTargetMap, StreamsOfMap, Trim, TrimBranch, UnusedTarget, }; pub(crate) mod connect; diff --git a/src/lib.rs b/src/lib.rs index 4c11e693..cc40b262 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -336,10 +336,10 @@ impl Plugin for ImpulsePlugin { pub mod prelude { pub use crate::{ buffer::{ - AnyBuffer, AnyBufferKey, AnyBufferMut, AnyBufferWorldAccess, AnyMessage, - Buffer, BufferAccess, BufferAccessMut, BufferKey, BufferSettings, Bufferable, - Buffered, IterBufferable, RetentionPolicy, BufferMapLayout, JoinedValue, BufferKeyMap, - IncompatibleLayout, + AnyBuffer, AnyBufferKey, AnyBufferMut, AnyBufferWorldAccess, AnyMessage, Buffer, + BufferAccess, BufferAccessMut, BufferKey, BufferKeyMap, BufferMapLayout, + BufferSettings, Bufferable, Buffered, IncompatibleLayout, IterBufferable, JoinedValue, + RetentionPolicy, }, builder::Builder, callback::{AsCallback, Callback, IntoAsyncCallback, IntoBlockingCallback}, diff --git a/src/operation/listen.rs b/src/operation/listen.rs index 43f7a22e..85561c2a 100644 --- a/src/operation/listen.rs +++ b/src/operation/listen.rs @@ -18,7 +18,7 @@ use bevy_ecs::prelude::Entity; use crate::{ - Accessed, buffer_key_usage, get_access_keys, BufferAccessStorage, BufferKeyUsage, + buffer_key_usage, get_access_keys, Accessed, BufferAccessStorage, BufferKeyUsage, FunnelInputStorage, Input, InputBundle, ManageInput, Operation, OperationCleanup, OperationReachability, OperationRequest, OperationResult, OperationSetup, OrBroken, ReachabilityResult, SingleInputStorage, SingleTargetStorage, diff --git a/src/operation/scope.rs b/src/operation/scope.rs index d5ac0d92..deb46cf1 100644 --- a/src/operation/scope.rs +++ b/src/operation/scope.rs @@ -17,14 +17,13 @@ use crate::{ check_reachability, execute_operation, is_downstream_of, Accessed, AddOperation, Blocker, - BufferKeyBuilder, Cancel, Cancellable, Cancellation, Cleanup, CleanupContents, - ClearBufferFn, CollectMarker, DisposalListener, DisposalUpdate, FinalizeCleanup, - FinalizeCleanupRequest, Input, InputBundle, InspectDisposals, ManageCancellation, ManageInput, - Operation, OperationCancel, OperationCleanup, OperationError, OperationReachability, - OperationRequest, OperationResult, OperationRoster, OperationSetup, OrBroken, - ReachabilityResult, ScopeSettings, SingleInputStorage, SingleTargetStorage, Stream, StreamPack, - StreamRequest, StreamTargetMap, StreamTargetStorage, UnhandledErrors, Unreachability, - UnusedTarget, + BufferKeyBuilder, Cancel, Cancellable, Cancellation, Cleanup, CleanupContents, ClearBufferFn, + CollectMarker, DisposalListener, DisposalUpdate, FinalizeCleanup, FinalizeCleanupRequest, + Input, InputBundle, InspectDisposals, ManageCancellation, ManageInput, Operation, + OperationCancel, OperationCleanup, OperationError, OperationReachability, OperationRequest, + OperationResult, OperationRoster, OperationSetup, OrBroken, ReachabilityResult, ScopeSettings, + SingleInputStorage, SingleTargetStorage, Stream, StreamPack, StreamRequest, StreamTargetMap, + StreamTargetStorage, UnhandledErrors, Unreachability, UnusedTarget, }; use backtrace::Backtrace; From 800a112f1cbbc5958a166f096f98eaa512136bc9 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Sun, 2 Feb 2025 18:36:01 +0000 Subject: [PATCH 19/37] Remove use of 1.75 unstable function Signed-off-by: Michael X. Grey --- src/buffer/any_buffer.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 3e098a1d..391719ba 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -514,12 +514,20 @@ pub(crate) struct AnyRange { impl AnyRange { pub(crate) fn new>(range: T) -> Self { AnyRange { - start_bound: range.start_bound().map(|x| *x), - end_bound: range.end_bound().map(|x| *x), + start_bound: deref_bound(range.start_bound()), + end_bound: deref_bound(range.end_bound()), } } } +fn deref_bound(bound: std::ops::Bound<&usize>) -> std::ops::Bound { + match bound { + std::ops::Bound::Included(v) => std::ops::Bound::Included(*v), + std::ops::Bound::Excluded(v) => std::ops::Bound::Excluded(*v), + std::ops::Bound::Unbounded => std::ops::Bound::Unbounded, + } +} + impl std::ops::RangeBounds for AnyRange { fn start_bound(&self) -> std::ops::Bound<&usize> { self.start_bound.as_ref() From de6596de56bf92ed64b8d7aba8b5399225a5b6c9 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 3 Feb 2025 23:00:10 +0800 Subject: [PATCH 20/37] Implement more general buffer downcasting Signed-off-by: Michael X. Grey --- .github/workflows/ci_linux.yaml | 2 +- src/buffer.rs | 58 ++++++++-- src/buffer/any_buffer.rs | 191 ++++++++++++++++++++++---------- src/buffer/buffer_map.rs | 2 +- src/buffer/bufferable.rs | 4 +- src/buffer/buffered.rs | 36 +++--- src/buffer/json_buffer.rs | 159 ++++++++++++++++++++------ src/builder.rs | 21 ++-- src/chain.rs | 2 +- 9 files changed, 339 insertions(+), 136 deletions(-) diff --git a/.github/workflows/ci_linux.yaml b/.github/workflows/ci_linux.yaml index 116de865..660d6933 100644 --- a/.github/workflows/ci_linux.yaml +++ b/.github/workflows/ci_linux.yaml @@ -50,5 +50,5 @@ jobs: run: cargo test --features single_threaded_async - name: Build docs - run: cargo doc + run: cargo doc --all-features diff --git a/src/buffer.rs b/src/buffer.rs index eb7facd8..f69cad6c 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -63,8 +63,7 @@ pub use json_buffer::*; /// and release data. When a session is finished, the buffered data from the /// session will be automatically cleared. pub struct Buffer { - pub(crate) scope: Entity, - pub(crate) source: Entity, + pub(crate) location: BufferLocation, pub(crate) _ignore: std::marker::PhantomData, } @@ -74,11 +73,11 @@ impl Buffer { &self, builder: &'b mut Builder<'w, 's, 'a>, ) -> Chain<'w, 's, 'a, 'b, ()> { - assert_eq!(self.scope, builder.scope); + assert_eq!(self.scope(), builder.scope); let target = builder.commands.spawn(UnusedTarget).id(); builder .commands - .add(OnNewBufferValue::new(self.source, target)); + .add(OnNewBufferValue::new(self.id(), target)); Chain::new(target, builder) } @@ -90,15 +89,29 @@ impl Buffer { T: Clone, { CloneFromBuffer { - scope: self.scope, - source: self.source, + location: self.location, _ignore: Default::default(), } } /// Get an input slot for this buffer. pub fn input_slot(self) -> InputSlot { - InputSlot::new(self.scope, self.source) + InputSlot::new(self.scope(), self.id()) + } + + /// Get the entity ID of the buffer. + pub fn id(&self) -> Entity { + self.location.source + } + + /// Get the ID of the workflow that the buffer is associated with. + pub fn scope(&self) -> Entity { + self.location.scope + } + + /// Get general information about the buffer. + pub fn location(&self) -> BufferLocation { + self.location } } @@ -110,13 +123,40 @@ impl Clone for Buffer { impl Copy for Buffer {} +/// Get the general identifying information for a buffer to locate it within the +/// world. This does not indicate anything about the type of messages that the +/// buffer can contain. +#[derive(Clone, Copy, Debug)] +pub struct BufferLocation { + /// The entity ID of the buffer. + pub scope: Entity, + /// The ID of the workflow that the buffer is associated with. + pub source: Entity, +} + #[derive(Clone, Copy)] pub struct CloneFromBuffer { - pub(crate) scope: Entity, - pub(crate) source: Entity, + pub(crate) location: BufferLocation, pub(crate) _ignore: std::marker::PhantomData, } +impl CloneFromBuffer { + /// Get the entity ID of the buffer. + pub fn id(&self) -> Entity { + self.location.source + } + + /// Get the ID of the workflow that the buffer is associated with. + pub fn scope(&self) -> Entity { + self.location.scope + } + + /// Get general information about the buffer. + pub fn location(&self) -> BufferLocation { + self.location + } +} + /// Settings to describe the behavior of a buffer. #[derive(Default, Clone, Copy)] pub struct BufferSettings { diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 391719ba..e67bc2d0 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -19,7 +19,7 @@ use std::{ any::{Any, TypeId}, - collections::HashMap, + collections::{hash_map::Entry, HashMap}, ops::RangeBounds, sync::{Arc, Mutex, OnceLock}, }; @@ -35,8 +35,8 @@ use smallvec::SmallVec; use crate::{ add_listener_to_source, Accessed, Buffer, BufferAccessLifecycle, BufferAccessMut, - BufferAccessors, BufferError, BufferKey, BufferStorage, Bufferable, Buffered, Builder, - DrainBuffer, Gate, GateState, InspectBuffer, Joined, ManageBuffer, NotifyBufferUpdate, + BufferAccessors, BufferError, BufferKey, BufferLocation, BufferStorage, Bufferable, Buffered, + Builder, DrainBuffer, Gate, GateState, InspectBuffer, Joined, ManageBuffer, NotifyBufferUpdate, OperationError, OperationResult, OperationRoster, OrBroken, }; @@ -44,54 +44,86 @@ use crate::{ /// type will yield an [`AnyMessage`]. #[derive(Clone, Copy)] pub struct AnyBuffer { - pub(crate) scope: Entity, - pub(crate) source: Entity, + pub(crate) location: BufferLocation, pub(crate) interface: &'static (dyn AnyBufferAccessInterface + Send + Sync), } impl AnyBuffer { /// The buffer ID for this key. pub fn id(&self) -> Entity { - self.source + self.location.source + } + + /// ID of the workflow that this buffer is associated with. + pub fn scope(&self) -> Entity { + self.location.scope } /// Get the type ID of the messages that this buffer supports. pub fn message_type_id(&self) -> TypeId { self.interface.message_type_id() } + + /// Get the [`AnyBufferAccessInterface`] for this specific instance of [`AnyBuffer`]. + pub fn get_interface(&self) -> &'static (dyn AnyBufferAccessInterface + Send + Sync) { + self.interface + } + + /// Get the [`AnyBufferAccessInterface`] for a concrete message type. + pub fn interface_for( + ) -> &'static (dyn AnyBufferAccessInterface + Send + Sync) { + static INTERFACE_MAP: OnceLock< + Mutex>, + > = OnceLock::new(); + let interfaces = INTERFACE_MAP.get_or_init(|| Mutex::default()); + + let mut interfaces_mut = interfaces.lock().unwrap(); + *interfaces_mut + .entry(TypeId::of::()) + .or_insert_with(|| Box::leak(Box::new(AnyBufferAccessImpl::::new()))) + } } impl std::fmt::Debug for AnyBuffer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("AnyBuffer") - .field("scope", &self.scope) - .field("source", &self.source) + .field("scope", &self.location.scope) + .field("source", &self.location.source) .field("message_type_name", &self.interface.message_type_name()) .finish() } } impl AnyBuffer { - /// Downcast this into a concrete [`Buffer`] type. - pub fn downcast(&self) -> Option> { - if TypeId::of::() == self.interface.message_type_id() { + /// Downcast this into a concrete [`Buffer`] for the specified message type. + /// + /// To downcast into a specialized kind of buffer, use [`Self::downcast_buffer`] instead. + pub fn downcast_for_message(&self) -> Option> { + if TypeId::of::() == self.interface.message_type_id() { Some(Buffer { - scope: self.scope, - source: self.source, + location: self.location, _ignore: Default::default(), }) } else { None } } + + /// Downcast this into a different special buffer representation, such as a + /// `JsonBuffer`. + pub fn downcast_buffer(&self) -> Option { + self.interface.buffer_downcast(TypeId::of::())?(self.location) + .downcast::() + .ok() + .map(|x| *x) + } } impl From> for AnyBuffer { fn from(value: Buffer) -> Self { - let interface = AnyBufferAccessImpl::::get_interface(); + let interface = AnyBuffer::interface_for::(); AnyBuffer { - scope: value.scope, - source: value.source, + location: value.location, interface, } } @@ -100,8 +132,10 @@ impl From> for AnyBuffer { /// Similar to a [`BufferKey`] except it can be used for any buffer without /// knowing the buffer's message type at compile time. /// -/// Use this with [`AnyBufferAccess`] to directly view or manipulate the contents -/// of a buffer. +/// This can key be used with a [`World`][1] to directly view or manipulate the +/// contents of a buffer through the [`AnyBufferWorldAccess`] interface. +/// +/// [1]: bevy_ecs::prelude::World #[derive(Clone)] pub struct AnyBufferKey { pub(crate) buffer: Entity, @@ -168,7 +202,7 @@ impl std::fmt::Debug for AnyBufferKey { impl From> for AnyBufferKey { fn from(value: BufferKey) -> Self { - let interface = AnyBufferAccessImpl::::get_interface(); + let interface = AnyBuffer::interface_for::(); AnyBufferKey { buffer: value.buffer, session: value.session, @@ -702,7 +736,7 @@ where Ok(*value) } -pub(crate) trait AnyBufferAccessMutState { +pub trait AnyBufferAccessMutState { fn get_any_buffer_access_mut<'s, 'w: 's>( &'s mut self, world: &'w mut World, @@ -720,7 +754,7 @@ impl AnyBufferAccessMutState } } -pub(crate) trait AnyBufferAccessMut<'w, 's> { +pub trait AnyBufferAccessMut<'w, 's> { fn as_any_buffer_mut<'a>( &'a mut self, key: &AnyBufferKey, @@ -750,7 +784,7 @@ impl<'w, 's, T: 'static + Send + Sync + Any> AnyBufferAccessMut<'w, 's> } } -pub(crate) trait AnyBufferAccessInterface { +pub trait AnyBufferAccessInterface { fn message_type_id(&self) -> TypeId; fn message_type_name(&self) -> &'static str; @@ -759,6 +793,10 @@ pub(crate) trait AnyBufferAccessInterface { fn ensure_session(&self, entity_mut: &mut EntityWorldMut, session: Entity) -> OperationResult; + fn register_buffer_downcast(&self, buffer_type: TypeId, f: BufferDowncastBox); + + fn buffer_downcast(&self, buffer_type: TypeId) -> Option; + fn pull( &self, entity_mut: &mut EntityWorldMut, @@ -777,21 +815,33 @@ pub(crate) trait AnyBufferAccessInterface { ) -> Box; } -pub(crate) struct AnyBufferAccessImpl(std::marker::PhantomData); +pub type BufferDowncastBox = Box Box + Send + Sync>; +pub type BufferDowncastRef = &'static (dyn Fn(BufferLocation) -> Box + Send + Sync); -impl AnyBufferAccessImpl { - pub(crate) fn get_interface() -> &'static (dyn AnyBufferAccessInterface + Send + Sync) { - const INTERFACE_MAP: OnceLock< - Mutex>, - > = OnceLock::new(); - let binding = INTERFACE_MAP; - - let interfaces = binding.get_or_init(|| Mutex::default()); +struct AnyBufferAccessImpl { + buffer_downcasts: Mutex>, + _ignore: std::marker::PhantomData, +} - let mut interfaces_mut = interfaces.lock().unwrap(); - *interfaces_mut - .entry(TypeId::of::()) - .or_insert_with(|| Box::leak(Box::new(AnyBufferAccessImpl::(Default::default())))) +impl AnyBufferAccessImpl { + fn new() -> Self { + let mut buffer_downcasts: HashMap<_, BufferDowncastRef> = HashMap::new(); + + // Automatically register a downcast into AnyBuffer + buffer_downcasts.insert( + TypeId::of::(), + Box::leak(Box::new(|location| -> Box { + Box::new(AnyBuffer { + location, + interface: AnyBuffer::interface_for::(), + }) + })), + ); + + Self { + buffer_downcasts: Mutex::new(buffer_downcasts), + _ignore: Default::default(), + } } } @@ -812,6 +862,23 @@ impl AnyBufferAccessInterface for AnyBufferAcces entity_mut.ensure_session::(session) } + fn register_buffer_downcast(&self, buffer_type: TypeId, f: BufferDowncastBox) { + let mut downcasts = self.buffer_downcasts.lock().unwrap(); + + if let Entry::Vacant(entry) = downcasts.entry(buffer_type) { + // We should only leak this into the register once per type + entry.insert(Box::leak(f)); + } + } + + fn buffer_downcast(&self, buffer_type: TypeId) -> Option { + self.buffer_downcasts + .lock() + .unwrap() + .get(&buffer_type) + .copied() + } + fn pull( &self, entity_mut: &mut EntityWorldMut, @@ -876,23 +943,23 @@ impl DrainAnyBufferInterface for DrainBuffer<'_, impl Bufferable for AnyBuffer { type BufferType = Self; fn into_buffer(self, builder: &mut Builder) -> Self::BufferType { - assert_eq!(self.scope, builder.scope()); + assert_eq!(self.scope(), builder.scope()); self } } impl Buffered for AnyBuffer { fn verify_scope(&self, scope: Entity) { - assert_eq!(scope, self.scope); + assert_eq!(scope, self.scope()); } fn buffered_count(&self, session: Entity, world: &World) -> Result { - let entity_ref = world.get_entity(self.source).or_broken()?; + let entity_ref = world.get_entity(self.id()).or_broken()?; self.interface.buffered_count(&entity_ref, session) } fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { - add_listener_to_source(self.source, listener, world) + add_listener_to_source(self.id(), listener, world) } fn gate_action( @@ -902,15 +969,15 @@ impl Buffered for AnyBuffer { world: &mut World, roster: &mut OperationRoster, ) -> OperationResult { - GateState::apply(self.source, session, action, world, roster) + GateState::apply(self.id(), session, action, world, roster) } fn as_input(&self) -> SmallVec<[Entity; 8]> { - SmallVec::from_iter([self.source]) + SmallVec::from_iter([self.id()]) } fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { - let mut entity_mut = world.get_entity_mut(self.source).or_broken()?; + let mut entity_mut = world.get_entity_mut(self.id()).or_broken()?; self.interface.ensure_session(&mut entity_mut, session) } } @@ -918,7 +985,7 @@ impl Buffered for AnyBuffer { impl Joined for AnyBuffer { type Item = AnyMessage; fn pull(&self, session: Entity, world: &mut World) -> Result { - let mut buffer_mut = world.get_entity_mut(self.source).or_broken()?; + let mut buffer_mut = world.get_entity_mut(self.id()).or_broken()?; self.interface.pull(&mut buffer_mut, session) } } @@ -927,14 +994,14 @@ impl Accessed for AnyBuffer { type Key = AnyBufferKey; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { world - .get_mut::(self.source) + .get_mut::(self.id()) .or_broken()? .add_accessor(accessor); Ok(()) } fn create_key(&self, builder: &super::BufferKeyBuilder) -> Self::Key { - let components = builder.as_components(self.source); + let components = builder.as_components(self.id()); AnyBufferKey { buffer: components.buffer, session: components.session, @@ -1126,20 +1193,26 @@ mod tests { .into(); let (input_u32, input_i32, input_f32) = scope.input.chain(builder).unzip(); - input_u32 - .chain(builder) - .map_block(|v| 2 * v) - .connect(buffer_u32.downcast::().unwrap().input_slot()); - - input_i32 - .chain(builder) - .map_block(|v| 2 * v) - .connect(buffer_i32.downcast::().unwrap().input_slot()); - - input_f32 - .chain(builder) - .map_block(|v| 2.0 * v) - .connect(buffer_f32.downcast::().unwrap().input_slot()); + input_u32.chain(builder).map_block(|v| 2 * v).connect( + buffer_u32 + .downcast_for_message::() + .unwrap() + .input_slot(), + ); + + input_i32.chain(builder).map_block(|v| 2 * v).connect( + buffer_i32 + .downcast_for_message::() + .unwrap() + .input_slot(), + ); + + input_f32.chain(builder).map_block(|v| 2.0 * v).connect( + buffer_f32 + .downcast_for_message::() + .unwrap() + .input_slot(), + ); (buffer_u32, buffer_i32, buffer_f32) .join(builder) diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index d2902a1e..e3ac852a 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -219,7 +219,7 @@ impl Clone for BufferedMap { impl Buffered for BufferedMap { fn verify_scope(&self, scope: Entity) { for buffer in self.map.inner.values() { - assert_eq!(scope, buffer.scope); + assert_eq!(scope, buffer.scope()); } } diff --git a/src/buffer/bufferable.rs b/src/buffer/bufferable.rs index b6eaa704..628ba899 100644 --- a/src/buffer/bufferable.rs +++ b/src/buffer/bufferable.rs @@ -156,7 +156,7 @@ pub trait Bufferable { impl Bufferable for Buffer { type BufferType = Self; fn into_buffer(self, builder: &mut Builder) -> Self::BufferType { - assert_eq!(self.scope, builder.scope()); + assert_eq!(self.scope(), builder.scope()); self } } @@ -164,7 +164,7 @@ impl Bufferable for Buffer { impl Bufferable for CloneFromBuffer { type BufferType = Self; fn into_buffer(self, builder: &mut Builder) -> Self::BufferType { - assert_eq!(self.scope, builder.scope()); + assert_eq!(self.scope(), builder.scope()); self } } diff --git a/src/buffer/buffered.rs b/src/buffer/buffered.rs index c9201ef1..50e63493 100644 --- a/src/buffer/buffered.rs +++ b/src/buffer/buffered.rs @@ -61,18 +61,18 @@ pub trait Accessed: Buffered { impl Buffered for Buffer { fn verify_scope(&self, scope: Entity) { - assert_eq!(scope, self.scope); + assert_eq!(scope, self.scope()); } fn buffered_count(&self, session: Entity, world: &World) -> Result { world - .get_entity(self.source) + .get_entity(self.id()) .or_broken()? .buffered_count::(session) } fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { - add_listener_to_source(self.source, listener, world) + add_listener_to_source(self.id(), listener, world) } fn gate_action( @@ -82,16 +82,16 @@ impl Buffered for Buffer { world: &mut World, roster: &mut OperationRoster, ) -> OperationResult { - GateState::apply(self.source, session, action, world, roster) + GateState::apply(self.id(), session, action, world, roster) } fn as_input(&self) -> SmallVec<[Entity; 8]> { - SmallVec::from_iter([self.source]) + SmallVec::from_iter([self.id()]) } fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { world - .get_mut::>(self.source) + .get_mut::>(self.id()) .or_broken()? .ensure_session(session); Ok(()) @@ -102,7 +102,7 @@ impl Joined for Buffer { type Item = T; fn pull(&self, session: Entity, world: &mut World) -> Result { world - .get_entity_mut(self.source) + .get_entity_mut(self.id()) .or_broken()? .pull_from_buffer::(session) } @@ -112,14 +112,14 @@ impl Accessed for Buffer { type Key = BufferKey; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { world - .get_mut::(self.source) + .get_mut::(self.id()) .or_broken()? .add_accessor(accessor); Ok(()) } fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key { - builder.build(self.source) + builder.build(self.id()) } fn deep_clone_key(key: &Self::Key) -> Self::Key { @@ -133,18 +133,18 @@ impl Accessed for Buffer { impl Buffered for CloneFromBuffer { fn verify_scope(&self, scope: Entity) { - assert_eq!(scope, self.scope); + assert_eq!(scope, self.scope()); } fn buffered_count(&self, session: Entity, world: &World) -> Result { world - .get_entity(self.source) + .get_entity(self.id()) .or_broken()? .buffered_count::(session) } fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { - add_listener_to_source(self.source, listener, world) + add_listener_to_source(self.id(), listener, world) } fn gate_action( @@ -154,16 +154,16 @@ impl Buffered for CloneFromBuffer { world: &mut World, roster: &mut OperationRoster, ) -> OperationResult { - GateState::apply(self.source, session, action, world, roster) + GateState::apply(self.id(), session, action, world, roster) } fn as_input(&self) -> SmallVec<[Entity; 8]> { - SmallVec::from_iter([self.source]) + SmallVec::from_iter([self.id()]) } fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { world - .get_entity_mut(self.source) + .get_entity_mut(self.id()) .or_broken()? .ensure_session::(session) } @@ -173,7 +173,7 @@ impl Joined for CloneFromBuffer { type Item = T; fn pull(&self, session: Entity, world: &mut World) -> Result { world - .get_entity(self.source) + .get_entity(self.id()) .or_broken()? .try_clone_from_buffer(session) .and_then(|r| r.or_broken()) @@ -184,14 +184,14 @@ impl Accessed for CloneFromBuffer { type Key = BufferKey; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { world - .get_mut::(self.source) + .get_mut::(self.id()) .or_broken()? .add_accessor(accessor); Ok(()) } fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key { - builder.build(self.source) + builder.build(self.id()) } fn deep_clone_key(key: &Self::Key) -> Self::Key { diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index a427e636..b77cf113 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -36,11 +36,11 @@ pub use serde_json::Value as JsonMessage; use smallvec::SmallVec; use crate::{ - add_listener_to_source, Accessed, AnyBuffer, AnyBufferAccessImpl, AnyBufferAccessInterface, - AnyBufferKey, AnyRange, Buffer, BufferAccessLifecycle, BufferAccessMut, BufferAccessors, - BufferError, BufferKey, BufferKeyBuilder, BufferStorage, Bufferable, Buffered, Builder, - DrainBuffer, Gate, GateState, InspectBuffer, Joined, ManageBuffer, NotifyBufferUpdate, - OperationError, OperationResult, OrBroken, + add_listener_to_source, Accessed, AnyBuffer, AnyBufferAccessInterface, AnyBufferKey, AnyRange, + Buffer, BufferAccessLifecycle, BufferAccessMut, BufferAccessors, BufferError, BufferKey, + BufferKeyBuilder, BufferLocation, BufferStorage, Bufferable, Buffered, Builder, DrainBuffer, + Gate, GateState, InspectBuffer, Joined, ManageBuffer, NotifyBufferUpdate, OperationError, + OperationResult, OrBroken, }; /// A [`Buffer`] whose message type has been anonymized, but which is known to @@ -48,31 +48,71 @@ use crate::{ /// yield a [`JsonMessage`]. #[derive(Clone, Copy, Debug)] pub struct JsonBuffer { - scope: Entity, - source: Entity, + location: BufferLocation, interface: &'static (dyn JsonBufferAccessInterface + Send + Sync), } impl JsonBuffer { - /// Downcast this into a concerete [`Buffer`] type. - pub fn downcast(&self) -> Option> { + /// Downcast this into a concerete [`Buffer`] for the specific message type. + /// + /// To downcast this into a specialized kind of buffer, use [`Self::downcast_buffer`] instead. + pub fn downcast_for_message(&self) -> Option> { if TypeId::of::() == self.interface.any_access_interface().message_type_id() { Some(Buffer { - scope: self.scope, - source: self.source, + location: self.location, _ignore: Default::default(), }) } else { None } } + + /// Downcast this into a different specialized buffer representation. + pub fn downcast_buffer(&self) -> Option { + self.as_any_buffer().downcast_buffer::() + } + + /// Cast this into an [`AnyBuffer`]. + pub fn as_any_buffer(&self) -> AnyBuffer { + self.clone().into() + } + + /// Register the ability to cast into [`JsonBuffer`] and [`JsonBufferKey`] + /// for buffers containing messages of type `T`. This only needs to be done + /// once in the entire lifespan of a program. + /// + /// Note that this will take effect automatically any time you create an + /// instance of [`JsonBuffer`] or [`JsonBufferKey`] for a buffer with + /// messages of type `T`. + pub fn register_for() + where + T: 'static + Serialize + DeserializeOwned + Send + Sync, + { + // We just need to ensure that this function gets called so that the + // downcast callback gets registered. Nothing more needs to be done. + JsonBufferAccessImpl::::get_interface(); + } + + /// Get the entity ID of the buffer. + pub fn id(&self) -> Entity { + self.location.source + } + + /// Get the ID of the workflow that the buffer is associated with. + pub fn scope(&self) -> Entity { + self.location.scope + } + + /// Get general information about the buffer. + pub fn location(&self) -> BufferLocation { + self.location + } } impl From> for JsonBuffer { fn from(value: Buffer) -> Self { Self { - scope: value.scope, - source: value.source, + location: value.location, interface: JsonBufferAccessImpl::::get_interface(), } } @@ -81,8 +121,7 @@ impl From> fo impl From for AnyBuffer { fn from(value: JsonBuffer) -> Self { Self { - scope: value.scope, - source: value.source, + location: value.location, interface: value.interface.any_access_interface(), } } @@ -92,8 +131,10 @@ impl From for AnyBuffer { /// serialization and deserialization without knowing the buffer's specific /// message type at compile time. /// -/// Use this with [`JsonBufferAccess`] to directly view or manipulate the contents -/// of a buffer. +/// This can key be used with a [`World`][1] to directly view or manipulate the +/// contents of a buffer through the [`JsonBufferWorldAccess`] interface. +/// +/// [1]: bevy_ecs::prelude::World #[derive(Clone)] pub struct JsonBufferKey { buffer: Entity, @@ -104,7 +145,10 @@ pub struct JsonBufferKey { } impl JsonBufferKey { - pub fn downcast(&self) -> Option> { + /// Downcast this into a concrete [`BufferKey`] for the specified message type. + /// + /// To downcast into a specialized kind of buffer key, use [`Self::downcast_buffer_key`] instead. + pub fn downcast_for_message(&self) -> Option> { if TypeId::of::() == self.interface.any_access_interface().message_type_id() { Some(BufferKey { buffer: self.buffer, @@ -118,6 +162,11 @@ impl JsonBufferKey { } } + /// Cast this into an [`AnyBufferKey`] + pub fn as_any_buffer_key(&self) -> AnyBufferKey { + self.clone().into() + } + fn deep_clone(&self) -> Self { let mut deep = self.clone(); deep.lifecycle = self @@ -777,12 +826,23 @@ struct JsonBufferAccessImpl(std::marker::PhantomData); impl JsonBufferAccessImpl { pub(crate) fn get_interface() -> &'static (dyn JsonBufferAccessInterface + Send + Sync) { - const INTERFACE_MAP: OnceLock< + // Register downcasting for JsonBuffer and JsonBufferKey + let any_interface = AnyBuffer::interface_for::(); + any_interface.register_buffer_downcast( + TypeId::of::(), + Box::new(|location| { + Box::new(JsonBuffer { + location, + interface: Self::get_interface(), + }) + }), + ); + + // Create and cache the json buffer access interface + static INTERFACE_MAP: OnceLock< Mutex>, > = OnceLock::new(); - let binding = INTERFACE_MAP; - - let interfaces = binding.get_or_init(|| Mutex::default()); + let interfaces = INTERFACE_MAP.get_or_init(|| Mutex::default()); let mut interfaces_mut = interfaces.lock().unwrap(); *interfaces_mut @@ -795,7 +855,7 @@ impl JsonBufferAccessIn for JsonBufferAccessImpl { fn any_access_interface(&self) -> &'static (dyn AnyBufferAccessInterface + Send + Sync) { - AnyBufferAccessImpl::::get_interface() + AnyBuffer::interface_for::() } fn buffered_count( @@ -923,23 +983,23 @@ impl DrainJsonBufferInterface for DrainBuf impl Bufferable for JsonBuffer { type BufferType = Self; fn into_buffer(self, builder: &mut Builder) -> Self::BufferType { - assert_eq!(self.scope, builder.scope()); + assert_eq!(self.scope(), builder.scope()); self } } impl Buffered for JsonBuffer { fn verify_scope(&self, scope: Entity) { - assert_eq!(scope, self.scope); + assert_eq!(scope, self.scope()); } fn buffered_count(&self, session: Entity, world: &World) -> Result { - let buffer_ref = world.get_entity(self.source).or_broken()?; + let buffer_ref = world.get_entity(self.id()).or_broken()?; self.interface.buffered_count(&buffer_ref, session) } fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { - add_listener_to_source(self.source, listener, world) + add_listener_to_source(self.id(), listener, world) } fn gate_action( @@ -949,15 +1009,15 @@ impl Buffered for JsonBuffer { world: &mut World, roster: &mut crate::OperationRoster, ) -> OperationResult { - GateState::apply(self.source, session, action, world, roster) + GateState::apply(self.id(), session, action, world, roster) } fn as_input(&self) -> smallvec::SmallVec<[Entity; 8]> { - SmallVec::from_iter([self.source]) + SmallVec::from_iter([self.id()]) } fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { - let mut buffer_mut = world.get_entity_mut(self.source).or_broken()?; + let mut buffer_mut = world.get_entity_mut(self.id()).or_broken()?; self.interface.ensure_session(&mut buffer_mut, session) } } @@ -965,7 +1025,7 @@ impl Buffered for JsonBuffer { impl Joined for JsonBuffer { type Item = JsonMessage; fn pull(&self, session: Entity, world: &mut World) -> Result { - let mut buffer_mut = world.get_entity_mut(self.source).or_broken()?; + let mut buffer_mut = world.get_entity_mut(self.id()).or_broken()?; self.interface.pull(&mut buffer_mut, session) } } @@ -974,14 +1034,14 @@ impl Accessed for JsonBuffer { type Key = JsonBufferKey; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { world - .get_mut::(self.source) + .get_mut::(self.id()) .or_broken()? .add_accessor(accessor); Ok(()) } fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key { - let components = builder.as_components(self.source); + let components = builder.as_components(self.id()); JsonBufferKey { buffer: components.buffer, session: components.session, @@ -1216,7 +1276,7 @@ mod tests { }) .connect( buffer_double_u32 - .downcast::() + .downcast_for_message::() .unwrap() .input_slot(), ) @@ -1229,7 +1289,7 @@ mod tests { }) .connect( buffer_double_i32 - .downcast::() + .downcast_for_message::() .unwrap() .input_slot(), ) @@ -1242,7 +1302,7 @@ mod tests { }) .connect( buffer_double_string - .downcast::() + .downcast_for_message::() .unwrap() .input_slot(), ) @@ -1268,4 +1328,31 @@ mod tests { ); assert!(context.no_unhandled_errors()); } + + #[test] + fn test_buffer_downcast() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + // We just need to test that these buffers can be downcast without + // a panic occurring. + JsonBuffer::register_for::(); + let buffer = builder.create_buffer::(BufferSettings::keep_all()); + let any_buffer: AnyBuffer = buffer.into(); + let json_buffer: JsonBuffer = any_buffer.downcast_buffer().unwrap(); + let _original_from_any: Buffer = + any_buffer.downcast_for_message().unwrap(); + let _original_from_json: Buffer = + json_buffer.downcast_for_message().unwrap(); + + builder.connect(scope.input, scope.terminate); + }); + + let mut promise = context.command(|commands| commands.request(1, workflow).take_response()); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let response = promise.take().available().unwrap(); + assert_eq!(1, response); + assert!(context.no_unhandled_errors()); + } } diff --git a/src/builder.rs b/src/builder.rs index 28ac6787..fe41eea2 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -23,13 +23,14 @@ use std::future::Future; use smallvec::SmallVec; use crate::{ - AddOperation, AsMap, BeginCleanupWorkflow, Buffer, BufferKeys, BufferMap, BufferSettings, - Bufferable, Buffered, Chain, Collect, ForkClone, ForkCloneOutput, ForkTargetStorage, Gate, - GateRequest, IncompatibleLayout, Injection, InputSlot, IntoAsyncMap, IntoBlockingMap, - JoinedItem, JoinedValue, Node, OperateBuffer, OperateBufferAccess, OperateDynamicGate, - OperateScope, OperateSplit, OperateStaticGate, Output, Provider, RequestOfMap, ResponseOfMap, - Scope, ScopeEndpoints, ScopeSettings, ScopeSettingsStorage, Sendish, Service, SplitOutputs, - Splittable, StreamPack, StreamTargetMap, StreamsOfMap, Trim, TrimBranch, UnusedTarget, + AddOperation, AsMap, BeginCleanupWorkflow, Buffer, BufferKeys, BufferLocation, BufferMap, + BufferSettings, Bufferable, Buffered, Chain, Collect, ForkClone, ForkCloneOutput, + ForkTargetStorage, Gate, GateRequest, IncompatibleLayout, Injection, InputSlot, IntoAsyncMap, + IntoBlockingMap, JoinedItem, JoinedValue, Node, OperateBuffer, OperateBufferAccess, + OperateDynamicGate, OperateScope, OperateSplit, OperateStaticGate, Output, Provider, + RequestOfMap, ResponseOfMap, Scope, ScopeEndpoints, ScopeSettings, ScopeSettingsStorage, + Sendish, Service, SplitOutputs, Splittable, StreamPack, StreamTargetMap, StreamsOfMap, Trim, + TrimBranch, UnusedTarget, }; pub(crate) mod connect; @@ -165,8 +166,10 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { )); Buffer { - scope: self.scope, - source, + location: BufferLocation { + scope: self.scope(), + source, + }, _ignore: Default::default(), } } diff --git a/src/chain.rs b/src/chain.rs index 7fa5ffa5..46dbba11 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -546,7 +546,7 @@ impl<'w, 's, 'a, 'b, T: 'static + Send + Sync> Chain<'w, 's, 'a, 'b, T> { /// If the buffer is broken (e.g. its operation has been despawned) the /// workflow will be cancelled. pub fn then_push(self, buffer: Buffer) -> Chain<'w, 's, 'a, 'b, ()> { - assert_eq!(self.scope(), buffer.scope); + assert_eq!(self.scope(), buffer.scope()); self.with_access(buffer) .then(push_into_buffer.into_blocking_callback()) .cancel_on_err() From bd32a47b5179a8683728d309e7d61d8a80ea98f9 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 3 Feb 2025 23:04:37 +0800 Subject: [PATCH 21/37] fix doc comments Signed-off-by: Michael X. Grey --- src/buffer/json_buffer.rs | 4 +-- src/diagram.rs | 64 ++++++++++++++++++------------------- src/diagram/registration.rs | 2 +- 3 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index b77cf113..0a71d6bd 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -146,8 +146,6 @@ pub struct JsonBufferKey { impl JsonBufferKey { /// Downcast this into a concrete [`BufferKey`] for the specified message type. - /// - /// To downcast into a specialized kind of buffer key, use [`Self::downcast_buffer_key`] instead. pub fn downcast_for_message(&self) -> Option> { if TypeId::of::() == self.interface.any_access_interface().message_type_id() { Some(BufferKey { @@ -420,7 +418,7 @@ impl<'w, 's, 'a> JsonBufferMut<'w, 's, 'a> { self.storage.json_push_as_oldest(self.session, message) } - /// Same as [`Self:push_as_oldest`] but no serialization step is needed for + /// Same as [`Self::push_as_oldest`] but no serialization step is needed for /// the incoming message. pub fn push_json_as_oldest( &mut self, diff --git a/src/diagram.rs b/src/diagram.rs index f6b70203..b4822a36 100644 --- a/src/diagram.rs +++ b/src/diagram.rs @@ -375,37 +375,7 @@ pub struct Diagram { } impl Diagram { - /// Spawns a workflow from this diagram. - /// - /// # Examples - /// - /// ``` - /// use bevy_impulse::{Diagram, DiagramError, NodeBuilderOptions, DiagramElementRegistry, RunCommandsOnWorldExt}; - /// - /// let mut app = bevy_app::App::new(); - /// let mut registry = DiagramElementRegistry::new(); - /// registry.register_node_builder(NodeBuilderOptions::new("echo".to_string()), |builder, _config: ()| { - /// builder.create_map_block(|msg: String| msg) - /// }); - /// - /// let json_str = r#" - /// { - /// "version": "0.1.0", - /// "start": "echo", - /// "ops": { - /// "echo": { - /// "type": "node", - /// "builder": "echo", - /// "next": { "builtin": "terminate" } - /// } - /// } - /// } - /// "#; - /// - /// let diagram = Diagram::from_json_str(json_str)?; - /// let workflow = app.world.command(|cmds| diagram.spawn_io_workflow(cmds, ®istry))?; - /// # Ok::<_, DiagramError>(()) - /// ``` + /// Implementation for [Self::spawn_io_workflow]. // TODO(koonpeng): Support streams other than `()` #43. /* pub */ fn spawn_workflow( @@ -447,7 +417,37 @@ impl Diagram { Ok(w) } - /// Wrapper to [spawn_workflow::<()>](Self::spawn_workflow). + /// Spawns a workflow from this diagram. + /// + /// # Examples + /// + /// ``` + /// use bevy_impulse::{Diagram, DiagramError, NodeBuilderOptions, DiagramElementRegistry, RunCommandsOnWorldExt}; + /// + /// let mut app = bevy_app::App::new(); + /// let mut registry = DiagramElementRegistry::new(); + /// registry.register_node_builder(NodeBuilderOptions::new("echo".to_string()), |builder, _config: ()| { + /// builder.create_map_block(|msg: String| msg) + /// }); + /// + /// let json_str = r#" + /// { + /// "version": "0.1.0", + /// "start": "echo", + /// "ops": { + /// "echo": { + /// "type": "node", + /// "builder": "echo", + /// "next": { "builtin": "terminate" } + /// } + /// } + /// } + /// "#; + /// + /// let diagram = Diagram::from_json_str(json_str)?; + /// let workflow = app.world.command(|cmds| diagram.spawn_io_workflow(cmds, ®istry))?; + /// # Ok::<_, DiagramError>(()) + /// ``` pub fn spawn_io_workflow( &self, cmds: &mut Commands, diff --git a/src/diagram/registration.rs b/src/diagram/registration.rs index 66afb59f..d0ce66e9 100644 --- a/src/diagram/registration.rs +++ b/src/diagram/registration.rs @@ -985,7 +985,7 @@ impl DiagramElementRegistry { /// Register a node builder with all the common operations (deserialize the /// request, serialize the response, and clone the response) enabled. /// - /// You will receive a [`RegistrationBuilder`] which you can then use to + /// You will receive a [`NodeRegistrationBuilder`] which you can then use to /// enable more operations around your node, such as fork result, split, /// or unzip. The data types of your node need to be suitable for those /// operations or else the compiler will not allow you to enable them. From c419ab3611c4aa4506cefb10e05a160b43716eb2 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 3 Feb 2025 23:33:43 +0800 Subject: [PATCH 22/37] Refactor buffer keys Signed-off-by: Michael X. Grey --- src/buffer.rs | 86 ++++++++++++++++++++++++-------- src/buffer/any_buffer.rs | 67 +++++++++---------------- src/buffer/buffer_key_builder.rs | 27 ++-------- src/buffer/json_buffer.rs | 64 ++++++++---------------- 4 files changed, 112 insertions(+), 132 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index f69cad6c..3b1928d8 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -123,7 +123,7 @@ impl Clone for Buffer { impl Copy for Buffer {} -/// Get the general identifying information for a buffer to locate it within the +/// The general identifying information for a buffer to locate it within the /// world. This does not indicate anything about the type of messages that the /// buffer can contain. #[derive(Clone, Copy, Debug)] @@ -227,20 +227,14 @@ impl Default for RetentionPolicy { /// [1]: crate::Chain::with_access /// [2]: crate::Bufferable::listen pub struct BufferKey { - buffer: Entity, - session: Entity, - accessor: Entity, - lifecycle: Option>, + tag: BufferKeyTag, _ignore: std::marker::PhantomData, } impl Clone for BufferKey { fn clone(&self) -> Self { Self { - buffer: self.buffer, - session: self.session, - accessor: self.accessor, - lifecycle: self.lifecycle.as_ref().map(Arc::clone), + tag: self.tag.clone(), _ignore: Default::default(), } } @@ -248,17 +242,21 @@ impl Clone for BufferKey { impl BufferKey { /// The buffer ID of this key. - pub fn id(&self) -> Entity { - self.buffer + pub fn buffer(&self) -> Entity { + self.tag.buffer } /// The session that this key belongs to. pub fn session(&self) -> Entity { - self.session + self.tag.session + } + + pub(crate) fn tag(&self) -> &BufferKeyTag { + &self.tag } pub(crate) fn is_in_use(&self) -> bool { - self.lifecycle.as_ref().is_some_and(|l| l.is_in_use()) + self.tag.is_in_use() } // We do a deep clone of the key when distributing it to decouple the @@ -270,6 +268,41 @@ impl BufferKey { // need to have their own independent lifecycles or else we won't detect // when the workflow has dropped them. pub(crate) fn deep_clone(&self) -> Self { + Self { + tag: self.tag.deep_clone(), + _ignore: Default::default(), + } + } +} + +impl std::fmt::Debug for BufferKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BufferKey") + .field("message_type_name", &std::any::type_name::()) + .field("tag", &self.tag) + .finish() + } +} + +/// The identifying information for a buffer key. This does not indicate +/// anything about the type of messages that the buffer can contain. +/// +/// This struct will be internal to the crate until we decide to make +/// [`BufferAccessLifecycle`] a public struct. +#[derive(Clone)] +pub(crate) struct BufferKeyTag { + pub buffer: Entity, + pub session: Entity, + pub accessor: Entity, + pub lifecycle: Option>, +} + +impl BufferKeyTag { + pub fn is_in_use(&self) -> bool { + self.lifecycle.as_ref().is_some_and(|l| l.is_in_use()) + } + + pub fn deep_clone(&self) -> Self { let mut deep = self.clone(); deep.lifecycle = self .lifecycle @@ -279,6 +312,17 @@ impl BufferKey { } } +impl std::fmt::Debug for BufferKeyTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BufferKeyTag") + .field("buffer", &self.buffer) + .field("session", &self.session) + .field("accessor", &self.accessor) + .field("in_use", &self.is_in_use()) + .finish() + } +} + /// This system parameter lets you get read-only access to a buffer that exists /// within a workflow. Use a [`BufferKey`] to unlock the access. /// @@ -293,9 +337,9 @@ where impl<'w, 's, T: 'static + Send + Sync> BufferAccess<'w, 's, T> { pub fn get<'a>(&'a self, key: &BufferKey) -> Result, QueryEntityError> { - let session = key.session; + let session = key.session(); self.query - .get(key.buffer) + .get(key.buffer()) .map(|(storage, gate)| BufferView { storage, gate, @@ -322,9 +366,9 @@ where T: 'static + Send + Sync, { pub fn get<'a>(&'a self, key: &BufferKey) -> Result, QueryEntityError> { - let session = key.session; + let session = key.session(); self.query - .get(key.buffer) + .get(key.buffer()) .map(|(storage, gate)| BufferView { storage, gate, @@ -336,10 +380,10 @@ where &'a mut self, key: &BufferKey, ) -> Result, QueryEntityError> { - let buffer = key.buffer; - let session = key.session; - let accessor = key.accessor; - self.query.get_mut(key.buffer).map(|(storage, gate)| { + let buffer = key.buffer(); + let session = key.session(); + let accessor = key.tag.accessor; + self.query.get_mut(key.buffer()).map(|(storage, gate)| { BufferMut::new(storage, gate, buffer, session, accessor, &mut self.commands) }) } diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index e67bc2d0..38c1cdb0 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -35,9 +35,9 @@ use smallvec::SmallVec; use crate::{ add_listener_to_source, Accessed, Buffer, BufferAccessLifecycle, BufferAccessMut, - BufferAccessors, BufferError, BufferKey, BufferLocation, BufferStorage, Bufferable, Buffered, - Builder, DrainBuffer, Gate, GateState, InspectBuffer, Joined, ManageBuffer, NotifyBufferUpdate, - OperationError, OperationResult, OperationRoster, OrBroken, + BufferAccessors, BufferError, BufferKey, BufferKeyTag, BufferLocation, BufferStorage, + Bufferable, Buffered, Builder, DrainBuffer, Gate, GateState, InspectBuffer, Joined, + ManageBuffer, NotifyBufferUpdate, OperationError, OperationResult, OperationRoster, OrBroken, }; /// A [`Buffer`] whose message type has been anonymized. Joining with this buffer @@ -138,10 +138,7 @@ impl From> for AnyBuffer { /// [1]: bevy_ecs::prelude::World #[derive(Clone)] pub struct AnyBufferKey { - pub(crate) buffer: Entity, - pub(crate) session: Entity, - pub(crate) accessor: Entity, - pub(crate) lifecycle: Option>, + pub(crate) tag: BufferKeyTag, pub(crate) interface: &'static (dyn AnyBufferAccessInterface + Send + Sync), } @@ -150,10 +147,7 @@ impl AnyBufferKey { pub fn downcast(&self) -> Option> { if TypeId::of::() == self.interface.message_type_id() { Some(BufferKey { - buffer: self.buffer, - session: self.session, - accessor: self.accessor, - lifecycle: self.lifecycle.clone(), + tag: self.tag.clone(), _ignore: Default::default(), }) } else { @@ -163,39 +157,31 @@ impl AnyBufferKey { /// The buffer ID of this key. pub fn id(&self) -> Entity { - self.buffer + self.tag.buffer } /// The session that this key belongs to. pub fn session(&self) -> Entity { - self.session + self.tag.session } - fn deep_clone(&self) -> Self { - let mut deep = self.clone(); - deep.lifecycle = self - .lifecycle - .as_ref() - .map(|l| Arc::new(l.as_ref().clone())); - deep + fn is_in_use(&self) -> bool { + self.tag.is_in_use() } - fn is_in_use(&self) -> bool { - self.lifecycle.as_ref().is_some_and(|l| l.is_in_use()) + fn deep_clone(&self) -> Self { + Self { + tag: self.tag.deep_clone(), + interface: self.interface, + } } } impl std::fmt::Debug for AnyBufferKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("AnyBufferKey") - .field("buffer", &self.buffer) - .field("session", &self.session) - .field("accessor", &self.accessor) - .field( - "in_use", - &self.lifecycle.as_ref().is_some_and(|l| l.is_in_use()), - ) .field("message_type_name", &self.interface.message_type_name()) + .field("tag", &self.tag) .finish() } } @@ -204,10 +190,7 @@ impl From> for AnyBufferKey { fn from(value: BufferKey) -> Self { let interface = AnyBuffer::interface_for::(); AnyBufferKey { - buffer: value.buffer, - session: value.session, - accessor: value.accessor, - lifecycle: value.lifecycle.clone(), + tag: value.tag, interface, } } @@ -770,14 +753,14 @@ impl<'w, 's, T: 'static + Send + Sync + Any> AnyBufferAccessMut<'w, 's> ) -> Result, BufferError> { let BufferAccessMut { query, commands } = self; let (storage, gate) = query - .get_mut(key.buffer) + .get_mut(key.tag.buffer) .map_err(|_| BufferError::BufferMissing)?; Ok(AnyBufferMut { storage: Box::new(storage), gate, - buffer: key.buffer, - session: key.session, - accessor: Some(key.accessor), + buffer: key.tag.buffer, + session: key.tag.session, + accessor: Some(key.tag.accessor), commands, modified: false, }) @@ -895,7 +878,7 @@ impl AnyBufferAccessInterface for AnyBufferAcces world: &'a World, ) -> Result, BufferError> { let buffer_ref = world - .get_entity(key.buffer) + .get_entity(key.tag.buffer) .ok_or(BufferError::BufferMissing)?; let storage = buffer_ref .get::>() @@ -906,7 +889,7 @@ impl AnyBufferAccessInterface for AnyBufferAcces Ok(AnyBufferView { storage: Box::new(storage), gate, - session: key.session, + session: key.tag.session, }) } @@ -1001,12 +984,8 @@ impl Accessed for AnyBuffer { } fn create_key(&self, builder: &super::BufferKeyBuilder) -> Self::Key { - let components = builder.as_components(self.id()); AnyBufferKey { - buffer: components.buffer, - session: components.session, - accessor: components.accessor, - lifecycle: components.lifecycle, + tag: builder.make_tag(self.id()), interface: self.interface, } } diff --git a/src/buffer/buffer_key_builder.rs b/src/buffer/buffer_key_builder.rs index cf9ef6a1..de6f7f13 100644 --- a/src/buffer/buffer_key_builder.rs +++ b/src/buffer/buffer_key_builder.rs @@ -19,7 +19,7 @@ use bevy_ecs::prelude::Entity; use std::sync::Arc; -use crate::{BufferAccessLifecycle, BufferKey, ChannelSender}; +use crate::{BufferAccessLifecycle, BufferKey, BufferKeyTag, ChannelSender}; pub struct BufferKeyBuilder { scope: Entity, @@ -28,37 +28,18 @@ pub struct BufferKeyBuilder { lifecycle: Option<(ChannelSender, Arc<()>)>, } -pub struct BufferKeyComponents { - pub buffer: Entity, - pub session: Entity, - pub accessor: Entity, - pub lifecycle: Option>, -} - impl BufferKeyBuilder { pub(crate) fn build(&self, buffer: Entity) -> BufferKey { BufferKey { - buffer, - session: self.session, - accessor: self.accessor, - lifecycle: self.lifecycle.as_ref().map(|(sender, tracker)| { - Arc::new(BufferAccessLifecycle::new( - self.scope, - buffer, - self.session, - self.accessor, - sender.clone(), - tracker.clone(), - )) - }), + tag: self.make_tag(buffer), _ignore: Default::default(), } } // TODO(@mxgrey): Consider refactoring all the buffer key structs to use a // single inner struct like BufferKeyComponents - pub(crate) fn as_components(&self, buffer: Entity) -> BufferKeyComponents { - BufferKeyComponents { + pub(crate) fn make_tag(&self, buffer: Entity) -> BufferKeyTag { + BufferKeyTag { buffer, session: self.session, accessor: self.accessor, diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 0a71d6bd..37e13ca1 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -37,9 +37,9 @@ use smallvec::SmallVec; use crate::{ add_listener_to_source, Accessed, AnyBuffer, AnyBufferAccessInterface, AnyBufferKey, AnyRange, - Buffer, BufferAccessLifecycle, BufferAccessMut, BufferAccessors, BufferError, BufferKey, - BufferKeyBuilder, BufferLocation, BufferStorage, Bufferable, Buffered, Builder, DrainBuffer, - Gate, GateState, InspectBuffer, Joined, ManageBuffer, NotifyBufferUpdate, OperationError, + Buffer, BufferAccessMut, BufferAccessors, BufferError, BufferKey, BufferKeyBuilder, + BufferKeyTag, BufferLocation, BufferStorage, Bufferable, Buffered, Builder, DrainBuffer, Gate, + GateState, InspectBuffer, Joined, ManageBuffer, NotifyBufferUpdate, OperationError, OperationResult, OrBroken, }; @@ -137,10 +137,7 @@ impl From for AnyBuffer { /// [1]: bevy_ecs::prelude::World #[derive(Clone)] pub struct JsonBufferKey { - buffer: Entity, - session: Entity, - accessor: Entity, - lifecycle: Option>, + tag: BufferKeyTag, interface: &'static (dyn JsonBufferAccessInterface + Send + Sync), } @@ -149,10 +146,7 @@ impl JsonBufferKey { pub fn downcast_for_message(&self) -> Option> { if TypeId::of::() == self.interface.any_access_interface().message_type_id() { Some(BufferKey { - buffer: self.buffer, - session: self.session, - accessor: self.accessor, - lifecycle: self.lifecycle.clone(), + tag: self.tag.clone(), _ignore: Default::default(), }) } else { @@ -166,33 +160,25 @@ impl JsonBufferKey { } fn deep_clone(&self) -> Self { - let mut deep = self.clone(); - deep.lifecycle = self - .lifecycle - .as_ref() - .map(|l| Arc::new(l.as_ref().clone())); - deep + Self { + tag: self.tag.deep_clone(), + interface: self.interface, + } } fn is_in_use(&self) -> bool { - self.lifecycle.as_ref().is_some_and(|l| l.is_in_use()) + self.tag.is_in_use() } } impl std::fmt::Debug for JsonBufferKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("JsonBufferKey") - .field("buffer", &self.buffer) - .field("session", &self.session) - .field("accessor", &self.accessor) - .field( - "in_use", - &self.lifecycle.as_ref().is_some_and(|l| l.is_in_use()), - ) .field( "message_type_name", &self.interface.any_access_interface().message_type_name(), ) + .field("tag", &self.tag) .finish() } } @@ -201,10 +187,7 @@ impl From> fn from(value: BufferKey) -> Self { let interface = JsonBufferAccessImpl::::get_interface(); JsonBufferKey { - buffer: value.buffer, - session: value.session, - accessor: value.accessor, - lifecycle: value.lifecycle, + tag: value.tag, interface, } } @@ -213,10 +196,7 @@ impl From> impl From for AnyBufferKey { fn from(value: JsonBufferKey) -> Self { AnyBufferKey { - buffer: value.buffer, - session: value.session, - accessor: value.accessor, - lifecycle: value.lifecycle, + tag: value.tag, interface: value.interface.any_access_interface(), } } @@ -883,7 +863,7 @@ impl JsonBufferAccessIn world: &'a World, ) -> Result, BufferError> { let buffer_ref = world - .get_entity(key.buffer) + .get_entity(key.tag.buffer) .ok_or(BufferError::BufferMissing)?; let storage = buffer_ref .get::>() @@ -894,7 +874,7 @@ impl JsonBufferAccessIn Ok(JsonBufferView { storage: Box::new(storage), gate, - session: key.session, + session: key.tag.session, }) } @@ -942,14 +922,14 @@ where ) -> Result, BufferError> { let BufferAccessMut { query, commands } = self; let (storage, gate) = query - .get_mut(key.buffer) + .get_mut(key.tag.buffer) .map_err(|_| BufferError::BufferMissing)?; Ok(JsonBufferMut { storage: Box::new(storage), gate, - buffer: key.buffer, - session: key.session, - accessor: Some(key.accessor), + buffer: key.tag.buffer, + session: key.tag.session, + accessor: Some(key.tag.accessor), commands, modified: false, }) @@ -1039,12 +1019,8 @@ impl Accessed for JsonBuffer { } fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key { - let components = builder.as_components(self.id()); JsonBufferKey { - buffer: components.buffer, - session: components.session, - accessor: components.accessor, - lifecycle: components.lifecycle, + tag: builder.make_tag(self.id()), interface: self.interface, } } From a6bf08d832084b87ca9c266b74a777f7fd0cf707 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 4 Feb 2025 00:03:31 +0800 Subject: [PATCH 23/37] Implement more general buffer key downcasting Signed-off-by: Michael X. Grey --- src/buffer.rs | 7 +-- src/buffer/any_buffer.rs | 67 +++++++++++++++++++++++---- src/buffer/buffer_access_lifecycle.rs | 2 +- src/buffer/json_buffer.rs | 49 ++++++++++++++------ 4 files changed, 97 insertions(+), 28 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 3b1928d8..6ee4109f 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -251,7 +251,7 @@ impl BufferKey { self.tag.session } - pub(crate) fn tag(&self) -> &BufferKeyTag { + pub fn tag(&self) -> &BufferKeyTag { &self.tag } @@ -286,11 +286,8 @@ impl std::fmt::Debug for BufferKey { /// The identifying information for a buffer key. This does not indicate /// anything about the type of messages that the buffer can contain. -/// -/// This struct will be internal to the crate until we decide to make -/// [`BufferAccessLifecycle`] a public struct. #[derive(Clone)] -pub(crate) struct BufferKeyTag { +pub struct BufferKeyTag { pub buffer: Entity, pub session: Entity, pub accessor: Entity, diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 38c1cdb0..c0741b32 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -21,7 +21,7 @@ use std::{ any::{Any, TypeId}, collections::{hash_map::Entry, HashMap}, ops::RangeBounds, - sync::{Arc, Mutex, OnceLock}, + sync::{Mutex, OnceLock}, }; use bevy_ecs::{ @@ -34,10 +34,10 @@ use thiserror::Error as ThisError; use smallvec::SmallVec; use crate::{ - add_listener_to_source, Accessed, Buffer, BufferAccessLifecycle, BufferAccessMut, - BufferAccessors, BufferError, BufferKey, BufferKeyTag, BufferLocation, BufferStorage, - Bufferable, Buffered, Builder, DrainBuffer, Gate, GateState, InspectBuffer, Joined, - ManageBuffer, NotifyBufferUpdate, OperationError, OperationResult, OperationRoster, OrBroken, + add_listener_to_source, Accessed, Buffer, BufferAccessMut, BufferAccessors, BufferError, + BufferKey, BufferKeyTag, BufferLocation, BufferStorage, Bufferable, Buffered, Builder, + DrainBuffer, Gate, GateState, InspectBuffer, Joined, ManageBuffer, NotifyBufferUpdate, + OperationError, OperationResult, OperationRoster, OrBroken, }; /// A [`Buffer`] whose message type has been anonymized. Joining with this buffer @@ -143,11 +143,13 @@ pub struct AnyBufferKey { } impl AnyBufferKey { - /// Downcast this into a concrete [`BufferKey`] type. - pub fn downcast(&self) -> Option> { - if TypeId::of::() == self.interface.message_type_id() { + /// Downcast this into a concrete [`BufferKey`] for the specified message type. + /// + /// To downcast to a specialized kind of key, use [`Self::downcast_buffer_key`] instead. + pub fn downcast_for_message(self) -> Option> { + if TypeId::of::() == self.interface.message_type_id() { Some(BufferKey { - tag: self.tag.clone(), + tag: self.tag, _ignore: Default::default(), }) } else { @@ -155,6 +157,15 @@ impl AnyBufferKey { } } + /// Downcast this into a different special buffer key representation, such + /// as a `JsonBufferKey`. + pub fn downcast_buffer_key(self) -> Option { + self.interface.key_downcast(TypeId::of::())?(self.tag) + .downcast::() + .ok() + .map(|x| *x) + } + /// The buffer ID of this key. pub fn id(&self) -> Entity { self.tag.buffer @@ -780,6 +791,10 @@ pub trait AnyBufferAccessInterface { fn buffer_downcast(&self, buffer_type: TypeId) -> Option; + fn register_key_downcast(&self, key_type: TypeId, f: KeyDowncastBox); + + fn key_downcast(&self, key_type: TypeId) -> Option; + fn pull( &self, entity_mut: &mut EntityWorldMut, @@ -800,9 +815,12 @@ pub trait AnyBufferAccessInterface { pub type BufferDowncastBox = Box Box + Send + Sync>; pub type BufferDowncastRef = &'static (dyn Fn(BufferLocation) -> Box + Send + Sync); +pub type KeyDowncastBox = Box Box + Send + Sync>; +pub type KeyDowncastRef = &'static (dyn Fn(BufferKeyTag) -> Box + Send + Sync); struct AnyBufferAccessImpl { buffer_downcasts: Mutex>, + key_downcasts: Mutex>, _ignore: std::marker::PhantomData, } @@ -810,6 +828,10 @@ impl AnyBufferAccessImpl { fn new() -> Self { let mut buffer_downcasts: HashMap<_, BufferDowncastRef> = HashMap::new(); + // SAFETY: These leaks are okay because we will only ever instantiate + // AnyBufferAccessImpl once per generic argument T, which puts a firm + // ceiling on how many of these callbacks will get leaked. + // Automatically register a downcast into AnyBuffer buffer_downcasts.insert( TypeId::of::(), @@ -821,8 +843,22 @@ impl AnyBufferAccessImpl { })), ); + let mut key_downcasts: HashMap<_, KeyDowncastRef> = HashMap::new(); + + // Automatically register a downcast to AnyBufferKey + key_downcasts.insert( + TypeId::of::(), + Box::leak(Box::new(|tag| -> Box { + Box::new(AnyBufferKey { + tag, + interface: AnyBuffer::interface_for::(), + }) + })), + ); + Self { buffer_downcasts: Mutex::new(buffer_downcasts), + key_downcasts: Mutex::new(key_downcasts), _ignore: Default::default(), } } @@ -862,6 +898,19 @@ impl AnyBufferAccessInterface for AnyBufferAcces .copied() } + fn register_key_downcast(&self, key_type: TypeId, f: KeyDowncastBox) { + let mut downcasts = self.key_downcasts.lock().unwrap(); + + if let Entry::Vacant(entry) = downcasts.entry(key_type) { + // We should only leak this in to the register once per type + entry.insert(Box::leak(f)); + } + } + + fn key_downcast(&self, key_type: TypeId) -> Option { + self.key_downcasts.lock().unwrap().get(&key_type).copied() + } + fn pull( &self, entity_mut: &mut EntityWorldMut, diff --git a/src/buffer/buffer_access_lifecycle.rs b/src/buffer/buffer_access_lifecycle.rs index d368a484..879a26ec 100644 --- a/src/buffer/buffer_access_lifecycle.rs +++ b/src/buffer/buffer_access_lifecycle.rs @@ -29,7 +29,7 @@ use crate::{emit_disposal, ChannelItem, Disposal, OperationRoster}; /// we would be needlessly doing a reachability check every time the key gets /// cloned. #[derive(Clone)] -pub(crate) struct BufferAccessLifecycle { +pub struct BufferAccessLifecycle { scope: Entity, accessor: Entity, session: Entity, diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 37e13ca1..4a8054d8 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -21,7 +21,7 @@ use std::{ any::TypeId, collections::HashMap, ops::RangeBounds, - sync::{Arc, Mutex, OnceLock}, + sync::{Mutex, OnceLock}, }; use bevy_ecs::{ @@ -143,20 +143,19 @@ pub struct JsonBufferKey { impl JsonBufferKey { /// Downcast this into a concrete [`BufferKey`] for the specified message type. - pub fn downcast_for_message(&self) -> Option> { - if TypeId::of::() == self.interface.any_access_interface().message_type_id() { - Some(BufferKey { - tag: self.tag.clone(), - _ignore: Default::default(), - }) - } else { - None - } + /// + /// To downcast to a specialized kind of key, use [`Self::downcast_buffer_key`] instead. + pub fn downcast_for_message(self) -> Option> { + self.as_any_buffer_key().downcast_for_message() + } + + pub fn downcast_buffer_key(self) -> Option { + self.as_any_buffer_key().downcast_buffer_key() } /// Cast this into an [`AnyBufferKey`] - pub fn as_any_buffer_key(&self) -> AnyBufferKey { - self.clone().into() + pub fn as_any_buffer_key(self) -> AnyBufferKey { + self.into() } fn deep_clone(&self) -> Self { @@ -816,6 +815,16 @@ impl JsonBufferAccessIm }), ); + any_interface.register_key_downcast( + TypeId::of::(), + Box::new(|tag| { + Box::new(JsonBufferKey { + tag, + interface: Self::get_interface(), + }) + }), + ); + // Create and cache the json buffer access interface static INTERFACE_MAP: OnceLock< Mutex>, @@ -1319,7 +1328,21 @@ mod tests { let _original_from_json: Buffer = json_buffer.downcast_for_message().unwrap(); - builder.connect(scope.input, scope.terminate); + scope + .input + .chain(builder) + .with_access(buffer) + .map_block(|(data, key)| { + let any_key: AnyBufferKey = key.clone().into(); + let json_key: JsonBufferKey = any_key.clone().downcast_buffer_key().unwrap(); + let _original_from_any: BufferKey = + any_key.downcast_for_message().unwrap(); + let _original_from_json: BufferKey = + json_key.downcast_for_message().unwrap(); + + data + }) + .connect(scope.terminate); }); let mut promise = context.command(|commands| commands.request(1, workflow).take_response()); From a40b8d33946d6c5fcc4485c8aedb4b5f1c2a4990 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Thu, 6 Feb 2025 08:17:21 +0000 Subject: [PATCH 24/37] Invert the implementation of JoinedValue Signed-off-by: Michael X. Grey --- src/buffer.rs | 8 + src/buffer/any_buffer.rs | 56 ++--- src/buffer/buffer_map.rs | 476 +++++++++++++++----------------------- src/buffer/json_buffer.rs | 113 ++++++++- src/builder.rs | 4 +- src/lib.rs | 4 +- 6 files changed, 343 insertions(+), 318 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 6ee4109f..e9d6f6b8 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -113,6 +113,14 @@ impl Buffer { pub fn location(&self) -> BufferLocation { self.location } + + /// Cast this into an [`AnyBuffer`]. + pub fn as_any_buffer(&self) -> AnyBuffer + where + T: 'static + Send + Sync, + { + self.clone().into() + } } impl Clone for Buffer { diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index c0741b32..580baa28 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -64,6 +64,10 @@ impl AnyBuffer { self.interface.message_type_id() } + pub fn message_type_name(&self) -> &'static str { + self.interface.message_type_name() + } + /// Get the [`AnyBufferAccessInterface`] for this specific instance of [`AnyBuffer`]. pub fn get_interface(&self) -> &'static (dyn AnyBufferAccessInterface + Send + Sync) { self.interface @@ -176,11 +180,11 @@ impl AnyBufferKey { self.tag.session } - fn is_in_use(&self) -> bool { + pub(crate) fn is_in_use(&self) -> bool { self.tag.is_in_use() } - fn deep_clone(&self) -> Self { + pub(crate) fn deep_clone(&self) -> Self { Self { tag: self.tag.deep_clone(), interface: self.interface, @@ -338,14 +342,14 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { } /// Pull the oldest message from the buffer. - pub fn pull(&mut self) -> Option { + pub fn pull(&mut self) -> Option { self.modified = true; self.storage.any_pull(self.session) } /// Pull the message that was most recently put into the buffer (instead of the /// oldest, which is what [`Self::pull`] gives). - pub fn pull_newest(&mut self) -> Option { + pub fn pull_newest(&mut self) -> Option { self.modified = true; self.storage.any_pull_newest(self.session) } @@ -385,7 +389,7 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { /// If the input value does not match the message type of the buffer, this /// will return [`Err`] and give back an error with the message that you /// tried to push and the type information for the expected message type. - pub fn push_any(&mut self, value: AnyMessage) -> Result, AnyMessageError> { + pub fn push_any(&mut self, value: AnyMessageBox) -> Result, AnyMessageError> { self.storage.any_push(self.session, value) } @@ -420,8 +424,8 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { /// The result follows the same rules as [`Self::push_any`]. pub fn push_any_as_oldest( &mut self, - value: AnyMessage, - ) -> Result, AnyMessageError> { + value: AnyMessageBox, + ) -> Result, AnyMessageError> { self.storage.any_push_as_oldest(self.session, value) } @@ -520,10 +524,10 @@ trait AnyBufferViewing { } trait AnyBufferManagement: AnyBufferViewing { - fn any_push(&mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult; - fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult; - fn any_pull(&mut self, session: Entity) -> Option; - fn any_pull_newest(&mut self, session: Entity) -> Option; + fn any_push(&mut self, session: Entity, value: AnyMessageBox) -> AnyMessagePushResult; + fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessageBox) -> AnyMessagePushResult; + fn any_pull(&mut self, session: Entity) -> Option; + fn any_pull_newest(&mut self, session: Entity) -> Option; fn any_oldest_mut<'a>(&'a mut self, session: Entity) -> Option>; fn any_newest_mut<'a>(&'a mut self, session: Entity) -> Option>; fn any_get_mut<'a>(&'a mut self, session: Entity, index: usize) -> Option>; @@ -650,37 +654,37 @@ impl AnyBufferViewing for Mut<'_, BufferStorage< pub type AnyMessageMut<'a> = &'a mut (dyn Any + 'static + Send + Sync); -pub type AnyMessage = Box; +pub type AnyMessageBox = Box; #[derive(ThisError, Debug)] #[error("failed to convert a message")] pub struct AnyMessageError { /// The original value provided - pub value: AnyMessage, + pub value: AnyMessageBox, /// The ID of the type expected by the buffer pub type_id: TypeId, /// The name of the type expected by the buffer pub type_name: &'static str, } -pub type AnyMessagePushResult = Result, AnyMessageError>; +pub type AnyMessagePushResult = Result, AnyMessageError>; impl AnyBufferManagement for Mut<'_, BufferStorage> { - fn any_push(&mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult { + fn any_push(&mut self, session: Entity, value: AnyMessageBox) -> AnyMessagePushResult { let value = from_any_message::(value)?; Ok(self.push(session, value).map(to_any_message)) } - fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessage) -> AnyMessagePushResult { + fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessageBox) -> AnyMessagePushResult { let value = from_any_message::(value)?; Ok(self.push_as_oldest(session, value).map(to_any_message)) } - fn any_pull(&mut self, session: Entity) -> Option { + fn any_pull(&mut self, session: Entity) -> Option { self.pull(session).map(to_any_message) } - fn any_pull_newest(&mut self, session: Entity) -> Option { + fn any_pull_newest(&mut self, session: Entity) -> Option { self.pull_newest(session).map(to_any_message) } @@ -713,11 +717,11 @@ fn to_any_mut<'a, T: 'static + Send + Sync + Any>(x: &'a mut T) -> AnyMessageMut x } -fn to_any_message(x: T) -> AnyMessage { +fn to_any_message(x: T) -> AnyMessageBox { Box::new(x) } -fn from_any_message(value: AnyMessage) -> Result +fn from_any_message(value: AnyMessageBox) -> Result where T: 'static, { @@ -799,7 +803,7 @@ pub trait AnyBufferAccessInterface { &self, entity_mut: &mut EntityWorldMut, session: Entity, - ) -> Result; + ) -> Result; fn create_any_buffer_view<'a>( &self, @@ -915,7 +919,7 @@ impl AnyBufferAccessInterface for AnyBufferAcces &self, entity_mut: &mut EntityWorldMut, session: Entity, - ) -> Result { + ) -> Result { entity_mut .pull_from_buffer::(session) .map(to_any_message) @@ -955,7 +959,7 @@ pub struct DrainAnyBuffer<'a> { } impl<'a> Iterator for DrainAnyBuffer<'a> { - type Item = AnyMessage; + type Item = AnyMessageBox; fn next(&mut self) -> Option { self.interface.any_next() @@ -963,11 +967,11 @@ impl<'a> Iterator for DrainAnyBuffer<'a> { } trait DrainAnyBufferInterface { - fn any_next(&mut self) -> Option; + fn any_next(&mut self) -> Option; } impl DrainAnyBufferInterface for DrainBuffer<'_, T> { - fn any_next(&mut self) -> Option { + fn any_next(&mut self) -> Option { self.next().map(to_any_message) } } @@ -1015,7 +1019,7 @@ impl Buffered for AnyBuffer { } impl Joined for AnyBuffer { - type Item = AnyMessage; + type Item = AnyMessageBox; fn pull(&self, session: Entity, world: &mut World) -> Result { let mut buffer_mut = world.get_entity_mut(self.id()).or_broken()?; self.interface.pull(&mut buffer_mut, session) diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index e3ac852a..852711d2 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -15,7 +15,7 @@ * */ -use std::{any::TypeId, borrow::Cow, collections::HashMap}; +use std::{borrow::Cow, collections::HashMap}; use thiserror::Error as ThisError; @@ -24,9 +24,9 @@ use smallvec::SmallVec; use bevy_ecs::prelude::{Entity, World}; use crate::{ - add_listener_to_source, Accessed, AddOperation, AnyBuffer, AnyBufferKey, BufferKeyBuilder, - Buffered, Builder, Chain, Gate, GateState, Join, Joined, OperationError, OperationResult, - OperationRoster, Output, UnusedTarget, + add_listener_to_source, Accessed, AddOperation, AnyBuffer, AnyBufferKey, AnyMessageBox, + Buffer, BufferKeyBuilder, Buffered, Builder, Chain, Gate, GateState, Join, Joined, + OperationError, OperationResult, OperationRoster, Output, UnusedTarget, }; #[derive(Clone, Default)] @@ -36,8 +36,8 @@ pub struct BufferMap { impl BufferMap { /// Insert a named buffer into the map. - pub fn insert(&mut self, name: Cow<'static, str>, buffer: impl Into) { - self.inner.insert(name, buffer.into()); + pub fn insert(&mut self, name: impl Into>, buffer: impl Into) { + self.inner.insert(name.into(), buffer.into()); } /// Get one of the buffers from the map by its name. @@ -58,31 +58,45 @@ pub struct IncompatibleLayout { } impl IncompatibleLayout { - /// Check whether a named buffer is compatible with a specific type. - pub fn require_buffer(&mut self, expected_name: &str, buffers: &BufferMap) { + /// Check whether a named buffer is compatible with a specific concrete message type. + pub fn require_message_type(&mut self, expected_name: &str, buffers: &BufferMap) -> Result, ()> { if let Some((name, buffer)) = buffers.inner.get_key_value(expected_name) { - if buffer.message_type_id() != TypeId::of::() { + if let Some(buffer) = buffer.downcast_for_message::() { + return Ok(buffer); + } else { self.incompatible_buffers.push(BufferIncompatibility { name: name.clone(), - expected: TypeId::of::(), - received: buffer.message_type_id(), + expected: std::any::type_name::>(), + received: buffer.message_type_name(), }); } } else { self.missing_buffers .push(Cow::Owned(expected_name.to_owned())); } + + Err(()) } - /// Convert the instance into a result. If any field has content in it, then - /// this will produce an [`Err`]. Otherwise if no incompatibilities are - /// present, this will produce an [`Ok`]. - pub fn into_result(self) -> Result<(), IncompatibleLayout> { - if self.missing_buffers.is_empty() && self.incompatible_buffers.is_empty() { - Ok(()) + /// Check whether a named buffer is compatible with a specialized buffer type, + /// such as `JsonBuffer`. + pub fn require_buffer_type(&mut self, expected_name: &str, buffers: &BufferMap) -> Result { + if let Some((name, buffer)) = buffers.inner.get_key_value(expected_name) { + if let Some(buffer) = buffer.downcast_buffer::() { + return Ok(buffer); + } else { + self.incompatible_buffers.push(BufferIncompatibility { + name: name.clone(), + expected: std::any::type_name::(), + received: buffer.message_type_name(), + }); + } } else { - Err(self) + self.missing_buffers + .push(Cow::Owned(expected_name.to_owned())); } + + Err(()) } } @@ -92,81 +106,105 @@ pub struct BufferIncompatibility { /// Name of the expected buffer pub name: Cow<'static, str>, /// The type that was expected for this buffer - pub expected: TypeId, + pub expected: &'static str, /// The type that was received for this buffer - pub received: TypeId, + pub received: &'static str, // TODO(@mxgrey): Replace TypeId with TypeInfo } -pub trait BufferMapLayout: Sized { - fn is_compatible(buffers: &BufferMap) -> Result<(), IncompatibleLayout>; +/// This trait can be implemented on structs that represent a layout of buffers. +/// You do not normally have to implement this yourself. Instead you should +/// `#[derive(JoinedValue)]` on a struct that you want a join operation to +/// produce. +pub trait BufferMapLayout: Sized + Clone { + /// Produce a list of the buffers that exist in this layout. + fn buffer_list(&self) -> SmallVec<[AnyBuffer; 8]>; + + /// Try to convert a generic [`BufferMap`] into this specific layout. + fn try_from_buffer_map(buffers: &BufferMap) -> Result; +} + +impl Buffered for T { + fn verify_scope(&self, scope: Entity) { + for buffer in self.buffer_list() { + assert_eq!(buffer.scope(), scope); + } + } fn buffered_count( - buffers: &BufferMap, + &self, session: Entity, world: &World, - ) -> Result; + ) -> Result { + let mut min_count = None; + + for buffer in self.buffer_list() { + let count = buffer.buffered_count(session, world)?; + min_count = if min_count.is_some_and(|m| m < count) { + min_count + } else { + Some(count) + }; + } + + Ok(min_count.unwrap_or(0)) + } fn ensure_active_session( - buffers: &BufferMap, + &self, session: Entity, world: &mut World, - ) -> OperationResult; + ) -> OperationResult { + for buffer in self.buffer_list() { + buffer.ensure_active_session(session, world)?; + } + + Ok(()) + } - fn add_listener(buffers: &BufferMap, listener: Entity, world: &mut World) -> OperationResult { - for buffer in buffers.inner.values() { + fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { + for buffer in self.buffer_list() { add_listener_to_source(buffer.id(), listener, world)?; } Ok(()) } fn gate_action( - buffers: &BufferMap, + &self, session: Entity, action: Gate, world: &mut World, roster: &mut OperationRoster, ) -> OperationResult { - for buffer in buffers.inner.values() { + for buffer in self.buffer_list() { GateState::apply(buffer.id(), session, action, world, roster)?; } Ok(()) } - fn as_input(buffers: &BufferMap) -> SmallVec<[Entity; 8]> { + fn as_input(&self) -> SmallVec<[Entity; 8]> { let mut inputs = SmallVec::new(); - for buffer in buffers.inner.values() { + for buffer in self.buffer_list() { inputs.push(buffer.id()); } inputs } } -pub trait JoinedValue: 'static + BufferMapLayout + Send + Sync { - /// This associated type must represent a buffer map layout that is - /// guaranteed to be compatible for this JoinedValue. Failure to implement - /// this trait accordingly will result in panics. - type Buffers: Into; - - fn pull( - buffers: &BufferMap, - session: Entity, - world: &mut World, - ) -> Result; +/// This trait can be implemented for structs that are created by joining a +/// collection of buffers. Usually this +pub trait JoinedValue: 'static + Send + Sync + Sized { + /// This associated type must represent a buffer map layout that implements + /// the [`Joined`] trait. The message type yielded by [`Joined`] for this + /// associated type must match the [`JoinedValue`] type. + type Buffers: 'static + BufferMapLayout + Joined + Send + Sync; + /// Used by [`Builder::join_into`] fn join_into<'w, 's, 'a, 'b>( buffers: Self::Buffers, builder: &'b mut Builder<'w, 's, 'a>, ) -> Chain<'w, 's, 'a, 'b, Self> { - Self::try_join_into(buffers.into(), builder).unwrap() - } - - fn try_join_into<'w, 's, 'a, 'b>( - buffers: BufferMap, - builder: &'b mut Builder<'w, 's, 'a>, - ) -> Result, IncompatibleLayout> { let scope = builder.scope(); - let buffers = BufferedMap::::new(buffers)?; buffers.verify_scope(scope); let join = builder.commands.spawn(()).id(); @@ -177,12 +215,23 @@ pub trait JoinedValue: 'static + BufferMapLayout + Send + Sync { Join::new(buffers, target), )); - Ok(Output::new(scope, target).chain(builder)) + Output::new(scope, target).chain(builder) + } + + /// Used by [`Builder::try_join_into`] + fn try_join_into<'w, 's, 'a, 'b>( + buffers: &BufferMap, + builder: &'b mut Builder<'w, 's, 'a>, + ) -> Result, IncompatibleLayout> { + let buffers: Self::Buffers = Self::Buffers::try_from_buffer_map(buffers)?; + Ok(Self::join_into(buffers, builder)) } } /// Trait to describe a layout of buffer keys -pub trait BufferKeyMap: BufferMapLayout + Clone { +pub trait BufferKeyMap: 'static + Send + Sync + Sized + Clone { + type Buffers: 'static + BufferMapLayout + Accessed + Send + Sync; + fn add_accessor(buffers: &BufferMap, accessor: Entity, world: &mut World) -> OperationResult; fn create_key(buffers: &BufferMap, builder: &BufferKeyBuilder) -> Self; @@ -192,177 +241,76 @@ pub trait BufferKeyMap: BufferMapLayout + Clone { fn is_key_in_use(&self) -> bool; } -struct BufferedMap { - map: BufferMap, - _ignore: std::marker::PhantomData, -} - -impl BufferedMap { - fn new(map: BufferMap) -> Result { - K::is_compatible(&map)?; - Ok(Self { - map, - _ignore: Default::default(), - }) +impl BufferMapLayout for BufferMap { + fn buffer_list(&self) -> SmallVec<[AnyBuffer; 8]> { + self.inner.values().cloned().collect() } -} - -impl Clone for BufferedMap { - fn clone(&self) -> Self { - Self { - map: self.map.clone(), - _ignore: Default::default(), - } + fn try_from_buffer_map(buffers: &BufferMap) -> Result { + Ok(buffers.clone()) } } -impl Buffered for BufferedMap { - fn verify_scope(&self, scope: Entity) { - for buffer in self.map.inner.values() { - assert_eq!(scope, buffer.scope()); - } - } - - fn buffered_count(&self, session: Entity, world: &World) -> Result { - L::buffered_count(&self.map, session, world) - } - - fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { - L::add_listener(&self.map, listener, world) - } - - fn gate_action( - &self, - session: Entity, - action: Gate, - world: &mut World, - roster: &mut OperationRoster, - ) -> OperationResult { - L::gate_action(&self.map, session, action, world, roster) - } - - fn as_input(&self) -> SmallVec<[Entity; 8]> { - L::as_input(&self.map) - } - - fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { - L::ensure_active_session(&self.map, session, world) - } -} - -impl Joined for BufferedMap { - type Item = V; +impl Joined for BufferMap { + type Item = HashMap, AnyMessageBox>; fn pull(&self, session: Entity, world: &mut World) -> Result { - V::pull(&self.map, session, world) - } -} - -impl Accessed for BufferedMap { - type Key = K; - - fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { - K::add_accessor(&self.map, accessor, world) - } - - fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key { - K::create_key(&self.map, builder) - } + let mut value = HashMap::new(); + for (name, buffer) in &self.inner { + value.insert(name.clone(), buffer.pull(session, world)?); + } - fn deep_clone_key(key: &Self::Key) -> Self::Key { - K::deep_clone_key(key) - } - - fn is_key_in_use(key: &Self::Key) -> bool { - K::is_key_in_use(key) + Ok(value) } } -/// Container to represent any layout of buffer keys. -#[derive(Clone, Debug)] -pub struct AnyBufferKeyMap { - pub keys: HashMap, AnyBufferKey>, +impl JoinedValue for HashMap, AnyMessageBox> { + type Buffers = BufferMap; } -impl BufferMapLayout for AnyBufferKeyMap { - fn is_compatible(_: &BufferMap) -> Result<(), IncompatibleLayout> { - // AnyBufferKeyMap is always compatible with BufferMap - Ok(()) - } - - fn buffered_count( - buffers: &BufferMap, - session: Entity, - world: &World, - ) -> Result { - let mut min_count = None; - for buffer in buffers.inner.values() { - let count = buffer.buffered_count(session, world)?; +impl Accessed for BufferMap { + type Key = HashMap, AnyBufferKey>; - min_count = if min_count.is_some_and(|m| m < count) { - min_count - } else { - Some(count) + fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key { + let mut keys = HashMap::new(); + for (name, buffer) in &self.inner { + let key = AnyBufferKey { + tag: builder.make_tag(buffer.id()), + interface: buffer.interface, }; + keys.insert(name.clone(), key); } - - Ok(min_count.unwrap_or(0)) - } - - fn add_listener(buffers: &BufferMap, listener: Entity, world: &mut World) -> OperationResult { - for buffer in buffers.inner.values() { - buffer.add_listener(listener, world)?; - } - - Ok(()) + keys } - fn gate_action( - buffers: &BufferMap, - session: Entity, - action: Gate, - world: &mut World, - roster: &mut OperationRoster, - ) -> OperationResult { - for buffer in buffers.inner.values() { - buffer.gate_action(session, action, world, roster)?; + fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { + for buffer in self.inner.values() { + buffer.add_accessor(accessor, world)?; } - Ok(()) } - fn as_input(buffers: &BufferMap) -> SmallVec<[Entity; 8]> { - let mut input = SmallVec::new(); - input.reserve(buffers.inner.len()); - - for buffer in buffers.inner.values() { - input.extend(buffer.as_input()); + fn deep_clone_key(key: &Self::Key) -> Self::Key { + let mut cloned_key = HashMap::new(); + for (name, key) in key.iter() { + cloned_key.insert(name.clone(), key.deep_clone()); } - - input + cloned_key } - fn ensure_active_session( - buffers: &BufferMap, - session: Entity, - world: &mut World, - ) -> OperationResult { - for buffer in buffers.inner.values() { - buffer.ensure_active_session(session, world)?; + fn is_key_in_use(key: &Self::Key) -> bool { + for k in key.values() { + if k.is_in_use() { + return true; + } } - Ok(()) + return false; } } #[cfg(test)] mod tests { - use std::borrow::Cow; - - use crate::{ - prelude::*, testing::*, BufferMap, InspectBuffer, ManageBuffer, OperationError, - OperationResult, OrBroken, - }; + use crate::{prelude::*, testing::*, BufferMap, OperationError}; use bevy_ecs::prelude::World; @@ -373,88 +321,38 @@ mod tests { string: String, } - impl BufferMapLayout for TestJoinedValue { - fn is_compatible(buffers: &BufferMap) -> Result<(), IncompatibleLayout> { - let mut compatibility = IncompatibleLayout::default(); - compatibility.require_buffer::("integer", buffers); - compatibility.require_buffer::("float", buffers); - compatibility.require_buffer::("string", buffers); - compatibility.into_result() - } - - fn buffered_count( - buffers: &BufferMap, - session: Entity, - world: &World, - ) -> Result { - let integer_count = world - .get_entity(buffers.get("integer").unwrap().id()) - .or_broken()? - .buffered_count::(session)?; - - let float_count = world - .get_entity(buffers.get("float").unwrap().id()) - .or_broken()? - .buffered_count::(session)?; - - let string_count = world - .get_entity(buffers.get("string").unwrap().id()) - .or_broken()? - .buffered_count::(session)?; - - Ok([integer_count, float_count, string_count] - .iter() - .min() - .copied() - .unwrap_or(0)) - } + #[derive(Clone)] + struct TestJoinedValueBuffers { + integer: Buffer, + float: Buffer, + string: Buffer, + } - fn ensure_active_session( - buffers: &BufferMap, - session: Entity, - world: &mut World, - ) -> OperationResult { - world - .get_entity_mut(buffers.get("integer").unwrap().id()) - .or_broken()? - .ensure_session::(session)?; - - world - .get_entity_mut(buffers.get("float").unwrap().id()) - .or_broken()? - .ensure_session::(session)?; - - world - .get_entity_mut(buffers.get("string").unwrap().id()) - .or_broken()? - .ensure_session::(session)?; - - Ok(()) + impl BufferMapLayout for TestJoinedValueBuffers { + fn buffer_list(&self) -> smallvec::SmallVec<[AnyBuffer; 8]> { + use smallvec::smallvec; + smallvec![ + self.integer.as_any_buffer(), + self.float.as_any_buffer(), + self.string.as_any_buffer(), + ] } - } - impl JoinedValue for TestJoinedValue { - type Buffers = TestJoinedValueBuffers; + fn try_from_buffer_map(buffers: &BufferMap) -> Result { + let mut compatibility = IncompatibleLayout::default(); + let integer = compatibility.require_message_type::("integer", buffers); + let float = compatibility.require_message_type::("float", buffers); + let string = compatibility.require_message_type::("string", buffers); - fn pull( - buffers: &BufferMap, - session: Entity, - world: &mut World, - ) -> Result { - let integer = world - .get_entity_mut(buffers.get("integer").unwrap().id()) - .or_broken()? - .pull_from_buffer::(session)?; - - let float = world - .get_entity_mut(buffers.get("float").unwrap().id()) - .or_broken()? - .pull_from_buffer::(session)?; - - let string = world - .get_entity_mut(buffers.get("string").unwrap().id()) - .or_broken()? - .pull_from_buffer::(session)?; + let Ok(integer) = integer else { + return Err(compatibility); + }; + let Ok(float) = float else { + return Err(compatibility); + }; + let Ok(string) = string else { + return Err(compatibility); + }; Ok(Self { integer, @@ -464,22 +362,26 @@ mod tests { } } - struct TestJoinedValueBuffers { - integer: Buffer, - float: Buffer, - string: Buffer, - } + impl crate::Joined for TestJoinedValueBuffers { + type Item = TestJoinedValue; - impl From for BufferMap { - fn from(value: TestJoinedValueBuffers) -> Self { - let mut buffers = BufferMap::default(); - buffers.insert(Cow::Borrowed("integer"), value.integer); - buffers.insert(Cow::Borrowed("float"), value.float); - buffers.insert(Cow::Borrowed("string"), value.string); - buffers + fn pull(&self, session: Entity, world: &mut World) -> Result { + let integer = self.integer.pull(session, world)?; + let float = self.float.pull(session, world)?; + let string = self.string.pull(session, world)?; + + Ok(TestJoinedValue { + integer, + float, + string, + }) } } + impl JoinedValue for TestJoinedValue { + type Buffers = TestJoinedValueBuffers; + } + #[test] fn test_joined_value() { let mut context = TestingContext::minimal_plugins(); @@ -490,9 +392,9 @@ mod tests { let buffer_string = builder.create_buffer(BufferSettings::default()); let mut buffers = BufferMap::default(); - buffers.insert(Cow::Borrowed("integer"), buffer_i64); - buffers.insert(Cow::Borrowed("float"), buffer_f64); - buffers.insert(Cow::Borrowed("string"), buffer_string); + buffers.insert("integer", buffer_i64); + buffers.insert("float", buffer_f64); + buffers.insert("string", buffer_string); scope.input.chain(builder).fork_unzip(( |chain: Chain<_>| chain.connect(buffer_i64.input_slot()), @@ -501,7 +403,7 @@ mod tests { )); builder - .try_join_into(buffers) + .try_join_into(&buffers) .unwrap() .connect(scope.terminate); }); diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 4a8054d8..6a0a6583 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -1049,7 +1049,7 @@ mod tests { use bevy_ecs::prelude::World; use serde::{Deserialize, Serialize}; - #[derive(Serialize, Deserialize, Clone)] + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] struct TestMessage { v_i32: i32, v_u32: u32, @@ -1352,4 +1352,115 @@ mod tests { assert_eq!(1, response); assert!(context.no_unhandled_errors()); } + + #[derive(Clone)] + struct TestJoinedValueJson { + integer: i64, + float: f64, + json: JsonMessage, + } + + #[derive(Clone)] + struct TestJoinedValueJsonBuffers { + integer: Buffer, + float: Buffer, + json: JsonBuffer, + } + + impl BufferMapLayout for TestJoinedValueJsonBuffers { + fn buffer_list(&self) -> smallvec::SmallVec<[AnyBuffer; 8]> { + use smallvec::smallvec; + smallvec![ + self.integer.as_any_buffer(), + self.float.as_any_buffer(), + self.json.as_any_buffer(), + ] + } + + fn try_from_buffer_map(buffers: &BufferMap) -> Result { + let mut compatibility = IncompatibleLayout::default(); + let integer = compatibility.require_message_type::("integer", buffers); + let float = compatibility.require_message_type::("float", buffers); + let json = compatibility.require_buffer_type::("json", buffers); + + let Ok(integer) = integer else { + return Err(compatibility); + }; + let Ok(float) = float else { + return Err(compatibility); + }; + let Ok(json) = json else { + return Err(compatibility); + }; + + Ok(Self { + integer, + float, + json, + }) + } + } + + impl crate::Joined for TestJoinedValueJsonBuffers { + type Item = TestJoinedValueJson; + + fn pull(&self, session: Entity, world: &mut World) -> Result { + let integer = self.integer.pull(session, world)?; + let float = self.float.pull(session, world)?; + let json = self.json.pull(session, world)?; + + Ok(TestJoinedValueJson { + integer, + float, + json, + }) + } + } + + impl JoinedValue for TestJoinedValueJson { + type Buffers = TestJoinedValueJsonBuffers; + } + + #[test] + fn test_joined_value_json() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + JsonBuffer::register_for::(); + + let buffer_i64 = builder.create_buffer(BufferSettings::default()); + let buffer_f64 = builder.create_buffer(BufferSettings::default()); + let buffer_json = builder.create_buffer(BufferSettings::default()); + + let mut buffers = BufferMap::default(); + buffers.insert("integer", buffer_i64); + buffers.insert("float", buffer_f64); + buffers.insert("json", buffer_json); + + scope.input.chain(builder).fork_unzip(( + |chain: Chain<_>| chain.connect(buffer_i64.input_slot()), + |chain: Chain<_>| chain.connect(buffer_f64.input_slot()), + |chain: Chain<_>| chain.connect(buffer_json.input_slot()), + )); + + builder + .try_join_into(&buffers) + .unwrap() + .connect(scope.terminate); + }); + + let mut promise = context.command(|commands| { + commands + .request((5_i64, 3.14_f64, TestMessage::new()), workflow) + .take_response() + }); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let value: TestJoinedValueJson = promise.take().available().unwrap(); + assert_eq!(value.integer, 5); + assert_eq!(value.float, 3.14); + let deserialized_json: TestMessage = serde_json::from_value(value.json).unwrap(); + let expected_json = TestMessage::new(); + assert_eq!(deserialized_json, expected_json); + } } diff --git a/src/builder.rs b/src/builder.rs index fe41eea2..800d9d32 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -245,9 +245,9 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { /// Try joining a map of buffers into a single value. pub fn try_join_into<'b, Joined: JoinedValue>( &'b mut self, - buffers: impl Into, + buffers: &BufferMap, ) -> Result, IncompatibleLayout> { - Joined::try_join_into(buffers.into(), self) + Joined::try_join_into(buffers, self) } /// Join an appropriate layout of buffers into a single value. diff --git a/src/lib.rs b/src/lib.rs index cc40b262..84345246 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -336,8 +336,8 @@ impl Plugin for ImpulsePlugin { pub mod prelude { pub use crate::{ buffer::{ - AnyBuffer, AnyBufferKey, AnyBufferMut, AnyBufferWorldAccess, AnyMessage, Buffer, - BufferAccess, BufferAccessMut, BufferKey, BufferKeyMap, BufferMapLayout, + AnyBuffer, AnyBufferKey, AnyBufferMut, AnyBufferWorldAccess, AnyMessageBox, Buffer, + BufferAccess, BufferAccessMut, BufferKey, BufferKeyMap, BufferMap, BufferMapLayout, BufferSettings, Bufferable, Buffered, IncompatibleLayout, IterBufferable, JoinedValue, RetentionPolicy, }, From 617e22f77b9b952d2ad64c233077dbf58e69a93d Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Thu, 6 Feb 2025 09:18:18 +0000 Subject: [PATCH 25/37] Refactor Bufferable so we no longer need join_into Signed-off-by: Michael X. Grey --- src/buffer/buffer_map.rs | 60 ++++++++++--- src/buffer/bufferable.rs | 172 +++++++++++--------------------------- src/buffer/buffered.rs | 137 +++++++++++++++++++++++++++++- src/buffer/json_buffer.rs | 44 +++++++++- src/builder.rs | 87 ++++--------------- src/chain.rs | 11 +-- src/lib.rs | 4 +- 7 files changed, 297 insertions(+), 218 deletions(-) diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 852711d2..e90f5b30 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -26,7 +26,7 @@ use bevy_ecs::prelude::{Entity, World}; use crate::{ add_listener_to_source, Accessed, AddOperation, AnyBuffer, AnyBufferKey, AnyMessageBox, Buffer, BufferKeyBuilder, Buffered, Builder, Chain, Gate, GateState, Join, Joined, - OperationError, OperationResult, OperationRoster, Output, UnusedTarget, + OperationError, OperationResult, OperationRoster, Output, UnusedTarget, Bufferable, }; #[derive(Clone, Default)] @@ -116,7 +116,7 @@ pub struct BufferIncompatibility { /// You do not normally have to implement this yourself. Instead you should /// `#[derive(JoinedValue)]` on a struct that you want a join operation to /// produce. -pub trait BufferMapLayout: Sized + Clone { +pub trait BufferMapLayout: Sized + Clone + 'static + Send + Sync { /// Produce a list of the buffers that exist in this layout. fn buffer_list(&self) -> SmallVec<[AnyBuffer; 8]>; @@ -124,6 +124,14 @@ pub trait BufferMapLayout: Sized + Clone { fn try_from_buffer_map(buffers: &BufferMap) -> Result; } +impl Bufferable for T { + type BufferType = Self; + + fn into_buffer(self, _: &mut Builder) -> Self::BufferType { + self + } +} + impl Buffered for T { fn verify_scope(&self, scope: Entity) { for buffer in self.buffer_list() { @@ -231,14 +239,6 @@ pub trait JoinedValue: 'static + Send + Sync + Sized { /// Trait to describe a layout of buffer keys pub trait BufferKeyMap: 'static + Send + Sync + Sized + Clone { type Buffers: 'static + BufferMapLayout + Accessed + Send + Sync; - - fn add_accessor(buffers: &BufferMap, accessor: Entity, world: &mut World) -> OperationResult; - - fn create_key(buffers: &BufferMap, builder: &BufferKeyBuilder) -> Self; - - fn deep_clone_key(&self) -> Self; - - fn is_key_in_use(&self) -> bool; } impl BufferMapLayout for BufferMap { @@ -383,7 +383,7 @@ mod tests { } #[test] - fn test_joined_value() { + fn test_try_join() { let mut context = TestingContext::minimal_plugins(); let workflow = context.spawn_io_workflow(|scope, builder| { @@ -403,7 +403,7 @@ mod tests { )); builder - .try_join_into(&buffers) + .try_join(&buffers) .unwrap() .connect(scope.terminate); }); @@ -421,4 +421,40 @@ mod tests { assert_eq!(value.string, "hello"); assert!(context.no_unhandled_errors()); } + + #[test] + fn test_joined_value() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + let buffers = TestJoinedValueBuffers { + integer: builder.create_buffer(BufferSettings::default()), + float: builder.create_buffer(BufferSettings::default()), + string: builder.create_buffer(BufferSettings::default()), + }; + + scope.input.chain(builder).fork_unzip(( + |chain: Chain<_>| chain.connect(buffers.integer.input_slot()), + |chain: Chain<_>| chain.connect(buffers.float.input_slot()), + |chain: Chain<_>| chain.connect(buffers.string.input_slot()), + )); + + builder + .join(buffers) + .connect(scope.terminate); + }); + + let mut promise = context.command(|commands| { + commands + .request((5_i64, 3.14_f64, "hello".to_string()), workflow) + .take_response() + }); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let value: TestJoinedValue = promise.take().available().unwrap(); + assert_eq!(value.integer, 5); + assert_eq!(value.float, 3.14); + assert_eq!(value.string, "hello"); + assert!(context.no_unhandled_errors()); + } } diff --git a/src/buffer/bufferable.rs b/src/buffer/bufferable.rs index 628ba899..92201353 100644 --- a/src/buffer/bufferable.rs +++ b/src/buffer/bufferable.rs @@ -20,137 +20,18 @@ use smallvec::SmallVec; use crate::{ Accessed, AddOperation, Buffer, BufferSettings, Buffered, Builder, Chain, - CleanupWorkflowConditions, CloneFromBuffer, Join, Joined, Listen, Output, Scope, ScopeSettings, - UnusedTarget, + CloneFromBuffer, Join, Joined, Output, UnusedTarget, }; pub type BufferKeys = <::BufferType as Accessed>::Key; pub type JoinedItem = <::BufferType as Joined>::Item; pub trait Bufferable { - type BufferType: Joined + Accessed; + type BufferType: Buffered; /// Convert these bufferable workflow elements into buffers if they are not /// buffers already. fn into_buffer(self, builder: &mut Builder) -> Self::BufferType; - - /// Join these bufferable workflow elements. Each time every buffer contains - /// at least one element, this will pull the oldest element from each buffer - /// and join them into a tuple that gets sent to the target. - /// - /// If you need a more general way to get access to one or more buffers, - /// use [`listen`](Self::listen) instead. - fn join<'w, 's, 'a, 'b>( - self, - builder: &'b mut Builder<'w, 's, 'a>, - ) -> Chain<'w, 's, 'a, 'b, JoinedItem> - where - Self: Sized, - Self::BufferType: 'static + Send + Sync, - JoinedItem: 'static + Send + Sync, - { - let scope = builder.scope(); - let buffers = self.into_buffer(builder); - buffers.verify_scope(scope); - - let join = builder.commands.spawn(()).id(); - let target = builder.commands.spawn(UnusedTarget).id(); - builder.commands.add(AddOperation::new( - Some(scope), - join, - Join::new(buffers, target), - )); - - Output::new(scope, target).chain(builder) - } - - /// Create an operation that will output buffer access keys each time any - /// one of the buffers is modified. This can be used to create a node in a - /// workflow that wakes up every time one or more buffers change, and then - /// operates on those buffers. - /// - /// For an operation that simply joins the contents of two or more outputs - /// or buffers, use [`join`](Self::join) instead. - fn listen<'w, 's, 'a, 'b>( - self, - builder: &'b mut Builder<'w, 's, 'a>, - ) -> Chain<'w, 's, 'a, 'b, BufferKeys> - where - Self: Sized, - Self::BufferType: 'static + Send + Sync, - BufferKeys: 'static + Send + Sync, - { - let scope = builder.scope(); - let buffers = self.into_buffer(builder); - buffers.verify_scope(scope); - - let listen = builder.commands.spawn(()).id(); - let target = builder.commands.spawn(UnusedTarget).id(); - builder.commands.add(AddOperation::new( - Some(scope), - listen, - Listen::new(buffers, target), - )); - - Output::new(scope, target).chain(builder) - } - - /// Alternative way to call [`Builder::on_cleanup`]. - fn on_cleanup( - self, - builder: &mut Builder, - build: impl FnOnce(Scope, (), ()>, &mut Builder) -> Settings, - ) where - Self: Sized, - Self::BufferType: 'static + Send + Sync, - BufferKeys: 'static + Send + Sync, - Settings: Into, - { - builder.on_cleanup(self, build) - } - - /// Alternative way to call [`Builder::on_cancel`]. - fn on_cancel( - self, - builder: &mut Builder, - build: impl FnOnce(Scope, (), ()>, &mut Builder) -> Settings, - ) where - Self: Sized, - Self::BufferType: 'static + Send + Sync, - BufferKeys: 'static + Send + Sync, - Settings: Into, - { - builder.on_cancel(self, build) - } - - /// Alternative way to call [`Builder::on_terminate`]. - fn on_terminate( - self, - builder: &mut Builder, - build: impl FnOnce(Scope, (), ()>, &mut Builder) -> Settings, - ) where - Self: Sized, - Self::BufferType: 'static + Send + Sync, - BufferKeys: 'static + Send + Sync, - Settings: Into, - { - builder.on_terminate(self, build) - } - - /// Alternative way to call [`Builder::on_cleanup_if`]. - fn on_cleanup_if( - self, - builder: &mut Builder, - conditions: CleanupWorkflowConditions, - build: impl FnOnce(Scope, (), ()>, &mut Builder) -> Settings, - ) where - Self: Sized, - Self::BufferType: 'static + Send + Sync, - BufferKeys: 'static + Send + Sync, - Settings: Into, - { - builder.on_cleanup_if(conditions, self, build) - } } impl Bufferable for Buffer { @@ -179,6 +60,54 @@ impl Bufferable for Output { } } +pub trait Joinable: Bufferable { + type Item; + + fn join<'w, 's, 'a, 'b>( + self, + builder: &'b mut Builder<'w, 's, 'a>, + ) -> Chain<'w, 's, 'a, 'b, Self::Item>; +} + +impl Joinable for B +where + B: Bufferable, + B::BufferType: Joined, +{ + type Item = JoinedItem; + + fn join<'w, 's, 'a, 'b>( + self, + builder: &'b mut Builder<'w, 's, 'a>, + ) -> Chain<'w, 's, 'a, 'b, Self::Item> { + self.into_buffer(builder).join(builder) + } +} + +pub trait Accessible: Bufferable { + type Keys; + + fn listen<'w, 's, 'a, 'b>( + self, + builder: &'b mut Builder<'w, 's, 'a>, + ) -> Chain<'w, 's, 'a, 'b, Self::Keys>; +} + +impl Accessible for B +where + B: Bufferable, + B::BufferType: Accessed, +{ + type Keys = BufferKeys; + + fn listen<'w, 's, 'a, 'b>( + self, + builder: &'b mut Builder<'w, 's, 'a>, + ) -> Chain<'w, 's, 'a, 'b, Self::Keys> { + self.into_buffer(builder).listen(builder) + } +} + macro_rules! impl_bufferable_for_tuple { ($($T:ident),*) => { #[allow(non_snake_case)] @@ -248,6 +177,7 @@ impl IterBufferable for T where T: IntoIterator, T::Item: Bufferable, + ::BufferType: Joined, { type BufferElement = ::BufferType; diff --git a/src/buffer/buffered.rs b/src/buffer/buffered.rs index 50e63493..277d5c9a 100644 --- a/src/buffer/buffered.rs +++ b/src/buffer/buffered.rs @@ -16,6 +16,7 @@ */ use bevy_ecs::prelude::{Entity, World}; +use bevy_hierarchy::BuildChildren; use bevy_utils::all_tuples; use smallvec::SmallVec; @@ -23,10 +24,12 @@ use smallvec::SmallVec; use crate::{ Buffer, BufferAccessors, BufferKey, BufferKeyBuilder, BufferStorage, CloneFromBuffer, ForkTargetStorage, Gate, GateState, InspectBuffer, ManageBuffer, OperationError, - OperationResult, OperationRoster, OrBroken, SingleInputStorage, + OperationResult, OperationRoster, OrBroken, SingleInputStorage, Join, Output, + AddOperation, Chain, Builder, UnusedTarget, Listen, CleanupWorkflowConditions, + Scope, ScopeSettings, BeginCleanupWorkflow, }; -pub trait Buffered: Clone { +pub trait Buffered: 'static + Send + Sync + Clone { fn verify_scope(&self, scope: Entity); fn buffered_count(&self, session: Entity, world: &World) -> Result; @@ -47,16 +50,142 @@ pub trait Buffered: Clone { } pub trait Joined: Buffered { - type Item; + type Item: 'static + Send + Sync; fn pull(&self, session: Entity, world: &mut World) -> Result; + + /// Join these bufferable workflow elements. Each time every buffer contains + /// at least one element, this will pull the oldest element from each buffer + /// and join them into a tuple that gets sent to the target. + /// + /// If you need a more general way to get access to one or more buffers, + /// use [`listen`](Self::listen) instead. + fn join<'w, 's, 'a, 'b>( + self, + builder: &'b mut Builder<'w, 's, 'a>, + ) -> Chain<'w, 's, 'a, 'b, Self::Item> { + let scope = builder.scope(); + self.verify_scope(scope); + + let join = builder.commands.spawn(()).id(); + let target = builder.commands.spawn(UnusedTarget).id(); + builder.commands.add(AddOperation::new( + Some(scope), + join, + Join::new(self, target), + )); + + Output::new(scope, target).chain(builder) + } } pub trait Accessed: Buffered { - type Key: Clone; + type Key: 'static + Send + Sync + Clone; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult; fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key; fn deep_clone_key(key: &Self::Key) -> Self::Key; fn is_key_in_use(key: &Self::Key) -> bool; + + /// Create an operation that will output buffer access keys each time any + /// one of the buffers is modified. This can be used to create a node in a + /// workflow that wakes up every time one or more buffers change, and then + /// operates on those buffers. + /// + /// For an operation that simply joins the contents of two or more outputs + /// or buffers, use [`join`](Self::join) instead. + fn listen<'w, 's, 'a, 'b>( + self, + builder: &'b mut Builder<'w, 's, 'a>, + ) -> Chain<'w, 's, 'a, 'b, Self::Key> { + let scope = builder.scope(); + self.verify_scope(scope); + + let listen = builder.commands.spawn(()).id(); + let target = builder.commands.spawn(UnusedTarget).id(); + builder.commands.add(AddOperation::new( + Some(scope), + listen, + Listen::new(self, target), + )); + + Output::new(scope, target).chain(builder) + } + + + /// Alternative way to call [`Builder::on_cleanup`]. + fn on_cleanup( + self, + builder: &mut Builder, + build: impl FnOnce(Scope, &mut Builder) -> Settings, + ) where + Settings: Into, + { + self.on_cleanup_if( + builder, + CleanupWorkflowConditions::always_if(true, true), + build, + ) + } + + /// Alternative way to call [`Builder::on_cancel`]. + fn on_cancel( + self, + builder: &mut Builder, + build: impl FnOnce(Scope, &mut Builder) -> Settings, + ) where + Settings: Into, + { + self.on_cleanup_if( + builder, + CleanupWorkflowConditions::always_if(false, true), + build, + ) + } + + /// Alternative way to call [`Builder::on_terminate`]. + fn on_terminate( + self, + builder: &mut Builder, + build: impl FnOnce(Scope, &mut Builder) -> Settings, + ) where + Settings: Into, + { + self.on_cleanup_if( + builder, + CleanupWorkflowConditions::always_if(true, false), + build, + ) + } + + /// Alternative way to call [`Builder::on_cleanup_if`]. + fn on_cleanup_if( + self, + builder: &mut Builder, + conditions: CleanupWorkflowConditions, + build: impl FnOnce(Scope, &mut Builder) -> Settings, + ) where + Settings: Into, + { + let cancelling_scope_id = builder.commands.spawn(()).id(); + let _ = builder.create_scope_impl::( + cancelling_scope_id, + builder.finish_scope_cancel, + build, + ); + + let begin_cancel = builder.commands.spawn(()).set_parent(builder.scope).id(); + self.verify_scope(builder.scope); + builder.commands.add(AddOperation::new( + None, + begin_cancel, + BeginCleanupWorkflow::::new( + builder.scope, + self, + cancelling_scope_id, + conditions.run_on_terminate, + conditions.run_on_cancel, + ), + )); + } } impl Buffered for Buffer { diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 6a0a6583..b39af1b2 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -1422,7 +1422,7 @@ mod tests { } #[test] - fn test_joined_value_json() { + fn test_try_join_json() { let mut context = TestingContext::minimal_plugins(); let workflow = context.spawn_io_workflow(|scope, builder| { @@ -1444,7 +1444,7 @@ mod tests { )); builder - .try_join_into(&buffers) + .try_join(&buffers) .unwrap() .connect(scope.terminate); }); @@ -1463,4 +1463,44 @@ mod tests { let expected_json = TestMessage::new(); assert_eq!(deserialized_json, expected_json); } + + #[test] + fn test_joined_value_json() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + JsonBuffer::register_for::(); + + let json_buffer = builder.create_buffer::(BufferSettings::default()); + let buffers = TestJoinedValueJsonBuffers { + integer: builder.create_buffer(BufferSettings::default()), + float: builder.create_buffer(BufferSettings::default()), + json: json_buffer.into(), + }; + + scope.input.chain(builder).fork_unzip(( + |chain: Chain<_>| chain.connect(buffers.integer.input_slot()), + |chain: Chain<_>| chain.connect(buffers.float.input_slot()), + |chain: Chain<_>| chain.connect(json_buffer.input_slot()), + )); + + builder + .join(buffers) + .connect(scope.terminate); + }); + + let mut promise = context.command(|commands| { + commands + .request((5_i64, 3.14_f64, TestMessage::new()), workflow) + .take_response() + }); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let value: TestJoinedValueJson = promise.take().available().unwrap(); + assert_eq!(value.integer, 5); + assert_eq!(value.float, 3.14); + let deserialized_json: TestMessage = serde_json::from_value(value.json).unwrap(); + let expected_json = TestMessage::new(); + assert_eq!(deserialized_json, expected_json); + } } diff --git a/src/builder.rs b/src/builder.rs index 800d9d32..072451d6 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -16,21 +16,20 @@ */ use bevy_ecs::prelude::{Commands, Entity}; -use bevy_hierarchy::prelude::BuildChildren; use std::future::Future; use smallvec::SmallVec; use crate::{ - AddOperation, AsMap, BeginCleanupWorkflow, Buffer, BufferKeys, BufferLocation, BufferMap, + AddOperation, AsMap, Buffer, BufferKeys, BufferLocation, BufferMap, BufferSettings, Bufferable, Buffered, Chain, Collect, ForkClone, ForkCloneOutput, ForkTargetStorage, Gate, GateRequest, IncompatibleLayout, Injection, InputSlot, IntoAsyncMap, IntoBlockingMap, JoinedItem, JoinedValue, Node, OperateBuffer, OperateBufferAccess, OperateDynamicGate, OperateScope, OperateSplit, OperateStaticGate, Output, Provider, RequestOfMap, ResponseOfMap, Scope, ScopeEndpoints, ScopeSettings, ScopeSettingsStorage, Sendish, Service, SplitOutputs, Splittable, StreamPack, StreamTargetMap, StreamsOfMap, Trim, - TrimBranch, UnusedTarget, + TrimBranch, UnusedTarget, Joined, Accessed, }; pub(crate) mod connect; @@ -236,38 +235,29 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { /// Alternative way of calling [`Bufferable::join`] pub fn join<'b, B: Bufferable>(&'b mut self, buffers: B) -> Chain<'w, 's, 'a, 'b, JoinedItem> where - B::BufferType: 'static + Send + Sync, + B::BufferType: Joined, JoinedItem: 'static + Send + Sync, { - buffers.join(self) + buffers.into_buffer(self).join(self) } /// Try joining a map of buffers into a single value. - pub fn try_join_into<'b, Joined: JoinedValue>( + pub fn try_join<'b, Joined: JoinedValue>( &'b mut self, buffers: &BufferMap, ) -> Result, IncompatibleLayout> { Joined::try_join_into(buffers, self) } - /// Join an appropriate layout of buffers into a single value. - pub fn join_into<'b, Joined: JoinedValue>( - &'b mut self, - buffers: Joined::Buffers, - ) -> Chain<'w, 's, 'a, 'b, Joined> { - Joined::join_into(buffers, self) - } - /// Alternative way of calling [`Bufferable::listen`]. pub fn listen<'b, B: Bufferable>( &'b mut self, buffers: B, ) -> Chain<'w, 's, 'a, 'b, BufferKeys> where - B::BufferType: 'static + Send + Sync, - BufferKeys: 'static + Send + Sync, + B::BufferType: Accessed, { - buffers.listen(self) + buffers.into_buffer(self).listen(self) } /// Create a node that combines its inputs with access to some buffers. You @@ -281,8 +271,7 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { where T: 'static + Send + Sync, B: Bufferable, - B::BufferType: 'static + Send + Sync, - BufferKeys: 'static + Send + Sync, + B::BufferType: Accessed, { let buffers = buffers.into_buffer(self); let source = self.commands.spawn(()).id(); @@ -404,15 +393,10 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { build: impl FnOnce(Scope, (), ()>, &mut Builder) -> Settings, ) where B: Bufferable, - B::BufferType: 'static + Send + Sync, - BufferKeys: 'static + Send + Sync, + B::BufferType: Accessed, Settings: Into, { - self.on_cleanup_if( - CleanupWorkflowConditions::always_if(true, true), - from_buffers, - build, - ) + from_buffers.into_buffer(self).on_cleanup(self, build); } /// Define a cleanup workflow that only gets run if the scope was cancelled. @@ -434,15 +418,10 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { build: impl FnOnce(Scope, (), ()>, &mut Builder) -> Settings, ) where B: Bufferable, - B::BufferType: 'static + Send + Sync, - BufferKeys: 'static + Send + Sync, + B::BufferType: Accessed, Settings: Into, { - self.on_cleanup_if( - CleanupWorkflowConditions::always_if(false, true), - from_buffers, - build, - ) + from_buffers.into_buffer(self).on_cancel(self, build); } /// Define a cleanup workflow that only gets run if the scope was successfully @@ -458,15 +437,10 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { build: impl FnOnce(Scope, (), ()>, &mut Builder) -> Settings, ) where B: Bufferable, - B::BufferType: 'static + Send + Sync, - BufferKeys: 'static + Send + Sync, + B::BufferType: Accessed, Settings: Into, { - self.on_cleanup_if( - CleanupWorkflowConditions::always_if(true, false), - from_buffers, - build, - ) + from_buffers.into_buffer(self).on_terminate(self, build); } /// Define a sub-workflow that will be run when this workflow is being cleaned @@ -479,31 +453,10 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { build: impl FnOnce(Scope, (), ()>, &mut Builder) -> Settings, ) where B: Bufferable, - B::BufferType: 'static + Send + Sync, - BufferKeys: 'static + Send + Sync, + B::BufferType: Accessed, Settings: Into, { - let cancelling_scope_id = self.commands.spawn(()).id(); - let _ = self.create_scope_impl::, (), (), Settings>( - cancelling_scope_id, - self.finish_scope_cancel, - build, - ); - - let begin_cancel = self.commands.spawn(()).set_parent(self.scope).id(); - let buffers = from_buffers.into_buffer(self); - buffers.verify_scope(self.scope); - self.commands.add(AddOperation::new( - None, - begin_cancel, - BeginCleanupWorkflow::::new( - self.scope, - buffers, - cancelling_scope_id, - conditions.run_on_terminate, - conditions.run_on_cancel, - ), - )); + from_buffers.into_buffer(self).on_cleanup_if(self, conditions, build); } /// Create a node that trims (cancels) other nodes in the workflow when it @@ -544,7 +497,6 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { pub fn create_gate(&mut self, buffers: B) -> Node, T> where B: Bufferable, - B::BufferType: 'static + Send + Sync, T: 'static + Send + Sync, { let buffers = buffers.into_buffer(self); @@ -574,7 +526,6 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { pub fn create_gate_action(&mut self, action: Gate, buffers: B) -> Node where B: Bufferable, - B::BufferType: 'static + Send + Sync, T: 'static + Send + Sync, { let buffers = buffers.into_buffer(self); @@ -601,7 +552,6 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { pub fn create_gate_open(&mut self, buffers: B) -> Node where B: Bufferable, - B::BufferType: 'static + Send + Sync, T: 'static + Send + Sync, { self.create_gate_action(Gate::Open, buffers) @@ -613,7 +563,6 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { pub fn create_gate_close(&mut self, buffers: B) -> Node where B: Bufferable, - B::BufferType: 'static + Send + Sync, T: 'static + Send + Sync, { self.create_gate_action(Gate::Closed, buffers) @@ -715,8 +664,8 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { /// later without breaking API. #[derive(Clone)] pub struct CleanupWorkflowConditions { - run_on_terminate: bool, - run_on_cancel: bool, + pub(crate) run_on_terminate: bool, + pub(crate) run_on_cancel: bool, } impl CleanupWorkflowConditions { diff --git a/src/chain.rs b/src/chain.rs index 46dbba11..ebe76edf 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -24,7 +24,7 @@ use smallvec::SmallVec; use std::error::Error; use crate::{ - make_option_branching, make_result_branching, AddOperation, AsMap, Buffer, BufferKey, + make_option_branching, make_result_branching, Accessed, AddOperation, AsMap, Buffer, BufferKey, BufferKeys, Bufferable, Buffered, Builder, Collect, CreateCancelFilter, CreateDisposalFilter, ForkTargetStorage, Gate, GateRequest, InputSlot, IntoAsyncMap, IntoBlockingCallback, IntoBlockingMap, Node, Noop, OperateBufferAccess, OperateDynamicGate, OperateSplit, @@ -302,8 +302,7 @@ impl<'w, 's, 'a, 'b, T: 'static + Send + Sync> Chain<'w, 's, 'a, 'b, T> { pub fn with_access(self, buffers: B) -> Chain<'w, 's, 'a, 'b, (T, BufferKeys)> where B: Bufferable, - B::BufferType: 'static + Send + Sync, - BufferKeys: 'static + Send + Sync, + B::BufferType: Accessed, { let buffers = buffers.into_buffer(self.builder); buffers.verify_scope(self.builder.scope); @@ -324,8 +323,7 @@ impl<'w, 's, 'a, 'b, T: 'static + Send + Sync> Chain<'w, 's, 'a, 'b, T> { pub fn then_access(self, buffers: B) -> Chain<'w, 's, 'a, 'b, BufferKeys> where B: Bufferable, - B::BufferType: 'static + Send + Sync, - BufferKeys: 'static + Send + Sync, + B::BufferType: Accessed, { self.with_access(buffers).map_block(|(_, key)| key) } @@ -557,7 +555,6 @@ impl<'w, 's, 'a, 'b, T: 'static + Send + Sync> Chain<'w, 's, 'a, 'b, T> { pub fn then_gate_action(self, action: Gate, buffers: B) -> Chain<'w, 's, 'a, 'b, T> where B: Bufferable, - B::BufferType: 'static + Send + Sync, { let buffers = buffers.into_buffer(self.builder); buffers.verify_scope(self.builder.scope); @@ -578,7 +575,6 @@ impl<'w, 's, 'a, 'b, T: 'static + Send + Sync> Chain<'w, 's, 'a, 'b, T> { pub fn then_gate_open(self, buffers: B) -> Chain<'w, 's, 'a, 'b, T> where B: Bufferable, - B::BufferType: 'static + Send + Sync, { self.then_gate_action(Gate::Open, buffers) } @@ -588,7 +584,6 @@ impl<'w, 's, 'a, 'b, T: 'static + Send + Sync> Chain<'w, 's, 'a, 'b, T> { pub fn then_gate_close(self, buffers: B) -> Chain<'w, 's, 'a, 'b, T> where B: Bufferable, - B::BufferType: 'static + Send + Sync, { self.then_gate_action(Gate::Closed, buffers) } diff --git a/src/lib.rs b/src/lib.rs index 84345246..1fad8cee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -336,9 +336,9 @@ impl Plugin for ImpulsePlugin { pub mod prelude { pub use crate::{ buffer::{ - AnyBuffer, AnyBufferKey, AnyBufferMut, AnyBufferWorldAccess, AnyMessageBox, Buffer, + Accessible, AnyBuffer, AnyBufferKey, AnyBufferMut, AnyBufferWorldAccess, AnyMessageBox, Buffer, BufferAccess, BufferAccessMut, BufferKey, BufferKeyMap, BufferMap, BufferMapLayout, - BufferSettings, Bufferable, Buffered, IncompatibleLayout, IterBufferable, JoinedValue, + BufferSettings, Bufferable, Buffered, IncompatibleLayout, IterBufferable, Joinable, JoinedValue, RetentionPolicy, }, builder::Builder, From 4c087ead81fc1ad7a152f0f10d7232eceb1f6e4d Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Thu, 6 Feb 2025 09:18:28 +0000 Subject: [PATCH 26/37] Fix style Signed-off-by: Michael X. Grey --- src/buffer/any_buffer.rs | 18 +++++++++++++---- src/buffer/buffer_map.rs | 41 +++++++++++++++++---------------------- src/buffer/bufferable.rs | 4 ++-- src/buffer/buffered.rs | 10 ++++------ src/buffer/json_buffer.rs | 15 +++++++------- src/builder.rs | 19 +++++++++--------- src/lib.rs | 8 ++++---- 7 files changed, 59 insertions(+), 56 deletions(-) diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 580baa28..ebed79c5 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -389,7 +389,10 @@ impl<'w, 's, 'a> AnyBufferMut<'w, 's, 'a> { /// If the input value does not match the message type of the buffer, this /// will return [`Err`] and give back an error with the message that you /// tried to push and the type information for the expected message type. - pub fn push_any(&mut self, value: AnyMessageBox) -> Result, AnyMessageError> { + pub fn push_any( + &mut self, + value: AnyMessageBox, + ) -> Result, AnyMessageError> { self.storage.any_push(self.session, value) } @@ -525,7 +528,8 @@ trait AnyBufferViewing { trait AnyBufferManagement: AnyBufferViewing { fn any_push(&mut self, session: Entity, value: AnyMessageBox) -> AnyMessagePushResult; - fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessageBox) -> AnyMessagePushResult; + fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessageBox) + -> AnyMessagePushResult; fn any_pull(&mut self, session: Entity) -> Option; fn any_pull_newest(&mut self, session: Entity) -> Option; fn any_oldest_mut<'a>(&'a mut self, session: Entity) -> Option>; @@ -675,7 +679,11 @@ impl AnyBufferManagement for Mut<'_, BufferStora Ok(self.push(session, value).map(to_any_message)) } - fn any_push_as_oldest(&mut self, session: Entity, value: AnyMessageBox) -> AnyMessagePushResult { + fn any_push_as_oldest( + &mut self, + session: Entity, + value: AnyMessageBox, + ) -> AnyMessagePushResult { let value = from_any_message::(value)?; Ok(self.push_as_oldest(session, value).map(to_any_message)) } @@ -721,7 +729,9 @@ fn to_any_message(x: T) -> AnyMessageBox { Box::new(x) } -fn from_any_message(value: AnyMessageBox) -> Result +fn from_any_message( + value: AnyMessageBox, +) -> Result where T: 'static, { diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index e90f5b30..421f1c97 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -24,9 +24,9 @@ use smallvec::SmallVec; use bevy_ecs::prelude::{Entity, World}; use crate::{ - add_listener_to_source, Accessed, AddOperation, AnyBuffer, AnyBufferKey, AnyMessageBox, - Buffer, BufferKeyBuilder, Buffered, Builder, Chain, Gate, GateState, Join, Joined, - OperationError, OperationResult, OperationRoster, Output, UnusedTarget, Bufferable, + add_listener_to_source, Accessed, AddOperation, AnyBuffer, AnyBufferKey, AnyMessageBox, Buffer, + BufferKeyBuilder, Bufferable, Buffered, Builder, Chain, Gate, GateState, Join, Joined, + OperationError, OperationResult, OperationRoster, Output, UnusedTarget, }; #[derive(Clone, Default)] @@ -59,7 +59,11 @@ pub struct IncompatibleLayout { impl IncompatibleLayout { /// Check whether a named buffer is compatible with a specific concrete message type. - pub fn require_message_type(&mut self, expected_name: &str, buffers: &BufferMap) -> Result, ()> { + pub fn require_message_type( + &mut self, + expected_name: &str, + buffers: &BufferMap, + ) -> Result, ()> { if let Some((name, buffer)) = buffers.inner.get_key_value(expected_name) { if let Some(buffer) = buffer.downcast_for_message::() { return Ok(buffer); @@ -80,7 +84,11 @@ impl IncompatibleLayout { /// Check whether a named buffer is compatible with a specialized buffer type, /// such as `JsonBuffer`. - pub fn require_buffer_type(&mut self, expected_name: &str, buffers: &BufferMap) -> Result { + pub fn require_buffer_type( + &mut self, + expected_name: &str, + buffers: &BufferMap, + ) -> Result { if let Some((name, buffer)) = buffers.inner.get_key_value(expected_name) { if let Some(buffer) = buffer.downcast_buffer::() { return Ok(buffer); @@ -139,11 +147,7 @@ impl Buffered for T { } } - fn buffered_count( - &self, - session: Entity, - world: &World, - ) -> Result { + fn buffered_count(&self, session: Entity, world: &World) -> Result { let mut min_count = None; for buffer in self.buffer_list() { @@ -158,11 +162,7 @@ impl Buffered for T { Ok(min_count.unwrap_or(0)) } - fn ensure_active_session( - &self, - session: Entity, - world: &mut World, - ) -> OperationResult { + fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { for buffer in self.buffer_list() { buffer.ensure_active_session(session, world)?; } @@ -238,7 +238,7 @@ pub trait JoinedValue: 'static + Send + Sync + Sized { /// Trait to describe a layout of buffer keys pub trait BufferKeyMap: 'static + Send + Sync + Sized + Clone { - type Buffers: 'static + BufferMapLayout + Accessed + Send + Sync; + type Buffers: 'static + BufferMapLayout + Accessed + Send + Sync; } impl BufferMapLayout for BufferMap { @@ -402,10 +402,7 @@ mod tests { |chain: Chain<_>| chain.connect(buffer_string.input_slot()), )); - builder - .try_join(&buffers) - .unwrap() - .connect(scope.terminate); + builder.try_join(&buffers).unwrap().connect(scope.terminate); }); let mut promise = context.command(|commands| { @@ -439,9 +436,7 @@ mod tests { |chain: Chain<_>| chain.connect(buffers.string.input_slot()), )); - builder - .join(buffers) - .connect(scope.terminate); + builder.join(buffers).connect(scope.terminate); }); let mut promise = context.command(|commands| { diff --git a/src/buffer/bufferable.rs b/src/buffer/bufferable.rs index 92201353..06da6417 100644 --- a/src/buffer/bufferable.rs +++ b/src/buffer/bufferable.rs @@ -19,8 +19,8 @@ use bevy_utils::all_tuples; use smallvec::SmallVec; use crate::{ - Accessed, AddOperation, Buffer, BufferSettings, Buffered, Builder, Chain, - CloneFromBuffer, Join, Joined, Output, UnusedTarget, + Accessed, AddOperation, Buffer, BufferSettings, Buffered, Builder, Chain, CloneFromBuffer, + Join, Joined, Output, UnusedTarget, }; pub type BufferKeys = <::BufferType as Accessed>::Key; diff --git a/src/buffer/buffered.rs b/src/buffer/buffered.rs index 277d5c9a..74c08d09 100644 --- a/src/buffer/buffered.rs +++ b/src/buffer/buffered.rs @@ -22,11 +22,10 @@ use bevy_utils::all_tuples; use smallvec::SmallVec; use crate::{ - Buffer, BufferAccessors, BufferKey, BufferKeyBuilder, BufferStorage, CloneFromBuffer, - ForkTargetStorage, Gate, GateState, InspectBuffer, ManageBuffer, OperationError, - OperationResult, OperationRoster, OrBroken, SingleInputStorage, Join, Output, - AddOperation, Chain, Builder, UnusedTarget, Listen, CleanupWorkflowConditions, - Scope, ScopeSettings, BeginCleanupWorkflow, + AddOperation, BeginCleanupWorkflow, Buffer, BufferAccessors, BufferKey, BufferKeyBuilder, + BufferStorage, Builder, Chain, CleanupWorkflowConditions, CloneFromBuffer, ForkTargetStorage, + Gate, GateState, InspectBuffer, Join, Listen, ManageBuffer, OperationError, OperationResult, + OperationRoster, OrBroken, Output, Scope, ScopeSettings, SingleInputStorage, UnusedTarget, }; pub trait Buffered: 'static + Send + Sync + Clone { @@ -110,7 +109,6 @@ pub trait Accessed: Buffered { Output::new(scope, target).chain(builder) } - /// Alternative way to call [`Builder::on_cleanup`]. fn on_cleanup( self, diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index b39af1b2..337d889b 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -1404,7 +1404,11 @@ mod tests { impl crate::Joined for TestJoinedValueJsonBuffers { type Item = TestJoinedValueJson; - fn pull(&self, session: Entity, world: &mut World) -> Result { + fn pull( + &self, + session: Entity, + world: &mut World, + ) -> Result { let integer = self.integer.pull(session, world)?; let float = self.float.pull(session, world)?; let json = self.json.pull(session, world)?; @@ -1443,10 +1447,7 @@ mod tests { |chain: Chain<_>| chain.connect(buffer_json.input_slot()), )); - builder - .try_join(&buffers) - .unwrap() - .connect(scope.terminate); + builder.try_join(&buffers).unwrap().connect(scope.terminate); }); let mut promise = context.command(|commands| { @@ -1484,9 +1485,7 @@ mod tests { |chain: Chain<_>| chain.connect(json_buffer.input_slot()), )); - builder - .join(buffers) - .connect(scope.terminate); + builder.join(buffers).connect(scope.terminate); }); let mut promise = context.command(|commands| { diff --git a/src/builder.rs b/src/builder.rs index 072451d6..14f02f1b 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -22,14 +22,13 @@ use std::future::Future; use smallvec::SmallVec; use crate::{ - AddOperation, AsMap, Buffer, BufferKeys, BufferLocation, BufferMap, - BufferSettings, Bufferable, Buffered, Chain, Collect, ForkClone, ForkCloneOutput, - ForkTargetStorage, Gate, GateRequest, IncompatibleLayout, Injection, InputSlot, IntoAsyncMap, - IntoBlockingMap, JoinedItem, JoinedValue, Node, OperateBuffer, OperateBufferAccess, - OperateDynamicGate, OperateScope, OperateSplit, OperateStaticGate, Output, Provider, - RequestOfMap, ResponseOfMap, Scope, ScopeEndpoints, ScopeSettings, ScopeSettingsStorage, - Sendish, Service, SplitOutputs, Splittable, StreamPack, StreamTargetMap, StreamsOfMap, Trim, - TrimBranch, UnusedTarget, Joined, Accessed, + Accessed, AddOperation, AsMap, Buffer, BufferKeys, BufferLocation, BufferMap, BufferSettings, + Bufferable, Buffered, Chain, Collect, ForkClone, ForkCloneOutput, ForkTargetStorage, Gate, + GateRequest, IncompatibleLayout, Injection, InputSlot, IntoAsyncMap, IntoBlockingMap, Joined, + JoinedItem, JoinedValue, Node, OperateBuffer, OperateBufferAccess, OperateDynamicGate, + OperateScope, OperateSplit, OperateStaticGate, Output, Provider, RequestOfMap, ResponseOfMap, + Scope, ScopeEndpoints, ScopeSettings, ScopeSettingsStorage, Sendish, Service, SplitOutputs, + Splittable, StreamPack, StreamTargetMap, StreamsOfMap, Trim, TrimBranch, UnusedTarget, }; pub(crate) mod connect; @@ -456,7 +455,9 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { B::BufferType: Accessed, Settings: Into, { - from_buffers.into_buffer(self).on_cleanup_if(self, conditions, build); + from_buffers + .into_buffer(self) + .on_cleanup_if(self, conditions, build); } /// Create a node that trims (cancels) other nodes in the workflow when it diff --git a/src/lib.rs b/src/lib.rs index 1fad8cee..417bd59b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -336,10 +336,10 @@ impl Plugin for ImpulsePlugin { pub mod prelude { pub use crate::{ buffer::{ - Accessible, AnyBuffer, AnyBufferKey, AnyBufferMut, AnyBufferWorldAccess, AnyMessageBox, Buffer, - BufferAccess, BufferAccessMut, BufferKey, BufferKeyMap, BufferMap, BufferMapLayout, - BufferSettings, Bufferable, Buffered, IncompatibleLayout, IterBufferable, Joinable, JoinedValue, - RetentionPolicy, + Accessible, AnyBuffer, AnyBufferKey, AnyBufferMut, AnyBufferWorldAccess, AnyMessageBox, + Buffer, BufferAccess, BufferAccessMut, BufferKey, BufferKeyMap, BufferMap, + BufferMapLayout, BufferSettings, Bufferable, Buffered, IncompatibleLayout, + IterBufferable, Joinable, JoinedValue, RetentionPolicy, }, builder::Builder, callback::{AsCallback, Callback, IntoAsyncCallback, IntoBlockingCallback}, From 858c7e6d11149502e57899153ad113afb9aa33ac Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Thu, 6 Feb 2025 12:59:00 +0000 Subject: [PATCH 27/37] Update docs Signed-off-by: Michael X. Grey --- src/buffer.rs | 2 +- src/buffer/any_buffer.rs | 2 +- src/buffer/buffer_map.rs | 25 ++++++++++++------------ src/buffer/bufferable.rs | 20 ++++++++++++++++++-- src/buffer/buffered.rs | 4 ++-- src/buffer/json_buffer.rs | 8 ++++---- src/builder.rs | 40 +++++++++++++++++++-------------------- src/chain.rs | 4 ++-- src/gate.rs | 4 ++-- 9 files changed, 62 insertions(+), 47 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index e9d6f6b8..106dd8ed 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -233,7 +233,7 @@ impl Default for RetentionPolicy { /// To obtain a `BufferKey`, use [`Chain::with_access`][1], or [`listen`][2]. /// /// [1]: crate::Chain::with_access -/// [2]: crate::Bufferable::listen +/// [2]: crate::Accessible::listen pub struct BufferKey { tag: BufferKeyTag, _ignore: std::marker::PhantomData, diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index ebed79c5..32529079 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -41,7 +41,7 @@ use crate::{ }; /// A [`Buffer`] whose message type has been anonymized. Joining with this buffer -/// type will yield an [`AnyMessage`]. +/// type will yield an [`AnyMessageBox`]. #[derive(Clone, Copy)] pub struct AnyBuffer { pub(crate) location: BufferLocation, diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 421f1c97..24569d44 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -199,16 +199,17 @@ impl Buffered for T { } } -/// This trait can be implemented for structs that are created by joining a -/// collection of buffers. Usually this +/// This trait can be implemented for structs that are created by joining together +/// values from a collection of buffers. Usually you do not need to implement this +/// yourself. Instead you can use `#[derive(JoinedValue)]`. pub trait JoinedValue: 'static + Send + Sync + Sized { /// This associated type must represent a buffer map layout that implements /// the [`Joined`] trait. The message type yielded by [`Joined`] for this /// associated type must match the [`JoinedValue`] type. type Buffers: 'static + BufferMapLayout + Joined + Send + Sync; - /// Used by [`Builder::join_into`] - fn join_into<'w, 's, 'a, 'b>( + /// Used by [`Self::try_join_from`] + fn join_from<'w, 's, 'a, 'b>( buffers: Self::Buffers, builder: &'b mut Builder<'w, 's, 'a>, ) -> Chain<'w, 's, 'a, 'b, Self> { @@ -226,17 +227,17 @@ pub trait JoinedValue: 'static + Send + Sync + Sized { Output::new(scope, target).chain(builder) } - /// Used by [`Builder::try_join_into`] - fn try_join_into<'w, 's, 'a, 'b>( + /// Used by [`Builder::try_join`] + fn try_join_from<'w, 's, 'a, 'b>( buffers: &BufferMap, builder: &'b mut Builder<'w, 's, 'a>, ) -> Result, IncompatibleLayout> { let buffers: Self::Buffers = Self::Buffers::try_from_buffer_map(buffers)?; - Ok(Self::join_into(buffers, builder)) + Ok(Self::join_from(buffers, builder)) } } -/// Trait to describe a layout of buffer keys +/// Trait to describe a set of buffer keys. pub trait BufferKeyMap: 'static + Send + Sync + Sized + Clone { type Buffers: 'static + BufferMapLayout + Accessed + Send + Sync; } @@ -321,6 +322,10 @@ mod tests { string: String, } + impl JoinedValue for TestJoinedValue { + type Buffers = TestJoinedValueBuffers; + } + #[derive(Clone)] struct TestJoinedValueBuffers { integer: Buffer, @@ -378,10 +383,6 @@ mod tests { } } - impl JoinedValue for TestJoinedValue { - type Buffers = TestJoinedValueBuffers; - } - #[test] fn test_try_join() { let mut context = TestingContext::minimal_plugins(); diff --git a/src/buffer/bufferable.rs b/src/buffer/bufferable.rs index 06da6417..a607f835 100644 --- a/src/buffer/bufferable.rs +++ b/src/buffer/bufferable.rs @@ -61,7 +61,7 @@ impl Bufferable for Output { } pub trait Joinable: Bufferable { - type Item; + type Item: 'static + Send + Sync; fn join<'w, 's, 'a, 'b>( self, @@ -69,6 +69,8 @@ pub trait Joinable: Bufferable { ) -> Chain<'w, 's, 'a, 'b, Self::Item>; } +/// This trait is used to create join operations that pull exactly one value +/// from multiple buffers or outputs simultaneously. impl Joinable for B where B: Bufferable, @@ -76,6 +78,12 @@ where { type Item = JoinedItem; + /// Join these bufferable workflow elements. Each time every buffer contains + /// at least one element, this will pull the oldest element from each buffer + /// and join them into a tuple that gets sent to the target. + /// + /// If you need a more general way to get access to one or more buffers, + /// use [`listen`](Accessible::listen) instead. fn join<'w, 's, 'a, 'b>( self, builder: &'b mut Builder<'w, 's, 'a>, @@ -84,9 +92,17 @@ where } } +/// This trait is used to create operations that access buffers or outputs. pub trait Accessible: Bufferable { - type Keys; + type Keys: 'static + Send + Sync; + /// Create an operation that will output buffer access keys each time any + /// one of the buffers is modified. This can be used to create a node in a + /// workflow that wakes up every time one or more buffers change, and then + /// operates on those buffers. + /// + /// For an operation that simply joins the contents of two or more outputs + /// or buffers, use [`join`](Joinable::join) instead. fn listen<'w, 's, 'a, 'b>( self, builder: &'b mut Builder<'w, 's, 'a>, diff --git a/src/buffer/buffered.rs b/src/buffer/buffered.rs index 74c08d09..dd6bd71d 100644 --- a/src/buffer/buffered.rs +++ b/src/buffer/buffered.rs @@ -57,7 +57,7 @@ pub trait Joined: Buffered { /// and join them into a tuple that gets sent to the target. /// /// If you need a more general way to get access to one or more buffers, - /// use [`listen`](Self::listen) instead. + /// use [`listen`](Accessed::listen) instead. fn join<'w, 's, 'a, 'b>( self, builder: &'b mut Builder<'w, 's, 'a>, @@ -90,7 +90,7 @@ pub trait Accessed: Buffered { /// operates on those buffers. /// /// For an operation that simply joins the contents of two or more outputs - /// or buffers, use [`join`](Self::join) instead. + /// or buffers, use [`join`](Joined::join) instead. fn listen<'w, 's, 'a, 'b>( self, builder: &'b mut Builder<'w, 's, 'a>, diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 337d889b..181fb726 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -1367,6 +1367,10 @@ mod tests { json: JsonBuffer, } + impl JoinedValue for TestJoinedValueJson { + type Buffers = TestJoinedValueJsonBuffers; + } + impl BufferMapLayout for TestJoinedValueJsonBuffers { fn buffer_list(&self) -> smallvec::SmallVec<[AnyBuffer; 8]> { use smallvec::smallvec; @@ -1421,10 +1425,6 @@ mod tests { } } - impl JoinedValue for TestJoinedValueJson { - type Buffers = TestJoinedValueJsonBuffers; - } - #[test] fn test_try_join_json() { let mut context = TestingContext::minimal_plugins(); diff --git a/src/builder.rs b/src/builder.rs index 14f02f1b..52680832 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -22,10 +22,10 @@ use std::future::Future; use smallvec::SmallVec; use crate::{ - Accessed, AddOperation, AsMap, Buffer, BufferKeys, BufferLocation, BufferMap, BufferSettings, + Accessed, Accessible, AddOperation, AsMap, Buffer, BufferKeys, BufferLocation, BufferMap, BufferSettings, Bufferable, Buffered, Chain, Collect, ForkClone, ForkCloneOutput, ForkTargetStorage, Gate, - GateRequest, IncompatibleLayout, Injection, InputSlot, IntoAsyncMap, IntoBlockingMap, Joined, - JoinedItem, JoinedValue, Node, OperateBuffer, OperateBufferAccess, OperateDynamicGate, + GateRequest, IncompatibleLayout, Injection, InputSlot, IntoAsyncMap, IntoBlockingMap, Joinable, + JoinedValue, Node, OperateBuffer, OperateBufferAccess, OperateDynamicGate, OperateScope, OperateSplit, OperateStaticGate, Output, Provider, RequestOfMap, ResponseOfMap, Scope, ScopeEndpoints, ScopeSettings, ScopeSettingsStorage, Sendish, Service, SplitOutputs, Splittable, StreamPack, StreamTargetMap, StreamsOfMap, Trim, TrimBranch, UnusedTarget, @@ -231,13 +231,12 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { ) } - /// Alternative way of calling [`Bufferable::join`] - pub fn join<'b, B: Bufferable>(&'b mut self, buffers: B) -> Chain<'w, 's, 'a, 'b, JoinedItem> - where - B::BufferType: Joined, - JoinedItem: 'static + Send + Sync, - { - buffers.into_buffer(self).join(self) + /// Alternative way of calling [`Joinable::join`] + pub fn join<'b, B: Joinable>( + &'b mut self, + buffers: B, + ) -> Chain<'w, 's, 'a, 'b, B::Item> { + buffers.join(self) } /// Try joining a map of buffers into a single value. @@ -245,18 +244,15 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { &'b mut self, buffers: &BufferMap, ) -> Result, IncompatibleLayout> { - Joined::try_join_into(buffers, self) + Joined::try_join_from(buffers, self) } - /// Alternative way of calling [`Bufferable::listen`]. - pub fn listen<'b, B: Bufferable>( + /// Alternative way of calling [`Accessible::listen`]. + pub fn listen<'b, B: Accessible>( &'b mut self, buffers: B, - ) -> Chain<'w, 's, 'a, 'b, BufferKeys> - where - B::BufferType: Accessed, - { - buffers.into_buffer(self).listen(self) + ) -> Chain<'w, 's, 'a, 'b, B::Keys> { + buffers.listen(self) } /// Create a node that combines its inputs with access to some buffers. You @@ -266,11 +262,13 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { /// /// Other [outputs](Output) can also be passed in as buffers. These outputs /// will be transformed into a buffer with default buffer settings. - pub fn create_buffer_access(&mut self, buffers: B) -> Node)> + pub fn create_buffer_access( + &mut self, + buffers: B, + ) -> Node)> where - T: 'static + Send + Sync, - B: Bufferable, B::BufferType: Accessed, + T: 'static + Send + Sync, { let buffers = buffers.into_buffer(self); let source = self.commands.spawn(()).id(); diff --git a/src/chain.rs b/src/chain.rs index ebe76edf..218ed243 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -298,7 +298,7 @@ impl<'w, 's, 'a, 'b, T: 'static + Send + Sync> Chain<'w, 's, 'a, 'b, T> { /// will be transformed into a buffer with default buffer settings. /// /// To obtain a set of buffer keys each time a buffer is modified, use - /// [`listen`](crate::Bufferable::listen). + /// [`listen`](crate::Accessible::listen). pub fn with_access(self, buffers: B) -> Chain<'w, 's, 'a, 'b, (T, BufferKeys)> where B: Bufferable, @@ -391,7 +391,7 @@ impl<'w, 's, 'a, 'b, T: 'static + Send + Sync> Chain<'w, 's, 'a, 'b, T> { /// The return values of the individual chain builders will be zipped into /// one tuple return value by this function. If all of the builders return /// [`Output`] then you can easily continue chaining more operations using - /// [`join`](crate::Bufferable::join), or destructure them into individual + /// [`join`](crate::Joinable::join), or destructure them into individual /// outputs that you can continue to build with. pub fn fork_clone>(self, build: Build) -> Build::Outputs where diff --git a/src/gate.rs b/src/gate.rs index 8d34c757..03797337 100644 --- a/src/gate.rs +++ b/src/gate.rs @@ -23,14 +23,14 @@ pub enum Gate { /// receive a wakeup immediately when a gate switches from closed to open, /// even if none of the data inside the buffer has changed. /// - /// [1]: crate::Bufferable::join + /// [1]: crate::Joinable::join Open, /// Close the buffer gate so that listeners (including [join][1] operations) /// will not be woken up when the data in the buffer gets modified. This /// effectively blocks the workflow nodes that are downstream of the buffer. /// Data will build up in the buffer according to its [`BufferSettings`][2]. /// - /// [1]: crate::Bufferable::join + /// [1]: crate::Joinable::join /// [2]: crate::BufferSettings Closed, } From aa22f87fb645b84bdc1d171c24e91c9c09d528dc Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Thu, 6 Feb 2025 13:18:24 +0000 Subject: [PATCH 28/37] Fix style Signed-off-by: Michael X. Grey --- src/builder.rs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 52680832..79088b60 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -22,13 +22,14 @@ use std::future::Future; use smallvec::SmallVec; use crate::{ - Accessed, Accessible, AddOperation, AsMap, Buffer, BufferKeys, BufferLocation, BufferMap, BufferSettings, - Bufferable, Buffered, Chain, Collect, ForkClone, ForkCloneOutput, ForkTargetStorage, Gate, - GateRequest, IncompatibleLayout, Injection, InputSlot, IntoAsyncMap, IntoBlockingMap, Joinable, - JoinedValue, Node, OperateBuffer, OperateBufferAccess, OperateDynamicGate, - OperateScope, OperateSplit, OperateStaticGate, Output, Provider, RequestOfMap, ResponseOfMap, - Scope, ScopeEndpoints, ScopeSettings, ScopeSettingsStorage, Sendish, Service, SplitOutputs, - Splittable, StreamPack, StreamTargetMap, StreamsOfMap, Trim, TrimBranch, UnusedTarget, + Accessed, Accessible, AddOperation, AsMap, Buffer, BufferKeys, BufferLocation, BufferMap, + BufferSettings, Bufferable, Buffered, Chain, Collect, ForkClone, ForkCloneOutput, + ForkTargetStorage, Gate, GateRequest, IncompatibleLayout, Injection, InputSlot, IntoAsyncMap, + IntoBlockingMap, Joinable, JoinedValue, Node, OperateBuffer, OperateBufferAccess, + OperateDynamicGate, OperateScope, OperateSplit, OperateStaticGate, Output, Provider, + RequestOfMap, ResponseOfMap, Scope, ScopeEndpoints, ScopeSettings, ScopeSettingsStorage, + Sendish, Service, SplitOutputs, Splittable, StreamPack, StreamTargetMap, StreamsOfMap, Trim, + TrimBranch, UnusedTarget, }; pub(crate) mod connect; @@ -232,10 +233,7 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { } /// Alternative way of calling [`Joinable::join`] - pub fn join<'b, B: Joinable>( - &'b mut self, - buffers: B, - ) -> Chain<'w, 's, 'a, 'b, B::Item> { + pub fn join<'b, B: Joinable>(&'b mut self, buffers: B) -> Chain<'w, 's, 'a, 'b, B::Item> { buffers.join(self) } @@ -248,10 +246,7 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { } /// Alternative way of calling [`Accessible::listen`]. - pub fn listen<'b, B: Accessible>( - &'b mut self, - buffers: B, - ) -> Chain<'w, 's, 'a, 'b, B::Keys> { + pub fn listen<'b, B: Accessible>(&'b mut self, buffers: B) -> Chain<'w, 's, 'a, 'b, B::Keys> { buffers.listen(self) } From f64871ed8b6c1be0b6f3f3d048191e4196ad4e95 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Mon, 17 Feb 2025 14:45:00 +0800 Subject: [PATCH 29/37] add derive `JoinedValue` to implement trait automatically (#53) Signed-off-by: Teo Koon Peng Signed-off-by: Michael X. Grey Co-authored-by: Michael X. Grey --- macros/Cargo.toml | 1 + macros/src/buffer.rs | 266 ++++++++++++++++++++++++++++++++++++++ macros/src/lib.rs | 20 ++- src/buffer/any_buffer.rs | 15 +++ src/buffer/buffer_map.rs | 206 ++++++++++++++++++----------- src/buffer/json_buffer.rs | 108 ++++++---------- src/lib.rs | 2 + src/testing.rs | 112 +++++++++++++++- 8 files changed, 580 insertions(+), 150 deletions(-) create mode 100644 macros/src/buffer.rs diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 12fa2bb3..c8627251 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -16,3 +16,4 @@ proc-macro = true [dependencies] syn = "2.0" quote = "1.0" +proc-macro2 = "1.0.93" diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs new file mode 100644 index 00000000..6ab201dc --- /dev/null +++ b/macros/src/buffer.rs @@ -0,0 +1,266 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::{parse_quote, Field, Generics, Ident, ItemStruct, Type, TypePath}; + +use crate::Result; + +pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result { + let struct_ident = &input_struct.ident; + let (impl_generics, ty_generics, where_clause) = input_struct.generics.split_for_impl(); + let StructConfig { + buffer_struct_name: buffer_struct_ident, + } = StructConfig::from_data_struct(&input_struct); + let buffer_struct_vis = &input_struct.vis; + + let (field_ident, _, field_config) = get_fields_map(&input_struct.fields)?; + let buffer: Vec<&Type> = field_config.iter().map(|config| &config.buffer).collect(); + let noncopy = field_config.iter().any(|config| config.noncopy); + + let buffer_struct: ItemStruct = parse_quote! { + #[allow(non_camel_case_types, unused)] + #buffer_struct_vis struct #buffer_struct_ident #impl_generics #where_clause { + #( + #buffer_struct_vis #field_ident: #buffer, + )* + } + }; + + let buffer_clone_impl = if noncopy { + // Clone impl for structs with a buffer that is not copyable + quote! { + impl #impl_generics ::std::clone::Clone for #buffer_struct_ident #ty_generics #where_clause { + fn clone(&self) -> Self { + Self { + #( + #field_ident: self.#field_ident.clone(), + )* + } + } + } + } + } else { + // Clone and copy impl for structs with buffers that are all copyable + quote! { + impl #impl_generics ::std::clone::Clone for #buffer_struct_ident #ty_generics #where_clause { + fn clone(&self) -> Self { + *self + } + } + + impl #impl_generics ::std::marker::Copy for #buffer_struct_ident #ty_generics #where_clause {} + } + }; + + let impl_buffer_map_layout = impl_buffer_map_layout(&buffer_struct, &input_struct)?; + let impl_joined = impl_joined(&buffer_struct, &input_struct)?; + + let gen = quote! { + impl #impl_generics ::bevy_impulse::JoinedValue for #struct_ident #ty_generics #where_clause { + type Buffers = #buffer_struct_ident #ty_generics; + } + + #buffer_struct + + #buffer_clone_impl + + impl #impl_generics #struct_ident #ty_generics #where_clause { + fn select_buffers( + #( + #field_ident: #buffer, + )* + ) -> #buffer_struct_ident #ty_generics { + #buffer_struct_ident { + #( + #field_ident, + )* + } + } + } + + #impl_buffer_map_layout + + #impl_joined + }; + + Ok(gen.into()) +} + +/// Code that are currently unused but could be used in the future, move them out of this mod if +/// they are ever used. +#[allow(unused)] +mod _unused { + use super::*; + + /// Converts a list of generics to a [`PhantomData`] TypePath. + /// e.g. `::std::marker::PhantomData` + fn to_phantom_data(generics: &Generics) -> TypePath { + let lifetimes: Vec = generics + .lifetimes() + .map(|lt| { + let lt = <.lifetime; + let ty: Type = parse_quote! { & #lt () }; + ty + }) + .collect(); + let ty_params: Vec<&Ident> = generics.type_params().map(|ty| &ty.ident).collect(); + parse_quote! { ::std::marker::PhantomData } + } +} + +struct StructConfig { + buffer_struct_name: Ident, +} + +impl StructConfig { + fn from_data_struct(data_struct: &ItemStruct) -> Self { + let mut config = Self { + buffer_struct_name: format_ident!("__bevy_impulse_{}_Buffers", data_struct.ident), + }; + + let attr = data_struct + .attrs + .iter() + .find(|attr| attr.path().is_ident("joined")); + + if let Some(attr) = attr { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("buffers_struct_name") { + config.buffer_struct_name = meta.value()?.parse()?; + } + Ok(()) + }) + // panic if attribute is malformed, this will result in a compile error which is intended. + .unwrap(); + } + + config + } +} + +struct FieldConfig { + buffer: Type, + noncopy: bool, +} + +impl FieldConfig { + fn from_field(field: &Field) -> Self { + let ty = &field.ty; + let mut config = Self { + buffer: parse_quote! { ::bevy_impulse::Buffer<#ty> }, + noncopy: false, + }; + + for attr in field + .attrs + .iter() + .filter(|attr| attr.path().is_ident("joined")) + { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("buffer") { + config.buffer = meta.value()?.parse()?; + } + if meta.path.is_ident("noncopy_buffer") { + config.noncopy = true; + } + Ok(()) + }) + // panic if attribute is malformed, this will result in a compile error which is intended. + .unwrap(); + } + + config + } +} + +fn get_fields_map(fields: &syn::Fields) -> Result<(Vec<&Ident>, Vec<&Type>, Vec)> { + match fields { + syn::Fields::Named(data) => { + let mut idents = Vec::new(); + let mut types = Vec::new(); + let mut configs = Vec::new(); + for field in &data.named { + let ident = field + .ident + .as_ref() + .ok_or("expected named fields".to_string())?; + idents.push(ident); + types.push(&field.ty); + configs.push(FieldConfig::from_field(field)); + } + Ok((idents, types, configs)) + } + _ => return Err("expected named fields".to_string()), + } +} + +/// Params: +/// buffer_struct: The struct to implement `BufferMapLayout`. +/// item_struct: The struct which `buffer_struct` is derived from. +fn impl_buffer_map_layout( + buffer_struct: &ItemStruct, + item_struct: &ItemStruct, +) -> Result { + let struct_ident = &buffer_struct.ident; + let (impl_generics, ty_generics, where_clause) = buffer_struct.generics.split_for_impl(); + let (field_ident, _, field_config) = get_fields_map(&item_struct.fields)?; + let buffer: Vec<&Type> = field_config.iter().map(|config| &config.buffer).collect(); + let map_key: Vec = field_ident.iter().map(|v| v.to_string()).collect(); + + Ok(quote! { + impl #impl_generics ::bevy_impulse::BufferMapLayout for #struct_ident #ty_generics #where_clause { + fn buffer_list(&self) -> ::smallvec::SmallVec<[AnyBuffer; 8]> { + use smallvec::smallvec; + smallvec![#( + self.#field_ident.as_any_buffer(), + )*] + } + + fn try_from_buffer_map(buffers: &::bevy_impulse::BufferMap) -> Result { + let mut compatibility = ::bevy_impulse::IncompatibleLayout::default(); + #( + let #field_ident = if let Ok(buffer) = compatibility.require_buffer_type::<#buffer>(#map_key, buffers) { + buffer + } else { + return Err(compatibility); + }; + )* + + Ok(Self { + #( + #field_ident, + )* + }) + } + } + } + .into()) +} + +/// Params: +/// joined_struct: The struct to implement `Joined`. +/// item_struct: The associated `Item` type to use for the `Joined` implementation. +fn impl_joined( + joined_struct: &ItemStruct, + item_struct: &ItemStruct, +) -> Result { + let struct_ident = &joined_struct.ident; + let item_struct_ident = &item_struct.ident; + let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl(); + let (field_ident, _, _) = get_fields_map(&item_struct.fields)?; + + Ok(quote! { + impl #impl_generics ::bevy_impulse::Joined for #struct_ident #ty_generics #where_clause { + type Item = #item_struct_ident #ty_generics; + + fn pull(&self, session: ::bevy_ecs::prelude::Entity, world: &mut ::bevy_ecs::prelude::World) -> Result { + #( + let #field_ident = self.#field_ident.pull(session, world)?; + )* + + Ok(Self::Item {#( + #field_ident, + )*}) + } + } + }.into()) +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index d40c9309..df58fdc6 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -15,9 +15,12 @@ * */ +mod buffer; +use buffer::impl_joined_value; + use proc_macro::TokenStream; use quote::quote; -use syn::DeriveInput; +use syn::{parse_macro_input, DeriveInput, ItemStruct}; #[proc_macro_derive(Stream)] pub fn simple_stream_macro(item: TokenStream) -> TokenStream { @@ -58,3 +61,18 @@ pub fn delivery_label_macro(item: TokenStream) -> TokenStream { } .into() } + +/// The result error is the compiler error message to be displayed. +type Result = std::result::Result; + +#[proc_macro_derive(JoinedValue, attributes(joined))] +pub fn derive_joined_value(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemStruct); + match impl_joined_value(&input) { + Ok(tokens) => tokens.into(), + Err(msg) => quote! { + compile_error!(#msg); + } + .into(), + } +} diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 32529079..b4315fe8 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -121,6 +121,10 @@ impl AnyBuffer { .ok() .map(|x| *x) } + + pub fn as_any_buffer(&self) -> Self { + self.clone().into() + } } impl From> for AnyBuffer { @@ -857,6 +861,17 @@ impl AnyBufferAccessImpl { })), ); + // Allow downcasting back to the original Buffer + buffer_downcasts.insert( + TypeId::of::>(), + Box::leak(Box::new(|location| -> Box { + Box::new(Buffer:: { + location, + _ignore: Default::default(), + }) + })), + ); + let mut key_downcasts: HashMap<_, KeyDowncastRef> = HashMap::new(); // Automatically register a downcast to AnyBufferKey diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 24569d44..c522dbfe 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -29,6 +29,8 @@ use crate::{ OperationError, OperationResult, OperationRoster, Output, UnusedTarget, }; +pub use bevy_impulse_derive::JoinedValue; + #[derive(Clone, Default)] pub struct BufferMap { inner: HashMap, AnyBuffer>, @@ -311,76 +313,16 @@ impl Accessed for BufferMap { #[cfg(test)] mod tests { - use crate::{prelude::*, testing::*, BufferMap, OperationError}; - - use bevy_ecs::prelude::World; + use crate::{prelude::*, testing::*, BufferMap}; - #[derive(Clone)] - struct TestJoinedValue { + #[derive(JoinedValue)] + struct TestJoinedValue { integer: i64, float: f64, string: String, - } - - impl JoinedValue for TestJoinedValue { - type Buffers = TestJoinedValueBuffers; - } - - #[derive(Clone)] - struct TestJoinedValueBuffers { - integer: Buffer, - float: Buffer, - string: Buffer, - } - - impl BufferMapLayout for TestJoinedValueBuffers { - fn buffer_list(&self) -> smallvec::SmallVec<[AnyBuffer; 8]> { - use smallvec::smallvec; - smallvec![ - self.integer.as_any_buffer(), - self.float.as_any_buffer(), - self.string.as_any_buffer(), - ] - } - - fn try_from_buffer_map(buffers: &BufferMap) -> Result { - let mut compatibility = IncompatibleLayout::default(); - let integer = compatibility.require_message_type::("integer", buffers); - let float = compatibility.require_message_type::("float", buffers); - let string = compatibility.require_message_type::("string", buffers); - - let Ok(integer) = integer else { - return Err(compatibility); - }; - let Ok(float) = float else { - return Err(compatibility); - }; - let Ok(string) = string else { - return Err(compatibility); - }; - - Ok(Self { - integer, - float, - string, - }) - } - } - - impl crate::Joined for TestJoinedValueBuffers { - type Item = TestJoinedValue; - - fn pull(&self, session: Entity, world: &mut World) -> Result { - let integer = self.integer.pull(session, world)?; - let float = self.float.pull(session, world)?; - let string = self.string.pull(session, world)?; - - Ok(TestJoinedValue { - integer, - float, - string, - }) - } + generic: T, + #[joined(buffer = AnyBuffer)] + any: AnyMessageBox, } #[test] @@ -391,16 +333,22 @@ mod tests { let buffer_i64 = builder.create_buffer(BufferSettings::default()); let buffer_f64 = builder.create_buffer(BufferSettings::default()); let buffer_string = builder.create_buffer(BufferSettings::default()); + let buffer_generic = builder.create_buffer(BufferSettings::default()); + let buffer_any = builder.create_buffer(BufferSettings::default()); let mut buffers = BufferMap::default(); buffers.insert("integer", buffer_i64); buffers.insert("float", buffer_f64); buffers.insert("string", buffer_string); + buffers.insert("generic", buffer_generic); + buffers.insert("any", buffer_any); scope.input.chain(builder).fork_unzip(( |chain: Chain<_>| chain.connect(buffer_i64.input_slot()), |chain: Chain<_>| chain.connect(buffer_f64.input_slot()), |chain: Chain<_>| chain.connect(buffer_string.input_slot()), + |chain: Chain<_>| chain.connect(buffer_generic.input_slot()), + |chain: Chain<_>| chain.connect(buffer_any.input_slot()), )); builder.try_join(&buffers).unwrap().connect(scope.terminate); @@ -408,15 +356,20 @@ mod tests { let mut promise = context.command(|commands| { commands - .request((5_i64, 3.14_f64, "hello".to_string()), workflow) + .request( + (5_i64, 3.14_f64, "hello".to_string(), "world", 42_i64), + workflow, + ) .take_response() }); context.run_with_conditions(&mut promise, Duration::from_secs(2)); - let value: TestJoinedValue = promise.take().available().unwrap(); + let value: TestJoinedValue<&'static str> = promise.take().available().unwrap(); assert_eq!(value.integer, 5); assert_eq!(value.float, 3.14); assert_eq!(value.string, "hello"); + assert_eq!(value.generic, "world"); + assert_eq!(*value.any.downcast::().unwrap(), 42); assert!(context.no_unhandled_errors()); } @@ -425,32 +378,129 @@ mod tests { let mut context = TestingContext::minimal_plugins(); let workflow = context.spawn_io_workflow(|scope, builder| { - let buffers = TestJoinedValueBuffers { - integer: builder.create_buffer(BufferSettings::default()), - float: builder.create_buffer(BufferSettings::default()), - string: builder.create_buffer(BufferSettings::default()), - }; + let buffer_i64 = builder.create_buffer(BufferSettings::default()); + let buffer_f64 = builder.create_buffer(BufferSettings::default()); + let buffer_string = builder.create_buffer(BufferSettings::default()); + let buffer_generic = builder.create_buffer(BufferSettings::default()); + let buffer_any = builder.create_buffer::(BufferSettings::default()); scope.input.chain(builder).fork_unzip(( - |chain: Chain<_>| chain.connect(buffers.integer.input_slot()), - |chain: Chain<_>| chain.connect(buffers.float.input_slot()), - |chain: Chain<_>| chain.connect(buffers.string.input_slot()), + |chain: Chain<_>| chain.connect(buffer_i64.input_slot()), + |chain: Chain<_>| chain.connect(buffer_f64.input_slot()), + |chain: Chain<_>| chain.connect(buffer_string.input_slot()), + |chain: Chain<_>| chain.connect(buffer_generic.input_slot()), + |chain: Chain<_>| chain.connect(buffer_any.input_slot()), )); + let buffers = TestJoinedValue::select_buffers( + buffer_i64, + buffer_f64, + buffer_string, + buffer_generic, + buffer_any.into(), + ); + builder.join(buffers).connect(scope.terminate); }); let mut promise = context.command(|commands| { commands - .request((5_i64, 3.14_f64, "hello".to_string()), workflow) + .request( + (5_i64, 3.14_f64, "hello".to_string(), "world", 42_i64), + workflow, + ) .take_response() }); context.run_with_conditions(&mut promise, Duration::from_secs(2)); - let value: TestJoinedValue = promise.take().available().unwrap(); + let value: TestJoinedValue<&'static str> = promise.take().available().unwrap(); assert_eq!(value.integer, 5); assert_eq!(value.float, 3.14); assert_eq!(value.string, "hello"); + assert_eq!(value.generic, "world"); + assert_eq!(*value.any.downcast::().unwrap(), 42); + assert!(context.no_unhandled_errors()); + } + + #[derive(Clone, JoinedValue)] + #[joined(buffers_struct_name = FooBuffers)] + struct TestDeriveWithConfig {} + + #[test] + fn test_derive_with_config() { + // a compile test to check that the name of the generated struct is correct + fn _check_buffer_struct_name(_: FooBuffers) {} + } + + struct MultiGenericValue { + t: T, + u: U, + } + + #[derive(JoinedValue)] + #[joined(buffers_struct_name = MultiGenericBuffers)] + struct JoinedMultiGenericValue { + #[joined(buffer = Buffer>)] + a: MultiGenericValue, + b: String, + } + + #[test] + fn test_multi_generic_joined_value() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow( + |scope: Scope<(i32, String), JoinedMultiGenericValue>, builder| { + let multi_generic_buffers = MultiGenericBuffers:: { + a: builder.create_buffer(BufferSettings::default()), + b: builder.create_buffer(BufferSettings::default()), + }; + + let copy = multi_generic_buffers; + + scope + .input + .chain(builder) + .map_block(|(integer, string)| { + ( + MultiGenericValue { + t: integer, + u: string.clone(), + }, + string, + ) + }) + .fork_unzip(( + |a: Chain<_>| a.connect(multi_generic_buffers.a.input_slot()), + |b: Chain<_>| b.connect(multi_generic_buffers.b.input_slot()), + )); + + multi_generic_buffers.join(builder).connect(scope.terminate); + copy.join(builder).connect(scope.terminate); + }, + ); + + let mut promise = context.command(|commands| { + commands + .request((5, "hello".to_string()), workflow) + .take_response() + }); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let value = promise.take().available().unwrap(); + assert_eq!(value.a.t, 5); + assert_eq!(value.a.u, "hello"); + assert_eq!(value.b, "hello"); assert!(context.no_unhandled_errors()); } + + /// We create this struct just to verify that it is able to compile despite + /// NonCopyBuffer not being copyable. + #[derive(JoinedValue)] + #[allow(unused)] + struct JoinedValueForNonCopyBuffer { + #[joined(buffer = NonCopyBuffer, noncopy_buffer)] + _a: String, + _b: u32, + } } diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 181fb726..d470da01 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -1353,78 +1353,15 @@ mod tests { assert!(context.no_unhandled_errors()); } - #[derive(Clone)] + #[derive(Clone, JoinedValue)] + #[joined(buffers_struct_name = TestJoinedValueJsonBuffers)] struct TestJoinedValueJson { integer: i64, float: f64, + #[joined(buffer = JsonBuffer)] json: JsonMessage, } - #[derive(Clone)] - struct TestJoinedValueJsonBuffers { - integer: Buffer, - float: Buffer, - json: JsonBuffer, - } - - impl JoinedValue for TestJoinedValueJson { - type Buffers = TestJoinedValueJsonBuffers; - } - - impl BufferMapLayout for TestJoinedValueJsonBuffers { - fn buffer_list(&self) -> smallvec::SmallVec<[AnyBuffer; 8]> { - use smallvec::smallvec; - smallvec![ - self.integer.as_any_buffer(), - self.float.as_any_buffer(), - self.json.as_any_buffer(), - ] - } - - fn try_from_buffer_map(buffers: &BufferMap) -> Result { - let mut compatibility = IncompatibleLayout::default(); - let integer = compatibility.require_message_type::("integer", buffers); - let float = compatibility.require_message_type::("float", buffers); - let json = compatibility.require_buffer_type::("json", buffers); - - let Ok(integer) = integer else { - return Err(compatibility); - }; - let Ok(float) = float else { - return Err(compatibility); - }; - let Ok(json) = json else { - return Err(compatibility); - }; - - Ok(Self { - integer, - float, - json, - }) - } - } - - impl crate::Joined for TestJoinedValueJsonBuffers { - type Item = TestJoinedValueJson; - - fn pull( - &self, - session: Entity, - world: &mut World, - ) -> Result { - let integer = self.integer.pull(session, world)?; - let float = self.float.pull(session, world)?; - let json = self.json.pull(session, world)?; - - Ok(TestJoinedValueJson { - integer, - float, - json, - }) - } - } - #[test] fn test_try_join_json() { let mut context = TestingContext::minimal_plugins(); @@ -1502,4 +1439,43 @@ mod tests { let expected_json = TestMessage::new(); assert_eq!(deserialized_json, expected_json); } + + #[test] + fn test_select_buffers_json() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + let buffer_integer = builder.create_buffer::(BufferSettings::default()); + let buffer_float = builder.create_buffer::(BufferSettings::default()); + let buffer_json = + JsonBuffer::from(builder.create_buffer::(BufferSettings::default())); + + let buffers = + TestJoinedValueJson::select_buffers(buffer_integer, buffer_float, buffer_json); + + scope.input.chain(builder).fork_unzip(( + |chain: Chain<_>| chain.connect(buffers.integer.input_slot()), + |chain: Chain<_>| chain.connect(buffers.float.input_slot()), + |chain: Chain<_>| { + chain.connect(buffers.json.downcast_for_message().unwrap().input_slot()) + }, + )); + + builder.join(buffers).connect(scope.terminate); + }); + + let mut promise = context.command(|commands| { + commands + .request((5_i64, 3.14_f64, TestMessage::new()), workflow) + .take_response() + }); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let value: TestJoinedValueJson = promise.take().available().unwrap(); + assert_eq!(value.integer, 5); + assert_eq!(value.float, 3.14); + let deserialized_json: TestMessage = serde_json::from_value(value.json).unwrap(); + let expected_json = TestMessage::new(); + assert_eq!(deserialized_json, expected_json); + } } diff --git a/src/lib.rs b/src/lib.rs index 417bd59b..4853bd93 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -148,6 +148,8 @@ pub use trim::*; use bevy_app::prelude::{App, Plugin, Update}; use bevy_ecs::prelude::{Entity, In}; +extern crate self as bevy_impulse; + /// Use `BlockingService` to indicate that your system is a blocking [`Service`]. /// /// A blocking service will have exclusive world access while it runs, which diff --git a/src/testing.rs b/src/testing.rs index 83d21dd9..a5ad4ec1 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -19,7 +19,7 @@ use bevy_app::ScheduleRunnerPlugin; pub use bevy_app::{App, Update}; use bevy_core::{FrameCountPlugin, TaskPoolPlugin, TypeRegistrationPlugin}; pub use bevy_ecs::{ - prelude::{Commands, Component, Entity, In, Local, Query, ResMut, Resource}, + prelude::{Commands, Component, Entity, In, Local, Query, ResMut, Resource, World}, system::{CommandQueue, IntoSystem}, }; use bevy_time::TimePlugin; @@ -32,10 +32,11 @@ pub use std::time::{Duration, Instant}; use smallvec::SmallVec; use crate::{ - flush_impulses, AddContinuousServicesExt, AsyncServiceInput, BlockingMap, BlockingServiceInput, - Builder, ContinuousQuery, ContinuousQueueView, ContinuousService, FlushParameters, - GetBufferedSessionsFn, Promise, RunCommandsOnWorldExt, Scope, Service, SpawnWorkflowExt, - StreamOf, StreamPack, UnhandledErrors, WorkflowSettings, + flush_impulses, Accessed, AddContinuousServicesExt, AnyBuffer, AsyncServiceInput, BlockingMap, + BlockingServiceInput, Buffer, BufferKey, Bufferable, Buffered, Builder, ContinuousQuery, + ContinuousQueueView, ContinuousService, FlushParameters, GetBufferedSessionsFn, Joined, + OperationError, OperationResult, OperationRoster, Promise, RunCommandsOnWorldExt, Scope, + Service, SpawnWorkflowExt, StreamOf, StreamPack, UnhandledErrors, WorkflowSettings, }; pub struct TestingContext { @@ -478,3 +479,104 @@ pub struct TestComponent; pub struct Integer { pub value: i32, } + +/// This is an ordinary buffer newtype whose only purpose is to test the +/// #[joined(noncopy_buffer)] feature. We intentionally do not implement +/// the Copy trait for it. +pub struct NonCopyBuffer { + inner: Buffer, +} + +impl NonCopyBuffer { + pub fn register_downcast() { + let any_interface = AnyBuffer::interface_for::(); + any_interface.register_buffer_downcast( + std::any::TypeId::of::>(), + Box::new(|location| { + Box::new(NonCopyBuffer:: { + inner: Buffer { + location, + _ignore: Default::default(), + }, + }) + }), + ); + } +} + +impl NonCopyBuffer { + pub fn as_any_buffer(&self) -> AnyBuffer { + self.inner.as_any_buffer() + } +} + +impl Clone for NonCopyBuffer { + fn clone(&self) -> Self { + Self { inner: self.inner } + } +} + +impl Bufferable for NonCopyBuffer { + type BufferType = Self; + fn into_buffer(self, _builder: &mut Builder) -> Self::BufferType { + self + } +} + +impl Buffered for NonCopyBuffer { + fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { + self.inner.add_listener(listener, world) + } + + fn as_input(&self) -> smallvec::SmallVec<[Entity; 8]> { + self.inner.as_input() + } + + fn buffered_count(&self, session: Entity, world: &World) -> Result { + self.inner.buffered_count(session, world) + } + + fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { + self.inner.ensure_active_session(session, world) + } + + fn gate_action( + &self, + session: Entity, + action: crate::Gate, + world: &mut World, + roster: &mut OperationRoster, + ) -> OperationResult { + self.inner.gate_action(session, action, world, roster) + } + + fn verify_scope(&self, scope: Entity) { + self.inner.verify_scope(scope); + } +} + +impl Joined for NonCopyBuffer { + type Item = T; + fn pull(&self, session: Entity, world: &mut World) -> Result { + self.inner.pull(session, world) + } +} + +impl Accessed for NonCopyBuffer { + type Key = BufferKey; + fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { + self.inner.add_accessor(accessor, world) + } + + fn create_key(&self, builder: &crate::BufferKeyBuilder) -> Self::Key { + self.inner.create_key(builder) + } + + fn deep_clone_key(key: &Self::Key) -> Self::Key { + key.deep_clone() + } + + fn is_key_in_use(key: &Self::Key) -> bool { + key.is_in_use() + } +} From e94c1704d857d14234150327c8e174f3bf84d369 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 17 Feb 2025 20:22:36 +0800 Subject: [PATCH 30/37] Generalize buffer map keys Signed-off-by: Michael X. Grey --- macros/src/buffer.rs | 4 +- src/buffer.rs | 8 --- src/buffer/any_buffer.rs | 23 +++++-- src/buffer/buffer_map.rs | 126 +++++++++++++++++++++++++------------- src/buffer/json_buffer.rs | 21 ++++--- src/lib.rs | 2 +- src/testing.rs | 14 ++--- 7 files changed, 123 insertions(+), 75 deletions(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index 6ab201dc..9a5100de 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -211,14 +211,14 @@ fn impl_buffer_map_layout( fn buffer_list(&self) -> ::smallvec::SmallVec<[AnyBuffer; 8]> { use smallvec::smallvec; smallvec![#( - self.#field_ident.as_any_buffer(), + ::bevy_impulse::AsAnyBuffer::as_any_buffer(&self.#field_ident), )*] } fn try_from_buffer_map(buffers: &::bevy_impulse::BufferMap) -> Result { let mut compatibility = ::bevy_impulse::IncompatibleLayout::default(); #( - let #field_ident = if let Ok(buffer) = compatibility.require_buffer_type::<#buffer>(#map_key, buffers) { + let #field_ident = if let Ok(buffer) = compatibility.require_buffer_by_literal::<#buffer>(#map_key, buffers) { buffer } else { return Err(compatibility); diff --git a/src/buffer.rs b/src/buffer.rs index 106dd8ed..03cc329b 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -113,14 +113,6 @@ impl Buffer { pub fn location(&self) -> BufferLocation { self.location } - - /// Cast this into an [`AnyBuffer`]. - pub fn as_any_buffer(&self) -> AnyBuffer - where - T: 'static + Send + Sync, - { - self.clone().into() - } } impl Clone for Buffer { diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index b4315fe8..dbc28687 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -121,10 +121,6 @@ impl AnyBuffer { .ok() .map(|x| *x) } - - pub fn as_any_buffer(&self) -> Self { - self.clone().into() - } } impl From> for AnyBuffer { @@ -137,6 +133,25 @@ impl From> for AnyBuffer { } } +/// A trait for turning a buffer into an [`AnyBuffer`]. It is expected that all +/// buffer types implement this trait. +pub trait AsAnyBuffer { + /// Convert this buffer into an [`AnyBuffer`]. + fn as_any_buffer(&self) -> AnyBuffer; +} + +impl AsAnyBuffer for AnyBuffer { + fn as_any_buffer(&self) -> AnyBuffer { + *self + } +} + +impl AsAnyBuffer for Buffer { + fn as_any_buffer(&self) -> AnyBuffer { + (*self).into() + } +} + /// Similar to a [`BufferKey`] except it can be used for any buffer without /// knowing the buffer's message type at compile time. /// diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index c522dbfe..f1357e8b 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -24,27 +24,66 @@ use smallvec::SmallVec; use bevy_ecs::prelude::{Entity, World}; use crate::{ - add_listener_to_source, Accessed, AddOperation, AnyBuffer, AnyBufferKey, AnyMessageBox, Buffer, + add_listener_to_source, Accessed, AddOperation, AnyBuffer, AnyBufferKey, AnyMessageBox, AsAnyBuffer, BufferKeyBuilder, Bufferable, Buffered, Builder, Chain, Gate, GateState, Join, Joined, OperationError, OperationResult, OperationRoster, Output, UnusedTarget, }; pub use bevy_impulse_derive::JoinedValue; -#[derive(Clone, Default)] -pub struct BufferMap { - inner: HashMap, AnyBuffer>, +/// Uniquely identify a buffer within a buffer map, either by name or by an +/// index value. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum BufferIdentifier<'a> { + /// Identify a buffer by name + Name(Cow<'a, str>), + /// Identify a buffer by an index value + Index(usize), } -impl BufferMap { - /// Insert a named buffer into the map. - pub fn insert(&mut self, name: impl Into>, buffer: impl Into) { - self.inner.insert(name.into(), buffer.into()); +impl BufferIdentifier<'static> { + /// Clone a name to use as an identifier. + pub fn clone_name(name: &str) -> Self { + BufferIdentifier::Name(Cow::Owned(name.to_owned())) } - /// Get one of the buffers from the map by its name. - pub fn get(&self, name: &str) -> Option<&AnyBuffer> { - self.inner.get(name) + /// Borrow a string literal name to use as an identifier. + pub fn literal_name(name: &'static str) -> Self { + BufferIdentifier::Name(Cow::Borrowed(name)) + } + + /// Use an index as an identifier. + pub fn index(index: usize) -> Self { + BufferIdentifier::Index(index) + } +} + +impl From<&'static str> for BufferIdentifier<'static> { + fn from(value: &'static str) -> Self { + BufferIdentifier::Name(Cow::Borrowed(value)) + } +} + +pub type BufferMap = HashMap, AnyBuffer>; + +/// Extension trait that makes it more convenient to insert buffers into a [`BufferMap`]. +pub trait AddBufferToMap { + /// Convenience function for inserting items into a [`BufferMap`]. This + /// automatically takes care of converting the types. + fn insert_buffer>, B: AsAnyBuffer>( + &mut self, + identifier: I, + buffer: B, + ); +} + +impl AddBufferToMap for BufferMap { + fn insert_buffer>, B: AsAnyBuffer>( + &mut self, + identifier: I, + buffer: B, + ) { + self.insert(identifier.into(), buffer.as_any_buffer()); } } @@ -54,56 +93,57 @@ impl BufferMap { #[error("the incoming buffer map is incompatible with the layout")] pub struct IncompatibleLayout { /// Names of buffers that were missing from the incoming buffer map. - pub missing_buffers: Vec>, + pub missing_buffers: Vec>, /// Buffers whose expected type did not match the received type. pub incompatible_buffers: Vec, } impl IncompatibleLayout { - /// Check whether a named buffer is compatible with a specific concrete message type. - pub fn require_message_type( + /// Check whether a named buffer is compatible with a specialized buffer type, + /// such as `JsonBuffer`. + pub fn require_buffer_by_name( &mut self, expected_name: &str, buffers: &BufferMap, - ) -> Result, ()> { - if let Some((name, buffer)) = buffers.inner.get_key_value(expected_name) { - if let Some(buffer) = buffer.downcast_for_message::() { + ) -> Result { + let identifier = BufferIdentifier::Name(Cow::Borrowed(expected_name)); + if let Some(buffer) = buffers.get(&identifier) { + if let Some(buffer) = buffer.downcast_buffer::() { return Ok(buffer); } else { self.incompatible_buffers.push(BufferIncompatibility { - name: name.clone(), - expected: std::any::type_name::>(), + identifier: BufferIdentifier::Name(Cow::Owned(expected_name.to_owned())), + expected: std::any::type_name::(), received: buffer.message_type_name(), }); } } else { - self.missing_buffers - .push(Cow::Owned(expected_name.to_owned())); + self.missing_buffers.push(BufferIdentifier::Name(Cow::Owned(expected_name.to_owned()))); } Err(()) } - /// Check whether a named buffer is compatible with a specialized buffer type, - /// such as `JsonBuffer`. - pub fn require_buffer_type( + /// Same as [`Self::require_buffer_by_name`] but more efficient for names + /// given by string literal values. + pub fn require_buffer_by_literal( &mut self, - expected_name: &str, + expected_name: &'static str, buffers: &BufferMap, ) -> Result { - if let Some((name, buffer)) = buffers.inner.get_key_value(expected_name) { + let identifier = BufferIdentifier::Name(Cow::Borrowed(expected_name)); + if let Some(buffer) = buffers.get(&identifier) { if let Some(buffer) = buffer.downcast_buffer::() { return Ok(buffer); } else { self.incompatible_buffers.push(BufferIncompatibility { - name: name.clone(), + identifier, expected: std::any::type_name::(), received: buffer.message_type_name(), }); } } else { - self.missing_buffers - .push(Cow::Owned(expected_name.to_owned())); + self.missing_buffers.push(identifier); } Err(()) @@ -114,7 +154,7 @@ impl IncompatibleLayout { #[derive(Debug, Clone)] pub struct BufferIncompatibility { /// Name of the expected buffer - pub name: Cow<'static, str>, + pub identifier: BufferIdentifier<'static>, /// The type that was expected for this buffer pub expected: &'static str, /// The type that was received for this buffer @@ -246,7 +286,7 @@ pub trait BufferKeyMap: 'static + Send + Sync + Sized + Clone { impl BufferMapLayout for BufferMap { fn buffer_list(&self) -> SmallVec<[AnyBuffer; 8]> { - self.inner.values().cloned().collect() + self.values().cloned().collect() } fn try_from_buffer_map(buffers: &BufferMap) -> Result { Ok(buffers.clone()) @@ -254,11 +294,11 @@ impl BufferMapLayout for BufferMap { } impl Joined for BufferMap { - type Item = HashMap, AnyMessageBox>; + type Item = HashMap, AnyMessageBox>; fn pull(&self, session: Entity, world: &mut World) -> Result { let mut value = HashMap::new(); - for (name, buffer) in &self.inner { + for (name, buffer) in self.iter() { value.insert(name.clone(), buffer.pull(session, world)?); } @@ -266,16 +306,16 @@ impl Joined for BufferMap { } } -impl JoinedValue for HashMap, AnyMessageBox> { +impl JoinedValue for HashMap, AnyMessageBox> { type Buffers = BufferMap; } impl Accessed for BufferMap { - type Key = HashMap, AnyBufferKey>; + type Key = HashMap, AnyBufferKey>; fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key { let mut keys = HashMap::new(); - for (name, buffer) in &self.inner { + for (name, buffer) in self.iter() { let key = AnyBufferKey { tag: builder.make_tag(buffer.id()), interface: buffer.interface, @@ -286,7 +326,7 @@ impl Accessed for BufferMap { } fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { - for buffer in self.inner.values() { + for buffer in self.values() { buffer.add_accessor(accessor, world)?; } Ok(()) @@ -313,7 +353,7 @@ impl Accessed for BufferMap { #[cfg(test)] mod tests { - use crate::{prelude::*, testing::*, BufferMap}; + use crate::{prelude::*, testing::*, BufferMap, AddBufferToMap}; #[derive(JoinedValue)] struct TestJoinedValue { @@ -337,11 +377,11 @@ mod tests { let buffer_any = builder.create_buffer(BufferSettings::default()); let mut buffers = BufferMap::default(); - buffers.insert("integer", buffer_i64); - buffers.insert("float", buffer_f64); - buffers.insert("string", buffer_string); - buffers.insert("generic", buffer_generic); - buffers.insert("any", buffer_any); + buffers.insert_buffer("integer", buffer_i64); + buffers.insert_buffer("float", buffer_f64); + buffers.insert_buffer("string", buffer_string); + buffers.insert_buffer("generic", buffer_generic); + buffers.insert_buffer("any", buffer_any); scope.input.chain(builder).fork_unzip(( |chain: Chain<_>| chain.connect(buffer_i64.input_slot()), diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index d470da01..03789014 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -37,7 +37,7 @@ use smallvec::SmallVec; use crate::{ add_listener_to_source, Accessed, AnyBuffer, AnyBufferAccessInterface, AnyBufferKey, AnyRange, - Buffer, BufferAccessMut, BufferAccessors, BufferError, BufferKey, BufferKeyBuilder, + AsAnyBuffer, Buffer, BufferAccessMut, BufferAccessors, BufferError, BufferKey, BufferKeyBuilder, BufferKeyTag, BufferLocation, BufferStorage, Bufferable, Buffered, Builder, DrainBuffer, Gate, GateState, InspectBuffer, Joined, ManageBuffer, NotifyBufferUpdate, OperationError, OperationResult, OrBroken, @@ -72,11 +72,6 @@ impl JsonBuffer { self.as_any_buffer().downcast_buffer::() } - /// Cast this into an [`AnyBuffer`]. - pub fn as_any_buffer(&self) -> AnyBuffer { - self.clone().into() - } - /// Register the ability to cast into [`JsonBuffer`] and [`JsonBufferKey`] /// for buffers containing messages of type `T`. This only needs to be done /// once in the entire lifespan of a program. @@ -127,6 +122,12 @@ impl From for AnyBuffer { } } +impl AsAnyBuffer for JsonBuffer { + fn as_any_buffer(&self) -> AnyBuffer { + (*self).into() + } +} + /// Similar to a [`BufferKey`] except it can be used for any buffer that supports /// serialization and deserialization without knowing the buffer's specific /// message type at compile time. @@ -1045,7 +1046,7 @@ impl Accessed for JsonBuffer { #[cfg(test)] mod tests { - use crate::{prelude::*, testing::*}; + use crate::{prelude::*, testing::*, AddBufferToMap}; use bevy_ecs::prelude::World; use serde::{Deserialize, Serialize}; @@ -1374,9 +1375,9 @@ mod tests { let buffer_json = builder.create_buffer(BufferSettings::default()); let mut buffers = BufferMap::default(); - buffers.insert("integer", buffer_i64); - buffers.insert("float", buffer_f64); - buffers.insert("json", buffer_json); + buffers.insert_buffer("integer", buffer_i64); + buffers.insert_buffer("float", buffer_f64); + buffers.insert_buffer("json", buffer_json); scope.input.chain(builder).fork_unzip(( |chain: Chain<_>| chain.connect(buffer_i64.input_slot()), diff --git a/src/lib.rs b/src/lib.rs index 4853bd93..4c4fb168 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -339,7 +339,7 @@ pub mod prelude { pub use crate::{ buffer::{ Accessible, AnyBuffer, AnyBufferKey, AnyBufferMut, AnyBufferWorldAccess, AnyMessageBox, - Buffer, BufferAccess, BufferAccessMut, BufferKey, BufferKeyMap, BufferMap, + AsAnyBuffer, Buffer, BufferAccess, BufferAccessMut, BufferKey, BufferKeyMap, BufferMap, BufferMapLayout, BufferSettings, Bufferable, Buffered, IncompatibleLayout, IterBufferable, Joinable, JoinedValue, RetentionPolicy, }, diff --git a/src/testing.rs b/src/testing.rs index a5ad4ec1..8c8a0d75 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -32,7 +32,7 @@ pub use std::time::{Duration, Instant}; use smallvec::SmallVec; use crate::{ - flush_impulses, Accessed, AddContinuousServicesExt, AnyBuffer, AsyncServiceInput, BlockingMap, + flush_impulses, Accessed, AddContinuousServicesExt, AnyBuffer, AsAnyBuffer, AsyncServiceInput, BlockingMap, BlockingServiceInput, Buffer, BufferKey, Bufferable, Buffered, Builder, ContinuousQuery, ContinuousQueueView, ContinuousService, FlushParameters, GetBufferedSessionsFn, Joined, OperationError, OperationResult, OperationRoster, Promise, RunCommandsOnWorldExt, Scope, @@ -504,18 +504,18 @@ impl NonCopyBuffer { } } -impl NonCopyBuffer { - pub fn as_any_buffer(&self) -> AnyBuffer { - self.inner.as_any_buffer() - } -} - impl Clone for NonCopyBuffer { fn clone(&self) -> Self { Self { inner: self.inner } } } +impl AsAnyBuffer for NonCopyBuffer { + fn as_any_buffer(&self) -> AnyBuffer { + self.inner.as_any_buffer() + } +} + impl Bufferable for NonCopyBuffer { type BufferType = Self; fn into_buffer(self, _builder: &mut Builder) -> Self::BufferType { From 6e19af7403a08e0071a25d33b837e0683e77ca4e Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 17 Feb 2025 22:32:52 +0800 Subject: [PATCH 31/37] Support for joining Vec and json values Signed-off-by: Michael X. Grey --- macros/src/buffer.rs | 27 +++++---- src/buffer/buffer_map.rs | 120 ++++++++++++++++++++++++++++++++------ src/buffer/bufferable.rs | 7 +++ src/buffer/buffered.rs | 97 ++++++++++++++++++++++++++++++ src/buffer/json_buffer.rs | 99 +++++++++++++++++++++++++++++-- src/testing.rs | 11 ++-- 6 files changed, 323 insertions(+), 38 deletions(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index 9a5100de..c25f9a5e 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -208,19 +208,17 @@ fn impl_buffer_map_layout( Ok(quote! { impl #impl_generics ::bevy_impulse::BufferMapLayout for #struct_ident #ty_generics #where_clause { - fn buffer_list(&self) -> ::smallvec::SmallVec<[AnyBuffer; 8]> { - use smallvec::smallvec; - smallvec![#( - ::bevy_impulse::AsAnyBuffer::as_any_buffer(&self.#field_ident), - )*] - } - fn try_from_buffer_map(buffers: &::bevy_impulse::BufferMap) -> Result { let mut compatibility = ::bevy_impulse::IncompatibleLayout::default(); #( - let #field_ident = if let Ok(buffer) = compatibility.require_buffer_by_literal::<#buffer>(#map_key, buffers) { - buffer - } else { + let #field_ident = compatibility.require_buffer_by_literal::<#buffer>(#map_key, buffers); + )* + + // Unwrap the Ok after inspecting every field so that the + // IncompatibleLayout error can include all information about + // which fields were incompatible. + #( + let Ok(#field_ident) = #field_ident else { return Err(compatibility); }; )* @@ -232,6 +230,15 @@ fn impl_buffer_map_layout( }) } } + + impl #impl_generics ::bevy_impulse::BufferMapStruct for #struct_ident #ty_generics #where_clause { + fn buffer_list(&self) -> ::smallvec::SmallVec<[AnyBuffer; 8]> { + use smallvec::smallvec; + smallvec![#( + ::bevy_impulse::AsAnyBuffer::as_any_buffer(&self.#field_ident), + )*] + } + } } .into()) } diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index f1357e8b..0dcdd123 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -24,9 +24,9 @@ use smallvec::SmallVec; use bevy_ecs::prelude::{Entity, World}; use crate::{ - add_listener_to_source, Accessed, AddOperation, AnyBuffer, AnyBufferKey, AnyMessageBox, AsAnyBuffer, - BufferKeyBuilder, Bufferable, Buffered, Builder, Chain, Gate, GateState, Join, Joined, - OperationError, OperationResult, OperationRoster, Output, UnusedTarget, + add_listener_to_source, Accessed, AddOperation, AnyBuffer, AnyBufferKey, AnyMessageBox, + AsAnyBuffer, Buffer, BufferKeyBuilder, Bufferable, Buffered, Builder, Chain, Gate, GateState, + Join, Joined, OperationError, OperationResult, OperationRoster, Output, UnusedTarget, }; pub use bevy_impulse_derive::JoinedValue; @@ -92,15 +92,32 @@ impl AddBufferToMap for BufferMap { #[derive(ThisError, Debug, Clone, Default)] #[error("the incoming buffer map is incompatible with the layout")] pub struct IncompatibleLayout { - /// Names of buffers that were missing from the incoming buffer map. + /// Identities of buffers that were missing from the incoming buffer map. pub missing_buffers: Vec>, + /// Identities of buffers in the incoming buffer map which cannot exist in + /// the target layout. + pub forbidden_buffers: Vec>, /// Buffers whose expected type did not match the received type. pub incompatible_buffers: Vec, } impl IncompatibleLayout { - /// Check whether a named buffer is compatible with a specialized buffer type, - /// such as `JsonBuffer`. + /// Convert this into an error if it has any contents inside. + pub fn as_result(self) -> Result<(), Self> { + if !self.missing_buffers.is_empty() { + return Err(self); + } + + if !self.incompatible_buffers.is_empty() { + return Err(self); + } + + Ok(()) + } + + /// Same as [`Self::require_buffer_by_literal`], but can be used with + /// temporary borrows of a string slice. The string slice will be cloned if + /// an error message needs to be produced. pub fn require_buffer_by_name( &mut self, expected_name: &str, @@ -118,20 +135,38 @@ impl IncompatibleLayout { }); } } else { - self.missing_buffers.push(BufferIdentifier::Name(Cow::Owned(expected_name.to_owned()))); + self.missing_buffers + .push(BufferIdentifier::Name(Cow::Owned(expected_name.to_owned()))); } Err(()) } - /// Same as [`Self::require_buffer_by_name`] but more efficient for names - /// given by string literal values. + /// Check whether a named buffer is compatible with the required buffer type. pub fn require_buffer_by_literal( &mut self, expected_name: &'static str, buffers: &BufferMap, ) -> Result { - let identifier = BufferIdentifier::Name(Cow::Borrowed(expected_name)); + self.require_buffer::(BufferIdentifier::literal_name(expected_name), buffers) + } + + /// Check whether an indexed buffer is compatible with the required buffer type. + pub fn require_buffer_by_index( + &mut self, + expected_index: usize, + buffers: &BufferMap, + ) -> Result { + self.require_buffer::(BufferIdentifier::Index(expected_index), buffers) + } + + /// Check whether the buffer associated with the identifier is compatible with + /// the required buffer type. + pub fn require_buffer( + &mut self, + identifier: BufferIdentifier<'static>, + buffers: &BufferMap, + ) -> Result { if let Some(buffer) = buffers.get(&identifier) { if let Some(buffer) = buffer.downcast_buffer::() { return Ok(buffer); @@ -167,14 +202,18 @@ pub struct BufferIncompatibility { /// `#[derive(JoinedValue)]` on a struct that you want a join operation to /// produce. pub trait BufferMapLayout: Sized + Clone + 'static + Send + Sync { - /// Produce a list of the buffers that exist in this layout. - fn buffer_list(&self) -> SmallVec<[AnyBuffer; 8]>; - /// Try to convert a generic [`BufferMap`] into this specific layout. fn try_from_buffer_map(buffers: &BufferMap) -> Result; } -impl Bufferable for T { +/// This trait helps auto-generated buffer map structs to implement the Buffered +/// trait. +pub trait BufferMapStruct: Sized + Clone + 'static + Send + Sync { + /// Produce a list of the buffers that exist in this layout. + fn buffer_list(&self) -> SmallVec<[AnyBuffer; 8]>; +} + +impl Bufferable for T { type BufferType = Self; fn into_buffer(self, _: &mut Builder) -> Self::BufferType { @@ -182,7 +221,7 @@ impl Bufferable for T { } } -impl Buffered for T { +impl Buffered for T { fn verify_scope(&self, scope: Entity) { for buffer in self.buffer_list() { assert_eq!(buffer.scope(), scope); @@ -285,14 +324,17 @@ pub trait BufferKeyMap: 'static + Send + Sync + Sized + Clone { } impl BufferMapLayout for BufferMap { - fn buffer_list(&self) -> SmallVec<[AnyBuffer; 8]> { - self.values().cloned().collect() - } fn try_from_buffer_map(buffers: &BufferMap) -> Result { Ok(buffers.clone()) } } +impl BufferMapStruct for BufferMap { + fn buffer_list(&self) -> SmallVec<[AnyBuffer; 8]> { + self.values().cloned().collect() + } +} + impl Joined for BufferMap { type Item = HashMap, AnyMessageBox>; @@ -351,9 +393,49 @@ impl Accessed for BufferMap { } } +impl JoinedValue for Vec { + type Buffers = Vec>; +} + +impl BufferMapLayout for Vec { + fn try_from_buffer_map(buffers: &BufferMap) -> Result { + let mut downcast_buffers = Vec::new(); + let mut compatibility = IncompatibleLayout::default(); + for i in 0..buffers.len() { + if let Ok(downcast) = compatibility.require_buffer_by_index::(i, buffers) { + downcast_buffers.push(downcast); + } + } + + compatibility.as_result()?; + Ok(downcast_buffers) + } +} + +impl JoinedValue for SmallVec<[T; N]> { + type Buffers = SmallVec<[Buffer; N]>; +} + +impl BufferMapLayout + for SmallVec<[B; N]> +{ + fn try_from_buffer_map(buffers: &BufferMap) -> Result { + let mut downcast_buffers = SmallVec::new(); + let mut compatibility = IncompatibleLayout::default(); + for i in 0..buffers.len() { + if let Ok(downcast) = compatibility.require_buffer_by_index::(i, buffers) { + downcast_buffers.push(downcast); + } + } + + compatibility.as_result()?; + Ok(downcast_buffers) + } +} + #[cfg(test)] mod tests { - use crate::{prelude::*, testing::*, BufferMap, AddBufferToMap}; + use crate::{prelude::*, testing::*, AddBufferToMap, BufferMap}; #[derive(JoinedValue)] struct TestJoinedValue { diff --git a/src/buffer/bufferable.rs b/src/buffer/bufferable.rs index a607f835..9b2a1549 100644 --- a/src/buffer/bufferable.rs +++ b/src/buffer/bufferable.rs @@ -152,6 +152,13 @@ impl Bufferable for [T; N] { } } +impl Bufferable for Vec { + type BufferType = Vec; + fn into_buffer(self, builder: &mut Builder) -> Self::BufferType { + self.into_iter().map(|b| b.into_buffer(builder)).collect() + } +} + pub trait IterBufferable { type BufferElement: Buffered + Joined; diff --git a/src/buffer/buffered.rs b/src/buffer/buffered.rs index dd6bd71d..3c357707 100644 --- a/src/buffer/buffered.rs +++ b/src/buffer/buffered.rs @@ -662,6 +662,103 @@ impl Accessed for SmallVec<[T; N]> { } } +impl Buffered for Vec { + fn verify_scope(&self, scope: Entity) { + for buffer in self { + buffer.verify_scope(scope); + } + } + + fn buffered_count(&self, session: Entity, world: &World) -> Result { + let mut min_count = None; + for buffer in self { + let count = buffer.buffered_count(session, world)?; + if !min_count.is_some_and(|min| min < count) { + min_count = Some(count); + } + } + + Ok(min_count.unwrap_or(0)) + } + + fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { + for buffer in self { + buffer.add_listener(listener, world)?; + } + Ok(()) + } + + fn gate_action( + &self, + session: Entity, + action: Gate, + world: &mut World, + roster: &mut OperationRoster, + ) -> OperationResult { + for buffer in self { + buffer.gate_action(session, action, world, roster)?; + } + Ok(()) + } + + fn as_input(&self) -> SmallVec<[Entity; 8]> { + self.iter().flat_map(|buffer| buffer.as_input()).collect() + } + + fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult { + for buffer in self { + buffer.ensure_active_session(session, world)?; + } + + Ok(()) + } +} + +impl Joined for Vec { + type Item = Vec; + fn pull(&self, session: Entity, world: &mut World) -> Result { + self.iter() + .map(|buffer| buffer.pull(session, world)) + .collect() + } +} + +impl Accessed for Vec { + type Key = Vec; + fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { + for buffer in self { + buffer.add_accessor(accessor, world)?; + } + Ok(()) + } + + fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key { + let mut keys = Vec::new(); + for buffer in self { + keys.push(buffer.create_key(builder)); + } + keys + } + + fn deep_clone_key(key: &Self::Key) -> Self::Key { + let mut keys = Vec::new(); + for k in key { + keys.push(B::deep_clone_key(k)); + } + keys + } + + fn is_key_in_use(key: &Self::Key) -> bool { + for k in key { + if B::is_key_in_use(k) { + return true; + } + } + + false + } +} + pub(crate) fn add_listener_to_source( source: Entity, listener: Entity, diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 03789014..4d6efbf3 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -37,10 +37,11 @@ use smallvec::SmallVec; use crate::{ add_listener_to_source, Accessed, AnyBuffer, AnyBufferAccessInterface, AnyBufferKey, AnyRange, - AsAnyBuffer, Buffer, BufferAccessMut, BufferAccessors, BufferError, BufferKey, BufferKeyBuilder, - BufferKeyTag, BufferLocation, BufferStorage, Bufferable, Buffered, Builder, DrainBuffer, Gate, - GateState, InspectBuffer, Joined, ManageBuffer, NotifyBufferUpdate, OperationError, - OperationResult, OrBroken, + AsAnyBuffer, Buffer, BufferAccessMut, BufferAccessors, BufferError, BufferIdentifier, + BufferKey, BufferKeyBuilder, BufferKeyTag, BufferLocation, BufferMap, BufferMapLayout, + BufferMapStruct, BufferStorage, Bufferable, Buffered, Builder, DrainBuffer, Gate, GateState, + IncompatibleLayout, InspectBuffer, Joined, JoinedValue, ManageBuffer, NotifyBufferUpdate, + OperationError, OperationResult, OrBroken, }; /// A [`Buffer`] whose message type has been anonymized, but which is known to @@ -1044,6 +1045,51 @@ impl Accessed for JsonBuffer { } } +impl JoinedValue for serde_json::Map { + type Buffers = HashMap; +} + +impl BufferMapLayout for HashMap { + fn try_from_buffer_map(buffers: &BufferMap) -> Result { + let mut downcast_buffers = HashMap::new(); + let mut compatibility = IncompatibleLayout::default(); + for name in buffers.keys() { + match name { + BufferIdentifier::Name(name) => { + if let Ok(downcast) = + compatibility.require_buffer_by_name::(&name, buffers) + { + downcast_buffers.insert(name.clone().into_owned(), downcast); + } + } + BufferIdentifier::Index(index) => { + compatibility + .forbidden_buffers + .push(BufferIdentifier::Index(*index)); + } + } + } + + compatibility.as_result()?; + Ok(downcast_buffers) + } +} + +impl BufferMapStruct for HashMap { + fn buffer_list(&self) -> SmallVec<[AnyBuffer; 8]> { + self.values().map(|b| b.as_any_buffer()).collect() + } +} + +impl Joined for HashMap { + type Item = serde_json::Map; + fn pull(&self, session: Entity, world: &mut World) -> Result { + self.iter() + .map(|(key, value)| value.pull(session, world).map(|v| (key.clone(), v))) + .collect() + } +} + #[cfg(test)] mod tests { use crate::{prelude::*, testing::*, AddBufferToMap}; @@ -1479,4 +1525,49 @@ mod tests { let expected_json = TestMessage::new(); assert_eq!(deserialized_json, expected_json); } + + #[test] + fn test_join_json_buffer_vec() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + let buffer_u32 = builder.create_buffer::(BufferSettings::default()); + let buffer_i32 = builder.create_buffer::(BufferSettings::default()); + let buffer_string = builder.create_buffer::(BufferSettings::default()); + let buffer_msg = builder.create_buffer::(BufferSettings::default()); + let buffers: Vec = vec![ + buffer_i32.into(), + buffer_u32.into(), + buffer_string.into(), + buffer_msg.into(), + ]; + + scope + .input + .chain(builder) + .map_block(|msg: TestMessage| (msg.v_u32, msg.v_i32, msg.v_string.clone(), msg)) + .fork_unzip(( + |chain: Chain<_>| chain.connect(buffer_u32.input_slot()), + |chain: Chain<_>| chain.connect(buffer_i32.input_slot()), + |chain: Chain<_>| chain.connect(buffer_string.input_slot()), + |chain: Chain<_>| chain.connect(buffer_msg.input_slot()), + )); + + builder.join(buffers).connect(scope.terminate); + }); + + let mut promise = context.command(|commands| { + commands + .request(TestMessage::new(), workflow) + .take_response() + }); + + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let values = promise.take().available().unwrap(); + assert_eq!(values.len(), 4); + assert_eq!(values[0], serde_json::Value::Number(1.into())); + assert_eq!(values[1], serde_json::Value::Number(2.into())); + assert_eq!(values[2], serde_json::Value::String("hello".to_string())); + assert_eq!(values[3], serde_json::to_value(TestMessage::new()).unwrap()); + } } diff --git a/src/testing.rs b/src/testing.rs index 8c8a0d75..2db61ecc 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -32,11 +32,12 @@ pub use std::time::{Duration, Instant}; use smallvec::SmallVec; use crate::{ - flush_impulses, Accessed, AddContinuousServicesExt, AnyBuffer, AsAnyBuffer, AsyncServiceInput, BlockingMap, - BlockingServiceInput, Buffer, BufferKey, Bufferable, Buffered, Builder, ContinuousQuery, - ContinuousQueueView, ContinuousService, FlushParameters, GetBufferedSessionsFn, Joined, - OperationError, OperationResult, OperationRoster, Promise, RunCommandsOnWorldExt, Scope, - Service, SpawnWorkflowExt, StreamOf, StreamPack, UnhandledErrors, WorkflowSettings, + flush_impulses, Accessed, AddContinuousServicesExt, AnyBuffer, AsAnyBuffer, AsyncServiceInput, + BlockingMap, BlockingServiceInput, Buffer, BufferKey, Bufferable, Buffered, Builder, + ContinuousQuery, ContinuousQueueView, ContinuousService, FlushParameters, + GetBufferedSessionsFn, Joined, OperationError, OperationResult, OperationRoster, Promise, + RunCommandsOnWorldExt, Scope, Service, SpawnWorkflowExt, StreamOf, StreamPack, UnhandledErrors, + WorkflowSettings, }; pub struct TestingContext { From 4e35ec837a281144c66f178fdb3b5bf474e5ed58 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 18 Feb 2025 06:53:13 +0000 Subject: [PATCH 32/37] Working towards BufferKeyMap implementation Signed-off-by: Michael X. Grey --- macros/src/buffer.rs | 4 +- src/buffer.rs | 38 ++++-- src/buffer/any_buffer.rs | 21 +++- src/buffer/buffer_access_lifecycle.rs | 28 ++++- src/buffer/buffer_key_builder.rs | 14 +-- src/buffer/buffer_map.rs | 159 ++++++++++++++++++++++---- src/buffer/buffered.rs | 27 ++++- src/buffer/json_buffer.rs | 25 ++-- src/builder.rs | 46 +++++--- src/testing.rs | 4 +- 10 files changed, 278 insertions(+), 88 deletions(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index c25f9a5e..188c4b0b 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -25,7 +25,7 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result Result { pub(crate) location: BufferLocation, pub(crate) _ignore: std::marker::PhantomData, } +// +impl Copy for CloneFromBuffer {} + impl CloneFromBuffer { /// Get the entity ID of the buffer. pub fn id(&self) -> Entity { @@ -157,6 +161,15 @@ impl CloneFromBuffer { } } +impl From> for Buffer { + fn from(value: CloneFromBuffer) -> Self { + Buffer { + location: value.location, + _ignore: Default::default(), + } + } +} + /// Settings to describe the behavior of a buffer. #[derive(Default, Clone, Copy)] pub struct BufferSettings { @@ -254,20 +267,23 @@ impl BufferKey { pub fn tag(&self) -> &BufferKeyTag { &self.tag } +} + +impl BufferKeyLifecycle for BufferKey { + type TargetBuffer = Buffer; + + fn create_key(buffer: &Self::TargetBuffer, builder: &BufferKeyBuilder) -> Self { + BufferKey { + tag: builder.make_tag(buffer.id()), + _ignore: Default::default(), + } + } - pub(crate) fn is_in_use(&self) -> bool { + fn is_in_use(&self) -> bool { self.tag.is_in_use() } - // We do a deep clone of the key when distributing it to decouple the - // lifecycle of the keys that we send out from the key that's held by the - // accessor node. - // - // The key instance held by the accessor node will never be dropped until - // the session is cleaned up, so the keys that we send out into the workflow - // need to have their own independent lifecycles or else we won't detect - // when the workflow has dropped them. - pub(crate) fn deep_clone(&self) -> Self { + fn deep_clone(&self) -> Self { Self { tag: self.tag.deep_clone(), _ignore: Default::default(), diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index dbc28687..feb04e0f 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -35,9 +35,9 @@ use smallvec::SmallVec; use crate::{ add_listener_to_source, Accessed, Buffer, BufferAccessMut, BufferAccessors, BufferError, - BufferKey, BufferKeyTag, BufferLocation, BufferStorage, Bufferable, Buffered, Builder, - DrainBuffer, Gate, GateState, InspectBuffer, Joined, ManageBuffer, NotifyBufferUpdate, - OperationError, OperationResult, OperationRoster, OrBroken, + BufferKey, BufferKeyBuilder, BufferKeyLifecycle, BufferKeyTag, BufferLocation, BufferStorage, + Bufferable, Buffered, Builder, DrainBuffer, Gate, GateState, InspectBuffer, Joined, + ManageBuffer, NotifyBufferUpdate, OperationError, OperationResult, OperationRoster, OrBroken, }; /// A [`Buffer`] whose message type has been anonymized. Joining with this buffer @@ -198,12 +198,23 @@ impl AnyBufferKey { pub fn session(&self) -> Entity { self.tag.session } +} + +impl BufferKeyLifecycle for AnyBufferKey { + type TargetBuffer = AnyBuffer; + + fn create_key(buffer: &AnyBuffer, builder: &BufferKeyBuilder) -> Self { + AnyBufferKey { + tag: builder.make_tag(buffer.id()), + interface: buffer.interface, + } + } - pub(crate) fn is_in_use(&self) -> bool { + fn is_in_use(&self) -> bool { self.tag.is_in_use() } - pub(crate) fn deep_clone(&self) -> Self { + fn deep_clone(&self) -> Self { Self { tag: self.tag.deep_clone(), interface: self.interface, diff --git a/src/buffer/buffer_access_lifecycle.rs b/src/buffer/buffer_access_lifecycle.rs index 879a26ec..b7596fac 100644 --- a/src/buffer/buffer_access_lifecycle.rs +++ b/src/buffer/buffer_access_lifecycle.rs @@ -21,7 +21,7 @@ use tokio::sync::mpsc::UnboundedSender as TokioSender; use std::sync::Arc; -use crate::{emit_disposal, ChannelItem, Disposal, OperationRoster}; +use crate::{emit_disposal, BufferKeyBuilder, ChannelItem, Disposal, OperationRoster}; /// This is used as a field inside of [`crate::BufferKey`] which keeps track of /// when a key that was sent out into the world gets fully dropped from use. We @@ -87,3 +87,29 @@ impl Drop for BufferAccessLifecycle { } } } + +/// This trait is implemented by [`crate::BufferKey`]-like structs so their +/// lifecycles can be managed. +pub trait BufferKeyLifecycle { + /// What kind of buffer this key can unlock. + type TargetBuffer; + + /// Create a new key of this type. + fn create_key(buffer: &Self::TargetBuffer, builder: &BufferKeyBuilder) -> Self; + + /// Check if the key is currently in use. + fn is_in_use(&self) -> bool; + + /// Create a deep clone of the key. The usage tracking of the clone will + /// be unrelated to the usage tracking of the original. + /// + /// We do a deep clone of the key when distributing it to decouple the + /// lifecycle of the keys that we send out from the key that's held by the + /// accessor node. + // + /// The key instance held by the accessor node will never be dropped until + /// the session is cleaned up, so the keys that we send out into the workflow + /// need to have their own independent lifecycles or else we won't detect + /// when the workflow has dropped them. + fn deep_clone(&self) -> Self; +} diff --git a/src/buffer/buffer_key_builder.rs b/src/buffer/buffer_key_builder.rs index de6f7f13..e1866e2e 100644 --- a/src/buffer/buffer_key_builder.rs +++ b/src/buffer/buffer_key_builder.rs @@ -19,7 +19,7 @@ use bevy_ecs::prelude::Entity; use std::sync::Arc; -use crate::{BufferAccessLifecycle, BufferKey, BufferKeyTag, ChannelSender}; +use crate::{BufferAccessLifecycle, BufferKeyTag, ChannelSender}; pub struct BufferKeyBuilder { scope: Entity, @@ -29,16 +29,8 @@ pub struct BufferKeyBuilder { } impl BufferKeyBuilder { - pub(crate) fn build(&self, buffer: Entity) -> BufferKey { - BufferKey { - tag: self.make_tag(buffer), - _ignore: Default::default(), - } - } - - // TODO(@mxgrey): Consider refactoring all the buffer key structs to use a - // single inner struct like BufferKeyComponents - pub(crate) fn make_tag(&self, buffer: Entity) -> BufferKeyTag { + /// Make a [`BufferKeyTag`] that can be given to a [`crate::BufferKey`]-like struct. + pub fn make_tag(&self, buffer: Entity) -> BufferKeyTag { BufferKeyTag { buffer, session: self.session, diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 0dcdd123..5eef2cb3 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -24,9 +24,9 @@ use smallvec::SmallVec; use bevy_ecs::prelude::{Entity, World}; use crate::{ - add_listener_to_source, Accessed, AddOperation, AnyBuffer, AnyBufferKey, AnyMessageBox, - AsAnyBuffer, Buffer, BufferKeyBuilder, Bufferable, Buffered, Builder, Chain, Gate, GateState, - Join, Joined, OperationError, OperationResult, OperationRoster, Output, UnusedTarget, + add_listener_to_source, Accessed, AnyBuffer, AnyBufferKey, AnyMessageBox, AsAnyBuffer, Buffer, + BufferKeyBuilder, BufferKeyLifecycle, Bufferable, Buffered, Builder, Chain, Gate, GateState, + Joined, Node, OperationError, OperationResult, OperationRoster, }; pub use bevy_impulse_derive::JoinedValue; @@ -209,7 +209,8 @@ pub trait BufferMapLayout: Sized + Clone + 'static + Send + Sync { /// This trait helps auto-generated buffer map structs to implement the Buffered /// trait. pub trait BufferMapStruct: Sized + Clone + 'static + Send + Sync { - /// Produce a list of the buffers that exist in this layout. + /// Produce a list of the buffers that exist in this layout. Implementing + /// this function alone is sufficient to implement the entire [`Buffered`] trait. fn buffer_list(&self) -> SmallVec<[AnyBuffer; 8]>; } @@ -289,38 +290,35 @@ pub trait JoinedValue: 'static + Send + Sync + Sized { /// associated type must match the [`JoinedValue`] type. type Buffers: 'static + BufferMapLayout + Joined + Send + Sync; - /// Used by [`Self::try_join_from`] - fn join_from<'w, 's, 'a, 'b>( - buffers: Self::Buffers, - builder: &'b mut Builder<'w, 's, 'a>, - ) -> Chain<'w, 's, 'a, 'b, Self> { - let scope = builder.scope(); - buffers.verify_scope(scope); - - let join = builder.commands.spawn(()).id(); - let target = builder.commands.spawn(UnusedTarget).id(); - builder.commands.add(AddOperation::new( - Some(scope), - join, - Join::new(buffers, target), - )); - - Output::new(scope, target).chain(builder) - } - /// Used by [`Builder::try_join`] fn try_join_from<'w, 's, 'a, 'b>( buffers: &BufferMap, builder: &'b mut Builder<'w, 's, 'a>, ) -> Result, IncompatibleLayout> { let buffers: Self::Buffers = Self::Buffers::try_from_buffer_map(buffers)?; - Ok(Self::join_from(buffers, builder)) + Ok(buffers.join(builder)) } } /// Trait to describe a set of buffer keys. pub trait BufferKeyMap: 'static + Send + Sync + Sized + Clone { type Buffers: 'static + BufferMapLayout + Accessed + Send + Sync; + + fn try_listen_from<'w, 's, 'a, 'b>( + buffers: &BufferMap, + builder: &'b mut Builder<'w, 's, 'a>, + ) -> Result, IncompatibleLayout> { + let buffers: Self::Buffers = Self::Buffers::try_from_buffer_map(buffers)?; + Ok(buffers.listen(builder)) + } + + fn try_buffer_access( + buffers: &BufferMap, + builder: &mut Builder, + ) -> Result, IncompatibleLayout> { + let buffers: Self::Buffers = Self::Buffers::try_from_buffer_map(buffers)?; + Ok(buffers.access(builder)) + } } impl BufferMapLayout for BufferMap { @@ -435,7 +433,7 @@ impl BufferMapLa #[cfg(test)] mod tests { - use crate::{prelude::*, testing::*, AddBufferToMap, BufferMap}; + use crate::{prelude::*, testing::*, Accessed, AddBufferToMap, BufferMap}; #[derive(JoinedValue)] struct TestJoinedValue { @@ -625,4 +623,115 @@ mod tests { _a: String, _b: u32, } + + #[derive(Clone)] + struct TestKeys { + integer: BufferKey, + float: BufferKey, + string: BufferKey, + generic: BufferKey, + any: AnyBufferKey, + } + + impl BufferKeyMap for TestKeys { + type Buffers = TestKeysBuffers; + } + + #[derive(Clone)] + struct TestKeysBuffers { + integer: as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer, + float: as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer, + string: as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer, + generic: as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer, + any: ::TargetBuffer, + } + + impl BufferMapLayout for TestKeysBuffers { + fn try_from_buffer_map(buffers: &BufferMap) -> Result { + let mut compatibility = ::bevy_impulse::IncompatibleLayout::default(); + let integer = compatibility.require_buffer_by_literal::< as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer>("integer", buffers); + let float = compatibility.require_buffer_by_literal::< as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer>("float", buffers); + let string = compatibility.require_buffer_by_literal::< as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer>("string", buffers); + let generic = compatibility.require_buffer_by_literal::< as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer>("generic", buffers); + let any = compatibility.require_buffer_by_literal::<::TargetBuffer>("any", buffers); + + let Ok(integer) = integer else { + return Err(compatibility); + }; + let Ok(float) = float else { + return Err(compatibility); + }; + let Ok(string) = string else { + return Err(compatibility); + }; + let Ok(generic) = generic else { + return Err(compatibility); + }; + let Ok(any) = any else { + return Err(compatibility); + }; + + Ok(Self { + integer, + float, + string, + generic, + any, + }) + } + } + + impl ::bevy_impulse::BufferMapStruct for TestKeysBuffers { + fn buffer_list(&self) -> smallvec::SmallVec<[AnyBuffer; 8]> { + smallvec::smallvec![ + ::bevy_impulse::AsAnyBuffer::as_any_buffer(&self.integer), + ::bevy_impulse::AsAnyBuffer::as_any_buffer(&self.float), + ::bevy_impulse::AsAnyBuffer::as_any_buffer(&self.string), + ::bevy_impulse::AsAnyBuffer::as_any_buffer(&self.generic), + ::bevy_impulse::AsAnyBuffer::as_any_buffer(&self.any), + ] + } + } + + impl Accessed for TestKeysBuffers { + type Key = TestKeys; + + fn add_accessor(&self, accessor: Entity, world: &mut World) -> crate::OperationResult { + ::bevy_impulse::Accessed::add_accessor(&self.integer, accessor, world)?; + ::bevy_impulse::Accessed::add_accessor(&self.float, accessor, world)?; + ::bevy_impulse::Accessed::add_accessor(&self.string, accessor, world)?; + ::bevy_impulse::Accessed::add_accessor(&self.generic, accessor, world)?; + ::bevy_impulse::Accessed::add_accessor(&self.any, accessor, world)?; + Ok(()) + } + + fn create_key(&self, builder: &crate::BufferKeyBuilder) -> Self::Key { + TestKeys { + integer: ::bevy_impulse::BufferKeyLifecycle::create_key(&self.integer, builder), + float: ::bevy_impulse::BufferKeyLifecycle::create_key(&self.float, builder), + string: ::bevy_impulse::BufferKeyLifecycle::create_key(&self.string, builder), + generic: ::bevy_impulse::BufferKeyLifecycle::create_key(&self.generic, builder), + any: ::bevy_impulse::BufferKeyLifecycle::create_key(&self.any, builder), + } + } + + fn deep_clone_key(key: &Self::Key) -> Self::Key { + TestKeys { + integer: ::bevy_impulse::BufferKeyLifecycle::deep_clone(&key.integer), + float: ::bevy_impulse::BufferKeyLifecycle::deep_clone(&key.float), + string: ::bevy_impulse::BufferKeyLifecycle::deep_clone(&key.string), + generic: ::bevy_impulse::BufferKeyLifecycle::deep_clone(&key.generic), + any: ::bevy_impulse::BufferKeyLifecycle::deep_clone(&key.any), + } + } + + fn is_key_in_use(key: &Self::Key) -> bool { + false + || ::bevy_impulse::BufferKeyLifecycle::is_in_use(&key.integer) + || ::bevy_impulse::BufferKeyLifecycle::is_in_use(&key.float) + || ::bevy_impulse::BufferKeyLifecycle::is_in_use(&key.string) + || ::bevy_impulse::BufferKeyLifecycle::is_in_use(&key.generic) + || ::bevy_impulse::BufferKeyLifecycle::is_in_use(&key.any) + } + } } diff --git a/src/buffer/buffered.rs b/src/buffer/buffered.rs index 3c357707..8f6912d0 100644 --- a/src/buffer/buffered.rs +++ b/src/buffer/buffered.rs @@ -23,9 +23,10 @@ use smallvec::SmallVec; use crate::{ AddOperation, BeginCleanupWorkflow, Buffer, BufferAccessors, BufferKey, BufferKeyBuilder, - BufferStorage, Builder, Chain, CleanupWorkflowConditions, CloneFromBuffer, ForkTargetStorage, - Gate, GateState, InspectBuffer, Join, Listen, ManageBuffer, OperationError, OperationResult, - OperationRoster, OrBroken, Output, Scope, ScopeSettings, SingleInputStorage, UnusedTarget, + BufferKeyLifecycle, BufferStorage, Builder, Chain, CleanupWorkflowConditions, CloneFromBuffer, + ForkTargetStorage, Gate, GateState, InputSlot, InspectBuffer, Join, Listen, ManageBuffer, Node, + OperateBufferAccess, OperationError, OperationResult, OperationRoster, OrBroken, Output, Scope, + ScopeSettings, SingleInputStorage, UnusedTarget, }; pub trait Buffered: 'static + Send + Sync + Clone { @@ -109,6 +110,22 @@ pub trait Accessed: Buffered { Output::new(scope, target).chain(builder) } + fn access(self, builder: &mut Builder) -> Node { + let source = builder.commands.spawn(()).id(); + let target = builder.commands.spawn(UnusedTarget).id(); + builder.commands.add(AddOperation::new( + Some(builder.scope), + source, + OperateBufferAccess::::new(self, target), + )); + + Node { + input: InputSlot::new(builder.scope, source), + output: Output::new(builder.scope, target), + streams: (), + } + } + /// Alternative way to call [`Builder::on_cleanup`]. fn on_cleanup( self, @@ -246,7 +263,7 @@ impl Accessed for Buffer { } fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key { - builder.build(self.id()) + Self::Key::create_key(&self, builder) } fn deep_clone_key(key: &Self::Key) -> Self::Key { @@ -318,7 +335,7 @@ impl Accessed for CloneFromBuffer { } fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key { - builder.build(self.id()) + Self::Key::create_key(&(*self).into(), builder) } fn deep_clone_key(key: &Self::Key) -> Self::Key { diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 4d6efbf3..08d2fb62 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -38,10 +38,10 @@ use smallvec::SmallVec; use crate::{ add_listener_to_source, Accessed, AnyBuffer, AnyBufferAccessInterface, AnyBufferKey, AnyRange, AsAnyBuffer, Buffer, BufferAccessMut, BufferAccessors, BufferError, BufferIdentifier, - BufferKey, BufferKeyBuilder, BufferKeyTag, BufferLocation, BufferMap, BufferMapLayout, - BufferMapStruct, BufferStorage, Bufferable, Buffered, Builder, DrainBuffer, Gate, GateState, - IncompatibleLayout, InspectBuffer, Joined, JoinedValue, ManageBuffer, NotifyBufferUpdate, - OperationError, OperationResult, OrBroken, + BufferKey, BufferKeyBuilder, BufferKeyLifecycle, BufferKeyTag, BufferLocation, BufferMap, + BufferMapLayout, BufferMapStruct, BufferStorage, Bufferable, Buffered, Builder, DrainBuffer, + Gate, GateState, IncompatibleLayout, InspectBuffer, Joined, JoinedValue, ManageBuffer, + NotifyBufferUpdate, OperationError, OperationResult, OrBroken, }; /// A [`Buffer`] whose message type has been anonymized, but which is known to @@ -159,17 +159,28 @@ impl JsonBufferKey { pub fn as_any_buffer_key(self) -> AnyBufferKey { self.into() } +} - fn deep_clone(&self) -> Self { +impl BufferKeyLifecycle for JsonBufferKey { + type TargetBuffer = JsonBuffer; + + fn create_key(buffer: &Self::TargetBuffer, builder: &BufferKeyBuilder) -> Self { Self { - tag: self.tag.deep_clone(), - interface: self.interface, + tag: builder.make_tag(buffer.id()), + interface: buffer.interface, } } fn is_in_use(&self) -> bool { self.tag.is_in_use() } + + fn deep_clone(&self) -> Self { + Self { + tag: self.tag.deep_clone(), + interface: self.interface, + } + } } impl std::fmt::Debug for JsonBufferKey { diff --git a/src/builder.rs b/src/builder.rs index 79088b60..2d8d6725 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -22,14 +22,13 @@ use std::future::Future; use smallvec::SmallVec; use crate::{ - Accessed, Accessible, AddOperation, AsMap, Buffer, BufferKeys, BufferLocation, BufferMap, - BufferSettings, Bufferable, Buffered, Chain, Collect, ForkClone, ForkCloneOutput, + Accessed, Accessible, AddOperation, AsMap, Buffer, BufferKeyMap, BufferKeys, BufferLocation, + BufferMap, BufferSettings, Bufferable, Buffered, Chain, Collect, ForkClone, ForkCloneOutput, ForkTargetStorage, Gate, GateRequest, IncompatibleLayout, Injection, InputSlot, IntoAsyncMap, - IntoBlockingMap, Joinable, JoinedValue, Node, OperateBuffer, OperateBufferAccess, - OperateDynamicGate, OperateScope, OperateSplit, OperateStaticGate, Output, Provider, - RequestOfMap, ResponseOfMap, Scope, ScopeEndpoints, ScopeSettings, ScopeSettingsStorage, - Sendish, Service, SplitOutputs, Splittable, StreamPack, StreamTargetMap, StreamsOfMap, Trim, - TrimBranch, UnusedTarget, + IntoBlockingMap, Joinable, JoinedValue, Node, OperateBuffer, OperateDynamicGate, OperateScope, + OperateSplit, OperateStaticGate, Output, Provider, RequestOfMap, ResponseOfMap, Scope, + ScopeEndpoints, ScopeSettings, ScopeSettingsStorage, Sendish, Service, SplitOutputs, + Splittable, StreamPack, StreamTargetMap, StreamsOfMap, Trim, TrimBranch, UnusedTarget, }; pub(crate) mod connect; @@ -250,6 +249,14 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { buffers.listen(self) } + /// Try listening to a map of buffers. + pub fn try_listen<'b, Keys: BufferKeyMap>( + &'b mut self, + buffers: &BufferMap, + ) -> Result, IncompatibleLayout> { + Keys::try_listen_from(buffers, self) + } + /// Create a node that combines its inputs with access to some buffers. You /// must specify one ore more buffers to access. FOr multiple buffers, /// combine then into a tuple or an [`Iterator`]. Tuples of buffers can be @@ -266,19 +273,20 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { T: 'static + Send + Sync, { let buffers = buffers.into_buffer(self); - let source = self.commands.spawn(()).id(); - let target = self.commands.spawn(UnusedTarget).id(); - self.commands.add(AddOperation::new( - Some(self.scope), - source, - OperateBufferAccess::::new(buffers, target), - )); + buffers.access(self) + } - Node { - input: InputSlot::new(self.scope, source), - output: Output::new(self.scope, target), - streams: (), - } + /// Try to create access to some buffers. Same as [`Self::create_buffer_access`] + /// except it will return an error if the buffers in the [`BufferMap`] are not + /// compatible with the keys that are being asked for. + pub fn try_create_buffer_access( + &mut self, + buffers: &BufferMap, + ) -> Result, IncompatibleLayout> + where + T: 'static + Send + Sync, + { + Keys::try_buffer_access(buffers, self) } /// Collect incoming workflow threads into a container. diff --git a/src/testing.rs b/src/testing.rs index 2db61ecc..2982eb91 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -33,8 +33,8 @@ use smallvec::SmallVec; use crate::{ flush_impulses, Accessed, AddContinuousServicesExt, AnyBuffer, AsAnyBuffer, AsyncServiceInput, - BlockingMap, BlockingServiceInput, Buffer, BufferKey, Bufferable, Buffered, Builder, - ContinuousQuery, ContinuousQueueView, ContinuousService, FlushParameters, + BlockingMap, BlockingServiceInput, Buffer, BufferKey, BufferKeyLifecycle, Bufferable, Buffered, + Builder, ContinuousQuery, ContinuousQueueView, ContinuousService, FlushParameters, GetBufferedSessionsFn, Joined, OperationError, OperationResult, OperationRoster, Promise, RunCommandsOnWorldExt, Scope, Service, SpawnWorkflowExt, StreamOf, StreamPack, UnhandledErrors, WorkflowSettings, From 87387e3d7b120630bbb02129119f6d566da5f778 Mon Sep 17 00:00:00 2001 From: Grey Date: Thu, 20 Feb 2025 10:28:45 +0800 Subject: [PATCH 33/37] BufferKeyMap macro (#56) Signed-off-by: Michael X. Grey --- macros/src/buffer.rs | 342 +++++++++++++++++++++++++++++++------- macros/src/lib.rs | 14 +- src/buffer.rs | 67 +++++++- src/buffer/any_buffer.rs | 4 +- src/buffer/buffer_map.rs | 340 ++++++++++++++++++++++++++----------- src/buffer/json_buffer.rs | 11 ++ src/lib.rs | 6 +- src/re_exports.rs | 21 +++ 8 files changed, 642 insertions(+), 163 deletions(-) create mode 100644 src/re_exports.rs diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index 188c4b0b..d10d8711 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -1,61 +1,128 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{parse_quote, Field, Generics, Ident, ItemStruct, Type, TypePath}; +use syn::{ + parse_quote, Field, Generics, Ident, ImplGenerics, ItemStruct, Type, TypeGenerics, TypePath, + Visibility, WhereClause, +}; use crate::Result; +const JOINED_ATTR_TAG: &'static str = "joined"; +const KEY_ATTR_TAG: &'static str = "key"; + pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result { let struct_ident = &input_struct.ident; let (impl_generics, ty_generics, where_clause) = input_struct.generics.split_for_impl(); let StructConfig { buffer_struct_name: buffer_struct_ident, - } = StructConfig::from_data_struct(&input_struct); + } = StructConfig::from_data_struct(&input_struct, &JOINED_ATTR_TAG); let buffer_struct_vis = &input_struct.vis; - let (field_ident, _, field_config) = get_fields_map(&input_struct.fields)?; + let (field_ident, _, field_config) = + get_fields_map(&input_struct.fields, FieldSettings::for_joined())?; let buffer: Vec<&Type> = field_config.iter().map(|config| &config.buffer).collect(); let noncopy = field_config.iter().any(|config| config.noncopy); - let buffer_struct: ItemStruct = parse_quote! { - #[allow(non_camel_case_types, unused)] - #buffer_struct_vis struct #buffer_struct_ident #impl_generics #where_clause { - #( - #buffer_struct_vis #field_ident: #buffer, - )* - } - }; + let buffer_struct: ItemStruct = generate_buffer_struct( + &buffer_struct_ident, + buffer_struct_vis, + &impl_generics, + &where_clause, + &field_ident, + &buffer, + ); + + let impl_buffer_clone = impl_buffer_clone( + &buffer_struct_ident, + &impl_generics, + &ty_generics, + &where_clause, + &field_ident, + noncopy, + ); + + let impl_select_buffers = impl_select_buffers( + struct_ident, + &buffer_struct_ident, + buffer_struct_vis, + &impl_generics, + &ty_generics, + &where_clause, + &field_ident, + &buffer, + ); + + let impl_buffer_map_layout = + impl_buffer_map_layout(&buffer_struct, &field_ident, &field_config)?; + let impl_joined = impl_joined(&buffer_struct, &input_struct, &field_ident)?; - let impl_buffer_clone = if noncopy { - // Clone impl for structs with a buffer that is not copyable - quote! { - impl #impl_generics ::std::clone::Clone for #buffer_struct_ident #ty_generics #where_clause { - fn clone(&self) -> Self { - Self { - #( - #field_ident: self.#field_ident.clone(), - )* - } - } - } + let gen = quote! { + impl #impl_generics ::bevy_impulse::JoinedValue for #struct_ident #ty_generics #where_clause { + type Buffers = #buffer_struct_ident #ty_generics; } - } else { - // Clone and copy impl for structs with buffers that are all copyable - quote! { - impl #impl_generics ::std::clone::Clone for #buffer_struct_ident #ty_generics #where_clause { - fn clone(&self) -> Self { - *self - } - } - impl #impl_generics ::std::marker::Copy for #buffer_struct_ident #ty_generics #where_clause {} - } + #buffer_struct + + #impl_buffer_clone + + #impl_select_buffers + + #impl_buffer_map_layout + + #impl_joined }; - let impl_buffer_map_layout = impl_buffer_map_layout(&buffer_struct, &input_struct)?; - let impl_joined = impl_joined(&buffer_struct, &input_struct)?; + Ok(gen.into()) +} + +pub(crate) fn impl_buffer_key_map(input_struct: &ItemStruct) -> Result { + let struct_ident = &input_struct.ident; + let (impl_generics, ty_generics, where_clause) = input_struct.generics.split_for_impl(); + let StructConfig { + buffer_struct_name: buffer_struct_ident, + } = StructConfig::from_data_struct(&input_struct, &KEY_ATTR_TAG); + let buffer_struct_vis = &input_struct.vis; + + let (field_ident, field_type, field_config) = + get_fields_map(&input_struct.fields, FieldSettings::for_key())?; + let buffer: Vec<&Type> = field_config.iter().map(|config| &config.buffer).collect(); + let noncopy = field_config.iter().any(|config| config.noncopy); + + let buffer_struct: ItemStruct = generate_buffer_struct( + &buffer_struct_ident, + buffer_struct_vis, + &impl_generics, + &where_clause, + &field_ident, + &buffer, + ); + + let impl_buffer_clone = impl_buffer_clone( + &buffer_struct_ident, + &impl_generics, + &ty_generics, + &where_clause, + &field_ident, + noncopy, + ); + + let impl_select_buffers = impl_select_buffers( + struct_ident, + &buffer_struct_ident, + buffer_struct_vis, + &impl_generics, + &ty_generics, + &where_clause, + &field_ident, + &buffer, + ); + + let impl_buffer_map_layout = + impl_buffer_map_layout(&buffer_struct, &field_ident, &field_config)?; + let impl_accessed = impl_accessed(&buffer_struct, &input_struct, &field_ident, &field_type)?; let gen = quote! { - impl #impl_generics ::bevy_impulse::JoinedValue for #struct_ident #ty_generics #where_clause { + impl #impl_generics ::bevy_impulse::BufferKeyMap for #struct_ident #ty_generics #where_clause { type Buffers = #buffer_struct_ident #ty_generics; } @@ -63,23 +130,11 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result #buffer_struct_ident #ty_generics { - #buffer_struct_ident { - #( - #field_ident, - )* - } - } - } + #impl_select_buffers #impl_buffer_map_layout - #impl_joined + #impl_accessed }; Ok(gen.into()) @@ -112,7 +167,7 @@ struct StructConfig { } impl StructConfig { - fn from_data_struct(data_struct: &ItemStruct) -> Self { + fn from_data_struct(data_struct: &ItemStruct, attr_tag: &str) -> Self { let mut config = Self { buffer_struct_name: format_ident!("__bevy_impulse_{}_Buffers", data_struct.ident), }; @@ -120,7 +175,7 @@ impl StructConfig { let attr = data_struct .attrs .iter() - .find(|attr| attr.path().is_ident("joined")); + .find(|attr| attr.path().is_ident(attr_tag)); if let Some(attr) = attr { attr.parse_nested_meta(|meta| { @@ -137,23 +192,52 @@ impl StructConfig { } } +struct FieldSettings { + default_buffer: fn(&Type) -> Type, + attr_tag: &'static str, +} + +impl FieldSettings { + fn for_joined() -> Self { + Self { + default_buffer: Self::default_field_for_joined, + attr_tag: JOINED_ATTR_TAG, + } + } + + fn for_key() -> Self { + Self { + default_buffer: Self::default_field_for_key, + attr_tag: KEY_ATTR_TAG, + } + } + + fn default_field_for_joined(ty: &Type) -> Type { + parse_quote! { ::bevy_impulse::Buffer<#ty> } + } + + fn default_field_for_key(ty: &Type) -> Type { + parse_quote! { <#ty as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer } + } +} + struct FieldConfig { buffer: Type, noncopy: bool, } impl FieldConfig { - fn from_field(field: &Field) -> Self { + fn from_field(field: &Field, settings: &FieldSettings) -> Self { let ty = &field.ty; let mut config = Self { - buffer: parse_quote! { ::bevy_impulse::Buffer<#ty> }, + buffer: (settings.default_buffer)(ty), noncopy: false, }; for attr in field .attrs .iter() - .filter(|attr| attr.path().is_ident("joined")) + .filter(|attr| attr.path().is_ident(settings.attr_tag)) { attr.parse_nested_meta(|meta| { if meta.path.is_ident("buffer") { @@ -172,7 +256,10 @@ impl FieldConfig { } } -fn get_fields_map(fields: &syn::Fields) -> Result<(Vec<&Ident>, Vec<&Type>, Vec)> { +fn get_fields_map( + fields: &syn::Fields, + settings: FieldSettings, +) -> Result<(Vec<&Ident>, Vec<&Type>, Vec)> { match fields { syn::Fields::Named(data) => { let mut idents = Vec::new(); @@ -185,7 +272,7 @@ fn get_fields_map(fields: &syn::Fields) -> Result<(Vec<&Ident>, Vec<&Type>, Vec< .ok_or("expected named fields".to_string())?; idents.push(ident); types.push(&field.ty); - configs.push(FieldConfig::from_field(field)); + configs.push(FieldConfig::from_field(field, &settings)); } Ok((idents, types, configs)) } @@ -193,16 +280,98 @@ fn get_fields_map(fields: &syn::Fields) -> Result<(Vec<&Ident>, Vec<&Type>, Vec< } } +fn generate_buffer_struct( + buffer_struct_ident: &Ident, + buffer_struct_vis: &Visibility, + impl_generics: &ImplGenerics, + where_clause: &Option<&WhereClause>, + field_ident: &Vec<&Ident>, + buffer: &Vec<&Type>, +) -> ItemStruct { + parse_quote! { + #[allow(non_camel_case_types, unused)] + #buffer_struct_vis struct #buffer_struct_ident #impl_generics #where_clause { + #( + #buffer_struct_vis #field_ident: #buffer, + )* + } + } +} + +fn impl_select_buffers( + struct_ident: &Ident, + buffer_struct_ident: &Ident, + buffer_struct_vis: &Visibility, + impl_generics: &ImplGenerics, + ty_generics: &TypeGenerics, + where_clause: &Option<&WhereClause>, + field_ident: &Vec<&Ident>, + buffer: &Vec<&Type>, +) -> TokenStream { + quote! { + impl #impl_generics #struct_ident #ty_generics #where_clause { + #buffer_struct_vis fn select_buffers( + #( + #field_ident: #buffer, + )* + ) -> #buffer_struct_ident #ty_generics { + #buffer_struct_ident { + #( + #field_ident, + )* + } + } + } + } + .into() +} + +fn impl_buffer_clone( + buffer_struct_ident: &Ident, + impl_generics: &ImplGenerics, + ty_generics: &TypeGenerics, + where_clause: &Option<&WhereClause>, + field_ident: &Vec<&Ident>, + noncopy: bool, +) -> TokenStream { + if noncopy { + // Clone impl for structs with a buffer that is not copyable + quote! { + impl #impl_generics ::std::clone::Clone for #buffer_struct_ident #ty_generics #where_clause { + fn clone(&self) -> Self { + Self { + #( + #field_ident: self.#field_ident.clone(), + )* + } + } + } + } + } else { + // Clone and copy impl for structs with buffers that are all copyable + quote! { + impl #impl_generics ::std::clone::Clone for #buffer_struct_ident #ty_generics #where_clause { + fn clone(&self) -> Self { + *self + } + } + + impl #impl_generics ::std::marker::Copy for #buffer_struct_ident #ty_generics #where_clause {} + } + } +} + /// Params: /// buffer_struct: The struct to implement `BufferMapLayout`. /// item_struct: The struct which `buffer_struct` is derived from. +/// settings: [`FieldSettings`] to use when parsing the field attributes fn impl_buffer_map_layout( buffer_struct: &ItemStruct, - item_struct: &ItemStruct, + field_ident: &Vec<&Ident>, + field_config: &Vec, ) -> Result { let struct_ident = &buffer_struct.ident; let (impl_generics, ty_generics, where_clause) = buffer_struct.generics.split_for_impl(); - let (field_ident, _, field_config) = get_fields_map(&item_struct.fields)?; let buffer: Vec<&Type> = field_config.iter().map(|config| &config.buffer).collect(); let map_key: Vec = field_ident.iter().map(|v| v.to_string()).collect(); @@ -249,17 +418,17 @@ fn impl_buffer_map_layout( fn impl_joined( joined_struct: &ItemStruct, item_struct: &ItemStruct, + field_ident: &Vec<&Ident>, ) -> Result { let struct_ident = &joined_struct.ident; let item_struct_ident = &item_struct.ident; let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl(); - let (field_ident, _, _) = get_fields_map(&item_struct.fields)?; Ok(quote! { impl #impl_generics ::bevy_impulse::Joined for #struct_ident #ty_generics #where_clause { type Item = #item_struct_ident #ty_generics; - fn pull(&self, session: ::bevy_ecs::prelude::Entity, world: &mut ::bevy_ecs::prelude::World) -> Result { + fn pull(&self, session: ::bevy_impulse::re_exports::Entity, world: &mut ::bevy_impulse::re_exports::World) -> Result { #( let #field_ident = self.#field_ident.pull(session, world)?; )* @@ -271,3 +440,54 @@ fn impl_joined( } }.into()) } + +fn impl_accessed( + accessed_struct: &ItemStruct, + key_struct: &ItemStruct, + field_ident: &Vec<&Ident>, + field_type: &Vec<&Type>, +) -> Result { + let struct_ident = &accessed_struct.ident; + let key_struct_ident = &key_struct.ident; + let (impl_generics, ty_generics, where_clause) = key_struct.generics.split_for_impl(); + + Ok(quote! { + impl #impl_generics ::bevy_impulse::Accessed for #struct_ident #ty_generics #where_clause { + type Key = #key_struct_ident #ty_generics; + + fn add_accessor( + &self, + accessor: ::bevy_impulse::re_exports::Entity, + world: &mut ::bevy_impulse::re_exports::World, + ) -> ::bevy_impulse::OperationResult { + #( + ::bevy_impulse::Accessed::add_accessor(&self.#field_ident, accessor, world)?; + )* + Ok(()) + } + + fn create_key(&self, builder: &::bevy_impulse::BufferKeyBuilder) -> Self::Key { + Self::Key {#( + // TODO(@mxgrey): This currently does not have good support for the user + // substituting in a different key type than what the BufferKeyLifecycle expects. + // We could consider adding a .clone().into() to help support that use case, but + // this would be such a niche use case that I think we can ignore it for now. + #field_ident: <#field_type as ::bevy_impulse::BufferKeyLifecycle>::create_key(&self.#field_ident, builder), + )*} + } + + fn deep_clone_key(key: &Self::Key) -> Self::Key { + Self::Key {#( + #field_ident: ::bevy_impulse::BufferKeyLifecycle::deep_clone(&key.#field_ident), + )*} + } + + fn is_key_in_use(key: &Self::Key) -> bool { + false + #( + || ::bevy_impulse::BufferKeyLifecycle::is_in_use(&key.#field_ident) + )* + } + } + }.into()) +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index df58fdc6..d63b8913 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -16,7 +16,7 @@ */ mod buffer; -use buffer::impl_joined_value; +use buffer::{impl_buffer_key_map, impl_joined_value}; use proc_macro::TokenStream; use quote::quote; @@ -76,3 +76,15 @@ pub fn derive_joined_value(input: TokenStream) -> TokenStream { .into(), } } + +#[proc_macro_derive(BufferKeyMap, attributes(key))] +pub fn derive_buffer_key_map(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemStruct); + match impl_buffer_key_map(&input) { + Ok(tokens) => tokens.into(), + Err(msg) => quote! { + compile_error!(#msg); + } + .into(), + } +} diff --git a/src/buffer.rs b/src/buffer.rs index 7418107b..77baf5f1 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -17,9 +17,9 @@ use bevy_ecs::{ change_detection::Mut, - prelude::{Commands, Entity, Query}, + prelude::{Commands, Entity, Query, World}, query::QueryEntityError, - system::SystemParam, + system::{SystemParam, SystemState}, }; use std::{ops::RangeBounds, sync::Arc}; @@ -38,7 +38,7 @@ pub use buffer_access_lifecycle::BufferKeyLifecycle; pub(crate) use buffer_access_lifecycle::*; mod buffer_key_builder; -pub(crate) use buffer_key_builder::*; +pub use buffer_key_builder::*; mod buffer_map; pub use buffer_map::*; @@ -402,6 +402,67 @@ where } } +/// This trait allows [`World`] to give you access to any buffer using a [`BufferKey`] +pub trait BufferWorldAccess { + /// Call this to get read-only access to a buffer from a [`World`]. + /// + /// Alternatively you can use [`BufferAccess`] as a regular bevy system parameter, + /// which does not need direct world access. + fn buffer_view(&self, key: &BufferKey) -> Result, BufferError> + where + T: 'static + Send + Sync; + + /// Call this to get mutable access to a buffer. + /// + /// Pass in a callback that will receive [`BufferMut`], allowing it to view + /// and modify the contents of the buffer. + fn buffer_mut( + &mut self, + key: &BufferKey, + f: impl FnOnce(BufferMut) -> U, + ) -> Result + where + T: 'static + Send + Sync; +} + +impl BufferWorldAccess for World { + fn buffer_view(&self, key: &BufferKey) -> Result, BufferError> + where + T: 'static + Send + Sync, + { + let buffer_ref = self + .get_entity(key.tag.buffer) + .ok_or(BufferError::BufferMissing)?; + let storage = buffer_ref + .get::>() + .ok_or(BufferError::BufferMissing)?; + let gate = buffer_ref + .get::() + .ok_or(BufferError::BufferMissing)?; + Ok(BufferView { + storage, + gate, + session: key.tag.session, + }) + } + + fn buffer_mut( + &mut self, + key: &BufferKey, + f: impl FnOnce(BufferMut) -> U, + ) -> Result + where + T: 'static + Send + Sync, + { + let mut state = SystemState::>::new(self); + let mut buffer_access_mut = state.get_mut(self); + let buffer_mut = buffer_access_mut + .get_mut(key) + .map_err(|_| BufferError::BufferMissing)?; + Ok(f(buffer_mut)) + } +} + /// Access to view a buffer that exists inside a workflow. pub struct BufferView<'a, T> where diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index feb04e0f..a7a4f2d2 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -517,7 +517,7 @@ pub trait AnyBufferWorldAccess { /// For technical reasons this requires direct [`World`] access, but you can /// do other read-only queries on the world while holding onto the /// [`AnyBufferView`]. - fn any_buffer_view<'a>(&self, key: &AnyBufferKey) -> Result, BufferError>; + fn any_buffer_view(&self, key: &AnyBufferKey) -> Result, BufferError>; /// Call this to get mutable access to any buffer. /// @@ -531,7 +531,7 @@ pub trait AnyBufferWorldAccess { } impl AnyBufferWorldAccess for World { - fn any_buffer_view<'a>(&self, key: &AnyBufferKey) -> Result, BufferError> { + fn any_buffer_view(&self, key: &AnyBufferKey) -> Result, BufferError> { key.interface.create_any_buffer_view(key, self) } diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 5eef2cb3..173ec1af 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -29,7 +29,7 @@ use crate::{ Joined, Node, OperationError, OperationResult, OperationRoster, }; -pub use bevy_impulse_derive::JoinedValue; +pub use bevy_impulse_derive::{BufferKeyMap, JoinedValue}; /// Uniquely identify a buffer within a buffer map, either by name or by an /// index value. @@ -282,8 +282,66 @@ impl Buffered for T { } /// This trait can be implemented for structs that are created by joining together -/// values from a collection of buffers. Usually you do not need to implement this -/// yourself. Instead you can use `#[derive(JoinedValue)]`. +/// values from a collection of buffers. This allows [`join`][1] to produce arbitrary +/// structs. Structs with this trait can be produced by [`try_join`][2]. +/// +/// Each field in this struct needs to have the trait bounds `'static + Send + Sync`. +/// +/// This does not generally need to be implemented explicitly. Instead you should +/// use `#[derive(JoinedValue)]`: +/// +/// ``` +/// use bevy_impulse::prelude::*; +/// +/// #[derive(JoinedValue)] +/// struct SomeValues { +/// integer: i64, +/// string: String, +/// } +/// ``` +/// +/// The above example would allow you to join a value from an `i64` buffer with +/// a value from a `String` buffer. You can have as many fields in the struct +/// as you'd like. +/// +/// This macro will generate a struct of buffers to match the fields of the +/// struct that it's applied to. The name of that struct is anonymous by default +/// since you don't generally need to use it directly, but if you want to give +/// it a name you can use #[joined(buffers_struct_name = ...)]`: +/// +/// ``` +/// # use bevy_impulse::prelude::*; +/// +/// #[derive(JoinedValue)] +/// #[joined(buffers_struct_name = SomeBuffers)] +/// struct SomeValues { +/// integer: i64, +/// string: String, +/// } +/// ``` +/// +/// By default each field of the generated buffers struct will have a type of +/// [`Buffer`], but you can override this using `#[joined(buffer = ...)]` +/// to specify a special buffer type. For example if your `JoinedValue` struct +/// contains an [`AnyMessageBox`] then by default the macro will use `Buffer`, +/// but you probably really want it to have an [`AnyBuffer`]: +/// +/// ``` +/// # use bevy_impulse::prelude::*; +/// +/// #[derive(JoinedValue)] +/// struct SomeValues { +/// integer: i64, +/// string: String, +/// #[joined(buffer = AnyBuffer)] +/// any: AnyMessageBox, +/// } +/// ``` +/// +/// The above method also works for joining a `JsonMessage` field from a `JsonBuffer`. +/// +/// [1]: crate::Builder::join +/// [2]: crate::Builder::try_join pub trait JoinedValue: 'static + Send + Sync + Sized { /// This associated type must represent a buffer map layout that implements /// the [`Joined`] trait. The message type yielded by [`Joined`] for this @@ -300,7 +358,47 @@ pub trait JoinedValue: 'static + Send + Sync + Sized { } } -/// Trait to describe a set of buffer keys. +/// Trait to describe a set of buffer keys. This allows [listen][1] and [access][2] +/// to work for arbitrary structs of buffer keys. Structs with this trait can be +/// produced by [`try_listen`][3] and [`try_create_buffer_access`][4]. +/// +/// Each field in the struct must be some kind of buffer key. +/// +/// This does not generally need to be implemented explicitly. Instead you should +/// define a struct where all fields are buffer keys and then apply +/// `#[derive(BufferKeyMap)]` to it, e.g.: +/// +/// ``` +/// use bevy_impulse::prelude::*; +/// +/// #[derive(Clone, BufferKeyMap)] +/// struct SomeKeys { +/// integer: BufferKey, +/// string: BufferKey, +/// any: AnyBufferKey, +/// } +/// ``` +/// +/// The macro will generate a struct of buffers to match the keys. The name of +/// that struct is anonymous by default since you don't generally need to use it +/// directly, but if you want to give it a name you can use `#[key(buffers_struct_name = ...)]`: +/// +/// ``` +/// # use bevy_impulse::prelude::*; +/// +/// #[derive(Clone, BufferKeyMap)] +/// #[key(buffers_struct_name = SomeBuffers)] +/// struct SomeKeys { +/// integer: BufferKey, +/// string: BufferKey, +/// any: AnyBufferKey, +/// } +/// ``` +/// +/// [1]: crate::Builder::listen +/// [2]: crate::Builder::create_buffer_access +/// [3]: crate::Builder::try_listen +/// [4]: crate::Builder::try_create_buffer_access pub trait BufferKeyMap: 'static + Send + Sync + Sized + Clone { type Buffers: 'static + BufferMapLayout + Accessed + Send + Sync; @@ -433,7 +531,7 @@ impl BufferMapLa #[cfg(test)] mod tests { - use crate::{prelude::*, testing::*, Accessed, AddBufferToMap, BufferMap}; + use crate::{prelude::*, testing::*, AddBufferToMap, BufferMap}; #[derive(JoinedValue)] struct TestJoinedValue { @@ -624,7 +722,8 @@ mod tests { _b: u32, } - #[derive(Clone)] + #[derive(Clone, BufferKeyMap)] + #[key(buffers_struct_name = TestKeysBuffers)] struct TestKeys { integer: BufferKey, float: BufferKey, @@ -632,106 +731,159 @@ mod tests { generic: BufferKey, any: AnyBufferKey, } + #[test] + fn test_listen() { + let mut context = TestingContext::minimal_plugins(); - impl BufferKeyMap for TestKeys { - type Buffers = TestKeysBuffers; - } + let workflow = context.spawn_io_workflow(|scope, builder| { + let buffer_any = builder.create_buffer::(BufferSettings::default()); - #[derive(Clone)] - struct TestKeysBuffers { - integer: as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer, - float: as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer, - string: as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer, - generic: as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer, - any: ::TargetBuffer, - } + let buffers = TestKeys::select_buffers( + builder.create_buffer(BufferSettings::default()), + builder.create_buffer(BufferSettings::default()), + builder.create_buffer(BufferSettings::default()), + builder.create_buffer(BufferSettings::default()), + buffer_any.as_any_buffer(), + ); - impl BufferMapLayout for TestKeysBuffers { - fn try_from_buffer_map(buffers: &BufferMap) -> Result { - let mut compatibility = ::bevy_impulse::IncompatibleLayout::default(); - let integer = compatibility.require_buffer_by_literal::< as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer>("integer", buffers); - let float = compatibility.require_buffer_by_literal::< as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer>("float", buffers); - let string = compatibility.require_buffer_by_literal::< as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer>("string", buffers); - let generic = compatibility.require_buffer_by_literal::< as ::bevy_impulse::BufferKeyLifecycle>::TargetBuffer>("generic", buffers); - let any = compatibility.require_buffer_by_literal::<::TargetBuffer>("any", buffers); + scope.input.chain(builder).fork_unzip(( + |chain: Chain<_>| chain.connect(buffers.integer.input_slot()), + |chain: Chain<_>| chain.connect(buffers.float.input_slot()), + |chain: Chain<_>| chain.connect(buffers.string.input_slot()), + |chain: Chain<_>| chain.connect(buffers.generic.input_slot()), + |chain: Chain<_>| chain.connect(buffer_any.input_slot()), + )); - let Ok(integer) = integer else { - return Err(compatibility); - }; - let Ok(float) = float else { - return Err(compatibility); - }; - let Ok(string) = string else { - return Err(compatibility); - }; - let Ok(generic) = generic else { - return Err(compatibility); - }; - let Ok(any) = any else { - return Err(compatibility); - }; + builder + .listen(buffers) + .then(join_via_listen.into_blocking_callback()) + .dispose_on_none() + .connect(scope.terminate); + }); - Ok(Self { - integer, - float, - string, - generic, - any, - }) - } - } + let mut promise = context.command(|commands| { + commands + .request( + (5_i64, 3.14_f64, "hello".to_string(), "world", 42_i64), + workflow, + ) + .take_response() + }); - impl ::bevy_impulse::BufferMapStruct for TestKeysBuffers { - fn buffer_list(&self) -> smallvec::SmallVec<[AnyBuffer; 8]> { - smallvec::smallvec![ - ::bevy_impulse::AsAnyBuffer::as_any_buffer(&self.integer), - ::bevy_impulse::AsAnyBuffer::as_any_buffer(&self.float), - ::bevy_impulse::AsAnyBuffer::as_any_buffer(&self.string), - ::bevy_impulse::AsAnyBuffer::as_any_buffer(&self.generic), - ::bevy_impulse::AsAnyBuffer::as_any_buffer(&self.any), - ] - } + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let value: TestJoinedValue<&'static str> = promise.take().available().unwrap(); + assert_eq!(value.integer, 5); + assert_eq!(value.float, 3.14); + assert_eq!(value.string, "hello"); + assert_eq!(value.generic, "world"); + assert_eq!(*value.any.downcast::().unwrap(), 42); + assert!(context.no_unhandled_errors()); } - impl Accessed for TestKeysBuffers { - type Key = TestKeys; + #[test] + fn test_try_listen() { + let mut context = TestingContext::minimal_plugins(); - fn add_accessor(&self, accessor: Entity, world: &mut World) -> crate::OperationResult { - ::bevy_impulse::Accessed::add_accessor(&self.integer, accessor, world)?; - ::bevy_impulse::Accessed::add_accessor(&self.float, accessor, world)?; - ::bevy_impulse::Accessed::add_accessor(&self.string, accessor, world)?; - ::bevy_impulse::Accessed::add_accessor(&self.generic, accessor, world)?; - ::bevy_impulse::Accessed::add_accessor(&self.any, accessor, world)?; - Ok(()) - } + let workflow = context.spawn_io_workflow(|scope, builder| { + let buffer_i64 = builder.create_buffer::(BufferSettings::default()); + let buffer_f64 = builder.create_buffer::(BufferSettings::default()); + let buffer_string = builder.create_buffer::(BufferSettings::default()); + let buffer_generic = builder.create_buffer::<&'static str>(BufferSettings::default()); + let buffer_any = builder.create_buffer::(BufferSettings::default()); - fn create_key(&self, builder: &crate::BufferKeyBuilder) -> Self::Key { - TestKeys { - integer: ::bevy_impulse::BufferKeyLifecycle::create_key(&self.integer, builder), - float: ::bevy_impulse::BufferKeyLifecycle::create_key(&self.float, builder), - string: ::bevy_impulse::BufferKeyLifecycle::create_key(&self.string, builder), - generic: ::bevy_impulse::BufferKeyLifecycle::create_key(&self.generic, builder), - any: ::bevy_impulse::BufferKeyLifecycle::create_key(&self.any, builder), - } - } + scope.input.chain(builder).fork_unzip(( + |chain: Chain<_>| chain.connect(buffer_i64.input_slot()), + |chain: Chain<_>| chain.connect(buffer_f64.input_slot()), + |chain: Chain<_>| chain.connect(buffer_string.input_slot()), + |chain: Chain<_>| chain.connect(buffer_generic.input_slot()), + |chain: Chain<_>| chain.connect(buffer_any.input_slot()), + )); - fn deep_clone_key(key: &Self::Key) -> Self::Key { - TestKeys { - integer: ::bevy_impulse::BufferKeyLifecycle::deep_clone(&key.integer), - float: ::bevy_impulse::BufferKeyLifecycle::deep_clone(&key.float), - string: ::bevy_impulse::BufferKeyLifecycle::deep_clone(&key.string), - generic: ::bevy_impulse::BufferKeyLifecycle::deep_clone(&key.generic), - any: ::bevy_impulse::BufferKeyLifecycle::deep_clone(&key.any), - } - } + let mut buffer_map = BufferMap::new(); + buffer_map.insert_buffer("integer", buffer_i64); + buffer_map.insert_buffer("float", buffer_f64); + buffer_map.insert_buffer("string", buffer_string); + buffer_map.insert_buffer("generic", buffer_generic); + buffer_map.insert_buffer("any", buffer_any); + + builder + .try_listen(&buffer_map) + .unwrap() + .then(join_via_listen.into_blocking_callback()) + .dispose_on_none() + .connect(scope.terminate); + }); + + let mut promise = context.command(|commands| { + commands + .request( + (5_i64, 3.14_f64, "hello".to_string(), "world", 42_i64), + workflow, + ) + .take_response() + }); - fn is_key_in_use(key: &Self::Key) -> bool { - false - || ::bevy_impulse::BufferKeyLifecycle::is_in_use(&key.integer) - || ::bevy_impulse::BufferKeyLifecycle::is_in_use(&key.float) - || ::bevy_impulse::BufferKeyLifecycle::is_in_use(&key.string) - || ::bevy_impulse::BufferKeyLifecycle::is_in_use(&key.generic) - || ::bevy_impulse::BufferKeyLifecycle::is_in_use(&key.any) + context.run_with_conditions(&mut promise, Duration::from_secs(2)); + let value: TestJoinedValue<&'static str> = promise.take().available().unwrap(); + assert_eq!(value.integer, 5); + assert_eq!(value.float, 3.14); + assert_eq!(value.string, "hello"); + assert_eq!(value.generic, "world"); + assert_eq!(*value.any.downcast::().unwrap(), 42); + assert!(context.no_unhandled_errors()); + } + + /// This macro is a manual implementation of the join operation that uses + /// the buffer listening mechanism. There isn't any reason to reimplement + /// join here except so we can test that listening is working correctly for + /// BufferKeyMap. + fn join_via_listen( + In(keys): In>, + world: &mut World, + ) -> Option> { + if world.buffer_view(&keys.integer).ok()?.is_empty() { + return None; + } + if world.buffer_view(&keys.float).ok()?.is_empty() { + return None; } + if world.buffer_view(&keys.string).ok()?.is_empty() { + return None; + } + if world.buffer_view(&keys.generic).ok()?.is_empty() { + return None; + } + if world.any_buffer_view(&keys.any).ok()?.is_empty() { + return None; + } + + let integer = world + .buffer_mut(&keys.integer, |mut buffer| buffer.pull()) + .unwrap() + .unwrap(); + let float = world + .buffer_mut(&keys.float, |mut buffer| buffer.pull()) + .unwrap() + .unwrap(); + let string = world + .buffer_mut(&keys.string, |mut buffer| buffer.pull()) + .unwrap() + .unwrap(); + let generic = world + .buffer_mut(&keys.generic, |mut buffer| buffer.pull()) + .unwrap() + .unwrap(); + let any = world + .any_buffer_mut(&keys.any, |mut buffer| buffer.pull()) + .unwrap() + .unwrap(); + + Some(TestJoinedValue { + integer, + float, + string, + generic, + any, + }) } } diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 08d2fb62..80d768c4 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -1581,4 +1581,15 @@ mod tests { assert_eq!(values[2], serde_json::Value::String("hello".to_string())); assert_eq!(values[3], serde_json::to_value(TestMessage::new()).unwrap()); } + + // We define this struct just to make sure the BufferKeyMap macro successfully + // compiles with JsonBufferKey. + #[derive(Clone, BufferKeyMap)] + #[allow(unused)] + struct TestJsonKeyMap { + integer: BufferKey, + string: BufferKey, + json: JsonBufferKey, + any: AnyBufferKey, + } } diff --git a/src/lib.rs b/src/lib.rs index 4c4fb168..47213e8c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,6 +72,8 @@ pub use async_execution::Sendish; pub mod buffer; pub use buffer::*; +pub mod re_exports; + pub mod builder; pub use builder::*; @@ -340,8 +342,8 @@ pub mod prelude { buffer::{ Accessible, AnyBuffer, AnyBufferKey, AnyBufferMut, AnyBufferWorldAccess, AnyMessageBox, AsAnyBuffer, Buffer, BufferAccess, BufferAccessMut, BufferKey, BufferKeyMap, BufferMap, - BufferMapLayout, BufferSettings, Bufferable, Buffered, IncompatibleLayout, - IterBufferable, Joinable, JoinedValue, RetentionPolicy, + BufferMapLayout, BufferSettings, BufferWorldAccess, Bufferable, Buffered, + IncompatibleLayout, IterBufferable, Joinable, JoinedValue, RetentionPolicy, }, builder::Builder, callback::{AsCallback, Callback, IntoAsyncCallback, IntoBlockingCallback}, diff --git a/src/re_exports.rs b/src/re_exports.rs new file mode 100644 index 00000000..84f22076 --- /dev/null +++ b/src/re_exports.rs @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2025 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +//! This module contains symbols that are being re-exported so they can be used +//! by bevy_impulse_derive. + +pub use bevy_ecs::prelude::{Entity, World}; From c096209da27c69f4cf99bdb5f7445fff4904e401 Mon Sep 17 00:00:00 2001 From: Grey Date: Thu, 20 Feb 2025 14:48:25 +0800 Subject: [PATCH 34/37] Rename traits to improve semantics (#57) Signed-off-by: Michael X. Grey --- macros/src/buffer.rs | 14 ++--- macros/src/lib.rs | 4 +- src/buffer.rs | 4 +- src/buffer/any_buffer.rs | 10 ++-- src/buffer/buffer_map.rs | 66 ++++++++++++------------ src/buffer/bufferable.rs | 22 ++++---- src/buffer/{buffered.rs => buffering.rs} | 50 +++++++++--------- src/buffer/json_buffer.rs | 22 ++++---- src/builder.rs | 26 +++++----- src/chain.rs | 16 +++--- src/lib.rs | 8 +-- src/operation/cleanup.rs | 4 +- src/operation/join.rs | 4 +- src/operation/listen.rs | 4 +- src/operation/operate_buffer_access.rs | 16 +++--- src/operation/operate_gate.rs | 6 +-- src/operation/scope.rs | 4 +- src/testing.rs | 14 ++--- 18 files changed, 147 insertions(+), 147 deletions(-) rename src/buffer/{buffered.rs => buffering.rs} (94%) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index d10d8711..4d716948 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -57,7 +57,7 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result Result Result { @@ -452,7 +452,7 @@ fn impl_accessed( let (impl_generics, ty_generics, where_clause) = key_struct.generics.split_for_impl(); Ok(quote! { - impl #impl_generics ::bevy_impulse::Accessed for #struct_ident #ty_generics #where_clause { + impl #impl_generics ::bevy_impulse::Accessing for #struct_ident #ty_generics #where_clause { type Key = #key_struct_ident #ty_generics; fn add_accessor( @@ -461,7 +461,7 @@ fn impl_accessed( world: &mut ::bevy_impulse::re_exports::World, ) -> ::bevy_impulse::OperationResult { #( - ::bevy_impulse::Accessed::add_accessor(&self.#field_ident, accessor, world)?; + ::bevy_impulse::Accessing::add_accessor(&self.#field_ident, accessor, world)?; )* Ok(()) } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index d63b8913..58873049 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -65,7 +65,7 @@ pub fn delivery_label_macro(item: TokenStream) -> TokenStream { /// The result error is the compiler error message to be displayed. type Result = std::result::Result; -#[proc_macro_derive(JoinedValue, attributes(joined))] +#[proc_macro_derive(Joined, attributes(joined))] pub fn derive_joined_value(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as ItemStruct); match impl_joined_value(&input) { @@ -77,7 +77,7 @@ pub fn derive_joined_value(input: TokenStream) -> TokenStream { } } -#[proc_macro_derive(BufferKeyMap, attributes(key))] +#[proc_macro_derive(Accessor, attributes(key))] pub fn derive_buffer_key_map(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as ItemStruct); match impl_buffer_key_map(&input) { diff --git a/src/buffer.rs b/src/buffer.rs index 77baf5f1..1d33c28d 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -46,8 +46,8 @@ pub use buffer_map::*; mod buffer_storage; pub(crate) use buffer_storage::*; -mod buffered; -pub use buffered::*; +mod buffering; +pub use buffering::*; mod bufferable; pub use bufferable::*; diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index a7a4f2d2..3038c3a6 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -34,9 +34,9 @@ use thiserror::Error as ThisError; use smallvec::SmallVec; use crate::{ - add_listener_to_source, Accessed, Buffer, BufferAccessMut, BufferAccessors, BufferError, + add_listener_to_source, Accessing, Buffer, BufferAccessMut, BufferAccessors, BufferError, BufferKey, BufferKeyBuilder, BufferKeyLifecycle, BufferKeyTag, BufferLocation, BufferStorage, - Bufferable, Buffered, Builder, DrainBuffer, Gate, GateState, InspectBuffer, Joined, + Bufferable, Buffering, Builder, DrainBuffer, Gate, GateState, InspectBuffer, Joining, ManageBuffer, NotifyBufferUpdate, OperationError, OperationResult, OperationRoster, OrBroken, }; @@ -1035,7 +1035,7 @@ impl Bufferable for AnyBuffer { } } -impl Buffered for AnyBuffer { +impl Buffering for AnyBuffer { fn verify_scope(&self, scope: Entity) { assert_eq!(scope, self.scope()); } @@ -1069,7 +1069,7 @@ impl Buffered for AnyBuffer { } } -impl Joined for AnyBuffer { +impl Joining for AnyBuffer { type Item = AnyMessageBox; fn pull(&self, session: Entity, world: &mut World) -> Result { let mut buffer_mut = world.get_entity_mut(self.id()).or_broken()?; @@ -1077,7 +1077,7 @@ impl Joined for AnyBuffer { } } -impl Accessed for AnyBuffer { +impl Accessing for AnyBuffer { type Key = AnyBufferKey; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { world diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 173ec1af..50a1d78a 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -24,12 +24,12 @@ use smallvec::SmallVec; use bevy_ecs::prelude::{Entity, World}; use crate::{ - add_listener_to_source, Accessed, AnyBuffer, AnyBufferKey, AnyMessageBox, AsAnyBuffer, Buffer, - BufferKeyBuilder, BufferKeyLifecycle, Bufferable, Buffered, Builder, Chain, Gate, GateState, - Joined, Node, OperationError, OperationResult, OperationRoster, + add_listener_to_source, Accessing, AnyBuffer, AnyBufferKey, AnyMessageBox, AsAnyBuffer, Buffer, + BufferKeyBuilder, BufferKeyLifecycle, Bufferable, Buffering, Builder, Chain, Gate, GateState, + Joining, Node, OperationError, OperationResult, OperationRoster, }; -pub use bevy_impulse_derive::{BufferKeyMap, JoinedValue}; +pub use bevy_impulse_derive::{Accessor, Joined}; /// Uniquely identify a buffer within a buffer map, either by name or by an /// index value. @@ -199,18 +199,18 @@ pub struct BufferIncompatibility { /// This trait can be implemented on structs that represent a layout of buffers. /// You do not normally have to implement this yourself. Instead you should -/// `#[derive(JoinedValue)]` on a struct that you want a join operation to +/// `#[derive(Joined)]` on a struct that you want a join operation to /// produce. pub trait BufferMapLayout: Sized + Clone + 'static + Send + Sync { /// Try to convert a generic [`BufferMap`] into this specific layout. fn try_from_buffer_map(buffers: &BufferMap) -> Result; } -/// This trait helps auto-generated buffer map structs to implement the Buffered +/// This trait helps auto-generated buffer map structs to implement the Buffering /// trait. pub trait BufferMapStruct: Sized + Clone + 'static + Send + Sync { /// Produce a list of the buffers that exist in this layout. Implementing - /// this function alone is sufficient to implement the entire [`Buffered`] trait. + /// this function alone is sufficient to implement the entire [`Buffering`] trait. fn buffer_list(&self) -> SmallVec<[AnyBuffer; 8]>; } @@ -222,7 +222,7 @@ impl Bufferable for T { } } -impl Buffered for T { +impl Buffering for T { fn verify_scope(&self, scope: Entity) { for buffer in self.buffer_list() { assert_eq!(buffer.scope(), scope); @@ -288,12 +288,12 @@ impl Buffered for T { /// Each field in this struct needs to have the trait bounds `'static + Send + Sync`. /// /// This does not generally need to be implemented explicitly. Instead you should -/// use `#[derive(JoinedValue)]`: +/// use `#[derive(Joined)]`: /// /// ``` /// use bevy_impulse::prelude::*; /// -/// #[derive(JoinedValue)] +/// #[derive(Joined)] /// struct SomeValues { /// integer: i64, /// string: String, @@ -312,7 +312,7 @@ impl Buffered for T { /// ``` /// # use bevy_impulse::prelude::*; /// -/// #[derive(JoinedValue)] +/// #[derive(Joined)] /// #[joined(buffers_struct_name = SomeBuffers)] /// struct SomeValues { /// integer: i64, @@ -322,14 +322,14 @@ impl Buffered for T { /// /// By default each field of the generated buffers struct will have a type of /// [`Buffer`], but you can override this using `#[joined(buffer = ...)]` -/// to specify a special buffer type. For example if your `JoinedValue` struct +/// to specify a special buffer type. For example if your `Joined` struct /// contains an [`AnyMessageBox`] then by default the macro will use `Buffer`, /// but you probably really want it to have an [`AnyBuffer`]: /// /// ``` /// # use bevy_impulse::prelude::*; /// -/// #[derive(JoinedValue)] +/// #[derive(Joined)] /// struct SomeValues { /// integer: i64, /// string: String, @@ -342,11 +342,11 @@ impl Buffered for T { /// /// [1]: crate::Builder::join /// [2]: crate::Builder::try_join -pub trait JoinedValue: 'static + Send + Sync + Sized { +pub trait Joined: 'static + Send + Sync + Sized { /// This associated type must represent a buffer map layout that implements - /// the [`Joined`] trait. The message type yielded by [`Joined`] for this - /// associated type must match the [`JoinedValue`] type. - type Buffers: 'static + BufferMapLayout + Joined + Send + Sync; + /// the [`Joining`] trait. The message type yielded by [`Joining`] for this + /// associated type must match the [`Joined`] type. + type Buffers: 'static + BufferMapLayout + Joining + Send + Sync; /// Used by [`Builder::try_join`] fn try_join_from<'w, 's, 'a, 'b>( @@ -366,12 +366,12 @@ pub trait JoinedValue: 'static + Send + Sync + Sized { /// /// This does not generally need to be implemented explicitly. Instead you should /// define a struct where all fields are buffer keys and then apply -/// `#[derive(BufferKeyMap)]` to it, e.g.: +/// `#[derive(Accessor)]` to it, e.g.: /// /// ``` /// use bevy_impulse::prelude::*; /// -/// #[derive(Clone, BufferKeyMap)] +/// #[derive(Clone, Accessor)] /// struct SomeKeys { /// integer: BufferKey, /// string: BufferKey, @@ -386,7 +386,7 @@ pub trait JoinedValue: 'static + Send + Sync + Sized { /// ``` /// # use bevy_impulse::prelude::*; /// -/// #[derive(Clone, BufferKeyMap)] +/// #[derive(Clone, Accessor)] /// #[key(buffers_struct_name = SomeBuffers)] /// struct SomeKeys { /// integer: BufferKey, @@ -399,8 +399,8 @@ pub trait JoinedValue: 'static + Send + Sync + Sized { /// [2]: crate::Builder::create_buffer_access /// [3]: crate::Builder::try_listen /// [4]: crate::Builder::try_create_buffer_access -pub trait BufferKeyMap: 'static + Send + Sync + Sized + Clone { - type Buffers: 'static + BufferMapLayout + Accessed + Send + Sync; +pub trait Accessor: 'static + Send + Sync + Sized + Clone { + type Buffers: 'static + BufferMapLayout + Accessing + Send + Sync; fn try_listen_from<'w, 's, 'a, 'b>( buffers: &BufferMap, @@ -431,7 +431,7 @@ impl BufferMapStruct for BufferMap { } } -impl Joined for BufferMap { +impl Joining for BufferMap { type Item = HashMap, AnyMessageBox>; fn pull(&self, session: Entity, world: &mut World) -> Result { @@ -444,11 +444,11 @@ impl Joined for BufferMap { } } -impl JoinedValue for HashMap, AnyMessageBox> { +impl Joined for HashMap, AnyMessageBox> { type Buffers = BufferMap; } -impl Accessed for BufferMap { +impl Accessing for BufferMap { type Key = HashMap, AnyBufferKey>; fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key { @@ -489,7 +489,7 @@ impl Accessed for BufferMap { } } -impl JoinedValue for Vec { +impl Joined for Vec { type Buffers = Vec>; } @@ -508,7 +508,7 @@ impl BufferMapLayout for Vec } } -impl JoinedValue for SmallVec<[T; N]> { +impl Joined for SmallVec<[T; N]> { type Buffers = SmallVec<[Buffer; N]>; } @@ -533,7 +533,7 @@ impl BufferMapLa mod tests { use crate::{prelude::*, testing::*, AddBufferToMap, BufferMap}; - #[derive(JoinedValue)] + #[derive(Joined)] struct TestJoinedValue { integer: i64, float: f64, @@ -640,7 +640,7 @@ mod tests { assert!(context.no_unhandled_errors()); } - #[derive(Clone, JoinedValue)] + #[derive(Clone, Joined)] #[joined(buffers_struct_name = FooBuffers)] struct TestDeriveWithConfig {} @@ -655,7 +655,7 @@ mod tests { u: U, } - #[derive(JoinedValue)] + #[derive(Joined)] #[joined(buffers_struct_name = MultiGenericBuffers)] struct JoinedMultiGenericValue { #[joined(buffer = Buffer>)] @@ -714,7 +714,7 @@ mod tests { /// We create this struct just to verify that it is able to compile despite /// NonCopyBuffer not being copyable. - #[derive(JoinedValue)] + #[derive(Joined)] #[allow(unused)] struct JoinedValueForNonCopyBuffer { #[joined(buffer = NonCopyBuffer, noncopy_buffer)] @@ -722,7 +722,7 @@ mod tests { _b: u32, } - #[derive(Clone, BufferKeyMap)] + #[derive(Clone, Accessor)] #[key(buffers_struct_name = TestKeysBuffers)] struct TestKeys { integer: BufferKey, @@ -836,7 +836,7 @@ mod tests { /// This macro is a manual implementation of the join operation that uses /// the buffer listening mechanism. There isn't any reason to reimplement /// join here except so we can test that listening is working correctly for - /// BufferKeyMap. + /// Accessor. fn join_via_listen( In(keys): In>, world: &mut World, diff --git a/src/buffer/bufferable.rs b/src/buffer/bufferable.rs index 9b2a1549..daf55738 100644 --- a/src/buffer/bufferable.rs +++ b/src/buffer/bufferable.rs @@ -19,15 +19,15 @@ use bevy_utils::all_tuples; use smallvec::SmallVec; use crate::{ - Accessed, AddOperation, Buffer, BufferSettings, Buffered, Builder, Chain, CloneFromBuffer, - Join, Joined, Output, UnusedTarget, + Accessing, AddOperation, Buffer, BufferSettings, Buffering, Builder, Chain, CloneFromBuffer, + Join, Joining, Output, UnusedTarget, }; -pub type BufferKeys = <::BufferType as Accessed>::Key; -pub type JoinedItem = <::BufferType as Joined>::Item; +pub type BufferKeys = <::BufferType as Accessing>::Key; +pub type JoinedItem = <::BufferType as Joining>::Item; pub trait Bufferable { - type BufferType: Buffered; + type BufferType: Buffering; /// Convert these bufferable workflow elements into buffers if they are not /// buffers already. @@ -74,7 +74,7 @@ pub trait Joinable: Bufferable { impl Joinable for B where B: Bufferable, - B::BufferType: Joined, + B::BufferType: Joining, { type Item = JoinedItem; @@ -112,7 +112,7 @@ pub trait Accessible: Bufferable { impl Accessible for B where B: Bufferable, - B::BufferType: Accessed, + B::BufferType: Accessing, { type Keys = BufferKeys; @@ -160,7 +160,7 @@ impl Bufferable for Vec { } pub trait IterBufferable { - type BufferElement: Buffered + Joined; + type BufferElement: Buffering + Joining; /// Convert an iterable collection of bufferable workflow elements into /// buffers if they are not buffers already. @@ -177,11 +177,11 @@ pub trait IterBufferable { fn join_vec<'w, 's, 'a, 'b, const N: usize>( self, builder: &'b mut Builder<'w, 's, 'a>, - ) -> Chain<'w, 's, 'a, 'b, SmallVec<[::Item; N]>> + ) -> Chain<'w, 's, 'a, 'b, SmallVec<[::Item; N]>> where Self: Sized, Self::BufferElement: 'static + Send + Sync, - ::Item: 'static + Send + Sync, + ::Item: 'static + Send + Sync, { let buffers = self.into_buffer_vec::(builder); let join = builder.commands.spawn(()).id(); @@ -200,7 +200,7 @@ impl IterBufferable for T where T: IntoIterator, T::Item: Bufferable, - ::BufferType: Joined, + ::BufferType: Joining, { type BufferElement = ::BufferType; diff --git a/src/buffer/buffered.rs b/src/buffer/buffering.rs similarity index 94% rename from src/buffer/buffered.rs rename to src/buffer/buffering.rs index 8f6912d0..81eea5f1 100644 --- a/src/buffer/buffered.rs +++ b/src/buffer/buffering.rs @@ -29,7 +29,7 @@ use crate::{ ScopeSettings, SingleInputStorage, UnusedTarget, }; -pub trait Buffered: 'static + Send + Sync + Clone { +pub trait Buffering: 'static + Send + Sync + Clone { fn verify_scope(&self, scope: Entity); fn buffered_count(&self, session: Entity, world: &World) -> Result; @@ -49,7 +49,7 @@ pub trait Buffered: 'static + Send + Sync + Clone { fn ensure_active_session(&self, session: Entity, world: &mut World) -> OperationResult; } -pub trait Joined: Buffered { +pub trait Joining: Buffering { type Item: 'static + Send + Sync; fn pull(&self, session: Entity, world: &mut World) -> Result; @@ -58,7 +58,7 @@ pub trait Joined: Buffered { /// and join them into a tuple that gets sent to the target. /// /// If you need a more general way to get access to one or more buffers, - /// use [`listen`](Accessed::listen) instead. + /// use [`listen`](Accessing::listen) instead. fn join<'w, 's, 'a, 'b>( self, builder: &'b mut Builder<'w, 's, 'a>, @@ -78,7 +78,7 @@ pub trait Joined: Buffered { } } -pub trait Accessed: Buffered { +pub trait Accessing: Buffering { type Key: 'static + Send + Sync + Clone; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult; fn create_key(&self, builder: &BufferKeyBuilder) -> Self::Key; @@ -91,7 +91,7 @@ pub trait Accessed: Buffered { /// operates on those buffers. /// /// For an operation that simply joins the contents of two or more outputs - /// or buffers, use [`join`](Joined::join) instead. + /// or buffers, use [`join`](Joining::join) instead. fn listen<'w, 's, 'a, 'b>( self, builder: &'b mut Builder<'w, 's, 'a>, @@ -203,7 +203,7 @@ pub trait Accessed: Buffered { } } -impl Buffered for Buffer { +impl Buffering for Buffer { fn verify_scope(&self, scope: Entity) { assert_eq!(scope, self.scope()); } @@ -242,7 +242,7 @@ impl Buffered for Buffer { } } -impl Joined for Buffer { +impl Joining for Buffer { type Item = T; fn pull(&self, session: Entity, world: &mut World) -> Result { world @@ -252,7 +252,7 @@ impl Joined for Buffer { } } -impl Accessed for Buffer { +impl Accessing for Buffer { type Key = BufferKey; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { world @@ -275,7 +275,7 @@ impl Accessed for Buffer { } } -impl Buffered for CloneFromBuffer { +impl Buffering for CloneFromBuffer { fn verify_scope(&self, scope: Entity) { assert_eq!(scope, self.scope()); } @@ -313,7 +313,7 @@ impl Buffered for CloneFromBuffer { } } -impl Joined for CloneFromBuffer { +impl Joining for CloneFromBuffer { type Item = T; fn pull(&self, session: Entity, world: &mut World) -> Result { world @@ -324,7 +324,7 @@ impl Joined for CloneFromBuffer { } } -impl Accessed for CloneFromBuffer { +impl Accessing for CloneFromBuffer { type Key = BufferKey; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { world @@ -350,7 +350,7 @@ impl Accessed for CloneFromBuffer { macro_rules! impl_buffered_for_tuple { ($(($T:ident, $K:ident)),*) => { #[allow(non_snake_case)] - impl<$($T: Buffered),*> Buffered for ($($T,)*) + impl<$($T: Buffering),*> Buffering for ($($T,)*) { fn verify_scope(&self, scope: Entity) { let ($($T,)*) = self; @@ -421,7 +421,7 @@ macro_rules! impl_buffered_for_tuple { } #[allow(non_snake_case)] - impl<$($T: Joined),*> Joined for ($($T,)*) + impl<$($T: Joining),*> Joining for ($($T,)*) { type Item = ($($T::Item),*); fn pull( @@ -437,7 +437,7 @@ macro_rules! impl_buffered_for_tuple { } #[allow(non_snake_case)] - impl<$($T: Accessed),*> Accessed for ($($T,)*) + impl<$($T: Accessing),*> Accessing for ($($T,)*) { type Key = ($($T::Key), *); fn add_accessor( @@ -479,11 +479,11 @@ macro_rules! impl_buffered_for_tuple { } } -// Implements the `Buffered` trait for all tuples between size 2 and 12 -// (inclusive) made of types that implement `Buffered` +// Implements the `Buffering` trait for all tuples between size 2 and 12 +// (inclusive) made of types that implement `Buffering` all_tuples!(impl_buffered_for_tuple, 2, 12, T, K); -impl Buffered for [T; N] { +impl Buffering for [T; N] { fn verify_scope(&self, scope: Entity) { for buffer in self.iter() { buffer.verify_scope(scope); @@ -535,7 +535,7 @@ impl Buffered for [T; N] { } } -impl Joined for [T; N] { +impl Joining for [T; N] { // TODO(@mxgrey) We may be able to use [T::Item; N] here instead of SmallVec // when try_map is stabilized: https://github.com/rust-lang/rust/issues/79711 type Item = SmallVec<[T::Item; N]>; @@ -546,7 +546,7 @@ impl Joined for [T; N] { } } -impl Accessed for [T; N] { +impl Accessing for [T; N] { type Key = SmallVec<[T::Key; N]>; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { for buffer in self { @@ -582,7 +582,7 @@ impl Accessed for [T; N] { } } -impl Buffered for SmallVec<[T; N]> { +impl Buffering for SmallVec<[T; N]> { fn verify_scope(&self, scope: Entity) { for buffer in self.iter() { buffer.verify_scope(scope); @@ -634,7 +634,7 @@ impl Buffered for SmallVec<[T; N]> { } } -impl Joined for SmallVec<[T; N]> { +impl Joining for SmallVec<[T; N]> { type Item = SmallVec<[T::Item; N]>; fn pull(&self, session: Entity, world: &mut World) -> Result { self.iter() @@ -643,7 +643,7 @@ impl Joined for SmallVec<[T; N]> { } } -impl Accessed for SmallVec<[T; N]> { +impl Accessing for SmallVec<[T; N]> { type Key = SmallVec<[T::Key; N]>; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { for buffer in self { @@ -679,7 +679,7 @@ impl Accessed for SmallVec<[T; N]> { } } -impl Buffered for Vec { +impl Buffering for Vec { fn verify_scope(&self, scope: Entity) { for buffer in self { buffer.verify_scope(scope); @@ -731,7 +731,7 @@ impl Buffered for Vec { } } -impl Joined for Vec { +impl Joining for Vec { type Item = Vec; fn pull(&self, session: Entity, world: &mut World) -> Result { self.iter() @@ -740,7 +740,7 @@ impl Joined for Vec { } } -impl Accessed for Vec { +impl Accessing for Vec { type Key = Vec; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { for buffer in self { diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 80d768c4..9a9a5879 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -36,11 +36,11 @@ pub use serde_json::Value as JsonMessage; use smallvec::SmallVec; use crate::{ - add_listener_to_source, Accessed, AnyBuffer, AnyBufferAccessInterface, AnyBufferKey, AnyRange, + add_listener_to_source, Accessing, AnyBuffer, AnyBufferAccessInterface, AnyBufferKey, AnyRange, AsAnyBuffer, Buffer, BufferAccessMut, BufferAccessors, BufferError, BufferIdentifier, BufferKey, BufferKeyBuilder, BufferKeyLifecycle, BufferKeyTag, BufferLocation, BufferMap, - BufferMapLayout, BufferMapStruct, BufferStorage, Bufferable, Buffered, Builder, DrainBuffer, - Gate, GateState, IncompatibleLayout, InspectBuffer, Joined, JoinedValue, ManageBuffer, + BufferMapLayout, BufferMapStruct, BufferStorage, Bufferable, Buffering, Builder, DrainBuffer, + Gate, GateState, IncompatibleLayout, InspectBuffer, Joined, Joining, ManageBuffer, NotifyBufferUpdate, OperationError, OperationResult, OrBroken, }; @@ -988,7 +988,7 @@ impl Bufferable for JsonBuffer { } } -impl Buffered for JsonBuffer { +impl Buffering for JsonBuffer { fn verify_scope(&self, scope: Entity) { assert_eq!(scope, self.scope()); } @@ -1022,7 +1022,7 @@ impl Buffered for JsonBuffer { } } -impl Joined for JsonBuffer { +impl Joining for JsonBuffer { type Item = JsonMessage; fn pull(&self, session: Entity, world: &mut World) -> Result { let mut buffer_mut = world.get_entity_mut(self.id()).or_broken()?; @@ -1030,7 +1030,7 @@ impl Joined for JsonBuffer { } } -impl Accessed for JsonBuffer { +impl Accessing for JsonBuffer { type Key = JsonBufferKey; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { world @@ -1056,7 +1056,7 @@ impl Accessed for JsonBuffer { } } -impl JoinedValue for serde_json::Map { +impl Joined for serde_json::Map { type Buffers = HashMap; } @@ -1092,7 +1092,7 @@ impl BufferMapStruct for HashMap { } } -impl Joined for HashMap { +impl Joining for HashMap { type Item = serde_json::Map; fn pull(&self, session: Entity, world: &mut World) -> Result { self.iter() @@ -1411,7 +1411,7 @@ mod tests { assert!(context.no_unhandled_errors()); } - #[derive(Clone, JoinedValue)] + #[derive(Clone, Joined)] #[joined(buffers_struct_name = TestJoinedValueJsonBuffers)] struct TestJoinedValueJson { integer: i64, @@ -1582,9 +1582,9 @@ mod tests { assert_eq!(values[3], serde_json::to_value(TestMessage::new()).unwrap()); } - // We define this struct just to make sure the BufferKeyMap macro successfully + // We define this struct just to make sure the Accessor macro successfully // compiles with JsonBufferKey. - #[derive(Clone, BufferKeyMap)] + #[derive(Clone, Accessor)] #[allow(unused)] struct TestJsonKeyMap { integer: BufferKey, diff --git a/src/builder.rs b/src/builder.rs index ba38568e..45727868 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -22,10 +22,10 @@ use std::future::Future; use smallvec::SmallVec; use crate::{ - Accessed, Accessible, AddOperation, AsMap, Buffer, BufferKeyMap, BufferKeys, BufferLocation, - BufferMap, BufferSettings, Bufferable, Buffered, Chain, Collect, ForkClone, ForkCloneOutput, + Accessible, Accessing, Accessor, AddOperation, AsMap, Buffer, BufferKeys, BufferLocation, + BufferMap, BufferSettings, Bufferable, Buffering, Chain, Collect, ForkClone, ForkCloneOutput, ForkTargetStorage, Gate, GateRequest, IncompatibleLayout, Injection, InputSlot, IntoAsyncMap, - IntoBlockingMap, Joinable, JoinedValue, Node, OperateBuffer, OperateDynamicGate, OperateScope, + IntoBlockingMap, Joinable, Joined, Node, OperateBuffer, OperateDynamicGate, OperateScope, OperateSplit, OperateStaticGate, Output, Provider, RequestOfMap, ResponseOfMap, Scope, ScopeEndpoints, ScopeSettings, ScopeSettingsStorage, Sendish, Service, SplitOutputs, Splittable, StreamPack, StreamTargetMap, StreamsOfMap, Trim, TrimBranch, UnusedTarget, @@ -237,11 +237,11 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { } /// Try joining a map of buffers into a single value. - pub fn try_join<'b, Joined: JoinedValue>( + pub fn try_join<'b, J: Joined>( &'b mut self, buffers: &BufferMap, - ) -> Result, IncompatibleLayout> { - Joined::try_join_from(buffers, self) + ) -> Result, IncompatibleLayout> { + J::try_join_from(buffers, self) } /// Alternative way of calling [`Accessible::listen`]. @@ -250,7 +250,7 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { } /// Try listening to a map of buffers. - pub fn try_listen<'b, Keys: BufferKeyMap>( + pub fn try_listen<'b, Keys: Accessor>( &'b mut self, buffers: &BufferMap, ) -> Result, IncompatibleLayout> { @@ -269,7 +269,7 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { buffers: B, ) -> Node)> where - B::BufferType: Accessed, + B::BufferType: Accessing, T: 'static + Send + Sync, { let buffers = buffers.into_buffer(self); @@ -279,7 +279,7 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { /// Try to create access to some buffers. Same as [`Self::create_buffer_access`] /// except it will return an error if the buffers in the [`BufferMap`] are not /// compatible with the keys that are being asked for. - pub fn try_create_buffer_access( + pub fn try_create_buffer_access( &mut self, buffers: &BufferMap, ) -> Result, IncompatibleLayout> @@ -393,7 +393,7 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { build: impl FnOnce(Scope, (), ()>, &mut Builder) -> Settings, ) where B: Bufferable, - B::BufferType: Accessed, + B::BufferType: Accessing, Settings: Into, { from_buffers.into_buffer(self).on_cleanup(self, build); @@ -418,7 +418,7 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { build: impl FnOnce(Scope, (), ()>, &mut Builder) -> Settings, ) where B: Bufferable, - B::BufferType: Accessed, + B::BufferType: Accessing, Settings: Into, { from_buffers.into_buffer(self).on_cancel(self, build); @@ -437,7 +437,7 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { build: impl FnOnce(Scope, (), ()>, &mut Builder) -> Settings, ) where B: Bufferable, - B::BufferType: Accessed, + B::BufferType: Accessing, Settings: Into, { from_buffers.into_buffer(self).on_terminate(self, build); @@ -453,7 +453,7 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> { build: impl FnOnce(Scope, (), ()>, &mut Builder) -> Settings, ) where B: Bufferable, - B::BufferType: Accessed, + B::BufferType: Accessing, Settings: Into, { from_buffers diff --git a/src/chain.rs b/src/chain.rs index 218ed243..fbdee9f6 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -24,12 +24,12 @@ use smallvec::SmallVec; use std::error::Error; use crate::{ - make_option_branching, make_result_branching, Accessed, AddOperation, AsMap, Buffer, BufferKey, - BufferKeys, Bufferable, Buffered, Builder, Collect, CreateCancelFilter, CreateDisposalFilter, - ForkTargetStorage, Gate, GateRequest, InputSlot, IntoAsyncMap, IntoBlockingCallback, - IntoBlockingMap, Node, Noop, OperateBufferAccess, OperateDynamicGate, OperateSplit, - OperateStaticGate, Output, ProvideOnce, Provider, Scope, ScopeSettings, Sendish, Service, - Spread, StreamOf, StreamPack, StreamTargetMap, Trim, TrimBranch, UnusedTarget, + make_option_branching, make_result_branching, Accessing, AddOperation, AsMap, Buffer, + BufferKey, BufferKeys, Bufferable, Buffering, Builder, Collect, CreateCancelFilter, + CreateDisposalFilter, ForkTargetStorage, Gate, GateRequest, InputSlot, IntoAsyncMap, + IntoBlockingCallback, IntoBlockingMap, Node, Noop, OperateBufferAccess, OperateDynamicGate, + OperateSplit, OperateStaticGate, Output, ProvideOnce, Provider, Scope, ScopeSettings, Sendish, + Service, Spread, StreamOf, StreamPack, StreamTargetMap, Trim, TrimBranch, UnusedTarget, }; pub mod fork_clone_builder; @@ -302,7 +302,7 @@ impl<'w, 's, 'a, 'b, T: 'static + Send + Sync> Chain<'w, 's, 'a, 'b, T> { pub fn with_access(self, buffers: B) -> Chain<'w, 's, 'a, 'b, (T, BufferKeys)> where B: Bufferable, - B::BufferType: Accessed, + B::BufferType: Accessing, { let buffers = buffers.into_buffer(self.builder); buffers.verify_scope(self.builder.scope); @@ -323,7 +323,7 @@ impl<'w, 's, 'a, 'b, T: 'static + Send + Sync> Chain<'w, 's, 'a, 'b, T> { pub fn then_access(self, buffers: B) -> Chain<'w, 's, 'a, 'b, BufferKeys> where B: Bufferable, - B::BufferType: Accessed, + B::BufferType: Accessing, { self.with_access(buffers).map_block(|(_, key)| key) } diff --git a/src/lib.rs b/src/lib.rs index 47213e8c..ceab07f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -340,10 +340,10 @@ impl Plugin for ImpulsePlugin { pub mod prelude { pub use crate::{ buffer::{ - Accessible, AnyBuffer, AnyBufferKey, AnyBufferMut, AnyBufferWorldAccess, AnyMessageBox, - AsAnyBuffer, Buffer, BufferAccess, BufferAccessMut, BufferKey, BufferKeyMap, BufferMap, - BufferMapLayout, BufferSettings, BufferWorldAccess, Bufferable, Buffered, - IncompatibleLayout, IterBufferable, Joinable, JoinedValue, RetentionPolicy, + Accessible, Accessor, AnyBuffer, AnyBufferKey, AnyBufferMut, AnyBufferWorldAccess, + AnyMessageBox, AsAnyBuffer, Buffer, BufferAccess, BufferAccessMut, BufferKey, + BufferMap, BufferMapLayout, BufferSettings, BufferWorldAccess, Bufferable, Buffering, + IncompatibleLayout, IterBufferable, Joinable, Joined, RetentionPolicy, }, builder::Builder, callback::{AsCallback, Callback, IntoAsyncCallback, IntoBlockingCallback}, diff --git a/src/operation/cleanup.rs b/src/operation/cleanup.rs index 6c11c72b..8bac864e 100644 --- a/src/operation/cleanup.rs +++ b/src/operation/cleanup.rs @@ -16,7 +16,7 @@ */ use crate::{ - Accessed, BufferAccessStorage, ManageDisposal, ManageInput, MiscellaneousFailure, + Accessing, BufferAccessStorage, ManageDisposal, ManageInput, MiscellaneousFailure, OperationError, OperationResult, OperationRoster, OrBroken, ScopeStorage, UnhandledErrors, }; @@ -98,7 +98,7 @@ impl<'a> OperationCleanup<'a> { pub fn cleanup_buffer_access(&mut self) -> OperationResult where - B: Accessed + 'static + Send + Sync, + B: Accessing + 'static + Send + Sync, B::Key: 'static + Send + Sync, { let scope = self diff --git a/src/operation/join.rs b/src/operation/join.rs index 3bd8bafd..314e64b8 100644 --- a/src/operation/join.rs +++ b/src/operation/join.rs @@ -18,7 +18,7 @@ use bevy_ecs::prelude::{Component, Entity}; use crate::{ - FunnelInputStorage, Input, InputBundle, Joined, ManageInput, Operation, OperationCleanup, + FunnelInputStorage, Input, InputBundle, Joining, ManageInput, Operation, OperationCleanup, OperationError, OperationReachability, OperationRequest, OperationResult, OperationSetup, OrBroken, ReachabilityResult, SingleInputStorage, SingleTargetStorage, }; @@ -37,7 +37,7 @@ impl Join { #[derive(Component)] struct BufferStorage(Buffers); -impl Operation for Join +impl Operation for Join where Buffers::Item: 'static + Send + Sync, { diff --git a/src/operation/listen.rs b/src/operation/listen.rs index 85561c2a..378fe9f5 100644 --- a/src/operation/listen.rs +++ b/src/operation/listen.rs @@ -18,7 +18,7 @@ use bevy_ecs::prelude::Entity; use crate::{ - buffer_key_usage, get_access_keys, Accessed, BufferAccessStorage, BufferKeyUsage, + buffer_key_usage, get_access_keys, Accessing, BufferAccessStorage, BufferKeyUsage, FunnelInputStorage, Input, InputBundle, ManageInput, Operation, OperationCleanup, OperationReachability, OperationRequest, OperationResult, OperationSetup, OrBroken, ReachabilityResult, SingleInputStorage, SingleTargetStorage, @@ -37,7 +37,7 @@ impl Listen { impl Operation for Listen where - B: Accessed + 'static + Send + Sync, + B: Accessing + 'static + Send + Sync, B::Key: 'static + Send + Sync, { fn setup(self, OperationSetup { source, world }: OperationSetup) -> OperationResult { diff --git a/src/operation/operate_buffer_access.rs b/src/operation/operate_buffer_access.rs index c4cf0cb3..07208410 100644 --- a/src/operation/operate_buffer_access.rs +++ b/src/operation/operate_buffer_access.rs @@ -25,7 +25,7 @@ use std::{ use smallvec::SmallVec; use crate::{ - Accessed, BufferKeyBuilder, ChannelQueue, Input, InputBundle, ManageInput, Operation, + Accessing, BufferKeyBuilder, ChannelQueue, Input, InputBundle, ManageInput, Operation, OperationCleanup, OperationError, OperationReachability, OperationRequest, OperationResult, OperationSetup, OrBroken, ReachabilityResult, ScopeStorage, SingleInputStorage, SingleTargetStorage, @@ -34,7 +34,7 @@ use crate::{ pub(crate) struct OperateBufferAccess where T: 'static + Send + Sync, - B: Accessed, + B: Accessing, { buffers: B, target: Entity, @@ -44,7 +44,7 @@ where impl OperateBufferAccess where T: 'static + Send + Sync, - B: Accessed, + B: Accessing, { pub(crate) fn new(buffers: B, target: Entity) -> Self { Self { @@ -59,12 +59,12 @@ where pub struct BufferKeyUsage(pub(crate) fn(Entity, Entity, &World) -> ReachabilityResult); #[derive(Component)] -pub(crate) struct BufferAccessStorage { +pub(crate) struct BufferAccessStorage { pub(crate) buffers: B, pub(crate) keys: HashMap, } -impl BufferAccessStorage { +impl BufferAccessStorage { pub(crate) fn new(buffers: B) -> Self { Self { buffers, @@ -76,7 +76,7 @@ impl BufferAccessStorage { impl Operation for OperateBufferAccess where T: 'static + Send + Sync, - B: Accessed + 'static + Send + Sync, + B: Accessing + 'static + Send + Sync, B::Key: 'static + Send + Sync, { fn setup(self, OperationSetup { source, world }: OperationSetup) -> OperationResult { @@ -138,7 +138,7 @@ pub(crate) fn get_access_keys( world: &mut World, ) -> Result where - B: Accessed + 'static + Send + Sync, + B: Accessing + 'static + Send + Sync, B::Key: 'static + Send + Sync, { let scope = world.get::(source).or_broken()?.get(); @@ -180,7 +180,7 @@ pub(crate) fn buffer_key_usage( world: &World, ) -> ReachabilityResult where - B: Accessed + 'static + Send + Sync, + B: Accessing + 'static + Send + Sync, B::Key: 'static + Send + Sync, { let key = world diff --git a/src/operation/operate_gate.rs b/src/operation/operate_gate.rs index 9a677e37..0a45d449 100644 --- a/src/operation/operate_gate.rs +++ b/src/operation/operate_gate.rs @@ -18,7 +18,7 @@ use bevy_ecs::prelude::{Component, Entity}; use crate::{ - emit_disposal, Buffered, Disposal, Gate, GateRequest, Input, InputBundle, ManageInput, + emit_disposal, Buffering, Disposal, Gate, GateRequest, Input, InputBundle, ManageInput, Operation, OperationCleanup, OperationReachability, OperationRequest, OperationResult, OperationSetup, OrBroken, ReachabilityResult, SingleInputStorage, SingleTargetStorage, }; @@ -48,7 +48,7 @@ impl OperateDynamicGate { impl Operation for OperateDynamicGate where T: 'static + Send + Sync, - B: Buffered + 'static + Send + Sync, + B: Buffering + 'static + Send + Sync, { fn setup(self, OperationSetup { source, world }: OperationSetup) -> OperationResult { world @@ -144,7 +144,7 @@ impl OperateStaticGate { impl Operation for OperateStaticGate where - B: Buffered + 'static + Send + Sync, + B: Buffering + 'static + Send + Sync, T: 'static + Send + Sync, { fn setup(self, OperationSetup { source, world }: OperationSetup) -> OperationResult { diff --git a/src/operation/scope.rs b/src/operation/scope.rs index deb46cf1..f757653b 100644 --- a/src/operation/scope.rs +++ b/src/operation/scope.rs @@ -16,7 +16,7 @@ */ use crate::{ - check_reachability, execute_operation, is_downstream_of, Accessed, AddOperation, Blocker, + check_reachability, execute_operation, is_downstream_of, Accessing, AddOperation, Blocker, BufferKeyBuilder, Cancel, Cancellable, Cancellation, Cleanup, CleanupContents, ClearBufferFn, CollectMarker, DisposalListener, DisposalUpdate, FinalizeCleanup, FinalizeCleanupRequest, Input, InputBundle, InspectDisposals, ManageCancellation, ManageInput, Operation, @@ -1124,7 +1124,7 @@ impl BeginCleanupWorkflow { impl Operation for BeginCleanupWorkflow where - B: Accessed + 'static + Send + Sync, + B: Accessing + 'static + Send + Sync, B::Key: 'static + Send + Sync, { fn setup(self, OperationSetup { source, world }: OperationSetup) -> OperationResult { diff --git a/src/testing.rs b/src/testing.rs index 2982eb91..f7ba4664 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -32,10 +32,10 @@ pub use std::time::{Duration, Instant}; use smallvec::SmallVec; use crate::{ - flush_impulses, Accessed, AddContinuousServicesExt, AnyBuffer, AsAnyBuffer, AsyncServiceInput, - BlockingMap, BlockingServiceInput, Buffer, BufferKey, BufferKeyLifecycle, Bufferable, Buffered, - Builder, ContinuousQuery, ContinuousQueueView, ContinuousService, FlushParameters, - GetBufferedSessionsFn, Joined, OperationError, OperationResult, OperationRoster, Promise, + flush_impulses, Accessing, AddContinuousServicesExt, AnyBuffer, AsAnyBuffer, AsyncServiceInput, + BlockingMap, BlockingServiceInput, Buffer, BufferKey, BufferKeyLifecycle, Bufferable, + Buffering, Builder, ContinuousQuery, ContinuousQueueView, ContinuousService, FlushParameters, + GetBufferedSessionsFn, Joining, OperationError, OperationResult, OperationRoster, Promise, RunCommandsOnWorldExt, Scope, Service, SpawnWorkflowExt, StreamOf, StreamPack, UnhandledErrors, WorkflowSettings, }; @@ -524,7 +524,7 @@ impl Bufferable for NonCopyBuffer { } } -impl Buffered for NonCopyBuffer { +impl Buffering for NonCopyBuffer { fn add_listener(&self, listener: Entity, world: &mut World) -> OperationResult { self.inner.add_listener(listener, world) } @@ -556,14 +556,14 @@ impl Buffered for NonCopyBuffer { } } -impl Joined for NonCopyBuffer { +impl Joining for NonCopyBuffer { type Item = T; fn pull(&self, session: Entity, world: &mut World) -> Result { self.inner.pull(session, world) } } -impl Accessed for NonCopyBuffer { +impl Accessing for NonCopyBuffer { type Key = BufferKey; fn add_accessor(&self, accessor: Entity, world: &mut World) -> OperationResult { self.inner.add_accessor(accessor, world) From 911a5e17e9baf77f4df648a943aa567fa3d022bb Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Fri, 21 Feb 2025 11:06:27 +0800 Subject: [PATCH 35/37] Tighten up leaks Signed-off-by: Michael X. Grey --- src/buffer/any_buffer.rs | 6 +++-- src/buffer/json_buffer.rs | 51 +++++++++++++++++++++------------------ 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 3038c3a6..efde9907 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -81,6 +81,8 @@ impl AnyBuffer { > = OnceLock::new(); let interfaces = INTERFACE_MAP.get_or_init(|| Mutex::default()); + // SAFETY: This will leak memory exactly once per type, so the leakage is bounded. + // Leaking this allows the interface to be shared freely across all instances. let mut interfaces_mut = interfaces.lock().unwrap(); *interfaces_mut .entry(TypeId::of::()) @@ -940,7 +942,7 @@ impl AnyBufferAccessInterface for AnyBufferAcces let mut downcasts = self.buffer_downcasts.lock().unwrap(); if let Entry::Vacant(entry) = downcasts.entry(buffer_type) { - // We should only leak this into the register once per type + // SAFETY: We only leak this into the register once per type entry.insert(Box::leak(f)); } } @@ -957,7 +959,7 @@ impl AnyBufferAccessInterface for AnyBufferAcces let mut downcasts = self.key_downcasts.lock().unwrap(); if let Entry::Vacant(entry) = downcasts.entry(key_type) { - // We should only leak this in to the register once per type + // SAFTY: We only leak this in to the register once per type entry.insert(Box::leak(f)); } } diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 9a9a5879..e65c9b50 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -816,28 +816,6 @@ struct JsonBufferAccessImpl(std::marker::PhantomData); impl JsonBufferAccessImpl { pub(crate) fn get_interface() -> &'static (dyn JsonBufferAccessInterface + Send + Sync) { - // Register downcasting for JsonBuffer and JsonBufferKey - let any_interface = AnyBuffer::interface_for::(); - any_interface.register_buffer_downcast( - TypeId::of::(), - Box::new(|location| { - Box::new(JsonBuffer { - location, - interface: Self::get_interface(), - }) - }), - ); - - any_interface.register_key_downcast( - TypeId::of::(), - Box::new(|tag| { - Box::new(JsonBufferKey { - tag, - interface: Self::get_interface(), - }) - }), - ); - // Create and cache the json buffer access interface static INTERFACE_MAP: OnceLock< Mutex>, @@ -847,7 +825,34 @@ impl JsonBufferAccessIm let mut interfaces_mut = interfaces.lock().unwrap(); *interfaces_mut .entry(TypeId::of::()) - .or_insert_with(|| Box::leak(Box::new(JsonBufferAccessImpl::(Default::default())))) + .or_insert_with(|| { + // Register downcasting for JsonBuffer and JsonBufferKey the + // first time that we retrieve an interface for this type. + let any_interface = AnyBuffer::interface_for::(); + any_interface.register_buffer_downcast( + TypeId::of::(), + Box::new(|location| { + Box::new(JsonBuffer { + location, + interface: Self::get_interface(), + }) + }), + ); + + any_interface.register_key_downcast( + TypeId::of::(), + Box::new(|tag| { + Box::new(JsonBufferKey { + tag, + interface: Self::get_interface(), + }) + }), + ); + + // SAFETY: This will leak memory exactly once per type, so the leakage is bounded. + // Leaking this allows the interface to be shared freely across all instances. + Box::leak(Box::new(JsonBufferAccessImpl::(Default::default()))) + }) } } From 8a7603c82a030ac2ee9536d33b06bfd85e9e944a Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Fri, 21 Feb 2025 11:06:53 +0800 Subject: [PATCH 36/37] Fix style Signed-off-by: Michael X. Grey --- src/buffer/json_buffer.rs | 58 +++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index e65c9b50..5738f7ed 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -823,36 +823,34 @@ impl JsonBufferAccessIm let interfaces = INTERFACE_MAP.get_or_init(|| Mutex::default()); let mut interfaces_mut = interfaces.lock().unwrap(); - *interfaces_mut - .entry(TypeId::of::()) - .or_insert_with(|| { - // Register downcasting for JsonBuffer and JsonBufferKey the - // first time that we retrieve an interface for this type. - let any_interface = AnyBuffer::interface_for::(); - any_interface.register_buffer_downcast( - TypeId::of::(), - Box::new(|location| { - Box::new(JsonBuffer { - location, - interface: Self::get_interface(), - }) - }), - ); - - any_interface.register_key_downcast( - TypeId::of::(), - Box::new(|tag| { - Box::new(JsonBufferKey { - tag, - interface: Self::get_interface(), - }) - }), - ); - - // SAFETY: This will leak memory exactly once per type, so the leakage is bounded. - // Leaking this allows the interface to be shared freely across all instances. - Box::leak(Box::new(JsonBufferAccessImpl::(Default::default()))) - }) + *interfaces_mut.entry(TypeId::of::()).or_insert_with(|| { + // Register downcasting for JsonBuffer and JsonBufferKey the + // first time that we retrieve an interface for this type. + let any_interface = AnyBuffer::interface_for::(); + any_interface.register_buffer_downcast( + TypeId::of::(), + Box::new(|location| { + Box::new(JsonBuffer { + location, + interface: Self::get_interface(), + }) + }), + ); + + any_interface.register_key_downcast( + TypeId::of::(), + Box::new(|tag| { + Box::new(JsonBufferKey { + tag, + interface: Self::get_interface(), + }) + }), + ); + + // SAFETY: This will leak memory exactly once per type, so the leakage is bounded. + // Leaking this allows the interface to be shared freely across all instances. + Box::leak(Box::new(JsonBufferAccessImpl::(Default::default()))) + }) } } From 530c92f84eedcf012d588d6ff090b23ab9012dc9 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Fri, 21 Feb 2025 11:27:05 +0800 Subject: [PATCH 37/37] Iterate on require buffer functions Signed-off-by: Michael X. Grey --- macros/src/buffer.rs | 2 +- src/buffer/buffer_map.rs | 79 +++++++++++++++++++++------------------ src/buffer/json_buffer.rs | 2 +- 3 files changed, 44 insertions(+), 39 deletions(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index 4d716948..b231da09 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -380,7 +380,7 @@ fn impl_buffer_map_layout( fn try_from_buffer_map(buffers: &::bevy_impulse::BufferMap) -> Result { let mut compatibility = ::bevy_impulse::IncompatibleLayout::default(); #( - let #field_ident = compatibility.require_buffer_by_literal::<#buffer>(#map_key, buffers); + let #field_ident = compatibility.require_buffer_for_identifier::<#buffer>(#map_key, buffers); )* // Unwrap the Ok after inspecting every field so that the diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 50a1d78a..04fe4ea7 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -58,12 +58,18 @@ impl BufferIdentifier<'static> { } } -impl From<&'static str> for BufferIdentifier<'static> { - fn from(value: &'static str) -> Self { +impl<'a> From<&'a str> for BufferIdentifier<'a> { + fn from(value: &'a str) -> Self { BufferIdentifier::Name(Cow::Borrowed(value)) } } +impl<'a> From for BufferIdentifier<'a> { + fn from(value: usize) -> Self { + BufferIdentifier::Index(value) + } +} + pub type BufferMap = HashMap, AnyBuffer>; /// Extension trait that makes it more convenient to insert buffers into a [`BufferMap`]. @@ -115,70 +121,69 @@ impl IncompatibleLayout { Ok(()) } - /// Same as [`Self::require_buffer_by_literal`], but can be used with - /// temporary borrows of a string slice. The string slice will be cloned if - /// an error message needs to be produced. - pub fn require_buffer_by_name( + /// Check whether the buffer associated with the identifier is compatible with + /// the required buffer type. You can pass in a `&static str` or a `usize` + /// directly as the identifier. + /// + /// ``` + /// # use bevy_impulse::prelude::*; + /// + /// let buffer_map = BufferMap::default(); + /// let mut compatibility = IncompatibleLayout::default(); + /// let buffer = compatibility.require_buffer_for_identifier::>("some_field", &buffer_map); + /// assert!(buffer.is_err()); + /// assert!(compatibility.as_result().is_err()); + /// + /// let mut compatibility = IncompatibleLayout::default(); + /// let buffer = compatibility.require_buffer_for_identifier::>(10, &buffer_map); + /// assert!(buffer.is_err()); + /// assert!(compatibility.as_result().is_err()); + /// ``` + pub fn require_buffer_for_identifier( &mut self, - expected_name: &str, + identifier: impl Into>, buffers: &BufferMap, ) -> Result { - let identifier = BufferIdentifier::Name(Cow::Borrowed(expected_name)); + let identifier = identifier.into(); if let Some(buffer) = buffers.get(&identifier) { if let Some(buffer) = buffer.downcast_buffer::() { return Ok(buffer); } else { self.incompatible_buffers.push(BufferIncompatibility { - identifier: BufferIdentifier::Name(Cow::Owned(expected_name.to_owned())), + identifier, expected: std::any::type_name::(), received: buffer.message_type_name(), }); } } else { - self.missing_buffers - .push(BufferIdentifier::Name(Cow::Owned(expected_name.to_owned()))); + self.missing_buffers.push(identifier); } Err(()) } - /// Check whether a named buffer is compatible with the required buffer type. - pub fn require_buffer_by_literal( - &mut self, - expected_name: &'static str, - buffers: &BufferMap, - ) -> Result { - self.require_buffer::(BufferIdentifier::literal_name(expected_name), buffers) - } - - /// Check whether an indexed buffer is compatible with the required buffer type. - pub fn require_buffer_by_index( - &mut self, - expected_index: usize, - buffers: &BufferMap, - ) -> Result { - self.require_buffer::(BufferIdentifier::Index(expected_index), buffers) - } - - /// Check whether the buffer associated with the identifier is compatible with - /// the required buffer type. - pub fn require_buffer( + /// Same as [`Self::require_buffer_for_identifier`], but can be used with + /// temporary borrows of a string slice. The string slice will be cloned if + /// an error message needs to be produced. + pub fn require_buffer_for_borrowed_name( &mut self, - identifier: BufferIdentifier<'static>, + expected_name: &str, buffers: &BufferMap, ) -> Result { + let identifier = BufferIdentifier::Name(Cow::Borrowed(expected_name)); if let Some(buffer) = buffers.get(&identifier) { if let Some(buffer) = buffer.downcast_buffer::() { return Ok(buffer); } else { self.incompatible_buffers.push(BufferIncompatibility { - identifier, + identifier: BufferIdentifier::Name(Cow::Owned(expected_name.to_owned())), expected: std::any::type_name::(), received: buffer.message_type_name(), }); } } else { - self.missing_buffers.push(identifier); + self.missing_buffers + .push(BufferIdentifier::Name(Cow::Owned(expected_name.to_owned()))); } Err(()) @@ -498,7 +503,7 @@ impl BufferMapLayout for Vec let mut downcast_buffers = Vec::new(); let mut compatibility = IncompatibleLayout::default(); for i in 0..buffers.len() { - if let Ok(downcast) = compatibility.require_buffer_by_index::(i, buffers) { + if let Ok(downcast) = compatibility.require_buffer_for_identifier::(i, buffers) { downcast_buffers.push(downcast); } } @@ -519,7 +524,7 @@ impl BufferMapLa let mut downcast_buffers = SmallVec::new(); let mut compatibility = IncompatibleLayout::default(); for i in 0..buffers.len() { - if let Ok(downcast) = compatibility.require_buffer_by_index::(i, buffers) { + if let Ok(downcast) = compatibility.require_buffer_for_identifier::(i, buffers) { downcast_buffers.push(downcast); } } diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 5738f7ed..a3eba1ff 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -1071,7 +1071,7 @@ impl BufferMapLayout for HashMap { match name { BufferIdentifier::Name(name) => { if let Ok(downcast) = - compatibility.require_buffer_by_name::(&name, buffers) + compatibility.require_buffer_for_borrowed_name::(&name, buffers) { downcast_buffers.insert(name.clone().into_owned(), downcast); }