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.
Files changed (106) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +564 -569
  4. data/Rakefile +5 -1
  5. data/benchmarks/memory_profile.rb +11 -0
  6. data/benchmarks/memory_usage.rb +16 -9
  7. data/finite_machine.gemspec +10 -3
  8. data/lib/finite_machine.rb +34 -46
  9. data/lib/finite_machine/async_call.rb +5 -21
  10. data/lib/finite_machine/callable.rb +4 -4
  11. data/lib/finite_machine/catchable.rb +4 -2
  12. data/lib/finite_machine/choice_merger.rb +19 -19
  13. data/lib/finite_machine/const.rb +16 -0
  14. data/lib/finite_machine/definition.rb +2 -2
  15. data/lib/finite_machine/dsl.rb +66 -149
  16. data/lib/finite_machine/env.rb +4 -2
  17. data/lib/finite_machine/event_definition.rb +7 -15
  18. data/lib/finite_machine/{events_chain.rb → events_map.rb} +39 -51
  19. data/lib/finite_machine/hook_event.rb +60 -61
  20. data/lib/finite_machine/hooks.rb +44 -36
  21. data/lib/finite_machine/listener.rb +2 -2
  22. data/lib/finite_machine/logger.rb +5 -4
  23. data/lib/finite_machine/message_queue.rb +39 -30
  24. data/lib/finite_machine/observer.rb +55 -37
  25. data/lib/finite_machine/safety.rb +12 -10
  26. data/lib/finite_machine/state_definition.rb +3 -5
  27. data/lib/finite_machine/state_machine.rb +83 -64
  28. data/lib/finite_machine/state_parser.rb +51 -79
  29. data/lib/finite_machine/subscribers.rb +1 -1
  30. data/lib/finite_machine/threadable.rb +3 -1
  31. data/lib/finite_machine/transition.rb +30 -31
  32. data/lib/finite_machine/transition_builder.rb +23 -32
  33. data/lib/finite_machine/transition_event.rb +12 -11
  34. data/lib/finite_machine/two_phase_lock.rb +3 -1
  35. data/lib/finite_machine/undefined_transition.rb +5 -6
  36. data/lib/finite_machine/version.rb +2 -2
  37. data/spec/integration/system_spec.rb +36 -38
  38. data/spec/performance/benchmark_spec.rb +13 -21
  39. data/spec/unit/alias_target_spec.rb +22 -41
  40. data/spec/unit/async_callbacks_spec.rb +8 -13
  41. data/spec/unit/auto_methods_spec.rb +44 -0
  42. data/spec/unit/callable/call_spec.rb +1 -3
  43. data/spec/unit/callbacks_spec.rb +372 -463
  44. data/spec/unit/can_spec.rb +13 -23
  45. data/spec/unit/cancel_callbacks_spec.rb +46 -0
  46. data/spec/unit/choice_spec.rb +105 -141
  47. data/spec/unit/define_spec.rb +31 -31
  48. data/spec/unit/definition_spec.rb +24 -41
  49. data/spec/unit/event_names_spec.rb +6 -10
  50. data/spec/unit/events_map/add_spec.rb +23 -0
  51. data/spec/unit/events_map/choice_transition_spec.rb +25 -0
  52. data/spec/unit/events_map/clear_spec.rb +13 -0
  53. data/spec/unit/events_map/events_spec.rb +16 -0
  54. data/spec/unit/events_map/inspect_spec.rb +22 -0
  55. data/spec/unit/{events_chain → events_map}/match_transition_spec.rb +12 -14
  56. data/spec/unit/{events_chain → events_map}/move_to_spec.rb +14 -17
  57. data/spec/unit/events_map/states_for_spec.rb +17 -0
  58. data/spec/unit/events_spec.rb +91 -160
  59. data/spec/unit/handlers_spec.rb +34 -66
  60. data/spec/unit/hook_event/any_state_or_event_spec.rb +13 -0
  61. data/spec/unit/hook_event/build_spec.rb +1 -3
  62. data/spec/unit/hook_event/eql_spec.rb +1 -3
  63. data/spec/unit/hook_event/initialize_spec.rb +2 -4
  64. data/spec/unit/hook_event/notify_spec.rb +2 -4
  65. data/spec/unit/hooks/clear_spec.rb +1 -1
  66. data/spec/unit/hooks/{call_spec.rb → find_spec.rb} +4 -9
  67. data/spec/unit/hooks/inspect_spec.rb +16 -8
  68. data/spec/unit/hooks/register_spec.rb +4 -9
  69. data/spec/unit/if_unless_spec.rb +76 -115
  70. data/spec/unit/initial_spec.rb +50 -82
  71. data/spec/unit/inspect_spec.rb +14 -9
  72. data/spec/unit/is_spec.rb +12 -18
  73. data/spec/unit/log_transitions_spec.rb +4 -10
  74. data/spec/unit/logger_spec.rb +1 -3
  75. data/spec/unit/{event_queue_spec.rb → message_queue_spec.rb} +15 -8
  76. data/spec/unit/new_spec.rb +50 -0
  77. data/spec/unit/respond_to_spec.rb +2 -6
  78. data/spec/unit/state_parser/parse_spec.rb +9 -12
  79. data/spec/unit/states_spec.rb +12 -18
  80. data/spec/unit/subscribers_spec.rb +1 -3
  81. data/spec/unit/target_spec.rb +60 -93
  82. data/spec/unit/terminated_spec.rb +15 -25
  83. data/spec/unit/transition/check_conditions_spec.rb +16 -15
  84. data/spec/unit/transition/inspect_spec.rb +6 -6
  85. data/spec/unit/transition/matches_spec.rb +5 -7
  86. data/spec/unit/transition/states_spec.rb +5 -7
  87. data/spec/unit/transition/to_state_spec.rb +5 -13
  88. data/spec/unit/trigger_spec.rb +5 -9
  89. data/spec/unit/undefined_transition/eql_spec.rb +1 -3
  90. metadata +86 -49
  91. data/.gitignore +0 -18
  92. data/.rspec +0 -5
  93. data/.travis.yml +0 -27
  94. data/Gemfile +0 -16
  95. data/assets/finite_machine_logo.png +0 -0
  96. data/lib/finite_machine/async_proxy.rb +0 -55
  97. data/spec/unit/async_events_spec.rb +0 -107
  98. data/spec/unit/events_chain/add_spec.rb +0 -25
  99. data/spec/unit/events_chain/cancel_transitions_spec.rb +0 -22
  100. data/spec/unit/events_chain/choice_transition_spec.rb +0 -28
  101. data/spec/unit/events_chain/clear_spec.rb +0 -15
  102. data/spec/unit/events_chain/events_spec.rb +0 -18
  103. data/spec/unit/events_chain/inspect_spec.rb +0 -24
  104. data/spec/unit/events_chain/states_for_spec.rb +0 -17
  105. data/spec/unit/hook_event/infer_default_name_spec.rb +0 -13
  106. data/spec/unit/state_parser/inspect_spec.rb +0 -25
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'monitor'
4
4
 
