|int> */ private array $stateDataToStateIdLookup = []; /** * @var int[][]|null * @phpstan-var array|int>|null */ private ?array $idMetaToStateIdLookupCache = null; /** * @param BlockStateDictionaryEntry[] $states * * @phpstan-param list $states */ public function __construct( private array $states ){ $table = []; foreach($this->states as $stateId => $stateNbt){ $table[$stateNbt->getStateName()][$stateNbt->getRawStateProperties()] = $stateId; } //setup fast path for stateless blocks foreach(Utils::stringifyKeys($table) as $name => $stateIds){ if(count($stateIds) === 1){ $this->stateDataToStateIdLookup[$name] = $stateIds[array_key_first($stateIds)]; }else{ $this->stateDataToStateIdLookup[$name] = $stateIds; } } } /** * @return int[][] * @phpstan-return array|int> */ private function getIdMetaToStateIdLookup() : array{ if($this->idMetaToStateIdLookupCache === null){ $table = []; //TODO: if we ever allow mutating the dictionary, this would need to be rebuilt on modification foreach($this->states as $i => $state){ $table[$state->getStateName()][$state->getMeta()] = $i; } $this->idMetaToStateIdLookupCache = []; foreach(Utils::stringifyKeys($table) as $name => $metaToStateId){ //if only one meta value exists if(count($metaToStateId) === 1){ $this->idMetaToStateIdLookupCache[$name] = $metaToStateId[array_key_first($metaToStateId)]; }else{ $this->idMetaToStateIdLookupCache[$name] = $metaToStateId; } } } return $this->idMetaToStateIdLookupCache; } public function generateDataFromStateId(int $networkRuntimeId) : ?BlockStateData{ return ($this->states[$networkRuntimeId] ?? null)?->generateStateData(); } /** * Searches for the appropriate state ID which matches the given blockstate NBT. * Returns null if there were no matches. */ public function lookupStateIdFromData(BlockStateData $data) : ?int{ $name = $data->getName(); $lookup = $this->stateDataToStateIdLookup[$name] ?? null; return match(true){ $lookup === null => null, is_int($lookup) => $lookup, is_array($lookup) => $lookup[BlockStateDictionaryEntry::encodeStateProperties($data->getStates())] ?? null }; } /** * Returns the blockstate meta value associated with the given blockstate runtime ID. * This is used for serializing crafting recipe inputs. */ public function getMetaFromStateId(int $networkRuntimeId) : ?int{ return ($this->states[$networkRuntimeId] ?? null)?->getMeta(); } /** * Returns the blockstate data associated with the given block ID and meta value. * This is used for deserializing crafting recipe inputs. */ public function lookupStateIdFromIdMeta(string $id, int $meta) : ?int{ $metas = $this->getIdMetaToStateIdLookup()[$id] ?? null; return match(true){ $metas === null => null, is_int($metas) => $metas, is_array($metas) => $metas[$meta] ?? null }; } /** * Returns an array mapping runtime ID => blockstate data. * @return BlockStateDictionaryEntry[] * @phpstan-return array */ public function getStates() : array{ return $this->states; } /** * @return BlockStateData[] * @phpstan-return list * * @throws NbtDataException */ public static function loadPaletteFromString(string $blockPaletteContents) : array{ return array_map( fn(TreeRoot $root) => BlockStateData::fromNbt($root->mustGetCompoundTag()), (new NetworkNbtSerializer())->readMultiple($blockPaletteContents) ); } public static function loadFromString(string $blockPaletteContents, string $metaMapContents) : self{ $metaMap = json_decode($metaMapContents, flags: JSON_THROW_ON_ERROR); if(!is_array($metaMap)){ throw new \InvalidArgumentException("Invalid metaMap, expected array for root type, got " . get_debug_type($metaMap)); } $entries = []; $uniqueNames = []; //this hack allows the internal cache index to use interned strings which are already available in the //core code anyway, saving around 40 KB of memory foreach((new \ReflectionClass(BlockTypeNames::class))->getConstants() as $value){ if(is_string($value)){ $uniqueNames[$value] = $value; } } foreach(self::loadPaletteFromString($blockPaletteContents) as $i => $state){ $meta = $metaMap[$i] ?? null; if($meta === null){ throw new \InvalidArgumentException("Missing associated meta value for state $i (" . $state->toNbt() . ")"); } if(!is_int($meta)){ throw new \InvalidArgumentException("Invalid metaMap offset $i, expected int, got " . get_debug_type($meta)); } $uniqueName = $uniqueNames[$state->getName()] ??= $state->getName(); $entries[$i] = new BlockStateDictionaryEntry($uniqueName, $state->getStates(), $meta); } return new self($entries); } }