Skip to content

Instantly share code, notes, and snippets.

@bwoebi
Created August 30, 2020 16:34
Show Gist options
  • Save bwoebi/4180f8b1695d4ee5c09d1c1d99e5bdbf to your computer and use it in GitHub Desktop.
Save bwoebi/4180f8b1695d4ee5c09d1c1d99e5bdbf to your computer and use it in GitHub Desktop.
Simplest Fiber API with usage example
<?php
class Fiber {
public static Fiber $main;
public static function current(): Fiber;
public function __construct(callable $new);
public function continue();
}
<?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