barebone-fsm 0.0.1.1 → 0.0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +86 -0
- data/lib/barebone-fsm.rb +57 -69
- metadata +5 -3
data/README.rdoc
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
== Overview
|
2
|
+
This module implements a basic finite-state machine (FSM).
|
3
|
+
An FSM consists of a finite number of states,
|
4
|
+
with one of them being the current state of the FSM.
|
5
|
+
Transitions are defined between states, which are triggered on events.
|
6
|
+
For details on FSM, see this wiki page: {FSM}[http://en.wikipedia.org/wiki/Finite-state_machine].
|
7
|
+
|
8
|
+
The motivation behind the module was to implement a very basic barebone FSM in Ruby.
|
9
|
+
Features are kept at minimum as well as the code.
|
10
|
+
Only two classes for the FSM are defined as the following:
|
11
|
+
* FSM -> the finite state machine class
|
12
|
+
* FSMState -> the state class
|
13
|
+
|
14
|
+
Author:: Md. Imrul Hassan (mailto:mihassan@gmail.com)
|
15
|
+
Copyright:: Copyright: Md. Imrul Hassan, 2013
|
16
|
+
|
17
|
+
== Features
|
18
|
+
Apart from having support for states and events, this module offers the following features:
|
19
|
+
1. Default state
|
20
|
+
2. Default event for each state
|
21
|
+
3. Entry and exit events for each state
|
22
|
+
4. DSL like coding style
|
23
|
+
5. Access to state variables (including @state and @event) inside event blocks.
|
24
|
+
Currently the state variables can only be shared among event blocks of the same state.
|
25
|
+
|
26
|
+
== Usage
|
27
|
+
The FSM can be setup and triggered Succinctly using Domain Specific Language(DSL) like coding style.
|
28
|
+
There are two methods to build and run which are defined for both FSM and FSMState classes.
|
29
|
+
These methods, #build and #run, are alias of each other and can be used interchangebly.
|
30
|
+
A basic fintite-state machine for a microwave is simulated in the following example:
|
31
|
+
|
32
|
+
fsm = FSM::FSM.new(:stopped) # :stopped is the default state to fall back
|
33
|
+
|
34
|
+
fsm.build do # the states and events can be configured within the build block
|
35
|
+
state :stopped do # the events can be setup within the state block for :stopped state
|
36
|
+
event :open do # the event block is run when the event :open is triggered
|
37
|
+
puts "[Stopped]: Door Opened"
|
38
|
+
:open # the return value of the event block, :open, is the next state
|
39
|
+
end
|
40
|
+
event :start do
|
41
|
+
puts "[Stopped]: Started"
|
42
|
+
:started
|
43
|
+
end
|
44
|
+
end
|
45
|
+
state :open do
|
46
|
+
event :close do
|
47
|
+
puts "[#{@state}]: Door #{@event}" # the event block has access to state variables such as @state and @event
|
48
|
+
:stopped
|
49
|
+
end
|
50
|
+
end
|
51
|
+
state :started do
|
52
|
+
event :open do
|
53
|
+
@open_time = Time::now # new state variables can be defined
|
54
|
+
puts "[Started]: Door Opened"
|
55
|
+
:open
|
56
|
+
end
|
57
|
+
event :stop do
|
58
|
+
puts "The door was opened at #{@open_time}" # state variables from other event blocks can be used
|
59
|
+
puts "[Started]: Door Stopped after #{elapsed_time}"
|
60
|
+
:stopped
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
fsm.run do # the events can be triggered from the run block
|
66
|
+
event :start
|
67
|
+
event :open
|
68
|
+
event :close
|
69
|
+
event :start
|
70
|
+
event :stop
|
71
|
+
end
|
72
|
+
|
73
|
+
puts fsm
|
74
|
+
|
75
|
+
== Status
|
76
|
+
The module works as it is, but further testing and documentation is needed.
|
77
|
+
The api is not stable yet, it may go trhough lots of changes before the first stable version is released.
|
78
|
+
I am open to any suggestion or request to support custom features.
|
79
|
+
|
80
|
+
== Changes
|
81
|
+
* Version: 0.0.1.2
|
82
|
+
* @state instance variable for FSM is dropped in favour of the #state instance method.
|
83
|
+
* #state method and index operator [] now accept nil arguement for state name, which returns the current state.
|
84
|
+
* Two methods #build and #run are added to FSM and FSMState classes to support DSL like features.
|
85
|
+
* Explicit parameter passing for the event blocks is no longer available.
|
86
|
+
Instead the block code for the events now have access to all instance variables of FSMState class such as @state and @event.
|
data/lib/barebone-fsm.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
1
|
##
|
2
|
-
# == Overview
|
3
|
-
# ---
|
4
2
|
# This module implements a basic finite-state machine (FSM).
|
5
3
|
# An FSM consists of a finite number of states,
|
6
4
|
# with one of them being the current state of the FSM.
|
@@ -12,62 +10,12 @@
|
|
12
10
|
# Only two classes for the FSM are defined as the following:
|
13
11
|
# * FSM -> the finite state machine class
|
14
12
|
# * FSMState -> the state class
|
13
|
+
#
|
14
|
+
# For further details see the README file.
|
15
15
|
#
|
16
16
|
# Author:: Md. Imrul Hassan (mailto:mihassan@gmail.com)
|
17
17
|
# Copyright:: Copyright: Md. Imrul Hassan, 2013
|
18
18
|
#
|
19
|
-
# == Status
|
20
|
-
# The module works, but further testing and documentation is needed.
|
21
|
-
# The api is not stable yet, I may decide to change the way FSM is used.
|
22
|
-
#
|
23
|
-
# == Features
|
24
|
-
# Apart from having support for states and events, this module offers the following features:
|
25
|
-
# 1. Default state
|
26
|
-
# 2. Default event for each state
|
27
|
-
# 3. Entry and exit events for each state
|
28
|
-
#
|
29
|
-
# == Usage
|
30
|
-
# ---
|
31
|
-
# A simple example:
|
32
|
-
#
|
33
|
-
# fsm = FSM::FSM.new
|
34
|
-
# state = fsm[:state_name]
|
35
|
-
# state.event(:event_name) do |st, ev|
|
36
|
-
# puts "#{ev} triggered on state #{st}"
|
37
|
-
# :new_state}
|
38
|
-
# end
|
39
|
-
# fsm.event :event_name
|
40
|
-
#
|
41
|
-
# A complete example:
|
42
|
-
#
|
43
|
-
# fsm = FSM::FSM.new(:stopped)
|
44
|
-
#
|
45
|
-
# fsm[:stopped].event(:open) { puts "[Stopped]: Door Opened"; :open}
|
46
|
-
# fsm[:stopped].event(:start) { puts "[Stopped]: Started"; :started}
|
47
|
-
# fsm[:stopped].event(:enter) {|st, ev| puts "[Stopped]: Eneter into state #{st}"}
|
48
|
-
# fsm[:stopped].event(:exit) {|st, ev| puts "[Stopped]: Exit from state #{st}"}
|
49
|
-
#
|
50
|
-
# fsm[:open].event(:close) { puts "[Open]: Door Closed"; :stopped}
|
51
|
-
# fsm[:open].event(:default) {|st, ev| puts "[Open]: Event [#{ev}] not defined in state #{st}"; :open}
|
52
|
-
# fsm[:open].event(:enter) {|st, ev| puts "[Open]: Eneter into state #{st}"}
|
53
|
-
# fsm[:open].event(:exit) {|st, ev| puts "[Open]: Exit from state #{st}"}
|
54
|
-
#
|
55
|
-
# fsm[:started].event(:open) { puts "[Started]: Door Opened"; :open}
|
56
|
-
# fsm[:started].event(:stop) { puts "[Started]: Stopped"; :stopped}
|
57
|
-
# fsm[:started].event(:enter) {|st, ev| puts "[Started]: Eneter into state #{st}"}
|
58
|
-
# fsm[:started].event(:exit) {|st, ev| puts "[Started]: Exit from state #{st}"}
|
59
|
-
#
|
60
|
-
# puts fsm
|
61
|
-
#
|
62
|
-
# fsm.event :open
|
63
|
-
# fsm.event :start
|
64
|
-
# fsm.event :close
|
65
|
-
# fsm.event :start
|
66
|
-
# fsm.event :stop
|
67
|
-
# fsm.event :start
|
68
|
-
#
|
69
|
-
# puts sm
|
70
|
-
#
|
71
19
|
module FSM
|
72
20
|
|
73
21
|
# == Overview
|
@@ -83,31 +31,45 @@ module FSM
|
|
83
31
|
#
|
84
32
|
class FSMState
|
85
33
|
|
86
|
-
|
34
|
+
# The readonly state variable represents an unique state.
|
35
|
+
# Though it can have any data type, usage of symbol or string is preferable.
|
36
|
+
attr_reader :state
|
87
37
|
|
88
38
|
def initialize(state_name)
|
89
|
-
@
|
39
|
+
@state = state_name
|
90
40
|
@events = {}
|
91
41
|
end
|
92
42
|
|
93
43
|
def to_s()
|
94
|
-
@
|
44
|
+
@state.to_s +
|
95
45
|
": [" +
|
96
46
|
@events.keys.map(&:to_s).join(', ') +
|
97
47
|
"]"
|
98
48
|
end
|
99
49
|
|
50
|
+
# When the event_block is provided, it sets up a new event for this state.
|
51
|
+
# Otherwise, when the event_block is missing, the event_name is triggered.
|
52
|
+
# If the event is nil or not already setup, then the default event is triggered.
|
100
53
|
def event(event_name, &event_block)
|
101
|
-
if block_given? then
|
54
|
+
if event_name and block_given? then
|
102
55
|
@events[event_name] = event_block
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
56
|
+
elsif event_name and @events.has_key? event_name then
|
57
|
+
@event = event_name
|
58
|
+
self.instance_eval &@events[@event]
|
59
|
+
elsif @events.has_key? :default then
|
60
|
+
@event = :default
|
61
|
+
self.instance_eval &@events[@event]
|
109
62
|
end
|
110
63
|
end
|
64
|
+
|
65
|
+
# The #build/#run method sets up the events as given in the build_block.
|
66
|
+
# Only event method is supported within the build_block with the name of the event and an optional block supplied.
|
67
|
+
# The operation for each such line is carried out by the #event method.
|
68
|
+
def build(&build_block)
|
69
|
+
self.instance_eval &build_block
|
70
|
+
end
|
71
|
+
|
72
|
+
alias_method :run, :build
|
111
73
|
|
112
74
|
end
|
113
75
|
|
@@ -131,7 +93,6 @@ module FSM
|
|
131
93
|
# end
|
132
94
|
#
|
133
95
|
class FSM
|
134
|
-
attr_reader :state
|
135
96
|
|
136
97
|
def initialize(default_state=nil)
|
137
98
|
@states = {}
|
@@ -145,19 +106,36 @@ module FSM
|
|
145
106
|
"FSM" +
|
146
107
|
": {" +
|
147
108
|
@states.values.map{ |st|
|
148
|
-
(st.
|
109
|
+
(st.state==@state ? ">" : "") + st.to_s
|
149
110
|
}.join(', ') +
|
150
111
|
"}"
|
151
112
|
end
|
152
113
|
|
153
|
-
|
154
|
-
|
114
|
+
# It returns the FSMState object for state_name.
|
115
|
+
# If the state is missing, then it first sets up the state, and then returns the newly created state.
|
116
|
+
def [](state_name=nil)
|
117
|
+
state_name ||= @state
|
118
|
+
if state_name and not @states.has_key? state_name then
|
155
119
|
@states[state_name] = FSMState.new(state_name)
|
156
120
|
@state ||= state_name
|
157
121
|
end
|
158
122
|
@states[state_name]
|
159
123
|
end
|
124
|
+
|
125
|
+
# When the state_block is provided, it sets up a new state.
|
126
|
+
# Otherwise, when the state_block is missing, the FSMState object for state_name is returned.
|
127
|
+
# If called without any parameter, then the current state is returned.
|
128
|
+
def state(state_name=nil, &state_block)
|
129
|
+
if block_given? then
|
130
|
+
self.[](state_name).build &state_block
|
131
|
+
else
|
132
|
+
self.[](state_name)
|
133
|
+
end
|
134
|
+
end
|
160
135
|
|
136
|
+
# It triggers the event_name event and changes the state of the FSM to its new state.
|
137
|
+
# The :entry and :exit events are called on the leaving state and the entering state.
|
138
|
+
# If the event does not mention the new state, then the state changes to the default state.
|
161
139
|
def event(event_name)
|
162
140
|
@states[@state].event :exit
|
163
141
|
new_state = @states[@state].event event_name
|
@@ -168,5 +146,15 @@ module FSM
|
|
168
146
|
@states[@state].event :enter
|
169
147
|
end
|
170
148
|
|
149
|
+
# The #build/#run method sets up the states and events as given in the build_block.
|
150
|
+
# Only state and event methods are supported within the build_block with the name of the state/event and a block supplied.
|
151
|
+
# The operation for each such line is carried out by the #state/#event method.
|
152
|
+
def build(&build_block)
|
153
|
+
self.instance_eval &build_block
|
154
|
+
end
|
155
|
+
|
156
|
+
alias_method :run, :build
|
157
|
+
|
171
158
|
end
|
172
|
-
|
159
|
+
|
160
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: barebone-fsm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.1.
|
4
|
+
version: 0.0.1.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,16 +9,18 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-19 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: A barebone implementation of finite state machine keeping simplicity
|
15
15
|
in mind.
|
16
16
|
email: mihassan@gmail.com
|
17
17
|
executables: []
|
18
18
|
extensions: []
|
19
|
-
extra_rdoc_files:
|
19
|
+
extra_rdoc_files:
|
20
|
+
- README.rdoc
|
20
21
|
files:
|
21
22
|
- lib/barebone-fsm.rb
|
23
|
+
- README.rdoc
|
22
24
|
homepage: http://rubygems.org/gems/barebone-fsm
|
23
25
|
licenses: []
|
24
26
|
post_install_message:
|