wires 0.5.6 → 0.5.7

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: 51987a7590eb20419d2b26717efc9085ea19043f
4
- data.tar.gz: ed10fe66856e3517d7e9779188957e022232d4d2
3
+ metadata.gz: e9f2487653b2bc09e49a835f4945890973744d1a
4
+ data.tar.gz: 273bf1a229f890c1a1bae0e7002cfa8c99c56e2d
5
5
  SHA512:
6
- metadata.gz: 75b868f5b060e88b5fc8f8024946cd6c862af613f8afbccfea85247e11a33bb87498f38677f941869cbd6ee7b8b81122f32249bbd61c82d95b7876244fa9350a
7
- data.tar.gz: 9f70b430842b38fbec44c558720e6a846ce88d738a7e742777a873f46c9d129aa4ee519a1663ed0194f9befcd99c05aa6e62b3eb5f1590cf1df91221351ee110
6
+ metadata.gz: a087b9a5bc3c6cc604d1272401e24d0e428b9868b6c3a615384bd35af3862b5ef896832bbb6d6c671b683871f8993ca6630ad37b53eff1f1ce418cb075ca888b
7
+ data.tar.gz: f6fd9105f3d1cbc90489e2e933065214657463a261e94de1fe85faac8f2c609689b303b6384bd6f50e388ccfd0b5689418c7caf6533406324700baf22ee89c62
@@ -291,6 +291,123 @@ module Wires.current_network::Namespace
291
291
  fire(*args, **kwargs)
292
292
  end
293
293
 
294
+ # Synchronize execution of this thread to an incoming event.
295
+ # The user block will be executed, and will not return until a matching
296
+ # incoming event has been fired
297
+ # (see the {SyncHelper})
298
+ #
299
+ # @note In order to use this method correctly, the "action" that causes the
300
+ # incoming event to be fired should happen within the user block. If
301
+ # it happens before the user block, one risks a race condition; if after,
302
+ # one risks deadlock (or timeout) due to the feedback event being missed.
303
+ # As long as the feedback event happens after execution of the user block
304
+ # has begun, the event is guaranteed to be caught and processed.
305
+ #
306
+ # @param *events [<Symbol, Event>] the event pattern(s) filter
307
+ # to listen with. (see {#register}).
308
+ # @param timeout [Fixnum] the timeout, in seconds.
309
+ # (see {SyncHelper#wait}).
310
+ # @param &block [Proc] the user block. This block will be executed inline
311
+ # and passed an instance of {SyncHelper}. Within this block, the helper
312
+ # should be configured using its methods. Optionally, {SyncHelper#wait}
313
+ # can be called to wait in a specific location in the block. Otherwise,
314
+ # it will be called implicitly at the end of the block.
315
+ #
316
+ def sync_on(*events, timeout:nil, &block)
317
+ SyncHelper.new(events, self, timeout:timeout, &block)
318
+ nil
319
+ end
320
+
321
+ # Helper class passed to user block in {Channel#sync} method.
322
+ # Read here for how to use the helper, but never instantiate it yourself.
323
+ class SyncHelper
324
+
325
+ # Don't instantiate this class directly, use {Channel#sync}
326
+ # @api private
327
+ def initialize(events, channel, timeout:nil)
328
+ @timeout = timeout
329
+ @lock, @cond = Mutex.new, ConditionVariable.new
330
+ @conditions = []
331
+ @executions = []
332
+ @received = []
333
+
334
+ # Create the temporary event handler to capture incoming matches
335
+ proc = Proc.new { |e,c| @lock.synchronize { snag e,c } }
336
+
337
+ # Run the user block within the lock and wait afterward if they didn't
338
+ @lock.synchronize {
339
+ channel.register events, &proc
340
+ yield self
341
+ wait unless @waited
342
+ channel.unregister &proc
343
+ }
344
+ end
345
+
346
+ # Add a condition which must be fulfilled for {#wait} to find a match.
347
+ #
348
+ # @param &block [Proc] the block specifiying the condition to be met.
349
+ # It will be passed the event and channel, and the truthiness of its
350
+ # return value will be evaluated to determine if the condition is met.
351
+ # It will only be executed if the +[event,channel]+ pair fits the
352
+ # filter and meets all of the other evaluated conditions so far.
353
+ #
354
+ def condition(&block)
355
+ @conditions << block if block
356
+ nil
357
+ end
358
+
359
+ # Add a execution to run on the matching event for each {#wait}.
360
+ #
361
+ # @param &block [Proc] the block to be executed.
362
+ # It will only be executed if the +[event,channel]+ pair fits the
363
+ # filter and met all of the conditions to fulfill the {#wait}.
364
+ # The block will not be run if the {#wait} times out.
365
+ #
366
+ def execute(&block)
367
+ @executions << block if block
368
+ nil
369
+ end
370
+
371
+ # Wait for exactly one matching event meeting all {#conditions} to come.
372
+ #
373
+ # @note This will be called once implicitly at the end of the user block
374
+ # unless it gets called explicitly somewhere within the user block.
375
+ # It can be called multiple times within the user block to require
376
+ # one matching event each time within the block.
377
+ #
378
+ # @param timeout [Fixnum] The maximum time to wait for a match,
379
+ # specified in seconds. By default, it will be the number used at
380
+ # instantiation (passed from {Channel#sync_on}).
381
+ #
382
+ # @return the matching {Event} object, or nil if timed out.
383
+ #
384
+ def wait(timeout=@timeout)
385
+ @waited = true
386
+ result = nil
387
+
388
+ # Loop through each result, making sure it matches the conditions,
389
+ # returning nil if the wait timed out and didn't push into @received
390
+ loop do
391
+ @cond.wait @lock, timeout
392
+ result = @received.pop
393
+ return nil unless result
394
+ break if !@conditions.detect { |blk| !blk.call *result }
395
+ end
396
+
397
+ # Run all the execute blocks on the result
398
+ @executions.each { |blk| blk.call *result }
399
+ result.first #=> return event
400
+ end
401
+
402
+ private
403
+
404
+ # Snag the given event and channel to try it out in the blocking thread
405
+ def snag(*args)
406
+ @received << args
407
+ @cond.signal # Pass execution back to blocking thread and block this one
408
+ end
409
+ end
410
+
294
411
  # Determine if one channel matches another.
295
412
  #
296
413
  # In this context, a match indicates a receiver relationship.
@@ -13,6 +13,11 @@ module Wires.current_network::Namespace
13
13
  codeblock
14
14
  end
15
15
 
16
+ def sync_on(event, channel=self, **kwargs, &codeblock)
17
+ channel = Channel.new(channel) unless channel.is_a? Channel
18
+ channel.sync_on(event, **kwargs, &codeblock)
19
+ end
20
+
16
21
  def fire(event, channel=self, **kwargs)
17
22
  channel = Channel.new(channel) unless channel.is_a? Channel
18
23
 
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.6
4
+ version: 0.5.7
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-12-19 00:00:00.000000000 Z
11
+ date: 2014-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: threadlock
@@ -162,7 +162,7 @@ files:
162
162
  - lib/wires/core_ext/time.rb
163
163
  homepage: https://github.com/jemc/wires/
164
164
  licenses:
165
- - Copyright 2013 Joe McIlvain. All rights reserved.
165
+ - Copyright 2013-2014 Joe McIlvain. All rights reserved.
166
166
  metadata: {}
167
167
  post_install_message:
168
168
  rdoc_options: []
@@ -185,3 +185,4 @@ signing_key:
185
185
  specification_version: 4
186
186
  summary: wires
187
187
  test_files: []
188
+ has_rdoc: