class FlatBuffers::Serializer::TableSerializer

def add_field(field, value)

def add_field(field, value)
  if value.nil?
    @field_metadata[field] = {
      inline: true,
      offset: nil,
    }
  else
    case field.base_type
    when String
      add_field_object(field, value)
    when Array
      add_field_array(field, value)
    when :string
      align!(@table, View::OFFSET_SIZE)
      @field_metadata[field] = {
        inline: false,
        table_offset: @table.bytesize,
        value_offset: @values.bytesize,
      }
      @values.append_as_bytes(pack_value(field.base_type, value))
      @table.append_as_bytes(DUMMY_OFFSET) # Replaced later
    else
      align!(@table, field.alignment_size)
      @field_metadata[field] = {
        inline: true,
        offset: @table.bytesize,
      }
      @table.append_as_bytes(pack_value(field.base_type, value))
    end
  end
end

def add_field_array(field, value)

def add_field_array(field, value)
  align!(@table, View::OFFSET_SIZE)
  align!(@values, View::OFFSET_SIZE)
  value_offset = @values.bytesize
  # The number of elements.
  @values.append_as_bytes([value.size].pack("L<"))
  element_base_type = field.base_type[0]
  case element_base_type
  when String
    klass = Object.const_get(element_base_type)
    if klass < Struct
      value.each do |v|
        sub_struct_serializer = StructSerializer.new(@values)
        klass.serialize(v, sub_struct_serializer)
      end
    else
      offset_base = @values.bytesize
      # Placeholder for offsets.
      value.size.times do
        @values.append_as_bytes(DUMMY_OFFSET) # Replaced later
      end
      align!(@values, LARGEST_ALIGNMENT_SIZE)
      value.each do |v|
        sub_table_serializer = TableSerializer.new
        sub_table_data, sub_table_offset =
        klass.serialize(v, sub_table_serializer)
        element_offset = @values.bytesize + sub_table_offset
        # Update offset placeholder.
        relative_element_offset = element_offset - offset_base
        @values[offset_base, View::OFFSET_SIZE] =
          [relative_element_offset].pack("L<")
        offset_base += View::OFFSET_SIZE
        @values.append_as_bytes(sub_table_data)
      end
    end
    @field_metadata[field] = {
      inline: false,
      table_offset: @table.bytesize,
      value_offset: value_offset,
    }
  when :string
    offset_base = @values.bytesize
    # Placeholder for offsets.
    value.size.times do
      @values.append_as_bytes(DUMMY_OFFSET) # Replaced later
    end
    value.each do |v|
      packed_value = pack_value(element_base_type, v)
      element_offset = @values.bytesize
      # Update offset placeholder.
      relative_element_offset = element_offset - offset_base
      @values[offset_base, View::OFFSET_SIZE] =
        [relative_element_offset].pack("L<")
      offset_base += View::OFFSET_SIZE
      @values.append_as_bytes(packed_value)
    end
    @field_metadata[field] = {
      inline: false,
      table_offset: @table.bytesize,
      value_offset: value_offset,
    }
  else
    value.each do |v|
      packed_value = pack_value(element_base_type, v)
      @values.append_as_bytes(packed_value)
    end
    @field_metadata[field] = {
      inline: false,
      table_offset: @table.bytesize,
      value_offset: value_offset,
    }
  end
  @table.append_as_bytes(DUMMY_OFFSET) # Replaced later
end

def add_field_object(field, value)

Struct, union or table
def add_field_object(field, value)
  align!(@table, View::OFFSET_SIZE)
  klass = Object.const_get(field.base_type)
  if klass < Struct
    @field_metadata[field] = {
      inline: true,
      offset: @table.bytesize,
    }
    sub_struct_serializer = StructSerializer.new(@table)
    klass.serialize(value, sub_struct_serializer)
  elsif klass < Union
    align!(@values, LARGEST_ALIGNMENT_SIZE)
    sub_table_serializer = TableSerializer.new
    sub_table_data, sub_table_offset =
    value.class.table_class.serialize(value, sub_table_serializer)
    @field_metadata[field] = {
      inline: false,
      table_offset: @table.bytesize,
      value_offset: @values.bytesize + sub_table_offset,
    }
    @values.append_as_bytes(sub_table_data)
    @table.append_as_bytes(DUMMY_OFFSET) # Replaced later
  else
    align!(@values, LARGEST_ALIGNMENT_SIZE)
    sub_table_serializer = TableSerializer.new
    sub_table_data, sub_table_offset =
    klass.serialize(value, sub_table_serializer)
    @field_metadata[field] = {
      inline: false,
      table_offset: @table.bytesize,
      value_offset: @values.bytesize + sub_table_offset,
    }
    @values.append_as_bytes(sub_table_data)
    @table.append_as_bytes(DUMMY_OFFSET) # Replaced later
  end
end

def finish

def finish
  vtable_size = View::VTable.compute_size(@field_metadata.size)
  align!(@table, LARGEST_ALIGNMENT_SIZE)
  table_size = @table.bytesize
  field_offsets = []
  @field_metadata.each do |field, metadata|
    if metadata[:inline] # Scalar or struct
      offset = metadata[:offset]
      if offset.nil?
        field_offsets[field.index] = 0
      else
        field_offsets[field.index] = offset
      end
    else # Table, string or vector.
      # |vtable|@table|@values|
      #
      # `vtable:` |vtable_size|table_size|field_offsets|
      # `field_offsets` uses relative offset from the start of
      # `vtable`.
      #
      # `@table`: |vtable_offset|fields|
      # `fields` uses relative offset from itself.
      # The offset in `fields`. This is relative from the start
      # of `@table`.
      table_offset = metadata[:table_offset]
      # The offset in `@values`. This is relative from the start
      # of `@table`.
      value_offset = metadata[:value_offset]
      # `@values` is placed just after `@table`.
      # `offset` is relative from `table_offset`.
      offset = table_size - table_offset + value_offset
      @table[table_offset, View::Table::VTABLE_OFFSET_SIZE] =
        [offset].pack("L<")
      field_offsets[field.index] = table_offset
    end
  end
  field_offsets.each_with_index do |offset, i|
    field_offsets[i] = 0 if offset.nil?
  end
  data = +"".b
  vtable = View::VTable.serialize(vtable_size,
                                  table_size,
                                  field_offsets)
  data.append_as_bytes(vtable)
  # We don't need "-" here because vtable_offset is subtracted
  # (not added).
  vtable_offset = vtable_size
  @table[0, View::Table::VTABLE_OFFSET_SIZE] = [vtable_offset].pack("l<")
  data.append_as_bytes(@table)
  align!(@values, LARGEST_ALIGNMENT_SIZE)
  data.append_as_bytes(@values)
  table_offset = vtable_size
  [data, table_offset]
end

def initialize

def initialize
  @field_metadata = {}
  # vtable_offset. This is replaced later.
  @table = DUMMY_OFFSET.dup
  @values = +"".b
end

def start

def start
  yield
  finish
end