ruby: codegen code cleanup, move ruby-memstruct in ruby.rb, handle bool struct fields, rename Enum.to_sym/to_i to sym()/int(), define nested compound sizeof()

develop
jj 2012-05-29 17:28:51 +02:00
parent bcb698a5b4
commit 3e61452f15
7 changed files with 1108 additions and 960 deletions

@ -2,7 +2,7 @@ find_package(Ruby)
if(RUBY_FOUND) if(RUBY_FOUND)
ADD_CUSTOM_COMMAND( ADD_CUSTOM_COMMAND(
OUTPUT ruby-autogen.rb OUTPUT ruby-autogen.rb
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb ${CMAKE_CURRENT_SOURCE_DIR}/ruby-memstruct.rb COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb
DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml codegen.pl DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml codegen.pl
) )
ADD_CUSTOM_TARGET(ruby-autogen-rb ALL DEPENDS ruby-autogen.rb) ADD_CUSTOM_TARGET(ruby-autogen-rb ALL DEPENDS ruby-autogen.rb)

@ -3,7 +3,7 @@ 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. 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:: These objects are described in ruby-autogen.rb, they are all in the DFHack::
module. The toplevel 'df' method returs the DFHack module. module. The toplevel 'df' method is a shortcut to the DFHack module.
The plugin does *not* map most of dfhack methods (MapCache, ...) ; only direct The plugin does *not* map most of dfhack methods (MapCache, ...) ; only direct
access to the raw DF data structures in memory is provided. access to the raw DF data structures in memory is provided.
@ -12,7 +12,7 @@ Some library methods are stored in the ruby.rb file, with shortcuts to read a
map block, find an unit or an item, etc. map block, find an unit or an item, etc.
Global objects are stored in the GlobalObjects class ; each object accessor is Global objects are stored in the GlobalObjects class ; each object accessor is
mirrored as a DFHack module method. mirrored as a DFHack module method (eg df.world).
The ruby plugin defines 2 dfhack console commands: The ruby plugin defines 2 dfhack console commands:
rb_load <filename> ; load a ruby script. Ex: rb_load hack/plants.rb (no quotes) rb_load <filename> ; load a ruby script. Ex: rb_load hack/plants.rb (no quotes)
@ -23,10 +23,12 @@ and removed by the dfhack console.
The plugin also interfaces with dfhack 'onupdate' hook. The plugin also interfaces with dfhack 'onupdate' hook.
To register ruby code to be run every graphic frame, use: To register ruby code to be run every graphic frame, use:
handle = df.onupdate_register { puts 'i love flood' } handle = df.onupdate_register { puts 'i love flooding the console' }
To stop being called, use: To stop being called, use:
df.onupdate_unregister handle df.onupdate_unregister handle
The same mechanism is available for onstatechange.
Exemples Exemples
-------- --------
@ -42,6 +44,9 @@ Set a custom nickname to unit with id '123'
Show current unit profession Show current unit profession
p df.find_unit.profession p df.find_unit.profession
Change current unit profession
df.find_unit.profession = :MASON
Center the screen on unit '123' Center the screen on unit '123'
df.center_viewscreen(df.find_unit(123)) df.center_viewscreen(df.find_unit(123))
@ -49,54 +54,42 @@ Find an item at a given position, show its C++ classname
df.find_item(df.cursor)._rtti_classname df.find_item(df.cursor)._rtti_classname
Find the raws name of the plant under cursor Find the raws name of the plant under cursor
plant = df.world.plants.all.find { |p| df.at_cursor?(p) } plant = df.world.plants.all.find { |plt| df.at_cursor?(plt) }
df.world.raws.plants.all[plant.mat_index].id p df.world.raws.plants.all[plant.mat_index].id
Dig a channel under the cursor Dig a channel under the cursor
df.map_designation_at(df.cursor).dig = TileDigDesignation::Channel df.map_designation_at(df.cursor).dig = :Channel
df.map_block_at(df.cursor).flags.designated = true df.map_block_at(df.cursor).flags.designated = true
Compilation Compilation
----------- -----------
The plugin consists of the ruby.rb file including user comfort functions ; The plugin consists of the ruby.rb file including user comfort functions and
ruby-memstruct.rb describing basic classes used by the autogenerated code, and describing basic classes used by the autogenerated code, and ruby-autogen.rb,
embedded at the beginnig of ruby-autogen.rb, and the generated code. the auto-generated code.
The generated code is generated by codegen.pl, which takes the codegen.out.xml The generated code is generated by codegen.pl, which takes the codegen.out.xml
file as input. file as input.
One of the limitations of the xml file is that it does not include structure For exemple,
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:global-type ld:meta="struct-type" type-name="unit">
<ld:field type-name="language_name" name="name" ld:meta="global"/> <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 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"/> <ld:field ld:subtype="enum" base-type="int16_t" name="profession" type-name="profession" ld:meta="global"/>
We generate the cpp Will generate
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 class Unit < MemHack::Compound
field(:name, 0) { global :LanguageName } field(:name, 0) { global :LanguageName }
field(:custom_profession, 60) { stl_string } field(:custom_profession, 60) { stl_string }
field(:profession, 64) { number 16, true } field(:profession, 64) { number 16, true }
The field method has 2 arguments: the name of the method and the member offset ; The syntax for the 'field' method is:
the block specifies the member type. See ruby-memstruct.rb for more information. 1st argument = name of the method
2nd argument = offset of this field from the beginning of the struct.
The block argument describes the type of the field: uint32, ptr to global...
Primitive type access is done through native methods in ruby.cpp (vector length, Primitive type access is done through native methods in ruby.cpp (vector length,
raw memory access, etc) raw memory access, etc)
@ -104,10 +97,4 @@ MemHack::Pointers are automatically dereferenced ; so a vector of pointer to
Units will yield Units directly. Null pointers yield the 'nil' value. 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 This allows to use code such as 'df.world.units.all[0].pos', with 'all' being
really a vector of pointer. in fact a vector of *pointers* to DFHack::Unit objects.
Todo
----
Correct c++ object (de)allocation (call ctor etc) ; ability to call vtable methods

