finite_machine 0.13.0 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -33,7 +33,7 @@ module FiniteMachine
33
33
  if block_given?
34
34
  options[:with] = block
35
35
  else
36
- raise ArgumentError, 'Need to provide error handler.'
36
+ raise ArgumentError, "Need to provide error handler."
37
37
  end
38
38
  end
39
39
  evaluate_exceptions(exceptions, options)
@@ -71,7 +71,7 @@ module FiniteMachine
71
71
  #
72
72
  # @api private
73
73
  def extract_const(class_name)
74
- class_name.split('::').reduce(FiniteMachine) do |constant, part|
74
+ class_name.split("::").reduce(FiniteMachine) do |constant, part|
75
75
  constant.const_get(part)
76
76
  end
77
77
  end
@@ -85,9 +85,9 @@ module FiniteMachine
85
85
  target.method(handler)
86
86
  when Proc
87
87
  if handler.arity.zero?
88
- proc { instance_exec(&handler) }
88
+ -> { instance_exec(&handler) }
89
89
  else
90
- proc { |_exception| instance_exec(_exception, &handler) }
90
+ ->(exception) { instance_exec(exception, &handler) }
91
91
  end
92
92
  end
93
93
  end
@@ -101,16 +101,24 @@ module FiniteMachine
101
101
  # @api private
102
102
  def evaluate_exceptions(exceptions, options)
103
103
  exceptions.each do |exception|
104
- key = if exception.is_a?(Class) && exception <= Exception
105
- exception.name
106
- elsif exception.is_a?(String)
107
- exception
108
- else
109
- raise ArgumentError, "#{exception} isn't an Exception"
110
- end
111
-
104
+ key = extract_exception_name(exception)
112
105
  error_handlers << [key, options[:with]]
113
106
  end
114
107
  end
108
+
109
+ # Extract exception name
110
+ #
111
+ # @param [Class,Exception,String] exception
112
+ #
113
+ # @api private
114
+ def extract_exception_name(exception)
115
+ if exception.is_a?(Class) && exception <= Exception
116
+ exception.name
117
+ elsif exception.is_a?(String)
118
+ exception
119
+ else
120
+ raise ArgumentError, "#{exception} isn't an Exception"
121
+ end
122
+ end
115
123
  end # Catchable
116
124
  end # FiniteMachine
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'transition_builder'
3
+ require_relative "transition_builder"
4
4
 
5
5
  module FiniteMachine
6
6
  # A class responsible for merging choice options
@@ -39,6 +39,6 @@ module FiniteMachine
39
39
  @transitions.merge(conditions))
40
40
  transition_builder.call(@transitions[:from] => to)
41
41
  end
42
- alias_method :default, :choice
42
+ alias default choice
43
43
  end # ChoiceMerger
44
44
  end # FiniteMachine
@@ -1,30 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FiniteMachine
4
- # A class responsible for defining standalone state machine
4
+ # Responsible for defining a standalone state machine
5
+ #
6
+ # @api public
5
7
  class Definition
6
- # The machine deferreds
8
+ # The any event constant
7
9
  #
8
- # @return [Array[Proc]]
10
+ # @example
11
+ # on_before(any_event) { ... }
9
12
  #
10
- # @api private
11
- def self.deferreds
12
- @deferreds ||= []
13
+ # @return [FiniteMachine::Const]
14
+ #
15
+ # @api public
16
+ def self.any_event
17
+ ANY_EVENT
13
18
  end
14
19
 
15
- # Add deferred
20
+ # The any state constant
16
21
  #
17
- # @param [Proc] deferred
18
- # the deferred execution
22
+ # @example
23
+ # event :go, any_state => :green
19
24
  #
20
- # @return [Array[Proc]]
25
+ # @example
26
+ # on_enter(any_state) { ... }
21
27
  #
22
- # @api private
23
- def self.add_deferred(deferred)
24
- deferreds << deferred
28
+ # @return [FiniteMachine::Const]
29
+ #
30
+ # @api public
31
+ def self.any_state
32
+ ANY_STATE
25
33
  end
26
34
 
27
- # Instantiate a new Definition
35
+ # Initialize a StateMachine
28
36
  #
29
37
  # @example
30
38
  # class Engine < FiniteMachine::Definition
