Module: Hegemon

Defined in:
lib/hegemon.rb

Instance Method Summary collapse

Instance Method Details

#block_until_state(s, throttle = 0) ⇒ Object

Sleep the current thread until the current state is equal to state

state

The symbol to compare against the current state against

throttle = 0

The amount of time to sleep between state checks, 0 by default.

Raises:

  • (ArgumentError)


189
190
191
192
193
194
195
196
# File 'lib/hegemon.rb', line 189

def block_until_state(s, throttle = 0); # :args: state, throttle = 0
  raise ArgumentError, "Cannot block until undefined state :#{s}" \
    unless @_hegemon_states.keys.include? s
  
  @_hegemon_transition_lock ||= Monitor.new
  sleep 0 until @_hegemon_transition_lock.synchronize { @_hegemon_state==s }

nil end

#declare_state(state, &block) ⇒ Object

Declare a state in the state machine.

Returns the HegemonState object created.

state

The state name to use, as a symbol

block

The state’s declarative block.

All code within gets evaluated not in its original binding, but in the context of a new HegemonState instance object.

For stable use, only call methods in this block that are listed in HegemonState@Declarative+Methods.

The state and associated state machine data and methods will be associated with the object referred to by self object in the context in which the declarative method is called.

This means that in typical usage, the declarative methods listed in Hegemon@Declarative+Methods (including declare_state) should be called only from within an instance method, such as initialize.

The following example creates a skeleton state machine in each MyStateMachine instance object with two states: :working and :idle.

require 'hegemon'

class MyStateMachine
  include Hegemon

  def initialize
    declare_state :working do
      # various HegemonState Declarative Methods
    end
    declare_state :idle do
      # various HegemonState Declarative Methods
    end
  end
end


67
68
69
70
# File 'lib/hegemon.rb', line 67

def declare_state(state, &block)
  @_hegemon_states ||= Hash.new
  @_hegemon_states[state] = HegemonState.new(self, state, &block)
end

#do_state_tasks(iter_num = 0) ⇒ Object

Perform all HegemonState#tasks associated with the current state.

Tasks are performed in the order they were declared in the HegemonState declarative block.

iter_num = 0

Specify the iteration number to be passed to the task block. When called by the hegemon_auto_thread (see start_hegemon_auto_thread), this number counts up from zero for each iteration of the hegemon_auto_thread loop. If no value is specified, 0 is used.



209
210
211
212
# File 'lib/hegemon.rb', line 209

def do_state_tasks(iter_num = 0)
  return nil unless @_hegemon_states[@_hegemon_state]
  @_hegemon_states[@_hegemon_state].do_tasks(iter_num)
nil end

#end_hegemon_auto_threadObject

Raise a flag to stop the loop inside hegemon_auto_thread.



270
271
272
273
274
# File 'lib/hegemon.rb', line 270

def end_hegemon_auto_thread
  @_end_hegemon_auto_thread = true
  @_hegemon_auto_thread.join unless @_hegemon_auto_thread==Thread.current
  @_hegemon_auto_thread     = nil
end

#impose_state(s) ⇒ Object

Bypass all transition requirements and actions to directly impose state state as the current state.

This should not be used in the public API except to set the initial state of the state machine, because state changes imposed by impose_state do not obey any rules of the state machine.

state

The state to impose, as a symbol

This method should be called in the same scope in which the state was declared with declare_state.

The following example creates a skeleton state machine in each MyStateMachine instance object with two states: :working and :idle and sets :working as the initial state.

require 'hegemon'

class MyStateMachine
  include Hegemon

  def initialize
    impose_state :working
    declare_state :working do
      # various HegemonState Declarative Methods
    end
    declare_state :idle do
      # various HegemonState Declarative Methods
    end
  end
end


107
108
109
# File 'lib/hegemon.rb', line 107

def impose_state(s) # :args: state
  @_hegemon_state = s
nil end

#join_hegemon_auto_threadObject

Block until the hegemon_auto_thread is finished.



263
264
265
# File 'lib/hegemon.rb', line 263

def join_hegemon_auto_thread
  @_hegemon_auto_thread.join if @_hegemon_auto_thread
end

