MINT-statemachine 1.2.3 → 1.3.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.
@@ -10,23 +10,46 @@ module Statemachine
10
10
  @id = id
11
11
  @superstate = superstate
12
12
  @statemachine = state_machine
13
- @transitions = {}
13
+ @transitions = []
14
+ @spontaneous_transitions = []
15
+ end
16
+
17
+ # TODO hack to support SCXML parser parallel state creation
18
+ def modify_statemachine(state_machine)
19
+ @statemachine = state_machine
14
20
  end
15
21
 
16
22
  def add(transition)
17
- @transitions[transition.event] = transition
23
+ if transition.event == nil
24
+ @spontaneous_transitions.push(transition)
25
+ else
26
+ @transitions.push(transition)
27
+ end
18
28
  end
19
29
 
20
30
  def transitions
21
- return @superstate ? @transitions.merge(@superstate.transitions) : @transitions
31
+ return @superstate ? @transitions | @superstate.transitions : @transitions
32
+ end
33
+
34
+ def get_transitions(event)
35
+ transitions = []
36
+ @transitions.each do |t|
37
+ if t.event == event
38
+ transitions << t
39
+ end
40
+ end
41
+ if transitions.empty?
42
+ return nil
43
+ end
44
+ transitions
22
45
  end
23
46
 
24
47
  def non_default_transition_for(event,check_superstates = true)
25
- transition = @transitions[event]
48
+ transition = get_transitions(event)
26
49
  if check_superstates and @superstate
27
50
  transition = @superstate.non_default_transition_for(event) if @superstate and @superstate.is_parallel == false and not transition
28
51
  end
29
- return transition
52
+ transition
30
53
  end
31
54
 
32
55
  def default_transition
@@ -35,10 +58,28 @@ module Statemachine
35
58
  return nil
36
59
  end
37
60
 
61
+ def spontaneous_transition
62
+ transition = []
63
+ @spontaneous_transitions.each do |s|
64
+ if s.cond == true
65
+ transition << [s,self]
66
+ else
67
+ if s.cond
68
+ transition << [s,self] if @statemachine.invoke_action(s.cond, [], "condition from #{@state} invoked by '#{nil}' event", nil, nil)
69
+ end
70
+ end
71
+ end
72
+
73
+ if transition.empty?
74
+ return nil
75
+ end
76
+ return transition
77
+ end
78
+
38
79
  def transition_for(event,check_superstate=true)
39
80
  transition = non_default_transition_for(event,check_superstate)
40
81
  transition = default_transition if not transition
41
- return transition
82
+ transition
42
83
  end
43
84
 
44
85
  def exit(args)
@@ -58,11 +99,11 @@ module Statemachine
58
99
 
59
100
  def activate
60
101
  @statemachine.state = self
61
- # if (@statemachine.is_parallel)
102
+ # if (@statemachine.is_parallel)
62
103
  # @statemachine.activation.call(self.id,@statemachine.is_parallel.abstract_states,@statemachine.is_parallel.statemachine.states_id) if @statemachine.activation
63
104
  #else
64
- @statemachine.activation.call(self.id,@statemachine.abstract_states,@statemachine.states_id) if @statemachine.activation
65
- # end
105
+ #@statemachine.activation.call(self.id,self.abstract_states,@statemachine.states_id) if @statemachine.activation # and not @statemachine.is_parallel
106
+ # end
66
107
  end
67
108
 
68
109
  def concrete?
@@ -83,13 +124,13 @@ module Statemachine
83
124
 
84
125
  def has_superstate(id)
85
126
  return false if not @superstate
86
- return true if @superstate.id == id
127
+ return true if @superstate.id == id
87
128
  return @superstate.has_superstate(id)
88
129
  end
89
130
 
90
131
  def abstract_states
91
- return [] if not superstate
92
- return @superstate.abstract_states if not @superstate.is_parallel
132
+ return [] if not @superstate
133
+ return @superstate.abstract_states #if not @superstate.is_parallel
93
134
  []
