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 "JsonUtils.h"
5 
6 
7 
8 
9 
13 static size_t findNextSeparator(const AString & aString, size_t aStartIdx = 0)
14 {
15  for (size_t i = aStartIdx, len = aString.length(); i < len; ++i)
16  {
17  switch (aString[i])
18  {
19  case '\t':
20  case '\n':
21  case '\r':
22  {
23  return i;
24  }
25  }
26  }
27  return AString::npos;
28 }
29 
30 
31 
32 
33 
35  mMaxIndex(0)
36 {
37 }
38 
39 
40 
41 
42 
43 UInt32 BlockTypePalette::index(const AString & aBlockTypeName, const BlockState & aBlockState)
44 {
45  auto idx = maybeIndex(aBlockTypeName, aBlockState);
46  if (idx.second)
47  {
48  return idx.first;
49  }
50 
51  // Not found, append:
52  auto index = mMaxIndex++;
53  mBlockToNumber[aBlockTypeName][aBlockState] = index;
54  mNumberToBlock[index] = {aBlockTypeName, aBlockState};
55  return index;
56 }
57 
58 
59 
60 
61 
62 std::pair<UInt32, bool> BlockTypePalette::maybeIndex(const AString & aBlockTypeName, const BlockState & aBlockState) const
63 {
64  auto itr1 = mBlockToNumber.find(aBlockTypeName);
65  if (itr1 == mBlockToNumber.end())
66  {
67  return {0, false};
68  }
69  auto itr2 = itr1->second.find(aBlockState);
70  if (itr2 == itr1->second.end())
71  {
72  return {0, false};
73  }
74  return {itr2->second, true};
75 }
76 
77 
78 
79 
80 
82 {
83  return static_cast<UInt32>(mNumberToBlock.size());
84 }
85 
86 
87 
88 
89 
90 const std::pair<AString, BlockState> & BlockTypePalette::entry(UInt32 aIndex) const
91 {
92  auto itr = mNumberToBlock.find(aIndex);
93  if (itr == mNumberToBlock.end())
94  {
95  throw NoSuchIndexException(aIndex);
96  }
97  return itr->second;
98 }
99 
100 
101 
102 
103 
105 {
106  std::map<UInt32, UInt32> res;
107  for (const auto & fromEntry: aFrom.mNumberToBlock)
108  {
109  auto fromIndex = fromEntry.first;
110  const auto & blockTypeName = fromEntry.second.first;
111  const auto & blockState = fromEntry.second.second;
112  res[fromIndex] = index(blockTypeName, blockState);
113  }
114  return res;
115 }
116 
117 
118 
119 
120 
121 std::map<UInt32, UInt32> BlockTypePalette::createTransformMapWithFallback(const BlockTypePalette & aFrom, UInt32 aFallbackIndex) const
122 {
123  std::map<UInt32, UInt32> res;
124  for (const auto & fromEntry: aFrom.mNumberToBlock)
125  {
126  auto fromIndex = fromEntry.first;
127  const auto & blockTypeName = fromEntry.second.first;
128  const auto & blockState = fromEntry.second.second;
129  auto thisIndex = maybeIndex(blockTypeName, blockState);
130  if (thisIndex.second)
131  {
132  // The entry was found in this
133  res[fromIndex] = thisIndex.first;
134  }
135  else
136  {
137  // The entry was NOT found in this, replace with fallback:
138  res[fromIndex] = aFallbackIndex;
139  }
140  }
141  return res;
142 }
143 
144 
145 
146 
147 
149 {
150  static const AString hdrTsvRegular = "BlockTypePalette";
151  static const AString hdrTsvUpgrade = "UpgradeBlockTypePalette";
152 
153  // Detect format by checking the header line (none -> JSON):
154  if (aString.substr(0, hdrTsvRegular.length()) == hdrTsvRegular)
155  {
156  return loadFromTsv(aString, false);
157  }
158  else if (aString.substr(0, hdrTsvUpgrade.length()) == hdrTsvUpgrade)
159  {
160  return loadFromTsv(aString, true);
161  }
162  return loadFromJsonString(aString);
163 }
164 
165 
166 
167 
168 
170 {
171  // Parse the string into JSON object:
172  Json::Value root;
173  std::string errs;
174  if (!JsonUtils::ParseString(aJsonPalette, root, &errs))
175  {
176  throw LoadFailedException(errs);
177  }
178 
179  // Sanity-check the JSON's structure:
180  if (!root.isObject())
181  {
182  throw LoadFailedException("Incorrect palette format, expected an object at root.");
183  }
184 
185  // Load the palette:
186  for (auto itr = root.begin(), end = root.end(); itr != end; ++itr)
187  {
188  const auto & blockTypeName = itr.name();
189  const auto & states = (*itr)["states"];
190  if (states == Json::Value())
191  {
192  throw LoadFailedException(fmt::format(FMT_STRING("Missing \"states\" for block type \"{}\""), blockTypeName));
193  }
194  for (const auto & state: states)
195  {
196  auto id = static_cast<UInt32>(std::stoul(state["id"].asString()));
197  std::map<AString, AString> props;
198  if (state.isMember("properties"))
199  {
200  const auto & properties = state["properties"];
201  if (!properties.isObject())
202  {
203  throw LoadFailedException(fmt::format(FMT_STRING("Member \"properties\" is not a JSON object (block type \"{}\", id {})."), blockTypeName, id));
204  }
205  for (const auto & key: properties.getMemberNames())
206  {
207  props[key] = properties[key].asString();
208  }
209  }
210  addMapping(id, blockTypeName, props);
211  }
212  }
213 }
214 
215 
216 
217 
218 
219 void BlockTypePalette::loadFromTsv(const AString & aTsvPalette, bool aIsUpgrade)
220 {
221  static const AString hdrTsvRegular = "BlockTypePalette";
222  static const AString hdrTsvUpgrade = "UpgradeBlockTypePalette";
223 
224  // Check the file signature:
225  auto idx = findNextSeparator(aTsvPalette);
226  if ((idx == AString::npos) || (aTsvPalette[idx] == '\t'))
227  {
228  throw LoadFailedException("Invalid signature");
229  }
230  auto signature = aTsvPalette.substr(0, idx);
231  bool isUpgrade = (signature == hdrTsvUpgrade);
232  if (!isUpgrade && (signature != hdrTsvRegular))
233  {
234  throw LoadFailedException("Unknown signature");
235  }
236  if (aTsvPalette[idx] == '\r') // CR of the CRLF pair, skip the LF:
237  {
238  idx += 1;
239  }
240 
241  // Parse the header:
242  bool hasHadVersion = false;
243  AString commonPrefix;
244  int line = 2;
245  auto len = aTsvPalette.length();
246  while (true)
247  {
248  auto keyStart = idx + 1;
249  auto keyEnd = findNextSeparator(aTsvPalette, idx + 1);
250  if (keyEnd == AString::npos)
251  {
252  throw LoadFailedException(fmt::format(FMT_STRING("Invalid header key format on line {}"), line));
253  }
254  if (keyEnd == idx + 1) // Empty line, end of headers
255  {
256  if (aTsvPalette[keyEnd] == '\r') // CR of the CRLF pair, skip the LF:
257  {
258  ++keyEnd;
259  }
260  idx = keyEnd;
261  ++line;
262  break;
263  }
264  auto valueEnd = findNextSeparator(aTsvPalette, keyEnd + 1);
265  if ((valueEnd == AString::npos) || (aTsvPalette[valueEnd] == '\t'))
266  {
267  throw LoadFailedException(fmt::format(FMT_STRING("Invalid header value format on line {}"), line));
268  }
269  auto key = aTsvPalette.substr(keyStart, keyEnd - keyStart);
270  if (key == "FileVersion")
271  {
272  unsigned version = 0;
273  auto value = aTsvPalette.substr(keyEnd + 1, valueEnd - keyEnd - 1);
274  if (!StringToInteger(value, version))
275  {
276  throw LoadFailedException("Invalid FileVersion value");
277  }
278  else if (version != 1)
279  {
280  throw LoadFailedException(fmt::format(FMT_STRING("Unknown FileVersion: {}. Only version 1 is supported."), version));
281  }
282  hasHadVersion = true;
283  }
284  else if (key == "CommonPrefix")
285  {
286  commonPrefix = aTsvPalette.substr(keyEnd + 1, valueEnd - keyEnd - 1);
287  }
288  idx = valueEnd;
289  if (aTsvPalette[idx] == '\r') // CR of the CRLF pair, skip the LF:
290  {
291  ++idx;
292  }
293  ++line;
294  }
295  if (!hasHadVersion)
296  {
297  throw LoadFailedException("No FileVersion value");
298  }
299 
300  // Parse the data:
301  while (idx + 1 < len)
302  {
303  auto lineStart = idx + 1;
304  auto idEnd = findNextSeparator(aTsvPalette, lineStart);
305  if ((idEnd == AString::npos) || (aTsvPalette[idEnd] != '\t'))
306  {
307  throw LoadFailedException(fmt::format(FMT_STRING("Incomplete data on line {} (id)"), line));
308  }
309  UInt32 id;
310  if (!StringToInteger(aTsvPalette.substr(lineStart, idEnd - lineStart), id))
311  {
312  throw LoadFailedException(fmt::format(FMT_STRING("Failed to parse id on line {}"), line));
313  }
314  size_t metaEnd = idEnd;
315  if (isUpgrade)
316  {
317  metaEnd = findNextSeparator(aTsvPalette, idEnd + 1);
318  if ((metaEnd == AString::npos) || (aTsvPalette[metaEnd] != '\t'))
319  {
320  throw LoadFailedException(fmt::format(FMT_STRING("Incomplete data on line {} (meta)"), line));
321  }
322  UInt32 meta = 0;
323  if (!StringToInteger(aTsvPalette.substr(idEnd + 1, metaEnd - idEnd - 1), meta))
324  {
325  throw LoadFailedException(fmt::format(FMT_STRING("Failed to parse meta on line {}"), line));
326  }
327  if (meta > 15)
328  {
329  throw LoadFailedException(fmt::format(FMT_STRING("Invalid meta value on line {}: {}"), line, meta));
330  }
331  id = (id * 16) | meta;
332  }
333  auto blockTypeEnd = findNextSeparator(aTsvPalette, metaEnd + 1);
334  if (blockTypeEnd == AString::npos)
335  {
336  throw LoadFailedException(fmt::format(FMT_STRING("Incomplete data on line {} (blockTypeName)"), line));
337  }
338  auto blockTypeName = aTsvPalette.substr(metaEnd + 1, blockTypeEnd - metaEnd - 1);
339  auto blockStateEnd = blockTypeEnd;
340  AStringMap blockState;
341  while (aTsvPalette[blockStateEnd] == '\t')
342  {
343  auto keyEnd = findNextSeparator(aTsvPalette, blockStateEnd + 1);
344  if ((keyEnd == AString::npos) || (aTsvPalette[keyEnd] != '\t'))
345  {
346  throw LoadFailedException(fmt::format(FMT_STRING("Incomplete data on line {} (blockState key)"), line));
347  }
348  auto valueEnd = findNextSeparator(aTsvPalette, keyEnd + 1);
349  if (valueEnd == AString::npos)
350  {
351  throw LoadFailedException(fmt::format(FMT_STRING("Incomplete data on line {} (blockState value)"), line));
352  }
353  auto key = aTsvPalette.substr(blockStateEnd + 1, keyEnd - blockStateEnd - 1);
354  auto value = aTsvPalette.substr(keyEnd + 1, valueEnd - keyEnd - 1);
355  blockState[key] = value;
356  blockStateEnd = valueEnd;
357  }
358  addMapping(id, commonPrefix + blockTypeName, std::move(blockState));
359  ++line;
360  if (aTsvPalette[blockStateEnd] == '\r') // CR of the CRLF pair, skip the LF:
361  {
362  ++blockStateEnd;
363  }
364  idx = blockStateEnd;
365  }
366 }
367 
368 
369 
370 
371 
372 void BlockTypePalette::addMapping(UInt32 aID, const AString & aBlockTypeName, const BlockState & aBlockState)
373 {
374  mNumberToBlock[aID] = {aBlockTypeName, aBlockState};
375  mBlockToNumber[aBlockTypeName][aBlockState] = aID;
376  if (aID > mMaxIndex)
377  {
378  mMaxIndex = aID;
379  }
380 }
static size_t findNextSeparator(const AString &aString, size_t aStartIdx=0)
Returns the index into aString >= aStartIdx at which the next separator occurs.
unsigned int UInt32
Definition: Globals.h:157
std::string AString
Definition: StringUtils.h:11
bool StringToInteger(const AString &a_str, T &a_Num)
Parses any integer type.
Definition: StringUtils.h:143
std::map< AString, AString > AStringMap
A string dictionary, used for key-value pairs.
Definition: StringUtils.h:16
bool ParseString(const AString &a_JsonStr, Json::Value &a_Root, AString *a_ErrorMsg)
Definition: JsonUtils.cpp:34
Represents the state of a single block (previously known as "block meta").
Definition: BlockState.h:20
Holds a palette that maps between block type + state and numbers.
std::map< UInt32, UInt32 > createTransformMapAddMissing(const BlockTypePalette &aFrom)
Returns an index-transform map from aFrom to this (this.entry(idx) == aFrom.entry(res[idx])).
void loadFromString(const AString &aString)
Loads the palette from the string representation.
void loadFromTsv(const AString &aTsvPalette, bool aIsUpgrade)
Loads the palette from the regular or upgrade TSV representation.
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.
std::unordered_map< AString, std::map< BlockState, UInt32 > > mBlockToNumber
The mapping from stringular to numeric representation.
UInt32 count() const
Returns the total number of entries in the palette.
void loadFromJsonString(const AString &aJsonPalette)
Loads the palette from the JSON representation, https://wiki.vg/Data_Generators Throws a LoadFailedEx...
UInt32 index(const AString &aBlockTypeName, const BlockState &aBlockState)
Returns the index of the specified block type name and state.
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])).
const std::pair< AString, BlockState > & entry(UInt32 aIndex) const
Returns the blockspec represented by the specified palette index.
std::map< UInt32, std::pair< AString, BlockState > > mNumberToBlock
The mapping from numeric to stringular representation.
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 mMaxIndex
The maximum index ever used in the maps.
BlockTypePalette()
Create a new empty instance.
Exception that is thrown if requiesting an index not present in the palette.
Exception that is thrown when loading the palette fails hard (bad format).