@@ -1,4 +1,6 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'two_phase_lock'
2
4
 
3
5
  module FiniteMachine
4
6
  # A mixin to allow instance methods to be synchronized
@@ -1,7 +1,21 @@
1
- # encoding: utf-8
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 :machine
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 = {parsed_states: {green: :yellow}}
27
- # Transition.new(machine, attributes)
37
+ # attributes = {states: {green: :yellow}}
38
+ # Transition.new(context, :go, attributes)
28
39
  #
29
- # @param [StateMachine] machine
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(machine, attrs = {})
37
- @machine = machine
38
- @name = attrs[: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
- @cancelled = attrs.fetch(:cancelled, false)
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(machine.target, *args)
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(machine, states: {:green => :red})
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(machine, states: {:green => :red})
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
- if cancelled?
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(machine, name: :go)
119
+ # transition = Transition.new(context, name: :go)
121
120
  # transition.to_s # => 'go'
122
121
  #
123
122
  # @return [String]
@@ -1,8 +1,9 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'finite_machine/state_parser'
4
- require 'finite_machine/event_definition'
5
- require 'finite_machine/state_definition'
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 = 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
- # Creates transitions for the states
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]] states
44
- # The states to extract
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(states)
50
- StateParser.new(states).parse do |from, to|
51
- attributes.merge!(states: { from => to })
52
- transition = Transition.new(machine, attributes)
53
- name = attributes[:name]
54
- silent = attributes.fetch(:silent, false)
55
-
56
- machine.events_chain.add(name, transition)
57
-
58
- unless machine.respond_to?(name)
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({ from => to })
52
+ @state_definition.apply(from => to)
62
53
  end
