introduce DFHACK_RUBY_DESTINATION From: eroen --- CMakeLists.txt | 2 plugins/ruby/CMakeLists.txt | 8 + plugins/ruby/ruby.cpp | 2 plugins/ruby/ruby.rb | 258 ------------------------------------------- plugins/ruby/ruby.rb.in | 258 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 267 insertions(+), 261 deletions(-) delete mode 100644 plugins/ruby/ruby.rb create mode 100644 plugins/ruby/ruby.rb.in diff --git a/CMakeLists.txt b/CMakeLists.txt index aada996d..64cbfdaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,6 +204,8 @@ SET(DFHACK_PLUGIN_DESTINATION hack/plugins CACHE INTERNAL "") SET(DFHACK_INCLUDES_DESTINATION hack/include) # dfhack lua files go here: SET(DFHACK_LUA_DESTINATION hack/lua CACHE INTERNAL "") +# dfhack ruby files go here: +SET(DFHACK_RUBY_DESTINATION hack/ruby CACHE INTERNAL "") # the windows .lib file goes here: SET(DFHACK_DEVLIB_DESTINATION hack) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index 36750bee..bae0396e 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -77,7 +77,8 @@ INCLUDE_DIRECTORIES("${dfhack_SOURCE_DIR}/depends/tthread") DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread) ADD_DEPENDENCIES(ruby ruby-autogen-rb) set_property( SOURCE "ruby.cpp" APPEND PROPERTY COMPILE_DEFINITIONS - "DFHACK_LIBRARY_DESTINATION=\"${DFHACK_LIBRARY_DESTINATION}\"" ) + "DFHACK_LIBRARY_DESTINATION=\"${DFHACK_LIBRARY_DESTINATION}\"" + "DFHACK_RUBY_DESTINATION=\"${DFHACK_RUBY_DESTINATION}\"") IF(EXISTS ${RUBYLIB}) INSTALL(FILES ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION} RENAME ${RUBYLIB_INSTALL_NAME}) @@ -88,8 +89,11 @@ ELSE() ENDIF() ENDIF() +configure_file( ruby.rb.in ruby.rb @ONLY ) INSTALL(DIRECTORY . - DESTINATION hack/ruby + DESTINATION ${DFHACK_RUBY_DESTINATION} FILES_MATCHING PATTERN "*.rb") +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/ruby.rb + DESTINATION ${DFHACK_RUBY_DESTINATION}) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/${RUBYAUTOGEN} DESTINATION hack/ruby) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 4811aa2f..7005e605 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -460,7 +460,7 @@ static void df_rubythread(void *p) // load the default ruby-level definitions in the background state=0; - rb_eval_string_protect("require './hack/ruby/ruby'", &state); + rb_eval_string_protect("require '" DFHACK_RUBY_DESTINATION "/ruby'", &state); if (state) dump_rb_error(); diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb deleted file mode 100644 index 663f8511..00000000 --- a/plugins/ruby/ruby.rb +++ /dev/null @@ -1,258 +0,0 @@ -# redefine standard i/o methods to use the dfhack console -module Kernel - def puts(*a) - a.flatten.each { |l| - # XXX looks like print_str crashes with strings longer than 4096... maybe not nullterminated ? - # this workaround fixes it - s = l.to_s.chomp + "\n" - while s.length > 0 - DFHack.print_str(s[0, 4000]) - s[0, 4000] = '' - end - } - nil - end - - def puts_err(*a) - a.flatten.each { |l| - s = l.to_s.chomp + "\n" - while s.length > 0 - DFHack.print_err(s[0, 4000]) - s[0, 4000] = '' - end - } - nil - end - - def p(*a) - a.each { |e| - puts_err e.inspect - } - nil - end -end - -module DFHack - VERSION = version - - class OnupdateCallback - attr_accessor :callback, :timelimit, :minyear, :minyeartick, :description - def initialize(descr, cb, tl, initdelay=0) - @description = descr - @callback = cb - @ticklimit = tl - @minyear = (tl ? df.cur_year : 0) - @minyeartick = (tl ? df.cur_year_tick+initdelay : 0) - end - - # run callback if timedout - def check_run(year, yeartick, yearlen) - if @ticklimit - return unless year > @minyear or (year == @minyear and yeartick >= @minyeartick) - @minyear = year - @minyeartick = yeartick + @ticklimit - if @minyeartick > yearlen - @minyear += 1 - @minyeartick -= yearlen - end - end - # t0 = Time.now - @callback.call - # dt = Time.now - t0 ; puts "rb cb #@description took #{'%.02f' % dt}s" if dt > 0.1 - rescue Exception - df.onupdate_unregister self - puts_err "onupdate #@description unregistered: #$!", $!.backtrace - 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('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 ||= [] - @onupdate_list << OnupdateCallback.new(descr, b, ticklimit, initialtickdelay) - DFHack.onupdate_active = true - if onext = @onupdate_list.min - 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 or the description - def onupdate_unregister(b) - b = @onupdate_list.find { |bb| bb.description == b } if b.kind_of?(String) - @onupdate_list.delete b - if @onupdate_list.empty? - DFHack.onupdate_active = false - DFHack.onupdate_minyear = DFHack.onupdate_minyeartick = DFHack.onupdate_minyeartickadv = -1 - end - end - - # same as onupdate_register, but remove the callback once it returns true - def onupdate_register_once(*a) - handle = onupdate_register(*a) { - onupdate_unregister(handle) if yield - } - end - - TICKS_PER_YEAR = 1200*28*12 - # this method is called by ruby.cpp if df.onupdate_active is true - def onupdate - @onupdate_list ||= [] - - y = yt = 0 - y = cur_year rescue 0 - 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 rescue 0 - end - - @onupdate_list.each { |o| - o.check_run(y, yt, ytmax) - } - - if onext = @onupdate_list.min - DFHack.onupdate_minyear = onext.minyear - 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 - - # register a callback to be called every gframe or more - # ex: DFHack.onstatechange_register { |newstate| puts "state changed to #{newstate}" } - def onstatechange_register(&b) - @onstatechange_list ||= [] - @onstatechange_list << b - @onstatechange_list.last - end - - # delete the callback for onstatechange ; use the value returned by onstatechange_register - def onstatechange_unregister(b) - @onstatechange_list.delete b - end - - # same as onstatechange_register, but auto-unregisters if the block returns true - def onstatechange_register_once - handle = onstatechange_register { |st| - onstatechange_unregister(handle) if yield(st) - } - end - - - # this method is called by dfhack every 'onstatechange' - def onstatechange(newstate) - @onstatechange_list ||= [] - @onstatechange_list.each { |cb| cb.call(newstate) } - end - - # return true if the argument is under the cursor - def at_cursor?(obj) - same_pos?(obj, cursor) - end - - # returns true if both arguments are at the same x/y/z - def same_pos?(pos1, pos2) - pos1 = pos1.pos if pos1.respond_to?(:pos) - pos2 = pos2.pos if pos2.respond_to?(:pos) - pos1.x == pos2.x and pos1.y == pos2.y and pos1.z == pos2.z - end - - # try to match a user-specified name to one from the raws - # uses case-switching and substring matching - # eg match_rawname('coal', ['COAL_BITUMINOUS', 'BAUXITE']) => 'COAL_BITUMINOUS' - def match_rawname(name, rawlist) - rawlist.each { |r| return r if name == r } - rawlist.each { |r| return r if name.downcase == r.downcase } - may = rawlist.find_all { |r| r.downcase.index(name.downcase) } - may.first if may.length == 1 - end - - def translate_name(name, english=true, onlylastpart=false) - out = [] - - if not onlylastpart - out << name.first_name if name.first_name != '' - if name.nickname != '' - case respond_to?(:d_init) && d_init.nickname[gametype] - when :REPLACE_ALL; return "`#{name.nickname}'" - when :REPLACE_FIRST; out.pop - end - out << "`#{name.nickname}'" - end - end - return out.join(' ') unless name.words.find { |w| w >= 0 } - - if not english - tsl = world.raws.language.translations[name.language] - if name.words[0] >= 0 or name.words[1] >= 0 - out << '' - out.last << tsl.words[name.words[0]] if name.words[0] >= 0 - out.last << tsl.words[name.words[1]] if name.words[1] >= 0 - end - if name.words[5] >= 0 - out << '' - (2..5).each { |i| out.last << tsl.words[name.words[i]] if name.words[i] >= 0 } - end - if name.words[6] >= 0 - out << tsl.words[name.words[6]] - end - else - wl = world.raws.language - if name.words[0] >= 0 or name.words[1] >= 0 - out << '' - out.last << wl.words[name.words[0]].forms[name.parts_of_speech[0]] if name.words[0] >= 0 - out.last << wl.words[name.words[1]].forms[name.parts_of_speech[1]] if name.words[1] >= 0 - end - if name.words[5] >= 0 - out << 'the' - out.last.capitalize! if out.length == 1 - out << wl.words[name.words[2]].forms[name.parts_of_speech[2]] if name.words[2] >= 0 - out << wl.words[name.words[3]].forms[name.parts_of_speech[3]] if name.words[3] >= 0 - if name.words[4] >= 0 - out << wl.words[name.words[4]].forms[name.parts_of_speech[4]] - out.last << '-' - else - out << '' - end - out.last << wl.words[name.words[5]].forms[name.parts_of_speech[5]] - end - if name.words[6] >= 0 - out << 'of' - out.last.capitalize! if out.length == 1 - out << wl.words[name.words[6]].forms[name.parts_of_speech[6]] - end - end - - out.join(' ') - end - end -end - -# global alias so we can write 'df.world.units.all[0]' -def df - DFHack -end - -# load autogenned file -require './hack/ruby/ruby-autogen-defs' -require(RUBY_PLATFORM =~ /mswin|mingw|cygwin/i ? './hack/ruby/ruby-autogen-win' : './hack/ruby/ruby-autogen-gcc') - -# load all modules -Dir['./hack/ruby/*.rb'].each { |m| require m.chomp('.rb') if m !~ /ruby-autogen/ } diff --git a/plugins/ruby/ruby.rb.in b/plugins/ruby/ruby.rb.in new file mode 100644 index 00000000..75c385ca --- /dev/null +++ b/plugins/ruby/ruby.rb.in @@ -0,0 +1,258 @@ +# redefine standard i/o methods to use the dfhack console +module Kernel + def puts(*a) + a.flatten.each { |l| + # XXX looks like print_str crashes with strings longer than 4096... maybe not nullterminated ? + # this workaround fixes it + s = l.to_s.chomp + "\n" + while s.length > 0 + DFHack.print_str(s[0, 4000]) + s[0, 4000] = '' + end + } + nil + end + + def puts_err(*a) + a.flatten.each { |l| + s = l.to_s.chomp + "\n" + while s.length > 0 + DFHack.print_err(s[0, 4000]) + s[0, 4000] = '' + end + } + nil + end + + def p(*a) + a.each { |e| + puts_err e.inspect + } + nil + end +end + +module DFHack + VERSION = version + + class OnupdateCallback + attr_accessor :callback, :timelimit, :minyear, :minyeartick, :description + def initialize(descr, cb, tl, initdelay=0) + @description = descr + @callback = cb + @ticklimit = tl + @minyear = (tl ? df.cur_year : 0) + @minyeartick = (tl ? df.cur_year_tick+initdelay : 0) + end + + # run callback if timedout + def check_run(year, yeartick, yearlen) + if @ticklimit + return unless year > @minyear or (year == @minyear and yeartick >= @minyeartick) + @minyear = year + @minyeartick = yeartick + @ticklimit + if @minyeartick > yearlen + @minyear += 1 + @minyeartick -= yearlen + end + end + # t0 = Time.now + @callback.call + # dt = Time.now - t0 ; puts "rb cb #@description took #{'%.02f' % dt}s" if dt > 0.1 + rescue Exception + df.onupdate_unregister self + puts_err "onupdate #@description unregistered: #$!", $!.backtrace + 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('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 ||= [] + @onupdate_list << OnupdateCallback.new(descr, b, ticklimit, initialtickdelay) + DFHack.onupdate_active = true + if onext = @onupdate_list.min + 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 or the description + def onupdate_unregister(b) + b = @onupdate_list.find { |bb| bb.description == b } if b.kind_of?(String) + @onupdate_list.delete b + if @onupdate_list.empty? + DFHack.onupdate_active = false + DFHack.onupdate_minyear = DFHack.onupdate_minyeartick = DFHack.onupdate_minyeartickadv = -1 + end + end + + # same as onupdate_register, but remove the callback once it returns true + def onupdate_register_once(*a) + handle = onupdate_register(*a) { + onupdate_unregister(handle) if yield + } + end + + TICKS_PER_YEAR = 1200*28*12 + # this method is called by ruby.cpp if df.onupdate_active is true + def onupdate + @onupdate_list ||= [] + + y = yt = 0 + y = cur_year rescue 0 + 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 rescue 0 + end + + @onupdate_list.each { |o| + o.check_run(y, yt, ytmax) + } + + if onext = @onupdate_list.min + DFHack.onupdate_minyear = onext.minyear + 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 + + # register a callback to be called every gframe or more + # ex: DFHack.onstatechange_register { |newstate| puts "state changed to #{newstate}" } + def onstatechange_register(&b) + @onstatechange_list ||= [] + @onstatechange_list << b + @onstatechange_list.last + end + + # delete the callback for onstatechange ; use the value returned by onstatechange_register + def onstatechange_unregister(b) + @onstatechange_list.delete b + end + + # same as onstatechange_register, but auto-unregisters if the block returns true + def onstatechange_register_once + handle = onstatechange_register { |st| + onstatechange_unregister(handle) if yield(st) + } + end + + + # this method is called by dfhack every 'onstatechange' + def onstatechange(newstate) + @onstatechange_list ||= [] + @onstatechange_list.each { |cb| cb.call(newstate) } + end + + # return true if the argument is under the cursor + def at_cursor?(obj) + same_pos?(obj, cursor) + end + + # returns true if both arguments are at the same x/y/z + def same_pos?(pos1, pos2) + pos1 = pos1.pos if pos1.respond_to?(:pos) + pos2 = pos2.pos if pos2.respond_to?(:pos) + pos1.x == pos2.x and pos1.y == pos2.y and pos1.z == pos2.z + end + + # try to match a user-specified name to one from the raws + # uses case-switching and substring matching + # eg match_rawname('coal', ['COAL_BITUMINOUS', 'BAUXITE']) => 'COAL_BITUMINOUS' + def match_rawname(name, rawlist) + rawlist.each { |r| return r if name == r } + rawlist.each { |r| return r if name.downcase == r.downcase } + may = rawlist.find_all { |r| r.downcase.index(name.downcase) } + may.first if may.length == 1 + end + + def translate_name(name, english=true, onlylastpart=false) + out = [] + + if not onlylastpart + out << name.first_name if name.first_name != '' + if name.nickname != '' + case respond_to?(:d_init) && d_init.nickname[gametype] + when :REPLACE_ALL; return "`#{name.nickname}'" + when :REPLACE_FIRST; out.pop + end + out << "`#{name.nickname}'" + end + end + return out.join(' ') unless name.words.find { |w| w >= 0 } + + if not english + tsl = world.raws.language.translations[name.language] + if name.words[0] >= 0 or name.words[1] >= 0 + out << '' + out.last << tsl.words[name.words[0]] if name.words[0] >= 0 + out.last << tsl.words[name.words[1]] if name.words[1] >= 0 + end + if name.words[5] >= 0 + out << '' + (2..5).each { |i| out.last << tsl.words[name.words[i]] if name.words[i] >= 0 } + end + if name.words[6] >= 0 + out << tsl.words[name.words[6]] + end + else + wl = world.raws.language + if name.words[0] >= 0 or name.words[1] >= 0 + out << '' + out.last << wl.words[name.words[0]].forms[name.parts_of_speech[0]] if name.words[0] >= 0 + out.last << wl.words[name.words[1]].forms[name.parts_of_speech[1]] if name.words[1] >= 0 + end + if name.words[5] >= 0 + out << 'the' + out.last.capitalize! if out.length == 1 + out << wl.words[name.words[2]].forms[name.parts_of_speech[2]] if name.words[2] >= 0 + out << wl.words[name.words[3]].forms[name.parts_of_speech[3]] if name.words[3] >= 0 + if name.words[4] >= 0 + out << wl.words[name.words[4]].forms[name.parts_of_speech[4]] + out.last << '-' + else + out << '' + end + out.last << wl.words[name.words[5]].forms[name.parts_of_speech[5]] + end + if name.words[6] >= 0 + out << 'of' + out.last.capitalize! if out.length == 1 + out << wl.words[name.words[6]].forms[name.parts_of_speech[6]] + end + end + + out.join(' ') + end + end +end + +# global alias so we can write 'df.world.units.all[0]' +def df + DFHack +end + +# load autogenned file +require '@DFHACK_RUBY_DESTINATION@/ruby-autogen-defs' +require(RUBY_PLATFORM =~ /mswin|mingw|cygwin/i ? '@DFHACK_RUBY_DESTINATION@/ruby-autogen-win' : '@DFHACK_RUBY_DESTINATION@/ruby-autogen-gcc') + +# load all modules +Dir['@DFHACK_RUBY_DESTINATION@/*.rb'].each { |m| require m.chomp('.rb') if m !~ /ruby-autogen/ }