Cuberite
A lightweight, fast and extensible game server for Minecraft
BlockTypePalette.cpp
Go to the documentation of this file.
1 #include "Globals.h"
2 #include "BlockTypePalette.h"
3 #include "json/value.h"
4 #include "json/reader.h"
5 
6 
7 
8 
9 
11  mMaxIndex(0)
12 {
13 }
14 
15 
16 
17 
18 
19 UInt32 BlockTypePalette::index(const AString & aBlockTypeName, const BlockState & aBlockState)
20 {
21  auto idx = maybeIndex(aBlockTypeName, aBlockState);
22  if (idx.second)
23  {
24  return idx.first;
25  }
26 
27  // Not found, append:
28  auto index = mMaxIndex++;
29  mBlockToNumber[aBlockTypeName][aBlockState] = index;
30  mNumberToBlock[index] = {aBlockTypeName, aBlockState};
31  return index;
32 }
33 
34 
35 
36 
37 
38 std::pair<UInt32, bool> BlockTypePalette::maybeIndex(const AString & aBlockTypeName, const BlockState & aBlockState) const
39 {
40  auto itr1 = mBlockToNumber.find(aBlockTypeName);
41  if (itr1 == mBlockToNumber.end())
42  {
43  return {0, false};
44  }
45  auto itr2 = itr1->second.find(aBlockState);
46  if (itr2 == itr1->second.end())
47  {
48  return {0, false};
49  }
50  return {itr2->second, true};
51 }
52 
53 
54 
55 
56 
58 {
59  return static_cast<UInt32>(mNumberToBlock.size());
60 }
61 
62 
63 
64 
65 
66 const std::pair<AString, BlockState> & BlockTypePalette::entry(UInt32 aIndex) const
67 {
68  auto itr = mNumberToBlock.find(aIndex);
69  if (itr == mNumberToBlock.end())
70  {
71  throw NoSuchIndexException(aIndex);
72  }
73  return itr->second;
74 }
75 
76 
77 
78 
79 
81 {
82  std::map<UInt32, UInt32> res;
83  for (const auto & fromEntry: aFrom.mNumberToBlock)
84  {
85  auto fromIndex = fromEntry.first;
86  const auto & blockTypeName = fromEntry.second.first;
87  const auto & blockState = fromEntry.second.second;
88  res[fromIndex] = index(blockTypeName, blockState);
89  }
90  return res;
91 }
92 
93 
94 
95 
96 
97 std::map<UInt32, UInt32> BlockTypePalette::createTransformMapWithFallback(const BlockTypePalette & aFrom, UInt32 aFallbackIndex) const
98 {
99  std::map<UInt32, UInt32> res;
100  for (const auto & fromEntry: aFrom.mNumberToBlock)
101  {
102  auto fromIndex = fromEntry.first;
103  const auto & blockTypeName = fromEntry.second.first;
104  const auto & blockState = fromEntry.second.second;
105  auto thisIndex = maybeIndex(blockTypeName, blockState);
106  if (thisIndex.second)
107  {
108  // The entry was found in this
109  res[fromIndex] = thisIndex.first;
110  }
111  else
112  {
113  // The entry was NOT found in this, replace with fallback:
114  res[fromIndex] = aFallbackIndex;
115  }
116  }
117  return res;
118 }
119 
120 
121 
122 
123 
125 {
126  static const AString hdrTsvRegular = "BlockTypePalette";
127  static const AString hdrTsvUpgrade = "UpgradeBlockTypePalette";
128 
129  // Detect format by checking the header line (none -> JSON):
130  if (aString.substr(0, hdrTsvRegular.length()) == hdrTsvRegular)
131  {
132  return loadFromTsv(aString, false);
133  }
134  else if (aString.substr(0, hdrTsvUpgrade.length()) == hdrTsvUpgrade)
135  {
136  return loadFromTsv(aString, true);
137  }
138  return loadFromJsonString(aString);
139 }
140 
141 
142 
143 
144 
146 {
147  // Parse the string into JSON object:
148  Json::Value root;
149  Json::CharReaderBuilder builder;
150  std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
151  std::string errs;
152  if (!reader->parse(aJsonPalette.data(), aJsonPalette.data() + aJsonPalette.size(), &root, &errs))
153  {
154  throw LoadFailedException(errs);
155  }
156 
157  // Sanity-check the JSON's structure:
158  if (!root.isObject())
159  {
160  throw LoadFailedException("Incorrect palette format, expected an object at root.");
161  }
162 
163  // Load the palette:
164  for (auto itr = root.begin(), end = root.end(); itr != end; ++itr)
165  {
166  const auto & blockTypeName = itr.name();
167  const auto & states = (*itr)["states"];
168  if (states == Json::Value())
169  {
170  throw LoadFailedException(Printf("Missing \"states\" for block type \"%s\"", blockTypeName));
171  }
172  for (const auto & state: states)
173  {
174  auto id = static_cast<UInt32>(std::stoul(state["id"].asString()));
175  std::map<AString, AString> props;
176  if (state.isMember("properties"))
177  {
178  const auto & properties = state["properties"];
179  if (!properties.isObject())
180  {
181  throw LoadFailedException(Printf("Member \"properties\" is not a JSON object (block type \"%s\", id %u).", blockTypeName, id));
182  }
183  for (const auto & key: properties.getMemberNames())
184  {
185  props[key] = properties[key].asString();
186  }
187  }
188  addMapping(id, blockTypeName, props);
189  }
190  }
191 }
192 
193 
194 
195 
196 
197 void BlockTypePalette::loadFromTsv(const AString & aTsvPalette, bool aIsUpgrade)
198 {
199  auto lines = StringSplitAndTrim(aTsvPalette, "\n");
200 
201  // Parse the header:
202  int fileVersion = 0;
203  AString commonPrefix;
204  auto numLines = lines.size();
205  for (size_t idx = 1; idx < numLines; ++idx)
206  {
207  const auto & line = lines[idx];
208  if (line.empty())
209  {
210  // End of headers, erase them from lines[] and go parse the data
211  lines.erase(lines.begin(), lines.begin() + static_cast<AStringVector::difference_type>(idx) + 1);
212  break;
213  }
214  auto s = StringSplit(line, "\t");
215  if (s.size() != 2)
216  {
217  throw LoadFailedException(Printf("Invalid header format on line %u", idx + 1));
218  }
219  if (s[0] == "FileVersion")
220  {
221  try
222  {
223  fileVersion = std::stoi(s[1]);
224  }
225  catch (const std::exception & exc)
226  {
227  throw LoadFailedException(Printf("Invalid file version: \"%d\" (%s)", s[1], exc.what()));
228  }
229  }
230  else if (s[0] == "CommonPrefix")
231  {
232  commonPrefix = s[1];
233  }
234  }
235  if (fileVersion != 1)
236  {
237  throw LoadFailedException(Printf("Unknown file version (%d), only version 1 is supported", fileVersion));
238  }
239 
240  // Parse the data:
241  size_t minSplit = aIsUpgrade ? 3 : 2;
242  for (const auto & line: lines)
243  {
244  auto s = StringSplit(line, "\t");
245  auto numSplit = s.size();
246  if (numSplit < minSplit)
247  {
248  throw LoadFailedException(Printf("Not enough values on data line: \"%s\"", line));
249  }
250  UInt32 id;
251  try
252  {
253  id = static_cast<UInt32>(std::stoi(s[0]));
254  }
255  catch (const std::exception & exc)
256  {
257  throw LoadFailedException(Printf("Invalid block ID: \"%s\" (%s)", s[0], exc.what()));
258  }
259  size_t idx = 1;
260  if (aIsUpgrade)
261  {
262  id = id * 16;
263  try
264  {
265  id = id + static_cast<UInt32>(Clamp(std::stoi(s[1]), 0, 15));
266  }
267  catch (const std::exception & exc)
268  {
269  throw LoadFailedException(Printf("Invalid block meta: \"%s\" (%s)", s[1], exc.what()));
270  }
271  idx = 2;
272  }
273  const auto & blockTypeName = s[idx];
274  idx += 1;
275  std::map<AString, AString> state;
276  while (idx + 1 < numSplit)
277  {
278  state[s[idx]] = s[idx + 1];
279  idx += 2;
280  }
281  addMapping(id, commonPrefix + blockTypeName, state);
282  }
283 }
284 
285 
286 
287 
288 
289 void BlockTypePalette::addMapping(UInt32 aID, const AString & aBlockTypeName, const BlockState & aBlockState)
290 {
291  mNumberToBlock[aID] = {aBlockTypeName, aBlockState};
292  mBlockToNumber[aBlockTypeName][aBlockState] = aID;
293  if (aID > mMaxIndex)
294  {
295  mMaxIndex = aID;
296  }
297 }
void loadFromJsonString(const AString &aJsonPalette)
Loads the palette from the JSON representation, https://wiki.vg/Data_Generators Throws a LoadFailedEx...
std::pair< UInt32, bool > maybeIndex(const AString &aBlockTypeName, const BlockState &aBlockState) const
Returns the <index, true> of the specified block type name and state, if it exists.
const std::pair< AString, BlockState > & entry(UInt32 aIndex) const
Returns the blockspec represented by the specified palette index.
void addMapping(UInt32 aID, const AString &aBlockTypeName, const BlockState &aBlockState)
Adds a mapping between the numeric and stringular representation into both maps, updates the mMaxInde...
UInt32 index(const AString &aBlockTypeName, const BlockState &aBlockState)
Returns the index of the specified block type name and state.
Exception that is thrown if requiesting an index not present in the palette.
std::map< UInt32, UInt32 > createTransformMapAddMissing(const BlockTypePalette &aFrom)
Returns an index-transform map from aFrom to this (this.entry(idx) == aFrom.entry(res[idx])).
Holds a palette that maps between block type + state and numbers.
UInt32 mMaxIndex
The maximum index ever used in the maps.
AStringVector StringSplitAndTrim(const AString &str, const AString &delim)
Split the string at any of the listed delimiters and trim each value.
BlockTypePalette()
Create a new empty instance.
void loadFromString(const AString &aString)
Loads the palette from the string representation.
std::map< UInt32, std::pair< AString, BlockState > > mNumberToBlock
The mapping from numeric to stringular representation.
std::map< UInt32, UInt32 > createTransformMapWithFallback(const BlockTypePalette &aFrom, UInt32 aFallbackIndex) const
Returns an index-transform map from aFrom to this (this.entry(idx) == aFrom.entry(res[idx])).
AString & Printf(AString &str, const char *format, fmt::ArgList args)
Output the formatted text into the string.
Definition: StringUtils.cpp:55
std::unordered_map< AString, std::map< BlockState, UInt32 > > mBlockToNumber
The mapping from stringular to numeric representation.
T Clamp(T a_Value, T a_Min, T a_Max)
Clamp X to the specified range.
Definition: Globals.h:351
std::string AString
Definition: StringUtils.h:13
AStringVector StringSplit(const AString &str, const AString &delim)
Split the string at any of the listed delimiters.
Definition: StringUtils.cpp:76
void loadFromTsv(const AString &aTsvPalette, bool aIsUpgrade)
Loads the palette from the regular or upgrade TSV representation.
unsigned int UInt32
Definition: Globals.h:113
UInt32 count() const
Returns the total number of entries in the palette.
Exception that is thrown when loading the palette fails hard (bad format).
Represents the state of a single block (previously known as "block meta").
Definition: BlockState.h:19