finite_machine 0.11.3 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|