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.
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