@ -31,6 +31,7 @@ sub rb_ucase {
return join("", map { ucfirst $_ } (split('_', $name))); return join("", map { ucfirst $_ } (split('_', $name)));
} }
my %item_renderer = ( my %item_renderer = (
'global' => \&render_item_global, 'global' => \&render_item_global,
'number' => \&render_item_number, 'number' => \&render_item_number,
@ -42,6 +43,7 @@ my %item_renderer = (
'bytes' => \&render_item_bytes, 'bytes' => \&render_item_bytes,
); );
my %global_types; my %global_types;
our $current_typename; our $current_typename;
@ -55,62 +57,88 @@ sub render_global_enum {
}; };
push @lines_rb, "end\n"; push @lines_rb, "end\n";
} }
sub render_enum_fields { sub render_enum_fields {
my ($type) = @_; my ($type) = @_;
my $value = -1;
push @lines_rb, "ENUM = Hash.new"; push @lines_rb, "ENUM = Hash.new";
push @lines_rb, "NUME = Hash.new"; push @lines_rb, "NUME = Hash.new";
my %attr_type; my %attr_type;
my %attr_list; my %attr_list;
for my $attr ($type->findnodes('child::enum-attr')) { render_enum_initattrs($type, \%attr_type, \%attr_list);
my $value = -1;
for my $item ($type->findnodes('child::enum-item'))
{
$value = $item->getAttribute('value') || ($value+1);
my $elemname = $item->getAttribute('name'); # || "unk_$value";
if ($elemname)
{
my $rbelemname = rb_ucase($elemname);
push @lines_rb, "ENUM[$value] = :$rbelemname ; NUME[:$rbelemname] = $value";
for my $iattr ($item->findnodes('child::item-attr'))
{
my $attr = render_enum_attr($rbelemname, $iattr, \%attr_type, \%attr_list);
$lines_rb[$#lines_rb] .= ' ; ' . $attr;
}
}
}
}
sub render_enum_initattrs {
my ($type, $attr_type, $attr_list) = @_;
for my $attr ($type->findnodes('child::enum-attr'))
{
my $rbattr = rb_ucase($attr->getAttribute('name')); my $rbattr = rb_ucase($attr->getAttribute('name'));
my $typeattr = $attr->getAttribute('type-name'); my $typeattr = $attr->getAttribute('type-name');
# find how we need to encode the attribute values: string, symbol (for enums), raw (number, bool) # find how we need to encode the attribute values: string, symbol (for enums), raw (number, bool)
if ($typeattr) { if ($typeattr) {
if ($global_types{$typeattr}) { if ($global_types{$typeattr}) {
$attr_type{$rbattr} = 'symbol'; $attr_type->{$rbattr} = 'symbol';
} else { } else {
$attr_type{$rbattr} = 'naked'; $attr_type->{$rbattr} = 'naked';
} }
} else { } else {
$attr_type{$rbattr} = 'quote'; $attr_type->{$rbattr} = 'quote';
} }
my $def = $attr->getAttribute('default-value'); my $def = $attr->getAttribute('default-value');
if ($attr->getAttribute('is-list')) { if ($attr->getAttribute('is-list'))
{
push @lines_rb, "$rbattr = Hash.new { |h, k| h[k] = [] }"; push @lines_rb, "$rbattr = Hash.new { |h, k| h[k] = [] }";
$attr_list{$rbattr} = 1; $attr_list->{$rbattr} = 1;
} elsif ($def) { }
$def = ":$def" if ($attr_type{$rbattr} eq 'symbol'); elsif ($def)
$def =~ s/'/\\'/g if ($attr_type{$rbattr} eq 'quote'); {
$def = "'$def'" if ($attr_type{$rbattr} eq 'quote'); $def = ":$def" if ($attr_type->{$rbattr} eq 'symbol');
$def =~ s/'/\\'/g if ($attr_type->{$rbattr} eq 'quote');
$def = "'$def'" if ($attr_type->{$rbattr} eq 'quote');
push @lines_rb, "$rbattr = Hash.new($def)"; push @lines_rb, "$rbattr = Hash.new($def)";
} else { }
else
{
push @lines_rb, "$rbattr = Hash.new"; push @lines_rb, "$rbattr = Hash.new";
} }
} }
}
for my $item ($type->findnodes('child::enum-item')) { sub render_enum_attr {
$value = $item->getAttribute('value') || ($value+1); my ($rbelemname, $iattr, $attr_type, $attr_list) = @_;
my $elemname = $item->getAttribute('name'); # || "unk_$value";
if ($elemname) {
my $rbelemname = rb_ucase($elemname);
push @lines_rb, "ENUM[$value] = :$rbelemname ; NUME[:$rbelemname] = $value";
for my $iattr ($item->findnodes('child::item-attr')) {
my $ian = $iattr->getAttribute('name'); my $ian = $iattr->getAttribute('name');
my $iav = $iattr->getAttribute('value'); my $iav = $iattr->getAttribute('value');
my $rbattr = rb_ucase($ian); my $rbattr = rb_ucase($ian);
my $op = ($attr_list{$rbattr} ? '<<' : '=');
$iav = ":$iav" if ($attr_type{$rbattr} eq 'symbol'); my $op = ($attr_list->{$rbattr} ? '<<' : '=');
$iav =~ s/'/\\'/g if ($attr_type{$rbattr} eq 'quote');
$iav = "'$iav'" if ($attr_type{$rbattr} eq 'quote'); $iav = ":$iav" if ($attr_type->{$rbattr} eq 'symbol');
$lines_rb[$#lines_rb] .= " ; ${rbattr}[:$rbelemname] $op $iav"; $iav =~ s/'/\\'/g if ($attr_type->{$rbattr} eq 'quote');
} $iav = "'$iav'" if ($attr_type->{$rbattr} eq 'quote');
}
} return "${rbattr}[:$rbelemname] $op $iav";
} }
@ -124,6 +152,7 @@ sub render_global_bitfield {
}; };
push @lines_rb, "end\n"; push @lines_rb, "end\n";
} }
sub render_bitfield_fields { sub render_bitfield_fields {
my ($type) = @_; my ($type) = @_;
@ -134,20 +163,26 @@ sub render_bitfield_fields {
push @lines_rb, "}"; push @lines_rb, "}";
my $shift = 0; my $shift = 0;
for my $field ($type->findnodes('child::ld:field')) { for my $field ($type->findnodes('child::ld:field'))
{
my $count = $field->getAttribute('count') || 1; my $count = $field->getAttribute('count') || 1;
my $name = $field->getAttribute('name'); my $name = $field->getAttribute('name');
my $type = $field->getAttribute('type-name'); my $type = $field->getAttribute('type-name');
my $enum = rb_ucase($type) if ($type and $global_types{$type}); my $enum = rb_ucase($type) if ($type and $global_types{$type});
$name = $field->getAttribute('ld:anon-name') if (!$name); $name = $field->getAttribute('ld:anon-name') if (!$name);
print "bitfield $name !number\n" if (!($field->getAttribute('ld:meta') eq 'number')); print "bitfield $name !number\n" if (!($field->getAttribute('ld:meta') eq 'number'));
if ($name)
{
if ($count == 1) { if ($count == 1) {
push @lines_rb, "field(:$name, 0) { bit $shift }" if ($name); push @lines_rb, "field(:$name, 0) { bit $shift }";
} elsif ($enum) { } elsif ($enum) {
push @lines_rb, "field(:$name, 0) { bits $shift, $count, $enum }" if ($name); push @lines_rb, "field(:$name, 0) { bits $shift, $count, $enum }";
} else { } else {
push @lines_rb, "field(:$name, 0) { bits $shift, $count }" if ($name); push @lines_rb, "field(:$name, 0) { bits $shift, $count }";
}
} }
$shift += $count; $shift += $count;
} }
} }
@ -155,9 +190,12 @@ sub render_bitfield_fields {
my %seen_class; my %seen_class;
our $compound_off; our $compound_off;
our $compound_pointer;
sub render_global_class { sub render_global_class {
my ($name, $type) = @_; my ($name, $type) = @_;
my $meta = $type->getAttribute('ld:meta');
my $rbname = rb_ucase($name); my $rbname = rb_ucase($name);
# ensure pre-definition of ancestors # ensure pre-definition of ancestors
@ -168,46 +206,55 @@ sub render_global_class {
$seen_class{$name}++; $seen_class{$name}++;
local $compound_off = 0; local $compound_off = 0;
$compound_off = 4 if ($meta eq 'class-type');
$compound_off = sizeof($global_types{$parent}) if $parent;
local $current_typename = $rbname;
my $rtti_name; my $rtti_name;
my $meta = $type->getAttribute('ld:meta'); if ($meta eq 'class-type')
if ($meta eq 'class-type') { {
$compound_off = 4;
$rtti_name = $type->getAttribute('original-name') || $rtti_name = $type->getAttribute('original-name') ||
$type->getAttribute('type-name') || $type->getAttribute('type-name') ||
$name; $name;
} }
my $rbparent = ($parent ? rb_ucase($parent) : 'MemHack::Compound'); my $rbparent = ($parent ? rb_ucase($parent) : 'MemHack::Compound');
$compound_off = sizeof($global_types{$parent}) if $parent;
local $current_typename = $rbname;
push @lines_rb, "class $rbname < $rbparent"; push @lines_rb, "class $rbname < $rbparent";
indent_rb { indent_rb {
my $sz = sizeof($type); my $sz = sizeof($type);
# see comment is sub sizeof ; but gcc has sizeof(cls) aligned # see comment is sub sizeof ; but gcc has sizeof(cls) aligned
$sz = align_field($sz, 4) if $os eq 'linux' and $meta eq 'class-type'; $sz = align_field($sz, 4) if $os eq 'linux' and $meta eq 'class-type';
push @lines_rb, "sizeof $sz"; push @lines_rb, "sizeof $sz\n";
push @lines_rb, "rtti_classname :$rtti_name" if $rtti_name;
push @lines_rb, "rtti_classname :$rtti_name\n" if $rtti_name;
render_struct_fields($type); render_struct_fields($type);
my $vms = $type->findnodes('child::virtual-methods')->[0]; my $vms = $type->findnodes('child::virtual-methods')->[0];
render_class_vmethods($vms) if $vms; render_class_vmethods($vms) if $vms;
}; };
push @lines_rb, "end\n"; push @lines_rb, "end\n";
} }
sub render_struct_fields { sub render_struct_fields {
my ($type) = @_; my ($type) = @_;
my $isunion = $type->getAttribute('is-union'); my $isunion = $type->getAttribute('is-union');
for my $field ($type->findnodes('child::ld:field')) {
for my $field ($type->findnodes('child::ld:field'))
{
my $name = $field->getAttribute('name'); my $name = $field->getAttribute('name');
$name = $field->getAttribute('ld:anon-name') if (!$name); $name = $field->getAttribute('ld:anon-name') if (!$name);
if (!$name and $field->getAttribute('ld:anon-compound')) {
if (!$name and $field->getAttribute('ld:anon-compound'))
{
render_struct_fields($field); render_struct_fields($field);
} else { }
else
{
$compound_off = align_field($compound_off, get_field_align($field)); $compound_off = align_field($compound_off, get_field_align($field));
if ($name) { if ($name)
{
push @lines_rb, "field(:$name, $compound_off) {"; push @lines_rb, "field(:$name, $compound_off) {";
indent_rb { indent_rb {
render_item($field); render_item($field);
@ -215,27 +262,39 @@ sub render_struct_fields {
push @lines_rb, "}"; push @lines_rb, "}";
} }
} }
$compound_off += sizeof($field) if (!$isunion); $compound_off += sizeof($field) if (!$isunion);
} }
} }
sub render_class_vmethods { sub render_class_vmethods {
my ($vms) = @_; my ($vms) = @_;
my $voff = 0; my $voff = 0;
for my $meth ($vms->findnodes('child::vmethod')) {
for my $meth ($vms->findnodes('child::vmethod'))
{
my $name = $meth->getAttribute('name'); my $name = $meth->getAttribute('name');
if ($name) {
if ($name)
{
my @argnames; my @argnames;
my @argargs; my @argargs;
for my $arg ($meth->findnodes('child::ld:field')) {
# check if arguments need special treatment (eg auto-convert from symbol to enum value)
for my $arg ($meth->findnodes('child::ld:field'))
{
my $nr = $#argnames + 1; my $nr = $#argnames + 1;
my $argname = lcfirst($arg->getAttribute('name') || "arg$nr"); my $argname = lcfirst($arg->getAttribute('name') || "arg$nr");
push @argnames, $argname; push @argnames, $argname;
if ($arg->getAttribute('ld:meta') eq 'global' and $arg->getAttribute('ld:subtype') eq 'enum') { if ($arg->getAttribute('ld:meta') eq 'global' and $arg->getAttribute('ld:subtype') eq 'enum') {
push @argargs, rb_ucase($arg->getAttribute('type-name')) . ".to_i($argname)"; push @argargs, rb_ucase($arg->getAttribute('type-name')) . ".int($argname)";
} else { } else {
push @argargs, $argname; push @argargs, $argname;
} }
} }
# write vmethod ruby wrapper
push @lines_rb, "def $name(" . join(', ', @argnames) . ')'; push @lines_rb, "def $name(" . join(', ', @argnames) . ')';
indent_rb { indent_rb {
my $args = join('', map { ", $_" } @argargs); my $args = join('', map { ", $_" } @argargs);
@ -245,47 +304,74 @@ sub render_class_vmethods {
}; };
push @lines_rb, 'end'; push @lines_rb, 'end';
} }
# on linux, the destructor uses 2 entries # on linux, the destructor uses 2 entries
$voff += 4 if $meth->getAttribute('is-destructor') and $os eq 'linux'; $voff += 4 if $meth->getAttribute('is-destructor') and $os eq 'linux';
$voff += 4; $voff += 4;
} }
} }
sub render_class_vmethod_ret { sub render_class_vmethod_ret {
my ($call, $ret) = @_; my ($call, $ret) = @_;
if (!$ret) {
if (!$ret)
{
# method returns void, hide return value
push @lines_rb, "$call ; nil"; push @lines_rb, "$call ; nil";
return; return;
} }
my $retmeta = $ret->getAttribute('ld:meta') || ''; my $retmeta = $ret->getAttribute('ld:meta') || '';
if ($retmeta eq 'global') { # enum if ($retmeta eq 'global')
{
# method returns an enum value: auto-convert to symbol
my $retname = $ret->getAttribute('type-name'); my $retname = $ret->getAttribute('type-name');
if ($retname and $global_types{$retname} and if ($retname and $global_types{$retname} and
$global_types{$retname}->getAttribute('ld:meta') eq 'enum-type') { $global_types{$retname}->getAttribute('ld:meta') eq 'enum-type')
push @lines_rb, rb_ucase($retname) . ".to_sym($call)"; {
} else { push @lines_rb, rb_ucase($retname) . ".sym($call)";
}
else
{
print "vmethod global nonenum $call\n"; print "vmethod global nonenum $call\n";
push @lines_rb, $call; push @lines_rb, $call;
} }
} elsif ($retmeta eq 'number') {
}
elsif ($retmeta eq 'number')
{
# raw method call returns an int32, mask according to actual return type
my $retsubtype = $ret->getAttribute('ld:subtype'); my $retsubtype = $ret->getAttribute('ld:subtype');
my $retbits = $ret->getAttribute('ld:bits'); my $retbits = $ret->getAttribute('ld:bits');
push @lines_rb, "val = $call"; push @lines_rb, "val = $call";
if ($retsubtype eq 'bool') { if ($retsubtype eq 'bool')
{
push @lines_rb, "(val & 1) != 0"; push @lines_rb, "(val & 1) != 0";
} elsif ($ret->getAttribute('ld:unsigned')) { }
push @lines_rb, "val & ((1 << $retbits) - 1)"; # if $retbits != 32; elsif ($ret->getAttribute('ld:unsigned'))
} else { # signed {
push @lines_rb, "val &= ((1 << $retbits) - 1)"; # if $retbits != 32; push @lines_rb, "val & ((1 << $retbits) - 1)";
}
elsif ($retbits != 32)
{
# manual sign extension
push @lines_rb, "val &= ((1 << $retbits) - 1)";
push @lines_rb, "((val >> ($retbits-1)) & 1) == 0 ? val : val - (1 << $retbits)"; push @lines_rb, "((val >> ($retbits-1)) & 1) == 0 ? val : val - (1 << $retbits)";
} }
} elsif ($retmeta eq 'pointer') {
}
elsif ($retmeta eq 'pointer')
{
# method returns a pointer to some struct, create the correct ruby wrapper
push @lines_rb, "ptr = $call"; push @lines_rb, "ptr = $call";
push @lines_rb, "class << self"; push @lines_rb, "class << self";
indent_rb { indent_rb {
render_item($ret->findnodes('child::ld:item')->[0]); render_item($ret->findnodes('child::ld:item')->[0]);
}; };
push @lines_rb, "end._at(ptr) if ptr != 0"; push @lines_rb, "end._at(ptr) if ptr != 0";
} else { }
else
{
print "vmethod unkret $call\n"; print "vmethod unkret $call\n";
push @lines_rb, $call; push @lines_rb, $call;
} }
@ -295,16 +381,17 @@ sub render_global_objects {
my (@objects) = @_; my (@objects) = @_;
my @global_objects; my @global_objects;
my $sname = 'global_objects';
my $rbname = rb_ucase($sname);
local $compound_off = 0; local $compound_off = 0;
local $current_typename = 'Global'; local $current_typename = 'Global';
push @lines_rb, "class $rbname < MemHack::Compound"; # define all globals as 'fields' of a virtual globalobject wrapping the whole address space
push @lines_rb, 'class GlobalObjects < MemHack::Compound';
indent_rb { indent_rb {
for my $obj (@objects) { for my $obj (@objects)
{
my $oname = $obj->getAttribute('name'); my $oname = $obj->getAttribute('name');
# check if the symbol is defined in xml to avoid NULL deref
my $addr = "DFHack.get_global_address('$oname')"; my $addr = "DFHack.get_global_address('$oname')";
push @lines_rb, "addr = $addr"; push @lines_rb, "addr = $addr";
push @lines_rb, "if addr != 0"; push @lines_rb, "if addr != 0";
@ -323,9 +410,11 @@ sub render_global_objects {
}; };
push @lines_rb, "end"; push @lines_rb, "end";
# define friendlier accessors, eg df.world -> DFHack::GlobalObjects.new._at(0).world
indent_rb { indent_rb {
push @lines_rb, "Global = GlobalObjects.new._at(0)"; push @lines_rb, "Global = GlobalObjects.new._at(0)";
for my $obj (@global_objects) { for my $obj (@global_objects)
{
push @lines_rb, "def self.$obj ; Global.$obj ; end"; push @lines_rb, "def self.$obj ; Global.$obj ; end";
push @lines_rb, "def self.$obj=(v) ; Global.$obj = v ; end"; push @lines_rb, "def self.$obj=(v) ; Global.$obj = v ; end";
} }
@ -333,6 +422,9 @@ sub render_global_objects {
} }
my %align_cache;
my %sizeof_cache;
sub align_field { sub align_field {
my ($off, $fldalign) = @_; my ($off, $fldalign) = @_;
my $dt = $off % $fldalign; my $dt = $off % $fldalign;
@ -340,126 +432,116 @@ sub align_field {
return $off; return $off;
} }
my %align_cache;
my %sizeof_cache;
sub get_field_align { sub get_field_align {
my ($field) = @_; my ($field) = @_;
my $al = 4; my $al = 4;
my $meta = $field->getAttribute('ld:meta'); my $meta = $field->getAttribute('ld:meta');
if ($meta eq 'number') { if ($meta eq 'number') {
$al = $field->getAttribute('ld:bits')/8; $al = $field->getAttribute('ld:bits')/8;
$al = 4 if $al > 4; $al = 4 if $al > 4;
} elsif ($meta eq 'global') { } elsif ($meta eq 'global') {
$al = get_global_align($field);
} elsif ($meta eq 'compound') {
$al = get_compound_align($field);
} elsif ($meta eq 'static-array') {
my $tg = $field->findnodes('child::ld:item')->[0];
$al = get_field_align($tg);
} elsif ($meta eq 'bytes') {
$al = $field->getAttribute('alignment') || 1;
}
return $al;
}
sub get_global_align {
my ($field) = @_;
my $typename = $field->getAttribute('type-name'); my $typename = $field->getAttribute('type-name');
return $align_cache{$typename} if $align_cache{$typename}; return $align_cache{$typename} if $align_cache{$typename};
my $g = $global_types{$typename}; my $g = $global_types{$typename};
my $st = $field->getAttribute('ld:subtype') || ''; my $st = $field->getAttribute('ld:subtype') || '';
if ($st eq 'bitfield' or $st eq 'enum' or $g->getAttribute('ld:meta') eq 'bitfield-type') { if ($st eq 'bitfield' or $st eq 'enum' or $g->getAttribute('ld:meta') eq 'bitfield-type')
{
my $base = $field->getAttribute('base-type') || $g->getAttribute('base-type') || 'uint32_t'; my $base = $field->getAttribute('base-type') || $g->getAttribute('base-type') || 'uint32_t';
print "$st type $base\n" if $base !~ /int(\d+)_t/; print "$st type $base\n" if $base !~ /int(\d+)_t/;
# dont cache, field->base-type may differ # dont cache, field->base-type may differ
return $1/8; return $1/8;
} }
$al = 1;
my $al = 1;
for my $gf ($g->findnodes('child::ld:field')) { for my $gf ($g->findnodes('child::ld:field')) {
my $fal = get_field_align($gf); my $fld_al = get_field_align($gf);
$al = $fal if $fal > $al; $al = $fld_al if $fld_al > $al;
} }
$align_cache{$typename} = $al; $align_cache{$typename} = $al;
} elsif ($meta eq 'compound') {
return $al;
}
sub get_compound_align {
my ($field) = @_;
my $st = $field->getAttribute('ld:subtype') || ''; my $st = $field->getAttribute('ld:subtype') || '';
if ($st eq 'bitfield' or $st eq 'enum') { if ($st eq 'bitfield' or $st eq 'enum')
{
my $base = $field->getAttribute('base-type') || 'uint32_t'; my $base = $field->getAttribute('base-type') || 'uint32_t';
print "$st type $base\n" if $base !~ /int(\d+)_t/; print "$st type $base\n" if $base !~ /int(\d+)_t/;
return $1/8; return $1/8;
} }
$al = 1;
my $al = 1;
for my $f ($field->findnodes('child::ld:field')) { for my $f ($field->findnodes('child::ld:field')) {
my $fal = get_field_align($f); my $fal = get_field_align($f);
$al = $fal if $fal > $al; $al = $fal if $fal > $al;
} }
} elsif ($meta eq 'static-array') {
my $tg = $field->findnodes('child::ld:item')->[0];
$al = get_field_align($tg);
} elsif ($meta eq 'bytes') {
$al = $field->getAttribute('alignment') || 1;
}
return $al; return $al;
} }
sub sizeof { sub sizeof {
my ($field) = @_; my ($field) = @_;
my $meta = $field->getAttribute('ld:meta'); my $meta = $field->getAttribute('ld:meta');
if ($meta eq 'number') { if ($meta eq 'number') {
return $field->getAttribute('ld:bits')/8; return $field->getAttribute('ld:bits')/8;
} elsif ($meta eq 'pointer') { } elsif ($meta eq 'pointer') {
return 4; return 4;
} elsif ($meta eq 'static-array') { } elsif ($meta eq 'static-array') {
my $count = $field->getAttribute('count'); my $count = $field->getAttribute('count');
my $tg = $field->findnodes('child::ld:item')->[0]; my $tg = $field->findnodes('child::ld:item')->[0];
return $count * sizeof($tg); return $count * sizeof($tg);
} elsif ($meta eq 'bitfield-type' or $meta eq 'enum-type') { } elsif ($meta eq 'bitfield-type' or $meta eq 'enum-type') {
my $base = $field->getAttribute('base-type') || 'uint32_t'; my $base = $field->getAttribute('base-type') || 'uint32_t';
print "$meta type $base\n" if $base !~ /int(\d+)_t/; print "$meta type $base\n" if $base !~ /int(\d+)_t/;
return $1/8; return $1/8;
} elsif ($meta eq 'global') { } elsif ($meta eq 'global') {
my $typename = $field->getAttribute('type-name'); my $typename = $field->getAttribute('type-name');
return $sizeof_cache{$typename} if $sizeof_cache{$typename}; return $sizeof_cache{$typename} if $sizeof_cache{$typename};
my $g = $global_types{$typename}; my $g = $global_types{$typename};
my $st = $field->getAttribute('ld:subtype') || ''; my $st = $field->getAttribute('ld:subtype') || '';
if ($st eq 'bitfield' or $st eq 'enum' or $g->getAttribute('ld:meta') eq 'bitfield-type') { if ($st eq 'bitfield' or $st eq 'enum' or $g->getAttribute('ld:meta') eq 'bitfield-type')
{
my $base = $field->getAttribute('base-type') || $g->getAttribute('base-type') || 'uint32_t'; my $base = $field->getAttribute('base-type') || $g->getAttribute('base-type') || 'uint32_t';
print "$st type $base\n" if $base !~ /int(\d+)_t/; print "$st type $base\n" if $base !~ /int(\d+)_t/;
return $1/8; return $1/8;
} }
return sizeof($g); return sizeof($g);
} elsif ($meta eq 'class-type') {
my $typename = $field->getAttribute('type-name'); } elsif ($meta eq 'class-type' or $meta eq 'struct-type' or $meta eq 'compound') {
return $sizeof_cache{$typename} if $sizeof_cache{$typename}; return sizeof_compound($field);
my $off = 4;
my $parent = $field->getAttribute('inherits-from');
$off = sizeof($global_types{$parent}) if ($parent);
for my $f ($field->findnodes('child::ld:field')) {
$off = align_field($off, get_field_align($f));
$off += sizeof($f);
}
# GCC: class a { vtable; char; } ; class b:a { char c2; } -> c2 has offset 5 (Windows MSVC: offset 8)
$off = align_field($off, 4) if $os ne 'linux';
$sizeof_cache{$typename} = $off;
return $off;
} elsif ($meta eq 'struct-type' or $meta eq 'compound') {
my $typename = $field->getAttribute('type-name');
return $sizeof_cache{$typename} if $typename and $sizeof_cache{$typename};
my $st = $field->getAttribute('ld:subtype') || '';
if ($st eq 'bitfield' or $st eq 'enum') {
my $base = $field->getAttribute('base-type') || 'uint32_t';
print "$st type $base\n" if $base !~ /int(\d+)_t/;
$sizeof_cache{$typename} = $1/8 if $typename;
return $1/8;
}
if ($field->getAttribute('is-union')) {
my $sz = 0;
for my $f ($field->findnodes('child::ld:field')) {
my $fsz = sizeof($f);
$sz = $fsz if $fsz > $sz;
}
return $sz;
}
my $off = 0;
my $parent = $field->getAttribute('inherits-from');
$off = sizeof($global_types{$parent}) if ($parent);
my $al = 1;
for my $f ($field->findnodes('child::ld:field')) {
my $fa = get_field_align($f);
$al = $fa if $fa > $al;
$off = align_field($off, $fa);
$off += sizeof($f);
}
$off = align_field($off, $al);
$sizeof_cache{$typename} = $off if $typename;
return $off;
} elsif ($meta eq 'container') { } elsif ($meta eq 'container') {
my $subtype = $field->getAttribute('ld:subtype'); my $subtype = $field->getAttribute('ld:subtype');
if ($subtype eq 'stl-vector') { if ($subtype eq 'stl-vector') {
if ($os eq 'linux') { if ($os eq 'linux') {
return 12; return 12;
@ -493,10 +575,11 @@ sub sizeof {
} else { } else {
print "sizeof container $subtype\n"; print "sizeof container $subtype\n";
} }
} elsif ($meta eq 'primitive') { } elsif ($meta eq 'primitive') {
my $subtype = $field->getAttribute('ld:subtype'); my $subtype = $field->getAttribute('ld:subtype');
if ($subtype eq 'stl-string') {
if ($os eq 'linux') { if ($subtype eq 'stl-string') { if ($os eq 'linux') {
return 4; return 4;
} elsif ($os eq 'windows') { } elsif ($os eq 'windows') {
return 28; return 28;
@ -507,6 +590,7 @@ sub sizeof {
} else { } else {
print "sizeof primitive $subtype\n"; print "sizeof primitive $subtype\n";
} }
} elsif ($meta eq 'bytes') { } elsif ($meta eq 'bytes') {
return $field->getAttribute('size'); return $field->getAttribute('size');
} else { } else {
@ -514,6 +598,59 @@ sub sizeof {
} }
} }
sub sizeof_compound {
my ($field) = @_;
my $typename = $field->getAttribute('type-name');
return $sizeof_cache{$typename} if $typename and $sizeof_cache{$typename};
my $meta = $field->getAttribute('ld:meta');
my $st = $field->getAttribute('ld:subtype') || '';
if ($st eq 'bitfield' or $st eq 'enum')
{
my $base = $field->getAttribute('base-type') || 'uint32_t';
print "$st type $base\n" if $base !~ /int(\d+)_t/;
$sizeof_cache{$typename} = $1/8 if $typename;
return $1/8;
}
if ($field->getAttribute('is-union'))
{
my $sz = 0;
for my $f ($field->findnodes('child::ld:field'))
{
my $fsz = sizeof($f);
$sz = $fsz if $fsz > $sz;
}
return $sz;
}
my $parent = $field->getAttribute('inherits-from');
my $off = 0;
$off = 4 if ($meta eq 'class-type');
$off = sizeof($global_types{$parent}) if ($parent);
my $al = 1;
$al = 4 if ($meta eq 'class-type');
for my $f ($field->findnodes('child::ld:field'))
{
my $fa = get_field_align($f);
$al = $fa if $fa > $al;
$off = align_field($off, $fa);
$off += sizeof($f);
}
# GCC: class a { vtable; char; } ; class b:a { char c2; } -> c2 has offset 5 (Windows MSVC: offset 8)
$al = 1 if ($meta eq 'class-type' and $os eq 'linux');
$off = align_field($off, $al);
$sizeof_cache{$typename} = $off if $typename;
return $off;
}
sub render_item { sub render_item {
my ($item) = @_; my ($item) = @_;
return if (!$item); return if (!$item);
@ -576,6 +713,8 @@ sub render_item_number {
push @lines_rb, 'number 8, false'; push @lines_rb, 'number 8, false';
} elsif ($subtype eq 'bool') { } elsif ($subtype eq 'bool') {
push @lines_rb, 'number 8, true'; push @lines_rb, 'number 8, true';
$initvalue ||= 'nil';
$typename ||= 'BooleanEnum';
} elsif ($subtype eq 's-float') { } elsif ($subtype eq 's-float') {
push @lines_rb, 'float'; push @lines_rb, 'float';
return; return;
@ -583,6 +722,7 @@ sub render_item_number {
print "no render number $subtype\n"; print "no render number $subtype\n";
return; return;
} }
$lines_rb[$#lines_rb] .= ", $initvalue" if ($initvalue); $lines_rb[$#lines_rb] .= ", $initvalue" if ($initvalue);
$lines_rb[$#lines_rb] .= ", $typename" if ($typename); $lines_rb[$#lines_rb] .= ", $typename" if ($typename);
} }
@ -596,18 +736,26 @@ sub render_item_compound {
my $classname = $current_typename . '_' . rb_ucase($item->getAttribute('ld:typedef-name')); my $classname = $current_typename . '_' . rb_ucase($item->getAttribute('ld:typedef-name'));
local $current_typename = $classname; local $current_typename = $classname;
if (!$subtype || $subtype eq 'bitfield') { if (!$subtype || $subtype eq 'bitfield')
{
push @lines_rb, "compound(:$classname) {"; push @lines_rb, "compound(:$classname) {";
#push @lines_rb, 'sizeof';
indent_rb { indent_rb {
# declare sizeof() only for useful compound, eg the one behind pointers
# that the user may want to allocate
my $sz = sizeof($item);
push @lines_rb, "sizeof $sz\n" if $compound_pointer;
if (!$subtype) { if (!$subtype) {
local $compound_pointer = 0;
render_struct_fields($item); render_struct_fields($item);
} else { } else {
render_bitfield_fields($item); render_bitfield_fields($item);
} }
}; };
push @lines_rb, "}" push @lines_rb, "}"
} elsif ($subtype eq 'enum') { }
elsif ($subtype eq 'enum')
{
push @lines_rb, "class ::DFHack::$classname < MemHack::Enum"; push @lines_rb, "class ::DFHack::$classname < MemHack::Enum";
indent_rb { indent_rb {
# declare constants # declare constants
@ -617,7 +765,9 @@ sub render_item_compound {
# actual field # actual field
render_item_number($item, $classname); render_item_number($item, $classname);
} else { }
else
{
print "no render compound $subtype\n"; print "no render compound $subtype\n";
} }
} }
@ -629,7 +779,8 @@ sub render_item_container {
my $rbmethod = join('_', split('-', $subtype)); my $rbmethod = join('_', split('-', $subtype));
my $tg = $item->findnodes('child::ld:item')->[0]; my $tg = $item->findnodes('child::ld:item')->[0];
my $indexenum = $item->getAttribute('index-enum'); my $indexenum = $item->getAttribute('index-enum');
if ($tg) { if ($tg)
{
if ($rbmethod eq 'df_linked_list') { if ($rbmethod eq 'df_linked_list') {
push @lines_rb, "$rbmethod {"; push @lines_rb, "$rbmethod {";
} else { } else {
@ -640,10 +791,14 @@ sub render_item_container {
render_item($tg); render_item($tg);
}; };
push @lines_rb, "}"; push @lines_rb, "}";
} elsif ($indexenum) { }
elsif ($indexenum)
{
$indexenum = rb_ucase($indexenum); $indexenum = rb_ucase($indexenum);
push @lines_rb, "$rbmethod($indexenum)"; push @lines_rb, "$rbmethod($indexenum)";
} else { }
else
{
push @lines_rb, "$rbmethod"; push @lines_rb, "$rbmethod";
} }
} }
@ -652,14 +807,16 @@ sub render_item_pointer {
my ($item) = @_; my ($item) = @_;
my $tg = $item->findnodes('child::ld:item')->[0]; my $tg = $item->findnodes('child::ld:item')->[0];
my $ary = $item->getAttribute('is-array'); my $ary = $item->getAttribute('is-array') || '';
if ($ary and $ary eq 'true') {
if ($ary eq 'true') {
my $tglen = sizeof($tg) if $tg; my $tglen = sizeof($tg) if $tg;
push @lines_rb, "pointer_ary($tglen) {"; push @lines_rb, "pointer_ary($tglen) {";
} else { } else {
push @lines_rb, "pointer {"; push @lines_rb, "pointer {";
} }
indent_rb { indent_rb {
local $compound_pointer = 1;
render_item($tg); render_item($tg);
}; };
push @lines_rb, "}"; push @lines_rb, "}";
@ -672,6 +829,7 @@ sub render_item_staticarray {
my $tg = $item->findnodes('child::ld:item')->[0]; my $tg = $item->findnodes('child::ld:item')->[0];
my $tglen = sizeof($tg) if $tg; my $tglen = sizeof($tg) if $tg;
my $indexenum = $item->getAttribute('index-enum'); my $indexenum = $item->getAttribute('index-enum');
if ($indexenum) { if ($indexenum) {
$indexenum = rb_ucase($indexenum); $indexenum = rb_ucase($indexenum);
push @lines_rb, "static_array($count, $tglen, $indexenum) {"; push @lines_rb, "static_array($count, $tglen, $indexenum) {";
@ -708,16 +866,18 @@ sub render_item_bytes {
} }
} }
my $input = $ARGV[0] || '../../library/include/df/codegen.out.xml';
my $input = $ARGV[0] or die "need input xml";
my $output = $ARGV[1] or die "need output file"; my $output = $ARGV[1] or die "need output file";
my $memstruct = $ARGV[2];
my $doc = XML::LibXML->new()->parse_file($input); my $doc = XML::LibXML->new()->parse_file($input);
$global_types{$_->getAttribute('type-name')} = $_ foreach $doc->findnodes('/ld:data-definition/ld:global-type'); $global_types{$_->getAttribute('type-name')} = $_ foreach $doc->findnodes('/ld:data-definition/ld:global-type');
# render enums first, this allows later code to refer to them directly
my @nonenums; my @nonenums;
for my $name (sort { $a cmp $b } keys %global_types) { for my $name (sort { $a cmp $b } keys %global_types)
{
my $type = $global_types{$name}; my $type = $global_types{$name};
my $meta = $type->getAttribute('ld:meta'); my $meta = $type->getAttribute('ld:meta');
if ($meta eq 'enum-type') { if ($meta eq 'enum-type') {
@ -727,7 +887,9 @@ for my $name (sort { $a cmp $b } keys %global_types) {
} }
} }
for my $name (@nonenums) { # render other structs/bitfields/classes
for my $name (@nonenums)
{
my $type = $global_types{$name}; my $type = $global_types{$name};
my $meta = $type->getAttribute('ld:meta'); my $meta = $type->getAttribute('ld:meta');
if ($meta eq 'struct-type' or $meta eq 'class-type') { if ($meta eq 'struct-type' or $meta eq 'class-type') {
@ -739,16 +901,11 @@ for my $name (@nonenums) {
} }
} }
# render globals
render_global_objects($doc->findnodes('/ld:data-definition/ld:global-object')); render_global_objects($doc->findnodes('/ld:data-definition/ld:global-object'));
open FH, ">$output"; open FH, ">$output";
if ($memstruct) {
open MH, "<$memstruct";
print FH "$_" while(<MH>);
close MH;
}
print FH "module DFHack\n"; print FH "module DFHack\n";
print FH "$_\n" for @lines_rb; print FH "$_\n" for @lines_rb;
print FH "end\n"; print FH "end\n";

@ -2,7 +2,6 @@ module DFHack
# allocate a new building object # allocate a new building object
def self.building_alloc(type, subtype=-1, custom=-1) def self.building_alloc(type, subtype=-1, custom=-1)
type = BuildingType.to_sym(type)
cls = rtti_n2c[BuildingType::Classname[type].to_sym] cls = rtti_n2c[BuildingType::Classname[type].to_sym]
raise "invalid building type #{type.inspect}" if not cls raise "invalid building type #{type.inspect}" if not cls
bld = cls.cpp_new bld = cls.cpp_new

@ -1,747 +0,0 @@
module DFHack
module MemHack
INSPECT_SIZE_LIMIT=16384
class MemStruct
attr_accessor :_memaddr
def _at(addr) ; d = dup ; d._memaddr = addr ; d ; end
def _get ; self ; end
def _cpp_init ; end
end
class Compound < MemStruct
class << self
attr_accessor :_fields, :_rtti_classname, :_sizeof
def field(name, offset)
struct = yield
return if not struct
@_fields ||= []
@_fields << [name, offset, struct]
define_method(name) { struct._at(@_memaddr+offset)._get }
define_method("#{name}=") { |v| struct._at(@_memaddr+offset)._set(v) }
end
def _fields_ancestors
if superclass.respond_to?(:_fields_ancestors)
superclass._fields_ancestors + _fields.to_a
else
_fields.to_a
end
end
def number(bits, signed, initvalue=nil, enum=nil)
Number.new(bits, signed, initvalue, enum)
end
def float
Float.new
end
def bit(shift)
BitField.new(shift, 1)
end
def bits(shift, len, enum=nil)
BitField.new(shift, len, enum)
end
def pointer
Pointer.new((yield if block_given?))
end
def pointer_ary(tglen)
PointerAry.new(tglen, yield)
end
def static_array(len, tglen, indexenum=nil)
StaticArray.new(tglen, len, indexenum, yield)
end
def static_string(len)
StaticString.new(len)
end
def stl_vector(tglen=nil)
tg = yield if tglen
case tglen
when 1; StlVector8.new(tg)
when 2; StlVector16.new(tg)
else StlVector32.new(tg)
end
end
def stl_string
StlString.new
end
def stl_bit_vector
StlBitVector.new
end
def stl_deque(tglen)
StlDeque.new(tglen, yield)
end
def df_flagarray(indexenum=nil)
DfFlagarray.new(indexenum)
end
def df_array(tglen)
DfArray.new(tglen, yield)
end
def df_linked_list
DfLinkedList.new(yield)
end
def global(glob)
Global.new(glob)
end
def compound(name=nil, &b)
m = Class.new(Compound)
DFHack.const_set(name, m) if name
m.instance_eval(&b)
m.new
end
def rtti_classname(n)
DFHack.rtti_register(n, self)
@_rtti_classname = n
end
def sizeof(n)
@_sizeof = n
end
# allocate a new c++ object, return its ruby wrapper
def cpp_new
ptr = DFHack.malloc(_sizeof)
if _rtti_classname and vt = DFHack.rtti_getvtable(_rtti_classname)
DFHack.memory_write_int32(ptr, vt)
# TODO call constructor
end
o = new._at(ptr)
o._cpp_init
o
end
end
def _cpp_init
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init }
end
def _set(h)
case h
when Hash; h.each { |k, v| send("_#{k}=", v) }
when Array; names = _field_names ; raise 'bad size' if names.length != h.length ; names.zip(h).each { |n, a| send("#{n}=", a) }
when Compound; _field_names.each { |n| send("#{n}=", h.send(n)) }
else raise 'wut?'
end
end
def _fields ; self.class._fields.to_a ; end
def _fields_ancestors ; self.class._fields_ancestors.to_a ; end
def _field_names ; _fields_ancestors.map { |n, o, s| n } ; end
def _rtti_classname ; self.class._rtti_classname ; end
def _sizeof ; self.class._sizeof ; end
@@inspecting = {} # avoid infinite recursion on mutually-referenced objects
def inspect
cn = self.class.name.sub(/^DFHack::/, '')
cn << ' @' << ('0x%X' % _memaddr) if cn != ''
out = "#<#{cn}"
return out << ' ...>' if @@inspecting[_memaddr]
@@inspecting[_memaddr] = true
_fields_ancestors.each { |n, o, s|
out << ' ' if out.length != 0 and out[-1, 1] != ' '
if out.length > INSPECT_SIZE_LIMIT
out << '...'
break
end
out << inspect_field(n, o, s)
}
out.chomp!(' ')
@@inspecting.delete _memaddr
out << '>'
end
def inspect_field(n, o, s)
if s.kind_of?(BitField) and s._len == 1
send(n) ? n.to_s : ''
elsif s.kind_of?(Pointer)
"#{n}=#{s._at(@_memaddr+o).inspect}"
elsif n == :_whole
"_whole=0x#{_whole.to_s(16)}"
else
v = send(n).inspect
"#{n}=#{v}"
end
rescue
"#{n}=ERR(#{$!})"
end
end
class Enum
# number -> symbol
def self.enum
# ruby weirdness, needed to make the constants 'virtual'
@enum ||= const_get(:ENUM)
end
# symbol -> number
def self.nume
@nume ||= const_get(:NUME)
end
def self.to_i(i)
nume[i] || i
end
def self.to_sym(i)
enum[i] || i
end
end
class Number < MemStruct
attr_accessor :_bits, :_signed, :_initvalue, :_enum
def initialize(bits, signed, initvalue, enum)
@_bits = bits
@_signed = signed
@_initvalue = initvalue
@_enum = enum
end
def _get
v = case @_bits
when 32; DFHack.memory_read_int32(@_memaddr)
when 16; DFHack.memory_read_int16(@_memaddr)
when 8; DFHack.memory_read_int8( @_memaddr)
when 64;(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + (DFHack.memory_read_int32(@_memaddr+4) << 32)
end
v &= (1 << @_bits) - 1 if not @_signed
v = @_enum.to_sym(v) if @_enum
v
end
def _set(v)
v = @_enum.to_i(v) if @_enum
case @_bits
when 32; DFHack.memory_write_int32(@_memaddr, v)
when 16; DFHack.memory_write_int16(@_memaddr, v)
when 8; DFHack.memory_write_int8( @_memaddr, v)
when 64; DFHack.memory_write_int32(@_memaddr, v & 0xffffffff) ; DFHack.memory_write_int32(@memaddr+4, v>>32)
end
end
def _cpp_init
_set(@_initvalue) if @_initvalue
end
end
class Float < MemStruct
def _get
DFHack.memory_read_float(@_memaddr)
end
def _set(v)
DFHack.memory_write_float(@_memaddr, v)
end
def _cpp_init
_set(0.0)
end
end
class BitField < MemStruct
attr_accessor :_shift, :_len, :_enum
def initialize(shift, len, enum=nil)
@_shift = shift
@_len = len
@_enum = enum
end
def _mask
(1 << @_len) - 1
end
def _get
v = DFHack.memory_read_int32(@_memaddr) >> @_shift
if @_len == 1
((v & 1) == 0) ? false : true
else
v &= _mask
v = @_enum.to_sym(v) if @_enum
v
end
end
def _set(v)
if @_len == 1
# allow 'bit = 0'
v = (v && v != 0 ? 1 : 0)
end
v = @_enum.to_i(v) if @_enum
v = (v & _mask) << @_shift
ori = DFHack.memory_read_int32(@_memaddr) & 0xffffffff
DFHack.memory_write_int32(@_memaddr, ori - (ori & ((-1 & _mask) << @_shift)) + v)
end
end
class Pointer < MemStruct
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
def _getp
DFHack.memory_read_int32(@_memaddr) & 0xffffffff
end
def _get
addr = _getp
return if addr == 0
@_tg._at(addr)._get
end
# XXX shaky...
def _set(v)
if v.kind_of?(Pointer)
DFHack.memory_write_int32(@_memaddr, v._getp)
elsif v.kind_of?(MemStruct)
DFHack.memory_write_int32(@_memaddr, v._memaddr)
else
_get._set(v)
end
end
def inspect
ptr = _getp
if ptr == 0
'NULL'
else
cn = ''
cn = @_tg.class.name.sub(/^DFHack::/, '').sub(/^MemHack::/, '') if @_tg
cn = @_tg._glob if cn == 'Global'
"#<Pointer #{cn} #{'0x%X' % _getp}>"
end
end
end
class PointerAry < MemStruct
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
def _getp(i=0)
delta = (i != 0 ? i*@_tglen : 0)
(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + delta
end
def _get
addr = _getp
return if addr == 0
self
end
def [](i)
addr = _getp(i)
return if addr == 0
@_tg._at(addr)._get
end
def []=(i, v)
addr = _getp(i)
raise 'null pointer' if addr == 0
@_tg._at(addr)._set(v)
end
def inspect ; ptr = _getp ; (ptr == 0) ? 'NULL' : "#<PointerAry #{'0x%X' % ptr}>" ; end
end
module Enumerable
include ::Enumerable
attr_accessor :_indexenum
def each ; (0...length).each { |i| yield self[i] } ; end
def inspect
out = '['
each_with_index { |e, idx|
out << ', ' if out.length > 1
if out.length > INSPECT_SIZE_LIMIT
out << '...'
break
end
out << "#{_indexenum.to_sym(idx)}=" if _indexenum
out << e.inspect
}
out << ']'
end
def empty? ; length == 0 ; end
def flatten ; map { |e| e.respond_to?(:flatten) ? e.flatten : e }.flatten ; end
end
class StaticArray < MemStruct
attr_accessor :_tglen, :_length, :_indexenum, :_tg
def initialize(tglen, length, indexenum, tg)
@_tglen = tglen
@_length = length
@_indexenum = indexenum
@_tg = tg
end
def _set(a)
a.each_with_index { |v, i| self[i] = v }
end
def _cpp_init
_length.times { |i| _tgat(i)._cpp_init }
end
alias length _length
alias size _length
def _tgat(i)
@_tg._at(@_memaddr + i*@_tglen) if i >= 0 and i < @_length
end
def [](i)
i = _indexenum.to_i(i) if _indexenum
i += @_length if i < 0
_tgat(i)._get
end
def []=(i, v)
i = _indexenum.to_i(i) if _indexenum
i += @_length if i < 0
_tgat(i)._set(v)
end
include Enumerable
end
class StaticString < MemStruct
attr_accessor :_length
def initialize(length)
@_length = length
end
def _get
DFHack.memory_read(@_memaddr, @_length)
end
def _set(v)
DFHack.memory_write(@_memaddr, v[0, @_length])
end
end
class StlVector32 < MemStruct
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
def length
DFHack.memory_vector32_length(@_memaddr)
end
def size ; length ; end # alias wouldnt work for subclasses
def valueptr_at(idx)
DFHack.memory_vector32_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector32_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector32_delete(@_memaddr, idx)
end
def _set(v)
delete_at(length-1) while length > v.length # match lengthes
v.each_with_index { |e, i| self[i] = e } # patch entries
end
def _cpp_init
DFHack.memory_vector_init(@_memaddr)
end
def clear
delete_at(length-1) while length > 0
end
def [](idx)
idx += length if idx < 0
@_tg._at(valueptr_at(idx))._get if idx >= 0 and idx < length
end
def []=(idx, v)
idx += length if idx < 0
if idx >= length
insert_at(idx, 0)
elsif idx < 0
raise 'invalid idx'
end
@_tg._at(valueptr_at(idx))._set(v)
end
def push(v)
self[length] = v
self
end
def <<(v) ; push(v) ; end
def pop
l = length
if l > 0
v = self[l-1]
delete_at(l-1)
end
v
end
include Enumerable
# do a binary search in an ordered vector for a specific target attribute
# ex: world.history.figures.binsearch(unit.hist_figure_id)
def binsearch(target, field=:id)
o_start = 0
o_end = length - 1
while o_end >= o_start
o_half = o_start + (o_end-o_start)/2
obj = self[o_half]
oval = obj.send(field)
if oval == target
return obj
elsif oval < target
o_start = o_half+1
else
o_end = o_half-1
end
end
end
end
class StlVector16 < StlVector32
def length
DFHack.memory_vector16_length(@_memaddr)
end
def valueptr_at(idx)
DFHack.memory_vector16_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector16_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector16_delete(@_memaddr, idx)
end
end
class StlVector8 < StlVector32
def length
DFHack.memory_vector8_length(@_memaddr)
end
def valueptr_at(idx)
DFHack.memory_vector8_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector8_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector8_delete(@_memaddr, idx)
end
end
class StlBitVector < StlVector32
def initialize ; end
def length
DFHack.memory_vectorbool_length(@_memaddr)
end
def insert_at(idx, val)
DFHack.memory_vectorbool_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vectorbool_delete(@_memaddr, idx)
end
def [](idx)
idx += length if idx < 0
DFHack.memory_vectorbool_at(@_memaddr, idx) if idx >= 0 and idx < length
end
def []=(idx, v)
idx += length if idx < 0
if idx >= length
insert_at(idx, v)
elsif idx < 0
raise 'invalid idx'
else
DFHack.memory_vectorbool_setat(@_memaddr, idx, v)
end
end
end
class StlString < MemStruct
def _get
DFHack.memory_read_stlstring(@_memaddr)
end
def _set(v)
DFHack.memory_write_stlstring(@_memaddr, v)
end
def _cpp_init
DFHack.memory_stlstring_init(@_memaddr)
end
end
class StlDeque < MemStruct
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
# XXX DF uses stl::deque<some_struct>, so to have a C binding we'd need to single-case every
# possible struct size, like for StlVector. Just ignore it for now, deque are rare enough.
def inspect ; "#<StlDeque>" ; end
end
class DfFlagarray < MemStruct
attr_accessor :_indexenum
def initialize(indexenum)
@_indexenum = indexenum
end
def length
DFHack.memory_bitarray_length(@_memaddr)
end
# TODO _cpp_init
def size ; length ; end
def resize(len)
DFHack.memory_bitarray_resize(@_memaddr, len)
end
def [](idx)
idx = _indexenum.to_i(idx) if _indexenum
idx += length if idx < 0
DFHack.memory_bitarray_isset(@_memaddr, idx) if idx >= 0 and idx < length
end
def []=(idx, v)
idx = _indexenum.to_i(idx) if _indexenum
idx += length if idx < 0
if idx >= length or idx < 0
raise 'invalid idx'
else
DFHack.memory_bitarray_set(@_memaddr, idx, v)
end
end
include Enumerable
end
class DfArray < Compound
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
field(:_ptr, 0) { number 32, false }
field(:_length, 4) { number 16, false }
def length ; _length ; end
def size ; _length ; end
# TODO _cpp_init
def _tgat(i)
@_tg._at(_ptr + i*@_tglen) if i >= 0 and i < _length
end
def [](i)
i += _length if i < 0
_tgat(i)._get
end
def []=(i, v)
i += _length if i < 0
_tgat(i)._set(v)
end
def _set(a)
a.each_with_index { |v, i| self[i] = v }
end
include Enumerable
end
class DfLinkedList < Compound
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
field(:_ptr, 0) { number 32, false }
field(:_prev, 4) { number 32, false }
field(:_next, 8) { number 32, false }
def item
# With the current xml structure, currently _tg designate
# the type of the 'next' and 'prev' fields, not 'item'.
# List head has item == NULL, so we can safely return nil.
#addr = _ptr
#return if addr == 0
#@_tg._at(addr)._get
end
def item=(v)
#addr = _ptr
#raise 'null pointer' if addr == 0
#@_tg.at(addr)._set(v)
raise 'null pointer'
end
def prev
addr = _prev
return if addr == 0
@_tg._at(addr)._get
end
def next
addr = _next
return if addr == 0
@_tg._at(addr)._get
end
include Enumerable
def each
o = self
while o
yield o.item if o.item
o = o.next
end
end
def inspect ; "#<DfLinkedList #{item.inspect} prev=#{'0x%X' % _prev} next=#{'0x%X' % _next}>" ; end
end
class Global < MemStruct
attr_accessor :_glob
def initialize(glob)
@_glob = glob
end
def _at(addr)
g = DFHack.const_get(@_glob)
g = DFHack.rtti_getclassat(g, addr)
g.new._at(addr)
end
def inspect ; "#<#{@_glob}>" ; end
end
end # module MemHack
# cpp rtti name -> rb class
@rtti_n2c = {}
@rtti_c2n = {}
# cpp rtti name -> vtable ptr
@rtti_n2v = {}
@rtti_v2n = {}
def self.rtti_n2c ; @rtti_n2c ; end
def self.rtti_c2n ; @rtti_c2n ; end
def self.rtti_n2v ; @rtti_n2v ; end
def self.rtti_v2n ; @rtti_v2n ; end
# register a ruby class with a cpp rtti class name
def self.rtti_register(cppname, cls)
@rtti_n2c[cppname] = cls
@rtti_c2n[cls] = cppname
end
# return the ruby class to use for the cpp object at address if rtti info is available
def self.rtti_getclassat(cls, addr)
if addr != 0 and @rtti_c2n[cls]
# rtti info exist for class => cpp object has a vtable
@rtti_n2c[rtti_readclassname(get_vtable_ptr(addr))] || cls
else
cls
end
end
# try to read the rtti classname from an object vtable pointer
def self.rtti_readclassname(vptr)
unless n = @rtti_v2n[vptr]
n = @rtti_v2n[vptr] = get_rtti_classname(vptr).to_sym
@rtti_n2v[n] = vptr
end
n
end
# return the vtable pointer from the cpp rtti name
def self.rtti_getvtable(cppname)
unless v = @rtti_n2v[cppname]
v = get_vtable(cppname.to_s)
@rtti_n2v[cppname] = v
@rtti_v2n[v] = cppname if v != 0
end
v if v != 0
end
def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0)
vmethod_do_call(obj._memaddr, voff, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), vmethod_arg(a3))
end
def self.vmethod_arg(arg)
case arg
when nil, false; 0
when true; 1
when Integer; arg
#when String; [arg].pack('p').unpack('L')[0] # raw pointer to buffer
when MemHack::Compound; arg._memaddr
else raise "bad vmethod arg #{arg.class}"
end
end
end

@ -641,7 +641,7 @@ static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE cppvoff, VALUE a0, VALUE
int ret; int ret;
fptr = (decltype(fptr))*(void**)(*that + rb_num2ulong(cppvoff)); fptr = (decltype(fptr))*(void**)(*that + rb_num2ulong(cppvoff));
ret = fptr(that, rb_num2ulong(a0), rb_num2ulong(a1), rb_num2ulong(a2), rb_num2ulong(a3)); ret = fptr(that, rb_num2ulong(a0), rb_num2ulong(a1), rb_num2ulong(a2), rb_num2ulong(a3));
return rb_uint2inum(ret); return rb_int2inum(ret);
} }

@ -1,5 +1,3 @@
require 'hack/ruby-autogen'
module DFHack module DFHack
class << self class << self
# update the ruby.cpp version to accept a block # update the ruby.cpp version to accept a block
@ -298,5 +296,759 @@ def df
DFHack DFHack
end end
# load user-specified startup file # following: definitions used by ruby-autogen.rb
module DFHack
module MemHack
INSPECT_SIZE_LIMIT=16384
class MemStruct
attr_accessor :_memaddr
def _at(addr) ; d = dup ; d._memaddr = addr ; d ; end
def _get ; self ; end
def _cpp_init ; end
end
class Compound < MemStruct
class << self
attr_accessor :_fields, :_rtti_classname, :_sizeof
def field(name, offset)
struct = yield
return if not struct
@_fields ||= []
@_fields << [name, offset, struct]
define_method(name) { struct._at(@_memaddr+offset)._get }
define_method("#{name}=") { |v| struct._at(@_memaddr+offset)._set(v) }
end
def _fields_ancestors
if superclass.respond_to?(:_fields_ancestors)
superclass._fields_ancestors + _fields.to_a
else
_fields.to_a
end
end
def number(bits, signed, initvalue=nil, enum=nil)
Number.new(bits, signed, initvalue, enum)
end
def float
Float.new
end
def bit(shift)
BitField.new(shift, 1)
end
def bits(shift, len, enum=nil)
BitField.new(shift, len, enum)
end
def pointer
Pointer.new((yield if block_given?))
end
def pointer_ary(tglen)
PointerAry.new(tglen, yield)
end
def static_array(len, tglen, indexenum=nil)
StaticArray.new(tglen, len, indexenum, yield)
end
def static_string(len)
StaticString.new(len)
end
def stl_vector(tglen=nil)
tg = yield if tglen
case tglen
when 1; StlVector8.new(tg)
when 2; StlVector16.new(tg)
else StlVector32.new(tg)
end
end
def stl_string
StlString.new
end
def stl_bit_vector
StlBitVector.new
end
def stl_deque(tglen)
StlDeque.new(tglen, yield)
end
def df_flagarray(indexenum=nil)
DfFlagarray.new(indexenum)
end
def df_array(tglen)
DfArray.new(tglen, yield)
end
def df_linked_list
DfLinkedList.new(yield)
end
def global(glob)
Global.new(glob)
end
def compound(name=nil, &b)
m = Class.new(Compound)
DFHack.const_set(name, m) if name
m.instance_eval(&b)
m.new
end
def rtti_classname(n)
DFHack.rtti_register(n, self)
@_rtti_classname = n
end
def sizeof(n)
@_sizeof = n
end
# allocate a new c++ object, return its ruby wrapper
def cpp_new
ptr = DFHack.malloc(_sizeof)
if _rtti_classname and vt = DFHack.rtti_getvtable(_rtti_classname)
DFHack.memory_write_int32(ptr, vt)
# TODO call constructor
end
o = new._at(ptr)
o._cpp_init
o
end
end
def _cpp_init
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init }
end
def _set(h)
case h
when Hash; h.each { |k, v| send("_#{k}=", v) }
when Array; names = _field_names ; raise 'bad size' if names.length != h.length ; names.zip(h).each { |n, a| send("#{n}=", a) }
when Compound; _field_names.each { |n| send("#{n}=", h.send(n)) }
else raise 'wut?'
end
end
def _fields ; self.class._fields.to_a ; end
def _fields_ancestors ; self.class._fields_ancestors.to_a ; end
def _field_names ; _fields_ancestors.map { |n, o, s| n } ; end
def _rtti_classname ; self.class._rtti_classname ; end
def _sizeof ; self.class._sizeof ; end
@@inspecting = {} # avoid infinite recursion on mutually-referenced objects
def inspect
cn = self.class.name.sub(/^DFHack::/, '')
cn << ' @' << ('0x%X' % _memaddr) if cn != ''
out = "#<#{cn}"
return out << ' ...>' if @@inspecting[_memaddr]
@@inspecting[_memaddr] = true
_fields_ancestors.each { |n, o, s|
out << ' ' if out.length != 0 and out[-1, 1] != ' '
if out.length > INSPECT_SIZE_LIMIT
out << '...'
break
end
out << inspect_field(n, o, s)
}
out.chomp!(' ')
@@inspecting.delete _memaddr
out << '>'
end
def inspect_field(n, o, s)
if s.kind_of?(BitField) and s._len == 1
send(n) ? n.to_s : ''
elsif s.kind_of?(Pointer)
"#{n}=#{s._at(@_memaddr+o).inspect}"
elsif n == :_whole
"_whole=0x#{_whole.to_s(16)}"
else
v = send(n).inspect
"#{n}=#{v}"
end
rescue
"#{n}=ERR(#{$!})"
end
end
class Enum
# number -> symbol
def self.enum
# ruby weirdness, needed to make the constants 'virtual'
@enum ||= const_get(:ENUM)
end
# symbol -> number
def self.nume
@nume ||= const_get(:NUME)
end
def self.int(i)
nume[i] || i
end
def self.sym(i)
enum[i] || i
end
end
class Number < MemStruct
attr_accessor :_bits, :_signed, :_initvalue, :_enum
def initialize(bits, signed, initvalue, enum)
@_bits = bits
@_signed = signed
@_initvalue = initvalue
@_enum = enum
end
def _get
v = case @_bits
when 32; DFHack.memory_read_int32(@_memaddr)
when 16; DFHack.memory_read_int16(@_memaddr)
when 8; DFHack.memory_read_int8( @_memaddr)
when 64;(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + (DFHack.memory_read_int32(@_memaddr+4) << 32)
end
v &= (1 << @_bits) - 1 if not @_signed
v = @_enum.sym(v) if @_enum
v
end
def _set(v)
v = @_enum.int(v) if @_enum
case @_bits
when 32; DFHack.memory_write_int32(@_memaddr, v)
when 16; DFHack.memory_write_int16(@_memaddr, v)
when 8; DFHack.memory_write_int8( @_memaddr, v)
when 64; DFHack.memory_write_int32(@_memaddr, v & 0xffffffff) ; DFHack.memory_write_int32(@memaddr+4, v>>32)
end
end
def _cpp_init
_set(@_initvalue) if @_initvalue
end
end
class Float < MemStruct
def _get
DFHack.memory_read_float(@_memaddr)
end
def _set(v)
DFHack.memory_write_float(@_memaddr, v)
end
def _cpp_init
_set(0.0)
end
end
class BitField < MemStruct
attr_accessor :_shift, :_len, :_enum
def initialize(shift, len, enum=nil)
@_shift = shift
@_len = len
@_enum = enum
end
def _mask
(1 << @_len) - 1
end
def _get
v = DFHack.memory_read_int32(@_memaddr) >> @_shift
if @_len == 1
((v & 1) == 0) ? false : true
else
v &= _mask
v = @_enum.sym(v) if @_enum
v
end
end
def _set(v)
if @_len == 1
# allow 'bit = 0'
v = (v && v != 0 ? 1 : 0)
end
v = @_enum.int(v) if @_enum
v = (v & _mask) << @_shift
ori = DFHack.memory_read_int32(@_memaddr) & 0xffffffff
DFHack.memory_write_int32(@_memaddr, ori - (ori & ((-1 & _mask) << @_shift)) + v)
end
end
class Pointer < MemStruct
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
def _getp
DFHack.memory_read_int32(@_memaddr) & 0xffffffff
end
def _get
addr = _getp
return if addr == 0
@_tg._at(addr)._get
end
# XXX shaky...
def _set(v)
if v.kind_of?(Pointer)
DFHack.memory_write_int32(@_memaddr, v._getp)
elsif v.kind_of?(MemStruct)
DFHack.memory_write_int32(@_memaddr, v._memaddr)
else
_get._set(v)
end
end
def inspect
ptr = _getp
if ptr == 0
'NULL'
else
cn = ''
cn = @_tg.class.name.sub(/^DFHack::/, '').sub(/^MemHack::/, '') if @_tg
cn = @_tg._glob if cn == 'Global'
"#<Pointer #{cn} #{'0x%X' % _getp}>"
end
end
end
class PointerAry < MemStruct
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
def _getp(i=0)
delta = (i != 0 ? i*@_tglen : 0)
(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + delta
end
def _get
addr = _getp
return if addr == 0
self
end
def [](i)
addr = _getp(i)
return if addr == 0
@_tg._at(addr)._get
end
def []=(i, v)
addr = _getp(i)
raise 'null pointer' if addr == 0
@_tg._at(addr)._set(v)
end
def inspect ; ptr = _getp ; (ptr == 0) ? 'NULL' : "#<PointerAry #{'0x%X' % ptr}>" ; end
end
module Enumerable
include ::Enumerable
attr_accessor :_indexenum
def each ; (0...length).each { |i| yield self[i] } ; end
def inspect
out = '['
each_with_index { |e, idx|
out << ', ' if out.length > 1
if out.length > INSPECT_SIZE_LIMIT
out << '...'
break
end
out << "#{_indexenum.sym(idx)}=" if _indexenum
out << e.inspect
}
out << ']'
end
def empty? ; length == 0 ; end
def flatten ; map { |e| e.respond_to?(:flatten) ? e.flatten : e }.flatten ; end
end
class StaticArray < MemStruct
attr_accessor :_tglen, :_length, :_indexenum, :_tg
def initialize(tglen, length, indexenum, tg)
@_tglen = tglen
@_length = length
@_indexenum = indexenum
@_tg = tg
end
def _set(a)
a.each_with_index { |v, i| self[i] = v }
end
def _cpp_init
_length.times { |i| _tgat(i)._cpp_init }
end
alias length _length
alias size _length
def _tgat(i)
@_tg._at(@_memaddr + i*@_tglen) if i >= 0 and i < @_length
end
def [](i)
i = _indexenum.int(i) if _indexenum
i += @_length if i < 0
_tgat(i)._get
end
def []=(i, v)
i = _indexenum.int(i) if _indexenum
i += @_length if i < 0
_tgat(i)._set(v)
end
include Enumerable
end
class StaticString < MemStruct
attr_accessor :_length
def initialize(length)
@_length = length
end
def _get
DFHack.memory_read(@_memaddr, @_length)
end
def _set(v)
DFHack.memory_write(@_memaddr, v[0, @_length])
end
end
class StlVector32 < MemStruct
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
def length
DFHack.memory_vector32_length(@_memaddr)
end
def size ; length ; end # alias wouldnt work for subclasses
def valueptr_at(idx)
DFHack.memory_vector32_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector32_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector32_delete(@_memaddr, idx)
end
def _set(v)
delete_at(length-1) while length > v.length # match lengthes
v.each_with_index { |e, i| self[i] = e } # patch entries
end
def _cpp_init
DFHack.memory_vector_init(@_memaddr)
end
def clear
delete_at(length-1) while length > 0
end
def [](idx)
idx += length if idx < 0
@_tg._at(valueptr_at(idx))._get if idx >= 0 and idx < length
end
def []=(idx, v)
idx += length if idx < 0
if idx >= length
insert_at(idx, 0)
elsif idx < 0
raise 'invalid idx'
end
@_tg._at(valueptr_at(idx))._set(v)
end
def push(v)
self[length] = v
self
end
def <<(v) ; push(v) ; end
def pop
l = length
if l > 0
v = self[l-1]
delete_at(l-1)
end
v
end
include Enumerable
# do a binary search in an ordered vector for a specific target attribute
# ex: world.history.figures.binsearch(unit.hist_figure_id)
def binsearch(target, field=:id)
o_start = 0
o_end = length - 1
while o_end >= o_start
o_half = o_start + (o_end-o_start)/2
obj = self[o_half]
oval = obj.send(field)
if oval == target
return obj
elsif oval < target
o_start = o_half+1
else
o_end = o_half-1
end
end
end
end
class StlVector16 < StlVector32
def length
DFHack.memory_vector16_length(@_memaddr)
end
def valueptr_at(idx)
DFHack.memory_vector16_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector16_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector16_delete(@_memaddr, idx)
end
end
class StlVector8 < StlVector32
def length
DFHack.memory_vector8_length(@_memaddr)
end
def valueptr_at(idx)
DFHack.memory_vector8_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector8_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector8_delete(@_memaddr, idx)
end
end
class StlBitVector < StlVector32
def initialize ; end
def length
DFHack.memory_vectorbool_length(@_memaddr)
end
def insert_at(idx, val)
DFHack.memory_vectorbool_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vectorbool_delete(@_memaddr, idx)
end
def [](idx)
idx += length if idx < 0
DFHack.memory_vectorbool_at(@_memaddr, idx) if idx >= 0 and idx < length
end
def []=(idx, v)
idx += length if idx < 0
if idx >= length
insert_at(idx, v)
elsif idx < 0
raise 'invalid idx'
else
DFHack.memory_vectorbool_setat(@_memaddr, idx, v)
end
end
end
class StlString < MemStruct
def _get
DFHack.memory_read_stlstring(@_memaddr)
end
def _set(v)
DFHack.memory_write_stlstring(@_memaddr, v)
end
def _cpp_init
DFHack.memory_stlstring_init(@_memaddr)
end
end
class StlDeque < MemStruct
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
# XXX DF uses stl::deque<some_struct>, so to have a C binding we'd need to single-case every
# possible struct size, like for StlVector. Just ignore it for now, deque are rare enough.
def inspect ; "#<StlDeque>" ; end
end
class DfFlagarray < MemStruct
attr_accessor :_indexenum
def initialize(indexenum)
@_indexenum = indexenum
end
def length
DFHack.memory_bitarray_length(@_memaddr)
end
# TODO _cpp_init
def size ; length ; end
def resize(len)
DFHack.memory_bitarray_resize(@_memaddr, len)
end
def [](idx)
idx = _indexenum.int(idx) if _indexenum
idx += length if idx < 0
DFHack.memory_bitarray_isset(@_memaddr, idx) if idx >= 0 and idx < length
end
def []=(idx, v)
idx = _indexenum.int(idx) if _indexenum
idx += length if idx < 0
if idx >= length or idx < 0
raise 'invalid idx'
else
DFHack.memory_bitarray_set(@_memaddr, idx, v)
end
end
include Enumerable
end
class DfArray < Compound
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
field(:_ptr, 0) { number 32, false }
field(:_length, 4) { number 16, false }
def length ; _length ; end
def size ; _length ; end
# TODO _cpp_init
def _tgat(i)
@_tg._at(_ptr + i*@_tglen) if i >= 0 and i < _length
end
def [](i)
i += _length if i < 0
_tgat(i)._get
end
def []=(i, v)
i += _length if i < 0
_tgat(i)._set(v)
end
def _set(a)
a.each_with_index { |v, i| self[i] = v }
end
include Enumerable
end
class DfLinkedList < Compound
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
field(:_ptr, 0) { number 32, false }
field(:_prev, 4) { number 32, false }
field(:_next, 8) { number 32, false }
def item
# With the current xml structure, currently _tg designate
# the type of the 'next' and 'prev' fields, not 'item'.
# List head has item == NULL, so we can safely return nil.
#addr = _ptr
#return if addr == 0
#@_tg._at(addr)._get
end
def item=(v)
#addr = _ptr
#raise 'null pointer' if addr == 0
#@_tg.at(addr)._set(v)
raise 'null pointer'
end
def prev
addr = _prev
return if addr == 0
@_tg._at(addr)._get
end
def next
addr = _next
return if addr == 0
@_tg._at(addr)._get
end
include Enumerable
def each
o = self
while o
yield o.item if o.item
o = o.next
end
end
def inspect ; "#<DfLinkedList #{item.inspect} prev=#{'0x%X' % _prev} next=#{'0x%X' % _next}>" ; end
end
class Global < MemStruct
attr_accessor :_glob
def initialize(glob)
@_glob = glob
end
def _at(addr)
g = DFHack.const_get(@_glob)
g = DFHack.rtti_getclassat(g, addr)
g.new._at(addr)
end
def inspect ; "#<#{@_glob}>" ; end
end
end # module MemHack
class BooleanEnum
def self.int(v) ; ((v == true) || (v == 1)) ? 1 : 0 ; end
def self.sym(v) ; (!v || (v == 0)) ? false : true ; end
end
# cpp rtti name -> rb class
@rtti_n2c = {}
@rtti_c2n = {}
# cpp rtti name -> vtable ptr
@rtti_n2v = {}
@rtti_v2n = {}
def self.rtti_n2c ; @rtti_n2c ; end
def self.rtti_c2n ; @rtti_c2n ; end
def self.rtti_n2v ; @rtti_n2v ; end
def self.rtti_v2n ; @rtti_v2n ; end
# register a ruby class with a cpp rtti class name
def self.rtti_register(cppname, cls)
@rtti_n2c[cppname] = cls
@rtti_c2n[cls] = cppname
end
# return the ruby class to use for the cpp object at address if rtti info is available
def self.rtti_getclassat(cls, addr)
if addr != 0 and @rtti_c2n[cls]
# rtti info exist for class => cpp object has a vtable
@rtti_n2c[rtti_readclassname(get_vtable_ptr(addr))] || cls
else
cls
end
end
# try to read the rtti classname from an object vtable pointer
def self.rtti_readclassname(vptr)
unless n = @rtti_v2n[vptr]
n = @rtti_v2n[vptr] = get_rtti_classname(vptr).to_sym
@rtti_n2v[n] = vptr
end
n
end
# return the vtable pointer from the cpp rtti name
def self.rtti_getvtable(cppname)
unless v = @rtti_n2v[cppname]
v = get_vtable(cppname.to_s)
@rtti_n2v[cppname] = v
@rtti_v2n[v] = cppname if v != 0
end
v if v != 0
end
def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0)
vmethod_do_call(obj._memaddr, voff, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), vmethod_arg(a3))
end
def self.vmethod_arg(arg)
case arg
when nil, false; 0
when true; 1
when Integer; arg
#when String; [arg].pack('p').unpack('L')[0] # raw pointer to buffer
when MemHack::Compound; arg._memaddr
else raise "bad vmethod arg #{arg.class}"
end
end
end
# load autogen'd file
require 'hack/ruby-autogen'
# load optional user-specified startup file
load 'ruby_custom.rb' if File.exist?('ruby_custom.rb') load 'ruby_custom.rb' if File.exist?('ruby_custom.rb')