4 # creates a new Struct-based class, takes the following
7 # :db - pre-created LWES::TypeDB object
8 # this is required unless :file is given
9 # :file - pathname to the ESF file,
10 # this is required unless :db is given
11 # :class - Ruby base class name, if the ESF file only has one
12 # event defined (besides MetaEventInfo), then specifying
13 # it is optional, otherwise it is required when multiple
14 # events are defined in the same ESF :file given above
15 # :parent - parent class or module, the default is 'Object' putting
16 # the new class in the global namespace.
17 # :name - event name if it differs from the Ruby base class name
18 # given (or inferred) above. For DRY-ness, you are
19 # recommended to keep your event names and Ruby class
20 # names in sync and not need this option.
21 # :skip - Array of field names to skip from the Event defininition
22 # entirely, these could include fields that are only
23 # implemented by the Listener. This may also be a
25 # :defaults - hash of default key -> value pairs to set at
28 def self.new(options, &block)
31 file = options[:file] or
32 raise ArgumentError, "TypeDB :db or ESF :file missing"
33 File.readable?(file) or
34 raise ArgumentError, "file #{file.inspect} not readable"
38 klass = options[:class] || begin
39 # make it easier to deal with single event files
40 events = (dump.keys - [ :MetaEventInfo ])
43 "multiple event defs available: #{events.inspect}\n" \
44 "pick one with :class"
48 name = options[:name] || klass
49 parent = options[:parent] || Object
50 event_def = dump[name.to_sym] or
51 raise RuntimeError, "#{name.inspect} not defined in #{file}"
53 # merge MetaEventInfo fields in
54 meta_event_info = dump[:MetaEventInfo]
55 alpha = proc { |a,b| a.first.to_s <=> b.first.to_s }
56 event_def = event_def.sort(&alpha)
58 seen = event_def.map { |(field, _)| field }
59 meta_event_info.sort(&alpha).each do |field_type|
60 seen.include?(field_type.first) or event_def << field_type
64 Array(options[:skip]).each do |x|
66 event_def.delete_if { |(f,_)| x =~ f.to_s }
68 if x.to_sym == :MetaEventInfo
69 meta_event_info.nil? and
70 raise RuntimeError, "MetaEventInfo not defined in #{file}"
71 meta_event_info.each do |(field,_)|
72 event_def.delete_if { |(f,_)| field == f }
75 event_def.delete_if { |(f,_)| f == x.to_sym }
80 tmp = ::Struct.new(*(event_def.map { |(field,_)| field }))
81 components = klass.to_s.split(/::/)
82 components.each_with_index do |component, i|
83 if i == (components.size - 1)
84 tmp = parent.const_set(component, tmp)
87 parent.const_get(component)
89 eval "module #{parent.name}::#{component}; end"
90 parent.const_get(component)
94 tmp.const_set :TYPE_DB, db
95 tmp.const_set :NAME, name.to_s
96 ed = tmp.const_set :EVENT_DEF, {}
97 event_def.each { |(field,type)| ed[field] = type }
99 # freeze since emitter.c can segfault if this ever changes
100 type_list = event_def.map do |(field,type)|
101 [ field, field.to_s.freeze, type ].freeze
103 tmp.const_set :TYPE_LIST, type_list
105 aref_map = tmp.const_set :AREF_MAP, {}
106 type_list.each_with_index do |(field_sym,field_str,_),idx|
107 aref_map[field_sym] = aref_map[field_str] = idx
110 tmp.const_set :HAVE_ENCODING,
111 type_list.include?([ :enc, 'enc', LWES::INT_16 ])
113 defaults = options[:defaults] || {}
114 defaults = tmp.const_set :DEFAULTS, defaults.dup
115 tmp.class_eval(&block) if block_given?
117 # define a parent-level method, eval is faster than define_method
124 if Hash === (init = args.first)
126 DEFAULTS.merge(init).each_pair { |k,v| rv[k] = v }
130 DEFAULTS.each_pair { |k,v| rv[k] ||= v }
138 # avoid linear scans for large structs, not sure if 50 is a good enough
139 # threshold but it won't help for anything <= 10 since Ruby (or at least
140 # MRI) already optimizes those cases
141 if event_def.size > 50
147 __aref(key.kind_of?(Fixnum) ? key : AREF_MAP[key])
151 __aset(key.kind_of?(Fixnum) ? key : AREF_MAP[key], value)
156 event_def.each_with_index do |(fld,_), idx|
158 if idx != aref_map[fld]
159 raise LoadError, "event_def corrupt: #{event_def}"
161 fast_methods << "undef_method :#{fld}, :#{fld}=\n"
162 fast_methods << "\ndef #{fld}; __aref #{idx}; end\n"
163 fast_methods << "\ndef #{fld}=(val); __aset #{idx}, val ; end\n"
166 eval("class ::#{tmp.name}; #{fast_methods.join("\n")}\n end")