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
83        #[unsafe(no_mangle)]
84        pub extern "C" fn alloc(size: i32) -> i32 {
85            let layout = std::alloc::Layout::from_size_align(size as usize, 8).unwrap();
86            unsafe { std::alloc::alloc(layout) as i32 }
87        }
88
89        #[unsafe(no_mangle)]
90        pub extern "C" fn dealloc(ptr: i32, size: i32) {
91            let layout = std::alloc::Layout::from_size_align(size as usize, 8).unwrap();
92            unsafe { std::alloc::dealloc(ptr as *mut u8, layout) }
93        }
94
95        // Panic hook to log panics to host
96        #[$crate::init]
97        fn interstice_init() {
98            std::panic::set_hook(Box::new(|info| {
99                let msg = if let Some(s) = info.payload().downcast_ref::<&str>() {
100                    *s
101                } else if let Some(s) = info.payload().downcast_ref::<String>() {
102                    s.as_str()
103                } else {
104                    "panic occurred"
105                };
106
107                // send to host
108                interstice_sdk::host_calls::log(&format!("Panic Error: {}", msg));
109            }));
110        }
111
112        // BINDINGS
113        pub mod bindings {
114            include!(concat!(env!("OUT_DIR"), "/interstice_bindings.rs"));
115        }
116
117        // Module Schema Description
118
119        const __INTERSTICE_MODULE_NAME: &str = env!("CARGO_PKG_NAME");
120        const __INTERSTICE_MODULE_VERSION: &str = env!("CARGO_PKG_VERSION");
121        const __INTERSTICE_VISIBILITY: ModuleVisibility = ModuleVisibility::$vis;
122        const __INTERSTICE_AUTHORITIES: &[interstice_abi::Authority] = &[
123            $(interstice_abi::Authority::$auth),*
124        ];
125
126        #[unsafe(no_mangle)]
127        pub extern "C" fn interstice_describe() -> i64 {
128            let __interstice_replicated_tables: Vec<interstice_sdk::ReplicatedTableSchema> = vec![
129                $(
130                    {
131                        const _: () = interstice_sdk::macros::validate_replicated_table_literal($rep);
132                        let parts: Vec<&str> = $rep.split('.').collect();
133                        interstice_sdk::ReplicatedTableSchema {
134                            node_name: parts[0].to_string(),
135                            module_name: parts[1].to_string(),
136                            table_name: parts[2].to_string(),
137                        }
138                    }
139                ),*
140            ];
141
142            let __interstice_node_dependencies = bindings::__GET_INTERSTICE_NODE_DEPENDENCIES();
143            for table in &__interstice_replicated_tables {
144                if let Err(error) = bindings::__INTERSTICE_VALIDATE_REPLICATED_TABLE(
145                    &table.node_name,
146                    &table.module_name,
147                    &table.table_name,
148                    &__interstice_node_dependencies,
149                ) {
150                    panic!("{}", error);
151                }
152            }
153
154            interstice_sdk::macros::describe_module(
155                __INTERSTICE_MODULE_NAME,
156                __INTERSTICE_MODULE_VERSION,
157                __INTERSTICE_VISIBILITY,
158                __INTERSTICE_AUTHORITIES,
159                bindings::__GET_INTERSTICE_MODULE_DEPENDENCIES(),
160                __interstice_node_dependencies,
161                __interstice_replicated_tables,
162            )
163        }
164
165    };
166    // Authorites calls
167
168    (@impl_authority Input) => {
169    };
170
171    (@impl_authority Audio) => {
172        pub trait AudioExt {
173            fn audio(&self) -> Audio;
174        }
175
176        impl AudioExt for interstice_sdk::ReducerContext {
177            fn audio(&self) -> interstice_sdk::Audio {
178                interstice_sdk::Audio
179            }
180        }
181    };
182
183    (@impl_authority Gpu) => {
184        pub trait GpuExt {
185            fn gpu(&self) -> Gpu;
186        }
187
188        impl GpuExt for interstice_sdk::ReducerContext {
189            fn gpu(&self) -> interstice_sdk::Gpu {
190                interstice_sdk::Gpu
191            }
192        }
193    };
194
195    (@impl_authority File) => {
196        pub trait FileExt {
197            fn file(&self) -> File;
198        }
199
200        impl FileExt for interstice_sdk::ReducerContext {
201            fn file(&self) -> interstice_sdk::File {
202                interstice_sdk::File
203            }
204        }
205    };
206
207    (@impl_authority Module) => {
208        pub trait ModuleExt {
209            fn module(&self) -> ModuleAuthority;
210        }
211
212        impl ModuleExt for interstice_sdk::ReducerContext {
213            fn module(&self) -> interstice_sdk::ModuleAuthority {
214                interstice_sdk::ModuleAuthority
215            }
216        }
217    };
218}
219
220pub fn describe_module(
221    name: &str,
222    version: &str,
223    visibility: ModuleVisibility,
224    authorities: &'static [Authority],
225    module_dependencies: Vec<ModuleDependency>,
226    node_dependencies: Vec<NodeDependency>,
227    replicated_tables: Vec<interstice_abi::ReplicatedTableSchema>,
228) -> i64 {
229    let reducers = interstice_sdk_core::registry::collect_reducers();
230    let queries = interstice_sdk_core::registry::collect_queries();
231    let tables = interstice_sdk_core::registry::collect_tables();
232    let subscriptions = interstice_sdk_core::registry::collect_subscriptions();
233    let type_definitions = interstice_sdk_core::registry::collect_type_definitions();
234
235    let schema = interstice_abi::ModuleSchema {
236        abi_version: interstice_abi::ABI_VERSION,
237        name: name.to_string(),
238        version: version.into(),
239        visibility,
240        reducers,
241        queries,
242        tables,
243        subscriptions,
244        type_definitions,
245        authorities: authorities.to_vec(),
246        module_dependencies,
247        node_dependencies,
248        replicated_tables,
249    };
250
251    let bytes = encode(&schema).unwrap();
252    let len = bytes.len() as i32;
253    let ptr = Box::into_raw(bytes.into_boxed_slice()) as *mut u8 as i32;
254    return pack_ptr_len(ptr, len);
255}