diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..881fa948 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.rake_tasks~ +pkg/* +build/ +.DS_Store +0 +.repl_history +vendor/ +.dat* diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..866c0338 --- /dev/null +++ b/Gemfile @@ -0,0 +1,5 @@ +source 'https://rubygems.org' +gem 'bubble-wrap', '1.0.0.pre.2' + +# Specify your gem's dependencies in bw-dispatch.gemspec +gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..b4146b02 --- /dev/null +++ b/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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..1620d3b1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..fc2b5a00 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# 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 diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..a970d56e --- /dev/null +++ b/Rakefile @@ -0,0 +1,16 @@ +#!/usr/bin/env rake +$:.unshift("/Library/RubyMotion/lib") +require 'motion/project' +require 'bundler' +require "bundler/gem_tasks" +Bundler.setup +#Bundler.require +require 'bubble-wrap/loader' +require 'bubble-wrap/test' + +BW.require 'motion/**/*.rb' + +Motion::Project::App.setup do |app| + app.name = 'deferrableTestSuite' + app.identifier = 'io.bubblewrap.deferrableTestSuite' +end diff --git a/bw-dispatch.gemspec b/bw-dispatch.gemspec new file mode 100644 index 00000000..26b38c4e --- /dev/null +++ b/bw-dispatch.gemspec @@ -0,0 +1,20 @@ +# -*- encoding: utf-8 -*- +require File.expand_path('../lib/bw-dispatch/version', __FILE__) + +Gem::Specification.new do |gem| + gem.authors = ["James Harton"] + gem.email = ["james@sociable.co.nz"] + gem.description = %q{Event loop bubblewrapper} + gem.summary = %q{A bubblewrapper for GCD using common Ruby idioms} + gem.homepage = "https://github.com/jamesotron/bw-dispatch" + + gem.files = `git ls-files`.split($\) + gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } + gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) + gem.name = "bw-dispatch" + gem.require_paths = ["lib"] + gem.version = BW::Dispatch::VERSION + + gem.add_dependency 'bubble-wrap' + gem.add_development_dependency 'rake' +end diff --git a/lib/bw-dispatch.rb b/lib/bw-dispatch.rb new file mode 100644 index 00000000..32a977e6 --- /dev/null +++ b/lib/bw-dispatch.rb @@ -0,0 +1,11 @@ +require 'bubble-wrap/loader' +require "bw-dispatch/version" + +BW.require 'motion/**/*.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 diff --git a/lib/bw-dispatch/version.rb b/lib/bw-dispatch/version.rb new file mode 100644 index 00000000..b68026d3 --- /dev/null +++ b/lib/bw-dispatch/version.rb @@ -0,0 +1,5 @@ +module BW + module Dispatch + VERSION = "0.0.1" + end +end diff --git a/motion/dispatch.rb b/motion/dispatch.rb new file mode 100644 index 00000000..bce9fcfa --- /dev/null +++ b/motion/dispatch.rb @@ -0,0 +1,29 @@ +module BubbleWrap + module Dispatch + module_function + + def add_timer(interval, callback=nil, &blk) + @timers ||= [] + timer = Timer.new(interval,callback,&blk) + timer.on(:fired) do + @timers.delete(timer) + end + @timers.unshift(timer) + timer + end + + def add_periodic_timer(interval, callback=nil, &blk) + @periodic_timers ||= [] + timer = PeriodicTimer.new(internval,callback,blk) + timer.on(:cancelled) do + @periodic_timers.delete(timer) + end + @periodic_timers.unshift(timer) + timer + end + + def defer(op=nil,cb=nil,&blk) + end + + end +end diff --git a/motion/dispatch/default_deferrable.rb b/motion/dispatch/default_deferrable.rb new file mode 100644 index 00000000..380e776f --- /dev/null +++ b/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 diff --git a/motion/dispatch/deferrable.rb b/motion/dispatch/deferrable.rb new file mode 100644 index 00000000..e18616d5 --- /dev/null +++ b/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 diff --git a/motion/dispatch/eventable.rb b/motion/dispatch/eventable.rb new file mode 100644 index 00000000..f9289f4e --- /dev/null +++ b/motion/dispatch/eventable.rb @@ -0,0 +1,17 @@ +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(&call) + end + + end + end +end diff --git a/motion/dispatch/future.rb b/motion/dispatch/future.rb new file mode 100644 index 00000000..7655ee24 --- /dev/null +++ b/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 diff --git a/motion/dispatch/periodic_timer.rb b/motion/dispatch/periodic_timer.rb new file mode 100644 index 00000000..b0ecd427 --- /dev/null +++ b/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 diff --git a/motion/dispatch/queue.rb b/motion/dispatch/queue.rb new file mode 100644 index 00000000..3de71fba --- /dev/null +++ b/motion/dispatch/queue.rb @@ -0,0 +1,28 @@ +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 = EM::Queue.new + # q.push('one', 'two', 'three') + # 3.times do + # q.pop{ |msg| puts(msg) } + # end + class Queue + + # Create a new queue + # label: + # A label to attach to the queue to uniquely identify it in debugging + # tools such as Instruments, sample, stackshots, and crash reports. + # Because applications, libraries, and frameworks can all create their + # own dispatch queues, a reverse-DNS naming style (com.example.myqueue) + # is recommended. This parameter is optional and can be nil. + def initialize(label=nil) + end + end + end +end diff --git a/motion/dispatch/timer.rb b/motion/dispatch/timer.rb new file mode 100644 index 00000000..16ea55c8 --- /dev/null +++ b/motion/dispatch/timer.rb @@ -0,0 +1,24 @@ +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) + end + + end + end +end