From 7732901b6ec1af9ed5e77a118bade7285da3e018 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 5 Jul 2012 15:35:37 +0200 Subject: [PATCH] ruby: tweak onupdate to allow tick rate-limiting --- plugins/ruby/README | 4 +++ plugins/ruby/ruby.cpp | 45 +++++++++++++++++++++++++++----- plugins/ruby/ruby.rb | 60 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 98 insertions(+), 11 deletions(-) diff --git a/plugins/ruby/README b/plugins/ruby/README index 493e3c821..97a4cbd30 100644 --- a/plugins/ruby/README +++ b/plugins/ruby/README @@ -124,6 +124,10 @@ DFHack callbacks The plugin interfaces with dfhack 'onupdate' hook. To register ruby code to be run every graphic frame, use: handle = df.onupdate_register { puts 'i love flooding the console' } +You can also rate-limit when your callback is called to a number of game ticks: + handle = df.onupdate_register(10) { puts '10 more in-game ticks elapsed' } +In this case, the callback is called immediately, and then every X in-game +ticks (advances only when the game is unpaused). To stop being called, use: df.onupdate_unregister handle diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index f1f007263..6dd0b39c8 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -6,8 +6,7 @@ #include "VersionInfo.h" #include "DataDefs.h" -#include "df/world.h" -#include "df/unit.h" +#include "df/global_objects.h" #include "tinythread.h" @@ -39,6 +38,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 color_ostream_proxy *console_proxy; @@ -165,10 +165,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) // ruby sets this flag when needed, to avoid lag running ruby code every // frame if not necessary - // TODO dynamic check on df::cur_year{_tick} if (!onupdate_active) return CR_OK; + if (*df::global::cur_year < onupdate_minyear) + return CR_OK; + if (*df::global::cur_year == onupdate_minyear && + *df::global::cur_year_tick < onupdate_minyeartick) + return CR_OK; + return plugin_eval_ruby(out, "DFHack.onupdate"); } @@ -422,7 +427,7 @@ static VALUE rb_cDFHack; // DFHack module ruby methods, binds specific dfhack methods // enable/disable calls to DFHack.onupdate() -static VALUE rb_dfonupdateactive(VALUE self) +static VALUE rb_dfonupdate_active(VALUE self) { if (onupdate_active) return Qtrue; @@ -430,12 +435,34 @@ static VALUE rb_dfonupdateactive(VALUE self) return Qfalse; } -static VALUE rb_dfonupdateactiveset(VALUE self, VALUE val) +static VALUE rb_dfonupdate_active_set(VALUE self, VALUE val) { onupdate_active = (BOOL_ISFALSE(val) ? 0 : 1); return Qtrue; } +static VALUE rb_dfonupdate_minyear(VALUE self) +{ + return rb_uint2inum(onupdate_minyear); +} + +static VALUE rb_dfonupdate_minyear_set(VALUE self, VALUE val) +{ + onupdate_minyear = rb_num2ulong(val); + return Qtrue; +} + +static VALUE rb_dfonupdate_minyeartick(VALUE self) +{ + return rb_uint2inum(onupdate_minyeartick); +} + +static VALUE rb_dfonupdate_minyeartick_set(VALUE self, VALUE val) +{ + onupdate_minyeartick = rb_num2ulong(val); + return Qtrue; +} + static VALUE rb_dfprint_str(VALUE self, VALUE s) { if (r_console) @@ -777,8 +804,12 @@ static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE cppvoff, VALUE a0, VALUE static void ruby_bind_dfhack(void) { rb_cDFHack = rb_define_module("DFHack"); - 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, "onupdate_active", RUBY_METHOD_FUNC(rb_dfonupdate_active), 0); + rb_define_singleton_method(rb_cDFHack, "onupdate_active=", RUBY_METHOD_FUNC(rb_dfonupdate_active_set), 1); + rb_define_singleton_method(rb_cDFHack, "onupdate_minyear", RUBY_METHOD_FUNC(rb_dfonupdate_minyear), 0); + 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, "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); diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index 4c692c92c..eda6ffd52 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -23,26 +23,78 @@ module Kernel end module DFHack + class OnupdateCallback + attr_accessor :callback, :timelimit, :minyear, :minyeartick + def initialize(cb, tl) + @callback = cb + @ticklimit = tl + @minyear = (tl ? df.cur_year : 0) + @minyeartick = (tl ? df.cur_year_tick : 0) + end + + # run callback if timedout + def check_run(year, yeartick, yearlen) + if !@ticklimit + @callback.call + else + if year > @minyear or (year == @minyear and yeartick >= @minyeartick) + @callback.call + @minyear = year + @minyeartick = yeartick + @ticklimit + if @minyeartick > yearlen + @minyear += 1 + @minyeartick -= yearlen + end + end + end + end + + def <=>(o) + [@minyear, @minyeartick] <=> [o.minyear, o.minyeartick] + end + end + class << self + attr_accessor :onupdate_list, :onstatechange_list + # register a callback to be called every gframe or more # ex: DFHack.onupdate_register { DFHack.world.units[0].counters.job_counter = 0 } - def onupdate_register(&b) + def onupdate_register(ticklimit=nil, &b) @onupdate_list ||= [] - @onupdate_list << b + @onupdate_list << OnupdateCallback.new(b, ticklimit) DFHack.onupdate_active = true + if onext = @onupdate_list.sort.first + DFHack.onupdate_minyear = onext.minyear + DFHack.onupdate_minyeartick = onext.minyeartick + end @onupdate_list.last end # delete the callback for onupdate ; use the value returned by onupdate_register def onupdate_unregister(b) @onupdate_list.delete b - DFHack.onupdate_active = false if @onupdate_list.empty? + if @onupdate_list.empty? + DFHack.onupdate_active = false + DFHack.onupdate_minyear = DFHack.onupdate_minyeartick = 0 + end end + TICKS_PER_YEAR = 1200*28*12 # this method is called by dfhack every 'onupdate' if onupdate_active is true def onupdate @onupdate_list ||= [] - @onupdate_list.each { |cb| cb.call } + + ticks_per_year = TICKS_PER_YEAR + ticks_per_year *= 72 if gametype == :ADVENTURE_MAIN or gametype == :ADVENTURE_ARENA + + @onupdate_list.each { |o| + o.check_run(cur_year, cur_year_tick, ticks_per_year) + } + + if onext = @onupdate_list.sort.first + DFHack.onupdate_minyear = onext.minyear + DFHack.onupdate_minyeartick = onext.minyeartick + end end # register a callback to be called every gframe or more