@@ -43,20 +51,49 @@ module FiniteMachine
43
51
  end
44
52
  end
45
53
 
46
- # Set deferrerd methods on the subclass
54
+ # Add deferred methods to the subclass
55
+ #
56
+ # @param [Class] subclass
57
+ # the inheriting subclass
58
+ #
59
+ # @return [void]
47
60
  #
48
61
  # @api private
49
62
  def self.inherited(subclass)
50
63
  super
51
64
 
52
- self.deferreds.each { |d| subclass.add_deferred(d) }
65
+ deferreds.each { |d| subclass.add_deferred(d) }
66
+ end
67
+
68
+ # The state machine deferreds
69
+ #
70
+ # @return [Array<Proc>]
71
+ #
72
+ # @api private
73
+ def self.deferreds
74
+ @deferreds ||= []
75
+ end
76
+
77
+ # Add deferred
78
+ #
79
+ # @param [Proc] deferred
80
+ # the deferred execution
81
+ #
82
+ # @return [Array<Proc>]
83
+ #
84
+ # @api private
85
+ def self.add_deferred(deferred)
86
+ deferreds << deferred
53
87
  end
54
88
 
55
89
  # Delay lookup of DSL method
56
90
  #
57
91
  # @param [Symbol] method_name
92
+ # the method name
93
+ # @param [Array] arguments
94
+ # the method arguments
58
95
  #
59
- # @return [nil]
96
+ # @return [void]
60
97
  #
61
98
  # @api private
62
99
  def self.method_missing(method_name, *arguments, &block)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'choice_merger'
4
- require_relative 'safety'
5
- require_relative 'transition_builder'
3
+ require_relative "choice_merger"
4
+ require_relative "safety"
5
+ require_relative "transition_builder"
6
6
 
7
7
  module FiniteMachine
8
8
  # A generic DSL for describing the state machine
@@ -38,6 +38,13 @@ module FiniteMachine
38
38
  end
39
39
  end
40
40
 
41
+ # Check if message can be handled by this DSL
42
+ #
43
+ # @api private
44
+ def respond_to_missing?(method_name, include_private = false)
45
+ @machine.respond_to?(method_name) || super
46
+ end
47
+
41
48
  # Configure state machine properties
42
49
  #
43
50
  # @api private
@@ -65,6 +72,29 @@ module FiniteMachine
65
72
  log_transitions(@attrs.fetch(:log_transitions, false))
66
73
  end
67
74
 
75
+ # Add aliases for the target object
76
+ #
77
+ # @example
78
+ # FiniteMachine.define do
79
+ # target_alias :engine
80
+ #
81
+ # on_transition do |event|
82
+ # engine.state = event.to
83
+ # end
84
+ # end
85
+ #
86
+ # @param [Array<Symbol>] aliases
87
+ # the names for target alias
88
+ #
89
+ # @api public
90
+ def alias_target(*aliases)
91
+ aliases.each do |alias_name|
92
+ next if env.aliases.include?(alias_name)
93
+
94
+ env.aliases << alias_name
95
+ end
96
+ end
97
+
68
98
  # Define initial state
69
99
  #
70
100
  # @param [Symbol] value
@@ -197,8 +227,8 @@ module FiniteMachine
197
227
  #
198
228
  # @api private
199
229
  def raise_missing_state
200
- fail MissingInitialStateError,
201
- 'Provide state to transition :to for the initial event'
230
+ raise MissingInitialStateError,
231
+ "Provide state to transition :to for the initial event"
202
232
  end
203
233
  end # DSL
204
234
  end # FiniteMachine
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'threadable'
3
+ require_relative "threadable"
4
4
 
5
5
  module FiniteMachine
6
6
  # Holds references to targets and aliases
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent/map'
4
- require 'forwardable'
3
+ require "concurrent/map"
4
+ require "forwardable"
5
5
 
6
- require_relative 'threadable'
7
- require_relative 'undefined_transition'
6
+ require_relative "threadable"
7
+ require_relative "undefined_transition"
8
8
 
9
9
  module FiniteMachine
10
10
  # A class responsible for storing mappings between event namess and
@@ -182,8 +182,7 @@ module FiniteMachine
182
182
  # @api public
183
183
  def match_transition_with(name, from_state, *conditions)
