Skip to content

Instantly share code, notes, and snippets.

@nlinker
Forked from anonymous/playground.rs
Last active October 8, 2017 09:11
Show Gist options
  • Save nlinker/1d7570228ff8ec9a6421c9d479b1962b to your computer and use it in GitHub Desktop.
Save nlinker/1d7570228ff8ec9a6421c9d479b1962b to your computer and use it in GitHub Desktop.
BWAPI idiomatic Rust binding variant 2 from Migi
use std::marker::PhantomData;
use std::cell::Cell;
pub struct Game<'g> {
// Putting the lifetime 'g into a PhantomData<Cell<&'g ()>> instead of a
// PhantomData<&'g ()> makes 'g invariant instead of covariant.
// I don't know if that's necessary here, but better be safe than sorry.
// See https://doc.rust-lang.org/nomicon/subtyping.html for more info.
phantom: PhantomData<Cell<&'g ()>>,
}
pub struct Unit<'g> {
phantom: PhantomData<Cell<&'g ()>>,
}
impl<'g> Game<'g> {
pub fn some_fn_that_returns_a_unit(&mut self) -> Unit<'g> {
Unit { phantom: PhantomData }
}
}
// The crux of this "solution" is this function.
// It takes a closure that gets called every time a new game starts.
// This closure takes the game as an argument, and this game has a certain lifetime 'g associated with it,
// and everything you want to do with the game (accessing units etc.) must use that lifetime 'g.
//
// The lifetime that 'g will actually be is 'static, but the closure doesn't know that.
// It must work with all possible lifetimes 'g. Furthermore, it can't know that when the closure
// gets called again later (when a second game starts), that the lifetime is the same,
// so it should be impossible to "store" a unit and use it in a different game.
//
// This closure gets leaked actually, but there should only be one so it's mostly ok.
// This can be fixed in nightly by using Box instead of *mut, but in current stable,
// you can't have statics with destructors.
pub fn init_bwapi<F:'static + for<'g> FnMut(&mut Game<'g>) -> Box<EventHandler<'g> + 'g>>(f:F) {
unsafe {
assert!(on_start_handler_ptr.is_none(), "init_bwapi can only be called once");
on_start_handler_ptr = Some(Box::into_raw(Box::new(f)));
}
}
pub trait EventHandler<'g> {
fn on_end(&mut self, game: &mut Game<'g>);
fn on_frame(&mut self, game: &mut Game<'g>);
fn on_unit_create(&mut self, game: &mut Game<'g>, unit: Unit<'g>);
// etc
}
#[allow(non_upper_case_globals)]
static mut on_start_handler_ptr : Option<*mut FnMut(&mut Game<'static>) -> Box<EventHandler<'static>>> = None;
#[allow(non_upper_case_globals)]
static mut game_ptr : Option<*mut Game> = None;
#[allow(non_upper_case_globals)]
static mut event_handler_ptr : Option<*mut EventHandler> = None;
// The fact that these pointers above are wrapped in Options is only because
// I don't know how to initialize them otherwise.
// This can be solved with the lazy_static crate I think, but you can't use crates here
// on the playground.
fn _dll_callback_on_start() {
unsafe {
game_ptr = Some(Box::into_raw(Box::new(Game { phantom: PhantomData })));
let game = &mut *game_ptr.unwrap();
let on_start = &mut *on_start_handler_ptr.unwrap();
event_handler_ptr = Some(Box::into_raw(on_start(game)));
}
}
fn _dll_callback_on_end() {
unsafe {
{
let game = &mut *game_ptr.unwrap();
let event_handler = &mut *event_handler_ptr.unwrap();
event_handler.on_end(game);
}
// delete game and event_handler
Box::from_raw(game_ptr.unwrap());
Box::from_raw(event_handler_ptr.unwrap());
}
}
fn _dll_callback_on_frame() {
unsafe {
let game = &mut *game_ptr.unwrap();
let event_handler = &mut *event_handler_ptr.unwrap();
event_handler.on_frame(game);
}
}
fn _dll_callback_on_unit_create() {
unsafe {
let game = &mut *game_ptr.unwrap();
let event_handler = &mut *event_handler_ptr.unwrap();
let unit = game.some_fn_that_returns_a_unit();
event_handler.on_unit_create(game, unit);
}
}
////////// USER CODE /////////////
struct MyEventHandler<'g> {
last_unit: Option<Unit<'g>>
}
impl<'g> EventHandler<'g> for MyEventHandler<'g> {
fn on_end(&mut self, _game: &mut Game<'g>) {
println!("game ended");
}
fn on_frame(&mut self, _game: &mut Game<'g>) {
println!("New frame. Unit exists? {}", self.last_unit.is_some());
}
fn on_unit_create(&mut self, _game: &mut Game<'g>, unit: Unit<'g>) {
println!("unit created");
self.last_unit = Some(unit);
}
}
fn main() {
init_bwapi(|_game| {
println!("new game");
Box::new(MyEventHandler {
last_unit: None
})
});
_dll_callback_on_start();
_dll_callback_on_frame();
_dll_callback_on_unit_create();
_dll_callback_on_frame();
_dll_callback_on_frame();
_dll_callback_on_end();
_dll_callback_on_start();
_dll_callback_on_frame();
_dll_callback_on_frame();
_dll_callback_on_end();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment