wires 0.5.1 → 0.5.2
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/lib/wires/base/channel.rb +228 -47
- data/lib/wires/base/hub.rb +2 -2
- data/lib/wires/base/time_scheduler_item.rb +2 -1
- data/lib/wires/base/util/hooks.rb +5 -2
- 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: 98b3ac8452b025a1a79925458a5731528ba14bb7
|
4
|
+
data.tar.gz: 926044b8d23b987c3f6d5e1d590d8257bd2ccfe2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68494726a3f024be4901491891967494f9e11984118e7cf605bfa7db5c4e6f1483e2f0b6348cbe35ab88060e31b9f71164c6fcb5e9edeb3f3ed80c5720febff3
|
7
|
+
data.tar.gz: c8722a29b08c8c47d66ec8ff617975aa3be544f35a0e9bd6ea03602ec944f593e1eb5cf829287bc682a577a1dfa810367ada5c450b7cf8219ec1e8dd83f615af
|
data/lib/wires/base/channel.rb
CHANGED
@@ -1,101 +1,243 @@
|
|
1
1
|
|
2
2
|
module Wires
|
3
3
|
|
4
|
+
#
|
5
|
+
#
|
6
|
+
# = Hooks Summary
|
7
|
+
# As a courtesy for custom services seeking to integrate with {Wires}, some
|
8
|
+
# hooks are provided for the {Channel} class. These hooks can be accessed
|
9
|
+
# by the methods inherited from {Util::Hooks}. Note that they are on a class
|
10
|
+
# level, so registering a hook will affect all instances of {Channel}.
|
11
|
+
# There are two hooks types that {Channel} will invoke:
|
12
|
+
# * +:@before_fire+ - yields the event object and channel {#name} to the
|
13
|
+
# user block before {#fire} invokes {Hub.spawn} (see source code).
|
14
|
+
# * +:@after_fire+ - yields the event object and channel {#name} to the
|
15
|
+
# user block after {#fire} invokes {Hub.spawn} (see source code).
|
4
16
|
class Channel
|
5
17
|
|
18
|
+
# The unique name of the channel, which can be any kind of hashable object.
|
19
|
+
#
|
20
|
+
# Because it is unique, a reference to the channel may be obtained from
|
21
|
+
# the class-level method {Channel.[] Channel[]} using only the {#name}.
|
22
|
+
#
|
6
23
|
attr_reader :name
|
7
|
-
|
24
|
+
|
25
|
+
# An array specifying the exception type and string to raise if {#fire} is
|
26
|
+
# called, or +nil+ if it's okay to {#fire} from this channel.
|
27
|
+
#
|
28
|
+
# This is meant to be determined by the {Router} that is selected as the
|
29
|
+
# current {.router}, and not accessed from any other user code.
|
30
|
+
#
|
8
31
|
attr_accessor :not_firable
|
9
32
|
|
33
|
+
# @return [String] friendly output showing the class and channel {#name}
|
10
34
|
def inspect; "#{self.class}[#{name.inspect}]"; end
|
11
35
|
|
12
36
|
@hub = Hub
|
13
37
|
@router = Router::Default
|
14
38
|
@new_lock = Monitor.new
|
15
|
-
@@aim_lock = Mutex.new
|
39
|
+
@@aim_lock = Mutex.new # @api private
|
16
40
|
|
17
|
-
# Add hook methods
|
18
41
|
extend Util::Hooks
|
19
42
|
|
20
43
|
class << self
|
44
|
+
|
45
|
+
# The currently selected {Hub} for all channels ({Hub} by default).
|
46
|
+
#
|
47
|
+
# It is the {Hub}'s responsibility to execute event handlers.
|
48
|
+
# @api private
|
49
|
+
#
|
21
50
|
attr_accessor :hub
|
51
|
+
|
52
|
+
# The currently selected {Router} for all channels
|
53
|
+
# ({Router::Default} by default).
|
54
|
+
#
|
55
|
+
# It is the router's responsibility to decide whether to create new
|
56
|
+
# channel objects or return existing ones when {.[] Channel[]} is called
|
57
|
+
# and to determine which channels should receive {#fire}d events from
|
58
|
+
# a channel (its {#receivers}).
|
59
|
+
#
|
60
|
+
# {Wires} provides two routers: {Router::Default} and {Router::Simple},
|
61
|
+
# but any object that implements the router interface can be selected.
|
62
|
+
#
|
22
63
|
attr_accessor :router
|
23
64
|
|
24
|
-
|
65
|
+
# Access a channel by {#name}, creating a new channel object if necessary.
|
66
|
+
#
|
67
|
+
# The work of deciding if an object with the given {#name} already
|
68
|
+
# exists is delegated to the currently selected {.router}.
|
69
|
+
#
|
70
|
+
# @note Because this method does not always create a new object,
|
71
|
+
# it is recommended to use the alias {[] Channel[]}, which more clearly
|
72
|
+
# communicates the intention.
|
73
|
+
#
|
74
|
+
# @param name [#hash] a hashable object of any type
|
75
|
+
# to use as the channel {#name}
|
76
|
+
#
|
77
|
+
# @return [Channel] the new or existing channel object with that {#name}
|
78
|
+
#
|
79
|
+
def new(name)
|
25
80
|
channel = @new_lock.synchronize do
|
26
|
-
router.get_channel(self,
|
81
|
+
router.get_channel(self, name) { |name| super(name) }
|
27
82
|
end
|
28
83
|
end
|
29
84
|
alias_method :[], :new
|
30
85
|
end
|
31
86
|
|
87
|
+
# Assigns the given +name+ as the {#name} of this channel object
|
88
|
+
#
|
89
|
+
# @param name [#hash] a hashable object of any type, unique to this channel
|
90
|
+
#
|
32
91
|
def initialize(name)
|
33
92
|
@name = name
|
34
|
-
@handlers =
|
93
|
+
@handlers = {}
|
35
94
|
end
|
36
95
|
|
37
|
-
# Register
|
38
|
-
#
|
39
|
-
|
96
|
+
# Register an event handler to be executed when a matching event occurs.
|
97
|
+
#
|
98
|
+
# One or more event patterns should be passed as the arguments.
|
99
|
+
# If an event matching one or more of the given event patterns is
|
100
|
+
# {#fire}d on a channel that has this channel as one of its {#receivers},
|
101
|
+
# then the handler is executed, and yielded the event object and the {#name}
|
102
|
+
# of the channel upon which {#fire} was called as arguments.
|
103
|
+
#
|
104
|
+
# @param *events [<Symbol, Event>] the event pattern(s)
|
105
|
+
# to listen for. If the pattern is a symbol or event with a type and no
|
106
|
+
# other arguments, any event with that type will be heard. If the
|
107
|
+
# pattern is an event that has other arguments, each of the arguments
|
108
|
+
# and keyword arguments in the pattern must also be present in the
|
109
|
+
# fired event for it to be heard by the handler, but the fired event may
|
110
|
+
# also include other arguments that were not declared in the pattern
|
111
|
+
# and still be heard by the handler (see {Event#=~}).
|
112
|
+
# @param &callable [Proc] the executable code to register as a handler
|
113
|
+
# on this channel for the given pattern.
|
114
|
+
#
|
115
|
+
# @return [Proc] the +&callable+ given, which has been extended with an
|
116
|
+
# +#unregister+ method on the object itself. The injected method takes
|
117
|
+
# no arguments and will unregister the +&callable+ from every channel
|
118
|
+
# on which it is {#register}ed. This can be a helpful alternative to
|
119
|
+
# calling {#unregister} on the relevant channel(s) by hand.
|
120
|
+
#
|
121
|
+
# @raise [ArgumentError] if no ampersand-argument or inline block is
|
122
|
+
# given as the +&callable+.
|
123
|
+
#
|
124
|
+
def register(*events, &callable)
|
40
125
|
raise ArgumentError, "No callable given to execute on event: #{events}" \
|
41
|
-
unless
|
126
|
+
unless callable.respond_to? :call
|
42
127
|
events = Event.list_from *events
|
43
128
|
|
129
|
+
# Register the events under the callable in the @handlers hash
|
44
130
|
@@aim_lock.synchronize do
|
45
|
-
@handlers
|
46
|
-
|
131
|
+
ary = (@handlers.has_key?(callable) ?
|
132
|
+
@handlers[callable] :
|
133
|
+
@handlers[callable] = [])
|
134
|
+
events.each { |e| ary << e unless ary.include? e }
|
47
135
|
end
|
48
136
|
|
49
|
-
# Insert the @registered_channels variable into the
|
50
|
-
channels =
|
137
|
+
# Insert the @registered_channels variable into the callable
|
138
|
+
channels = callable.instance_variable_get(:@registered_channels)
|
51
139
|
if channels
|
52
140
|
channels << self
|
53
141
|
else
|
54
|
-
|
142
|
+
callable.instance_variable_set(:@registered_channels, [self])
|
55
143
|
end
|
56
144
|
|
57
|
-
# Insert the #unregister method into the
|
58
|
-
|
145
|
+
# Insert the #unregister method into the callable
|
146
|
+
callable.singleton_class.send :define_method, :unregister do
|
59
147
|
singleton_class.send :remove_method, :unregister
|
60
148
|
@registered_channels.each do |c|
|
61
|
-
c.unregister self
|
149
|
+
c.unregister &self
|
62
150
|
end
|
63
|
-
end unless
|
151
|
+
end unless callable.respond_to? :unregister
|
64
152
|
|
65
|
-
|
153
|
+
callable
|
66
154
|
end
|
67
155
|
|
68
|
-
# Unregister
|
69
|
-
#
|
70
|
-
|
156
|
+
# Unregister an event handler that was defined with #register
|
157
|
+
#
|
158
|
+
# @note It is not necessary to unregister event handlers 'owned' by
|
159
|
+
# persistent objects, but for short-lived objects, it is critical to
|
160
|
+
# to do so. Due to the way that Proc objects (including those
|
161
|
+
# generated implicitly from inline blocks) enclose their surrounding
|
162
|
+
# scope, the +&callable+ handler will keep alive any objects it encloses,
|
163
|
+
# and the channel that holds a reference to the +&callable+ will keep it
|
164
|
+
# alive until it is unregistered.
|
165
|
+
#
|
166
|
+
# @note As an alternative to calling {Channel#unregister}, one may use
|
167
|
+
# the +#unregister+ method that was injected into the +&callable+
|
168
|
+
# (refer to the return value of {#register})
|
169
|
+
#
|
170
|
+
# @param &callable [Proc] the same callable object that was given to
|
171
|
+
# (and returned by) {#register}.
|
172
|
+
#
|
173
|
+
# @return [Boolean] +true+ if the callable was previously {#register}ed
|
174
|
+
# (and is now {#unregister}ed); +false+ otherwise.
|
175
|
+
#
|
176
|
+
# @TODO break the GC note out into a dedicated document and link to it
|
177
|
+
# @TODO try to remove all references to this channel object when it
|
178
|
+
# has no more handlers (and try to determine if this would ever be a
|
179
|
+
# bad idea or would cause errant behavior) - possibly implement
|
180
|
+
# Channel#forget instead?
|
181
|
+
#
|
182
|
+
def unregister(&callable)
|
71
183
|
@@aim_lock.synchronize do
|
72
|
-
!!(@handlers.
|
73
|
-
proc==stored_proc
|
74
|
-
end)
|
184
|
+
!!(@handlers.delete callable)
|
75
185
|
end
|
76
186
|
end
|
77
187
|
|
78
|
-
#
|
79
|
-
|
188
|
+
# Return the list of {#register}ed handlers for this channel
|
189
|
+
#
|
190
|
+
# @return [Array<Array(Array<Symbol,Event>,Proc)>] an array of arrays, each
|
191
|
+
# containing an array of event patterns followed by the associated Proc.
|
192
|
+
#
|
193
|
+
def handlers
|
194
|
+
@handlers.map { |callable, events| [events, callable] }
|
195
|
+
end
|
196
|
+
|
197
|
+
# Fire an event on this channel.
|
198
|
+
#
|
199
|
+
# Each handler with an event pattern matching the fired event (see
|
200
|
+
# {Event#=~}) that is {#register}ed on a channel that matches the firing
|
201
|
+
# channel (see {Channel#=~}) will be executed, and passed the event object
|
202
|
+
# and channel name as arguments.
|
203
|
+
#
|
204
|
+
# This method fires in a nonblocking manner by default, but this behavior
|
205
|
+
# can be overriden with the +:blocking+ parameter. See {#fire!} for
|
206
|
+
# blocking default behavior.
|
207
|
+
#
|
208
|
+
# @param [Event, Symbol] event the event to be fired, as an Event or Symbol.
|
209
|
+
# In this context, a Symbol is treated as an 'empty' Event of that type
|
210
|
+
# (see to {Symbol#[]}).
|
211
|
+
# @param [Boolean] :blocking when true, the method will wait to return
|
212
|
+
# until all handlers have finished their execution.
|
213
|
+
# @param [Boolean] :parallel when true, the handlers will be executed in
|
214
|
+
# parallel, if there are more than one; otherwise, they will be executed
|
215
|
+
# serially (in an undefined order). Unless otherwise specified, this
|
216
|
+
# parameter will be set to the opposite of the value of the +:blocking+
|
217
|
+
# parameter; that is, nonblocking firing will by default also be parallel,
|
218
|
+
# and blocking firing will by default also be sequential.
|
219
|
+
#
|
220
|
+
# @return [Array<Thread>] the array of threads spawned by the method,
|
221
|
+
# if any. This could be useful for manually joining the threads later
|
222
|
+
# or monitoring their status.
|
223
|
+
#
|
224
|
+
# @raise An exception of the type and message contained in the
|
225
|
+
# {#not_firable} attribute if it has been assigned by the active
|
226
|
+
# {.router} through the {#not_firable=} accessor.
|
227
|
+
#
|
228
|
+
# @TODO Test the return array in each of the four concurrency cases
|
229
|
+
#
|
230
|
+
def fire(event, blocking: false, parallel: !blocking)
|
80
231
|
raise *@not_firable if @not_firable
|
81
232
|
|
82
|
-
return [] << Thread.new { fire(
|
233
|
+
return [] << Thread.new { fire(event, blocking:true, parallel:false) } \
|
83
234
|
if !blocking and !parallel
|
84
235
|
|
85
236
|
backtrace = caller
|
86
237
|
|
87
|
-
event =
|
238
|
+
event = event.to_wires_event
|
88
239
|
|
89
|
-
|
90
|
-
when 0
|
91
|
-
raise ArgumentError,"Can't create an event from input: #{input.inspect}"
|
92
|
-
when 1
|
93
|
-
event = event.first
|
94
|
-
else
|
95
|
-
raise ArgumentError,"Can't fire on multiple events: #{event.inspect}"
|
96
|
-
end
|
97
|
-
|
98
|
-
self.class.run_hooks(:@before_fire, event, self.name)
|
240
|
+
self.class.send(:run_hooks, :@before_fire, event, self.name)
|
99
241
|
|
100
242
|
# Select appropriate targets
|
101
243
|
procs = []
|
@@ -104,7 +246,7 @@ module Wires
|
|
104
246
|
.get_receivers(self).each do |chan|
|
105
247
|
chan.handlers.each do |elist, pr|
|
106
248
|
elist.each do |e|
|
107
|
-
procs << pr if e =~ event
|
249
|
+
procs << pr if e =~ [event, 55, 55.6, 0x00, /regexp/, 'string', "string"]
|
108
250
|
end
|
109
251
|
end
|
110
252
|
end
|
@@ -123,29 +265,68 @@ module Wires
|
|
123
265
|
|
124
266
|
threads.each &:join if blocking and parallel
|
125
267
|
|
126
|
-
self.class.run_hooks
|
268
|
+
self.class.send(:run_hooks, :@after_fire, event, self.name)
|
127
269
|
|
128
270
|
threads
|
129
271
|
end
|
130
272
|
|
131
|
-
# Fire
|
273
|
+
# Fire an event on this channel.
|
274
|
+
#
|
275
|
+
# Each handler with an event pattern matching the fired event (see
|
276
|
+
# {Event#=~}) that is {#register}ed on a channel that matches the firing
|
277
|
+
# channel (see {Channel#=~}) will be executed, and passed the event object
|
278
|
+
# and channel name as arguments.
|
279
|
+
#
|
280
|
+
# This method fires in a blocking manner by default, but this behavior
|
281
|
+
# can be overriden with the +:blocking+ parameter. See {#fire!} for
|
282
|
+
# nonblocking default behavior.
|
283
|
+
#
|
284
|
+
# @param (see Channel#fire)
|
285
|
+
# @return (see Channel#fire)
|
286
|
+
# @raise (see Channel#fire)
|
287
|
+
# @overload fire!(event, blocking: true, parallel: !blocking)
|
288
|
+
#
|
132
289
|
def fire!(*args, **kwargs)
|
133
290
|
kwargs[:blocking] = true unless kwargs.has_key? :blocking
|
134
291
|
fire(*args, **kwargs)
|
135
292
|
end
|
136
293
|
|
137
|
-
#
|
138
|
-
#
|
294
|
+
# Determine if one channel matches another.
|
295
|
+
#
|
296
|
+
# In this context, a match indicates a receiver relationship.
|
297
|
+
# That is, this method tests if +self+ is one of the {#receivers} of
|
298
|
+
# +other+. For a matching pair of channels, a {#fire}d event on the
|
299
|
+
# right-hand channel could be received by a relevant event handler on
|
300
|
+
# the left-hand channel.
|
301
|
+
#
|
302
|
+
# Note that, depending on the strategy of the {.router}, this operator
|
303
|
+
# is not necessarily commutative. That is, having +a =~ b+ does not
|
304
|
+
# guarantee that +b =~ a+.
|
305
|
+
#
|
306
|
+
# @note Channel receiver relationships are entirely dictated by the
|
307
|
+
# selected {.router}. Refer to the examples in the documentation for
|
308
|
+
# {Router::Default} to learn about the routing patterns of the default
|
309
|
+
# router, but know that other {Router}s may be substituted as needed
|
310
|
+
# on a global basis with the {.router=} class-level accessor.
|
311
|
+
#
|
312
|
+
# @param other [Channel] the channel to which +self+ should be compared.
|
313
|
+
# @return [Boolean] +true+ if +self+ is one of +other+'s {#receivers};
|
314
|
+
# +false+ otherwise.
|
315
|
+
#
|
139
316
|
def =~(other)
|
140
317
|
(other.is_a? Channel) ?
|
141
318
|
(self.class.router.get_receivers(other).include? self) :
|
142
319
|
super
|
143
320
|
end
|
144
321
|
|
322
|
+
# @return [Array<Channel>] the list of channels whose {#register}ed
|
323
|
+
# event handlers would receive a relevant event {#fire}d by this channel.
|
324
|
+
#
|
325
|
+
# @note (see Channel#=~)
|
326
|
+
#
|
145
327
|
def receivers
|
146
328
|
self.class.router.get_receivers self
|
147
329
|
end
|
148
330
|
|
149
331
|
end
|
150
|
-
|
151
|
-
end
|
332
|
+
end
|
data/lib/wires/base/hub.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
module Wires
|
3
3
|
# An Event Hub. Event/proc associations come in, and the procs
|
4
4
|
# get called in new threads in the order received
|
5
|
-
class
|
5
|
+
class Hub
|
6
6
|
class << self
|
7
7
|
|
8
8
|
# Refuse to instantiate; it's a singleton!
|
@@ -64,7 +64,7 @@ module Wires
|
|
64
64
|
end
|
65
65
|
|
66
66
|
# Spawn a task - user code should never call this directly
|
67
|
-
def spawn(*args) # :args: event, chan, proc, blocking, fire_bt
|
67
|
+
def spawn(*args) # :args: event, chan, proc, blocking, parallel, fire_bt
|
68
68
|
|
69
69
|
event, chan, proc, blocking, parallel, fire_bt = *args
|
70
70
|
*proc_args = event, chan
|
@@ -61,7 +61,8 @@ module Wires
|
|
61
61
|
|
62
62
|
# Fire the event now, regardless of time or active status
|
63
63
|
def fire(**kwargs) # kwargs merge with and override @kwargs
|
64
|
-
|
64
|
+
kwargs = @fire_kwargs.merge kwargs
|
65
|
+
@events.each { |e| @channel.fire(e, **kwargs) }
|
65
66
|
count_dec
|
66
67
|
|
67
68
|
if @active
|
@@ -4,6 +4,7 @@ module Wires
|
|
4
4
|
module Hooks
|
5
5
|
|
6
6
|
# Register a hook - can be called multiple times if retain is true
|
7
|
+
# @param hooks_sym [Symbol] the symbol...
|
7
8
|
def add_hook(hooks_sym, retain=false, &proc)
|
8
9
|
hooks = instance_variable_get(hooks_sym.to_sym)
|
9
10
|
if hooks
|
@@ -21,12 +22,14 @@ module Wires
|
|
21
22
|
hooks.reject! {|h| h[0]==proc}
|
22
23
|
end
|
23
24
|
|
24
|
-
|
25
|
+
private
|
26
|
+
|
27
|
+
# Run all hooks
|
25
28
|
def run_hooks(hooks_sym, *exc_args)
|
26
29
|
hooks = instance_variable_get(hooks_sym.to_sym)
|
27
30
|
return unless hooks
|
28
31
|
for hook in hooks
|
29
|
-
proc,
|
32
|
+
proc, _ = hook
|
30
33
|
proc.call(*exc_args)
|
31
34
|
end
|
32
35
|
nil end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wires
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe McIlvain
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-12-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: threadlock
|