rey meustrus
Sponsor
I feel special, and you should too! I've written a Table class which is designed specifically to replace the built-in Table class, right down to the ability to Marshal.load the data that came out of RMXP or anyplace else that only knew about the built-in class. I would like to thank my hex editor (0xED for Mac) for making this possible.
[rgss]#===============================================================================
# ** Table
#-------------------------------------------------------------------------------
# Rewrites the Table class. This is slower than RGSS but more portable.
#===============================================================================
class Table < Hash
#-----------------------------------------------------------------------------
# * Initialize the table with variable length arguments
#-----------------------------------------------------------------------------
def initialize(*args)
super(0) # 0 is default value
@sizes = []
resize(*args)
end
#-----------------------------------------------------------------------------
# * Set the size of (and number of) the dimensions
#-----------------------------------------------------------------------------
def resize(*args)
args.pop while args.last == 0 # Remove 0 values from end of args
if args.include?(0)
raise ArgumentError, 'Size of part of a Table cannot be zero.'
end
self.clear unless @sizes.length == args.length
@sizes = args
end
#-----------------------------------------------------------------------------
# * Returns the size at a specific depth, or nil if out of range
#-----------------------------------------------------------------------------
def size(depth)
@sizes[depth]
end
def xsize ; @sizes[0] ; end
def ysize ; @sizes[1] ; end
def zsize ; @sizes[2] ; end
#-----------------------------------------------------------------------------
# * Clear hash (reset integer-only flag as well)
#-----------------------------------------------------------------------------
def clear
super
@ints_only = true
end
#-----------------------------------------------------------------------------
# * Retrieve data
#-----------------------------------------------------------------------------
def [](*args)
# Return nil if out of range
return nil unless check_indeces(args)
super(args) # Notice the variable arguments is passed as a single array
end
#-----------------------------------------------------------------------------
# * Set data
#-----------------------------------------------------------------------------
def []=(*args)
# Remove the last argument, which is what the data will be set to
operand = args.pop
# Set @ints_only flag
@ints_only &= operand.is_a? Integer
# Return nil if out of range
return nil unless check_indeces(args)
super(args, operand)
end
#-----------------------------------------------------------------------------
# * Check arguments for size and within range
#-----------------------------------------------------------------------------
def check_indeces(args)
# Prevent different number of arguments than at creation
if args.size != @sizes.size
raise ArgumentError, "Table has #{@sizes.size} dimensions, but " +
"received #{args.size} arguments."
end
# Check if index is within range
args.each_with_index {|i, depth| return false if i < 0 or i > @sizes[depth]}
true
end
#-----------------------------------------------------------------------------
# * Dump to String (compatible with RGSS Table dump format for Integers)
#-----------------------------------------------------------------------------
def _dump(marshal_depth = -1)
integer_format = (@ints_only && @sizes.size <= 3)
# First, store information about the size
size_info = [@sizes.length, *@sizes]
size_info << 0x01 while size_info.length < 4
output = size_info.pack('I*')
# Then, store whether this is integer format or not
output += [integer_format ? 0xD2 : 0x00].pack('I')
if integer_format
# All values are integers, so store them as such
self.each { |int| output += [int].pack('s') }
else
# Copy self into a plain hash to dump
output += Marshal.dump(Hash.new.merge!(self), marshal_depth)
end
output
end
#-----------------------------------------------------------------------------
# * Load from String (compatible with RGSS Table dump format for Integers)
#-----------------------------------------------------------------------------
def self._load(data)
# Get depth of the Table
depth = data.slice!(0...4).unpack('I')[0]
# Get size of each dimension
sizes = data.slice!(0...[12, depth * 4].max).unpack('I*')[0...depth]
output = Table.new(*sizes)
# Non-integer format: load the rest as a plain Hash
if data.slice!(0...4).unpack('I')[0] == 0x00
return output.merge!(Marshal.load(data))
end
# Integer format: load each piece of data in the correct order
output.each_index { |*i| output[*i] = data.slice!(0...2).unpack('s')[0] }
return output
rescue RangeError
# Catches failed attempts to slice existing data due to out of range
raise RuntimeError, 'Load data for Table is not as long as specified. ' +
'The file may be corrupted.'
end
#-----------------------------------------------------------------------------
# * Process through each element in order
#-----------------------------------------------------------------------------
def each
self.each_index { |*index| yield self[*index] }
end
#-----------------------------------------------------------------------------
# * Process through each element in order, with index
#-----------------------------------------------------------------------------
def each_with_index
self.each_index { |*index| yield self[*index], *index }
end
#-----------------------------------------------------------------------------
# * Process a block for each in range from 0...arg, permutated thru all args
#-----------------------------------------------------------------------------
def each_index(&block) ; Table.each_index(*@sizes.dup, &block) ; end
def self.each_index(*sizes, &block)
range = 0...sizes.shift
if sizes.empty?
range.each(&block)
else
range.each { |i| self.each_index(*sizes) { |*index| yield i, *index } }
end
end
end
[/rgss]
As a plus, it works as a table for non-integers as well, in which case its Marshal dumping and loading go back somewhat to normal in order to accommodate non-integers.
Note that in the _dump function, 0x01 and 0xD2 are inserted in the size section. These are values that were part of the result of Table's original dump function, though I could not discern their purpose besides filling space. 0x01 is just filler. I'm using the space where 0xD2 is to determine the load type, so all that's important is that that value is NOT 0x00.
[rgss]#===============================================================================
# ** Table
#-------------------------------------------------------------------------------
# Rewrites the Table class. This is slower than RGSS but more portable.
#===============================================================================
class Table < Hash
#-----------------------------------------------------------------------------
# * Initialize the table with variable length arguments
#-----------------------------------------------------------------------------
def initialize(*args)
super(0) # 0 is default value
@sizes = []
resize(*args)
end
#-----------------------------------------------------------------------------
# * Set the size of (and number of) the dimensions
#-----------------------------------------------------------------------------
def resize(*args)
args.pop while args.last == 0 # Remove 0 values from end of args
if args.include?(0)
raise ArgumentError, 'Size of part of a Table cannot be zero.'
end
self.clear unless @sizes.length == args.length
@sizes = args
end
#-----------------------------------------------------------------------------
# * Returns the size at a specific depth, or nil if out of range
#-----------------------------------------------------------------------------
def size(depth)
@sizes[depth]
end
def xsize ; @sizes[0] ; end
def ysize ; @sizes[1] ; end
def zsize ; @sizes[2] ; end
#-----------------------------------------------------------------------------
# * Clear hash (reset integer-only flag as well)
#-----------------------------------------------------------------------------
def clear
super
@ints_only = true
end
#-----------------------------------------------------------------------------
# * Retrieve data
#-----------------------------------------------------------------------------
def [](*args)
# Return nil if out of range
return nil unless check_indeces(args)
super(args) # Notice the variable arguments is passed as a single array
end
#-----------------------------------------------------------------------------
# * Set data
#-----------------------------------------------------------------------------
def []=(*args)
# Remove the last argument, which is what the data will be set to
operand = args.pop
# Set @ints_only flag
@ints_only &= operand.is_a? Integer
# Return nil if out of range
return nil unless check_indeces(args)
super(args, operand)
end
#-----------------------------------------------------------------------------
# * Check arguments for size and within range
#-----------------------------------------------------------------------------
def check_indeces(args)
# Prevent different number of arguments than at creation
if args.size != @sizes.size
raise ArgumentError, "Table has #{@sizes.size} dimensions, but " +
"received #{args.size} arguments."
end
# Check if index is within range
args.each_with_index {|i, depth| return false if i < 0 or i > @sizes[depth]}
true
end
#-----------------------------------------------------------------------------
# * Dump to String (compatible with RGSS Table dump format for Integers)
#-----------------------------------------------------------------------------
def _dump(marshal_depth = -1)
integer_format = (@ints_only && @sizes.size <= 3)
# First, store information about the size
size_info = [@sizes.length, *@sizes]
size_info << 0x01 while size_info.length < 4
output = size_info.pack('I*')
# Then, store whether this is integer format or not
output += [integer_format ? 0xD2 : 0x00].pack('I')
if integer_format
# All values are integers, so store them as such
self.each { |int| output += [int].pack('s') }
else
# Copy self into a plain hash to dump
output += Marshal.dump(Hash.new.merge!(self), marshal_depth)
end
output
end
#-----------------------------------------------------------------------------
# * Load from String (compatible with RGSS Table dump format for Integers)
#-----------------------------------------------------------------------------
def self._load(data)
# Get depth of the Table
depth = data.slice!(0...4).unpack('I')[0]
# Get size of each dimension
sizes = data.slice!(0...[12, depth * 4].max).unpack('I*')[0...depth]
output = Table.new(*sizes)
# Non-integer format: load the rest as a plain Hash
if data.slice!(0...4).unpack('I')[0] == 0x00
return output.merge!(Marshal.load(data))
end
# Integer format: load each piece of data in the correct order
output.each_index { |*i| output[*i] = data.slice!(0...2).unpack('s')[0] }
return output
rescue RangeError
# Catches failed attempts to slice existing data due to out of range
raise RuntimeError, 'Load data for Table is not as long as specified. ' +
'The file may be corrupted.'
end
#-----------------------------------------------------------------------------
# * Process through each element in order
#-----------------------------------------------------------------------------
def each
self.each_index { |*index| yield self[*index] }
end
#-----------------------------------------------------------------------------
# * Process through each element in order, with index
#-----------------------------------------------------------------------------
def each_with_index
self.each_index { |*index| yield self[*index], *index }
end
#-----------------------------------------------------------------------------
# * Process a block for each in range from 0...arg, permutated thru all args
#-----------------------------------------------------------------------------
def each_index(&block) ; Table.each_index(*@sizes.dup, &block) ; end
def self.each_index(*sizes, &block)
range = 0...sizes.shift
if sizes.empty?
range.each(&block)
else
range.each { |i| self.each_index(*sizes) { |*index| yield i, *index } }
end
end
end
[/rgss]
As a plus, it works as a table for non-integers as well, in which case its Marshal dumping and loading go back somewhat to normal in order to accommodate non-integers.
Note that in the _dump function, 0x01 and 0xD2 are inserted in the size section. These are values that were part of the result of Table's original dump function, though I could not discern their purpose besides filling space. 0x01 is just filler. I'm using the space where 0xD2 is to determine the load type, so all that's important is that that value is NOT 0x00.