From 662d3101c70196a220b8226d0ebeb9121d76b5fc Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 11 Dec 2012 17:25:51 +0100 Subject: [PATCH] ruby: fix onupdate tick limiting + advmode, add pageprotect, add :script_finished --- library/Core.cpp | 2 +- plugins/ruby/README | 1 + plugins/ruby/ruby.cpp | 90 +++++++++++++++++++++++++++++++++++++++++-- plugins/ruby/ruby.rb | 25 +++++++++--- scripts/superdwarf.rb | 7 +--- 5 files changed, 108 insertions(+), 17 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 7e9c90e98..7a0186362 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -316,7 +316,7 @@ static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr, rbcmd += "'" + args[i] + "', "; rbcmd += "]\n"; - rbcmd += "load './hack/scripts/" + name + ".rb'"; + rbcmd += "catch(:script_finished) { load './hack/scripts/" + name + ".rb' }"; return plug_mgr->eval_ruby(out, rbcmd.c_str()); } diff --git a/plugins/ruby/README b/plugins/ruby/README index d35c34bbe..9bead57d4 100644 --- a/plugins/ruby/README +++ b/plugins/ruby/README @@ -48,6 +48,7 @@ If you create such a script, e.g. 'test.rb', that will add a new dfhack console command 'test'. The script can access the console command arguments through the global variable '$script_args', which is an array of ruby Strings. +To exit early from a script, use 'throw :script_finished' The help string displayed in dfhack 'ls' command is the first line of the script, if it is a comment (ie starts with '# '). diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index d75fa2402..187b90e3d 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -39,7 +39,7 @@ static color_ostream *r_console; // color_ostream given as argument, if NU static const char *r_command; static tthread::thread *r_thread; static int onupdate_active; -static int onupdate_minyear, onupdate_minyeartick; +static int onupdate_minyear, onupdate_minyeartick=-1, onupdate_minyeartickadv=-1; static color_ostream_proxy *console_proxy; @@ -180,10 +180,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (!onupdate_active) return CR_OK; - if (*df::global::cur_year < onupdate_minyear) + if (df::global::cur_year && (*df::global::cur_year < onupdate_minyear)) return CR_OK; - if (*df::global::cur_year == onupdate_minyear && - *df::global::cur_year_tick < onupdate_minyeartick) + if (df::global::cur_year_tick && onupdate_minyeartick >= 0 && + (*df::global::cur_year == onupdate_minyear && + *df::global::cur_year_tick < onupdate_minyeartick)) + return CR_OK; + if (df::global::cur_year_tick_advmode && onupdate_minyeartickadv >= 0 && + (*df::global::cur_year == onupdate_minyear && + *df::global::cur_year_tick_advmode < onupdate_minyeartickadv)) return CR_OK; return plugin_eval_ruby(out, "DFHack.onupdate"); @@ -481,6 +486,17 @@ static VALUE rb_dfonupdate_minyeartick_set(VALUE self, VALUE val) return Qtrue; } +static VALUE rb_dfonupdate_minyeartickadv(VALUE self) +{ + return rb_uint2inum(onupdate_minyeartickadv); +} + +static VALUE rb_dfonupdate_minyeartickadv_set(VALUE self, VALUE val) +{ + onupdate_minyeartickadv = rb_num2ulong(val); + return Qtrue; +} + static VALUE rb_dfprint_str(VALUE self, VALUE s) { if (r_console) @@ -531,6 +547,23 @@ static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr) return rb_uint2inum(*(uint32_t*)rb_num2ulong(objptr)); } +// run a dfhack command, as if typed from the dfhack console +static VALUE rb_dfhack_run(VALUE self, VALUE cmd) +{ + if (!r_console) // XXX + return Qnil; + + std::string s; + int strlen = FIX2INT(rb_funcall(cmd, rb_intern("length"), 0)); + s.assign(rb_string_value_ptr(&cmd), strlen); + + // allow the target command to suspend + // FIXME + //CoreSuspendClaimer suspend(true); + Core::getInstance().runCommand(*r_console, s); + + return Qtrue; +} @@ -663,6 +696,49 @@ static VALUE rb_dfmemory_patch(VALUE self, VALUE addr, VALUE raw) return ret ? Qtrue : Qfalse; } +// allocate memory pages +static VALUE rb_dfmemory_pagealloc(VALUE self, VALUE len) +{ + void *ret = Core::getInstance().p->memAlloc(rb_num2ulong(len)); + + return (ret == (void*)-1) ? Qnil : rb_uint2inum((uint32_t)ret); +} + +// free memory from pagealloc +static VALUE rb_dfmemory_pagedealloc(VALUE self, VALUE ptr, VALUE len) +{ + int ret = Core::getInstance().p->memDealloc((void*)rb_num2ulong(ptr), rb_num2ulong(len)); + + return ret ? Qfalse : Qtrue; +} + +// change memory page permissions +// ptr must be page-aligned +// prot is a String, eg 'rwx', 'r', 'x' +static VALUE rb_dfmemory_pageprotect(VALUE self, VALUE ptr, VALUE len, VALUE prot_str) +{ + int ret, prot=0; + char *prot_p = rb_string_value_ptr(&prot_str); + + if (*prot_p == 'r') { + prot |= Process::MemProt::READ; + ++prot_p; + } + if (*prot_p == 'w') { + prot |= Process::MemProt::WRITE; + ++prot_p; + } + if (*prot_p == 'x') { + prot |= Process::MemProt::EXEC; + ++prot_p; + } + + Core::printerr("pageprot %x %x %x\n", rb_num2ulong(ptr), rb_num2ulong(len), prot); + ret = Core::getInstance().p->memProtect((void*)rb_num2ulong(ptr), rb_num2ulong(len), prot); + + return ret ? Qfalse : Qtrue; +} + // stl::string static VALUE rb_dfmemory_stlstring_new(VALUE self) @@ -963,14 +1039,20 @@ static void ruby_bind_dfhack(void) { rb_define_singleton_method(rb_cDFHack, "onupdate_minyear=", RUBY_METHOD_FUNC(rb_dfonupdate_minyear_set), 1); rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartick", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartick), 0); rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartick=", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartick_set), 1); + rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartickadv", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartickadv), 0); + rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartickadv=", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartickadv_set), 1); rb_define_singleton_method(rb_cDFHack, "get_global_address", RUBY_METHOD_FUNC(rb_dfget_global_address), 1); rb_define_singleton_method(rb_cDFHack, "get_vtable", RUBY_METHOD_FUNC(rb_dfget_vtable), 1); rb_define_singleton_method(rb_cDFHack, "get_rtti_classname", RUBY_METHOD_FUNC(rb_dfget_rtti_classname), 1); rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1); + //rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1); 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, "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, "pagealloc", RUBY_METHOD_FUNC(rb_dfmemory_pagealloc), 1); + rb_define_singleton_method(rb_cDFHack, "pagedealloc", RUBY_METHOD_FUNC(rb_dfmemory_pagedealloc), 2); + rb_define_singleton_method(rb_cDFHack, "pageprotect", RUBY_METHOD_FUNC(rb_dfmemory_pageprotect), 3); rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 8); rb_define_singleton_method(rb_cDFHack, "version", RUBY_METHOD_FUNC(rb_dfversion), 0); diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index 27cde675a..47924dcdf 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -64,6 +64,7 @@ module DFHack # register a callback to be called every gframe or more # ex: DFHack.onupdate_register('fastdwarf') { DFHack.world.units[0].counters.job_counter = 0 } + # if ticklimit is given, do not call unless this much game ticks have passed. Handles advmode time stretching. def onupdate_register(descr, ticklimit=nil, initialtickdelay=0, &b) raise ArgumentError, 'need a description as 1st arg' unless descr.kind_of?(::String) @onupdate_list ||= [] @@ -82,7 +83,7 @@ module DFHack @onupdate_list.delete b if @onupdate_list.empty? DFHack.onupdate_active = false - DFHack.onupdate_minyear = DFHack.onupdate_minyeartick = 0 + DFHack.onupdate_minyear = DFHack.onupdate_minyeartick = DFHack.onupdate_minyeartickadv = -1 end end @@ -94,20 +95,32 @@ module DFHack end TICKS_PER_YEAR = 1200*28*12 - # this method is called by dfhack every 'onupdate' if onupdate_active is true + # this method is called by ruby.cpp if df.onupdate_active is true def onupdate @onupdate_list ||= [] - ticks_per_year = TICKS_PER_YEAR - ticks_per_year *= 72 if gametype == :ADVENTURE_MAIN or gametype == :ADVENTURE_ARENA + y = cur_year + ytmax = TICKS_PER_YEAR + if df.gamemode == :ADVENTURE and df.respond_to?(:cur_year_tick_advmode) + yt = cur_year_tick_advmode + ytmax *= 144 + else + yt = cur_year_tick + end @onupdate_list.each { |o| - o.check_run(cur_year, cur_year_tick, ticks_per_year) + o.check_run(y, yt, ytmax) } if onext = @onupdate_list.sort.first DFHack.onupdate_minyear = onext.minyear - DFHack.onupdate_minyeartick = onext.minyeartick + if ytmax > TICKS_PER_YEAR + DFHack.onupdate_minyeartick = -1 + DFHack.onupdate_minyeartickadv = onext.minyeartick + else + DFHack.onupdate_minyeartick = onext.minyeartick + DFHack.onupdate_minyeartickadv = -1 + end end end diff --git a/scripts/superdwarf.rb b/scripts/superdwarf.rb index eac9802fa..2dd873b50 100644 --- a/scripts/superdwarf.rb +++ b/scripts/superdwarf.rb @@ -8,12 +8,7 @@ when 'add' if u = df.unit_find $superdwarf_ids |= [u.id] - if df.gamemode == :ADVENTURE and not df.respond_to?(:cur_year_tick_advmode) - onupdate_delay = nil - else - onupdate_delay = 1 - end - $superdwarf_onupdate ||= df.onupdate_register('superdwarf', onupdate_delay) { + $superdwarf_onupdate ||= df.onupdate_register('superdwarf', 1) { if $superdwarf_ids.empty? df.onupdate_unregister($superdwarf_onupdate) $superdwarf_onupdate = nil