Skip to main content

interstice_sdk/
macros.rs

1use interstice_abi::{
2    Authority, ModuleDependency, ModuleVisibility, NodeDependency, encode, pack_ptr_len,
3};
4
5pub const fn validate_replicated_table_literal(value: &str) {
6    let bytes = value.as_bytes();
7    if bytes.is_empty() {
8        panic!("Replicated table path cannot be empty");
9    }
10
11    let mut dot_count = 0;
12    let mut segment_len = 0;
13    let mut index = 0;
14
15    while index < bytes.len() {
16        let byte = bytes[index];
17
18        if byte == b'.' {
19            if segment_len == 0 {
20                panic!("Replicated table path must not contain empty segments");
21            }
22            dot_count += 1;
23            segment_len = 0;
24        } else {
25            let is_valid_char = (byte >= b'a' && byte <= b'z')
26                || (byte >= b'A' && byte <= b'Z')
27                || (byte >= b'0' && byte <= b'9')
28                || byte == b'_'
29                || byte == b'-';
30
31            if !is_valid_char {
32                panic!("Replicated table path contains unsupported characters");
33            }
34            segment_len += 1;
35        }
36
37        index += 1;
38    }
39
40    if dot_count != 2 || segment_len == 0 {
41        panic!("Replicated table path must use 'node.module.table'");
42    }
43}
44
45#[macro_export]
46macro_rules! interstice_module {
47    () => {
48        interstice_module!(visibility: Private, authorities: [], replicated_tables: []);
49    };
50
51    (visibility: $vis:ident) => {
52        interstice_module!(visibility: $vis, authorities: [], replicated_tables: []);
53    };
54
55    (authorities: [$($auth:ident),* $(,)?]) => {
56        interstice_module!(visibility: Private, authorities: [$($auth),*], replicated_tables: []);
57    };
58
59    (replicated_tables: [$($rep:literal),* $(,)?]) => {
60        interstice_module!(visibility: Private, authorities: [], replicated_tables: [$($rep),*]);
61    };
62
63    (visibility: $vis:ident, authorities: [$($auth:ident),* $(,)?]) => {
64        interstice_module!(visibility: $vis, authorities: [$($auth),*], replicated_tables: []);
65    };
66
67    (visibility: $vis:ident, replicated_tables: [$($rep:literal),* $(,)?]) => {
68        interstice_module!(visibility: $vis, authorities: [], replicated_tables: [$($rep),*]);
69    };
70
71    (authorities: [$($auth:ident),* $(,)?], replicated_tables: [$($rep:literal),* $(,)?]) => {
72        interstice_module!(visibility: Private, authorities: [$($auth),*], replicated_tables: [$($rep),*]);
73    };
74
75    (visibility: $vis:ident, authorities: [$($auth:ident),* $(,)?], replicated_tables: [$($rep:literal),* $(,)?]) => {
76        $(
77            interstice_module!(@impl_authority $auth);
78        )*
79
80        // Global imports (for traits used in macros)
81        use std::str::FromStr;
82        // Use wee_alloc as the global allocator.
83
84        #[global_allocator]
85        static ALLOC: interstice_sdk::wee_alloc::WeeAlloc =
86            interstice_sdk::wee_alloc::WeeAlloc::INIT;
87
88        #[unsafe(no_mangle)]
89        pub extern "C" fn alloc(size: i32) -> i32 {
90            let layout = std::alloc::Layout::from_size_align(size as usize, 8).unwrap();
91            unsafe { std::alloc::alloc(layout) as i32 }
92        }
93
94        #[unsafe(no_mangle)]
95        pub extern "C" fn dealloc(ptr: i32, size: i32) {
96            let layout = std::alloc::Layout::from_size_align(size as usize, 8).unwrap();
97            unsafe { std::alloc::dealloc(ptr as *mut u8, layout) }
98        }
99
100        // Panic hook to log panics to host
101        #[$crate::init]
102        fn interstice_init() {
103            std::panic::set_hook(Box::new(|info| {
104                let msg = if let Some(s) = info.payload().downcast_ref::<&str>() {
105                    *s
106                } else if let Some(s) = info.payload().downcast_ref::<String>() {
107                    s.as_str()
108                } else {
109                    "panic occurred"
110                };
111
112                // send to host
113                interstice_sdk::host_calls::log(&format!("Panic Error: {}", msg));
114            }));
115        }
116
117        // BINDINGS
118        pub mod bindings {
119            include!(concat!(env!("OUT_DIR"), "/interstice_bindings.rs"));
120        }
121
122        // Module Schema Description
123
124        const __INTERSTICE_MODULE_NAME: &str = env!("CARGO_PKG_NAME");
125        const __INTERSTICE_MODULE_VERSION: &str = env!("CARGO_PKG_VERSION");
126        const __INTERSTICE_VISIBILITY: ModuleVisibility = ModuleVisibility::$vis;
127        const __INTERSTICE_AUTHORITIES: &[interstice_abi::Authority] = &[
128            $(interstice_abi::Authority::$auth),*
129        ];
130
131        #[unsafe(no_mangle)]
132        pub extern "C" fn interstice_describe() -> i64 {
133            let __interstice_replicated_tables: Vec<interstice_sdk::ReplicatedTableSchema> = vec![
134                $(
135                    {
136                        const _: () = interstice_sdk::macros::validate_replicated_table_literal($rep);
137                        let parts: Vec<&str> = $rep.split('.').collect();
138                        interstice_sdk::ReplicatedTableSchema {
139                            node_name: parts[0].to_string(),
140                            module_name: parts[1].to_string(),
141                            table_name: parts[2].to_string(),
142                        }
143                    }
144                ),*
145            ];
146
147            let __interstice_node_dependencies = bindings::__GET_INTERSTICE_NODE_DEPENDENCIES();
148            for table in &__interstice_replicated_tables {
149                if let Err(error) = bindings::__INTERSTICE_VALIDATE_REPLICATED_TABLE(
150                    &table.node_name,
151                    &table.module_name,
152                    &table.table_name,
153                    &__interstice_node_dependencies,
154                ) {
155                    panic!("{}", error);
156                }
157            }
158
159            interstice_sdk::macros::describe_module(
160                __INTERSTICE_MODULE_NAME,
161                __INTERSTICE_MODULE_VERSION,
162                __INTERSTICE_VISIBILITY,
163                __INTERSTICE_AUTHORITIES,
164                bindings::__GET_INTERSTICE_MODULE_DEPENDENCIES(),
165                __interstice_node_dependencies,
166                __interstice_replicated_tables,
167            )
168        }
169
170    };
171    // Authorites calls
172
173    (@impl_authority Input) => {
174    };
175
176    (@impl_authority Audio) => {
177        pub trait AudioExt {
178            fn audio(&self) -> Audio;
179        }
180
181        impl AudioExt for interstice_sdk::ReducerContext {
182            fn audio(&self) -> interstice_sdk::Audio {
183                interstice_sdk::Audio
184            }
185        }
186    };
187
188    (@impl_authority Gpu) => {
189        pub trait GpuExt {
190            fn gpu(&self) -> Gpu;
191        }
192
193        impl GpuExt for interstice_sdk::ReducerContext {
194            fn gpu(&self) -> interstice_sdk::Gpu {
195                interstice_sdk::Gpu
196            }
197        }
198    };
199
200    (@impl_authority File) => {
201        pub trait FileExt {
202            fn file(&self) -> File;
203        }
204
205        impl FileExt for interstice_sdk::ReducerContext {
206            fn file(&self) -> interstice_sdk::File {
207                interstice_sdk::File
208            }
209        }
210    };
211
212    (@impl_authority Module) => {
213        pub trait ModuleExt {
214            fn module(&self) -> ModuleAuthority;
215        }
216
217        impl ModuleExt for interstice_sdk::ReducerContext {
218            fn module(&self) -> interstice_sdk::ModuleAuthority {
219                interstice_sdk::ModuleAuthority
220            }
221        }
222    };
223}
224
225pub fn describe_module(
226    name: &str,
227    version: &str,
228    visibility: ModuleVisibility,
229    authorities: &'static [Authority],
230    module_dependencies: Vec<ModuleDependency>,
231    node_dependencies: Vec<NodeDependency>,
232    replicated_tables: Vec<interstice_abi::ReplicatedTableSchema>,
233) -> i64 {
234    let reducers = interstice_sdk_core::registry::collect_reducers();
235    let queries = interstice_sdk_core::registry::collect_queries();
236    let tables = interstice_sdk_core::registry::collect_tables();
237    let subscriptions = interstice_sdk_core::registry::collect_subscriptions();
238    let type_definitions = interstice_sdk_core::registry::collect_type_definitions();
239
240    let schema = interstice_abi::ModuleSchema {
241        abi_version: interstice_abi::ABI_VERSION,
242        name: name.to_string(),
243        version: version.into(),
244        visibility,
245        reducers,
246        queries,
247        tables,
248        subscriptions,
249        type_definitions,
250        authorities: authorities.to_vec(),
251        module_dependencies,
252        node_dependencies,
253        replicated_tables,
254    };
255
256    let bytes = encode(&schema).unwrap();
257    let len = bytes.len() as i32;
258    let ptr = Box::into_raw(bytes.into_boxed_slice()) as *mut u8 as i32;
259    return pack_ptr_len(ptr, len);
260}