[macruby-changes] [3640] MacRuby/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri Feb 26 17:17:21 PST 2010


Revision: 3640
          http://trac.macosforge.org/projects/ruby/changeset/3640
Author:   ernest.prabhakar at gmail.com
Date:     2010-02-26 17:17:20 -0800 (Fri, 26 Feb 2010)
Log Message:
-----------
Document Custom Dispatch::Source 'or' and 'add'

Modified Paths:
--------------
    MacRuby/trunk/lib/dispatch/README.rdoc
    MacRuby/trunk/lib/dispatch/source.rb
    MacRuby/trunk/spec/macruby/library/dispatch/source_spec.rb

Modified: MacRuby/trunk/lib/dispatch/README.rdoc
===================================================================
--- MacRuby/trunk/lib/dispatch/README.rdoc	2010-02-27 01:17:02 UTC (rev 3639)
+++ MacRuby/trunk/lib/dispatch/README.rdoc	2010-02-27 01:17:20 UTC (rev 3640)
@@ -26,15 +26,17 @@
 
 === What You Need
 
-As the MacRuby 0.6 features help reduce the learning curve for GCD, we will assume those for the remainder of this article.  Note that MacRuby 0.6 is currently (as of Feb 2010) only available via the source[http://www.macruby.org/source.html] or the {nightly builds}[http://www.icoretech.org/2009/09/macruby-nightlies/].
+As the MacRuby 0.6 features help reduce the learning curve for GCD, we will assume those for the remainder of this article.  Note that MacRuby 0.6 is currently (as of Feb 2010) only available via the source[http://www.macruby.org/source.html] or the {nightly builds}[http://www.icoretech.org/2009/09/macruby-nightlies/]. The examples all assume you  run the latest macirb and require the +dispatch+ library:
 
+	$ macirb
+	require 'dispatch'	
+
 We also assume that you are already familiar with Ruby, though not necessarily MacRuby. No prior knowledge of C or GCD is assumed or required, but the {dispatch(3) man page}[http://developer.apple.com/mac/library/DOCUMENTATION/Darwin/Reference/ManPages/man3/dispatch.3.html] may be helpful if you wish to better understand the underlying semantics.
 
 == Dispatch::Job: Easy Concurrency
 
-The easiest way to perform concurrent work is via a +Job+ object. Say you have a complex, long-running calculation you want to happen in the background. Create a job by passing the block you want to execute:
+The easiest way to perform concurrent work is via a +Job+ object. Say you have a complex, long-running calculation you want to happen in the background. Create a job by passing Dispatch::Job's initializer the block you want to execute:
 
-	require 'dispatch'
 	job = Dispatch::Job.new { Math.sqrt(10**100) }
 
 This atomically[http://en.wikipedia.org/wiki/Atomic_operation] adds the block to GCD's default concurrent queue, then returns immediately so you don't stall the main thread.
@@ -43,7 +45,7 @@
 
 === Job#value: Asynchronous Return Values
 
-The downside of asynchrony is that you don't know exactly when your job will execute.  Fortunately, +Dispatch::Job+ duck-types +Thread[http://ruby-doc.org/core/classes/Thread.html]+ as much as possible, so you can call +value[http://ruby-doc.org/core/classes/Thread.html#M000460]+ to obtain the result of executing that block:
+The downside of asynchrony is that you don't know exactly when your job will execute.  Fortunately, +Dispatch::Job+ attempts to duck-type +Thread[http://ruby-doc.org/core/classes/Thread.html]+, so you can call +value[http://ruby-doc.org/core/classes/Thread.html#M000460]+ to obtain the result of executing that block:
 
 	@result = job.value
 	puts @result.to_int.to_s.size # => 51
@@ -253,74 +255,118 @@
 * Mach ports
 * Custom application-specific events
 	
-When the source “fires,” GCD will schedule the handler on the specific queue if it is not currently running, or -- more importantly -- coalesce pending events if it is. This provides excellent responsiveness without the expense of either polling or binding a thread to the event source.  Plus, since the handler is never run more than once at a time, the block doesn’t even need to be reentrant -- and thus you don't need to +synchronize+ any variables that are only used there.
+When the source “fires,” GCD will schedule the _handler_ block on the specific queue if it is not currently running, or -- more importantly -- coalesce pending events if it is. This provides excellent responsiveness without the expense of either polling or binding a thread to the event source.  Plus, since the handler is never run more than once at a time, the block doesn’t even need to be reentrant -- and thus you don't need to +synchronize+ any variables that are only used there.
 
 === Source.periodic
 
-For example, this creates a +periodic+ timer that runs every 1.2 seconds and prints out the number of pending events:
+We'll start with a simple example: a +periodic+ timer that runs every 0.9 seconds and prints out the number of pending events:
 
-	source = Source.periodic(1.2) { |src| puts src.data }
-	sleep 3 # => 1 1
+	timer = Source.periodic(0.9) { |src| puts src.data }
+	sleep 2 # => 1 1
 
 === Source#data
 
-As you can see above, the +handler+ block gets called with the source itself as a parameter.  
+As you can see above, the handler  gets called with the source itself as a parameter, which allows you query it for the source's +data+. The meaning of the data varies with the type of +Source+, though it is always an integer. Most commonly -- as in this case -- it is a count of the number of events being processed, and thus "1".
 
 === Source#suspend!
 
-This would rapidly get annoying; to pause, just +suspend!+ the source:
+This monotony rapidly gets annoying; to pause, just +suspend!+ the source:
 
-	source.suspend!
+	timer.suspend!
+	sleep 2
 
-You can suspend a source at any time to prevent it from executing new blocks, though this will not affect a block that is already being processed.
+You can suspend a source at any time to prevent it from running another block, though this will not affect a block that is already being processed.
 
 === Source#resume!
 
-If you change your mind, you can always +resume+ the source:
+If you change your mind, you can always +resume!+ the source:
 
-	source.resume!
-	sleep 2
+	timer.resume!
+	sleep 2 # => 2 1
 
-If the +Source+ has fired one or more times, it will schedule a block containing the coalesced events. 
+If the +Source+ has fired one or more times, it will schedule a block containing the coalesced events. In this case, we were suspended for over 2 intervals, so the pending block will fire with +data+ being at least 2.  
 
 === Source#cancel!
 
-Finally,  you can stop the source entirely by calling +cancel!+:
+Finally, you can stop the source entirely by calling +cancel!+:
 
-	source.cancel!
+	timer.cancel!
 
-This is particularly important to do in MacRuby's implementation of GCD, since (due to garbage collection) there is no other way to get rid of a source.  
+Cancellation is particularly significant in MacRuby's implementation of GCD, since (due to the use of garbage collection) there is no other way to explicitly stop using a source.  
 
+=== Custom Sources
+
+Next up are _custom_ or _application-specific_ sources, which are fired explicitly by the developer instead of in response to an external event.  These can also be considered the primitive upon which other sources are built.
+
+==== Source.add
+
+The +add+ source accumulates the sum of the event data (e.g., for numbers):
+
+	@sum = 0
+	adder = Dispatch::Source.add { |s| @sum += s.data;  }
+
+Note that we use an instance variable (since it is re-assigned), but we don't need to +synchronize+ it since the event handler does not need to be reentrant.
+
+==== Source#<<
+
+To fire a custom source, we invoke what GCD calls a _merge_ using the shovel operator ('+<<+'):
+
+	adder << 1 # => "add 1 -> 1"
+
+The name makes more sense when you see the source firing multiple times:
+
+	adder.suspend!
+	adder << 3
+	adder << 5
+	adder.resume! # => "add 8 -> 9"
+	adder.cancel!
+
+Since the block isn't running, GCD automatically _merges_ the results together using addition.  This is useful for tracking cumulative results across multiple threads, e.g. for a progress viewer.  Note that is also the same event coalescing behavior used by +periodic+.
+
+==== Source.or
+
+Similarly, the +or+ source combines events using a logical OR (e.g, for booleans or bitmasks):
+
+	@mask = 0
+	masker = Dispatch::Source.or { |s| @mask |= s.data }
+	masker.suspend!
+	masker << 0b0011
+	masker << 0b1010
+	masker.resume!
+	puts  "%b" % @mask # => 1011
+	masker.cancel!
+
+This is primarily useful communicating what _kinds_ of events have taken place since the last time the handler fired.
+
 ---
 = UNDER CONSTRUCTION = 
 
-=== Custom Events Example
+=== Process Sources
 
-GCD provides two different types of user events, which differ in how they coalesce the data passed to dispatch_source_merge_data:
+==== Source.process
 
-DISPATCH_SOURCE_TYPE_DATA_ADD: accumulates the sum of the event data (e.g., for numbers)
-DISPATCH_SOURCE_TYPE_DATA_OR: combines events using a logical OR (e.g, for booleans or bitmasks)
+This +or+-style event.
 
-Though it is arguably overkill, we can even use events to rewrite our dispatch_apply example. Since the event handler is only ever called once at a time, we get automatic serialization over the "sum" variable without needing to worry about reentrancy or private queues:
+==== Source.signal
 
-	__block unsigned long sum = 0;
-	dispatch_source_t adder = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, q_default);
-	dispatch_source_set_event_handler(adder, ^{
-		sum += dispatch_source_get_data(adder);
-	});
-	dispatch_resume(adder);
+This +add+-style event.
 
-	#define COUNT 128
-	dispatch_apply(COUNT, q_default, ^(size_t i){
-		unsigned long x = integer_calculation(i);
-		dispatch_source_merge_data(adder, x);
-	});
-	dispatch_release(adder);
+=== File Sources
 
-Note that for this example we changed our calculation to use integers, as dispatch_source_merge_data expects an unsigned long parameter.  
+==== Source.file
 
-=== File Descriptor Example
+This +or+-style event.
 
+==== Source.read
+
+This +add+-style event.
+
+==== Source.write
+
+This +add+-style event.
+
+===  Example
+
 Here is a more sophisticated example involving reading from a file. Note the use of non-blocking I/O to avoid stalling a thread:
 
 	int fd = open(filename, O_RDONLY);

Modified: MacRuby/trunk/lib/dispatch/source.rb
===================================================================
--- MacRuby/trunk/lib/dispatch/source.rb	2010-02-27 01:17:02 UTC (rev 3639)
+++ MacRuby/trunk/lib/dispatch/source.rb	2010-02-27 01:17:20 UTC (rev 3640)
@@ -3,14 +3,12 @@
 module Dispatch
   class Source
     
-    @@proc_event = {
-       exit:PROC_EXIT,
-       fork:PROC_FORK,
-       exec:PROC_EXEC,
-       signal:PROC_SIGNAL
-       }
-
-    @@vnode_event = {
+    @@events = {
+      exit:PROC_EXIT,
+      fork:PROC_FORK,
+      exec:PROC_EXEC,
+      signal:PROC_SIGNAL, 
+      
       delete:VNODE_DELETE,
       write:VNODE_WRITE, 
       extend:VNODE_EXTEND, 
@@ -18,24 +16,24 @@
       link:VNODE_LINK, 
       rename:VNODE_RENAME, 
       revoke:VNODE_REVOKE
-      }
+    }
       
     class << self
-      
-      def event(e)
-        convert(e, @@proc_event) || convert(e, @@vnode_event)
-      end        
-    
-      def events2mask(events, hash)
-        mask = events.collect { |e| convert(e, hash) }.reduce(:|)
-      end
-      
-      def convert(e, hash)
-        value = e.to_int rescue hash[e.to_sym]
+          
+      def event2num(e)
+        value = e.to_int rescue  @@events[e.to_sym]
         raise ArgumentError, "No event type #{e.inspect}" if value.nil?
         value
       end
+    
+      def events2mask(events)
+        mask = events.collect { |e| convert(e) }.reduce(:|)
+      end
 
+      def data2events(bitmask)
+        @@events.select {|k,v| v & bitmask }
+      end
+
       # Returns Dispatch::Source of type DATA_ADD
       def add(queue = Dispatch::Queue.concurrent, &block)
         Dispatch::Source.new(Dispatch::Source::DATA_ADD, 0, 0, queue, &block)

Modified: MacRuby/trunk/spec/macruby/library/dispatch/source_spec.rb
===================================================================
--- MacRuby/trunk/spec/macruby/library/dispatch/source_spec.rb	2010-02-27 01:17:02 UTC (rev 3639)
+++ MacRuby/trunk/spec/macruby/library/dispatch/source_spec.rb	2010-02-27 01:17:20 UTC (rev 3640)
@@ -47,7 +47,7 @@
         Process.kill(@signal, $$)
         Signal.trap(@signal, "DEFAULT")
         @q.sync {}
-        (@event & Dispatch::Source.event(:signal)).should > 0
+        (@event & Dispatch::Source.event2num(:signal)).should > 0
       end
     end
 
@@ -126,7 +126,7 @@
           @q.sync { }
           #while (@fired == false) do; end
           @fired.should == true
-          @flag.should == Dispatch::Source.event(:write)
+          @flag.should == Dispatch::Source.event2num(:write)
         end
       end          
     end
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/macruby-changes/attachments/20100226/493e5d6c/attachment-0001.html>


More information about the macruby-changes mailing list