184
184
  find(name).find do |trans|
185
- trans.matches?(from_state) &&
186
- trans.check_conditions(*conditions)
185
+ trans.matches?(from_state) && trans.check_conditions(*conditions)
187
186
  end
188
187
  end
189
188
 
@@ -29,7 +29,7 @@ module FiniteMachine
29
29
  #
30
30
  # @api public
31
31
  def self.event_name
32
- name.split('::').last.downcase.to_sym
32
+ name.split("::").last.downcase.to_sym
33
33
  end
34
34
 
35
35
  # String representation
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent/map'
3
+ require "concurrent/array"
4
+ require "concurrent/map"
4
5
 
5
- require_relative 'hook_event'
6
+ require_relative "hook_event"
6
7
 
7
8
  module FiniteMachine
8
9
  # A class reponsible for registering callbacks
@@ -12,13 +13,17 @@ module FiniteMachine
12
13
  # Initialize a hooks_map of hooks
13
14
  #
14
15
  # @example
15
- # Hoosk.new(machine)
16
+ # Hooks.new
16
17
  #
17
18
  # @api public
18
19
  def initialize
19
20
  @hooks_map = Concurrent::Map.new do |events_hash, hook_event|
20
- events_hash[hook_event] = Concurrent::Map.new do |state_hash, name|
21
- state_hash[name] = []
21
+ events_hash.compute_if_absent(hook_event) do
22
+ Concurrent::Map.new do |state_hash, name|
23
+ state_hash.compute_if_absent(name) do
24
+ Concurrent::Array.new
25
+ end
26
+ end
22
27
  end
23
28
  end
24
29
  end
@@ -1,20 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'listener'
4
- require 'thread'
3
+ require_relative "listener"
4
+ require "thread"
5
5
 
6
6
  module FiniteMachine
7
- # Allows for storage of asynchronous messages such as events
7
+ # Responsible for storage of asynchronous messages such as events
8
8
  # and callbacks.
9
9
  #
10
- # Used internally by {Observer} and {StateMachine}
10
+ # Used internally by {Observer}
11
11
  #
12
12
  # @api private
13
13
  class MessageQueue
14
- # Initialize an event queue in separate thread
14
+ # Initialize a MessageQueue
15
15
  #
16
16
  # @example
17
- # MessageQueue.new
17
+ # message_queue = FiniteMachine::MessageQueue.new
18
18
  #
19
19
  # @api public
20
20
  def initialize
@@ -28,13 +28,21 @@ module FiniteMachine
28
28
 
29
29
  # Start a new thread with a queue of callback events to run
30
30
  #
31
+ # @example
32
+ # message_queue.start
33
+ #
34
+ # @return [Thread, nil]
35
+ #
31
36
  # @api private
32
37
  def start
33
38
  return if running?
39
+
34
40
  @mutex.synchronize { spawn_thread }
35
41
  end
36
42
 
37
- # Spawn new background thread
43
+ # Spawn a new background thread
44
+ #
45
+ # @return [Thread]
38
46
  #
39
47
  # @api private
40
48
  def spawn_thread
@@ -44,18 +52,27 @@ module FiniteMachine
44
52
  end
45
53
  end
46
54
 
55
+ # Check whether or not the message queue is running
56
+ #
57
+ # @example
58
+ # message_queue.running?
59
+ #
60
+ # @return [Boolean]
61
+ #
62
+ # @api public
47
63
  def running?
48
64
  !@thread.nil? && alive?
49
65
  end
50
66
 
51
- # Add asynchronous event to the event queue to process
67
+ # Add an asynchronous event to the message queue to process
52
68
  #
53
69
  # @example
54
- # event_queue << AsyncCall.build(...)
70
+ # message_queue << AsyncCall.build(...)
55
71
  #
56
- # @param [AsyncCall] event
72
+ # @param [FiniteMachine::AsyncCall] event
73
+ # the event to add
57
74
  #
58
- # @return [nil]
75
+ # @return [void]
59
76
  #
60
77
  # @api public
61
78
  def <<(event)
@@ -69,7 +86,12 @@ module FiniteMachine
69
86
  end
70
87
  end
71
88
 
