hifsm 0.3.1 → 0.4.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.
- checksums.yaml +4 -4
- data/README.md +12 -8
- data/lib/hifsm/adapters/active_record_adapter.rb +3 -1
- data/lib/hifsm/callbacks.rb +3 -3
- data/lib/hifsm/fsm.rb +2 -9
- data/lib/hifsm/version.rb +1 -1
- data/lib/hifsm.rb +15 -3
- data/test/monster.rb +8 -4
- data/test/test_dynamic_initial_state.rb +3 -2
- data/test/test_event_guard.rb +8 -5
- data/test/test_ifless_factorial.rb +3 -2
- data/test/test_two_machines.rb +40 -13
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d654a06d28adc762a19f31cacf281568503e4de
|
4
|
+
data.tar.gz: 2d8e83ee31acc329efcce082d33d43d65574e71c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ea67c4a15aaee373a2b672184781e975ba67b2ef2681ce461a66c9c062d7bce9509d5bc01e1d8ce82848391d0a63c1df21e9530bc177251d6fb798f2a5560f5
|
7
|
+
data.tar.gz: 7eb5c56002cc5a98c74a55255e465fa280e91b9ba038b3d99355c23884f960040938b97931d65ea0406c63f84e73afe0e978ed37d490b2f6e3d485a7369bc92a
|
data/README.md
CHANGED
@@ -36,13 +36,17 @@ __This is in early development, so be careful.__
|
|
36
36
|
|
37
37
|
## Usage
|
38
38
|
|
39
|
+
Start with the [basic example](https://github.com/stiff/hifsm/blob/master/test/test_basic_fsm.rb) and then try [something](https://github.com/stiff/hifsm/blob/master/test/test_hierarchical.rb) [more](https://github.com/stiff/hifsm/blob/master/test/test_many_states.rb) [interesting](https://github.com/stiff/hifsm/blob/master/test/test_dynamic_initial_state.rb).
|
40
|
+
|
39
41
|
Here is how to use it to model a monster in a Quake-like game. It covers most Hifsm features:
|
40
42
|
|
41
43
|
```ruby
|
42
44
|
require 'hifsm'
|
43
45
|
|
44
46
|
class Monster
|
45
|
-
include Hifsm
|
47
|
+
include Hifsm
|
48
|
+
|
49
|
+
hifsm do
|
46
50
|
state :idle, :initial => true
|
47
51
|
state :attacking do
|
48
52
|
state :acquiring_target, :initial => true do
|
@@ -92,7 +96,7 @@ class Monster
|
|
92
96
|
self.target = nil
|
93
97
|
end
|
94
98
|
end
|
95
|
-
|
99
|
+
end
|
96
100
|
|
97
101
|
attr_accessor :target, :low_hp, :debug
|
98
102
|
|
@@ -103,11 +107,13 @@ class Monster
|
|
103
107
|
@low_hp = false
|
104
108
|
end
|
105
109
|
|
106
|
-
def
|
110
|
+
def act_with_tick!
|
107
111
|
debug && puts("Acting @#{state}")
|
108
|
-
|
112
|
+
act_without_tick! @tick
|
109
113
|
@tick = @tick + 1
|
110
114
|
end
|
115
|
+
alias_method :act_without_tick!, :act!
|
116
|
+
alias_method :act!, :act_with_tick!
|
111
117
|
|
112
118
|
def hit(target)
|
113
119
|
debug && puts("~~> #{target}")
|
@@ -168,11 +174,9 @@ ogre.act! # Acting @runaway
|
|
168
174
|
|
169
175
|
```
|
170
176
|
|
171
|
-
Note the use of `{..}` construct instead of `do..end` in `include`. `do..end` is treated as block for include itself, instead of `fsm_module`.
|
172
|
-
|
173
177
|
## Guards
|
174
178
|
|
175
|
-
Events are tried in order they were defined, if guard callback returns `false` then event is skipped.
|
179
|
+
Events are tried in order they were defined, if guard callback returns `false` then event is skipped as if it was not defined at all. See [example of this](https://github.com/stiff/hifsm/blob/master/test/test_event_guard.rb).
|
176
180
|
|
177
181
|
## Callbacks
|
178
182
|
|
@@ -188,7 +192,7 @@ On event:
|
|
188
192
|
|
189
193
|
If any of `before...` callbacks returns `false` then no further processing is done, no exceptions raised, machine state is not changed.
|
190
194
|
|
191
|
-
On `act!`
|
195
|
+
On `act!` state's actions called from top state to nested. If [several FSMs defined]](https://github.com/stiff/hifsm/blob/master/test/test_two_machines.rb), object's `act!` invokes them all in order as they were defined and returns value from last action.
|
192
196
|
|
193
197
|
## ActiveRecord integration
|
194
198
|
|
@@ -9,7 +9,8 @@ module Hifsm
|
|
9
9
|
|
10
10
|
module ClassMethods
|
11
11
|
def hifsm(column, &block)
|
12
|
-
|
12
|
+
super
|
13
|
+
|
13
14
|
before_save "hifsm_write_#{column}_attribute"
|
14
15
|
|
15
16
|
send("#{column}_machine_definition").all_states.each do |st|
|
@@ -40,5 +41,6 @@ module Hifsm
|
|
40
41
|
end
|
41
42
|
|
42
43
|
ActiveRecord::Base.class_eval do
|
44
|
+
include Hifsm
|
43
45
|
include Hifsm::Adapters::ActiveRecordAdapter
|
44
46
|
end
|
data/lib/hifsm/callbacks.rb
CHANGED
@@ -4,10 +4,10 @@ class Callbacks
|
|
4
4
|
def invoke(target, cb, *args)
|
5
5
|
if cb.nil?
|
6
6
|
elsif cb.is_a? Symbol
|
7
|
-
if target.method(cb).arity
|
8
|
-
target.send(cb, *args)
|
9
|
-
else
|
7
|
+
if target.method(cb).arity.zero?
|
10
8
|
target.send(cb)
|
9
|
+
else
|
10
|
+
target.send(cb, *args)
|
11
11
|
end
|
12
12
|
else
|
13
13
|
target.instance_exec(*args, &cb)
|
data/lib/hifsm/fsm.rb
CHANGED
@@ -82,16 +82,9 @@ module Hifsm
|
|
82
82
|
machine_var = "@#{name}_machine"
|
83
83
|
machine_name = "#{name}_machine"
|
84
84
|
|
85
|
-
module_class_methods = Module.new do
|
86
|
-
define_method("#{machine_name}_definition") { fsm }
|
87
|
-
end
|
88
|
-
|
89
85
|
Module.new do
|
90
|
-
|
91
|
-
|
92
|
-
base.class_exec do
|
93
|
-
extend const_get('ClassMethods')
|
94
|
-
end
|
86
|
+
define_singleton_method :included do |base|
|
87
|
+
base.send(:define_singleton_method, "#{machine_name}_definition") { fsm }
|
95
88
|
end
|
96
89
|
|
97
90
|
# <state>_machine returns machine instance
|
data/lib/hifsm/version.rb
CHANGED
data/lib/hifsm.rb
CHANGED
@@ -18,9 +18,21 @@ module Hifsm
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
def self.included(base)
|
22
|
+
base.send(:extend, ClassMethods) unless base.respond_to?(:hifsm)
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
def hifsm(name = :state, &block)
|
27
|
+
include FSM::new(name, &block).to_module
|
28
|
+
|
29
|
+
# act!
|
30
|
+
define_method("act_with_#{name}_machine!") do |*args|
|
31
|
+
send("act_without_#{name}_machine!", *args) if respond_to?("act_without_#{name}_machine!")
|
32
|
+
send("#{name}_machine").act!(*args)
|
33
|
+
end
|
34
|
+
alias_method "act_without_#{name}_machine!", :act! if method_defined?(:act!)
|
35
|
+
alias_method :act!, "act_with_#{name}_machine!"
|
24
36
|
end
|
25
37
|
end
|
26
38
|
end
|
data/test/monster.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'hifsm'
|
2
2
|
|
3
3
|
class Monster
|
4
|
-
include Hifsm
|
4
|
+
include Hifsm
|
5
|
+
|
6
|
+
hifsm do
|
5
7
|
state :idle, :initial => true
|
6
8
|
state :attacking do
|
7
9
|
state :acquiring_target, :initial => true do
|
@@ -51,7 +53,7 @@ class Monster
|
|
51
53
|
self.target = nil
|
52
54
|
end
|
53
55
|
end
|
54
|
-
|
56
|
+
end
|
55
57
|
|
56
58
|
attr_accessor :target, :low_hp, :debug
|
57
59
|
|
@@ -62,11 +64,13 @@ class Monster
|
|
62
64
|
@low_hp = false
|
63
65
|
end
|
64
66
|
|
65
|
-
def
|
67
|
+
def act_with_tick!
|
66
68
|
debug && puts("Acting @#{state}")
|
67
|
-
|
69
|
+
act_without_tick! @tick
|
68
70
|
@tick = @tick + 1
|
69
71
|
end
|
72
|
+
alias_method :act_without_tick!, :act!
|
73
|
+
alias_method :act!, :act_with_tick!
|
70
74
|
|
71
75
|
def hit(target)
|
72
76
|
debug && puts("~~> #{target}")
|
@@ -2,7 +2,8 @@ require 'setup_tests'
|
|
2
2
|
|
3
3
|
class TestDynamicInitialState < Minitest::Test
|
4
4
|
class Value < Struct.new(:value)
|
5
|
-
include Hifsm
|
5
|
+
include Hifsm
|
6
|
+
hifsm :group do
|
6
7
|
state :few do
|
7
8
|
state :very
|
8
9
|
state :almost
|
@@ -13,7 +14,7 @@ class TestDynamicInitialState < Minitest::Test
|
|
13
14
|
end
|
14
15
|
state :throng
|
15
16
|
state :swarm
|
16
|
-
|
17
|
+
end
|
17
18
|
|
18
19
|
def initial_group
|
19
20
|
case value
|
data/test/test_event_guard.rb
CHANGED
@@ -2,7 +2,9 @@ require 'setup_tests'
|
|
2
2
|
|
3
3
|
class TestEventGuard < Minitest::Test
|
4
4
|
class Wall < Struct.new(:stones)
|
5
|
-
include Hifsm
|
5
|
+
include Hifsm
|
6
|
+
|
7
|
+
hifsm do
|
6
8
|
state :constructed, :initial => true
|
7
9
|
state :broken
|
8
10
|
|
@@ -10,9 +12,10 @@ class TestEventGuard < Minitest::Test
|
|
10
12
|
event :break, :from => :constructed, :to => :broken, :guard => proc { stones < 5 }
|
11
13
|
|
12
14
|
# event parameters are passed to guards only if arity > 0
|
13
|
-
event :
|
14
|
-
|
15
|
-
|
15
|
+
event :break, :from => :constructed, :to => :broken, :guard => :breakable?
|
16
|
+
end
|
17
|
+
|
18
|
+
def breakable?(hits = 1)
|
16
19
|
hits * 5 > stones
|
17
20
|
end
|
18
21
|
end
|
@@ -32,7 +35,7 @@ class TestEventGuard < Minitest::Test
|
|
32
35
|
|
33
36
|
def test_can_break_thick_wall_if_hit_3_times
|
34
37
|
wall = Wall.new(10)
|
35
|
-
wall.
|
38
|
+
wall.break 3
|
36
39
|
assert_equal 'broken', wall.state
|
37
40
|
end
|
38
41
|
|
@@ -2,7 +2,8 @@ require 'setup_tests'
|
|
2
2
|
|
3
3
|
class TestIflessFactorial < Minitest::Test
|
4
4
|
class Value < Struct.new(:value)
|
5
|
-
include Hifsm
|
5
|
+
include Hifsm
|
6
|
+
hifsm do
|
6
7
|
state :idle, :initial => true
|
7
8
|
state :computing
|
8
9
|
|
@@ -16,7 +17,7 @@ class TestIflessFactorial < Minitest::Test
|
|
16
17
|
self.value *= x
|
17
18
|
end
|
18
19
|
end
|
19
|
-
|
20
|
+
end
|
20
21
|
end
|
21
22
|
|
22
23
|
def factorial(n)
|
data/test/test_two_machines.rb
CHANGED
@@ -1,27 +1,34 @@
|
|
1
1
|
require 'setup_tests'
|
2
2
|
|
3
3
|
class TestTwoMachines < Minitest::Test
|
4
|
-
class ColorPrinter
|
5
|
-
include Hifsm
|
4
|
+
class ColorPrinter < Struct.new(:log)
|
5
|
+
include Hifsm
|
6
|
+
|
7
|
+
hifsm :color do
|
8
|
+
state :red, :initial => true
|
9
|
+
state :green
|
10
|
+
state :blue do
|
11
|
+
action do
|
12
|
+
self.log = [log, 'blue'].compact.join(' ; ')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
event :cycle_color!, :from => :red, :to => :green
|
17
|
+
event :cycle_color!, :from => :green, :to => :blue
|
18
|
+
event :cycle_color!, :from => :blue, :to => :red
|
19
|
+
end
|
20
|
+
|
21
|
+
hifsm :working_state do
|
6
22
|
state :off, :initial => true
|
7
23
|
state :on do
|
8
24
|
action do
|
9
|
-
color_machine.to_s
|
25
|
+
'printing ' + color_machine.to_s
|
10
26
|
end
|
11
27
|
end
|
12
28
|
|
13
29
|
event :toggle, :from => :off, :to => :on
|
14
30
|
event :toggle, :from => :on, :to => :off
|
15
|
-
|
16
|
-
include Hifsm.fsm_module(:color) {
|
17
|
-
state :red, :initial => true
|
18
|
-
state :green
|
19
|
-
state :blue
|
20
|
-
|
21
|
-
event :cycle_color!, :from => :red, :to => :green
|
22
|
-
event :cycle_color!, :from => :green, :to => :blue
|
23
|
-
event :cycle_color!, :from => :blue, :to => :red
|
24
|
-
}
|
31
|
+
end
|
25
32
|
end
|
26
33
|
|
27
34
|
def setup
|
@@ -38,4 +45,24 @@ class TestTwoMachines < Minitest::Test
|
|
38
45
|
assert_equal 'red', @color_printer.color
|
39
46
|
end
|
40
47
|
|
48
|
+
def test_color_changes_independently
|
49
|
+
@color_printer.cycle_color!
|
50
|
+
assert_equal 'off', @color_printer.working_state
|
51
|
+
assert_equal 'green', @color_printer.color
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_working_state_changes_independently
|
55
|
+
@color_printer.cycle_color!
|
56
|
+
assert_equal 'off', @color_printer.working_state
|
57
|
+
assert_equal 'green', @color_printer.color
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_both_machines_act
|
61
|
+
@color_printer.cycle_color!
|
62
|
+
@color_printer.toggle
|
63
|
+
@color_printer.cycle_color!
|
64
|
+
assert_equal 'printing blue', @color_printer.act!
|
65
|
+
assert_equal 'blue', @color_printer.log
|
66
|
+
end
|
67
|
+
|
41
68
|
end
|