bevy_ios_iap/
request.rs

1use std::marker::PhantomData;
2
3use bevy_app::{App, PreUpdate};
4use bevy_ecs::{
5    prelude::*,
6    system::{EntityCommands, IntoObserverSystem, SystemParam},
7};
8
9use crate::{
10    IosIapProductsResponse, IosIapPurchaseResponse, IosIapTransactionFinishResponse,
11    IosIapTransactionResponse, plugin::IosIapResponse,
12};
13
14#[derive(EntityEvent, Debug)]
15pub struct CurrentEntitlements {
16    pub entity: Entity,
17    pub response: IosIapTransactionResponse,
18}
19
20#[derive(EntityEvent, Debug)]
21pub struct Products {
22    pub entity: Entity,
23    pub response: IosIapProductsResponse,
24}
25
26#[derive(EntityEvent, Debug)]
27pub struct Purchase {
28    pub entity: Entity,
29    pub response: IosIapPurchaseResponse,
30}
31
32#[derive(EntityEvent, Debug)]
33pub struct FinishTransaction {
34    pub entity: Entity,
35    pub response: IosIapTransactionFinishResponse,
36}
37
38#[derive(EntityEvent, Debug)]
39pub struct AllTransactions {
40    pub entity: Entity,
41    pub response: IosIapTransactionResponse,
42}
43
44#[derive(Resource, Default)]
45struct BevyIosIapSate {
46    request_id: i64,
47}
48
49#[derive(Component)]
50#[component(storage = "SparseSet")]
51struct RequestCurrentEntitlements;
52
53#[derive(Component)]
54#[component(storage = "SparseSet")]
55struct RequestProducts;
56
57#[derive(Component)]
58#[component(storage = "SparseSet")]
59struct RequestPurchase;
60
61#[derive(Component)]
62#[component(storage = "SparseSet")]
63struct RequestFinishTransaction;
64
65#[derive(Component)]
66#[component(storage = "SparseSet")]
67struct RequestAllTransactions;
68
69#[derive(Component)]
70struct RequestId(i64);
71
72#[derive(Component)]
73struct RequestEntity;
74
75#[derive(SystemParam)]
76pub struct BevyIosIap<'w, 's> {
77    commands: Commands<'w, 's>,
78    res: ResMut<'w, BevyIosIapSate>,
79}
80
81impl BevyIosIap<'_, '_> {
82    pub fn current_entitlements(&mut self) -> BevyIosIapRequestBuilder<'_, CurrentEntitlements> {
83        let id = self.res.request_id;
84        self.res.request_id += 1;
85        crate::methods::current_entitlements(id);
86        BevyIosIapRequestBuilder::new(self.commands.spawn((
87            RequestCurrentEntitlements,
88            RequestId(id),
89            RequestEntity,
90        )))
91    }
92
93    pub fn products(&mut self, products: Vec<String>) -> BevyIosIapRequestBuilder<'_, Products> {
94        let id = self.res.request_id;
95        self.res.request_id += 1;
96        crate::methods::get_products(id, products);
97        BevyIosIapRequestBuilder::new(self.commands.spawn((
98            RequestProducts,
99            RequestId(id),
100            RequestEntity,
101        )))
102    }
103
104    pub fn purchase(&mut self, product_id: String) -> BevyIosIapRequestBuilder<'_, Purchase> {
105        let id = self.res.request_id;
106        self.res.request_id += 1;
107        crate::methods::purchase(id, product_id);
108        BevyIosIapRequestBuilder::new(self.commands.spawn((
109            RequestPurchase,
110            RequestId(id),
111            RequestEntity,
112        )))
113    }
114
115    pub fn finish_transaction(
116        &mut self,
117        transaction_id: u64,
118    ) -> BevyIosIapRequestBuilder<'_, FinishTransaction> {
119        let id = self.res.request_id;
120        self.res.request_id += 1;
121        crate::methods::finish_transaction(id, transaction_id);
122        BevyIosIapRequestBuilder::new(self.commands.spawn((
123            RequestFinishTransaction,
124            RequestId(id),
125            RequestEntity,
126        )))
127    }
128
129    pub fn all_transactions(&mut self) -> BevyIosIapRequestBuilder<'_, AllTransactions> {
130        let id = self.res.request_id;
131        self.res.request_id += 1;
132        crate::methods::all_transactions(id);
133        BevyIosIapRequestBuilder::new(self.commands.spawn((
134            RequestAllTransactions,
135            RequestId(id),
136            RequestEntity,
137        )))
138    }
139}
140
141pub struct BevyIosIapRequestBuilder<'a, T>(EntityCommands<'a>, PhantomData<T>);
142
143impl<'a, T> BevyIosIapRequestBuilder<'a, T>
144where
145    T: 'static + Event + bevy_ecs::event::EntityEvent,
146{
147    fn new(ec: EntityCommands<'a>) -> Self {
148        Self(ec, PhantomData)
149    }
150
151    pub fn on_response<RB: Bundle, RM, OR: IntoObserverSystem<T, RB, RM>>(
152        &mut self,
153        on_response: OR,
154    ) -> &mut Self {
155        self.0.observe(on_response);
156        self
157    }
158}
159
160#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
161pub struct BevyIosIapSet;
162
163pub fn plugin(app: &mut App) {
164    app.init_resource::<BevyIosIapSate>();
165    app.add_systems(
166        PreUpdate,
167        (
168            cleanup_finished_requests,
169            process_events.run_if(on_message::<IosIapResponse>),
170        )
171            .chain()
172            .in_set(BevyIosIapSet),
173    );
174}
175
176fn cleanup_finished_requests(
177    mut commands: Commands,
178    query: Query<Entity, (With<RequestEntity>, Without<RequestId>)>,
179) {
180    for e in query.iter() {
181        if let Ok(mut ec) = commands.get_entity(e) {
182            ec.despawn();
183        }
184    }
185}
186
187#[allow(unused_variables, unused_mut)]
188fn process_events(
189    mut events: MessageReader<IosIapResponse>,
190    mut commands: Commands,
191    query_current_entitlements: Query<(Entity, &RequestId), With<RequestCurrentEntitlements>>,
192    query_products: Query<(Entity, &RequestId), With<RequestProducts>>,
193    query_purchases: Query<(Entity, &RequestId), With<RequestPurchase>>,
194) {
195    for e in events.read() {
196        match e {
197            IosIapResponse::CurrentEntitlements((r, response)) => {
198                for (e, id) in &query_current_entitlements {
199                    if id.0 == *r {
200                        commands.trigger(CurrentEntitlements {
201                            entity: e,
202                            response: response.clone(),
203                        });
204                        if let Ok(mut ec) = commands.get_entity(e) {
205                            ec.remove::<RequestId>();
206                        }
207                        break;
208                    }
209                }
210            }
211            IosIapResponse::Products((r, response)) => {
212                for (e, id) in &query_products {
213                    if id.0 == *r {
214                        commands.trigger(Products {
215                            entity: e,
216                            response: response.clone(),
217                        });
218                        if let Ok(mut ec) = commands.get_entity(e) {
219                            ec.remove::<RequestId>();
220                        }
221                        break;
222                    }
223                }
224            }
225            IosIapResponse::Purchase((r, response)) => {
226                for (e, id) in &query_purchases {
227                    if id.0 == *r {
228                        commands.trigger(Purchase {
229                            entity: e,
230                            response: response.clone(),
231                        });
232                        if let Ok(mut ec) = commands.get_entity(e) {
233                            ec.remove::<RequestId>();
234                        }
235                        break;
236                    }
237                }
238            }
239            IosIapResponse::TransactionFinished((r, response)) => {
240                for (e, id) in &query_purchases {
241                    if id.0 == *r {
242                        commands.trigger(FinishTransaction {
243                            entity: e,
244                            response: response.clone(),
245                        });
246                        if let Ok(mut ec) = commands.get_entity(e) {
247                            ec.remove::<RequestId>();
248                        }
249                        break;
250                    }
251                }
252            }
253            IosIapResponse::AllTransactions((r, response)) => {
254                for (e, id) in &query_purchases {
255                    if id.0 == *r {
256                        commands.trigger(AllTransactions {
257                            entity: e,
258                            response: response.clone(),
259                        });
260                        if let Ok(mut ec) = commands.get_entity(e) {
261                            ec.remove::<RequestId>();
262                        }
263                        break;
264                    }
265                }
266            }
267        }
268    }
269}