Skip to content

Commit

Permalink
Made world data paths adjustable, and added API to temporarily disabl…
Browse files Browse the repository at this point in the history
…e saving chunks to disk. (#3912)
  • Loading branch information
lkolbly authored and bearbin committed Sep 7, 2017
1 parent b5a23e5 commit b12f4ef
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 37 deletions.
11 changes: 11 additions & 0 deletions Server/Plugins/APIDump/APIDesc.lua
Expand Up @@ -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 =
{
Expand Down
31 changes: 31 additions & 0 deletions Server/Plugins/APIDump/Classes/World.lua
Expand Up @@ -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 =
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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 =
Expand Down
3 changes: 1 addition & 2 deletions Server/Plugins/APIDump/Hooks/OnChunkUnloading.lua
Expand Up @@ -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 =
{
Expand Down
4 changes: 2 additions & 2 deletions src/Entities/Player.cpp
Expand Up @@ -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\"",
Expand Down Expand Up @@ -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());
Expand Down
8 changes: 4 additions & 4 deletions src/MapManager.cpp
Expand Up @@ -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())
{
Expand All @@ -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())
{
Expand All @@ -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()));

Expand All @@ -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())
{
Expand Down
48 changes: 34 additions & 14 deletions src/Root.cpp
Expand Up @@ -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");

Expand Down Expand Up @@ -453,23 +457,28 @@ 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))
{
// The world is called x_nether, see if a world called x exists. If yes, choose it as the linked world,
// 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))
Expand All @@ -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))
Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions src/Root.h
Expand Up @@ -135,6 +135,9 @@ class cRoot
/** 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 <<

Expand Down
32 changes: 21 additions & 11 deletions src/World.cpp
Expand Up @@ -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),
Expand Down Expand Up @@ -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();
}

Expand All @@ -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.
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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();
}
}


Expand Down
18 changes: 17 additions & 1 deletion src/World.h
Expand Up @@ -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"
Expand Down Expand Up @@ -103,6 +104,12 @@ class cWorld :

// 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? */
Expand Down Expand Up @@ -657,6 +664,9 @@ class cWorld :
/** 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; }

Expand Down Expand Up @@ -900,6 +910,9 @@ class cWorld :

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;
Expand All @@ -911,6 +924,9 @@ class cWorld :

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;

Expand Down Expand Up @@ -1065,7 +1081,7 @@ class cWorld :
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);
Expand Down

0 comments on commit b12f4ef

Please sign in to comment.