wires 0.5.1 → 0.5.2

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: b75245c516a29082cbc30499aa0ccdb1a66e4a06
4
- data.tar.gz: 82267cb4a27d04f8fce823f29a4a85767fe3be4b
3
+ metadata.gz: 98b3ac8452b025a1a79925458a5731528ba14bb7
4
+ data.tar.gz: 926044b8d23b987c3f6d5e1d590d8257bd2ccfe2
5
5
  SHA512:
6
- metadata.gz: ba93a9b65347f9c547ae977fc331665cd8f977290cbfa75b38a3945f6c27acf54938bf074301bfe10e856e559fc8a9cb72ff99f3aa9ed2b05fbd8f74d36fee18
7
- data.tar.gz: 3df28faff0f05f21b1ffdfaa08dfaa8d5f8cb2f9689bd2501d31db886bd946b1006fde807afbba0b0c0f017d81319e0e326e86d229a5eec11d53546a2847eedf
6
+ metadata.gz: 68494726a3f024be4901491891967494f9e11984118e7cf605bfa7db5c4e6f1483e2f0b6348cbe35ab88060e31b9f71164c6fcb5e9edeb3f3ed80c5720febff3
7
+ data.tar.gz: c8722a29b08c8c47d66ec8ff617975aa3be544f35a0e9bd6ea03602ec944f593e1eb5cf829287bc682a577a1dfa810367ada5c450b7cf8219ec1e8dd83f615af
@@ -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
- attr_reader :handlers
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
- def new(*args)
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, *args) { |name| super(name) }
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 a proc to be triggered by an event on this channel
38
- # Return the proc that was passed in
39
- def register(*events, &proc)
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 proc.respond_to? :call
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 << [events, proc] \
46
- unless @handlers.include? [events, proc]
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 proc
50
- channels = proc.instance_variable_get(:@registered_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
- proc.instance_variable_set(:@registered_channels, [self])
142
+ callable.instance_variable_set(:@registered_channels, [self])
55
143
  end
56
144
 
57
- # Insert the #unregister method into the proc
58
- proc.singleton_class.send :define_method, :unregister do
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 proc.respond_to? :unregister
151
+ end unless callable.respond_to? :unregister
64
152
 
65
- proc
153
+ callable
66
154
  end
67
155
 
68
- # Unregister a proc from the target list of this channel
69
- # Return true if at least one matching target was unregistered, else false
70
- def unregister(proc)
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.reject! do |stored_events, stored_proc|
73
- proc==stored_proc
74
- end)
184
+ !!(@handlers.delete callable)
75
185
  end
76
186
  end
77
187
 
78
- # Fire an event on this channel
79
- def fire(input, blocking:false, parallel:!blocking)
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(input, blocking:true, parallel:false) } \
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 = Event.list_from input
238
+ event = event.to_wires_event
88
239
 
89
- case event.count
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(:@after_fire, event, self.name)
268
+ self.class.send(:run_hooks, :@after_fire, event, self.name)
127
269
 
128
270
  threads
129
271
  end
130
272
 
131
- # Fire a blocking event on this channel
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
- # Returns true if listening on 'self' would hear a firing on 'other'
138
- # (not commutative)
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
@@ -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 self::Hub
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
- @channel.fire(@events, **(@fire_kwargs.merge kwargs))
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
- # Run all hooks, deleting those not marked for retention
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, retain = hook
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.1
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-24 00:00:00.000000000 Z
11
+ date: 2013-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: threadlock