72
- # Add listener to the queue to receive messages
89
+ # Add a listener for the message queue to receive notifications
90
+ #
91
+ # @example
92
+ # message_queue.subscribe { |event| ... }
93
+ #
94
+ # @return [void]
73
95
  #
74
96
  # @api public
75
97
  def subscribe(*args, &block)
@@ -80,20 +102,20 @@ module FiniteMachine
80
102
  end
81
103
  end
82
104
 
83
- # Check if there are any events to handle
105
+ # Check whether or not there are any messages to handle
84
106
  #
85
107
  # @example
86
- # event_queue.empty?
108
+ # message_queue.empty?
87
109
  #
88
110
  # @api public
89
111
  def empty?
90
112
  @mutex.synchronize { @queue.empty? }
91
113
  end
92
114
 
93
- # Check if the event queue is alive
115
+ # Check whether or not the message queue is alive
94
116
  #
95
117
  # @example
96
- # event_queue.alive?
118
+ # message_queue.alive?
97
119
  #
98
120
  # @return [Boolean]
99
121
  #
@@ -102,31 +124,35 @@ module FiniteMachine
102
124
  @mutex.synchronize { !@dead }
103
125
  end
104
126
 
105
- # Join the event queue from current thread
127
+ # Join the message queue from the current thread
106
128
  #
107
129
  # @param [Fixnum] timeout
130
+ # the time limit
108
131
  #
109
132
  # @example
110
- # event_queue.join
133
+ # message_queue.join
111
134
  #
112
- # @return [nil, Thread]
135
+ # @return [Thread, nil]
113
136
  #
114
137
  # @api public
115
138
  def join(timeout = nil)
116
139
  return unless @thread
140
+
117
141
  timeout.nil? ? @thread.join : @thread.join(timeout)
118
142
  end
119
143
 
120
- # Shut down this event queue and clean it up
144
+ # Shut down this message queue and clean it up
121
145
  #
122
146
  # @example
123
- # event_queue.shutdown
147
+ # message_queue.shutdown
148
+ #
149
+ # @raise [FiniteMachine::MessageQueueDeadError]
124
150
  #
125
151
  # @return [Boolean]
126
152
  #
127
153
  # @api public
128
154
  def shutdown
129
- fail EventQueueDeadError, 'event queue already dead' if @dead
155
+ raise MessageQueueDeadError, "message queue already dead" if @dead
130
156
 
131
157
  queue = []
132
158
  @mutex.synchronize do
@@ -142,10 +168,10 @@ module FiniteMachine
142
168
  true
143
169
  end
144
170
 
145
- # Get number of events waiting for processing
171
+ # The number of messages waiting for processing
146
172
  #
147
173
  # @example
148
- # event_queue.size
174
+ # message_queue.size
149
175
  #
150
176
  # @return [Integer]
151
177
  #
@@ -154,6 +180,14 @@ module FiniteMachine
154
180
  @mutex.synchronize { @queue.size }
155
181
  end
156
182
 
183
+ # Inspect this message queue
184
+ #
185
+ # @example
186
+ # message_queue.inspect
187
+ #
188
+ # @return [String]
189
+ #
190
+ # @api public
157
191
  def inspect
158
192
  @mutex.synchronize do
159
193
  "#<#{self.class}:#{object_id.to_s(16)} @size=#{size}, @dead=#{@dead}>"
@@ -162,9 +196,12 @@ module FiniteMachine
162
196
 
163
197
  private
164
198
 
165
- # Notify consumers about process event
199
+ # Notify listeners about the event
200
+ #
201
+ # @param [FiniteMachine::AsyncCall] event
202
+ # the event to notify listeners about
166
203
  #
167
- # @param [AsyncCall] event
204
+ # @return [void]
168
205
  #
169
206
  # @api private
170
207
  def notify_listeners(event)
@@ -184,6 +221,7 @@ module FiniteMachine
184
221
  end
185
222
  event = @queue.pop
186
223
  break unless event
224
+
187
225
  notify_listeners(event)
188
226
  event.dispatch
189
227
  end
@@ -192,6 +230,14 @@ module FiniteMachine
192
230
  Logger.error "Error while running event: #{Logger.format_error(ex)}"
193
231
  end
194
232
 
233
+ # Log discarded message
234
+ #
235
+ # @param [FiniteMachine::AsyncCall] message
236
+ # the message to discard
237
+ #
238
+ # @return [void]
239
+ #
240
+ # @api private
195
241
  def discard_message(message)
