Guest User

Untitled

a guest
May 20th, 2018
112
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.14 KB | None | 0 0
  1. //! Defines the types and helpers for binary/library communication.
  2. //!
  3. //! The general idea is that a driver program will be started by the user, then
  4. //! they'll either go straight to a particular game or pick from among their
  5. //! available games (much like with an emulator). The selected game will have
  6. //! its DLL loaded, initialized, user input will be given as long as the game is
  7. //! loaded, and then potentially the game will be unloaded.
  8. //!
  9. //! Note that it is possible for the game process to exit without having called
  10. //! the unload function for the DLL, so you must not rely on it being called.
  11. //!
  12. //! Also note that all of the interchange types are fully compatible with the C
  13. //! ABI, so you don't _have_ to use Rust if you don't want to. As long as you
  14. //! follow the types you can write your DLL and/or driving program in any
  15. //! language you like.
  16. //!
  17. //! # Building a game DLL with cargo
  18. //!
  19. //! To be loaded properly, your DLL should have one function of each function
  20. //! type given here, named in snake_case without the "Fn" part at the end:
  21. //!
  22. //! * [`GameInitFn`](type.GameInitFn.html): `game_init`
  23. //! * [`GameProcessLineFn`](type.GameProcessLineFn.html): `game_process_line`
  24. //! * [`GameDropFn`](type.GameDropFn.html): `game_drop`
  25. //!
  26. //! Be sure to mark your functions with `#[no_mangle]` and `pub`. You must also
  27. //! have "cdylib" as one of your `crate-type` values for your crate's library
  28. //! portion. If you want `cargo test` to work properly, you'll need "rlib" as
  29. //! well. It'll look something like this:
  30. //!
  31. //! ```toml
  32. //! [lib]
  33. //! name = "zavis"
  34. //! path = "src/lib.rs"
  35. //! crate-type = ["rlib", "cdylib"]
  36. //! ```
  37. //!
  38. //! You can easily ensure that your functions have the correct type at compile
  39. //! time with a simple set of `const` declarations.
  40. //!
  41. //! A "complete" example might look something like this.
  42. //!
  43. //! ```rust
  44. //! extern crate zavis;
  45. //! use zavis::interchange::*;
  46. //! use std::io::Write;
  47. //! use std::os::raw::c_void;
  48. //!
  49. //! struct GameState {
  50. //! world_seed: u64,
  51. //! call_count: u64,
  52. //! }
  53. //!
  54. //! #[no_mangle]
  55. //! pub unsafe extern "C" fn game_init(world_seed: u64) -> *mut c_void {
  56. //! Box::into_raw(Box::new(GameState {
  57. //! world_seed,
  58. //! call_count: 0
  59. //! })) as *mut c_void
  60. //! }
  61. //!
  62. //! #[no_mangle]
  63. //! pub unsafe extern "C" fn game_process_line(handle: *mut c_void,
  64. //! line: *const u8, line_len: usize, mut buf: *mut u8, buf_len: usize) -> usize {
  65. //! if handle.is_null() || line.is_null() || buf.is_null() {
  66. //! return 0;
  67. //! }
  68. //! let user_line = unsafe { ffi_recover_str(&line, line_len) };
  69. //! let mut out_buffer = unsafe { ffi_recover_out_buffer(&mut buf, buf_len) };
  70. //! let game_state: &mut GameState = unsafe {
  71. //! (handle as *mut GameState).as_mut().expect("game session was null!")
  72. //! };
  73. //! // do something silly just so we can see an effect
  74. //! game_state.call_count += 1;
  75. //! if user_line.len() > 0 {
  76. //! write!(out_buffer, "{}: {}", game_state.call_count, user_line);
  77. //! } else {
  78. //! write!(out_buffer, "{}: {}", game_state.call_count, game_state.world_seed);
  79. //! }
  80. //! out_buffer.position() as usize
  81. //! }
  82. //!
  83. //! #[no_mangle]
  84. //! pub unsafe extern "C" fn game_drop(handle: *mut c_void) {
  85. //! Box::from_raw(handle as *mut GameState);
  86. //! }
  87. //!
  88. //! #[allow(dead_code)]
  89. //! const GAME_INIT_CONST: GameInitFn = game_init;
  90. //! #[allow(dead_code)]
  91. //! const GAME_PROCESS_LINE_CONST: GameProcessLineFn = game_process_line;
  92. //! #[allow(dead_code)]
  93. //! const GAME_DROP_CONST: GameDropFn = game_drop;
  94. //! ```
  95.  
  96. use std::borrow::Cow;
  97. use std::io::Cursor;
  98. use std::os::raw::c_void;
  99.  
  100. /// Initializes a game session.
  101. ///
  102. /// The argument passed is expected to be used as an RNG seed of some sort. If
  103. /// your game has no randomization at all it can be ignored, but otherwise you
  104. /// should use this seed in place of any other seed source.
  105. ///
  106. /// Returns a pointer to the game data, which is then passed along to the other
  107. /// two functions of the DLL. If the game somehow couldn't be initialized you
  108. /// can return a null pointer instead.
  109. pub type GameInitFn = unsafe extern "C" fn(u64) -> *mut c_void;
  110.  
  111. /// Processes a single line of user input.
  112. ///
  113. /// * The first argument is the pointer to the game data.
  114. /// * The next two arguments are a pointer to the bytes and length of bytes for
  115. /// the user's input line. Taken together these two values act like a rust
  116. /// `&str`, just in an FFI usable form. The bytes are _probably_ valid utf-8,
  117. /// but it's up to you how resilient you want to be about that.
  118. /// [`ffi_recover_str`](fn.ffi_recover_str.html) can handle this for you.
  119. /// * The final two arguments are a pointer to some bytes and a size for the
  120. /// allocation. This can be converted into a `&mut [u8]` and then you can
  121. /// write into it. You should obviously write valid utf-8 if at all possible,
  122. /// but the driving program should not explode if you don't. You probably want
  123. /// to use [`ffi_recover_out_buffer`](fn.ffi_recover_out_buffer.html).
  124. ///
  125. /// The return value is the number of bytes written into the output buffer.
  126. ///
  127. /// It can generally be expected that each set of output will be word wrapped as
  128. /// appropriate and displayed as its own "block". The exact nature of the output
  129. /// depends on the driving program. On a desktop the game might run within a
  130. /// terminal, but on a smartphone input and output might appear in a send/reply
  131. /// setup as if it were a messaging app.
  132. ///
  133. /// The input or output buffer can at any time use the `SOH` character
  134. /// (`'\x01'`) to "escape" the normal text mode and then give special commands
  135. /// to change the text color or whatever. At the end of a control code sequence
  136. /// a `STX` character (`'\x02'`) signals that the buffer has returned to the
  137. /// normal textual mode.
  138. ///
  139. /// * If the driver or game does not handle special modes, it must still parse
  140. /// for `SOH` characters and then discard characters up to and including the
  141. /// matching `STX`, without rendering any of the control characters and
  142. /// without treating them as user input.
  143. /// * The exact details of any special modes supported are not clear yet. Sorry.
  144. pub type GameProcessLineFn = unsafe extern "C" fn(*mut c_void, *const u8, usize, *mut u8, usize) -> usize;
  145.  
  146. /// Frees up the game session data.
  147. ///
  148. /// The argument given must be a game data pointer obtained from the
  149. /// initialization function of this same DLL, or you'll have a very bad time.
  150. pub type GameDropFn = unsafe extern "C" fn(*mut c_void);
  151.  
  152. /// Converts the const pointer and len for the user's input line into a rusty
  153. /// form.
  154. ///
  155. /// This accepts a reference to a const pointer so that the lifetime of the
  156. /// `Cow` value can be properly tracked. This way you can't make the Cow
  157. /// accidentally outlive the pointer it was built from.
  158. ///
  159. /// ```rust
  160. /// extern crate zavis;
  161. /// use zavis::interchange::ffi_recover_str;
  162. ///
  163. /// let static_str = "demo";
  164. /// let ptr_ref = &static_str.as_ptr();
  165. /// let len = static_str.len();
  166. /// let recovered_value = unsafe { ffi_recover_str(ptr_ref, len) };
  167. /// assert_eq!(static_str, recovered_value);
  168. /// ```
  169. pub unsafe fn ffi_recover_str<'a>(ptr_ref: &'a *const u8, len: usize) -> Cow<'a, str> {
  170. let slice = ::std::slice::from_raw_parts(*ptr_ref, len);
  171. String::from_utf8_lossy(slice)
  172. }
  173.  
  174. /// Converts the pointer and len for the output buffer into a rusty form.
  175. ///
  176. /// This accepts a mutable reference to a mut pointer so that the lifetime of
  177. /// the `Cursor` will be correct. It can't accidentally outlive the pointer it
  178. /// was based on. You also can't accidentally make two buffers to the same
  179. /// block of data.
  180. ///
  181. /// ```rust
  182. /// extern crate zavis;
  183. /// use zavis::interchange::ffi_recover_out_buffer;
  184. /// use std::io::Write;
  185. ///
  186. /// const BUFFER_SIZE: usize = 20;
  187. /// let mut vec: Vec<u8> = Vec::with_capacity(BUFFER_SIZE);
  188. /// unsafe { vec.set_len(BUFFER_SIZE) };
  189. /// {
  190. /// let len = vec.len();
  191. /// let ptr_ref_mut = &mut unsafe { vec.as_mut_ptr() };
  192. /// let mut cursor = unsafe { ffi_recover_out_buffer(ptr_ref_mut, len) };
  193. /// write!(cursor, "a");
  194. /// }
  195. /// assert_eq!(vec[0], 'a' as u8);
  196. /// ```
  197. pub unsafe fn ffi_recover_out_buffer<'a>(ptr_ref_mut: &'a mut *mut u8, len: usize) -> Cursor<&'a mut [u8]> {
  198. let slice_mut = ::std::slice::from_raw_parts_mut(*ptr_ref_mut, len);
  199. Cursor::new(slice_mut)
  200. }
Add Comment
Please, Sign In to add comment