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}