simplificator-fsm 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +19 -5
- data/VERSION.yml +1 -1
- data/lib/fsm/builder.rb +1 -0
- data/lib/fsm/machine.rb +34 -29
- data/lib/fsm/options.rb +0 -1
- data/test/transition_test.rb +15 -9
- metadata +2 -2
data/README.markdown
CHANGED
@@ -5,6 +5,7 @@ FSM is a simple finite state machine
|
|
5
5
|
## Usage
|
6
6
|
class Water
|
7
7
|
include FSM
|
8
|
+
# The state machine is specified as a block in define_fsm.
|
8
9
|
define_fsm do
|
9
10
|
# now define all the states
|
10
11
|
# you can add :enter / :exit callbacks (callback can be a String, Symbol or Proc)
|
@@ -14,12 +15,12 @@ FSM is a simple finite state machine
|
|
14
15
|
state(:liquid)
|
15
16
|
state(:solid, :enter => :on_enter_solid, :exit => :on_exit_solid)
|
16
17
|
|
17
|
-
# define all valid transitions (name, from, to)
|
18
|
+
# define all valid transitions (arguments are name of transition, from state name, to state name)
|
18
19
|
# you can define callbacks which are called only on this transition
|
19
|
-
transition(:
|
20
|
-
transition(:
|
21
|
-
transition(:
|
22
|
-
transition(:
|
20
|
+
transition(:heat_up, :solid, :liquid, :event => :liquified)
|
21
|
+
transition(:heat_up, :liquid, :gas) # look mam.... two transitions with same name
|
22
|
+
transition(:cool_down, :gas, :liquid)
|
23
|
+
transition(:cool_down, :liquid, :solid)
|
23
24
|
|
24
25
|
# define the attribute which is used to store the state (defaults to :state)
|
25
26
|
state(:state_of_material)
|
@@ -27,6 +28,19 @@ FSM is a simple finite state machine
|
|
27
28
|
# define the initial state (defaults to the first state defined - :gas in this sample)
|
28
29
|
initial(:liquid)
|
29
30
|
end
|
31
|
+
|
32
|
+
private
|
33
|
+
# callbacks here...
|
34
|
+
def ...
|
30
35
|
end
|
36
|
+
|
37
|
+
# then you can call these methods
|
38
|
+
w = Water.new
|
39
|
+
w.heat # the name of the transition is the name of the method
|
40
|
+
w.reachable_state_names
|
41
|
+
w.available_transition_names
|
42
|
+
w.cool_down # again... it's the name of the transition
|
43
|
+
w.state_of_material
|
44
|
+
|
31
45
|
## Copyright
|
32
46
|
Copyright (c) 2009 simplificator GmbH. See LICENSE for details.
|
data/VERSION.yml
CHANGED
data/lib/fsm/builder.rb
CHANGED
data/lib/fsm/machine.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
module FSM
|
2
2
|
class Machine
|
3
|
-
attr_accessor(:initial_state_name, :current_state_attribute_name, :states)
|
3
|
+
attr_accessor(:initial_state_name, :current_state_attribute_name, :states, :transitions)
|
4
4
|
|
5
5
|
def initialize(target_class)
|
6
6
|
@target_class = target_class
|
7
|
-
self.states =
|
7
|
+
self.states = []
|
8
|
+
self.transitions = []
|
8
9
|
self.current_state_attribute_name = :state
|
9
10
|
end
|
10
11
|
|
@@ -17,29 +18,24 @@ module FSM
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def state(name, options = {})
|
20
|
-
raise "State is already defined: '#{name}'" if self.
|
21
|
-
self.states
|
21
|
+
raise "State is already defined: '#{name}'" if self.state_for_name(name, true)
|
22
|
+
self.states << State.new(name, options)
|
22
23
|
self.initial_state_name=(name) unless self.initial_state_name
|
23
24
|
end
|
24
25
|
|
25
26
|
def initial_state_name=(value)
|
26
|
-
raise UnknownState.new("Unknown state '#{value}'. Define states first with state(name)") unless self.
|
27
|
+
raise UnknownState.new("Unknown state '#{value}'. Define states first with state(name)") unless self.state_for_name(value, true)
|
27
28
|
@initial_state_name = value
|
28
29
|
end
|
29
30
|
|
30
31
|
def transition(name, from_name, to_name, options = {})
|
31
32
|
raise ArgumentError.new("name, from_name and to_name are required") if name.nil? || from_name.nil? || to_name.nil?
|
32
|
-
raise UnknownState.new("Unknown source state '#{from}'") unless self.states[from_name]
|
33
|
-
raise UnknownState.new("Unknown target state '#{to}'") unless self.states[to_name]
|
34
|
-
|
35
|
-
from_state = self.states[from_name]
|
36
|
-
to_state = self.states[to_name]
|
37
33
|
|
34
|
+
from_state = self.state_for_name(from_name)
|
35
|
+
to_state = self.state_for_name(to_name)
|
38
36
|
transition = Transition.new(name, from_state, to_state, options)
|
39
37
|
from_state.add_transition(transition)
|
40
|
-
|
41
|
-
define_transition_method(name)
|
42
|
-
|
38
|
+
self.transitions << transition
|
43
39
|
end
|
44
40
|
|
45
41
|
def self.get_current_state_name(target)
|
@@ -55,24 +51,45 @@ module FSM
|
|
55
51
|
def reachable_states(target)
|
56
52
|
reachables = []
|
57
53
|
current_state_name = Machine.get_current_state_name(target)
|
58
|
-
self.states.map do |
|
54
|
+
self.states.map do |state|
|
59
55
|
reachables += state.to_states if state.name == current_state_name
|
60
56
|
end
|
61
57
|
reachables
|
62
58
|
end
|
63
59
|
|
64
60
|
def available_transitions(target)
|
65
|
-
|
61
|
+
current_state_name = Machine.get_current_state_name(target)
|
62
|
+
state = state_for_name(current_state_name)
|
63
|
+
state.transitions.values
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def build_transition_methods
|
68
|
+
names = self.transitions.map() {|transition| transition.name}.uniq
|
69
|
+
names.each do |name|
|
70
|
+
define_transition_method(name)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Lookup a State by it's name
|
75
|
+
# raises ArgumentError if state can not be found unless quiet is set to true
|
76
|
+
def state_for_name(name, quiet = false)
|
77
|
+
state = self.states.detect() {|state| state.name == name}
|
78
|
+
raise ArgumentError.new("Unknonw state '#{name}'") unless quiet || state
|
79
|
+
state
|
66
80
|
end
|
67
81
|
|
68
82
|
private
|
69
83
|
|
84
|
+
# defines a transition method on the target class.
|
85
|
+
# this is the method triggering the state change.
|
86
|
+
#
|
70
87
|
def define_transition_method(name)
|
71
88
|
@target_class.instance_eval do
|
72
89
|
define_method(name) do |*args|
|
73
90
|
machine = Machine[self.class]
|
74
91
|
from_name = Machine.get_current_state_name(self)
|
75
|
-
from_state = machine.
|
92
|
+
from_state = machine.state_for_name(from_name)
|
76
93
|
|
77
94
|
entry = from_state.transitions.detect() {|to_name, tr| tr.name == name}
|
78
95
|
transition = entry.last if entry
|
@@ -83,19 +100,7 @@ module FSM
|
|
83
100
|
transition.fire_event(self, args)
|
84
101
|
to_state.enter(self)
|
85
102
|
Machine.set_current_state_name(self, to_state.name)
|
86
|
-
true
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
#to_state = machine.states[to_name]
|
91
|
-
#transition = from_state.transitions[to_name]
|
92
|
-
#raise InvalidStateTransition.new("No transition defined from #{from_name} -> #{to_name}") unless transition
|
93
|
-
|
94
|
-
#from_state.exit(self)
|
95
|
-
#transition.fire_event(self, args)
|
96
|
-
#to_state.enter(self)
|
97
|
-
#Machine.set_current_state_name(self, to_name)
|
98
|
-
#true # at the moment always return true ... as soon as we have guards or thelike this could be false as well
|
103
|
+
true # at the moment always return true ... as soon as we have guards or thelike this could be false as well
|
99
104
|
end
|
100
105
|
end
|
101
106
|
end
|
data/lib/fsm/options.rb
CHANGED
data/test/transition_test.rb
CHANGED
@@ -2,37 +2,43 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class TransitionTest < Test::Unit::TestCase
|
4
4
|
context 'Initializer' do
|
5
|
+
|
6
|
+
|
5
7
|
should 'require name, from and to' do
|
8
|
+
bli_state = FSM::State.new('bli')
|
9
|
+
blo_state = FSM::State.new('blo')
|
6
10
|
assert_raise(ArgumentError) do
|
7
11
|
FSM::Transition.new(nil, nil, nil)
|
8
12
|
end
|
9
13
|
assert_raise(ArgumentError) do
|
10
|
-
FSM::Transition.new(
|
14
|
+
FSM::Transition.new(:name, nil, nil)
|
11
15
|
end
|
12
16
|
assert_raise(ArgumentError) do
|
13
|
-
FSM::Transition.new(nil,
|
17
|
+
FSM::Transition.new(nil, bli_state, nil)
|
14
18
|
end
|
15
19
|
assert_raise(ArgumentError) do
|
16
|
-
FSM::Transition.new(nil, nil,
|
20
|
+
FSM::Transition.new(nil, nil, blo_state)
|
17
21
|
end
|
18
22
|
assert_raise(ArgumentError) do
|
19
|
-
FSM::Transition.new(
|
23
|
+
FSM::Transition.new(:name, bli_state, nil)
|
20
24
|
end
|
21
25
|
assert_raise(ArgumentError) do
|
22
|
-
FSM::Transition.new(nil,
|
26
|
+
FSM::Transition.new(nil, blo_state, blo_state)
|
23
27
|
end
|
24
28
|
assert_raise(ArgumentError) do
|
25
|
-
FSM::Transition.new(
|
29
|
+
FSM::Transition.new(:name, nil, bli_state)
|
26
30
|
end
|
27
31
|
|
28
|
-
FSM::Transition.new(
|
32
|
+
FSM::Transition.new(:name, bli_state, blo_state)
|
29
33
|
end
|
30
34
|
|
31
35
|
should 'allow only valid options' do
|
36
|
+
bli_state = FSM::State.new('bli')
|
37
|
+
blo_state = FSM::State.new('blo')
|
32
38
|
assert_raise(ArgumentError) do
|
33
|
-
FSM::Transition.new(
|
39
|
+
FSM::Transition.new(:name, bli_state, blo_state, :foo => 12)
|
34
40
|
end
|
35
|
-
FSM::Transition.new(
|
41
|
+
FSM::Transition.new(:name, bli_state, blo_state, :event => :some)
|
36
42
|
end
|
37
43
|
end
|
38
44
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simplificator-fsm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- simplificator
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-05-
|
12
|
+
date: 2009-05-19 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|