implement callbacks with unboxed closure

This commit is contained in:
Corwin Kuiper 2021-06-04 12:29:56 +01:00
parent 363e032119
commit f45ecea493
4 changed files with 67 additions and 47 deletions

View file

@ -10,20 +10,20 @@ void log_output(struct mLogger* _log, int category, enum mLogLevel level,
const char* format, va_list args); const char* format, va_list args);
char* log_level_str(enum mLogLevel); char* log_level_str(enum mLogLevel);
static void (*EXTERNAL_LOGGING)(char*);
struct mLogger LOGGER = {.log = log_output};
struct MGBA { struct MGBA {
struct mLogger mlogger;
struct mCore* core; struct mCore* core;
struct video_buffer videoBuffer; struct video_buffer videoBuffer;
char* filename; char* filename;
struct callback callback;
}; };
struct MGBA* new_runner(char* filename) { struct MGBA* new_runner(char* filename) {
struct MGBA* mgba = malloc(sizeof(struct MGBA)); struct MGBA* mgba = malloc(sizeof(struct MGBA));
mgba->mlogger.log = log_output;
mgba->callback.callback = NULL;
mLogSetDefaultLogger(&LOGGER); mLogSetDefaultLogger(&mgba->mlogger);
char* filename_new = strdup(filename); char* filename_new = strdup(filename);
mgba->filename = filename_new; mgba->filename = filename_new;
@ -61,10 +61,13 @@ struct MGBA* new_runner(char* filename) {
return mgba; return mgba;
} }
void set_logger(void logger(char*)) { EXTERNAL_LOGGING = logger; } void set_logger(struct MGBA* mgba, struct callback callback) {
mgba->callback = callback;
}
void free_runner(struct MGBA* mgba) { void free_runner(struct MGBA* mgba) {
mgba->core->deinit(mgba->core); mgba->core->deinit(mgba->core);
mgba->callback.destroy(mgba->callback.data);
free(mgba->filename); free(mgba->filename);
free(mgba->videoBuffer.buffer); free(mgba->videoBuffer.buffer);
free(mgba); free(mgba);
@ -76,9 +79,12 @@ struct video_buffer get_video_buffer(struct MGBA* mgba) {
return mgba->videoBuffer; return mgba->videoBuffer;
} }
void log_output(struct mLogger* _log, int category, enum mLogLevel level, void log_output(struct mLogger* log, int category, enum mLogLevel level,
const char* format, va_list args) { const char* format, va_list args) {
UNUSED(_log); // cast log to mgba, this works as the logger is the top entry of the mgba
// struct
struct MGBA* mgba = (struct MGBA*)log;
if (level & 31) { if (level & 31) {
int32_t size = 0; int32_t size = 0;
@ -97,11 +103,11 @@ void log_output(struct mLogger* _log, int category, enum mLogLevel level,
size -= offset; size -= offset;
vsnprintf(&str[offset], size, format, args); vsnprintf(&str[offset], size, format, args);
if (EXTERNAL_LOGGING != NULL) { if (mgba->callback.callback != NULL)
EXTERNAL_LOGGING(str); mgba->callback.callback(mgba->callback.data, str);
} else { else
printf("%s\n", str); printf("%s\n", str);
}
free(str); free(str);
} }
} }

View file

@ -10,8 +10,14 @@ struct video_buffer {
uint32_t* buffer; uint32_t* buffer;
}; };
struct callback {
void* data;
void (*callback)(void*, char[]);
void (*destroy)(void*);
};
struct MGBA* new_runner(char filename[]); struct MGBA* new_runner(char filename[]);
void free_runner(struct MGBA* mgba); void free_runner(struct MGBA* mgba);
void set_logger(void logger(char[])); void set_logger(struct MGBA*, struct callback);
void advance_frame(struct MGBA* mgba); void advance_frame(struct MGBA* mgba);
struct video_buffer get_video_buffer(struct MGBA* mgba); struct video_buffer get_video_buffer(struct MGBA* mgba);

View file

@ -6,10 +6,8 @@ use image::io::Reader;
use io::Write; use io::Write;
use regex::Regex; use regex::Regex;
use runner::VideoBuffer; use runner::VideoBuffer;
use std::cell::RefCell;
use std::io; use std::io;
use std::path::Path; use std::path::Path;
use std::rc::Rc;
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
enum Status { enum Status {
@ -19,14 +17,13 @@ enum Status {
} }
fn test_file(file_to_run: &str) -> Status { fn test_file(file_to_run: &str) -> Status {
let finished = Rc::new(RefCell::new(Status::Running)); let mut finished = Status::Running;
let debug_reader_mutex = Regex::new(r"^\[(.*)\] GBA Debug: (.*)$").unwrap(); let debug_reader_mutex = Regex::new(r"^\[(.*)\] GBA Debug: (.*)$").unwrap();
let mut mgba = runner::MGBA::new(file_to_run); let mut mgba = runner::MGBA::new(file_to_run);
let video_buffer = mgba.get_video_buffer(); let video_buffer = mgba.get_video_buffer();
let fin_closure = Rc::clone(&finished); mgba.set_logger(|message| {
runner::set_logger(Box::new(move |message| {
if let Some(captures) = debug_reader_mutex.captures(message) { if let Some(captures) = debug_reader_mutex.captures(message) {
let log_level = &captures[1]; let log_level = &captures[1];
let out = &captures[2]; let out = &captures[2];
@ -37,8 +34,7 @@ fn test_file(file_to_run: &str) -> Status {
Err(e) => { Err(e) => {
println!("[failed]"); println!("[failed]");
println!("{}", e); println!("{}", e);
let mut done = fin_closure.borrow_mut(); finished = Status::Failed;
*done = Status::Failed;
} }
Ok(_) => {} Ok(_) => {}
} }
@ -50,28 +46,23 @@ fn test_file(file_to_run: &str) -> Status {
} }
if log_level == "FATAL" { if log_level == "FATAL" {
let mut done = fin_closure.borrow_mut(); finished = Status::Failed;
*done = Status::Failed;
} }
if out == "Tests finished successfully" { if out == "Tests finished successfully" {
let mut done = fin_closure.borrow_mut(); finished = Status::Sucess;
*done = Status::Sucess;
} }
} }
})); });
loop { loop {
mgba.advance_frame(); mgba.advance_frame();
let done = finished.borrow(); if finished != Status::Running {
if *done != Status::Running {
break; break;
} }
} }
runner::clear_logger(); return finished;
return (*finished.borrow()).clone();
} }
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {

View file

@ -1,3 +1,4 @@
use std::ffi::c_void;
use std::ffi::CStr; use std::ffi::CStr;
use std::ffi::CString; use std::ffi::CString;
@ -35,7 +36,6 @@ impl VideoBuffer {
impl MGBA { impl MGBA {
pub fn new(filename: &str) -> Self { pub fn new(filename: &str) -> Self {
unsafe { bindings::set_logger(Some(logger)) };
let c_str = CString::new(filename).expect("should be able to make cstring from filename"); let c_str = CString::new(filename).expect("should be able to make cstring from filename");
MGBA { MGBA {
mgba: unsafe { bindings::new_runner(c_str.as_ptr() as *mut i8) }, mgba: unsafe { bindings::new_runner(c_str.as_ptr() as *mut i8) },
@ -54,28 +54,45 @@ impl MGBA {
pub fn advance_frame(&mut self) { pub fn advance_frame(&mut self) {
unsafe { bindings::advance_frame(self.mgba) } unsafe { bindings::advance_frame(self.mgba) }
} }
} pub fn set_logger(&mut self, mut logger: impl FnMut(&str)) {
unsafe {
static mut CALLBACK: Option<Box<dyn Fn(&str)>> = None; let callback = generate_c_callback(move |message: *mut i8| {
logger(
pub fn set_logger(x: Box<dyn Fn(&str)>) { CStr::from_ptr(message)
unsafe { .to_str()
assert!(CALLBACK.is_none()); .expect("should be able to convert logging message to rust String"),
CALLBACK = Some(x); );
});
bindings::set_logger(self.mgba, callback)
}
} }
} }
pub fn clear_logger() { unsafe fn generate_c_callback<F>(f: F) -> bindings::callback
unsafe { CALLBACK = None } where
F: FnMut(*mut i8),
{
let data = Box::into_raw(Box::new(f));
bindings::callback {
callback: Some(call_closure::<F>),
data: data as *mut _,
destroy: Some(drop_box::<F>),
}
} }
extern "C" fn logger(c_str: *mut i8) { extern "C" fn call_closure<F>(data: *mut c_void, message: *mut i8)
where
F: FnMut(*mut i8),
{
let callback_ptr = data as *mut F;
let callback = unsafe { &mut *callback_ptr };
callback(message);
}
extern "C" fn drop_box<T>(data: *mut c_void) {
unsafe { unsafe {
if let Some(f) = &CALLBACK { Box::from_raw(data as *mut T);
f(CStr::from_ptr(c_str)
.to_str()
.expect("should be able to convert logging message to rust String"));
}
} }
} }