94
135
  end
95
136
 
@@ -97,5 +138,5 @@ module Statemachine
97
138
  false
98
139
  end
99
140
  end
100
-
141
+
101
142
  end
@@ -37,6 +37,15 @@ module Statemachine
37
37
  attr_accessor :messenger, :message_queue, :is_parallel #:nodoc:
38
38
  attr_accessor :activation
39
39
 
40
+ # workaround that activation is not automatically set for parallel state machines
41
+ def activation
42
+ if is_parallel
43
+ is_parallel.statemachine.activation
44
+ else
45
+ @activation
46
+ end
47
+ end
48
+
40
49
  # Should not be called directly. Instances of Statemachine::Statemachine are created
41
50
  # through the Statemachine.build method.
42
51
  def initialize(root = Superstate.new(:root, nil, self))
@@ -46,11 +55,11 @@ module Statemachine
46
55
 
47
56
  # Returns the id of the startstate of the statemachine.
48
57
  def startstate
49
- return @root.startstate_id
58
+ @root.startstate_id
50
59
  end
51
60
 
52
61
  # Resets the statemachine back to its starting state.
53
- def reset(startstate_id=nil)
62
+ def reset(startstate_id=nil, use_activation_callback = true)
54
63
 
55
64
  if (startstate_id and @root.is_parallel) # called when enterin a parallel state or dierctly entering a child of a parallel state from outside the parallel state
56
65
  @state = get_state(startstate_id)
@@ -62,9 +71,11 @@ module Statemachine
62
71
  end
63
72
  raise StatemachineException.new("The state machine doesn't know where to start. Try setting the startstate.") if @state == nil
64
73
  @state.enter
74
+ @state.activate
65
75
  @states.values.each { |state|
66
76
  state.reset if not state.is_a? Parallelstate
67
77
  }
78
+ activation.call([@state.id],abstract_states,states_id) if use_activation_callback and activation and not is_parallel
68
79
  end
69
80
 
70
81
  def context= c
@@ -94,7 +105,11 @@ module Statemachine
94
105
 
95
106
  # returns an array with all currently active super states
96
107
  def abstract_states
97
- @state.abstract_states
108
+ belongs, parallel = belongs_to_parallel(@state.id)
109
+ if belongs
110
+ return parallel.abstract_states
111
+ end
112
+ return @state.abstract_states
98
113
  end
99
114
 
100
115
  # You may change the state of the statemachine by using this method. The parameter should be
@@ -136,22 +151,32 @@ module Statemachine
136
151
  event = event.to_sym
137
152
  trace "Event: #{event}"
138
153
  if @state
139
- belongs, parallel = belongs_to_parallel(@state.id)
140
- if belongs
141
- r = parallel.process_event(event, *args)
142
- return true if r
143
- end
154
+
144
155
  transition = @state.transition_for(event)
145
156
  if transition
146
- cond = true
147
- if transition.cond!=true
148
- cond = @state.statemachine.invoke_action(transition.cond, [], "condition from #{@state} invoked by '#{event}' event", nil, nil)
157
+ if not transition.is_a? Array
158
+ transition = [transition]
149
159
  end
150
- if cond
151
- transition.invoke(@state, self, args)
160
+ transition.each do |t|
161
+ cond = true
162
+ if t.cond != true
163
+ cond = @state.statemachine.invoke_action(t.cond, [], "condition from #{@state} invoked by '#{event}' event", nil, nil)
164
+ end
165
+ if cond
166
+ t.invoke(@state, self, args)
167
+ end
152
168
  end
153
169
  else
170
+
171
+ belongs, parallel = belongs_to_parallel(@state.id)
172
+ if belongs
173
+ r = parallel.process_event(event, *args)
174
+ if r
175
+ return true
176
+ end
177
+ end
154
178
  raise TransitionMissingException.new("#{@state} does not respond to the '#{event}' event.")
179
+
155
180
  end
156
181
 
157
182
  else
