#include "eruta.h" #include "image.h" #include "rh.h" #include "fifi.h" #include "zori.h" #include "str.h" #include "mem.h" #include "state.h" #include "monolog.h" #include #include #include #include /* Debugging level for console output. */ #define LOG_CONSOLE(FORMAT, ...) LOG_LEVEL("CONSOLE", FORMAT, __VA_ARGS__) #define LOG_ENABLE_CONSOLE() LOG_ENABLE(CONSOLE) #define LOG_DISABLE_CONSOLE() LOG_DISABLE(CONSOLE) /* Amount of parameters a mruby function can be called with using _va functions */ #define RH_ARGS_MAX 64 /* * RH contains helper functions for the mruby ruby interpreter. */ /* * Converts C like arguments to an array of mruby mrb_values. * Returns amount of arguments parsed, negative on failure. * * Format specifier characters for the format argument: * * z: String from null terminated C string [char*] * s: String [char*, int] * Y: sYmbol from null terminated C string [char*] * y: symbol [char*, int] * * f: Float [double] * i: Integer [int] * b: Boolean [int] * 0: nil * Other characters: Error. Will return -(index or erroneous character) . */ int rh_args_va(Ruby * ruby, mrb_value * values, int size, char * format, va_list list) { int ch; int index; int i; double d; const char * str; index = 0; for (ch = (*format) ; (ch) && (index < size) ; format++ , ch = (*format)) { mrb_value val; int error = FALSE; switch(ch) { case 's': str = va_arg(list, const char*); i = va_arg(list, int); val = mrb_str_new(ruby, str, i); break; case 'z': str = va_arg(list, const char*); val = mrb_str_new_cstr(ruby, str); break; case 'y': str = va_arg(list, const char*); i = va_arg(list, int); val = mrb_symbol_value(mrb_intern(ruby, str, (size_t)i)); break; case 'Y': str = va_arg(list, const char*); val = mrb_symbol_value(mrb_intern_cstr(ruby, str)); break; case 'i': i = va_arg(list, int); val = mrb_fixnum_value(i); break; case 'f': d = va_arg(list, double); val = mrb_float_value(ruby, d); break; case 'b': i = va_arg(list, int); val = ( i ? mrb_true_value() : mrb_false_value()); break; case '0': val = mrb_nil_value(); break; default: error = TRUE; break; } if (error) { return -index; } values[index] = val; index ++; } return index; } /* strdup isn't ANSI C, just posix... :p so we need our own version.*/ char *rh_strdup(const char *str) { char * res = malloc(strlen(str) + 1); if(res) { strcpy(res, str); } return res; } /* Helps convert C values to mruby values in an array. */ int rh_args(Ruby * ruby, mrb_value * values, int size, char * format, ...) { int res; va_list list; va_start(list, format); res = rh_args_va(ruby, values, size, format, list); va_end(list); return res; } /** Calulates the execption string. Result only tempoarily available.. XXX: check if this doesn't leak memory... you must free the results manually. */ char * rh_exceptionstring(Ruby * self) { char * result; mrb_value value; mrb_value backtrace; mrb_value backtrace_str; if (!self->exc) return NULL; // /* XXX: Too bad, the backtrace doesn't seem to be filled in for some reason... * Should figure out how to fix this. */ mrb_print_backtrace(self); backtrace = // mrb_get_backtrace(self); mrb_funcall(self, mrb_obj_value(self->exc), "backtrace", 0); backtrace_str = mrb_funcall(self, backtrace, "join", 1, mrb_str_new_lit(self, "\n")); LOG_ERROR("backtrace: %s\n", mrb_string_value_cstr(self, &backtrace_str)); value = mrb_funcall(self, mrb_obj_value(self->exc), "inspect", 0); // reset exception since we have it's string value. // Does this leak memory or not??? self->exc = NULL; return rh_strdup(mrb_string_value_cstr(self, &value)); } /** Allocates and initialzes a new ruby state. */ Ruby * rh_new() { Ruby * self = mrb_open(); /*mrb_define_method(self, self->kernel_module, "path", tr_Path, ARGS_REQ(1));*/ return self; } /** Frees a ruby state. */ Ruby * rh_free(Ruby * self) { mrb_close(self); return NULL; } /** Returns an mrb_value that contains the value of object.inspect. */ mrb_value rh_inspect(mrb_state *mrb, mrb_value obj) { return mrb_inspect(mrb, obj); } char * rh_inspect_cstr(mrb_state *mrb, mrb_value value) { mrb_value res = rh_inspect(mrb, value); /* XXX: check that it's not modified anywere or export the const? */ return (char *) mrb_string_value_cstr(mrb, &res); } /* Does the actual reporting depending on the current state of ruby and the returned value. */ int rh_make_report(Ruby * self, mrb_value v) { int res = 0; char * str; /* Report exceptions */ str = rh_exceptionstring(self); if(str) { LOG_WARNING("mruby exception: %s", str); free(str); return 0; } /* Report result value if it's not nil on debug and console levels. */ if (!mrb_nil_p(v)) { str = rh_inspect_cstr(self, v); LOG_DEBUG("mruby result: %s", str); LOG_CONSOLE("-> %s", str); return 0; } return 0; } /* Runs a file and reports any errors over the monolog logging system. */ int rh_run_file(Ruby * self, const char * filename, FILE * file) { int res; char * str; mrbc_context * c ; mrb_value v; int ai; ai = mrb_gc_arena_save(self); c = mrbc_context_new(self); mrbc_filename(self, c, filename); v = mrb_load_file_cxt(self, file, c); mrbc_context_free(self, c); /* Report exceptions */ res = rh_make_report(self, v); mrb_gc_arena_restore(self, ai); return res; } int rh_run_filename(Ruby * self, const char * filename) { FILE * file = fopen(filename, "rt"); int res; if (!file) { LOG_ERROR("No such ruby file: %s\n", filename); return -1; } res = rh_run_file(self, filename, file); fclose(file); return 0; } /** * Executes a ruby file in Eruta's data/script directory with reporting. * Returns -2 if the file was not found. * Returns -3 if the path wasn't found. */ int rh_run_script(Ruby * self, const char * filename) { char * buf; int runres; buf = malloc(strlen(filename) + 256); if(!buf) return -3; strcpy(buf, "script/"); strncat(buf, filename, strlen(filename)); ALLEGRO_PATH * path = fifi_data_vpath(buf); if(!path) { free(buf); return -3; } runres = rh_run_filename(self, PATH_CSTR(path)); al_destroy_path(path); free(buf); return runres; } /* Executes a ruby command string. Errors are reported to the reporter callback if it isn't NULL. */ int rh_dostring(Ruby * self, const char * command) { int res = 0; char * str; mrb_value v; int ai; ai = mrb_gc_arena_save(self); #ifdef RH_USE_CONTEXT mrbc_context * c = mrbc_context_new(self); mrbc_filename(self, c, "command"); v = mrb_load_string_cxt(self, command, c); mrbc_context_free(self, c); #else v = mrb_load_string(self, command); #endif /* Report exceptions */ res = rh_make_report(self, v); /* mrb GC area seems to be an area for 1024 "new" objects for the generational * GC. It can overflow if a lot of new objects are generated * (say exceptions, etc) on the C side. To prevent this the area must be saved * and restored anywhere many ruby objects may have been generated. * It seems that here too this is needed. */ mrb_gc_arena_restore(self, ai); return res; } /* Executes a ruby function with parameters. Errors are reported to the reporter callback if it isn't NULL. */ mrb_value rh_run_function_args(Ruby * self, mrb_value rubyself, char * funcname, int argc, mrb_value * argv) { int res = 0; char * str; mrb_value v; mrb_sym symname = mrb_intern_cstr(self, funcname); int ai; if(!mrb_respond_to(self, rubyself, symname)) { return mrb_nil_value(); } ai = mrb_gc_arena_save(self); v = mrb_funcall_argv(self, rubyself, symname, argc, argv); res = rh_make_report(self, v); mrb_gc_arena_restore(self, ai); return v; } /** Runs a function in the ruby interpreter, with C arguments according to the * given format string, logging results and errors back to * the reporter. The limit is RH_ARGS_MAX arguments. */ mrb_value rh_run_function_va(Ruby * self, mrb_value rubyself, char * funcname, char * format, va_list list) { mrb_value argv[RH_ARGS_MAX]; int argc; argc = rh_args_va(self, argv, RH_ARGS_MAX, format, list); if (argc < 0) return mrb_nil_value(); return rh_run_function_args(self, rubyself, funcname, argc, argv); } /** Runs a function in the ruby interpreter, under the toplevel self. * This logs results and errors using monolog.h interfaces. */ mrb_value rh_run_toplevel_args(Ruby * ruby, char * name, int argc, mrb_value * argv) { return rh_run_function_args(ruby, mrb_top_self(ruby), name, argc, argv); } /** Runs a function in the ruby interpreter, under the toplevel self. * This logs results and errors using monolog.h interfaces. */ mrb_value rh_run_toplevel_va(Ruby * ruby, char * name, char * format, va_list list) { return rh_run_function_va(ruby, mrb_top_self(ruby), name, format, list); } /** Runs a function in the ruby interpreter, logging results and errors * using monolog.h interfaces. */ mrb_value rh_run_function(Ruby * ruby, mrb_value rubyself, char * name, char * format, ...) { mrb_value res; va_list list; va_start(list, format); res = rh_run_function_va(ruby, rubyself, name, format, list); va_end(list); return res; } /** Runs a function in the ruby interpreter, under the toplevel self. * This logs results and errors using monolog.h interfaces. */ mrb_value rh_run_toplevel(Ruby * ruby, char * name, char * format, ...) { mrb_value res; va_list list; va_start(list, format); res = rh_run_function_va(ruby, mrb_top_self(ruby), name, format, list); va_end(list); return res; } /* Calls a function, doesn't log anything. */ mrb_value rh_simple_funcall(Ruby * ruby, char * name) { int ai; Ruby * mrb = (Ruby *) ruby; mrb_value args[16]; ai = mrb_gc_arena_save(mrb); LOG("GC Area: %d\n", ai); // if(ai> 99) exit(0); mrb_value v = mrb_funcall_argv(mrb, mrb_top_self(mrb), mrb_intern_cstr(mrb, name), 0, args); if (mrb->exc) { if (!mrb_undef_p(v)) { mrb_p(mrb, mrb_obj_value(mrb->exc)); } return mrb_nil_value(); } mrb_gc_arena_restore(mrb, 0); return v; } /* Maybe wrap this too? mrb_value mrb_funcall_argv(mrb_state*, mrb_value, mrb_sym, int, mrb_value*); */ /* Converts and mrb_value to a C boolean as per the ruby interpretation. */ int rh_tobool(mrb_value v) { if (mrb_nil_p(v)) return FALSE; return mrb_bool(v); } /* Documentation for mrb_get_args retrieve arguments from mrb_state. mrb_get_args(mrb, format, ...) returns number of arguments parsed. format specifiers: o: Object [mrb_value] S: String [mrb_value] A: Array [mrb_value] H: Hash [mrb_value] s: String [char*,int] z: String [char*] a: Array [mrb_value*,int] f: Float [mrb_float] i: Integer [mrb_int] n: Symbol [mrb_sym] &: Block [mrb_value] *: rest argument [mrb_value*,int] |: optional */ #define RH_EVARGS_MAX 32 /* Returns the current number of the joystick, that is it's index in the allegro * JS list. Too bad but this number may change. Returns -1 if not found; */ static int joystick_nr(ALLEGRO_JOYSTICK * stick) { int index; ALLEGRO_JOYSTICK * aid; for(index = 0 ; index < al_get_num_joysticks(); index ++) { aid = al_get_joystick(index); if (aid == stick) return index; } return -1; } /* Send the even to the ruby main "on_poll" */ int rh_poll_event(mrb_state * mrb, ALLEGRO_EVENT * event) { State * state = state_get(); mrb_value event_args[RH_EVARGS_MAX]; int nargs = 0; if (!event) return FALSE; switch (event->type) { case ALLEGRO_EVENT_JOYSTICK_AXIS: nargs = rh_args(mrb, event_args, RH_EVARGS_MAX, "Yfiiif", "joystick_axis", event->any.timestamp, joystick_nr(event->joystick.id), event->joystick.stick, event->joystick.axis, event->joystick.pos); break; case ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN: nargs = rh_args(mrb, event_args, RH_EVARGS_MAX, "Yfiiif", "joystick_button_down", event->any.timestamp, joystick_nr(event->joystick.id), event->joystick.button); break; case ALLEGRO_EVENT_JOYSTICK_BUTTON_UP: nargs = rh_args(mrb, event_args, RH_EVARGS_MAX, "Yfiiif", "joystick_button_up", event->any.timestamp, joystick_nr(event->joystick.id), event->joystick.button); break; case ALLEGRO_EVENT_JOYSTICK_CONFIGURATION: nargs = rh_args(mrb, event_args, RH_EVARGS_MAX, "Yfiiif", "joystick_configuration", event->any.timestamp); break; case ALLEGRO_EVENT_KEY_DOWN: nargs = rh_args(mrb, event_args, RH_EVARGS_MAX, "Yfi", "key_down", event->any.timestamp, event->keyboard.keycode ); break; case ALLEGRO_EVENT_KEY_UP: nargs = rh_args(mrb, event_args, RH_EVARGS_MAX, "Yfi", "key_up", event->any.timestamp, event->keyboard.keycode ); break; case ALLEGRO_EVENT_KEY_CHAR: nargs = rh_args(mrb, event_args, RH_EVARGS_MAX, "Yfiiib", "key_char", event->any.timestamp, event->keyboard.keycode, event->keyboard.unichar, event->keyboard.modifiers, event->keyboard.repeat ); break; case ALLEGRO_EVENT_MOUSE_AXES: nargs = rh_args(mrb, event_args, RH_EVARGS_MAX, "Yfiiiiiiii", "mouse_axes", event->any.timestamp, event->mouse.x, event->mouse.y, event->mouse.z, event->mouse.w, event->mouse.dx, event->mouse.dy, event->mouse.dz, event->mouse.dw ); break; case ALLEGRO_EVENT_MOUSE_WARPED: nargs = rh_args(mrb, event_args, RH_EVARGS_MAX, "Yfiiiiiiii", "mouse_warped", event->any.timestamp, event->mouse.x, event->mouse.y, event->mouse.z, event->mouse.w, event->mouse.dx, event->mouse.dy, event->mouse.dz, event->mouse.dw ); break; case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN: nargs = rh_args(mrb, event_args, RH_EVARGS_MAX, "Yfiiiii", "mouse_button_down", event->any.timestamp, event->mouse.x, event->mouse.y, event->mouse.z, event->mouse.w, event->mouse.button ); break; case ALLEGRO_EVENT_MOUSE_BUTTON_UP: nargs = rh_args(mrb, event_args, RH_EVARGS_MAX, "Yfiiiii", "mouse_button_up", event->any.timestamp, event->mouse.x, event->mouse.y, event->mouse.z, event->mouse.w, event->mouse.button ); break; case ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY: nargs = rh_args(mrb, event_args, RH_EVARGS_MAX, "Yfiiii", "mouse_enter_display", event->any.timestamp, event->mouse.x, event->mouse.y, event->mouse.z, event->mouse.w ); break; case ALLEGRO_EVENT_MOUSE_LEAVE_DISPLAY: nargs = rh_args(mrb, event_args, RH_EVARGS_MAX, "Yfiiii", "mouse_leave_display", event->any.timestamp, event->mouse.x, event->mouse.y, event->mouse.z, event->mouse.w ); break; default: nargs = rh_args(mrb, event_args, RH_EVARGS_MAX, "Yf", "unknown", event->any.timestamp ); break; } rh_run_toplevel_args(state_ruby(state), "eruta_on_poll", nargs, event_args); return TRUE; } /* Polls the event queue and while events are available, send them to the ruby main "eruta_on_poll" */ int rh_poll_events(mrb_state * mrb, ALLEGRO_EVENT_QUEUE * queue) { ALLEGRO_EVENT event; while(al_get_next_event(queue, &event)) { rh_poll_event(mrb, &event); } return TRUE; } /* Tries to (re-)load the main ruby file, output to console. */ int rh_load_main() { State * state = state_get(); // Try to load the main ruby file. return rh_run_script(state_ruby(state), "main.rb"); } /** For execution of ruby strings by the console */ int rh_run_console_command(struct zori_console * console, const char * command, void * extra) { int res; LOG_ENABLE_CONSOLE(); res = rh_dostring(state_ruby(state_get()), command); LOG_DISABLE_CONSOLE(); (void) console; (void) extra; return res; }