1#![cfg_attr(test, allow(clippy::float_cmp))] #![deny(rust_2018_idioms)]
5#![deny(rustdoc::broken_intra_doc_links)]
6#![deny(missing_docs)]
7
8use core::ffi::c_char;
9
10#[derive(Debug, Copy, Clone, PartialEq)]
13#[repr(transparent)]
14pub struct OutputCode(f64);
15
16impl OutputCode {
17 pub const SUCCESS: OutputCode = OutputCode(1.0);
19 pub const FAILURE: OutputCode = OutputCode(0.0);
21
22 pub const fn custom(code: f64) -> Self {
25 Self(code)
26 }
27}
28
29impl<T, E> From<Result<T, E>> for OutputCode {
31 fn from(o: Result<T, E>) -> Self {
32 if o.is_ok() {
33 OutputCode::SUCCESS
34 } else {
35 OutputCode::FAILURE
36 }
37 }
38}
39
40#[repr(transparent)]
43#[derive(Debug, Copy, Clone, PartialEq, Eq)]
44pub struct GmPtr(*const c_char);
45
46impl GmPtr {
47 pub fn new(ptr: *const c_char) -> Self {
49 Self(ptr)
50 }
51
52 pub const fn null() -> Self {
58 Self(core::ptr::null())
59 }
60
61 pub const fn inner(self) -> *const c_char {
63 self.0
64 }
65
66 pub fn to_str(self) -> Result<&'static str, core::str::Utf8Error> {
71 unsafe { core::ffi::CStr::from_ptr(self.0) }.to_str()
72 }
73}
74
75impl core::ops::Deref for GmPtr {
76 type Target = *const c_char;
77 fn deref(&self) -> &Self::Target {
78 &self.0
79 }
80}
81
82impl core::ops::DerefMut for GmPtr {
83 fn deref_mut(&mut self) -> &mut Self::Target {
84 &mut self.0
85 }
86}
87
88unsafe impl Send for GmPtr {}
89unsafe impl Sync for GmPtr {}
90
91#[repr(transparent)]
102#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
103pub struct GmId(f64);
104
105impl GmId {
106 #[cfg(test)]
108 pub const fn new(id: f64) -> Self {
109 Self(id)
110 }
111
112 pub const fn dummy() -> Self {
114 Self(f64::MAX)
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
124#[repr(transparent)]
125pub struct GmReal(pub f64);
126
127impl GmReal {
128 pub const fn new(id: f64) -> Self {
131 Self(id)
132 }
133
134 pub const fn as_usize(self) -> usize {
136 self.0 as usize
137 }
138
139 pub const fn as_f64(self) -> f64 {
141 self.0
142 }
143
144 pub const fn inner(self) -> f64 {
146 self.0
147 }
148
149 pub const fn dummy() -> Self {
151 Self(f64::MAX)
152 }
153}
154
155#[derive(Debug)]
165pub struct GmBuffer<T: 'static> {
166 id: GmId,
168
169 pub buffer: &'static mut [T],
171}
172
173impl<T> GmBuffer<T> {
174 pub unsafe fn new(gm_id: GmId, gm_ptr: GmPtr, len: usize) -> Self {
189 let buffer = {
190 let buf = gm_ptr.inner() as *mut T;
191
192 core::slice::from_raw_parts_mut(buf, len)
193 };
194
195 Self { id: gm_id, buffer }
196 }
197
198 pub fn id(self) -> GmId {
202 self.id
203 }
204}
205
206impl<T> core::ops::Index<usize> for GmBuffer<T> {
207 type Output = T;
208
209 fn index(&self, index: usize) -> &Self::Output {
210 &self.buffer[index]
211 }
212}
213
214impl<T> core::ops::IndexMut<usize> for GmBuffer<T> {
215 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
216 &mut self.buffer[index]
217 }
218}
219
220pub struct Bridge(GmBuffer<u32>);
230
231impl Bridge {
232 pub fn new(buf: GmBuffer<u32>) -> Self {
234 debug_assert!(
235 buf.buffer.len() >= 256,
236 "your backing buffer needs to be at least 256 bytes"
237 );
238
239 Self(buf)
240 }
241
242 pub fn writer(&mut self) -> BridgeWriter<'_> {
244 BridgeWriter::new(self)
245 }
246}
247
248pub struct BridgeWriter<'a>(&'a mut Bridge, usize);
251impl<'a> BridgeWriter<'a> {
252 fn new(bridge: &'a mut Bridge) -> Self {
253 Self(bridge, 0)
254 }
255
256 pub fn write_u32(&mut self, value: u32) {
258 self.0 .0[self.1] = value;
259 self.1 += 1;
260 }
261
262 pub fn write_f32(&mut self, value: f32) {
264 self.0 .0[self.1] = value.to_bits();
265 self.1 += 1;
266 }
267}
268
269#[macro_export]
271macro_rules! gm_println {
272 ($($arg:tt)*) => {
273 #[cfg(not(target_os = "windows"))]
274 {
275 use std::io::Write;
276
277 let mut output = $crate::GmStdOut::stdout();
278 output.write_fmt(format_args!($($arg)*)).unwrap();
279 output.write_str("\n");
280 }
281
282 #[cfg(target_os = "windows")]
283 {
284 println!($($arg)*);
285 }
286 };
287}
288
289#[macro_export]
291macro_rules! gm_print {
292 ($($arg:tt)*) => {
293 #[cfg(not(target_os = "windows"))]
294 {
295 use std::io::Write;
296 let mut output = $crate::GmStdOut::stdout();
297 output.write_fmt(format_args!($($arg)*)).unwrap();
298 }
299
300 #[cfg(target_os = "windows")]
301 {
302 print!($($arg)*);
303 }
304 };
305}
306
307#[cfg(target_os = "windows")]
308mod windows_stub_gm_std_out {
309 pub fn setup_panic_hook(program_name: &'static str) {
311 let base_message = format!("panicked in `{}` at ", program_name);
312
313 std::panic::set_hook(Box::new(move |panic_info| {
314 print!("{}", base_message);
315
316 if let Some(message) = panic_info.payload().downcast_ref::<String>() {
317 print!("'{}', ", message);
318 } else if let Some(message) = panic_info.payload().downcast_ref::<&'static str>() {
319 print!("'{}', ", message);
320 }
321
322 if let Some(location) = panic_info.location() {
323 print!("{}", location);
324 }
325 println!();
326 }));
327 }
328}
329
330#[cfg(not(target_os = "windows"))]
331mod mac_os_gm_std_out {
332 use interprocess::local_socket::LocalSocketStream;
333 use once_cell::sync::Lazy;
334 use parking_lot::RwLock;
335 use std::io::Write;
336
337 #[derive(Debug)]
340 pub struct GmStdOut(LocalSocketStream);
341
342 static GM_STD_OUT: Lazy<RwLock<GmStdOut>> = Lazy::new(|| {
343 let socket_name =
344 std::env::var("ADAM_IPC_SOCKET").expect("could not find `ADAM_IPC_SOCKET`");
345
346 let socket_stream =
347 LocalSocketStream::connect(socket_name).expect("could not connect to socket name!");
348 RwLock::new(GmStdOut(socket_stream))
349 });
350
351 impl GmStdOut {
352 pub fn stdout() -> impl std::ops::DerefMut<Target = GmStdOut> {
354 GM_STD_OUT.write()
355 }
356
357 pub fn write_str(&mut self, input: &str) {
359 let Ok(()) = self.0.write_all(&(input.len() as u64).to_le_bytes()) else { return; };
360 let Ok(()) = self.0.write_all(input.as_bytes()) else { return };
361 let _ = self.0.flush();
362 }
363 }
364
365 impl std::io::Write for GmStdOut {
366 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
367 self.0.write(buf)
368 }
369
370 fn flush(&mut self) -> std::io::Result<()> {
371 self.0.flush()
372 }
373
374 fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> std::io::Result<()> {
375 struct Adapter<'a> {
378 inner: &'a mut GmStdOut,
379 }
380
381 impl std::fmt::Write for Adapter<'_> {
382 fn write_str(&mut self, s: &str) -> std::fmt::Result {
383 self.inner.write_str(s);
384
385 Ok(())
386 }
387 }
388
389 let mut output = Adapter { inner: self };
390 let _ = std::fmt::write(&mut output, fmt);
391
392 Ok(())
393 }
394 }
395
396 pub fn setup_panic_hook(project_name: &str) {
398 let base_message = format!("panicked in `{}` at ", project_name);
399
400 std::panic::set_hook(Box::new(move |panic_info| {
401 use std::fmt::Write;
402
403 let mut output = base_message.clone();
404
405 if let Some(message) = panic_info.payload().downcast_ref::<String>() {
406 write!(output, "'{}', ", message).unwrap();
407 } else if let Some(message) = panic_info.payload().downcast_ref::<&'static str>() {
408 write!(output, "'{}', ", message).unwrap();
409 }
410
411 if let Some(location) = panic_info.location() {
412 write!(output, "{}", location).unwrap();
413 }
414 output.push('\n');
415
416 GmStdOut::stdout().write_str(&output);
417
418 std::process::exit(1);
419 }));
420 }
421}
422
423#[cfg(target_os = "windows")]
424pub use windows_stub_gm_std_out::setup_panic_hook;
425
426#[cfg(not(target_os = "windows"))]
427pub use mac_os_gm_std_out::{setup_panic_hook, GmStdOut};
428
429#[cfg(test)]
430mod tests {
431
432 use super::*;
433
434 #[test]
435 fn make_string_ptr() {
436 GmPtr::new("Hello, world!\0".as_ptr() as *const c_char);
437 }
438
439 #[test]
440 fn read_string_ptr() {
441 let ptr = GmPtr::new("Hello, world!\0".as_ptr() as *const c_char);
442 let out = ptr.to_str().unwrap();
443 assert_eq!(out, "Hello, world!");
444 }
445
446 #[test]
447 fn bridge() {
448 let buf = vec![0u32; 256];
449 let gm_ptr = GmPtr::new(buf.as_ptr() as *const _);
450
451 let mut bridge = unsafe { Bridge::new(GmBuffer::new(GmId::new(0.0), gm_ptr, 256)) };
452
453 let mut writer = bridge.writer();
454 writer.write_u32(18);
455 writer.write_f32(4.2);
456
457 assert_eq!(buf[0], 18);
458 assert_eq!(f32::from_bits(buf[1]), 4.2);
459
460 let mut writer = bridge.writer();
461 writer.write_f32(44.3);
462 writer.write_f32(22.2);
463
464 assert_eq!(f32::from_bits(buf[0]), 44.3);
465 assert_eq!(f32::from_bits(buf[1]), 22.2);
466 }
467}