196
242
  Logger.debug "Discarded message: #{message}" if $DEBUG
197
243
  end
@@ -1,32 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'async_call'
4
- require_relative 'callable'
5
- require_relative 'hook_event'
6
- require_relative 'hooks'
7
- require_relative 'message_queue'
8
- require_relative 'safety'
9
- require_relative 'transition_event'
3
+ require "securerandom"
4
+
5
+ require_relative "async_call"
6
+ require_relative "callable"
7
+ require_relative "hook_event"
8
+ require_relative "hooks"
9
+ require_relative "message_queue"
10
+ require_relative "safety"
11
+ require_relative "transition_event"
10
12
 
11
13
  module FiniteMachine
12
14
  # A class responsible for observing state changes
13
15
  class Observer < GenericDSL
14
16
  include Safety
15
17
 
16
- # Clean up callback queue
17
- #
18
- # @api private
19
- def self.cleanup_callback_queue
20
- proc do
21
- begin
22
- if callback_queue.alive?
23
- callback_queue.shutdown
24
- end
25
- rescue MessageQueueDeadError
26
- end
27
- end
28
- end
29
-
30
18
  # The current state machine
31
19
  attr_reader :machine
32
20
 
@@ -44,11 +32,6 @@ module FiniteMachine
44
32
  @hooks = Hooks.new
45
33
 
46
34
  @machine.subscribe(self)
47
- ObjectSpace.define_finalizer(self, self.class.cleanup_callback_queue)
48
- end
49
-
50
- def callback_queue
51
- @callback_queue ||= MessageQueue.new
52
35
  end
53
36
 
54
37
  # Evaluate in current context
@@ -204,6 +187,35 @@ module FiniteMachine
204
187
  callback_queue << async_call
205
188
  end
206
189
 
190
+ # Get an existing callback queue or create a new one
191
+ #
192
+ # @return [FiniteMachine::MessageQueue]
193
+ #
194
+ # @api private
195
+ def callback_queue
196
+ @callback_queue ||= MessageQueue.new.tap do
197
+ @queue_id = SecureRandom.uuid
198
+ ObjectSpace.define_finalizer(@queue_id, proc do
199
+ cleanup_callback_queue
200
+ end)
201
+ end
202
+ end
203
+
204
+ # Clean up the callback queue
205
+ #
206
+ # @return [Boolean, nil]
207
+ #
208
+ # @api private
209
+ def cleanup_callback_queue
210
+ ObjectSpace.undefine_finalizer(@queue_id) if @queue_id
211
+ return unless @callback_queue && callback_queue.alive?
212
+
213
+ begin
214
+ callback_queue.shutdown
215
+ rescue MessageQueueDeadError
216
+ end
217
+ end
218
+
207
219
  # Create callable instance
208
220
  #
209
221
  # @api private
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'hook_event'
3
+ require_relative "hook_event"
4
4
 
5
5
  module FiniteMachine
6
6
  # Module responsible for safety checks against known methods
@@ -39,7 +39,7 @@ module FiniteMachine
39
39
  name: event_name,
40
40
  type: :instance,
41
41
  method: method_name,
42
- source: 'FiniteMachine'
42
+ source: "FiniteMachine"
43
43
  }
44
44
  end
45
45
  end
@@ -89,8 +89,8 @@ module FiniteMachine
89
89
  # @api private
90
90
  def wrong_event_name?(name, event_type)
91
91
  machine.states.include?(name) &&
92
- !machine.events.include?(name) &&
93
- event_type < HookEvent::Anyaction
92
+ !machine.events.include?(name) &&
93
+ event_type < HookEvent::Anyaction
94
94
  end
95
95
 
96
96
  # Check if state name exists
@@ -104,8 +104,8 @@ module FiniteMachine
104
104
  # @api private
105
105
  def wrong_state_name?(name, event_type)
106
106
  machine.events.include?(name) &&
107
- !machine.states.include?(name) &&
108
- event_type < HookEvent::Anystate
107
+ !machine.states.include?(name) &&
108
+ event_type < HookEvent::Anystate
109
109
  end
110
110
 
111
111
  def raise_invalid_callback_error(message)