diff --git a/Minecraft.Client/TileRenderer.cpp b/Minecraft.Client/TileRenderer.cpp index 91e71829..1bf5f239 100644 --- a/Minecraft.Client/TileRenderer.cpp +++ b/Minecraft.Client/TileRenderer.cpp @@ -326,7 +326,10 @@ bool TileRenderer::tesselateInWorld( Tile* tt, int x, int y, int z, int forceDat retVal = tesselateQuartzInWorld(tt, x, y, z); break; case Tile::SHAPE_WATER: - retVal = tesselateWaterInWorld( tt, x, y, z ); + if (Minecraft::GetInstance()->options->mipmapsBlend) + retVal = tesselateWaterInWorldAO( tt, x, y, z ); + else + retVal = tesselateWaterInWorld( tt, x, y, z ); break; case Tile::SHAPE_CACTUS: retVal = tesselateCactusInWorld( tt, x, y, z ); @@ -4755,6 +4758,209 @@ bool TileRenderer::tesselateWaterInWorld(Tile* tt, int x, int y, int z) return changed; } +bool TileRenderer::tesselateWaterInWorldAO(Tile* tt, int x, int y, int z) +{ + Tesselator* t = Tesselator::getInstance(); + + int col = tt->getColor(level, x, y, z); + float r = ((col >> 16) & 0xff) / 255.0f; + float g = ((col >> 8) & 0xff) / 255.0f; + float b = (col & 0xff) / 255.0f; + + bool up = tt->shouldRenderFace(level, x, y + 1, z, 1); + bool down = tt->shouldRenderFace(level, x, y - 1, z, 0); + bool dirs[4]; + + dirs[0] = tt->shouldRenderFace( level, x, y, z - 1, 2 ); + dirs[1] = tt->shouldRenderFace( level, x, y, z + 1, 3 ); + dirs[2] = tt->shouldRenderFace( level, x - 1, y, z, 4 ); + dirs[3] = tt->shouldRenderFace( level, x + 1, y, z, 5 ); + + if (!up && !down && !dirs[0] && !dirs[1] && !dirs[2] && !dirs[3]) + return false; + + bool changed = false; + Material* m = tt->material; + int data = level->getData(x, y, z); + + float h0 = getWaterHeight(x, y, z, m); + float h1 = getWaterHeight(x, y, z + 1, m); + float h2 = getWaterHeight(x + 1, y, z + 1, m); + float h3 = getWaterHeight(x + 1, y, z, m); + + float maxh = h0; + if (h1 > maxh) maxh = h1; + if (h2 > maxh) maxh = h2; + if (h3 > maxh) maxh = h3; + + // 4J - added. Farm tiles often found beside water, but they consider themselves non-solid as they only extend up to 15.0f / 16.0f. + // If the max height of this water is below that level, don't bother rendering sides bordering onto farmland. + if (maxh <= ( 15.0f / 16.0f )) + { + if (level->getTile(x, y, z - 1) == Tile::farmland_Id) dirs[0] = false; + if (level->getTile(x, y, z + 1) == Tile::farmland_Id) dirs[1] = false; + if (level->getTile(x - 1, y, z) == Tile::farmland_Id) dirs[2] = false; + if (level->getTile(x + 1, y, z) == Tile::farmland_Id) dirs[3] = false; + } + + constexpr float EPS = 0.0001f; + + if (noCulling || up) + { + changed = true; + Icon* tex = getTexture(tt, 1, data); + float angle = static_cast(LiquidTile::getSlopeAngle(level, x, y, z, m)); + + if (angle > -999.0f) tex = getTexture(tt, 2, data); + + h0 -= EPS; h1 -= EPS; h2 -= EPS; h3 -= EPS; + + float u0, v0, u1, v1, u2, v2, u3, v3; + + if (angle >= -999.0f) + { + float cc = 8.0f; + float fsin = Mth::sin(angle) * 0.25f; + float fcos = Mth::cos(angle) * 0.25f; + u0 = tex->getU(cc + (-fcos - fsin) * SharedConstants::WORLD_RESOLUTION); + v0 = tex->getV(cc + (fsin - fcos) * SharedConstants::WORLD_RESOLUTION); + u1 = tex->getU(cc + (fsin - fcos) * SharedConstants::WORLD_RESOLUTION); + v1 = tex->getV(cc + (fcos + fsin) * SharedConstants::WORLD_RESOLUTION); + u2 = tex->getU(cc + (fcos + fsin) * SharedConstants::WORLD_RESOLUTION); + v2 = tex->getV(cc + (fcos - fsin) * SharedConstants::WORLD_RESOLUTION); + u3 = tex->getU(cc + (fcos - fsin) * SharedConstants::WORLD_RESOLUTION); + v3 = tex->getV(cc + (-fcos - fsin) * SharedConstants::WORLD_RESOLUTION); + } + else + { + u0 = tex->getU(0.0f, true); v0 = tex->getV(0.0f, true); + u1 = u0; v1 = tex->getV(SharedConstants::WORLD_RESOLUTION, true); + u2 = tex->getU(SharedConstants::WORLD_RESOLUTION, true); v2 = v1; + u3 = u2; v3 = v0; + } + + int pY = y + 1; + int centerColor = tt->getLightColor(level, x, pY, z); + float ll0Y0 = tt->getShadeBrightness(level, x, pY, z); + + auto getWaterLight = [&](int sx, int sy, int sz) -> int { + if (level->isSolidBlockingTile(sx, sy, sz)) return centerColor; + return tt->getLightColor(level, sx, sy, sz); + }; + + auto getWaterBr = [&](int sx, int sy, int sz) -> float { + if (level->isSolidBlockingTile(sx, sy, sz)) return ll0Y0; + return tt->getShadeBrightness(level, sx, sy, sz); + }; + + int ccxY0 = getWaterLight(x - 1, pY, z); + int ccXY0 = getWaterLight(x + 1, pY, z); + int cc0Yz = getWaterLight(x, pY, z - 1); + int cc0YZ = getWaterLight(x, pY, z + 1); + + float llxY0 = getWaterBr(x - 1, pY, z); + float llXY0 = getWaterBr(x + 1, pY, z); + float ll0Yz = getWaterBr(x, pY, z - 1); + float ll0YZ = getWaterBr(x, pY, z + 1); + + float llxYz, llXYz, llxYZ, llXYZ; + int ccxYz, ccXYz, ccxYZ, ccXYZ; + + llxYz = getWaterBr(x - 1, pY, z - 1); ccxYz = getWaterLight(x - 1, pY, z - 1); + llXYz = getWaterBr(x + 1, pY, z - 1); ccXYz = getWaterLight(x + 1, pY, z - 1); + llxYZ = getWaterBr(x - 1, pY, z + 1); ccxYZ = getWaterLight(x - 1, pY, z + 1); + llXYZ = getWaterBr(x + 1, pY, z + 1); ccXYZ = getWaterLight(x + 1, pY, z + 1); + + float b0 = (llxY0 + llxYz + ll0Y0 + ll0Yz) / 4.0f; + int c0 = blend(ccxY0, ccxYz, centerColor, cc0Yz); + + float b1 = (llxYZ + llxY0 + ll0YZ + ll0Y0) / 4.0f; + int c1 = blend(ccxYZ, ccxY0, cc0YZ, centerColor); + + float b2 = (ll0YZ + ll0Y0 + llXYZ + llXY0) / 4.0f; + int c2 = blend(cc0YZ, centerColor, ccXYZ, ccXY0); + + float b3 = (ll0Y0 + ll0Yz + llXY0 + llXYz) / 4.0f; + int c3 = blend(centerColor, cc0Yz, ccXY0, ccXYz); + + t->tex2(c0); t->color(r * b0, g * b0, b * b0); + t->vertexUV((float)x, (float)(y + h0), (float)z, u0, v0); + + t->tex2(c1); t->color(r * b1, g * b1, b * b1); + t->vertexUV((float)x, (float)(y + h1), (float)(z + 1), u1, v1); + + t->tex2(c2); t->color(r * b2, g * b2, b * b2); + t->vertexUV((float)(x + 1), (float)(y + h2), (float)(z + 1), u2, v2); + + t->tex2(c3); t->color(r * b3, g * b3, b * b3); + t->vertexUV((float)(x + 1), (float)(y + h3), (float)z, u3, v3); + + if (static_cast(tt)->LiquidTile::shouldRenderBackwardUpFace(level, x, y + 1, z)) + { + t->tex2(c0); t->color(r * b0, g * b0, b * b0); + t->vertexUV((float)x, (float)(y + h0), (float)z, u0, v0); + t->tex2(c3); t->color(r * b3, g * b3, b * b3); + t->vertexUV((float)(x + 1), (float)(y + h3), (float)z, u3, v3); + t->tex2(c2); t->color(r * b2, g * b2, b * b2); + t->vertexUV((float)(x + 1), (float)(y + h2), (float)(z + 1), u2, v2); + t->tex2(c1); t->color(r * b1, g * b1, b * b1); + t->vertexUV((float)x, (float)(y + h1), (float)(z + 1), u1, v1); + } + } + + if (noCulling || down) + { + t->tex2(getLightColor(tt, level, x, y - 1, z)); + float bl = tt->getShadeBrightness(level, x, y - 1, z); + t->color(r * 0.5f * bl, g * 0.5f * bl, b * 0.5f * bl); + renderFaceDown(tt, x, y + EPS, z, getTexture(tt, 0)); + changed = true; + } + + for (int face = 0; face < 4; face++) + { + if (noCulling || dirs[face]) + { + int xt = x, zt = z; + float x0, z0, x1, z1, hh0, hh1; + + if (face == 0) { zt--; x1 = x + 1.0f; z1 = z + EPS; x0 = x; z0 = z + EPS; hh0 = h0; hh1 = h3; } + else if (face == 1) { zt++; x1 = x; z1 = z + 1.0f - EPS; x0 = x + 1.0f; z0 = z + 1.0f - EPS; hh0 = h2; hh1 = h1; } + else if (face == 2) { xt--; x1 = x + EPS; z1 = z; x0 = x + EPS; z0 = z + 1.0f; hh0 = h1; hh1 = h0; } + else { xt++; x0 = x + 1.0f - EPS; z1 = z + 1.0f; x1 = x + 1.0f - EPS; z0 = z; hh0 = h3; hh1 = h2; } + + changed = true; + Icon* tex = getTexture(tt, face + 2, data); + float side_v_h0 = tex->getV((1.0f - hh0) * 8.0f); + float side_v_h1 = tex->getV((1.0f - hh1) * 8.0f); + float side_u0 = tex->getU(0.0f, true), side_u1 = tex->getU(8.0f, true), side_v1 = tex->getV(8.0f, true); + + int lightTop = getLightColor(tt, level, xt, y + 1, zt); + int lightBot = getLightColor(tt, level, xt, y, zt); + float br = (face < 2) ? 0.8f : 0.6f; + + t->tex2(lightTop); t->color(br * r, br * g, br * b); + t->vertexUV(x0, y + hh0, z0, side_u0, side_v_h0); + t->vertexUV(x1, y + hh1, z1, side_u1, side_v_h1); + t->tex2(lightBot); + t->vertexUV(x1, (float)y, z1, side_u1, side_v1); + t->vertexUV(x0, (float)y, z0, side_u0, side_v1); + + t->tex2(lightBot); + t->vertexUV(x0, (float)y, z0, side_u0, side_v1); + t->vertexUV(x1, (float)y, z1, side_u1, side_v1); + t->tex2(lightTop); + t->vertexUV(x1, y + hh1, z1, side_u1, side_v_h1); + t->vertexUV(x0, y + hh0, z0, side_u0, side_v_h0); + } + } + + tileShapeY0 = 0.0f; + tileShapeY1 = 1.0f; + + return changed; +} + float TileRenderer::getWaterHeight( int x, int y, int z, Material* m ) { int count = 0; diff --git a/Minecraft.Client/TileRenderer.h b/Minecraft.Client/TileRenderer.h index 817bbf48..a4eaa79a 100644 --- a/Minecraft.Client/TileRenderer.h +++ b/Minecraft.Client/TileRenderer.h @@ -150,6 +150,7 @@ private: void tesselateRowTexture( Tile* tt, int data, float x, float y, float z ); bool tesselateWaterInWorld( Tile* tt, int x, int y, int z ); + bool tesselateWaterInWorldAO( Tile* tt, int x, int y, int z ); private: float getWaterHeight( int x, int y, int z, Material* m ); public: diff --git a/Minecraft.Server/Console/ServerCliEngine.cpp b/Minecraft.Server/Console/ServerCliEngine.cpp index 82bbdcc8..85acca13 100644 --- a/Minecraft.Server/Console/ServerCliEngine.cpp +++ b/Minecraft.Server/Console/ServerCliEngine.cpp @@ -159,7 +159,7 @@ namespace ServerRuntime } IServerCliCommand *command = m_registry->FindMutable(parsed.tokens[0]); - if (command == NULL) + if (command == nullptr) { LogWarn("Unknown command: " + parsed.tokens[0]); return false; @@ -170,7 +170,7 @@ namespace ServerRuntime void ServerCliEngine::BuildCompletions(const std::string &line, std::vector *out) const { - if (out == NULL) + if (out == nullptr) { return; } @@ -211,7 +211,7 @@ namespace ServerRuntime else { const IServerCliCommand *command = m_registry->Find(commandToken); - if (command != NULL) + if (command != nullptr) { command->Complete(context, this, out); } @@ -254,13 +254,13 @@ namespace ServerRuntime { std::vector result; MinecraftServer *server = MinecraftServer::getInstance(); - if (server == NULL) + if (server == nullptr) { return result; } PlayerList *players = server->getPlayers(); - if (players == NULL) + if (players == nullptr) { return result; } @@ -268,7 +268,7 @@ namespace ServerRuntime for (size_t i = 0; i < players->players.size(); ++i) { std::shared_ptr player = players->players[i]; - if (player != NULL) + if (player != nullptr) { result.push_back(StringUtils::WideToUtf8(player->getName())); } @@ -280,13 +280,13 @@ namespace ServerRuntime std::shared_ptr ServerCliEngine::FindPlayerByNameUtf8(const std::string &name) const { MinecraftServer *server = MinecraftServer::getInstance(); - if (server == NULL) + if (server == nullptr) { return nullptr; } PlayerList *players = server->getPlayers(); - if (players == NULL) + if (players == nullptr) { return nullptr; } @@ -295,7 +295,7 @@ namespace ServerRuntime for (size_t i = 0; i < players->players.size(); ++i) { std::shared_ptr player = players->players[i]; - if (player != NULL && equalsIgnoreCase(player->getName(), target)) + if (player != nullptr && equalsIgnoreCase(player->getName(), target)) { return player; } @@ -345,28 +345,28 @@ namespace ServerRuntime return GameType::CREATIVE; } - char *end = NULL; + char *end = nullptr; long id = strtol(lowered.c_str(), &end, 10); - if (end != NULL && *end == 0) + if (end != nullptr && *end == 0) { // Numeric fallback supports extended ids handled by level settings. return LevelSettings::validateGameType((int)id); } - return NULL; + return nullptr; } bool ServerCliEngine::DispatchWorldCommand(EGameCommand command, byteArray commandData, const std::shared_ptr &sender) const { MinecraftServer *server = MinecraftServer::getInstance(); - if (server == NULL) + if (server == nullptr) { LogWarn("MinecraftServer instance is not available."); return false; } CommandDispatcher *dispatcher = server->getCommandDispatcher(); - if (dispatcher == NULL) + if (dispatcher == nullptr) { LogWarn("Command dispatcher is not available."); return false; diff --git a/Minecraft.Server/Console/ServerCliInput.cpp b/Minecraft.Server/Console/ServerCliInput.cpp index d873980a..8064cfed 100644 --- a/Minecraft.Server/Console/ServerCliInput.cpp +++ b/Minecraft.Server/Console/ServerCliInput.cpp @@ -14,7 +14,7 @@ namespace bool UseStreamInputMode() { const char *mode = getenv("SERVER_CLI_INPUT_MODE"); - if (mode != NULL) + if (mode != nullptr) { return _stricmp(mode, "stream") == 0 || _stricmp(mode, "stdin") == 0; @@ -25,7 +25,7 @@ namespace int WaitForStdinReadable(HANDLE stdinHandle, DWORD waitMs) { - if (stdinHandle == NULL || stdinHandle == INVALID_HANDLE_VALUE) + if (stdinHandle == nullptr || stdinHandle == INVALID_HANDLE_VALUE) { return -1; } @@ -34,7 +34,7 @@ namespace if (fileType == FILE_TYPE_PIPE) { DWORD available = 0; - if (!PeekNamedPipe(stdinHandle, NULL, 0, NULL, &available, NULL)) + if (!PeekNamedPipe(stdinHandle, nullptr, 0, nullptr, &available, nullptr)) { return -1; } @@ -64,11 +64,11 @@ namespace namespace ServerRuntime { // C-style completion callback bridge requires a static instance pointer. - ServerCliInput *ServerCliInput::s_instance = NULL; + ServerCliInput *ServerCliInput::s_instance = nullptr; ServerCliInput::ServerCliInput() : m_running(false) - , m_engine(NULL) + , m_engine(nullptr) { } @@ -79,7 +79,7 @@ namespace ServerRuntime void ServerCliInput::Start(ServerCliEngine *engine) { - if (engine == NULL || m_running.exchange(true)) + if (engine == nullptr || m_running.exchange(true)) { return; } @@ -107,14 +107,14 @@ namespace ServerRuntime CancelSynchronousIo((HANDLE)m_inputThread.native_handle()); m_inputThread.join(); } - linenoiseSetCompletionCallback(NULL); + linenoiseSetCompletionCallback(nullptr); if (s_instance == this) { - s_instance = NULL; + s_instance = nullptr; } - m_engine = NULL; + m_engine = nullptr; LogInfo("console", "CLI input thread stopped."); } @@ -143,9 +143,9 @@ namespace ServerRuntime while (m_running) { char *line = linenoise("server> "); - if (line == NULL) + if (line == nullptr) { - // NULL is expected on stop request (or Ctrl+C inside linenoise). + // nullptr is expected on stop request (or Ctrl+C inside linenoise). if (!m_running) { break; @@ -166,7 +166,7 @@ namespace ServerRuntime void ServerCliInput::RunStreamInputLoop() { HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE); - if (stdinHandle == NULL || stdinHandle == INVALID_HANDLE_VALUE) + if (stdinHandle == nullptr || stdinHandle == INVALID_HANDLE_VALUE) { LogWarn("console", "stream input mode requested but STDIN handle is unavailable; falling back to linenoise."); RunLinenoiseLoop(); @@ -190,7 +190,7 @@ namespace ServerRuntime char ch = 0; DWORD bytesRead = 0; - if (!ReadFile(stdinHandle, &ch, 1, &bytesRead, NULL) || bytesRead == 0) + if (!ReadFile(stdinHandle, &ch, 1, &bytesRead, nullptr) || bytesRead == 0) { Sleep(10); continue; @@ -249,7 +249,7 @@ namespace ServerRuntime void ServerCliInput::EnqueueLine(const char *line) { - if (line == NULL || line[0] == 0 || m_engine == NULL) + if (line == nullptr || line[0] == 0 || m_engine == nullptr) { return; } @@ -262,7 +262,7 @@ namespace ServerRuntime void ServerCliInput::CompletionThunk(const char *line, linenoiseCompletions *completions) { // Static thunk forwards callback into instance state. - if (s_instance != NULL) + if (s_instance != nullptr) { s_instance->BuildCompletions(line, completions); } @@ -270,7 +270,7 @@ namespace ServerRuntime void ServerCliInput::BuildCompletions(const char *line, linenoiseCompletions *completions) { - if (line == NULL || completions == NULL || m_engine == NULL) + if (line == nullptr || completions == nullptr || m_engine == nullptr) { return; } diff --git a/Minecraft.Server/Console/ServerCliRegistry.cpp b/Minecraft.Server/Console/ServerCliRegistry.cpp index 432907b2..0bb666ca 100644 --- a/Minecraft.Server/Console/ServerCliRegistry.cpp +++ b/Minecraft.Server/Console/ServerCliRegistry.cpp @@ -52,7 +52,7 @@ namespace ServerRuntime auto it = m_lookup.find(key); if (it == m_lookup.end()) { - return NULL; + return nullptr; } return it->second; } @@ -63,7 +63,7 @@ namespace ServerRuntime auto it = m_lookup.find(key); if (it == m_lookup.end()) { - return NULL; + return nullptr; } return it->second; } diff --git a/Minecraft.Server/ServerLogManager.cpp b/Minecraft.Server/ServerLogManager.cpp index 84805f7e..89ab2d5f 100644 --- a/Minecraft.Server/ServerLogManager.cpp +++ b/Minecraft.Server/ServerLogManager.cpp @@ -47,7 +47,7 @@ namespace ServerRuntime static void ResetConnectionLogEntry(ConnectionLogEntry *entry) { - if (entry == NULL) + if (entry == nullptr) { return; } @@ -58,7 +58,7 @@ namespace ServerRuntime static std::string NormalizeRemoteIp(const char *ip) { - if (ip == NULL || ip[0] == 0) + if (ip == nullptr || ip[0] == 0) { return std::string("unknown"); } @@ -80,7 +80,7 @@ namespace ServerRuntime // Default to the main app channel when the caller does not provide a source tag. static const char *NormalizeClientLogSource(const char *source) { - if (source == NULL || source[0] == 0) + if (source == nullptr || source[0] == 0) { return "app"; } @@ -101,7 +101,7 @@ namespace ServerRuntime // Split one debug payload into individual lines so each line becomes a prompt-safe server log entry. static void ForwardClientDebugMessage(const char *source, const char *message) { - if (message == NULL || message[0] == 0) + if (message == nullptr || message[0] == 0) { return; } @@ -131,7 +131,7 @@ namespace ServerRuntime // Share the same formatting path for app, user, and legacy debug-spew forwards. static void ForwardFormattedClientDebugLogV(const char *source, const char *format, va_list args) { - if (!IsDedicatedServerLoggingEnabled() || format == NULL || format[0] == 0) + if (!IsDedicatedServerLoggingEnabled() || format == nullptr || format[0] == 0) { return; } @@ -376,7 +376,7 @@ namespace ServerRuntime */ bool TryGetConnectionRemoteIp(unsigned char smallId, std::string *outIp) { - if (!IsDedicatedServerLoggingEnabled() || outIp == NULL) + if (!IsDedicatedServerLoggingEnabled() || outIp == nullptr) { return false; } diff --git a/Minecraft.Server/ServerProperties.cpp b/Minecraft.Server/ServerProperties.cpp index d6ba64e7..f8c8de36 100644 --- a/Minecraft.Server/ServerProperties.cpp +++ b/Minecraft.Server/ServerProperties.cpp @@ -117,7 +117,7 @@ static int ClampInt(int value, int minValue, int maxValue) static bool TryParseBool(const std::string &value, bool *outValue) { - if (outValue == NULL) + if (outValue == nullptr) { return false; } @@ -138,7 +138,7 @@ static bool TryParseBool(const std::string &value, bool *outValue) static bool TryParseInt(const std::string &value, int *outValue) { - if (outValue == NULL) + if (outValue == nullptr) { return false; } @@ -149,7 +149,7 @@ static bool TryParseInt(const std::string &value, int *outValue) return false; } - char *end = NULL; + char *end = nullptr; long parsed = strtol(trimmed.c_str(), &end, 10); if (end == trimmed.c_str() || *end != 0) { @@ -162,7 +162,7 @@ static bool TryParseInt(const std::string &value, int *outValue) static bool TryParseInt64(const std::string &value, __int64 *outValue) { - if (outValue == NULL) + if (outValue == nullptr) { return false; } @@ -173,7 +173,7 @@ static bool TryParseInt64(const std::string &value, __int64 *outValue) return false; } - char *end = NULL; + char *end = nullptr; __int64 parsed = _strtoi64(trimmed.c_str(), &end, 10); if (end == trimmed.c_str() || *end != 0) { @@ -265,7 +265,7 @@ static std::string NormalizeSaveId(const std::string &source) static void ApplyDefaultServerProperties(std::unordered_map *properties) { - if (properties == NULL) + if (properties == nullptr) { return; } @@ -288,13 +288,13 @@ static void ApplyDefaultServerProperties(std::unordered_map *properties, int *outParsedCount) { - if (properties == NULL) + if (properties == nullptr) { return false; } std::string text; - if (filePath == NULL || !FileUtils::ReadTextFile(filePath, &text)) + if (filePath == nullptr || !FileUtils::ReadTextFile(filePath, &text)) { return false; } @@ -373,7 +373,7 @@ static bool ReadServerPropertiesFile(const char *filePath, std::unordered_map &properties) { - if (filePath == NULL) + if (filePath == nullptr) { return false; } @@ -428,7 +428,7 @@ static bool ReadNormalizedBoolProperty( if (raw != normalized) { (*properties)[key] = normalized; - if (shouldWrite != NULL) + if (shouldWrite != nullptr) { *shouldWrite = true; } @@ -457,7 +457,7 @@ static int ReadNormalizedIntProperty( if (raw != normalized) { (*properties)[key] = normalized; - if (shouldWrite != NULL) + if (shouldWrite != nullptr) { *shouldWrite = true; } @@ -486,7 +486,7 @@ static std::string ReadNormalizedStringProperty( if (value != (*properties)[key]) { (*properties)[key] = value; - if (shouldWrite != NULL) + if (shouldWrite != nullptr) { *shouldWrite = true; } @@ -507,7 +507,7 @@ static bool ReadNormalizedOptionalInt64Property( if ((*properties)[key] != "") { (*properties)[key] = ""; - if (shouldWrite != NULL) + if (shouldWrite != nullptr) { *shouldWrite = true; } @@ -519,7 +519,7 @@ static bool ReadNormalizedOptionalInt64Property( if (!TryParseInt64(raw, &parsed)) { (*properties)[key] = ""; - if (shouldWrite != NULL) + if (shouldWrite != nullptr) { *shouldWrite = true; } @@ -530,13 +530,13 @@ static bool ReadNormalizedOptionalInt64Property( if (raw != normalized) { (*properties)[key] = normalized; - if (shouldWrite != NULL) + if (shouldWrite != nullptr) { *shouldWrite = true; } } - if (outValue != NULL) + if (outValue != nullptr) { *outValue = parsed; } @@ -560,7 +560,7 @@ static EServerLogLevel ReadNormalizedLogLevelProperty( if (raw != normalized) { (*properties)[key] = normalized; - if (shouldWrite != NULL) + if (shouldWrite != nullptr) { *shouldWrite = true; } @@ -594,13 +594,13 @@ static std::string ReadNormalizedLevelTypeProperty( if (raw != normalized) { (*properties)[key] = normalized; - if (shouldWrite != NULL) + if (shouldWrite != nullptr) { *shouldWrite = true; } } - if (outIsFlat != NULL) + if (outIsFlat != nullptr) { *outIsFlat = isFlat; } @@ -658,7 +658,7 @@ static int WorldSizeToHellScale(int worldSize) static bool TryParseWorldSize(const std::string &lowered, int *outWorldSize) { - if (outWorldSize == NULL) + if (outWorldSize == nullptr) { return false; } @@ -712,17 +712,17 @@ static int ReadNormalizedWorldSizeProperty( if (raw != normalized) { (*properties)[key] = normalized; - if (shouldWrite != NULL) + if (shouldWrite != nullptr) { *shouldWrite = true; } } - if (outXzChunks != NULL) + if (outXzChunks != nullptr) { *outXzChunks = WorldSizeToXzChunks(worldSize); } - if (outHellScale != NULL) + if (outHellScale != nullptr) { *outHellScale = WorldSizeToHellScale(worldSize); } diff --git a/Minecraft.Server/Windows64/ServerMain.cpp b/Minecraft.Server/Windows64/ServerMain.cpp index a8d5fc66..d6abf144 100644 --- a/Minecraft.Server/Windows64/ServerMain.cpp +++ b/Minecraft.Server/Windows64/ServerMain.cpp @@ -28,6 +28,7 @@ #include "../../Minecraft.World/TilePos.h" #include "../../Minecraft.World/compression.h" #include "../../Minecraft.World/OldChunkStorage.h" +#include "../../Minecraft.World/ConsoleSaveFileOriginal.h" #include "../../Minecraft.World/net.minecraft.world.level.tile.h" #include "../../Minecraft.World/Random.h" @@ -183,10 +184,10 @@ using ServerRuntime::WorldBootstrapResult; static bool ParseIntArg(const char *value, int *outValue) { - if (value == NULL || *value == 0) + if (value == nullptr || *value == 0) return false; - char *end = NULL; + char *end = nullptr; long parsed = strtol(value, &end, 10); if (end == value || *end != 0) return false; @@ -197,10 +198,10 @@ static bool ParseIntArg(const char *value, int *outValue) static bool ParseInt64Arg(const char *value, __int64 *outValue) { - if (value == NULL || *value == 0) + if (value == nullptr || *value == 0) return false; - char *end = NULL; + char *end = nullptr; __int64 parsed = _strtoi64(value, &end, 10); if (end == value || *end != 0) return false; @@ -277,9 +278,9 @@ static bool ParseCommandLine(int argc, char **argv, DedicatedServerConfig *confi static void SetExeWorkingDirectory() { char exePath[MAX_PATH] = {}; - GetModuleFileNameA(NULL, exePath, MAX_PATH); + GetModuleFileNameA(nullptr, exePath, MAX_PATH); char *slash = strrchr(exePath, '\\'); - if (slash != NULL) + if (slash != nullptr) { *(slash + 1) = 0; SetCurrentDirectoryA(exePath); @@ -288,7 +289,7 @@ static void SetExeWorkingDirectory() static void ApplyServerPropertiesToDedicatedConfig(const ServerPropertiesConfig &serverProperties, DedicatedServerConfig *config) { - if (config == NULL) + if (config == nullptr) { return; } @@ -325,6 +326,7 @@ static void TickCoreSystems() g_NetworkManager.DoWork(); ProfileManager.Tick(); StorageManager.Tick(); + ConsoleSaveFileOriginal::flushPendingBackgroundSave(); } /** @@ -420,7 +422,7 @@ int main(int argc, char **argv) #endif LogStartupStep("registering hidden window class"); - HINSTANCE hInstance = GetModuleHandle(NULL); + HINSTANCE hInstance = GetModuleHandle(nullptr); MyRegisterClass(hInstance); LogStartupStep("creating hidden window"); @@ -490,7 +492,7 @@ int main(int argc, char **argv) LogStartupStep("creating Minecraft singleton"); Minecraft::main(); Minecraft *minecraft = Minecraft::GetInstance(); - if (minecraft == NULL) + if (minecraft == nullptr) { LogError("startup", "Minecraft initialization failed."); CleanupDevice(); @@ -655,7 +657,7 @@ int main(int argc, char **argv) break; } - if (autosaveRequested && app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle) + if (autosaveRequested && app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle && !ConsoleSaveFileOriginal::hasPendingBackgroundSave()) { LogWorldIO("autosave completed"); autosaveRequested = false; @@ -669,7 +671,7 @@ int main(int argc, char **argv) DWORD now = GetTickCount(); if ((LONG)(now - nextAutosaveTick) >= 0) { - if (app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle) + if (app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle && !ConsoleSaveFileOriginal::hasPendingBackgroundSave()) { LogWorldIO("requesting autosave"); app.SetXuiServerAction(kServerActionPad, eXuiServerAction_AutoSaveGame); @@ -685,25 +687,38 @@ int main(int argc, char **argv) LogInfof("shutdown", "Dedicated server stopped"); MinecraftServer *server = MinecraftServer::getInstance(); - if (server != NULL) + if (server != nullptr && !ConsoleSaveFileOriginal::hasPendingBackgroundSave()) { server->setSaveOnExit(true); - } - if (server != NULL) - { LogWorldIO("requesting save before shutdown"); LogWorldIO("using saveOnExit for shutdown"); } + if (ConsoleSaveFileOriginal::hasPendingBackgroundSave()) + { + LogWorldIO("Waiting for autosave to complete..."); + } + MinecraftServer::HaltServer(); if (g_NetworkManager.ServerStoppedValid()) { - C4JThread waitThread(&WaitForServerStoppedThreadProc, NULL, "WaitServerStopped"); + C4JThread waitThread(&WaitForServerStoppedThreadProc, nullptr, "WaitServerStopped"); waitThread.Run(); + while (waitThread.isRunning()) + { + TickCoreSystems(); + Sleep(10); + } waitThread.WaitForCompletion(INFINITE); } + while (ConsoleSaveFileOriginal::hasPendingBackgroundSave()) + { + TickCoreSystems(); + Sleep(10); + } + LogInfof("shutdown", "Cleaning up and exiting."); WinsockNetLayer::Shutdown(); LogDebugf("shutdown", "Network layer shutdown complete."); @@ -714,5 +729,4 @@ int main(int argc, char **argv) return 0; -} - +} \ No newline at end of file diff --git a/Minecraft.Server/WorldManager.cpp b/Minecraft.Server/WorldManager.cpp index c106a0c3..ee8696a7 100644 --- a/Minecraft.Server/WorldManager.cpp +++ b/Minecraft.Server/WorldManager.cpp @@ -31,7 +31,7 @@ struct SaveInfoQueryContext SaveInfoQueryContext() : done(false) , success(false) - , details(NULL) + , details(nullptr) { } }; @@ -75,7 +75,7 @@ static void SetStorageSaveUniqueFilename(const std::string &saveFilename) static void LogSaveFilename(const char *prefix, const std::string &saveFilename) { - LogInfof("world-io", "%s: %s", (prefix != NULL) ? prefix : "save-filename", saveFilename.c_str()); + LogInfof("world-io", "%s: %s", (prefix != nullptr) ? prefix : "save-filename", saveFilename.c_str()); } /** @@ -86,7 +86,7 @@ static void LogSaveFilename(const char *prefix, const std::string &saveFilename) */ static bool EnsureDirectoryExists(const std::wstring &directoryPath, bool *outCreated) { - if (outCreated != NULL) + if (outCreated != nullptr) { *outCreated = false; } @@ -107,9 +107,9 @@ static bool EnsureDirectoryExists(const std::wstring &directoryPath, bool *outCr return false; } - if (CreateDirectoryW(directoryPath.c_str(), NULL)) + if (CreateDirectoryW(directoryPath.c_str(), nullptr)) { - if (outCreated != NULL) + if (outCreated != nullptr) { *outCreated = true; } @@ -189,7 +189,7 @@ static void LogEnumeratedSaveInfo(int index, const SAVE_INFO &saveInfo) static int GetSavesInfoCallbackProc(LPVOID lpParam, SAVE_DETAILS *pSaveDetails, const bool bRes) { SaveInfoQueryContext *context = (SaveInfoQueryContext *)lpParam; - if (context != NULL) + if (context != nullptr) { context->details = pSaveDetails; context->success = bRes; @@ -207,7 +207,7 @@ static int GetSavesInfoCallbackProc(LPVOID lpParam, SAVE_DETAILS *pSaveDetails, static int LoadSaveDataCallbackProc(LPVOID lpParam, const bool bIsCorrupt, const bool bIsOwner) { SaveDataLoadContext *context = (SaveDataLoadContext *)lpParam; - if (context != NULL) + if (context != nullptr) { context->isCorrupt = bIsCorrupt; context->isOwner = bIsOwner; @@ -236,12 +236,12 @@ static bool WaitForSaveInfoResult(SaveInfoQueryContext *context, DWORD timeoutMs return true; } - if (context->details == NULL) + if (context->details == nullptr) { // Some implementations fill ReturnSavesInfo before the callback // Keep polling as a fallback instead of relying only on callback completion SAVE_DETAILS *details = StorageManager.ReturnSavesInfo(); - if (details != NULL) + if (details != nullptr) { context->details = details; context->success = true; @@ -250,7 +250,7 @@ static bool WaitForSaveInfoResult(SaveInfoQueryContext *context, DWORD timeoutMs } } - if (tickProc != NULL) + if (tickProc != nullptr) { tickProc(); } @@ -278,7 +278,7 @@ static bool WaitForSaveLoadResult(SaveDataLoadContext *context, DWORD timeoutMs, return true; } - if (tickProc != NULL) + if (tickProc != nullptr) { tickProc(); } @@ -370,12 +370,12 @@ static EWorldSaveLoadResult PrepareWorldSaveData( LoadSaveDataThreadParam **outSaveData, std::string *outResolvedSaveFilename) { - if (outSaveData == NULL) + if (outSaveData == nullptr) { return eWorldSaveLoad_Failed; } - *outSaveData = NULL; - if (outResolvedSaveFilename != NULL) + *outSaveData = nullptr; + if (outResolvedSaveFilename != nullptr) { outResolvedSaveFilename->clear(); } @@ -404,11 +404,11 @@ static EWorldSaveLoadResult PrepareWorldSaveData( return eWorldSaveLoad_Failed; } - if (infoContext.details == NULL) + if (infoContext.details == nullptr) { infoContext.details = StorageManager.ReturnSavesInfo(); } - if (infoContext.details == NULL) + if (infoContext.details == nullptr) { LogWorldIO("failed to retrieve save list"); return eWorldSaveLoad_Failed; @@ -486,7 +486,7 @@ static EWorldSaveLoadResult PrepareWorldSaveData( resolvedSaveFilename = targetSaveFilename; } - if (outResolvedSaveFilename != NULL) + if (outResolvedSaveFilename != nullptr) { *outResolvedSaveFilename = resolvedSaveFilename; } @@ -621,11 +621,11 @@ bool WaitForWorldActionIdle( { // Keep network and storage progressing while waiting // If this stops, save action itself may stall and time out - if (tickProc != NULL) + if (tickProc != nullptr) { tickProc(); } - if (handleActionsProc != NULL) + if (handleActionsProc != nullptr) { handleActionsProc(); } diff --git a/Minecraft.Server/cmake/sources/Common.cmake b/Minecraft.Server/cmake/sources/Common.cmake index 6a452f56..04c7fd32 100644 --- a/Minecraft.Server/cmake/sources/Common.cmake +++ b/Minecraft.Server/cmake/sources/Common.cmake @@ -496,6 +496,8 @@ set(_MINECRAFT_SERVER_COMMON_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.Client/iob_shim.asm" "${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.Client/stdafx.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.Client/stubs.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/ConsoleSaveFileOriginal.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/ConsoleSaveFileOriginal.h" "${CMAKE_CURRENT_SOURCE_DIR}/../include/lce_filesystem/lce_filesystem.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Console/ServerCliInput.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Console/ServerCliInput.h" diff --git a/Minecraft.World/ConsoleSaveFileOriginal.cpp b/Minecraft.World/ConsoleSaveFileOriginal.cpp index 42a65e03..d35f4cfb 100644 --- a/Minecraft.World/ConsoleSaveFileOriginal.cpp +++ b/Minecraft.World/ConsoleSaveFileOriginal.cpp @@ -12,6 +12,25 @@ #include "..\Minecraft.Client\Common\GameRules\LevelGenerationOptions.h" #include "..\Minecraft.World\net.minecraft.world.level.chunk.storage.h" +#ifdef MINECRAFT_SERVER_BUILD +#include +#include +#include + +static std::atomic s_bgSaveActive{false}; +static std::mutex s_bgSaveMutex; + +struct BackgroundSaveResult +{ + ConsoleSaveFile *owner = nullptr; + PBYTE thumbData = nullptr; + DWORD thumbSize = 0; + BYTE textMeta[88] = {}; + int textMetaBytes = 0; + bool pending = false; +}; +static BackgroundSaveResult s_bgResult; +#endif #ifdef _XBOX #define RESERVE_ALLOCATION MEM_RESERVE | MEM_LARGE_PAGES @@ -219,7 +238,7 @@ ConsoleSaveFileOriginal::~ConsoleSaveFileOriginal() pagesCommitted = 0; // Make sure we don't have any thumbnail data still waiting round - we can't need it now we've destroyed the save file anyway #if defined _XBOX - app.GetSaveThumbnail(NULL,NULL); + app.GetSaveThumbnail(nullptr,nullptr); #elif defined __PS3__ app.GetSaveThumbnail(nullptr,nullptr, nullptr,nullptr); #endif @@ -478,7 +497,7 @@ void ConsoleSaveFileOriginal::finalizeWrite() { unsigned int pagesRequired = ( desiredSize + (CSF_PAGE_SIZE - 1 ) ) / CSF_PAGE_SIZE; void *pvRet = VirtualAlloc(pvHeap, pagesRequired * CSF_PAGE_SIZE, COMMIT_ALLOCATION, PAGE_READWRITE); - if( pvRet == NULL ) + if( pvRet == nullptr ) { __debugbreak(); } @@ -678,6 +697,83 @@ void ConsoleSaveFileOriginal::Flush(bool autosave, bool updateThumbnail ) unsigned int fileSize = header.GetFileSize(); +#ifdef MINECRAFT_SERVER_BUILD + // on the server we dont want to block the tick thread doing compression!!! + // sna[pshot pvSaveMem while we still hold the lock then hand it off to a background thread + byte *snap = new (std::nothrow) byte[fileSize]; + if (snap) + { + // copy the save buffer while we still own the lock so nothing can write to it mid-copy + QueryPerformanceCounter(&qwTime); + memcpy(snap, pvSaveMem, fileSize); + QueryPerformanceCounter(&qwNewTime); + app.DebugPrintf("snapshot %u bytes in %.3f sec\n", fileSize, + (qwNewTime.QuadPart - qwTime.QuadPart) * fSecsPerTick); + + PBYTE thumb = nullptr; + DWORD thumbSz = 0; + app.GetSaveThumbnail(&thumb, &thumbSz); + + BYTE meta[88]; + ZeroMemory(meta, 88); + int64_t seed = 0; + bool hasSeed = false; + if (MinecraftServer *sv = MinecraftServer::getInstance(); sv && sv->levels[0]) + { + seed = sv->levels[0]->getLevelData()->getSeed(); + hasSeed = true; + } + int metaLen = app.CreateImageTextData(meta, seed, hasSeed, + app.GetGameHostOption(eGameHostOption_All), Minecraft::GetInstance()->getCurrentTexturePackId()); + + // telemetry + INT uid = 0; + StorageManager.GetSaveUniqueNumber(&uid); + TelemetryManager->RecordLevelSaveOrCheckpoint(ProfileManager.GetPrimaryPad(), uid, fileSize); + + ReleaseSaveAccess(); + s_bgSaveActive.store(true, std::memory_order_release); + + std::thread([snap, fileSize, thumb, thumbSz, meta, metaLen, this]() { + unsigned int compLen = fileSize + 8; + byte *buf = static_cast(StorageManager.AllocateSaveData(compLen)); + if (!buf) + { + // FAIL!! attempt precalc + compLen = 0; + Compression::getCompression()->Compress(nullptr, &compLen, snap, fileSize); + compLen += 8; + buf = static_cast(StorageManager.AllocateSaveData(compLen)); + } + if (buf) + { + // COM,PRESS + Compression::getCompression()->Compress(buf + 8, &compLen, snap, fileSize); + ZeroMemory(buf, 8); + memcpy(buf + 4, &fileSize, sizeof(fileSize)); + + // store the result so flushPendingBackgroundSave() can pick it up on the main thread next tick + // StorageManager isnt thread safe so we cant call SetSaveImages or SaveSaveData from here. Bwoomp + std::lock_guard lk(s_bgSaveMutex); + s_bgResult.owner = this; + s_bgResult.thumbData = thumb; + s_bgResult.thumbSize = thumbSz; + memcpy(s_bgResult.textMeta, meta, sizeof(meta)); + s_bgResult.textMetaBytes = metaLen; + s_bgResult.pending = true; + } + else + { + app.DebugPrintf("save buf alloc failed\n"); + s_bgSaveActive.store(false, std::memory_order_release); + } + delete[] snap; + }).detach(); + return; + } + app.DebugPrintf("snapshot alloc failed (%u bytes)\n", fileSize); +#endif + // Assume that the compression will make it smaller so initially attempt to allocate the current file size // We add 4 bytes to the start so that we can signal compressed data // And another 4 bytes to store the decompressed data size @@ -843,6 +939,10 @@ int ConsoleSaveFileOriginal::SaveSaveDataCallback(LPVOID lpParam,bool bRes) { ConsoleSaveFile *pClass=static_cast(lpParam); +#ifdef MINECRAFT_SERVER_BUILD + s_bgSaveActive.store(false, std::memory_order_release); +#endif + return 0; } @@ -1090,3 +1190,26 @@ void *ConsoleSaveFileOriginal::getWritePointer(FileEntry *file) { return static_cast(pvSaveMem) + file->currentFilePointer;; } + + +#ifdef MINECRAFT_SERVER_BUILD +void ConsoleSaveFileOriginal::flushPendingBackgroundSave() +{ + std::lock_guard lk(s_bgSaveMutex); + if (!s_bgResult.pending) + return; + + StorageManager.SetSaveImages( + s_bgResult.thumbData, s_bgResult.thumbSize, + nullptr, 0, s_bgResult.textMeta, s_bgResult.textMetaBytes); + StorageManager.SaveSaveData(&ConsoleSaveFileOriginal::SaveSaveDataCallback, s_bgResult.owner); + + s_bgResult.pending = false; + // the actual write isnt done until SaveSaveDataCallback fires +} + +bool ConsoleSaveFileOriginal::hasPendingBackgroundSave() +{ + return s_bgSaveActive.load(std::memory_order_acquire); +} +#endif \ No newline at end of file diff --git a/Minecraft.World/ConsoleSaveFileOriginal.h b/Minecraft.World/ConsoleSaveFileOriginal.h index 9c91fafc..3924b09b 100644 --- a/Minecraft.World/ConsoleSaveFileOriginal.h +++ b/Minecraft.World/ConsoleSaveFileOriginal.h @@ -71,6 +71,11 @@ public: virtual vector *getValidPlayerDatFiles(); #endif //__PS3__ +#ifdef MINECRAFT_SERVER_BUILD + static void flushPendingBackgroundSave(); + static bool hasPendingBackgroundSave(); +#endif + virtual int getSaveVersion(); virtual int getOriginalSaveVersion();