Permalink
Browse files

commit the gem files

  • Loading branch information...
1 parent 9489959 commit be3d890de83ebab824bbefc7a558ed0f73f773dd @seanlilmateus committed Mar 26, 2013
View
BIN futuristic-0.4.3.gem
Binary file not shown.
View
21 futuristic.gemspec
@@ -0,0 +1,21 @@
+# -*- encoding: utf-8 -*-
+lib = File.expand_path('../lib', __FILE__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
+require 'futuristic/version'
+
+Gem::Specification.new do |gem|
+ gem.name = 'futuristic'
+ gem.version = Futuristic::VERSION
+ gem.date = '2013-03-21'
+ gem.summary = %q{Rubymotion Promise and Futures}
+ gem.description = %q{Rubymotion Promise and Futures helper on top of Grand Central Dispatch}
+ gem.authors = ["Mateus Armando"]
+ gem.email = 'seanlilmateus@yahoo.de'
+ gem.files = ["lib/futuristic.rb"]
+ gem.homepage = 'http://github.com/seanlilmateus/futuristic'
+
+ 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.require_paths = ["lib"]
+end
View
9 lib/futuristic.rb
@@ -0,0 +1,9 @@
+unless defined?(Motion::Project::Config)
+ raise "This file must be required within a RubyMotion project Rakefile."
+end
+
+Motion::Project::App.setup do |app|
+ Dir.glob(File.join(File.dirname(__FILE__), 'futuristic/**/*.rb')).each do |file|
+ app.files.unshift(file)
+ end
+end
View
56 lib/futuristic/dispatch/future.rb
@@ -0,0 +1,56 @@
+# Future
+# actually future acts just like a Promise,
+# the only difference is that they are not lazy
+module Dispatch
+ class Future < Promise
+ # Create s new Future
+ #
+ # Example:
+ # >> future = Dispatch::Future.new { long_taking_task; 10 }
+ # => future.value # 10
+ # Arguments
+ # block, last
+ def self.new(&block)
+ # MacRuby and Rubymotion BasicObject#initialize doesn't like blocks, so we have to do this
+ # new :: a -> Eval (Future a)
+ unless block_given?
+ ::Kernel.raise(::ArgumentError, "You cannot initalize a Dispatch::Future without a block")
+ end
+ self.alloc.initialization(block)
+ end
+
+
+ def when_done(&call_back)
+ @group.notify(@promise_queue) { call_back.call __value__ }
+ self
+ end
+
+
+ def initialization(block)
+ super(block)
+ __force__
+ self
+ end
+
+ #
+ # Future#description
+ # => <Future: 0x400d382a0 run>
+ #
+ def description
+ state = done? ? :dead : :run
+ NSString.stringWithString(super.gsub(/>/, " #{state}>"))
+ end
+ alias_method :to_s, :description
+ alias_method :inspect, :description
+
+
+ def done?
+ !!@value
+ end
+
+
+ def value
+ __value__
+ end
+ end
+end
View
73 lib/futuristic/dispatch/promise.rb
@@ -0,0 +1,73 @@
+module Dispatch
+ class Promise < BasicObject
+ # new :: Promise a -> Eval a
+ # MacRuby and Rubymotion BasicObject#initialize doesn't like blocks, so we have to do this
+ def self.new(&block)
+ unless block_given?
+ ::Kernel.raise(::ArgumentError, "You cannot initalize a Dispatch::Promise without a block")
+ end
+ self.alloc.initialization(block)
+ end
+
+
+ # setup Grand Central Dispatch concurrent Queue and Group
+ def initialization(block)
+ init
+ @computation = block
+ # Groups are just simple layers on top of semaphores.
+ @group = ::Dispatch::Group.new
+ # Each thread gets its own FIFO queue upon which we will dispatch
+ # the delayed computation passed in the &block variable.
+ @promise_queue = ::Dispatch::Queue.concurrent("org.macruby.#{self.class}-0x#{hash.to_s(16)}") #
+ self
+ end
+
+
+ def inspect
+ __value__.inspect
+ end
+
+
+ private
+ # Asynchronously dispatch the future to the thread-local queue.
+ def __force__
+ @running = true # should only be initiliazed once
+ @promise_queue.async(@group) do
+ begin
+ @value = @computation.call
+ rescue ::Exception => e
+ @exception = e
+ end
+ end
+ end
+
+
+ # Wait fo the computation to finish. If it has already finished, then
+ # just return the value in question.
+ def __value__
+ __force__ unless @running
+ @group.wait
+ ::Kernel.raise(@exception) if @exception
+ @value
+ end
+
+
+ # like method_missing for objc
+ # without this 'promise = Dispatch::Promise.new { NSData.dataWithContentsOfFile(file_name) }' will not work
+ # NSString.alloc.initWithData(promise, encoding:NSUTF8StringEncoding)
+ # since promise will not respond to NSData#bytes and return a NSInvalidArgumentException
+ def method_missing(meth, *args, &block)
+ __value__.send(meth, *args, &block)
+ end
+
+
+ def respond_to_missing?(method_name, include_private = false)
+ __value__.respond_to?(method_name, include_private) || super
+ end
+
+
+ def forwardingTargetForSelector(sel)
+ __value__ if __value__.respond_to?(sel)
+ end
+ end
+end
View
19 lib/futuristic/futuristic.rb
@@ -0,0 +1,19 @@
+# Futuristic
+module Futuristic
+ def future
+ proxy = Class.new(BasicObject) do
+ def initialize(obj)
+ @object = obj
+ end
+
+ def method_missing(meth, *args, &blk)
+ Dispatch::Future.new { @object.send(meth, *args, &blk) }
+ end
+
+ def respond_to_missing?(meth, include_private = false)
+ @object.respond_to?(meth) || super
+ end
+ end
+ proxy.new(self)
+ end
+end
View
3 lib/futuristic/version.rb
@@ -0,0 +1,3 @@
+module Futuristic
+ VERSION = "0.4.3"
+end
View
25 spec/future_spec.rb
@@ -0,0 +1,25 @@
+describe Dispatch::Future do
+
+ before { @method = Kernel.method(:future) }
+
+ it "should inherit from BasicObject if available, and not otherwise" do
+ Dispatch::Future.ancestors.should.include BasicObject
+ end
+
+ # behaves_like "A Promise"
+
+ def range_of(range)
+ lambda { |obj| range.include?(obj) }
+ end
+
+ it "should work in the background" do
+ start = Time.now
+ x = future { sleep 3; 5 }
+ middle = Time.now
+ y = x.value + 5
+ y.should == 10
+ finish = Time.now
+ (middle - start).should.be range_of(0.0..1.0)
+ (finish - start).should.be range_of(3.0..3.9)
+ end
+end
View
25 spec/promise_spec.rb
@@ -0,0 +1,25 @@
+describe Dispatch::Promise do
+ before do
+ @method = Kernel.method(:promise)
+ end
+
+ it "should inherit from BasicObject if available, and not otherwise" do
+ Dispatch::Promise.ancestors.should.include BasicObject
+ end
+
+ behaves_like "A Promise"
+
+ it "should delay execution" do
+ value = 5
+ x = @method.call { value = 10 ; value }
+ value.should == 5
+ y = x + 5
+ y.should == 15
+ value.should == 10
+ end
+
+ it "should delay execution of invalid code" do
+ lambda { 1 / 0 }.should.raise(ZeroDivisionError).message.should.match(/divided by 0/)
+ lambda { x = [ 1, @method.call { x / 0 }]}.should.not.raise(ZeroDivisionError)
+ end
+end
View
109 spec/shared_spec.rb
@@ -0,0 +1,109 @@
+shared "A Promise" do
+ it "should be createable" do
+ lambda { x = @method.call { 3 + 5 } }.should.not.raise(Exception)
+ end
+
+ it "should accept a block requiring arguments" do
+ lambda { x = @method.call { |x| 3 + 5 }}.should.not.raise(Exception)
+ end
+
+ it "should be forceable" do
+ x = @method.call { 3 + 5 }
+ x.to_i.should == 8
+ x.should == 8
+ end
+
+ it "should respond_to? a method on the result" do
+ x = @method.call { 3 + 5 }
+ x.respond_to?(:+).should == true
+ end
+
+ it "should not respond_to? a method not on the result" do
+ x = @method.call { 3 + 5 }
+ x.respond_to?(:asdf).should == false
+ end
+
+ it "should evaluate to a value" do
+ (5 + @method.call { 1 + 2 }).should == 8
+ end
+
+ it "should hold its value" do
+ y = 5
+ x = @method.call { y = y + 5 }
+ x.should == 10
+ x.should == 10
+ end
+
+ it "should only execute once" do
+ y = 1
+ x = @method.call { (y += 1) && false }
+ x.should == false
+ x.should == false
+ y.should == 2
+ end
+
+ it "should raise exceptions raised during execution when accessed" do
+ y = Object.new
+ y = @method.call { 1 / 0 }
+ lambda { y.inspect }.should.raise(ZeroDivisionError)
+ lambda { y.inspect }.should.raise(ZeroDivisionError)
+ end
+
+ it "should only execute once when execptions are raised" do
+ y = 1
+ x = @method.call { (y += 1) ; (1 / 0) }
+ lambda { x.inspect }.should.raise(ZeroDivisionError)
+ lambda { x.inspect }.should.raise(ZeroDivisionError)
+ y.should == 2
+ end
+
+ it "should remain the same for an object reference" do
+ h = {}
+ x = Object.new
+ h[:test] = @method.call { x }
+ h[:test].should == x
+ end
+
+ it "should be eql? for results" do
+ x = Object.new
+ y = @method.call { x }
+ y.should.equal x
+ # this would be ideal, but it can't be done in Ruby. result
+ # objects that have a redefined #eql? should do fine.
+ #x.should eql y
+ end
+
+ it "should be equal? for results" do
+ x = Object.new
+ y = @method.call { x }
+ y.should.equal x
+ # this would be ideal, but it can't be done in Ruby.
+ #x.should equal y
+ end
+
+
+
+ it "should be thread safe" do
+ x = @method.call { res = 1; 3.times { res = res * 5 ; sleep 1 } ; res }
+ results = []
+ changeds = []
+ changed = false
+ Dispatch::Queue.new('future.rspec').apply(10) do |idx|
+ res = old_res = 125
+ res = x + 5
+ results[idx] = res
+ changed != (res == old_res || idx == 0)
+ changeds[idx] = changed
+ end
+
+ results.each do |result|
+ result.should == 130
+ end
+
+ changeds.each do |changed|
+ changed.should == false
+ end
+
+ changeds.size.should == 10
+ end
+end

0 comments on commit be3d890

Please sign in to comment.