refactor: faster chunk & schematic loading

This commit is contained in:
JuiceyDev 2026-04-03 20:40:21 +02:00
parent 342a195ba8
commit 4dfba6ccd8
4 changed files with 128 additions and 21 deletions

View file

@ -269,39 +269,103 @@ void LevelGenerationOptions::addAttribute(const std::wstring& attributeName,
GameRuleDefinition::addAttribute(attributeName, attributeValue);
}
}
// 4jcraft: better schematic caching
void LevelGenerationOptions::processSchematics(LevelChunk* chunk) {
AABB chunkBox(chunk->x * 16, 0, chunk->z * 16, chunk->x * 16 + 16,
Level::maxBuildHeight, chunk->z * 16 + 16);
for (auto it = m_schematicRules.begin(); it != m_schematicRules.end();
++it) {
ApplySchematicRuleDefinition* rule = *it;
rule->processSchematic(&chunkBox, chunk);
ChunkRuleCacheKey key;
key.chunkX = chunk->x;
key.chunkZ = chunk->z;
key.dimension = chunk->level->dimension->id;
auto cacheIt = m_chunkRuleCache.find(key);
if (cacheIt == m_chunkRuleCache.end()) {
// if no cache hit, show em the goods
ChunkRuleCacheEntry entry;
for (auto it = m_schematicRules.begin(); it != m_schematicRules.end();
++it) {
ApplySchematicRuleDefinition* rule = *it;
if (rule->checkIntersects(chunkBox.x0, chunkBox.y0, chunkBox.z0,
chunkBox.x1, chunkBox.y1, chunkBox.z1)) {
entry.schematicRules.push_back(rule);
}
}
int cx = (chunk->x << 4);
int cz = (chunk->z << 4);
for (auto it = m_structureRules.begin(); it != m_structureRules.end();
++it) {
ConsoleGenerateStructure* structureStart = *it;
if (structureStart->getBoundingBox()->intersects(cx, cz, cx + 15,
cz + 15)) {
entry.structureRules.push_back(structureStart);
}
}
cacheIt = m_chunkRuleCache.insert(
std::pair<ChunkRuleCacheKey, ChunkRuleCacheEntry>(key, entry)).first;
} else if (cacheIt->second.structureRules.empty() && !m_structureRules.empty()) {
int cx = (chunk->x << 4);
int cz = (chunk->z << 4);
for (auto it = m_structureRules.begin(); it != m_structureRules.end();
++it) {
ConsoleGenerateStructure* structureStart = *it;
if (structureStart->getBoundingBox()->intersects(cx, cz, cx + 15,
cz + 15)) {
cacheIt->second.structureRules.push_back(structureStart);
}
}
}
for (auto it = cacheIt->second.schematicRules.begin();
it != cacheIt->second.schematicRules.end(); ++it) {
(*it)->processSchematic(&chunkBox, chunk);
}
int cx = (chunk->x << 4);
int cz = (chunk->z << 4);
for (auto it = m_structureRules.begin(); it != m_structureRules.end();
it++) {
for (auto it = cacheIt->second.structureRules.begin();
it != cacheIt->second.structureRules.end(); ++it) {
ConsoleGenerateStructure* structureStart = *it;
if (structureStart->getBoundingBox()->intersects(cx, cz, cx + 15,
cz + 15)) {
BoundingBox* bb = new BoundingBox(cx, cz, cx + 15, cz + 15);
structureStart->postProcess(chunk->level, nullptr, bb);
delete bb;
}
BoundingBox* bb = new BoundingBox(cx, cz, cx + 15, cz + 15);
structureStart->postProcess(chunk->level, nullptr, bb);
delete bb;
}
}
void LevelGenerationOptions::processSchematicsLighting(LevelChunk* chunk) {
AABB chunkBox(chunk->x * 16, 0, chunk->z * 16, chunk->x * 16 + 16,
Level::maxBuildHeight, chunk->z * 16 + 16);
for (auto it = m_schematicRules.begin(); it != m_schematicRules.end();
++it) {
ApplySchematicRuleDefinition* rule = *it;
rule->processSchematicLighting(&chunkBox, chunk);
ChunkRuleCacheKey key;
key.chunkX = chunk->x;
key.chunkZ = chunk->z;
key.dimension = chunk->level->dimension->id;
auto cacheIt = m_chunkRuleCache.find(key);
if (cacheIt == m_chunkRuleCache.end()) {
// lighting shouldn't affect structure rules...
ChunkRuleCacheEntry entry;
for (auto it = m_schematicRules.begin(); it != m_schematicRules.end();
++it) {
ApplySchematicRuleDefinition* rule = *it;
if (rule->checkIntersects(chunkBox.x0, chunkBox.y0, chunkBox.z0,
chunkBox.x1, chunkBox.y1, chunkBox.z1)) {
entry.schematicRules.push_back(rule);
}
}
// structureRules is initially empty because it will be populated by processSchematics later onn
cacheIt = m_chunkRuleCache.insert(
std::pair<ChunkRuleCacheKey, ChunkRuleCacheEntry>(key, entry)).first;
}
for (auto it = cacheIt->second.schematicRules.begin();
it != cacheIt->second.schematicRules.end(); ++it) {
(*it)->processSchematicLighting(&chunkBox, chunk);
}
}
@ -359,6 +423,11 @@ void LevelGenerationOptions::clearSchematics() {
delete it->second;
}
m_schematics.clear();
clearChunkRuleCache();
}
void LevelGenerationOptions::clearChunkRuleCache() {
m_chunkRuleCache.clear();
}
ConsoleSchematicFile* LevelGenerationOptions::loadSchematicFile(
@ -376,8 +445,9 @@ ConsoleSchematicFile* LevelGenerationOptions::loadSchematicFile(
}
ConsoleSchematicFile* schematic = nullptr;
// 4jcraft: we use a constructor to reduce copies.
std::vector<uint8_t> data(pbData, pbData + dataLength);
ByteArrayInputStream bais(data);
ByteArrayInputStream bais(std::move(data));
DataInputStream dis(&bais);
schematic = new ConsoleSchematicFile();
schematic->load(&dis);
@ -572,13 +642,15 @@ int LevelGenerationOptions::packMounted(void* pParam, int iPad, uint32_t dwErr,
}
void LevelGenerationOptions::reset_start() {
clearChunkRuleCache();
for (auto it = m_schematicRules.begin(); it != m_schematicRules.end();
it++) {
++it) { // what in the flip in the fuck
(*it)->reset();
}
}
void LevelGenerationOptions::reset_finish() {
clearChunkRuleCache();
// if (m_spawnPos) { delete m_spawnPos; m_spawnPos
// = nullptr; } if (m_stringTable) { delete m_stringTable;
// m_stringTable = nullptr; }

View file

@ -106,6 +106,31 @@ public:
};
private:
struct ChunkRuleCacheKey {
int chunkX;
int chunkZ;
int dimension;
bool operator==(const ChunkRuleCacheKey& other) const {
return chunkX == other.chunkX && chunkZ == other.chunkZ &&
dimension == other.dimension;
}
};
struct ChunkRuleCacheKeyHash {
std::size_t operator()(const ChunkRuleCacheKey& key) const {
std::size_t h1 = std::hash<int>()(key.chunkX);
std::size_t h2 = std::hash<int>()(key.chunkZ);
std::size_t h3 = std::hash<int>()(key.dimension);
return h1 ^ (h2 << 1) ^ (h3 << 2);
}
};
struct ChunkRuleCacheEntry {
std::vector<ApplySchematicRuleDefinition*> schematicRules;
std::vector<ConsoleGenerateStructure*> structureRules;
};
eSrc m_src;
GrSource* m_pSrc;
@ -164,6 +189,8 @@ private:
bool m_bHaveMinY;
int m_minY;
std::unordered_map<std::wstring, ConsoleSchematicFile*> m_schematics;
std::unordered_map<ChunkRuleCacheKey, ChunkRuleCacheEntry, ChunkRuleCacheKeyHash>
m_chunkRuleCache;
std::vector<BiomeOverride*> m_biomeOverrides;
std::vector<StartFeature*> m_features;
@ -196,6 +223,7 @@ public:
void processSchematics(LevelChunk* chunk);
void processSchematicsLighting(LevelChunk* chunk);
void clearChunkRuleCache();
bool checkIntersects(int x0, int y0, int z0, int x1, int y1, int z1);

View file

@ -20,6 +20,8 @@ public:
ByteArrayInputStream(std::vector<uint8_t>& buf, unsigned int offset,
unsigned int length);
ByteArrayInputStream(std::vector<uint8_t>& buf);
// takes ownership of the vector
ByteArrayInputStream(std::vector<uint8_t>&& buf);
virtual ~ByteArrayInputStream();
virtual int read();
virtual int read(std::vector<uint8_t>& b);
@ -36,4 +38,4 @@ public:
mark = 0;
pos = 0;
}
};
};

View file

@ -28,6 +28,11 @@ ByteArrayInputStream::ByteArrayInputStream(std::vector<uint8_t>& buf)
this->buf = buf;
}
// 4jcraft: helper function to create a ByteArrayInputStream from a vector of bytes to avoid one copy
ByteArrayInputStream::ByteArrayInputStream(std::vector<uint8_t>&& buf)
: buf(std::move(buf)), pos(0), count(this->buf.size()), mark(0) {
}
// Reads the next byte of data from this input stream. The value byte is
// returned as an int in the range 0 to 255. If no byte is available because the
// end of the stream has been reached, the value -1 is returned. This read