mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-08 02:42:58 +00:00
Relocate cache-related classes to mcpe\cache namespace
This commit is contained in:
225
src/network/mcpe/cache/ChunkCache.php
vendored
Normal file
225
src/network/mcpe/cache/ChunkCache.php
vendored
Normal file
@ -0,0 +1,225 @@
|
||||
<?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\network\mcpe\cache;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\ChunkRequestTask;
|
||||
use pocketmine\network\mcpe\compression\CompressBatchPromise;
|
||||
use pocketmine\network\mcpe\compression\Compressor;
|
||||
use pocketmine\world\ChunkListener;
|
||||
use pocketmine\world\ChunkListenerNoOpTrait;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\World;
|
||||
use function spl_object_id;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* This class is used by the current MCPE protocol system to store cached chunk packets for fast resending.
|
||||
*
|
||||
* TODO: make MemoryManager aware of this so the cache can be destroyed when memory is low
|
||||
* TODO: this needs a hook for world unloading
|
||||
*/
|
||||
class ChunkCache implements ChunkListener{
|
||||
/** @var self[][] */
|
||||
private static $instances = [];
|
||||
|
||||
/**
|
||||
* Fetches the ChunkCache instance for the given world. This lazily creates cache systems as needed.
|
||||
*
|
||||
* @return ChunkCache
|
||||
*/
|
||||
public static function getInstance(World $world, Compressor $compressor) : self{
|
||||
$worldId = spl_object_id($world);
|
||||
$compressorId = spl_object_id($compressor);
|
||||
if(!isset(self::$instances[$worldId])){
|
||||
self::$instances[$worldId] = [];
|
||||
$world->addOnUnloadCallback(static function() use ($worldId) : void{
|
||||
foreach(self::$instances[$worldId] as $cache){
|
||||
$cache->caches = [];
|
||||
}
|
||||
unset(self::$instances[$worldId]);
|
||||
\GlobalLogger::get()->debug("Destroyed chunk packet caches for world#$worldId");
|
||||
});
|
||||
}
|
||||
if(!isset(self::$instances[$worldId][$compressorId])){
|
||||
\GlobalLogger::get()->debug("Created new chunk packet cache (world#$worldId, compressor#$compressorId)");
|
||||
self::$instances[$worldId][$compressorId] = new self($world, $compressor);
|
||||
}
|
||||
return self::$instances[$worldId][$compressorId];
|
||||
}
|
||||
|
||||
/** @var World */
|
||||
private $world;
|
||||
/** @var Compressor */
|
||||
private $compressor;
|
||||
|
||||
/** @var CompressBatchPromise[] */
|
||||
private $caches = [];
|
||||
|
||||
/** @var int */
|
||||
private $hits = 0;
|
||||
/** @var int */
|
||||
private $misses = 0;
|
||||
|
||||
private function __construct(World $world, Compressor $compressor){
|
||||
$this->world = $world;
|
||||
$this->compressor = $compressor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests asynchronous preparation of the chunk at the given coordinates.
|
||||
*
|
||||
* @return CompressBatchPromise a promise of resolution which will contain a compressed chunk packet.
|
||||
*/
|
||||
public function request(int $chunkX, int $chunkZ) : CompressBatchPromise{
|
||||
$this->world->registerChunkListener($this, $chunkX, $chunkZ);
|
||||
$chunkHash = World::chunkHash($chunkX, $chunkZ);
|
||||
|
||||
if(isset($this->caches[$chunkHash])){
|
||||
++$this->hits;
|
||||
return $this->caches[$chunkHash];
|
||||
}
|
||||
|
||||
++$this->misses;
|
||||
|
||||
$this->world->timings->syncChunkSendPrepareTimer->startTiming();
|
||||
try{
|
||||
$this->caches[$chunkHash] = new CompressBatchPromise();
|
||||
|
||||
$this->world->getServer()->getAsyncPool()->submitTask(
|
||||
new ChunkRequestTask(
|
||||
$chunkX,
|
||||
$chunkZ,
|
||||
$this->world->getChunk($chunkX, $chunkZ),
|
||||
$this->caches[$chunkHash],
|
||||
$this->compressor,
|
||||
function() use ($chunkX, $chunkZ) : void{
|
||||
$this->world->getLogger()->error("Failed preparing chunk $chunkX $chunkZ, retrying");
|
||||
|
||||
$this->restartPendingRequest($chunkX, $chunkZ);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return $this->caches[$chunkHash];
|
||||
}finally{
|
||||
$this->world->timings->syncChunkSendPrepareTimer->stopTiming();
|
||||
}
|
||||
}
|
||||
|
||||
private function destroy(int $chunkX, int $chunkZ) : bool{
|
||||
$chunkHash = World::chunkHash($chunkX, $chunkZ);
|
||||
$existing = $this->caches[$chunkHash] ?? null;
|
||||
unset($this->caches[$chunkHash]);
|
||||
|
||||
return $existing !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts an async request for an unresolved chunk.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function restartPendingRequest(int $chunkX, int $chunkZ) : void{
|
||||
$chunkHash = World::chunkHash($chunkX, $chunkZ);
|
||||
$existing = $this->caches[$chunkHash] ?? null;
|
||||
if($existing === null or $existing->hasResult()){
|
||||
throw new \InvalidArgumentException("Restart can only be applied to unresolved promises");
|
||||
}
|
||||
$existing->cancel();
|
||||
unset($this->caches[$chunkHash]);
|
||||
|
||||
$this->request($chunkX, $chunkZ)->onResolve(...$existing->getResolveCallbacks());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function destroyOrRestart(int $chunkX, int $chunkZ) : void{
|
||||
$cache = $this->caches[World::chunkHash($chunkX, $chunkZ)] ?? null;
|
||||
if($cache !== null){
|
||||
if(!$cache->hasResult()){
|
||||
//some requesters are waiting for this chunk, so their request needs to be fulfilled
|
||||
$this->restartPendingRequest($chunkX, $chunkZ);
|
||||
}else{
|
||||
//dump the cache, it'll be regenerated the next time it's requested
|
||||
$this->destroy($chunkX, $chunkZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use ChunkListenerNoOpTrait {
|
||||
//force overriding of these
|
||||
onChunkChanged as private;
|
||||
onBlockChanged as private;
|
||||
onChunkUnloaded as private;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ChunkListener::onChunkChanged()
|
||||
*/
|
||||
public function onChunkChanged(Chunk $chunk) : void{
|
||||
//FIXME: this gets fired for stuff that doesn't change terrain related things (like lighting updates)
|
||||
$this->destroyOrRestart($chunk->getX(), $chunk->getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ChunkListener::onBlockChanged()
|
||||
*/
|
||||
public function onBlockChanged(Vector3 $block) : void{
|
||||
//FIXME: requesters will still receive this chunk after it's been dropped, but we can't mark this for a simple
|
||||
//sync here because it can spam the worker pool
|
||||
$this->destroy($block->getFloorX() >> 4, $block->getFloorZ() >> 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ChunkListener::onChunkUnloaded()
|
||||
*/
|
||||
public function onChunkUnloaded(Chunk $chunk) : void{
|
||||
$this->destroy($chunk->getX(), $chunk->getZ());
|
||||
$this->world->unregisterChunkListener($this, $chunk->getX(), $chunk->getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes occupied by the cache data in this cache. This does not include the size of any
|
||||
* promises referenced by the cache.
|
||||
*/
|
||||
public function calculateCacheSize() : int{
|
||||
$result = 0;
|
||||
foreach($this->caches as $cache){
|
||||
if($cache->hasResult()){
|
||||
$result += strlen($cache->getResult());
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the percentage of requests to the cache which resulted in a cache hit.
|
||||
*/
|
||||
public function getHitPercentage() : float{
|
||||
$total = $this->hits + $this->misses;
|
||||
return $total > 0 ? $this->hits / $total : 0.0;
|
||||
}
|
||||
}
|
132
src/network/mcpe/cache/CraftingDataCache.php
vendored
Normal file
132
src/network/mcpe/cache/CraftingDataCache.php
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
<?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\network\mcpe\cache;
|
||||
|
||||
use pocketmine\crafting\CraftingManager;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\network\mcpe\convert\TypeConverter;
|
||||
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe as ProtocolFurnaceRecipe;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\ShapedRecipe as ProtocolShapedRecipe;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\ShapelessRecipe as ProtocolShapelessRecipe;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\uuid\UUID;
|
||||
use function array_map;
|
||||
use function spl_object_id;
|
||||
use function str_repeat;
|
||||
|
||||
final class CraftingDataCache{
|
||||
use SingletonTrait;
|
||||
|
||||
/**
|
||||
* @var CraftingDataPacket[]
|
||||
* @phpstan-var array<int, CraftingDataPacket>
|
||||
*/
|
||||
private $caches = [];
|
||||
|
||||
public function getCache(CraftingManager $manager) : CraftingDataPacket{
|
||||
$id = spl_object_id($manager);
|
||||
if(!isset($this->caches[$id])){
|
||||
$manager->getDestructorCallbacks()->add(function() use ($id) : void{
|
||||
unset($this->caches[$id]);
|
||||
});
|
||||
$manager->getRecipeRegisteredCallbacks()->add(function() use ($id) : void{
|
||||
unset($this->caches[$id]);
|
||||
});
|
||||
$this->caches[$id] = $this->buildCraftingDataCache($manager);
|
||||
}
|
||||
return $this->caches[$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the cached CraftingDataPacket.
|
||||
*/
|
||||
private function buildCraftingDataCache(CraftingManager $manager) : CraftingDataPacket{
|
||||
Timings::$craftingDataCacheRebuildTimer->startTiming();
|
||||
$pk = new CraftingDataPacket();
|
||||
$pk->cleanRecipes = true;
|
||||
|
||||
$counter = 0;
|
||||
$nullUUID = UUID::fromData(str_repeat("\x00", 16));
|
||||
$converter = TypeConverter::getInstance();
|
||||
foreach($manager->getShapelessRecipes() as $list){
|
||||
foreach($list as $recipe){
|
||||
$pk->entries[] = new ProtocolShapelessRecipe(
|
||||
CraftingDataPacket::ENTRY_SHAPELESS,
|
||||
Binary::writeInt($counter++),
|
||||
array_map(function(Item $item) use ($converter) : RecipeIngredient{
|
||||
return $converter->coreItemStackToRecipeIngredient($item);
|
||||
}, $recipe->getIngredientList()),
|
||||
array_map(function(Item $item) use ($converter) : ItemStack{
|
||||
return $converter->coreItemStackToNet($item);
|
||||
}, $recipe->getResults()),
|
||||
$nullUUID,
|
||||
"crafting_table",
|
||||
50,
|
||||
$counter
|
||||
);
|
||||
}
|
||||
}
|
||||
foreach($manager->getShapedRecipes() as $list){
|
||||
foreach($list as $recipe){
|
||||
$inputs = [];
|
||||
|
||||
for($row = 0, $height = $recipe->getHeight(); $row < $height; ++$row){
|
||||
for($column = 0, $width = $recipe->getWidth(); $column < $width; ++$column){
|
||||
$inputs[$row][$column] = $converter->coreItemStackToRecipeIngredient($recipe->getIngredient($column, $row));
|
||||
}
|
||||
}
|
||||
$pk->entries[] = $r = new ProtocolShapedRecipe(
|
||||
CraftingDataPacket::ENTRY_SHAPED,
|
||||
Binary::writeInt($counter++),
|
||||
$inputs,
|
||||
array_map(function(Item $item) use ($converter) : ItemStack{
|
||||
return $converter->coreItemStackToNet($item);
|
||||
}, $recipe->getResults()),
|
||||
$nullUUID,
|
||||
"crafting_table",
|
||||
50,
|
||||
$counter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach($manager->getFurnaceRecipeManager()->getAll() as $recipe){
|
||||
$input = $converter->coreItemStackToNet($recipe->getInput());
|
||||
$pk->entries[] = new ProtocolFurnaceRecipe(
|
||||
CraftingDataPacket::ENTRY_FURNACE_DATA,
|
||||
$input->getId(),
|
||||
$input->getMeta(),
|
||||
$converter->coreItemStackToNet($recipe->getResult()),
|
||||
"furnace"
|
||||
);
|
||||
}
|
||||
|
||||
return $pk;
|
||||
}
|
||||
}
|
69
src/network/mcpe/cache/StaticPacketCache.php
vendored
Normal file
69
src/network/mcpe/cache/StaticPacketCache.php
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
<?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\network\mcpe\cache;
|
||||
|
||||
use pocketmine\network\mcpe\protocol\AvailableActorIdentifiersPacket;
|
||||
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
|
||||
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
|
||||
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use function file_get_contents;
|
||||
|
||||
class StaticPacketCache{
|
||||
use SingletonTrait;
|
||||
|
||||
/** @var BiomeDefinitionListPacket */
|
||||
private $biomeDefs;
|
||||
/** @var AvailableActorIdentifiersPacket */
|
||||
private $availableActorIdentifiers;
|
||||
|
||||
/**
|
||||
* @phpstan-return CacheableNbt<\pocketmine\nbt\tag\CompoundTag>
|
||||
*/
|
||||
private static function loadCompoundFromFile(string $filePath) : CacheableNbt{
|
||||
$rawNbt = @file_get_contents($filePath);
|
||||
if($rawNbt === false) throw new \RuntimeException("Failed to read file");
|
||||
return new CacheableNbt((new NetworkNbtSerializer())->read($rawNbt)->mustGetCompoundTag());
|
||||
}
|
||||
|
||||
private static function make() : self{
|
||||
return new self(
|
||||
BiomeDefinitionListPacket::create(self::loadCompoundFromFile(\pocketmine\RESOURCE_PATH . '/vanilla/biome_definitions.nbt')),
|
||||
AvailableActorIdentifiersPacket::create(self::loadCompoundFromFile(\pocketmine\RESOURCE_PATH . '/vanilla/entity_identifiers.nbt'))
|
||||
);
|
||||
}
|
||||
|
||||
public function __construct(BiomeDefinitionListPacket $biomeDefs, AvailableActorIdentifiersPacket $availableActorIdentifiers){
|
||||
$this->biomeDefs = $biomeDefs;
|
||||
$this->availableActorIdentifiers = $availableActorIdentifiers;
|
||||
}
|
||||
|
||||
public function getBiomeDefs() : BiomeDefinitionListPacket{
|
||||
return $this->biomeDefs;
|
||||
}
|
||||
|
||||
public function getAvailableActorIdentifiers() : AvailableActorIdentifiersPacket{
|
||||
return $this->availableActorIdentifiers;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user