// blindly copied imports from fastdwarf #include "Core.h" #include "Console.h" #include "Export.h" #include "PluginManager.h" #include "DataDefs.h" #include "df/world.h" #include "df/unit.h" #include "tinythread.h" #include using std::string; using std::vector; using namespace DFHack; static void df_rubythread(void*); static command_result df_rubyload(color_ostream &out, vector & parameters); static command_result df_rubyeval(color_ostream &out, vector & parameters); static void ruby_bind_dfhack(void); // inter-thread communication stuff enum RB_command { RB_IDLE, RB_INIT, RB_DIE, RB_LOAD, RB_EVAL, RB_CUSTOM, }; tthread::mutex *m_irun; tthread::mutex *m_mutex; static RB_command r_type; static const char *r_command; static command_result r_result; static tthread::thread *r_thread; static int onupdate_active; // dfhack interface DFHACK_PLUGIN("ruby") DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { m_irun = new tthread::mutex(); m_mutex = new tthread::mutex(); r_type = RB_INIT; r_thread = new tthread::thread(df_rubythread, 0); while (r_type != RB_IDLE) tthread::this_thread::yield(); m_irun->lock(); if (r_result == CR_FAILURE) return CR_FAILURE; onupdate_active = 0; commands.push_back(PluginCommand("rb_load", "Ruby interpreter. Loads the given ruby script.", df_rubyload)); commands.push_back(PluginCommand("rb_eval", "Ruby interpreter. Eval() a ruby string.", df_rubyeval)); commands.push_back(PluginCommand("r", "Ruby interpreter dev. Eval() a ruby string.", df_rubyeval)); return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { m_mutex->lock(); if (!r_thread) return CR_OK; r_type = RB_DIE; r_command = 0; m_irun->unlock(); r_thread->join(); delete r_thread; r_thread = 0; delete m_irun; m_mutex->unlock(); delete m_mutex; return CR_OK; } DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { if (!onupdate_active) return CR_OK; command_result ret; m_mutex->lock(); if (!r_thread) return CR_OK; r_type = RB_EVAL; r_command = "DFHack.onupdate"; m_irun->unlock(); while (r_type != RB_IDLE) tthread::this_thread::yield(); ret = r_result; m_irun->lock(); m_mutex->unlock(); return ret; } static command_result df_rubyload(color_ostream &out, vector & parameters) { command_result ret; if (parameters.size() == 1 && (parameters[0] == "help" || parameters[0] == "?")) { out.print("This command loads the ruby script whose path is given as parameter, and run it.\n"); return CR_OK; } // serialize 'accesses' to the ruby thread m_mutex->lock(); if (!r_thread) // raced with plugin_shutdown ? return CR_OK; r_type = RB_LOAD; r_command = parameters[0].c_str(); m_irun->unlock(); // could use a condition_variable or something... while (r_type != RB_IDLE) tthread::this_thread::yield(); // XXX non-atomic with previous r_type change check ret = r_result; m_irun->lock(); m_mutex->unlock(); return ret; } static command_result df_rubyeval(color_ostream &out, vector & parameters) { command_result ret; if (parameters.size() == 1 && (parameters[0] == "help" || parameters[0] == "?")) { out.print("This command executes an arbitrary ruby statement.\n"); return CR_OK; } std::string full = ""; full += "DFHack.puts(("; for (unsigned i=0 ; ilock(); if (!r_thread) return CR_OK; r_type = RB_EVAL; r_command = full.c_str(); m_irun->unlock(); while (r_type != RB_IDLE) tthread::this_thread::yield(); ret = r_result; m_irun->lock(); m_mutex->unlock(); return ret; } // ruby thread code static void dump_rb_error(void) { VALUE s, err; err = rb_gv_get("$!"); s = rb_funcall(err, rb_intern("class"), 0); s = rb_funcall(s, rb_intern("name"), 0); Core::printerr("E: %s: ", rb_string_value_ptr(&s)); s = rb_funcall(err, rb_intern("message"), 0); Core::printerr("%s\n", rb_string_value_ptr(&s)); err = rb_funcall(err, rb_intern("backtrace"), 0); for (int i=0 ; i<8 ; ++i) if ((s = rb_ary_shift(err)) != Qnil) Core::printerr(" %s\n", rb_string_value_ptr(&s)); } // ruby thread main loop static void df_rubythread(void *p) { int state, running; // initialize the ruby interpreter ruby_init(); ruby_init_loadpath(); // default value for the $0 "current script name" ruby_script("dfhack"); // create the ruby objects to map DFHack to ruby methods ruby_bind_dfhack(); r_result = CR_OK; r_type = RB_IDLE; running = 1; while (running) { // wait for new command m_irun->lock(); switch (r_type) { case RB_IDLE: case RB_INIT: break; case RB_DIE: running = 0; ruby_finalize(); break; case RB_LOAD: state = 0; rb_load_protect(rb_str_new2(r_command), Qfalse, &state); if (state) dump_rb_error(); break; case RB_EVAL: state = 0; rb_eval_string_protect(r_command, &state); if (state) dump_rb_error(); break; case RB_CUSTOM: // TODO handle ruby custom commands break; } r_result = CR_OK; r_type = RB_IDLE; m_irun->unlock(); tthread::this_thread::yield(); } } // ruby classes static VALUE rb_cDFHack; static VALUE rb_c_WrapData; // DFHack methods // enable/disable calls to DFHack.onupdate() static VALUE rb_dfonupdateactive(VALUE self) { if (onupdate_active) return Qtrue; else return Qfalse; } static VALUE rb_dfonupdateactiveset(VALUE self, VALUE val) { onupdate_active = (val == Qtrue || val == INT2FIX(1)) ? 1 : 0; return Qtrue; } static VALUE rb_dfresume(VALUE self) { Core::getInstance().Resume(); return Qtrue; } static VALUE rb_dfsuspend(VALUE self) { Core::getInstance().Suspend(); return Qtrue; } /* static VALUE rb_dfgetversion(VALUE self) { return rb_str_new2(getcore().vinfo->getVersion().c_str()); } */ // TODO color_ostream proxy yadda yadda static VALUE rb_dfprint_str(VALUE self, VALUE s) { //getcore().con.print("%s", rb_string_value_ptr(&s)); Core::printerr("%s", rb_string_value_ptr(&s)); return Qnil; } static VALUE rb_dfprint_err(VALUE self, VALUE s) { Core::printerr("%s", rb_string_value_ptr(&s)); return Qnil; } // raw memory access // WARNING: may cause game crash ! double-check your addresses ! static VALUE rb_dfmemread(VALUE self, VALUE addr, VALUE len) { return rb_str_new((char*)rb_num2ulong(addr), rb_num2ulong(len)); } static VALUE rb_dfmemwrite(VALUE self, VALUE addr, VALUE raw) { // no stable api for raw.length between rb1.8/rb1.9 ... int strlen = FIX2INT(rb_funcall(raw, rb_intern("length"), 0)); memcpy((void*)rb_num2ulong(addr), rb_string_value_ptr(&raw), strlen); return Qtrue; } static VALUE rb_dfmalloc(VALUE self, VALUE len) { return rb_uint2inum((long)malloc(FIX2INT(len))); } static VALUE rb_dffree(VALUE self, VALUE ptr) { free((void*)rb_num2ulong(ptr)); return Qtrue; } // raw c++ wrappers // return the nth element of a vector static VALUE rb_dfvectorat(VALUE self, VALUE vect_addr, VALUE idx) { vector *v = (vector*)rb_num2ulong(vect_addr); return rb_uint2inum(v->at(FIX2INT(idx))); } // return a c++ string as a ruby string (nul-terminated) static VALUE rb_dfreadstring(VALUE self, VALUE str_addr) { string *s = (string*)rb_num2ulong(str_addr); return rb_str_new2(s->c_str()); } /* XXX this needs a custom DFHack::Plugin subclass to pass the cmdname to invoke(), to match the ruby callback // register a ruby method as dfhack console command // usage: DFHack.register("moo", "this commands prints moo on the console") { DFHack.puts "moo !" } static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr) { commands.push_back(PluginCommand(rb_string_value_ptr(&name), rb_string_value_ptr(&descr), df_rubycustom)); return Qtrue; } */ static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr) { rb_raise(rb_eRuntimeError, "not implemented"); } // return the address of the struct in DF memory (for raw memread/write) static VALUE rb_memaddr(VALUE self) { void *data; Data_Get_Struct(self, void, data); return rb_uint2inum((uint32_t)data); } // BEGIN GENERATED SECTION // begin generated T_cursor binding static VALUE rb_c_T_cursor; static VALUE rb_m_T_cursor_x(VALUE self) { struct df::global::T_cursor *var; Data_Get_Struct(self, struct df::global::T_cursor, var); return rb_uint2inum(var->x); } static VALUE rb_m_T_cursor_x_set(VALUE self, VALUE val) { struct df::global::T_cursor *var; Data_Get_Struct(self, struct df::global::T_cursor, var); var->x = rb_num2ulong(val); return Qtrue; } static VALUE rb_m_T_cursor_y(VALUE self) { struct df::global::T_cursor *var; Data_Get_Struct(self, struct df::global::T_cursor, var); return rb_uint2inum(var->y); } static VALUE rb_m_T_cursor_y_set(VALUE self, VALUE val) { struct df::global::T_cursor *var; Data_Get_Struct(self, struct df::global::T_cursor, var); var->y = rb_num2ulong(val); return Qtrue; } static VALUE rb_m_T_cursor_z(VALUE self) { struct df::global::T_cursor *var; Data_Get_Struct(self, struct df::global::T_cursor, var); return rb_uint2inum(var->z); } static VALUE rb_m_T_cursor_z_set(VALUE self, VALUE val) { struct df::global::T_cursor *var; Data_Get_Struct(self, struct df::global::T_cursor, var); var->z = rb_num2ulong(val); return Qtrue; } // link methods to the class static void ruby_bind_T_cursor(void) { // create a class, child of WrapData, in module DFHack rb_c_T_cursor = rb_define_class_under(rb_cDFHack, "T_cursor", rb_c_WrapData); // reader for 'x' (0 = no arg) rb_define_method(rb_c_T_cursor, "x", RUBY_METHOD_FUNC(rb_m_T_cursor_x), 0); // writer for 'x' (1 arg) rb_define_method(rb_c_T_cursor, "x=", RUBY_METHOD_FUNC(rb_m_T_cursor_x_set), 1); rb_define_method(rb_c_T_cursor, "y", RUBY_METHOD_FUNC(rb_m_T_cursor_y), 0); rb_define_method(rb_c_T_cursor, "y=", RUBY_METHOD_FUNC(rb_m_T_cursor_y_set), 1); rb_define_method(rb_c_T_cursor, "z", RUBY_METHOD_FUNC(rb_m_T_cursor_z), 0); rb_define_method(rb_c_T_cursor, "z=", RUBY_METHOD_FUNC(rb_m_T_cursor_z_set), 1); } // create an instance of T_cursor from global::cursor // this method is linked to DFHack.cursor in ruby_bind_dfhack() static VALUE rb_global_cursor(VALUE self) { return Data_Wrap_Struct(rb_c_T_cursor, 0, 0, df::global::cursor); } // begin generated unit binding static VALUE rb_c_unit; static VALUE rb_m_unit_id(VALUE self) { struct df::unit *var; Data_Get_Struct(self, struct df::unit, var); return rb_uint2inum(var->id); } static VALUE rb_m_unit_id_set(VALUE self, VALUE val) { struct df::unit *var; Data_Get_Struct(self, struct df::unit, var); var->id = rb_num2ulong(val); return Qtrue; } static void ruby_bind_unit(void) { // ruby class name must begin with uppercase rb_c_unit = rb_define_class_under(rb_cDFHack, "Unit", rb_c_WrapData); rb_define_method(rb_c_unit, "id", RUBY_METHOD_FUNC(rb_m_unit_id), 0); rb_define_method(rb_c_unit, "id=", RUBY_METHOD_FUNC(rb_m_unit_id_set), 1); } // begin generated world binding static VALUE rb_c_world; static VALUE rb_c_world_T_units; static VALUE rb_m_world_T_units_all(VALUE self) { struct df::world::T_units *var; Data_Get_Struct(self, struct df::world::T_units, var); // read a vector VALUE ret = rb_ary_new(); for (unsigned i=0 ; iall.size() ; ++i) rb_ary_push(ret, Data_Wrap_Struct(rb_c_unit, 0, 0, var->all[i])); return ret; } static VALUE rb_m_world_units(VALUE self) { struct df::world *var; Data_Get_Struct(self, struct df::world, var); return Data_Wrap_Struct(rb_c_world_T_units, 0, 0, &var->units); } static void ruby_bind_world(void) { rb_c_world = rb_define_class_under(rb_cDFHack, "World", rb_c_WrapData); rb_c_world_T_units = rb_define_class_under(rb_c_world, "T_units", rb_c_WrapData); rb_define_method(rb_c_world, "units", RUBY_METHOD_FUNC(rb_m_world_units), 0); rb_define_method(rb_c_world_T_units, "all", RUBY_METHOD_FUNC(rb_m_world_T_units_all), 0); } static VALUE rb_global_world(VALUE self) { return Data_Wrap_Struct(rb_c_world, 0, 0, df::global::world); } /* static VALUE rb_dfcreatures(VALUE self) { OffsetGroup *ogc = getcore().vinfo->getGroup("Creatures"); vector *v = (vector*)ogc->getAddress("vector"); VALUE ret = rb_ary_new(); for (unsigned i=0 ; isize() ; ++i) rb_ary_push(ret, Data_Wrap_Struct(rb_cCreature, 0, 0, v->at(i))); return ret; } static VALUE rb_getlaborname(VALUE self, VALUE idx) { return rb_str_new2(getcore().vinfo->getLabor(FIX2INT(idx)).c_str()); } static VALUE rb_getskillname(VALUE self, VALUE idx) { return rb_str_new2(getcore().vinfo->getSkill(FIX2INT(idx)).c_str()); } static VALUE rb_mapblock(VALUE self, VALUE x, VALUE y, VALUE z) { Maps *map; Data_Get_Struct(self, Maps, map); df_block *block; block = map->getBlock(FIX2INT(x)/16, FIX2INT(y)/16, FIX2INT(z)); if (!block) return Qnil; return Data_Wrap_Struct(rb_cMapBlock, 0, 0, block); } */ // define module DFHack and its methods static void ruby_bind_dfhack(void) { rb_cDFHack = rb_define_module("DFHack"); // global DFHack commands rb_define_singleton_method(rb_cDFHack, "onupdate_active", RUBY_METHOD_FUNC(rb_dfonupdateactive), 0); rb_define_singleton_method(rb_cDFHack, "onupdate_active=", RUBY_METHOD_FUNC(rb_dfonupdateactiveset), 1); rb_define_singleton_method(rb_cDFHack, "resume", RUBY_METHOD_FUNC(rb_dfresume), 0); rb_define_singleton_method(rb_cDFHack, "do_suspend", RUBY_METHOD_FUNC(rb_dfsuspend), 0); rb_define_singleton_method(rb_cDFHack, "resume", RUBY_METHOD_FUNC(rb_dfresume), 0); //rb_define_singleton_method(rb_cDFHack, "version", RUBY_METHOD_FUNC(rb_dfgetversion), 0); rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1); rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1); rb_define_singleton_method(rb_cDFHack, "memread", RUBY_METHOD_FUNC(rb_dfmemread), 2); rb_define_singleton_method(rb_cDFHack, "memwrite", RUBY_METHOD_FUNC(rb_dfmemwrite), 2); rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1); rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1); rb_define_singleton_method(rb_cDFHack, "vectorat", RUBY_METHOD_FUNC(rb_dfvectorat), 2); rb_define_singleton_method(rb_cDFHack, "readstring", RUBY_METHOD_FUNC(rb_dfreadstring), 1); rb_define_singleton_method(rb_cDFHack, "register_dfcommand", RUBY_METHOD_FUNC(rb_dfregister), 2); // accessors for dfhack globals rb_define_singleton_method(rb_cDFHack, "cursor", RUBY_METHOD_FUNC(rb_global_cursor), 0); rb_define_singleton_method(rb_cDFHack, "world", RUBY_METHOD_FUNC(rb_global_world), 0); // parent class for all wrapped objects rb_c_WrapData = rb_define_class_under(rb_cDFHack, "WrapData", rb_cObject); rb_define_method(rb_c_WrapData, "memaddr", RUBY_METHOD_FUNC(rb_memaddr), 0); // call generated bindings ruby_bind_T_cursor(); ruby_bind_unit(); ruby_bind_world(); // load the default ruby-level definitions int state=0; rb_load_protect(rb_str_new2("./hack/ruby.rb"), Qfalse, &state); if (state) dump_rb_error(); }