@@ -205,6 +230,9 @@ module Statemachine
205
230
  elsif p = get_parallel and s = p.get_state(id)
206
231
  return s
207
232
  else
233
+ if @root.is_a? Parallelstate
234
+ return false
235
+ end
208
236
  state = State.new(id, @root, self)
209
237
  @states[id] = state
210
238
  return state
@@ -233,6 +261,12 @@ module Statemachine
233
261
  return false
234
262
  end
235
263
 
264
+ def which_state_respond_to?(message)
265
+ #return super if super(message)
266
+ return @state if @state and @state.transition_for(message)
267
+ nil
268
+ end
269
+
236
270
  def method_missing(message, *args) #:nodoc:
237
271
  if @state and @state.transition_for(message)
238
272
  process_event(message.to_sym, *args)
@@ -240,8 +274,9 @@ module Statemachine
240
274
  # params = [message.to_sym].concat(args)
241
275
  # method.call(*params)
242
276
  else
243
- begin
244
- super(message, args)
277
+ begin return super if super(message)
278
+
279
+ super(message, args)
245
280
  rescue NoMethodError
246
281
  process_event(message.to_sym, *args)
247
282
  end
@@ -258,18 +293,18 @@ module Statemachine
258
293
  # check if it is one of the running parallel states
259
294
  belongs, parallel = belongs_to_parallel(@state.id)
260
295
  if belongs
261
- return parallel.In(id)
296
+ parallel.In(id)
262
297
  end
263
298
  end
264
299
 
265
300
  private
266
301
 
267
302
  def is_history_state_id?(id)
268
- return id.to_s[-2..-1] == "_H"
303
+ id.to_s[-2..-1] == "_H"
269
304
  end
270
305
 
271
306
  def base_id(history_id)
272
- return history_id.to_s[0...-2].to_sym
307
+ history_id.to_s[0...-2].to_sym
273
308
  end
274
309
 
275
310
  def load_history(superstate)
@@ -47,7 +47,11 @@ module Statemachine
47
47
  return [@id] if not @superstate or @superstate.is_parallel
48
48
  ([@id] + @superstate.abstract_states).uniq
49
49
  end
50
-
50
+
51
+ def spontaneous_transition
52
+ startstate.spontaneous_transition
53
+ end
54
+
51
55
  end
52
56
 
53
57
  end
@@ -13,14 +13,21 @@ module Statemachine
13
13
  @cond = cond
14
14
  end
15
15
 
16
+ def is_self_transition?
17
+ @origin_id == @destination_id
18
+ end
19
+
16
20
  def invoke(origin, statemachine, args)
21
+ old_abstract_states = statemachine.abstract_states
22
+ old_atomic_states = statemachine.states_id
23
+
17
24
  destination = statemachine.get_state(@destination_id)
18
25
  exits, entries = exits_and_entries(origin, destination)
19
26
  exits.each { |exited_state| exited_state.exit(args) }
20
27
  messenger = origin.statemachine.messenger
21
28
  message_queue = origin.statemachine.message_queue
22
29
 
23
- if @action # changed this if statement to return if action fails
30
+ if @action # changed this if statement to return if action fails
24
31
  if not origin.statemachine.invoke_action(@action, args, "transition action from #{origin} invoked by '#{@event}' event", messenger, message_queue)
25
32
  raise StatemachineException.new("Transition to state #{destination.id} failed because action for event #{@event} return false.")
26
33
  end
@@ -30,16 +37,50 @@ module Statemachine
30
37
 
31
38
  terminal_state = entries.last
32
39
 
33
- terminal_state.activate if terminal_state and not terminal_state.is_parallel
34
40
 
41
+ terminal_state.activate if terminal_state and not terminal_state.is_parallel
35
42
  entries.each { |entered_state| entered_state.enter(args) }
36
- entries.each { |entered_state| entered_state.activate(terminal_state.id) if entered_state.is_parallel }
43
+ #entries.each { |entered_state| entered_state.activate(terminal_state.id) if entered_state.is_parallel }
37
44
  statemachine.state = terminal_state if statemachine.has_state(terminal_state.id) and statemachine.is_parallel
