hifsm 0.4.5 → 0.6.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/.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
|