Skip to main content

kc_osm_proc_macros/
lib.rs

1use heck::ToSnakeCase;
2use proc_macro::TokenStream;
3use proc_macro2::Span;
4use proc_macro2::TokenStream as TokenStream2;
5use quote::{ToTokens, format_ident, quote};
6use syn::{
7    Field, Fields, Ident, ItemStruct, Meta, parse::Parser, parse_macro_input,
8    punctuated::Punctuated,
9};
10
11#[proc_macro_attribute]
12pub fn dark_script(attr: TokenStream, item: TokenStream) -> TokenStream {
13    let messages =
14        parse_macro_input!(attr with Punctuated::<Meta, syn::Token![,]>::parse_terminated);
15
16    let mut registrations = TokenStream2::default();
17    for msg in messages {
18        let message = msg.to_token_stream().to_string();
19        let message_func = Ident::new(
20            &format!("on_{}", message.to_snake_case()),
21            Span::call_site(),
22        );
23        registrations.extend(quote! {
24            self.handlers.insert(#message.to_string(), Self::#message_func);
25        });
26    }
27
28    let mut item = parse_macro_input!(item as ItemStruct);
29
30    if let Fields::Named(ref mut fields) = item.fields {
31        fields.named.push(
32            Field::parse_named
33                .parse2(
34                    quote! {
35                        handlers: std::collections::HashMap<String, fn(&Self, &Services, &sScrMsg) -> HRESULT>
36                    },
37                )
38                .unwrap(),
39        );
40    }
41
42    let name = &item.ident;
43    let script_name = name.to_string();
44    let script_impl_block = format_ident!("{}_Impl", &name);
45
46    quote! {
47        #[implement(IScript)]
48        #[derive(Default, Debug)]
49        #item
50
51        impl #name {
52            fn register_handlers(&mut self) {
53                #registrations
54            }
55        }
56
57        impl IScript_Impl for #script_impl_block {
58            unsafe fn GetClassName(&self) -> *const std::ffi::c_char {
59                std::ffi::CString::from_str(#script_name).unwrap().into_raw()
60            }
61
62            unsafe fn ReceiveMessage(&self, msg: &mut sScrMsg, _: &mut sMultiParm, _: i32) -> HRESULT {
63                let services = services();
64
65                let message_name = unsafe {
66                    std::ffi::CStr::from_ptr(msg.message).to_str().unwrap()
67                };
68                if self.handlers.contains_key(message_name) {
69                    return self.handlers[message_name](self, services, msg);
70                }
71
72                HRESULT(1)
73            }
74        }
75
76        impl DarkScript for #name {
77            fn get_desc(mod_name: &str) -> sScrClassDesc {
78                let mod_ = std::ffi::CString::from_str(mod_name).unwrap();
79                let name = std::ffi::CString::from_str(#script_name).unwrap();
80                sScrClassDesc {
81                    mod_: mod_.into_raw(),
82                    name: name.into_raw(),
83                    base: std::ptr::null(),
84                    factory: Self::factory,
85                }
86            }
87
88            extern "C" fn factory(
89                _name: *const std::ffi::c_char,
90                _id: std::ffi::c_int
91            ) -> *mut IScript {
92                let mut ret: *mut std::ffi::c_void = std::ptr::null_mut();
93                let mut script = Self::default();
94                script.register_handlers();
95                let script_interface: IScript = script.into();
96                let guid = IScript::IID;
97                let query_result = unsafe { script_interface.query(&raw const guid, &mut ret) };
98                if !HRESULT::is_ok(query_result) {
99                    return std::ptr::null_mut();
100                }
101                ret as *mut IScript
102            }
103        }
104    }
105    .into()
106}