38
45
 
46
+ if is_self_transition? # handle special case of self transition
47
+ new_states = [@destination_id]
48
+ else
49
+ new_states = statemachine.states_id - (old_atomic_states & statemachine.states_id)
50
+ new_states = (statemachine.abstract_states - old_abstract_states) + new_states
51
+ end
52
+
53
+ if statemachine.activation
54
+ sm = statemachine
55
+ while (sm.is_parallel)
56
+ sm = sm.is_parallel.statemachine
57
+ end
58
+ sm.activation.call(new_states,sm.abstract_states,sm.states_id) if sm.activation # and not @statemachine.is_parallel
59
+ end
60
+
61
+ # Take any valid spontaneous transitions
62
+ transition = destination.spontaneous_transition
63
+
64
+ if transition
65
+ if destination.is_parallel
66
+ transition.each do |trans,statem|
67
+ trans.each do |t|
68
+ t[0].invoke(t[1], statem, args) if t[0].is_a? Transition
69
+ end
70
+ end
71
+ else
72
+
73
+ if transition.is_a? Array
74
+ transition.each do |t|
75
+ t[0].invoke(t[1], statemachine, args) if t[0].is_a? Transition
76
+ end
77
+ end
78
+ end
79
+ end
39
80
  end
40
81
 
41
82
  def exits_and_entries(origin, destination)
42
- # return [], [] if origin == destination
83
+ # return [], [] if origin == destination
43
84
  exits = []
44
85
  entries = exits_and_entries_helper(exits, origin, destination)
45
86
  return exits, entries.reverse
@@ -2,8 +2,8 @@ module Statemachine
2
2
  module VERSION #:nodoc:
3
3
  unless defined? MAJOR
4
4
  MAJOR = 1
5
- MINOR = 2
6
- TINY = 3
5
+ MINOR = 3
6
+ TINY = 0
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY].join('.')
9
9
  TAG = "REL_" + [MAJOR, MINOR, TINY].join('_')
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
1
+ require 'spec_helper'
2
2
  #require 'statemachine'
3
3
  #require 'lib/statemachine/action_invokation.rb'
4
4
  require "noodle"
data/spec/builder_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe "Builder" do
4
4
 
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe "Default Transition" do
4
4
 
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/../../spec_helper'
1
+ require "./"+File.dirname(__FILE__) + '/../../spec_helper'
2
2
  require 'statemachine/generate/dot_graph/dot_graph_statemachine'
3
3
 
4
4
  describe Statemachine::Statemachine, "(Turn Stile)" do
data/spec/history_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe "History States" do
4
4
 
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe "State Machine Odds And Ends" do
4
4
  include SwitchStatemachine
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe "State Activation Callback" do
4
4
  include SwitchStatemachine
@@ -7,46 +7,54 @@ describe "State Activation Callback" do
7
7
  before(:each) do
8
8
  class ActivationCallback
9
9
  attr_reader :called
10
- attr_reader :state
10
+ attr_reader :new_states
11
11
  attr_reader :abstract_states
12
12
  attr_reader :atomic_states
13
13
 
14
14
  def initialize
15
15
  @called = []
16
- @state = []
16
+ @new_states = []
17
17
  @abstract_states = []
18
18
  @atomic_states =[]
19
19
 
20
20
  end
21
- def activate(state,abstract_states, atomic_states)
21
+ def activate(new_states,abstract_states, atomic_states)
22
22
  @called << true
23
- @state << state
23
+ @new_states<< new_states
24
24
  @abstract_states << abstract_states
25
25
  @atomic_states << atomic_states
26
- puts "activate #{@state.last} #{@abstract_states.last} #{@atomic_states.last}"
26
+ puts "activate #{@new_states.last} #{@abstract_states.last} #{@atomic_states.last}"
27
27
  end
28
28
  end
29
29
 
