RuntimeBlockMapping: lazy-load NBT blockstates

this saves a considerable amount of memory.

we don't actually need this state array in PM4 anyway, since we don't support the client-side chunk cache yet.
when the time comes to support it, it'll be much more practical to cache binary states and copy bytes anyway, instead of doing it the current way, which is both slow and memory-intensive.

Measured footprint change: 9 MB -> 400 KB.
This commit is contained in:
Dylan K. Taylor 2023-05-05 16:18:03 +01:00
parent 6cad559dbe
commit 02cf5ed388
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D

View File

@ -48,8 +48,8 @@ final class RuntimeBlockMapping{
private array $legacyToRuntimeMap = []; private array $legacyToRuntimeMap = [];
/** @var int[] */ /** @var int[] */
private array $runtimeToLegacyMap = []; private array $runtimeToLegacyMap = [];
/** @var CompoundTag[] */ /** @var CompoundTag[]|null */
private array $bedrockKnownStates; private ?array $bedrockKnownStates = null;
private static function make() : self{ private static function make() : self{
return new self( return new self(
@ -85,25 +85,13 @@ final class RuntimeBlockMapping{
return $newTag; return $newTag;
} }
public function __construct(string $canonicalBlockStatesFile, string $r12ToCurrentBlockMapFile){ public function __construct(
$stream = new BinaryStream(Filesystem::fileGetContents($canonicalBlockStatesFile)); private string $canonicalBlockStatesFile,
$list = []; string $r12ToCurrentBlockMapFile
$nbtReader = new NetworkNbtSerializer(); ){
//do not cache this - we only need it to set up mappings under normal circumstances
$bedrockKnownStates = $this->loadBedrockKnownStates();
$keyIndex = [];
$valueIndex = [];
while(!$stream->feof()){
$offset = $stream->getOffset();
$blockState = $nbtReader->read($stream->getBuffer(), $offset)->mustGetCompoundTag();
$stream->setOffset($offset);
$list[] = self::deduplicateCompound($blockState, $keyIndex, $valueIndex);
}
$this->bedrockKnownStates = $list;
$this->setupLegacyMappings($r12ToCurrentBlockMapFile);
}
private function setupLegacyMappings(string $r12ToCurrentBlockMapFile) : void{
$legacyIdMap = LegacyBlockIdToStringIdMap::getInstance(); $legacyIdMap = LegacyBlockIdToStringIdMap::getInstance();
/** @var R12ToCurrentBlockMapEntry[] $legacyStateMap */ /** @var R12ToCurrentBlockMapEntry[] $legacyStateMap */
$legacyStateMap = []; $legacyStateMap = [];
@ -123,7 +111,7 @@ final class RuntimeBlockMapping{
* @var int[][] $idToStatesMap string id -> int[] list of candidate state indices * @var int[][] $idToStatesMap string id -> int[] list of candidate state indices
*/ */
$idToStatesMap = []; $idToStatesMap = [];
foreach($this->bedrockKnownStates as $k => $state){ foreach($bedrockKnownStates as $k => $state){
$idToStatesMap[$state->getString("name")][] = $k; $idToStatesMap[$state->getString("name")][] = $k;
} }
foreach($legacyStateMap as $pair){ foreach($legacyStateMap as $pair){
@ -142,7 +130,7 @@ final class RuntimeBlockMapping{
throw new \RuntimeException("Mapped new state does not appear in network table"); throw new \RuntimeException("Mapped new state does not appear in network table");
} }
foreach($idToStatesMap[$mappedName] as $k){ foreach($idToStatesMap[$mappedName] as $k){
$networkState = $this->bedrockKnownStates[$k]; $networkState = $bedrockKnownStates[$k];
if($mappedState->equals($networkState)){ if($mappedState->equals($networkState)){
$this->registerMapping($k, $id, $data); $this->registerMapping($k, $id, $data);
continue 2; continue 2;
@ -152,6 +140,25 @@ final class RuntimeBlockMapping{
} }
} }
/**
* @return CompoundTag[]
*/
private function loadBedrockKnownStates() : array{
$stream = new BinaryStream(Filesystem::fileGetContents($this->canonicalBlockStatesFile));
$list = [];
$nbtReader = new NetworkNbtSerializer();
$keyIndex = [];
$valueIndex = [];
while(!$stream->feof()){
$offset = $stream->getOffset();
$blockState = $nbtReader->read($stream->getBuffer(), $offset)->mustGetCompoundTag();
$stream->setOffset($offset);
$list[] = self::deduplicateCompound($blockState, $keyIndex, $valueIndex);
}
return $list;
}
public function toRuntimeId(int $internalStateId) : int{ public function toRuntimeId(int $internalStateId) : int{
return $this->legacyToRuntimeMap[$internalStateId] ?? $this->legacyToRuntimeMap[BlockLegacyIds::INFO_UPDATE << Block::INTERNAL_METADATA_BITS]; return $this->legacyToRuntimeMap[$internalStateId] ?? $this->legacyToRuntimeMap[BlockLegacyIds::INFO_UPDATE << Block::INTERNAL_METADATA_BITS];
} }
@ -166,9 +173,14 @@ final class RuntimeBlockMapping{
} }
/** /**
* WARNING: This method may load the palette from disk, which is a slow operation.
* Afterwards, it will cache the palette in memory, which requires (in some cases) tens of MB of memory.
* Avoid using this where possible.
*
* @deprecated
* @return CompoundTag[] * @return CompoundTag[]
*/ */
public function getBedrockKnownStates() : array{ public function getBedrockKnownStates() : array{
return $this->bedrockKnownStates; return $this->bedrockKnownStates ??= $this->loadBedrockKnownStates();
} }
} }