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
@@ -1,7 +1,21 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'callable'
|
4
|
+
require_relative 'threadable'
|
2
5
|
|
3
6
|
module FiniteMachine
|
4
7
|
# Class describing a transition associated with a given event
|
8
|
+
#
|
9
|
+
# The {Transition} is created with the `event` helper.
|
10
|
+
#
|
11
|
+
# @example Converting event into {Transition}
|
12
|
+
# event :go, :red => :green
|
13
|
+
#
|
14
|
+
# will be translated to
|
15
|
+
#
|
16
|
+
# Transition.new(context, :go, {states: {:red => :green}})
|
17
|
+
#
|
18
|
+
# @api private
|
5
19
|
class Transition
|
6
20
|
include Threadable
|
7
21
|
|
@@ -11,11 +25,8 @@ module FiniteMachine
|
|
11
25
|
# Predicates before transitioning
|
12
26
|
attr_threadsafe :conditions
|
13
27
|
|
14
|
-
# The current state machine
|
15
|
-
attr_threadsafe :
|
16
|
-
|
17
|
-
# Check if transition should be cancelled
|
18
|
-
attr_threadsafe :cancelled
|
28
|
+
# The current state machine context
|
29
|
+
attr_threadsafe :context
|
19
30
|
|
20
31
|
# All states for this transition event
|
21
32
|
attr_threadsafe :states
|
@@ -23,33 +34,25 @@ module FiniteMachine
|
|
23
34
|
# Initialize a Transition
|
24
35
|
#
|
25
36
|
# @example
|
26
|
-
# attributes = {
|
27
|
-
# Transition.new(
|
37
|
+
# attributes = {states: {green: :yellow}}
|
38
|
+
# Transition.new(context, :go, attributes)
|
28
39
|
#
|
29
|
-
# @param [
|
40
|
+
# @param [Object] context
|
41
|
+
# the context this transition evaluets conditions in
|
30
42
|
#
|
31
43
|
# @param [Hash] attrs
|
32
44
|
#
|
33
45
|
# @return [Transition]
|
34
46
|
#
|
35
47
|
# @api public
|
36
|
-
def initialize(
|
37
|
-
@
|
38
|
-
@name =
|
48
|
+
def initialize(context, name, attrs = {})
|
49
|
+
@context = context
|
50
|
+
@name = name
|
39
51
|
@states = attrs.fetch(:states, {})
|
40
52
|
@if = Array(attrs.fetch(:if, []))
|
41
53
|
@unless = Array(attrs.fetch(:unless, []))
|
42
54
|
@conditions = make_conditions
|
43
|
-
|
44
|
-
end
|
45
|
-
|
46
|
-
# Check if this transition is cancelled or not
|
47
|
-
#
|
48
|
-
# @return [Boolean]
|
49
|
-
#
|
50
|
-
# @api public
|
51
|
-
def cancelled?
|
52
|
-
@cancelled
|
55
|
+
freeze
|
53
56
|
end
|
54
57
|
|
55
58
|
# Reduce conditions
|
@@ -72,7 +75,7 @@ module FiniteMachine
|
|
72
75
|
# @api private
|
73
76
|
def check_conditions(*args)
|
74
77
|
conditions.all? do |condition|
|
75
|
-
condition.call(
|
78
|
+
condition.call(context, *args)
|
76
79
|
end
|
77
80
|
end
|
78
81
|
|
@@ -82,7 +85,7 @@ module FiniteMachine
|
|
82
85
|
# the from state to match against
|
83
86
|
#
|
84
87
|
# @example
|
85
|
-
# transition = Transition.new(
|
88
|
+
# transition = Transition.new(context, states: {:green => :red})
|
86
89
|
# transition.matches?(:green) # => true
|
87
90
|
#
|
88
91
|
# @return [Boolean]
|
@@ -99,7 +102,7 @@ module FiniteMachine
|
|
99
102
|
# the from state to check
|
100
103
|
#
|
101
104
|
# @example
|
102
|
-
# transition = Transition.new(
|
105
|
+
# transition = Transition.new(context, states: {:green => :red})
|
103
106
|
# transition.to_state(:green) # => :red
|
104
107
|
#
|
105
108
|
# @return [Symbol]
|
@@ -107,17 +110,13 @@ module FiniteMachine
|
|
107
110
|
#
|
108
111
|
# @api public
|
109
112
|
def to_state(from)
|
110
|
-
|
111
|
-
from
|
112
|
-
else
|
113
|
-
states[from] || states[ANY_STATE]
|
114
|
-
end
|
113
|
+
states[from] || states[ANY_STATE]
|
115
114
|
end
|
116
115
|
|
117
116
|
# Return transition name
|
118
117
|
#
|
119
118
|
# @example
|
120
|
-
# transition = Transition.new(
|
119
|
+
# transition = Transition.new(context, name: :go)
|
121
120
|
# transition.to_s # => 'go'
|
122
121
|
#
|
123
122
|
# @return [String]
|
@@ -1,8 +1,9 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
require_relative 'event_definition'
|
4
|
+
require_relative 'state_definition'
|
5
|
+
require_relative 'state_parser'
|
6
|
+
require_relative 'transition'
|
6
7
|
|
7
8
|
module FiniteMachine
|
8
9
|
# A class reponsible for building transition out of parsed states
|
@@ -11,54 +12,44 @@ module FiniteMachine
|
|
11
12
|
#
|
12
13
|
# @api private
|
13
14
|
class TransitionBuilder
|
14
|
-
include Threadable
|
15
|
-
|
16
|
-
# The current state machine
|
17
|
-
attr_threadsafe :machine
|
18
|
-
|
19
|
-
attr_threadsafe :attributes
|
20
|
-
|
21
|
-
attr_threadsafe :event_definition
|
22
|
-
|
23
|
-
attr_threadsafe :state_definition
|
24
|
-
|
25
15
|
# Initialize a TransitionBuilder
|
26
16
|
#
|
27
17
|
# @example
|
28
18
|
# TransitionBuilder.new(machine, {})
|
29
19
|
#
|
30
20
|
# @api public
|
31
|
-
def initialize(machine, attributes = {})
|
32
|
-
@machine
|
21
|
+
def initialize(machine, name, attributes = {})
|
22
|
+
@machine = machine
|
23
|
+
@name = name
|
33
24
|
@attributes = attributes
|
25
|
+
|
34
26
|
@event_definition = EventDefinition.new(machine)
|
35
27
|
@state_definition = StateDefinition.new(machine)
|
36
28
|
end
|
37
29
|
|
38
|
-
#
|
30
|
+
# Converts user transitions into internal {Transition} representation
|
39
31
|
#
|
40
32
|
# @example
|
41
33
|
# transition_builder.call([:green, :yellow] => :red)
|
42
34
|
#
|
43
|
-
# @param [Hash[Symbol]]
|
44
|
-
# The
|
35
|
+
# @param [Hash[Symbol]] transitions
|
36
|
+
# The transitions to extract states from
|
45
37
|
#
|
46
38
|
# @return [self]
|
47
39
|
#
|
48
40
|
# @api public
|
49
|
-
def call(
|
50
|
-
StateParser.
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
event_definition.apply(name, silent)
|
41
|
+
def call(transitions)
|
42
|
+
StateParser.parse(transitions) do |from, to|
|
43
|
+
transition = Transition.new(@machine.env.target, @name,
|
44
|
+
@attributes.merge(states: { from => to }))
|
45
|
+
silent = @attributes.fetch(:silent, false)
|
46
|
+
@machine.events_map.add(@name, transition)
|
47
|
+
next unless @machine.auto_methods?
|
48
|
+
|
49
|
+
unless @machine.respond_to?(@name)
|
50
|
+
@event_definition.apply(@name, silent)
|
60
51
|
end
|
61
|
-
state_definition.apply(
|
52
|
+
@state_definition.apply(from => to)
|
62
53
|
end
|
63
54
|
self
|
64
55
|
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'threadable'
|
2
4
|
|
3
5
|
module FiniteMachine
|
4
6
|
# A class representing a callback transition event
|
@@ -7,38 +9,37 @@ module FiniteMachine
|
|
7
9
|
#
|
8
10
|
# @api private
|
9
11
|
class TransitionEvent
|
10
|
-
include Threadable
|
11
|
-
|
12
12
|
# This event from state name
|
13
13
|
#
|
14
14
|
# @return [Object]
|
15
15
|
#
|
16
16
|
# @api public
|
17
|
-
|
17
|
+
attr_reader :from
|
18
18
|
|
19
19
|
# This event to state name
|
20
20
|
#
|
21
21
|
# @return [Object]
|
22
22
|
#
|
23
23
|
# @api public
|
24
|
-
|
24
|
+
attr_reader :to
|
25
25
|
|
26
26
|
# This event name
|
27
27
|
#
|
28
28
|
# @api public
|
29
|
-
|
29
|
+
attr_reader :name
|
30
30
|
|
31
31
|
# Build a transition event
|
32
32
|
#
|
33
|
-
# @param [
|
33
|
+
# @param [String] event_name
|
34
|
+
# @param [String] from
|
35
|
+
# @param [String] to
|
34
36
|
#
|
35
37
|
# @return [self]
|
36
38
|
#
|
37
39
|
# @api private
|
38
|
-
|
39
|
-
|
40
|
-
@
|
41
|
-
@from = hook_event.from
|
40
|
+
def initialize(event_name, from, to)
|
41
|
+
@name = event_name
|
42
|
+
@from = from
|
42
43
|
@to = to
|
43
44
|
freeze
|
44
45
|
end
|
@@ -1,19 +1,18 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FiniteMachine
|
4
4
|
# Stand in for lack of matching transition.
|
5
5
|
#
|
6
|
-
# Used internally by {
|
6
|
+
# Used internally by {EventsMap}
|
7
7
|
#
|
8
8
|
# @api private
|
9
9
|
class UndefinedTransition
|
10
|
-
include Threadable
|
11
|
-
|
12
10
|
# Initialize an undefined transition
|
13
11
|
#
|
14
12
|
# @api private
|
15
13
|
def initialize(name)
|
16
|
-
|
14
|
+
@name = name
|
15
|
+
freeze
|
17
16
|
end
|
18
17
|
|
19
18
|
def to_state(from)
|
@@ -26,7 +25,7 @@ module FiniteMachine
|
|
26
25
|
|
27
26
|
protected
|
28
27
|
|
29
|
-
|
28
|
+
attr_reader :name
|
30
29
|
|
31
30
|
end # UndefinedTransition
|
32
31
|
end # FiniteMachine
|
@@ -1,41 +1,38 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
RSpec.describe FiniteMachine, 'system' do
|
4
4
|
|
5
5
|
it "doesn't share state between machine callbacks" do
|
6
6
|
callbacks = []
|
7
7
|
stub_const("FSM_A", Class.new(FiniteMachine::Definition) do
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
callbacks << "fsmA once_on_enter_green"
|
22
|
-
end
|
23
|
-
}
|
8
|
+
event :init, :none => :green
|
9
|
+
event :start, any_state => :green
|
10
|
+
|
11
|
+
on_before do |event|
|
12
|
+
callbacks << "fsmA on_before(#{event.name})"
|
13
|
+
end
|
14
|
+
on_enter_green do |event|
|
15
|
+
target.fire
|
16
|
+
callbacks << "fsmA on_enter(:green)"
|
17
|
+
end
|
18
|
+
once_on_enter_green do |event|
|
19
|
+
callbacks << "fsmA once_on_enter(:green)"
|
20
|
+
end
|
24
21
|
end)
|
25
22
|
|
26
23
|
stub_const("FSM_B", Class.new(FiniteMachine::Definition) do
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
24
|
+
event :init, :none => :stopped
|
25
|
+
event :start, :stopped => :started
|
26
|
+
|
27
|
+
on_before do |event|
|
28
|
+
callbacks << "fsmB on_before(#{event.name})"
|
29
|
+
end
|
30
|
+
on_enter_stopped do |event|
|
31
|
+
callbacks << "fsmB on_enter(:stopped)"
|
32
|
+
end
|
33
|
+
on_enter_started do |event|
|
34
|
+
callbacks << "fsmB on_enter(:started)"
|
35
|
+
end
|
39
36
|
end)
|
40
37
|
|
41
38
|
class Backend
|
@@ -64,8 +61,7 @@ RSpec.describe FiniteMachine, 'system' do
|
|
64
61
|
|
65
62
|
class Fire
|
66
63
|
def initialize
|
67
|
-
@fsmA = FSM_A.new
|
68
|
-
@fsmA.target(self)
|
64
|
+
@fsmA = FSM_A.new(self)
|
69
65
|
|
70
66
|
@backend = Backend.new
|
71
67
|
@backend.operate
|
@@ -76,7 +72,8 @@ RSpec.describe FiniteMachine, 'system' do
|
|
76
72
|
end
|
77
73
|
|
78
74
|
def operate
|
79
|
-
|
75
|
+
#@fsmA.start # should trigger as well
|
76
|
+
@fsmA.init
|
80
77
|
end
|
81
78
|
end
|
82
79
|
|
@@ -84,12 +81,13 @@ RSpec.describe FiniteMachine, 'system' do
|
|
84
81
|
fire.operate
|
85
82
|
|
86
83
|
expect(callbacks).to match_array([
|
87
|
-
'
|
88
|
-
'
|
89
|
-
'fsmA
|
90
|
-
'
|
91
|
-
'
|
92
|
-
'fsmB
|
84
|
+
'fsmB on_before(init)',
|
85
|
+
'fsmB on_enter(:stopped)',
|
86
|
+
'fsmA on_before(init)',
|
87
|
+
'fsmA on_enter(:green)',
|
88
|
+
'fsmA once_on_enter(:green)',
|
89
|
+
'fsmB on_before(start)',
|
90
|
+
'fsmB on_enter(:started)'
|
93
91
|
])
|
94
92
|
end
|
95
93
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
RSpec.describe FiniteMachine do
|
3
|
+
RSpec.describe FiniteMachine, perf: true do
|
4
4
|
include RSpec::Benchmark::Matchers
|
5
5
|
|
6
6
|
class Measurement
|
@@ -23,21 +23,15 @@ RSpec.describe FiniteMachine do
|
|
23
23
|
it "correctly loops through events" do
|
24
24
|
measurement = Measurement.new
|
25
25
|
|
26
|
-
fsm = FiniteMachine.
|
26
|
+
fsm = FiniteMachine.new(measurement) do
|
27
27
|
initial :green
|
28
28
|
|
29
|
-
|
29
|
+
event :next, :green => :yellow,
|
30
|
+
:yellow => :red,
|
31
|
+
:red => :green
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
:yellow => :red,
|
34
|
-
:red => :green
|
35
|
-
}
|
36
|
-
|
37
|
-
callbacks {
|
38
|
-
on_enter do |event| target.inc_step; true end
|
39
|
-
on_enter :red do |event| target.inc_loop; true end
|
40
|
-
}
|
33
|
+
on_enter do |event| target.inc_step; true end
|
34
|
+
on_enter :red do |event| target.inc_loop; true end
|
41
35
|
end
|
42
36
|
|
43
37
|
100.times { fsm.next }
|
@@ -46,15 +40,13 @@ RSpec.describe FiniteMachine do
|
|
46
40
|
expect(measurement.loops).to eq(100 / 3)
|
47
41
|
end
|
48
42
|
|
49
|
-
it "performs at least
|
50
|
-
fsm = FiniteMachine.
|
43
|
+
it "performs at least 400 ips" do
|
44
|
+
fsm = FiniteMachine.new do
|
51
45
|
initial :green
|
52
46
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
:red => :green
|
57
|
-
}
|
47
|
+
event :next, :green => :yellow,
|
48
|
+
:yellow => :red,
|
49
|
+
:red => :green
|
58
50
|
end
|
59
51
|
|
60
52
|
expect { fsm.next }.to perform_at_least(400).ips
|