From 1137c08127cfcd3a6954785d4f37d9c2006d7520 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Wed, 5 Feb 2025 03:45:10 +0000 Subject: [PATCH 01/20] derive joined Signed-off-by: Teo Koon Peng --- macros/src/buffers.rs | 118 +++++++++++++++++++++++++++++++++++++++ macros/src/lib.rs | 20 ++++++- src/buffer/buffer_map.rs | 110 +----------------------------------- 3 files changed, 139 insertions(+), 109 deletions(-) create mode 100644 macros/src/buffers.rs diff --git a/macros/src/buffers.rs b/macros/src/buffers.rs new file mode 100644 index 00000000..e765c28e --- /dev/null +++ b/macros/src/buffers.rs @@ -0,0 +1,118 @@ +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{DeriveInput, Ident, Type}; + +use crate::Result; + +pub(crate) fn impl_buffer_map_layout(ast: DeriveInput) -> Result { + let struct_ident = ast.ident; + let (field_ident, field_type) = match ast.data { + syn::Data::Struct(data) => get_fields_map(data.fields)?, + _ => return Err("expected struct".to_string()), + }; + let map_key: Vec = field_ident.iter().map(|v| v.to_string()).collect(); + let struct_buffer_ident = format_ident!("__{}Buffers", struct_ident); + + let gen = quote! { + impl BufferMapLayout for #struct_ident { + fn is_compatible(buffers: &BufferMap) -> Result<(), IncompatibleLayout> { + let mut compatibility = IncompatibleLayout::default(); + #( + compatibility.require_buffer::<#field_type>(#map_key, buffers); + )* + compatibility.into_result() + } + + fn buffered_count( + buffers: &BufferMap, + session: Entity, + world: &World, + ) -> Result { + #( + let #field_ident = world + .get_entity(buffers.get(#map_key).unwrap().id()) + .or_broken()? + .buffered_count::<#field_type>(session)?; + )* + + Ok([#( #field_ident ),*] + .iter() + .min() + .copied() + .unwrap_or(0)) + } + + fn ensure_active_session( + buffers: &BufferMap, + session: Entity, + world: &mut World, + ) -> OperationResult { + #( + world + .get_entity_mut(buffers.get(#map_key).unwrap().id()) + .or_broken()? + .ensure_session::<#field_type>(session)?; + )* + + Ok(()) + } + } + + impl JoinedValue for #struct_ident { + type Buffers = #struct_buffer_ident; + + fn pull( + buffers: &BufferMap, + session: Entity, + world: &mut World, + ) -> Result { + #( + let #field_ident = world + .get_entity_mut(buffers.get(#map_key).unwrap().id()) + .or_broken()? + .pull_from_buffer::<#field_type>(session)?; + )* + + Ok(Self { + #( + #field_ident + ),* + }) + } + } + + struct #struct_buffer_ident { + #( + #field_ident: Buffer<#field_type> + ),* + } + + impl From<#struct_buffer_ident> for BufferMap { + fn from(value: #struct_buffer_ident) -> Self { + let mut buffers = BufferMap::default(); + #( + buffers.insert(Cow::Borrowed(#map_key), value.#field_ident); + )* + buffers + } + } + }; + + Ok(gen.into()) +} + +fn get_fields_map(fields: syn::Fields) -> Result<(Vec, Vec)> { + match fields { + syn::Fields::Named(data) => { + let mut idents = Vec::with_capacity(data.named.len()); + let mut types = Vec::with_capacity(data.named.len()); + for field in data.named { + let ident = field.ident.ok_or("expected named fields".to_string())?; + idents.push(ident); + types.push(field.ty); + } + Ok((idents, types)) + } + _ => return Err("expected named fields".to_string()), + } +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index d40c9309..8c18c788 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -15,9 +15,12 @@ * */ +mod buffers; +use buffers::impl_buffer_map_layout; + use proc_macro::TokenStream; use quote::quote; -use syn::DeriveInput; +use syn::{parse_macro_input, DeriveInput}; #[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(Joined)] +pub fn derive_joined(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + match impl_buffer_map_layout(input) { + Ok(tokens) => tokens, + Err(msg) => quote! { + compile_error!(#msg); + } + .into(), + } +} diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index e3ac852a..3683ea2c 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -365,121 +365,15 @@ mod tests { }; use bevy_ecs::prelude::World; + use bevy_impulse_derive::Joined; - #[derive(Clone)] + #[derive(Clone, Joined)] 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(); From 9bc7cfe1312da3227a883a2705d5dfea9df70b50 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Wed, 5 Feb 2025 04:05:19 +0000 Subject: [PATCH 02/20] remove use of unwrap Signed-off-by: Teo Koon Peng --- macros/src/buffers.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/macros/src/buffers.rs b/macros/src/buffers.rs index e765c28e..940509ee 100644 --- a/macros/src/buffers.rs +++ b/macros/src/buffers.rs @@ -30,7 +30,7 @@ pub(crate) fn impl_buffer_map_layout(ast: DeriveInput) -> Result { ) -> Result { #( let #field_ident = world - .get_entity(buffers.get(#map_key).unwrap().id()) + .get_entity(buffers.get(#map_key).or_broken()?.id()) .or_broken()? .buffered_count::<#field_type>(session)?; )* @@ -49,7 +49,7 @@ pub(crate) fn impl_buffer_map_layout(ast: DeriveInput) -> Result { ) -> OperationResult { #( world - .get_entity_mut(buffers.get(#map_key).unwrap().id()) + .get_entity_mut(buffers.get(#map_key).or_broken()?.id()) .or_broken()? .ensure_session::<#field_type>(session)?; )* @@ -68,7 +68,7 @@ pub(crate) fn impl_buffer_map_layout(ast: DeriveInput) -> Result { ) -> Result { #( let #field_ident = world - .get_entity_mut(buffers.get(#map_key).unwrap().id()) + .get_entity_mut(buffers.get(#map_key).or_broken()?.id()) .or_broken()? .pull_from_buffer::<#field_type>(session)?; )* From 83d65954193143d6352890afd24f00a9a80f4f91 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Wed, 5 Feb 2025 05:25:15 +0000 Subject: [PATCH 03/20] use full path Signed-off-by: Teo Koon Peng --- macros/src/{buffers.rs => buffer.rs} | 46 ++++++++++++++++------------ macros/src/lib.rs | 10 +++--- src/buffer/buffer_map.rs | 13 +++----- src/lib.rs | 2 ++ 4 files changed, 38 insertions(+), 33 deletions(-) rename macros/src/{buffers.rs => buffer.rs} (67%) diff --git a/macros/src/buffers.rs b/macros/src/buffer.rs similarity index 67% rename from macros/src/buffers.rs rename to macros/src/buffer.rs index 940509ee..9c10bf72 100644 --- a/macros/src/buffers.rs +++ b/macros/src/buffer.rs @@ -4,7 +4,7 @@ use syn::{DeriveInput, Ident, Type}; use crate::Result; -pub(crate) fn impl_buffer_map_layout(ast: DeriveInput) -> Result { +pub(crate) fn impl_joined_value(ast: DeriveInput) -> Result { let struct_ident = ast.ident; let (field_ident, field_type) = match ast.data { syn::Data::Struct(data) => get_fields_map(data.fields)?, @@ -15,8 +15,8 @@ pub(crate) fn impl_buffer_map_layout(ast: DeriveInput) -> Result { let gen = quote! { impl BufferMapLayout for #struct_ident { - fn is_compatible(buffers: &BufferMap) -> Result<(), IncompatibleLayout> { - let mut compatibility = IncompatibleLayout::default(); + fn is_compatible(buffers: &BufferMap) -> Result<(), ::bevy_impulse::IncompatibleLayout> { + let mut compatibility = ::bevy_impulse::IncompatibleLayout::default(); #( compatibility.require_buffer::<#field_type>(#map_key, buffers); )* @@ -24,10 +24,12 @@ pub(crate) fn impl_buffer_map_layout(ast: DeriveInput) -> Result { } fn buffered_count( - buffers: &BufferMap, - session: Entity, - world: &World, - ) -> Result { + buffers: &::bevy_impulse::BufferMap, + session: ::bevy_ecs::prelude::Entity, + world: &::bevy_ecs::prelude::World, + ) -> Result { + use ::bevy_impulse::{InspectBuffer, OrBroken}; + #( let #field_ident = world .get_entity(buffers.get(#map_key).or_broken()?.id()) @@ -43,10 +45,12 @@ pub(crate) fn impl_buffer_map_layout(ast: DeriveInput) -> Result { } fn ensure_active_session( - buffers: &BufferMap, - session: Entity, - world: &mut World, - ) -> OperationResult { + buffers: &::bevy_impulse::BufferMap, + session: ::bevy_ecs::prelude::Entity, + world: &mut ::bevy_ecs::prelude::World, + ) -> ::bevy_impulse::OperationResult { + use ::bevy_impulse::{ManageBuffer, OrBroken}; + #( world .get_entity_mut(buffers.get(#map_key).or_broken()?.id()) @@ -58,14 +62,16 @@ pub(crate) fn impl_buffer_map_layout(ast: DeriveInput) -> Result { } } - impl JoinedValue for #struct_ident { + impl ::bevy_impulse::JoinedValue for #struct_ident { type Buffers = #struct_buffer_ident; fn pull( - buffers: &BufferMap, - session: Entity, - world: &mut World, - ) -> Result { + buffers: &::bevy_impulse::BufferMap, + session: ::bevy_ecs::prelude::Entity, + world: &mut ::bevy_ecs::prelude::World, + ) -> Result { + use ::bevy_impulse::{ManageBuffer, OrBroken}; + #( let #field_ident = world .get_entity_mut(buffers.get(#map_key).or_broken()?.id()) @@ -83,15 +89,15 @@ pub(crate) fn impl_buffer_map_layout(ast: DeriveInput) -> Result { struct #struct_buffer_ident { #( - #field_ident: Buffer<#field_type> + #field_ident: ::bevy_impulse::Buffer<#field_type> ),* } - impl From<#struct_buffer_ident> for BufferMap { + impl From<#struct_buffer_ident> for ::bevy_impulse::BufferMap { fn from(value: #struct_buffer_ident) -> Self { - let mut buffers = BufferMap::default(); + let mut buffers = ::bevy_impulse::BufferMap::default(); #( - buffers.insert(Cow::Borrowed(#map_key), value.#field_ident); + buffers.insert(std::borrow::Cow::Borrowed(#map_key), value.#field_ident); )* buffers } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 8c18c788..48c1b0bd 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -15,8 +15,8 @@ * */ -mod buffers; -use buffers::impl_buffer_map_layout; +mod buffer; +use buffer::impl_joined_value; use proc_macro::TokenStream; use quote::quote; @@ -65,10 +65,10 @@ 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(Joined)] -pub fn derive_joined(input: TokenStream) -> TokenStream { +#[proc_macro_derive(JoinedValue)] +pub fn derive_joined_value(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - match impl_buffer_map_layout(input) { + match impl_joined_value(input) { Ok(tokens) => tokens, Err(msg) => quote! { compile_error!(#msg); diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 3683ea2c..9ca5064d 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -29,6 +29,8 @@ use crate::{ OperationRoster, Output, UnusedTarget, }; +pub use bevy_impulse_derive::JoinedValue; + #[derive(Clone, Default)] pub struct BufferMap { inner: HashMap, AnyBuffer>, @@ -359,15 +361,9 @@ impl BufferMapLayout for AnyBufferKeyMap { mod tests { use std::borrow::Cow; - use crate::{ - prelude::*, testing::*, BufferMap, InspectBuffer, ManageBuffer, OperationError, - OperationResult, OrBroken, - }; - - use bevy_ecs::prelude::World; - use bevy_impulse_derive::Joined; + use crate::{prelude::*, testing::*, BufferMap}; - #[derive(Clone, Joined)] + #[derive(Clone, JoinedValue)] struct TestJoinedValue { integer: i64, float: f64, @@ -379,6 +375,7 @@ mod tests { let mut context = TestingContext::minimal_plugins(); let workflow = context.spawn_io_workflow(|scope, builder| { + // ::bevy_impulse::OrBroken::or_broken(self) let buffer_i64 = builder.create_buffer(BufferSettings::default()); let buffer_f64 = builder.create_buffer(BufferSettings::default()); let buffer_string = builder.create_buffer(BufferSettings::default()); diff --git a/src/lib.rs b/src/lib.rs index cc40b262..755079b0 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 From 5dbfa63aa118472b102b7ab3c1380842bcdb2eb8 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Wed, 5 Feb 2025 05:32:48 +0000 Subject: [PATCH 04/20] cleanup Signed-off-by: Teo Koon Peng --- macros/src/lib.rs | 2 +- src/buffer/buffer_map.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 48c1b0bd..8ac2d38a 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -62,7 +62,7 @@ pub fn delivery_label_macro(item: TokenStream) -> TokenStream { .into() } -// The result error is the compiler error message to be displayed. +/// The result error is the compiler error message to be displayed. type Result = std::result::Result; #[proc_macro_derive(JoinedValue)] diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 9ca5064d..32613829 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -375,7 +375,6 @@ mod tests { let mut context = TestingContext::minimal_plugins(); let workflow = context.spawn_io_workflow(|scope, builder| { - // ::bevy_impulse::OrBroken::or_broken(self) let buffer_i64 = builder.create_buffer(BufferSettings::default()); let buffer_f64 = builder.create_buffer(BufferSettings::default()); let buffer_string = builder.create_buffer(BufferSettings::default()); From 68681e4010360cca6715a8b3959697d8caa1e2e9 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Thu, 6 Feb 2025 03:30:08 +0000 Subject: [PATCH 05/20] derive BufferKeyMap Signed-off-by: Teo Koon Peng --- macros/Cargo.toml | 1 + macros/src/buffer.rs | 160 +++++++++++++++++++++++++++------------ macros/src/lib.rs | 14 +++- src/buffer/any_buffer.rs | 12 ++- src/buffer/buffer_map.rs | 10 ++- 5 files changed, 145 insertions(+), 52 deletions(-) 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 index 9c10bf72..499315cf 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -6,15 +6,86 @@ use crate::Result; pub(crate) fn impl_joined_value(ast: DeriveInput) -> Result { let struct_ident = ast.ident; - let (field_ident, field_type) = match ast.data { - syn::Data::Struct(data) => get_fields_map(data.fields)?, + let (field_ident, field_type): (Vec, Vec) = match ast.data { + syn::Data::Struct(data) => get_fields_map(data.fields)?.into_iter().unzip(), _ => return Err("expected struct".to_string()), }; let map_key: Vec = field_ident.iter().map(|v| v.to_string()).collect(); let struct_buffer_ident = format_ident!("__{}Buffers", struct_ident); + let impl_buffer_map_layout = + buffer_map_layout(&struct_ident, &field_ident, &field_type, &map_key); + let gen = quote! { - impl BufferMapLayout for #struct_ident { + #impl_buffer_map_layout + + impl ::bevy_impulse::JoinedValue for #struct_ident { + type Buffers = #struct_buffer_ident; + + fn pull( + buffers: &::bevy_impulse::BufferMap, + session: ::bevy_ecs::prelude::Entity, + world: &mut ::bevy_ecs::prelude::World, + ) -> Result { + use ::bevy_impulse::{ManageBuffer, OrBroken}; + + #( + let #field_ident = world + .get_entity_mut(buffers.get(#map_key).or_broken()?.id()) + .or_broken()? + .pull_from_buffer::<#field_type>(session)?; + )* + + Ok(Self { + #( + #field_ident + ),* + }) + } + } + + struct #struct_buffer_ident { + #( + #field_ident: ::bevy_impulse::Buffer<#field_type> + ),* + } + + impl From<#struct_buffer_ident> for ::bevy_impulse::BufferMap { + fn from(value: #struct_buffer_ident) -> Self { + let mut buffers = ::bevy_impulse::BufferMap::default(); + #( + buffers.insert(std::borrow::Cow::Borrowed(#map_key), value.#field_ident); + )* + buffers + } + } + }; + + Ok(gen.into()) +} + +fn get_fields_map(fields: syn::Fields) -> Result> { + match fields { + syn::Fields::Named(data) => { + let mut idents_types = Vec::with_capacity(data.named.len()); + for field in data.named { + let ident = field.ident.ok_or("expected named fields".to_string())?; + idents_types.push((ident, field.ty)); + } + Ok(idents_types) + } + _ => return Err("expected named fields".to_string()), + } +} + +fn buffer_map_layout( + struct_ident: &Ident, + field_ident: &Vec, + field_type: &Vec, + map_key: &Vec, +) -> proc_macro2::TokenStream { + quote! { + impl ::bevy_impulse::BufferMapLayout for #struct_ident { fn is_compatible(buffers: &BufferMap) -> Result<(), ::bevy_impulse::IncompatibleLayout> { let mut compatibility = ::bevy_impulse::IncompatibleLayout::default(); #( @@ -61,64 +132,57 @@ pub(crate) fn impl_joined_value(ast: DeriveInput) -> Result { Ok(()) } } + } +} - impl ::bevy_impulse::JoinedValue for #struct_ident { - type Buffers = #struct_buffer_ident; +pub(crate) fn impl_buffer_key_map(ast: DeriveInput) -> Result { + let struct_ident = ast.ident; + let (field_ident, field_type): (Vec, Vec) = match ast.data { + syn::Data::Struct(data) => get_fields_map(data.fields)?.into_iter().unzip(), + _ => return Err("expected struct".to_string()), + }; + let map_key: Vec = field_ident.iter().map(|v| v.to_string()).collect(); - fn pull( - buffers: &::bevy_impulse::BufferMap, - session: ::bevy_ecs::prelude::Entity, - world: &mut ::bevy_ecs::prelude::World, - ) -> Result { - use ::bevy_impulse::{ManageBuffer, OrBroken}; + let impl_buffer_map_layout = + buffer_map_layout(&struct_ident, &field_ident, &field_type, &map_key); + + // FIXME(koonpeng): `create_key` does not allow failure and we can't guarantee that the buffer + // from buffer is valid. + let gen = quote! { + impl ::bevy_impulse::BufferKeyMap for #struct_ident { + fn add_accessor(buffers: &::bevy_impulse::BufferMap, accessor: ::bevy_ecs::prelude::Entity, world: &mut ::bevy_ecs::prelude::World) -> ::bevy_impulse::OperationResult { + use ::bevy_impulse::{Accessed, OrBroken}; #( - let #field_ident = world - .get_entity_mut(buffers.get(#map_key).or_broken()?.id()) - .or_broken()? - .pull_from_buffer::<#field_type>(session)?; + buffers.get(#map_key).or_broken()?.add_accessor(accessor, world)?; )* - Ok(Self { - #( - #field_ident - ),* - }) + Ok(()) } - } - struct #struct_buffer_ident { - #( - #field_ident: ::bevy_impulse::Buffer<#field_type> - ),* - } + fn create_key(buffers: &::bevy_impulse::BufferMap, builder: &::bevy_impulse::BufferKeyBuilder) -> Self { + use ::bevy_impulse::{Accessed, OrBroken}; - impl From<#struct_buffer_ident> for ::bevy_impulse::BufferMap { - fn from(value: #struct_buffer_ident) -> Self { - let mut buffers = ::bevy_impulse::BufferMap::default(); + Self {#( + #field_ident: buffers.get(#map_key).unwrap().create_key(builder).try_into().unwrap(), + )*} + } + + fn deep_clone_key(&self) -> Self { + Self {#( + #field_ident: self.#field_ident.deep_clone(), + )*} + } + + fn is_key_in_use(&self) -> bool { #( - buffers.insert(std::borrow::Cow::Borrowed(#map_key), value.#field_ident); - )* - buffers + self.#field_ident.is_in_use() + )||* } } + + #impl_buffer_map_layout }; Ok(gen.into()) } - -fn get_fields_map(fields: syn::Fields) -> Result<(Vec, Vec)> { - match fields { - syn::Fields::Named(data) => { - let mut idents = Vec::with_capacity(data.named.len()); - let mut types = Vec::with_capacity(data.named.len()); - for field in data.named { - let ident = field.ident.ok_or("expected named fields".to_string())?; - idents.push(ident); - types.push(field.ty); - } - Ok((idents, types)) - } - _ => return Err("expected named fields".to_string()), - } -} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 8ac2d38a..dcfe72bd 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)] +pub fn derive_buffer_key_map(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + match impl_buffer_key_map(input) { + Ok(tokens) => tokens, + Err(msg) => quote! { + compile_error!(#msg); + } + .into(), + } +} diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index c0741b32..85b1821b 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -176,11 +176,11 @@ impl AnyBufferKey { self.tag.session } - fn is_in_use(&self) -> bool { + pub fn is_in_use(&self) -> bool { self.tag.is_in_use() } - fn deep_clone(&self) -> Self { + pub fn deep_clone(&self) -> Self { Self { tag: self.tag.deep_clone(), interface: self.interface, @@ -207,6 +207,14 @@ impl From> for AnyBufferKey { } } +impl TryFrom for BufferKey { + type Error = OperationError; + + fn try_from(value: AnyBufferKey) -> Result { + value.downcast_for_message().or_broken() + } +} + /// 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. diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 32613829..aee6e9d6 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -29,7 +29,7 @@ use crate::{ OperationRoster, Output, UnusedTarget, }; -pub use bevy_impulse_derive::JoinedValue; +pub use bevy_impulse_derive::{BufferKeyMap, JoinedValue}; #[derive(Clone, Default)] pub struct BufferMap { @@ -409,4 +409,12 @@ mod tests { assert_eq!(value.string, "hello"); assert!(context.no_unhandled_errors()); } + + // TODO(koonpeng): Add test that uses `TestBufferKeyMap`. + #[derive(Clone, BufferKeyMap)] + struct TestBufferKeyMap { + integer: BufferKey, + float: BufferKey, + string: AnyBufferKey, + } } From 1deada061b373def767b5e90546c232afdaeaff0 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Thu, 6 Feb 2025 03:42:35 +0000 Subject: [PATCH 06/20] comments Signed-off-by: Teo Koon Peng --- macros/src/buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index 499315cf..0148f7bd 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -147,7 +147,7 @@ pub(crate) fn impl_buffer_key_map(ast: DeriveInput) -> Result { buffer_map_layout(&struct_ident, &field_ident, &field_type, &map_key); // FIXME(koonpeng): `create_key` does not allow failure and we can't guarantee that the buffer - // from buffer is valid. + // from the buffer map is valid. let gen = quote! { impl ::bevy_impulse::BufferKeyMap for #struct_ident { fn add_accessor(buffers: &::bevy_impulse::BufferMap, accessor: ::bevy_ecs::prelude::Entity, world: &mut ::bevy_ecs::prelude::World) -> ::bevy_impulse::OperationResult { From af2caf7f30f496c3571feb0453bcc31c5aaac7ef Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Wed, 12 Feb 2025 03:32:40 +0000 Subject: [PATCH 07/20] support generics Signed-off-by: Teo Koon Peng --- macros/src/buffer.rs | 98 ++++++++++++++++++++++++---------------- macros/src/lib.rs | 6 +-- src/buffer/buffer_map.rs | 18 ++++++-- 3 files changed, 75 insertions(+), 47 deletions(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index 909002fa..8b284761 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -1,34 +1,35 @@ use proc_macro::TokenStream; use quote::{format_ident, quote}; -use syn::{DeriveInput, Ident, Type}; +use syn::{parse_quote, Ident, ItemStruct, Type}; use crate::Result; -pub(crate) fn impl_joined_value(ast: DeriveInput) -> Result { - let struct_ident = ast.ident; - let (field_ident, field_type): (Vec, Vec) = match ast.data { - syn::Data::Struct(data) => get_fields_map(data.fields)?.into_iter().unzip(), - _ => return Err("expected struct".to_string()), - }; - let map_key: Vec = field_ident.iter().map(|v| v.to_string()).collect(); +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 (field_ident, field_type): (Vec<_>, Vec<_>) = + get_fields_map(&input_struct.fields)?.into_iter().unzip(); let struct_buffer_ident = format_ident!("__bevy_impulse_{}_Buffers", struct_ident); - let impl_buffer_map_layout = - buffer_map_layout(&struct_buffer_ident, &field_ident, &field_type, &map_key); - let impl_joined = joined(&struct_buffer_ident, &struct_ident, &field_ident); - - let gen = quote! { - impl ::bevy_impulse::JoinedValue for #struct_ident { - type Buffers = #struct_buffer_ident; - } - + let buffer_struct: ItemStruct = parse_quote! { #[derive(Clone)] #[allow(non_camel_case_types)] - struct #struct_buffer_ident { + struct #struct_buffer_ident #impl_generics #where_clause { #( #field_ident: ::bevy_impulse::Buffer<#field_type>, )* } + }; + + 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 = #struct_buffer_ident #ty_generics; + } + + #buffer_struct #impl_buffer_map_layout @@ -38,13 +39,16 @@ pub(crate) fn impl_joined_value(ast: DeriveInput) -> Result { Ok(gen.into()) } -fn get_fields_map(fields: syn::Fields) -> Result> { +fn get_fields_map(fields: &syn::Fields) -> Result> { match fields { syn::Fields::Named(data) => { let mut idents_types = Vec::with_capacity(data.named.len()); - for field in data.named { - let ident = field.ident.ok_or("expected named fields".to_string())?; - idents_types.push((ident, field.ty)); + for field in &data.named { + let ident = field + .ident + .as_ref() + .ok_or("expected named fields".to_string())?; + idents_types.push((ident, &field.ty)); } Ok(idents_types) } @@ -52,14 +56,21 @@ fn get_fields_map(fields: syn::Fields) -> Result> { } } -fn buffer_map_layout( - struct_ident: &Ident, - field_ident: &Vec, - field_type: &Vec, - map_key: &Vec, -) -> proc_macro2::TokenStream { - quote! { - impl ::bevy_impulse::BufferMapLayout for #struct_ident { +/// 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_type): (Vec<_>, Vec<_>) = + get_fields_map(&item_struct.fields)?.into_iter().unzip(); + 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![#( @@ -83,16 +94,25 @@ fn buffer_map_layout( } } } + .into()) } -fn joined( - struct_ident: &Ident, - item_struct_ident: &Ident, - field_ident: &Vec, -) -> proc_macro2::TokenStream { - quote! { - impl ::bevy_impulse::Joined for #struct_ident { - type Item = #item_struct_ident; +/// 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, _): (Vec<_>, Vec<_>) = + get_fields_map(&item_struct.fields)?.into_iter().unzip(); + + 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 { #( @@ -104,5 +124,5 @@ fn joined( )*}) } } - } + }.into()) } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 8ac2d38a..57937ffd 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -20,7 +20,7 @@ use buffer::impl_joined_value; use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, DeriveInput}; +use syn::{parse_macro_input, DeriveInput, ItemStruct}; #[proc_macro_derive(Stream)] pub fn simple_stream_macro(item: TokenStream) -> TokenStream { @@ -67,8 +67,8 @@ type Result = std::result::Result; #[proc_macro_derive(JoinedValue)] pub fn derive_joined_value(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - match impl_joined_value(input) { + let input = parse_macro_input!(input as ItemStruct); + match impl_joined_value(&input) { Ok(tokens) => tokens, Err(msg) => quote! { compile_error!(#msg); diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index f25e1e7c..38dddf56 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -316,10 +316,11 @@ mod tests { use crate::{prelude::*, testing::*, BufferMap}; #[derive(Clone, JoinedValue)] - struct TestJoinedValue { + struct TestJoinedValue { integer: i64, float: f64, string: String, + generic: T, } #[test] @@ -330,16 +331,19 @@ 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 mut buffers = BufferMap::default(); buffers.insert("integer", buffer_i64); buffers.insert("float", buffer_f64); buffers.insert("string", buffer_string); + buffers.insert("generic", buffer_generic); 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()), )); builder.try_join(&buffers).unwrap().connect(scope.terminate); @@ -347,15 +351,16 @@ 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"), 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!(context.no_unhandled_errors()); } @@ -368,12 +373,14 @@ mod tests { integer: builder.create_buffer(BufferSettings::default()), float: builder.create_buffer(BufferSettings::default()), string: builder.create_buffer(BufferSettings::default()), + generic: 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(buffers.generic.input_slot()), )); builder.join(buffers).connect(scope.terminate); @@ -381,15 +388,16 @@ 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"), 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!(context.no_unhandled_errors()); } } From cff7138e53a27fc14255392088a3027c1b946b9a Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Wed, 12 Feb 2025 03:57:34 +0000 Subject: [PATCH 08/20] add `select_buffers` to avoid the need to directly reference generate buffer struct Signed-off-by: Teo Koon Peng --- macros/src/buffer.rs | 14 ++++++++++++++ src/buffer/buffer_map.rs | 12 ++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index 8b284761..cd674860 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -31,6 +31,20 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result, + )* + ) -> #struct_buffer_ident #ty_generics { + #struct_buffer_ident { + #( + #field_ident, + )* + } + } + } + #impl_buffer_map_layout #impl_joined diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 38dddf56..a6a0f22f 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -369,12 +369,12 @@ mod tests { let mut context = TestingContext::minimal_plugins(); let workflow = context.spawn_io_workflow(|scope, builder| { - let buffers = __bevy_impulse_TestJoinedValue_Buffers { - integer: builder.create_buffer(BufferSettings::default()), - float: builder.create_buffer(BufferSettings::default()), - string: builder.create_buffer(BufferSettings::default()), - generic: builder.create_buffer(BufferSettings::default()), - }; + let buffers = TestJoinedValue::select_buffers( + builder.create_buffer(BufferSettings::default()), + builder.create_buffer(BufferSettings::default()), + builder.create_buffer(BufferSettings::default()), + builder.create_buffer(BufferSettings::default()), + ); scope.input.chain(builder).fork_unzip(( |chain: Chain<_>| chain.connect(buffers.integer.input_slot()), From bb88c8dcc8a5b6bad37d01df95a2e03d5ff4533e Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Wed, 12 Feb 2025 07:10:02 +0000 Subject: [PATCH 09/20] remove support for customizing buffers, select_buffers allows any buffer types instead Signed-off-by: Teo Koon Peng --- macros/src/buffer.rs | 60 ++++++++++++++++++++++++++++++---------- macros/src/lib.rs | 4 +-- src/buffer/any_buffer.rs | 4 +++ src/buffer/buffer_map.rs | 14 +++++++--- 4 files changed, 62 insertions(+), 20 deletions(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index cd674860..13a88692 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -1,14 +1,13 @@ -use proc_macro::TokenStream; +use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{parse_quote, Ident, ItemStruct, Type}; +use syn::{parse_quote, Field, Ident, ItemStruct, Type}; 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 (field_ident, field_type): (Vec<_>, Vec<_>) = - get_fields_map(&input_struct.fields)?.into_iter().unzip(); + let (field_ident, field_type, _) = get_fields_map(&input_struct.fields)?; let struct_buffer_ident = format_ident!("__bevy_impulse_{}_Buffers", struct_ident); let buffer_struct: ItemStruct = parse_quote! { @@ -33,13 +32,14 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result, + #field_ident: impl ::bevy_impulse::Bufferable>, )* ) -> #struct_buffer_ident #ty_generics { #struct_buffer_ident { #( - #field_ident, + #field_ident: #field_ident.into_buffer(builder), )* } } @@ -53,18 +53,52 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result Result> { +struct FieldConfig { + buffer_type: TokenStream, +} + +impl FieldConfig { + fn from_field(field: &Field) -> Self { + let ty = &field.ty; + let mut buffer_type = quote! { ::bevy_impulse::Buffer<#ty> }; + + let attr = field + .attrs + .iter() + .find(|attr| attr.path().is_ident("bevy_impulse")); + + if let Some(attr) = attr { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("buffer_type") { + buffer_type = meta.value()?.parse()?; + } + Ok(()) + }) + // panic if attribute is malformed, this will result in a compile error + .unwrap(); + } + + Self { buffer_type } + } +} + +fn get_fields_map(fields: &syn::Fields) -> Result<(Vec<&Ident>, Vec<&Type>, Vec)> { match fields { syn::Fields::Named(data) => { - let mut idents_types = Vec::with_capacity(data.named.len()); + 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_types.push((ident, &field.ty)); + let config = FieldConfig::from_field(field); + idents.push(ident); + types.push(&field.ty); + configs.push(config); } - Ok(idents_types) + Ok((idents, types, configs)) } _ => return Err("expected named fields".to_string()), } @@ -79,8 +113,7 @@ fn impl_buffer_map_layout( ) -> Result { let struct_ident = &buffer_struct.ident; let (impl_generics, ty_generics, where_clause) = buffer_struct.generics.split_for_impl(); - let (field_ident, field_type): (Vec<_>, Vec<_>) = - get_fields_map(&item_struct.fields)?.into_iter().unzip(); + let (field_ident, field_type, _) = get_fields_map(&item_struct.fields)?; let map_key: Vec = field_ident.iter().map(|v| v.to_string()).collect(); Ok(quote! { @@ -121,8 +154,7 @@ fn impl_joined( 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, _): (Vec<_>, Vec<_>) = - get_fields_map(&item_struct.fields)?.into_iter().unzip(); + let (field_ident, _, _) = get_fields_map(&item_struct.fields)?; Ok(quote! { impl #impl_generics ::bevy_impulse::Joined for #struct_ident #ty_generics #where_clause { diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 57937ffd..783c0127 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -65,11 +65,11 @@ 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)] +#[proc_macro_derive(JoinedValue, attributes(bevy_impulse))] pub fn derive_joined_value(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as ItemStruct); match impl_joined_value(&input) { - Ok(tokens) => tokens, + Ok(tokens) => tokens.into(), Err(msg) => quote! { compile_error!(#msg); } diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index fb46e51f..82eaaf2e 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -86,6 +86,10 @@ impl AnyBuffer { .entry(TypeId::of::()) .or_insert_with(|| Box::leak(Box::new(AnyBufferAccessImpl::::new()))) } + + pub fn as_any_buffer(self) -> AnyBuffer { + self + } } impl std::fmt::Debug for AnyBuffer { diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index a6a0f22f..8c2e48df 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -369,11 +369,17 @@ mod tests { 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 buffer_generic = builder.create_buffer(BufferSettings::default()); + let buffers = TestJoinedValue::select_buffers( - builder.create_buffer(BufferSettings::default()), - builder.create_buffer(BufferSettings::default()), - builder.create_buffer(BufferSettings::default()), - builder.create_buffer(BufferSettings::default()), + builder, + buffer_i64, + buffer_f64, + buffer_string, + buffer_generic, ); scope.input.chain(builder).fork_unzip(( From 957f17d5b7ce738303393a6c21f7d2a9d9871c8a Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Wed, 12 Feb 2025 07:13:32 +0000 Subject: [PATCH 10/20] revert changes to AnyBuffer Signed-off-by: Teo Koon Peng --- src/buffer/any_buffer.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 82eaaf2e..32529079 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -86,10 +86,6 @@ impl AnyBuffer { .entry(TypeId::of::()) .or_insert_with(|| Box::leak(Box::new(AnyBufferAccessImpl::::new()))) } - - pub fn as_any_buffer(self) -> AnyBuffer { - self - } } impl std::fmt::Debug for AnyBuffer { @@ -215,14 +211,6 @@ impl From> for AnyBufferKey { } } -impl TryFrom for BufferKey { - type Error = OperationError; - - fn try_from(value: AnyBufferKey) -> Result { - value.downcast_for_message().or_broken() - } -} - /// 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. From dde0e6d9dba612a0010ddec3f31007d8ce0bfcac Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Thu, 13 Feb 2025 03:00:39 +0000 Subject: [PATCH 11/20] add helper attribute to customized generated buffer struct ident Signed-off-by: Teo Koon Peng --- macros/src/buffer.rs | 55 ++++++++++++++++++++-------------------- macros/src/lib.rs | 2 +- src/buffer/buffer_map.rs | 10 ++++++++ 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index 13a88692..17ae1d1c 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -1,21 +1,24 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{parse_quote, Field, Ident, ItemStruct, Type}; +use syn::{parse_quote, Ident, ItemStruct, Type}; 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 (field_ident, field_type, _) = get_fields_map(&input_struct.fields)?; - let struct_buffer_ident = format_ident!("__bevy_impulse_{}_Buffers", struct_ident); + let (field_ident, field_type) = get_fields_map(&input_struct.fields)?; + let BufferStructConfig { + struct_name: buffer_struct_ident, + } = BufferStructConfig::from_data_struct(&input_struct); + let buffer_struct_vis = &input_struct.vis; let buffer_struct: ItemStruct = parse_quote! { #[derive(Clone)] #[allow(non_camel_case_types)] - struct #struct_buffer_ident #impl_generics #where_clause { + #buffer_struct_vis struct #buffer_struct_ident #impl_generics #where_clause { #( - #field_ident: ::bevy_impulse::Buffer<#field_type>, + #buffer_struct_vis #field_ident: ::bevy_impulse::Buffer<#field_type>, )* } }; @@ -25,7 +28,7 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result Result>, )* - ) -> #struct_buffer_ident #ty_generics { - #struct_buffer_ident { + ) -> #buffer_struct_ident #ty_generics { + #buffer_struct_ident { #( #field_ident: #field_ident.into_buffer(builder), )* @@ -53,52 +56,50 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result Self { - let ty = &field.ty; - let mut buffer_type = quote! { ::bevy_impulse::Buffer<#ty> }; +impl BufferStructConfig { + fn from_data_struct(data_struct: &ItemStruct) -> Self { + let mut config = Self { + struct_name: format_ident!("__bevy_impulse_{}_Buffers", data_struct.ident), + }; - let attr = field + let attr = data_struct .attrs .iter() - .find(|attr| attr.path().is_ident("bevy_impulse")); + .find(|attr| attr.path().is_ident("buffers")); if let Some(attr) = attr { attr.parse_nested_meta(|meta| { - if meta.path.is_ident("buffer_type") { - buffer_type = meta.value()?.parse()?; + if meta.path.is_ident("struct_name") { + config.struct_name = meta.value()?.parse()?; } Ok(()) }) - // panic if attribute is malformed, this will result in a compile error + // panic if attribute is malformed, this will result in a compile error which is intended. .unwrap(); } - Self { buffer_type } + config } } -fn get_fields_map(fields: &syn::Fields) -> Result<(Vec<&Ident>, Vec<&Type>, Vec)> { +fn get_fields_map(fields: &syn::Fields) -> Result<(Vec<&Ident>, Vec<&Type>)> { 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())?; - let config = FieldConfig::from_field(field); idents.push(ident); types.push(&field.ty); - configs.push(config); } - Ok((idents, types, configs)) + Ok((idents, types)) } _ => return Err("expected named fields".to_string()), } @@ -113,7 +114,7 @@ fn impl_buffer_map_layout( ) -> Result { let struct_ident = &buffer_struct.ident; let (impl_generics, ty_generics, where_clause) = buffer_struct.generics.split_for_impl(); - let (field_ident, field_type, _) = get_fields_map(&item_struct.fields)?; + let (field_ident, field_type) = get_fields_map(&item_struct.fields)?; let map_key: Vec = field_ident.iter().map(|v| v.to_string()).collect(); Ok(quote! { @@ -154,7 +155,7 @@ fn impl_joined( 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)?; + let (field_ident, _) = get_fields_map(&item_struct.fields)?; Ok(quote! { impl #impl_generics ::bevy_impulse::Joined for #struct_ident #ty_generics #where_clause { diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 783c0127..421343bd 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(bevy_impulse))] +#[proc_macro_derive(JoinedValue, attributes(buffers))] pub fn derive_joined_value(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as ItemStruct); match impl_joined_value(&input) { diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 8c2e48df..5b4ce2c6 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -406,4 +406,14 @@ mod tests { assert_eq!(value.generic, "world"); assert!(context.no_unhandled_errors()); } + + #[derive(Clone, JoinedValue)] + #[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) {} + } } From 01af23fa468f6f55c50bc9d6a8e9495e489c94c0 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Thu, 13 Feb 2025 03:36:43 +0000 Subject: [PATCH 12/20] remove builder argument in select_buffers; add test for select_buffers with json buffers Signed-off-by: Teo Koon Peng --- macros/src/buffer.rs | 5 ++-- src/buffer/buffer_map.rs | 50 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index 17ae1d1c..846e5aca 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -35,14 +35,13 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result>, + #field_ident: ::bevy_impulse::Buffer<#field_type>, )* ) -> #buffer_struct_ident #ty_generics { #buffer_struct_ident { #( - #field_ident: #field_ident.into_buffer(builder), + #field_ident, )* } } diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 5b4ce2c6..ce65ae6c 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -375,7 +375,6 @@ mod tests { let buffer_generic = builder.create_buffer(BufferSettings::default()); let buffers = TestJoinedValue::select_buffers( - builder, buffer_i64, buffer_f64, buffer_string, @@ -407,6 +406,55 @@ mod tests { assert!(context.no_unhandled_errors()); } + #[test] + fn test_select_buffers_json() { + let mut context = TestingContext::minimal_plugins(); + + let workflow = context.spawn_io_workflow(|scope, builder| { + let buffer_i64 = + JsonBuffer::from(builder.create_buffer::(BufferSettings::default())); + let buffer_f64 = + JsonBuffer::from(builder.create_buffer::(BufferSettings::default())); + let buffer_string = + JsonBuffer::from(builder.create_buffer::(BufferSettings::default())); + let buffer_generic = + JsonBuffer::from(builder.create_buffer::(BufferSettings::default())); + + let buffers = TestJoinedValue::select_buffers( + buffer_i64.downcast_for_message().unwrap(), + buffer_f64.downcast_for_message().unwrap(), + buffer_string.downcast_for_message().unwrap(), + buffer_generic.downcast_for_message().unwrap(), + ); + + 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()), + )); + + builder.join(buffers).connect(scope.terminate); + }); + + let mut promise = context.command(|commands| { + commands + .request( + (5_i64, 3.14_f64, "hello".to_string(), "world".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_eq!(value.generic, "world"); + assert!(context.no_unhandled_errors()); + } + #[derive(Clone, JoinedValue)] #[buffers(struct_name = FooBuffers)] struct TestDeriveWithConfig {} From 25ec4f2e050c460bf27789772676af0cef813af1 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Thu, 13 Feb 2025 08:24:07 +0000 Subject: [PATCH 13/20] wip buffer_type helper but without auto downcasting, doesnt work due to AnyBuffer limitations Signed-off-by: Teo Koon Peng --- macros/src/buffer.rs | 102 +++++++++++++++++++++++++++++++-------- src/buffer/any_buffer.rs | 4 ++ src/buffer/buffer_map.rs | 51 +++++++++++++++----- 3 files changed, 124 insertions(+), 33 deletions(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index 846e5aca..d9c131f4 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -1,24 +1,29 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{parse_quote, Ident, ItemStruct, Type}; +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 (field_ident, field_type) = get_fields_map(&input_struct.fields)?; - let BufferStructConfig { - struct_name: buffer_struct_ident, - } = BufferStructConfig::from_data_struct(&input_struct); + 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_type: Vec<&Type> = field_config + .iter() + .map(|config| &config.buffer_type) + .collect(); + let buffer_struct: ItemStruct = parse_quote! { #[derive(Clone)] #[allow(non_camel_case_types)] #buffer_struct_vis struct #buffer_struct_ident #impl_generics #where_clause { #( - #buffer_struct_vis #field_ident: ::bevy_impulse::Buffer<#field_type>, + #buffer_struct_vis #field_ident: #buffer_type, )* } }; @@ -36,7 +41,7 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result, + #field_ident: #buffer_type, )* ) -> #buffer_struct_ident #ty_generics { #buffer_struct_ident { @@ -55,14 +60,30 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result` +// Currently unused but could be used in the future +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<(#(#lifetimes,)* #(#ty_params,)*)> } +} + +struct StructConfig { + buffer_struct_name: Ident, } -impl BufferStructConfig { +impl StructConfig { fn from_data_struct(data_struct: &ItemStruct) -> Self { let mut config = Self { - struct_name: format_ident!("__bevy_impulse_{}_Buffers", data_struct.ident), + buffer_struct_name: format_ident!("__bevy_impulse_{}_Buffers", data_struct.ident), }; let attr = data_struct @@ -72,8 +93,39 @@ impl BufferStructConfig { if let Some(attr) = attr { attr.parse_nested_meta(|meta| { - if meta.path.is_ident("struct_name") { - config.struct_name = meta.value()?.parse()?; + if meta.path.is_ident("buffer_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: Type, +} + +impl FieldConfig { + fn from_field(field: &Field) -> Self { + let ty = &field.ty; + let mut config = Self { + buffer_type: parse_quote! { ::bevy_impulse::Buffer<#ty> }, + }; + + let attr = field + .attrs + .iter() + .find(|attr| attr.path().is_ident("buffers")); + + if let Some(attr) = attr { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("buffer_type") { + config.buffer_type = meta.value()?.parse()?; } Ok(()) }) @@ -85,11 +137,12 @@ impl BufferStructConfig { } } -fn get_fields_map(fields: &syn::Fields) -> Result<(Vec<&Ident>, Vec<&Type>)> { +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 @@ -97,8 +150,9 @@ fn get_fields_map(fields: &syn::Fields) -> Result<(Vec<&Ident>, Vec<&Type>)> { .ok_or("expected named fields".to_string())?; idents.push(ident); types.push(&field.ty); + configs.push(FieldConfig::from_field(field)); } - Ok((idents, types)) + Ok((idents, types, configs)) } _ => return Err("expected named fields".to_string()), } @@ -113,7 +167,11 @@ fn impl_buffer_map_layout( ) -> Result { let struct_ident = &buffer_struct.ident; let (impl_generics, ty_generics, where_clause) = buffer_struct.generics.split_for_impl(); - let (field_ident, field_type) = get_fields_map(&item_struct.fields)?; + let (field_ident, _, field_config) = get_fields_map(&item_struct.fields)?; + let buffer_type: Vec<&Type> = field_config + .iter() + .map(|config| &config.buffer_type) + .collect(); let map_key: Vec = field_ident.iter().map(|v| v.to_string()).collect(); Ok(quote! { @@ -128,16 +186,18 @@ 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 = if let Ok(buffer) = compatibility.require_message_type::<#field_type>(#map_key, buffers) { + let #field_ident = if let Ok(buffer) = compatibility.require_buffer_type::<#buffer_type>(#map_key, buffers) { buffer } else { return Err(compatibility); }; )* - Ok(Self {#( - #field_ident, - )*}) + Ok(Self { + #( + #field_ident, + )* + }) } } } @@ -154,7 +214,7 @@ fn impl_joined( 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)?; + let (field_ident, _, _) = get_fields_map(&item_struct.fields)?; Ok(quote! { impl #impl_generics ::bevy_impulse::Joined for #struct_ident #ty_generics #where_clause { diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 32529079..15660861 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 { diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index ce65ae6c..75528840 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -315,12 +315,15 @@ impl Accessed for BufferMap { mod tests { use crate::{prelude::*, testing::*, BufferMap}; - #[derive(Clone, JoinedValue)] + #[derive(JoinedValue)] struct TestJoinedValue { integer: i64, float: f64, string: String, generic: T, + #[buffers(buffer_type = AnyBuffer)] + #[allow(unused)] + any: AnyMessageBox, } #[test] @@ -332,18 +335,21 @@ mod tests { 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); @@ -351,7 +357,10 @@ mod tests { let mut promise = context.command(|commands| { commands - .request((5_i64, 3.14_f64, "hello".to_string(), "world"), workflow) + .request( + (5_i64, 3.14_f64, "hello".to_string(), "world", ()), + workflow, + ) .take_response() }); @@ -373,27 +382,33 @@ mod tests { 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(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(), ); - 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()), - )); - builder.join(buffers).connect(scope.terminate); }); let mut promise = context.command(|commands| { commands - .request((5_i64, 3.14_f64, "hello".to_string(), "world"), workflow) + .request( + (5_i64, 3.14_f64, "hello".to_string(), "world", ()), + workflow, + ) .take_response() }); @@ -419,12 +434,15 @@ mod tests { JsonBuffer::from(builder.create_buffer::(BufferSettings::default())); let buffer_generic = JsonBuffer::from(builder.create_buffer::(BufferSettings::default())); + let buffer_any = + JsonBuffer::from(builder.create_buffer::<()>(BufferSettings::default())); let buffers = TestJoinedValue::select_buffers( buffer_i64.downcast_for_message().unwrap(), buffer_f64.downcast_for_message().unwrap(), buffer_string.downcast_for_message().unwrap(), buffer_generic.downcast_for_message().unwrap(), + buffer_any.downcast_buffer().unwrap(), ); scope.input.chain(builder).fork_unzip(( @@ -432,6 +450,9 @@ mod tests { |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(buffers.any.downcast_for_message().unwrap().input_slot()) + }, )); builder.join(buffers).connect(scope.terminate); @@ -440,7 +461,13 @@ mod tests { let mut promise = context.command(|commands| { commands .request( - (5_i64, 3.14_f64, "hello".to_string(), "world".to_string()), + ( + 5_i64, + 3.14_f64, + "hello".to_string(), + "world".to_string(), + (), + ), workflow, ) .take_response() @@ -456,7 +483,7 @@ mod tests { } #[derive(Clone, JoinedValue)] - #[buffers(struct_name = FooBuffers)] + #[buffers(buffer_struct_name = FooBuffers)] struct TestDeriveWithConfig {} #[test] From 943ea335879c77960ff14128cd178dbbb4bbccbf Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Thu, 13 Feb 2025 09:06:56 +0000 Subject: [PATCH 14/20] check for any Signed-off-by: Teo Koon Peng --- src/buffer/buffer_map.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 75528840..310b4a59 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -358,7 +358,7 @@ mod tests { let mut promise = context.command(|commands| { commands .request( - (5_i64, 3.14_f64, "hello".to_string(), "world", ()), + (5_i64, 3.14_f64, "hello".to_string(), "world", 42_i64), workflow, ) .take_response() @@ -370,6 +370,7 @@ mod tests { 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()); } @@ -382,7 +383,7 @@ mod tests { 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 buffer_any = builder.create_buffer::(BufferSettings::default()); scope.input.chain(builder).fork_unzip(( |chain: Chain<_>| chain.connect(buffer_i64.input_slot()), @@ -406,7 +407,7 @@ mod tests { let mut promise = context.command(|commands| { commands .request( - (5_i64, 3.14_f64, "hello".to_string(), "world", ()), + (5_i64, 3.14_f64, "hello".to_string(), "world", 42_i64), workflow, ) .take_response() @@ -418,6 +419,7 @@ mod tests { 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()); } @@ -435,7 +437,7 @@ mod tests { let buffer_generic = JsonBuffer::from(builder.create_buffer::(BufferSettings::default())); let buffer_any = - JsonBuffer::from(builder.create_buffer::<()>(BufferSettings::default())); + JsonBuffer::from(builder.create_buffer::(BufferSettings::default())); let buffers = TestJoinedValue::select_buffers( buffer_i64.downcast_for_message().unwrap(), @@ -466,7 +468,7 @@ mod tests { 3.14_f64, "hello".to_string(), "world".to_string(), - (), + 42_i64, ), workflow, ) @@ -479,6 +481,7 @@ mod tests { 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()); } From 37ee9b948387a45e3c8a1138300252d259b809a2 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Fri, 14 Feb 2025 02:58:42 +0000 Subject: [PATCH 15/20] allow buffer_downcast to downcast back to the original Buffer Signed-off-by: Teo Koon Peng --- src/buffer/any_buffer.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/buffer/any_buffer.rs b/src/buffer/any_buffer.rs index 15660861..b4315fe8 100644 --- a/src/buffer/any_buffer.rs +++ b/src/buffer/any_buffer.rs @@ -861,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 From a00f09f5fbc66b0d2c63fd131e625b967e3d6621 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Fri, 14 Feb 2025 03:10:32 +0000 Subject: [PATCH 16/20] move test_select_buffers_json Signed-off-by: Teo Koon Peng --- src/buffer/buffer_map.rs | 62 ---------------------- src/buffer/json_buffer.rs | 108 +++++++++++++++----------------------- 2 files changed, 42 insertions(+), 128 deletions(-) diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 310b4a59..2c4244ee 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -423,68 +423,6 @@ mod tests { assert!(context.no_unhandled_errors()); } - #[test] - fn test_select_buffers_json() { - let mut context = TestingContext::minimal_plugins(); - - let workflow = context.spawn_io_workflow(|scope, builder| { - let buffer_i64 = - JsonBuffer::from(builder.create_buffer::(BufferSettings::default())); - let buffer_f64 = - JsonBuffer::from(builder.create_buffer::(BufferSettings::default())); - let buffer_string = - JsonBuffer::from(builder.create_buffer::(BufferSettings::default())); - let buffer_generic = - JsonBuffer::from(builder.create_buffer::(BufferSettings::default())); - let buffer_any = - JsonBuffer::from(builder.create_buffer::(BufferSettings::default())); - - let buffers = TestJoinedValue::select_buffers( - buffer_i64.downcast_for_message().unwrap(), - buffer_f64.downcast_for_message().unwrap(), - buffer_string.downcast_for_message().unwrap(), - buffer_generic.downcast_for_message().unwrap(), - buffer_any.downcast_buffer().unwrap(), - ); - - 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(buffers.any.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, - "hello".to_string(), - "world".to_string(), - 42_i64, - ), - 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_eq!(value.generic, "world"); - assert_eq!(*value.any.downcast::().unwrap(), 42); - assert!(context.no_unhandled_errors()); - } - #[derive(Clone, JoinedValue)] #[buffers(buffer_struct_name = FooBuffers)] struct TestDeriveWithConfig {} diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 181fb726..9c55859f 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)] + #[buffers(buffer_struct_name = TestJoinedValueJsonBuffers)] struct TestJoinedValueJson { integer: i64, float: f64, + #[buffers(buffer_type = 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); + } } From e61c3ff0f7ac67584f628841b0aee4eaabd22d4e Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Fri, 14 Feb 2025 08:35:45 +0000 Subject: [PATCH 17/20] put unused code into its own mod; to_phantom_data uses fn(...) to allow impl some traits like Copy, Send, Sync Signed-off-by: Teo Koon Peng --- macros/src/buffer.rs | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index d9c131f4..6f4ae5ec 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -60,20 +60,26 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result` -// Currently unused but could be used in the future -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<(#(#lifetimes,)* #(#ty_params,)*)> } +/// 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 { From 5299c25b2be749fbb613a2c74371b32aa73df894 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Mon, 17 Feb 2025 02:01:05 +0000 Subject: [PATCH 18/20] rename helper attributes Signed-off-by: Teo Koon Peng --- macros/src/buffer.rs | 32 +++++++++++++------------------- macros/src/lib.rs | 2 +- src/buffer/buffer_map.rs | 5 ++--- src/buffer/json_buffer.rs | 4 ++-- 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index 6f4ae5ec..cbd17f7f 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -13,17 +13,14 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result = field_config - .iter() - .map(|config| &config.buffer_type) - .collect(); + let buffer: Vec<&Type> = field_config.iter().map(|config| &config.buffer).collect(); let buffer_struct: ItemStruct = parse_quote! { #[derive(Clone)] - #[allow(non_camel_case_types)] + #[allow(non_camel_case_types, unused)] #buffer_struct_vis struct #buffer_struct_ident #impl_generics #where_clause { #( - #buffer_struct_vis #field_ident: #buffer_type, + #buffer_struct_vis #field_ident: #buffer, )* } }; @@ -41,7 +38,7 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result #buffer_struct_ident #ty_generics { #buffer_struct_ident { @@ -95,11 +92,11 @@ impl StructConfig { let attr = data_struct .attrs .iter() - .find(|attr| attr.path().is_ident("buffers")); + .find(|attr| attr.path().is_ident("joined")); if let Some(attr) = attr { attr.parse_nested_meta(|meta| { - if meta.path.is_ident("buffer_struct_name") { + if meta.path.is_ident("buffers_struct_name") { config.buffer_struct_name = meta.value()?.parse()?; } Ok(()) @@ -113,25 +110,25 @@ impl StructConfig { } struct FieldConfig { - buffer_type: Type, + buffer: Type, } impl FieldConfig { fn from_field(field: &Field) -> Self { let ty = &field.ty; let mut config = Self { - buffer_type: parse_quote! { ::bevy_impulse::Buffer<#ty> }, + buffer: parse_quote! { ::bevy_impulse::Buffer<#ty> }, }; let attr = field .attrs .iter() - .find(|attr| attr.path().is_ident("buffers")); + .find(|attr| attr.path().is_ident("joined")); if let Some(attr) = attr { attr.parse_nested_meta(|meta| { - if meta.path.is_ident("buffer_type") { - config.buffer_type = meta.value()?.parse()?; + if meta.path.is_ident("buffer") { + config.buffer = meta.value()?.parse()?; } Ok(()) }) @@ -174,10 +171,7 @@ fn impl_buffer_map_layout( 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_type: Vec<&Type> = field_config - .iter() - .map(|config| &config.buffer_type) - .collect(); + 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! { @@ -192,7 +186,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 = if let Ok(buffer) = compatibility.require_buffer_type::<#buffer_type>(#map_key, buffers) { + let #field_ident = if let Ok(buffer) = compatibility.require_buffer_type::<#buffer>(#map_key, buffers) { buffer } else { return Err(compatibility); diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 421343bd..df58fdc6 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(buffers))] +#[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) { diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 2c4244ee..cb4f8144 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -321,8 +321,7 @@ mod tests { float: f64, string: String, generic: T, - #[buffers(buffer_type = AnyBuffer)] - #[allow(unused)] + #[joined(buffer = AnyBuffer)] any: AnyMessageBox, } @@ -424,7 +423,7 @@ mod tests { } #[derive(Clone, JoinedValue)] - #[buffers(buffer_struct_name = FooBuffers)] + #[joined(buffers_struct_name = FooBuffers)] struct TestDeriveWithConfig {} #[test] diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 9c55859f..d470da01 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -1354,11 +1354,11 @@ mod tests { } #[derive(Clone, JoinedValue)] - #[buffers(buffer_struct_name = TestJoinedValueJsonBuffers)] + #[joined(buffers_struct_name = TestJoinedValueJsonBuffers)] struct TestJoinedValueJson { integer: i64, float: f64, - #[buffers(buffer_type = JsonBuffer)] + #[joined(buffer = JsonBuffer)] json: JsonMessage, } From b110e870eadcd36162aea93b38ee0e6f99b974b4 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 17 Feb 2025 14:18:15 +0800 Subject: [PATCH 19/20] Test for generics in buffers, and fix Clone/Copy semantics Signed-off-by: Michael X. Grey --- macros/src/buffer.rs | 42 +++++++++++++--- src/buffer/buffer_map.rs | 68 ++++++++++++++++++++++++++ src/testing.rs | 103 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 204 insertions(+), 9 deletions(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index cbd17f7f..8f76bbd1 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -14,9 +14,9 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result = field_config.iter().map(|config| &config.buffer).collect(); + let noncopy = field_config.iter().any(|config| config.noncopy); let buffer_struct: ItemStruct = parse_quote! { - #[derive(Clone)] #[allow(non_camel_case_types, unused)] #buffer_struct_vis struct #buffer_struct_ident #impl_generics #where_clause { #( @@ -25,6 +25,32 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result 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)?; @@ -35,6 +61,8 @@ pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result }, + noncopy: false, }; - let attr = field - .attrs - .iter() - .find(|attr| attr.path().is_ident("joined")); - - if let Some(attr) = attr { + 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. diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index cb4f8144..970e5dde 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -431,4 +431,72 @@ mod tests { // 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/testing.rs b/src/testing.rs index 83d21dd9..33bb8d61 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; @@ -35,7 +35,8 @@ use crate::{ flush_impulses, AddContinuousServicesExt, AsyncServiceInput, BlockingMap, BlockingServiceInput, Builder, ContinuousQuery, ContinuousQueueView, ContinuousService, FlushParameters, GetBufferedSessionsFn, Promise, RunCommandsOnWorldExt, Scope, Service, SpawnWorkflowExt, - StreamOf, StreamPack, UnhandledErrors, WorkflowSettings, + StreamOf, StreamPack, UnhandledErrors, WorkflowSettings, OperationRoster, OperationResult, + OperationError, Buffered, Bufferable, Joined, Buffer, Accessed, BufferKey, AnyBuffer, }; pub struct TestingContext { @@ -478,3 +479,101 @@ 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 31b392703a66f6f235a761ec0217eb99a89e9827 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 17 Feb 2025 14:18:59 +0800 Subject: [PATCH 20/20] Fix style Signed-off-by: Michael X. Grey --- macros/src/buffer.rs | 6 +++- src/buffer/buffer_map.rs | 64 +++++++++++++++++++++------------------- src/testing.rs | 17 ++++++----- 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/macros/src/buffer.rs b/macros/src/buffer.rs index 8f76bbd1..6ab201dc 100644 --- a/macros/src/buffer.rs +++ b/macros/src/buffer.rs @@ -150,7 +150,11 @@ impl FieldConfig { noncopy: false, }; - for attr in field.attrs.iter().filter(|attr| attr.path().is_ident("joined")) { + 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()?; diff --git a/src/buffer/buffer_map.rs b/src/buffer/buffer_map.rs index 970e5dde..c522dbfe 100644 --- a/src/buffer/buffer_map.rs +++ b/src/buffer/buffer_map.rs @@ -447,39 +447,43 @@ mod tests { #[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()), - )); + let mut context = TestingContext::minimal_plugins(); - multi_generic_buffers.join(builder).connect(scope.terminate); - copy.join(builder).connect(scope.terminate); - }); + 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() + commands + .request((5, "hello".to_string()), workflow) + .take_response() }); context.run_with_conditions(&mut promise, Duration::from_secs(2)); diff --git a/src/testing.rs b/src/testing.rs index 33bb8d61..a5ad4ec1 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -32,11 +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, OperationRoster, OperationResult, - OperationError, Buffered, Bufferable, Joined, Buffer, Accessed, BufferKey, AnyBuffer, + 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 { @@ -494,9 +494,12 @@ impl NonCopyBuffer { std::any::TypeId::of::>(), Box::new(|location| { Box::new(NonCopyBuffer:: { - inner: Buffer { location, _ignore: Default::default() }, + inner: Buffer { + location, + _ignore: Default::default(), + }, }) - }) + }), ); } }