module Listen::FSM
Attributes
state[R]
Current state of the FSM
, stored as a symbol
Public Class Methods
included(klass)
click to toggle source
Included hook to extend class methods
# File lib/listen/fsm.rb, line 10 def self.included(klass) klass.send :extend, ClassMethods end
Public Instance Methods
initialize_fsm()
click to toggle source
Note: including classes must call initialize_fsm
from their initialize method.
# File lib/listen/fsm.rb, line 42 def initialize_fsm @fsm_initialized = true @state = self.class.start_state @mutex = ::Mutex.new @state_changed = ::ConditionVariable.new end
wait_for_state(*wait_for_states, timeout: nil)
click to toggle source
checks for one of the given states to wait for if not already, waits for a state change (up to timeout seconds–‘nil` means infinite) returns truthy iff the transition to one of the desired state has occurred
# File lib/listen/fsm.rb, line 55 def wait_for_state(*wait_for_states, timeout: nil) wait_for_states.each do |state| state.is_a?(Symbol) or raise ArgumentError, "states must be symbols (got #{state.inspect})" end @mutex.synchronize do if !wait_for_states.include?(@state) @state_changed.wait(@mutex, timeout) end wait_for_states.include?(@state) end end
Private Instance Methods
current_state()
click to toggle source
# File lib/listen/fsm.rb, line 108 def current_state self.class.states[@state] end
transition(new_state_name)
click to toggle source
# File lib/listen/fsm.rb, line 69 def transition(new_state_name) new_state_name.is_a?(Symbol) or raise ArgumentError, "state name must be a Symbol (got #{new_state_name.inspect})" if (new_state = validate_and_sanitize_new_state(new_state_name)) transition_with_callbacks!(new_state) end end
transition!(new_state_name) { || ... }
click to toggle source
Low-level, immediate state transition with no checks or callbacks.
# File lib/listen/fsm.rb, line 77 def transition!(new_state_name) new_state_name.is_a?(Symbol) or raise ArgumentError, "state name must be a Symbol (got #{new_state_name.inspect})" @fsm_initialized or raise ArgumentError, "FSM not initialized. You must call initialize_fsm from initialize!" @mutex.synchronize do yield if block_given? @state = new_state_name @state_changed.broadcast end end
transition_with_callbacks!(new_state)
click to toggle source
# File lib/listen/fsm.rb, line 103 def transition_with_callbacks!(new_state) transition! new_state.name new_state.call(self) end
validate_and_sanitize_new_state(new_state_name)
click to toggle source
# File lib/listen/fsm.rb, line 87 def validate_and_sanitize_new_state(new_state_name) return nil if @state == new_state_name if current_state && !current_state.valid_transition?(new_state_name) valid = current_state.transitions.map(&:to_s).join(', ') msg = "#{self.class} can't change state from '#{@state}' to '#{new_state_name}', only to: #{valid}" raise ArgumentError, msg end unless (new_state = self.class.states[new_state_name]) new_state_name == self.class.start_state or raise ArgumentError, "invalid state for #{self.class}: #{new_state_name}" end new_state end