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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 58e6687ae4f9842846c1053888058f2a900472a2
4
- data.tar.gz: b8bd4da00022fca41f541224b09a717189395d58
3
+ metadata.gz: e8c840d5a3b6429146d55b87579db8d29ca6c6c6
4
+ data.tar.gz: 2130df37d77e426940c430a7b9a29abc4619838e
5
5
  SHA512:
6
- metadata.gz: 01b20d9770b5a2623ed3af913de677b28e3de8110580d20781ed601d4d42c8a70ec7178d44c6050a9889cf709fd8737b0f27a234ffaa0aa413f624b58cf03ee8
7
- data.tar.gz: 455a85c0392c5e64038f62e8b5025a282ba9657b1b0dec9f082a7e41b1ed2c685c94429ea4ed42d4f258e544f109cac954cc508f8fd52aa6b474af43643d8c47
6
+ metadata.gz: a182af19cfb5cc12291e395c4041934d9ae5c25997d865ab573807bbf18b27efcc7c131038b0f3bd18f8fae5d5d4440ce4d08bece8ea0b8efd8dd3c3bdb668bb
7
+ data.tar.gz: b9e9e923ea20dfbf4cb335a65c297b877397559cfc2bc2b12be2a7ea1d34b9837fec772e8c857431a863fd7a1dac48d35601d232741bb6f39f3b8e081e9597d6
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ .DS_Store
1
2
  *.gem
2
3
  *.rbc
3
4
  .bundle
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
- include Hifsm
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 roar! returns nil it would stop processing
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
 
@@ -41,6 +41,6 @@ module Hifsm
41
41
  end
42
42
 
43
43
  ActiveRecord::Base.class_eval do
44
- include Hifsm
44
+ extend Hifsm
45
45
  include Hifsm::Adapters::ActiveRecordAdapter
46
46
  end
@@ -1,22 +1,30 @@
1
- class Callbacks
1
+ module Hifsm
2
+ module Callbacks
2
3
 
3
- def initialize(listeners = [])
4
- @listeners = listeners
5
- end
4
+ def set_callbacks(key, listeners)
5
+ @__callbacks ||= {}
6
+ @__callbacks[key] = listeners
7
+ end
6
8
 
7
- def trigger(target, *args)
8
- @listeners.map do |cb|
9
- if cb.nil?
10
- # raise something maybe? :)
11
- elsif cb.is_a? Symbol
12
- if target.method(cb).arity.zero?
13
- target.send(cb)
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.send(cb, *args)
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
- @callbacks[cb] = Callbacks.new(callbacks_options[cb])
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(target, :guard, *args).all?
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
- @callbacks[cb] = Callbacks.new(options[cb])
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(target, :action, *args).last
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(target, :before, *args).all? &&
59
- to_state.trigger(target, :before_enter, *args).all? &&
60
- from_state.trigger(target, :before_exit, *args).all?
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(target, :after_exit, *args)
63
- to_state.trigger(target, :after_enter, *args)
64
- ev.trigger(target, :after, *args)
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
@@ -1,3 +1,3 @@
1
1
  module Hifsm
2
- VERSION = "0.4.5"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/hifsm.rb CHANGED
@@ -22,13 +22,11 @@ module Hifsm
22
22
  end
23
23
 
24
24
  def self.included(base)
25
- base.send(:extend, ClassMethods) unless base.respond_to?(:hifsm)
25
+ raise 'use extend Hifsm instead of include'
26
26
  end
27
27
 
28
- module ClassMethods
29
- def hifsm(name = :state, &block)
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
- include Hifsm
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 roar! returns nil it would stop processing
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
@@ -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
- include Hifsm
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
@@ -2,7 +2,7 @@ require 'setup_tests'
2
2
 
3
3
  class TestDynamicInitialState < Minitest::Test
4
4
  class Value < Struct.new(:value)
5
- include Hifsm
5
+ extend Hifsm
6
6
  hifsm :group do
7
7
  state :few do
8
8
  state :very
@@ -2,19 +2,27 @@ require 'setup_tests'
2
2
 
3
3
  class TestEventGuard < Minitest::Test
4
4
  class Wall < Struct.new(:stones)
5
- include Hifsm
5
+ extend Hifsm
6
6
 
7
7
  hifsm do
8
8
  state :constructed, :initial => true
9
9
  state :broken
10
10
 
11
- # guards can be inline proc, or symbols
12
- event :break, :from => :constructed, :to => :broken, :guard => proc { stones < 5 }
11
+ event :break do
12
+ # guards can be inline proc, or symbols
13
+ from :constructed, :to => :broken, :guard => proc { stones < 5 }
13
14
 
14
- # event parameters are passed to guards only if arity > 0
15
- event :break, :from => :constructed, :to => :broken, :guard => :breakable?
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
- include Hifsm
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
@@ -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
@@ -2,7 +2,7 @@ require 'setup_tests'
2
2
 
3
3
  class TestIflessFactorial < Minitest::Test
4
4
  class Value < Struct.new(:value)
5
- include Hifsm
5
+ extend Hifsm
6
6
  hifsm do
7
7
  state :idle, :initial => true
8
8
  state :computing
@@ -2,7 +2,7 @@ require 'setup_tests'
2
2
 
3
3
  class TestTwoMachines < Minitest::Test
4
4
  class ColorPrinter < Struct.new(:log)
5
- include Hifsm
5
+ extend Hifsm
6
6
 
7
7
  hifsm :color do
8
8
  state :red, :initial => true
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.5
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-07 00:00:00.000000000 Z
11
+ date: 2014-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler