lib/surface_master/interaction.rb in surface_master-0.2.0 vs lib/surface_master/interaction.rb in surface_master-0.2.1

- old
+ new

@@ -1,7 +1,8 @@ module SurfaceMaster - # Base class for event-based drivers. Sub-classes should extend the constructor, and implement `respond_to_action` + # Base class for event-based drivers. Sub-classes should extend the constructor, and implement + # `respond_to_action`, etc. class Interaction include Logging attr_reader :device, :active @@ -10,14 +11,13 @@ self.logger = opts[:logger] logger.debug "Initializing #{self.class}##{object_id} with #{opts.inspect}" @use_threads = opts[:use_threads] || true - @device = opts[:device] - @device ||= @device_class.new(opts.merge(input: true, - output: true, - logger: opts[:logger])) + @device = opts[:device] || @device_class.new(opts.merge(input: true, + output: true, + logger: opts[:logger])) @latency = (opts[:latency] || 0.001).to_f.abs @active = false @action_threads = ThreadGroup.new end @@ -34,39 +34,14 @@ def closed?; @device.closed?; end def start(opts = nil) logger.debug "Starting #{self.class}##{object_id}" - opts = { detached: false }.merge(opts || {}) + opts = { detached: false }.merge(opts || {}) + @active = true + @reader_thread ||= create_reader_thread - @active = true - - @reader_thread ||= Thread.new do - begin - while @active do - @device.read.each do |action| - if @use_threads - action_thread = Thread.new(action) do |act| - respond_to_action(act) - end - @action_threads.add(action_thread) - else - respond_to_action(action) - end - end - sleep @latency# if @latency > 0.0 - end - rescue Portmidi::DeviceError => e - logger.fatal "Could not read from device, stopping reader!" - raise SurfaceMaster::CommunicationError.new(e) - rescue Exception => e - logger.fatal "Unkown error, stopping reader: #{e.inspect}" - raise e - ensure - @device.reset! - end - end @reader_thread.join unless opts[:detached] end def stop logger.debug "Stopping #{self.class}##{object_id}" @@ -76,58 +51,109 @@ @reader_thread.run if @reader_thread.alive? @reader_thread.join @reader_thread = nil end ensure - @action_threads.list.each do |thread| - begin - thread.kill - thread.join - rescue StandardException => e # TODO: RuntimeError, Exception, or this? - logger.error "Error when killing action thread: #{e.inspect}" - end - end + kill_action_threads! nil end def response_to(types = :all, state = :both, opts = nil, &block) - logger.debug "Setting response to #{types.inspect} for state #{state.inspect} with #{opts.inspect}" + logger.debug "Setting response to #{types.inspect} for state #{state.inspect} with"\ + " #{opts.inspect}" types = Array(types) opts ||= {} no_response_to(types, state) if opts[:exclusive] == true - Array(state == :both ? %i(down up) : state).each do |st| - types.each do |type| - combined_types(type, opts).each do |combined_type| - responses[combined_type][st] << block - end - end + expand_states(state).each do |st| + add_response_for_state!(types, opts, st, block) end nil end def no_response_to(types = nil, state = :both, opts = nil) logger.debug "Removing response to #{types.inspect} for state #{state.inspect}" types = Array(types) - Array(state == :both ? %i(down up) : state).each do |st| - types.each do |type| - combined_types(type, opts).each do |combined_type| - responses[combined_type][st].clear - end - end + expand_states(state).each do |st| + clear_responses_for_state!(types, opts, st) end nil end def respond_to(type, state, opts = nil) respond_to_action((opts || {}).merge(type: type, state: state)) end protected + def create_reader_thread + Thread.new do + guard_input_and_reset_at_end! do + while @active + @device.read.each { |action| handle_action(action) } + sleep @latency if @latency && @latency > 0.0 + end + end + end + end + + def guard_input_and_reset_at_end!(&block) + block.call + rescue Portmidi::DeviceError => e + logger.fatal "Could not read from device, stopping reader!" + raise SurfaceMaster::CommunicationError, e + rescue Exception => e + logger.fatal "Unkown error, stopping reader: #{e.inspect}" + raise e + ensure + @device.reset! + end + + def kill_action_threads! + @action_threads.list.each do |thread| + begin + thread.kill + thread.join + rescue StandardException => e # TODO: RuntimeError, Exception, or this? + logger.error "Error when killing action thread: #{e.inspect}" + end + end + end + + def add_response_for_state!(types, opts, state, block) + response_groups_for(types, opts, state) do |responses| + responses << block + end + end + + def clear_responses_for_state!(types, opts, state) + response_groups_for(types, opts, state, &:clear) + end + + def response_groups_for(types, opts, state, &block) + types.each do |type| + combined_types(type, opts).each do |combined_type| + block.call(responses[combined_type][state]) + end + end + end + + def expand_states(state); Array(state == :both ? %i(down up) : state); end + + def handle_action(action) + if @use_threads + action_thread = Thread.new(action) do |act| + respond_to_action(act) + end + @action_threads.add(action_thread) + else + respond_to_action(action) + end + end + def responses # TODO: Generalize for arbitrary actions... @responses ||= Hash.new { |hash, key| hash[key] = { down: [], up: [] } } end - def respond_to_action(action); end + def respond_to_action(_action); end end end