Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //! Defines the types and helpers for binary/library communication.
- //!
- //! The general idea is that a driver program will be started by the user, then
- //! they'll either go straight to a particular game or pick from among their
- //! available games (much like with an emulator). The selected game will have
- //! its DLL loaded, initialized, user input will be given as long as the game is
- //! loaded, and then potentially the game will be unloaded.
- //!
- //! Note that it is possible for the game process to exit without having called
- //! the unload function for the DLL, so you must not rely on it being called.
- //!
- //! Also note that all of the interchange types are fully compatible with the C
- //! ABI, so you don't _have_ to use Rust if you don't want to. As long as you
- //! follow the types you can write your DLL and/or driving program in any
- //! language you like.
- //!
- //! # Building a game DLL with cargo
- //!
- //! To be loaded properly, your DLL should have one function of each function
- //! type given here, named in snake_case without the "Fn" part at the end:
- //!
- //! * [`GameInitFn`](type.GameInitFn.html): `game_init`
- //! * [`GameProcessLineFn`](type.GameProcessLineFn.html): `game_process_line`
- //! * [`GameDropFn`](type.GameDropFn.html): `game_drop`
- //!
- //! Be sure to mark your functions with `#[no_mangle]` and `pub`. You must also
- //! have "cdylib" as one of your `crate-type` values for your crate's library
- //! portion. If you want `cargo test` to work properly, you'll need "rlib" as
- //! well. It'll look something like this:
- //!
- //! ```toml
- //! [lib]
- //! name = "zavis"
- //! path = "src/lib.rs"
- //! crate-type = ["rlib", "cdylib"]
- //! ```
- //!
- //! You can easily ensure that your functions have the correct type at compile
- //! time with a simple set of `const` declarations.
- //!
- //! A "complete" example might look something like this.
- //!
- //! ```rust
- //! extern crate zavis;
- //! use zavis::interchange::*;
- //! use std::io::Write;
- //! use std::os::raw::c_void;
- //!
- //! struct GameState {
- //! world_seed: u64,
- //! call_count: u64,
- //! }
- //!
- //! #[no_mangle]
- //! pub unsafe extern "C" fn game_init(world_seed: u64) -> *mut c_void {
- //! Box::into_raw(Box::new(GameState {
- //! world_seed,
- //! call_count: 0
- //! })) as *mut c_void
- //! }
- //!
- //! #[no_mangle]
- //! pub unsafe extern "C" fn game_process_line(handle: *mut c_void,
- //! line: *const u8, line_len: usize, mut buf: *mut u8, buf_len: usize) -> usize {
- //! if handle.is_null() || line.is_null() || buf.is_null() {
- //! return 0;
- //! }
- //! let user_line = unsafe { ffi_recover_str(&line, line_len) };
- //! let mut out_buffer = unsafe { ffi_recover_out_buffer(&mut buf, buf_len) };
- //! let game_state: &mut GameState = unsafe {
- //! (handle as *mut GameState).as_mut().expect("game session was null!")
- //! };
- //! // do something silly just so we can see an effect
- //! game_state.call_count += 1;
- //! if user_line.len() > 0 {
- //! write!(out_buffer, "{}: {}", game_state.call_count, user_line);
- //! } else {
- //! write!(out_buffer, "{}: {}", game_state.call_count, game_state.world_seed);
- //! }
- //! out_buffer.position() as usize
- //! }
- //!
- //! #[no_mangle]
- //! pub unsafe extern "C" fn game_drop(handle: *mut c_void) {
- //! Box::from_raw(handle as *mut GameState);
- //! }
- //!
- //! #[allow(dead_code)]
- //! const GAME_INIT_CONST: GameInitFn = game_init;
- //! #[allow(dead_code)]
- //! const GAME_PROCESS_LINE_CONST: GameProcessLineFn = game_process_line;
- //! #[allow(dead_code)]
- //! const GAME_DROP_CONST: GameDropFn = game_drop;
- //! ```
- use std::borrow::Cow;
- use std::io::Cursor;
- use std::os::raw::c_void;
- /// Initializes a game session.
- ///
- /// The argument passed is expected to be used as an RNG seed of some sort. If
- /// your game has no randomization at all it can be ignored, but otherwise you
- /// should use this seed in place of any other seed source.
- ///
- /// Returns a pointer to the game data, which is then passed along to the other
- /// two functions of the DLL. If the game somehow couldn't be initialized you
- /// can return a null pointer instead.
- pub type GameInitFn = unsafe extern "C" fn(u64) -> *mut c_void;
- /// Processes a single line of user input.
- ///
- /// * The first argument is the pointer to the game data.
- /// * The next two arguments are a pointer to the bytes and length of bytes for
- /// the user's input line. Taken together these two values act like a rust
- /// `&str`, just in an FFI usable form. The bytes are _probably_ valid utf-8,
- /// but it's up to you how resilient you want to be about that.
- /// [`ffi_recover_str`](fn.ffi_recover_str.html) can handle this for you.
- /// * The final two arguments are a pointer to some bytes and a size for the
- /// allocation. This can be converted into a `&mut [u8]` and then you can
- /// write into it. You should obviously write valid utf-8 if at all possible,
- /// but the driving program should not explode if you don't. You probably want
- /// to use [`ffi_recover_out_buffer`](fn.ffi_recover_out_buffer.html).
- ///
- /// The return value is the number of bytes written into the output buffer.
- ///
- /// It can generally be expected that each set of output will be word wrapped as
- /// appropriate and displayed as its own "block". The exact nature of the output
- /// depends on the driving program. On a desktop the game might run within a
- /// terminal, but on a smartphone input and output might appear in a send/reply
- /// setup as if it were a messaging app.
- ///
- /// The input or output buffer can at any time use the `SOH` character
- /// (`'\x01'`) to "escape" the normal text mode and then give special commands
- /// to change the text color or whatever. At the end of a control code sequence
- /// a `STX` character (`'\x02'`) signals that the buffer has returned to the
- /// normal textual mode.
- ///
- /// * If the driver or game does not handle special modes, it must still parse
- /// for `SOH` characters and then discard characters up to and including the
- /// matching `STX`, without rendering any of the control characters and
- /// without treating them as user input.
- /// * The exact details of any special modes supported are not clear yet. Sorry.
- pub type GameProcessLineFn = unsafe extern "C" fn(*mut c_void, *const u8, usize, *mut u8, usize) -> usize;
- /// Frees up the game session data.
- ///
- /// The argument given must be a game data pointer obtained from the
- /// initialization function of this same DLL, or you'll have a very bad time.
- pub type GameDropFn = unsafe extern "C" fn(*mut c_void);
- /// Converts the const pointer and len for the user's input line into a rusty
- /// form.
- ///
- /// This accepts a reference to a const pointer so that the lifetime of the
- /// `Cow` value can be properly tracked. This way you can't make the Cow
- /// accidentally outlive the pointer it was built from.
- ///
- /// ```rust
- /// extern crate zavis;
- /// use zavis::interchange::ffi_recover_str;
- ///
- /// let static_str = "demo";
- /// let ptr_ref = &static_str.as_ptr();
- /// let len = static_str.len();
- /// let recovered_value = unsafe { ffi_recover_str(ptr_ref, len) };
- /// assert_eq!(static_str, recovered_value);
- /// ```
- pub unsafe fn ffi_recover_str<'a>(ptr_ref: &'a *const u8, len: usize) -> Cow<'a, str> {
- let slice = ::std::slice::from_raw_parts(*ptr_ref, len);
- String::from_utf8_lossy(slice)
- }
- /// Converts the pointer and len for the output buffer into a rusty form.
- ///
- /// This accepts a mutable reference to a mut pointer so that the lifetime of
- /// the `Cursor` will be correct. It can't accidentally outlive the pointer it
- /// was based on. You also can't accidentally make two buffers to the same
- /// block of data.
- ///
- /// ```rust
- /// extern crate zavis;
- /// use zavis::interchange::ffi_recover_out_buffer;
- /// use std::io::Write;
- ///
- /// const BUFFER_SIZE: usize = 20;
- /// let mut vec: Vec<u8> = Vec::with_capacity(BUFFER_SIZE);
- /// unsafe { vec.set_len(BUFFER_SIZE) };
- /// {
- /// let len = vec.len();
- /// let ptr_ref_mut = &mut unsafe { vec.as_mut_ptr() };
- /// let mut cursor = unsafe { ffi_recover_out_buffer(ptr_ref_mut, len) };
- /// write!(cursor, "a");
- /// }
- /// assert_eq!(vec[0], 'a' as u8);
- /// ```
- pub unsafe fn ffi_recover_out_buffer<'a>(ptr_ref_mut: &'a mut *mut u8, len: usize) -> Cursor<&'a mut [u8]> {
- let slice_mut = ::std::slice::from_raw_parts_mut(*ptr_ref_mut, len);
- Cursor::new(slice_mut)
- }
Add Comment
Please, Sign In to add comment