30
30
  @callback = ActivationCallback.new
31
31
  end
32
-
32
+
33
33
  it "should fire on successful state change" do
34
34
  create_switch
35
35
  @sm.activation=@callback.method(:activate)
36
- @callback.called.length.should == 0
36
+
37
37
  @sm.toggle
38
38
  @callback.called.length.should == 1
39
39
  end
40
40
 
41
+ it "should call activation callback after rest" do
42
+ create_switch
43
+ @sm.activation=@callback.method(:activate)
44
+ @callback.called.length.should == 0
45
+ @sm.reset
46
+ @callback.called.length.should == 1
47
+ end
48
+
41
49
  it "should deliver new active state on state change" do
42
50
  create_switch
43
51
  @sm.activation=@callback.method(:activate)
44
52
  @sm.toggle
45
- @callback.state.last.should == :on
53
+ @callback.new_states.last.should == [:on]
46
54
  @callback.atomic_states.last.should == [:on]
47
55
  @callback.abstract_states.last.should == [:root]
48
56
  @sm.toggle
49
- @callback.state.last.should == :off
57
+ @callback.new_states.last.should == [:off]
50
58
  end
51
59
 
52
60
  it "should deliver new active state on state change of parallel state machine" do
@@ -54,24 +62,25 @@ describe "State Activation Callback" do
54
62
 
55
63
  @sm.activation=@callback.method(:activate)
56
64
  @sm.go
57
- @callback.called.length.should == 2
58
- @callback.state.last.should == :on
59
- @callback.abstract_states.last.should.eql? [:operative, :root, :onoff]
60
- @callback.atomic_states.last.should == [:locked, :on]
65
+ @callback.called.length.should == 1
66
+ @callback.new_states.last.should == [:p, :operative, :onoff, :locked, :on]
67
+ @callback.abstract_states.last.should == [:root,:p, :operative, :onoff]
68
+ @callback.atomic_states.last.should == [:locked,:on]
61
69
  @sm.toggle
62
- @callback.state.last.should == :off
63
- @callback.abstract_states.last.should.eql? [:onoff,:operative,:root]
64
- @callback.atomic_states.last.should.eql? [:off,:locked]
70
+ @callback.new_states.last.should == [:off]
71
+ @callback.abstract_states.last.should == [:root, :p, :operative, :onoff]
72
+ @callback.atomic_states.last.should == [:locked,:off]
65
73
 
66
74
  end
67
75
 
68
76
  it "activation works for on_entry ticks as well" do
77
+ pending "throwing an event inside on entry is substituted by event-less transitions"
69
78
  create_tick
70
79
  @sm.activation=@callback.method(:activate)
71
80
  @sm.toggle
72
81
  @callback.called.length.should == 2
73
- @callback.state.last.should == :off
74
- @callback.state.first.should == :on
82
+ @callback.new_states.last.should == :off
83
+ @callback.new_states.first.should == :on
75
84
  @callback.atomic_states.last.should == [:off]
76
85
  @callback.atomic_states.first.should == [:on]
77
86
  @callback.abstract_states.last.should == [:root]
@@ -82,7 +91,7 @@ describe "State Activation Callback" do
82
91
  @sm.activation=@callback.method(:activate)
83
92
  @sm.toggle
84
93
  @callback.called.length.should == 1
85
- @callback.state.last.should == :me
94
+ @callback.new_states.last.should == [:me]
86
95
  @callback.atomic_states.last.should == [:me]
87
96
  @callback.abstract_states.last.should == [:root]
88
97
  end
@@ -108,8 +117,8 @@ describe "State Activation Callback" do
108
117
 
109
118
  @sm.activation=@callback.method(:activate)
110
119
  @sm.go
111
- @callback.state.should.eql? [:unlocked,:on]
112
- @callback.called.length.should == 2
120
+ @callback.new_states.last.should == [:p, :operative, :onoff, :unlocked, :on]
121
+ @callback.called.length.should == 1
113
122
  end
114
123
 
115
124