spatialos-macro 0.2.4

Procmacro crate used to expand SpatialOS component and types created with spatialos-codegen
Documentation
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use syn::{Fields, Ident, ItemStruct};

use super::{field::FieldAST, get_id};

#[derive(Debug)]
pub struct StructAST<'a> {
    pub id: Option<u32>,
    pub name: &'a Ident,
    pub fields: Vec<FieldAST>,
}

impl StructAST<'_> {
    pub fn is_component(&self) -> bool {
        self.id.is_some()
    }

    fn get_update_constructor(&self) -> TokenStream2 {
        let idents = self
            .fields
            .iter()
            .map(|field| &field.name)
            .collect::<Vec<_>>();
        quote! {
            Self::Update { #(#idents,)* }
        }
    }

    fn get_data_constructor(&self) -> TokenStream2 {
        let idents = self
            .fields
            .iter()
            .map(|field| &field.name)
            .collect::<Vec<_>>();
        quote! {
            Self::Data { #(#idents,)* }
        }
    }

    fn get_data_deserializers(&self, source: &Ident) -> TokenStream2 {
        let deserializers = self
            .fields
            .iter()
            .map(|field| field.get_data_deserializer(source))
            .collect::<Vec<_>>();
        quote! {
            #(#deserializers)*
        }
    }

    fn get_data_serializers(&self, data: &Ident, target: &Ident) -> TokenStream2 {
        let serializers = self
            .fields
            .iter()
            .map(|field| field.get_data_serializer(data, target))
            .collect::<Vec<_>>();
        quote! {
            #(#serializers)*
        }
    }

    fn get_update_deserializers(&self, source: &Ident) -> TokenStream2 {
        let deserializers = self
            .fields
            .iter()
            .map(|field| field.get_update_deserializer(source))
            .collect::<Vec<_>>();
        quote! {
            #(#deserializers)*
        }
    }

    fn get_update_serializers(&self, data: &Ident, target: &Ident) -> TokenStream2 {
        let serializers = self
            .fields
            .iter()
            .map(|field| field.get_update_serializer(data, target))
            .collect::<Vec<_>>();
        quote! {
            #(#serializers)*
        }
    }

    fn get_impl_type(
        &self,
        struct_name: &Ident,
        data_struct_name: &Ident,
        update_struct_name: &Ident,
    ) -> TokenStream2 {
        let type_data_deserialize = {
            let source = format_ident!("source");
            let deserializers = self.get_data_deserializers(&source);
            let constructor = self.get_data_constructor();
            quote! {
                fn type_data_deserialize(
                    user_data: *mut core::ffi::c_void,
                    #source: &mut spatialos::schema::Object,
                ) -> Self::Data {
                    #deserializers
                    #constructor
                }
            }
        };

        let type_data_serialize = {
            let target = format_ident!("target");
            let data = format_ident!("data");
            let serializers = self.get_data_serializers(&data, &target);
            quote! {
                fn type_data_serialize(
                    user_data: *mut core::ffi::c_void,
                    #data: &mut Self::Data,
                    #target: &mut spatialos::schema::Object,
                ) {
                    #serializers
                }
            }
        };

        let type_update_deserialize = {
            let source = format_ident!("source");
            let deserializers = self.get_update_deserializers(&source);
            let constructor = self.get_update_constructor();
            quote! {
                fn type_update_deserialize(
                    user_data: *mut core::ffi::c_void,
                    #source: &mut spatialos::schema::Object,
                ) -> Self::Update {
                    #deserializers
                    #constructor
                }
            }
        };

        let type_update_serialize = {
            let target = format_ident!("target");
            let data = format_ident!("data");
            let serializers = self.get_update_serializers(&data, &target);
            quote! {
                fn type_update_serialize(
                    user_data: *mut core::ffi::c_void,
                    #data: &mut Self::Update,
                    #target: &mut spatialos::schema::Object,
                ) {
                    #serializers
                }
            }
        };

        let type_update_free = {
            let data = format_ident!("data");
            let freeers = quote! {};
            quote! {
                fn type_update_free(user_data: *mut core::ffi::c_void, #data: Self::Update) {
                    #freeers
                }
            }
        };

        let type_update_copy = {
            let data = format_ident!("data");
            let new_data = format_ident!("new_data");
            let copiers = quote! {};
            quote! {
                fn type_update_copy(user_data: *mut core::ffi::c_void, #data: &Self::Update) -> Self::Update {
                    let mut #new_data = data.clone();
                    #copiers
                    #new_data
                }
            }
        };

        quote! {
            impl spatialos_sdk::Type for #struct_name {
                type Data = #data_struct_name;
                type Update = #update_struct_name;

                #type_data_deserialize

                #type_data_serialize

                #type_update_deserialize

                #type_update_serialize

                #type_update_free

                #type_update_copy

            }
        }
    }

    fn get_impl_component(
        &self,
        struct_name: &Ident,
        data_struct_name: &Ident,
        update_struct_name: &Ident,
    ) -> TokenStream2 {
        let id = self.id.unwrap();
        let component_data_deserialize = {
            let fields = format_ident!("fields");
            let deserializers = self.get_data_deserializers(&fields);
            let constructor = self.get_data_constructor();
            quote! {
                fn component_data_deserialize(
                    component_id: spatialos::worker::ComponentId,
                    user_data: *mut core::ffi::c_void,
                    mut source: spatialos::schema::ComponentData
                ) -> Self::Data {
                    let mut #fields = source.get_fields();
                    #deserializers
                    #constructor
                }
            }
        };

        let component_data_serialize = {
            let fields = format_ident!("fields");
            let data = format_ident!("data");
            let serializers = self.get_data_serializers(&data, &fields);
            quote! {
                fn component_data_serialize(
                    component_id: spatialos::worker::ComponentId,
                    user_data: *mut core::ffi::c_void,
                    #data: &mut Self::Data,
                ) -> spatialos::schema::ComponentData {
                    let mut component_data = spatialos::schema::ComponentData::new();
                    let mut #fields = component_data.get_fields();
                    #serializers
                    component_data
                }
            }
        };

        let component_update_deserialize = {
            let fields = format_ident!("fields");
            let deserializers = self.get_update_deserializers(&fields);
            let constructor = self.get_update_constructor();
            quote! {
                fn component_update_deserialize(
                    component_id: spatialos::worker::ComponentId,
                    user_data: *mut core::ffi::c_void,
                    mut source: spatialos::schema::ComponentUpdate,
                ) -> Self::Update {
                    let mut #fields = source.get_fields();
                    #deserializers
                    #constructor
                }
            }
        };

        let component_update_serialize = {
            let fields = format_ident!("fields");
            let data = format_ident!("data");
            let serializers = self.get_update_serializers(&data, &fields);
            quote! {
                fn component_update_serialize(
                    component_id: spatialos::worker::ComponentId,
                    user_data: *mut core::ffi::c_void,
                    #data: &mut Self::Update,
                ) -> spatialos::schema::ComponentUpdate {
                    let mut new_update = spatialos::schema::ComponentUpdate::new();
                    let mut #fields = new_update.get_fields();
                    #serializers
                    new_update
                }
            }
        };

        let component_update_free = {
            let data = format_ident!("data");
            let freeers = quote! {};
            quote! {
                fn component_update_free(
                    component_id: spatialos::worker::ComponentId,
                    user_data: *mut core::ffi::c_void,
                    #data: Self::Update
                ) {
                    #freeers
                }
            }
        };

        let component_update_copy = {
            let data = format_ident!("data");
            let new_data = format_ident!("new_data");
            let copiers = quote! {};
            quote! {
                fn component_update_copy(
                    component_id: spatialos::worker::ComponentId,
                    user_data: *mut core::ffi::c_void,
                    #data: &Self::Update
                ) -> Self::Update {
                    let mut #new_data = #data.clone();
                    #copiers
                    #new_data
                }
            }
        };

        quote! {
            impl spatialos_sdk::Component for #struct_name {

                const ID: u32 = #id;

                type Data = #data_struct_name;
                type Update = #update_struct_name;

                #component_data_deserialize

                #component_data_serialize

                #component_update_deserialize

                #component_update_serialize

                #component_update_free

                #component_update_copy

            }
        }
    }
}

