finite_machine 0.11.3 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +34 -0
- data/README.md +564 -569
- data/Rakefile +5 -1
- data/benchmarks/memory_profile.rb +11 -0
- data/benchmarks/memory_usage.rb +16 -9
- data/finite_machine.gemspec +10 -3
- data/lib/finite_machine.rb +34 -46
- data/lib/finite_machine/async_call.rb +5 -21
- data/lib/finite_machine/callable.rb +4 -4
- data/lib/finite_machine/catchable.rb +4 -2
- data/lib/finite_machine/choice_merger.rb +19 -19
- data/lib/finite_machine/const.rb +16 -0
- data/lib/finite_machine/definition.rb +2 -2
- data/lib/finite_machine/dsl.rb +66 -149
- data/lib/finite_machine/env.rb +4 -2
- data/lib/finite_machine/event_definition.rb +7 -15
- data/lib/finite_machine/{events_chain.rb → events_map.rb} +39 -51
- data/lib/finite_machine/hook_event.rb +60 -61
- data/lib/finite_machine/hooks.rb +44 -36
- data/lib/finite_machine/listener.rb +2 -2
- data/lib/finite_machine/logger.rb +5 -4
- data/lib/finite_machine/message_queue.rb +39 -30
- data/lib/finite_machine/observer.rb +55 -37
- data/lib/finite_machine/safety.rb +12 -10
- data/lib/finite_machine/state_definition.rb +3 -5
- data/lib/finite_machine/state_machine.rb +83 -64
- data/lib/finite_machine/state_parser.rb +51 -79
- data/lib/finite_machine/subscribers.rb +1 -1
- data/lib/finite_machine/threadable.rb +3 -1
- data/lib/finite_machine/transition.rb +30 -31
- data/lib/finite_machine/transition_builder.rb +23 -32
- data/lib/finite_machine/transition_event.rb +12 -11
- data/lib/finite_machine/two_phase_lock.rb +3 -1
- data/lib/finite_machine/undefined_transition.rb +5 -6
- data/lib/finite_machine/version.rb +2 -2
- data/spec/integration/system_spec.rb +36 -38
- data/spec/performance/benchmark_spec.rb +13 -21
- data/spec/unit/alias_target_spec.rb +22 -41
- data/spec/unit/async_callbacks_spec.rb +8 -13
- data/spec/unit/auto_methods_spec.rb +44 -0
- data/spec/unit/callable/call_spec.rb +1 -3
- data/spec/unit/callbacks_spec.rb +372 -463
- data/spec/unit/can_spec.rb +13 -23
- data/spec/unit/cancel_callbacks_spec.rb +46 -0
- data/spec/unit/choice_spec.rb +105 -141
- data/spec/unit/define_spec.rb +31 -31
- data/spec/unit/definition_spec.rb +24 -41
- data/spec/unit/event_names_spec.rb +6 -10
- data/spec/unit/events_map/add_spec.rb +23 -0
- data/spec/unit/events_map/choice_transition_spec.rb +25 -0
- data/spec/unit/events_map/clear_spec.rb +13 -0
- data/spec/unit/events_map/events_spec.rb +16 -0
- data/spec/unit/events_map/inspect_spec.rb +22 -0
- data/spec/unit/{events_chain → events_map}/match_transition_spec.rb +12 -14
- data/spec/unit/{events_chain → events_map}/move_to_spec.rb +14 -17
- data/spec/unit/events_map/states_for_spec.rb +17 -0
- data/spec/unit/events_spec.rb +91 -160
- data/spec/unit/handlers_spec.rb +34 -66
- data/spec/unit/hook_event/any_state_or_event_spec.rb +13 -0
- data/spec/unit/hook_event/build_spec.rb +1 -3
- data/spec/unit/hook_event/eql_spec.rb +1 -3
- data/spec/unit/hook_event/initialize_spec.rb +2 -4
- data/spec/unit/hook_event/notify_spec.rb +2 -4
- data/spec/unit/hooks/clear_spec.rb +1 -1
- data/spec/unit/hooks/{call_spec.rb → find_spec.rb} +4 -9
- data/spec/unit/hooks/inspect_spec.rb +16 -8
- data/spec/unit/hooks/register_spec.rb +4 -9
- data/spec/unit/if_unless_spec.rb +76 -115
- data/spec/unit/initial_spec.rb +50 -82
- data/spec/unit/inspect_spec.rb +14 -9
- data/spec/unit/is_spec.rb +12 -18
- data/spec/unit/log_transitions_spec.rb +4 -10
- data/spec/unit/logger_spec.rb +1 -3
- data/spec/unit/{event_queue_spec.rb → message_queue_spec.rb} +15 -8
- data/spec/unit/new_spec.rb +50 -0
- data/spec/unit/respond_to_spec.rb +2 -6
- data/spec/unit/state_parser/parse_spec.rb +9 -12
- data/spec/unit/states_spec.rb +12 -18
- data/spec/unit/subscribers_spec.rb +1 -3
- data/spec/unit/target_spec.rb +60 -93
- data/spec/unit/terminated_spec.rb +15 -25
- data/spec/unit/transition/check_conditions_spec.rb +16 -15
- data/spec/unit/transition/inspect_spec.rb +6 -6
- data/spec/unit/transition/matches_spec.rb +5 -7
- data/spec/unit/transition/states_spec.rb +5 -7
- data/spec/unit/transition/to_state_spec.rb +5 -13
- data/spec/unit/trigger_spec.rb +5 -9
- data/spec/unit/undefined_transition/eql_spec.rb +1 -3
- metadata +86 -49
- data/.gitignore +0 -18
- data/.rspec +0 -5
- data/.travis.yml +0 -27
- data/Gemfile +0 -16
- data/assets/finite_machine_logo.png +0 -0
- data/lib/finite_machine/async_proxy.rb +0 -55
- data/spec/unit/async_events_spec.rb +0 -107
- data/spec/unit/events_chain/add_spec.rb +0 -25
- data/spec/unit/events_chain/cancel_transitions_spec.rb +0 -22
- data/spec/unit/events_chain/choice_transition_spec.rb +0 -28
- data/spec/unit/events_chain/clear_spec.rb +0 -15
- data/spec/unit/events_chain/events_spec.rb +0 -18
- data/spec/unit/events_chain/inspect_spec.rb +0 -24
- data/spec/unit/events_chain/states_for_spec.rb +0 -17
- data/spec/unit/hook_event/infer_default_name_spec.rb +0 -13
- data/spec/unit/state_parser/inspect_spec.rb +0 -25
data/lib/finite_machine/env.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'threadable'
|
2
4
|
|
3
5
|
module FiniteMachine
|
4
6
|
# Holds references to targets and aliases
|
@@ -11,7 +13,7 @@ module FiniteMachine
|
|
11
13
|
|
12
14
|
attr_threadsafe :aliases
|
13
15
|
|
14
|
-
def initialize(target, aliases)
|
16
|
+
def initialize(target, aliases = [])
|
15
17
|
@target = target
|
16
18
|
@aliases = aliases
|
17
19
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FiniteMachine
|
4
4
|
# A class responsible for defining event methods on state machine
|
@@ -8,10 +8,8 @@ module FiniteMachine
|
|
8
8
|
#
|
9
9
|
# @api private
|
10
10
|
class EventDefinition
|
11
|
-
include Threadable
|
12
|
-
|
13
11
|
# The current state machine
|
14
|
-
|
12
|
+
attr_reader :machine
|
15
13
|
|
16
14
|
# Initialize an EventDefinition
|
17
15
|
#
|
@@ -19,7 +17,7 @@ module FiniteMachine
|
|
19
17
|
#
|
20
18
|
# @api private
|
21
19
|
def initialize(machine)
|
22
|
-
|
20
|
+
@machine = machine
|
23
21
|
end
|
24
22
|
|
25
23
|
# Define transition event names as state machine events
|
@@ -50,11 +48,8 @@ module FiniteMachine
|
|
50
48
|
# @api private
|
51
49
|
def define_event_transition(event_name, silent)
|
52
50
|
machine.send(:define_singleton_method, event_name) do |*data, &block|
|
53
|
-
|
54
|
-
|
55
|
-
else
|
56
|
-
machine.trigger(event_name, *data, &block)
|
57
|
-
end
|
51
|
+
method = silent ? :transition : :trigger
|
52
|
+
machine.public_send(method, event_name, *data, &block)
|
58
53
|
end
|
59
54
|
end
|
60
55
|
|
@@ -71,11 +66,8 @@ module FiniteMachine
|
|
71
66
|
# @api private
|
72
67
|
def define_event_bang(event_name, silent)
|
73
68
|
machine.send(:define_singleton_method, "#{event_name}!") do |*data, &block|
|
74
|
-
|
75
|
-
|
76
|
-
else
|
77
|
-
machine.trigger!(event_name, *data, &block)
|
78
|
-
end
|
69
|
+
method = silent ? :transition! : :trigger!
|
70
|
+
machine.public_send(method, event_name, *data, &block)
|
79
71
|
end
|
80
72
|
end
|
81
73
|
end # EventBuilder
|
@@ -1,35 +1,34 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'concurrent/map'
|
4
|
+
require 'forwardable'
|
5
|
+
|
6
|
+
require_relative 'threadable'
|
7
|
+
require_relative 'undefined_transition'
|
4
8
|
|
5
9
|
module FiniteMachine
|
6
|
-
# A class responsible for storing
|
10
|
+
# A class responsible for storing mappings between event namess and
|
11
|
+
# their transition objects.
|
7
12
|
#
|
8
13
|
# Used internally by {StateMachine}.
|
9
14
|
#
|
10
15
|
# @api private
|
11
|
-
class
|
12
|
-
include Threadable
|
16
|
+
class EventsMap
|
13
17
|
extend Forwardable
|
14
18
|
|
15
|
-
|
16
|
-
#
|
17
|
-
# @api private
|
18
|
-
attr_threadsafe :chain
|
19
|
-
|
20
|
-
def_delegators :@chain, :empty?, :size
|
19
|
+
def_delegators :@events_map, :empty?, :size
|
21
20
|
|
22
|
-
# Initialize a
|
21
|
+
# Initialize a EventsMap
|
23
22
|
#
|
24
23
|
# @api private
|
25
24
|
def initialize
|
26
|
-
@
|
25
|
+
@events_map = Concurrent::Map.new
|
27
26
|
end
|
28
27
|
|
29
28
|
# Check if event is present
|
30
29
|
#
|
31
30
|
# @example
|
32
|
-
#
|
31
|
+
# events_map.exists?(:go) # => true
|
33
32
|
#
|
34
33
|
# @param [Symbol] name
|
35
34
|
# the event name
|
@@ -39,7 +38,7 @@ module FiniteMachine
|
|
39
38
|
#
|
40
39
|
# @api public
|
41
40
|
def exists?(name)
|
42
|
-
|
41
|
+
@events_map.key?(name)
|
43
42
|
end
|
44
43
|
|
45
44
|
# Add transition under name
|
@@ -54,9 +53,9 @@ module FiniteMachine
|
|
54
53
|
# @api public
|
55
54
|
def add(name, transition)
|
56
55
|
if exists?(name)
|
57
|
-
|
56
|
+
@events_map[name] << transition
|
58
57
|
else
|
59
|
-
|
58
|
+
@events_map[name] = [transition]
|
60
59
|
end
|
61
60
|
self
|
62
61
|
end
|
@@ -66,41 +65,41 @@ module FiniteMachine
|
|
66
65
|
# @param [Symbol] name
|
67
66
|
#
|
68
67
|
# @example
|
69
|
-
#
|
68
|
+
# events_map[:start] # => []
|
70
69
|
#
|
71
70
|
# @return [Array[Transition]]
|
72
71
|
# the transitions matching event name
|
73
72
|
#
|
74
73
|
# @api public
|
75
74
|
def find(name)
|
76
|
-
|
75
|
+
@events_map.fetch(name) { [] }
|
77
76
|
end
|
78
|
-
|
77
|
+
alias [] find
|
79
78
|
|
80
79
|
# Retrieve all event names
|
81
80
|
#
|
82
81
|
# @example
|
83
|
-
#
|
82
|
+
# events_map.events # => [:init, :start, :stop]
|
84
83
|
#
|
85
84
|
# @return [Array[Symbol]]
|
86
85
|
# All event names
|
87
86
|
#
|
88
87
|
# @api public
|
89
88
|
def events
|
90
|
-
|
89
|
+
@events_map.keys
|
91
90
|
end
|
92
91
|
|
93
92
|
# Retreive all unique states
|
94
93
|
#
|
95
94
|
# @example
|
96
|
-
#
|
95
|
+
# events_map.states # => [:yellow, :green, :red]
|
97
96
|
#
|
98
97
|
# @return [Array[Symbol]]
|
99
98
|
# the array of all unique states
|
100
99
|
#
|
101
100
|
# @api public
|
102
101
|
def states
|
103
|
-
|
102
|
+
@events_map.values.flatten.map(&:states).map(&:to_a).flatten.uniq
|
104
103
|
end
|
105
104
|
|
106
105
|
# Retrieves all state transitions
|
@@ -109,7 +108,7 @@ module FiniteMachine
|
|
109
108
|
#
|
110
109
|
# @api public
|
111
110
|
def state_transitions
|
112
|
-
|
111
|
+
@events_map.values.flatten.map(&:states)
|
113
112
|
end
|
114
113
|
|
115
114
|
# Retrieve from states for the event name
|
@@ -117,7 +116,7 @@ module FiniteMachine
|
|
117
116
|
# @param [Symbol] event_name
|
118
117
|
#
|
119
118
|
# @example
|
120
|
-
#
|
119
|
+
# events_map.states_for(:start) # => [:yellow, :green]
|
121
120
|
#
|
122
121
|
# @api public
|
123
122
|
def states_for(name)
|
@@ -136,7 +135,7 @@ module FiniteMachine
|
|
136
135
|
# Check if event has branching choice transitions or not
|
137
136
|
#
|
138
137
|
# @example
|
139
|
-
#
|
138
|
+
# events_map.choice_transition?(:go, :green) # => true
|
140
139
|
#
|
141
140
|
# @param [Symbol] name
|
142
141
|
# the event name
|
@@ -211,7 +210,7 @@ module FiniteMachine
|
|
211
210
|
# Find state that this machine can move to
|
212
211
|
#
|
213
212
|
# @example
|
214
|
-
#
|
213
|
+
# evenst_map.move_to(:go, :green) # => :red
|
215
214
|
#
|
216
215
|
# @param [Symbol] name
|
217
216
|
# the event name
|
@@ -229,53 +228,42 @@ module FiniteMachine
|
|
229
228
|
def move_to(name, from_state, *conditions)
|
230
229
|
transition = select_transition(name, from_state, *conditions)
|
231
230
|
transition ||= UndefinedTransition.new(name)
|
232
|
-
|
233
231
|
transition.to_state(from_state)
|
234
232
|
end
|
235
233
|
|
236
|
-
#
|
237
|
-
#
|
238
|
-
# @param [Symbol] name
|
239
|
-
# the event name
|
240
|
-
#
|
241
|
-
# @return [nil]
|
242
|
-
#
|
243
|
-
# @api public
|
244
|
-
def cancel_transitions(name)
|
245
|
-
chain[name].each do |trans|
|
246
|
-
trans.cancelled = true
|
247
|
-
end
|
248
|
-
end
|
249
|
-
|
250
|
-
# Reset chain
|
234
|
+
# Reset map
|
251
235
|
#
|
252
236
|
# @return [self]
|
253
237
|
#
|
254
238
|
# @api public
|
255
239
|
def clear
|
256
|
-
@
|
240
|
+
@events_map.clear
|
257
241
|
self
|
258
242
|
end
|
259
243
|
|
260
|
-
# Return string representation of this
|
244
|
+
# Return string representation of this map
|
261
245
|
#
|
262
246
|
# @return [String]
|
263
247
|
#
|
264
248
|
# @api public
|
265
249
|
def to_s
|
266
|
-
|
250
|
+
hash = {}
|
251
|
+
@events_map.each_pair do |name, trans|
|
252
|
+
hash[name] = trans
|
253
|
+
end
|
254
|
+
hash.to_s
|
267
255
|
end
|
268
256
|
|
269
|
-
# Inspect
|
257
|
+
# Inspect map content
|
270
258
|
#
|
271
259
|
# @example
|
272
|
-
#
|
260
|
+
# events_map.inspect
|
273
261
|
#
|
274
262
|
# @return [String]
|
275
263
|
#
|
276
264
|
# @api public
|
277
265
|
def inspect
|
278
|
-
"<##{self.class} @
|
266
|
+
"<##{self.class} @events_map=#{self}>"
|
279
267
|
end
|
280
|
-
end #
|
268
|
+
end # EventsMap
|
281
269
|
end # FiniteMachine
|
@@ -1,9 +1,8 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FiniteMachine
|
4
4
|
# A class responsible for event notification
|
5
5
|
class HookEvent
|
6
|
-
include Threadable
|
7
6
|
include Comparable
|
8
7
|
|
9
8
|
class Anystate < HookEvent; end
|
@@ -24,38 +23,34 @@ module FiniteMachine
|
|
24
23
|
|
25
24
|
MESSAGE = :emit
|
26
25
|
|
27
|
-
#
|
28
|
-
|
29
|
-
|
30
|
-
#
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
# The event name triggering this hook event
|
37
|
-
attr_threadsafe :event_name
|
26
|
+
# Extract event name
|
27
|
+
#
|
28
|
+
# @return [String] the event name
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
def self.event_name
|
32
|
+
name.split('::').last.downcase.to_sym
|
33
|
+
end
|
38
34
|
|
39
|
-
#
|
35
|
+
# String representation
|
40
36
|
#
|
41
|
-
# @
|
42
|
-
# The action or state name
|
37
|
+
# @return [String] the event name
|
43
38
|
#
|
44
|
-
# @
|
45
|
-
|
39
|
+
# @api public
|
40
|
+
def self.to_s
|
41
|
+
event_name.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
# Choose any state or event name based on even type
|
46
45
|
#
|
47
|
-
# @
|
48
|
-
# HookEvent.new(:green, :move, :green)
|
46
|
+
# @param [HookEvent] event_type
|
49
47
|
#
|
50
|
-
# @return [
|
48
|
+
# @return [Symbol]
|
49
|
+
# out of :any or :any_event
|
51
50
|
#
|
52
51
|
# @api public
|
53
|
-
def
|
54
|
-
|
55
|
-
@type = self.class
|
56
|
-
@event_name = event_name
|
57
|
-
@from = from
|
58
|
-
freeze
|
52
|
+
def self.any_state_or_event(event_type)
|
53
|
+
event_type < Anyaction ? ANY_EVENT : ANY_STATE
|
59
54
|
end
|
60
55
|
|
61
56
|
# Build event hook
|
@@ -74,16 +69,44 @@ module FiniteMachine
|
|
74
69
|
new(state_or_action, event_name, from)
|
75
70
|
end
|
76
71
|
|
77
|
-
|
72
|
+
EVENTS.each do |event|
|
73
|
+
(class << self; self; end).class_eval do
|
74
|
+
define_method(event.event_name) { event }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# HookEvent state or action name
|
79
|
+
attr_reader :name
|
80
|
+
|
81
|
+
# HookEvent type
|
82
|
+
attr_reader :type
|
83
|
+
|
84
|
+
# The from state this hook has been fired
|
85
|
+
attr_reader :from
|
86
|
+
|
87
|
+
# The event name triggering this hook event
|
88
|
+
attr_reader :event_name
|
89
|
+
|
90
|
+
# Instantiate a new HookEvent object
|
78
91
|
#
|
79
|
-
# @param [
|
92
|
+
# @param [Symbol] name
|
93
|
+
# The action or state name
|
80
94
|
#
|
81
|
-
# @
|
82
|
-
#
|
95
|
+
# @param [Symbol] event_name
|
96
|
+
# The event name associated with this hook event.
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# HookEvent.new(:green, :move, :green)
|
100
|
+
#
|
101
|
+
# @return [self]
|
83
102
|
#
|
84
103
|
# @api public
|
85
|
-
def
|
86
|
-
|
104
|
+
def initialize(name, event_name, from)
|
105
|
+
@name = name
|
106
|
+
@type = self.class
|
107
|
+
@event_name = event_name
|
108
|
+
@from = from
|
109
|
+
freeze
|
87
110
|
end
|
88
111
|
|
89
112
|
# Notify subscriber about this event
|
@@ -98,27 +121,9 @@ module FiniteMachine
|
|
98
121
|
#
|
99
122
|
# @api public
|
100
123
|
def notify(subscriber, *data)
|
101
|
-
|
102
|
-
subscriber.public_send(MESSAGE, self, *data)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
# Extract event name
|
107
|
-
#
|
108
|
-
# @return [String] the event name
|
109
|
-
#
|
110
|
-
# @api public
|
111
|
-
def self.event_name
|
112
|
-
name.split('::').last.downcase.to_sym
|
113
|
-
end
|
124
|
+
return unless subscriber.respond_to?(MESSAGE)
|
114
125
|
|
115
|
-
|
116
|
-
#
|
117
|
-
# @return [String] the event name
|
118
|
-
#
|
119
|
-
# @api public
|
120
|
-
def self.to_s
|
121
|
-
event_name.to_s
|
126
|
+
subscriber.public_send(MESSAGE, self, *data)
|
122
127
|
end
|
123
128
|
|
124
129
|
# Compare whether the instance is greater, less then or equal to other
|
@@ -128,14 +133,8 @@ module FiniteMachine
|
|
128
133
|
# @api public
|
129
134
|
def <=>(other)
|
130
135
|
other.is_a?(type) &&
|
131
|
-
|
132
|
-
end
|
133
|
-
alias_method :eql?, :==
|
134
|
-
|
135
|
-
EVENTS.each do |event|
|
136
|
-
(class << self; self; end).class_eval do
|
137
|
-
define_method(event.event_name) { event }
|
138
|
-
end
|
136
|
+
[name, from, event_name] <=> [other.name, other.from, other.event_name]
|
139
137
|
end
|
138
|
+
alias eql? ==
|
140
139
|
end # HookEvent
|
141
140
|
end # FiniteMachine
|