Extract GeneratorExecutor system from World, v2 (#6682)

- `AsyncGeneratorExecutor` class added that encapsulates the logic of generating chunks using async tasks as previously
- `GeneratorExecutor` interface added that can be implemented to provide chunks in other ways
- `SyncGeneratorExecutor` which invokes the generator directly on the main thread, useful for simple generators like `Flat` where async tasks are not needed
- Some redundant APIs were removed from `World` (these will probably come back as deprecated stubs for the remainder of 5.x, but I was having too much fun deleting code)
- Removed internal `World->registerGeneratorToWorker()` (no longer useful)
- `World` now invokes generator executor instead of posting AsyncTasks directly
- Some internal classes moved to `pocketmine\world\generator\executor` (PopulationTask excluded because plugins use it in lieu of being able to regenerate chunks
- Generators can opt into main-thread execution by setting the `$fast` parameter to `true` in `GeneratorManager::register()`
This commit is contained in:
Dylan T.
2025-05-27 21:51:10 +01:00
committed by GitHub
parent dd17adeaaf
commit 059f4ee7bf
12 changed files with 318 additions and 94 deletions

View File

@@ -93,9 +93,11 @@ use pocketmine\world\format\io\GlobalBlockStateHandlers;
use pocketmine\world\format\io\WritableWorldProvider;
use pocketmine\world\format\LightArray;
use pocketmine\world\format\SubChunk;
use pocketmine\world\generator\executor\AsyncGeneratorExecutor;
use pocketmine\world\generator\executor\GeneratorExecutor;
use pocketmine\world\generator\executor\GeneratorExecutorSetupParameters;
use pocketmine\world\generator\executor\SyncGeneratorExecutor;
use pocketmine\world\generator\GeneratorManager;
use pocketmine\world\generator\GeneratorRegisterTask;
use pocketmine\world\generator\GeneratorUnregisterTask;
use pocketmine\world\generator\PopulationTask;
use pocketmine\world\light\BlockLightUpdate;
use pocketmine\world\light\LightPopulationTask;
@@ -336,11 +338,7 @@ class World implements ChunkManager{
*/
private array $chunkPopulationRequestQueueIndex = [];
/**
* @var true[]
* @phpstan-var array<int, true>
*/
private array $generatorRegisteredWorkers = [];
private readonly GeneratorExecutor $generatorExecutor;
private bool $autoSave = true;
@@ -360,9 +358,6 @@ class World implements ChunkManager{
private bool $doingTick = false;
/** @phpstan-var class-string<generator\Generator> */
private string $generator;
private bool $unloaded = false;
/**
* @var \Closure[]
@@ -498,7 +493,23 @@ class World implements ChunkManager{
$generator = GeneratorManager::getInstance()->getGenerator($this->provider->getWorldData()->getGenerator()) ??
throw new AssumptionFailedError("WorldManager should already have checked that the generator exists");
$generator->validateGeneratorOptions($this->provider->getWorldData()->getGeneratorOptions());
$this->generator = $generator->getGeneratorClass();
$executorSetupParameters = new GeneratorExecutorSetupParameters(
worldMinY: $this->minY,
worldMaxY: $this->maxY,
generatorSeed: $this->getSeed(),
generatorClass: $generator->getGeneratorClass(),
generatorSettings: $this->provider->getWorldData()->getGeneratorOptions()
);
$this->generatorExecutor = $generator->isFast() ?
new SyncGeneratorExecutor($executorSetupParameters) :
new AsyncGeneratorExecutor(
$this->logger,
$this->workerPool,
$executorSetupParameters,
$this->worldId
);
$this->chunkPopulationRequestQueue = new \SplQueue();
$this->addOnUnloadCallback(function() : void{
$this->logger->debug("Cancelling unfulfilled generation requests");
@@ -534,17 +545,6 @@ class World implements ChunkManager{
$this->initRandomTickBlocksFromConfig($cfg);
$this->timings = new WorldTimings($this);
$this->workerPool->addWorkerStartHook($workerStartHook = function(int $workerId) : void{
if(array_key_exists($workerId, $this->generatorRegisteredWorkers)){
$this->logger->debug("Worker $workerId with previously registered generator restarted, flagging as unregistered");
unset($this->generatorRegisteredWorkers[$workerId]);
}
});
$workerPool = $this->workerPool;
$this->addOnUnloadCallback(static function() use ($workerPool, $workerStartHook) : void{
$workerPool->removeWorkerStartHook($workerStartHook);
});
}
private function initRandomTickBlocksFromConfig(ServerConfigGroup $cfg) : void{
@@ -585,21 +585,6 @@ class World implements ChunkManager{
return $this->tickRateTime;
}
public function registerGeneratorToWorker(int $worker) : void{
$this->logger->debug("Registering generator on worker $worker");
$this->workerPool->submitTaskToWorker(new GeneratorRegisterTask($this, $this->generator, $this->provider->getWorldData()->getGeneratorOptions()), $worker);
$this->generatorRegisteredWorkers[$worker] = true;
}
public function unregisterGenerator() : void{
foreach($this->workerPool->getRunningWorkers() as $i){
if(isset($this->generatorRegisteredWorkers[$i])){
$this->workerPool->submitTaskToWorker(new GeneratorUnregisterTask($this), $i);
}
}
$this->generatorRegisteredWorkers = [];
}
public function getServer() : Server{
return $this->server;
}
@@ -657,7 +642,7 @@ class World implements ChunkManager{
$this->save();
$this->unregisterGenerator();
$this->generatorExecutor->shutdown();
$this->provider->close();
$this->blockCache = [];
@@ -3486,8 +3471,8 @@ class World implements ChunkManager{
$centerChunk = $this->loadChunk($chunkX, $chunkZ);
$adjacentChunks = $this->getAdjacentChunks($chunkX, $chunkZ);
$task = new PopulationTask(
$this->worldId,
$this->generatorExecutor->populate(
$chunkX,
$chunkZ,
$centerChunk,
@@ -3500,15 +3485,6 @@ class World implements ChunkManager{
$this->generateChunkCallback($chunkPopulationLockId, $chunkX, $chunkZ, $centerChunk, $adjacentChunks, $temporaryChunkLoader);
}
);
$workerId = $this->workerPool->selectWorker();
if(!isset($this->workerPool->getRunningWorkers()[$workerId]) && isset($this->generatorRegisteredWorkers[$workerId])){
$this->logger->debug("Selected worker $workerId previously had generator registered, but is now offline");
unset($this->generatorRegisteredWorkers[$workerId]);
}
if(!isset($this->generatorRegisteredWorkers[$workerId])){
$this->registerGeneratorToWorker($workerId);
}
$this->workerPool->submitTaskToWorker($task, $workerId);
return $resolver->getPromise();
}finally{