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}