dfhack/plugins/ruby
jj dfe86f9bd7 ruby: interpret types in vmethod signatures 2012-05-11 01:34:03 +02:00
..
plugins ruby: tweak enums 2012-05-10 23:41:50 +02:00
CMakeLists.txt ruby: clean helper with offsetof 2012-04-21 21:20:51 +02:00
README ruby: interpret types in vmethod signatures 2012-05-11 01:34:03 +02:00
codegen.pl ruby: interpret types in vmethod signatures 2012-05-11 01:34:03 +02:00
ruby-memstruct.rb ruby: tweak enums 2012-05-10 23:41:50 +02:00
ruby.cpp ruby: vmethod call 2012-05-03 18:49:12 +02:00
ruby.rb ruby: tweak enums 2012-05-10 23:41:50 +02:00

README

This plugins embeds a ruby interpreter inside DFHack (ie inside Dwarf Fortress).

The plugin maps all the structures available in library/xml/ to ruby objects.

These objects are described in ruby-autogen.rb, they are all in the DFHack::
module. The toplevel 'df' method returs the DFHack module.

The plugin does *not* map most of dfhack methods (MapCache, ...) ; only direct
access to the raw DF data structures in memory is provided.

Some library methods are stored in the ruby.rb file, with shortcuts to read a
map block, find an unit or an item, etc.

Global objects are stored in the GlobalObjects class ; each object accessor is
mirrored as a DFHack module method.

The ruby plugin defines 2 dfhack console commands:
 rb_load <filename> ; load a ruby script. Ex: rb_load hack/plants.rb (no quotes)
 rb_eval <ruby expression> ; evaluate a ruby expression, show the result in the
console. Ex: rb_eval df.find_unit.name.first_name
You can use single-quotes for strings ; avoid double-quotes that are parsed
and removed by the dfhack console.

The plugin also interfaces with dfhack 'onupdate' hook.
To register ruby code to be run every graphic frame, use:
 handle = df.onupdate_register { puts 'i love flood' }
To stop being called, use:
 df.onupdate_unregister handle


Exemples
--------

For more complex exemples, check the ruby/plugins/ folder.

Show info on the currently selected unit ('v' or 'k' DF menu)
 p df.find_unit.flags1

Set a custom nickname to unit with id '123'
 df.find_unit(123).name.nickname = 'moo'

Show current unit profession
 p df.find_unit.profession

Center the screen on unit '123'
 df.center_viewscreen(df.find_unit(123))

Find an item at a given position, show its C++ classname
 df.find_item(df.cursor)._rtti_classname

Find the raws name of the plant under cursor
 plant = df.world.plants.all.find { |p| df.at_cursor?(p) }
 df.world.raws.plants.all[plant.mat_index].id

Dig a channel under the cursor
 df.map_designation_at(df.cursor).dig = TileDigDesignation::Channel
 df.map_block_at(df.cursor).flags.designated = true


Compilation
-----------

The plugin consists of the ruby.rb file including user comfort functions ;
ruby-memstruct.rb describing basic classes used by the autogenerated code, and
embedded at the beginnig of ruby-autogen.rb, and the generated code.

The generated code is generated by codegen.pl, which takes the codegen.out.xml
file as input.

One of the limitations of the xml file is that it does not include structure
offsets, as they depend on the compiler. To overcome that, codegen runs in two
passes. The first pass generates a ruby-autogen.cpp file, that will output the
structure offsets ; the second pass will generate the ruby-autogen.rb using the
output of the compiled ruby-autogen.cpp.

For exemple, from
 <ld:global-type ld:meta="struct-type" type-name="unit">
   <ld:field type-name="language_name" name="name" ld:meta="global"/>
   <ld:field name="custom_profession" ld:meta="primitive" ld:subtype="stl-string"/>
   <ld:field ld:subtype="enum" base-type="int16_t" name="profession" type-name="profession" ld:meta="global"/>

We generate the cpp
 printf("%s = %d", "offsetof(df::unit, language_name)", offsetof(df::unit, language_name));
 printf("%s = %d", "offsetof(df::unit, custom_profession)", offsetof(df::unit, custom_profession));
 printf("%s = %d", "offsetof(df::unit, profession)", offsetof(df::unit, profession));

Which generates (on linux)
 offsetof(df::unit, name) = 0
 offsetof(df::unit, custom_profession) = 60
 offsetof(df::unit, profession) = 64

Which generates
 class Unit < MemHack::Compound
  field(:name, 0) { global :LanguageName }
  field(:custom_profession, 60) { stl_string }
  field(:profession, 64) { number 16, true }

The field method has 2 arguments: the name of the method and the member offset ;
the block specifies the member type. See ruby-memstruct.rb for more information.
Primitive type access is done through native methods in ruby.cpp (vector length,
raw memory access, etc)

MemHack::Pointers are automatically dereferenced ; so a vector of pointer to
Units will yield Units directly. Null pointers yield the 'nil' value.

This allows to use code such as 'df.world.units.all[0].pos', with 'all' being
really a vector of pointer.


Todo
----

Correct c++ object (de)allocation (call ctor etc) ; ability to call vtable methods