Tidy, allow using closures to setup generators instead of class strings

This commit is contained in:
Dylan K. Taylor 2025-04-06 19:00:33 +01:00
parent 09c074ed22
commit a4ae1991c6
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
6 changed files with 67 additions and 51 deletions

View File

@ -92,8 +92,12 @@ use pocketmine\world\format\io\GlobalBlockStateHandlers;
use pocketmine\world\format\io\WritableWorldProvider; use pocketmine\world\format\io\WritableWorldProvider;
use pocketmine\world\format\LightArray; use pocketmine\world\format\LightArray;
use pocketmine\world\format\SubChunk; use pocketmine\world\format\SubChunk;
use pocketmine\world\generator\executor\AsyncGeneratorExecutor;
use pocketmine\world\generator\executor\GeneratorExecutor;
use pocketmine\world\generator\executor\SyncGeneratorExecutor;
use pocketmine\world\generator\Generator; use pocketmine\world\generator\Generator;
use pocketmine\world\generator\GeneratorManager; use pocketmine\world\generator\GeneratorManager;
use pocketmine\world\generator\GeneratorManagerEntry;
use pocketmine\world\light\BlockLightUpdate; use pocketmine\world\light\BlockLightUpdate;
use pocketmine\world\light\LightPopulationTask; use pocketmine\world\light\LightPopulationTask;
use pocketmine\world\light\SkyLightUpdate; use pocketmine\world\light\SkyLightUpdate;
@ -306,7 +310,7 @@ class World implements ChunkManager{
*/ */
private array $neighbourBlockUpdateQueueIndex = []; private array $neighbourBlockUpdateQueueIndex = [];
private readonly ChunkGenerator $chunkGenerator; private readonly GeneratorExecutor $generatorExecutor;
/** /**
* @var ChunkLockId[] * @var ChunkLockId[]
@ -332,9 +336,6 @@ class World implements ChunkManager{
private bool $doingTick = false; private bool $doingTick = false;
/** @phpstan-var class-string<\pocketmine\world\generator\Generator> */
private string $generator;
private bool $unloaded = false; private bool $unloaded = false;
/** /**
* @var \Closure[] * @var \Closure[]
@ -471,25 +472,24 @@ class World implements ChunkManager{
throw new AssumptionFailedError("WorldManager should already have checked that the generator exists"); throw new AssumptionFailedError("WorldManager should already have checked that the generator exists");
$generatorOptions = $this->provider->getWorldData()->getGeneratorOptions(); $generatorOptions = $this->provider->getWorldData()->getGeneratorOptions();
$generator->validateGeneratorOptions($generatorOptions); $generator->validateGeneratorOptions($generatorOptions);
$this->generator = $generator->getGeneratorClass(); $generatorClass = $generator->getGeneratorClass();
$cfg = $this->server->getConfigGroup(); $cfg = $this->server->getConfigGroup();
$seed = $this->getSeed();
if($generator->isFast()){ if($generator->isFast()){
/** $this->generatorExecutor = new SyncGeneratorExecutor(GeneratorManagerEntry::make($generatorClass, $seed, $generatorOptions));
* @see Generator::__construct() $this->logger->debug("Using main thread generator system for fast generator " . $generatorClass);
*/
$this->chunkGenerator = new SyncChunkGenerator(new $this->generator($this->getSeed(), $generatorOptions));
$this->logger->debug("Using main thread generator system for fast generator " . $this->generator);
}else{ }else{
$this->chunkGenerator = new AsyncChunkGenerator( $this->generatorExecutor = new AsyncGeneratorExecutor(
$this->workerPool, $this->workerPool,
$this->logger, $this->logger,
static fn() => GeneratorManagerEntry::make($generatorClass, $seed, $generatorOptions),
$cfg->getPropertyInt(YmlServerProperties::CHUNK_GENERATION_POPULATION_QUEUE_SIZE, 2) $cfg->getPropertyInt(YmlServerProperties::CHUNK_GENERATION_POPULATION_QUEUE_SIZE, 2)
); );
$this->logger->debug("Using async task generator system for slow generator " . $this->generator); $this->logger->debug("Using async task generator system for slow generator " . $generatorClass);
} }
$this->addOnUnloadCallback(function() : void{ $this->addOnUnloadCallback(function() : void{
$this->chunkGenerator->shutdown($this); $this->generatorExecutor->shutdown($this);
}); });
$this->scheduledBlockUpdateQueue = new ReversePriorityQueue(); $this->scheduledBlockUpdateQueue = new ReversePriorityQueue();
@ -549,13 +549,6 @@ class World implements ChunkManager{
return $this->tickRateTime; return $this->tickRateTime;
} }
/**
* @phpstan-return class-string<covariant \pocketmine\world\generator\Generator>
*/
public function getGeneratorClass() : string{
return $this->generator;
}
public function getServer() : Server{ public function getServer() : Server{
return $this->server; return $this->server;
} }
@ -791,7 +784,7 @@ class World implements ChunkManager{
if(count($this->chunkLoaders[$chunkHash]) === 1){ if(count($this->chunkLoaders[$chunkHash]) === 1){
unset($this->chunkLoaders[$chunkHash]); unset($this->chunkLoaders[$chunkHash]);
$this->unloadChunkRequest($chunkX, $chunkZ, true); $this->unloadChunkRequest($chunkX, $chunkZ, true);
$this->chunkGenerator->cancelChunkPopulation($this, $chunkX, $chunkZ); $this->generatorExecutor->cancelChunkPopulation($this, $chunkX, $chunkZ);
}else{ }else{
unset($this->chunkLoaders[$chunkHash][$loaderId]); unset($this->chunkLoaders[$chunkHash][$loaderId]);
} }
@ -3068,7 +3061,7 @@ class World implements ChunkManager{
unset($this->registeredTickingChunks[$chunkHash]); unset($this->registeredTickingChunks[$chunkHash]);
$this->markTickingChunkForRecheck($x, $z); $this->markTickingChunkForRecheck($x, $z);
$this->chunkGenerator->cancelChunkPopulation($this, $x, $z); $this->generatorExecutor->cancelChunkPopulation($this, $x, $z);
$this->timings->doChunkUnload->stopTiming(); $this->timings->doChunkUnload->stopTiming();
@ -3265,7 +3258,7 @@ class World implements ChunkManager{
* @phpstan-return Promise<Chunk> * @phpstan-return Promise<Chunk>
*/ */
public function requestChunkPopulation(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader) : Promise{ public function requestChunkPopulation(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader) : Promise{
return $this->chunkGenerator->requestChunkPopulation($this, $chunkX, $chunkZ, $associatedChunkLoader); return $this->generatorExecutor->requestChunkPopulation($this, $chunkX, $chunkZ, $associatedChunkLoader);
} }
/** /**
@ -3279,7 +3272,7 @@ class World implements ChunkManager{
* @phpstan-return Promise<Chunk> * @phpstan-return Promise<Chunk>
*/ */
public function orderChunkPopulation(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader) : Promise{ public function orderChunkPopulation(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader) : Promise{
return $this->chunkGenerator->orderChunkPopulation($this, $chunkX, $chunkZ, $associatedChunkLoader); return $this->generatorExecutor->orderChunkPopulation($this, $chunkX, $chunkZ, $associatedChunkLoader);
} }
public function doChunkGarbageCollection() : void{ public function doChunkGarbageCollection() : void{

View File

@ -50,4 +50,14 @@ final class GeneratorManagerEntry{
public function isFast() : bool{ public function isFast() : bool{
return $this->fast; return $this->fast;
} }
/**
* @phpstan-param class-string<covariant Generator> $class
*/
public static function make(string $class, int $seed, string $options) : Generator{
/**
* @see Generator::__construct()
*/
return new $class($seed, $options);
}
} }

View File

@ -27,31 +27,24 @@ use pocketmine\scheduler\AsyncTask;
use pocketmine\world\World; use pocketmine\world\World;
class GeneratorRegisterTask extends AsyncTask{ class GeneratorRegisterTask extends AsyncTask{
public int $seed;
public int $worldId; public int $worldId;
public int $worldMinY; public int $worldMinY;
public int $worldMaxY; public int $worldMaxY;
/** /**
* @phpstan-param class-string<Generator> $generatorClass * @phpstan-param \Closure() : Generator $generatorFactory
*/ */
public function __construct( public function __construct(
World $world, World $world,
public string $generatorClass, private readonly \Closure $generatorFactory
public string $generatorSettings
){ ){
$this->seed = $world->getSeed();
$this->worldId = $world->getId(); $this->worldId = $world->getId();
$this->worldMinY = $world->getMinY(); $this->worldMinY = $world->getMinY();
$this->worldMaxY = $world->getMaxY(); $this->worldMaxY = $world->getMaxY();
} }
public function onRun() : void{ public function onRun() : void{
/** $generator = ($this->generatorFactory)();
* @var Generator $generator
* @see Generator::__construct()
*/
$generator = new $this->generatorClass($this->seed, $this->generatorSettings);
ThreadLocalGeneratorContext::register(new ThreadLocalGeneratorContext($generator, $this->worldMinY, $this->worldMaxY), $this->worldId); ThreadLocalGeneratorContext::register(new ThreadLocalGeneratorContext($generator, $this->worldMinY, $this->worldMaxY), $this->worldId);
} }
} }

View File

@ -21,16 +21,21 @@
declare(strict_types=1); declare(strict_types=1);
namespace pocketmine\world; namespace pocketmine\world\generator\executor;
use pmmp\thread\ThreadSafeArray;
use pocketmine\event\world\ChunkPopulateEvent; use pocketmine\event\world\ChunkPopulateEvent;
use pocketmine\promise\Promise; use pocketmine\promise\Promise;
use pocketmine\promise\PromiseResolver; use pocketmine\promise\PromiseResolver;
use pocketmine\scheduler\AsyncPool; use pocketmine\scheduler\AsyncPool;
use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\ChunkLoader;
use pocketmine\world\ChunkLockId;
use pocketmine\world\format\Chunk; use pocketmine\world\format\Chunk;
use pocketmine\world\generator\Generator;
use pocketmine\world\generator\GeneratorRegisterTask; use pocketmine\world\generator\GeneratorRegisterTask;
use pocketmine\world\generator\PopulationTask; use pocketmine\world\generator\PopulationTask;
use pocketmine\world\World;
use function array_key_exists; use function array_key_exists;
use function assert; use function assert;
use function count; use function count;
@ -38,7 +43,7 @@ use function count;
/** /**
* @phpstan-import-type ChunkPosHash from World * @phpstan-import-type ChunkPosHash from World
*/ */
final class AsyncChunkGenerator implements ChunkGenerator{ final class AsyncGeneratorExecutor implements GeneratorExecutor{
/** /**
* @var bool[] chunkHash => isValid * @var bool[] chunkHash => isValid
* @phpstan-var array<ChunkPosHash, bool> * @phpstan-var array<ChunkPosHash, bool>
@ -71,11 +76,19 @@ final class AsyncChunkGenerator implements ChunkGenerator{
/** @phpstan-var \Closure(int) : void */ /** @phpstan-var \Closure(int) : void */
private \Closure $workerStartHook; private \Closure $workerStartHook;
/**
* @phpstan-param \Closure() : Generator $generatorFactory Must be a thread-safe closure
*/
public function __construct( public function __construct(
private readonly AsyncPool $workerPool, private readonly AsyncPool $workerPool,
private readonly \Logger $logger, private readonly \Logger $logger,
private readonly int $maxConcurrentChunkPopulationTasks = 2, private readonly \Closure $generatorFactory,
private readonly int $maxConcurrentChunkPopulationTasks = 2
){ ){
//TODO: we really need a better way to check if a closure is thread-safe :(
$temp = new ThreadSafeArray();
$temp["dummy"] = $this->generatorFactory;
$this->chunkPopulationRequestQueue = new \SplQueue(); $this->chunkPopulationRequestQueue = new \SplQueue();
//TODO: don't love the circular reference here, but we need to make sure this gets cleaned up on shutdown //TODO: don't love the circular reference here, but we need to make sure this gets cleaned up on shutdown
$this->workerStartHook = function(int $workerId) : void{ $this->workerStartHook = function(int $workerId) : void{
@ -91,8 +104,7 @@ final class AsyncChunkGenerator implements ChunkGenerator{
$world->getLogger()->debug("Registering generator on worker $worker"); $world->getLogger()->debug("Registering generator on worker $worker");
$this->workerPool->submitTaskToWorker(new GeneratorRegisterTask( $this->workerPool->submitTaskToWorker(new GeneratorRegisterTask(
$world, $world,
$world->getGeneratorClass(), $this->generatorFactory,
$world->getProvider()->getWorldData()->getGeneratorOptions()
), $worker); ), $worker);
$this->generatorRegisteredWorkers[$worker] = true; $this->generatorRegisteredWorkers[$worker] = true;
} }

View File

@ -21,19 +21,25 @@
declare(strict_types=1); declare(strict_types=1);
namespace pocketmine\world; namespace pocketmine\world\generator\executor;
use pocketmine\promise\Promise; use pocketmine\promise\Promise;
use pocketmine\world\ChunkLoader;
use pocketmine\world\format\Chunk; use pocketmine\world\format\Chunk;
use pocketmine\world\World;
/** /**
* Decides how and when to invoke the world generator.
*
* @phpstan-import-type ChunkPosHash from World * @phpstan-import-type ChunkPosHash from World
*/ */
interface ChunkGenerator{ interface GeneratorExecutor{
/** /**
* Attempts to initiate asynchronous generation/population of the target chunk, if it's currently reasonable to do * Requests generation/population of the target chunk, if it's currently reasonable to do so (and if it isn't
* so (and if it isn't already generated/populated). * already generated/populated).
* If the generator is busy, the request will be put into a queue and delayed until a better time. * The executor may decide not to process this request immediately based on internal conditions (e.g. the number of
* concurrently active generation tasks may have reached a limit). If this happens, it will be queued and processed
* as soon as possible.
* *
* A ChunkLoader can be associated with the generation request to ensure that the generation request is cancelled if * A ChunkLoader can be associated with the generation request to ensure that the generation request is cancelled if
* no loaders are attached to the target chunk. If no loader is provided, one will be assigned (and automatically * no loaders are attached to the target chunk. If no loader is provided, one will be assigned (and automatically
@ -44,12 +50,12 @@ interface ChunkGenerator{
public function requestChunkPopulation(World $world, int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader) : Promise; public function requestChunkPopulation(World $world, int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader) : Promise;
/** /**
* Initiates asynchronous generation/population of the target chunk, if it's not already generated/populated. * Initiates generation/population of the target chunk, if it's not already generated/populated.
* If generation has already been requested for the target chunk, the promise for the already active request will be
* returned directly.
* *
* If the chunk is currently locked (for example due to another chunk using it for async generation), the request * This function will begin processing the request immediately, unless any of the adjacent chunks are currently in
* will be queued and executed at the earliest opportunity. * use for other generation tasks.
*
* @see World::isChunkLocked()
* *
* @phpstan-return Promise<Chunk> * @phpstan-return Promise<Chunk>
*/ */

View File

@ -21,20 +21,22 @@
declare(strict_types=1); declare(strict_types=1);
namespace pocketmine\world; namespace pocketmine\world\generator\executor;
use pocketmine\event\world\ChunkPopulateEvent; use pocketmine\event\world\ChunkPopulateEvent;
use pocketmine\promise\Promise; use pocketmine\promise\Promise;
use pocketmine\promise\PromiseResolver; use pocketmine\promise\PromiseResolver;
use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\ChunkLoader;
use pocketmine\world\format\Chunk; use pocketmine\world\format\Chunk;
use pocketmine\world\generator\Generator; use pocketmine\world\generator\Generator;
use pocketmine\world\World;
/** /**
* Very simple chunk generator which runs everything immediately on the main thread. * Very simple chunk generator which runs everything immediately on the main thread.
* Useful if your generator is very fast and doesn't benefit from async tasks or threading. * Useful if your generator is very fast and doesn't benefit from async tasks or threading.
*/ */
final class SyncChunkGenerator implements ChunkGenerator{ final class SyncGeneratorExecutor implements GeneratorExecutor{
public function __construct( public function __construct(
private readonly Generator $generator private readonly Generator $generator
){} ){}