Created
August 30, 2020 16:34
-
-
Save bwoebi/4180f8b1695d4ee5c09d1c1d99e5bdbf to your computer and use it in GitHub Desktop.
Simplest Fiber API with usage example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class Fiber { | |
public static Fiber $main; | |
public static function current(): Fiber; | |
public function __construct(callable $new); | |
public function continue(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class Loop { | |
private static $delayed = []; | |
private static $immediates = []; | |
private static Generator $fiberGen; | |
# $cb returns null|[Fiber, ?Throwable, $result] | |
public static function delay(callable $cb, int $ms) { | |
$this->delayed[floor(microtime() * 1000) + $ms][] = $cb; | |
} | |
public static function immediate(callable $cb) { | |
self::$immediates[] = $cb: | |
} | |
public static function yield() { | |
$nextFiber = (self::$fiberGen ?? self::$fiberGen = self::fiberGenerator())->next(); | |
$nextFiber->continue(); | |
$fiber = Fiber::current(); | |
if ($fiber->exception != null) { | |
throw $fiber->exception; | |
} | |
return $fiber->result; | |
} | |
public static function fiberGenerator() { | |
for (;;) { | |
$immediates = self::$immediates; | |
self::$immediates = []; | |
foreach ($immediates as $cb) { | |
if ([$fiber, $exception, $result] = $cb()) { | |
$fiber->exception = $exception; | |
$fiber->result = $result; | |
yield $fiber; | |
} | |
} | |
$minExpire = INF; | |
foreach (self::$delayed as $expired => $cbs) { | |
unset(self::$delayed[$expired]); | |
foreach ($cbs as $cb) { | |
if ([$fiber, $exception, $result] = $cb()) { | |
$fiber->exception = $exception; | |
$fiber->result = $result; | |
yield $fiber; | |
} else { | |
$minExpire = min($minExpire, $expired); | |
} | |
} | |
} | |
if ($minExpire == INF || self::$immediates) { | |
unset(self::$fiberGen); | |
yield Fiber::$main; | |
} else { | |
usleep($minExpire / 1000 - microtime(1)); | |
} | |
} | |
} | |
} | |
// just to show off direct fiber interaction, can obviously also do delay(): Promise and return null in the callback, resolving a deferred instead | |
function sleepFiber(int $ms) { | |
$fiber = Fiber::current(); | |
Loop::delay(function() use ($fiber) { | |
return [$fiber, null, true]; | |
}, $ms); | |
return Loop::yield(); | |
} | |
function asnyc(callable $cb): Promise { | |
$deferred = new Deferred; | |
new Fiber(function () use ($deferred) { | |
try { | |
$deferred->resolve($cb()); | |
} catch (Throwable $e) { | |
$deferred->fail($e); | |
} | |
}); | |
return $deferred; | |
} | |
function await(Promise $promise) { | |
$fiber = Fiber::current(); | |
$promise->onResolve(function($result, $exception) use ($fiber) { | |
Loop::immediate(fn() => [$fiber, $excpetion, $result]); | |
}); | |
return Loop::yield(); | |
} | |
$task1 = async(function() { | |
sleepFiber(100); | |
}); | |
$task2 = async(function() { | |
sleepFiber(50); | |
}); | |
$allResults = await(all($task1, $task2)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment