1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use std::fmt::Write;
4
5#[proc_macro_attribute]
6pub fn unity_authoring(_: TokenStream, item: TokenStream) -> TokenStream {
7 let input = proc_macro2::TokenStream::from(item);
8 quote! {
9 #[derive(bevy::prelude::Component, Clone, Copy, Debug)]
10 #[repr(C)]
11 #input
12 }
13 .into()
14}
15
16#[proc_macro_attribute]
17pub fn bevy_state(_: TokenStream, item: TokenStream) -> TokenStream {
18 let input = proc_macro2::TokenStream::from(item);
19 quote! {
20 #[repr(u8)]
21 #[derive(Clone, Eq, PartialEq, Debug, Hash, Default, unrust::bevy::prelude::States)]
22 #input
23 }
24 .into()
25}
26
27#[proc_macro]
28pub fn generate_inbuilt(item: TokenStream) -> TokenStream {
29 let config = syn::parse2::<syn::ExprTuple>(item.into()).expect("expecting tuples");
30
31 let enum_types = config.elems.iter().filter_map(|p| {
32 let syn::Expr::Path(p) = p else {
33 return None;
34 };
35 let p = p.path.get_ident()?;
36 Some(p)
37 });
38
39 let custom_types = enum_types.clone().enumerate().map(|(index, ident)| {
40 let index = index as u8;
41 quote! {
42 #ident= #index
43 }
44 });
45
46 let component_types = enum_types.clone().map(|ident| {
47 quote! {
48 pub #ident: #ident
49 }
50 });
51
52 let ingest_types = enum_types.clone().map(|ident| {
53 let fn_name = format_ident!("{}_ingest_component", ident);
54
55 quote! {
56 InbuiltTypes::#ident => #fn_name(entity, &ele.value.#ident)
57 }
58 });
59
60 let unity_enums =
61 enum_types
62 .clone()
63 .enumerate()
64 .fold(String::default(), |mut acc, (index, ident)| {
65 write!(acc, "{} = {},\n", ident, index).unwrap();
66
67 acc
68 });
69
70 let union_types = enum_types
71 .clone()
72 .fold(String::default(), |mut acc, ident| {
73 write!(
74 acc,
75 r#"
76 [FieldOffset(0)]
77 public {} {};
78"#,
79 ident, ident
80 )
81 .unwrap();
82
83 acc
84 });
85
86 let count = enum_types.clone().count() as i32;
87
88 let csharp_fns = enum_types.clone().map(|ident| {
89 let fn_name = format_ident!("{}_CSHARP_TOKEN", ident);
90
91 quote! {
92 #fn_name()
93 }
94 });
95
96 quote! {
97 #[repr(u8)]
98 pub enum InbuiltTypes {
99 #(#custom_types,)*
100 }
101
102 #[allow(non_snake_case)]
103 pub union InbuiltComponents {
104 #(#component_types,)*
105 }
106
107 #[repr(C)]
108 pub struct InbuiltData {
109 pub ty: InbuiltTypes,
110 pub value: InbuiltComponents,
111 }
112
113 #[repr(C)]
114 pub struct InbuiltEntityData {
115 pub entity: UnityEntity,
116 pub data: *const InbuiltData,
117 pub len: usize,
118 }
119
120 pub unsafe fn ingest_component(entity: &mut EntityMut, components: &[InbuiltData]) {
121 for ele in components {
122 match ele.ty {
123 #(#ingest_types,)*
124 }
125 }
126 }
127
128 const UNITY_TYPES: &str = #unity_enums;
129 const UNITY_UNION: &str = #union_types;
130 const UNITY_COUNT: i32 = #count;
131
132 fn get_inbuilt_csharp_tokens() -> Vec<csharp::Tokens> {
133 vec![#(#csharp_fns,)*]
134 }
135 }
136 .into()
137}
138
139#[proc_macro_attribute]
140pub fn unity_prefab(_: TokenStream, item: TokenStream) -> TokenStream {
141 let input = proc_macro2::TokenStream::from(item.clone());
142
143 let parsed_enum = syn::parse_macro_input!(item as syn::ItemEnum);
144
145 let enum_name = parsed_enum.ident;
146
147 let res_name = format_ident!("{}Resource", enum_name);
148
149 let variants = parsed_enum.variants.iter().enumerate().map(|(index, v)| {
150 let id = &v.ident;
151 quote! {
152 self.vals.insert(#enum_name::#id, unrust::InstantiateEntity {
153 entity: vals[#index].clone()
154 });
155 }
156 });
157
158 quote! {
159 #[derive(PartialEq,Eq, Hash)]
160 #input
161
162 #[derive(unrust::bevy::prelude::Resource, Default)]
163 pub struct #res_name {
164 pub vals: std::collections::HashMap<#enum_name,unrust::InstantiateEntity>
165 }
166
167 impl #res_name {
168 pub fn get_unity_prefab(&self, val: &#enum_name) -> Option<&unrust::InstantiateEntity> {
169 self.vals.get(val)
170 }
171
172 pub fn insert_prefabs(&mut self, vals: &[unrust::UnityEntity]) {
173 #(#variants)*
174 }
175 }
176 }
177 .into()
178}
179
180#[proc_macro_attribute]
181pub fn unrust_setup(attr: TokenStream, item: TokenStream) -> TokenStream {
182 let input = proc_macro2::TokenStream::from(item.clone());
183 let parsed = syn::parse_macro_input!(item as syn::ItemFn);
184 let ident = parsed.sig.ident;
185
186 let custom_incoming = handle_custom_components(attr.clone());
187 let state_incoming = handle_custom_states(attr.clone());
188
189 let states = custom_states(attr.clone());
190 let prefabs = prefab_resources(attr.clone());
191 let register = register_prefabs(attr.clone());
192
193 quote! {
194 #input
195
196 #[derive(Default,Copy,Clone)]
197 #[repr(C)]
198 pub struct Game;
199
200 impl GamePlugin for Game {
201 fn initialize(&self, app: &mut App) {
202 #states
203 #prefabs
204 #ident(app);
205 }
206
207 fn register(&self, world: &mut World, prefabs: unrust::PrefabData) {
208 #register
209 }
210
211 #[allow(clippy::missing_safety_doc)]
212 unsafe fn spawn_custom(
213 &self,
214 entity: &mut unrust::bevy::ecs::world::EntityMut,
215 custom: *const u8,
216 custom_len: usize,
217 custom_state: *const u8,
218 custom_state_len: usize,
219 ) {
220 unsafe { handle_custom_components(entity, custom, custom_len) };
221 unsafe { handle_custom_states(entity, custom_state, custom_state_len); }
222 }
223 }
224
225 #custom_incoming
226
227 #state_incoming
228
229 #[no_mangle]
230 pub extern "C" fn create_game() {
231 let game = Box::new(Game);
232
233 unsafe { unrust::setup_game(game) };
234 }
235 }
236 .into()
237}
238
239fn get_nth_tuple(item: TokenStream, n: usize) -> Option<syn::ExprTuple> {
240 let tokens = proc_macro2::TokenStream::from(item);
241 let config = syn::parse2::<syn::ExprTuple>(tokens).expect("expecting a tuple of tuples");
242
243 let Some(config) = config.elems.iter().nth(n) else {
244 panic!("expected at least n tuples")
245 };
246
247 let syn::Expr::Tuple(types) = config else {
248 panic!("expected tuple for custom incoming types!");
249 };
250
251 Some(types.clone())
252}
253
254fn handle_custom_components(item: TokenStream) -> proc_macro2::TokenStream {
255 let Some(types) = get_nth_tuple(item, 0) else {
256 return quote! {
257 fn handle_custom_components(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {}
258 };
259 };
260
261 let filtered = types.elems.iter().filter_map(|expr| {
262 let syn::Expr::Path(expr) = expr else {
263 return None;
264 };
265
266 let last = expr.path.segments.last()?;
267 Some((last, &expr.path))
268 });
269
270 let custom_types = filtered.clone().enumerate().map(|(index, (exp, _))| {
271 let index = index as u8;
272 quote! {
273 #exp = #index
274 }
275 });
276
277 let component_types = filtered.clone().map(|(exp, rest)| {
278 quote! {
279 pub #exp: #rest
280 }
281 });
282
283 let match_types = filtered.clone().map(|(exp, _)| {
284 quote! {
285 CustomTypes::#exp => entity.insert(ele.value.#exp)
286 }
287 });
288
289 if filtered.count() > 0 {
290 quote! {
291 #[repr(u8)]
292 pub enum CustomTypes {
293 #(#custom_types,)*
294 }
295
296 #[allow(non_snake_case)]
297 union CustomComponents {
298 #(#component_types,)*
299 }
300
301 #[repr(C)]
302 struct CustomData {
303 pub ty: CustomTypes,
304 pub value: CustomComponents,
305 }
306
307 unsafe fn handle_custom_components(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {
308 let components = unsafe { std::slice::from_raw_parts(custom as *const CustomData, len) };
309 for ele in components {
310 match ele.ty {
311 #(#match_types,)*
312 };
313 };
314 }
315 }
316 } else {
317 quote! {
318 fn handle_custom_components(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {}
319 }
320 }
321}
322
323fn handle_custom_states(item: TokenStream) -> proc_macro2::TokenStream {
324 let Some(types) = get_nth_tuple(item, 1) else {
325 return quote! {
326 fn handle_custom_components(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {}
327 };
328 };
329
330 let filtered = types.elems.iter().filter_map(|expr| {
331 let syn::Expr::Path(expr) = expr else {
332 return None;
333 };
334
335 let last = expr.path.segments.last()?;
336 Some((last, &expr.path))
337 });
338
339 let custom_types = filtered.clone().enumerate().map(|(index, (exp, _))| {
340 let index = index as u8;
341 quote! {
342 #exp = #index
343 }
344 });
345
346 let match_types = filtered.clone().map(|(exp, _)| {
347 let ident = &exp.ident;
348 let comp_name = format_ident!("Custom{ident}");
349 quote! {
350 CustomStates::#exp => {
351 entity.insert(#comp_name { val: ele.value });
352 }
353 }
354 });
355
356 let custom_components = filtered.clone().map(|(exp, p)| {
357 let ident = &exp.ident;
358 let comp_name = format_ident!("Custom{ident}");
359 let fn_name = format_ident!("update_{ident}");
360 quote! {
361 #[derive(unrust::bevy::prelude::Component,Debug, Clone, PartialEq)]
362 pub struct #comp_name {
363 pub val: u8,
364 }
365
366 #[allow(non_snake_case)]
367 fn #fn_name(
368 mut next_state: ResMut<NextState<#p>>,
369 entities: Query<&#comp_name, Changed<#comp_name>>,
370 ) {
371 let Ok(state) = entities.get_single() else {
372 return;
373 };
374
375 unrust::tracing::trace!("switching to {}", state.val);
376
377 unsafe {
378 next_state.set(std::mem::transmute(state.val));
379 }
380 }
381 }
382 });
383
384 if filtered.count() > 0 {
385 quote! {
386 #(#custom_components)*
387
388 #[repr(u8)]
389 #[derive(Clone, Debug, PartialEq)]
390 pub enum CustomStates {
391 #(#custom_types,)*
392 }
393
394 #[repr(C)]
395 struct CustomStateData {
396 pub ty: CustomStates,
397 pub value: u8,
398 }
399
400 unsafe fn handle_custom_states(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {
401 let components = unsafe { std::slice::from_raw_parts(custom as *const CustomStateData, len) };
402 for ele in components {
403 match ele.ty {
404 #(#match_types,)*
405 };
406 };
407 }
408 }
409 } else {
410 quote! {
411 unsafe fn handle_custom_states(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {}
412 }
413 }
414}
415
416fn custom_states(item: TokenStream) -> proc_macro2::TokenStream {
417 let Some(states) = get_nth_tuple(item, 1) else {
418 return quote! {};
419 };
420
421 let filtered = states.elems.iter().filter_map(|expr| {
422 let syn::Expr::Path(expr) = expr else {
423 return None;
424 };
425
426 let last = expr.path.segments.last()?;
427 let comp_name = &last.ident;
428 let fn_name = format_ident!("update_{comp_name}");
429
430 Some(quote! {
431 app.add_state::<#expr>();
432 app.add_systems(PostUpdate, #fn_name);
433 })
434 });
435
436 quote! {
437 #(#filtered)*
438 }
439}
440
441fn prefab_resources(item: TokenStream) -> proc_macro2::TokenStream {
442 let Some(states) = get_nth_tuple(item, 2) else {
443 return quote! {};
444 };
445
446 let filtered = states.elems.iter().filter_map(|expr| {
447 let syn::Expr::Path(expr) = expr else {
448 return None;
449 };
450
451 let last = expr.path.segments.last()?;
452 let comp_name = &last.ident;
453 let res_name = format_ident!("{comp_name}Resource");
454
455 Some(quote! {
456 app.insert_resource(#res_name::default());
457 })
458 });
459
460 quote! {
461 #(#filtered)*
462 }
463}
464
465fn register_prefabs(item: TokenStream) -> proc_macro2::TokenStream {
466 let Some(states) = get_nth_tuple(item, 2) else {
467 return quote! {};
468 };
469
470 let filtered = states
471 .elems
472 .iter()
473 .filter_map(|expr| {
474 let syn::Expr::Path(expr) = expr else {
475 return None;
476 };
477
478 Some(expr)
479 })
480 .enumerate()
481 .map(|(index, ident)| {
482 let count = index as i32;
483 let ident = quote! { #ident };
484 let ident: syn::Expr =
485 syn::parse_str(&format!("{}Resource", ident.to_string().replace(' ', ""))).unwrap();
486
487 Some(quote! {
488 #count => {
489 let Some(mut res) = world.get_resource_mut::<#ident>() else {
490 return;
491 };
492
493 res.insert_prefabs(guids);
494 }
495 })
496 });
497
498 quote! {
499 let guids = unsafe { std::slice::from_raw_parts(prefabs.guids, prefabs.len) };
500 match prefabs.ref_id {
501 #(#filtered)*
502 _ => {}
503 }
504 }
505}