#![cfg_attr(test, allow(clippy::float_cmp))] #![deny(rust_2018_idioms)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(missing_docs)]
use core::ffi::c_char;
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(transparent)]
pub struct OutputCode(f64);
impl OutputCode {
pub const SUCCESS: OutputCode = OutputCode(1.0);
pub const FAILURE: OutputCode = OutputCode(0.0);
pub const fn custom(code: f64) -> Self {
Self(code)
}
}
impl<T, E> From<Result<T, E>> for OutputCode {
fn from(o: Result<T, E>) -> Self {
if o.is_ok() {
OutputCode::SUCCESS
} else {
OutputCode::FAILURE
}
}
}
#[repr(transparent)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct GmPtr(*const c_char);
impl GmPtr {
pub fn new(ptr: *const c_char) -> Self {
Self(ptr)
}
pub const fn null() -> Self {
Self(core::ptr::null())
}
pub const fn inner(self) -> *const c_char {
self.0
}
pub fn to_str(self) -> Result<&'static str, core::str::Utf8Error> {
unsafe { core::ffi::CStr::from_ptr(self.0) }.to_str()
}
}
impl core::ops::Deref for GmPtr {
type Target = *const c_char;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl core::ops::DerefMut for GmPtr {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
unsafe impl Send for GmPtr {}
unsafe impl Sync for GmPtr {}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct GmId(f64);
impl GmId {
#[cfg(test)]
pub const fn new(id: f64) -> Self {
Self(id)
}
pub const fn dummy() -> Self {
Self(f64::MAX)
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct GmReal(pub f64);
impl GmReal {
pub const fn new(id: f64) -> Self {
Self(id)
}
pub const fn as_usize(self) -> usize {
self.0 as usize
}
pub const fn as_f64(self) -> f64 {
self.0
}
pub const fn inner(self) -> f64 {
self.0
}
pub const fn dummy() -> Self {
Self(f64::MAX)
}
}
#[derive(Debug)]
pub struct GmBuffer<T: 'static> {
id: GmId,
pub buffer: &'static mut [T],
}
impl<T> GmBuffer<T> {
pub unsafe fn new(gm_id: GmId, gm_ptr: GmPtr, len: usize) -> Self {
let buffer = {
let buf = gm_ptr.inner() as *mut T;
core::slice::from_raw_parts_mut(buf, len)
};
Self { id: gm_id, buffer }
}
pub fn id(self) -> GmId {
self.id
}
}
impl<T> core::ops::Index<usize> for GmBuffer<T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
&self.buffer[index]
}
}
impl<T> core::ops::IndexMut<usize> for GmBuffer<T> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.buffer[index]
}
}
pub struct Bridge(GmBuffer<u32>);
impl Bridge {
pub fn new(buf: GmBuffer<u32>) -> Self {
debug_assert!(
buf.buffer.len() >= 256,
"your backing buffer needs to be at least 256 bytes"
);
Self(buf)
}
pub fn writer(&mut self) -> BridgeWriter<'_> {
BridgeWriter::new(self)
}
}
pub struct BridgeWriter<'a>(&'a mut Bridge, usize);
impl<'a> BridgeWriter<'a> {
fn new(bridge: &'a mut Bridge) -> Self {
Self(bridge, 0)
}
pub fn write_u32(&mut self, value: u32) {
self.0 .0[self.1] = value;
self.1 += 1;
}
pub fn write_f32(&mut self, value: f32) {
self.0 .0[self.1] = value.to_bits();
self.1 += 1;
}
}
#[macro_export]
macro_rules! gm_println {
($($arg:tt)*) => {
#[cfg(not(target_os = "windows"))]
{
use std::io::Write;
let mut output = $crate::GmStdOut::stdout();
output.write_fmt(format_args!($($arg)*)).unwrap();
output.write_str("\n");
}
#[cfg(target_os = "windows")]
{
println!($($arg)*);
}
};
}
#[macro_export]
macro_rules! gm_print {
($($arg:tt)*) => {
#[cfg(not(target_os = "windows"))]
{
use std::io::Write;
let mut output = $crate::GmStdOut::stdout();
output.write_fmt(format_args!($($arg)*)).unwrap();
}
#[cfg(target_os = "windows")]
{
print!($($arg)*);
}
};
}
#[cfg(target_os = "windows")]
mod windows_stub_gm_std_out {
pub fn setup_panic_hook(program_name: &'static str) {
let base_message = format!("panicked in `{}` at ", program_name);
std::panic::set_hook(Box::new(move |panic_info| {
print!("{}", base_message);
if let Some(message) = panic_info.payload().downcast_ref::<String>() {
print!("'{}', ", message);
} else if let Some(message) = panic_info.payload().downcast_ref::<&'static str>() {
print!("'{}', ", message);
}
if let Some(location) = panic_info.location() {
print!("{}", location);
}
println!();
}));
}
}
#[cfg(not(target_os = "windows"))]
mod mac_os_gm_std_out {
use interprocess::local_socket::LocalSocketStream;
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use std::io::Write;
#[derive(Debug)]
pub struct GmStdOut(LocalSocketStream);
static GM_STD_OUT: Lazy<RwLock<GmStdOut>> = Lazy::new(|| {
let socket_name =
std::env::var("ADAM_IPC_SOCKET").expect("could not find `ADAM_IPC_SOCKET`");
let socket_stream =
LocalSocketStream::connect(socket_name).expect("could not connect to socket name!");
RwLock::new(GmStdOut(socket_stream))
});
impl GmStdOut {
pub fn stdout() -> impl std::ops::DerefMut<Target = GmStdOut> {
GM_STD_OUT.write()
}
pub fn write_str(&mut self, input: &str) {
let Ok(()) = self.0.write_all(&(input.len() as u64).to_le_bytes()) else { return; };
let Ok(()) = self.0.write_all(input.as_bytes()) else { return };
let _ = self.0.flush();
}
}
impl std::io::Write for GmStdOut {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.0.flush()
}
fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> std::io::Result<()> {
struct Adapter<'a> {
inner: &'a mut GmStdOut,
}
impl std::fmt::Write for Adapter<'_> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.inner.write_str(s);
Ok(())
}
}
let mut output = Adapter { inner: self };
let _ = std::fmt::write(&mut output, fmt);
Ok(())
}
}
pub fn setup_panic_hook(project_name: &str) {
let base_message = format!("panicked in `{}` at ", project_name);
std::panic::set_hook(Box::new(move |panic_info| {
use std::fmt::Write;
let mut output = base_message.clone();
if let Some(message) = panic_info.payload().downcast_ref::<String>() {
write!(output, "'{}', ", message).unwrap();
} else if let Some(message) = panic_info.payload().downcast_ref::<&'static str>() {
write!(output, "'{}', ", message).unwrap();
}
if let Some(location) = panic_info.location() {
write!(output, "{}", location).unwrap();
}
output.push('\n');
GmStdOut::stdout().write_str(&output);
std::process::exit(1);
}));
}
}
#[cfg(target_os = "windows")]
pub use windows_stub_gm_std_out::setup_panic_hook;
#[cfg(not(target_os = "windows"))]
pub use mac_os_gm_std_out::{setup_panic_hook, GmStdOut};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn make_string_ptr() {
GmPtr::new("Hello, world!\0".as_ptr() as *const c_char);
}
#[test]
fn read_string_ptr() {
let ptr = GmPtr::new("Hello, world!\0".as_ptr() as *const c_char);
let out = ptr.to_str().unwrap();
assert_eq!(out, "Hello, world!");
}
#[test]
fn bridge() {
let buf = vec![0u32; 256];
let gm_ptr = GmPtr::new(buf.as_ptr() as *const _);
let mut bridge = unsafe { Bridge::new(GmBuffer::new(GmId::new(0.0), gm_ptr, 256)) };
let mut writer = bridge.writer();
writer.write_u32(18);
writer.write_f32(4.2);
assert_eq!(buf[0], 18);
assert_eq!(f32::from_bits(buf[1]), 4.2);
let mut writer = bridge.writer();
writer.write_f32(44.3);
writer.write_f32(22.2);
assert_eq!(f32::from_bits(buf[0]), 44.3);
assert_eq!(f32::from_bits(buf[1]), 22.2);
}
}