├── src ├── vendor │ ├── composer │ │ ├── installed.json │ │ ├── autoload_namespaces.php │ │ ├── autoload_psr4.php │ │ ├── autoload_classmap.php │ │ ├── installed.php │ │ ├── LICENSE │ │ ├── autoload_static.php │ │ ├── autoload_real.php │ │ ├── InstalledVersions.php │ │ └── ClassLoader.php │ └── autoload.php └── vennv │ └── vapm │ ├── StatusPromise.php │ ├── Settings.php │ ├── AssumptionFailedError.php │ ├── ThreadException.php │ ├── InternetException.php │ ├── DeferredException.php │ ├── FiberManager.php │ ├── VapmPMMP.php │ ├── GeneratorManager.php │ ├── PromiseResult.php │ ├── utils │ ├── Property.php │ └── Utils.php │ ├── InternetRequestResult.php │ ├── DescriptorSpec.php │ ├── Error.php │ ├── MicroTask.php │ ├── GarbageCollection.php │ ├── Async.php │ ├── StatusThread.php │ ├── io │ └── functions.php │ ├── ChildCoroutine.php │ ├── MacroTask.php │ ├── Mutex.php │ ├── ct │ └── functions.php │ ├── AwaitGroup.php │ ├── ClosureThread.php │ ├── SampleMacro.php │ ├── Channel.php │ ├── Work.php │ ├── Deferred.php │ ├── CoroutineGen.php │ ├── EventLoop.php │ ├── PHPUtils.php │ ├── Internet.php │ ├── Worker.php │ ├── Stream.php │ ├── System.php │ └── Thread.php ├── .poggit.yml ├── virion.yml ├── phpstan.neon.dist ├── .gitignore ├── .github └── workflows │ └── phpstan.yml ├── composer.json └── README.md /src/vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [], 3 | "dev": true, 4 | "dev-package-names": [] 5 | } 6 | -------------------------------------------------------------------------------- /.poggit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | build-by-default: false 3 | branches: 4 | - main 5 | projects: 6 | LibVapmPMMP: 7 | model: virion 8 | type: library 9 | ... 10 | -------------------------------------------------------------------------------- /virion.yml: -------------------------------------------------------------------------------- 1 | name: LibVapmPMMP 2 | version: 2.5.6 3 | api: 5.0.0 4 | php: 5 | - 8.1 6 | - 8.2 7 | - 8.3 8 | - 8.4 9 | antigen: vennv\vapm 10 | author: VennV 11 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 9 3 | paths: 4 | - src\vennv 5 | treatPhpDocTypesAsCertain: false 6 | checkGenericClassInNonGenericObjectType: false 7 | -------------------------------------------------------------------------------- /src/vendor/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | array($baseDir . '/vennv/vapm'), 10 | ); 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | /vendor/ 3 | 4 | # Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control 5 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 6 | # composer.lock 7 | -------------------------------------------------------------------------------- /src/vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/composer/InstalledVersions.php', 10 | ); 11 | -------------------------------------------------------------------------------- /src/vendor/composer/installed.php: -------------------------------------------------------------------------------- 1 | array( 3 | 'name' => 'nam/src', 4 | 'pretty_version' => '1.0.0+no-version-set', 5 | 'version' => '1.0.0.0', 6 | 'reference' => NULL, 7 | 'type' => 'library', 8 | 'install_path' => __DIR__ . '/../../', 9 | 'aliases' => array(), 10 | 'dev' => true, 11 | ), 12 | 'versions' => array( 13 | 'nam/src' => array( 14 | 'pretty_version' => '1.0.0+no-version-set', 15 | 'version' => '1.0.0.0', 16 | 'reference' => NULL, 17 | 'type' => 'library', 18 | 'install_path' => __DIR__ . '/../../', 19 | 'aliases' => array(), 20 | 'dev_requirement' => false, 21 | ), 22 | ), 23 | ); 24 | -------------------------------------------------------------------------------- /src/vendor/autoload.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'vennv\\vapm\\' => 11, 13 | ), 14 | ); 15 | 16 | public static $prefixDirsPsr4 = array ( 17 | 'vennv\\vapm\\' => 18 | array ( 19 | 0 => __DIR__ . '/../..' . '/vennv/vapm', 20 | ), 21 | ); 22 | 23 | public static $classMap = array ( 24 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 25 | ); 26 | 27 | public static function getInitializer(ClassLoader $loader) 28 | { 29 | return \Closure::bind(function () use ($loader) { 30 | $loader->prefixLengthsPsr4 = ComposerStaticInitfd8e931fecab81cb0db0789466a481c5::$prefixLengthsPsr4; 31 | $loader->prefixDirsPsr4 = ComposerStaticInitfd8e931fecab81cb0db0789466a481c5::$prefixDirsPsr4; 32 | $loader->classMap = ComposerStaticInitfd8e931fecab81cb0db0789466a481c5::$classMap; 33 | 34 | }, null, ClassLoader::class); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vennv/vapm-pmmp", 3 | "keywords": ["vapm-pmmp", "asynchronous", "LibVapmPMMP", "lib-vapm-pmmp", "process", "thread"], 4 | "license": "LGPL-2.0-only", 5 | "version": "2.5.6", 6 | "autoload": { 7 | "psr-4": { 8 | "vennv\\vapm\\": "src/vennv/vapm/" 9 | }, 10 | "files": [ 11 | "src/vennv/vapm/io/functions.php", 12 | "src/vennv/vapm/ct/functions.php" 13 | ] 14 | }, 15 | "authors": [ 16 | { 17 | "name": "VennDev", 18 | "email": "1050080275@sv.hcmunre.edu.vn" 19 | } 20 | ], 21 | "config": { 22 | "allow-plugins": { 23 | "phpstan/extension-installer": true 24 | } 25 | }, 26 | "require": { 27 | "php": ">=8.2", 28 | "php-64bit": "*", 29 | "composer-runtime-api": "^2.0", 30 | "pocketmine/pocketmine-mp": "^5.17.0" 31 | }, 32 | "require-dev": { 33 | "phpstan/phpstan": "*" 34 | }, 35 | "scripts": { 36 | "analyse-src": "./vendor/bin/phpstan analyse -c phpstan.neon.dist" 37 | }, 38 | "extra": { 39 | "virion": { 40 | "spec": "3.0", 41 | "namespace-root": "vennv\\vapm" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | register(true); 33 | 34 | return $loader; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/vennv/vapm/StatusPromise.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | final class StatusPromise 27 | { 28 | 29 | public const PENDING = "pending"; 30 | public const FULFILLED = "fulfilled"; 31 | public const REJECTED = "rejected"; 32 | 33 | } -------------------------------------------------------------------------------- /src/vennv/vapm/Settings.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | final class Settings 27 | { 28 | 29 | /** 30 | * The time in seconds to check should drop the promise if the promise is not resolved or rejected 31 | * in the specified time. 32 | */ 33 | public const TIME_DROP = 7; 34 | 35 | } -------------------------------------------------------------------------------- /src/vennv/vapm/AssumptionFailedError.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use TypeError; 27 | 28 | final class AssumptionFailedError extends TypeError 29 | { 30 | 31 | public function __construct( 32 | protected string $errorMessage, 33 | protected int $errorCode = 0 34 | ) 35 | { 36 | parent::__construct($this->errorMessage, $this->errorCode); 37 | } 38 | 39 | public function __toString(): string 40 | { 41 | return __CLASS__ . ": [$this->errorCode]: $this->errorMessage\n"; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/vennv/vapm/ThreadException.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use TypeError; 27 | 28 | final class ThreadException extends TypeError 29 | { 30 | 31 | public function __construct( 32 | protected string $errorMessage, 33 | protected int $errorCode = 0 34 | ) 35 | { 36 | parent::__construct($this->errorMessage, $this->errorCode); 37 | } 38 | 39 | public function __toString(): string 40 | { 41 | return __CLASS__ . ": [$this->errorCode]: $this->errorMessage\n"; 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/vennv/vapm/InternetException.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use TypeError; 27 | 28 | final class InternetException extends TypeError 29 | { 30 | 31 | public function __construct( 32 | protected string $errorMessage, 33 | protected int $errorCode = 0 34 | ) 35 | { 36 | parent::__construct($this->errorMessage, $this->errorCode); 37 | } 38 | 39 | public function __toString(): string 40 | { 41 | return __CLASS__ . ": [$this->errorCode]: $this->errorMessage\n"; 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/vennv/vapm/DeferredException.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use TypeError; 27 | 28 | final class DeferredException extends TypeError 29 | { 30 | 31 | public function __construct( 32 | protected string $errorMessage, 33 | protected int $errorCode = 0 34 | ) 35 | { 36 | parent::__construct($this->errorMessage, $this->errorCode); 37 | } 38 | 39 | public function __toString(): string 40 | { 41 | return __CLASS__ . ": [$this->errorCode]: $this->errorMessage\n"; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/vennv/vapm/FiberManager.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Fiber; 27 | use Throwable; 28 | use function is_null; 29 | 30 | interface FiberManagerInterface 31 | { 32 | 33 | /** 34 | * @throws Throwable 35 | * 36 | * This is a function that waits for the current fiber to finish. 37 | */ 38 | public static function wait(): void; 39 | 40 | } 41 | 42 | final class FiberManager implements FiberManagerInterface 43 | { 44 | 45 | /** 46 | * @throws Throwable 47 | */ 48 | public static function wait(): void 49 | { 50 | if (!is_null(Fiber::getCurrent())) Fiber::suspend(); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/vennv/vapm/VapmPMMP.php: -------------------------------------------------------------------------------- 1 | 5 | * Copyright (C) 2023 VennDev 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | */ 17 | 18 | declare(strict_types=1); 19 | 20 | namespace vennv\vapm; 21 | 22 | use pocketmine\plugin\PluginBase; 23 | use pocketmine\scheduler\ClosureTask; 24 | 25 | interface VapmPMMPInterface 26 | { 27 | 28 | /** 29 | * @param PluginBase $plugin 30 | * @return void 31 | * 32 | * This function is used to initialize the VapmPMMP class. 33 | * You should place this function in your onEnable() or onLoad() function. 34 | */ 35 | public static function init(PluginBase $plugin): void; 36 | 37 | } 38 | 39 | final class VapmPMMP implements VapmPMMPInterface 40 | { 41 | 42 | private static bool $isInit = false; 43 | 44 | public static function init(PluginBase $plugin): void 45 | { 46 | if (!self::$isInit) { 47 | self::$isInit = true; 48 | EventLoop::init(); 49 | $plugin->getScheduler()->scheduleRepeatingTask(new ClosureTask(fn() => System::runEventLoop()), 1); 50 | } 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/vennv/vapm/GeneratorManager.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | interface GeneratorManagerInterface 27 | { 28 | 29 | /** 30 | * @param int $milliseconds 31 | * @return int 32 | * 33 | * This is a function that calculates the seconds from milliseconds for Generator vapm. 34 | * For example, if you run a function with multiple yields, this calculates the time spent on each of them in seconds. 35 | */ 36 | public static function calculateSeconds(int $milliseconds): int; 37 | 38 | } 39 | 40 | final class GeneratorManager implements GeneratorManagerInterface 41 | { 42 | 43 | public static function calculateSeconds(int $milliseconds): int 44 | { 45 | return $milliseconds * 2000; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/vennv/vapm/PromiseResult.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | interface PromiseResultInterface 27 | { 28 | 29 | public function getStatus(): string; 30 | 31 | public function getResult(): mixed; 32 | 33 | } 34 | 35 | final class PromiseResult implements PromiseResultInterface 36 | { 37 | 38 | private string $status; 39 | 40 | private mixed $result; 41 | 42 | public function __construct(string $status, mixed $result) 43 | { 44 | $this->status = $status; 45 | $this->result = $result; 46 | } 47 | 48 | public function getStatus(): string 49 | { 50 | return $this->status; 51 | } 52 | 53 | public function getResult(): mixed 54 | { 55 | return $this->result; 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/vennv/vapm/utils/Property.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm\utils; 25 | 26 | use Exception; 27 | use function property_exists; 28 | 29 | // This is trait for JsonData|StaticData class 30 | trait Property 31 | { 32 | 33 | /** 34 | * @param object $data 35 | * @param array $options 36 | * @return object 37 | * @throws Exception 38 | */ 39 | public function update(object $data, array $options): object 40 | { 41 | /** 42 | * @var string $key 43 | * @var mixed $value 44 | */ 45 | foreach ($options as $key => $value) { 46 | if (property_exists($data, $key)) $data->{$key} = $value; 47 | /* @phpstan-ignore-line */ 48 | } 49 | 50 | return $data; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/vennv/vapm/InternetRequestResult.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | interface InternetRequestResultInterface 27 | { 28 | 29 | /** 30 | * @return string[][] 31 | */ 32 | public function getHeaders(): array; 33 | 34 | public function getBody(): string; 35 | 36 | public function getCode(): int; 37 | 38 | } 39 | 40 | final class InternetRequestResult implements InternetRequestResultInterface 41 | { 42 | 43 | /** 44 | * @var string[][] $headers 45 | */ 46 | private array $headers; 47 | 48 | private string $body; 49 | 50 | private int $code; 51 | 52 | /** 53 | * @param string[][] $headers 54 | * @param string $body 55 | * @param int $code 56 | */ 57 | public function __construct(array $headers, string $body, int $code) 58 | { 59 | $this->headers = $headers; 60 | $this->body = $body; 61 | $this->code = $code; 62 | } 63 | 64 | /** 65 | * @return string[][] 66 | */ 67 | public function getHeaders(): array 68 | { 69 | return $this->headers; 70 | } 71 | 72 | public function getBody(): string 73 | { 74 | return $this->body; 75 | } 76 | 77 | public function getCode(): int 78 | { 79 | return $this->code; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/vennv/vapm/DescriptorSpec.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | final class DescriptorSpec 27 | { 28 | 29 | public const BASIC = [ 30 | 0 => ['pipe', 'r'], 31 | 1 => ['pipe', 'w'], 32 | 2 => ['pipe', 'w'] 33 | ]; 34 | 35 | public const IGNORE_STDIN = [ 36 | 0 => ['file', '/dev/null', 'r'], 37 | 1 => ['pipe', 'w'], 38 | 2 => ['pipe', 'w'] 39 | ]; 40 | 41 | public const IGNORE_STDOUT = [ 42 | 0 => ['pipe', 'r'], 43 | 1 => ['file', '/dev/null', 'w'], 44 | 2 => ['pipe', 'w'] 45 | ]; 46 | 47 | public const IGNORE_STDERR = [ 48 | 0 => ['pipe', 'r'], 49 | 1 => ['pipe', 'w'], 50 | 2 => ['file', '/dev/null', 'w'] 51 | ]; 52 | 53 | public const IGNORE_STDOUT_AND_STDERR = [ 54 | 0 => ['pipe', 'r'], 55 | 1 => ['file', '/dev/null', 'w'], 56 | 2 => ['file', '/dev/null', 'w'] 57 | ]; 58 | 59 | public const IGNORE_STDIN_AND_STDERR = [ 60 | 0 => ['file', '/dev/null', 'r'], 61 | 1 => ['pipe', 'w'], 62 | 2 => ['file', '/dev/null', 'w'] 63 | ]; 64 | 65 | public const IGNORE_STDIN_AND_STDOUT = [ 66 | 0 => ['file', '/dev/null', 'r'], 67 | 1 => ['file', '/dev/null', 'w'], 68 | 2 => ['pipe', 'w'] 69 | ]; 70 | 71 | } -------------------------------------------------------------------------------- /src/vennv/vapm/Error.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | final class Error 27 | { 28 | 29 | public const FAILED_IN_FETCHING_DATA = "Error in fetching data"; 30 | 31 | public const WRONG_TYPE_WHEN_USE_CURL_EXEC = "curl_exec() should return string|false when CURL-OPT_RETURN-TRANSFER is set"; 32 | 33 | public const UNABLE_START_THREAD = "Unable to start thread"; 34 | 35 | public const DEFERRED_CALLBACK_MUST_RETURN_GENERATOR = "Deferred callback must return a Generator"; 36 | 37 | public const UNABLE_TO_OPEN_FILE = "Error: Unable to open file!"; 38 | 39 | public const FILE_DOES_NOT_EXIST = "Error: File does not exist!"; 40 | 41 | public const FILE_ALREADY_EXISTS = "Error: File already exists!"; 42 | 43 | public const CANNOT_FIND_FUNCTION_KEYWORD = "Cannot find function or fn keyword in closure"; 44 | 45 | public const CANNOT_READ_FILE = "Cannot read file"; 46 | 47 | public const INPUT_MUST_BE_STRING_OR_CALLABLE = "Input must be string or callable"; 48 | 49 | public const ERROR_TO_CREATE_SOCKET = "Error to create socket"; 50 | 51 | public const PAYLOAD_TOO_LARGE = "Payload too large"; 52 | 53 | public const INVALID_ARRAY = "Invalid array"; 54 | 55 | public const ASYNC_AWAIT_MUST_CALL_IN_ASYNC_FUNCTION = "Async::await() must call in async function"; 56 | 57 | public const CHANNEL_IS_CLOSED = "Channel is closed"; 58 | 59 | } -------------------------------------------------------------------------------- /src/vennv/vapm/MicroTask.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Throwable; 27 | use Generator; 28 | use function microtime; 29 | 30 | final class MicroTask 31 | { 32 | 33 | /** 34 | * @var array 35 | */ 36 | private static array $tasks = []; 37 | 38 | public static function addTask(int $id, Promise $promise): void 39 | { 40 | self::$tasks[$id] = $promise; 41 | } 42 | 43 | public static function removeTask(int $id): void 44 | { 45 | unset(self::$tasks[$id]); 46 | } 47 | 48 | public static function getTask(int $id): ?Promise 49 | { 50 | return self::$tasks[$id] ?? null; 51 | } 52 | 53 | /** 54 | * @return Generator 55 | */ 56 | public static function getTasks(): Generator 57 | { 58 | foreach (self::$tasks as $id => $promise) yield $id => $promise; 59 | } 60 | 61 | public static function isPrepare(): bool 62 | { 63 | return !empty(self::$tasks); 64 | } 65 | 66 | /** 67 | * @throws Throwable 68 | */ 69 | public static function run(): void 70 | { 71 | $gc = new GarbageCollection(); 72 | foreach (self::getTasks() as $id => $promise) { 73 | /** @var Promise $promise */ 74 | $promise->useCallbacks(); 75 | $promise->setTimeEnd(microtime(true)); 76 | EventLoop::addReturn($promise); 77 | /** @var int $id */ 78 | self::removeTask($id); 79 | $gc->collectWL(); 80 | } 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /src/vennv/vapm/GarbageCollection.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use function gc_collect_cycles; 27 | 28 | interface GarbageCollectionInterface 29 | { 30 | /** 31 | * Set the limit of the loop to collect garbage 32 | * 33 | * @param int $limit 34 | * @return void 35 | */ 36 | public function setLimitLoop(int $limit): void; 37 | 38 | /** 39 | * Collect garbage with limit 40 | * 41 | * @return void 42 | */ 43 | public function collectWL(): void; 44 | 45 | /** 46 | * Collect garbage 47 | * 48 | * @return void 49 | */ 50 | public static function collect(): void; 51 | 52 | } 53 | 54 | final class GarbageCollection implements GarbageCollectionInterface 55 | { 56 | 57 | 58 | private int $countLoop = 0; 59 | 60 | /** 61 | * @param int $limitLoop The limit of the loop to collect garbage 62 | */ 63 | public function __construct(private int $limitLoop = 1000) 64 | { 65 | // TODO: Implement __construct() method. 66 | } 67 | 68 | public function setLimitLoop(int $limit): void 69 | { 70 | $this->limitLoop = $limit; 71 | } 72 | 73 | public function collectWL(): void 74 | { 75 | if ($this->countLoop >= $this->limitLoop) { 76 | gc_collect_cycles(); 77 | $this->countLoop = 0; 78 | } else { 79 | ++$this->countLoop; 80 | } 81 | } 82 | 83 | public static function collect(): void 84 | { 85 | gc_collect_cycles(); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/vennv/vapm/Async.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use vennv\vapm\utils\Utils; 27 | use Throwable; 28 | use RuntimeException; 29 | use function is_callable; 30 | 31 | interface AsyncInterface 32 | { 33 | 34 | public function getId(): int; 35 | 36 | /** 37 | * @throws Throwable 38 | */ 39 | public static function await(mixed $await): mixed; 40 | 41 | } 42 | 43 | final class Async implements AsyncInterface 44 | { 45 | 46 | private Promise $promise; 47 | 48 | /** 49 | * @throws Throwable 50 | */ 51 | public function __construct(callable $callback) 52 | { 53 | $promise = new Promise($callback, true); 54 | $this->promise = $promise; 55 | } 56 | 57 | public function getId(): int 58 | { 59 | return $this->promise->getId(); 60 | } 61 | 62 | /** 63 | * @throws Throwable 64 | */ 65 | public static function await(mixed $await): mixed 66 | { 67 | if (!$await instanceof Promise && !$await instanceof Async) { 68 | if (is_callable($await)) { 69 | $await = new Async($await); 70 | } else { 71 | if (!Utils::isClass(Async::class)) { 72 | throw new RuntimeException(Error::ASYNC_AWAIT_MUST_CALL_IN_ASYNC_FUNCTION); 73 | } 74 | return $await; 75 | } 76 | } 77 | 78 | do { 79 | $return = EventLoop::getReturn($await->getId()); 80 | if ($return === null) { 81 | FiberManager::wait(); 82 | } 83 | } while ($return === null); 84 | 85 | return $return->getResult(); 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /src/vennv/vapm/StatusThread.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use function microtime; 27 | 28 | interface StatusThreadInterface 29 | { 30 | 31 | /** 32 | * @return int|float 33 | * 34 | * This method is used to get the time sleeping. 35 | */ 36 | public function getTimeSleeping(): int|float; 37 | 38 | /** 39 | * @return int|float 40 | * 41 | * This method is used to get the sleep start time. 42 | */ 43 | public function getSleepStartTime(): int|float; 44 | 45 | /** 46 | * @param int|float $seconds 47 | * 48 | * This method is used to sleep the thread. 49 | */ 50 | public function sleep(int|float $seconds): void; 51 | 52 | /** 53 | * @return bool 54 | * 55 | * This method is used to check if the thread can wake up. 56 | */ 57 | public function canWakeUp(): bool; 58 | 59 | } 60 | 61 | final class StatusThread implements StatusThreadInterface 62 | { 63 | 64 | private int|float $timeSleeping = 0; 65 | 66 | private int|float $sleepStartTime; 67 | 68 | public function __construct() 69 | { 70 | $this->sleepStartTime = microtime(true); 71 | } 72 | 73 | public function getTimeSleeping(): int|float 74 | { 75 | return $this->timeSleeping; 76 | } 77 | 78 | public function getSleepStartTime(): int|float 79 | { 80 | return $this->sleepStartTime; 81 | } 82 | 83 | public function sleep(int|float $seconds): void 84 | { 85 | $this->timeSleeping += $seconds; 86 | } 87 | 88 | public function canWakeUp(): bool 89 | { 90 | return microtime(true) - $this->sleepStartTime >= $this->timeSleeping; 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /src/vennv/vapm/io/functions.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm\io; 25 | 26 | use vennv\vapm\Async; 27 | use vennv\vapm\Promise; 28 | use vennv\vapm\System; 29 | 30 | /** 31 | * This functions is used to handle the IO operations 32 | */ 33 | 34 | /** 35 | * @param callable $callback 36 | * @return Async 37 | * 38 | * This function is used to run asynchronous callbacks 39 | */ 40 | function async(callable $callback): Async 41 | { 42 | return new Async($callback); 43 | } 44 | 45 | /** 46 | * @param mixed $await 47 | * @return mixed 48 | * 49 | * This function is used to wait for a callback to be executed 50 | */ 51 | function await(mixed $await): mixed 52 | { 53 | return Async::await($await); 54 | } 55 | 56 | /** 57 | * @param int $milliseconds 58 | * @return Promise 59 | * 60 | * This function is used to delay the execution of a callback 61 | */ 62 | function delay(int $milliseconds): Promise 63 | { 64 | return new Promise(function($resolve) use ($milliseconds) { 65 | System::setTimeout(function() use ($resolve) { 66 | $resolve(); 67 | }, $milliseconds); 68 | }); 69 | } 70 | 71 | /** 72 | * @param callable $callback 73 | * @param int $milliseconds 74 | * @return void 75 | * 76 | * This function is used to set a timeout for a callback 77 | */ 78 | function setTimeout(callable $callback, int $milliseconds): void 79 | { 80 | System::setTimeout($callback, $milliseconds); 81 | } 82 | 83 | /** 84 | * @param callable $callback 85 | * @param int $milliseconds 86 | * @return void 87 | * 88 | * This function is used to set an interval for a callback 89 | */ 90 | function setInterval(callable $callback, int $milliseconds): void 91 | { 92 | System::setInterval($callback, $milliseconds); 93 | } -------------------------------------------------------------------------------- /src/vennv/vapm/ChildCoroutine.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | use Exception; 28 | 29 | interface ChildCoroutineInterface 30 | { 31 | 32 | /** 33 | * @param Exception $exception 34 | * @return void 35 | * 36 | * This function sets the exception. 37 | */ 38 | public function setException(Exception $exception): void; 39 | 40 | /** 41 | * @return ChildCoroutine 42 | * 43 | * This function runs the coroutine. 44 | */ 45 | 46 | public function run(): ChildCoroutine; 47 | 48 | /** 49 | * @return bool 50 | * 51 | * This function checks if the coroutine is finished. 52 | */ 53 | public function isFinished(): bool; 54 | 55 | /** 56 | * @return mixed 57 | * 58 | * This function returns the return value of the coroutine. 59 | */ 60 | public function getReturn(): mixed; 61 | 62 | } 63 | 64 | final class ChildCoroutine implements ChildCoroutineInterface 65 | { 66 | 67 | protected Generator $coroutine; 68 | 69 | protected Exception $exception; 70 | 71 | public function __construct(Generator $coroutine) 72 | { 73 | $this->coroutine = $coroutine; 74 | } 75 | 76 | public function setException(Exception $exception): void 77 | { 78 | $this->exception = $exception; 79 | } 80 | 81 | public function run(): ChildCoroutine 82 | { 83 | $this->coroutine->send($this->coroutine->current()); 84 | return $this; 85 | } 86 | 87 | public function isFinished(): bool 88 | { 89 | return !$this->coroutine->valid(); 90 | } 91 | 92 | public function getReturn(): mixed 93 | { 94 | return $this->coroutine->getReturn(); 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /src/vennv/vapm/MacroTask.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | use const PHP_INT_MAX; 28 | 29 | final class MacroTask 30 | { 31 | 32 | private static int $nextId = 0; 33 | 34 | /** 35 | * @var array 36 | */ 37 | private static array $tasks = []; 38 | 39 | public static function generateId(): int 40 | { 41 | if (self::$nextId >= PHP_INT_MAX) self::$nextId = 0; 42 | return self::$nextId++; 43 | } 44 | 45 | public static function addTask(SampleMacro $sampleMacro): void 46 | { 47 | self::$tasks[$sampleMacro->getId()] = $sampleMacro; 48 | } 49 | 50 | public static function removeTask(SampleMacro $sampleMacro): void 51 | { 52 | $id = $sampleMacro->getId(); 53 | if (isset(self::$tasks[$id])) unset(self::$tasks[$id]); 54 | } 55 | 56 | public static function getTask(int $id): ?SampleMacro 57 | { 58 | return self::$tasks[$id] ?? null; 59 | } 60 | 61 | /** 62 | * @return Generator 63 | */ 64 | public static function getTasks(): Generator 65 | { 66 | foreach (self::$tasks as $id => $task) yield $id => $task; 67 | } 68 | 69 | public static function isPrepare(): bool 70 | { 71 | return !empty(self::$tasks); 72 | } 73 | 74 | public static function run(): void 75 | { 76 | $gc = new GarbageCollection(); 77 | foreach (self::getTasks() as $task) { 78 | /** @var SampleMacro $task */ 79 | if ($task->checkTimeOut()) { 80 | $task->run(); 81 | !$task->isRepeat() ? self::removeTask($task) : $task->resetTimeOut(); 82 | } 83 | $gc->collectWL(); 84 | } 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /src/vennv/vapm/Mutex.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | 28 | /** 29 | * @author VennDev 30 | * @package vennv\vapm 31 | * 32 | * This class is used to create a mutex object that can be used to synchronize access to shared resources. 33 | * Note: this just for coroutine, if you want to use it in other places, you need to implement it yourself. 34 | */ 35 | interface MutexInterface 36 | { 37 | 38 | /** 39 | * @return bool 40 | * 41 | * This function returns the lock status. 42 | */ 43 | public function isLocked(): bool; 44 | 45 | /** 46 | * @return Generator 47 | * 48 | * This function locks the mutex. 49 | */ 50 | public function lock(): Generator; 51 | 52 | /** 53 | * @return Generator 54 | * 55 | * This function unlocks the mutex. 56 | */ 57 | public function unlock(): Generator; 58 | 59 | } 60 | 61 | final class Mutex implements MutexInterface 62 | { 63 | 64 | private bool $locked = false; 65 | 66 | /** 67 | * @return bool 68 | * 69 | * This function returns the lock status. 70 | */ 71 | public function isLocked(): bool 72 | { 73 | return $this->locked; 74 | } 75 | 76 | /** 77 | * @return Generator 78 | * 79 | * This function locks the mutex. 80 | */ 81 | public function lock(): Generator 82 | { 83 | while ($this->locked) { 84 | yield; 85 | } 86 | $this->locked = true; 87 | } 88 | 89 | /** 90 | * @return Generator 91 | * 92 | * This function unlocks the mutex. 93 | */ 94 | public function unlock(): Generator 95 | { 96 | yield $this->locked = false; 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /src/vennv/vapm/ct/functions.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm\ct; 25 | 26 | use Generator; 27 | use Closure; 28 | use vennv\vapm\CoroutineGen; 29 | use vennv\vapm\Deferred; 30 | use vennv\vapm\Channel; 31 | use vennv\vapm\AwaitGroup; 32 | use vennv\vapm\Mutex; 33 | 34 | /** 35 | * This file is used to create a coroutine with non-blocking mode 36 | */ 37 | 38 | /** 39 | * @param callable ...$callbacks 40 | * @return void 41 | * 42 | * This function is used to create a coroutine with non-blocking mode 43 | */ 44 | function c(callable ...$callbacks): void 45 | { 46 | CoroutineGen::runNonBlocking(...$callbacks); 47 | } 48 | 49 | /** 50 | * @param callable ...$callbacks 51 | * @return void 52 | * 53 | * This function is used to create a coroutine with blocking mode 54 | */ 55 | function cBlock(callable ...$callbacks): void 56 | { 57 | CoroutineGen::runBlocking(...$callbacks); 58 | } 59 | 60 | /** 61 | * @param int $milliseconds 62 | * @return Generator 63 | * 64 | * This function is used to delay the execution of a coroutine 65 | */ 66 | function cDelay(int $milliseconds): Generator 67 | { 68 | return CoroutineGen::delay($milliseconds); 69 | } 70 | 71 | /** 72 | * @param callable $callback 73 | * @param int $times 74 | * @return Closure 75 | * 76 | * This function is used to repeat the execution of a coroutine 77 | */ 78 | function cRepeat(callable $callback, int $times): Closure 79 | { 80 | return CoroutineGen::repeat($callback, $times); 81 | } 82 | 83 | /** 84 | * @return Channel 85 | * 86 | * This function is used to create a channel 87 | */ 88 | function channel(): Channel 89 | { 90 | return new Channel(); 91 | } 92 | 93 | /** 94 | * @return AwaitGroup 95 | * 96 | * This function is used to create a await group 97 | */ 98 | function awaitGroup(): AwaitGroup 99 | { 100 | return new AwaitGroup(); 101 | } 102 | 103 | /** 104 | * @return Mutex 105 | * 106 | * This function is used to create a mutex 107 | */ 108 | function mutex(): Mutex 109 | { 110 | return new Mutex(); 111 | } 112 | 113 | /** 114 | * @param callable $callback 115 | * @return Deferred 116 | * 117 | * This function is used to create a deferred 118 | */ 119 | function deferred(callable $callback): Deferred 120 | { 121 | return new Deferred($callback); 122 | } -------------------------------------------------------------------------------- /src/vennv/vapm/AwaitGroup.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | 28 | /** 29 | * @author VennDev 30 | * @package vennv\vapm 31 | * 32 | * This interface is used to create a await group object that can be used to wait for a group of coroutines to complete. 33 | */ 34 | interface AwaitGroupInterface 35 | { 36 | 37 | /** 38 | * @param int $count 39 | * @return void 40 | * 41 | * This function is used to add the count to the group 42 | */ 43 | public function add(int $count): void; 44 | 45 | /** 46 | * @return Generator 47 | * 48 | * This function is used to decrement the count 49 | */ 50 | public function done(): Generator; 51 | 52 | /** 53 | * @return bool 54 | * 55 | * This function is used to check if the count is zero 56 | */ 57 | public function isDone(): bool; 58 | 59 | /** 60 | * @return int 61 | * 62 | * This function is used to get the count 63 | */ 64 | public function getCount(): int; 65 | 66 | /** 67 | * @return void 68 | * 69 | * This function is used to reset the count 70 | */ 71 | public function reset(): void; 72 | 73 | /** 74 | * @return void 75 | * 76 | * This function is used to wait for the count to be zero 77 | */ 78 | public function wait(): void; 79 | 80 | } 81 | 82 | final class AwaitGroup implements AwaitGroupInterface 83 | { 84 | 85 | private int $count = 0; 86 | 87 | public function add(int $count): void 88 | { 89 | $this->count += $count; 90 | } 91 | 92 | public function done(): Generator 93 | { 94 | $this->count--; 95 | yield; 96 | } 97 | 98 | public function isDone(): bool 99 | { 100 | return $this->count === 0; 101 | } 102 | 103 | public function getCount(): int 104 | { 105 | return $this->count; 106 | } 107 | 108 | public function reset(): void 109 | { 110 | $this->count = 0; 111 | } 112 | 113 | public function wait(): void 114 | { 115 | $gc = new GarbageCollection(); 116 | while ($this->count > 0) { 117 | CoroutineGen::run(); 118 | $gc->collectWL(); 119 | } 120 | } 121 | 122 | public function __destruct() 123 | { 124 | $this->reset(); 125 | } 126 | 127 | } -------------------------------------------------------------------------------- /src/vennv/vapm/ClosureThread.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | use function call_user_func; 28 | use function is_array; 29 | use function is_bool; 30 | use function is_callable; 31 | use function is_null; 32 | use function is_object; 33 | use function json_encode; 34 | use function iterator_to_array; 35 | 36 | interface ClosureThreadInterface 37 | { 38 | 39 | /** 40 | * @return void 41 | * 42 | * This function runs the callback function for the thread. 43 | */ 44 | public function onRun(): void; 45 | 46 | } 47 | 48 | final class ClosureThread extends Thread implements ClosureThreadInterface 49 | { 50 | 51 | private mixed $callback; 52 | 53 | /** 54 | * @var array 55 | * @phpstan-var array 56 | */ 57 | private array $argsCallback = []; 58 | 59 | /** 60 | * @param callable $callback 61 | * @param array $args 62 | */ 63 | public function __construct(callable $callback, array $args = []) 64 | { 65 | $this->callback = $callback; 66 | $this->argsCallback = $args; 67 | parent::__construct($callback, $args); 68 | } 69 | 70 | public function onRun(): void 71 | { 72 | if (is_callable($this->callback)) { 73 | $callback = call_user_func($this->callback, ...$this->argsCallback); 74 | if ($callback instanceof Generator) { 75 | $callback = function () use ($callback): Generator { 76 | yield from $callback; 77 | }; 78 | $callback = call_user_func($callback, ...$this->argsCallback); 79 | } 80 | if (is_array($callback)) { 81 | $callback = json_encode($callback); 82 | } elseif (is_object($callback) && !$callback instanceof Generator) { 83 | $callback = json_encode($callback); 84 | } elseif (is_bool($callback)) { 85 | $callback = $callback ? 'true' : 'false'; 86 | } elseif (is_null($callback)) { 87 | $callback = 'null'; 88 | } elseif ($callback instanceof Generator) { 89 | $callback = json_encode(iterator_to_array($callback)); 90 | } else { 91 | $callback = (string)$callback; 92 | } 93 | if (is_bool($callback)) $callback = (string)$callback; 94 | self::post($callback); 95 | } 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /src/vennv/vapm/SampleMacro.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use vennv\vapm\utils\Utils; 27 | use function call_user_func; 28 | use function microtime; 29 | 30 | interface SampleMacroInterface 31 | { 32 | 33 | public function isRepeat(): bool; 34 | 35 | public function getTimeOut(): float; 36 | 37 | public function getTimeStart(): float; 38 | 39 | public function getCallback(): callable; 40 | 41 | public function getId(): int; 42 | 43 | public function checkTimeOut(): bool; 44 | 45 | public function resetTimeOut(): void; 46 | 47 | public function isRunning(): bool; 48 | 49 | public function run(): void; 50 | 51 | public function stop(): void; 52 | 53 | } 54 | 55 | final class SampleMacro implements SampleMacroInterface 56 | { 57 | 58 | private float $timeOut; 59 | 60 | private float $timeStart; 61 | 62 | private bool $isRepeat; 63 | 64 | /** @var callable $callback */ 65 | private mixed $callback; 66 | 67 | private int $id; 68 | 69 | public function __construct(callable $callback, int $timeOut = 0, bool $isRepeat = false) 70 | { 71 | $this->id = MacroTask::generateId(); 72 | $this->timeOut = Utils::milliSecsToSecs($timeOut); 73 | $this->isRepeat = $isRepeat; 74 | $this->timeStart = microtime(true); 75 | $this->callback = $callback; 76 | } 77 | 78 | public function isRepeat(): bool 79 | { 80 | return $this->isRepeat; 81 | } 82 | 83 | public function getTimeOut(): float 84 | { 85 | return $this->timeOut; 86 | } 87 | 88 | public function getTimeStart(): float 89 | { 90 | return $this->timeStart; 91 | } 92 | 93 | public function getCallback(): callable 94 | { 95 | return $this->callback; 96 | } 97 | 98 | public function getId(): int 99 | { 100 | return $this->id; 101 | } 102 | 103 | public function checkTimeOut(): bool 104 | { 105 | return microtime(true) - $this->timeStart >= $this->timeOut; 106 | } 107 | 108 | public function resetTimeOut(): void 109 | { 110 | $this->timeStart = microtime(true); 111 | } 112 | 113 | public function isRunning(): bool 114 | { 115 | return MacroTask::getTask($this->id) !== null; 116 | } 117 | 118 | public function run(): void 119 | { 120 | call_user_func($this->callback); 121 | } 122 | 123 | public function stop(): void 124 | { 125 | MacroTask::removeTask($this); 126 | } 127 | 128 | } -------------------------------------------------------------------------------- /src/vennv/vapm/Channel.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | use RuntimeException; 28 | use function array_shift; 29 | 30 | interface ChannelInterface 31 | { 32 | 33 | /** 34 | * @param mixed $message 35 | * @return Generator 36 | * 37 | * This function is used to send a message to the channel. 38 | */ 39 | public function sendGen($message): Generator; 40 | 41 | /** 42 | * @param mixed $message 43 | * @return void 44 | * 45 | * This function is used to send a message to the channel. 46 | */ 47 | public function send($message): void; 48 | 49 | /** 50 | * @param callable $callback 51 | * @return Generator 52 | * 53 | * This function is used to receive a message from the channel. 54 | */ 55 | public function receiveGen(callable $callback): Generator; 56 | 57 | /** 58 | * @param callable $callback 59 | * @return void 60 | * 61 | * This function is used to receive a message from the channel. 62 | */ 63 | public function receive(callable $callback): void; 64 | 65 | /** 66 | * @return bool 67 | * 68 | * This function is used to check if the channel is empty. 69 | */ 70 | public function isEmpty(): bool; 71 | 72 | /** 73 | * @return void 74 | * 75 | * This function is used to close the channel. 76 | */ 77 | public function close(): void; 78 | 79 | /** 80 | * @return bool 81 | * 82 | * This function is used to check if the channel is closed. 83 | */ 84 | public function isClosed(): bool; 85 | 86 | } 87 | 88 | final class Channel implements ChannelInterface 89 | { 90 | 91 | /** 92 | * @var mixed[] 93 | */ 94 | private array $queue = []; 95 | 96 | private bool $locked = false; 97 | 98 | private bool $closed = false; 99 | 100 | public function sendGen($message): Generator 101 | { 102 | $this->exceptionIfClosed(); 103 | while ($this->locked) yield; 104 | $this->locked = true; 105 | $this->queue[] = $message; 106 | $this->locked = false; 107 | } 108 | 109 | public function send($message): void 110 | { 111 | $this->exceptionIfClosed(); 112 | while ($this->locked) { 113 | CoroutineGen::run(); 114 | } 115 | $this->locked = true; 116 | $this->queue[] = $message; 117 | $this->locked = false; 118 | } 119 | 120 | public function receiveGen(callable $callback): Generator 121 | { 122 | while (!$this->closed || !empty($this->queue)) { 123 | $message = array_shift($this->queue); 124 | if ($message !== null) $callback($message); 125 | yield; 126 | } 127 | } 128 | 129 | public function receive(callable $callback): void 130 | { 131 | while (!$this->closed || !empty($this->queue)) { 132 | $message = array_shift($this->queue); 133 | if ($message !== null) $callback($message); 134 | CoroutineGen::run(); 135 | } 136 | } 137 | 138 | public function isEmpty(): bool 139 | { 140 | return empty($this->queue); 141 | } 142 | 143 | public function close(): void 144 | { 145 | $this->closed = true; 146 | } 147 | 148 | public function isClosed(): bool 149 | { 150 | return $this->closed; 151 | } 152 | 153 | private function exceptionIfClosed(): void 154 | { 155 | if ($this->closed) throw new RuntimeException('Channel is closed'); 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/vennv/vapm/Work.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | use SplQueue; 28 | 29 | interface WorkInterface 30 | { 31 | 32 | /** 33 | * @param callable $work 34 | * @param array $args 35 | * @return void 36 | * 37 | * The work is a function that will be executed when the work is run. 38 | */ 39 | public function add(callable $work, array $args = []): void; 40 | 41 | /** 42 | * @param int $index 43 | * @return void 44 | * 45 | * Remove the work from the work list. 46 | */ 47 | public function remove(int $index): void; 48 | 49 | /** 50 | * @return void 51 | * 52 | * Remove all works from the work list. 53 | */ 54 | public function clear(): void; 55 | 56 | /** 57 | * @return int 58 | * 59 | * Get the number of works in the work list. 60 | */ 61 | public function count(): int; 62 | 63 | /** 64 | * @return bool 65 | * 66 | * Check if the work list is empty. 67 | */ 68 | public function isEmpty(): bool; 69 | 70 | /** 71 | * @return mixed 72 | * 73 | * Get the first work in the work list. 74 | */ 75 | public function dequeue(): mixed; 76 | 77 | /** 78 | * @param int $number 79 | * @return Generator 80 | * 81 | * Get the work list by number. 82 | */ 83 | public function getArrayByNumber(int $number): Generator; 84 | 85 | /** 86 | * @return Generator 87 | * 88 | * Get all works in the work list. 89 | */ 90 | public function getAll(): Generator; 91 | 92 | /** 93 | * @return void 94 | * 95 | * Run all works in the work list. 96 | */ 97 | public function run(): void; 98 | 99 | } 100 | 101 | final class Work implements WorkInterface 102 | { 103 | 104 | private SplQueue $queue; 105 | 106 | public function __construct() 107 | { 108 | $this->queue = new SplQueue(); 109 | } 110 | 111 | /** 112 | * @param callable $work 113 | * @param array $args 114 | * @return void 115 | * 116 | * Add a work to the work list. 117 | */ 118 | public function add(callable $work, array $args = []): void 119 | { 120 | $this->queue->enqueue(new ClosureThread($work, $args)); 121 | } 122 | 123 | public function remove(int $index): void 124 | { 125 | $this->queue->offsetUnset($index); 126 | } 127 | 128 | public function clear(): void 129 | { 130 | $this->queue = new SplQueue(); 131 | } 132 | 133 | public function count(): int 134 | { 135 | return $this->queue->count(); 136 | } 137 | 138 | public function isEmpty(): bool 139 | { 140 | return $this->queue->isEmpty(); 141 | } 142 | 143 | public function dequeue(): mixed 144 | { 145 | return $this->queue->dequeue(); 146 | } 147 | 148 | public function getArrayByNumber(int $number): Generator 149 | { 150 | for ($i = 0; $i < $number; $i++) yield $this->queue->dequeue(); 151 | } 152 | 153 | public function getAll(): Generator 154 | { 155 | while (!$this->queue->isEmpty()) yield $this->queue->dequeue(); 156 | } 157 | 158 | public function run(): void 159 | { 160 | $gc = new GarbageCollection(); 161 | while (!$this->queue->isEmpty()) { 162 | /** @var ClosureThread $work */ 163 | $work = $this->queue->dequeue(); 164 | $work->start(); 165 | $gc->collectWL(); 166 | } 167 | } 168 | 169 | } -------------------------------------------------------------------------------- /src/vennv/vapm/Deferred.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | 28 | /** 29 | * @author VennDev 30 | * @package vennv\vapm 31 | * 32 | * This interface is used to create a deferred object that can be used to get the result of a coroutine. 33 | */ 34 | interface DeferredInterface 35 | { 36 | 37 | /** 38 | * This method is used to get the result of the deferred. 39 | */ 40 | public function await(): Generator; 41 | 42 | /** 43 | * @param DeferredInterface ...$deferreds 44 | * @return Generator 45 | * 46 | * This method is used to get all the results of the deferred. 47 | */ 48 | public static function awaitAll(DeferredInterface ...$deferreds): Generator; 49 | 50 | /** 51 | * @param DeferredInterface ...$deferreds 52 | * @return Generator 53 | * 54 | * This method is used to get the first result of the deferred. 55 | */ 56 | public static function awaitAny(DeferredInterface ...$deferreds): Generator; 57 | 58 | /** 59 | * This method is used to check if the deferred is finished. 60 | */ 61 | public function isFinished(): bool; 62 | 63 | /** 64 | * This method is used to get the child coroutine of the deferred. 65 | */ 66 | public function getChildCoroutine(): ChildCoroutine; 67 | 68 | /** 69 | * This method is used to get the result of the deferred. 70 | */ 71 | public function getComplete(): mixed; 72 | 73 | } 74 | 75 | final class Deferred implements DeferredInterface 76 | { 77 | 78 | protected mixed $return = null; 79 | 80 | protected ChildCoroutine $childCoroutine; 81 | 82 | public function __construct(callable $callback) 83 | { 84 | $generator = call_user_func($callback); 85 | $generator instanceof Generator ? $this->childCoroutine = new ChildCoroutine($generator) : throw new DeferredException(Error::DEFERRED_CALLBACK_MUST_RETURN_GENERATOR); 86 | } 87 | 88 | public function await(): Generator 89 | { 90 | while (!$this->childCoroutine->isFinished()) { 91 | $this->childCoroutine->run(); 92 | yield; 93 | } 94 | 95 | $this->return = $this->childCoroutine->getReturn(); 96 | 97 | return $this->return; 98 | } 99 | 100 | public static function awaitAll(DeferredInterface ...$deferreds): Generator 101 | { 102 | $result = []; 103 | 104 | while (count($result) <= count($deferreds)) { 105 | foreach ($deferreds as $index => $deferred) { 106 | $childCoroutine = $deferred->getChildCoroutine(); 107 | 108 | if ($childCoroutine->isFinished()) { 109 | $result[] = $childCoroutine->getReturn(); 110 | unset($deferreds[$index]); 111 | } else { 112 | $childCoroutine->run(); 113 | } 114 | yield; 115 | } 116 | 117 | yield; 118 | } 119 | 120 | return $result; 121 | } 122 | 123 | public static function awaitAny(DeferredInterface ...$deferreds): Generator 124 | { 125 | $result = []; 126 | 127 | while (count($result) <= count($deferreds)) { 128 | foreach ($deferreds as $deferred) { 129 | $childCoroutine = $deferred->getChildCoroutine(); 130 | 131 | if ($childCoroutine->isFinished()) { 132 | $result[] = $childCoroutine->getReturn(); 133 | $deferreds = []; 134 | break; 135 | } else { 136 | $childCoroutine->run(); 137 | } 138 | yield; 139 | } 140 | 141 | yield; 142 | } 143 | 144 | return $result; 145 | } 146 | 147 | public function isFinished(): bool 148 | { 149 | return $this->childCoroutine->isFinished(); 150 | } 151 | 152 | public function getChildCoroutine(): ChildCoroutine 153 | { 154 | return $this->childCoroutine; 155 | } 156 | 157 | public function getComplete(): mixed 158 | { 159 | return $this->return; 160 | } 161 | 162 | } -------------------------------------------------------------------------------- /src/vennv/vapm/CoroutineGen.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Closure; 27 | use ReflectionException; 28 | use SplQueue; 29 | use Generator; 30 | use Throwable; 31 | 32 | interface CoroutineGenInterface 33 | { 34 | 35 | /** 36 | * @return SplQueue|null 37 | * 38 | * This function returns the task queue. 39 | */ 40 | public static function getTaskQueue(): ?SplQueue; 41 | 42 | /** 43 | * @param mixed ...$coroutines 44 | * @return void 45 | * 46 | * This is a blocking function that runs all the coroutines passed to it. 47 | */ 48 | public static function runNonBlocking(mixed ...$coroutines): void; 49 | 50 | /** 51 | * @param mixed ...$coroutines 52 | * @return void 53 | * 54 | * This is a blocking function that runs all the coroutines passed to it. 55 | */ 56 | public static function runBlocking(mixed ...$coroutines): void; 57 | 58 | /** 59 | * @param callable $callback 60 | * @param int $times 61 | * @return Closure 62 | * 63 | * This is a generator that runs a callback function a specified amount of times. 64 | */ 65 | public static function repeat(callable $callback, int $times): Closure; 66 | 67 | /** 68 | * @param int $milliseconds 69 | * @return Generator 70 | * 71 | * This is a generator that yields for a specified amount of milliseconds. 72 | */ 73 | public static function delay(int $milliseconds): Generator; 74 | 75 | /** 76 | * @return void 77 | * 78 | * This function runs the task queue. 79 | */ 80 | public static function run(): void; 81 | 82 | } 83 | 84 | final class CoroutineGen implements CoroutineGenInterface 85 | { 86 | 87 | private static ?SplQueue $taskQueue = null; 88 | 89 | public static function getTaskQueue(): ?SplQueue 90 | { 91 | return self::$taskQueue; 92 | } 93 | 94 | /** 95 | * @param mixed ...$coroutines 96 | * @return void 97 | * @throws Throwable 98 | */ 99 | public static function runNonBlocking(mixed ...$coroutines): void 100 | { 101 | System::init(); 102 | self::$taskQueue ??= new SplQueue(); 103 | foreach ($coroutines as $coroutine) { 104 | $result = is_callable($coroutine) ? $coroutine() : $coroutine; 105 | $result instanceof Generator 106 | ? self::schedule(new ChildCoroutine($result)) 107 | : $result; 108 | } 109 | self::run(); 110 | } 111 | 112 | /** 113 | * @param mixed ...$coroutines 114 | * @return void 115 | * @throws Throwable 116 | */ 117 | public static function runBlocking(mixed ...$coroutines): void 118 | { 119 | self::runNonBlocking(...$coroutines); 120 | $gc = new GarbageCollection(); 121 | while (!self::$taskQueue?->isEmpty()) { 122 | self::run(); 123 | $gc->collectWL(); 124 | } 125 | } 126 | 127 | /** 128 | * @param mixed ...$coroutines 129 | * @return Closure 130 | */ 131 | private static function processCoroutine(mixed ...$coroutines): Closure 132 | { 133 | return function () use ($coroutines): void { 134 | foreach ($coroutines as $coroutine) { 135 | $result = is_callable($coroutine) ? $coroutine() : $coroutine; 136 | $result instanceof Generator 137 | ? self::schedule(new ChildCoroutine($result)) 138 | : $result; 139 | } 140 | self::run(); 141 | }; 142 | } 143 | 144 | public static function repeat(callable $callback, int $times): Closure 145 | { 146 | $gc = new GarbageCollection(); 147 | for ($i = 0; $i < $times; $i++) { 148 | $result = $callback(); 149 | if ($result instanceof Generator) { 150 | $callback = self::processCoroutine($result); 151 | } 152 | $gc->collectWL(); 153 | } 154 | return fn() => null; 155 | } 156 | 157 | public static function delay(int $milliseconds): Generator 158 | { 159 | for ($i = 0; $i < GeneratorManager::calculateSeconds($milliseconds); $i++) yield; 160 | } 161 | 162 | private static function schedule(ChildCoroutine $childCoroutine): void 163 | { 164 | self::$taskQueue?->enqueue($childCoroutine); 165 | } 166 | 167 | /** 168 | * @throws ReflectionException 169 | * @throws Throwable 170 | */ 171 | public static function run(): void 172 | { 173 | if (!self::$taskQueue?->isEmpty()) { 174 | $coroutine = self::$taskQueue?->dequeue(); 175 | if ($coroutine instanceof ChildCoroutine && !$coroutine->isFinished()) { 176 | self::schedule($coroutine->run()); 177 | } 178 | } 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /src/vennv/vapm/EventLoop.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use SplQueue; 27 | use Generator; 28 | use Throwable; 29 | use const PHP_INT_MAX; 30 | 31 | interface EventLoopInterface 32 | { 33 | 34 | public static function init(): void; 35 | 36 | public static function generateId(): int; 37 | 38 | public static function addQueue(Promise $promise): void; 39 | 40 | public static function getQueue(int $id): ?Promise; 41 | 42 | public static function addReturn(Promise $promise): void; 43 | 44 | public static function removeReturn(int $id): void; 45 | 46 | public static function isReturn(int $id): bool; 47 | 48 | public static function getReturn(int $id): ?Promise; 49 | 50 | /** 51 | * @return Generator 52 | */ 53 | public static function getReturns(): Generator; 54 | 55 | } 56 | 57 | class EventLoop implements EventLoopInterface 58 | { 59 | 60 | protected const LIMIT = 20; // 20 times run 61 | 62 | protected static int $nextId = 0; 63 | 64 | /** 65 | * @var SplQueue 66 | */ 67 | protected static SplQueue $queues; 68 | 69 | /** 70 | * @var array 71 | */ 72 | protected static array $returns = []; 73 | 74 | public static function init(): void 75 | { 76 | self::$queues ??= new SplQueue(); 77 | } 78 | 79 | public static function generateId(): int 80 | { 81 | if (self::$nextId >= PHP_INT_MAX) self::$nextId = 0; 82 | return self::$nextId++; 83 | } 84 | 85 | public static function addQueue(Promise $promise): void 86 | { 87 | self::$queues->enqueue($promise); 88 | } 89 | 90 | public static function getQueue(int $id): ?Promise 91 | { 92 | while (!self::$queues->isEmpty()) { 93 | /** 94 | * @var Promise $promise 95 | */ 96 | $promise = self::$queues->dequeue(); 97 | if ($promise->getId() === $id) return $promise; 98 | self::$queues->enqueue($promise); 99 | } 100 | return null; 101 | } 102 | 103 | public static function addReturn(Promise $promise): void 104 | { 105 | if (!isset(self::$returns[$promise->getId()])) self::$returns[$promise->getId()] = $promise; 106 | } 107 | 108 | public static function isReturn(int $id): bool 109 | { 110 | return isset(self::$returns[$id]); 111 | } 112 | 113 | public static function removeReturn(int $id): void 114 | { 115 | if (self::isReturn($id)) unset(self::$returns[$id]); 116 | } 117 | 118 | public static function getReturn(int $id): ?Promise 119 | { 120 | return self::$returns[$id] ?? null; 121 | } 122 | 123 | /** 124 | * @return Generator 125 | */ 126 | public static function getReturns(): Generator 127 | { 128 | foreach (self::$returns as $id => $promise) yield $id => $promise; 129 | } 130 | 131 | /** 132 | * @throws Throwable 133 | */ 134 | private static function clearGarbage(): void 135 | { 136 | $gc = new GarbageCollection(); 137 | foreach (self::getReturns() as $id => $promise) { 138 | if ($promise instanceof Promise && $promise->canDrop()) unset(self::$returns[$id]); 139 | $gc->collectWL(); 140 | } 141 | } 142 | 143 | /** 144 | * @throws Throwable 145 | */ 146 | protected static function run(): void 147 | { 148 | CoroutineGen::run(); 149 | 150 | $i = 0; 151 | while (!self::$queues->isEmpty() && $i++ < self::LIMIT) { 152 | /** @var Promise $promise */ 153 | $promise = self::$queues->dequeue(); 154 | $fiber = $promise->getFiber(); 155 | if ($fiber->isSuspended()) $fiber->resume(); 156 | if ( 157 | $fiber->isTerminated() && 158 | ($promise->getStatus() !== StatusPromise::PENDING || $promise->isJustGetResult()) 159 | ) { 160 | try { 161 | $promise->isJustGetResult() && $promise->setResult($fiber->getReturn()); 162 | } catch (Throwable $e) { 163 | echo $e->getMessage(); 164 | } 165 | MicroTask::addTask($promise->getId(), $promise); 166 | } else { 167 | self::$queues->enqueue($promise); 168 | } 169 | } 170 | 171 | MicroTask::isPrepare() && MicroTask::run(); 172 | MacroTask::isPrepare() && MacroTask::run(); 173 | 174 | self::clearGarbage(); 175 | } 176 | 177 | /** 178 | * @throws Throwable 179 | */ 180 | protected static function runSingle(): void 181 | { 182 | $gc = new GarbageCollection(); 183 | while ( 184 | !self::$queues->isEmpty() || 185 | (CoroutineGen::getTaskQueue() !== null && !CoroutineGen::getTaskQueue()->isEmpty()) || 186 | MicroTask::isPrepare() || MacroTask::isPrepare() 187 | ) { 188 | self::run(); 189 | $gc->collectWL(); 190 | } 191 | } 192 | 193 | } 194 | -------------------------------------------------------------------------------- /src/vennv/vapm/PHPUtils.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Throwable; 27 | 28 | interface PHPUtilsInterface 29 | { 30 | 31 | /** 32 | * @param array $array 33 | * @param callable $callback 34 | * @return Async 35 | * 36 | * @phpstan-param array $array 37 | * @throws Throwable 38 | * 39 | * This function is used to iterate over an array and call a callback function for each element. 40 | */ 41 | public static function forEach(array $array, callable $callback): Async; 42 | 43 | /** 44 | * @param array $array 45 | * @param callable $callback 46 | * @return Async 47 | * 48 | * @phpstan-param array $array 49 | * @throws Throwable 50 | * 51 | * This function is used to map over an array and apply a callback function to each element. 52 | */ 53 | public static function arrayMap(array $array, callable $callback): Async; 54 | 55 | /** 56 | * @param array $array 57 | * @param callable $callback 58 | * @return Async 59 | * 60 | * @phpstan-param array $array 61 | * @throws Throwable 62 | */ 63 | public static function arrayFilter(array $array, callable $callback): Async; 64 | 65 | /** 66 | * @param array $array 67 | * @param callable $callback 68 | * @param mixed $initialValue 69 | * @return Async 70 | * 71 | * @throws Throwable 72 | * 73 | * This function is used to reduce an array to a single value by applying a callback function to each element. 74 | */ 75 | public static function arrayReduce(array $array, callable $callback, mixed $initialValue): Async; 76 | 77 | /** 78 | * @param array $array 79 | * @param string $className 80 | * @return Async 81 | * 82 | * @throws Throwable 83 | * 84 | * This function is used to check if all elements in an array are instances of a specific class. 85 | */ 86 | public static function instanceOfAll(array $array, string $className): Async; 87 | 88 | /** 89 | * @param array $array 90 | * @param string $className 91 | * @return Async 92 | * 93 | * @throws Throwable 94 | * 95 | * This function is used to check if any element in an array is an instance of a specific class. 96 | */ 97 | public static function instanceOfAny(array $array, string $className): Async; 98 | 99 | } 100 | 101 | final class PHPUtils implements PHPUtilsInterface 102 | { 103 | 104 | public static function forEach(array $array, callable $callback): Async 105 | { 106 | return new Async(function () use ($array, $callback) { 107 | foreach ($array as $key => $value) { 108 | $callback($key, $value); 109 | FiberManager::wait(); 110 | } 111 | }); 112 | } 113 | 114 | public static function arrayMap(array $array, callable $callback): Async 115 | { 116 | return new Async(function () use ($array, $callback) { 117 | $result = []; 118 | foreach ($array as $key => $value) { 119 | $result[$key] = $callback($key, $value); 120 | FiberManager::wait(); 121 | } 122 | return $result; 123 | }); 124 | } 125 | 126 | public static function arrayFilter(array $array, callable $callback): Async 127 | { 128 | return new Async(function () use ($array, $callback) { 129 | $result = []; 130 | foreach ($array as $key => $value) { 131 | if ($callback($key, $value)) { 132 | $result[$key] = $value; 133 | } 134 | FiberManager::wait(); 135 | } 136 | return $result; 137 | }); 138 | } 139 | 140 | public static function arrayReduce(array $array, callable $callback, mixed $initialValue): Async 141 | { 142 | return new Async(function () use ($array, $callback, $initialValue) { 143 | $accumulator = $initialValue; 144 | foreach ($array as $key => $value) { 145 | $accumulator = $callback($accumulator, $value, $key); 146 | FiberManager::wait(); 147 | } 148 | return $accumulator; 149 | }); 150 | } 151 | 152 | public static function instanceOfAll(array $array, string $className): Async 153 | { 154 | return new Async(function () use ($array, $className) { 155 | foreach ($array as $value) { 156 | if (!($value instanceof $className)) return false; 157 | FiberManager::wait(); 158 | } 159 | return true; 160 | }); 161 | } 162 | 163 | public static function instanceOfAny(array $array, string $className): Async 164 | { 165 | return new Async(function () use ($array, $className) { 166 | foreach ($array as $value) { 167 | if ($value instanceof $className) return true; 168 | FiberManager::wait(); 169 | } 170 | return false; 171 | }); 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/vennv/vapm/Internet.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Closure; 27 | use CurlHandle; 28 | use function array_merge; 29 | use function curl_close; 30 | use function curl_error; 31 | use function curl_exec; 32 | use function curl_getinfo; 33 | use function curl_init; 34 | use function curl_setopt_array; 35 | use function explode; 36 | use function is_string; 37 | use function strtolower; 38 | use function substr; 39 | use function trim; 40 | use const CURLINFO_HEADER_SIZE; 41 | use const CURLINFO_HTTP_CODE; 42 | use const CURLOPT_AUTOREFERER; 43 | use const CURLOPT_CONNECTTIMEOUT_MS; 44 | use const CURLOPT_FOLLOWLOCATION; 45 | use const CURLOPT_FORBID_REUSE; 46 | use const CURLOPT_FRESH_CONNECT; 47 | use const CURLOPT_HEADER; 48 | use const CURLOPT_HTTPHEADER; 49 | use const CURLOPT_POST; 50 | use const CURLOPT_POSTFIELDS; 51 | use const CURLOPT_RETURNTRANSFER; 52 | use const CURLOPT_SSL_VERIFYHOST; 53 | use const CURLOPT_SSL_VERIFYPEER; 54 | use const CURLOPT_TIMEOUT_MS; 55 | 56 | final class Internet 57 | { 58 | 59 | /** 60 | * GETs a URL using cURL 61 | * NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread. 62 | * 63 | * @param int $timeout default 10 64 | * @param string[] $extraHeaders 65 | * @param string|null $error reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occurred during the operation. 66 | */ 67 | public static function getURL( 68 | string $page, 69 | int $timeout = 10, 70 | array $extraHeaders = [], 71 | string &$error = null 72 | ): ?InternetRequestResult 73 | { 74 | try { 75 | return self::simpleCurl( 76 | $page, 77 | $timeout, 78 | $extraHeaders 79 | ); 80 | } catch (InternetException $exception) { 81 | $error = $exception->getMessage(); 82 | return null; 83 | } 84 | } 85 | 86 | /** 87 | * POSTs data to a URL 88 | * NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread. 89 | * 90 | * @param string[]|string $args 91 | * @param string[] $extraHeaders 92 | * @param string|null $error reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occurred during the operation. 93 | */ 94 | public static function postURL( 95 | string $page, 96 | array|string $args, 97 | int $timeout = 10, 98 | array $extraHeaders = [], 99 | string &$error = null 100 | ): ?InternetRequestResult 101 | { 102 | try { 103 | return self::simpleCurl($page, $timeout, $extraHeaders, [ 104 | CURLOPT_POST => 1, 105 | CURLOPT_POSTFIELDS => $args 106 | ]); 107 | } catch (InternetException $ex) { 108 | $error = $ex->getMessage(); 109 | return null; 110 | } 111 | } 112 | 113 | /** 114 | * General cURL shorthand function. 115 | * NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread. 116 | * 117 | * @param float $timeout The maximum connect timeout and timeout in seconds, correct to ms. 118 | * @param string[] $extraHeaders extra headers to send as a plain string array 119 | * @param array $extraOpts extra CURL-OPT_* to set as an [opt => value] map 120 | * @param Closure|null $onSuccess function to be called if there is no error. Accepts a resource argument as the cURL handle. 121 | * @phpstan-param array $extraOpts 122 | * @phpstan-param list $extraHeaders 123 | * @phpstan-param (Closure(CurlHandle) : void)|null $onSuccess 124 | * 125 | * @throws InternetException if a cURL error occurs 126 | */ 127 | public static function simpleCurl( 128 | string $page, 129 | float $timeout = 10, 130 | array $extraHeaders = [], 131 | array $extraOpts = [], 132 | ?Closure $onSuccess = null 133 | ): InternetRequestResult 134 | { 135 | 136 | $time = (int)($timeout * 1000); 137 | 138 | $curlHandle = curl_init($page); 139 | 140 | if ($curlHandle === false) throw new InternetException("Unable to create new cURL session"); 141 | 142 | curl_setopt_array($curlHandle, $extraOpts + 143 | [ 144 | CURLOPT_SSL_VERIFYPEER => false, 145 | CURLOPT_SSL_VERIFYHOST => 2, 146 | CURLOPT_FORBID_REUSE => 1, 147 | CURLOPT_FRESH_CONNECT => 1, 148 | CURLOPT_AUTOREFERER => true, 149 | CURLOPT_FOLLOWLOCATION => true, 150 | CURLOPT_RETURNTRANSFER => true, 151 | CURLOPT_CONNECTTIMEOUT_MS => $time, 152 | CURLOPT_TIMEOUT_MS => $time, 153 | CURLOPT_HTTPHEADER => array_merge( 154 | ["User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0"], 155 | $extraHeaders 156 | ), 157 | CURLOPT_HEADER => true 158 | ]); 159 | 160 | try { 161 | $raw = curl_exec($curlHandle); 162 | 163 | if ($raw === false) throw new InternetException(curl_error($curlHandle)); 164 | if (!is_string($raw)) throw new AssumptionFailedError(Error::WRONG_TYPE_WHEN_USE_CURL_EXEC); 165 | 166 | $httpCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE); 167 | $headerSize = curl_getinfo($curlHandle, CURLINFO_HEADER_SIZE); 168 | $rawHeaders = substr($raw, 0, $headerSize); 169 | $body = substr($raw, $headerSize); 170 | $headers = []; 171 | 172 | foreach (explode("\r\n\r\n", $rawHeaders) as $rawHeaderGroup) { 173 | $headerGroup = []; 174 | foreach (explode("\r\n", $rawHeaderGroup) as $line) { 175 | $nameValue = explode(":", $line, 2); 176 | if (isset($nameValue[1])) $headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]); 177 | } 178 | 179 | $headers[] = $headerGroup; 180 | } 181 | 182 | if (!is_null($onSuccess)) $onSuccess($curlHandle); 183 | 184 | return new InternetRequestResult($headers, $body, $httpCode); 185 | } finally { 186 | curl_close($curlHandle); 187 | } 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /src/vennv/vapm/Worker.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Throwable; 27 | 28 | /** 29 | * Class Worker 30 | * @package vennv\vapm 31 | * 32 | * This class is used to create a worker to run the work. 33 | * All asynchronous methods are based on this class. 34 | */ 35 | interface WorkerInterface 36 | { 37 | 38 | /** 39 | * @return bool 40 | * 41 | * Check the worker is started. 42 | */ 43 | public function isStarted(): bool; 44 | 45 | /** 46 | * @return Work 47 | * 48 | * Get the work. 49 | */ 50 | public function getWork(): Work; 51 | 52 | /** 53 | * @return void 54 | * 55 | * This is method help you to remove the worker from the worker list. 56 | * You should call this method when the work is done to avoid memory leaks. 57 | */ 58 | public function done(): void; 59 | 60 | /** 61 | * @param mixed $result 62 | * @return void 63 | * 64 | * Collect the result of the work. 65 | */ 66 | public function collect(mixed $result): void; 67 | 68 | /** 69 | * @return array 70 | * 71 | * Get the result of the work. 72 | */ 73 | public function get(): array; 74 | 75 | /** 76 | * @return bool 77 | * 78 | * Check the worker is locked. 79 | */ 80 | public function isLocked(): bool; 81 | 82 | /** 83 | * @return void 84 | * 85 | * Lock the worker. 86 | */ 87 | public function lock(): void; 88 | 89 | /** 90 | * @return void 91 | * 92 | * Unlock the worker. 93 | */ 94 | public function unlock(): void; 95 | 96 | /** 97 | * @param Worker $worker 98 | * @param callable $callback 99 | * @return void 100 | * 101 | * Add a child worker to the parent worker. 102 | */ 103 | public function addWorker(Worker $worker, callable $callback): void; 104 | 105 | /** 106 | * @return Async 107 | * 108 | * Run the work. 109 | * @throws Throwable 110 | */ 111 | public function run(callable $callback): Async; 112 | 113 | } 114 | 115 | final class Worker implements WorkerInterface 116 | { 117 | 118 | private const LOCKED = "locked"; 119 | 120 | public bool $isStarted = false; 121 | 122 | public bool $isChild = false; 123 | 124 | protected static int $nextId = 0; 125 | 126 | public int $id; 127 | 128 | /** 129 | * @var array 130 | */ 131 | private array $options; 132 | 133 | /** 134 | * @var array> 135 | */ 136 | private array $childWorkers = []; 137 | 138 | /** 139 | * @var array> 140 | */ 141 | private static array $workers = []; 142 | 143 | private Work $work; 144 | 145 | /** 146 | * @param Work $work 147 | * @param array $options 148 | */ 149 | public function __construct(Work $work, array $options = ["threads" => 4]) 150 | { 151 | $this->work = $work; 152 | $this->options = $options; 153 | $this->id = $this->generateId(); 154 | self::$workers[$this->id] = []; 155 | } 156 | 157 | private function generateId(): int 158 | { 159 | if (self::$nextId >= PHP_INT_MAX) self::$nextId = 0; 160 | return self::$nextId++; 161 | } 162 | 163 | public function isStarted(): bool 164 | { 165 | return $this->isStarted; 166 | } 167 | 168 | public function getWork(): Work 169 | { 170 | return $this->work; 171 | } 172 | 173 | public function done(): void 174 | { 175 | if ($this->isChild) return; 176 | unset(self::$workers[$this->id]); 177 | } 178 | 179 | public function collect(mixed $result): void 180 | { 181 | self::$workers[$this->id][] = $result; 182 | } 183 | 184 | /** 185 | * @return array 186 | */ 187 | public function get(): array 188 | { 189 | return self::$workers[$this->id]; 190 | } 191 | 192 | public function isLocked(): bool 193 | { 194 | return isset(self::$workers[$this->id][self::LOCKED]); 195 | } 196 | 197 | public function lock(): void 198 | { 199 | self::$workers[$this->id][self::LOCKED] = true; 200 | } 201 | 202 | public function unlock(): void 203 | { 204 | unset(self::$workers[$this->id][self::LOCKED]); 205 | } 206 | 207 | public function addWorker(Worker $worker, callable $callback): void 208 | { 209 | $worker->isChild = true; 210 | $this->childWorkers[] = [$worker, $callback]; 211 | } 212 | 213 | /** 214 | * @throws Throwable 215 | */ 216 | public function run(callable $callback): Async 217 | { 218 | $this->isStarted = true; 219 | $work = $this->getWork(); 220 | 221 | return new Async(function () use ($work, $callback): void { 222 | $threads = $this->options["threads"]; 223 | 224 | if ($threads >= 1) { 225 | $promises = []; 226 | $totalCountWorks = $work->count(); 227 | 228 | $gc = new GarbageCollection(); 229 | while ($this->isLocked() || $totalCountWorks > 0) { 230 | if (!$this->isLocked()) { 231 | if (count($promises) < $threads && $work->count() > 0) { 232 | /** @var ClosureThread $callbackQueue */ 233 | $callbackQueue = $work->dequeue(); 234 | $promises[] = $callbackQueue->start(); 235 | } else { 236 | /** @var Promise $promise */ 237 | foreach ($promises as $index => $promise) { 238 | $result = EventLoop::getReturn($promise->getId()); 239 | if ($result !== null) { 240 | $result = $promise->getResult(); 241 | $this->collect($result); 242 | unset($promises[$index]); 243 | $totalCountWorks--; 244 | } 245 | } 246 | } 247 | } 248 | $gc->collectWL(); 249 | FiberManager::wait(); 250 | } 251 | 252 | while (count($this->childWorkers) > 0) { 253 | $childWorker = array_shift($this->childWorkers); 254 | if ($childWorker !== null) { 255 | /** @var WorkerInterface $worker */ 256 | $worker = $childWorker[0]; 257 | 258 | /** @var callable $workerCallback */ 259 | $workerCallback = $childWorker[1]; 260 | 261 | Async::await($worker->run($workerCallback)); 262 | 263 | $this->collect($worker->get()); 264 | $worker->done(); 265 | } 266 | FiberManager::wait(); 267 | $gc->collectWL(); 268 | } 269 | 270 | $data = Async::await(Stream::flattenArray($this->get())); 271 | call_user_func($callback, $data, $this); 272 | } 273 | }); 274 | } 275 | 276 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LibVapmPMMP 2 | - This is Virion Async/Promise/Coroutine/Thread/GreenThread for PocketMine-PMMP 3 | 4 | # Composer 5 | ```composer require vennv/vapm-pmmp``` 6 | 7 | # How to setup ? 8 | - Download the Phar officially [here](https://poggit.pmmp.io/ci/VennDev/LibVapmPMMP/LibVapmPMMP) 9 | - Take them and put them in your Virion folder. If you do not understand what Virion is, then [click here](https://poggit.pmmp.io/p/DEVirion/1.2.8) 10 | - To implement and use methods, you must first use this library's method at the top of the plugin's onEnable function 11 | - Example: 12 | ```php 13 | protected function onEnable() : void 14 | { 15 | VapmPMMP::init($this); 16 | } 17 | ``` 18 | - What is VapmPMMP::init($this) ? 19 | ```php 20 | /** 21 | * @param PluginBase $plugin 22 | * 23 | * This method is called by VapmPMMP::init(), it will run event loop. 24 | */ 25 | public static function init(PluginBase $plugin) : void; 26 | ``` 27 | - Finally, here's a guide to the methods you can use: [here](https://venndev.gitbook.io/vapm/) 28 | - This is plugin Example: 29 | - [VFormOOPAPI](https://github.com/VennDev/VFormOOPAPI) 30 | - [SimplifyLibasynql](https://github.com/VennDev/SimplifyLibasynql/tree/main/Examples/Test) 31 | - [VSharedData](https://github.com/VennDev/VSharedData) 32 | - [VJesusBucket](https://github.com/VennDev/VJesusBucket) 33 | - [VBasket](https://github.com/VennDev/VBasket) 34 | - [VOreSpawner](https://github.com/VennDev/VOreSpawner) 35 | 36 | # LibVapmPMMP vs Await-Generator 37 | - [Await-Generator](https://github.com/SOF3/await-generator) 38 | 39 | | Library | Asynchronous polymorphism | Cocurrent | Asynchronous threads | Handles large systems | Multiple ways to support asynchrony | 40 | | --------------- | ------------------------- | --------- | -------------------- | --------------------- | ---------------------------------- | 41 | | LibVapmPMMP | Yes | Yes | Yes | Yes | Yes | 42 | | Await-Generator | Yes | Yes | No | No | No | 43 | 44 | - **Why handles large systems ?** 45 | Vapm uses a Task scheduler with tick-based execution and Await-Gennerator uses queue and execute instantly within a scope. 46 | Comparing the two asynchronous models, one that uses queues to store and execute instantly in a specific scope, and one that saves to the task scheduler for tick execution, should be based on the following factors: 47 | 1. **Performance and Flexibility:** 48 | - **Queue and execute instantly in a scope:** This model is usually faster in processing because tasks are executed immediately as they appear in the queue. However, this can lead to overload if there are too many tasks appearing at the same time, as they are all being processed at the same time. 49 | - **Task scheduler with tick-based execution:** Using a task scheduler allows you to control the speed at which tasks are processed by executing them in ticks. This helps to avoid overload and allows for a more streamlined allocation of system resources. However, execution may be slower if the ticks are set up with long intervals. 50 | 3. **Resource Control:** 51 | - **Queue and Instant Execution:** More resources can be consumed in the event that there are too many tasks that need to be processed immediately, leading to the risk of resource exhaustion or increased system latency. 52 | - **Task scheduler:** Helps you better manage and control your resources by executing only a limited number of tasks per tick, reducing the pressure on the system. 53 | 5. **Real-Time Response:** 54 | - **Queue and execute instantly:** It's better if real-time feedback is required, as tasks are processed instantly. 55 | - **Task scheduler:** May not be suitable for applications that require instant response, as the task has to wait until the next tick to be processed. 56 | 7. **Complexity and Maintenance:** 57 | - **Queue and execute instantly:** Often simpler to deploy, but difficult to manage if the system is highly complex. 58 | - **Task scheduler:** More complex, but offers more flexibility and ease of maintenance in the long run. 59 | 9. **Use cases:** 60 | - **Queue and Instant Execution:** Suitable for applications that require immediate processing and not too many simultaneous tasks. 61 | - **Task scheduler:** Suitable for more complex systems where it is necessary to control the processing of tasks from time to time to ensure stable performance. 62 | - **So is there a way for you to use Vapm as a Wait-Generator?** 63 | You can do it by running methods such as ```CoroutineGen::runBlocking()``` or ```AwaitGroup``` which are available in Vapm or more.. 64 | - **Is it okay to handle such asynchronous tasks on the Task-Scheduler?** 65 | That's perfectly fine because the Task-Schedulers are only allowed to handle up to 20 asynchronous tasks and +1 of the scheduled CoroutineGens. (*Note that the processing here means that it will process each task only once, and if it is not completed, it will be skipped and processed for the next time!*) 66 | - **Why is that okay?** 67 | That's really stable when you know the processing they wait at the parts where you think it's a really heavy task! 68 | Example for you: [Click](https://github.com/VennDev/VBasket/blob/main/src/vennv/vbasket/event/VBasketPlantEvent.php#L108) 69 | As you can see that I stopped at that very moment to do a wait for a task to complete and the Tick-Scheduler would repeat and process again. This makes it possible for many other tasks to be processed simultaneously in the other tick. 70 | - **Await-Generator Problem:** 71 | The Await-Generator has a problem that if I create a promise and just ask it to run without waiting immediately after I declare it, it's like if you have a for loop for billions of numbers, if I wait and run the promise right below it, it will tell me that I'm synchronizing? 72 | I've noticed that there is a queue in the library's Await processing class, however, assuming that if no promises are triggered, the promises that need to be fulfilled are when they are processed? and where is their real-time? 73 | What if I want the promise of processing 1 billion tasks and needing to do it immediately after completing it will fulfill some parameter to do the next thing? Note that this is 1 billion. 74 | - Await-Generator 75 | ```php 76 | $channel = new Channel; 77 | Await::f2c(function() use ($channel) { 78 | for ($i = 0; $i < 5000000; $i++) { 79 | yield from $channel->sendAndWait($i); 80 | } 81 | }); 82 | ``` 83 | - Vapm 84 | ```php 85 | /** 86 | * @throws Throwable 87 | */ 88 | public function loadWorlds(): Channel 89 | { 90 | $channel = new Channel(); 91 | CoroutineGen::runNonBlocking(function () use (&$channel): Generator { 92 | $i = 0; 93 | foreach (scandir($this->plugin->getServer()->getDataPath() . "worlds") as $world) { 94 | if ($world === "." || $world === "..") continue; 95 | if ($this->plugin->getManager()->isIslandNether($world)) { 96 | $i++; 97 | yield from $channel->sendGen($i); 98 | $this->applyIslandNether($world); 99 | } elseif ($this->plugin->getManager()->isIslandEnd($world)) { 100 | $i++; 101 | yield from $channel->sendGen($i); 102 | $this->applyIslandEnd($world); 103 | } else { 104 | $this->applyIslandOverworld($world); 105 | } 106 | } 107 | return $i; 108 | }); 109 | return $channel; 110 | } 111 | 112 | // Process with Task by PMMP 113 | // Load worlds 114 | if ( 115 | self::$doneLoadWorlds === null && 116 | (microtime(true) - self::$lastTimeLoadWorlds) >= 5.0 117 | ) { 118 | self::$lastTimeLoadWorlds = microtime(true); 119 | self::$doneLoadWorlds = $this->plugin->getWorldManager()->loadWorlds(); 120 | } elseif (self::$doneLoadWorlds !== null) { 121 | CoroutineGen::runNonBlocking(function (): Generator { 122 | yield from self::$doneLoadWorlds->receiveGen(fn() => null); 123 | self::$doneLoadWorlds = null; 124 | }); 125 | } 126 | // Completely waits and processes each slow incoming content that Channel sends without over-load the server when too many things are sent and received at once. 127 | ``` 128 | The question arises that why do I have to?? wait a long time to handle a big disagreement like this without handling them asynchronously, quickly and slowly by ticks? 129 | 130 | - **Speed test:** [Code](https://gist.github.com/VennDev/4f7be83d55abfbbf44ff2d249e94968c) with according to the inherent method, Await-Generator still wants to wait and process as usual without using the Task-Scheduler. 131 | ![image](https://github.com/user-attachments/assets/07a39109-8db4-488d-a0db-6e3404edadf3) 132 | -------------------------------------------------------------------------------- /src/vennv/vapm/Stream.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Throwable; 27 | use function fclose; 28 | use function fgets; 29 | use function fopen; 30 | use function fwrite; 31 | use function is_array; 32 | use function touch; 33 | use function unlink; 34 | use function file_exists; 35 | use function call_user_func; 36 | use function stream_set_blocking; 37 | 38 | interface StreamInterface 39 | { 40 | 41 | /** 42 | * @return Promise 43 | * @throws Throwable 44 | * 45 | * Use this to read a file or url. 46 | */ 47 | public static function read(string $path): Promise; 48 | 49 | /** 50 | * @return Promise 51 | * @throws Throwable 52 | * 53 | * Use this to write to a file. 54 | */ 55 | public static function write(string $path, string $data): Promise; 56 | 57 | /** 58 | * @return Promise 59 | * @throws Throwable 60 | * 61 | * Use this to append to a file. 62 | */ 63 | public static function append(string $path, string $data): Promise; 64 | 65 | /** 66 | * @return Promise 67 | * @throws Throwable 68 | * 69 | * Use this to delete a file. 70 | */ 71 | public static function delete(string $path): Promise; 72 | 73 | /** 74 | * @return Promise 75 | * @throws Throwable 76 | * 77 | * Use this to create a file. 78 | */ 79 | public static function create(string $path): Promise; 80 | 81 | /** 82 | * @return Promise 83 | * @throws Throwable 84 | * 85 | * Use this to create a file or overwrite a file. 86 | */ 87 | public static function overWrite(string $path, string $data): Promise; 88 | 89 | /** 90 | * @param array $array 91 | * @return Promise 92 | * @throws Throwable 93 | * 94 | * Use this to flatten an array. 95 | */ 96 | public static function flattenArray(array $array): Promise; 97 | 98 | } 99 | 100 | final class Stream implements StreamInterface 101 | { 102 | 103 | /** 104 | * @throws Throwable 105 | */ 106 | public static function read(string $path): Promise 107 | { 108 | return new Promise(function ($resolve, $reject) use ($path): void { 109 | $lines = ''; 110 | $handle = fopen($path, 'r'); 111 | 112 | if ($handle === false) { 113 | $reject(Error::UNABLE_TO_OPEN_FILE); 114 | } else { 115 | stream_set_blocking($handle, false); 116 | 117 | while (($line = fgets($handle)) !== false) { 118 | $lines .= $line; 119 | FiberManager::wait(); 120 | } 121 | 122 | fclose($handle); 123 | } 124 | 125 | $resolve($lines); 126 | }); 127 | } 128 | 129 | /** 130 | * @throws Throwable 131 | */ 132 | public static function write(string $path, string $data): Promise 133 | { 134 | return new Promise(function ($resolve, $reject) use ($path, $data): void { 135 | System::setTimeout(function () use ($resolve, $reject, $path, $data): void { 136 | $callback = function ($path, $data) use ($reject): void { 137 | $handle = fopen($path, 'w'); 138 | 139 | if ($handle === false) { 140 | $reject(Error::UNABLE_TO_OPEN_FILE); 141 | } else { 142 | stream_set_blocking($handle, false); 143 | fwrite($handle, $data); 144 | fclose($handle); 145 | } 146 | }; 147 | 148 | call_user_func($callback, $path, $data); 149 | $resolve(); 150 | }, 0); 151 | }); 152 | } 153 | 154 | /** 155 | * @throws Throwable 156 | */ 157 | public static function append(string $path, string $data): Promise 158 | { 159 | return new Promise(function ($resolve, $reject) use ($path, $data): void { 160 | System::setTimeout(function () use ($resolve, $reject, $path, $data): void { 161 | $callback = function ($path, $data) use ($reject): void { 162 | $handle = fopen($path, 'a'); 163 | 164 | if ($handle === false) { 165 | $reject(Error::UNABLE_TO_OPEN_FILE); 166 | } else { 167 | stream_set_blocking($handle, false); 168 | fwrite($handle, $data); 169 | fclose($handle); 170 | } 171 | }; 172 | 173 | call_user_func($callback, $path, $data); 174 | $resolve(); 175 | }, 0); 176 | }); 177 | } 178 | 179 | /** 180 | * @throws Throwable 181 | */ 182 | public static function delete(string $path): Promise 183 | { 184 | return new Promise(function ($resolve, $reject) use ($path): void { 185 | System::setTimeout(function () use ($resolve, $reject, $path): void { 186 | $callback = function ($path) use ($reject): void { 187 | file_exists($path) ? unlink($path) : $reject(Error::FILE_DOES_NOT_EXIST); 188 | }; 189 | call_user_func($callback, $path); 190 | $resolve(); 191 | }, 0); 192 | }); 193 | } 194 | 195 | /** 196 | * @throws Throwable 197 | */ 198 | public static function create(string $path): Promise 199 | { 200 | return new Promise(function ($resolve, $reject) use ($path): void { 201 | System::setTimeout(function () use ($resolve, $reject, $path): void { 202 | $callback = function ($path) use ($reject): void { 203 | !file_exists($path) ? touch($path) : $reject(Error::FILE_ALREADY_EXISTS); 204 | }; 205 | call_user_func($callback, $path); 206 | $resolve(); 207 | }, 0); 208 | }); 209 | } 210 | 211 | /** 212 | * @throws Throwable 213 | */ 214 | public static function overWrite(string $path, string $data): Promise 215 | { 216 | return new Promise(function ($resolve, $reject) use ($path, $data): void { 217 | System::setTimeout(function () use ($resolve, $reject, $path, $data): void { 218 | $callback = function ($path, $data) use ($reject): void { 219 | $handle = fopen($path, 'w+'); 220 | if ($handle === false) { 221 | $reject(Error::UNABLE_TO_OPEN_FILE); 222 | } else { 223 | stream_set_blocking($handle, false); 224 | fwrite($handle, $data); 225 | fclose($handle); 226 | } 227 | }; 228 | 229 | call_user_func($callback, $path, $data); 230 | $resolve(); 231 | }, 0); 232 | }); 233 | } 234 | 235 | /** 236 | * @param array $array 237 | * @throws Throwable 238 | */ 239 | public static function flattenArray(array $array): Promise 240 | { 241 | return new Promise(function ($resolve, $reject) use ($array) { 242 | $result = []; 243 | $stack = [$array]; 244 | 245 | while (!empty($stack)) { 246 | $element = array_shift($stack); 247 | if ($element === null) { 248 | $reject(Error::INVALID_ARRAY); 249 | break; 250 | } 251 | 252 | foreach ($element as $value) { 253 | is_array($value) ? array_unshift($stack, $value) : $result[] = $value; 254 | FiberManager::wait(); 255 | } 256 | FiberManager::wait(); 257 | } 258 | 259 | $resolve($result); 260 | }); 261 | } 262 | 263 | } -------------------------------------------------------------------------------- /src/vennv/vapm/System.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Throwable; 27 | use function curl_init; 28 | use function curl_multi_add_handle; 29 | use function curl_multi_close; 30 | use function curl_multi_exec; 31 | use function curl_multi_getcontent; 32 | use function curl_multi_init; 33 | use function curl_multi_remove_handle; 34 | use function file_get_contents; 35 | use const CURLM_OK; 36 | use const CURLOPT_RETURNTRANSFER; 37 | 38 | interface SystemInterface 39 | { 40 | 41 | /** 42 | * @throws Throwable 43 | * 44 | * This function is used to run the event loop with multiple event loops 45 | */ 46 | public static function runEventLoop(): void; 47 | 48 | /** 49 | * @throws Throwable 50 | * 51 | * This function is used to run the event loop with single event loop 52 | */ 53 | public static function runSingleEventLoop(): void; 54 | 55 | /** 56 | * @throws Throwable 57 | * 58 | * This function is used to initialize the event loop 59 | */ 60 | public static function init(): void; 61 | 62 | /** 63 | * This function is used to run a callback in the event loop with timeout 64 | */ 65 | public static function setTimeout(callable $callback, int $timeout): SampleMacro; 66 | 67 | /** 68 | * This function is used to clear the timeout 69 | */ 70 | public static function clearTimeout(SampleMacro $sampleMacro): void; 71 | 72 | /** 73 | * This function is used to run a callback in the event loop with interval 74 | */ 75 | public static function setInterval(callable $callback, int $interval): SampleMacro; 76 | 77 | /** 78 | * This function is used to clear the interval 79 | */ 80 | public static function clearInterval(SampleMacro $sampleMacro): void; 81 | 82 | /** 83 | * @param string $url 84 | * @param array $options 85 | * @return Promise when Promise resolve InternetRequestResult and when Promise reject Error 86 | * @throws Throwable 87 | * @phpstan-param array{method?: string, headers?: array, timeout?: int, body?: array} $options 88 | */ 89 | public static function fetch(string $url, array $options = []): Promise; 90 | 91 | /** 92 | * @param string ...$curls 93 | * @return Promise 94 | * @throws Throwable 95 | * 96 | * Use this to curl multiple addresses at once 97 | */ 98 | public static function fetchAll(string ...$curls): Promise; 99 | 100 | /** 101 | * @throws Throwable 102 | * 103 | * This is a function used only to retrieve results from an address or file path via the file_get_contents method 104 | */ 105 | public static function read(string $path): Promise; 106 | 107 | /** 108 | * @param string $name 109 | * @return void 110 | * 111 | * This function is used to start a timer 112 | */ 113 | public static function time(string $name = 'Console'): void; 114 | 115 | /** 116 | * @param string $name 117 | * @return void 118 | * 119 | * This function is used to end a timer 120 | */ 121 | public static function timeEnd(string $name = 'Console'): void; 122 | 123 | } 124 | 125 | final class System extends EventLoop implements SystemInterface 126 | { 127 | 128 | /** 129 | * @var array 130 | */ 131 | private static array $timings = []; 132 | 133 | private static bool $hasInit = false; 134 | 135 | /** 136 | * @throws Throwable 137 | */ 138 | public static function runEventLoop(): void 139 | { 140 | self::init(); 141 | parent::run(); 142 | } 143 | 144 | /** 145 | * @throws Throwable 146 | */ 147 | public static function runSingleEventLoop(): void 148 | { 149 | self::init(); 150 | parent::runSingle(); 151 | } 152 | 153 | public static function init(): void 154 | { 155 | if (!self::$hasInit) { 156 | self::$hasInit = true; 157 | register_shutdown_function(fn() => self::runSingleEventLoop()); 158 | register_tick_function(fn() => CoroutineGen::run()); 159 | } 160 | 161 | parent::init(); 162 | } 163 | 164 | /** 165 | * @throws Throwable 166 | */ 167 | public static function setTimeout(callable $callback, int $timeout): SampleMacro 168 | { 169 | self::init(); 170 | $sampleMacro = new SampleMacro($callback, $timeout); 171 | MacroTask::addTask($sampleMacro); 172 | return $sampleMacro; 173 | } 174 | 175 | public static function clearTimeout(SampleMacro $sampleMacro): void 176 | { 177 | if ($sampleMacro->isRunning() && !$sampleMacro->isRepeat()) $sampleMacro->stop(); 178 | } 179 | 180 | /** 181 | * @throws Throwable 182 | */ 183 | public static function setInterval(callable $callback, int $interval): SampleMacro 184 | { 185 | self::init(); 186 | $sampleMacro = new SampleMacro($callback, $interval, true); 187 | MacroTask::addTask($sampleMacro); 188 | return $sampleMacro; 189 | } 190 | 191 | public static function clearInterval(SampleMacro $sampleMacro): void 192 | { 193 | if ($sampleMacro->isRunning() && $sampleMacro->isRepeat()) $sampleMacro->stop(); 194 | } 195 | 196 | /** 197 | * @param string $url 198 | * @param array $options 199 | * @return Promise when Promise resolve InternetRequestResult and when Promise reject Error 200 | * @throws Throwable 201 | * @phpstan-param array{method?: string, headers?: array, timeout?: int, body?: array} $options 202 | */ 203 | public static function fetch(string $url, array $options = []): Promise 204 | { 205 | return new Promise(function ($resolve, $reject) use ($url, $options) { 206 | self::setTimeout(function () use ($resolve, $reject, $url, $options) { 207 | $method = $options["method"] ?? "GET"; 208 | 209 | /** @var array $headers */ 210 | $headers = $options["headers"] ?? []; 211 | 212 | /** @var int $timeout */ 213 | $timeout = $options["timeout"] ?? 10; 214 | 215 | /** @var array $body */ 216 | $body = $options["body"] ?? []; 217 | 218 | $method === "GET" ? $result = Internet::getURL($url, $timeout, $headers) : $result = Internet::postURL($url, $body, $timeout, $headers); 219 | $result === null ? $reject(Error::FAILED_IN_FETCHING_DATA) : $resolve($result); 220 | }, 0); 221 | }); 222 | } 223 | 224 | /** 225 | * @param string ...$curls 226 | * @return Promise 227 | * @throws Throwable 228 | * 229 | * Use this to curl multiple addresses at once 230 | */ 231 | public static function fetchAll(string ...$curls): Promise 232 | { 233 | return new Promise(function ($resolve, $reject) use ($curls): void { 234 | $multiHandle = curl_multi_init(); 235 | $handles = []; 236 | foreach ($curls as $url) { 237 | $handle = curl_init($url); 238 | if ($handle === false) { 239 | $reject(Error::FAILED_IN_FETCHING_DATA); 240 | } else { 241 | curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); 242 | curl_multi_add_handle($multiHandle, $handle); 243 | $handles[] = $handle; 244 | } 245 | } 246 | 247 | $running = 0; 248 | 249 | do { 250 | $status = curl_multi_exec($multiHandle, $running); 251 | if ($status !== CURLM_OK) $reject(Error::FAILED_IN_FETCHING_DATA); 252 | FiberManager::wait(); 253 | } while ($running > 0); 254 | 255 | $results = []; 256 | foreach ($handles as $handle) { 257 | $results[] = curl_multi_getcontent($handle); 258 | curl_multi_remove_handle($multiHandle, $handle); 259 | } 260 | 261 | curl_multi_close($multiHandle); 262 | $resolve($results); 263 | }); 264 | } 265 | 266 | /** 267 | * @throws Throwable 268 | */ 269 | public static function read(string $path): Promise 270 | { 271 | return new Promise(function ($resolve, $reject) use ($path) { 272 | self::setTimeout(function () use ($resolve, $reject, $path) { 273 | $ch = file_get_contents($path); 274 | $ch === false ? $reject(Error::FAILED_IN_FETCHING_DATA) : $resolve($ch); 275 | }, 0); 276 | }); 277 | } 278 | 279 | public static function time(string $name = 'Console'): void 280 | { 281 | self::$timings[$name] = microtime(true); 282 | } 283 | 284 | public static function timeEnd(string $name = 'Console'): void 285 | { 286 | if (!isset(self::$timings[$name])) return; 287 | $time = microtime(true) - self::$timings[$name]; 288 | echo "Time for $name: $time\n"; 289 | unset(self::$timings[$name]); 290 | } 291 | 292 | } 293 | -------------------------------------------------------------------------------- /src/vennv/vapm/utils/Utils.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm\utils; 25 | 26 | use vennv\vapm\Error; 27 | use Closure; 28 | use Generator; 29 | use RecursiveDirectoryIterator; 30 | use RecursiveIteratorIterator; 31 | use ReflectionException; 32 | use ReflectionFunction; 33 | use SplFileInfo; 34 | use RuntimeException; 35 | use function array_slice; 36 | use function file; 37 | use function implode; 38 | use function is_array; 39 | use function is_object; 40 | use function is_string; 41 | use function preg_match; 42 | use function serialize; 43 | use function strlen; 44 | use function strpos; 45 | use function substr; 46 | 47 | interface UtilsInterface 48 | { 49 | 50 | /** 51 | * Transform milliseconds to seconds 52 | */ 53 | public static function milliSecsToSecs(float $milliSecs): float; 54 | 55 | /** 56 | * @throws ReflectionException 57 | * 58 | * Transform a closure or callable to string 59 | */ 60 | public static function closureToString(Closure $closure): string; 61 | 62 | /** 63 | * @throws RuntimeException 64 | * @return string 65 | * 66 | * Transform a closure or callable to string 67 | */ 68 | public static function closureToStringSafe(Closure $closure): string; 69 | 70 | /** 71 | * Get all Dot files in a directory 72 | */ 73 | public static function getAllByDotFile(string $path, string $dotFile): Generator; 74 | 75 | /** 76 | * @return array|string 77 | * 78 | * Transform a string to inline 79 | */ 80 | public static function outlineToInline(string $text): array|string; 81 | 82 | /** 83 | * @return array|string 84 | * 85 | * Fix input command 86 | */ 87 | public static function fixInputCommand(string $text): array|string; 88 | 89 | /** 90 | * @return null|string|array 91 | * 92 | * Remove comments from a string 93 | */ 94 | public static function removeComments(string $text): null|string|array; 95 | 96 | /** 97 | * @param mixed $data 98 | * 99 | * Get bytes of a string or object or array 100 | */ 101 | public static function getBytes(mixed $data): int; 102 | 103 | /** 104 | * @return Generator 105 | * 106 | * Split a string by slash 107 | */ 108 | public static function splitStringBySlash(string $string): Generator; 109 | 110 | /** 111 | * @return false|string 112 | * 113 | * Replace path 114 | */ 115 | public static function replacePath(string $path, string $segment): false|string; 116 | 117 | /** 118 | * @return array|string|null 119 | * 120 | * Replace advanced 121 | */ 122 | public static function replaceAdvanced(string $text, string $search, string $replace): array|string|null; 123 | 124 | /** 125 | * @return Generator 126 | * 127 | * Evenly divide a number 128 | */ 129 | public static function evenlyDivide(int $number, int $parts): Generator; 130 | 131 | /** 132 | * @param array $array 133 | * @param int $size 134 | * @return Generator 135 | */ 136 | public static function splitArray(array $array, int $size): Generator; 137 | 138 | /** 139 | * @param string $class 140 | * @return bool 141 | * 142 | * This method is used to check if the current class is the same as the class passed in 143 | */ 144 | public static function isClass(string $class): bool; 145 | 146 | 147 | /** 148 | * @return string 149 | * 150 | * Get string after sign 151 | */ 152 | public static function getStringAfterSign(string $string, string $sign): string; 153 | 154 | /** 155 | * @param mixed $data 156 | * @return array 157 | * 158 | * Convert data to string 159 | */ 160 | public static function toStringAny(mixed $data): array; 161 | 162 | /** 163 | * @param array $data 164 | * @return mixed 165 | * 166 | * Convert data to real it's type 167 | */ 168 | public static function fromStringToAny(array $data): mixed; 169 | 170 | } 171 | 172 | final class Utils implements UtilsInterface 173 | { 174 | 175 | public static function milliSecsToSecs(float $milliSecs): float 176 | { 177 | return $milliSecs / 1000; 178 | } 179 | 180 | /** 181 | * @throws ReflectionException 182 | */ 183 | public static function closureToString(Closure $closure): string 184 | { 185 | $reflection = new ReflectionFunction($closure); 186 | $startLine = $reflection->getStartLine(); 187 | $endLine = $reflection->getEndLine(); 188 | $filename = $reflection->getFileName(); 189 | 190 | if ($filename === false || $startLine === false || $endLine === false) throw new ReflectionException(Error::CANNOT_FIND_FUNCTION_KEYWORD); 191 | 192 | $lines = file($filename); 193 | if ($lines === false) throw new ReflectionException(Error::CANNOT_READ_FILE); 194 | 195 | $result = implode("", array_slice($lines, $startLine - 1, $endLine - $startLine + 1)); 196 | $startPos = strpos($result, 'function'); 197 | if ($startPos === false) { 198 | $startPos = strpos($result, 'fn'); 199 | if ($startPos === false) throw new ReflectionException(Error::CANNOT_FIND_FUNCTION_KEYWORD); 200 | } 201 | 202 | $endBracketPos = strrpos($result, '}'); 203 | if ($endBracketPos === false) throw new ReflectionException(Error::CANNOT_FIND_FUNCTION_KEYWORD); 204 | 205 | return substr($result, $startPos, $endBracketPos - $startPos + 1); 206 | } 207 | 208 | /** 209 | * @throws RuntimeException 210 | * @return string 211 | */ 212 | public static function closureToStringSafe(Closure $closure): string 213 | { 214 | $input = self::closureToString($closure); 215 | $input = self::removeComments($input); 216 | 217 | if (!is_string($input)) throw new RuntimeException(Error::INPUT_MUST_BE_STRING_OR_CALLABLE); 218 | 219 | $input = self::outlineToInline($input); 220 | 221 | if (!is_string($input)) throw new RuntimeException(Error::INPUT_MUST_BE_STRING_OR_CALLABLE); 222 | 223 | $input = self::fixInputCommand($input); 224 | 225 | if (!is_string($input)) throw new RuntimeException(Error::INPUT_MUST_BE_STRING_OR_CALLABLE); 226 | 227 | return $input; 228 | } 229 | 230 | public static function getAllByDotFile(string $path, string $dotFile): Generator 231 | { 232 | $dir = new RecursiveDirectoryIterator($path); 233 | $iterator = new RecursiveIteratorIterator($dir); 234 | 235 | foreach ($iterator as $file) { 236 | if ($file instanceof SplFileInfo && preg_match('%' . $dotFile . '$%', $file->getFilename()) === 1) yield $file->getPathname(); 237 | } 238 | } 239 | 240 | /** 241 | * @return array|string 242 | */ 243 | public static function outlineToInline(string $text): array|string 244 | { 245 | return str_replace(array("\r", "\n", "\t", ' '), '', $text); 246 | } 247 | 248 | /** 249 | * @return array|string 250 | */ 251 | public static function fixInputCommand(string $text): array|string 252 | { 253 | return str_replace('"', '\'', $text); 254 | } 255 | 256 | /** 257 | * @return null|string|array 258 | * 259 | * Remove comments from a string 260 | */ 261 | public static function removeComments(string $text): null|string|array 262 | { 263 | $text = preg_replace('/(?|string|null 309 | * 310 | * Replace advanced 311 | */ 312 | public static function replaceAdvanced(string $text, string $search, string $replace): array|string|null 313 | { 314 | return preg_replace('/(? 0 ? 1 : 0); 324 | $remainder--; 325 | } 326 | } 327 | 328 | /** 329 | * @param array $array 330 | * @param int $size 331 | * @return Generator 332 | */ 333 | public static function splitArray(array $array, int $size): Generator 334 | { 335 | $totalItems = count($array); 336 | $quotient = intdiv($totalItems, $size); 337 | $remainder = $totalItems % $size; 338 | 339 | $offset = 0; 340 | for ($i = 0; $i < $size; $i++) { 341 | $length = $quotient + ($remainder > 0 ? 1 : 0); 342 | 343 | yield array_slice($array, $offset, $length); 344 | 345 | $offset += $length; 346 | $remainder--; 347 | } 348 | } 349 | 350 | /** 351 | * @param string $class 352 | * @return bool 353 | * @throws ReflectionException 354 | */ 355 | public static function isClass(string $class): bool 356 | { 357 | $trace = debug_backtrace(); 358 | if (isset($trace[2])) { 359 | if (!empty($trace[2]['args'])) { 360 | $args = $trace[2]['args']; 361 | /** @var Closure $closure */ 362 | $closure = $args[0]; 363 | $reflectionFunction = new ReflectionFunction($closure); 364 | $scopeClass = $reflectionFunction->getClosureScopeClass(); 365 | if ($scopeClass === null) return false; 366 | return $scopeClass->getName() === $class; 367 | } else { 368 | return true; // This is a class 369 | } 370 | } 371 | return false; 372 | } 373 | 374 | /** 375 | * @return string 376 | * 377 | * Get string after sign 378 | */ 379 | public static function getStringAfterSign(string $string, string $sign): string 380 | { 381 | if (preg_match('/' . preg_quote($sign, '/') . '(.*)/s', $string, $matches)) return $matches[1]; 382 | return ''; 383 | } 384 | 385 | /** 386 | * @param mixed $data 387 | * @return array 388 | * 389 | * Convert data to string 390 | */ 391 | public static function toStringAny(mixed $data): array 392 | { 393 | $type = gettype($data); 394 | if (!is_callable($data) && (is_array($data) || is_object($data))) { 395 | return [$type => json_encode($data)]; 396 | } elseif (is_bool($data)) { 397 | $data = $data ? 'true' : 'false'; 398 | return [$type => $data]; 399 | } elseif (is_resource($data)) { 400 | return [$type => get_resource_type($data)]; 401 | } elseif (is_null($data)) { 402 | return [$type => 'null']; 403 | } elseif (is_callable($data)) { 404 | /** @phpstan-ignore-next-line */ 405 | return ['callable' => self::closureToStringSafe($data)]; 406 | } elseif (is_string($data)) { 407 | return [$type => '\'' . $data . '\'']; 408 | } 409 | /** @phpstan-ignore-next-line */ 410 | return [$type => (string) $data]; 411 | } 412 | 413 | /** 414 | * @param array $data 415 | * @return mixed 416 | * 417 | * Convert data to real it's type 418 | */ 419 | public static function fromStringToAny(array $data): mixed 420 | { 421 | $type = array_keys($data)[0]; 422 | $value = array_values($data)[0]; 423 | return match ($type) { 424 | 'boolean' => $value === 'true', 425 | 'integer' => (int) $value, 426 | 'float' => (float) $value, 427 | 'double' => (float) $value, 428 | 'string' => $value, 429 | 'array' => json_decode($value, true), 430 | 'object' => json_decode($value), 431 | 'callable' => eval('return ' . $value . ';'), 432 | 'null' => null, 433 | default => $value, 434 | }; 435 | } 436 | 437 | } -------------------------------------------------------------------------------- /src/vendor/composer/InstalledVersions.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer; 14 | 15 | use Composer\Autoload\ClassLoader; 16 | use Composer\Semver\VersionParser; 17 | 18 | /** 19 | * This class is copied in every Composer installed project and available to all 20 | * 21 | * See also https://getcomposer.org/doc/07-runtime.md#installed-versions 22 | * 23 | * To require its presence, you can require `composer-runtime-api ^2.0` 24 | * 25 | * @final 26 | */ 27 | class InstalledVersions 28 | { 29 | /** 30 | * @var mixed[]|null 31 | * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null 32 | */ 33 | private static $installed; 34 | 35 | /** 36 | * @var bool|null 37 | */ 38 | private static $canGetVendors; 39 | 40 | /** 41 | * @var array[] 42 | * @psalm-var array}> 43 | */ 44 | private static $installedByVendor = array(); 45 | 46 | /** 47 | * Returns a list of all package names which are present, either by being installed, replaced or provided 48 | * 49 | * @return string[] 50 | * @psalm-return list 51 | */ 52 | public static function getInstalledPackages() 53 | { 54 | $packages = array(); 55 | foreach (self::getInstalled() as $installed) { 56 | $packages[] = array_keys($installed['versions']); 57 | } 58 | 59 | if (1 === \count($packages)) { 60 | return $packages[0]; 61 | } 62 | 63 | return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); 64 | } 65 | 66 | /** 67 | * Returns a list of all package names with a specific type e.g. 'library' 68 | * 69 | * @param string $type 70 | * @return string[] 71 | * @psalm-return list 72 | */ 73 | public static function getInstalledPackagesByType($type) 74 | { 75 | $packagesByType = array(); 76 | 77 | foreach (self::getInstalled() as $installed) { 78 | foreach ($installed['versions'] as $name => $package) { 79 | if (isset($package['type']) && $package['type'] === $type) { 80 | $packagesByType[] = $name; 81 | } 82 | } 83 | } 84 | 85 | return $packagesByType; 86 | } 87 | 88 | /** 89 | * Checks whether the given package is installed 90 | * 91 | * This also returns true if the package name is provided or replaced by another package 92 | * 93 | * @param string $packageName 94 | * @param bool $includeDevRequirements 95 | * @return bool 96 | */ 97 | public static function isInstalled($packageName, $includeDevRequirements = true) 98 | { 99 | foreach (self::getInstalled() as $installed) { 100 | if (isset($installed['versions'][$packageName])) { 101 | return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; 102 | } 103 | } 104 | 105 | return false; 106 | } 107 | 108 | /** 109 | * Checks whether the given package satisfies a version constraint 110 | * 111 | * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: 112 | * 113 | * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') 114 | * 115 | * @param VersionParser $parser Install composer/semver to have access to this class and functionality 116 | * @param string $packageName 117 | * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package 118 | * @return bool 119 | */ 120 | public static function satisfies(VersionParser $parser, $packageName, $constraint) 121 | { 122 | $constraint = $parser->parseConstraints((string) $constraint); 123 | $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); 124 | 125 | return $provided->matches($constraint); 126 | } 127 | 128 | /** 129 | * Returns a version constraint representing all the range(s) which are installed for a given package 130 | * 131 | * It is easier to use this via isInstalled() with the $constraint argument if you need to check 132 | * whether a given version of a package is installed, and not just whether it exists 133 | * 134 | * @param string $packageName 135 | * @return string Version constraint usable with composer/semver 136 | */ 137 | public static function getVersionRanges($packageName) 138 | { 139 | foreach (self::getInstalled() as $installed) { 140 | if (!isset($installed['versions'][$packageName])) { 141 | continue; 142 | } 143 | 144 | $ranges = array(); 145 | if (isset($installed['versions'][$packageName]['pretty_version'])) { 146 | $ranges[] = $installed['versions'][$packageName]['pretty_version']; 147 | } 148 | if (array_key_exists('aliases', $installed['versions'][$packageName])) { 149 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); 150 | } 151 | if (array_key_exists('replaced', $installed['versions'][$packageName])) { 152 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); 153 | } 154 | if (array_key_exists('provided', $installed['versions'][$packageName])) { 155 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); 156 | } 157 | 158 | return implode(' || ', $ranges); 159 | } 160 | 161 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 162 | } 163 | 164 | /** 165 | * @param string $packageName 166 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 167 | */ 168 | public static function getVersion($packageName) 169 | { 170 | foreach (self::getInstalled() as $installed) { 171 | if (!isset($installed['versions'][$packageName])) { 172 | continue; 173 | } 174 | 175 | if (!isset($installed['versions'][$packageName]['version'])) { 176 | return null; 177 | } 178 | 179 | return $installed['versions'][$packageName]['version']; 180 | } 181 | 182 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 183 | } 184 | 185 | /** 186 | * @param string $packageName 187 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 188 | */ 189 | public static function getPrettyVersion($packageName) 190 | { 191 | foreach (self::getInstalled() as $installed) { 192 | if (!isset($installed['versions'][$packageName])) { 193 | continue; 194 | } 195 | 196 | if (!isset($installed['versions'][$packageName]['pretty_version'])) { 197 | return null; 198 | } 199 | 200 | return $installed['versions'][$packageName]['pretty_version']; 201 | } 202 | 203 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 204 | } 205 | 206 | /** 207 | * @param string $packageName 208 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference 209 | */ 210 | public static function getReference($packageName) 211 | { 212 | foreach (self::getInstalled() as $installed) { 213 | if (!isset($installed['versions'][$packageName])) { 214 | continue; 215 | } 216 | 217 | if (!isset($installed['versions'][$packageName]['reference'])) { 218 | return null; 219 | } 220 | 221 | return $installed['versions'][$packageName]['reference']; 222 | } 223 | 224 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 225 | } 226 | 227 | /** 228 | * @param string $packageName 229 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. 230 | */ 231 | public static function getInstallPath($packageName) 232 | { 233 | foreach (self::getInstalled() as $installed) { 234 | if (!isset($installed['versions'][$packageName])) { 235 | continue; 236 | } 237 | 238 | return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; 239 | } 240 | 241 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 242 | } 243 | 244 | /** 245 | * @return array 246 | * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} 247 | */ 248 | public static function getRootPackage() 249 | { 250 | $installed = self::getInstalled(); 251 | 252 | return $installed[0]['root']; 253 | } 254 | 255 | /** 256 | * Returns the raw installed.php data for custom implementations 257 | * 258 | * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. 259 | * @return array[] 260 | * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} 261 | */ 262 | public static function getRawData() 263 | { 264 | @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); 265 | 266 | if (null === self::$installed) { 267 | // only require the installed.php file if this file is loaded from its dumped location, 268 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 269 | if (substr(__DIR__, -8, 1) !== 'C') { 270 | self::$installed = include __DIR__ . '/installed.php'; 271 | } else { 272 | self::$installed = array(); 273 | } 274 | } 275 | 276 | return self::$installed; 277 | } 278 | 279 | /** 280 | * Returns the raw data of all installed.php which are currently loaded for custom implementations 281 | * 282 | * @return array[] 283 | * @psalm-return list}> 284 | */ 285 | public static function getAllRawData() 286 | { 287 | return self::getInstalled(); 288 | } 289 | 290 | /** 291 | * Lets you reload the static array from another file 292 | * 293 | * This is only useful for complex integrations in which a project needs to use 294 | * this class but then also needs to execute another project's autoloader in process, 295 | * and wants to ensure both projects have access to their version of installed.php. 296 | * 297 | * A typical case would be PHPUnit, where it would need to make sure it reads all 298 | * the data it needs from this class, then call reload() with 299 | * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure 300 | * the project in which it runs can then also use this class safely, without 301 | * interference between PHPUnit's dependencies and the project's dependencies. 302 | * 303 | * @param array[] $data A vendor/composer/installed.php data set 304 | * @return void 305 | * 306 | * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data 307 | */ 308 | public static function reload($data) 309 | { 310 | self::$installed = $data; 311 | self::$installedByVendor = array(); 312 | } 313 | 314 | /** 315 | * @return array[] 316 | * @psalm-return list}> 317 | */ 318 | private static function getInstalled() 319 | { 320 | if (null === self::$canGetVendors) { 321 | self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); 322 | } 323 | 324 | $installed = array(); 325 | 326 | if (self::$canGetVendors) { 327 | foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { 328 | if (isset(self::$installedByVendor[$vendorDir])) { 329 | $installed[] = self::$installedByVendor[$vendorDir]; 330 | } elseif (is_file($vendorDir.'/composer/installed.php')) { 331 | /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ 332 | $required = require $vendorDir.'/composer/installed.php'; 333 | $installed[] = self::$installedByVendor[$vendorDir] = $required; 334 | if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { 335 | self::$installed = $installed[count($installed) - 1]; 336 | } 337 | } 338 | } 339 | } 340 | 341 | if (null === self::$installed) { 342 | // only require the installed.php file if this file is loaded from its dumped location, 343 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 344 | if (substr(__DIR__, -8, 1) !== 'C') { 345 | /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ 346 | $required = require __DIR__ . '/installed.php'; 347 | self::$installed = $required; 348 | } else { 349 | self::$installed = array(); 350 | } 351 | } 352 | 353 | if (self::$installed !== array()) { 354 | $installed[] = self::$installed; 355 | } 356 | 357 | return $installed; 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /src/vennv/vapm/Thread.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use vennv\vapm\utils\Utils; 27 | use Closure; 28 | use ReflectionClass; 29 | use ReflectionException; 30 | use RuntimeException; 31 | use InvalidArgumentException; 32 | use Throwable; 33 | use function strlen; 34 | use function rtrim; 35 | use function fclose; 36 | use function fwrite; 37 | use function get_called_class; 38 | use function is_array; 39 | use function is_callable; 40 | use function is_resource; 41 | use function is_string; 42 | use function json_decode; 43 | use function json_encode; 44 | use function proc_get_status; 45 | use function proc_open; 46 | use function str_replace; 47 | use function stream_get_contents; 48 | use function stream_set_blocking; 49 | use const PHP_BINARY; 50 | use const PHP_EOL; 51 | use const STDIN; 52 | use const STDOUT; 53 | 54 | interface ThreadInterface 55 | { 56 | 57 | /** 58 | * This abstract method use to run the thread 59 | */ 60 | public function onRun(): void; 61 | 62 | /** 63 | * @param array> $mode 64 | * @throws ReflectionException 65 | * @throws Throwable 66 | * @phpstan-param array> $mode 67 | * 68 | * This method use to start the thread 69 | */ 70 | public function start(array $mode = DescriptorSpec::BASIC): Promise; 71 | 72 | } 73 | 74 | interface ThreadedInterface 75 | { 76 | 77 | /** 78 | * @return mixed 79 | */ 80 | public function getInput(): mixed; 81 | 82 | /** 83 | * This method use to get the pid of the thread 84 | */ 85 | public function getPid(): int; 86 | 87 | /** 88 | * This method use to get the exit code of the thread 89 | */ 90 | public function getExitCode(): int; 91 | 92 | /* 93 | * This method use to get the running status of the thread 94 | */ 95 | public function isRunning(): bool; 96 | 97 | /** 98 | * This method use to get the signaled status of the thread 99 | */ 100 | public function isSignaled(): bool; 101 | 102 | /** 103 | * This method use to get the stopped status of the thread 104 | */ 105 | public function isStopped(): bool; 106 | 107 | /** 108 | * @return array 109 | * @phpstan-return array 110 | * 111 | * This method use to get the shared data of the main thread 112 | */ 113 | public static function getDataMainThread(): array; 114 | 115 | /** 116 | * @param array $shared 117 | * @phpstan-param array $shared 118 | * 119 | * This method use to set the shared data of the main thread 120 | */ 121 | public static function setShared(array $shared): void; 122 | 123 | /** 124 | * @param string $key 125 | * @param mixed $value 126 | * @phpstan-param mixed $value 127 | * 128 | * This method use to add the shared data of the MAIN-THREAD 129 | */ 130 | public static function addShared(string $key, mixed $value): void; 131 | 132 | /** 133 | * @return array 134 | * 135 | * This method use to get the shared data of the child thread 136 | */ 137 | public static function getSharedData(): array; 138 | 139 | /** 140 | * @param array $data 141 | * @return void 142 | * @phpstan-param array $data 143 | * 144 | * This method use to post all data the main thread 145 | */ 146 | public static function postMainThread(array $data): void; 147 | 148 | /** 149 | * @param string $data 150 | * @return void 151 | * 152 | * This method use to load the shared data from the main thread 153 | */ 154 | public static function loadSharedData(string $data): void; 155 | 156 | /** 157 | * @param string $data 158 | * @return void 159 | * 160 | * This method use to post the data on the thread 161 | */ 162 | public static function post(string $data): void; 163 | 164 | /** 165 | * @param int $pid 166 | * @return bool 167 | * 168 | * This method use to check the thread is running or not 169 | */ 170 | public static function threadIsRunning(int $pid): bool; 171 | 172 | /** 173 | * @param int $pid 174 | * @return bool 175 | * 176 | * This method use to kill the thread 177 | */ 178 | public static function killThread(int $pid): bool; 179 | 180 | } 181 | 182 | abstract class Thread implements ThreadInterface, ThreadedInterface 183 | { 184 | 185 | private const POST_MAIN_THREAD = 'postMainThread'; // example: postMainThread=>{data} 186 | 187 | private const POST_THREAD = 'postThread'; // example: postAlertThread=>{data} 188 | 189 | private int $pid = -1; 190 | 191 | private int $exitCode = -1; 192 | 193 | private bool $isRunning = false; 194 | 195 | private bool $signaled = false; 196 | 197 | private bool $stopped = false; 198 | 199 | /** 200 | * @var array 201 | * @phpstan-var array 202 | */ 203 | private static array $shared = []; 204 | 205 | /** 206 | * @var array 207 | * @phpstan-var array 208 | */ 209 | private static array $threads = []; 210 | 211 | /** 212 | * @var array 213 | * @phpstan-var array 214 | */ 215 | private static array $inputs = []; 216 | 217 | /** 218 | * @var array 219 | * @phpstan-var array 220 | */ 221 | private static array $args = []; 222 | 223 | /** 224 | * @param mixed $input 225 | * @param array $args 226 | * @phpstan-param array $args 227 | */ 228 | public function __construct(mixed $input = '', array $args = []) 229 | { 230 | self::$inputs[$this->getCalledClassId()] = $input; 231 | self::$args[$this->getCalledClassId()] = $args; 232 | } 233 | 234 | private function getCalledClassId(): int 235 | { 236 | return spl_object_id($this); 237 | } 238 | 239 | public function getInput(): mixed 240 | { 241 | return self::$inputs[$this->getCalledClassId()]; 242 | } 243 | 244 | public function getPid(): int 245 | { 246 | return $this->pid; 247 | } 248 | 249 | public function setPid(int $pid): void 250 | { 251 | $this->pid = $pid; 252 | } 253 | 254 | public function getExitCode(): int 255 | { 256 | return $this->exitCode; 257 | } 258 | 259 | protected function setExitCode(int $exitCode): void 260 | { 261 | $this->exitCode = $exitCode; 262 | } 263 | 264 | public function isRunning(): bool 265 | { 266 | return $this->isRunning; 267 | } 268 | 269 | protected function setRunning(bool $isRunning): void 270 | { 271 | $this->isRunning = $isRunning; 272 | } 273 | 274 | public function isSignaled(): bool 275 | { 276 | return $this->signaled; 277 | } 278 | 279 | protected function setSignaled(bool $signaled): void 280 | { 281 | $this->signaled = $signaled; 282 | } 283 | 284 | public function isStopped(): bool 285 | { 286 | return $this->stopped; 287 | } 288 | 289 | protected function setStopped(bool $stopped): void 290 | { 291 | $this->stopped = $stopped; 292 | } 293 | 294 | /** 295 | * @return array 296 | * @phpstan-return array 297 | */ 298 | public static function getDataMainThread(): array 299 | { 300 | return self::$shared; 301 | } 302 | 303 | /** 304 | * @param array $shared 305 | * @phpstan-param array $shared 306 | */ 307 | public static function setShared(array $shared): void 308 | { 309 | self::$shared = $shared; 310 | } 311 | 312 | public static function addShared(string $key, mixed $value): void 313 | { 314 | self::$shared[$key] = $value; 315 | } 316 | 317 | /** 318 | * @return array 319 | * @phpstan-return array 320 | */ 321 | public static function getSharedData(): array 322 | { 323 | $data = fgets(STDIN); 324 | 325 | if (is_string($data)) { 326 | $data = json_decode($data, true); 327 | if (is_array($data)) return $data; 328 | } 329 | 330 | return []; 331 | } 332 | 333 | /** 334 | * @param array $data 335 | * @phpstan-param array $data 336 | */ 337 | public static function postMainThread(array $data): void 338 | { 339 | fwrite(STDOUT, self::POST_MAIN_THREAD . '=>' . json_encode($data) . PHP_EOL); 340 | } 341 | 342 | private static function isPostMainThread(string $data): bool 343 | { 344 | return strlen(Utils::getStringAfterSign($data, self::POST_MAIN_THREAD . '=>')) > 0; 345 | } 346 | 347 | private static function isPostThread(string $data): bool 348 | { 349 | return strlen(Utils::getStringAfterSign($data, self::POST_THREAD . '=>')) > 0; 350 | } 351 | 352 | public static function loadSharedData(string $data): void 353 | { 354 | if (self::isPostMainThread($data)) { 355 | $result = json_decode(Utils::getStringAfterSign($data, self::POST_MAIN_THREAD . '=>'), true); 356 | if (is_array($result)) self::setShared(array_merge(self::$shared, $result)); 357 | } 358 | } 359 | 360 | public static function post(string $data): void 361 | { 362 | fwrite(STDOUT, self::POST_THREAD . '=>' . $data . PHP_EOL); 363 | } 364 | 365 | public static function threadIsRunning(int $pid): bool 366 | { 367 | return isset(self::$threads[$pid]); 368 | } 369 | 370 | public static function killThread(int $pid): bool 371 | { 372 | if (isset(self::$threads[$pid])) { 373 | $thread = self::$threads[$pid]; 374 | 375 | if ($thread->isRunning()) { 376 | $thread->setStopped(true); 377 | return true; 378 | } 379 | } 380 | 381 | return false; 382 | } 383 | 384 | abstract public function onRun(): void; 385 | 386 | /** 387 | * @param array> $mode 388 | * @return Promise 389 | * @throws ReflectionException 390 | * @throws Throwable 391 | * @phpstan-param array> $mode 392 | * @phpstan-return Promise 393 | */ 394 | public function start(array $mode = DescriptorSpec::BASIC): Promise 395 | { 396 | return new Promise(function ($resolve, $reject) use ($mode): mixed { 397 | $idCall = $this->getCalledClassId(); 398 | 399 | $className = get_called_class(); 400 | 401 | $reflection = new ReflectionClass($className); 402 | 403 | $class = $reflection->getFileName(); 404 | 405 | $pathAutoLoad = __FILE__; 406 | $pathAutoLoad = str_replace( 407 | 'src\vennv\vapm\Thread.php', 408 | 'src\vendor\autoload.php', 409 | $pathAutoLoad 410 | ); 411 | 412 | $input = self::$inputs[$idCall]; 413 | 414 | if (is_string($input)) $input = '\'' . self::$inputs[$idCall] . '\''; 415 | 416 | if (is_callable($input) && $input instanceof Closure) { 417 | try { 418 | $input = Utils::closureToStringSafe($input); 419 | } catch (Throwable $e) { 420 | return $reject(new ThreadException($e->getMessage())); 421 | } 422 | } 423 | 424 | if (!is_string($input)) return $reject(new RuntimeException(Error::INPUT_MUST_BE_STRING_OR_CALLABLE)); 425 | 426 | $args = self::$args[$idCall]; 427 | 428 | if (is_array($args)) { 429 | foreach ($args as $key => $arg) { 430 | $tryToString = Utils::toStringAny($arg); 431 | $args[$key] = array_values($tryToString)[0]; 432 | FiberManager::wait(); 433 | } 434 | } else { 435 | throw new InvalidArgumentException('Expected $args to be an array or Traversable.'); 436 | } 437 | 438 | $args = '[' . implode(', ', array_map(function($item) { return '' . $item . ''; }, $args)) . ']'; 439 | $args = str_replace('"', '\'', $args); 440 | 441 | $command = PHP_BINARY . ' -r "require_once \'' . $pathAutoLoad . '\'; include \'' . $class . '\'; $input = ' . $input . '; $args = ' . $args . '; $class = new ' . static::class . '($input, $args); $class->onRun();"'; 442 | 443 | unset(self::$inputs[$idCall]); 444 | unset(self::$args[$idCall]); 445 | 446 | $process = proc_open($command, $mode, $pipes); 447 | 448 | $timeStart = microtime(true); 449 | while (microtime(true) - $timeStart <= 1) { 450 | FiberManager::wait(); // wait for 1 second 451 | } 452 | 453 | $output = ''; 454 | $error = ''; 455 | 456 | if (is_resource($process)) { 457 | stream_set_blocking($pipes[0], false); 458 | stream_set_blocking($pipes[1], false); 459 | stream_set_blocking($pipes[2], false); 460 | 461 | stream_set_write_buffer($pipes[0], 0); 462 | stream_set_write_buffer($pipes[1], 0); 463 | stream_set_write_buffer($pipes[2], 0); 464 | 465 | $data = json_encode(self::getDataMainThread()); 466 | 467 | if (is_string($data)) @fwrite($pipes[0], $data); 468 | @fclose($pipes[0]); 469 | 470 | $status = proc_get_status($process); 471 | $pid = $status['pid']; 472 | if (!isset(self::$threads[$pid])) { 473 | $this->setPid($pid); 474 | self::$threads[$pid] = $this; 475 | } 476 | 477 | $thread = self::$threads[$pid]; 478 | $thread->setExitCode($status['exitcode']); 479 | $thread->setRunning($status['running']); 480 | $thread->setSignaled($status['signaled']); 481 | $thread->setStopped($status['stopped']); 482 | while ($thread->isRunning()) { 483 | $status = proc_get_status($process); 484 | $thread->setExitCode($status['exitcode']); 485 | $thread->setRunning($status['running']); 486 | $thread->setSignaled($status['signaled']); 487 | $thread->setStopped($status['stopped']); 488 | 489 | if ($thread->isRunning()) { 490 | $read = [$pipes[1], $pipes[2]]; 491 | $write = null; 492 | $except = null; 493 | $timeout = 0; 494 | $n = stream_select($read, $write, $except, $timeout); 495 | if ($n === false) break; 496 | if ($n > 0) { 497 | foreach ($read as $stream) { 498 | if (!feof($stream)) { 499 | $data = stream_get_contents($stream); 500 | if ($data !== '') { 501 | $stream === $pipes[1] ? $output .= $data : $error .= $data; 502 | } 503 | } 504 | FiberManager::wait(); 505 | } 506 | } 507 | } elseif ($thread->isStopped() || $thread->isSignaled()) { 508 | proc_terminate($process); 509 | break; 510 | } else { 511 | proc_terminate($process); 512 | break; 513 | } 514 | FiberManager::wait(); 515 | } 516 | 517 | $outputStream = stream_get_contents($pipes[1]); 518 | $errorStream = stream_get_contents($pipes[2]); 519 | if (!is_bool($outputStream)) $output .= str_contains($output, $outputStream) ? '' : $outputStream; 520 | if (!is_bool($errorStream)) $error .= str_contains($error, $errorStream) ? '' : $errorStream; 521 | 522 | fclose($pipes[1]); 523 | fclose($pipes[2]); 524 | 525 | if ($error !== '') { 526 | return $reject(new ThreadException($error)); 527 | } else { 528 | if ($output !== '' && self::isPostMainThread($output)) self::loadSharedData($output); 529 | elseif ($output !== '' && self::isPostThread($output)) { 530 | $output = rtrim(Utils::getStringAfterSign($output, self::POST_THREAD . '=>')); 531 | } 532 | } 533 | } else { 534 | return $reject(new ThreadException(Error::UNABLE_START_THREAD)); 535 | } 536 | 537 | proc_close($process); 538 | unset(self::$threads[$this->getPid()]); 539 | return $resolve($output); 540 | }); 541 | } 542 | 543 | } -------------------------------------------------------------------------------- /src/vendor/composer/ClassLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see https://www.php-fig.org/psr/psr-0/ 41 | * @see https://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | /** @var \Closure(string):void */ 46 | private static $includeFile; 47 | 48 | /** @var ?string */ 49 | private $vendorDir; 50 | 51 | // PSR-4 52 | /** 53 | * @var array[] 54 | * @psalm-var array> 55 | */ 56 | private $prefixLengthsPsr4 = array(); 57 | /** 58 | * @var array[] 59 | * @psalm-var array> 60 | */ 61 | private $prefixDirsPsr4 = array(); 62 | /** 63 | * @var array[] 64 | * @psalm-var array 65 | */ 66 | private $fallbackDirsPsr4 = array(); 67 | 68 | // PSR-0 69 | /** 70 | * @var array[] 71 | * @psalm-var array> 72 | */ 73 | private $prefixesPsr0 = array(); 74 | /** 75 | * @var array[] 76 | * @psalm-var array 77 | */ 78 | private $fallbackDirsPsr0 = array(); 79 | 80 | /** @var bool */ 81 | private $useIncludePath = false; 82 | 83 | /** 84 | * @var string[] 85 | * @psalm-var array 86 | */ 87 | private $classMap = array(); 88 | 89 | /** @var bool */ 90 | private $classMapAuthoritative = false; 91 | 92 | /** 93 | * @var bool[] 94 | * @psalm-var array 95 | */ 96 | private $missingClasses = array(); 97 | 98 | /** @var ?string */ 99 | private $apcuPrefix; 100 | 101 | /** 102 | * @var self[] 103 | */ 104 | private static $registeredLoaders = array(); 105 | 106 | /** 107 | * @param ?string $vendorDir 108 | */ 109 | public function __construct($vendorDir = null) 110 | { 111 | $this->vendorDir = $vendorDir; 112 | self::initializeIncludeClosure(); 113 | } 114 | 115 | /** 116 | * @return string[] 117 | */ 118 | public function getPrefixes() 119 | { 120 | if (!empty($this->prefixesPsr0)) { 121 | return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); 122 | } 123 | 124 | return array(); 125 | } 126 | 127 | /** 128 | * @return array[] 129 | * @psalm-return array> 130 | */ 131 | public function getPrefixesPsr4() 132 | { 133 | return $this->prefixDirsPsr4; 134 | } 135 | 136 | /** 137 | * @return array[] 138 | * @psalm-return array 139 | */ 140 | public function getFallbackDirs() 141 | { 142 | return $this->fallbackDirsPsr0; 143 | } 144 | 145 | /** 146 | * @return array[] 147 | * @psalm-return array 148 | */ 149 | public function getFallbackDirsPsr4() 150 | { 151 | return $this->fallbackDirsPsr4; 152 | } 153 | 154 | /** 155 | * @return string[] Array of classname => path 156 | * @psalm-return array 157 | */ 158 | public function getClassMap() 159 | { 160 | return $this->classMap; 161 | } 162 | 163 | /** 164 | * @param string[] $classMap Class to filename map 165 | * @psalm-param array $classMap 166 | * 167 | * @return void 168 | */ 169 | public function addClassMap(array $classMap) 170 | { 171 | if ($this->classMap) { 172 | $this->classMap = array_merge($this->classMap, $classMap); 173 | } else { 174 | $this->classMap = $classMap; 175 | } 176 | } 177 | 178 | /** 179 | * Registers a set of PSR-0 directories for a given prefix, either 180 | * appending or prepending to the ones previously set for this prefix. 181 | * 182 | * @param string $prefix The prefix 183 | * @param string[]|string $paths The PSR-0 root directories 184 | * @param bool $prepend Whether to prepend the directories 185 | * 186 | * @return void 187 | */ 188 | public function add($prefix, $paths, $prepend = false) 189 | { 190 | if (!$prefix) { 191 | if ($prepend) { 192 | $this->fallbackDirsPsr0 = array_merge( 193 | (array) $paths, 194 | $this->fallbackDirsPsr0 195 | ); 196 | } else { 197 | $this->fallbackDirsPsr0 = array_merge( 198 | $this->fallbackDirsPsr0, 199 | (array) $paths 200 | ); 201 | } 202 | 203 | return; 204 | } 205 | 206 | $first = $prefix[0]; 207 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 208 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 209 | 210 | return; 211 | } 212 | if ($prepend) { 213 | $this->prefixesPsr0[$first][$prefix] = array_merge( 214 | (array) $paths, 215 | $this->prefixesPsr0[$first][$prefix] 216 | ); 217 | } else { 218 | $this->prefixesPsr0[$first][$prefix] = array_merge( 219 | $this->prefixesPsr0[$first][$prefix], 220 | (array) $paths 221 | ); 222 | } 223 | } 224 | 225 | /** 226 | * Registers a set of PSR-4 directories for a given namespace, either 227 | * appending or prepending to the ones previously set for this namespace. 228 | * 229 | * @param string $prefix The prefix/namespace, with trailing '\\' 230 | * @param string[]|string $paths The PSR-4 base directories 231 | * @param bool $prepend Whether to prepend the directories 232 | * 233 | * @throws \InvalidArgumentException 234 | * 235 | * @return void 236 | */ 237 | public function addPsr4($prefix, $paths, $prepend = false) 238 | { 239 | if (!$prefix) { 240 | // Register directories for the root namespace. 241 | if ($prepend) { 242 | $this->fallbackDirsPsr4 = array_merge( 243 | (array) $paths, 244 | $this->fallbackDirsPsr4 245 | ); 246 | } else { 247 | $this->fallbackDirsPsr4 = array_merge( 248 | $this->fallbackDirsPsr4, 249 | (array) $paths 250 | ); 251 | } 252 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 253 | // Register directories for a new namespace. 254 | $length = strlen($prefix); 255 | if ('\\' !== $prefix[$length - 1]) { 256 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 257 | } 258 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 259 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 260 | } elseif ($prepend) { 261 | // Prepend directories for an already registered namespace. 262 | $this->prefixDirsPsr4[$prefix] = array_merge( 263 | (array) $paths, 264 | $this->prefixDirsPsr4[$prefix] 265 | ); 266 | } else { 267 | // Append directories for an already registered namespace. 268 | $this->prefixDirsPsr4[$prefix] = array_merge( 269 | $this->prefixDirsPsr4[$prefix], 270 | (array) $paths 271 | ); 272 | } 273 | } 274 | 275 | /** 276 | * Registers a set of PSR-0 directories for a given prefix, 277 | * replacing any others previously set for this prefix. 278 | * 279 | * @param string $prefix The prefix 280 | * @param string[]|string $paths The PSR-0 base directories 281 | * 282 | * @return void 283 | */ 284 | public function set($prefix, $paths) 285 | { 286 | if (!$prefix) { 287 | $this->fallbackDirsPsr0 = (array) $paths; 288 | } else { 289 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 290 | } 291 | } 292 | 293 | /** 294 | * Registers a set of PSR-4 directories for a given namespace, 295 | * replacing any others previously set for this namespace. 296 | * 297 | * @param string $prefix The prefix/namespace, with trailing '\\' 298 | * @param string[]|string $paths The PSR-4 base directories 299 | * 300 | * @throws \InvalidArgumentException 301 | * 302 | * @return void 303 | */ 304 | public function setPsr4($prefix, $paths) 305 | { 306 | if (!$prefix) { 307 | $this->fallbackDirsPsr4 = (array) $paths; 308 | } else { 309 | $length = strlen($prefix); 310 | if ('\\' !== $prefix[$length - 1]) { 311 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 312 | } 313 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 314 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 315 | } 316 | } 317 | 318 | /** 319 | * Turns on searching the include path for class files. 320 | * 321 | * @param bool $useIncludePath 322 | * 323 | * @return void 324 | */ 325 | public function setUseIncludePath($useIncludePath) 326 | { 327 | $this->useIncludePath = $useIncludePath; 328 | } 329 | 330 | /** 331 | * Can be used to check if the autoloader uses the include path to check 332 | * for classes. 333 | * 334 | * @return bool 335 | */ 336 | public function getUseIncludePath() 337 | { 338 | return $this->useIncludePath; 339 | } 340 | 341 | /** 342 | * Turns off searching the prefix and fallback directories for classes 343 | * that have not been registered with the class map. 344 | * 345 | * @param bool $classMapAuthoritative 346 | * 347 | * @return void 348 | */ 349 | public function setClassMapAuthoritative($classMapAuthoritative) 350 | { 351 | $this->classMapAuthoritative = $classMapAuthoritative; 352 | } 353 | 354 | /** 355 | * Should class lookup fail if not found in the current class map? 356 | * 357 | * @return bool 358 | */ 359 | public function isClassMapAuthoritative() 360 | { 361 | return $this->classMapAuthoritative; 362 | } 363 | 364 | /** 365 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 366 | * 367 | * @param string|null $apcuPrefix 368 | * 369 | * @return void 370 | */ 371 | public function setApcuPrefix($apcuPrefix) 372 | { 373 | $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 374 | } 375 | 376 | /** 377 | * The APCu prefix in use, or null if APCu caching is not enabled. 378 | * 379 | * @return string|null 380 | */ 381 | public function getApcuPrefix() 382 | { 383 | return $this->apcuPrefix; 384 | } 385 | 386 | /** 387 | * Registers this instance as an autoloader. 388 | * 389 | * @param bool $prepend Whether to prepend the autoloader or not 390 | * 391 | * @return void 392 | */ 393 | public function register($prepend = false) 394 | { 395 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 396 | 397 | if (null === $this->vendorDir) { 398 | return; 399 | } 400 | 401 | if ($prepend) { 402 | self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; 403 | } else { 404 | unset(self::$registeredLoaders[$this->vendorDir]); 405 | self::$registeredLoaders[$this->vendorDir] = $this; 406 | } 407 | } 408 | 409 | /** 410 | * Unregisters this instance as an autoloader. 411 | * 412 | * @return void 413 | */ 414 | public function unregister() 415 | { 416 | spl_autoload_unregister(array($this, 'loadClass')); 417 | 418 | if (null !== $this->vendorDir) { 419 | unset(self::$registeredLoaders[$this->vendorDir]); 420 | } 421 | } 422 | 423 | /** 424 | * Loads the given class or interface. 425 | * 426 | * @param string $class The name of the class 427 | * @return true|null True if loaded, null otherwise 428 | */ 429 | public function loadClass($class) 430 | { 431 | if ($file = $this->findFile($class)) { 432 | $includeFile = self::$includeFile; 433 | $includeFile($file); 434 | 435 | return true; 436 | } 437 | 438 | return null; 439 | } 440 | 441 | /** 442 | * Finds the path to the file where the class is defined. 443 | * 444 | * @param string $class The name of the class 445 | * 446 | * @return string|false The path if found, false otherwise 447 | */ 448 | public function findFile($class) 449 | { 450 | // class map lookup 451 | if (isset($this->classMap[$class])) { 452 | return $this->classMap[$class]; 453 | } 454 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 455 | return false; 456 | } 457 | if (null !== $this->apcuPrefix) { 458 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 459 | if ($hit) { 460 | return $file; 461 | } 462 | } 463 | 464 | $file = $this->findFileWithExtension($class, '.php'); 465 | 466 | // Search for Hack files if we are running on HHVM 467 | if (false === $file && defined('HHVM_VERSION')) { 468 | $file = $this->findFileWithExtension($class, '.hh'); 469 | } 470 | 471 | if (null !== $this->apcuPrefix) { 472 | apcu_add($this->apcuPrefix.$class, $file); 473 | } 474 | 475 | if (false === $file) { 476 | // Remember that this class does not exist. 477 | $this->missingClasses[$class] = true; 478 | } 479 | 480 | return $file; 481 | } 482 | 483 | /** 484 | * Returns the currently registered loaders indexed by their corresponding vendor directories. 485 | * 486 | * @return self[] 487 | */ 488 | public static function getRegisteredLoaders() 489 | { 490 | return self::$registeredLoaders; 491 | } 492 | 493 | /** 494 | * @param string $class 495 | * @param string $ext 496 | * @return string|false 497 | */ 498 | private function findFileWithExtension($class, $ext) 499 | { 500 | // PSR-4 lookup 501 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 502 | 503 | $first = $class[0]; 504 | if (isset($this->prefixLengthsPsr4[$first])) { 505 | $subPath = $class; 506 | while (false !== $lastPos = strrpos($subPath, '\\')) { 507 | $subPath = substr($subPath, 0, $lastPos); 508 | $search = $subPath . '\\'; 509 | if (isset($this->prefixDirsPsr4[$search])) { 510 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 511 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 512 | if (file_exists($file = $dir . $pathEnd)) { 513 | return $file; 514 | } 515 | } 516 | } 517 | } 518 | } 519 | 520 | // PSR-4 fallback dirs 521 | foreach ($this->fallbackDirsPsr4 as $dir) { 522 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 523 | return $file; 524 | } 525 | } 526 | 527 | // PSR-0 lookup 528 | if (false !== $pos = strrpos($class, '\\')) { 529 | // namespaced class name 530 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 531 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 532 | } else { 533 | // PEAR-like class name 534 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 535 | } 536 | 537 | if (isset($this->prefixesPsr0[$first])) { 538 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 539 | if (0 === strpos($class, $prefix)) { 540 | foreach ($dirs as $dir) { 541 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 542 | return $file; 543 | } 544 | } 545 | } 546 | } 547 | } 548 | 549 | // PSR-0 fallback dirs 550 | foreach ($this->fallbackDirsPsr0 as $dir) { 551 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 552 | return $file; 553 | } 554 | } 555 | 556 | // PSR-0 include paths. 557 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 558 | return $file; 559 | } 560 | 561 | return false; 562 | } 563 | 564 | /** 565 | * @return void 566 | */ 567 | private static function initializeIncludeClosure() 568 | { 569 | if (self::$includeFile !== null) { 570 | return; 571 | } 572 | 573 | /** 574 | * Scope isolated include. 575 | * 576 | * Prevents access to $this/self from included files. 577 | * 578 | * @param string $file 579 | * @return void 580 | */ 581 | self::$includeFile = \Closure::bind(static function($file) { 582 | include $file; 583 | }, null, null); 584 | } 585 | } 586 | --------------------------------------------------------------------------------