From 51dcf959437431cc96aa11208da3727759186ab0 Mon Sep 17 00:00:00 2001 From: VennV <111500380+VennDev@users.noreply.github.com> Date: Fri, 16 Aug 2024 23:29:41 +0700 Subject: [PATCH] Fix! --- src/vendor/autoload.php | 25 + src/vendor/composer/ClassLoader.php | 585 +++++++++++++++++ src/vendor/composer/InstalledVersions.php | 359 +++++++++++ src/vendor/composer/LICENSE | 21 + src/vendor/composer/autoload_classmap.php | 10 + src/vendor/composer/autoload_namespaces.php | 9 + src/vendor/composer/autoload_psr4.php | 10 + src/vendor/composer/autoload_real.php | 36 ++ src/vendor/composer/autoload_static.php | 36 ++ src/vendor/composer/installed.json | 5 + src/vendor/composer/installed.php | 23 + src/vennv/vapm/AssumptionFailedError.php | 44 ++ src/vennv/vapm/Async.php | 87 +++ src/vennv/vapm/ChildCoroutine.php | 74 +++ src/vennv/vapm/CoroutineGen.php | 181 ++++++ src/vennv/vapm/CoroutineScope.php | 159 +++++ src/vennv/vapm/CoroutineThread.php | 58 ++ src/vennv/vapm/Deferred.php | 154 +++++ src/vennv/vapm/DeferredException.php | 44 ++ src/vennv/vapm/DescriptorSpec.php | 71 +++ src/vennv/vapm/Dispatchers.php | 43 ++ src/vennv/vapm/Error.php | 59 ++ src/vennv/vapm/EventLoop.php | 223 +++++++ src/vennv/vapm/FiberManager.php | 53 ++ src/vennv/vapm/GeneratorManager.php | 48 ++ src/vennv/vapm/Info.php | 37 ++ src/vennv/vapm/Internet.php | 190 ++++++ src/vennv/vapm/InternetException.php | 44 ++ src/vennv/vapm/InternetRequestResult.php | 82 +++ src/vennv/vapm/MacroTask.php | 79 +++ src/vennv/vapm/MicroTask.php | 75 +++ src/vennv/vapm/PHPUtils.php | 147 +++++ src/vennv/vapm/Promise.php | 654 ++++++++++++++++++++ src/vennv/vapm/PromiseResult.php | 58 ++ src/vennv/vapm/SampleMacro.php | 128 ++++ src/vennv/vapm/Settings.php | 35 ++ src/vennv/vapm/StatusPromise.php | 33 + src/vennv/vapm/StatusThread.php | 93 +++ src/vennv/vapm/Stream.php | 253 ++++++++ src/vennv/vapm/System.php | 293 +++++++++ src/vennv/vapm/Thread.php | 517 ++++++++++++++++ src/vennv/vapm/ThreadException.php | 44 ++ src/vennv/vapm/Utils.php | 165 +++++ src/vennv/vapm/VapmPMMP.php | 53 ++ src/vennv/vapm/Work.php | 158 +++++ src/vennv/vapm/Worker.php | 280 +++++++++ src/vennv/vapm/utils/Property.php | 52 ++ src/vennv/vapm/utils/Utils.php | 319 ++++++++++ 48 files changed, 6206 insertions(+) create mode 100644 src/vendor/autoload.php create mode 100644 src/vendor/composer/ClassLoader.php create mode 100644 src/vendor/composer/InstalledVersions.php create mode 100644 src/vendor/composer/LICENSE create mode 100644 src/vendor/composer/autoload_classmap.php create mode 100644 src/vendor/composer/autoload_namespaces.php create mode 100644 src/vendor/composer/autoload_psr4.php create mode 100644 src/vendor/composer/autoload_real.php create mode 100644 src/vendor/composer/autoload_static.php create mode 100644 src/vendor/composer/installed.json create mode 100644 src/vendor/composer/installed.php create mode 100644 src/vennv/vapm/AssumptionFailedError.php create mode 100644 src/vennv/vapm/Async.php create mode 100644 src/vennv/vapm/ChildCoroutine.php create mode 100644 src/vennv/vapm/CoroutineGen.php create mode 100644 src/vennv/vapm/CoroutineScope.php create mode 100644 src/vennv/vapm/CoroutineThread.php create mode 100644 src/vennv/vapm/Deferred.php create mode 100644 src/vennv/vapm/DeferredException.php create mode 100644 src/vennv/vapm/DescriptorSpec.php create mode 100644 src/vennv/vapm/Dispatchers.php create mode 100644 src/vennv/vapm/Error.php create mode 100644 src/vennv/vapm/EventLoop.php create mode 100644 src/vennv/vapm/FiberManager.php create mode 100644 src/vennv/vapm/GeneratorManager.php create mode 100644 src/vennv/vapm/Info.php create mode 100644 src/vennv/vapm/Internet.php create mode 100644 src/vennv/vapm/InternetException.php create mode 100644 src/vennv/vapm/InternetRequestResult.php create mode 100644 src/vennv/vapm/MacroTask.php create mode 100644 src/vennv/vapm/MicroTask.php create mode 100644 src/vennv/vapm/PHPUtils.php create mode 100644 src/vennv/vapm/Promise.php create mode 100644 src/vennv/vapm/PromiseResult.php create mode 100644 src/vennv/vapm/SampleMacro.php create mode 100644 src/vennv/vapm/Settings.php create mode 100644 src/vennv/vapm/StatusPromise.php create mode 100644 src/vennv/vapm/StatusThread.php create mode 100644 src/vennv/vapm/Stream.php create mode 100644 src/vennv/vapm/System.php create mode 100644 src/vennv/vapm/Thread.php create mode 100644 src/vennv/vapm/ThreadException.php create mode 100644 src/vennv/vapm/Utils.php create mode 100644 src/vennv/vapm/VapmPMMP.php create mode 100644 src/vennv/vapm/Work.php create mode 100644 src/vennv/vapm/Worker.php create mode 100644 src/vennv/vapm/utils/Property.php create mode 100644 src/vennv/vapm/utils/Utils.php diff --git a/src/vendor/autoload.php b/src/vendor/autoload.php new file mode 100644 index 000000000..5c95c078d --- /dev/null +++ b/src/vendor/autoload.php @@ -0,0 +1,25 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var ?string */ + private $vendorDir; + + // PSR-4 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array[] + * @psalm-var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixesPsr0 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var string[] + * @psalm-var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var bool[] + * @psalm-var array + */ + private $missingClasses = array(); + + /** @var ?string */ + private $apcuPrefix; + + /** + * @var self[] + */ + private static $registeredLoaders = array(); + + /** + * @param ?string $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return string[] + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array[] + * @psalm-return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return string[] Array of classname => path + * @psalm-return array + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param string[] $classMap Class to filename map + * @psalm-param array $classMap + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders indexed by their corresponding vendor directories. + * + * @return self[] + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/src/vendor/composer/InstalledVersions.php b/src/vendor/composer/InstalledVersions.php new file mode 100644 index 000000000..51e734a77 --- /dev/null +++ b/src/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @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 + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @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 + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @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 + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @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 + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @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. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @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. + * @return array[] + * @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} + */ + public static function getRawData() + { + @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); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @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 + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @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 */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @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 */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/src/vendor/composer/LICENSE b/src/vendor/composer/LICENSE new file mode 100644 index 000000000..f27399a04 --- /dev/null +++ b/src/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/src/vendor/composer/autoload_classmap.php b/src/vendor/composer/autoload_classmap.php new file mode 100644 index 000000000..0fb0a2c19 --- /dev/null +++ b/src/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $vendorDir . '/composer/InstalledVersions.php', +); diff --git a/src/vendor/composer/autoload_namespaces.php b/src/vendor/composer/autoload_namespaces.php new file mode 100644 index 000000000..15a2ff3ad --- /dev/null +++ b/src/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($baseDir . '/vennv/vapm'), +); diff --git a/src/vendor/composer/autoload_real.php b/src/vendor/composer/autoload_real.php new file mode 100644 index 000000000..f8ca5edfc --- /dev/null +++ b/src/vendor/composer/autoload_real.php @@ -0,0 +1,36 @@ +register(true); + + return $loader; + } +} diff --git a/src/vendor/composer/autoload_static.php b/src/vendor/composer/autoload_static.php new file mode 100644 index 000000000..46ecafa0d --- /dev/null +++ b/src/vendor/composer/autoload_static.php @@ -0,0 +1,36 @@ + + array ( + 'vennv\\vapm\\' => 11, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'vennv\\vapm\\' => + array ( + 0 => __DIR__ . '/../..' . '/vennv/vapm', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitfd8e931fecab81cb0db0789466a481c5::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitfd8e931fecab81cb0db0789466a481c5::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInitfd8e931fecab81cb0db0789466a481c5::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/src/vendor/composer/installed.json b/src/vendor/composer/installed.json new file mode 100644 index 000000000..87fda747e --- /dev/null +++ b/src/vendor/composer/installed.json @@ -0,0 +1,5 @@ +{ + "packages": [], + "dev": true, + "dev-package-names": [] +} diff --git a/src/vendor/composer/installed.php b/src/vendor/composer/installed.php new file mode 100644 index 000000000..98ce6f006 --- /dev/null +++ b/src/vendor/composer/installed.php @@ -0,0 +1,23 @@ + array( + 'name' => 'nam/src', + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => NULL, + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + 'nam/src' => array( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => NULL, + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/src/vennv/vapm/AssumptionFailedError.php b/src/vennv/vapm/AssumptionFailedError.php new file mode 100644 index 000000000..76c9f3a1f --- /dev/null +++ b/src/vennv/vapm/AssumptionFailedError.php @@ -0,0 +1,44 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use TypeError; + +final class AssumptionFailedError extends TypeError +{ + + public function __construct( + protected string $errorMessage, + protected int $errorCode = 0 + ) + { + parent::__construct($this->errorMessage, $this->errorCode); + } + + public function __toString(): string + { + return __CLASS__ . ": [$this->errorCode]: $this->errorMessage\n"; + } + +} diff --git a/src/vennv/vapm/Async.php b/src/vennv/vapm/Async.php new file mode 100644 index 000000000..f81a08ddb --- /dev/null +++ b/src/vennv/vapm/Async.php @@ -0,0 +1,87 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use vennv\vapm\utils\Utils; +use Throwable; +use RuntimeException; +use function is_callable; + +interface AsyncInterface +{ + + public function getId(): int; + + /** + * @throws Throwable + */ + public static function await(mixed $await): mixed; + +} + +final class Async implements AsyncInterface +{ + + private Promise $promise; + + /** + * @throws Throwable + */ + public function __construct(callable $callback) + { + $promise = new Promise($callback, true); + $this->promise = $promise; + } + + public function getId(): int + { + return $this->promise->getId(); + } + + /** + * @throws Throwable + */ + public static function await(mixed $await): mixed + { + if (!Utils::isClass(Async::class)) throw new RuntimeException(Error::ASYNC_AWAIT_MUST_CALL_IN_ASYNC_FUNCTION); + + $result = $await; + + if (is_callable($await)) $await = new Async($await); + + if ($await instanceof Promise || $await instanceof Async) { + $return = EventLoop::getReturn($await->getId()); + + while ($return === null) { + $return = EventLoop::getReturn($await->getId()); + FiberManager::wait(); + } + + if ($return instanceof Promise) $result = $return->getResult(); + } + + return $result; + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/ChildCoroutine.php b/src/vennv/vapm/ChildCoroutine.php new file mode 100644 index 000000000..bdbfbca41 --- /dev/null +++ b/src/vennv/vapm/ChildCoroutine.php @@ -0,0 +1,74 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use Generator; +use Exception; + +interface ChildCoroutineInterface +{ + + public function setException(Exception $exception): void; + + public function run(): void; + + public function isFinished(): bool; + + public function getReturn(): mixed; + +} + +final class ChildCoroutine implements ChildCoroutineInterface +{ + + protected Generator $coroutine; + + protected Exception $exception; + + public function __construct(Generator $coroutine) + { + $this->coroutine = $coroutine; + } + + public function setException(Exception $exception): void + { + $this->exception = $exception; + } + + public function run(): void + { + $this->coroutine->send($this->coroutine->current()); + } + + public function isFinished(): bool + { + return !$this->coroutine->valid(); + } + + public function getReturn(): mixed + { + return $this->coroutine->getReturn(); + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/CoroutineGen.php b/src/vennv/vapm/CoroutineGen.php new file mode 100644 index 000000000..b9777f1c9 --- /dev/null +++ b/src/vennv/vapm/CoroutineGen.php @@ -0,0 +1,181 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use Closure; +use ReflectionException; +use SplQueue; +use Generator; +use Throwable; +use function call_user_func; + +interface CoroutineGenInterface +{ + + /** + * @param mixed ...$coroutines + * @return void + * + * This is a blocking function that runs all the coroutines passed to it. + */ + public static function runBlocking(mixed ...$coroutines): void; + + /** + * @param callable $callback + * @param int $times + * @return Closure + * + * This is a generator that runs a callback function a specified amount of times. + */ + public static function repeat(callable $callback, int $times): Closure; + + /** + * @param int $milliseconds + * @return Generator + * + * This is a generator that yields for a specified amount of milliseconds. + */ + public static function delay(int $milliseconds): Generator; + + /** + * @param mixed ...$callback + * @return CoroutineScope + * + * This is a generator that runs a callback function. + */ + public static function launch(mixed ...$callback): CoroutineScope; + +} + +final class CoroutineGen implements CoroutineGenInterface +{ + + protected static ?SplQueue $taskQueue = null; + + /** + * @param mixed ...$coroutines + * @return void + * @throws Throwable + */ + public static function runBlocking(mixed ...$coroutines): void + { + foreach ($coroutines as $coroutine) { + if (is_callable($coroutine)) $coroutine = call_user_func($coroutine); + + if ($coroutine instanceof CoroutineScope) { + self::schedule($coroutine); + } else if ($coroutine instanceof Generator) { + self::schedule(new ChildCoroutine($coroutine)); + } else { + call_user_func(fn() => $coroutine); + } + } + + self::run(); + } + + /** + * @param mixed ...$coroutines + * @return Closure + */ + private static function processCoroutine(mixed ...$coroutines): Closure + { + return function () use ($coroutines): void { + foreach ($coroutines as $coroutine) { + if ($coroutine instanceof CoroutineScope) { + self::schedule($coroutine); + } else if (is_callable($coroutine)) { + $coroutine = call_user_func($coroutine); + } + + !$coroutine instanceof Generator ? call_user_func(fn() => $coroutine) : self::schedule(new ChildCoroutine($coroutine)); + } + + self::run(); + }; + } + + public static function repeat(callable $callback, int $times): Closure + { + for ($i = 0; $i <= $times; $i++) if (call_user_func($callback) instanceof Generator) $callback = self::processCoroutine($callback); + return fn() => null; + } + + public static function delay(int $milliseconds): Generator + { + for ($i = 0; $i < GeneratorManager::calculateSeconds((int)($milliseconds/5)); $i++) yield; + } + + /** + * @throws ReflectionException + * @throws Throwable + */ + public static function launch(mixed ...$callback): CoroutineScope + { + $coroutine = new CoroutineScope(); + $coroutine->launch(...$callback); + + return $coroutine; + } + + private static function schedule(ChildCoroutine|CoroutineScope $childCoroutine): void + { + if (self::$taskQueue === null) self::$taskQueue = new SplQueue(); + self::$taskQueue->enqueue($childCoroutine); + } + + /** + * @throws ReflectionException + * @throws Throwable + */ + private static function run(): void + { + new Async(function (): void { + try { + $fnWait = function (&$i) { + FiberManager::wait(); + $i = 0; + }; + $i = 0; + while (self::$taskQueue?->isEmpty() === false) { + $coroutine = self::$taskQueue->dequeue(); + + if ($coroutine instanceof ChildCoroutine) { + if ($i++ >= 5) $fnWait($i); + $coroutine->run(); + if (!$coroutine->isFinished()) self::schedule($coroutine); + } + + if ($coroutine instanceof CoroutineScope) { + Async::await($coroutine->run()); + if (!$coroutine->isFinished()) self::schedule($coroutine); + } + } + } catch (Throwable $e) { + echo $e->getMessage(); + } + }); + } + +} diff --git a/src/vennv/vapm/CoroutineScope.php b/src/vennv/vapm/CoroutineScope.php new file mode 100644 index 000000000..a701fef95 --- /dev/null +++ b/src/vennv/vapm/CoroutineScope.php @@ -0,0 +1,159 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use Generator; +use ReflectionException; +use SplQueue; +use Throwable; +use function call_user_func; +use function is_callable; + +interface CoroutineScopeInterface +{ + + /** + * @return bool + * + * This function checks if the coroutine has finished. + */ + public function isFinished(): bool; + + /** + * @return bool + * + * This function checks if the coroutine has been cancelled. + */ + public function isCancelled(): bool; + + /** + * This function cancels the coroutine. + */ + public function cancel(): void; + + /** + * @param mixed ...$callbacks + * @throws ReflectionException + * @throws Throwable + * + * This function launches a coroutine. + */ + public function launch(mixed ...$callbacks): void; + + /** + * This function runs the coroutine. + */ + public function run(): Async; + +} + +final class CoroutineScope implements CoroutineScopeInterface +{ + + protected static ?SplQueue $taskQueue = null; + + protected static bool $cancelled = false; + + protected static bool $finished = false; + + protected static string $dispatcher; + + public function __construct(string $dispatcher = Dispatchers::DEFAULT) + { + self::$dispatcher = $dispatcher; + } + + public function isFinished(): bool + { + return self::$finished; + } + + public function isCancelled(): bool + { + return self::$cancelled; + } + + public function cancel(): void + { + self::$cancelled = true; + } + + /** + * @throws ReflectionException + * @throws Throwable + */ + public function launch(mixed ...$callbacks): void + { + foreach ($callbacks as $callback) { + if ($callback instanceof CoroutineScope) { + self::schedule($callback); + } else if (is_callable($callback)) { + if (self::$dispatcher === Dispatchers::IO) { + $thread = new CoroutineThread($callback); + $thread->start(); + } + + if (self::$dispatcher === Dispatchers::DEFAULT) $callback = call_user_func($callback); + } else { + $callback = fn() => $callback; + } + + if ($callback instanceof Generator) self::schedule(new ChildCoroutine($callback)); + } + } + + /** + * @throws ReflectionException + * @throws Throwable + */ + public function run(): Async + { + return new Async(function (): void { + if (self::$taskQueue?->isEmpty() === false && !self::$cancelled) { + $coroutine = self::$taskQueue->dequeue(); + + if ($coroutine instanceof ChildCoroutine) { + $coroutine->run(); + + if (!$coroutine->isFinished()) self::schedule($coroutine); + } + + if ($coroutine instanceof CoroutineScope) { + Async::await($coroutine->run()); + + if (!$coroutine->isFinished()) self::schedule($coroutine); + } + } else { + self::$finished = true; + } + }); + } + + private static function schedule(ChildCoroutine|CoroutineScope $childCoroutine): void + { + if (self::$taskQueue === null) self::$taskQueue = new SplQueue(); + self::$taskQueue->enqueue($childCoroutine); + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/CoroutineThread.php b/src/vennv/vapm/CoroutineThread.php new file mode 100644 index 000000000..bdf68a495 --- /dev/null +++ b/src/vennv/vapm/CoroutineThread.php @@ -0,0 +1,58 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +interface CoroutineThreadInterface +{ + + /** + * @return void + * + * This function runs the callback function for the thread. + */ + public function onRun(): void; + +} + +final class CoroutineThread extends Thread implements CoroutineThreadInterface +{ + + private mixed $callback; + + public function __construct(callable $callback) + { + $this->callback = $callback; + parent::__construct($callback); + } + + public function onRun(): void + { + if (is_callable($this->callback)) { + $callback = call_user_func($this->callback); + if (!is_string($callback)) $callback = serialize($callback); + self::post($callback); + } + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/Deferred.php b/src/vennv/vapm/Deferred.php new file mode 100644 index 000000000..a5fafcd3e --- /dev/null +++ b/src/vennv/vapm/Deferred.php @@ -0,0 +1,154 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use Generator; + +interface DeferredInterface +{ + + /** + * This method is used to get the result of the deferred. + */ + public function await(): Generator; + + /** + * @param DeferredInterface ...$deferreds + * @return Generator + * + * This method is used to get all the results of the deferred. + */ + public static function awaitAll(DeferredInterface ...$deferreds): Generator; + + /** + * @param DeferredInterface ...$deferreds + * @return Generator + * + * This method is used to get the first result of the deferred. + */ + public static function awaitAny(DeferredInterface ...$deferreds): Generator; + + /** + * This method is used to check if the deferred is finished. + */ + public function isFinished(): bool; + + /** + * This method is used to get the child coroutine of the deferred. + */ + public function getChildCoroutine(): ChildCoroutine; + + /** + * This method is used to get the result of the deferred. + */ + public function getComplete(): mixed; + +} + +final class Deferred implements DeferredInterface +{ + + protected mixed $return = null; + + protected ChildCoroutine $childCoroutine; + + public function __construct(callable $callback) + { + $generator = call_user_func($callback); + $generator instanceof Generator ? $this->childCoroutine = new ChildCoroutine($generator) : throw new DeferredException(Error::DEFERRED_CALLBACK_MUST_RETURN_GENERATOR); + } + + public function await(): Generator + { + while (!$this->childCoroutine->isFinished()) { + $this->childCoroutine->run(); + yield; + } + + $this->return = $this->childCoroutine->getReturn(); + + return $this->return; + } + + public static function awaitAll(DeferredInterface ...$deferreds): Generator + { + $result = []; + + while (count($result) <= count($deferreds)) { + foreach ($deferreds as $index => $deferred) { + $childCoroutine = $deferred->getChildCoroutine(); + + if ($childCoroutine->isFinished()) { + $result[] = $childCoroutine->getReturn(); + unset($deferreds[$index]); + } else { + $childCoroutine->run(); + } + } + + yield; + } + + return $result; + } + + public static function awaitAny(DeferredInterface ...$deferreds): Generator + { + $result = []; + + while (count($result) <= count($deferreds)) { + foreach ($deferreds as $deferred) { + $childCoroutine = $deferred->getChildCoroutine(); + + if ($childCoroutine->isFinished()) { + $result[] = $childCoroutine->getReturn(); + $deferreds = []; + break; + } else { + $childCoroutine->run(); + } + } + + yield; + } + + return $result; + } + + public function isFinished(): bool + { + return $this->childCoroutine->isFinished(); + } + + public function getChildCoroutine(): ChildCoroutine + { + return $this->childCoroutine; + } + + public function getComplete(): mixed + { + return $this->return; + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/DeferredException.php b/src/vennv/vapm/DeferredException.php new file mode 100644 index 000000000..d2e34e1bc --- /dev/null +++ b/src/vennv/vapm/DeferredException.php @@ -0,0 +1,44 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use TypeError; + +final class DeferredException extends TypeError +{ + + public function __construct( + protected string $errorMessage, + protected int $errorCode = 0 + ) + { + parent::__construct($this->errorMessage, $this->errorCode); + } + + public function __toString(): string + { + return __CLASS__ . ": [$this->errorCode]: $this->errorMessage\n"; + } + +} diff --git a/src/vennv/vapm/DescriptorSpec.php b/src/vennv/vapm/DescriptorSpec.php new file mode 100644 index 000000000..ca73a6f73 --- /dev/null +++ b/src/vennv/vapm/DescriptorSpec.php @@ -0,0 +1,71 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +final class DescriptorSpec +{ + + public const BASIC = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'] + ]; + + public const IGNORE_STDIN = [ + 0 => ['file', '/dev/null', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'] + ]; + + public const IGNORE_STDOUT = [ + 0 => ['pipe', 'r'], + 1 => ['file', '/dev/null', 'w'], + 2 => ['pipe', 'w'] + ]; + + public const IGNORE_STDERR = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['file', '/dev/null', 'w'] + ]; + + public const IGNORE_STDOUT_AND_STDERR = [ + 0 => ['pipe', 'r'], + 1 => ['file', '/dev/null', 'w'], + 2 => ['file', '/dev/null', 'w'] + ]; + + public const IGNORE_STDIN_AND_STDERR = [ + 0 => ['file', '/dev/null', 'r'], + 1 => ['pipe', 'w'], + 2 => ['file', '/dev/null', 'w'] + ]; + + public const IGNORE_STDIN_AND_STDOUT = [ + 0 => ['file', '/dev/null', 'r'], + 1 => ['file', '/dev/null', 'w'], + 2 => ['pipe', 'w'] + ]; + +} \ No newline at end of file diff --git a/src/vennv/vapm/Dispatchers.php b/src/vennv/vapm/Dispatchers.php new file mode 100644 index 000000000..d5265a79b --- /dev/null +++ b/src/vennv/vapm/Dispatchers.php @@ -0,0 +1,43 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +final class Dispatchers +{ + + /** + * @var string + * + * Use the current thread to run tasks from Coroutine. + */ + public const DEFAULT = "default"; + + /** + * @var string + * + * Use Separate Threads to Run Coroutine. + */ + public const IO = "io"; + +} \ No newline at end of file diff --git a/src/vennv/vapm/Error.php b/src/vennv/vapm/Error.php new file mode 100644 index 000000000..e24f26f7b --- /dev/null +++ b/src/vennv/vapm/Error.php @@ -0,0 +1,59 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +final class Error +{ + + public const FAILED_IN_FETCHING_DATA = "Error in fetching data"; + + public const WRONG_TYPE_WHEN_USE_CURL_EXEC = "curl_exec() should return string|false when CURL-OPT_RETURN-TRANSFER is set"; + + public const UNABLE_START_THREAD = "Unable to start thread"; + + public const DEFERRED_CALLBACK_MUST_RETURN_GENERATOR = "Deferred callback must return a Generator"; + + public const UNABLE_TO_OPEN_FILE = "Error: Unable to open file!"; + + public const FILE_DOES_NOT_EXIST = "Error: File does not exist!"; + + public const FILE_ALREADY_EXISTS = "Error: File already exists!"; + + public const CANNOT_FIND_FUNCTION_KEYWORD = "Cannot find function or fn keyword in closure"; + + public const CANNOT_READ_FILE = "Cannot read file"; + + public const INPUT_MUST_BE_STRING_OR_CALLABLE = "Input must be string or callable"; + + public const ERROR_TO_CREATE_SOCKET = "Error to create socket"; + + public const PAYLOAD_TOO_LARGE = "Payload too large"; + + public const INVALID_ARRAY = "Invalid array"; + + public const ASYNC_AWAIT_MUST_CALL_IN_ASYNC_FUNCTION = "Async::await() must call in async function"; + + public const GREEN_THREAD_MUST_CALL_IN_GREEN_THREAD_FUNCTION = "GreenThread::sleep() must call in green thread scope"; + +} \ No newline at end of file diff --git a/src/vennv/vapm/EventLoop.php b/src/vennv/vapm/EventLoop.php new file mode 100644 index 000000000..7a41c12b9 --- /dev/null +++ b/src/vennv/vapm/EventLoop.php @@ -0,0 +1,223 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use Generator; +use SplObjectStorage; +use Throwable; +use function count; +use const PHP_INT_MAX; + +interface EventLoopInterface +{ + + public static function init(): void; + + public static function generateId(): int; + + public static function addQueue(Promise $promise): void; + + public static function removeQueue(int $id): void; + + public static function isQueue(int $id): bool; + + public static function getQueue(int $id): ?Promise; + + /** + * @return Generator + */ + public static function getQueues(): Generator; + + public static function addReturn(Promise $promise): void; + + public static function removeReturn(int $id): void; + + public static function isReturn(int $id): bool; + + public static function getReturn(int $id): ?Promise; + + /** + * @return Generator + */ + public static function getReturns(): Generator; + +} + +class EventLoop implements EventLoopInterface +{ + + protected static int $limit = 10; + + protected static int $nextId = 0; + + /** + * @var SplObjectStorage + */ + protected static SplObjectStorage $queues; + + /** + * @var array + */ + protected static array $returns = []; + + public static function init(): void + { + if (!isset(self::$queues)) self::$queues = new SplObjectStorage(); + } + + public static function generateId(): int + { + if (self::$nextId >= PHP_INT_MAX) self::$nextId = 0; + return self::$nextId++; + } + + public static function addQueue(Promise $promise): void + { + if (!self::getQueue($promise->getId())) self::$queues->offsetSet($promise, $promise->getId()); + } + + public static function removeQueue(int $id): void + { + foreach (self::$queues as $promise) { + if ($promise instanceof Promise && $promise->getId() === $id) { + self::$queues->offsetUnset($promise); + break; + } + } + } + + public static function isQueue(int $id): bool + { + /* @var Promise $promise */ + foreach (self::$queues as $promise) if ($promise instanceof Promise && $promise->getId() === $id) return true; + return false; + } + + public static function getQueue(int $id): ?Promise + { + /* @var Promise $promise */ + foreach (self::$queues as $promise) if ($promise instanceof Promise && $promise->getId() === $id) return $promise; + return null; + } + + /** + * @return Generator + */ + public static function getQueues(): Generator + { + foreach (self::$queues as $promise) { + yield $promise; + } + } + + public static function addReturn(Promise $promise): void + { + if (!isset(self::$returns[$promise->getId()])) self::$returns[$promise->getId()] = $promise; + } + + public static function isReturn(int $id): bool + { + return isset(self::$returns[$id]); + } + + public static function removeReturn(int $id): void + { + if (self::isReturn($id)) unset(self::$returns[$id]); + } + + public static function getReturn(int $id): ?Promise + { + return self::$returns[$id] ?? null; + } + + /** + * @return Generator + */ + public static function getReturns(): Generator + { + foreach (self::$returns as $id => $promise) { + yield $id => $promise; + } + } + + /** + * @throws Throwable + */ + private static function clearGarbage(): void + { + foreach (self::getReturns() as $id => $promise) if ($promise instanceof Promise && $promise->canDrop()) unset(self::$returns[$id]); + } + + /** + * @throws Throwable + */ + protected static function run(): void + { + $i = 0; + + /** + * @var Promise $promise + */ + foreach (self::getQueues() as $promise) { + if ($i++ >= self::$limit) break; + + $id = $promise->getId(); + $fiber = $promise->getFiber(); + + if ($fiber->isSuspended()) { + $fiber->resume(); + } else if (!$fiber->isTerminated()) { + FiberManager::wait(); + } + + if ($fiber->isTerminated() && ($promise->getStatus() !== StatusPromise::PENDING || $promise->isJustGetResult())) { + try { + if ($promise->isJustGetResult()) $promise->setResult($fiber->getReturn()); + } catch (Throwable $e) { + echo $e->getMessage(); + } + MicroTask::addTask($id, $promise); + self::$queues->offsetUnset($promise); // Remove from queue + } else { + self::$queues->detach($promise); // Remove from queue + self::$queues->attach($promise, $id); // Add to queue again + } + } + + if (count(MicroTask::getTasks()) > 0) MicroTask::run(); + if (count(MacroTask::getTasks()) > 0) MacroTask::run(); + + self::clearGarbage(); + } + + /** + * @throws Throwable + */ + protected static function runSingle(): void + { + self::$limit = min((int)((count(self::$queues) / 2) + 1), 100); // Limit 100 promises per loop + while (count(self::$queues) > 0 || count(MicroTask::getTasks()) > 0 || count(MacroTask::getTasks()) > 0) self::run(); + } + +} diff --git a/src/vennv/vapm/FiberManager.php b/src/vennv/vapm/FiberManager.php new file mode 100644 index 000000000..a2f91dec1 --- /dev/null +++ b/src/vennv/vapm/FiberManager.php @@ -0,0 +1,53 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use Fiber; +use Throwable; +use function is_null; + +interface FiberManagerInterface +{ + + /** + * @throws Throwable + * + * This is a function that waits for the current fiber to finish. + */ + public static function wait(): void; + +} + +final class FiberManager implements FiberManagerInterface +{ + + /** + * @throws Throwable + */ + public static function wait(): void + { + if (!is_null(Fiber::getCurrent())) Fiber::suspend(); + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/GeneratorManager.php b/src/vennv/vapm/GeneratorManager.php new file mode 100644 index 000000000..66b5a7cd2 --- /dev/null +++ b/src/vennv/vapm/GeneratorManager.php @@ -0,0 +1,48 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +interface GeneratorManagerInterface +{ + + /** + * @param int $milliseconds + * @return int + * + * This is a function that calculates the seconds from milliseconds for Generator vapm. + * For example, if you run a function with multiple yields, this calculates the time spent on each of them in seconds. + */ + public static function calculateSeconds(int $milliseconds): int; + +} + +final class GeneratorManager implements GeneratorManagerInterface +{ + + public static function calculateSeconds(int $milliseconds): int + { + return ($milliseconds * 1000) + ($milliseconds * 550); + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/Info.php b/src/vennv/vapm/Info.php new file mode 100644 index 000000000..1a6ae6080 --- /dev/null +++ b/src/vennv/vapm/Info.php @@ -0,0 +1,37 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +class Info +{ + + public const VERSION = "1.8.7"; + + public const AUTHOR = "VennV"; + + public const LICENSE = "GPL-2.0"; + + public const GITHUB = "https://github.com/VennDev"; + +} \ No newline at end of file diff --git a/src/vennv/vapm/Internet.php b/src/vennv/vapm/Internet.php new file mode 100644 index 000000000..c1e6ca85c --- /dev/null +++ b/src/vennv/vapm/Internet.php @@ -0,0 +1,190 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use Closure; +use CurlHandle; +use function array_merge; +use function curl_close; +use function curl_error; +use function curl_exec; +use function curl_getinfo; +use function curl_init; +use function curl_setopt_array; +use function explode; +use function is_string; +use function strtolower; +use function substr; +use function trim; +use const CURLINFO_HEADER_SIZE; +use const CURLINFO_HTTP_CODE; +use const CURLOPT_AUTOREFERER; +use const CURLOPT_CONNECTTIMEOUT_MS; +use const CURLOPT_FOLLOWLOCATION; +use const CURLOPT_FORBID_REUSE; +use const CURLOPT_FRESH_CONNECT; +use const CURLOPT_HEADER; +use const CURLOPT_HTTPHEADER; +use const CURLOPT_POST; +use const CURLOPT_POSTFIELDS; +use const CURLOPT_RETURNTRANSFER; +use const CURLOPT_SSL_VERIFYHOST; +use const CURLOPT_SSL_VERIFYPEER; +use const CURLOPT_TIMEOUT_MS; + +final class Internet +{ + + /** + * GETs a URL using cURL + * 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. + * + * @param int $timeout default 10 + * @param string[] $extraHeaders + * @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. + */ + public static function getURL( + string $page, + int $timeout = 10, + array $extraHeaders = [], + string &$error = null + ): ?InternetRequestResult + { + try { + return self::simpleCurl( + $page, + $timeout, + $extraHeaders + ); + } catch (InternetException $exception) { + $error = $exception->getMessage(); + return null; + } + } + + /** + * POSTs data to a URL + * 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. + * + * @param string[]|string $args + * @param string[] $extraHeaders + * @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. + */ + public static function postURL( + string $page, + array|string $args, + int $timeout = 10, + array $extraHeaders = [], + string &$error = null + ): ?InternetRequestResult + { + try { + return self::simpleCurl($page, $timeout, $extraHeaders, [ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => $args + ]); + } catch (InternetException $ex) { + $error = $ex->getMessage(); + return null; + } + } + + /** + * General cURL shorthand function. + * 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. + * + * @param float $timeout The maximum connect timeout and timeout in seconds, correct to ms. + * @param string[] $extraHeaders extra headers to send as a plain string array + * @param array $extraOpts extra CURL-OPT_* to set as an [opt => value] map + * @param Closure|null $onSuccess function to be called if there is no error. Accepts a resource argument as the cURL handle. + * @phpstan-param array $extraOpts + * @phpstan-param list $extraHeaders + * @phpstan-param (Closure(CurlHandle) : void)|null $onSuccess + * + * @throws InternetException if a cURL error occurs + */ + public static function simpleCurl( + string $page, + float $timeout = 10, + array $extraHeaders = [], + array $extraOpts = [], + ?Closure $onSuccess = null + ): InternetRequestResult + { + + $time = (int)($timeout * 1000); + + $curlHandle = curl_init($page); + + if ($curlHandle === false) throw new InternetException("Unable to create new cURL session"); + + curl_setopt_array($curlHandle, $extraOpts + + [ + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => 2, + CURLOPT_FORBID_REUSE => 1, + CURLOPT_FRESH_CONNECT => 1, + CURLOPT_AUTOREFERER => true, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CONNECTTIMEOUT_MS => $time, + CURLOPT_TIMEOUT_MS => $time, + CURLOPT_HTTPHEADER => array_merge( + ["User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0"], + $extraHeaders + ), + CURLOPT_HEADER => true + ]); + + try { + $raw = curl_exec($curlHandle); + + if ($raw === false) throw new InternetException(curl_error($curlHandle)); + if (!is_string($raw)) throw new AssumptionFailedError(Error::WRONG_TYPE_WHEN_USE_CURL_EXEC); + + $httpCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE); + $headerSize = curl_getinfo($curlHandle, CURLINFO_HEADER_SIZE); + $rawHeaders = substr($raw, 0, $headerSize); + $body = substr($raw, $headerSize); + $headers = []; + + foreach (explode("\r\n\r\n", $rawHeaders) as $rawHeaderGroup) { + $headerGroup = []; + foreach (explode("\r\n", $rawHeaderGroup) as $line) { + $nameValue = explode(":", $line, 2); + if (isset($nameValue[1])) $headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]); + } + + $headers[] = $headerGroup; + } + + if (!is_null($onSuccess)) $onSuccess($curlHandle); + + return new InternetRequestResult($headers, $body, $httpCode); + } finally { + curl_close($curlHandle); + } + } + +} diff --git a/src/vennv/vapm/InternetException.php b/src/vennv/vapm/InternetException.php new file mode 100644 index 000000000..19c2f8844 --- /dev/null +++ b/src/vennv/vapm/InternetException.php @@ -0,0 +1,44 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use TypeError; + +final class InternetException extends TypeError +{ + + public function __construct( + protected string $errorMessage, + protected int $errorCode = 0 + ) + { + parent::__construct($this->errorMessage, $this->errorCode); + } + + public function __toString(): string + { + return __CLASS__ . ": [$this->errorCode]: $this->errorMessage\n"; + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/InternetRequestResult.php b/src/vennv/vapm/InternetRequestResult.php new file mode 100644 index 000000000..955c9e2c7 --- /dev/null +++ b/src/vennv/vapm/InternetRequestResult.php @@ -0,0 +1,82 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +interface InternetRequestResultInterface +{ + + /** + * @return string[][] + */ + public function getHeaders(): array; + + public function getBody(): string; + + public function getCode(): int; + +} + +final class InternetRequestResult implements InternetRequestResultInterface +{ + + /** + * @var string[][] $headers + */ + private array $headers; + + private string $body; + + private int $code; + + /** + * @param string[][] $headers + * @param string $body + * @param int $code + */ + public function __construct(array $headers, string $body, int $code) + { + $this->headers = $headers; + $this->body = $body; + $this->code = $code; + } + + /** + * @return string[][] + */ + public function getHeaders(): array + { + return $this->headers; + } + + public function getBody(): string + { + return $this->body; + } + + public function getCode(): int + { + return $this->code; + } + +} diff --git a/src/vennv/vapm/MacroTask.php b/src/vennv/vapm/MacroTask.php new file mode 100644 index 000000000..71eb96d0e --- /dev/null +++ b/src/vennv/vapm/MacroTask.php @@ -0,0 +1,79 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use const PHP_INT_MAX; + +final class MacroTask +{ + + private static int $nextId = 0; + + /** + * @var array + */ + private static array $tasks = []; + + public static function generateId(): int + { + if (self::$nextId >= PHP_INT_MAX) self::$nextId = 0; + + return self::$nextId++; + } + + public static function addTask(SampleMacro $sampleMacro): void + { + self::$tasks[$sampleMacro->getId()] = $sampleMacro; + } + + public static function removeTask(SampleMacro $sampleMacro): void + { + $id = $sampleMacro->getId(); + if (isset(self::$tasks[$id])) unset(self::$tasks[$id]); + } + + public static function getTask(int $id): ?SampleMacro + { + return self::$tasks[$id] ?? null; + } + + /** + * @return array + */ + public static function getTasks(): array + { + return self::$tasks; + } + + public static function run(): void + { + foreach (self::$tasks as $task) { + if ($task->checkTimeOut()) { + $task->run(); + !$task->isRepeat() ? self::removeTask($task) : $task->resetTimeOut(); + } + } + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/MicroTask.php b/src/vennv/vapm/MicroTask.php new file mode 100644 index 000000000..d379540fd --- /dev/null +++ b/src/vennv/vapm/MicroTask.php @@ -0,0 +1,75 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use Throwable; +use function microtime; + +final class MicroTask +{ + + /** + * @var array + */ + private static array $tasks = []; + + public static function addTask(int $id, Promise $promise): void + { + self::$tasks[$id] = $promise; + } + + public static function removeTask(int $id): void + { + unset(self::$tasks[$id]); + } + + public static function getTask(int $id): ?Promise + { + return self::$tasks[$id] ?? null; + } + + /** + * @return array + */ + public static function getTasks(): array + { + return self::$tasks; + } + + /** + * @throws Throwable + */ + public static function run(): void + { + foreach (self::$tasks as $id => $promise) { + $promise->useCallbacks(); + $promise->setTimeEnd(microtime(true)); + + EventLoop::addReturn($promise); + + self::removeTask($id); + } + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/PHPUtils.php b/src/vennv/vapm/PHPUtils.php new file mode 100644 index 000000000..8d59c8013 --- /dev/null +++ b/src/vennv/vapm/PHPUtils.php @@ -0,0 +1,147 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use Throwable; + +final class PHPUtils +{ + + /** + * @throws Throwable + * @param array $array + * @param callable $callback + * @return Async + * + * @phpstan-param array $array + */ + public static function forEach(array $array, callable $callback): Async + { + return new Async(function () use ($array, $callback) { + foreach ($array as $key => $value) { + $callback($key, $value); + FiberManager::wait(); + } + }); + } + + /** + * @throws Throwable + * @param array $array + * @param callable $callback + * @return Async + * + * @phpstan-param array $array + */ + public static function arrayMap(array $array, callable $callback): Async + { + return new Async(function () use ($array, $callback) { + $result = []; + foreach ($array as $key => $value) { + $result[$key] = $callback($key, $value); + FiberManager::wait(); + } + return $result; + }); + } + + /** + * @throws Throwable + * @param array $array + * @param callable $callback + * @return Async + * + * @phpstan-param array $array + */ + public static function arrayFilter(array $array, callable $callback): Async + { + return new Async(function () use ($array, $callback) { + $result = []; + foreach ($array as $key => $value) { + if ($callback($key, $value)) { + $result[$key] = $value; + } + FiberManager::wait(); + } + return $result; + }); + } + + /** + * @param array $array + * @param callable $callback + * @param mixed $initialValue + * @return Async + * + * @throws Throwable + */ + public static function arrayReduce(array $array, callable $callback, mixed $initialValue): Async + { + return new Async(function () use ($array, $callback, $initialValue) { + $accumulator = $initialValue; + foreach ($array as $key => $value) { + $accumulator = $callback($accumulator, $value, $key); + FiberManager::wait(); + } + return $accumulator; + }); + } + + /** + * @param array $array + * @param string $className + * @return Async + * + * @throws Throwable + */ + public static function instanceOfAll(array $array, string $className): Async + { + return new Async(function () use ($array, $className) { + foreach ($array as $value) { + if (!($value instanceof $className)) return false; + FiberManager::wait(); + } + return true; + }); + } + + /** + * @param array $array + * @param string $className + * @return Async + * + * @throws Throwable + */ + public static function instanceOfAny(array $array, string $className): Async + { + return new Async(function () use ($array, $className) { + foreach ($array as $value) { + if ($value instanceof $className) return true; + FiberManager::wait(); + } + return false; + }); + } + +} diff --git a/src/vennv/vapm/Promise.php b/src/vennv/vapm/Promise.php new file mode 100644 index 000000000..48b500212 --- /dev/null +++ b/src/vennv/vapm/Promise.php @@ -0,0 +1,654 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use Fiber; +use Throwable; +use ArrayObject; +use function call_user_func; +use function count; +use function is_callable; +use function microtime; + +interface PromiseInterface +{ + + /** + * @param mixed $result + * @return Promise + * + * This method is used to set the result of the promise. + */ + public function setResult(mixed $result): Promise; + + /** + * @throws Throwable + * + * This method is used to create a new promise. + */ + public static function c(callable $callback, bool $justGetResult = false): Promise; + + /** + * This method is used to get the id of the promise. + */ + public function getId(): int; + + /** + * This method is used to get the fiber of the promise. + */ + public function getFiber(): Fiber; + + /** + * This method is used to check if the promise is just to get the result. + */ + public function isJustGetResult(): bool; + + /** + * This method is used to get the time out of the promise. + */ + public function getTimeOut(): float; + + /** + * This method is used to get the time start of the promise. + */ + public function getTimeStart(): float; + + /** + * This method is used to get the time end of the promise. + */ + public function getTimeEnd(): float; + + /** + * This method is used to set the time out of the promise. + */ + public function setTimeEnd(float $timeEnd): void; + + /** + * This method is used to check if the promise is timed out and can be dropped. + */ + public function canDrop(): bool; + + /** + * This method is used to get the status of the promise. + */ + public function getStatus(): string; + + /** + * This method is used to check if the promise is pending. + */ + public function isPending(): bool; + + /** + * This method is used to check if the promise is resolved. + */ + public function isResolved(): bool; + + /** + * This method is used to check if the promise is rejected. + */ + public function isRejected(): bool; + + /** + * This method is used to get the result of the promise. + */ + public function getResult(): mixed; + + /** + * This method is used to get the return when catch or then of the promise is resolved or rejected. + */ + public function getReturn(): mixed; + + /** + * @throws Throwable + * + * This method is used to get the callback of the promise. + */ + public function getCallback(): callable; + + /** + * This method is used to resolve the promise. + */ + public function resolve(mixed $value = ''): void; + + /** + * This method is used to reject the promise. + */ + public function reject(mixed $value = ''): void; + + /** + * This method is used to set the callback when the promise is resolved. + */ + public function then(callable $callback): Promise; + + /** + * This method is used to set the callback when the promise is rejected. + */ + public function catch(callable $callback): Promise; + + /** + * This method is used to set the callback when the promise is resolved or rejected. + */ + public function finally(callable $callback): Promise; + + /** + * @throws Throwable + * + * This method is used to use the callbacks of the promise. + */ + public function useCallbacks(): void; + + /** + * @param array $promises + * @phpstan-param array $promises + * @throws Throwable + */ + public static function all(array $promises): Promise; + + /** + * @param array $promises + * @phpstan-param array $promises + * @throws Throwable + */ + public static function allSettled(array $promises): Promise; + + /** + * @param array $promises + * @phpstan-param array $promises + * @throws Throwable + */ + public static function any(array $promises): Promise; + + /** + * @param array $promises + * @phpstan-param array $promises + * @throws Throwable + */ + public static function race(array $promises): Promise; + +} + +final class Promise implements PromiseInterface +{ + + private int $id; + + private float $timeOut = 0.0; + + private float $timeEnd = 0.0; + + private mixed $result = null; + + private mixed $return = null; + + private string $status = StatusPromise::PENDING; + + /** @var ArrayObject $callbacksResolve */ + private ArrayObject $callbacksResolve; + + /** @var callable $callbacksReject */ + private mixed $callbackReject; + + /** @var callable $callbackFinally */ + private mixed $callbackFinally; + + private float $timeStart; + + private Fiber $fiber; + + /** @var callable $callback */ + private mixed $callback; + + private bool $justGetResult; + + /** + * @param callable $callback + * @param bool $justGetResult + * @throws Throwable + */ + public function __construct(callable $callback, bool $justGetResult = false) + { + System::init(); + + $this->callbacksResolve = new ArrayObject(); + $this->id = EventLoop::generateId(); + $this->callback = $callback; + $this->fiber = new Fiber($callback); + + if ($justGetResult) { + $this->result = $this->fiber->start(); + } else { + $resolve = function ($result = ''): void { + $this->resolve($result); + }; + + $reject = function ($result = ''): void { + $this->reject($result); + }; + + $this->fiber->start($resolve, $reject); + } + + if (!$this->fiber->isTerminated()) FiberManager::wait(); + + $this->justGetResult = $justGetResult; + + $this->timeStart = microtime(true); + + $this->callbacksResolve->offsetSet("master", function ($result): mixed { + return $result; + }); + + $this->callbackReject = function ($result): mixed { + return $result; + }; + + $this->callbackFinally = function (): void {}; + + EventLoop::addQueue($this); + } + + public function setResult(mixed $result): Promise + { + $this->result = $result; + return $this; + } + + /** + * @throws Throwable + */ + public static function c(callable $callback, bool $justGetResult = false): Promise + { + return new self($callback, $justGetResult); + } + + public function getId(): int + { + return $this->id; + } + + public function getFiber(): Fiber + { + return $this->fiber; + } + + public function isJustGetResult(): bool + { + return $this->justGetResult; + } + + public function getTimeOut(): float + { + return $this->timeOut; + } + + public function getTimeStart(): float + { + return $this->timeStart; + } + + public function getTimeEnd(): float + { + return $this->timeEnd; + } + + public function setTimeEnd(float $timeEnd): void + { + $this->timeEnd = $timeEnd; + } + + public function canDrop(): bool + { + return microtime(true) - $this->timeEnd > Settings::TIME_DROP; + } + + public function getStatus(): string + { + return $this->status; + } + + public function isPending(): bool + { + return $this->status === StatusPromise::PENDING; + } + + public function isResolved(): bool + { + return $this->status === StatusPromise::FULFILLED; + } + + public function isRejected(): bool + { + return $this->status === StatusPromise::REJECTED; + } + + public function getResult(): mixed + { + return $this->result; + } + + public function getReturn(): mixed + { + return $this->return; + } + + public function getCallback(): callable + { + return $this->callback; + } + + public function resolve(mixed $value = ''): void + { + if ($this->isPending()) { + $this->status = StatusPromise::FULFILLED; + $this->result = $value; + } + } + + public function reject(mixed $value = ''): void + { + if ($this->isPending()) { + $this->status = StatusPromise::REJECTED; + $this->result = $value; + } + } + + public function then(callable $callback): Promise + { + $this->callbacksResolve[] = $callback; + return $this; + } + + public function catch(callable $callback): Promise + { + $this->callbackReject = $callback; + return $this; + } + + public function finally(callable $callback): Promise + { + $this->callbackFinally = $callback; + return $this; + } + + /** + * @throws Throwable + */ + public function useCallbacks(): void + { + $result = $this->result; + + if ($this->isResolved()) { + $callbacks = $this->callbacksResolve; + + /** @var callable $master */ + $master = $callbacks["master"] ?? null; + + if (is_callable($master)) { + $this->result = call_user_func($master, $result); + unset($callbacks["master"]); + } + + if (count($callbacks) > 0) { + /** @var callable $callback */ + $callback = $callbacks[0]; + $resultFirstCallback = call_user_func($callback, $this->result); + $this->result = $resultFirstCallback; + $this->return = $resultFirstCallback; + $this->checkStatus($callbacks, $this->return); + } + } else if ($this->isRejected()) { + if (is_callable($this->callbackReject)) $this->result = call_user_func($this->callbackReject, $result); + } + if (is_callable($this->callbackFinally)) call_user_func($this->callbackFinally); + } + + /** + * @param ArrayObject $callbacks + * @param mixed $return + */ + private function checkStatus(ArrayObject $callbacks, mixed $return): void + { + $lastPromise = null; + + while (count($callbacks) > 0) { + $cancel = false; + + /** + * @var int|string $case + * @var callable $callback + */ + foreach ($callbacks->getArrayCopy() as $case => $callback) { + if ($return === null) { + $cancel = true; + break; + } + + if ($case !== 0 && $return instanceof Promise) { + EventLoop::addQueue($return); + + $queue1 = EventLoop::getQueue($return->getId()); + $queue2 = MicroTask::getTask($return->getId()); + if (!is_null($queue1)) { + $queue1->then($callback); + if (is_callable($this->callbackReject)) $queue1->catch($this->callbackReject); + $lastPromise = $queue1; + } else if (!is_null($queue2)) { + $queue2->then($callback); + if (is_callable($this->callbackReject)) $queue2->catch($this->callbackReject); + $lastPromise = $queue2; + } + + $callbacks->offsetUnset($case); + continue; + } + + if (count($callbacks) === 1) $cancel = true; + } + + if ($cancel) break; + } + + if ($lastPromise !== null) { + $lastPromise->finally($this->callbackFinally); + } else { + if (is_callable($this->callbackFinally)) call_user_func($this->callbackFinally); + } + } + + /** + * @param array $promises + * @phpstan-param array $promises + * @throws Throwable + */ + public static function all(array $promises): Promise + { + $promise = new Promise(function ($resolve, $reject) use ($promises): void { + $count = count($promises); + $results = []; + $isSolved = false; + + while (!$isSolved) { + foreach ($promises as $index => $promise) { + if (is_callable($promise)) $promise = new Async($promise); + + if ($promise instanceof Async || $promise instanceof Promise) { + $return = EventLoop::getReturn($promise->getId()); + + if ($return?->isRejected() === true) { + $reject($return->getResult()); + $isSolved = true; + break; + } + + if ($return?->isResolved() === true) { + $results[] = $return->getResult(); + unset($promises[$index]); + } + } + + if (count($results) === $count) { + $resolve($results); + $isSolved = true; + } + FiberManager::wait(); + } + + if (!$isSolved) FiberManager::wait(); + } + }); + + EventLoop::addQueue($promise); + + return $promise; + } + + /** + * @param array $promises + * @phpstan-param array $promises + * @throws Throwable + */ + public static function allSettled(array $promises): Promise + { + $promise = new Promise(function ($resolve) use ($promises): void { + $count = count($promises); + $results = []; + $isSolved = false; + + while (!$isSolved) { + foreach ($promises as $index => $promise) { + if (is_callable($promise)) $promise = new Async($promise); + + if ($promise instanceof Async || $promise instanceof Promise) { + $return = EventLoop::getReturn($promise->getId()); + + if ($return !== null) { + $results[] = new PromiseResult($return->getStatus(), $return->getResult()); + unset($promises[$index]); + } + } + + if (count($results) === $count) { + $resolve($results); + $isSolved = true; + } + FiberManager::wait(); + } + + if (!$isSolved === false) FiberManager::wait(); + } + }); + + EventLoop::addQueue($promise); + + return $promise; + } + + /** + * @param array $promises + * @phpstan-param array $promises + * @throws Throwable + */ + public static function any(array $promises): Promise + { + $promise = new Promise(function ($resolve, $reject) use ($promises): void { + $count = count($promises); + $results = []; + $isSolved = false; + + while ($isSolved === false) { + foreach ($promises as $index => $promise) { + if (is_callable($promise)) $promise = new Async($promise); + + if ($promise instanceof Async || $promise instanceof Promise) { + $return = EventLoop::getReturn($promise->getId()); + + if ($return?->isRejected() === true) { + $results[] = $return->getResult(); + unset($promises[$index]); + } + + if ($return?->isResolved() === true) { + $resolve($return->getResult()); + $isSolved = true; + break; + } + } + + if (count($results) === $count) { + $reject($results); + $isSolved = true; + } + FiberManager::wait(); + } + + if ($isSolved === false) FiberManager::wait(); + } + }); + + EventLoop::addQueue($promise); + + return $promise; + } + + /** + * @param array $promises + * @phpstan-param array $promises + * @throws Throwable + */ + public static function race(array $promises): Promise + { + $promise = new Promise(function ($resolve, $reject) use ($promises): void { + $isSolved = false; + + while ($isSolved === false) { + foreach ($promises as $promise) { + if (is_callable($promise)) $promise = new Async($promise); + if ($promise instanceof Async || $promise instanceof Promise) { + $return = EventLoop::getReturn($promise->getId()); + + if ($return?->isRejected() === true) { + $reject($return->getResult()); + $isSolved = true; + break; + } + + if ($return?->isResolved() === true) { + $resolve($return->getResult()); + $isSolved = true; + break; + } + } + FiberManager::wait(); + } + + if ($isSolved === false) FiberManager::wait(); + } + }); + + EventLoop::addQueue($promise); + + return $promise; + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/PromiseResult.php b/src/vennv/vapm/PromiseResult.php new file mode 100644 index 000000000..14efc5334 --- /dev/null +++ b/src/vennv/vapm/PromiseResult.php @@ -0,0 +1,58 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +interface PromiseResultInterface +{ + + public function getStatus(): string; + + public function getResult(): mixed; + +} + +final class PromiseResult implements PromiseResultInterface +{ + + private string $status; + + private mixed $result; + + public function __construct(string $status, mixed $result) + { + $this->status = $status; + $this->result = $result; + } + + public function getStatus(): string + { + return $this->status; + } + + public function getResult(): mixed + { + return $this->result; + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/SampleMacro.php b/src/vennv/vapm/SampleMacro.php new file mode 100644 index 000000000..8b3fb935d --- /dev/null +++ b/src/vennv/vapm/SampleMacro.php @@ -0,0 +1,128 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use vennv\vapm\utils\Utils; +use function call_user_func; +use function microtime; + +interface SampleMacroInterface +{ + + public function isRepeat(): bool; + + public function getTimeOut(): float; + + public function getTimeStart(): float; + + public function getCallback(): callable; + + public function getId(): int; + + public function checkTimeOut(): bool; + + public function resetTimeOut(): void; + + public function isRunning(): bool; + + public function run(): void; + + public function stop(): void; + +} + +final class SampleMacro implements SampleMacroInterface +{ + + private float $timeOut; + + private float $timeStart; + + private bool $isRepeat; + + /** @var callable $callback */ + private mixed $callback; + + private int $id; + + public function __construct(callable $callback, int $timeOut = 0, bool $isRepeat = false) + { + $this->id = MacroTask::generateId(); + $this->timeOut = Utils::milliSecsToSecs($timeOut); + $this->isRepeat = $isRepeat; + $this->timeStart = microtime(true); + $this->callback = $callback; + } + + public function isRepeat(): bool + { + return $this->isRepeat; + } + + public function getTimeOut(): float + { + return $this->timeOut; + } + + public function getTimeStart(): float + { + return $this->timeStart; + } + + public function getCallback(): callable + { + return $this->callback; + } + + public function getId(): int + { + return $this->id; + } + + public function checkTimeOut(): bool + { + return microtime(true) - $this->timeStart >= $this->timeOut; + } + + public function resetTimeOut(): void + { + $this->timeStart = microtime(true); + } + + public function isRunning(): bool + { + return MacroTask::getTask($this->id) !== null; + } + + public function run(): void + { + call_user_func($this->callback); + } + + public function stop(): void + { + MacroTask::removeTask($this); + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/Settings.php b/src/vennv/vapm/Settings.php new file mode 100644 index 000000000..3eac6a1f3 --- /dev/null +++ b/src/vennv/vapm/Settings.php @@ -0,0 +1,35 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +final class Settings +{ + + /** + * The time in seconds to check should drop the promise if the promise is not resolved or rejected + * in the specified time. + */ + public const TIME_DROP = 7; + +} \ No newline at end of file diff --git a/src/vennv/vapm/StatusPromise.php b/src/vennv/vapm/StatusPromise.php new file mode 100644 index 000000000..04d22c939 --- /dev/null +++ b/src/vennv/vapm/StatusPromise.php @@ -0,0 +1,33 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +final class StatusPromise +{ + + public const PENDING = "pending"; + public const FULFILLED = "fulfilled"; + public const REJECTED = "rejected"; + +} \ No newline at end of file diff --git a/src/vennv/vapm/StatusThread.php b/src/vennv/vapm/StatusThread.php new file mode 100644 index 000000000..4620baf5f --- /dev/null +++ b/src/vennv/vapm/StatusThread.php @@ -0,0 +1,93 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use function microtime; + +interface StatusThreadInterface +{ + + /** + * @return int|float + * + * This method is used to get the time sleeping. + */ + public function getTimeSleeping(): int|float; + + /** + * @return int|float + * + * This method is used to get the sleep start time. + */ + public function getSleepStartTime(): int|float; + + /** + * @param int|float $seconds + * + * This method is used to sleep the thread. + */ + public function sleep(int|float $seconds): void; + + /** + * @return bool + * + * This method is used to check if the thread can wake up. + */ + public function canWakeUp(): bool; + +} + +final class StatusThread implements StatusThreadInterface +{ + + private int|float $timeSleeping = 0; + + private int|float $sleepStartTime; + + public function __construct() + { + $this->sleepStartTime = microtime(true); + } + + public function getTimeSleeping(): int|float + { + return $this->timeSleeping; + } + + public function getSleepStartTime(): int|float + { + return $this->sleepStartTime; + } + + public function sleep(int|float $seconds): void + { + $this->timeSleeping += $seconds; + } + + public function canWakeUp(): bool + { + return microtime(true) - $this->sleepStartTime >= $this->timeSleeping; + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/Stream.php b/src/vennv/vapm/Stream.php new file mode 100644 index 000000000..c84007d69 --- /dev/null +++ b/src/vennv/vapm/Stream.php @@ -0,0 +1,253 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use Throwable; +use function fclose; +use function fgets; +use function fopen; +use function fwrite; +use function is_array; +use function touch; +use function unlink; +use function file_exists; +use function call_user_func; +use function stream_set_blocking; + +interface StreamInterface +{ + + /** + * @throws Throwable + * + * Use this to read a file or url. + */ + public static function read(string $path): Promise; + + /** + * @throws Throwable + * + * Use this to write to a file. + */ + public static function write(string $path, string $data): Promise; + + /** + * @throws Throwable + * + * Use this to append to a file. + */ + public static function append(string $path, string $data): Promise; + + /** + * @throws Throwable + * + * Use this to delete a file. + */ + public static function delete(string $path): Promise; + + /** + * @throws Throwable + * + * Use this to create a file. + */ + public static function create(string $path): Promise; + + /** + * @throws Throwable + * + * Use this to create a file or overwrite a file. + */ + public static function overWrite(string $path, string $data): Promise; + + /** + * @param array $array + * @throws Throwable + * + * Use this to flatten an array. + */ + public static function flattenArray(array $array): Promise; + +} + +final class Stream implements StreamInterface +{ + + /** + * @throws Throwable + */ + public static function read(string $path): Promise + { + return new Promise(function ($resolve, $reject) use ($path): void { + $lines = ''; + $handle = fopen($path, 'r'); + + if ($handle === false) { + $reject(Error::UNABLE_TO_OPEN_FILE); + } else { + stream_set_blocking($handle, false); + + while (($line = fgets($handle)) !== false) { + $lines .= $line; + FiberManager::wait(); + } + + fclose($handle); + } + + $resolve($lines); + }); + } + + /** + * @throws Throwable + */ + public static function write(string $path, string $data): Promise + { + return new Promise(function ($resolve, $reject) use ($path, $data): void { + System::setTimeout(function () use ($resolve, $reject, $path, $data): void { + $callback = function ($path, $data) use ($reject): void { + $handle = fopen($path, 'w'); + + if ($handle === false) { + $reject(Error::UNABLE_TO_OPEN_FILE); + } else { + stream_set_blocking($handle, false); + fwrite($handle, $data); + fclose($handle); + } + }; + + call_user_func($callback, $path, $data); + $resolve(); + }, 0); + }); + } + + /** + * @throws Throwable + */ + public static function append(string $path, string $data): Promise + { + return new Promise(function ($resolve, $reject) use ($path, $data): void { + System::setTimeout(function () use ($resolve, $reject, $path, $data): void { + $callback = function ($path, $data) use ($reject): void { + $handle = fopen($path, 'a'); + + if ($handle === false) { + $reject(Error::UNABLE_TO_OPEN_FILE); + } else { + stream_set_blocking($handle, false); + fwrite($handle, $data); + fclose($handle); + } + }; + + call_user_func($callback, $path, $data); + $resolve(); + }, 0); + }); + } + + /** + * @throws Throwable + */ + public static function delete(string $path): Promise + { + return new Promise(function ($resolve, $reject) use ($path): void { + System::setTimeout(function () use ($resolve, $reject, $path): void { + $callback = function ($path) use ($reject): void { + file_exists($path) ? unlink($path) : $reject(Error::FILE_DOES_NOT_EXIST); + }; + call_user_func($callback, $path); + $resolve(); + }, 0); + }); + } + + /** + * @throws Throwable + */ + public static function create(string $path): Promise + { + return new Promise(function ($resolve, $reject) use ($path): void { + System::setTimeout(function () use ($resolve, $reject, $path): void { + $callback = function ($path) use ($reject): void { + !file_exists($path) ? touch($path) : $reject(Error::FILE_ALREADY_EXISTS); + }; + call_user_func($callback, $path); + $resolve(); + }, 0); + }); + } + + /** + * @throws Throwable + */ + public static function overWrite(string $path, string $data): Promise + { + return new Promise(function ($resolve, $reject) use ($path, $data): void { + System::setTimeout(function () use ($resolve, $reject, $path, $data): void { + $callback = function ($path, $data) use ($reject): void { + $handle = fopen($path, 'w+'); + if ($handle === false) { + $reject(Error::UNABLE_TO_OPEN_FILE); + } else { + stream_set_blocking($handle, false); + fwrite($handle, $data); + fclose($handle); + } + }; + + call_user_func($callback, $path, $data); + $resolve(); + }, 0); + }); + } + + /** + * @param array $array + * @throws Throwable + */ + public static function flattenArray(array $array): Promise + { + return new Promise(function ($resolve, $reject) use ($array) { + $result = []; + $stack = [$array]; + + while (!empty($stack)) { + $element = array_shift($stack); + if ($element === null) { + $reject(Error::INVALID_ARRAY); + break; + } + + foreach ($element as $value) is_array($value) ? array_unshift($stack, $value) : $result[] = $value; + FiberManager::wait(); + } + + $resolve($result); + }); + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/System.php b/src/vennv/vapm/System.php new file mode 100644 index 000000000..48fb9b613 --- /dev/null +++ b/src/vennv/vapm/System.php @@ -0,0 +1,293 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use Throwable; +use function curl_init; +use function curl_multi_add_handle; +use function curl_multi_close; +use function curl_multi_exec; +use function curl_multi_getcontent; +use function curl_multi_init; +use function curl_multi_remove_handle; +use function file_get_contents; +use const CURLM_OK; +use const CURLOPT_RETURNTRANSFER; + +interface SystemInterface +{ + + /** + * @throws Throwable + * + * This function is used to run the event loop with multiple event loops + */ + public static function runEventLoop(): void; + + /** + * @throws Throwable + * + * This function is used to run the event loop with single event loop + */ + public static function runSingleEventLoop(): void; + + /** + * @throws Throwable + * + * This function is used to initialize the event loop + */ + public static function init(): void; + + /** + * This function is used to run a callback in the event loop with timeout + */ + public static function setTimeout(callable $callback, int $timeout): SampleMacro; + + /** + * This function is used to clear the timeout + */ + public static function clearTimeout(SampleMacro $sampleMacro): void; + + /** + * This function is used to run a callback in the event loop with interval + */ + public static function setInterval(callable $callback, int $interval): SampleMacro; + + /** + * This function is used to clear the interval + */ + public static function clearInterval(SampleMacro $sampleMacro): void; + + /** + * @param string $url + * @param array $options + * @return Promise when Promise resolve InternetRequestResult and when Promise reject Error + * @throws Throwable + * @phpstan-param array{method?: string, headers?: array, timeout?: int, body?: array} $options + */ + public static function fetch(string $url, array $options = []): Promise; + + /** + * @param string ...$curls + * @return Promise + * @throws Throwable + * + * Use this to curl multiple addresses at once + */ + public static function fetchAll(string ...$curls): Promise; + + /** + * @throws Throwable + * + * This is a function used only to retrieve results from an address or file path via the file_get_contents method + */ + public static function read(string $path): Promise; + + /** + * @param string $name + * @return void + * + * This function is used to start a timer + */ + public static function time(string $name = 'Console'): void; + + /** + * @param string $name + * @return void + * + * This function is used to end a timer + */ + public static function timeEnd(string $name = 'Console'): void; + +} + +final class System extends EventLoop implements SystemInterface +{ + + /** + * @var array + */ + private static array $timings = []; + + private static bool $hasInit = false; + + /** + * @throws Throwable + */ + public static function runEventLoop(): void + { + self::init(); + parent::run(); + } + + /** + * @throws Throwable + */ + public static function runSingleEventLoop(): void + { + self::init(); + parent::runSingle(); + } + + public static function init(): void + { + if (!self::$hasInit) { + self::$hasInit = true; + register_shutdown_function(function () { + self::runSingleEventLoop(); + }); + } + + parent::init(); + } + + /** + * @throws Throwable + */ + public static function setTimeout(callable $callback, int $timeout): SampleMacro + { + self::init(); + $sampleMacro = new SampleMacro($callback, $timeout); + MacroTask::addTask($sampleMacro); + return $sampleMacro; + } + + public static function clearTimeout(SampleMacro $sampleMacro): void + { + if ($sampleMacro->isRunning() && !$sampleMacro->isRepeat()) $sampleMacro->stop(); + } + + /** + * @throws Throwable + */ + public static function setInterval(callable $callback, int $interval): SampleMacro + { + self::init(); + $sampleMacro = new SampleMacro($callback, $interval, true); + MacroTask::addTask($sampleMacro); + return $sampleMacro; + } + + public static function clearInterval(SampleMacro $sampleMacro): void + { + if ($sampleMacro->isRunning() && $sampleMacro->isRepeat()) $sampleMacro->stop(); + } + + /** + * @param string $url + * @param array $options + * @return Promise when Promise resolve InternetRequestResult and when Promise reject Error + * @throws Throwable + * @phpstan-param array{method?: string, headers?: array, timeout?: int, body?: array} $options + */ + public static function fetch(string $url, array $options = []): Promise + { + return new Promise(function ($resolve, $reject) use ($url, $options) { + self::setTimeout(function () use ($resolve, $reject, $url, $options) { + $method = $options["method"] ?? "GET"; + + /** @var array $headers */ + $headers = $options["headers"] ?? []; + + /** @var int $timeout */ + $timeout = $options["timeout"] ?? 10; + + /** @var array $body */ + $body = $options["body"] ?? []; + + $method === "GET" ? $result = Internet::getURL($url, $timeout, $headers) : $result = Internet::postURL($url, $body, $timeout, $headers); + $result === null ? $reject(Error::FAILED_IN_FETCHING_DATA) : $resolve($result); + }, 0); + }); + } + + /** + * @param string ...$curls + * @return Promise + * @throws Throwable + * + * Use this to curl multiple addresses at once + */ + public static function fetchAll(string ...$curls): Promise + { + return new Promise(function ($resolve, $reject) use ($curls): void { + $multiHandle = curl_multi_init(); + $handles = []; + foreach ($curls as $url) { + $handle = curl_init($url); + if ($handle === false) { + $reject(Error::FAILED_IN_FETCHING_DATA); + } else { + curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); + curl_multi_add_handle($multiHandle, $handle); + $handles[] = $handle; + } + } + + $running = 0; + + do { + $status = curl_multi_exec($multiHandle, $running); + if ($status !== CURLM_OK) $reject(Error::FAILED_IN_FETCHING_DATA); + FiberManager::wait(); + } while ($running > 0); + + $results = []; + foreach ($handles as $handle) { + $results[] = curl_multi_getcontent($handle); + curl_multi_remove_handle($multiHandle, $handle); + } + + curl_multi_close($multiHandle); + $resolve($results); + }); + } + + /** + * @throws Throwable + */ + public static function read(string $path): Promise + { + return new Promise(function ($resolve, $reject) use ($path) { + self::setTimeout(function () use ($resolve, $reject, $path) { + $ch = file_get_contents($path); + $ch === false ? $reject(Error::FAILED_IN_FETCHING_DATA) : $resolve($ch); + }, 0); + }); + } + + public static function time(string $name = 'Console'): void + { + self::$timings[$name] = microtime(true); + } + + public static function timeEnd(string $name = 'Console'): void + { + if (!isset(self::$timings[$name])) return; + $time = microtime(true) - self::$timings[$name]; + echo "Time for $name: $time\n"; + unset(self::$timings[$name]); + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/Thread.php b/src/vennv/vapm/Thread.php new file mode 100644 index 000000000..9c1bdd73e --- /dev/null +++ b/src/vennv/vapm/Thread.php @@ -0,0 +1,517 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use vennv\vapm\utils\Utils; +use Closure; +use ReflectionClass; +use ReflectionException; +use RuntimeException; +use Throwable; +use function explode; +use function fclose; +use function fwrite; +use function get_called_class; +use function is_array; +use function is_callable; +use function is_resource; +use function is_string; +use function json_decode; +use function json_encode; +use function proc_get_status; +use function proc_open; +use function str_replace; +use function stream_get_contents; +use function stream_set_blocking; +use const PHP_BINARY; +use const PHP_EOL; +use const STDIN; +use const STDOUT; + +interface ThreadInterface +{ + + /** + * This abstract method use to run the thread + */ + public function onRun(): void; + + /** + * @param array> $mode + * @throws ReflectionException + * @throws Throwable + * @phpstan-param array> $mode + * + * This method use to start the thread + */ + public function start(array $mode = DescriptorSpec::BASIC): Promise; + +} + +interface ThreadedInterface +{ + + /** + * @return mixed + */ + public function getInput(): mixed; + + /** + * This method use to get the pid of the thread + */ + public function getPid(): int; + + /** + * This method use to get the exit code of the thread + */ + public function getExitCode(): int; + + /* + * This method use to get the running status of the thread + */ + public function isRunning(): bool; + + /** + * This method use to get the signaled status of the thread + */ + public function isSignaled(): bool; + + /** + * This method use to get the stopped status of the thread + */ + public function isStopped(): bool; + + /** + * @return array + * @phpstan-return array + * + * This method use to get the shared data of the main thread + */ + public static function getDataMainThread(): array; + + /** + * @param array $shared + * @phpstan-param array $shared + * + * This method use to set the shared data of the main thread + */ + public static function setShared(array $shared): void; + + /** + * @param string $key + * @param mixed $value + * @phpstan-param mixed $value + * + * This method use to add the shared data of the MAIN-THREAD + */ + public static function addShared(string $key, mixed $value): void; + + /** + * @return array + * + * This method use to get the shared data of the child thread + */ + public static function getSharedData(): array; + + /** + * @param array $data + * @return void + * @phpstan-param array $data + * + * This method use to post all data the main thread + */ + public static function postMainThread(array $data): void; + + /** + * @param string $data + * @return void + * + * This method use to load the shared data from the main thread + */ + public static function loadSharedData(string $data): void; + + /** + * @param string $data + * @return void + * + * This method use to post the data on the thread + */ + public static function post(string $data): void; + + /** + * @param int $pid + * @return bool + * + * This method use to check the thread is running or not + */ + public static function threadIsRunning(int $pid): bool; + + /** + * @param int $pid + * @return bool + * + * This method use to kill the thread + */ + public static function killThread(int $pid): bool; + +} + +abstract class Thread implements ThreadInterface, ThreadedInterface +{ + + private const POST_MAIN_THREAD = 'postMainThread'; // example: postMainThread=>{data} + + private const POST_THREAD = 'postThread'; // example: postAlertThread=>{data} + + private int $pid = -1; + + private int $exitCode = -1; + + private bool $isRunning = false; + + private bool $signaled = false; + + private bool $stopped = false; + + /** + * @var array + * @phpstan-var array + */ + private static array $shared = []; + + /** + * @var array + * @phpstan-var array + */ + private static array $threads = []; + + /** + * @var array + * @phpstan-var array + */ + private static array $inputs = []; + + public function __construct(mixed $input = '') + { + self::$inputs[get_called_class()] = $input; + } + + public function getInput(): mixed + { + return self::$inputs[get_called_class()]; + } + + public function getPid(): int + { + return $this->pid; + } + + public function setPid(int $pid): void + { + $this->pid = $pid; + } + + public function getExitCode(): int + { + return $this->exitCode; + } + + protected function setExitCode(int $exitCode): void + { + $this->exitCode = $exitCode; + } + + public function isRunning(): bool + { + return $this->isRunning; + } + + protected function setRunning(bool $isRunning): void + { + $this->isRunning = $isRunning; + } + + public function isSignaled(): bool + { + return $this->signaled; + } + + protected function setSignaled(bool $signaled): void + { + $this->signaled = $signaled; + } + + public function isStopped(): bool + { + return $this->stopped; + } + + protected function setStopped(bool $stopped): void + { + $this->stopped = $stopped; + } + + /** + * @return array + * @phpstan-return array + */ + public static function getDataMainThread(): array + { + return self::$shared; + } + + /** + * @param array $shared + * @phpstan-param array $shared + */ + public static function setShared(array $shared): void + { + self::$shared = $shared; + } + + public static function addShared(string $key, mixed $value): void + { + self::$shared[$key] = $value; + } + + public static function getSharedData(): array + { + $data = fgets(STDIN); + + if (is_string($data)) { + $data = json_decode($data, true); + if (is_array($data)) return $data; + } + + return []; + } + + /** + * @param array $data + * @phpstan-param array $data + */ + public static function postMainThread(array $data): void + { + fwrite(STDOUT, self::POST_MAIN_THREAD . '=>' . json_encode($data) . PHP_EOL); + } + + private static function isPostMainThread(string $data): bool + { + return explode('=>', $data)[0] === self::POST_MAIN_THREAD; + } + + public static function loadSharedData(string $data): void + { + $data = explode('=>', $data); + + if ($data[0] === self::POST_MAIN_THREAD) { + $result = json_decode($data[1], true); + if (is_array($result)) self::setShared(array_merge(self::$shared, $result)); + } + } + + public static function post(string $data): void + { + fwrite(STDOUT, self::POST_THREAD . '=>' . $data . PHP_EOL); + } + + public static function threadIsRunning(int $pid): bool + { + return isset(self::$threads[$pid]); + } + + public static function killThread(int $pid): bool + { + if (isset(self::$threads[$pid])) { + $thread = self::$threads[$pid]; + + if ($thread->isRunning()) { + $thread->setStopped(true); + return true; + } + } + + return false; + } + + private static function isPost(string $data): bool + { + return explode('=>', $data)[0] === self::POST_THREAD; + } + + private static function loadPost(string $data): void + { + $data = explode('=>', $data); + + if ($data[0] === self::POST_THREAD) { + $result = json_decode($data[1], true); + echo $result . PHP_EOL; + } + } + + /** + * @param false|string $data + * @return array + * @phpstan-return array + */ + private static function getPost(false|string $data): array + { + $result = []; + + if (is_string($data)) { + $explode = explode(PHP_EOL, $data); + + foreach ($explode as $item) { + if ($item !== '') { + $dataExplode = explode('=>', $data); + + if ($dataExplode[0] == self::POST_THREAD) { + $try = json_decode($dataExplode[1], true); + is_array($try) ? $result[] = $try : $result[] = $dataExplode[1]; + } + } + } + } + + return $result; + } + + abstract public function onRun(): void; + + /** + * @param array> $mode + * @return Promise + * @throws ReflectionException + * @throws Throwable + * @phpstan-param array> $mode + * @phpstan-return Promise + */ + public function start(array $mode = DescriptorSpec::BASIC): Promise + { + return new Promise(function ($resolve, $reject) use ($mode): mixed { + $className = get_called_class(); + + $reflection = new ReflectionClass($className); + + $class = $reflection->getFileName(); + + $pathAutoLoad = __FILE__; + $pathAutoLoad = str_replace( + 'src\vennv\vapm\Thread.php', + 'src\vendor\autoload.php', + $pathAutoLoad + ); + + $input = self::$inputs[get_called_class()]; + + if (is_string($input)) $input = '\'' . self::$inputs[get_called_class()] . '\''; + + if (is_callable($input) && $input instanceof Closure) { + $input = Utils::closureToString($input); + $input = Utils::removeComments($input); + + if (!is_string($input)) return $reject(new RuntimeException(Error::INPUT_MUST_BE_STRING_OR_CALLABLE)); + + $input = Utils::outlineToInline($input); + + if (!is_string($input)) return $reject(new RuntimeException(Error::INPUT_MUST_BE_STRING_OR_CALLABLE)); + + $input = Utils::fixInputCommand($input); + + if (!is_string($input)) return $reject(new RuntimeException(Error::INPUT_MUST_BE_STRING_OR_CALLABLE)); + } + + if (!is_string($input)) return $reject(new RuntimeException(Error::INPUT_MUST_BE_STRING_OR_CALLABLE)); + + $command = PHP_BINARY . ' -r "require_once \'' . $pathAutoLoad . '\'; include \'' . $class . '\'; $input = ' . $input . '; $class = new ' . static::class . '($input); $class->onRun();"'; + + unset(self::$inputs[get_called_class()]); + + $process = proc_open($command, $mode, $pipes); + + if (is_resource($process)) { + stream_set_blocking($pipes[1], false); + stream_set_blocking($pipes[2], false); + + $data = json_encode(self::getDataMainThread()); + + if (is_string($data)) { + fwrite($pipes[0], $data); + fclose($pipes[0]); + } + + while (proc_get_status($process)['running']) { + $status = proc_get_status($process); + + if (!isset(self::$threads[$status['pid']])) { + $this->setPid($status['pid']); + self::$threads[$status['pid']] = $this; + } + + $thread = self::$threads[$status['pid']]; + + $thread->setExitCode($status['exitcode']); + $thread->setRunning($status['running']); + $thread->setSignaled($status['signaled']); + $thread->setStopped($status['stopped']); + + if ($thread->isStopped()) { + proc_terminate($process); + break; + } + + FiberManager::wait(); + } + + $output = stream_get_contents($pipes[1]); + $error = stream_get_contents($pipes[2]); + + fclose($pipes[1]); + fclose($pipes[2]); + + if ($error !== '' && is_string($error)) { + return $reject(new ThreadException($error)); + } else { + if (!is_bool($output)) { + $explode = explode(PHP_EOL, $output); + + foreach ($explode as $item) { + if ($item !== '') { + if (self::isPostMainThread($item)) self::loadSharedData($item); + if (self::isPost($item)) self::loadPost($item); + } + } + } + } + } else { + return $reject(new ThreadException(Error::UNABLE_START_THREAD)); + } + + proc_close($process); + unset(self::$threads[$this->getPid()]); + return $resolve(self::getPost($output)); + }); + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/ThreadException.php b/src/vennv/vapm/ThreadException.php new file mode 100644 index 000000000..20a6ef501 --- /dev/null +++ b/src/vennv/vapm/ThreadException.php @@ -0,0 +1,44 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use TypeError; + +final class ThreadException extends TypeError +{ + + public function __construct( + protected string $errorMessage, + protected int $errorCode = 0 + ) + { + parent::__construct($this->errorMessage, $this->errorCode); + } + + public function __toString(): string + { + return __CLASS__ . ": [$this->errorCode]: $this->errorMessage\n"; + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/Utils.php b/src/vennv/vapm/Utils.php new file mode 100644 index 000000000..0a59874dd --- /dev/null +++ b/src/vennv/vapm/Utils.php @@ -0,0 +1,165 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types = 1); + +namespace vennv\vapm; + +use Closure; +use Generator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use ReflectionException; +use ReflectionFunction; +use SplFileInfo; +use function preg_match; +use function file; +use function implode; +use function array_slice; + +interface UtilsInterface { + + /** + * Transform milliseconds to seconds + */ + public static function milliSecsToSecs(float $milliSecs) : float; + + /** + * @throws ReflectionException + * + * Transform a closure or callable to string + */ + public static function closureToString(Closure $closure) : string; + + /** + * Get all PHP files in a directory + */ + public static function getAllPHP(string $path) : Generator; + + /** + * @return array|string + * + * Transform a string to inline + */ + public static function outlineToInline(string $text) : array|string; + + /** + * @return array|string + * + * Fix input command + */ + public static function fixInputCommand(string $text) : array|string; + + /** + * @return null|string|array + * + * Remove comments from a string + */ + public static function removeComments(string $text) : null|string|array; + +} + +final class Utils implements UtilsInterface { + + public static function milliSecsToSecs(float $milliSecs) : float { + return $milliSecs / 1000; + } + + /** + * @throws ReflectionException + */ + public static function closureToString(Closure $closure) : string { + $reflection = new ReflectionFunction($closure); + $startLine = $reflection->getStartLine(); + $endLine = $reflection->getEndLine(); + $filename = $reflection->getFileName(); + + if ($filename === false || $startLine === false || $endLine === false) { + throw new ReflectionException(Error::CANNOT_FIND_FUNCTION_KEYWORD); + } + + $lines = file($filename); + if ($lines === false) { + throw new ReflectionException(Error::CANNOT_READ_FILE); + } + + $result = implode("", array_slice($lines, $startLine - 1, $endLine - $startLine + 1)); + + $startPos = strpos($result, 'function'); + if ($startPos === false) { + $startPos = strpos($result, 'fn'); + + if ($startPos === false) { + throw new ReflectionException(Error::CANNOT_FIND_FUNCTION_KEYWORD); + } + } + + $endBracketPos = strrpos($result, '}'); + if ($endBracketPos === false) { + throw new ReflectionException(Error::CANNOT_FIND_FUNCTION_KEYWORD); + } + + return substr($result, $startPos, $endBracketPos - $startPos + 1); + } + + public static function getAllPHP(string $path) : Generator { + $dir = new RecursiveDirectoryIterator($path); + $iterator = new RecursiveIteratorIterator($dir); + + foreach ($iterator as $file) { + if ($file instanceof SplFileInfo) { + $fname = $file->getFilename(); + + if (preg_match('%\.php$%', $fname) === 1) { + yield $file->getPathname(); + } + } + } + } + + /** + * @return array|string + */ + public static function outlineToInline(string $text) : array|string { + return str_replace(array("\r", "\n", "\t", ' '), '', $text); + } + + /** + * @return array|string + */ + public static function fixInputCommand(string $text) : array|string { + return str_replace('"', '\'', $text); + } + + /** + * @return null|string|array + * + * Remove comments from a string + */ + public static function removeComments(string $text) : null|string|array { + $text = preg_replace('/\/\/.*?(\r\n|\n|$)/', '', $text); + if ($text === null || is_array($text)) { + return null; + } + + return preg_replace('/\/\*.*?\*\//ms', '', $text); + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/VapmPMMP.php b/src/vennv/vapm/VapmPMMP.php new file mode 100644 index 000000000..20c6f9420 --- /dev/null +++ b/src/vennv/vapm/VapmPMMP.php @@ -0,0 +1,53 @@ + + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use pocketmine\plugin\PluginBase; +use pocketmine\scheduler\ClosureTask; + +interface VapmPMMPInterface +{ + + /** + * @param PluginBase $plugin + * @return void + * + * This function is used to initialize the VapmPMMP class. + * You should place this function in your onEnable() or onLoad() function. + */ + public static function init(PluginBase $plugin): void; + +} + +final class VapmPMMP implements VapmPMMPInterface +{ + + private static bool $isInit = false; + + public static function init(PluginBase $plugin): void + { + if (!self::$isInit) { + self::$isInit = true; + EventLoop::init(); + $plugin->getScheduler()->scheduleRepeatingTask(new ClosureTask(fn() => System::runEventLoop()), 1); + } + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/Work.php b/src/vennv/vapm/Work.php new file mode 100644 index 000000000..6308e99b8 --- /dev/null +++ b/src/vennv/vapm/Work.php @@ -0,0 +1,158 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use Generator; +use SplQueue; + +interface WorkInterface +{ + + /** + * @param callable $work + * @return void + * + * The work is a function that will be executed when the work is run. + */ + public function add(callable $work): void; + + /** + * @param int $index + * @return void + * + * Remove the work from the work list. + */ + public function remove(int $index): void; + + /** + * @return void + * + * Remove all works from the work list. + */ + public function clear(): void; + + /** + * @return int + * + * Get the number of works in the work list. + */ + public function count(): int; + + /** + * @return bool + * + * Check if the work list is empty. + */ + public function isEmpty(): bool; + + /** + * @return mixed + * + * Get the first work in the work list. + */ + public function dequeue(): mixed; + + /** + * @param int $number + * @return Generator + * + * Get the work list by number. + */ + public function getArrayByNumber(int $number): Generator; + + /** + * @return Generator + * + * Get all works in the work list. + */ + public function getAll(): Generator; + + /** + * @return void + * + * Run all works in the work list. + */ + public function run(): void; + +} + +final class Work implements WorkInterface +{ + + private SplQueue $queue; + + public function __construct() + { + $this->queue = new SplQueue(); + } + + public function add(callable $work): void + { + $this->queue->enqueue($work); + } + + public function remove(int $index): void + { + $this->queue->offsetUnset($index); + } + + public function clear(): void + { + $this->queue = new SplQueue(); + } + + public function count(): int + { + return $this->queue->count(); + } + + public function isEmpty(): bool + { + return $this->queue->isEmpty(); + } + + public function dequeue(): mixed + { + return $this->queue->dequeue(); + } + + public function getArrayByNumber(int $number): Generator + { + for ($i = 0; $i < $number; $i++) yield $this->queue->dequeue(); + } + + public function getAll(): Generator + { + while (!$this->queue->isEmpty()) yield $this->queue->dequeue(); + } + + public function run(): void + { + while (!$this->queue->isEmpty()) { + $work = $this->queue->dequeue(); + if (is_callable($work)) $work(); + } + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/Worker.php b/src/vennv/vapm/Worker.php new file mode 100644 index 000000000..759a1c669 --- /dev/null +++ b/src/vennv/vapm/Worker.php @@ -0,0 +1,280 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm; + +use Throwable; + +/** + * Class Worker + * @package vennv\vapm + * + * This class is used to create a worker to run the work. + * All asynchronous methods are based on this class. + */ +interface WorkerInterface +{ + + /** + * @return bool + * + * Check the worker is started. + */ + public function isStarted(): bool; + + /** + * @return Work + * + * Get the work. + */ + public function getWork(): Work; + + /** + * @return void + * + * This is method help you to remove the worker from the worker list. + * You should call this method when the work is done to avoid memory leaks. + */ + public function done(): void; + + /** + * @param mixed $result + * @return void + * + * Collect the result of the work. + */ + public function collect(mixed $result): void; + + /** + * @return array + * + * Get the result of the work. + */ + public function get(): array; + + /** + * @return bool + * + * Check the worker is locked. + */ + public function isLocked(): bool; + + /** + * @return void + * + * Lock the worker. + */ + public function lock(): void; + + /** + * @return void + * + * Unlock the worker. + */ + public function unlock(): void; + + /** + * @param Worker $worker + * @param callable $callback + * @return void + * + * Add a child worker to the parent worker. + */ + public function addWorker(Worker $worker, callable $callback): void; + + /** + * @return Async + * + * Run the work. + * @throws Throwable + */ + public function run(callable $callback): Async; + +} + +final class Worker implements WorkerInterface +{ + + private const LOCKED = "locked"; + + public bool $isStarted = false; + + public bool $isChild = false; + + protected static int $nextId = 0; + + public int $id; + + /** + * @var array + */ + private array $options; + + /** + * @var array> + */ + private array $childWorkers = []; + + /** + * @var array> + */ + private static array $workers = []; + + private Work $work; + + /** + * @param Work $work + * @param array $options + */ + public function __construct(Work $work, array $options = ["threads" => 4]) + { + $this->work = $work; + $this->options = $options; + $this->id = $this->generateId(); + + self::$workers[$this->id] = []; + } + + private function generateId(): int + { + if (self::$nextId >= PHP_INT_MAX) self::$nextId = 0; + return self::$nextId++; + } + + public function isStarted(): bool + { + return $this->isStarted; + } + + public function getWork(): Work + { + return $this->work; + } + + public function done(): void + { + if ($this->isChild) return; + unset(self::$workers[$this->id]); + } + + public function collect(mixed $result): void + { + self::$workers[$this->id][] = $result; + } + + /** + * @return array + */ + public function get(): array + { + return self::$workers[$this->id]; + } + + public function isLocked(): bool + { + return isset(self::$workers[$this->id][self::LOCKED]); + } + + public function lock(): void + { + self::$workers[$this->id][self::LOCKED] = true; + } + + public function unlock(): void + { + unset(self::$workers[$this->id][self::LOCKED]); + } + + public function addWorker(Worker $worker, callable $callback): void + { + $worker->isChild = true; + $this->childWorkers[] = [$worker, $callback]; + } + + /** + * @throws Throwable + */ + public function run(callable $callback): Async + { + $this->isStarted = true; + $work = $this->getWork(); + + return new Async(function () use ($work, $callback): void { + $threads = $this->options["threads"]; + + if ($threads >= 1) { + $promises = []; + $totalCountWorks = $work->count(); + while ($this->isLocked() || $totalCountWorks > 0) { + if (!$this->isLocked()) { + if (count($promises) < $threads && $work->count() > 0) { + $callbackQueue = $work->dequeue(); + + if (!is_callable($callbackQueue)) continue; + + $thread = new CoroutineThread($callbackQueue); + $promises[] = $thread->start(); + } else { + /** @var Promise $promise */ + foreach ($promises as $index => $promise) { + $result = EventLoop::getReturn($promise->getId()); + + if ($result !== null) { + $result = $promise->getResult(); + $this->collect($result); + unset($promises[$index]); + + $totalCountWorks--; + } + } + } + } + + FiberManager::wait(); + } + + while (count($this->childWorkers) > 0) { + $childWorker = array_shift($this->childWorkers); + + if ($childWorker !== null) { + /** @var WorkerInterface $worker */ + $worker = $childWorker[0]; + + /** @var callable $workerCallback */ + $workerCallback = $childWorker[1]; + + Async::await($worker->run($workerCallback)); + + $this->collect($worker->get()); + $worker->done(); + } + } + + $data = Async::await(Stream::flattenArray($this->get())); + + call_user_func($callback, $data, $this); + } + }); + } + +} \ No newline at end of file diff --git a/src/vennv/vapm/utils/Property.php b/src/vennv/vapm/utils/Property.php new file mode 100644 index 000000000..803241a28 --- /dev/null +++ b/src/vennv/vapm/utils/Property.php @@ -0,0 +1,52 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm\utils; + +use Exception; +use function property_exists; + +// This is trait for JsonData|StaticData class +trait Property +{ + + /** + * @param object $data + * @param array $options + * @return object + * @throws Exception + */ + public function update(object $data, array $options): object + { + /** + * @var string $key + * @var mixed $value + */ + foreach ($options as $key => $value) { + if (property_exists($data, $key)) $data->{$key} = $value; /* @phpstan-ignore-line */ + } + + return $data; + } + +} diff --git a/src/vennv/vapm/utils/Utils.php b/src/vennv/vapm/utils/Utils.php new file mode 100644 index 000000000..3be7f6e76 --- /dev/null +++ b/src/vennv/vapm/utils/Utils.php @@ -0,0 +1,319 @@ += 8.1 + * + * Copyright (C) 2023 VennDev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +declare(strict_types=1); + +namespace vennv\vapm\utils; + +use vennv\vapm\Error; +use Closure; +use Generator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use ReflectionException; +use ReflectionFunction; +use SplFileInfo; +use function array_slice; +use function file; +use function implode; +use function is_array; +use function is_object; +use function is_string; +use function preg_match; +use function serialize; +use function strlen; +use function strpos; +use function substr; + +interface UtilsInterface +{ + + /** + * Transform milliseconds to seconds + */ + public static function milliSecsToSecs(float $milliSecs): float; + + /** + * @throws ReflectionException + * + * Transform a closure or callable to string + */ + public static function closureToString(Closure $closure): string; + + /** + * Get all Dot files in a directory + */ + public static function getAllByDotFile(string $path, string $dotFile): Generator; + + /** + * @return array|string + * + * Transform a string to inline + */ + public static function outlineToInline(string $text): array|string; + + /** + * @return array|string + * + * Fix input command + */ + public static function fixInputCommand(string $text): array|string; + + /** + * @return null|string|array + * + * Remove comments from a string + */ + public static function removeComments(string $text): null|string|array; + + /** + * @param mixed $data + * + * Get bytes of a string or object or array + */ + public static function getBytes(mixed $data): int; + + /** + * @return Generator + * + * Split a string by slash + */ + public static function splitStringBySlash(string $string): Generator; + + /** + * @return false|string + * + * Replace path + */ + public static function replacePath(string $path, string $segment): false|string; + + /** + * @return array|string|null + * + * Replace advanced + */ + public static function replaceAdvanced(string $text, string $search, string $replace): array|string|null; + + /** + * @return Generator + * + * Evenly divide a number + */ + public static function evenlyDivide(int $number, int $parts): Generator; + + /** + * @param array $array + * @param int $size + * @return Generator + */ + public static function splitArray(array $array, int $size): Generator; + + /** + * @param string $class + * @return bool + * + * This method is used to check if the current class is the same as the class passed in + */ + public static function isClass(string $class): bool; + +} + +final class Utils implements UtilsInterface +{ + + public static function milliSecsToSecs(float $milliSecs): float + { + return $milliSecs / 1000; + } + + /** + * @throws ReflectionException + */ + public static function closureToString(Closure $closure): string + { + $reflection = new ReflectionFunction($closure); + $startLine = $reflection->getStartLine(); + $endLine = $reflection->getEndLine(); + $filename = $reflection->getFileName(); + + if ($filename === false || $startLine === false || $endLine === false) throw new ReflectionException(Error::CANNOT_FIND_FUNCTION_KEYWORD); + + $lines = file($filename); + if ($lines === false) throw new ReflectionException(Error::CANNOT_READ_FILE); + + $result = implode("", array_slice($lines, $startLine - 1, $endLine - $startLine + 1)); + $startPos = strpos($result, 'function'); + if ($startPos === false) { + $startPos = strpos($result, 'fn'); + if ($startPos === false) throw new ReflectionException(Error::CANNOT_FIND_FUNCTION_KEYWORD); + } + + $endBracketPos = strrpos($result, '}'); + if ($endBracketPos === false) throw new ReflectionException(Error::CANNOT_FIND_FUNCTION_KEYWORD); + + return substr($result, $startPos, $endBracketPos - $startPos + 1); + } + + public static function getAllByDotFile(string $path, string $dotFile): Generator + { + $dir = new RecursiveDirectoryIterator($path); + $iterator = new RecursiveIteratorIterator($dir); + + foreach ($iterator as $file) { + if ($file instanceof SplFileInfo && preg_match('%' . $dotFile . '$%', $file->getFilename()) === 1) yield $file->getPathname(); + } + } + + /** + * @return array|string + */ + public static function outlineToInline(string $text): array|string + { + return str_replace(array("\r", "\n", "\t", ' '), '', $text); + } + + /** + * @return array|string + */ + public static function fixInputCommand(string $text): array|string + { + return str_replace('"', '\'', $text); + } + + /** + * @return null|string|array + * + * Remove comments from a string + */ + public static function removeComments(string $text): null|string|array + { + $text = preg_replace('/\/\/.*?(\r\n|\n|$)/', '', $text); + if ($text === null || is_array($text)) return null; + return preg_replace('/\/\*.*?\*\//ms', '', $text); + } + + /** + * @param mixed $data + * + * Get bytes of a string or object or array + */ + public static function getBytes(mixed $data): int + { + if (is_string($data)) return strlen($data); + if (is_object($data) || is_array($data)) return strlen(serialize($data)); + return 0; + } + + /** + * @return Generator + * + * Split a string by slash + */ + public static function splitStringBySlash(string $string): Generator + { + $parts = explode('/', $string); + foreach ($parts as $value) { + $path = '/' . $value; + if ($path !== '/') yield $path; + } + } + + /** + * @return false|string + * + * Replace path + */ + public static function replacePath(string $path, string $segment): false|string + { + $pos = strpos($path, $segment); + if ($pos === false) return false; + return substr($path, $pos + strlen($segment)); + } + + /** + * @return array|string|null + * + * Replace advanced + */ + public static function replaceAdvanced(string $text, string $search, string $replace): array|string|null + { + return preg_replace('/(? 0 ? 1 : 0); + $remainder--; + } + } + + /** + * @param array $array + * @param int $size + * @return Generator + */ + public static function splitArray(array $array, int $size): Generator + { + $totalItems = count($array); + $quotient = intdiv($totalItems, $size); + $remainder = $totalItems % $size; + + $offset = 0; + for ($i = 0; $i < $size; $i++) { + $length = $quotient + ($remainder > 0 ? 1 : 0); + + yield array_slice($array, $offset, $length); + + $offset += $length; + $remainder--; + } + } + + /** + * @param string $class + * @return bool + * @throws ReflectionException + */ + public static function isClass(string $class): bool + { + $trace = debug_backtrace(); + if (isset($trace[2])) { + if (!empty($trace[2]['args'])) { + $args = $trace[2]['args']; + /** @var Closure $closure */ + $closure = $args[0]; + $reflectionFunction = new ReflectionFunction($closure); + $scopeClass = $reflectionFunction->getClosureScopeClass(); + if ($scopeClass === null) return false; + return $scopeClass->getName() === $class; + } else { + return true; // This is a class + } + } + + return false; + } + +} \ No newline at end of file