seh 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +0 -1
- data/Gemfile +2 -2
- data/Gemfile.lock +25 -0
- data/README.md +82 -8
- data/Rakefile +31 -0
- data/examples/epic_battle.rb +240 -0
- data/examples/event.rb +1 -0
- data/examples/event/damage.rb +31 -0
- data/examples/event/hostile.rb +11 -0
- data/examples/event/melee_attack.rb +32 -0
- data/examples/event_color.rb +57 -0
- data/examples/mob.rb +10 -0
- data/lib/seh.rb +4 -7
- data/lib/seh/event.rb +126 -143
- data/lib/seh/event_bind.rb +15 -0
- data/lib/seh/event_bind_disconnector.rb +17 -0
- data/lib/seh/event_target.rb +30 -22
- data/lib/seh/event_type.rb +11 -21
- data/lib/seh/version.rb +1 -1
- data/seh.gemspec +2 -2
- data/spec/seh/event_bind_disconnector_spec.rb +23 -0
- data/spec/seh/event_bind_spec.rb +22 -0
- data/spec/seh/event_spec.rb +209 -0
- data/spec/seh/event_target_spec.rb +87 -0
- data/spec/seh/event_type_spec.rb +134 -0
- data/spec/seh_spec.rb +25 -0
- metadata +23 -18
@@ -0,0 +1,17 @@
|
|
1
|
+
module Seh
|
2
|
+
# EventBindDisconnector should not be used directly. Event#bind and Event#bind_once return instances of EventBindDisconnector.
|
3
|
+
class EventBindDisconnector
|
4
|
+
def initialize disconnect_proc
|
5
|
+
@disconnect_proc = disconnect_proc
|
6
|
+
end
|
7
|
+
|
8
|
+
# Disconnect this bind from its EventTarget
|
9
|
+
def disconnect
|
10
|
+
unless @disconnect_proc.nil?
|
11
|
+
@disconnect_proc.call
|
12
|
+
@disconnect_proc = nil
|
13
|
+
end
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/seh/event_target.rb
CHANGED
@@ -1,36 +1,44 @@
|
|
1
|
+
require_relative 'event_bind'
|
2
|
+
require_relative 'event_bind_disconnector'
|
3
|
+
|
1
4
|
module Seh
|
2
5
|
module EventTarget
|
3
|
-
def bind
|
4
|
-
raise "
|
6
|
+
def bind event_type=nil, &block
|
7
|
+
raise "expected a block" unless block_given?
|
5
8
|
@_binds ||= []
|
6
|
-
bind =
|
9
|
+
bind = EventBind.new event_type, &block
|
7
10
|
@_binds << bind
|
8
|
-
->{ @_binds.delete bind }
|
11
|
+
EventBindDisconnector.new ->{ @_binds.delete bind; nil }
|
9
12
|
end
|
10
13
|
|
11
|
-
def bind_once
|
14
|
+
def bind_once event_type=nil, &block
|
12
15
|
return unless block_given?
|
13
|
-
|
14
|
-
|
16
|
+
bind_disconnector = self.bind(event_type) { |event| bind_disconnector.disconnect; block.call event }
|
17
|
+
end
|
18
|
+
|
19
|
+
# @private
|
20
|
+
# Internal use only. Used in Seh::Event.
|
21
|
+
# Override #observers as needed.
|
22
|
+
# @return [EventTarget] - array of other EventTargets observing this EventTarget
|
23
|
+
def observers
|
24
|
+
[]
|
15
25
|
end
|
16
26
|
|
17
|
-
|
27
|
+
# @private
|
28
|
+
# Internal use only. Used in Seh::Event.
|
29
|
+
# For each bind matching the passed event_types, yield the bind's callback
|
30
|
+
# @param event_types - Array of event types
|
31
|
+
# @yield each bind callback matching the passed event types
|
32
|
+
# @return nil
|
33
|
+
def each_matching_callback event_types
|
18
34
|
@_binds ||= []
|
19
|
-
@_binds.
|
35
|
+
@_binds.each { |bind| yield bind.block if bind.event_type.match event_types }
|
20
36
|
nil
|
21
37
|
end
|
22
|
-
end
|
23
38
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
def initialize( event_type, &block )
|
30
|
-
event_type = EventType.new event_type unless event_type.is_a? EventType
|
31
|
-
@event_type = event_type
|
32
|
-
@block = block
|
33
|
-
end
|
34
|
-
end # EventBind
|
35
|
-
end # Private
|
39
|
+
# An empty class that includes EventTarget
|
40
|
+
class Default
|
41
|
+
include EventTarget
|
42
|
+
end
|
43
|
+
end
|
36
44
|
end
|
data/lib/seh/event_type.rb
CHANGED
@@ -1,56 +1,46 @@
|
|
1
1
|
module Seh
|
2
2
|
class EventType
|
3
|
-
|
4
|
-
|
5
|
-
def initialize( type )
|
3
|
+
def initialize type
|
6
4
|
@type = type
|
7
5
|
end
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
private
|
15
|
-
def _match(types)
|
16
|
-
return true if not self.type or types.include? self.type
|
17
|
-
false
|
7
|
+
# @param types - an Array of types
|
8
|
+
def match types
|
9
|
+
return true if @type.nil? # define nil to match everything
|
10
|
+
types.include? @type
|
18
11
|
end
|
19
12
|
|
20
13
|
class And < EventType
|
21
|
-
def initialize
|
14
|
+
def initialize *types
|
22
15
|
@types = []
|
23
16
|
types.each { |t| t = EventType.new t unless t.kind_of? EventType ; @types << t }
|
24
17
|
end
|
25
18
|
|
26
|
-
|
27
|
-
def _match(types)
|
19
|
+
def match types
|
28
20
|
@types.each { |t| return false unless t.match types }
|
29
21
|
true
|
30
22
|
end
|
31
23
|
end # And
|
32
24
|
|
33
25
|
class Or < EventType
|
34
|
-
def initialize
|
26
|
+
def initialize *types
|
35
27
|
@types = []
|
36
28
|
types.each { |t| t = EventType.new t unless t.kind_of? EventType ; @types << t }
|
37
29
|
end
|
38
30
|
|
39
|
-
|
40
|
-
def _match(types)
|
31
|
+
def match types
|
41
32
|
@types.each { |t| return true if t.match types }
|
42
33
|
false
|
43
34
|
end
|
44
35
|
end # Or
|
45
36
|
|
46
37
|
class Not < EventType
|
47
|
-
def initialize
|
38
|
+
def initialize type
|
48
39
|
type = EventType.new type unless type.kind_of? EventType
|
49
40
|
@type = type
|
50
41
|
end
|
51
42
|
|
52
|
-
|
53
|
-
def _match(types)
|
43
|
+
def match types
|
54
44
|
! @type.match types
|
55
45
|
end
|
56
46
|
end # Not
|
data/lib/seh/version.rb
CHANGED
data/seh.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.authors = ["Ryan Berckmans"]
|
9
9
|
s.email = ["ryan.berckmans@gmail.com"]
|
10
10
|
s.homepage = "https://github.com/ryanberckmans/seh"
|
11
|
-
s.summary = "Structured event handler. Pure ruby event handling similar to w3c dom events
|
12
|
-
s.description = "#{s.summary}
|
11
|
+
s.summary = "Structured event handler. Pure ruby event handling similar to w3c dom events."
|
12
|
+
s.description = "#{s.summary} Synchronous, local event handling for complex emergent behaviors as required by something like a game engine. v0.3.0 improves a lot on v0.1.0."
|
13
13
|
|
14
14
|
s.rubyforge_project = "seh"
|
15
15
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'seh/event_bind_disconnector'
|
3
|
+
|
4
|
+
module Seh
|
5
|
+
describe EventBindDisconnector do
|
6
|
+
before :each do @proc = ->{ "nice!" } end
|
7
|
+
subject { EventBindDisconnector.new @proc }
|
8
|
+
|
9
|
+
it "calls proc on disconnect" do
|
10
|
+
@proc.should_receive(:call).once
|
11
|
+
subject.disconnect
|
12
|
+
end
|
13
|
+
|
14
|
+
its(:disconnect) { should be_nil }
|
15
|
+
|
16
|
+
it "sets the proc reference to nil on disconnect, so the proc is only ever called once" do
|
17
|
+
@proc.should_receive(:call).once
|
18
|
+
subject.disconnect
|
19
|
+
subject.disconnect
|
20
|
+
subject.disconnect
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'seh/event_bind'
|
3
|
+
require 'seh/event_type'
|
4
|
+
|
5
|
+
module Seh
|
6
|
+
describe EventBind do
|
7
|
+
before :each do
|
8
|
+
@event_type = EventType.new :some_type
|
9
|
+
@block = ->{ callback! }
|
10
|
+
end
|
11
|
+
subject { EventBind.new @event_type, &@block }
|
12
|
+
its(:block) { should eq(@block) }
|
13
|
+
its(:event_type) { should eq(@event_type) }
|
14
|
+
|
15
|
+
it "wraps a non-EventType in an EventType" do
|
16
|
+
type = :not_an_EventType
|
17
|
+
EventType.should_receive(:new).with(type).once.and_call_original
|
18
|
+
bind = EventBind.new type, &@block
|
19
|
+
bind.event_type.should be_a(EventType)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'seh/event'
|
4
|
+
require 'seh/event_target'
|
5
|
+
|
6
|
+
module Seh
|
7
|
+
class ObservableEventTarget
|
8
|
+
include EventTarget
|
9
|
+
def initialize
|
10
|
+
@observers = []
|
11
|
+
end
|
12
|
+
attr_accessor :observers
|
13
|
+
end
|
14
|
+
|
15
|
+
describe Event do
|
16
|
+
it "adds a few targets" do
|
17
|
+
target1 = EventTarget::Default.new
|
18
|
+
target2 = EventTarget::Default.new
|
19
|
+
expect { subject.target target1, target2 }.to change{ subject.send :collect_targets }.from(Set.new).to(Set.new << target1 << target2)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "dispatch runs target and then stage callbacks" do
|
23
|
+
subject.should_receive(:run_target_callbacks).once.ordered
|
24
|
+
subject.should_receive(:run_stage_callbacks).once.ordered
|
25
|
+
subject.dispatch
|
26
|
+
end
|
27
|
+
|
28
|
+
it "dispatch may not be run twice" do
|
29
|
+
subject.dispatch
|
30
|
+
expect { subject.dispatch }.to raise_error
|
31
|
+
end
|
32
|
+
|
33
|
+
context "testing abort" do
|
34
|
+
it "does nothing when #abort is called before #dispatch" do
|
35
|
+
subject.abort
|
36
|
+
subject.should_receive(:run_target_callbacks).never
|
37
|
+
subject.should_receive(:run_stage_callbacks).never
|
38
|
+
subject.dispatch
|
39
|
+
end
|
40
|
+
|
41
|
+
it "visits all targets when #abort is called during a target callback" do
|
42
|
+
type = :foo
|
43
|
+
|
44
|
+
target1 = ObservableEventTarget.new
|
45
|
+
target2 = EventTarget::Default.new
|
46
|
+
target3 = EventTarget::Default.new
|
47
|
+
target1.observers << target3
|
48
|
+
|
49
|
+
target1.bind type do |event| event.abort end
|
50
|
+
|
51
|
+
@result = 0
|
52
|
+
|
53
|
+
target3.bind type do |event| @result += 2 end
|
54
|
+
target2.bind type do |event| @result += 1 end
|
55
|
+
|
56
|
+
subject.type type
|
57
|
+
subject.target target1, target2
|
58
|
+
subject.dispatch
|
59
|
+
@result.should eq(3)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "finishes the start stage and then stops when #abort is called during a start callback" do
|
63
|
+
@result = 0
|
64
|
+
subject.start do subject.abort end
|
65
|
+
subject.start do @result += 1 end
|
66
|
+
subject.start do @result += 2 end
|
67
|
+
subject.add_stage :never_happens
|
68
|
+
subject.bind :never_happens do @result += 5 end
|
69
|
+
subject.dispatch
|
70
|
+
@result.should eq(3)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "finishes the current stage and then stops when #abort is called during a stage callback" do
|
74
|
+
@result = 0
|
75
|
+
subject.add_stage :does_happens
|
76
|
+
subject.add_stage :never_happens
|
77
|
+
subject.bind :does_happens do @result += 1; subject.abort end
|
78
|
+
subject.bind :does_happens do @result += 2 end
|
79
|
+
subject.bind :never_happens do @result += 5 end
|
80
|
+
subject.dispatch
|
81
|
+
@result.should eq(3)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "doesn't run finish callbacks when #abort is called during a stage callback" do
|
85
|
+
@result = 0
|
86
|
+
subject.add_stage :does_happens
|
87
|
+
subject.bind :does_happens do @result += 1; subject.abort end
|
88
|
+
subject.finish do @result += 5 end
|
89
|
+
subject.dispatch
|
90
|
+
@result.should eq(1)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "testing callbacks" do
|
95
|
+
before :each do
|
96
|
+
@counter = 0
|
97
|
+
@callback = ->event{
|
98
|
+
@counter += 1
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
context "testing stage callbacks" do
|
103
|
+
it "runs a start callback" do
|
104
|
+
expect { subject.start &@callback}.to change{ subject.send :run_stage_callbacks; @counter }.from(0).to(1)
|
105
|
+
end
|
106
|
+
it "runs a finish callback" do
|
107
|
+
expect { subject.finish &@callback}.to change{ subject.send :run_stage_callbacks; @counter }.from(0).to(1)
|
108
|
+
end
|
109
|
+
it "runs a stage callback" do
|
110
|
+
stage = :foo
|
111
|
+
subject.add_stage stage
|
112
|
+
expect { subject.bind stage, &@callback}.to change{ subject.send :run_stage_callbacks; @counter }.from(0).to(1)
|
113
|
+
end
|
114
|
+
it "runs a stage whose stage_decision_block evaluates to true" do
|
115
|
+
@result = 0
|
116
|
+
stage = :should_be_run
|
117
|
+
subject.add_stage stage do true end
|
118
|
+
subject.bind stage do @result += 1 end
|
119
|
+
subject.bind stage do @result += 2 end
|
120
|
+
subject.dispatch
|
121
|
+
@result.should eq(3)
|
122
|
+
end
|
123
|
+
it "skips a stage whose stage_decision_block evaluates to false" do
|
124
|
+
@result = 0
|
125
|
+
stage = :should_not_be_run
|
126
|
+
subject.add_stage stage do false end
|
127
|
+
subject.bind stage do @result += 1 end
|
128
|
+
subject.bind stage do @result += 2 end
|
129
|
+
subject.dispatch
|
130
|
+
@result.should eq(0)
|
131
|
+
end
|
132
|
+
it "runs start before a stage" do
|
133
|
+
result = []
|
134
|
+
subject.add_stage :stage2
|
135
|
+
subject.bind :stage2 do result << :second end
|
136
|
+
subject.start { result << :first }
|
137
|
+
subject.send :run_stage_callbacks
|
138
|
+
result.should eq([:first,:second])
|
139
|
+
end
|
140
|
+
it "runs finish after a stage" do
|
141
|
+
result = []
|
142
|
+
subject.add_stage :stage2
|
143
|
+
subject.bind :stage2 do result << :first end
|
144
|
+
subject.finish { result << :second }
|
145
|
+
subject.send :run_stage_callbacks
|
146
|
+
result.should eq([:first,:second])
|
147
|
+
end
|
148
|
+
it "runs stages the order they were added" do
|
149
|
+
result = []
|
150
|
+
subject.add_stage :stage2
|
151
|
+
subject.add_stage :stage3
|
152
|
+
subject.add_stage :stage4
|
153
|
+
subject.bind :stage4 do result << :third end
|
154
|
+
subject.bind :stage3 do result << :second end
|
155
|
+
subject.bind :stage2 do result << :first end
|
156
|
+
subject.send :run_stage_callbacks
|
157
|
+
result.should eq([:first,:second,:third])
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context "testing invoking of target callbacks" do
|
162
|
+
before :each do
|
163
|
+
@target = EventTarget::Default.new
|
164
|
+
@type = :some_type
|
165
|
+
subject.target @target
|
166
|
+
subject.type @type
|
167
|
+
end
|
168
|
+
|
169
|
+
it "calls a target callback" do
|
170
|
+
expect { @target.bind @type, &@callback }.to change{ subject.send :run_target_callbacks; @counter}.from(0).to(1)
|
171
|
+
end
|
172
|
+
|
173
|
+
it "passes self to a target callback" do
|
174
|
+
@result = nil
|
175
|
+
@target.bind @type do |event| @result = event end
|
176
|
+
subject.send:run_target_callbacks
|
177
|
+
@result.should eq(subject)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end # testing callbacks
|
181
|
+
|
182
|
+
|
183
|
+
context "with a target containing a DAG of observers" do
|
184
|
+
before :each do
|
185
|
+
@target1 = ObservableEventTarget.new
|
186
|
+
@observer1 = EventTarget::Default.new
|
187
|
+
@observer2 = EventTarget::Default.new
|
188
|
+
@observer3 = ObservableEventTarget.new
|
189
|
+
@observer4 = ObservableEventTarget.new
|
190
|
+
@observer5 = EventTarget::Default.new
|
191
|
+
|
192
|
+
@observer3.observers << @observer2
|
193
|
+
@observer3.observers << @observer4
|
194
|
+
@observer4.observers << @observer5 << @observer1
|
195
|
+
|
196
|
+
@target2 = ObservableEventTarget.new
|
197
|
+
@observer6 = EventTarget::Default.new
|
198
|
+
|
199
|
+
@target2.observers << @observer6
|
200
|
+
|
201
|
+
@target1.observers << @observer1 << @observer2 << @observer3
|
202
|
+
end
|
203
|
+
|
204
|
+
it "collects_targets in a Set which traverses the observer graph" do
|
205
|
+
expect { subject.target @target1, @observer5, @target2 }.to change { subject.send :collect_targets}.from(Set.new).to(Set.new << @target1 << @target2 << @observer1 << @observer2 << @observer3 << @observer4 << @observer5 << @observer6 )
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'seh/event_target'
|
3
|
+
require 'seh/event_bind_disconnector'
|
4
|
+
|
5
|
+
module Seh
|
6
|
+
module EventTarget
|
7
|
+
describe Default do
|
8
|
+
it { subject.should be_a(EventTarget) }
|
9
|
+
|
10
|
+
its(:observers) { should eq([]) }
|
11
|
+
|
12
|
+
it { subject.each_matching_callback([]).should be_nil }
|
13
|
+
|
14
|
+
it "adds a bind" do
|
15
|
+
event_type = :some_type
|
16
|
+
block = -> { callback! }
|
17
|
+
EventBind.should_receive(:new).with(event_type,&block).once.and_call_original
|
18
|
+
bind_disconnector = subject.bind event_type, &block
|
19
|
+
bind_disconnector.should be_a(EventBindDisconnector)
|
20
|
+
end
|
21
|
+
|
22
|
+
context "with added binds" do
|
23
|
+
before :each do
|
24
|
+
@event_type1 = :another_type
|
25
|
+
@block1 = -> { callback! }
|
26
|
+
@bind_disconnector1 = subject.bind @event_type1, &@block1
|
27
|
+
|
28
|
+
@event_type2 = :second_type
|
29
|
+
@block2 = -> { twocallback! }
|
30
|
+
@bind_disconnector2 = subject.bind @event_type2, &@block2
|
31
|
+
|
32
|
+
@event_type3 = :third_type
|
33
|
+
@block3 = ->event{ "do nothing" }
|
34
|
+
@bind_disconnector3 = subject.bind_once @event_type3, &@block3
|
35
|
+
end
|
36
|
+
|
37
|
+
it "yields the block for a matching bind in each_matching_callback" do
|
38
|
+
expected_blocks = []
|
39
|
+
subject.each_matching_callback([@event_type1]) { |block| expected_blocks << block }
|
40
|
+
expected_blocks.should include(@block1)
|
41
|
+
expected_blocks.size.should eq(1)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "yields both blocks for the matching binds in each_matching_callback" do
|
45
|
+
expected_blocks = []
|
46
|
+
subject.each_matching_callback([@event_type1,@event_type2]) { |block| expected_blocks << block }
|
47
|
+
expected_blocks.should include(@block1)
|
48
|
+
expected_blocks.should include(@block2)
|
49
|
+
expected_blocks.size.should eq(2)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "yields nothing when no binds match the event_types in each_matching_callback" do
|
53
|
+
expected_blocks = []
|
54
|
+
subject.each_matching_callback([:unknown_type]) { |block| expected_blocks << block }
|
55
|
+
expected_blocks.size.should eq(0)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "disconnects bind3 after a single block invocation, since bind3 was connected using bind_once" do
|
59
|
+
expected_blocks = []
|
60
|
+
subject.each_matching_callback([@event_type3]) { |block| expected_blocks << block }
|
61
|
+
expected_blocks.should_not include(@block3) # @block3 would have been wrapped in another block during bind_once, so we don't expect to find it
|
62
|
+
expected_blocks.size.should eq(1) # the single block in the array is the new block wrapping @block3
|
63
|
+
expected_blocks[0].call
|
64
|
+
|
65
|
+
# After @block3 was called, we shouldn't expect to find it anymore
|
66
|
+
expected_blocks.clear
|
67
|
+
expected_blocks.size.should eq(0) # verify clear
|
68
|
+
subject.each_matching_callback([@event_type3]) { |block| expected_blocks << block }
|
69
|
+
expected_blocks.size.should eq(0)
|
70
|
+
end
|
71
|
+
|
72
|
+
context "with bind1 disconnected" do
|
73
|
+
before :each do
|
74
|
+
@bind_disconnector1.disconnect
|
75
|
+
end
|
76
|
+
|
77
|
+
it "yields only the second block in each_matching_callback, since the first was disconnected" do
|
78
|
+
expected_blocks = []
|
79
|
+
subject.each_matching_callback([@event_type1,@event_type2]) { |block| expected_blocks << block }
|
80
|
+
expected_blocks.should include(@block2)
|
81
|
+
expected_blocks.size.should eq(1)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|