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