Absorb pocketmine/classloader into the core code

the only use for this class is to facilitate random runtime plugin loading, and it's not complete even for that purpose.

Since nothing but PM uses pocketmine/classloader anyway, it doesn't make sense to have it outside the core. As with LogPthreads, it's just adding more maintenance work.
This commit is contained in:
Dylan K. Taylor
2023-05-22 22:52:43 +01:00
parent d2c34615f5
commit 4aba9d9725
9 changed files with 199 additions and 60 deletions

View File

@ -30,8 +30,8 @@ use function error_reporting;
trait CommonThreadPartsTrait{
/**
* @var ThreadSafeArray|\ClassLoader[]|null
* @phpstan-var ThreadSafeArray<int, \ClassLoader>|null
* @var ThreadSafeArray|ThreadSafeClassLoader[]|null
* @phpstan-var ThreadSafeArray<int, ThreadSafeClassLoader>|null
*/
private ?ThreadSafeArray $classLoaders = null;
protected ?string $composerAutoloaderPath = null;
@ -39,14 +39,14 @@ trait CommonThreadPartsTrait{
protected bool $isKilled = false;
/**
* @return \ClassLoader[]
* @return ThreadSafeClassLoader[]
*/
public function getClassLoaders() : ?array{
return $this->classLoaders !== null ? (array) $this->classLoaders : null;
}
/**
* @param \ClassLoader[] $autoloaders
* @param ThreadSafeClassLoader[] $autoloaders
*/
public function setClassLoaders(?array $autoloaders = null) : void{
$this->composerAutoloaderPath = \pocketmine\COMPOSER_AUTOLOADER_PATH;
@ -82,7 +82,7 @@ trait CommonThreadPartsTrait{
$autoloaders = $this->classLoaders;
if($autoloaders !== null){
foreach($autoloaders as $autoloader){
/** @var \ClassLoader $autoloader */
/** @var ThreadSafeClassLoader $autoloader */
$autoloader->register(false);
}
}

View File

@ -0,0 +1,182 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\thread;
use pmmp\thread\ThreadSafe;
use pmmp\thread\ThreadSafeArray;
use function class_exists;
use function count;
use function explode;
use function file_exists;
use function interface_exists;
use function method_exists;
use function spl_autoload_register;
use function str_replace;
use function strrpos;
use function substr;
use function trait_exists;
use function trim;
use const DIRECTORY_SEPARATOR;
/**
* This autoloader can be used and updated from multiple threads.
* Useful if classes need to be dynamically added after threads have already been started.
*
* This is used to facilitate loading plugin classes, enabling plugins to be loaded after the server has started.
*/
class ThreadSafeClassLoader extends ThreadSafe{
/**
* @var ThreadSafeArray|string[]
* @phpstan-var ThreadSafeArray<int, string>
*/
private $fallbackLookup;
/**
* @var ThreadSafeArray|string[][]
* @phpstan-var ThreadSafeArray<string, ThreadSafeArray<int, string>>
*/
private $psr4Lookup;
public function __construct(){
$this->fallbackLookup = new ThreadSafeArray();
$this->psr4Lookup = new ThreadSafeArray();
}
protected function normalizePath(string $path) : string{
$parts = explode("://", $path, 2);
if(count($parts) === 2){
return $parts[0] . "://" . str_replace('/', DIRECTORY_SEPARATOR, $parts[1]);
}
return str_replace('/', DIRECTORY_SEPARATOR, $parts[0]);
}
public function addPath(string $namespacePrefix, string $path, bool $prepend = false) : void{
$path = $this->normalizePath($path);
if($namespacePrefix === '' || $namespacePrefix === '\\'){
$this->fallbackLookup->synchronized(function() use ($path, $prepend) : void{
$this->appendOrPrependLookupEntry($this->fallbackLookup, $path, $prepend);
});
}else{
$namespacePrefix = trim($namespacePrefix, '\\') . '\\';
$this->psr4Lookup->synchronized(function() use ($namespacePrefix, $path, $prepend) : void{
$list = $this->psr4Lookup[$namespacePrefix] ?? null;
if($list === null){
$list = $this->psr4Lookup[$namespacePrefix] = new ThreadSafeArray();
}
$this->appendOrPrependLookupEntry($list, $path, $prepend);
});
}
}
/**
* @phpstan-param ThreadSafeArray<int, string> $list
*/
protected function appendOrPrependLookupEntry(ThreadSafeArray $list, string $entry, bool $prepend) : void{
if($prepend){
$entries = $this->getAndRemoveLookupEntries($list);
$list[] = $entry;
foreach($entries as $removedEntry){
$list[] = $removedEntry;
}
}else{
$list[] = $entry;
}
}
/**
* @return string[]
*
* @phpstan-param ThreadSafeArray<int, string> $list
* @phpstan-return list<string>
*/
protected function getAndRemoveLookupEntries(ThreadSafeArray $list) : array{
$entries = [];
while(($entry = $list->shift()) !== null){
$entries[] = $entry;
}
return $entries;
}
public function register(bool $prepend = false) : bool{
return spl_autoload_register(function(string $name) : void{
$this->loadClass($name);
}, true, $prepend);
}
/**
* Called when there is a class to load
*/
public function loadClass(string $name) : bool{
$path = $this->findClass($name);
if($path !== null){
include($path);
if(!class_exists($name, false) && !interface_exists($name, false) && !trait_exists($name, false)){
return false;
}
if(method_exists($name, "onClassLoaded") && (new \ReflectionClass($name))->getMethod("onClassLoaded")->isStatic()){
$name::onClassLoaded();
}
return true;
}
return false;
}
/**
* Returns the path for the class, if any
*/
public function findClass(string $name) : ?string{
$baseName = str_replace("\\", DIRECTORY_SEPARATOR, $name);
foreach($this->fallbackLookup as $path){
$filename = $path . DIRECTORY_SEPARATOR . $baseName . ".php";
if(file_exists($filename)){
return $filename;
}
}
// PSR-4 lookup
$logicalPathPsr4 = $baseName . ".php";
return $this->psr4Lookup->synchronized(function() use ($name, $logicalPathPsr4) : ?string{
$subPath = $name;
while(false !== $lastPos = strrpos($subPath, '\\')){
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
$lookup = $this->psr4Lookup[$search] ?? null;
if($lookup !== null){
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach($lookup as $dir){
if(file_exists($file = $dir . $pathEnd)){
return $file;
}
}
}
}
return null;
});
}
}