Skip to content

Instantly share code, notes, and snippets.

@seanlilmateus
Last active January 12, 2018 16:11
Show Gist options
  • Save seanlilmateus/4134454 to your computer and use it in GitHub Desktop.
Save seanlilmateus/4134454 to your computer and use it in GitHub Desktop.
Rubymotion GCD Future; with lazy evaluation
#!/usr/bin/env macruby -wKU
framework 'Foundation'
module Dispatch
module Futuristic
def proxy
Class.new(BasicObject) do
def initialize(obj)
@obj = obj
end
def method_missing(meth, *args, &blk)
Future.new { @obj.send(meth, *args, &blk) }
end
def respond_to_missing?(meth, include_private = false)
@obj.respond_to?(meth)
end
end
end
def future
proxy.new(self)
end
end
class Promise < BasicObject
# MacRuby and Rubymotion BasicObject#initialize doesn't like blocks, so we have to do this
def self.new(&block)
::Kernel.raise(::ArgumentError, "You cannot initalize a Dispatch::Promise without a block") unless block_given?
self.alloc.initialization(block)
end
# setup Grand Central Dispatch concurrent Queue and Group
def initialization(block)
init
@computation = block
@arg = nil
# Groups are just simple layers on top of semaphores.
#Dispatch.once do
@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.dispatch.promise-0x#{hash.to_s(16)}") #
#end
@value = @success = false
self
end
def inspect
__value__.inspect
end
def done?
!!@value
end
alias_method :ready?, :done?
private
def __force__
# Asynchronously dispatch the future to the thread-local queue.
@promise_queue.async(@group) { @value = @computation.call(*@arg) }
end
def __value__
# Wait fo the computation to finish. If it has already finished, then
# just return the value in question.
__force__ unless done?
@group.wait
@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
class Future < Promise
# MacRuby and Rubymotion BasicObject#initialize doesn't like blocks, so we have to do this
class << self
def sequence(*args, &block)
array = Array(args).flatten
if block_given?
result = []
Queue.concurrent(:high).apply(array.count) do |idx|
result << Future.new { block.call(array[idx]) }
end
NSArray.arrayWithArray(result)
else
array.extend(Futuristic)
array.future
end
end
def new(arg=nil, &block)
::Kernel.raise(::ArgumentError, "You cannot initalize a Dispatch::Future without a block") unless block_given?
self.alloc.initialization(arg, block)
end
end
def when_done(&call_back)
@group.notify(@promise_queue) { call_back[__value__] }
self
end
def initialization(arg, block)
super(block)
@arg = arg
__force__
self
end
def description
state = done? ? :finished : :running
NSString.stringWithString("<#{self.class} 0x#{hash.to_s(16)} STATE:#{state} on #@promise_queue...>")
end
def value; __value__; end
private
def method_missing(meth, *args, &block)
self.send(meth, *args, &block)
end
def respond_to_missing?(method_name, include_private = false)
self.respond_to?(method_name, include_private)
end
def forwardingTargetForSelector(sel)
self if self.respond_to?(sel)
end
alias_method :inspect, :description
end
end
class Person
include Dispatch::Futuristic
attr_accessor :id, :name, :email
def initialize(dictionary = {})
setValuesForKeysWithDictionary(dictionary) if dictionary.is_a?(Hash)
end
def dance(&block)
sleep 4
block.call(Dispatch::Queue.current)
end
def setValue(value, forUndefinedKey:key); end
end
person = Person.new(name: "Mateus", id:10, email:"[email protected]")
# running on background queue
person.future.dance do |queue|
puts "running queue: #{queue}"
end
# running on main queue
person.dance do |queue|
puts "running queue: #{queue}"
end
urls = ["http://www.facebook.com/", "http://www.google.com/"].map { |url| NSURL.URLWithString(url) }
result = Dispatch::Future.sequence(urls) do |url|
NSString.stringWithContentsOfURL(url, encoding:NSUTF8StringEncoding, error:nil)
end
NSLog("Result: %@", result.last.value)
p result.first
future = Dispatch::Future.new { sleep 0.4; 10}
p future.ready?
p future.value
p future.ready?
future = Dispatch::Future.new(40) { |value| value + 2 }
p future.ready?
p future.value
sleep 5
# Dispatch Future
the_future = Dispatch::Future.new { sleep 1; 6 / 2 }
the_future.when_done { |value| value = 10 }
p the_future # <Dispatch::Future 0x400d4bae0 STATE:running on org.macruby.dispatch.promise-0x400d4bae0...>
calculation = Dispatch::Future.new { sleep 2; 4 * 4 }
p calculation # <Dispatch::Future 0x400d4bae0 STATE:running on org.macruby.dispatch.promise-0x400d4bae0...>
calculation.when_done { |value| puts value + 10 } # 26
p calculation.value + 2 # 18
# Dispatch Promise
promise = Dispatch::Promise.new { 12 }
promise2 = Dispatch::Promise.new { sleep 2; 10 }
result = promise2 + promise
p result
a = Dispatch::Promise.new { 10 / 2} # 10 / 2 = 5
b = Dispatch::Promise.new { a + 1 } # 5 + 1 = 6
c = Dispatch::Promise.new { a - 1 } # 5 - 1 = 4
puts b * c # 24
file_name = "/usr/share/dict/words"
promise = Dispatch::Promise.new { NSData.dataWithContentsOfFile(file_name) }
string = NSString.alloc.initWithData(promise, encoding:NSUTF8StringEncoding)
summe = Dispatch::Future.sequence([*1..100]).inject { |sum, x| sum += x*2-1 }
p summe.value
futures = [*1..1000].map { |i| Dispatch::Future.new { sleep 1; i * 2 } }
p futures.inject(0) { |sum, x| sum += x.value }
@seanlilmateus
Copy link
Author

changed from ::Thread.current[:futures] to @futures_queue to make it thread safe
example below used not to work, should be working now:

future = Dispatch::Future.new { :HALLLLO }
Dispatch::Queue.concurrent(:high).async { puts future.value }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment