Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 18 commits into from Sep 7, 2017
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions Server/Plugins/APIDump/APIDesc.lua
Expand Up @@ -11405,6 +11405,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 @@ -1678,6 +1688,16 @@ function OnAllChunksAvailable()</pre> All return values from the callbacks are i
},
Notes = "Returns the number of unused dirty chunks. That's the number of chunks that we can save and then unload.",
},
GetSavingEnabled =
{
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.",
},
GetScoreBoard =
{
Returns =
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 adversly affect both performance and stability (i.e. your computer will get slow and crash). Return true sparingly.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adversely

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Today I learned.

]],
Params =
{
Expand Down
4 changes: 2 additions & 2 deletions src/Entities/Player.cpp
Expand Up @@ -2196,7 +2196,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(), &m_Stats);
cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetDataPath(), GetName(), &m_Stats);
StatSerializer.Load();

LOGD("Player %s was read from file \"%s\", spawning at {%.2f, %.2f, %.2f} in world \"%s\"",
Expand Down Expand Up @@ -2294,7 +2294,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(), &m_Stats);
cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetDataPath(), GetName(), &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 @@ -137,6 +137,9 @@ class cRoot
/** Saves all chunks in all worlds */
void SaveAllChunks(void); // tolua_export

/** Sets whether saving chunks is enabled */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for all worlds

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 @@ -125,16 +125,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_SavingEnabled(true),
m_Dimension(a_Dimension),
m_IsSpawnExplicitlySet(false),
m_SpawnX(0),
Expand Down Expand Up @@ -199,10 +201,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 @@ -218,11 +220,14 @@ cWorld::~cWorld()

m_Storage.WaitForFinish();

// Unload the scoreboard
cScoreboardSerializer Serializer(m_WorldName, &m_Scoreboard);
Serializer.Save();
if (GetSavingEnabled())
{
// 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 @@ -2956,7 +2961,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() && GetSavingEnabled())
{
m_Storage.QueueSaveChunk(ChunkX, ChunkZ);
}
Expand Down Expand Up @@ -3564,8 +3571,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 (GetSavingEnabled())
{
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 @@ -19,6 +19,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 @@ -111,6 +112,12 @@ class cWorld :

// tolua_begin

/** Get whether saving chunks is enabled */
bool GetSavingEnabled(void) const { return m_SavingEnabled; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to IsSavingEnabled and m_IsSavingEnabled


/** Set whether saving chunks is enabled */
void SetSavingEnabled(bool a_SavingEnabled) { m_SavingEnabled = a_SavingEnabled; }

int GetTicksUntilWeatherChange(void) const { return m_WeatherInterval; }

/** Is the daylight cycle enabled? */
Expand Down Expand Up @@ -630,6 +637,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 @@ -871,6 +881,9 @@ class cWorld :

AString m_WorldName;

/** The path to the root directory for the world files */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't contain the trailing path separator.

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 @@ -882,6 +895,9 @@ class cWorld :

int m_StorageCompressionFactor;

/** Whether or not writing chunks to disk is currently enabled */
std::atomic<bool> m_SavingEnabled;

/** The dimension of the world, used by the client to provide correct lighting scheme */
eDimension m_Dimension;

Expand Down Expand Up @@ -1036,7 +1052,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