#request_state(s, *flags) ⇒ Object

Request a transition from the current state to state state.

Returns true if the transition was performed, else returns false.

state

The state to which transition is desired, as a symbol

flags

All subsequent arguments are interpreted as flags, and the only meaningful flag is :force (See transition requirements below).

In order for the transition to occur, the transition must have been defined (using HegemonState#transition_to), and the transition rules declared in the HegemonTransition declarative block have been suitably met by any one of the following situations:

  • All HegemonTransition#requirement blocks evaluate as true and the :force flag was included in flags.

  • At least one HegemonTransition#sufficient block and all HegemonTransition#requirement blocks evaluate as true

  • All HegemonTransition#condition blocks and all HegemonTransition#requirement blocks evaluate as true

Note that evaluation of the rule blocks stops when a match is found, so rule blocks with code that has “side effects” are discouraged.



143
144
145
146
147
# File 'lib/hegemon.rb', line 143

def request_state(s, *flags) # :args: state, *flags
  return true if @_hegemon_state==s
  return false unless @_hegemon_states[@_hegemon_state].transitions[s]
  @_hegemon_states[@_hegemon_state].transitions[s].try(*flags)
end

#start_hegemon_auto_thread(throttle = nil) ⇒ Object

Run the hegemon_auto_thread if it is not already running.

Returns the Thread object.

The hegemon_auto_thread continually calls do_state_tasks and update_state, counting up from 0 the value passed to do_state_tasks, until the thread is stopped with end_hegemon_auto_thread.

throttle = nil

The amount of time to sleep between iterations.

If left as nil, the last given throttle value is used. The default throttle value is 0.05 seconds.



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/hegemon.rb', line 240

def start_hegemon_auto_thread(throttle = nil)
  if (not @_hegemon_auto_thread) \
  or (not @_hegemon_auto_thread.status)
    
    @_end_hegemon_auto_thread = false
    @_hegemon_auto_thread_throttle ||= throttle
    @_hegemon_auto_thread_throttle ||= 0.05
    @_hegemon_auto_thread = Thread.new do
      i = 0
      until @_end_hegemon_auto_thread
        iter_hegemon_auto_loop(i)
        i += 1
        sleep @_hegemon_auto_thread_throttle \
          unless @_end_hegemon_auto_thread
      end
    end
  end
  @_hegemon_auto_thread
end

#stateObject

Return the current state (as a symbol)



12
# File 'lib/hegemon.rb', line 12

def state;      @_hegemon_state;                   end

#state_objObject

Return the current state (as a HegemonState object)



16
# File 'lib/hegemon.rb', line 16

def state_obj;  @_hegemon_states[@_hegemon_state]; end

#state_objsObject

Return the current state (as a Hash of HegemonState objects keyed by symbol)



18
# File 'lib/hegemon.rb', line 18

def state_objs; @_hegemon_states.clone;            end

#statesObject

Return the list of declared states (as an array of symbols)



14
# File 'lib/hegemon.rb', line 14

def states;     @_hegemon_states.keys;             end

#update_state(*flags) ⇒ Object

Check the list of possible transitions from the current state and perform the first transition that is found to be ready, if any.

Returns true if the transition was performed, else returns false.

flags

All subsequent arguments are interpreted as flags, and the only meaningful flag is :only_auto.

Use of the :only_auto flag indicates that all state transitions which have disabled automatic updating with HegemonTransition#auto_update should be ignored.

In order for a transition to occur, the transition rules declared in the HegemonTransition declarative block have been suitably met by any one of the following situations:

  • All HegemonTransition#requirement blocks evaluate as true and the :force flag was included in flags.

  • At least one HegemonTransition#sufficient block and all HegemonTransition#requirement blocks evaluate as true

  • All HegemonTransition#condition blocks and all HegemonTransition#requirement blocks evaluate as true



172
173
174
175
176
177
# File 'lib/hegemon.rb', line 172

def update_state(*flags)
  return false unless @_hegemon_states[@_hegemon_state]
  trans = @_hegemon_states[@_hegemon_state].transitions
  trans = trans.select{|k,t| t.auto_update} if (flags.include? :only_auto)
  trans.each {|k,t| return true if t.try}
false end