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