hifsm 0.4.5 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +6 -5
- data/lib/hifsm/adapters/active_record_adapter.rb +1 -1
- data/lib/hifsm/callbacks.rb +22 -14
- data/lib/hifsm/event.rb +4 -7
- data/lib/hifsm/fsm.rb +9 -0
- data/lib/hifsm/state.rb +10 -13
- data/lib/hifsm/version.rb +1 -1
- data/lib/hifsm.rb +3 -5
- data/test/monster.rb +3 -2
- data/test/test_basic_fsm.rb +5 -0
- data/test/test_before_returning_false.rb +6 -2
- data/test/test_dynamic_initial_state.rb +1 -1
- data/test/test_event_guard.rb +19 -5
- data/test/test_groupped_transitions.rb +2 -1
- data/test/test_hierarchical.rb +8 -0
- data/test/test_ifless_factorial.rb +1 -1
- data/test/test_two_machines.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e8c840d5a3b6429146d55b87579db8d29ca6c6c6
|
4
|
+
data.tar.gz: 2130df37d77e426940c430a7b9a29abc4619838e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a182af19cfb5cc12291e395c4041934d9ae5c25997d865ab573807bbf18b27efcc7c131038b0f3bd18f8fae5d5d4440ce4d08bece8ea0b8efd8dd3c3bdb668bb
|
7
|
+
data.tar.gz: b9e9e923ea20dfbf4cb335a65c297b877397559cfc2bc2b12be2a7ea1d34b9837fec772e8c857431a863fd7a1dac48d35601d232741bb6f39f3b8e081e9597d6
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -23,8 +23,6 @@ Or install it yourself as:
|
|
23
23
|
|
24
24
|
Written in Ruby 1.8-style (hashes, lambdas), but few non-essential 1.9 niceties used, tested in 2+.
|
25
25
|
|
26
|
-
__This is in early development, so be careful.__
|
27
|
-
|
28
26
|
## Features
|
29
27
|
|
30
28
|
* Easy to use
|
@@ -44,7 +42,7 @@ Here is how to use it to model a monster in a Quake-like game. It covers most Hi
|
|
44
42
|
require 'hifsm'
|
45
43
|
|
46
44
|
class Monster
|
47
|
-
|
45
|
+
extend Hifsm
|
48
46
|
|
49
47
|
hifsm do
|
50
48
|
state :idle, :initial => true
|
@@ -58,7 +56,7 @@ class Monster
|
|
58
56
|
state :pursuing do
|
59
57
|
before_enter do
|
60
58
|
self.roar!
|
61
|
-
true # since
|
59
|
+
true # since it would stop processing if roar! returns false
|
62
60
|
end
|
63
61
|
action do
|
64
62
|
step_towards target
|
@@ -162,6 +160,7 @@ ogre.act! # Acting @attacking.pursuing
|
|
162
160
|
# step step player2
|
163
161
|
ogre.reached
|
164
162
|
puts ogre.state # attacking.fighting
|
163
|
+
# ogre.attacking_fighting? = true
|
165
164
|
ogre.act! # Acting @attacking.fighting
|
166
165
|
# 6: Attack!
|
167
166
|
# ~~> player2
|
@@ -191,7 +190,7 @@ On event:
|
|
191
190
|
* to_state.after_enter
|
192
191
|
* event.after
|
193
192
|
|
194
|
-
If any of `before...` callbacks returns `false` then no further processing is done, no exceptions raised, machine state is not changed.
|
193
|
+
If any of `before...` callbacks returns `false` (literally, `nil` equals to `true` here) then no further processing is done, no exceptions raised, machine state is not changed.
|
195
194
|
|
196
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.
|
197
196
|
|
@@ -221,6 +220,8 @@ order.start_processing.save # 'processing.packaging'
|
|
221
220
|
|
222
221
|
# scopes defined automatically. parent scopes looked up via like "processing.%"
|
223
222
|
Order.processing.first.start_delivery.save
|
223
|
+
Order.first.processing? # true
|
224
|
+
Order.first.processing_delivering? # true
|
224
225
|
Order.processing_packaging.first # nil
|
225
226
|
Order.processing_delivering.first.cancel!.save # save is never called inisde hifsm
|
226
227
|
|
data/lib/hifsm/callbacks.rb
CHANGED
@@ -1,22 +1,30 @@
|
|
1
|
-
|
1
|
+
module Hifsm
|
2
|
+
module Callbacks
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
4
|
+
def set_callbacks(key, listeners)
|
5
|
+
@__callbacks ||= {}
|
6
|
+
@__callbacks[key] = listeners
|
7
|
+
end
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
target.
|
9
|
+
def trigger(key, target, *args)
|
10
|
+
return [] unless @__callbacks[key]
|
11
|
+
@__callbacks[key].map do |cb|
|
12
|
+
if cb.nil?
|
13
|
+
# raise something maybe? :)
|
14
|
+
elsif cb.is_a? Symbol
|
15
|
+
if target.method(cb).arity.zero?
|
16
|
+
target.send(cb)
|
17
|
+
else
|
18
|
+
target.send(cb, *args)
|
19
|
+
end
|
14
20
|
else
|
15
|
-
target.
|
21
|
+
target.instance_exec(*args, &cb)
|
16
22
|
end
|
17
|
-
else
|
18
|
-
target.instance_exec(*args, &cb)
|
19
23
|
end
|
20
24
|
end
|
25
|
+
|
26
|
+
def trigger?(key, target, *args)
|
27
|
+
!trigger(key, target, *args).any? {|v| v == false }
|
28
|
+
end
|
21
29
|
end
|
22
30
|
end
|
data/lib/hifsm/event.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module Hifsm
|
2
2
|
class Event
|
3
|
+
include Hifsm::Callbacks
|
4
|
+
|
3
5
|
CALLBACKS = [:before, :after, :guard].freeze
|
4
6
|
|
5
7
|
attr_reader :name, :to
|
@@ -7,18 +9,13 @@ module Hifsm
|
|
7
9
|
def initialize(name, to, callbacks_options)
|
8
10
|
@name = name
|
9
11
|
@to = to
|
10
|
-
@callbacks = {}
|
11
12
|
CALLBACKS.each do |cb|
|
12
|
-
|
13
|
+
set_callbacks cb, callbacks_options[cb]
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
|
-
def trigger(target, cb, *args)
|
17
|
-
@callbacks[cb].trigger(target, *args)
|
18
|
-
end
|
19
|
-
|
20
17
|
def guard?(target, *args)
|
21
|
-
trigger(
|
18
|
+
trigger?(:guard, target, *args)
|
22
19
|
end
|
23
20
|
end
|
24
21
|
end
|
data/lib/hifsm/fsm.rb
CHANGED
@@ -94,6 +94,15 @@ module Hifsm
|
|
94
94
|
fsm.all_events.each do |event_name, event|
|
95
95
|
define_method(event_name) {|*args| send(machine_name).fire(event_name, *args) }
|
96
96
|
end
|
97
|
+
|
98
|
+
# <state.to_s.underscore>? = is machine currently in state <state>?
|
99
|
+
fsm.all_states.each do |st|
|
100
|
+
query = st.to_s
|
101
|
+
define_method("#{query.gsub('.', '_')}?") do
|
102
|
+
current = send(machine_name).to_s
|
103
|
+
current == query || current.start_with?("#{query}.")
|
104
|
+
end
|
105
|
+
end
|
97
106
|
end
|
98
107
|
end
|
99
108
|
|
data/lib/hifsm/state.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module Hifsm
|
2
2
|
class State
|
3
|
+
include Hifsm::Callbacks
|
4
|
+
|
3
5
|
CALLBACKS = [:before_enter, :before_exit, :after_enter, :after_exit, :action].freeze
|
4
6
|
|
5
7
|
attr_reader :name, :sub_fsm
|
@@ -7,9 +9,8 @@ module Hifsm
|
|
7
9
|
def initialize(name, parent = nil, options)
|
8
10
|
@name = name.to_s
|
9
11
|
@parent = parent
|
10
|
-
@callbacks = {}
|
11
12
|
CALLBACKS.each do |cb|
|
12
|
-
|
13
|
+
set_callbacks cb, options[cb]
|
13
14
|
end
|
14
15
|
@transitions = Hash.new {|h, key| h[key] = Array.new }
|
15
16
|
|
@@ -24,7 +25,7 @@ module Hifsm
|
|
24
25
|
|
25
26
|
def act!(target, *args)
|
26
27
|
@parent.act!(target, *args) if @parent
|
27
|
-
trigger(
|
28
|
+
trigger(:action, target, *args).last
|
28
29
|
end
|
29
30
|
|
30
31
|
def add_transition(ev)
|
@@ -55,13 +56,13 @@ module Hifsm
|
|
55
56
|
if ev.guard?(target, *args)
|
56
57
|
from_state = self
|
57
58
|
to_state = ev.to
|
58
|
-
if ev.trigger(
|
59
|
-
to_state.trigger(
|
60
|
-
from_state.trigger(
|
59
|
+
if ev.trigger?(:before, target, *args) &&
|
60
|
+
to_state.trigger?(:before_enter, target, *args) &&
|
61
|
+
from_state.trigger?(:before_exit, target, *args)
|
61
62
|
new_state_callback.call(to_state.enter!)
|
62
|
-
from_state.trigger(
|
63
|
-
to_state.trigger(
|
64
|
-
ev.trigger(
|
63
|
+
from_state.trigger(:after_exit, target, *args)
|
64
|
+
to_state.trigger(:after_enter, target, *args)
|
65
|
+
ev.trigger(:after, target, *args)
|
65
66
|
end
|
66
67
|
return target
|
67
68
|
end
|
@@ -72,10 +73,6 @@ module Hifsm
|
|
72
73
|
raise Hifsm::MissingTransition.new(to_s, event_name)
|
73
74
|
end
|
74
75
|
|
75
|
-
def trigger(target, cb, *args)
|
76
|
-
@callbacks[cb].trigger(target, *args)
|
77
|
-
end
|
78
|
-
|
79
76
|
def to_s
|
80
77
|
if @parent
|
81
78
|
"#{@parent.to_s}.#{@name}"
|
data/lib/hifsm/version.rb
CHANGED
data/lib/hifsm.rb
CHANGED
@@ -22,13 +22,11 @@ module Hifsm
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def self.included(base)
|
25
|
-
|
25
|
+
raise 'use extend Hifsm instead of include'
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
include FSM::new(name, &block).to_module
|
31
|
-
end
|
28
|
+
def hifsm(name = :state, &block)
|
29
|
+
include FSM::new(name, &block).to_module
|
32
30
|
end
|
33
31
|
end
|
34
32
|
|
data/test/monster.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'hifsm'
|
2
2
|
|
3
3
|
class Monster
|
4
|
-
|
4
|
+
extend Hifsm
|
5
5
|
|
6
6
|
hifsm do
|
7
7
|
state :idle, :initial => true
|
@@ -15,7 +15,7 @@ class Monster
|
|
15
15
|
state :pursuing do
|
16
16
|
before_enter do
|
17
17
|
self.roar!
|
18
|
-
true # since
|
18
|
+
true # since it would stop processing if roar! returns false
|
19
19
|
end
|
20
20
|
action do
|
21
21
|
step_towards target
|
@@ -120,6 +120,7 @@ if $0 == __FILE__
|
|
120
120
|
# step step player2
|
121
121
|
ogre.reached
|
122
122
|
puts ogre.state # attacking.fighting
|
123
|
+
# ogre.attacking_fighting? = true
|
123
124
|
ogre.act! # Acting @attacking.fighting
|
124
125
|
# 6: Attack!
|
125
126
|
# ~~> player2
|
data/test/test_basic_fsm.rb
CHANGED
@@ -16,6 +16,11 @@ class TestBasicFSM < Minitest::Test
|
|
16
16
|
assert_equal 'off', @machine.state.to_s
|
17
17
|
end
|
18
18
|
|
19
|
+
def test_state_question_methods
|
20
|
+
refute @machine.on?, "Machine .on? should be false"
|
21
|
+
assert @machine.off?, "Machine .off? should be true"
|
22
|
+
end
|
23
|
+
|
19
24
|
def test_toggle_switches_state_to_on
|
20
25
|
@machine.toggle
|
21
26
|
assert_equal 'on', @machine.state.to_s
|
@@ -2,13 +2,17 @@ require 'setup_tests'
|
|
2
2
|
|
3
3
|
class TestBeforeReturningFalse < Minitest::Test
|
4
4
|
class Door
|
5
|
-
|
5
|
+
extend Hifsm
|
6
6
|
|
7
7
|
hifsm do
|
8
8
|
state :open
|
9
9
|
state :closed, :initial => true do
|
10
10
|
state :unlocked, :initial => true
|
11
|
-
state :locked
|
11
|
+
state :locked do
|
12
|
+
before_enter do
|
13
|
+
nil # nil equals true in this case, like in Rails
|
14
|
+
end
|
15
|
+
end
|
12
16
|
|
13
17
|
event :lock, :from => :unlocked, :to => :locked
|
14
18
|
end
|
data/test/test_event_guard.rb
CHANGED
@@ -2,19 +2,27 @@ require 'setup_tests'
|
|
2
2
|
|
3
3
|
class TestEventGuard < Minitest::Test
|
4
4
|
class Wall < Struct.new(:stones)
|
5
|
-
|
5
|
+
extend Hifsm
|
6
6
|
|
7
7
|
hifsm do
|
8
8
|
state :constructed, :initial => true
|
9
9
|
state :broken
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
event :break do
|
12
|
+
# guards can be inline proc, or symbols
|
13
|
+
from :constructed, :to => :broken, :guard => proc { stones < 5 }
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
# event parameters are passed to guards only if arity > 0
|
16
|
+
from :constructed, :to => :broken, :guard => :breakable?
|
17
|
+
|
18
|
+
before do |strength|
|
19
|
+
@last_hit_strength = strength.to_s
|
20
|
+
end
|
21
|
+
end
|
16
22
|
end
|
17
23
|
|
24
|
+
attr_reader :last_hit_strength
|
25
|
+
|
18
26
|
def breakable?(hits = 1)
|
19
27
|
hits * 5 > stones
|
20
28
|
end
|
@@ -24,6 +32,8 @@ class TestEventGuard < Minitest::Test
|
|
24
32
|
wall = Wall.new(3)
|
25
33
|
wall.break
|
26
34
|
assert_equal 'broken', wall.state
|
35
|
+
# before callback invoked
|
36
|
+
assert_equal '', wall.last_hit_strength
|
27
37
|
end
|
28
38
|
|
29
39
|
def test_cant_break_wall_10_stones_thick
|
@@ -31,12 +41,16 @@ class TestEventGuard < Minitest::Test
|
|
31
41
|
assert_raises(Hifsm::MissingTransition) do
|
32
42
|
wall.break
|
33
43
|
end
|
44
|
+
# before callback not called
|
45
|
+
assert_equal nil, wall.last_hit_strength
|
34
46
|
end
|
35
47
|
|
36
48
|
def test_can_break_thick_wall_if_hit_3_times
|
37
49
|
wall = Wall.new(10)
|
38
50
|
wall.break 3
|
39
51
|
assert_equal 'broken', wall.state
|
52
|
+
# before callback invoked
|
53
|
+
assert_equal '3', wall.last_hit_strength
|
40
54
|
end
|
41
55
|
|
42
56
|
end
|
@@ -2,7 +2,7 @@ require 'setup_tests'
|
|
2
2
|
|
3
3
|
class TestGrouppedTransitions < Minitest::Test
|
4
4
|
class Button
|
5
|
-
|
5
|
+
extend Hifsm
|
6
6
|
|
7
7
|
hifsm do
|
8
8
|
state :active, :initial => true
|
@@ -25,6 +25,7 @@ class TestGrouppedTransitions < Minitest::Test
|
|
25
25
|
attr_accessor :disable_on_click, :log
|
26
26
|
|
27
27
|
def initialize
|
28
|
+
self.disable_on_click = false # nil = true, same as for callbacks
|
28
29
|
self.log = []
|
29
30
|
end
|
30
31
|
end
|
data/test/test_hierarchical.rb
CHANGED
@@ -51,6 +51,14 @@ class TestHierarchical < Minitest::Test
|
|
51
51
|
assert_equal 'off.sync.third_level', machine.state.to_s
|
52
52
|
end
|
53
53
|
|
54
|
+
def test_state_question_methods
|
55
|
+
machine = @fsm.instantiate
|
56
|
+
refute machine.on?, "Machine .on? should be false"
|
57
|
+
assert machine.off?, "Machine .off? should be true"
|
58
|
+
refute machine.off_sync?, "Machine .off_sync? should be false"
|
59
|
+
assert machine.off_pending?, "Machine .off_pending? should be true"
|
60
|
+
end
|
61
|
+
|
54
62
|
def test_toggle_from_off_sync_to_on_pending
|
55
63
|
machine = @fsm.instantiate
|
56
64
|
machine.sync
|
data/test/test_two_machines.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hifsm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Meremyanin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-09-
|
11
|
+
date: 2014-09-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|