63
54
  self
64
55
  end
@@ -1,4 +1,6 @@
1
- # encoding: utf-8
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
- attr_threadsafe :from
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
- attr_threadsafe :to
24
+ attr_reader :to
25
25
 
26
26
  # This event name
27
27
  #
28
28
  # @api public
29
- attr_threadsafe :name
29
+ attr_reader :name
30
30
 
31
31
  # Build a transition event
32
32
  #
33
- # @param [FiniteMachine::Transition] transition
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
- # def initialize(transition, *data)
39
- def initialize(hook_event, to)
40
- @name = hook_event.event_name
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,4 +1,6 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require 'sync'
2
4
 
3
5
  module FiniteMachine
4
6
  # Mixin to provide lock to a {Threadable}
@@ -1,19 +1,18 @@
1
- # encoding: utf-8
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 {EventsChain}
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
- self.name = name
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
- attr_threadsafe :name
28
+ attr_reader :name
30
29
 
31
30
  end # UndefinedTransition
32
31
  end # FiniteMachine
@@ -1,5 +1,5 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module FiniteMachine
4
- VERSION = "0.11.3"
4
+ VERSION = "0.12.0"
5
5
  end
@@ -1,41 +1,38 @@
1
- # encoding: utf-8
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
- events {
9
- event :init, :none => :green
10
- event :green, :any => :green
11
- }
12
- callbacks {
13
- on_before do |event|
14
- callbacks << "fsmA on_before(#{event.to})"
15
- end
16
- on_enter_green do |event|
17
- target.fire
18
- callbacks << "fsmA on_enter_green"
19
- end
20
- once_on_enter_green do |event|
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
- events {
28
- event :init, :none => :stopped
29
- event :start, :stopped => :started
30
- }
31
- callbacks {
32
- on_before do |event|
33
- callbacks << "fsmB on_before(#{event.to})"
34
- end
35
- on_enter_started do |event|
36
- callbacks << "fsmB on_enter_started"
37
- end
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
- @fsmA.green
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
- 'fsmA on_before(green)',
88
- 'fsmA on_enter_green',
89
- 'fsmA once_on_enter_green',
90
- 'fsmB on_before(stopped)',
91
- 'fsmB on_before(started)',
92
- 'fsmB on_enter_started'
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
- # encoding: utf-8
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.define do
26
+ fsm = FiniteMachine.new(measurement) do
27
27
  initial :green
28
28
 
29
- target(measurement)
29
+ event :next, :green => :yellow,
30
+ :yellow => :red,
31
+ :red => :green
30
32
 
31
- events {
32
- event :next, :green => :yellow,
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 300 ips" do
50
- fsm = FiniteMachine.define do
43
+ it "performs at least 400 ips" do
44
+ fsm = FiniteMachine.new do
51
45
  initial :green
52
46
 
53
- events {
54
- event :next, :green => :yellow,
55
- :yellow => :red,
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