diff --git a/targets/app/common/src/GameRules/LevelGeneration/LevelGenerationOptions.cpp b/targets/app/common/src/GameRules/LevelGeneration/LevelGenerationOptions.cpp index 3c306897d..12d63266a 100644 --- a/targets/app/common/src/GameRules/LevelGeneration/LevelGenerationOptions.cpp +++ b/targets/app/common/src/GameRules/LevelGeneration/LevelGenerationOptions.cpp @@ -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(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(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 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; } diff --git a/targets/app/common/src/GameRules/LevelGeneration/LevelGenerationOptions.h b/targets/app/common/src/GameRules/LevelGeneration/LevelGenerationOptions.h index f2a39f174..92365c82a 100644 --- a/targets/app/common/src/GameRules/LevelGeneration/LevelGenerationOptions.h +++ b/targets/app/common/src/GameRules/LevelGeneration/LevelGenerationOptions.h @@ -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()(key.chunkX); + std::size_t h2 = std::hash()(key.chunkZ); + std::size_t h3 = std::hash()(key.dimension); + return h1 ^ (h2 << 1) ^ (h3 << 2); + } + }; + + struct ChunkRuleCacheEntry { + std::vector schematicRules; + std::vector structureRules; + }; + eSrc m_src; GrSource* m_pSrc; @@ -164,6 +189,8 @@ private: bool m_bHaveMinY; int m_minY; std::unordered_map m_schematics; + std::unordered_map + m_chunkRuleCache; std::vector m_biomeOverrides; std::vector 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); diff --git a/targets/java/include/java/InputOutputStream/ByteArrayInputStream.h b/targets/java/include/java/InputOutputStream/ByteArrayInputStream.h index c477a0ec0..64e06b118 100644 --- a/targets/java/include/java/InputOutputStream/ByteArrayInputStream.h +++ b/targets/java/include/java/InputOutputStream/ByteArrayInputStream.h @@ -20,6 +20,8 @@ public: ByteArrayInputStream(std::vector& buf, unsigned int offset, unsigned int length); ByteArrayInputStream(std::vector& buf); + // takes ownership of the vector + ByteArrayInputStream(std::vector&& buf); virtual ~ByteArrayInputStream(); virtual int read(); virtual int read(std::vector& b); @@ -36,4 +38,4 @@ public: mark = 0; pos = 0; } -}; \ No newline at end of file +}; diff --git a/targets/java/src/InputOutputStream/ByteArrayInputStream.cpp b/targets/java/src/InputOutputStream/ByteArrayInputStream.cpp index 34d484ca5..c2bf9123f 100644 --- a/targets/java/src/InputOutputStream/ByteArrayInputStream.cpp +++ b/targets/java/src/InputOutputStream/ByteArrayInputStream.cpp @@ -28,6 +28,11 @@ ByteArrayInputStream::ByteArrayInputStream(std::vector& buf) this->buf = buf; } +// 4jcraft: helper function to create a ByteArrayInputStream from a vector of bytes to avoid one copy +ByteArrayInputStream::ByteArrayInputStream(std::vector&& 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