PocketMine-MP 5.15.1 git-fb9a74e8799c71ed8292cfa53abe7a4c9204629d
BlockStateDictionary.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\network\mcpe\convert;
25
32use function array_key_first;
33use function array_map;
34use function count;
35use function get_debug_type;
36use function is_array;
37use function is_int;
38use function is_string;
39use function json_decode;
40use const JSON_THROW_ON_ERROR;
41
50 private array $stateDataToStateIdLookup = [];
51
56 private ?array $idMetaToStateIdLookupCache = null;
57
63 public function __construct(
64 private array $states
65 ){
66 $table = [];
67 foreach($this->states as $stateId => $stateNbt){
68 $table[$stateNbt->getStateName()][$stateNbt->getRawStateProperties()] = $stateId;
69 }
70
71 //setup fast path for stateless blocks
72 foreach(Utils::stringifyKeys($table) as $name => $stateIds){
73 if(count($stateIds) === 1){
74 $this->stateDataToStateIdLookup[$name] = $stateIds[array_key_first($stateIds)];
75 }else{
76 $this->stateDataToStateIdLookup[$name] = $stateIds;
77 }
78 }
79 }
80
85 private function getIdMetaToStateIdLookup() : array{
86 if($this->idMetaToStateIdLookupCache === null){
87 $table = [];
88 //TODO: if we ever allow mutating the dictionary, this would need to be rebuilt on modification
89
90 foreach($this->states as $i => $state){
91 $table[$state->getStateName()][$state->getMeta()] = $i;
92 }
93
94 $this->idMetaToStateIdLookupCache = [];
95 foreach(Utils::stringifyKeys($table) as $name => $metaToStateId){
96 //if only one meta value exists
97 if(count($metaToStateId) === 1){
98 $this->idMetaToStateIdLookupCache[$name] = $metaToStateId[array_key_first($metaToStateId)];
99 }else{
100 $this->idMetaToStateIdLookupCache[$name] = $metaToStateId;
101 }
102 }
103 }
104
105 return $this->idMetaToStateIdLookupCache;
106 }
107
108 public function generateDataFromStateId(int $networkRuntimeId) : ?BlockStateData{
109 return ($this->states[$networkRuntimeId] ?? null)?->generateStateData();
110 }
111
116 public function lookupStateIdFromData(BlockStateData $data) : ?int{
117 $name = $data->getName();
118
119 $lookup = $this->stateDataToStateIdLookup[$name] ?? null;
120 return match(true){
121 $lookup === null => null,
122 is_int($lookup) => $lookup,
123 is_array($lookup) => $lookup[BlockStateDictionaryEntry::encodeStateProperties($data->getStates())] ?? null
124 };
125 }
126
131 public function getMetaFromStateId(int $networkRuntimeId) : ?int{
132 return ($this->states[$networkRuntimeId] ?? null)?->getMeta();
133 }
134
139 public function lookupStateIdFromIdMeta(string $id, int $meta) : ?int{
140 $metas = $this->getIdMetaToStateIdLookup()[$id] ?? null;
141 return match(true){
142 $metas === null => null,
143 is_int($metas) => $metas,
144 is_array($metas) => $metas[$meta] ?? null
145 };
146 }
147
153 public function getStates() : array{ return $this->states; }
154
161 public static function loadPaletteFromString(string $blockPaletteContents) : array{
162 return array_map(
163 fn(TreeRoot $root) => BlockStateData::fromNbt($root->mustGetCompoundTag()),
164 (new NetworkNbtSerializer())->readMultiple($blockPaletteContents)
165 );
166 }
167
168 public static function loadFromString(string $blockPaletteContents, string $metaMapContents) : self{
169 $metaMap = json_decode($metaMapContents, flags: JSON_THROW_ON_ERROR);
170 if(!is_array($metaMap)){
171 throw new \InvalidArgumentException("Invalid metaMap, expected array for root type, got " . get_debug_type($metaMap));
172 }
173
174 $entries = [];
175
176 $uniqueNames = [];
177
178 //this hack allows the internal cache index to use interned strings which are already available in the
179 //core code anyway, saving around 40 KB of memory
180 foreach((new \ReflectionClass(BlockTypeNames::class))->getConstants() as $value){
181 if(is_string($value)){
182 $uniqueNames[$value] = $value;
183 }
184 }
185
186 foreach(self::loadPaletteFromString($blockPaletteContents) as $i => $state){
187 $meta = $metaMap[$i] ?? null;
188 if($meta === null){
189 throw new \InvalidArgumentException("Missing associated meta value for state $i (" . $state->toNbt() . ")");
190 }
191 if(!is_int($meta)){
192 throw new \InvalidArgumentException("Invalid metaMap offset $i, expected int, got " . get_debug_type($meta));
193 }
194 $uniqueName = $uniqueNames[$state->getName()] ??= $state->getName();
195 $entries[$i] = new BlockStateDictionaryEntry($uniqueName, $state->getStates(), $meta);
196 }
197
198 return new self($entries);
199 }
200}
static loadPaletteFromString(string $blockPaletteContents)
static stringifyKeys(array $array)
Definition: Utils.php:605