tgstation / rust-g

Rust based libraries for tgstation
MIT License
28 stars 100 forks source link

New FFI Macro #132

Open actioninja opened 1 year ago

actioninja commented 1 year ago

Brand new ludicrously overengineered macro for automagically™ interoping with byond smoothly and cleanly.

Here's what you can do with the new macro

All of these are callable directly from byond.

Write bare functions without being wrapped in a macro

#[byond_fn]
pub fn example_byond_fn() {
    let x = "yeah, we can bind vars";
    println!("{x}");
}

Have string arguments and return strings

#[byond_fn]
pub fn concat(first: &str, second: &str) -> String {
    format!("{first}{second")
}

Have arbitrary args that can be parsed from strings and return arbitrary types that can be converted to strings

#[byond_fn]
pub fn add(arg1: u8, arg2: u8) -> u8 {
    arg1 + arg2
}

Have some arguments be optional and not required to be passed

#[byond_fn]
pub fn example_optional_params(arg1: u8, arg2: Option<u8>) -> u8 {
    arg1 + arg2.unwrap_or(0)
}

Gracefully handle errors

#[byond_fn]
pub fn do_something_falliable(dunno: &str) -> Result<String, WhateverError> {
    let cool = probably_something_cool(dunno)?;
    let oh_no = actually_it_was_lame(cool)?;
    Ok(oh_no)
}

Automagic JSON Transport

pub fn fungle_the_goobler(goobler: Json<Goobler>) -> Json<Fungled> {
    let fungled = goobler.into_inner().fungle();
    Json(fungled)
}

New macro lives here (at the moment): https://github.com/actioninja/byond_fn

Docs here: https://docs.rs/byond_fn/0.5.1/byond_fn/

How the hell does this work?

In a very overcomplicated way

The tl;dr is it uses a trait to parse arguments from strings, and a separate trait to serialize back to string. There's blanket impls over Option and Result as well for optional args and error handling respectively.

This will be better documented later, read the code, the docs, etc

Error handling

This is very much in flux right now. Especially considering that the best way to handle this may change when FFI V2 becomes available. How it currently works can be found in the docs (though these docs may not be accurate atm): https://docs.rs/byond_fn/0.5.1/byond_fn/str_ffi/index.html#errors

Goals

I never really was super happy with the existing macro. Since it puts the function body inside a macro call, you lose a lot of IDE niceness since inside macros could be anything. On top of that, it always was kind of weird to me that each function was expected to just do the boilerplate of parsing each string argument manually when this is going to be the same in the overwhelming amount of usecases.

The unclear and not well documented nature of the previous macro resulted in a lot of cargo cult as well. Lots of functions that return results despite having no fallibility.

Someone mentioned in coderbus a while ago that it would be nice if the actual FFI junk could be pulled out of rust-g so it could be reused elsewhere more easily. Since proc macros require their own crate anyways, this makes a good opportunity to do so.

byond_fn todo

Things that should happen before this is merged

AffectedArc07 commented 1 year ago

fungle_the_goobler

Cyprex commented 1 year ago

How (un)performant is this?

actioninja commented 1 year ago

How (un)performant is this?

Shouldn't be meaningfully different from the existing macro. The two potential vectors for performance differences are the extra error handling and sanity checking (of which this mostly consists of a check to make sure the amount of arguments is correct), and the extra trait function calls which are all static dispatch anyways.

actioninja commented 1 year ago

Going to put this on hold until ffiv2 comes out

AffectedArc07 commented 1 year ago

FFIV2 is out - Tiger is currently making a crate for it https://github.com/spacestation13/byondapi-rs