From bcb698a5b45ce40c29215f0e38f36b22122ff969 Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 29 May 2012 16:10:33 +0200 Subject: [PATCH 01/11] ruby: have codegen compute struct offsets directly --- plugins/ruby/CMakeLists.txt | 18 +- plugins/ruby/codegen.pl | 461 ++++++++++++++++++++---------------- 2 files changed, 256 insertions(+), 223 deletions(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index 7057cb2dc..f28f269df 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -1,23 +1,9 @@ find_package(Ruby) if(RUBY_FOUND) - ADD_CUSTOM_COMMAND( - OUTPUT ruby-autogen.cpp - COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.cpp - DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml codegen.pl - ) - ADD_EXECUTABLE(ruby-autogen ruby-autogen.cpp) - if(CMAKE_COMPILER_IS_GNUCC) - set_target_properties (ruby-autogen PROPERTIES COMPILE_FLAGS "-Wno-invalid-offsetof") - endif(CMAKE_COMPILER_IS_GNUCC) - ADD_CUSTOM_COMMAND( - OUTPUT ruby-autogen.offsets - COMMAND ruby-autogen ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.offsets - DEPENDS ruby-autogen - ) ADD_CUSTOM_COMMAND( 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_BINARY_DIR}/ruby-autogen.offsets ${CMAKE_CURRENT_SOURCE_DIR}/ruby-memstruct.rb - DEPENDS ruby-autogen.offsets 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 ${CMAKE_CURRENT_SOURCE_DIR}/ruby-memstruct.rb + DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml codegen.pl ) ADD_CUSTOM_TARGET(ruby-autogen-rb ALL DEPENDS ruby-autogen.rb) include_directories("${dfhack_SOURCE_DIR}/depends/tthread" ${RUBY_INCLUDE_PATH}) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 5c60fcb41..53c67fd99 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -6,9 +6,13 @@ use warnings; use XML::LibXML; our @lines_rb; -my @lines_cpp; -my @include_cpp; -my %offsets; + +my $os; +if ($^O =~ /linux/i) { + $os = 'linux'; +} else { + $os = 'windows'; +} sub indent_rb(&) { my ($sub) = @_; @@ -39,6 +43,7 @@ my %item_renderer = ( ); my %global_types; +our $current_typename; sub render_global_enum { my ($name, $type) = @_; @@ -112,10 +117,6 @@ sub render_enum_fields { sub render_global_bitfield { my ($name, $type) = @_; - push @lines_cpp, "}" if @include_cpp; - push @lines_cpp, "void cpp_$name(FILE *fout) {"; - push @include_cpp, $name; - my $rbname = rb_ucase($name); push @lines_rb, "class $rbname < MemHack::Compound"; indent_rb { @@ -128,7 +129,7 @@ sub render_bitfield_fields { push @lines_rb, "field(:_whole, 0) {"; indent_rb { - render_item_number($type, ''); + render_item_number($type); }; push @lines_rb, "}"; @@ -152,25 +153,8 @@ sub render_bitfield_fields { } -sub render_global_struct { - my ($name, $type) = @_; - - my $rbname = rb_ucase($name); - - my $cppns = "df::$name"; - push @lines_cpp, "}" if @include_cpp; - push @lines_cpp, "void cpp_$name(FILE *fout) {"; - push @include_cpp, $name; - - push @lines_rb, "class $rbname < MemHack::Compound"; - indent_rb { - my $sz = query_cpp("sizeof($cppns)"); - push @lines_rb, "sizeof $sz"; - render_struct_fields($type, "$cppns"); - }; - push @lines_rb, "end\n"; -} my %seen_class; +our $compound_off; sub render_global_class { my ($name, $type) = @_; @@ -183,8 +167,12 @@ sub render_global_class { return if $seen_class{$name}; $seen_class{$name}++; + local $compound_off = 0; + my $rtti_name; - if ($type->getAttribute('ld:meta') eq 'class-type') { + my $meta = $type->getAttribute('ld:meta'); + if ($meta eq 'class-type') { + $compound_off = 4; $rtti_name = $type->getAttribute('original-name') || $type->getAttribute('type-name') || $name; @@ -192,39 +180,42 @@ sub render_global_class { my $rbparent = ($parent ? rb_ucase($parent) : 'MemHack::Compound'); - my $cppns = "df::$name"; - push @lines_cpp, "}" if @include_cpp; - push @lines_cpp, "void cpp_$name(FILE *fout) {"; - push @include_cpp, $name; + $compound_off = sizeof($global_types{$parent}) if $parent; + local $current_typename = $rbname; push @lines_rb, "class $rbname < $rbparent"; indent_rb { - my $sz = query_cpp("sizeof($cppns)"); + my $sz = sizeof($type); + # 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'; push @lines_rb, "sizeof $sz"; push @lines_rb, "rtti_classname :$rtti_name" if $rtti_name; - render_struct_fields($type, "$cppns"); + render_struct_fields($type); my $vms = $type->findnodes('child::virtual-methods')->[0]; render_class_vmethods($vms) if $vms; }; push @lines_rb, "end\n"; } sub render_struct_fields { - my ($type, $cppns) = @_; + my ($type) = @_; + my $isunion = $type->getAttribute('is-union'); for my $field ($type->findnodes('child::ld:field')) { my $name = $field->getAttribute('name'); $name = $field->getAttribute('ld:anon-name') if (!$name); if (!$name and $field->getAttribute('ld:anon-compound')) { - render_struct_fields($field, $cppns); + render_struct_fields($field); + } else { + $compound_off = align_field($compound_off, get_field_align($field)); + if ($name) { + push @lines_rb, "field(:$name, $compound_off) {"; + indent_rb { + render_item($field); + }; + push @lines_rb, "}"; + } } - next if (!$name); - my $offset = get_offset($cppns, $name); - - push @lines_rb, "field(:$name, $offset) {"; - indent_rb { - render_item($field, "$cppns"); - }; - push @lines_rb, "}"; + $compound_off += sizeof($field) if (!$isunion); } } sub render_class_vmethods { @@ -255,11 +246,10 @@ sub render_class_vmethods { push @lines_rb, 'end'; } # on linux, the destructor uses 2 entries - $voff += 4 if $meth->getAttribute('is-destructor') and $^O =~ /linux/i; + $voff += 4 if $meth->getAttribute('is-destructor') and $os eq 'linux'; $voff += 4; } } - sub render_class_vmethod_ret { my ($call, $ret) = @_; if (!$ret) { @@ -283,9 +273,9 @@ sub render_class_vmethod_ret { if ($retsubtype eq 'bool') { push @lines_rb, "(val & 1) != 0"; } elsif ($ret->getAttribute('ld:unsigned')) { - push @lines_rb, "val & ((1 << $retbits) - 1)"; + push @lines_rb, "val & ((1 << $retbits) - 1)"; # if $retbits != 32; } else { # signed - push @lines_rb, "val &= ((1 << $retbits) - 1)"; + push @lines_rb, "val &= ((1 << $retbits) - 1)"; # if $retbits != 32; push @lines_rb, "((val >> ($retbits-1)) & 1) == 0 ? val : val - (1 << $retbits)"; } } elsif ($retmeta eq 'pointer') { @@ -308,9 +298,8 @@ sub render_global_objects { my $sname = 'global_objects'; my $rbname = rb_ucase($sname); - push @lines_cpp, "}" if @include_cpp; - push @lines_cpp, "void cpp_$sname(FILE *fout) {"; - push @include_cpp, $sname; + local $compound_off = 0; + local $current_typename = 'Global'; push @lines_rb, "class $rbname < MemHack::Compound"; indent_rb { @@ -323,7 +312,7 @@ sub render_global_objects { push @lines_rb, "field(:$oname, addr) {"; my $item = $obj->findnodes('child::ld:item')->[0]; indent_rb { - render_item($item, 'df::global'); + render_item($item); }; push @lines_rb, "}"; }; @@ -344,28 +333,209 @@ sub render_global_objects { } +sub align_field { + my ($off, $fldalign) = @_; + my $dt = $off % $fldalign; + $off += $fldalign - $dt if $dt > 0; + return $off; +} + +my %align_cache; +my %sizeof_cache; +sub get_field_align { + my ($field) = @_; + my $al = 4; + my $meta = $field->getAttribute('ld:meta'); + if ($meta eq 'number') { + $al = $field->getAttribute('ld:bits')/8; + $al = 4 if $al > 4; + } elsif ($meta eq 'global') { + my $typename = $field->getAttribute('type-name'); + return $align_cache{$typename} if $align_cache{$typename}; + my $g = $global_types{$typename}; + my $st = $field->getAttribute('ld:subtype') || ''; + 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'; + print "$st type $base\n" if $base !~ /int(\d+)_t/; + # dont cache, field->base-type may differ + return $1/8; + } + $al = 1; + for my $gf ($g->findnodes('child::ld:field')) { + my $fal = get_field_align($gf); + $al = $fal if $fal > $al; + } + $align_cache{$typename} = $al; + } elsif ($meta eq 'compound') { + 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/; + return $1/8; + } + $al = 1; + for my $f ($field->findnodes('child::ld:field')) { + my $fal = get_field_align($f); + $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; +} + +sub sizeof { + my ($field) = @_; + my $meta = $field->getAttribute('ld:meta'); + if ($meta eq 'number') { + return $field->getAttribute('ld:bits')/8; + } elsif ($meta eq 'pointer') { + return 4; + } elsif ($meta eq 'static-array') { + my $count = $field->getAttribute('count'); + my $tg = $field->findnodes('child::ld:item')->[0]; + return $count * sizeof($tg); + } elsif ($meta eq 'bitfield-type' or $meta eq 'enum-type') { + my $base = $field->getAttribute('base-type') || 'uint32_t'; + print "$meta type $base\n" if $base !~ /int(\d+)_t/; + return $1/8; + } elsif ($meta eq 'global') { + my $typename = $field->getAttribute('type-name'); + return $sizeof_cache{$typename} if $sizeof_cache{$typename}; + my $g = $global_types{$typename}; + my $st = $field->getAttribute('ld:subtype') || ''; + 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'; + print "$st type $base\n" if $base !~ /int(\d+)_t/; + return $1/8; + } + return sizeof($g); + } elsif ($meta eq 'class-type') { + my $typename = $field->getAttribute('type-name'); + return $sizeof_cache{$typename} if $sizeof_cache{$typename}; + 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') { + my $subtype = $field->getAttribute('ld:subtype'); + if ($subtype eq 'stl-vector') { + if ($os eq 'linux') { + return 12; + } elsif ($os eq 'windows') { + return 16; + } else { + print "sizeof stl-vector on $os\n"; + } + } elsif ($subtype eq 'stl-bit-vector') { + if ($os eq 'linux') { + return 20; + } elsif ($os eq 'windows') { + return 20; + } else { + print "sizeof stl-bit-vector on $os\n"; + } + } elsif ($subtype eq 'stl-deque') { + if ($os eq 'linux') { + return 40; + } elsif ($os eq 'windows') { + return 24; + } else { + print "sizeof stl-deque on $os\n"; + } + } elsif ($subtype eq 'df-linked-list') { + return 12; + } elsif ($subtype eq 'df-flagarray') { + return 8; + } elsif ($subtype eq 'df-array') { + return 8; # XXX 6 ? + } else { + print "sizeof container $subtype\n"; + } + } elsif ($meta eq 'primitive') { + my $subtype = $field->getAttribute('ld:subtype'); + if ($subtype eq 'stl-string') { + if ($os eq 'linux') { + return 4; + } elsif ($os eq 'windows') { + return 28; + } else { + print "sizeof stl-string on $os\n"; + } + print "sizeof stl-string\n"; + } else { + print "sizeof primitive $subtype\n"; + } + } elsif ($meta eq 'bytes') { + return $field->getAttribute('size'); + } else { + print "sizeof $meta\n"; + } +} + sub render_item { - my ($item, $pns) = @_; + my ($item) = @_; return if (!$item); my $meta = $item->getAttribute('ld:meta'); my $renderer = $item_renderer{$meta}; if ($renderer) { - $renderer->($item, $pns); + $renderer->($item); } else { print "no render item $meta\n"; } } sub render_item_global { - my ($item, $pns) = @_; + my ($item) = @_; my $typename = $item->getAttribute('type-name'); my $subtype = $item->getAttribute('ld:subtype'); if ($subtype and $subtype eq 'enum') { - render_item_number($item, $pns); + render_item_number($item); } else { my $rbname = rb_ucase($typename); push @lines_rb, "global :$rbname"; @@ -373,7 +543,7 @@ sub render_item_global { } sub render_item_number { - my ($item, $pns) = @_; + my ($item, $classname) = @_; my $subtype = $item->getAttribute('ld:subtype'); my $meta = $item->getAttribute('ld:meta'); @@ -381,13 +551,13 @@ sub render_item_number { my $typename = $item->getAttribute('type-name'); undef $typename if ($meta and $meta eq 'bitfield-type'); $typename = rb_ucase($typename) if $typename; - $typename = $pns if (!$typename and $subtype and $subtype eq 'enum'); # compound enum + $typename = $classname if (!$typename and $subtype and $subtype eq 'enum'); # compound enum $initvalue = 1 if ($initvalue and $initvalue eq 'true'); $initvalue = ":$initvalue" if ($initvalue and $typename and $initvalue =~ /[a-zA-Z]/); $initvalue ||= 'nil' if $typename; - $subtype = $item->getAttribute('base-type') if (!$subtype or $subtype eq 'enum' or $subtype eq 'bitfield'); + $subtype = $item->getAttribute('base-type') if (!$subtype or $subtype eq 'bitfield' or $subtype eq 'enum'); $subtype = 'int32_t' if (!$subtype); if ($subtype eq 'int64_t') { @@ -418,20 +588,20 @@ sub render_item_number { } sub render_item_compound { - my ($item, $pns) = @_; + my ($item) = @_; - my $cppns = $pns . '::' . $item->getAttribute('ld:typedef-name'); my $subtype = $item->getAttribute('ld:subtype'); - my @namecomponents = split('::', $cppns); - shift @namecomponents; - my $classname = join('_', map { rb_ucase($_) } @namecomponents); + local $compound_off = 0; + my $classname = $current_typename . '_' . rb_ucase($item->getAttribute('ld:typedef-name')); + local $current_typename = $classname; if (!$subtype || $subtype eq 'bitfield') { push @lines_rb, "compound(:$classname) {"; + #push @lines_rb, 'sizeof'; indent_rb { if (!$subtype) { - render_struct_fields($item, $cppns); + render_struct_fields($item); } else { render_bitfield_fields($item); } @@ -453,7 +623,7 @@ sub render_item_compound { } sub render_item_container { - my ($item, $pns) = @_; + my ($item) = @_; my $subtype = $item->getAttribute('ld:subtype'); my $rbmethod = join('_', split('-', $subtype)); @@ -463,11 +633,11 @@ sub render_item_container { if ($rbmethod eq 'df_linked_list') { push @lines_rb, "$rbmethod {"; } else { - my $tglen = get_tglen($tg, $pns); + my $tglen = sizeof($tg) if $tg; push @lines_rb, "$rbmethod($tglen) {"; } indent_rb { - render_item($tg, $pns); + render_item($tg); }; push @lines_rb, "}"; } elsif ($indexenum) { @@ -479,28 +649,28 @@ sub render_item_container { } sub render_item_pointer { - my ($item, $pns) = @_; + my ($item) = @_; my $tg = $item->findnodes('child::ld:item')->[0]; my $ary = $item->getAttribute('is-array'); if ($ary and $ary eq 'true') { - my $tglen = get_tglen($tg, $pns); + my $tglen = sizeof($tg) if $tg; push @lines_rb, "pointer_ary($tglen) {"; } else { push @lines_rb, "pointer {"; } indent_rb { - render_item($tg, $pns); + render_item($tg); }; push @lines_rb, "}"; } sub render_item_staticarray { - my ($item, $pns) = @_; + my ($item) = @_; my $count = $item->getAttribute('count'); my $tg = $item->findnodes('child::ld:item')->[0]; - my $tglen = get_tglen($tg, $pns); + my $tglen = sizeof($tg) if $tg; my $indexenum = $item->getAttribute('index-enum'); if ($indexenum) { $indexenum = rb_ucase($indexenum); @@ -509,13 +679,13 @@ sub render_item_staticarray { push @lines_rb, "static_array($count, $tglen) {"; } indent_rb { - render_item($tg, $pns); + render_item($tg); }; push @lines_rb, "}"; } sub render_item_primitive { - my ($item, $pns) = @_; + my ($item) = @_; my $subtype = $item->getAttribute('ld:subtype'); if ($subtype eq 'stl-string') { @@ -526,7 +696,7 @@ sub render_item_primitive { } sub render_item_bytes { - my ($item, $pns) = @_; + my ($item) = @_; my $subtype = $item->getAttribute('ld:subtype'); if ($subtype eq 'padding') { @@ -538,113 +708,10 @@ sub render_item_bytes { } } -sub get_offset { - my ($cppns, $fname) = @_; - - return query_cpp("offsetof($cppns, $fname)"); -} - -sub get_tglen { - my ($tg, $cppns) = @_; - - if (!$tg) { - return 'nil'; - } - - my $meta = $tg->getAttribute('ld:meta'); - if ($meta eq 'number') { - return $tg->getAttribute('ld:bits')/8; - } elsif ($meta eq 'pointer') { - return 4; - } elsif ($meta eq 'container') { - my $subtype = $tg->getAttribute('ld:subtype'); - if ($subtype eq 'stl-vector') { - return query_cpp("sizeof(std::vector)"); - } elsif ($subtype eq 'df-linked-list') { - return 12; - } else { - print "cannot tglen container $subtype\n"; - } - } elsif ($meta eq 'compound') { - my $cname = $tg->getAttribute('ld:typedef-name'); - return query_cpp("sizeof(${cppns}::$cname)"); - } elsif ($meta eq 'static-array') { - my $count = $tg->getAttribute('count'); - my $ttg = $tg->findnodes('child::ld:item')->[0]; - my $ttgl = get_tglen($ttg, $cppns); - if ($ttgl =~ /^\d+$/) { - return $count * $ttgl; - } else { - return "$count*$ttgl"; - } - } elsif ($meta eq 'global') { - my $typename = $tg->getAttribute('type-name'); - my $subtype = $tg->getAttribute('ld:subtype'); - if ($subtype and $subtype eq 'enum') { - my $base = $tg->getAttribute('base-type') || 'int32_t'; - if ($base eq 'int32_t') { - return 4; - } elsif ($base eq 'int16_t') { - return 2; - } elsif ($base eq 'int8_t') { - return 1; - } else { - print "cannot tglen enum $base\n"; - } - } else { - return query_cpp("sizeof(df::$typename)"); - } - } elsif ($meta eq 'primitive') { - my $subtype = $tg->getAttribute('ld:subtype'); - if ($subtype eq 'stl-string') { - return query_cpp("sizeof(std::string)"); - } else { - print "cannot tglen primitive $subtype\n"; - } - } else { - print "cannot tglen $meta\n"; - } -} - -my %query_cpp_cache; -sub query_cpp { - my ($query) = @_; - - my $ans = $offsets{$query}; - return $ans if (defined($ans)); - - my $cached = $query_cpp_cache{$query}; - return $cached if (defined($cached)); - $query_cpp_cache{$query} = 1; - - push @lines_cpp, " fprintf(fout, \"%s = %d\\n\", \"$query\", $query);"; - return "'$query'"; -} - - - my $input = $ARGV[0] || '../../library/include/df/codegen.out.xml'; -# run once with output = 'ruby-autogen.cpp' -# compile -# execute, save output to 'ruby-autogen.offsets' -# re-run this script with output = 'ruby-autogen.rb' and offsetfile = 'ruby-autogen.offsets' -# delete binary -# delete offsets my $output = $ARGV[1] or die "need output file"; -my $offsetfile = $ARGV[2]; -my $memstruct = $ARGV[3]; - -if ($offsetfile) { - open OF, "<$offsetfile"; - while (my $line = ) { - chomp($line); - my ($key, $val) = split(' = ', $line); - $offsets{$key} = $val; - } - close OF; -} - +my $memstruct = $ARGV[2]; my $doc = XML::LibXML->new()->parse_file($input); $global_types{$_->getAttribute('type-name')} = $_ foreach $doc->findnodes('/ld:data-definition/ld:global-type'); @@ -663,9 +730,7 @@ for my $name (sort { $a cmp $b } keys %global_types) { for my $name (@nonenums) { my $type = $global_types{$name}; my $meta = $type->getAttribute('ld:meta'); - if ($meta eq 'struct-type') { - render_global_struct($name, $type); - } elsif ($meta eq 'class-type') { + if ($meta eq 'struct-type' or $meta eq 'class-type') { render_global_class($name, $type); } elsif ($meta eq 'bitfield-type') { render_global_bitfield($name, $type); @@ -679,30 +744,12 @@ render_global_objects($doc->findnodes('/ld:data-definition/ld:global-object')); open FH, ">$output"; -if ($output =~ /\.cpp$/) { - print FH "#include \"DataDefs.h\"\n"; - print FH "#include \"df/$_.h\"\n" for @include_cpp; - print FH "#include \n"; - print FH "#include \n"; - print FH "$_\n" for @lines_cpp; - print FH "}\n"; - print FH "int main(int argc, char **argv) {\n"; - print FH " FILE *fout;\n"; - print FH " if (argc < 2) return 1;\n"; - print FH " fout = fopen(argv[1], \"w\");\n"; - print FH " cpp_$_(fout);\n" for @include_cpp; - print FH " fclose(fout);\n"; - print FH " return 0;\n"; - print FH "}\n"; - -} else { - if ($memstruct) { - open MH, "<$memstruct"; - print FH "$_" while(); - close MH; - } - print FH "module DFHack\n"; - print FH "$_\n" for @lines_rb; - print FH "end\n"; +if ($memstruct) { + open MH, "<$memstruct"; + print FH "$_" while(); + close MH; } +print FH "module DFHack\n"; +print FH "$_\n" for @lines_rb; +print FH "end\n"; close FH; From 3e61452f157fd9e8d29eb28106a2c9ee8b5bde60 Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 29 May 2012 17:28:51 +0200 Subject: [PATCH 02/11] 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() --- plugins/ruby/CMakeLists.txt | 2 +- plugins/ruby/README | 59 +-- plugins/ruby/codegen.pl | 499 +++++++++++++------- plugins/ruby/plugins/building.rb | 1 - plugins/ruby/ruby-memstruct.rb | 747 ------------------------------ plugins/ruby/ruby.cpp | 2 +- plugins/ruby/ruby.rb | 758 ++++++++++++++++++++++++++++++- 7 files changed, 1108 insertions(+), 960 deletions(-) delete mode 100644 plugins/ruby/ruby-memstruct.rb diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index f28f269df..440f3a249 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -2,7 +2,7 @@ find_package(Ruby) if(RUBY_FOUND) ADD_CUSTOM_COMMAND( 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 ) ADD_CUSTOM_TARGET(ruby-autogen-rb ALL DEPENDS ruby-autogen.rb) diff --git a/plugins/ruby/README b/plugins/ruby/README index 7238c161d..25b2781bd 100644 --- a/plugins/ruby/README +++ b/plugins/ruby/README @@ -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. 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 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. 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: rb_load ; 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. 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: df.onupdate_unregister handle +The same mechanism is available for onstatechange. + Exemples -------- @@ -42,6 +44,9 @@ Set a custom nickname to unit with id '123' Show current unit profession p df.find_unit.profession +Change current unit profession + df.find_unit.profession = :MASON + Center the screen on 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 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 + plant = df.world.plants.all.find { |plt| df.at_cursor?(plt) } + 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_designation_at(df.cursor).dig = :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 plugin consists of the ruby.rb file including user comfort functions and +describing basic classes used by the autogenerated code, and ruby-autogen.rb, +the auto-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 +For exemple, -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 +Will generate 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. +The syntax for the 'field' method is: +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, 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. 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 +in fact a vector of *pointers* to DFHack::Unit objects. diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 53c67fd99..5cdeeedd9 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -31,6 +31,7 @@ sub rb_ucase { return join("", map { ucfirst $_ } (split('_', $name))); } + my %item_renderer = ( 'global' => \&render_item_global, 'number' => \&render_item_number, @@ -42,6 +43,7 @@ my %item_renderer = ( 'bytes' => \&render_item_bytes, ); + my %global_types; our $current_typename; @@ -55,62 +57,88 @@ sub render_global_enum { }; push @lines_rb, "end\n"; } + sub render_enum_fields { my ($type) = @_; - my $value = -1; push @lines_rb, "ENUM = Hash.new"; push @lines_rb, "NUME = Hash.new"; my %attr_type; 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 $typeattr = $attr->getAttribute('type-name'); # find how we need to encode the attribute values: string, symbol (for enums), raw (number, bool) if ($typeattr) { if ($global_types{$typeattr}) { - $attr_type{$rbattr} = 'symbol'; + $attr_type->{$rbattr} = 'symbol'; } else { - $attr_type{$rbattr} = 'naked'; + $attr_type->{$rbattr} = 'naked'; } } else { - $attr_type{$rbattr} = 'quote'; + $attr_type->{$rbattr} = 'quote'; } 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] = [] }"; - $attr_list{$rbattr} = 1; - } elsif ($def) { - $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'); + $attr_list->{$rbattr} = 1; + } + elsif ($def) + { + $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)"; - } else { + } + else + { push @lines_rb, "$rbattr = Hash.new"; } } +} - for my $item ($type->findnodes('child::enum-item')) { - $value = $item->getAttribute('value') || ($value+1); - my $elemname = $item->getAttribute('name'); # || "unk_$value"; +sub render_enum_attr { + my ($rbelemname, $iattr, $attr_type, $attr_list) = @_; - 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 $iav = $iattr->getAttribute('value'); - my $rbattr = rb_ucase($ian); - my $op = ($attr_list{$rbattr} ? '<<' : '='); - $iav = ":$iav" if ($attr_type{$rbattr} eq 'symbol'); - $iav =~ s/'/\\'/g if ($attr_type{$rbattr} eq 'quote'); - $iav = "'$iav'" if ($attr_type{$rbattr} eq 'quote'); - $lines_rb[$#lines_rb] .= " ; ${rbattr}[:$rbelemname] $op $iav"; - } - } - } + my $ian = $iattr->getAttribute('name'); + my $iav = $iattr->getAttribute('value'); + my $rbattr = rb_ucase($ian); + + my $op = ($attr_list->{$rbattr} ? '<<' : '='); + + $iav = ":$iav" if ($attr_type->{$rbattr} eq 'symbol'); + $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"; } + sub render_bitfield_fields { my ($type) = @_; @@ -134,20 +163,26 @@ sub render_bitfield_fields { push @lines_rb, "}"; 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 $name = $field->getAttribute('name'); my $type = $field->getAttribute('type-name'); my $enum = rb_ucase($type) if ($type and $global_types{$type}); $name = $field->getAttribute('ld:anon-name') if (!$name); print "bitfield $name !number\n" if (!($field->getAttribute('ld:meta') eq 'number')); - if ($count == 1) { - push @lines_rb, "field(:$name, 0) { bit $shift }" if ($name); - } elsif ($enum) { - push @lines_rb, "field(:$name, 0) { bits $shift, $count, $enum }" if ($name); - } else { - push @lines_rb, "field(:$name, 0) { bits $shift, $count }" if ($name); + + if ($name) + { + if ($count == 1) { + push @lines_rb, "field(:$name, 0) { bit $shift }"; + } elsif ($enum) { + push @lines_rb, "field(:$name, 0) { bits $shift, $count, $enum }"; + } else { + push @lines_rb, "field(:$name, 0) { bits $shift, $count }"; + } } + $shift += $count; } } @@ -155,9 +190,12 @@ sub render_bitfield_fields { my %seen_class; our $compound_off; +our $compound_pointer; + sub render_global_class { my ($name, $type) = @_; + my $meta = $type->getAttribute('ld:meta'); my $rbname = rb_ucase($name); # ensure pre-definition of ancestors @@ -168,46 +206,55 @@ sub render_global_class { $seen_class{$name}++; 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 $meta = $type->getAttribute('ld:meta'); - if ($meta eq 'class-type') { - $compound_off = 4; + if ($meta eq 'class-type') + { $rtti_name = $type->getAttribute('original-name') || - $type->getAttribute('type-name') || - $name; + $type->getAttribute('type-name') || + $name; } 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"; indent_rb { my $sz = sizeof($type); # 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'; - push @lines_rb, "sizeof $sz"; - push @lines_rb, "rtti_classname :$rtti_name" if $rtti_name; + push @lines_rb, "sizeof $sz\n"; + + push @lines_rb, "rtti_classname :$rtti_name\n" if $rtti_name; + render_struct_fields($type); + my $vms = $type->findnodes('child::virtual-methods')->[0]; render_class_vmethods($vms) if $vms; }; push @lines_rb, "end\n"; } + sub render_struct_fields { my ($type) = @_; 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'); $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); - } else { + } + else + { $compound_off = align_field($compound_off, get_field_align($field)); - if ($name) { + if ($name) + { push @lines_rb, "field(:$name, $compound_off) {"; indent_rb { render_item($field); @@ -215,27 +262,39 @@ sub render_struct_fields { push @lines_rb, "}"; } } + $compound_off += sizeof($field) if (!$isunion); } } + sub render_class_vmethods { my ($vms) = @_; my $voff = 0; - for my $meth ($vms->findnodes('child::vmethod')) { + + for my $meth ($vms->findnodes('child::vmethod')) + { my $name = $meth->getAttribute('name'); - if ($name) { + + if ($name) + { my @argnames; 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 $argname = lcfirst($arg->getAttribute('name') || "arg$nr"); push @argnames, $argname; + 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 { push @argargs, $argname; } } + + # write vmethod ruby wrapper push @lines_rb, "def $name(" . join(', ', @argnames) . ')'; indent_rb { my $args = join('', map { ", $_" } @argargs); @@ -245,47 +304,74 @@ sub render_class_vmethods { }; push @lines_rb, 'end'; } + # on linux, the destructor uses 2 entries $voff += 4 if $meth->getAttribute('is-destructor') and $os eq 'linux'; $voff += 4; } } + sub render_class_vmethod_ret { my ($call, $ret) = @_; - if (!$ret) { + + if (!$ret) + { + # method returns void, hide return value push @lines_rb, "$call ; nil"; return; } + 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'); if ($retname and $global_types{$retname} and - $global_types{$retname}->getAttribute('ld:meta') eq 'enum-type') { - push @lines_rb, rb_ucase($retname) . ".to_sym($call)"; - } else { + $global_types{$retname}->getAttribute('ld:meta') eq 'enum-type') + { + push @lines_rb, rb_ucase($retname) . ".sym($call)"; + } + else + { print "vmethod global nonenum $call\n"; 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 $retbits = $ret->getAttribute('ld:bits'); push @lines_rb, "val = $call"; - if ($retsubtype eq 'bool') { + if ($retsubtype eq 'bool') + { push @lines_rb, "(val & 1) != 0"; - } elsif ($ret->getAttribute('ld:unsigned')) { - push @lines_rb, "val & ((1 << $retbits) - 1)"; # if $retbits != 32; - } else { # signed - push @lines_rb, "val &= ((1 << $retbits) - 1)"; # if $retbits != 32; + } + elsif ($ret->getAttribute('ld:unsigned')) + { + 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)"; } - } 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, "class << self"; indent_rb { render_item($ret->findnodes('child::ld:item')->[0]); }; push @lines_rb, "end._at(ptr) if ptr != 0"; - } else { + } + else + { print "vmethod unkret $call\n"; push @lines_rb, $call; } @@ -295,16 +381,17 @@ sub render_global_objects { my (@objects) = @_; my @global_objects; - my $sname = 'global_objects'; - my $rbname = rb_ucase($sname); - local $compound_off = 0; 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 { - for my $obj (@objects) { + for my $obj (@objects) + { my $oname = $obj->getAttribute('name'); + + # check if the symbol is defined in xml to avoid NULL deref my $addr = "DFHack.get_global_address('$oname')"; push @lines_rb, "addr = $addr"; push @lines_rb, "if addr != 0"; @@ -323,9 +410,11 @@ sub render_global_objects { }; push @lines_rb, "end"; + # define friendlier accessors, eg df.world -> DFHack::GlobalObjects.new._at(0).world indent_rb { 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=(v) ; Global.$obj = v ; end"; } @@ -333,6 +422,9 @@ sub render_global_objects { } +my %align_cache; +my %sizeof_cache; + sub align_field { my ($off, $fldalign) = @_; my $dt = $off % $fldalign; @@ -340,126 +432,116 @@ sub align_field { return $off; } -my %align_cache; -my %sizeof_cache; sub get_field_align { my ($field) = @_; my $al = 4; my $meta = $field->getAttribute('ld:meta'); + if ($meta eq 'number') { $al = $field->getAttribute('ld:bits')/8; $al = 4 if $al > 4; } elsif ($meta eq 'global') { - my $typename = $field->getAttribute('type-name'); - return $align_cache{$typename} if $align_cache{$typename}; - my $g = $global_types{$typename}; - my $st = $field->getAttribute('ld:subtype') || ''; - 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'; - print "$st type $base\n" if $base !~ /int(\d+)_t/; - # dont cache, field->base-type may differ - return $1/8; - } - $al = 1; - for my $gf ($g->findnodes('child::ld:field')) { - my $fal = get_field_align($gf); - $al = $fal if $fal > $al; - } - $align_cache{$typename} = $al; + $al = get_global_align($field); } elsif ($meta eq 'compound') { - 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/; - return $1/8; - } - $al = 1; - for my $f ($field->findnodes('child::ld:field')) { - my $fal = get_field_align($f); - $al = $fal if $fal > $al; - } + $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'); + return $align_cache{$typename} if $align_cache{$typename}; + + my $g = $global_types{$typename}; + + my $st = $field->getAttribute('ld:subtype') || ''; + 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'; + print "$st type $base\n" if $base !~ /int(\d+)_t/; + # dont cache, field->base-type may differ + return $1/8; + } + + my $al = 1; + for my $gf ($g->findnodes('child::ld:field')) { + my $fld_al = get_field_align($gf); + $al = $fld_al if $fld_al > $al; + } + $align_cache{$typename} = $al; + + return $al; +} + +sub get_compound_align { + my ($field) = @_; + + 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/; + return $1/8; + } + + my $al = 1; + for my $f ($field->findnodes('child::ld:field')) { + my $fal = get_field_align($f); + $al = $fal if $fal > $al; + } + return $al; } sub sizeof { my ($field) = @_; my $meta = $field->getAttribute('ld:meta'); + if ($meta eq 'number') { return $field->getAttribute('ld:bits')/8; + } elsif ($meta eq 'pointer') { return 4; + } elsif ($meta eq 'static-array') { my $count = $field->getAttribute('count'); my $tg = $field->findnodes('child::ld:item')->[0]; return $count * sizeof($tg); + } elsif ($meta eq 'bitfield-type' or $meta eq 'enum-type') { my $base = $field->getAttribute('base-type') || 'uint32_t'; print "$meta type $base\n" if $base !~ /int(\d+)_t/; return $1/8; + } elsif ($meta eq 'global') { my $typename = $field->getAttribute('type-name'); return $sizeof_cache{$typename} if $sizeof_cache{$typename}; + my $g = $global_types{$typename}; 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'; print "$st type $base\n" if $base !~ /int(\d+)_t/; return $1/8; } + return sizeof($g); - } elsif ($meta eq 'class-type') { - my $typename = $field->getAttribute('type-name'); - return $sizeof_cache{$typename} if $sizeof_cache{$typename}; - 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 'class-type' or $meta eq 'struct-type' or $meta eq 'compound') { + return sizeof_compound($field); + } elsif ($meta eq 'container') { my $subtype = $field->getAttribute('ld:subtype'); + if ($subtype eq 'stl-vector') { if ($os eq 'linux') { return 12; @@ -493,10 +575,11 @@ sub sizeof { } else { print "sizeof container $subtype\n"; } + } elsif ($meta eq 'primitive') { 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; } elsif ($os eq 'windows') { return 28; @@ -507,6 +590,7 @@ sub sizeof { } else { print "sizeof primitive $subtype\n"; } + } elsif ($meta eq 'bytes') { return $field->getAttribute('size'); } 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 { my ($item) = @_; return if (!$item); @@ -576,6 +713,8 @@ sub render_item_number { push @lines_rb, 'number 8, false'; } elsif ($subtype eq 'bool') { push @lines_rb, 'number 8, true'; + $initvalue ||= 'nil'; + $typename ||= 'BooleanEnum'; } elsif ($subtype eq 's-float') { push @lines_rb, 'float'; return; @@ -583,6 +722,7 @@ sub render_item_number { print "no render number $subtype\n"; return; } + $lines_rb[$#lines_rb] .= ", $initvalue" if ($initvalue); $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')); local $current_typename = $classname; - if (!$subtype || $subtype eq 'bitfield') { + if (!$subtype || $subtype eq 'bitfield') + { push @lines_rb, "compound(:$classname) {"; - #push @lines_rb, 'sizeof'; 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) { + local $compound_pointer = 0; render_struct_fields($item); } else { render_bitfield_fields($item); } }; push @lines_rb, "}" - } elsif ($subtype eq 'enum') { + } + elsif ($subtype eq 'enum') + { push @lines_rb, "class ::DFHack::$classname < MemHack::Enum"; indent_rb { # declare constants @@ -617,7 +765,9 @@ sub render_item_compound { # actual field render_item_number($item, $classname); - } else { + } + else + { print "no render compound $subtype\n"; } } @@ -629,7 +779,8 @@ sub render_item_container { my $rbmethod = join('_', split('-', $subtype)); my $tg = $item->findnodes('child::ld:item')->[0]; my $indexenum = $item->getAttribute('index-enum'); - if ($tg) { + if ($tg) + { if ($rbmethod eq 'df_linked_list') { push @lines_rb, "$rbmethod {"; } else { @@ -640,10 +791,14 @@ sub render_item_container { render_item($tg); }; push @lines_rb, "}"; - } elsif ($indexenum) { + } + elsif ($indexenum) + { $indexenum = rb_ucase($indexenum); push @lines_rb, "$rbmethod($indexenum)"; - } else { + } + else + { push @lines_rb, "$rbmethod"; } } @@ -652,14 +807,16 @@ sub render_item_pointer { my ($item) = @_; my $tg = $item->findnodes('child::ld:item')->[0]; - my $ary = $item->getAttribute('is-array'); - if ($ary and $ary eq 'true') { + my $ary = $item->getAttribute('is-array') || ''; + + if ($ary eq 'true') { my $tglen = sizeof($tg) if $tg; push @lines_rb, "pointer_ary($tglen) {"; } else { push @lines_rb, "pointer {"; } indent_rb { + local $compound_pointer = 1; render_item($tg); }; push @lines_rb, "}"; @@ -672,6 +829,7 @@ sub render_item_staticarray { my $tg = $item->findnodes('child::ld:item')->[0]; my $tglen = sizeof($tg) if $tg; my $indexenum = $item->getAttribute('index-enum'); + if ($indexenum) { $indexenum = rb_ucase($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 $memstruct = $ARGV[2]; my $doc = XML::LibXML->new()->parse_file($input); $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; -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 $meta = $type->getAttribute('ld:meta'); 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 $meta = $type->getAttribute('ld:meta'); 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')); open FH, ">$output"; -if ($memstruct) { - open MH, "<$memstruct"; - print FH "$_" while(); - close MH; -} print FH "module DFHack\n"; print FH "$_\n" for @lines_rb; print FH "end\n"; diff --git a/plugins/ruby/plugins/building.rb b/plugins/ruby/plugins/building.rb index 15212092d..5dfbcdacd 100644 --- a/plugins/ruby/plugins/building.rb +++ b/plugins/ruby/plugins/building.rb @@ -2,7 +2,6 @@ module DFHack # allocate a new building object def self.building_alloc(type, subtype=-1, custom=-1) - type = BuildingType.to_sym(type) cls = rtti_n2c[BuildingType::Classname[type].to_sym] raise "invalid building type #{type.inspect}" if not cls bld = cls.cpp_new diff --git a/plugins/ruby/ruby-memstruct.rb b/plugins/ruby/ruby-memstruct.rb deleted file mode 100644 index 40f35bb42..000000000 --- a/plugins/ruby/ruby-memstruct.rb +++ /dev/null @@ -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' - "#" - 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' : "#" ; 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, 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 ; "#" ; 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 ; "#" ; 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 - - diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 76d6a0431..c6053900e 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -641,7 +641,7 @@ static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE cppvoff, VALUE a0, VALUE int ret; fptr = (decltype(fptr))*(void**)(*that + rb_num2ulong(cppvoff)); ret = fptr(that, rb_num2ulong(a0), rb_num2ulong(a1), rb_num2ulong(a2), rb_num2ulong(a3)); - return rb_uint2inum(ret); + return rb_int2inum(ret); } diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index 8c218eb63..fd597e43f 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -1,5 +1,3 @@ -require 'hack/ruby-autogen' - module DFHack class << self # update the ruby.cpp version to accept a block @@ -298,5 +296,759 @@ def df DFHack 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' + "#" + 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' : "#" ; 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, 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 ; "#" ; 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 ; "#" ; 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') From 470c9f60aa2303faea68c68e946cc02de1a20c04 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 31 May 2012 13:23:00 +0200 Subject: [PATCH 03/11] remoteclient: dont use gcc deprecated auto_ptr --- library/RemoteClient.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp index a1ac2ec92..4d30988c6 100644 --- a/library/RemoteClient.cpp +++ b/library/RemoteClient.cpp @@ -329,17 +329,19 @@ bool sendRemoteMessage(CSimpleSocket *socket, int16_t id, const MessageLite *msg int size = size_ready ? msg->GetCachedSize() : msg->ByteSize(); int fullsz = size + sizeof(RPCMessageHeader); - std::auto_ptr data(new uint8_t[fullsz]); - RPCMessageHeader *hdr = (RPCMessageHeader*)data.get(); + uint8_t *data = new uint8_t[fullsz]; + RPCMessageHeader *hdr = (RPCMessageHeader*)data; hdr->id = id; hdr->size = size; - uint8_t *pstart = data.get() + sizeof(RPCMessageHeader); + uint8_t *pstart = data + sizeof(RPCMessageHeader); uint8_t *pend = msg->SerializeWithCachedSizesToArray(pstart); assert((pend - pstart) == size); - return (socket->Send(data.get(), fullsz) == fullsz); + int got = socket->Send(data, fullsz); + delete[] data; + return (got == fullsz); } command_result RemoteFunctionBase::execute(color_ostream &out, @@ -402,9 +404,9 @@ command_result RemoteFunctionBase::execute(color_ostream &out, return CR_LINK_FAILURE; } - std::auto_ptr buf(new uint8_t[header.size]); + uint8_t *buf = new uint8_t[header.size]; - if (!readFullBuffer(p_client->socket, buf.get(), header.size)) + if (!readFullBuffer(p_client->socket, buf, header.size)) { out.printerr("In call to %s::%s: I/O error in receive %d bytes of data.\n", this->proto.c_str(), this->name.c_str(), header.size); @@ -413,18 +415,20 @@ command_result RemoteFunctionBase::execute(color_ostream &out, switch (header.id) { case RPC_REPLY_RESULT: - if (!output->ParseFromArray(buf.get(), header.size)) + if (!output->ParseFromArray(buf, header.size)) { out.printerr("In call to %s::%s: error parsing received result.\n", this->proto.c_str(), this->name.c_str()); + delete[] buf; return CR_LINK_FAILURE; } + delete[] buf; return CR_OK; case RPC_REPLY_TEXT: text_data.Clear(); - if (text_data.ParseFromArray(buf.get(), header.size)) + if (text_data.ParseFromArray(buf, header.size)) text_decoder.decode(&text_data); else out.printerr("In call to %s::%s: received invalid text data.\n", @@ -434,5 +438,6 @@ command_result RemoteFunctionBase::execute(color_ostream &out, default: break; } + delete[] buf; } } From b612532348ab18992d99f6abd507940cab877727 Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 2 Jun 2012 23:35:05 +0200 Subject: [PATCH 04/11] export openplugin/lookupplugin from plugin manager --- library/include/PluginManager.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 31633dfe2..b76df437d 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -61,11 +61,11 @@ namespace DFHack struct DFLibrary; // Open a plugin library - DFLibrary * OpenPlugin (const char * filename); + DFHACK_EXPORT DFLibrary * OpenPlugin (const char * filename); // find a symbol inside plugin - void * LookupPlugin (DFLibrary * plugin ,const char * function); + DFHACK_EXPORT void * LookupPlugin (DFLibrary * plugin ,const char * function); // Close a plugin library - void ClosePlugin (DFLibrary * plugin); + DFHACK_EXPORT void ClosePlugin (DFLibrary * plugin); struct DFHACK_EXPORT CommandReg { const char *name; From 2aace670eaa9eea168b4b4eb5e090e0047b9712d Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 2 Jun 2012 23:44:52 +0200 Subject: [PATCH 05/11] ruby: dlopen libruby -- currently segfaults with rb1.9 ... --- plugins/ruby/CMakeLists.txt | 24 +++----- plugins/ruby/ruby.cpp | 120 ++++++++++++++++++++++++++++++++++-- 2 files changed, 125 insertions(+), 19 deletions(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index 440f3a249..bcd10ac89 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -1,15 +1,9 @@ -find_package(Ruby) -if(RUBY_FOUND) - ADD_CUSTOM_COMMAND( - 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 - DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml codegen.pl - ) - ADD_CUSTOM_TARGET(ruby-autogen-rb ALL DEPENDS ruby-autogen.rb) - include_directories("${dfhack_SOURCE_DIR}/depends/tthread" ${RUBY_INCLUDE_PATH}) - DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread) - target_link_libraries(ruby ${RUBY_LIBRARY}) - install(FILES ruby.rb ruby-autogen.rb DESTINATION ${DFHACK_LIBRARY_DESTINATION}) -else(RUBY_FOUND) - MESSAGE(STATUS "Required library (ruby) not found - ruby plugin can't be built.") -endif(RUBY_FOUND) +ADD_CUSTOM_COMMAND( + 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 + DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml codegen.pl +) +ADD_CUSTOM_TARGET(ruby-autogen-rb ALL DEPENDS ruby-autogen.rb) +include_directories("${dfhack_SOURCE_DIR}/depends/tthread") +DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread) +install(FILES ruby.rb ruby-autogen.rb DESTINATION ${DFHACK_LIBRARY_DESTINATION}) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index c6053900e..189fc7a3b 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -11,8 +11,6 @@ #include "tinythread.h" -#include - using namespace DFHack; @@ -20,6 +18,8 @@ using namespace DFHack; // DFHack stuff +static int df_loadruby(void); +static void df_unloadruby(void); static void df_rubythread(void*); static command_result df_rubyload(color_ostream &out, std::vector & parameters); static command_result df_rubyeval(color_ostream &out, std::vector & parameters); @@ -45,6 +45,11 @@ DFHACK_PLUGIN("ruby") DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { + if (!df_loadruby()) { + Core::printerr("failed to load libruby\n"); + return CR_FAILURE; + } + m_irun = new tthread::mutex(); m_mutex = new tthread::mutex(); r_type = RB_INIT; @@ -90,6 +95,8 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) m_mutex->unlock(); delete m_mutex; + df_unloadruby(); + return CR_OK; } @@ -191,6 +198,111 @@ static command_result df_rubyeval(color_ostream &out, std::vector // ruby stuff +// ruby-dev on windows is messy +// ruby.h on linux 64 is broken +// so we dynamically load libruby instead of linking it at compile time +// lib path can be set in dfhack.ini to use the system libruby, but by +// default we'll embed our own (downloaded by cmake) + +// these ruby definitions are invalid for windows 64bit +typedef unsigned long VALUE; +typedef unsigned long ID; + +#define Qfalse ((VALUE)0) +#define Qtrue ((VALUE)2) +#define Qnil ((VALUE)4) + +#define INT2FIX(i) ((VALUE)((((long)i) << 1) | 1)) +#define FIX2INT(i) (((long)i) >> 1) +#define RUBY_METHOD_FUNC(func) ((VALUE(*)(...))func) + +VALUE *rb_eRuntimeError; + +void (*ruby_sysinit)(int *, const char ***); +void (*ruby_init)(void); +void (*ruby_init_loadpath)(void); +void (*ruby_script)(const char*); +void (*ruby_finalize)(void); +ID (*rb_intern)(const char*); +VALUE (*rb_raise)(VALUE, const char*, ...); +VALUE (*rb_funcall)(VALUE, ID, int, ...); +VALUE (*rb_define_module)(const char*); +void (*rb_define_singleton_method)(VALUE, const char*, VALUE(*)(...), int); +void (*rb_define_const)(VALUE, const char*, VALUE); +void (*rb_load_protect)(VALUE, int, int*); +VALUE (*rb_gv_get)(const char*); +VALUE (*rb_str_new)(const char*, long); +VALUE (*rb_str_new2)(const char*); +char* (*rb_string_value_ptr)(VALUE*); +VALUE (*rb_eval_string_protect)(const char*, int*); +VALUE (*rb_ary_shift)(VALUE); +VALUE (*rb_float_new)(double); +double (*rb_num2dbl)(VALUE); +VALUE (*rb_int2inum)(long); +VALUE (*rb_uint2inum)(unsigned long); +unsigned long (*rb_num2ulong)(VALUE); +// end of rip(ruby.h) + +DFHack::DFLibrary *libruby_handle; + +// load the ruby library, initialize function pointers +static int df_loadruby(void) +{ + const char *libpath = +#ifdef WIN32 + "msvcrt-ruby191.dll"; +#else + "libruby1.9.so.1.9.0"; +#endif + + libruby_handle = OpenPlugin(libpath); + if (!libruby_handle) + return 0; + + if (!(rb_eRuntimeError = (VALUE*)LookupPlugin(libruby_handle, "rb_eRuntimeError"))) + return 0; + + // XXX does msvc support decltype ? might need a #define decltype typeof + // or just assign to *(void**)(&s) = ... + // ruby_sysinit is optional (ruby1.9 only) + ruby_sysinit = (decltype(ruby_sysinit))LookupPlugin(libruby_handle, "ruby_sysinit"); +#define rbloadsym(s) if (!(s = (decltype(s))LookupPlugin(libruby_handle, #s))) return 0 + rbloadsym(ruby_init); + rbloadsym(ruby_init_loadpath); + rbloadsym(ruby_script); + rbloadsym(ruby_finalize); + rbloadsym(rb_intern); + rbloadsym(rb_raise); + rbloadsym(rb_funcall); + rbloadsym(rb_define_module); + rbloadsym(rb_define_singleton_method); + rbloadsym(rb_define_const); + rbloadsym(rb_load_protect); + rbloadsym(rb_gv_get); + rbloadsym(rb_str_new); + rbloadsym(rb_str_new2); + rbloadsym(rb_string_value_ptr); + rbloadsym(rb_eval_string_protect); + rbloadsym(rb_ary_shift); + rbloadsym(rb_float_new); + rbloadsym(rb_num2dbl); + rbloadsym(rb_int2inum); + rbloadsym(rb_uint2inum); + rbloadsym(rb_num2ulong); +#undef rbloadsym + + return 1; +} + +static void df_unloadruby(void) +{ + if (libruby_handle) { + ClosePlugin(libruby_handle); + libruby_handle = 0; + } +} + + // ruby thread code static void dump_rb_error(void) { @@ -353,7 +465,7 @@ static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr) */ static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr) { - rb_raise(rb_eRuntimeError, "not implemented"); + rb_raise(*rb_eRuntimeError, "not implemented"); } static VALUE rb_dfget_global_address(VALUE self, VALUE name) @@ -402,7 +514,7 @@ static VALUE rb_dfmalloc(VALUE self, VALUE len) { char *ptr = (char*)malloc(FIX2INT(len)); if (!ptr) - rb_raise(rb_eRuntimeError, "no memory"); + rb_raise(*rb_eRuntimeError, "no memory"); memset(ptr, 0, FIX2INT(len)); return rb_uint2inum((long)ptr); } From bc734619b473796fad36bd84e139a7752ddf14de Mon Sep 17 00:00:00 2001 From: jj Date: Sun, 10 Jun 2012 01:45:30 +0200 Subject: [PATCH 06/11] ruby: use ruby1.9.1 on linux --- plugins/ruby/ruby.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 189fc7a3b..863b06c0c 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -153,6 +153,7 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch SCASE(VIEWSCREEN_CHANGED); SCASE(CORE_INITIALIZED); SCASE(BEGIN_UNLOAD); +#undef SCASE } return plugin_eval_rb(cmd); @@ -252,7 +253,7 @@ static int df_loadruby(void) #ifdef WIN32 "msvcrt-ruby191.dll"; #else - "libruby1.9.so.1.9.0"; + "./libruby-1.9.1.so.1.9.1"; #endif libruby_handle = OpenPlugin(libpath); @@ -330,6 +331,13 @@ static void df_rubythread(void *p) { int state, running; + if (ruby_sysinit) { + // ruby1.9 specific API + static int argc; + static const char *argv[] = { "dfhack", 0 }; + ruby_sysinit(&argc, (const char ***)&argv); + } + // initialize the ruby interpreter ruby_init(); ruby_init_loadpath(); From 7ee8d79014fb6de9e05f74f010050b79f383a404 Mon Sep 17 00:00:00 2001 From: jj Date: Sun, 10 Jun 2012 02:06:22 +0200 Subject: [PATCH 07/11] ruby: download libruby with cmake --- plugins/ruby/CMakeLists.txt | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index bcd10ac89..d2d4d3167 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -1,9 +1,31 @@ +OPTION(DL_RUBY "download libruby from the internet" ON) +IF (DL_RUBY) + IF (UNIX) + FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/libruby-1.9.1.so.zip + ${dfhack_SOURCE_DIR}/libruby-1.9.1.so.zip + EXPECTED_MD5 f38c4d3effc547ccc60ac4620f40bd14) + execute_process(COMMAND unzip ${dfhack_SOURCE_DIR}/libruby-1.9.1.so.zip + WORKING_DIRECTORY ${dfhack_SOURCE_DIR}) + SET(RUBYLIB ${dfhack_SOURCE_DIR}/libruby-1.9.1.so.1.9.1) + ELSE (UNIX) + FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/msvcrt-ruby191.zip + ${dfhack_SOURCE_DIR}/msvcrt-ruby191.zip + EXPECTED_MD5 ed90e11e150b4b00588b66807a6276e7) + execute_process(COMMAND unzip ${dfhack_SOURCE_DIR}/msvcrt-ruby191.zip + WORKING_DIRECTORY ${dfhack_SOURCE_DIR}) + SET(RUBYLIB ${dfhack_SOURCE_DIR}/msvcrt-ruby191.dll) + ENDIF(UNIX) +ENDIF(DL_RUBY) + ADD_CUSTOM_COMMAND( 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 DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml codegen.pl ) ADD_CUSTOM_TARGET(ruby-autogen-rb ALL DEPENDS ruby-autogen.rb) + include_directories("${dfhack_SOURCE_DIR}/depends/tthread") + DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread) -install(FILES ruby.rb ruby-autogen.rb DESTINATION ${DFHACK_LIBRARY_DESTINATION}) + +install(FILES ruby.rb ruby-autogen.rb ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION}) From c0e7295f2240852f9930ba6b6084a37d52e51de4 Mon Sep 17 00:00:00 2001 From: jj Date: Sun, 10 Jun 2012 23:42:58 +0200 Subject: [PATCH 08/11] ruby: fix cmake to rebuild ruby-autogen.rb only when necessary --- plugins/ruby/CMakeLists.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index d2d4d3167..8f604e746 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -18,14 +18,16 @@ IF (DL_RUBY) ENDIF(DL_RUBY) ADD_CUSTOM_COMMAND( - OUTPUT ruby-autogen.rb + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/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 - DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml codegen.pl + DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl + COMMENT ruby-autogen.rb ) -ADD_CUSTOM_TARGET(ruby-autogen-rb ALL DEPENDS ruby-autogen.rb) +ADD_CUSTOM_TARGET(ruby-autogen-rb DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb) include_directories("${dfhack_SOURCE_DIR}/depends/tthread") DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread) +ADD_DEPENDENCIES(ruby ruby-autogen-rb) install(FILES ruby.rb ruby-autogen.rb ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION}) From 3c1cb24d9f94ca0fe678e4f91f1dee80cd73eb6e Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 12 Jun 2012 18:51:33 +0200 Subject: [PATCH 09/11] ruby: download lib from github as tgz, switch to ruby18, simply log to stderr.log if cannot load libruby --- plugins/ruby/CMakeLists.txt | 28 ++++++++++++++-------------- plugins/ruby/README | 11 ++++++++--- plugins/ruby/ruby.cpp | 25 +++++++++++++++++-------- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index 8f604e746..8c5c406bf 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -1,19 +1,19 @@ OPTION(DL_RUBY "download libruby from the internet" ON) IF (DL_RUBY) IF (UNIX) - FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/libruby-1.9.1.so.zip - ${dfhack_SOURCE_DIR}/libruby-1.9.1.so.zip - EXPECTED_MD5 f38c4d3effc547ccc60ac4620f40bd14) - execute_process(COMMAND unzip ${dfhack_SOURCE_DIR}/libruby-1.9.1.so.zip - WORKING_DIRECTORY ${dfhack_SOURCE_DIR}) - SET(RUBYLIB ${dfhack_SOURCE_DIR}/libruby-1.9.1.so.1.9.1) + FILE(DOWNLOAD http://github.com/downloads/jjyg/dfhack/libruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/libruby187.tar.gz + EXPECTED_MD5 eb2adea59911f68e6066966c1352f291) + EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf libruby187.tar.gz + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + FILE(RENAME libruby1.8.so.1.8.7 libruby.so) + SET(RUBYLIB libruby.so) ELSE (UNIX) - FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/msvcrt-ruby191.zip - ${dfhack_SOURCE_DIR}/msvcrt-ruby191.zip - EXPECTED_MD5 ed90e11e150b4b00588b66807a6276e7) - execute_process(COMMAND unzip ${dfhack_SOURCE_DIR}/msvcrt-ruby191.zip - WORKING_DIRECTORY ${dfhack_SOURCE_DIR}) - SET(RUBYLIB ${dfhack_SOURCE_DIR}/msvcrt-ruby191.dll) + FILE(DOWNLOAD http://github.com/downloads/jjyg/dfhack/msvcrtruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/msvcrtruby187.tar.gz + EXPECTED_MD5 9f4a1659ac3a5308f32d3a1937bbeeae) + EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf msvcrtruby187.tar.gz + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + FILE(RENAME msvcrt-ruby18.dll libruby.dll) + SET(RUBYLIB libruby.dll) ENDIF(UNIX) ENDIF(DL_RUBY) @@ -25,9 +25,9 @@ ADD_CUSTOM_COMMAND( ) ADD_CUSTOM_TARGET(ruby-autogen-rb DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb) -include_directories("${dfhack_SOURCE_DIR}/depends/tthread") +INCLUDE_DIRECTORIES("${dfhack_SOURCE_DIR}/depends/tthread") DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread) ADD_DEPENDENCIES(ruby ruby-autogen-rb) -install(FILES ruby.rb ruby-autogen.rb ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION}) +INSTALL(FILES ruby.rb ruby-autogen.rb ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION}) diff --git a/plugins/ruby/README b/plugins/ruby/README index 25b2781bd..9dc7d49f6 100644 --- a/plugins/ruby/README +++ b/plugins/ruby/README @@ -11,8 +11,7 @@ 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 (eg df.world). +Global objects are accessible through the 'df' accessor (eg df.world). The ruby plugin defines 2 dfhack console commands: rb_load ; load a ruby script. Ex: rb_load hack/plants.rb (no quotes) @@ -21,6 +20,12 @@ 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. +If dfhack reports 'rb_eval is not a recognized command', check stderr.log. You +need a valid 32-bit ruby library to work, and ruby1.8 is prefered (ruby1.9 may +crash DF on startup for now). Install the library in the df root folder (or +hack/ on linux), the library should be named 'libruby.dll' (.so on linux). +You can download a tested version at http://github.com/jjyg/dfhack/downloads/ + 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 flooding the console' } @@ -33,7 +38,7 @@ The same mechanism is available for onstatechange. Exemples -------- -For more complex exemples, check the ruby/plugins/ folder. +For more complex exemples, check the ruby/plugins/ source folder. Show info on the currently selected unit ('v' or 'k' DF menu) p df.find_unit.flags1 diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 863b06c0c..49119c9aa 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -45,10 +45,10 @@ DFHACK_PLUGIN("ruby") DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - if (!df_loadruby()) { - Core::printerr("failed to load libruby\n"); - return CR_FAILURE; - } + // fail silently instead of spamming the console with 'failed to initialize' if libruby is not present + // the error is still logged in stderr.log + if (!df_loadruby()) + return CR_OK; m_irun = new tthread::mutex(); m_mutex = new tthread::mutex(); @@ -79,10 +79,11 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector lock(); if (!r_thread) return CR_OK; + m_mutex->lock(); + r_type = RB_DIE; r_command = 0; m_irun->unlock(); @@ -135,6 +136,9 @@ static command_result plugin_eval_rb(std::string &command) DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { + if (!r_thread) + return CR_OK; + if (!onupdate_active) return CR_OK; @@ -143,6 +147,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event e) { + if (!r_thread) + return CR_OK; + std::string cmd = "DFHack.onstatechange "; switch (e) { #define SCASE(s) case SC_ ## s : cmd += ":" # s ; break @@ -251,14 +258,16 @@ static int df_loadruby(void) { const char *libpath = #ifdef WIN32 - "msvcrt-ruby191.dll"; + "./libruby.dll"; #else - "./libruby-1.9.1.so.1.9.1"; + "hack/libruby.so"; #endif libruby_handle = OpenPlugin(libpath); - if (!libruby_handle) + if (!libruby_handle) { + fprintf(stderr, "Cannot initialize ruby plugin: failed to load %s\n", libpath); return 0; + } if (!(rb_eRuntimeError = (VALUE*)LookupPlugin(libruby_handle, "rb_eRuntimeError"))) return 0; From 02854483672142df8879801932dd00b436289356 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 13 Jun 2012 00:20:52 +0200 Subject: [PATCH 10/11] ruby: fix download url, cmake doesnt handle HTTP 301 --- plugins/ruby/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index 8c5c406bf..e69632e61 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -1,14 +1,14 @@ OPTION(DL_RUBY "download libruby from the internet" ON) IF (DL_RUBY) IF (UNIX) - FILE(DOWNLOAD http://github.com/downloads/jjyg/dfhack/libruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/libruby187.tar.gz + FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/libruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/libruby187.tar.gz EXPECTED_MD5 eb2adea59911f68e6066966c1352f291) EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf libruby187.tar.gz WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) FILE(RENAME libruby1.8.so.1.8.7 libruby.so) SET(RUBYLIB libruby.so) ELSE (UNIX) - FILE(DOWNLOAD http://github.com/downloads/jjyg/dfhack/msvcrtruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/msvcrtruby187.tar.gz + FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/msvcrtruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/msvcrtruby187.tar.gz EXPECTED_MD5 9f4a1659ac3a5308f32d3a1937bbeeae) EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf msvcrtruby187.tar.gz WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) From c364b4204997af81b6b419fa245ed22c1c56f389 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 13 Jun 2012 00:21:23 +0200 Subject: [PATCH 11/11] fix minor gcc warning --- library/Console-linux.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Console-linux.cpp b/library/Console-linux.cpp index 3c0ad8938..24d13f42b 100644 --- a/library/Console-linux.cpp +++ b/library/Console-linux.cpp @@ -255,7 +255,7 @@ namespace DFHack void color(Console::color_value index) { if(!rawmode) - fprintf(dfout_C,getANSIColor(index)); + fprintf(dfout_C, "%s", getANSIColor(index)); else { const char * colstr = getANSIColor(index); @@ -761,4 +761,4 @@ void Console::msleep (unsigned int msec) { if (msec > 1000) sleep(msec/1000000); usleep((msec % 1000000) * 1000); -} \ No newline at end of file +}