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 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