From b12f4ef7d58cfe4d815feb2db1f88f223c7f2a61 Mon Sep 17 00:00:00 2001
From: Lane Kolbly
Date: Thu, 7 Sep 2017 07:41:16 -0500
Subject: Made world data paths adjustable, and added API to temporarily
disable saving chunks to disk. (#3912)
---
Server/Plugins/APIDump/APIDesc.lua | 11 ++++++
Server/Plugins/APIDump/Classes/World.lua | 31 +++++++++++++++
Server/Plugins/APIDump/Hooks/OnChunkUnloading.lua | 3 +-
src/Entities/Player.cpp | 4 +-
src/MapManager.cpp | 8 ++--
src/Root.cpp | 48 ++++++++++++++++-------
src/Root.h | 3 ++
src/World.cpp | 32 +++++++++------
src/World.h | 18 ++++++++-
src/WorldStorage/WSSAnvil.cpp | 6 +--
10 files changed, 127 insertions(+), 37 deletions(-)
diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua
index dafe98619..334fed378 100644
--- a/Server/Plugins/APIDump/APIDesc.lua
+++ b/Server/Plugins/APIDump/APIDesc.lua
@@ -11501,6 +11501,17 @@ a_Player:OpenWindow(Window);
{
Notes = "Saves all the chunks in all the worlds. Note that the saving is queued on each world's tick thread and this functions returns before the chunks are actually saved.",
},
+ SetSavingEnabled =
+ {
+ Params =
+ {
+ {
+ Name = "SavingEnabled",
+ Type = "boolean",
+ },
+ },
+ Notes = "Sets whether saving chunk data is enabled for all worlds. If disabled, dirty chunks will stay in memory forever, which can cause performance and stability issues.",
+ },
},
AdditionalInfo =
{
diff --git a/Server/Plugins/APIDump/Classes/World.lua b/Server/Plugins/APIDump/Classes/World.lua
index ac837dea3..82f31febe 100644
--- a/Server/Plugins/APIDump/Classes/World.lua
+++ b/Server/Plugins/APIDump/Classes/World.lua
@@ -1450,6 +1450,16 @@ function OnAllChunksAvailable() All return values from the callbacks are i
},
Notes = "Returns the block type and metadata for the block at the specified coords. The first value specifies if the block is in a valid loaded chunk, the other values are valid only if BlockValid is true.",
},
+ GetDataPath =
+ {
+ Returns =
+ {
+ {
+ Type = "boolean",
+ },
+ },
+ Notes = "Returns the path to the root of the world data.",
+ },
GetDefaultWeatherInterval =
{
Params =
@@ -2117,6 +2127,16 @@ function OnAllChunksAvailable() All return values from the callbacks are i
},
Notes = "Returns whether PVP is enabled in the world settings.",
},
+ IsSavingEnabled =
+ {
+ Returns =
+ {
+ {
+ Type = "boolean",
+ },
+ },
+ Notes = "Returns whether or not saving chunk data is enabled. If disabled, the world will keep dirty chunks in memory forever, and will simply regenerate non-dirty chunks that are unloaded.",
+ },
IsTrapdoorOpen =
{
Params =
@@ -2726,6 +2746,17 @@ function OnAllChunksAvailable() All return values from the callbacks are i
},
Notes = "Sets the blockticking to start at the specified block in the next tick.",
},
+ SetSavingEnabled =
+ {
+ Params =
+ {
+ {
+ Name = "SavingEnabled",
+ Type = "boolean",
+ },
+ },
+ Notes = "Sets whether saving chunk data is enabled. If disabled, dirty chunks will stay in memory forever, which may cause performance and stability issues.",
+ },
SetShouldUseChatPrefixes =
{
Params =
diff --git a/Server/Plugins/APIDump/Hooks/OnChunkUnloading.lua b/Server/Plugins/APIDump/Hooks/OnChunkUnloading.lua
index 98e0a71fd..7317db215 100644
--- a/Server/Plugins/APIDump/Hooks/OnChunkUnloading.lua
+++ b/Server/Plugins/APIDump/Hooks/OnChunkUnloading.lua
@@ -8,8 +8,7 @@ return
Cuberite calls this function when a chunk is about to be unloaded from the memory. A plugin may
force Cuberite to keep the chunk in memory by returning true.
- FIXME: The return value should be used only for event propagation stopping, not for the actual
- decision whether to unload.
+ CAUTION: Preventing the server from unloading chunks can cause the server to use too much RAM, which will adversely affect both performance and stability (i.e. your computer will get slow and crash). Return true sparingly.
]],
Params =
{
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index 614edef75..8d35d0bf5 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -2203,7 +2203,7 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
// Load the player stats.
// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
- cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetName(), GetName(), GetUUID().ToLongString(), &m_Stats);
+ cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetDataPath(), GetName(), GetUUID().ToLongString(), &m_Stats);
StatSerializer.Load();
LOGD("Player %s was read from file \"%s\", spawning at {%.2f, %.2f, %.2f} in world \"%s\"",
@@ -2301,7 +2301,7 @@ bool cPlayer::SaveToDisk()
// Save the player stats.
// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
- cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetName(), GetName(), GetUUID().ToLongString(), &m_Stats);
+ cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetDataPath(), GetName(), GetUUID().ToLongString(), &m_Stats);
if (!StatSerializer.Save())
{
LOGWARNING("Could not save stats for player %s", GetName().c_str());
diff --git a/src/MapManager.cpp b/src/MapManager.cpp
index 2729e67dd..4408af76b 100644
--- a/src/MapManager.cpp
+++ b/src/MapManager.cpp
@@ -96,7 +96,7 @@ void cMapManager::LoadMapData(void)
{
cCSLock Lock(m_CS);
- cIDCountSerializer IDSerializer(m_World->GetName());
+ cIDCountSerializer IDSerializer(m_World->GetDataPath());
if (!IDSerializer.Load())
{
@@ -111,7 +111,7 @@ void cMapManager::LoadMapData(void)
{
cMap Map(i, m_World);
- cMapSerializer Serializer(m_World->GetName(), &Map);
+ cMapSerializer Serializer(m_World->GetDataPath(), &Map);
if (!Serializer.Load())
{
@@ -135,7 +135,7 @@ void cMapManager::SaveMapData(void)
return;
}
- cIDCountSerializer IDSerializer(m_World->GetName());
+ cIDCountSerializer IDSerializer(m_World->GetDataPath());
IDSerializer.SetMapCount(static_cast(m_MapData.size()));
@@ -149,7 +149,7 @@ void cMapManager::SaveMapData(void)
{
cMap & Map = *it;
- cMapSerializer Serializer(m_World->GetName(), &Map);
+ cMapSerializer Serializer(m_World->GetDataPath(), &Map);
if (!Serializer.Save())
{
diff --git a/src/Root.cpp b/src/Root.cpp
index 3e30d8a07..38c95f822 100644
--- a/src/Root.cpp
+++ b/src/Root.cpp
@@ -396,16 +396,20 @@ void cRoot::LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIn
a_Settings.AddValue("Worlds", "DefaultWorld", "world");
a_Settings.AddValue("Worlds", "World", "world_nether");
a_Settings.AddValue("Worlds", "World", "world_the_end");
- m_pDefaultWorld = new cWorld("world");
+ a_Settings.AddValue("WorldPaths", "world", "world");
+ a_Settings.AddValue("WorldPaths", "world_nether", "world_nether");
+ a_Settings.AddValue("WorldPaths", "world_the_end", "world_the_end");
+ m_pDefaultWorld = new cWorld("world", "world");
m_WorldsByName["world"] = m_pDefaultWorld;
- m_WorldsByName["world_nether"] = new cWorld("world_nether", dimNether, "world");
- m_WorldsByName["world_the_end"] = new cWorld("world_the_end", dimEnd, "world");
+ m_WorldsByName["world_nether"] = new cWorld("world_nether", "world_nether", dimNether, "world");
+ m_WorldsByName["world_the_end"] = new cWorld("world_the_end", "world_the_end", dimEnd, "world");
return;
}
// First get the default world
AString DefaultWorldName = a_Settings.GetValueSet("Worlds", "DefaultWorld", "world");
- m_pDefaultWorld = new cWorld(DefaultWorldName.c_str());
+ AString DefaultWorldPath = a_Settings.GetValueSet("WorldPaths", DefaultWorldName, DefaultWorldName);
+ m_pDefaultWorld = new cWorld(DefaultWorldName.c_str(), DefaultWorldPath.c_str());
m_WorldsByName[ DefaultWorldName ] = m_pDefaultWorld;
auto Worlds = a_Settings.GetValues("Worlds");
@@ -453,10 +457,15 @@ void cRoot::LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIn
FoundAdditionalWorlds = true;
cWorld * NewWorld;
AString LowercaseName = StrToLower(WorldName);
+ AString WorldPath = a_Settings.GetValueSet("WorldPaths", WorldName, WorldName);
AString NetherAppend = "_nether";
AString EndAppend1 = "_the_end";
AString EndAppend2 = "_end";
+ // The default world is an overworld with no links
+ eDimension Dimension = dimOverworld;
+ AString LinkTo = "";
+
// if the world is called x_nether
if ((LowercaseName.size() > NetherAppend.size()) && (LowercaseName.substr(LowercaseName.size() - NetherAppend.size()) == NetherAppend))
{
@@ -464,12 +473,12 @@ void cRoot::LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIn
// otherwise, choose the default world as the linked world.
// As before, any ini settings will completely override this if an ini is already present.
- AString LinkTo = WorldName.substr(0, WorldName.size() - NetherAppend.size());
+ LinkTo = WorldName.substr(0, WorldName.size() - NetherAppend.size());
if (GetWorld(LinkTo) == nullptr)
{
LinkTo = DefaultWorldName;
}
- NewWorld = new cWorld(WorldName.c_str(), dimNether, LinkTo);
+ Dimension = dimNether;
}
// if the world is called x_the_end
else if ((LowercaseName.size() > EndAppend1.size()) && (LowercaseName.substr(LowercaseName.size() - EndAppend1.size()) == EndAppend1))
@@ -478,12 +487,12 @@ void cRoot::LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIn
// otherwise, choose the default world as the linked world.
// As before, any ini settings will completely override this if an ini is already present.
- AString LinkTo = WorldName.substr(0, WorldName.size() - EndAppend1.size());
+ LinkTo = WorldName.substr(0, WorldName.size() - EndAppend1.size());
if (GetWorld(LinkTo) == nullptr)
{
LinkTo = DefaultWorldName;
}
- NewWorld = new cWorld(WorldName.c_str(), dimEnd, LinkTo);
+ Dimension = dimEnd;
}
// if the world is called x_end
else if ((LowercaseName.size() > EndAppend2.size()) && (LowercaseName.substr(LowercaseName.size() - EndAppend2.size()) == EndAppend2))
@@ -492,17 +501,14 @@ void cRoot::LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIn
// otherwise, choose the default world as the linked world.
// As before, any ini settings will completely override this if an ini is already present.
- AString LinkTo = WorldName.substr(0, WorldName.size() - EndAppend2.size());
+ LinkTo = WorldName.substr(0, WorldName.size() - EndAppend2.size());
if (GetWorld(LinkTo) == nullptr)
{
LinkTo = DefaultWorldName;
}
- NewWorld = new cWorld(WorldName.c_str(), dimEnd, LinkTo);
- }
- else
- {
- NewWorld = new cWorld(WorldName.c_str());
+ Dimension = dimEnd;
}
+ NewWorld = new cWorld(WorldName.c_str(), WorldPath.c_str(), Dimension, LinkTo);
m_WorldsByName[WorldName] = NewWorld;
} // for i - Worlds
@@ -709,6 +715,20 @@ void cRoot::SaveAllChunks(void)
+
+
+void cRoot::SetSavingEnabled(bool a_SavingEnabled)
+{
+ for (WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr)
+ {
+ itr->second->SetSavingEnabled(a_SavingEnabled);
+ }
+}
+
+
+
+
+
void cRoot::SendPlayerLists(cPlayer * a_DestPlayer)
{
for (const auto & itr : m_WorldsByName)
diff --git a/src/Root.h b/src/Root.h
index 44567018d..46b202c88 100644
--- a/src/Root.h
+++ b/src/Root.h
@@ -135,6 +135,9 @@ public:
/** Saves all chunks in all worlds */
void SaveAllChunks(void); // tolua_export
+ /** Sets whether saving chunks is enabled in all worlds (overrides however the worlds were already set) */
+ void SetSavingEnabled(bool a_SavingEnabled); // tolua_export
+
/** Calls the callback for each player in all worlds */
bool ForEachPlayer(cPlayerListCallback & a_Callback); // >> EXPORTED IN MANUALBINDINGS <<
diff --git a/src/World.cpp b/src/World.cpp
index f610d4d2e..e2ac24e71 100644
--- a/src/World.cpp
+++ b/src/World.cpp
@@ -120,16 +120,18 @@ void cWorld::cTickThread::Execute(void)
////////////////////////////////////////////////////////////////////////////////
// cWorld:
-cWorld::cWorld(const AString & a_WorldName, eDimension a_Dimension, const AString & a_LinkedOverworldName) :
+cWorld::cWorld(const AString & a_WorldName, const AString & a_DataPath, eDimension a_Dimension, const AString & a_LinkedOverworldName) :
m_WorldName(a_WorldName),
+ m_DataPath(a_DataPath),
m_LinkedOverworldName(a_LinkedOverworldName),
- m_IniFileName(m_WorldName + "/world.ini"),
+ m_IniFileName(m_DataPath + "/world.ini"),
m_StorageSchema("Default"),
#ifdef __arm__
m_StorageCompressionFactor(0),
#else
m_StorageCompressionFactor(6),
#endif
+ m_IsSavingEnabled(true),
m_Dimension(a_Dimension),
m_IsSpawnExplicitlySet(false),
m_SpawnX(0),
@@ -194,10 +196,10 @@ cWorld::cWorld(const AString & a_WorldName, eDimension a_Dimension, const AStrin
{
LOGD("cWorld::cWorld(\"%s\")", a_WorldName.c_str());
- cFile::CreateFolder(FILE_IO_PREFIX + m_WorldName);
+ cFile::CreateFolderRecursive(FILE_IO_PREFIX + m_DataPath);
// Load the scoreboard
- cScoreboardSerializer Serializer(m_WorldName, &m_Scoreboard);
+ cScoreboardSerializer Serializer(m_DataPath, &m_Scoreboard);
Serializer.Load();
}
@@ -213,11 +215,14 @@ cWorld::~cWorld()
m_Storage.WaitForFinish();
- // Unload the scoreboard
- cScoreboardSerializer Serializer(m_WorldName, &m_Scoreboard);
- Serializer.Save();
+ if (IsSavingEnabled())
+ {
+ // Unload the scoreboard
+ cScoreboardSerializer Serializer(m_DataPath, &m_Scoreboard);
+ Serializer.Save();
- m_MapManager.SaveMapData();
+ m_MapManager.SaveMapData();
+ }
// Explicitly destroy the chunkmap, so that it's guaranteed to be destroyed before the other internals
// This fixes crashes on stopping the server, because chunk destructor deletes entities and those access the world.
@@ -2965,7 +2970,9 @@ void cWorld::SetChunkData(cSetChunkData & a_SetChunkData)
);
// Save the chunk right after generating, so that we don't have to generate it again on next run
- if (a_SetChunkData.ShouldMarkDirty())
+ // If saving is disabled, then the chunk was marked dirty so it will get
+ // saved if saving is later enabled.
+ if (a_SetChunkData.ShouldMarkDirty() && IsSavingEnabled())
{
m_Storage.QueueSaveChunk(ChunkX, ChunkZ);
}
@@ -3629,8 +3636,11 @@ bool cWorld::ForEachLoadedChunk(std::function a_Callback)
void cWorld::SaveAllChunks(void)
{
- m_LastSave = std::chrono::duration_cast(m_WorldAge);
- m_ChunkMap->SaveAllChunks();
+ if (IsSavingEnabled())
+ {
+ m_LastSave = std::chrono::duration_cast(m_WorldAge);
+ m_ChunkMap->SaveAllChunks();
+ }
}
diff --git a/src/World.h b/src/World.h
index 87110a5cf..22847b975 100644
--- a/src/World.h
+++ b/src/World.h
@@ -12,6 +12,7 @@
#include "ChunkSender.h"
#include "Defines.h"
#include "LightingThread.h"
+#include "IniFile.h"
#include "Item.h"
#include "Mobs/Monster.h"
#include "Entities/ProjectileEntity.h"
@@ -103,6 +104,12 @@ public:
// tolua_begin
+ /** Get whether saving chunks is enabled */
+ bool IsSavingEnabled(void) const { return m_IsSavingEnabled; }
+
+ /** Set whether saving chunks is enabled */
+ void SetSavingEnabled(bool a_IsSavingEnabled) { m_IsSavingEnabled = a_IsSavingEnabled; }
+
int GetTicksUntilWeatherChange(void) const { return m_WeatherInterval; }
/** Is the daylight cycle enabled? */
@@ -657,6 +664,9 @@ public:
/** Returns the name of the world */
const AString & GetName(void) const { return m_WorldName; }
+ /** Returns the data path to the world data */
+ const AString & GetDataPath(void) const { return m_DataPath; }
+
/** Returns the name of the world.ini file used by this world */
const AString & GetIniFileName(void) const {return m_IniFileName; }
@@ -900,6 +910,9 @@ private:
AString m_WorldName;
+ /** The path to the root directory for the world files. Does not including trailing path specifier. */
+ AString m_DataPath;
+
/** The name of the overworld that portals in this world should link to.
Only has effect if this world is a Nether or End world. */
AString m_LinkedOverworldName;
@@ -911,6 +924,9 @@ private:
int m_StorageCompressionFactor;
+ /** Whether or not writing chunks to disk is currently enabled */
+ std::atomic m_IsSavingEnabled;
+
/** The dimension of the world, used by the client to provide correct lighting scheme */
eDimension m_Dimension;
@@ -1065,7 +1081,7 @@ private:
cSetChunkDataPtrs m_SetChunkDataQueue;
- cWorld(const AString & a_WorldName, eDimension a_Dimension = dimOverworld, const AString & a_LinkedOverworldName = "");
+ cWorld(const AString & a_WorldName, const AString & a_DataPath, eDimension a_Dimension = dimOverworld, const AString & a_LinkedOverworldName = "");
virtual ~cWorld() override;
void Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_LastTickDurationMSec);
diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp
index 8f4d41598..158f7a819 100755
--- a/src/WorldStorage/WSSAnvil.cpp
+++ b/src/WorldStorage/WSSAnvil.cpp
@@ -85,7 +85,7 @@ cWSSAnvil::cWSSAnvil(cWorld * a_World, int a_CompressionFactor) :
{
// Create a level.dat file for mapping tools, if it doesn't already exist:
AString fnam;
- Printf(fnam, "%s%clevel.dat", a_World->GetName().c_str(), cFile::PathSeparator);
+ Printf(fnam, "%s%clevel.dat", a_World->GetDataPath().c_str(), cFile::PathSeparator);
if (!cFile::Exists(fnam))
{
cFastNBTWriter Writer;
@@ -180,7 +180,7 @@ void cWSSAnvil::ChunkLoadFailed(int a_ChunkX, int a_ChunkZ, const AString & a_Re
{
// Construct the filename for offloading:
AString OffloadFileName;
- Printf(OffloadFileName, "%s%cregion%cbadchunks", m_World->GetName().c_str(), cFile::PathSeparator, cFile::PathSeparator);
+ Printf(OffloadFileName, "%s%cregion%cbadchunks", m_World->GetDataPath().c_str(), cFile::PathSeparator, cFile::PathSeparator);
cFile::CreateFolder(FILE_IO_PREFIX + OffloadFileName);
auto t = time(nullptr);
struct tm stm;
@@ -286,7 +286,7 @@ cWSSAnvil::cMCAFile * cWSSAnvil::LoadMCAFile(const cChunkCoords & a_Chunk)
// Load it anew:
AString FileName;
- Printf(FileName, "%s%cregion", m_World->GetName().c_str(), cFile::PathSeparator);
+ Printf(FileName, "%s%cregion", m_World->GetDataPath().c_str(), cFile::PathSeparator);
cFile::CreateFolder(FILE_IO_PREFIX + FileName);
AppendPrintf(FileName, "/r.%d.%d.mca", RegionX, RegionZ);
cMCAFile * f = new cMCAFile(*this, FileName, RegionX, RegionZ);
--
cgit v1.2.3