diff options
m--------- | MCServer/Plugins/Core | 0 | ||||
-rw-r--r-- | MCServer/Plugins/Debuggers/Debuggers.lua | 124 | ||||
-rw-r--r-- | MCServer/Plugins/Debuggers/Info.lua | 223 | ||||
-rw-r--r-- | src/Bindings/ManualBindings.cpp | 67 | ||||
-rw-r--r-- | src/ChunkDef.h | 23 | ||||
-rw-r--r-- | src/ChunkMap.cpp | 97 | ||||
-rw-r--r-- | src/ChunkMap.h | 14 | ||||
-rw-r--r-- | src/Generating/ChunkGenerator.cpp | 47 | ||||
-rw-r--r-- | src/Generating/ChunkGenerator.h | 47 | ||||
-rw-r--r-- | src/LightingThread.cpp | 14 | ||||
-rw-r--r-- | src/OSSupport/Queue.h | 23 | ||||
-rw-r--r-- | src/Root.cpp | 2 | ||||
-rw-r--r-- | src/World.cpp | 13 | ||||
-rw-r--r-- | src/World.h | 6 | ||||
-rw-r--r-- | src/WorldStorage/WorldStorage.cpp | 58 | ||||
-rw-r--r-- | src/WorldStorage/WorldStorage.h | 6 |
16 files changed, 677 insertions, 87 deletions
diff --git a/MCServer/Plugins/Core b/MCServer/Plugins/Core -Subproject 4183d6cfb2049d0757d811a65bd4e75ed78b9f4 +Subproject 39d980e3a3447ac23f61c7d65426b33ee6c0718 diff --git a/MCServer/Plugins/Debuggers/Debuggers.lua b/MCServer/Plugins/Debuggers/Debuggers.lua index 3dcd4ebee..a047488b5 100644 --- a/MCServer/Plugins/Debuggers/Debuggers.lua +++ b/MCServer/Plugins/Debuggers/Debuggers.lua @@ -35,39 +35,15 @@ function Initialize(Plugin) -- _X: Disabled so that the normal operation doesn't interfere with anything -- PM:AddHook(cPluginManager.HOOK_CHUNK_GENERATED, OnChunkGenerated); - PM:BindCommand("/nick", "debuggers", HandleNickCmd, "- Gives you a custom name"); - PM:BindCommand("/le", "debuggers", HandleListEntitiesCmd, "- Shows a list of all the loaded entities"); - PM:BindCommand("/ke", "debuggers", HandleKillEntitiesCmd, "- Kills all the loaded entities"); - PM:BindCommand("/wool", "debuggers", HandleWoolCmd, "- Sets all your armor to blue wool"); - PM:BindCommand("/testwnd", "debuggers", HandleTestWndCmd, "- Opens up a window using plugin API"); - PM:BindCommand("/gc", "debuggers", HandleGCCmd, "- Activates the Lua garbage collector"); - PM:BindCommand("/fast", "debuggers", HandleFastCmd, "- Switches between fast and normal movement speed"); - PM:BindCommand("/dash", "debuggers", HandleDashCmd, "- Switches between fast and normal sprinting speed"); - PM:BindCommand("/hunger", "debuggers", HandleHungerCmd, "- Lists the current hunger-related variables"); - PM:BindCommand("/poison", "debuggers", HandlePoisonCmd, "- Sets food-poisoning for 15 seconds"); - PM:BindCommand("/starve", "debuggers", HandleStarveCmd, "- Sets the food level to zero"); - PM:BindCommand("/fl", "debuggers", HandleFoodLevelCmd, "- Sets the food level to the given value"); - PM:BindCommand("/spidey", "debuggers", HandleSpideyCmd, "- Shoots a line of web blocks until it hits non-air"); - PM:BindCommand("/ench", "debuggers", HandleEnchCmd, "- Provides an instant dummy enchantment window"); - PM:BindCommand("/fs", "debuggers", HandleFoodStatsCmd, "- Turns regular foodstats message on or off"); - PM:BindCommand("/arr", "debuggers", HandleArrowCmd, "- Creates an arrow going away from the player"); - PM:BindCommand("/fb", "debuggers", HandleFireballCmd, "- Creates a ghast fireball as if shot by the player"); - PM:BindCommand("/xpa", "debuggers", HandleAddExperience, "- Adds 200 experience to the player"); - PM:BindCommand("/xpr", "debuggers", HandleRemoveXp, "- Remove all xp"); - PM:BindCommand("/fill", "debuggers", HandleFill, "- Fills all block entities in current chunk with junk"); - PM:BindCommand("/fr", "debuggers", HandleFurnaceRecipe, "- Shows the furnace recipe for the currently held item"); - PM:BindCommand("/ff", "debuggers", HandleFurnaceFuel, "- Shows how long the currently held item would burn in a furnace"); - PM:BindCommand("/sched", "debuggers", HandleSched, "- Schedules a simple countdown using cWorld:ScheduleTask()"); - PM:BindCommand("/cs", "debuggers", HandleChunkStay, "- Tests the ChunkStay Lua integration for the specified chunk coords"); - PM:BindCommand("/compo", "debuggers", HandleCompo, "- Tests the cCompositeChat bindings"); - PM:BindCommand("/sb", "debuggers", HandleSetBiome, "- Sets the biome around you to the specified one"); - PM:BindCommand("/wesel", "debuggers", HandleWESel, "- Expands the current WE selection by 1 block in X/Z"); - PM:BindCommand("/rmitem", "debuggers", HandleRMItem, "- Remove the specified item from the inventory."); - PM:BindCommand("/pickups", "debuggers", HandlePickups, "- Spawns random pickups around you"); - PM:BindCommand("/poof", "debuggers", HandlePoof, "- Nudges pickups close to you away from you"); - - PM:BindConsoleCommand("sched", HandleConsoleSchedule, "Tests the world scheduling"); - + -- Load the InfoReg shared library: + dofile(cPluginManager:GetPluginsPath() .. "/InfoReg.lua") + + -- Bind all the commands: + RegisterPluginInfoCommands(); + + -- Bind all the console commands: + RegisterPluginInfoConsoleCommands(); + Plugin:AddWebTab("Debuggers", HandleRequest_Debuggers) Plugin:AddWebTab("StressTest", HandleRequest_StressTest) @@ -1643,3 +1619,85 @@ end + +function HandleConsoleLoadChunk(a_Split) + -- Check params: + local numParams = #a_Split + if (numParams ~= 3) and (numParams ~= 4) then + return true, "Usage: " .. a_Split[1] .. " <ChunkX> <ChunkZ> [<WorldName>]" + end + + -- Get the chunk coords: + local chunkX = tonumber(a_Split[2]) + if (chunkX == nil) then + return true, "Not a number: '" .. a_Split[2] .. "'" + end + local chunkZ = tonumber(a_Split[3]) + if (chunkZ == nil) then + return true, "Not a number: '" .. a_Split[3] .. "'" + end + + -- Get the world: + local world + if (a_Split[4] == nil) then + world = cRoot:Get():GetDefaultWorld() + else + world = cRoot:Get():GetWorld(a_Split[4]) + if (world == nil) then + return true, "There's no world named '" .. a_Split[4] .. "'." + end + end + + -- Queue a ChunkStay for the chunk, log a message when the chunk is loaded: + world:ChunkStay({{chunkX, chunkZ}}, nil, + function() + LOG("Chunk [" .. chunkX .. ", " .. chunkZ .. "] is loaded") + end + ) + return true +end + + + + + +function HandleConsolePrepareChunk(a_Split) + -- Check params: + local numParams = #a_Split + if (numParams ~= 3) and (numParams ~= 4) then + return true, "Usage: " .. a_Split[1] .. " <ChunkX> <ChunkZ> [<WorldName>]" + end + + -- Get the chunk coords: + local chunkX = tonumber(a_Split[2]) + if (chunkX == nil) then + return true, "Not a number: '" .. a_Split[2] .. "'" + end + local chunkZ = tonumber(a_Split[3]) + if (chunkZ == nil) then + return true, "Not a number: '" .. a_Split[3] .. "'" + end + + -- Get the world: + local world + if (a_Split[4] == nil) then + world = cRoot:Get():GetDefaultWorld() + else + world = cRoot:Get():GetWorld(a_Split[4]) + if (world == nil) then + return true, "There's no world named '" .. a_Split[4] .. "'." + end + end + + -- Queue the chunk for preparing, log a message when prepared: + world:PrepareChunk(chunkX, chunkZ, + function(a_CBChunkX, a_CBChunkZ) + LOG("Chunk [" .. chunkX .. ", " .. chunkZ .. "] has been prepared") + end + ) + return true +end + + + + diff --git a/MCServer/Plugins/Debuggers/Info.lua b/MCServer/Plugins/Debuggers/Info.lua new file mode 100644 index 000000000..b96ef3de5 --- /dev/null +++ b/MCServer/Plugins/Debuggers/Info.lua @@ -0,0 +1,223 @@ + +-- Info.lua + +-- Implements the g_PluginInfo standard plugin description + + + + + +g_PluginInfo = +{ + Name = "Debuggers", + Version = "14", + Date = "2014-12-11", + Description = [[Contains code for testing and debugging the server. Should not be enabled on a production server!]], + + Commands = + { + ["/arr"] = + { + Permission = "debuggers", + Handler = HandleArrowCmd, + HelpString = "Creates an arrow going away from the player" + }, + ["/compo"] = + { + Permission = "debuggers", + Handler = HandleCompo, + HelpString = "Tests the cCompositeChat bindings" + }, + ["/cs"] = + { + Permission = "debuggers", + Handler = HandleChunkStay, + HelpString = "Tests the ChunkStay Lua integration for the specified chunk coords" + }, + ["/dash"] = + { + Permission = "debuggers", + Handler = HandleDashCmd, + HelpString = "Switches between fast and normal sprinting speed" + }, + ["/ench"] = + { + Permission = "debuggers", + Handler = HandleEnchCmd, + HelpString = "Provides an instant dummy enchantment window" + }, + ["/fast"] = + { + Permission = "debuggers", + Handler = HandleFastCmd, + HelpString = "Switches between fast and normal movement speed" + }, + ["/fb"] = + { + Permission = "debuggers", + Handler = HandleFireballCmd, + HelpString = "Creates a ghast fireball as if shot by the player" + }, + ["/ff"] = + { + Permission = "debuggers", + Handler = HandleFurnaceFuel, + HelpString = "Shows how long the currently held item would burn in a furnace" + }, + ["/fill"] = + { + Permission = "debuggers", + Handler = HandleFill, + HelpString = "Fills all block entities in current chunk with junk" + }, + ["/fl"] = + { + Permission = "debuggers", + Handler = HandleFoodLevelCmd, + HelpString = "Sets the food level to the given value" + }, + ["/fr"] = + { + Permission = "debuggers", + Handler = HandleFurnaceRecipe, + HelpString = "Shows the furnace recipe for the currently held item" + }, + ["/fs"] = + { + Permission = "debuggers", + Handler = HandleFoodStatsCmd, + HelpString = "Turns regular foodstats message on or off" + }, + ["/gc"] = + { + Permission = "debuggers", + Handler = HandleGCCmd, + HelpString = "Activates the Lua garbage collector" + }, + ["/hunger"] = + { + Permission = "debuggers", + Handler = HandleHungerCmd, + HelpString = "Lists the current hunger-related variables" + }, + ["/ke"] = + { + Permission = "debuggers", + Handler = HandleKillEntitiesCmd, + HelpString = "Kills all the loaded entities" + }, + ["/le"] = + { + Permission = "debuggers", + Handler = HandleListEntitiesCmd, + HelpString = "Shows a list of all the loaded entities" + }, + ["/nick"] = + { + Permission = "debuggers", + Handler = HandleNickCmd, + HelpString = "Gives you a custom name", + }, + ["/pickups"] = + { + Permission = "debuggers", + Handler = HandlePickups, + HelpString = "Spawns random pickups around you" + }, + ["/poison"] = + { + Permission = "debuggers", + Handler = HandlePoisonCmd, + HelpString = "Sets food-poisoning for 15 seconds" + }, + ["/poof"] = + { + Permission = "debuggers", + Handler = HandlePoof, + HelpString = "Nudges pickups close to you away from you" + }, + ["/rmitem"] = + { + Permission = "debuggers", + Handler = HandleRMItem, + HelpString = "Remove the specified item from the inventory." + }, + ["/sb"] = + { + Permission = "debuggers", + Handler = HandleSetBiome, + HelpString = "Sets the biome around you to the specified one" + }, + ["/sched"] = + { + Permission = "debuggers", + Handler = HandleSched, + HelpString = "Schedules a simple countdown using cWorld:ScheduleTask()" + }, + ["/spidey"] = + { + Permission = "debuggers", + Handler = HandleSpideyCmd, + HelpString = "Shoots a line of web blocks until it hits non-air" + }, + ["/starve"] = + { + Permission = "debuggers", + Handler = HandleStarveCmd, + HelpString = "Sets the food level to zero" + }, + ["/testwnd"] = + { + Permission = "debuggers", + Handler = HandleTestWndCmd, + HelpString = "Opens up a window using plugin API" + }, + ["/wesel"] = + { + Permission = "debuggers", + Handler = HandleWESel, + HelpString = "Expands the current WE selection by 1 block in X/Z" + }, + ["/wool"] = + { + Permission = "debuggers", + Handler = HandleWoolCmd, + HelpString = "Sets all your armor to blue wool" + }, + ["/xpa"] = + { + Permission = "debuggers", + Handler = HandleAddExperience, + HelpString = "Adds 200 experience to the player" + }, + ["/xpr"] = + { + Permission = "debuggers", + Handler = HandleRemoveXp, + HelpString = "Remove all xp" + }, + }, -- Commands + + ConsoleCommands = + { + ["sched"] = + { + Handler = HandleConsoleSchedule, + HelpString = "Tests the world scheduling", + }, + ["loadchunk"] = + { + Handler = HandleConsoleLoadChunk, + HelpString = "Loads the specified chunk into memory", + }, + ["preparechunk"] = + { + Handler = HandleConsolePrepareChunk, + HelpString = "Prepares the specified chunk completely (load / gen / light)", + } + }, -- ConsoleCommands +} -- g_PluginInfo + + + + diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 750f7c65a..56f2e73bc 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -1874,6 +1874,72 @@ static int tolua_cWorld_ChunkStay(lua_State * tolua_S) +static int tolua_cWorld_PrepareChunk(lua_State * tolua_S) +{ + /* Function signature: + World:PrepareChunk(ChunkX, ChunkZ, Callback) + */ + + // Check the param types: + cLuaState L(tolua_S); + if ( + !L.CheckParamUserType (1, "cWorld") || + !L.CheckParamNumber (2, 3) || + !L.CheckParamFunctionOrNil(4) + ) + { + return 0; + } + + // Read the params: + cWorld * world = nullptr; + int chunkX = 0, chunkZ = 0; + L.GetStackValues(1, world, chunkX, chunkZ); + if (world == nullptr) + { + LOGWARNING("World:PrepareChunk(): invalid world parameter"); + L.LogStackTrace(); + return 0; + } + + // Wrap the Lua callback inside a C++ callback class: + class cCallback: + public cChunkCoordCallback + { + public: + cCallback(lua_State * a_LuaState): + m_LuaState(a_LuaState), + m_Callback(m_LuaState, 4) + { + } + + // cChunkCoordCallback override: + virtual void Call(int a_CBChunkX, int a_CBChunkZ) override + { + if (m_Callback.IsValid()) + { + m_LuaState.Call(m_Callback, a_CBChunkX, a_CBChunkZ); + } + + // This is the last reference of this object, we must delete it so that it doesn't leak: + delete this; + } + + protected: + cLuaState m_LuaState; + cLuaState::cRef m_Callback; + }; + cCallback * callback = new cCallback(tolua_S); + + // Call the chunk preparation: + world->PrepareChunk(chunkX, chunkZ, callback); + return 0; +} + + + + + static int tolua_cPlayer_GetPermissions(lua_State * tolua_S) { // Function signature: cPlayer:GetPermissions() -> {permissions-array} @@ -3401,6 +3467,7 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_function(tolua_S, "GetBlockInfo", tolua_cWorld_GetBlockInfo); tolua_function(tolua_S, "GetBlockTypeMeta", tolua_cWorld_GetBlockTypeMeta); tolua_function(tolua_S, "GetSignLines", tolua_cWorld_GetSignLines); + tolua_function(tolua_S, "PrepareChunk", tolua_cWorld_PrepareChunk); tolua_function(tolua_S, "QueueTask", tolua_cWorld_QueueTask); tolua_function(tolua_S, "ScheduleTask", tolua_cWorld_ScheduleTask); tolua_function(tolua_S, "SetSignLines", tolua_cWorld_SetSignLines); diff --git a/src/ChunkDef.h b/src/ChunkDef.h index 260aace58..8f1d416ad 100644 --- a/src/ChunkDef.h +++ b/src/ChunkDef.h @@ -410,7 +410,7 @@ typedef std::list<cChunkCoordsWithBool> cChunkCoordsWithBoolList; -/// Interface class used as a callback for operations that involve chunk coords +/** Interface class used as a callback for operations that involve chunk coords */ class cChunkCoordCallback { public: @@ -424,6 +424,27 @@ public: +/** Provides storage for a set of chunk coords together with a callback. +Used for chunk queues that notify about processed items. */ +class cChunkCoordsWithCallback +{ +public: + cChunkCoordsWithCallback(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback): + m_ChunkX(a_ChunkX), + m_ChunkZ(a_ChunkZ), + m_Callback(a_Callback) + { + } + + int m_ChunkX; + int m_ChunkZ; + cChunkCoordCallback * m_Callback; +}; + + + + + /** Generic template that can store any kind of data together with a triplet of 3 coords*/ template <typename X> class cCoordWithData { diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index 8a8f17a1b..222a756b1 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -2349,6 +2349,103 @@ void cChunkMap::TouchChunk(int a_ChunkX, int a_ChunkZ) +void cChunkMap::PrepareChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback) +{ + cCSLock Lock(m_CSLayers); + cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, a_ChunkZ); + + // If the chunk is not prepared, queue it in the lighting thread, that will do all the needed processing: + if ((Chunk == nullptr) || !Chunk->IsValid() || !Chunk->IsLightValid()) + { + m_World->GetLightingThread().QueueChunk(a_ChunkX, a_ChunkZ, a_Callback); + return; + } + + // The chunk is present and lit, just call the callback: + if (a_Callback != nullptr) + { + a_Callback->Call(a_ChunkX, a_ChunkZ); + } +} + + + + + +bool cChunkMap::GenerateChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback) +{ + cCSLock Lock(m_CSLayers); + cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, a_ChunkZ); + if (Chunk == nullptr) + { + // Generic error while getting the chunk - out of memory? + return false; + } + + // Try loading the chunk: + if ((Chunk == nullptr) || (!Chunk->IsValid())) + { + class cPrepareLoadCallback: public cChunkCoordCallback + { + public: + cPrepareLoadCallback(cWorld & a_World, cChunkMap & a_ChunkMap, cChunkCoordCallback * a_Callback): + m_World(a_World), + m_ChunkMap(a_ChunkMap), + m_Callback(a_Callback) + { + } + + // cChunkCoordCallback override: + virtual void Call(int a_CBChunkX, int a_CBChunkZ) override + { + // The chunk has been loaded or an error occurred, check if it's valid now: + cChunkPtr CBChunk = m_ChunkMap.GetChunkNoLoad(a_CBChunkX, a_CBChunkZ); + + if (CBChunk == nullptr) + { + // An error occurred, but we promised to call the callback, so call it even when there's no real chunk data: + if (m_Callback != nullptr) + { + m_Callback->Call(a_CBChunkX, a_CBChunkZ); + } + return; + } + + // If the chunk is not valid, queue it in the generator: + if (!CBChunk->IsValid()) + { + m_World.GetGenerator().QueueGenerateChunk(a_CBChunkX, a_CBChunkZ, false, m_Callback); + return; + } + + // The chunk was loaded, call the callback: + if (m_Callback != nullptr) + { + m_Callback->Call(a_CBChunkX, a_CBChunkZ); + } + } + + protected: + cWorld & m_World; + cChunkMap & m_ChunkMap; + cChunkCoordCallback * m_Callback; + }; + m_World->GetStorage().QueueLoadChunk(a_ChunkX, a_ChunkZ, new cPrepareLoadCallback(*m_World, *this, a_Callback)); + return true; + } + + // The chunk is valid, just call the callback: + if (a_Callback != nullptr) + { + a_Callback->Call(a_ChunkX, a_ChunkZ); + } + return true; +} + + + + + void cChunkMap::ChunkLoadFailed(int a_ChunkX, int a_ChunkZ) { cCSLock Lock(m_CSLayers); diff --git a/src/ChunkMap.h b/src/ChunkMap.h index 51be5cb3d..6a858b06d 100644 --- a/src/ChunkMap.h +++ b/src/ChunkMap.h @@ -280,6 +280,20 @@ public: /** Touches the chunk, causing it to be loaded or generated */ void TouchChunk(int a_ChunkX, int a_ChunkZ); + + /** Queues the chunk for preparing - making sure that it's generated and lit. + The specified chunk is queued to be loaded or generated, and lit if needed. + The specified callback is called after the chunk has been prepared. If there's no preparation to do, only the callback is called. + It is legal to call without the callback. */ + void PrepareChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_CallAfter = nullptr); // Lua-accessible + + /** Queues the chunk for generating. + First attempts to load the chunk from the storage. If that fails, queues the chunk for generating. + The specified callback is called after the chunk has been loaded / generated. + It is legal to call without the callback. + Returns true if successful, false if not (possibly an out-of-memory error). + If the return value is true, the callback was / will be called. */ + bool GenerateChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_CallAfter = nullptr); // Lua-accessible /** Marks the chunk as failed-to-load */ void ChunkLoadFailed(int a_ChunkX, int a_ChunkZ); diff --git a/src/Generating/ChunkGenerator.cpp b/src/Generating/ChunkGenerator.cpp index d2e7b47b4..854aac60d 100644 --- a/src/Generating/ChunkGenerator.cpp +++ b/src/Generating/ChunkGenerator.cpp @@ -110,29 +110,19 @@ void cChunkGenerator::Stop(void) -void cChunkGenerator::QueueGenerateChunk(int a_ChunkX, int a_ChunkZ, bool a_ForceGenerate) +void cChunkGenerator::QueueGenerateChunk(int a_ChunkX, int a_ChunkZ, bool a_ForceGenerate, cChunkCoordCallback * a_Callback) { ASSERT(m_ChunkSink->IsChunkQueued(a_ChunkX, a_ChunkZ)); { cCSLock Lock(m_CS); - // Check if it is already in the queue: - for (cChunkCoordsWithBoolList::iterator itr = m_Queue.begin(); itr != m_Queue.end(); ++itr) - { - if ((itr->m_ChunkX == a_ChunkX) && (itr->m_ChunkZ == a_ChunkZ)) - { - // Already in the queue, bail out - return; - } - } // for itr - m_Queue[] - // Add to queue, issue a warning if too many: if (m_Queue.size() >= QUEUE_WARNING_LIMIT) { LOGWARN("WARNING: Adding chunk [%i, %i] to generation queue; Queue is too big! (" SIZE_T_FMT ")", a_ChunkX, a_ChunkZ, m_Queue.size()); } - m_Queue.push_back(cChunkCoordsWithBool(a_ChunkX, a_ChunkZ, a_ForceGenerate)); + m_Queue.push_back(cQueueItem{a_ChunkX, a_ChunkZ, a_ForceGenerate, a_Callback}); } m_Event.Set(); @@ -242,9 +232,9 @@ void cChunkGenerator::Execute(void) continue; } - cChunkCoordsWithBool coords = m_Queue.front(); // Get next coord from queue + cQueueItem item = m_Queue.front(); // Get next chunk from the queue bool SkipEnabled = (m_Queue.size() > QUEUE_SKIP_LIMIT); - m_Queue.erase(m_Queue.begin()); // Remove coordinate from queue + m_Queue.erase(m_Queue.begin()); // Remove the item from the queue Lock.Unlock(); // Unlock ASAP m_evtRemoved.Set(); @@ -258,22 +248,35 @@ void cChunkGenerator::Execute(void) LastReportTick = clock(); } - if (!coords.m_ForceGenerate && m_ChunkSink->IsChunkValid(coords.m_ChunkX, coords.m_ChunkZ)) + // Skip the chunk if it's already generated and regeneration is not forced: + if (!item.m_ForceGenerate && m_ChunkSink->IsChunkValid(item.m_ChunkX, item.m_ChunkZ)) { - LOGD("Chunk [%d, %d] already generated, skipping generation", coords.m_ChunkX, coords.m_ChunkZ); - // Already generated, ignore request + LOGD("Chunk [%d, %d] already generated, skipping generation", item.m_ChunkX, item.m_ChunkZ); + if (item.m_Callback != nullptr) + { + item.m_Callback->Call(item.m_ChunkX, item.m_ChunkZ); + } continue; } - if (SkipEnabled && !m_ChunkSink->HasChunkAnyClients(coords.m_ChunkX, coords.m_ChunkZ)) + // Skip the chunk if the generator is overloaded: + if (SkipEnabled && !m_ChunkSink->HasChunkAnyClients(item.m_ChunkX, item.m_ChunkZ)) { - LOGWARNING("Chunk generator overloaded, skipping chunk [%d, %d]", coords.m_ChunkX, coords.m_ChunkZ); + LOGWARNING("Chunk generator overloaded, skipping chunk [%d, %d]", item.m_ChunkX, item.m_ChunkZ); + if (item.m_Callback != nullptr) + { + item.m_Callback->Call(item.m_ChunkX, item.m_ChunkZ); + } continue; } - LOGD("Generating chunk [%d, %d]", coords.m_ChunkX, coords.m_ChunkZ); - DoGenerate(coords.m_ChunkX, coords.m_ChunkZ); - + // Generate the chunk: + LOGD("Generating chunk [%d, %d]", item.m_ChunkX, item.m_ChunkZ); + DoGenerate(item.m_ChunkX, item.m_ChunkZ); + if (item.m_Callback != nullptr) + { + item.m_Callback->Call(item.m_ChunkX, item.m_ChunkZ); + } NumChunksGenerated++; } // while (!bStop) } diff --git a/src/Generating/ChunkGenerator.h b/src/Generating/ChunkGenerator.h index 190d9e616..4af486ae1 100644 --- a/src/Generating/ChunkGenerator.h +++ b/src/Generating/ChunkGenerator.h @@ -119,8 +119,12 @@ public: bool Start(cPluginInterface & a_PluginInterface, cChunkSink & a_ChunkSink, cIniFile & a_IniFile); void Stop(void); - /// Queues the chunk for generation; removes duplicate requests - void QueueGenerateChunk(int a_ChunkX, int a_ChunkZ, bool a_ForceGenerate); + /** Queues the chunk for generation + If a-ForceGenerate is set, the chunk is regenerated even if the data is already present in the chunksink. + a_Callback is called after the chunk is generated. If the chunk was already present, the callback is still called, even if not regenerating. + It is legal to set the callback to nullptr, no callback is called then. + If the generator becomes overloaded and skips this chunk, the callback is still called. */ + void QueueGenerateChunk(int a_ChunkX, int a_ChunkZ, bool a_ForceGenerate, cChunkCoordCallback * a_Callback = nullptr); /// Generates the biomes for the specified chunk (directly, not in a separate thread). Used by the world loader if biomes failed loading. void GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap); @@ -131,22 +135,46 @@ public: int GetSeed(void) const { return m_Seed; } - /// Returns the biome at the specified coords. Used by ChunkMap if an invalid chunk is queried for biome + /** Returns the biome at the specified coords. Used by ChunkMap if an invalid chunk is queried for biome */ EMCSBiome GetBiomeAt(int a_BlockX, int a_BlockZ); - /// Reads a block type from the ini file; returns the blocktype on success, emits a warning and returns a_Default's representation on failure. + /** Reads a block type from the ini file; returns the blocktype on success, emits a warning and returns a_Default's representation on failure. */ static BLOCKTYPE GetIniBlock(cIniFile & a_IniFile, const AString & a_SectionName, const AString & a_ValueName, const AString & a_Default); private: + struct cQueueItem + { + /** The chunk coords */ + int m_ChunkX, m_ChunkZ; + + /** Force the regeneration of an already existing chunk */ + bool m_ForceGenerate; + + /** Callback to call after generating.*/ + cChunkCoordCallback * m_Callback; + }; + + typedef std::list<cQueueItem> cGenQueue; + + + /** Seed used for the generator. */ int m_Seed; - cCriticalSection m_CS; - cChunkCoordsWithBoolList m_Queue; - cEvent m_Event; ///< Set when an item is added to the queue or the thread should terminate - cEvent m_evtRemoved; ///< Set when an item is removed from the queue + /** CS protecting access to the queue. */ + cCriticalSection m_CS; + + /** Queue of the chunks to be generated. Protected against multithreaded access by m_CS. */ + cGenQueue m_Queue; + + /** Set when an item is added to the queue or the thread should terminate. */ + cEvent m_Event; + + /** Set when an item is removed from the queue. */ + cEvent m_evtRemoved; - cGenerator * m_Generator; ///< The actual generator engine used to generate chunks + /** The actual generator engine used to generate chunks. */ + cGenerator * m_Generator; /** The plugin interface that may modify the generated chunks */ cPluginInterface * m_PluginInterface; @@ -158,6 +186,7 @@ private: // cIsThread override: virtual void Execute(void) override; + /** Generates the specified chunk and sets it into the chunksink. */ void DoGenerate(int a_ChunkX, int a_ChunkZ); }; diff --git a/src/LightingThread.cpp b/src/LightingThread.cpp index ae5e746e7..ced95d4e1 100644 --- a/src/LightingThread.cpp +++ b/src/LightingThread.cpp @@ -226,6 +226,8 @@ void cLightingThread::Execute(void) } // CSLock(m_CS) LightChunk(*Item); + Item->Disable(); + delete Item; } } @@ -236,6 +238,16 @@ void cLightingThread::Execute(void) void cLightingThread::LightChunk(cLightingChunkStay & a_Item) { + // If the chunk is already lit, skip it: + if (m_World->IsChunkLighted(a_Item.m_ChunkX, a_Item.m_ChunkZ)) + { + if (a_Item.m_CallbackAfter != nullptr) + { + a_Item.m_CallbackAfter->Call(a_Item.m_ChunkX, a_Item.m_ChunkZ); + } + return; + } + cChunkDef::BlockNibbles BlockLight, SkyLight; ReadChunks(a_Item.m_ChunkX, a_Item.m_ChunkZ); @@ -314,8 +326,6 @@ void cLightingThread::LightChunk(cLightingChunkStay & a_Item) { a_Item.m_CallbackAfter->Call(a_Item.m_ChunkX, a_Item.m_ChunkZ); } - a_Item.Disable(); - delete &a_Item; } diff --git a/src/OSSupport/Queue.h b/src/OSSupport/Queue.h index 8d096fe29..82f221453 100644 --- a/src/OSSupport/Queue.h +++ b/src/OSSupport/Queue.h @@ -163,6 +163,29 @@ public: return false; } + + /** Removes all items for which the predicate returns true. */ + template <class Predicate> + void RemoveIf(Predicate a_Predicate) + { + cCSLock Lock(m_CS); + for (auto itr = m_Contents.begin(); itr != m_Contents.end();) + { + if (a_Predicate(*itr)) + { + auto itr2 = itr; + ++itr2; + m_Contents.erase(itr); + m_evtRemoved.Set(); + itr = itr2; + } + else + { + ++itr; + } + } // for itr - m_Contents[] + } + private: /// The contents of the queue QueueType m_Contents; diff --git a/src/Root.cpp b/src/Root.cpp index 29daaedcc..9f8ffeeff 100644 --- a/src/Root.cpp +++ b/src/Root.cpp @@ -196,7 +196,7 @@ void cRoot::Start(void) } #endif - LOG("Startup complete, took %ld ms!", std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - BeginTime).count()); + LOG("Startup complete, took %ld ms!", static_cast<long int>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - BeginTime).count())); #ifdef _WIN32 EnableMenuItem(hmenu, SC_CLOSE, MF_ENABLED); // Re-enable close button #endif diff --git a/src/World.cpp b/src/World.cpp index db19649f3..1bee6e344 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -115,7 +115,7 @@ public: { int chunkX, chunkZ; DecodeChunkCoords(i, chunkX, chunkZ); - m_World.GetLightingThread().QueueChunk(chunkX, chunkZ, this); + m_World.PrepareChunk(chunkX, chunkZ, this); } // for i // Wait for the lighting thread to prepare everything. Event is set in the Call() callback: @@ -2907,6 +2907,15 @@ void cWorld::TouchChunk(int a_ChunkX, int a_ChunkZ) +void cWorld::PrepareChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_CallAfter) +{ + m_ChunkMap->PrepareChunk(a_ChunkX, a_ChunkZ, a_CallAfter); +} + + + + + void cWorld::ChunkLoadFailed(int a_ChunkX, int a_ChunkZ) { m_ChunkMap->ChunkLoadFailed(a_ChunkX, a_ChunkZ); @@ -3017,7 +3026,7 @@ void cWorld::RegenerateChunk(int a_ChunkX, int a_ChunkZ) void cWorld::GenerateChunk(int a_ChunkX, int a_ChunkZ) { - m_ChunkMap->TouchChunk(a_ChunkX, a_ChunkZ); + m_ChunkMap->GenerateChunk(a_ChunkX, a_ChunkZ); } diff --git a/src/World.h b/src/World.h index 2f51d60a1..6f28ba534 100644 --- a/src/World.h +++ b/src/World.h @@ -375,6 +375,12 @@ public: /** Touches the chunk, causing it to be loaded or generated */ void TouchChunk(int a_ChunkX, int a_ChunkZ); + + /** Queues the chunk for preparing - making sure that it's generated and lit. + The specified chunk is queued to be loaded or generated, and lit if needed. + The specified callback is called after the chunk has been prepared. If there's no preparation to do, only the callback is called. + It is legal to call with no callback. */ + void PrepareChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_CallAfter = nullptr); /** Marks the chunk as failed-to-load: */ void ChunkLoadFailed(int a_ChunkX, int a_ChunkZ); diff --git a/src/WorldStorage/WorldStorage.cpp b/src/WorldStorage/WorldStorage.cpp index 31318ee67..c7a295175 100644 --- a/src/WorldStorage/WorldStorage.cpp +++ b/src/WorldStorage/WorldStorage.cpp @@ -140,11 +140,11 @@ size_t cWorldStorage::GetSaveQueueLength(void) -void cWorldStorage::QueueLoadChunk(int a_ChunkX, int a_ChunkZ) +void cWorldStorage::QueueLoadChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback) { ASSERT(m_World->IsChunkQueued(a_ChunkX, a_ChunkZ)); - m_LoadQueue.EnqueueItem(cChunkCoords(a_ChunkX, a_ChunkZ)); + m_LoadQueue.EnqueueItem(cChunkCoordsWithCallback(a_ChunkX, a_ChunkZ, a_Callback)); m_Event.Set(); } @@ -152,11 +152,11 @@ void cWorldStorage::QueueLoadChunk(int a_ChunkX, int a_ChunkZ) -void cWorldStorage::QueueSaveChunk(int a_ChunkX, int a_ChunkZ) +void cWorldStorage::QueueSaveChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback) { ASSERT(m_World->IsChunkValid(a_ChunkX, a_ChunkZ)); - m_SaveQueue.EnqueueItemIfNotPresent(cChunkCoords(a_ChunkX, a_ChunkZ)); + m_SaveQueue.EnqueueItem(cChunkCoordsWithCallback(a_ChunkX, a_ChunkZ, a_Callback)); m_Event.Set(); } @@ -166,7 +166,11 @@ void cWorldStorage::QueueSaveChunk(int a_ChunkX, int a_ChunkZ) void cWorldStorage::UnqueueLoad(int a_ChunkX, int a_ChunkZ) { - m_LoadQueue.Remove(cChunkCoords(a_ChunkX, a_ChunkZ)); + m_LoadQueue.RemoveIf([=](cChunkCoordsWithCallback & a_Item) + { + return (a_Item.m_ChunkX == a_ChunkX) && (a_Item.m_ChunkZ == a_ChunkZ); + } + ); } @@ -175,7 +179,11 @@ void cWorldStorage::UnqueueLoad(int a_ChunkX, int a_ChunkZ) void cWorldStorage::UnqueueSave(const cChunkCoords & a_Chunk) { - m_SaveQueue.Remove(a_Chunk); + m_SaveQueue.RemoveIf([=](cChunkCoordsWithCallback & a_Item) + { + return (a_Item.m_ChunkX == a_Chunk.m_ChunkX) && (a_Item.m_ChunkZ == a_Chunk.m_ChunkZ); + } + ); } @@ -244,14 +252,23 @@ void cWorldStorage::Execute(void) bool cWorldStorage::LoadOneChunk(void) { - cChunkCoords ToLoad(0, 0); + // Dequeue an item, bail out if there's none left: + cChunkCoordsWithCallback ToLoad(0, 0, nullptr); bool ShouldLoad = m_LoadQueue.TryDequeueItem(ToLoad); + if (!ShouldLoad) + { + return false; + } - if (ShouldLoad) + // Load the chunk: + bool res = LoadChunk(ToLoad.m_ChunkX, ToLoad.m_ChunkZ); + + // Call the callback, if specified: + if (ToLoad.m_Callback != nullptr) { - return LoadChunk(ToLoad.m_ChunkX, ToLoad.m_ChunkZ); + ToLoad.m_Callback->Call(ToLoad.m_ChunkX, ToLoad.m_ChunkZ); } - return false; + return res; } @@ -260,17 +277,30 @@ bool cWorldStorage::LoadOneChunk(void) bool cWorldStorage::SaveOneChunk(void) { - cChunkCoords ToSave(0, 0); + // Dequeue one chunk to save: + cChunkCoordsWithCallback ToSave(0, 0, nullptr); bool ShouldSave = m_SaveQueue.TryDequeueItem(ToSave); - if (ShouldSave && m_World->IsChunkValid(ToSave.m_ChunkX, ToSave.m_ChunkZ)) + if (!ShouldSave) + { + return false; + } + + // Save the chunk, if it's valid: + if (m_World->IsChunkValid(ToSave.m_ChunkX, ToSave.m_ChunkZ)) { m_World->MarkChunkSaving(ToSave.m_ChunkX, ToSave.m_ChunkZ); - if (m_SaveSchema->SaveChunk(ToSave)) + if (m_SaveSchema->SaveChunk(cChunkCoords(ToSave.m_ChunkX, ToSave.m_ChunkZ))) { m_World->MarkChunkSaved(ToSave.m_ChunkX, ToSave.m_ChunkZ); } } - return ShouldSave; + + // Call the callback, if specified: + if (ToSave.m_Callback != nullptr) + { + ToSave.m_Callback->Call(ToSave.m_ChunkX, ToSave.m_ChunkZ); + } + return true; } diff --git a/src/WorldStorage/WorldStorage.h b/src/WorldStorage/WorldStorage.h index fc7e9f84c..e9ba2a8e2 100644 --- a/src/WorldStorage/WorldStorage.h +++ b/src/WorldStorage/WorldStorage.h @@ -25,7 +25,7 @@ // fwd: class cWorld; -typedef cQueue<cChunkCoords> cChunkCoordsQueue; +typedef cQueue<cChunkCoordsWithCallback> cChunkCoordsQueue; @@ -64,8 +64,8 @@ public: cWorldStorage(void); ~cWorldStorage(); - void QueueLoadChunk(int a_ChunkX, int a_ChunkZ); - void QueueSaveChunk(int a_ChunkX, int a_ChunkZ); + void QueueLoadChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback = nullptr); + void QueueSaveChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback = nullptr); void UnqueueLoad(int a_ChunkX, int a_ChunkZ); void UnqueueSave(const cChunkCoords & a_Chunk); |