Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge bw-dispatch gem into BubbleWrap 1.1

  • Loading branch information...
commit 57802513d6fe0c22cfbae9d4f9db5ede6e320bd7 2 parents 5c4cc8d + 70a69af
@jamesotron jamesotron authored
View
2  Gemfile
@@ -1,4 +1,4 @@
source 'https://rubygems.org'
# Specify your gem's dependencies in bubble-wrap.gemspec
-gemspec
+gemspec
View
19 Gemfile.lock
@@ -0,0 +1,19 @@
+PATH
+ remote: .
+ specs:
+ bw-dispatch (0.0.1)
+ bubble-wrap
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ bubble-wrap (1.0.0.pre.2)
+ rake (0.9.2.2)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ bubble-wrap (= 1.0.0.pre.2)
+ bw-dispatch!
+ rake
View
23 LICENSE
@@ -24,3 +24,26 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+=======
+Copyright (c) 2012 James Harton
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
31 README.md
@@ -306,3 +306,34 @@ Do you have a suggestion for a specific wrapper? Feel free to open an
issue/ticket and tell us about what you are after. If you have a
wrapper/helper you are using and are thinking that others might enjoy,
please send a pull request (with tests if possible).
+=======
+# BW::Dispatch
+
+TODO: Write a gem description
+
+## Installation
+
+Add this line to your application's Gemfile:
+
+ gem 'bw-dispatch'
+
+And then execute:
+
+ $ bundle
+
+Or install it yourself as:
+
+ $ gem install bw-dispatch
+
+## Usage
+
+TODO: Write usage instructions here
+
+## Contributing
+
+1. Fork it
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Commit your changes (`git commit -am 'Added some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create new Pull Request
+
View
1  Rakefile
@@ -21,4 +21,3 @@ namespace :spec do
task :all => [:lib, :motion]
end
-
View
10 lib/bubble-wrap/dispatch.rb
@@ -0,0 +1,10 @@
+require 'bubble-wrap/loader'
+
+BW.require 'motion/dispatch**/*.rb' do
+ file('motion/dispatch.rb').uses_framework 'GCD'
+ file('motion/dispatch.rb').depends_on Dir.glob('motion/dispatch/**/*.rb')
+ file('motion/dispatch/timer.rb').depends_on 'motion/dispatch/eventable.rb'
+ file('motion/dispatch/periodic_timer.rb').depends_on 'motion/dispatch/eventable.rb'
+ file('motion/dispatch/deferrable.rb').depends_on ['motion/dispatch/timer.rb', 'motion/dispatch/future.rb']
+ file('motion/dispatch/default_deferrable.rb').depends_on 'motion/dispatch/deferrable.rb'
+end
View
81 motion/dispatch.rb
@@ -0,0 +1,81 @@
+module BubbleWrap
+ module Dispatch
+ module_function
+
+ # Always returns true - for compatibility with EM
+ def reactor_running?
+ true
+ end
+ alias reactor_thread? reactor_running?
+
+ # Call `callback` or the passed block in `interval` seconds.
+ # Returns a timer signature that can be passed into
+ # `cancel_timer`
+ def add_timer(interval, callback=nil, &blk)
+ @timers ||= {}
+ timer = Timer.new(interval,callback,&blk)
+ timer.on(:fired) do
+ @timers.delete(timer.object_id)
+ end
+ timer.on(:cancelled) do
+ @timers.delete(timer.object_id)
+ end
+ @timers[timer.object_id] = timer
+ timer.object_id
+ end
+
+ # Cancel a timer by passing in either a Timer object or
+ # a timer id (as returned by `add_timer` and
+ # `add_periodic_timer`).
+ def cancel_timer(timer)
+ return timer.cancel if timer.respond_to(:cancel)
+ @timers ||= {}
+ return @timers[timer].cancel if @timers[timer]
+ false
+ end
+
+ # Call `callback` or the passed block every `interval` seconds.
+ # Returns a timer signature that can be passed into
+ # `cancel_timer`
+ def add_periodic_timer(interval, callback=nil, &blk)
+ @timers ||= {}
+ timer = PeriodicTimer.new(internval,callback,blk)
+ timer.on(:cancelled) do
+ @timers.delete(timer)
+ end
+ @timers[timer.object_id] = timer
+ timer.object_id
+ end
+
+ def defer(op=nil,cb=nil,&blk)
+ schedule do
+ result = (op||blk).call
+ schedule(result, &cb) if cb
+ end
+ end
+
+ # Schedule a block for execution on the reactor queue.
+ def schedule(*args, &blk)
+ @queue ||= ::Dispatch::Queue.concurrent("#{NSBundle.mainBundle.bundleIdentifier}.reactor")
+
+ cb = proc do
+ blk.call(*args)
+ end
+ @queue.async &cb
+ nil
+ end
+
+ # Schedule a block for execution on your applcation's main thread.
+ # This is useful as UI updates need to be executed from the main
+ # thread.
+ def main(*args, &blk)
+ cb = proc do
+ blk.call(*args)
+ end
+ ::Dispatch::Queue.main.async &cb
+ end
+
+ end
+end
+
+::EM = ::BubbleWrap::Dispatch # Yes I dare!
View
9 motion/dispatch/default_deferrable.rb
@@ -0,0 +1,9 @@
+module BubbleWrap
+ module Dispatch
+ # A basic class which includes Deferrable when all
+ # you need is a deferrable without any added behaviour.
+ class DefaultDeferrable
+# include ::BubbleWrap::Dispatch::Deferrable
+ end
+ end
+end
View
126 motion/dispatch/deferrable.rb
@@ -0,0 +1,126 @@
+module BubbleWrap
+ module Dispatch
+ # Provides a mixin for deferrable jobs.
+ module Deferrable
+
+ # def self.included(base)
+ # base.extend ::BubbleWrap::Dispatch::Future
+ # end
+
+ # Specify a block to be executed if and when the Deferrable object
+ # receives a status of :succeeded. See set_deferred_status for more
+ # information.
+ # Calling this method on a Deferrable object whose status is not yet
+ # known will cause the callback block to be stored on an internal
+ # list. If you call this method on a Deferrable whose status is
+ # :succeeded, the block will be executed immediately, receiving
+ # the parameters given to the prior set_deferred_status call.
+ def callback(&blk)
+ return unless blk
+ @deferred_status ||= :unknown
+ if @deferred_status == :succeeded
+ blk.call(*@deferred_args)
+ elsif @deferred_status != :failed
+ @callbacks ||= []
+ @callbacks.unshift blk
+ end
+ end
+
+ # Cancels an outstanding timeout if any. Undoes the action of timeout.
+ def cancel_timeout
+ @deferred_timeout ||= nil
+ if @deferred_timeout
+ @deferred_timeout.cancal
+ @deferred_timeout = nil
+ end
+ end
+
+ # Specify a block to be executed if and when the Deferrable object
+ # receives a status of :failed. See set_deferred_status for more
+ # information.
+ def errback(&blk)
+ return unless blk
+ @deferred_status ||= :unknown
+ if @deferred_status == :failed
+ blk.call(*@deferred_args)
+ elsif @deferred_status != :succeeded
+ @errbacks ||= []
+ @errbacks.unshift blk
+ end
+ end
+
+ # Sugar for set_deferred_status(:failed, …)
+ def fail(*args)
+ set_deferred_status :fail, *args
+ end
+ alias set_deferred_failure fail
+
+ # Sets the “disposition” (status) of the Deferrable object. See also
+ # the large set of sugarings for this method. Note that if you call
+ # this method without arguments, no arguments will be passed to the
+ # callback/errback. If the user has coded these with arguments,
+ # then the user code will throw an argument exception. Implementors
+ # of deferrable classes must document the arguments they will supply
+ # to user callbacks.
+ # OBSERVE SOMETHING VERY SPECIAL here: you may call this method even
+ # on the INSIDE of a callback. This is very useful when a
+ # previously-registered callback wants to change the parameters that
+ # will be passed to subsequently-registered ones.
+ # You may give either :succeeded or :failed as the status argument.
+ # If you pass :succeeded, then all of the blocks passed to the object
+ # using the callback method (if any) will be executed BEFORE the
+ # set_deferred_status method returns. All of the blocks passed to the
+ # object using errback will be discarded.
+ # If you pass :failed, then all of the blocks passed to the object
+ # using the errback method (if any) will be executed BEFORE the
+ # set_deferred_status method returns. All of the blocks passed to the
+ # object using # callback will be discarded.
+ # If you pass any arguments to set_deferred_status in addition to the
+ # status argument, they will be passed as arguments to any callbacks
+ # or errbacks that are executed. It’s your responsibility to ensure
+ # that the argument lists specified in your callbacks and errbacks match
+ # the arguments given in calls to set_deferred_status, otherwise Ruby
+ # will raise an ArgumentError.
+ def set_deferred_status(status, *args)
+ cancel_timeout
+ @errbacks ||= nil
+ @callbacks ||= nil
+ @deferred_status = status
+ @deferred_args = args
+ case @deferred_status
+ when :succeeded
+ if @callbacks
+ while cb = @callbacks.pop
+ cb.call(*@deferred_args)
+ end
+ end
+ @errbacks.clear if @errbacks
+ when :failed
+ if @errbacks
+ while eb = @errbacks.pop
+ eb.call(*@deferred_args)
+ end
+ end
+ @callbacks.clear if @callbacks
+ end
+ end
+
+ # Sugar for set_deferred_status(:succeeded, …)
+ def succeed(*args)
+ set_deferred_status :succeeded, *args
+ end
+ alias set_deferred_success succeed
+
+ # Setting a timeout on a Deferrable causes it to go into the failed
+ # state after the Timeout expires (passing no arguments to the object’s
+ # errbacks). Setting the status at any time prior to a call to the
+ # expiration of the timeout will cause the timer to be cancelled.
+ def timeout(seconds)
+ cancel_timeout
+ me = self
+ @deferred_timeout = Timer.new(seconds) {me.fail}
+ end
+
+ end
+ end
+end
View
19 motion/dispatch/eventable.rb
@@ -0,0 +1,19 @@
+module BubbleWrap
+ module Dispatch
+ module Eventable
+
+ def on(event, &blk)
+ @events ||= Hash.new { [] }
+ @events[event].unshift blk
+ end
+
+ def trigger(event, *args)
+ @events ||= Hash.new { [] }
+ @events[event].map do |event|
+ ::BubbleWrap::Dispatch.schedule(*args,&event)
+ end
+ end
+
+ end
+ end
+end
View
22 motion/dispatch/future.rb
@@ -0,0 +1,22 @@
+module BubbleWrap
+ module Dispatch
+ module Future
+
+ # A future is a sugaring of a typical deferrable usage.
+ def future arg, cb=nil, eb=nil, &blk
+ arg = arg.call if arg.respond_to?(:call)
+
+ if arg.respond_to?(:set_deferred_status)
+ if cb || eb
+ arg.callback(&cb) if cb
+ arg.errback(&eb) if eb
+ else
+ arg.callback(&blk) if blk
+ end
+ end
+
+ arg
+ end
+ end
+ end
+end
View
27 motion/dispatch/periodic_timer.rb
@@ -0,0 +1,27 @@
+module BubbleWrap
+ module Dispatch
+ # Creates a repeating timer.
+ class PeriodicTimer
+ include Eventable
+
+ attr_accessor :interval
+
+ # Create a new timer that fires after a given number of seconds
+ def initialize(interval, callback=nil, &blk)
+ self.interval = interval
+ fire = proc {
+ (callback || blk).call
+ trigger(:fired)
+ }
+ @timer = NSTimer.scheduledTimerWithTimeInterval(interval,target: fire, selector: 'call:', userInfo: nil, repeats: true)
+ end
+
+ # Cancel the timer
+ def cancel
+ @timer.invalidate
+ trigger(:cancelled)
+ end
+
+ end
+ end
+end
View
60 motion/dispatch/queue.rb
@@ -0,0 +1,60 @@
+module BubbleWrap
+ module Dispatch
+ # A GCD scheduled, linear queue.
+ #
+ # This class provides a simple “Queue” like abstraction on top of the
+ # GCD scheduler.
+ #
+ # Useful as an API sugar for stateful protocols
+ #
+ # q = BubbleWrap::Dispatch::Queue.new
+ # q.push('one', 'two', 'three')
+ # 3.times do
+ # q.pop{ |msg| puts(msg) }
+ # end
+ class Queue
+
+ # Create a new queue
+ def initialize
+ @items = []
+ end
+
+ # Is the queue empty?
+ def empty?
+ @items.empty?
+ end
+
+ # The size of the queue
+ def size
+ @items.size
+ end
+
+ # Push items onto the work queue. The items will not appear in the queue
+ # immediately, but will be scheduled for addition.
+ def push(*items)
+ ::BubbleWrap::Dispatch.schedule do
+ @items.push(*items)
+ @popq.shift.call @items.shift until @items.empty? || @popq.empty?
+ end
+ end
+
+ # Pop items off the queue, running the block on the work queue. The pop
+ # will not happen immediately, but at some point in the future, either
+ # in the next tick, if the queue has data, or when the queue is populated.
+ def pop(*args, &blk)
+ cb = proc do
+ blk.call(*args)
+ end
+ ::BubbleWrap::Dispatch.schedule do
+ if @items.empty?
+ @popq << cb
+ else
+ cb.call @items.shift
+ end
+ end
+ nil # Always returns nil
+ end
+
+ end
+ end
+end
View
25 motion/dispatch/timer.rb
@@ -0,0 +1,25 @@
+module BubbleWrap
+ module Dispatch
+ # Creates a one-time timer.
+ class Timer
+ include Eventable
+
+ # Create a new timer that fires after a given number of seconds
+ def initialize(interval, callback=nil, &blk)
+ fire = proc {
+ (callback || blk).call
+ trigger(:fired)
+ }
+ @timer = NSTimer.scheduledTimerWithTimeInterval(interval,target: fire, selector: 'call:', userInfo: nil, repeats: false)
+ end
+
+ # Cancel the timer
+ def cancel
+ @timer.invalidate
+ trigger(:cancelled)
+ true
+ end
+
+ end
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.