seh 0.1.0 → 0.3.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.
- 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
|