Class: LWES::Struct

Inherits:
Object
  • Object
show all
Extended by:
ClassMaker
Defined in:
lib/lwes/struct.rb

Overview

LWES Events in an ESF file can be automatically turned into Ruby ::Struct-based objects and emitted directly through LWES::Emitter.

LWES::Struct is may be more memory-efficient and faster if your application uses all or most of the fields in the event definition. LWES::Event should be used in cases where many event fields are unused in an event definition.

LWES::Struct is created by LWES::TypeDB#create_classes! by default where the :sparse flag is false. There is little need to use this class directly.

Class Method Summary collapse

Methods included from ClassMaker

class_for, set_constants, type_db

Class Method Details

.new(options, &block) ⇒ Object

There is usually no need to use this method, LWES::TypeDB.create_classes! will call this for you

Creates a new Struct-based class, takes the following options hash:

:db       - pre-created LWES::TypeDB object
            this is required unless :file is given
:file     - pathname to the ESF file,
            this is required unless :db is given
:class    - Ruby base class name, if the ESF file only has one
            event defined (besides MetaEventInfo), then specifying
            it is optional, otherwise it is required when multiple
            events are defined in the same ESF :file given above
:parent   - parent class or module, the default is 'Object' putting
            the new class in the global namespace.  May be +nil+ for
            creating anonymous classes
:name     - event name if it differs from the Ruby base class name
            given (or inferred) above.  For DRY-ness, you are
            recommended to keep your event names and Ruby class
            names in sync and not need this option.
:skip     - Array of field names to skip from the Event defininition
            entirely, these could include fields that are only
            implemented by the Listener.  This may also be a
            regular expression.
:defaults - hash of default key -> value pairs to set at
            creation time


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/lwes/struct.rb', line 42

def self.new(options, &block)
  db = type_db(options)
  dump = db.to_hash
  klass, name, event_def = class_for(options, dump)

  # merge MetaEventInfo fields in
  meta_event_info = dump[:MetaEventInfo]
  alpha = proc { |a,b| a.first.to_s <=> b.first.to_s }
  event_def = event_def.sort(&alpha)
  if meta_event_info
    seen = event_def.map { |(field, _)| field }
    meta_event_info.sort(&alpha).each do |field_type|
      seen.include?(field_type.first) or event_def << field_type
    end
  end

  Array(options[:skip]).each do |x|
    if Regexp === x
      event_def.delete_if { |(f,_)| x =~ f.to_s }
    else
      if x.to_sym == :MetaEventInfo
        meta_event_info.nil? and
          raise RuntimeError, "MetaEventInfo not defined in #{file}"
        meta_event_info.each do |(field,_)|
          event_def.delete_if { |(f,_)| field == f }
        end
      else
        event_def.delete_if { |(f,_)| f == x.to_sym }
      end
    end
  end

  tmp = ::Struct.new(*(event_def.map { |(field,_)| field }))
  set_constants(tmp, db, klass, name, options)
  ed = tmp.const_set :EVENT_DEF, {}
  event_def.each { |(field,type)| ed[field] = type }

  # freeze since emitter.c can segfault if this ever changes
  type_list = event_def.map do |(field,type)|
    [ field, field.to_s.freeze, type ].freeze
  end.freeze
  tmp.const_set :TYPE_LIST, type_list

  aref_map = tmp.const_set :AREF_MAP, {}
  type_list.each_with_index do |(field_sym,field_str,_),idx|
    aref_map[field_sym] = aref_map[field_str] = idx
  end

  tmp.const_set :HAVE_ENCODING,
                type_list.include?([ :enc, 'enc', LWES::INT_16 ])

  tmp.class_eval(&block) if block_given?

  # define a parent-level method, eval is faster than define_method
  tmp.class_eval <<EOS
class << self
alias _new new
undef_method :new
def new(*args)
  if Hash === (init = args.first)
    rv = _new()
    DEFAULTS.merge(init).each_pair { |k,v| rv[k] = v }
    rv
  else
    rv = _new(*args)
    DEFAULTS.each_pair { |k,v| rv[k] ||= v }
    rv
  end
end
end
EOS

# avoid linear scans for large structs, not sure if 50 is a good enough
# threshold but it won't help for anything <= 10 since Ruby (or at least
# MRI) already optimizes those cases
if event_def.size > 50
  tmp.class_eval <<EOS
alias __aref []
alias __aset []=
def [](key)
__aref(key.kind_of?(Fixnum) ? key : AREF_MAP[key])
end

def []=(key, value)
__aset(key.kind_of?(Fixnum) ? key : AREF_MAP[key], value)
end
EOS
  fast_methods = []
  event_def.each_with_index do |(fld,_), idx|
    next if idx <= 9
    if idx != aref_map[fld]
      raise LoadError, "event_def corrupt: #{event_def}"
    end
    fast_methods << "undef_method :#{fld}, :#{fld}=\n"
    fast_methods << "\ndef #{fld}; __aref #{idx}; end\n"
    fast_methods << "\ndef #{fld}=(val); __aset #{idx}, val ; end\n"
  end

  tmp.class_eval fast_methods.join("\n")
end

tmp
end