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,6 @@
1
- # encoding: utf-8
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
- # encoding: utf-8
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
- attr_threadsafe :machine
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
- self.machine = machine
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
- if silent
54
- machine.transition(event_name, *data, &block)
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
- if silent
75
- machine.transition!(event_name, *data, &block)
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
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'finite_machine/undefined_transition'
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 chain of events.
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 EventsChain
12
- include Threadable
16
+ class EventsMap
13
17
  extend Forwardable
14
18
 
15
- # The chain of events
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 EventsChain
21
+ # Initialize a EventsMap
23
22
  #
24
23
  # @api private
25
24
  def initialize
26
- @chain = {}
25
+ @events_map = Concurrent::Map.new
27
26
  end
28
27
 
29
28
  # Check if event is present
30
29
  #
31
30
  # @example
32
- # events_chain.exists?(:go) # => true
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
- !chain[name].nil?
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
- chain[name] << transition
56
+ @events_map[name] << transition
58
57
  else
59
- chain[name] = [transition]
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
- # events_chain[:start] # => []
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
- chain.fetch(name) { [] }
75
+ @events_map.fetch(name) { [] }
77
76
  end
78
- alias_method :[], :find
77
+ alias [] find
79
78
 
80
79
  # Retrieve all event names
81
80
  #
82
81
  # @example
83
- # events_chain.events # => [:init, :start, :stop]
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
- chain.keys
89
+ @events_map.keys
91
90
  end
92
91
 
93
92
  # Retreive all unique states
94
93
  #
95
94
  # @example
96
- # events_chain.states # => [:yellow, :green, :red]
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
- chain.values.flatten.map(&:states).map(&:to_a).flatten.uniq
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
- chain.values.flatten.map(&:states)
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
- # events_chain.states_for(:start) # => [:yellow, :green]
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
- # events_chain.choice_transition?(:go, :green) # => true
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
- # evenst_chain.move_to(:go, :green) # => :red
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
- # Set status to cancelled for all transitions matching event name
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
- @chain.clear
240
+ @events_map.clear
257
241
  self
258
242
  end
259
243
 
260
- # Return string representation of this chain
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
- chain.to_s
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 chain content
257
+ # Inspect map content
270
258
  #
271
259
  # @example
272
- # events_chain.inspect
260
+ # events_map.inspect
273
261
  #
274
262
  # @return [String]
275
263
  #
276
264
  # @api public
277
265
  def inspect
278
- "<##{self.class} @chain=#{chain.inspect}>"
266
+ "<##{self.class} @events_map=#{self}>"
279
267
  end
280
- end # EventsChain
268
+ end # EventsMap
281
269
  end # FiniteMachine
@@ -1,9 +1,8 @@
1
- # encoding: utf-8
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
- # HookEvent state or action
28
- attr_threadsafe :name
29
-
30
- # HookEvent type
31
- attr_threadsafe :type
32
-
33
- # The from state this hook has been fired
34
- attr_threadsafe :from
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
- # Instantiate a new HookEvent object
35
+ # String representation
40
36
  #
41
- # @param [Symbol] name
42
- # The action or state name
37
+ # @return [String] the event name
43
38
  #
44
- # @param [Symbol] event_name
45
- # The event name associated with this hook event.
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
- # @example
48
- # HookEvent.new(:green, :move, :green)
46
+ # @param [HookEvent] event_type
49
47
  #
50
- # @return [self]
48
+ # @return [Symbol]
49
+ # out of :any or :any_event
51
50
  #
52
51
  # @api public
53
- def initialize(name, event_name, from)
54
- @name = name
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
- # Deduce default name based on even type
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 [HookEvent] event_type
92
+ # @param [Symbol] name
93
+ # The action or state name
80
94
  #
81
- # @return [Symbol]
82
- # out of :any or :any_event
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 self.infer_default_name(event_type)
86
- event_type < Anyaction ? ANY_EVENT : ANY_STATE
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
- if subscriber.respond_to?(MESSAGE)
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
- # String representation
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
- [name, from, event_name] <=> [other.name, other.from, other.event_name]
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