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 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 #[$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 interstice_sdk::host_calls::log(&format!("Panic Error: {}", msg));
109 }));
110 }
111
112 pub mod bindings {
114 include!(concat!(env!("OUT_DIR"), "/interstice_bindings.rs"));
115 }
116
117 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 (@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}