mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 16:21:33 +11:00
implement callbacks with unboxed closure
This commit is contained in:
parent
363e032119
commit
f45ecea493
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
@ -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> {
|
||||||
|
|
|
@ -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"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue