1use bevy_app::{App, Plugin, Startup};
2use bevy_ecs::message::Message;
3use bevy_ecs::prelude::Messages;
4use bevy_ecs::resource::Resource;
5use bevy_ecs::world::World;
6use tokio_xmpp::Stanza;
7
8use crate::component::system::spawn_stanza_workers;
9
10pub trait StanzaMatcher: Send + Sync + 'static {
11 type Message: Message;
12 fn matches(&mut self, candidate: &Stanza) -> Option<Self::Message>;
13}
14
15// =============================================================================
16// HList Structure
17// =============================================================================
18
19pub struct HNil;
20
21pub struct HCons<Head, Tail> {
22 pub head: Head,
23 pub tail: Tail,
24}
25
26pub struct Matcher<M>(pub M);
27
28// =============================================================================
29// Dispatch Trait
30// =============================================================================
31
32pub trait StanzaDispatch: Send + Sync + 'static {
33 fn dispatch(&mut self, stanza: &Stanza, world: &mut World);
34 fn register(app: &mut App);
35}
36
37impl StanzaDispatch for HNil {
38 fn dispatch(&mut self, _stanza: &Stanza, _world: &mut World) {}
39 fn register(_app: &mut App) {}
40}
41
42impl<M, Tail> StanzaDispatch for HCons<Matcher<M>, Tail>
43where
44 M: StanzaMatcher,
45 Tail: StanzaDispatch,
46{
47 fn dispatch(&mut self, stanza: &Stanza, world: &mut World) {
48 if let Some(msg) = self.head.0.matches(stanza) {
49 world.resource_mut::<Messages<M::Message>>().write(msg);
50 return;
51 }
52 self.tail.dispatch(stanza, world)
53 }
54
55 fn register(app: &mut App) {
56 app.add_message::<M::Message>();
57 Tail::register(app);
58 }
59}
60
61// =============================================================================
62// Builder API
63// =============================================================================
64
65impl HNil {
66 pub fn matcher<M: StanzaMatcher>(self, m: M) -> HCons<Matcher<M>, HNil> {
67 HCons {
68 head: Matcher(m),
69 tail: HNil,
70 }
71 }
72}
73
74impl<Head, Tail> HCons<Head, Tail> {
75 pub fn matcher<M: StanzaMatcher>(self, m: M) -> HCons<Matcher<M>, Self> {
76 HCons {
77 head: Matcher(m),
78 tail: self,
79 }
80 }
81}
82
83// =============================================================================
84// Resource Wrapper
85// =============================================================================
86
87#[derive(Resource)]
88pub struct MatcherRegistry<D: StanzaDispatch> {
89 pub dispatchers: D,
90}
91
92// =============================================================================
93// Plugin
94// =============================================================================
95
96pub struct StanzaMatcherPlugin<D> {
97 dispatchers: D,
98}
99
100impl StanzaMatcherPlugin<HNil> {
101 pub fn new() -> Self {
102 StanzaMatcherPlugin { dispatchers: HNil }
103 }
104}
105
106impl Default for StanzaMatcherPlugin<HNil> {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112impl<D> StanzaMatcherPlugin<D> {
113 pub fn matcher<M: StanzaMatcher>(self, m: M) -> StanzaMatcherPlugin<HCons<Matcher<M>, D>> {
114 StanzaMatcherPlugin {
115 dispatchers: HCons {
116 head: Matcher(m),
117 tail: self.dispatchers,
118 },
119 }
120 }
121}
122
123impl<D: StanzaDispatch + Clone> Plugin for StanzaMatcherPlugin<D> {
124 fn build(&self, app: &mut App) {
125 D::register(app);
126 app.insert_resource(MatcherRegistry {
127 dispatchers: self.dispatchers.clone(),
128 });
129 app.add_systems(Startup, spawn_stanza_workers::<D>);
130 }
131}
132
133// =============================================================================
134// Clone Impls
135// =============================================================================
136
137impl Clone for HNil {
138 fn clone(&self) -> Self {
139 HNil
140 }
141}
142
143impl<M: Clone> Clone for Matcher<M> {
144 fn clone(&self) -> Self {
145 Matcher(self.0.clone())
146 }
147}
148
149impl<Head: Clone, Tail: Clone> Clone for HCons<Head, Tail> {
150 fn clone(&self) -> Self {
151 HCons {
152 head: self.head.clone(),
153 tail: self.tail.clone(),
154 }
155 }
156}