impl Into<TokenStream> for StructAST<'_> {
    fn into(self) -> TokenStream {
        let struct_name = &self.name;
        let data_struct_name = format_ident!("{}Data", &self.name);
        let update_struct_name = format_ident!("{}Update", &self.name);

        let data_struct = {
            let fields = self
                .fields
                .iter()
                .map(|field| field.get_data_field())
                .collect::<Vec<_>>();
            quote! {
                #[repr(C)]
                #[derive(Debug, Clone)]
                pub struct #data_struct_name {
                    #(#fields,)*
                }
            }
        };
        let update_struct = {
            let fields = self
                .fields
                .iter()
                .map(|field| field.get_update_field())
                .collect::<Vec<_>>();
            quote! {
                #[repr(C)]
                #[derive(Debug, Clone)]
                pub struct #update_struct_name {
                    #(#fields,)*
                }
            }
        };
        let implementation = {
            if self.is_component() {
                self.get_impl_component(struct_name, &data_struct_name, &update_struct_name)
            } else {
                self.get_impl_type(struct_name, &data_struct_name, &update_struct_name)
            }
        };

        let result = quote! {
            #[automatically_derived]
            #[allow(unused_qualifications)]
            #data_struct

            #[automatically_derived]
            #[allow(unused_qualifications)]
            #update_struct

            #[automatically_derived]
            #[allow(unused_qualifications)]
            #implementation
        };
        result.into()
    }
}

impl<'a> From<&'a ItemStruct> for StructAST<'a> {
    fn from(input: &'a ItemStruct) -> Self {
        let id = get_id(&input.attrs);
        let name = &input.ident;
        if let Fields::Named(fields) = &input.fields {
            let fields = fields.named.iter().map(FieldAST::from).collect::<Vec<_>>();
            Self { id, name, fields }
        } else {
            panic!("Didn't find fields for struct");
        }
    }
}