From 07402169eea76887c741c3a4994d08053f61d985 Mon Sep 17 00:00:00 2001 From: Navneet Aman Date: Fri, 11 Jul 2025 11:47:59 +0530 Subject: [PATCH] packet systems prototype. --- crates/bevy_ecs/macros/src/component.rs | 29 +++ crates/bevy_ecs/macros/src/lib.rs | 4 + crates/bevy_ecs/src/lib.rs | 1 + .../src/packet/exclusivepacketsystem.rs | 105 +++++++++ crates/bevy_ecs/src/packet/mod.rs | 213 ++++++++++++++++++ crates/bevy_ecs/src/packet/optionpacket.rs | 69 ++++++ crates/bevy_ecs/src/packet/packetsystem.rs | 110 +++++++++ crates/bevy_ecs/src/world/mod.rs | 2 + 8 files changed, 533 insertions(+) create mode 100644 crates/bevy_ecs/src/packet/exclusivepacketsystem.rs create mode 100644 crates/bevy_ecs/src/packet/mod.rs create mode 100644 crates/bevy_ecs/src/packet/optionpacket.rs create mode 100644 crates/bevy_ecs/src/packet/packetsystem.rs diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 1322022581..ab77081483 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -344,6 +344,35 @@ pub fn derive_component(input: TokenStream) -> TokenStream { #relationship_target }) } +pub fn derive_packet(input: TokenStream) -> TokenStream { + let mut ast = parse_macro_input!(input as DeriveInput); + let bevy_ecs_path: Path = crate::bevy_ecs_path(); + + ast.generics + .make_where_clause() + .predicates + .push(parse_quote! { Self: Send + Sync + 'static }); + + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + let inner_generic = if type_generics.to_token_stream().is_empty() { + quote! {} + } else { + quote! {<'i>} + }; + + let ts = TokenStream::from(quote! { + impl #impl_generics #bevy_ecs_path::packet::Packet for #struct_name #type_generics #where_clause { } + impl #impl_generics #bevy_ecs_path::packet::SystemInput for #struct_name #type_generics { + type Param<'i> = #struct_name #inner_generic; + type Inner<'i> = #struct_name #inner_generic; + fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { + this + } + } + }); + return ts; +} const ENTITIES: &str = "entities"; diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 7b388f4a14..c61258bcaf 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -672,6 +672,10 @@ pub fn derive_component(input: TokenStream) -> TokenStream { component::derive_component(input) } +#[proc_macro_derive(Packet)] +pub fn derive_packet(input: TokenStream) -> TokenStream { + component::derive_packet(input) +} /// Implement the `FromWorld` trait. #[proc_macro_derive(FromWorld, attributes(from_world))] pub fn derive_from_world(input: TokenStream) -> TokenStream { diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 8a07cdc8e1..9b91a02c83 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -38,6 +38,7 @@ pub mod entity; pub mod entity_disabling; pub mod error; pub mod event; +pub mod packet; pub mod hierarchy; pub mod intern; pub mod label; diff --git a/crates/bevy_ecs/src/packet/exclusivepacketsystem.rs b/crates/bevy_ecs/src/packet/exclusivepacketsystem.rs new file mode 100644 index 0000000000..140cff8ae4 --- /dev/null +++ b/crates/bevy_ecs/src/packet/exclusivepacketsystem.rs @@ -0,0 +1,105 @@ +use crate::{component::{ComponentId, Tick}, system::{ExclusiveFunctionSystem, ExclusiveSystemParamFunction, IntoSystem, IsExclusiveFunctionSystem, System, SystemIn}, world::{unsafe_world_cell::UnsafeWorldCell, World}}; + +use super::{packetsystem::IntoPacketSystem, OptionPacket}; + +pub struct ExclusivePacketSystem +where + F: ExclusiveSystemParamFunction, +{ + inner: ExclusiveFunctionSystem, +} +impl IntoPacketSystem for F +where + Marker: 'static, + F: ExclusiveSystemParamFunction, + F::Out: OptionPacket, +{ + type System = ExclusivePacketSystem; + fn into_system(func: Self) -> Self::System { + ExclusivePacketSystem { + inner: IntoSystem::into_system(func) + } + } +} + +impl System for ExclusivePacketSystem +where + Marker: 'static, + F: ExclusiveSystemParamFunction, + F::Out: OptionPacket, +{ + type In = F::In; + type Out = (); + + + #[inline] + fn is_send(&self) -> bool { + self.inner.is_send() + } + + #[inline] + fn is_exclusive(&self) -> bool { + self.inner.is_exclusive() + } + + #[inline] + fn has_deferred(&self) -> bool { + self.inner.has_deferred() + } + + fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Result { + let out = as System>::run(&mut self.inner, input, world)?; + out.run(world); + Ok(()) + } + + #[inline] + fn apply_deferred(&mut self, world: &mut World) { + self.inner.apply_deferred(world); + } + + #[inline] + fn queue_deferred(&mut self, world: crate::world::DeferredWorld) { + self.inner.queue_deferred(world); + } + + fn get_last_run(&self) -> Tick { + self.inner.get_last_run() + } + + fn set_last_run(&mut self, last_run: Tick) { + self.inner.set_last_run(last_run); + } + + fn name(&self) -> bevy_utils::prelude::DebugName { + self.inner.name() + } + + fn flags(&self) -> crate::system::SystemStateFlags { + self.inner.flags() + } + + unsafe fn run_unsafe( + &mut self, + _input: SystemIn<'_, Self>, + _world: UnsafeWorldCell, + ) -> Result { + panic!("exclusive system") + } + + unsafe fn validate_param_unsafe( + &mut self, + world: UnsafeWorldCell, + ) -> Result<(), crate::system::SystemParamValidationError> { + self.inner.validate_param_unsafe(world) + } + + fn initialize(&mut self, world: &mut World) -> crate::query::FilteredAccessSet { + self.inner.initialize(world) + } + + fn check_change_tick(&mut self, check: crate::component::CheckChangeTicks) { + self.inner.check_change_tick(check) + } + +} diff --git a/crates/bevy_ecs/src/packet/mod.rs b/crates/bevy_ecs/src/packet/mod.rs new file mode 100644 index 0000000000..e37d56b459 --- /dev/null +++ b/crates/bevy_ecs/src/packet/mod.rs @@ -0,0 +1,213 @@ +pub mod packetsystem; +pub mod exclusivepacketsystem; +mod optionpacket; +pub use optionpacket::OptionPacket; +use core::any::{type_name, TypeId}; +use std::boxed::Box; +use crate::{system::Commands}; +pub use crate::system::SystemInput; +use crate::system::System; +use packetsystem::IntoPacketSystem; +use smallvec::SmallVec; +use crate::{system::BoxedSystem, world::World}; +pub use bevy_ecs_macros::Packet; + +pub struct PacketInSystem { + pub v: BoxedSystem, + pub tid: TypeId, +} +pub struct RegisteredSystems{ + pub v: SmallVec<[PacketInSystem; 1]>, +} +pub trait Packet: Send + Sync + SystemInput + 'static { } + +pub fn register_system(world: &mut World, f: F) +where + I: SystemInput + 'static, + Out: OptionPacket, + F: IntoPacketSystem + 'static, + M: 'static, +{ + // don't forget to put it back. + let mut systems = world.remove_packet_system::().unwrap_or_default(); + + let tid = TypeId::of::(); + #[cfg(debug_assertions)] + { + for system in &systems.v { + assert_ne!(system.tid, tid); + } + } + let mut system = IntoPacketSystem::into_system(f); + system.initialize(world); + let system = PacketInSystem { v: Box::new(system), tid }; + systems.v.push(system); + + // put back here. + world.put_back_packet_system(systems); +} +pub fn unregister_system(world: &mut World, _: F) +where + I: SystemInput + 'static, + Out: OptionPacket, + F: IntoPacketSystem + 'static, + M: 'static, +{ + world.with_packet_system::(|_, systems| { + let tid = TypeId::of::(); + systems.v.retain(|s| s.tid != tid); + }); +} +pub fn run_this_packet_system<'a, E>(packet: E, world: &mut World) +where + E: Packet, + for<'d> E: SystemInput = E>, +{ + run_for_ref_packet(world, &packet); + run_for_val_packet(world, packet); +} + +fn run_for_val_packet(world: &mut World, event: E) +where + E: Packet, + E: for<'e> SystemInput = E> +{ + world.with_packet_system::(|world, systems| { + let mut systems_iter = systems.v.iter_mut(); + let Some(system) = systems_iter.next() else { return }; + system.v.run(event, world); + debug_assert!(systems_iter.len() == 0, "Only one system can take value {:?}", type_name::()); + }); +} + +fn run_for_ref_packet(world: &mut World, event: &E) +where + E: Packet, +{ + world.with_packet_system::<&E>(|world, systems| { + for system in &mut systems.v { + system.v.run(event, world); + } + }); +} +impl Default for RegisteredSystems { + fn default() -> Self { + RegisteredSystems { v: Default::default()} + } +} + +impl World { + pub fn send<'a,'b,E>(&mut self, packet: E) + where + E: Packet, + E: for<'e> SystemInput = E>, + { + run_this_packet_system::(packet, self); + } + + pub fn register_packet_system(&mut self, f: F) + where + I: SystemInput + 'static, + Out: OptionPacket, + F: IntoPacketSystem + 'static, + M: 'static, + { + register_system(self, f); + } + pub fn unregister_packet_system(&mut self, f: F) + where + I: SystemInput + 'static, + Out: OptionPacket, + F: IntoPacketSystem + 'static, + M: 'static, + { + unregister_system(self, f); + } + + fn with_packet_system(&mut self, f: impl FnOnce(&mut World, &mut RegisteredSystems),) + where + I: SystemInput + 'static, + { + let Some(mut systems) = self.remove_packet_system::() else {return}; + f(self, &mut systems); + self.put_back_packet_system(systems); + } + + /// don't forget to put it back. + fn remove_packet_system(&mut self) -> Option>> { + let packet_systems = &mut self.packet_systems; + let rv = packet_systems.remove(&TypeId::of::()); + return rv.map(|v| v.downcast().unwrap()); + } + + fn put_back_packet_system(&mut self, systems: Box>) { + let event_systems = &mut self.packet_systems; + let tid = TypeId::of::(); + debug_assert!(!event_systems.contains_key(&tid)); + event_systems.insert(tid, systems); + } + +} +impl<'w,'s> Commands<'w,'s> { + pub fn send(&mut self, packet: E) + where + E: Packet, + for<'e> E: SystemInput = E>, + { + self.queue(move |world: &mut World| world.send(packet)); + } +} +impl SystemInput for &E { + type Param<'i> = &'i E; + type Inner<'i> = &'i E; + fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { + this + } +} +#[cfg(test)] +mod tests { + use crate::{system::ResMut, world::World}; + use super::Packet; + use crate::resource::Resource; + + + #[derive(Resource)] + struct Count(u8); + + #[derive(Packet)] + struct Input(u8); + + #[derive(Packet)] + struct Moved; + + #[test] + fn test() { + let mut world = World::new(); + world.insert_resource(Count(0)); + world.register_packet_system(move_player); + world.register_packet_system(count_moved); + world.register_packet_system(count_moved1); + world.register_packet_system(count_moved2); + world.send(Input(b'a')); + let count = world.get_resource::().unwrap(); + assert_eq!(count.0, 3); + } + + fn move_player(Input(input): Input) -> Option { + match input { + b'a' => Some(Moved), + _ => None + } + } + + fn count_moved1(_: &Moved, mut count: ResMut) { + count.0 += 1; + } + fn count_moved2(_: &Moved, mut count: ResMut) { + count.0 += 1; + } + fn count_moved(_: Moved, mut count: ResMut) { + count.0 += 1; + } + +} diff --git a/crates/bevy_ecs/src/packet/optionpacket.rs b/crates/bevy_ecs/src/packet/optionpacket.rs new file mode 100644 index 0000000000..e79f5396ce --- /dev/null +++ b/crates/bevy_ecs/src/packet/optionpacket.rs @@ -0,0 +1,69 @@ +use crate::world::World; + +use super::{run_this_packet_system, Packet, SystemInput}; + +pub trait OptionPacket { + fn run(self, world: &mut World); +} +impl OptionPacket for (){ fn run(self, _: &mut World) {} } + +impl OptionPacket for E +where + for<'e> E: SystemInput = E>, +{ + fn run(self, world: &mut World) { + run_this_packet_system::(self, world); + } +} +impl OptionPacket for Option { + fn run(self, world: &mut World) { + let Some(event) = self else {return}; + event.run(world); + } +} +macro_rules! impl_option_event_tuple { + ($($param: ident),*) => { + impl<$($param: OptionPacket,)*> OptionPacket for ($($param,)*) { + fn run(self, world: &mut World) { + #[allow(non_snake_case)] + let ($($param,)*) = self; + $( + $param.run(world); + )* + } + } + } +} + +impl_option_event_tuple!(O1); +impl_option_event_tuple!(O1, O2); +impl_option_event_tuple!(O1, O2, O3); +impl_option_event_tuple!(O1, O2, O3, O4); +impl_option_event_tuple!(O1, O2, O3, O4, O5); +impl_option_event_tuple!(O1, O2, O3, O4, O5, O6); +impl_option_event_tuple!(O1, O2, O3, O4, O5, O6, O7); +impl_option_event_tuple!(O1, O2, O3, O4, O5, O6, O7, O8); +impl_option_event_tuple!(O1, O2, O3, O4, O5, O6, O7, O8, O9); +impl_option_event_tuple!(O1, O2, O3, O4, O5, O6, O7, O8, O9, O10); +impl_option_event_tuple!(O1, O2, O3, O4, O5, O6, O7, O8, O9, O10, O11); +impl_option_event_tuple!(O1, O2, O3, O4, O5, O6, O7, O8, O9, O10, O11, O12); +impl_option_event_tuple!(O1, O2, O3, O4, O5, O6, O7, O8, O9, O10, O11, O12, O13); +impl_option_event_tuple!(O1, O2, O3, O4, O5, O6, O7, O8, O9, O10, O11, O12, O13, O14); +impl_option_event_tuple!(O1, O2, O3, O4, O5, O6, O7, O8, O9, O10, O11, O12, O13, O14, O15); +impl_option_event_tuple!(O1, O2, O3, O4, O5, O6, O7, O8, O9, O10, O11, O12, O13, O14, O15, O16); + +macro_rules! impl_option_packet_array { + ($($N: literal),*) => { + $( + impl OptionPacket for [O;$N] { + fn run(self, world: &mut World) { + for packet in self { + packet.run(world); + } + } + } + )* + }; +} + +impl_option_packet_array!(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); diff --git a/crates/bevy_ecs/src/packet/packetsystem.rs b/crates/bevy_ecs/src/packet/packetsystem.rs new file mode 100644 index 0000000000..4d917cded0 --- /dev/null +++ b/crates/bevy_ecs/src/packet/packetsystem.rs @@ -0,0 +1,110 @@ +use crate::{ + component::Tick, + system::{ + FunctionSystem, IntoResult, IntoSystem, IsFunctionSystem, RunSystemError, System, SystemIn, SystemInput, SystemParamFunction + }, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World}, +}; + +use super::OptionPacket; + +pub trait IntoPacketSystem: Sized { + type System: System; + fn into_system(this: Self) -> Self::System; +} +pub struct FunctionPacketSystem +where + F: SystemParamFunction, +{ + inner: FunctionSystem, +} +impl IntoPacketSystem for F +where + Marker: 'static, + F: SystemParamFunction, + F::Out: OptionPacket, +{ + type System = FunctionPacketSystem; + fn into_system(func: Self) -> Self::System { + let inner = IntoSystem::into_system(func); + return FunctionPacketSystem { inner }; + } +} +impl System for FunctionPacketSystem +where + Marker: 'static, + F: SystemParamFunction, + F::Out: OptionPacket, +{ + type In = F::In; + type Out = (); + + #[inline] + fn is_send(&self) -> bool { + self.inner.is_send() + } + + #[inline] + fn is_exclusive(&self) -> bool { + self.inner.is_exclusive() + } + + #[inline] + fn has_deferred(&self) -> bool { + self.inner.has_deferred() + } + fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Result { + let out = self.inner.run(input, world)?; + let rv = out.run(world); + return IntoResult::into_result(rv); + } + + #[inline] + fn apply_deferred(&mut self, world: &mut World) { + self.inner.apply_deferred(world); + } + + #[inline] + fn queue_deferred(&mut self, world: DeferredWorld) { + self.inner.queue_deferred(world); + } + + fn get_last_run(&self) -> Tick { + self.inner.get_last_run() + } + + fn set_last_run(&mut self, last_run: Tick) { + self.inner.set_last_run(last_run); + } + + fn flags(&self) -> crate::system::SystemStateFlags { + self.inner.flags() + } + + fn name(&self) -> bevy_utils::prelude::DebugName { + self.inner.name() + } + + unsafe fn validate_param_unsafe( + &mut self, + world: UnsafeWorldCell, + ) -> Result<(), crate::system::SystemParamValidationError> { + self.inner.validate_param_unsafe(world) + } + + fn initialize(&mut self, world: &mut World) -> crate::query::FilteredAccessSet { + self.inner.initialize(world) + } + + fn check_change_tick(&mut self, check: crate::component::CheckChangeTicks) { + self.inner.check_change_tick(check) + } + + unsafe fn run_unsafe( + &mut self, + _input: SystemIn<'_, Self>, + _world: UnsafeWorldCell, + ) -> Result { + unimplemented!("no multithreading, use run") + } +} diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index e77b348c96..f075183b38 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -106,6 +106,7 @@ pub struct World { pub(crate) last_check_tick: Tick, pub(crate) last_trigger_id: u32, pub(crate) command_queue: RawCommandQueue, + pub(crate) packet_systems: bevy_platform::collections::HashMap>, } impl Default for World { @@ -127,6 +128,7 @@ impl Default for World { last_trigger_id: 0, command_queue: RawCommandQueue::new(), component_ids: ComponentIds::default(), + packet_systems: Default::default(), }; world.bootstrap(); world