id; $this->closures[$id] = true; go(function () use ($timeout, $closure, $identifier, $id) { try { ++Timer::$count; $isClosing = match (true) { $timeout > 0 => CoordinatorManager::until($identifier)->yield($timeout), // Run after $timeout seconds. $timeout == 0 => CoordinatorManager::until($identifier)->isClosing(), // Run immediately. default => CoordinatorManager::until($identifier)->yield(), // Run until $identifier resume. }; if (isset($this->closures[$id])) { $closure($isClosing); } } finally { unset($this->closures[$id]); --Timer::$count; } }); return $id; } public function tick(float $timeout, callable $closure, string $identifier = Constants::WORKER_EXIT): int { $id = ++$this->id; $this->closures[$id] = true; go(function () use ($timeout, $closure, $identifier, $id) { try { $round = 0; ++Timer::$count; while (true) { $isClosing = CoordinatorManager::until($identifier)->yield(max($timeout, 0.000001)); if (! isset($this->closures[$id])) { break; } $result = null; try { $result = $closure($isClosing); } catch (Throwable $exception) { $this->logger?->error((string) $exception); } if ($result === self::STOP || $isClosing) { break; } ++$round; ++Timer::$round; } } finally { unset($this->closures[$id]); Timer::$round -= $round; --Timer::$count; } }); return $id; } public function until(callable $closure, string $identifier = Constants::WORKER_EXIT): int { return $this->after(-1, $closure, $identifier); } public function clear(int $id): void { unset($this->closures[$id]); } public function clearAll(): void { $this->closures = []; } public static function stats(): array { return [ 'num' => Timer::$count, 'round' => Timer::$round, ]; } }