extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use syn::{ItemEnum, ItemStruct, ItemType};
fn get_name(item: &proc_macro2::TokenStream) -> Option<Ident> {
let ident = if let Ok(ItemStruct { ident, .. }) = syn::parse2(item.clone()) {
ident
} else if let Ok(ItemEnum { ident, .. }) = syn::parse2(item.clone()) {
ident
} else if let Ok(ItemType { ident, .. }) = syn::parse2(item.clone()) {
ident
} else {
return None;
};
Some(ident)
}
#[allow(clippy::too_many_lines)]
fn stateroom_wasm_impl(item: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
let name =
get_name(item).expect("Can only use #[stateroom_wasm] on a struct, enum, or type alias.");
quote! {
#item
mod _stateroom_wasm_macro_autogenerated {
extern crate alloc;
use super::*;
use stateroom_wasm::prelude::{
MessageRecipient,
SimpleStateroomService,
StateroomContext,
ClientId,
};
static mut SERVER_STATE: Option<#name> = None;
#[no_mangle]
pub static JAMSOCKET_API_VERSION: i32 = 1;
#[no_mangle]
pub static JAMSOCKET_API_PROTOCOL: i32 = 0;
struct GlobalStateroomContext;
impl StateroomContext for GlobalStateroomContext {
fn set_timer(&self, ms_delay: u32) {
unsafe {
ffi::set_timer(ms_delay);
}
}
fn send_message(&self, recipient: impl Into<MessageRecipient>, message: &str) {
unsafe {
ffi::send_message(
recipient.into().encode_i32(),
&message.as_bytes()[0] as *const u8 as u32,
message.len() as u32,
);
}
}
fn send_binary(&self, recipient: impl Into<MessageRecipient>, message: &[u8]) {
unsafe {
ffi::send_binary(
recipient.into().encode_i32(),
&message[0] as *const u8 as u32,
message.len() as u32,
);
}
}
}
mod ffi {
extern "C" {
pub fn send_message(client: i32, message: u32, message_len: u32);
pub fn send_binary(client: i32, message: u32, message_len: u32);
pub fn set_timer(ms_delay: u32);
}
}
#[no_mangle]
extern "C" fn initialize(room_id_ptr: *const u8, room_id_len: usize) {
let room_id = unsafe {
String::from_utf8(std::slice::from_raw_parts(room_id_ptr, room_id_len).to_vec()).map_err(|e| format!("Error parsing UTF-8 from host {:?}", e)).unwrap()
};
let mut c = #name::new(&room_id, &GlobalStateroomContext);
unsafe {
SERVER_STATE.replace(c);
}
}
#[no_mangle]
extern "C" fn connect(client_id: ClientId) {
match unsafe { SERVER_STATE.as_mut() } {
Some(st) => SimpleStateroomService::connect(st, client_id.into(), &GlobalStateroomContext),
None => ()
}
}
#[no_mangle]
extern "C" fn disconnect(client_id: ClientId) {
match unsafe { SERVER_STATE.as_mut() } {
Some(st) => SimpleStateroomService::disconnect(st, client_id.into(), &GlobalStateroomContext),
None => ()
}
}
#[no_mangle]
extern "C" fn timer() {
match unsafe { SERVER_STATE.as_mut() } {
Some(st) => SimpleStateroomService::timer(st, &GlobalStateroomContext),
None => ()
}
}
#[no_mangle]
extern "C" fn message(client_id: ClientId, ptr: *const u8, len: usize) {
unsafe {
let string = String::from_utf8(std::slice::from_raw_parts(ptr, len).to_vec()).map_err(|e| format!("Error parsing UTF-8 from host {:?}", e)).unwrap();
match SERVER_STATE.as_mut() {
Some(st) => SimpleStateroomService::message(st, client_id.into(), &string, &GlobalStateroomContext),
None => ()
}
}
}
#[no_mangle]
extern "C" fn binary(client_id: ClientId, ptr: *const u8, len: usize) {
unsafe {
let data = std::slice::from_raw_parts(ptr, len);
match SERVER_STATE.as_mut() {
Some(st) => SimpleStateroomService::binary(st, client_id.into(), data, &GlobalStateroomContext),
None => ()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn jam_malloc(size: u32) -> *mut u8 {
let layout = core::alloc::Layout::from_size_align_unchecked(size as usize, 0);
alloc::alloc::alloc(layout)
}
#[no_mangle]
pub unsafe extern "C" fn jam_free(ptr: *mut u8, size: u32) {
let layout = core::alloc::Layout::from_size_align_unchecked(size as usize, 0);
alloc::alloc::dealloc(ptr, layout);
}
}
}
}
#[proc_macro_attribute]
pub fn stateroom_wasm(_attr: TokenStream, item: TokenStream) -> TokenStream {
#[allow(clippy::needless_borrow)]
stateroom_wasm_impl(&item.into()).into()
}
#[cfg(test)]
mod test {
use super::get_name;
use quote::quote;
#[test]
fn test_parse_name() {
assert_eq!(
"MyStruct",
get_name("e! {
struct MyStruct {}
})
.unwrap()
.to_string()
);
assert_eq!(
"AnotherStruct",
get_name("e! {
struct AnotherStruct;
})
.unwrap()
.to_string()
);
assert_eq!(
"ATupleStruct",
get_name("e! {
struct ATupleStruct(u32, u32, u32);
})
.unwrap()
.to_string()
);
assert_eq!(
"AnEnum",
get_name("e! {
enum AnEnum {
Option1,
Option2(u32),
}
})
.unwrap()
.to_string()
);
assert_eq!(
"ATypeDecl",
get_name("e! {
type ATypeDecl = u32;
})
.unwrap()
.to_string()
);
assert!(get_name("e! {
impl Foo {}
})
.is_none());
}
}