1
0

Made world data paths adjustable, and added API to temporarily disable saving chunks to disk. (#3912)

This commit is contained in:
Lane Kolbly 2017-09-07 07:41:16 -05:00 committed by Alexander Harkness
parent b5a23e5cd9
commit b12f4ef7d5
10 changed files with 127 additions and 37 deletions

View File

@ -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 =
{

View File

@ -1450,6 +1450,16 @@ function OnAllChunksAvailable()</pre> 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()</pre> 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()</pre> 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 =

View File

@ -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.</p>
<p>
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 =
{

View File

@ -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());

View File

@ -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<unsigned>(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())
{

View File

@ -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)

View File

@ -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 <<

View File

@ -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<bool(int, int)> a_Callback)
void cWorld::SaveAllChunks(void)
{
m_LastSave = std::chrono::duration_cast<cTickTimeLong>(m_WorldAge);
m_ChunkMap->SaveAllChunks();
if (IsSavingEnabled())
{
m_LastSave = std::chrono::duration_cast<cTickTimeLong>(m_WorldAge);
m_ChunkMap->SaveAllChunks();
}
}

View File

@ -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<bool> 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);

View File

@ -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);