initial checkin
authorMichael Wallner <mike@php.net>
Fri, 8 Mar 2013 10:18:33 +0000 (11:18 +0100)
committerMichael Wallner <mike@php.net>
Fri, 8 Mar 2013 10:18:33 +0000 (11:18 +0100)
lib/atick/Ticker.php [new file with mode: 0644]
tests/lib/atick/TickerTest.php [new file with mode: 0644]
tests/setup.inc [new file with mode: 0644]

diff --git a/lib/atick/Ticker.php b/lib/atick/Ticker.php
new file mode 100644 (file)
index 0000000..cdc3364
--- /dev/null
@@ -0,0 +1,152 @@
+<?php
+
+namespace atick;
+
+/**
+ * Asynchronnous resource handling, optionally (ab)using ticks
+ * 
+ * Example with ticks:
+ * <code>
+ * <?php
+ * declare(ticks=1);
+ *
+ * $conn = new \pq\Connection;
+ * $conn->execAsync("SELECT * FROM foo", function ($rs) {
+ *     var_dump($rs);
+ * });
+ * 
+ * $ticker = new \atick\Ticker;
+ * $ticker->register();
+ * $ticker->read($conn->socket, function($fd) use ($conn) {
+ *     $conn->poll();
+ *     if ($conn->busy) {
+ *         return false;
+ *     }
+ *     $conn->getResult();
+ *     return true;
+ * });
+ *
+ * while (count($ticker));
+ * ?>
+ * </code>
+ * 
+ * And an example without ticks:
+ * <code>
+ * <?php
+ * $conn = new \pq\Connection;
+ * $conn->execAsync("SELECT * FROM foo", function ($r) {
+ *     var_dump($r);
+ * });
+ * 
+ * $ticker = new \atick\Ticker;
+ * $ticker->read($conn->socket, function($fd) use ($conn) {
+ *     $conn->poll();
+ *     if ($conn->busy) {
+ *         return false;
+ *     }
+ *     $conn->getResult();
+ *     return true;
+ * });
+ * 
+ * while($ticker());
+ * ?>
+ * </code>
+ */
+class Ticker implements \Countable
+{
+       /**
+        * @var array
+        */
+       protected $read = array();
+       
+       /**
+        * @var array
+        */
+       protected $write = array();
+       
+       /**
+        * Register the ticker as tick function
+        * @return \atick\Ticker
+        */
+       function register() {
+               register_tick_function(array($this, "__invoke"));
+               return $this;
+       }
+       
+       /**
+        * Unregister the ticker as tick function
+        * @return \atick\Ticker
+        */
+       function unregister() {
+               unregister_tick_function(array($this, "__invoke"));
+               return $this;
+       }
+       
+       /**
+        * The tick handler; calls atick\Ticker::wait(0)
+        * @return int
+        */
+       function __invoke() {
+               return $this->wait(0);
+       }
+       
+       /**
+        * Wait for read/write readiness on the watched fds
+        * @param float $timeout
+        * @return int count of wached fds
+        */
+       function wait($timeout = 1) {
+               $r = $w = $e = array();
+               foreach ($this->read as $s) {
+                       $r[] = $s[0];
+               }
+               foreach ($this->write as $s) {
+                       $w[] = $s[0];
+               }
+               $s = (int) $timeout;
+               $u = (int) (($timeout - $s) * 1000000);
+               if (($r || $w) && stream_select($r, $w, $e, $s, $u)) {
+                       foreach ($r as $s) {
+                               if ($this->read[(int)$s][1]($s)) {
+                                       unset($this->read[(int)$s]);
+                               }
+                       }
+                       foreach ($w as $s) {
+                               if ($this->write[(int)$s][1]($s)) {
+                                       unset($this->write[(int)$s]);
+                               }
+                       }
+               }
+               return $this->count();
+       }
+       
+       /**
+        * @implements \Countable
+        * @return int
+        */
+       function count() {
+               return count($this->read) + count($this->write);
+       }
+       
+       /**
+        * Attach a read handler; let the callback return true, to stop watching the fd.
+        * @param resource $fd
+        * @param callable $cb
+        * @return \atick\Ticker
+        */
+       function read($fd, callable $cb) {
+               $this->read[(int)$fd] = array($fd, $cb);
+               return $this;
+       }
+       
+       /**
+        * Attach a write handler; let the callback return true, to stop watching the fd.
+        * @param int $fd
+        * @param callable $cb
+        * @return \atick\Ticker
+        */
+       function write($fd, callable $cb) {
+               $this->write[(int)$fd] = array($fd, $cb);
+               return $this;
+       }
+}
diff --git a/tests/lib/atick/TickerTest.php b/tests/lib/atick/TickerTest.php
new file mode 100644 (file)
index 0000000..5d07985
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+namespace atick;
+
+include __DIR__."/../../setup.inc";
+
+class TickerTest extends \PHPUnit_Framework_TestCase {
+
+       /**
+        * @var Ticker
+        */
+       protected $ticker;
+
+       protected function setUp() {
+               $this->ticker = new Ticker;
+       }
+
+       public function testRegister() {
+               $this->ticker->register();
+               $this->ticker->unregister();
+               $this->ticker->register();
+               $this->ticker->unregister();
+       }
+       
+       public function testTicks() {
+               $file = fopen(__FILE__, "r");
+               stream_set_blocking($file, false);
+               $read = 0;
+               
+               declare(ticks=1);
+               $this->ticker->register();
+               $this->ticker->read($file, function ($file) use (&$read) {
+                       do {
+                               $data = fread($file, 4096);
+                               $read += strlen($data);
+                       } while (strlen($data));
+                       return feof($file);
+               });
+               
+               $dummy = "This test is do tiny, ";
+               $dummy.= "we don't have to do much.";
+               
+               $this->assertEquals(filesize(__FILE__), $read);
+       }
+
+       public function testBasic() {
+               $r = $w = false;
+               $this->assertCount(0, $this->ticker);
+               
+               $file = fopen(__FILE__, "r");
+               stream_set_blocking($file, false);
+               
+               $this->ticker->read($file, function ($file) use (&$r) {
+                       return $r;
+               });
+               $this->assertCount(1, $this->ticker);
+               $this->ticker->write($file, function ($file) use (&$w) {
+                       return $w;
+               });
+               $this->assertCount(2, $this->ticker);
+               
+               $this->assertSame(2, $this->ticker->wait());
+               $r = true;
+               $this->assertSame(1, $this->ticker->wait());
+               $w = true;
+               $this->assertSame(0, $this->ticker->wait());
+       }
+
+}
diff --git a/tests/setup.inc b/tests/setup.inc
new file mode 100644 (file)
index 0000000..e91234c
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+
+spl_autoload_register(function($c) {
+       if (substr($c, 0, 6) == "atick\\") return require_once sprintf("%s/../lib/%s.php", __DIR__, strtr($c, "\\", "/"));
+});