diff options
| author | daoge_cmd <3523206925@qq.com> | 2026-03-01 12:16:08 +0800 |
|---|---|---|
| committer | daoge_cmd <3523206925@qq.com> | 2026-03-01 12:16:08 +0800 |
| commit | b691c43c44ff180d10e7d4a9afc83b98551ff586 (patch) | |
| tree | 3e9849222cbc6ba49f2f1fc6e5fe7179632c7390 /Minecraft.Client/ServerChunkCache.cpp | |
| parent | def8cb415354ac390b7e89052a50605285f1aca9 (diff) | |
Initial commit
Diffstat (limited to 'Minecraft.Client/ServerChunkCache.cpp')
| -rw-r--r-- | Minecraft.Client/ServerChunkCache.cpp | 968 |
1 files changed, 968 insertions, 0 deletions
diff --git a/Minecraft.Client/ServerChunkCache.cpp b/Minecraft.Client/ServerChunkCache.cpp new file mode 100644 index 00000000..1fe1a86f --- /dev/null +++ b/Minecraft.Client/ServerChunkCache.cpp @@ -0,0 +1,968 @@ +#include "stdafx.h" +#include "ServerChunkCache.h" +#include "ServerLevel.h" +#include "MinecraftServer.h" +#include "..\Minecraft.World\net.minecraft.world.level.h" +#include "..\Minecraft.World\net.minecraft.world.level.dimension.h" +#include "..\Minecraft.World\net.minecraft.world.level.storage.h" +#include "..\Minecraft.World\net.minecraft.world.level.chunk.h" +#include "..\Minecraft.World\Pos.h" +#include "..\Minecraft.World\ProgressListener.h" +#include "..\Minecraft.World\ThreadName.h" +#include "..\Minecraft.World\compression.h" +#include "..\Minecraft.World\OldChunkStorage.h" + +ServerChunkCache::ServerChunkCache(ServerLevel *level, ChunkStorage *storage, ChunkSource *source) +{ + XZSIZE = source->m_XZSize; // 4J Added + XZOFFSET = XZSIZE/2; // 4J Added + + autoCreate = false; // 4J added + + emptyChunk = new EmptyLevelChunk(level, byteArray( Level::CHUNK_TILE_COUNT ), 0, 0); + + this->level = level; + this->storage = storage; + this->source = source; + this->m_XZSize = source->m_XZSize; + + this->cache = new LevelChunk *[XZSIZE * XZSIZE]; + memset(this->cache, 0, XZSIZE * XZSIZE * sizeof(LevelChunk *)); + +#ifdef _LARGE_WORLDS + m_unloadedCache = new LevelChunk *[XZSIZE * XZSIZE]; + memset(m_unloadedCache, 0, XZSIZE * XZSIZE * sizeof(LevelChunk *)); +#endif + + InitializeCriticalSectionAndSpinCount(&m_csLoadCreate,4000); +} + +// 4J-PB added +ServerChunkCache::~ServerChunkCache() +{ + delete emptyChunk; + delete cache; + delete source; + +#ifdef _LARGE_WORLDS + for(unsigned int i = 0; i < XZSIZE * XZSIZE; ++i) + { + delete m_unloadedCache[i]; + } + delete m_unloadedCache; +#endif + + AUTO_VAR(itEnd, m_loadedChunkList.end()); + for (AUTO_VAR(it, m_loadedChunkList.begin()); it != itEnd; it++) + delete *it; + DeleteCriticalSection(&m_csLoadCreate); +} + +bool ServerChunkCache::hasChunk(int x, int z) +{ + int ix = x + XZOFFSET; + int iz = z + XZOFFSET; + // Check we're in range of the stored level + // 4J Stu - Request for chunks outside the range always return an emptyChunk, so just return true here to say we have it + // If we return false entities less than 2 chunks from the edge do not tick properly due to them requiring a certain radius + // of chunks around them when they tick + if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return true; + if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return true; + int idx = ix * XZSIZE + iz; + LevelChunk *lc = cache[idx]; + if( lc == NULL ) return false; + return true; +} + +vector<LevelChunk *> *ServerChunkCache::getLoadedChunkList() +{ + return &m_loadedChunkList; +} + +void ServerChunkCache::drop(int x, int z) +{ + // 4J - we're not dropping things anymore now that we have a fixed sized cache +#ifdef _LARGE_WORLDS + + bool canDrop = false; +// if (level->dimension->mayRespawn()) +// { +// Pos *spawnPos = level->getSharedSpawnPos(); +// int xd = x * 16 + 8 - spawnPos->x; +// int zd = z * 16 + 8 - spawnPos->z; +// delete spawnPos; +// int r = 128; +// if (xd < -r || xd > r || zd < -r || zd > r) +// { +// canDrop = true; +//} +// } +// else + { + canDrop = true; + } + if(canDrop) + { + int ix = x + XZOFFSET; + int iz = z + XZOFFSET; + // Check we're in range of the stored level + if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return; + if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return; + int idx = ix * XZSIZE + iz; + LevelChunk *chunk = cache[idx]; + + if(chunk) + { + m_toDrop.push_back(chunk); + } + } +#endif +} + +void ServerChunkCache::dropAll() +{ +#ifdef _LARGE_WORLDS + for (LevelChunk *chunk : m_loadedChunkList) + { + drop(chunk->x, chunk->z); +} +#endif +} + +// 4J - this is the original (and virtual) interface to create +LevelChunk *ServerChunkCache::create(int x, int z) +{ + return create(x, z, false); +} + +LevelChunk *ServerChunkCache::create(int x, int z, bool asyncPostProcess) // 4J - added extra parameter +{ + int ix = x + XZOFFSET; + int iz = z + XZOFFSET; + // Check we're in range of the stored level + if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return emptyChunk; + if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return emptyChunk; + int idx = ix * XZSIZE + iz; + + LevelChunk *chunk = cache[idx]; + LevelChunk *lastChunk = chunk; + + if( ( chunk == NULL ) || ( chunk->x != x ) || ( chunk->z != z ) ) + { + EnterCriticalSection(&m_csLoadCreate); + chunk = load(x, z); + if (chunk == NULL) + { + if (source == NULL) + { + chunk = emptyChunk; + } + else + { + chunk = source->getChunk(x, z); + } + } + if (chunk != NULL) + { + chunk->load(); + } + + LeaveCriticalSection(&m_csLoadCreate); + +#if ( defined _WIN64 || defined __LP64__ ) + if( InterlockedCompareExchangeRelease64((LONG64 *)&cache[idx],(LONG64)chunk,(LONG64)lastChunk) == (LONG64)lastChunk ) +#else + if( InterlockedCompareExchangeRelease((LONG *)&cache[idx],(LONG)chunk,(LONG)lastChunk) == (LONG)lastChunk ) +#endif // _DURANGO + { + // Successfully updated the cache + EnterCriticalSection(&m_csLoadCreate); + // 4J - added - this will run a recalcHeightmap if source is a randomlevelsource, which has been split out from source::getChunk so that + // we are doing it after the chunk has been added to the cache - otherwise a lot of the lighting fails as lights aren't added if the chunk + // they are in fail ServerChunkCache::hasChunk. + source->lightChunk(chunk); + + updatePostProcessFlags( x, z ); + + m_loadedChunkList.push_back(chunk); + + // 4J - If post-processing is to be async, then let the server know about requests rather than processing directly here. Note that + // these hasChunk() checks appear to be incorrect - the chunks checked by these map out as: + // + // 1. 2. 3. 4. + // oxx xxo ooo ooo + // oPx Poo oox xoo + // ooo ooo oPx Pxo + // + // where P marks the chunk that is being considered for postprocessing, and x marks chunks that needs to be loaded. It would seem that the + // chunks which need to be loaded should stay the same relative to the chunk to be processed, but the hasChunk checks in 3 cases check again + // the chunk which is to be processed itself rather than (what I presume to be) the correct position. + // Don't think we should change in case it alters level creation. + + if( asyncPostProcess ) + { + // 4J Stu - TODO This should also be calling the same code as chunk->checkPostProcess, but then we cannot guarantee we are in the server add the post-process request + if ( ( (chunk->terrainPopulated & LevelChunk::sTerrainPopulatedFromHere) == 0) && hasChunk(x + 1, z + 1) && hasChunk(x, z + 1) && hasChunk(x + 1, z)) MinecraftServer::getInstance()->addPostProcessRequest(this, x, z); + if (hasChunk(x - 1, z) && ((getChunk(x - 1, z)->terrainPopulated & LevelChunk::sTerrainPopulatedFromHere ) == 0 ) && hasChunk(x - 1, z + 1) && hasChunk(x, z + 1) && hasChunk(x - 1, z)) MinecraftServer::getInstance()->addPostProcessRequest(this, x - 1, z); + if (hasChunk(x, z - 1) && ((getChunk(x, z - 1)->terrainPopulated & LevelChunk::sTerrainPopulatedFromHere ) == 0 ) && hasChunk(x + 1, z - 1) && hasChunk(x, z - 1) && hasChunk(x + 1, z)) MinecraftServer::getInstance()->addPostProcessRequest(this, x, z - 1); + if (hasChunk(x - 1, z - 1) && ((getChunk(x - 1, z - 1)->terrainPopulated & LevelChunk::sTerrainPopulatedFromHere ) == 0 ) && hasChunk(x - 1, z - 1) && hasChunk(x, z - 1) && hasChunk(x - 1, z)) MinecraftServer::getInstance()->addPostProcessRequest(this, x - 1, z - 1); + } + else + { + chunk->checkPostProcess(this, this, x, z); + } + + // 4J - Now try and fix up any chests that were saved pre-1.8.2. We don't want to do this to this particular chunk as we don't know if all its neighbours are loaded yet, and we + // need the neighbours to be able to work out the facing direction for the chests. Therefore process any neighbouring chunk that loading this chunk would be the last neighbour for. + // 5 cases illustrated below, where P is the chunk to be processed, T is this chunk, and x are other chunks that need to be checked for being present + + // 1. 2. 3. 4. 5. + // ooooo ooxoo ooooo ooooo ooooo + // oxooo oxPxo oooxo ooooo ooxoo + // xPToo ooToo ooTPx ooToo oxPxo (in 5th case P and T are same) + // oxooo ooooo oooxo oxPxo ooxoo + // ooooo ooooo ooooo ooxoo ooooo + + if( hasChunk( x - 1, z ) && hasChunk( x - 2, z ) && hasChunk( x - 1, z + 1 ) && hasChunk( x - 1, z - 1 ) ) chunk->checkChests( this, x - 1, z ); + if( hasChunk( x, z + 1) && hasChunk( x , z + 2 ) && hasChunk( x - 1, z + 1 ) && hasChunk( x + 1, z + 1 ) ) chunk->checkChests( this, x, z + 1); + if( hasChunk( x + 1, z ) && hasChunk( x + 2, z ) && hasChunk( x + 1, z + 1 ) && hasChunk( x + 1, z - 1 ) ) chunk->checkChests( this, x + 1, z ); + if( hasChunk( x, z - 1) && hasChunk( x , z - 2 ) && hasChunk( x - 1, z - 1 ) && hasChunk( x + 1, z - 1 ) ) chunk->checkChests( this, x, z - 1); + if( hasChunk( x - 1, z ) && hasChunk( x + 1, z ) && hasChunk ( x, z - 1 ) && hasChunk( x, z + 1 ) ) chunk->checkChests( this, x, z ); + + LeaveCriticalSection(&m_csLoadCreate); + } + else + { + // Something else must have updated the cache. Return that chunk and discard this one + chunk->unload(true); + delete chunk; + return cache[idx]; + } + } + +#ifdef __PS3__ + Sleep(1); +#endif // __PS3__ + return chunk; + +} + +// 4J Stu - Split out this function so that we get a chunk without loading entities +// This is used when sharing server chunk data on the main thread +LevelChunk *ServerChunkCache::getChunk(int x, int z) +{ + int ix = x + XZOFFSET; + int iz = z + XZOFFSET; + // Check we're in range of the stored level + if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return emptyChunk; + if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return emptyChunk; + int idx = ix * XZSIZE + iz; + + LevelChunk *lc = cache[idx]; + if( lc ) + { + return lc; + } + + if( level->isFindingSpawn || autoCreate ) + { + return create(x, z); + } + + return emptyChunk; +} + +#ifdef _LARGE_WORLDS +// 4J added - this special variation on getChunk also checks the unloaded chunk cache. It is called on a host machine from the client-side level when: +// (1) Trying to determine whether the client blocks and data are the same as those on the server, so we can start sharing them +// (2) Trying to resync the lighting data from the server to the client +// As such it is really important that we don't return emptyChunk in these situations, when we actually still have the block/data/lighting in the unloaded cache +LevelChunk *ServerChunkCache::getChunkLoadedOrUnloaded(int x, int z) +{ + int ix = x + XZOFFSET; + int iz = z + XZOFFSET; + // Check we're in range of the stored level + if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return emptyChunk; + if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return emptyChunk; + int idx = ix * XZSIZE + iz; + + LevelChunk *lc = cache[idx]; + if( lc ) + { + return lc; + } + + lc = m_unloadedCache[idx]; + if( lc ) + { + return lc; +} + + if( level->isFindingSpawn || autoCreate ) + { + return create(x, z); + } + + return emptyChunk; +} +#endif + +// 4J Added // +#ifdef _LARGE_WORLDS +void ServerChunkCache::dontDrop(int x, int z) +{ + LevelChunk *chunk = getChunk(x,z); + m_toDrop.erase(std::remove(m_toDrop.begin(), m_toDrop.end(), chunk), m_toDrop.end()); +} +#endif + +LevelChunk *ServerChunkCache::load(int x, int z) +{ + if (storage == NULL) return NULL; + + LevelChunk *levelChunk = NULL; + +#ifdef _LARGE_WORLDS + int ix = x + XZOFFSET; + int iz = z + XZOFFSET; + int idx = ix * XZSIZE + iz; + levelChunk = m_unloadedCache[idx]; + m_unloadedCache[idx] = NULL; + if(levelChunk == NULL) +#endif + { + levelChunk = storage->load(level, x, z); + } + if (levelChunk != NULL) + { + levelChunk->lastSaveTime = level->getTime(); + } + return levelChunk; +} + +void ServerChunkCache::saveEntities(LevelChunk *levelChunk) +{ + if (storage == NULL) return; + + storage->saveEntities(level, levelChunk); +} + +void ServerChunkCache::save(LevelChunk *levelChunk) +{ + if (storage == NULL) return; + + levelChunk->lastSaveTime = level->getTime(); + storage->save(level, levelChunk); +} + +// 4J added +void ServerChunkCache::updatePostProcessFlag(short flag, int x, int z, int xo, int zo, LevelChunk *lc) +{ + if( hasChunk( x + xo, z + zo ) ) + { + LevelChunk *lc2 = getChunk(x + xo, z + zo); + if( lc2 != emptyChunk ) // Will only be empty chunk of this is the edge (we've already checked hasChunk so won't just be a missing chunk) + { + if( lc2->terrainPopulated & LevelChunk::sTerrainPopulatedFromHere ) + { + lc->terrainPopulated |= flag; + } + } + else + { + // The edge - always consider as post-processed + lc->terrainPopulated |= flag; + } + } +} + +// 4J added - normally we try and set these flags when a chunk is post-processed. However, when setting in a north or easterly direction the +// affected chunks might not themselves exist, so we need to check the flags also when creating new chunks. +void ServerChunkCache::updatePostProcessFlags(int x, int z) +{ + LevelChunk *lc = getChunk(x, z); + if( lc != emptyChunk ) + { + // First check if any of our neighbours are post-processed, that should affect OUR flags + updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromS, x, z, 0, -1, lc ); + updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromSW, x, z, -1, -1, lc ); + updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromW, x, z, -1, 0, lc ); + updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromNW, x, z, -1, 1, lc ); + updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromN, x, z, 0, 1, lc ); + updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromNE, x, z, 1, 1, lc ); + updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromE, x, z, 1, 0, lc ); + updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromSE, x, z, 1, -1, lc ); + + // Then, if WE are post-processed, check that our neighbour's flags are also set + if( lc->terrainPopulated & LevelChunk::sTerrainPopulatedFromHere ) + { + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromW, x + 1, z + 0 ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromSW, x + 1, z + 1 ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromS, x + 0, z + 1 ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromSE, x - 1, z + 1 ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromE, x - 1, z + 0 ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromNE, x - 1, z - 1 ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromN, x + 0, z - 1 ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromNW, x + 1, z - 1 ); + } + } + + flagPostProcessComplete(0, x, z); +} + +// 4J added - add a flag to a chunk to say that one of its neighbours has completed post-processing. If this completes the set of +// chunks which can actually set tile tiles in this chunk (sTerrainPopulatedAllAffecting), then this is a good point to compress this chunk. +// If this completes the set of all 8 neighbouring chunks that have been fully post-processed, then this is a good time to fix up some +// lighting things that need all the tiles to be in place in the region into which they might propagate. +void ServerChunkCache::flagPostProcessComplete(short flag, int x, int z) +{ + // Set any extra flags for this chunk to indicate which neighbours have now had their post-processing done + if( !hasChunk(x, z) ) return; + + LevelChunk *lc = level->getChunk( x, z ); + if( lc == emptyChunk ) return; + + lc->terrainPopulated |= flag; + + // Are all neighbouring chunks which could actually place tiles on this chunk complete? (This is ones to W, SW, S) + if( ( lc->terrainPopulated & LevelChunk::sTerrainPopulatedAllAffecting ) == LevelChunk::sTerrainPopulatedAllAffecting ) + { + // Do the compression of data & lighting at this point + PIXBeginNamedEvent(0,"Compressing lighting/blocks"); + + // Check, using lower blocks as a reference, if we've already compressed - no point doing this multiple times, which + // otherwise we will do as we aren't checking for the flags transitioning in the if statement we're in here + if( !lc->isLowerBlockStorageCompressed() ) + lc->compressBlocks(); + if( !lc->isLowerBlockLightStorageCompressed() ) + lc->compressLighting(); + if( !lc->isLowerDataStorageCompressed() ) + lc->compressData(); + + PIXEndNamedEvent(); + } + + // Are all neighbouring chunks And this one now post-processed? + if( lc->terrainPopulated == LevelChunk::sTerrainPopulatedAllNeighbours ) + { + // Special lighting patching for schematics first + app.processSchematicsLighting(lc); + + // This would be a good time to fix up any lighting for this chunk since all the geometry that could affect it should now be in place + PIXBeginNamedEvent(0,"Recheck gaps"); + if( lc->level->dimension->id != 1 ) + { + lc->recheckGaps(true); + } + PIXEndNamedEvent(); + + // Do a checkLight on any tiles which are lava. + PIXBeginNamedEvent(0,"Light lava (this)"); + lc->lightLava(); + PIXEndNamedEvent(); + + // Flag as now having this post-post-processing stage completed + lc->terrainPopulated |= LevelChunk::sTerrainPostPostProcessed; + } +} + +void ServerChunkCache::postProcess(ChunkSource *parent, int x, int z ) +{ + LevelChunk *chunk = getChunk(x, z); + if ( (chunk->terrainPopulated & LevelChunk::sTerrainPopulatedFromHere) == 0 ) + { + if (source != NULL) + { + PIXBeginNamedEvent(0,"Main post processing"); + source->postProcess(parent, x, z); + PIXEndNamedEvent(); + + chunk->markUnsaved(); + } + + // Flag not only this chunk as being post-processed, but also all the chunks that this post-processing might affect. We can guarantee that these + // chunks exist as that's determined before post-processing can even run + chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromHere; + + // If we are an edge chunk, fill in missing flags from sides that will never post-process + if(x == -XZOFFSET) // Furthest west + { + chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromW; + chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromSW; + chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromNW; + } + if(x == (XZOFFSET - 1 )) // Furthest east + { + chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromE; + chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromSE; + chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromNE; + } + if(z == -XZOFFSET) // Furthest south + { + chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromS; + chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromSW; + chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromSE; + } + if(z == (XZOFFSET - 1)) // Furthest north + { + chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromN; + chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromNW; + chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromNE; + } + + // Set flags for post-processing being complete for neighbouring chunks. This also performs actions if this post-processing completes + // a full set of post-processing flags for one of these neighbours. + flagPostProcessComplete(0, x, z ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromW, x + 1, z + 0 ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromSW, x + 1, z + 1 ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromS, x + 0, z + 1 ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromSE, x - 1, z + 1 ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromE, x - 1, z + 0 ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromNE, x - 1, z - 1 ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromN, x + 0, z - 1 ); + flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromNW, x + 1, z - 1 ); + } +} + +// 4J Added for suspend +bool ServerChunkCache::saveAllEntities() +{ + PIXBeginNamedEvent(0, "Save all entities"); + + PIXBeginNamedEvent(0, "saving to NBT"); + EnterCriticalSection(&m_csLoadCreate); + for(AUTO_VAR(it,m_loadedChunkList.begin()); it != m_loadedChunkList.end(); ++it) + { + storage->saveEntities(level, *it); + } + LeaveCriticalSection(&m_csLoadCreate); + PIXEndNamedEvent(); + + PIXBeginNamedEvent(0,"Flushing"); + storage->flush(); + PIXEndNamedEvent(); + + PIXEndNamedEvent(); + return true; +} + +bool ServerChunkCache::save(bool force, ProgressListener *progressListener) +{ + EnterCriticalSection(&m_csLoadCreate); + int saves = 0; + + // 4J - added this to support progressListner + int count = 0; + if (progressListener != NULL) + { + AUTO_VAR(itEnd, m_loadedChunkList.end()); + for (AUTO_VAR(it, m_loadedChunkList.begin()); it != itEnd; it++) + { + LevelChunk *chunk = *it; + if (chunk->shouldSave(force)) + { + count++; + } + } + } + int cc = 0; + + bool maxSavesReached = false; + + if(!force) + { + //app.DebugPrintf("Unsaved chunks = %d\n", level->getUnsavedChunkCount() ); + // Single threaded implementation for small saves + for (unsigned int i = 0; i < m_loadedChunkList.size(); i++) + { + LevelChunk *chunk = m_loadedChunkList[i]; +#ifndef SPLIT_SAVES + if (force && !chunk->dontSave) saveEntities(chunk); +#endif + if (chunk->shouldSave(force)) + { + save(chunk); + chunk->setUnsaved(false); + if (++saves == MAX_SAVES && !force) + { + LeaveCriticalSection(&m_csLoadCreate); + return false; + } + + // 4J - added this to support progressListener + if (progressListener != NULL) + { + if (++cc % 10 == 0) + { + progressListener->progressStagePercentage(cc * 100 / count); + } + } + } + } + } + else + { +#if 1 //_LARGE_WORLDS + // 4J Stu - We have multiple for threads for all saving as part of the storage, so use that rather than new threads here + + // Created a roughly sorted list to match the order that the files were created in McRegionChunkStorage::McRegionChunkStorage. + // This is to minimise the amount of data that needs to be moved round when creating a new level. + + vector<LevelChunk *> sortedChunkList; + + for( int i = 0; i < m_loadedChunkList.size(); i++ ) + { + if( ( m_loadedChunkList[i]->x < 0 ) && ( m_loadedChunkList[i]->z < 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); + } + for( int i = 0; i < m_loadedChunkList.size(); i++ ) + { + if( ( m_loadedChunkList[i]->x >= 0 ) && ( m_loadedChunkList[i]->z < 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); + } + for( int i = 0; i < m_loadedChunkList.size(); i++ ) + { + if( ( m_loadedChunkList[i]->x >= 0 ) && ( m_loadedChunkList[i]->z >= 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); + } + for( int i = 0; i < m_loadedChunkList.size(); i++ ) + { + if( ( m_loadedChunkList[i]->x < 0 ) && ( m_loadedChunkList[i]->z >= 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); + } + + // Push all the chunks to be saved to the compression threads + for (unsigned int i = 0; i < sortedChunkList.size();++i) + { + LevelChunk *chunk = sortedChunkList[i]; + if (force && !chunk->dontSave) saveEntities(chunk); + if (chunk->shouldSave(force)) + { + save(chunk); + chunk->setUnsaved(false); + if (++saves == MAX_SAVES && !force) + { + LeaveCriticalSection(&m_csLoadCreate); + return false; + } + + // 4J - added this to support progressListener + if (progressListener != NULL) + { + if (++cc % 10 == 0) + { + progressListener->progressStagePercentage(cc * 100 / count); + } + } + } + // Wait if we are building up too big a queue of chunks to be written - on PS3 this has been seen to cause so much data to be queued that we run out of + // out of memory when saving after exploring a full map + storage->WaitIfTooManyQueuedChunks(); + } + + // Wait for the storage threads to be complete + storage->WaitForAll(); +#else + // Multithreaded implementation for larger saves + + C4JThread::Event *wakeEvent[3]; // This sets off the threads that are waiting to continue + C4JThread::Event *notificationEvent[3]; // These are signalled by the threads to let us know they are complete + C4JThread *saveThreads[3]; + DWORD threadId[3]; + SaveThreadData threadData[3]; + ZeroMemory(&threadData[0], sizeof(SaveThreadData)); + ZeroMemory(&threadData[1], sizeof(SaveThreadData)); + ZeroMemory(&threadData[2], sizeof(SaveThreadData)); + + for(unsigned int i = 0; i < 3; ++i) + { + saveThreads[i] = NULL; + + threadData[i].cache = this; + + wakeEvent[i] = new C4JThread::Event(); //CreateEvent(NULL,FALSE,FALSE,NULL); + threadData[i].wakeEvent = wakeEvent[i]; + + notificationEvent[i] = new C4JThread::Event(); //CreateEvent(NULL,FALSE,FALSE,NULL); + threadData[i].notificationEvent = notificationEvent[i]; + + if(i==0) threadData[i].useSharedThreadStorage = true; + else threadData[i].useSharedThreadStorage = false; + } + + LevelChunk *chunk = NULL; + byte workingThreads; + bool chunkSet = false; + + // Created a roughly sorted list to match the order that the files were created in McRegionChunkStorage::McRegionChunkStorage. + // This is to minimise the amount of data that needs to be moved round when creating a new level. + + vector<LevelChunk *> sortedChunkList; + + for( int i = 0; i < m_loadedChunkList.size(); i++ ) + { + if( ( m_loadedChunkList[i]->x < 0 ) && ( m_loadedChunkList[i]->z < 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); + } + for( int i = 0; i < m_loadedChunkList.size(); i++ ) + { + if( ( m_loadedChunkList[i]->x >= 0 ) && ( m_loadedChunkList[i]->z < 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); + } + for( int i = 0; i < m_loadedChunkList.size(); i++ ) + { + if( ( m_loadedChunkList[i]->x >= 0 ) && ( m_loadedChunkList[i]->z >= 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); + } + for( int i = 0; i < m_loadedChunkList.size(); i++ ) + { + if( ( m_loadedChunkList[i]->x < 0 ) && ( m_loadedChunkList[i]->z >= 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); + } + + for (unsigned int i = 0; i < sortedChunkList.size();) + { + workingThreads = 0; + PIXBeginNamedEvent(0,"Setting tasks for save threads\n"); + for(unsigned int j = 0; j < 3; ++j) + { + chunkSet = false; + + while(!chunkSet && i < sortedChunkList.size()) + { + chunk = sortedChunkList[i]; + + threadData[j].saveEntities = (force && !chunk->dontSave); + + if (chunk->shouldSave(force) || threadData[j].saveEntities) + { + chunkSet = true; + ++workingThreads; + + threadData[j].chunkToSave = chunk; + + //app.DebugPrintf("Chunk to save set for thread %d\n", j); + + if(saveThreads[j] == NULL) + { + char threadName[256]; + sprintf(threadName,"Save thread %d\n",j); + SetThreadName(threadId[j], threadName); + + //saveThreads[j] = CreateThread(NULL,0,runSaveThreadProc,&threadData[j],CREATE_SUSPENDED,&threadId[j]); + saveThreads[j] = new C4JThread(runSaveThreadProc,(void *)&threadData[j],threadName); + + + //app.DebugPrintf("Created new thread: %s\n",threadName); + + // Threads 1,3 and 5 are generally idle so use them (this call waits on thread 2) + if(j == 0) saveThreads[j]->SetProcessor(CPU_CORE_SAVE_THREAD_A); //XSetThreadProcessor( saveThreads[j], 1); + else if(j == 1) saveThreads[j]->SetProcessor(CPU_CORE_SAVE_THREAD_B); //XSetThreadProcessor( saveThreads[j], 3); + else if(j == 2) saveThreads[j]->SetProcessor(CPU_CORE_SAVE_THREAD_C); //XSetThreadProcessor( saveThreads[j], 5); + + //ResumeThread( saveThreads[j] ); + saveThreads[j]->Run(); + } + + if (++saves == MAX_SAVES && !force) + { + maxSavesReached = true; + break; + + //LeaveCriticalSection(&m_csLoadCreate); + // TODO Should we be returning from here? Probably not + //return false; + } + + // 4J - added this to support progressListener + if (progressListener != NULL) + { + if (count > 0 && ++cc % 10 == 0) + { + progressListener->progressStagePercentage(cc * 100 / count); + } + } + } + + ++i; + } + + if( !chunkSet ) + { + threadData[j].chunkToSave = NULL; + //app.DebugPrintf("No chunk to save set for thread %d\n",j); + } + } + PIXEndNamedEvent(); + PIXBeginNamedEvent(0,"Waking save threads\n"); + // Start the worker threads going + for(unsigned int k = 0; k < 3; ++k) + { + //app.DebugPrintf("Waking save thread %d\n",k); + threadData[k].wakeEvent->Set(); //SetEvent(threadData[k].wakeEvent); + } + PIXEndNamedEvent(); + PIXBeginNamedEvent(0,"Waiting for completion of save threads\n"); + //app.DebugPrintf("Waiting for %d save thread(s) to complete\n", workingThreads); + + // Wait for the worker threads to complete + //WaitForMultipleObjects(workingThreads,notificationEvent,TRUE,INFINITE); + // 4J Stu - TODO This isn't ideal as it's not a perfect re-implmentation of the Xbox behaviour + for(unsigned int k = 0; k < workingThreads; ++k) + { + threadData[k].notificationEvent->WaitForSignal(INFINITE); + } + PIXEndNamedEvent(); + if( maxSavesReached ) break; + } + + //app.DebugPrintf("Clearing up worker threads\n"); + // Stop all the worker threads by giving them nothing to process then telling them to start + unsigned char validThreads = 0; + for(unsigned int i = 0; i < 3; ++i) + { + //app.DebugPrintf("Settings chunk to NULL for save thread %d\n", i); + threadData[i].chunkToSave = NULL; + + //app.DebugPrintf("Setting wake event for save thread %d\n",i); + threadData[i].wakeEvent->Set(); //SetEvent(threadData[i].wakeEvent); + + if(saveThreads[i] != NULL) ++validThreads; + } + + //WaitForMultipleObjects(validThreads,saveThreads,TRUE,INFINITE); + // 4J Stu - TODO This isn't ideal as it's not a perfect re-implmentation of the Xbox behaviour + for(unsigned int k = 0; k < validThreads; ++k) + { + saveThreads[k]->WaitForCompletion(INFINITE);; + } + + for(unsigned int i = 0; i < 3; ++i) + { + //app.DebugPrintf("Closing handles for save thread %d\n", i); + delete threadData[i].wakeEvent; //CloseHandle(threadData[i].wakeEvent); + delete threadData[i].notificationEvent; //CloseHandle(threadData[i].notificationEvent); + delete saveThreads[i]; //CloseHandle(saveThreads[i]); + } +#endif + } + + if (force) + { + if (storage == NULL) + { + LeaveCriticalSection(&m_csLoadCreate); + return true; + } + storage->flush(); + } + + LeaveCriticalSection(&m_csLoadCreate); + return !maxSavesReached; + +} + +bool ServerChunkCache::tick() +{ + if (!level->noSave) + { +#ifdef _LARGE_WORLDS + for (int i = 0; i < 100; i++) + { + if (!m_toDrop.empty()) + { + LevelChunk *chunk = m_toDrop.front(); + if(!chunk->isUnloaded()) + { + save(chunk); + saveEntities(chunk); + chunk->unload(true); + + //loadedChunks.remove(cp); + //loadedChunkList.remove(chunk); + AUTO_VAR(it, std::find( m_loadedChunkList.begin(), m_loadedChunkList.end(), chunk) ); + if(it != m_loadedChunkList.end()) m_loadedChunkList.erase(it); + + int ix = chunk->x + XZOFFSET; + int iz = chunk->z + XZOFFSET; + int idx = ix * XZSIZE + iz; + m_unloadedCache[idx] = chunk; + cache[idx] = NULL; + } + m_toDrop.pop_front(); + } + } +#endif + if (storage != NULL) storage->tick(); + } + + return source->tick(); + +} + +bool ServerChunkCache::shouldSave() +{ + return !level->noSave; +} + +wstring ServerChunkCache::gatherStats() +{ + return L"ServerChunkCache: ";// + _toString<int>(loadedChunks.size()) + L" Drop: " + _toString<int>(toDrop.size()); +} + +vector<Biome::MobSpawnerData *> *ServerChunkCache::getMobsAt(MobCategory *mobCategory, int x, int y, int z) +{ + return source->getMobsAt(mobCategory, x, y, z); +} + +TilePos *ServerChunkCache::findNearestMapFeature(Level *level, const wstring &featureName, int x, int y, int z) +{ + return source->findNearestMapFeature(level, featureName, x, y, z); +} + +int ServerChunkCache::runSaveThreadProc(LPVOID lpParam) +{ + SaveThreadData *params = (SaveThreadData *)lpParam; + + if(params->useSharedThreadStorage) + { + Compression::UseDefaultThreadStorage(); + OldChunkStorage::UseDefaultThreadStorage(); + } + else + { + Compression::CreateNewThreadStorage(); + OldChunkStorage::CreateNewThreadStorage(); + } + + // Wait for the producer thread to tell us to start + params->wakeEvent->WaitForSignal(INFINITE); //WaitForSingleObject(params->wakeEvent,INFINITE); + + //app.DebugPrintf("Save thread has started\n"); + + while(params->chunkToSave != NULL) + { + PIXBeginNamedEvent(0,"Saving entities"); + //app.DebugPrintf("Save thread has started processing a chunk\n"); + if (params->saveEntities) params->cache->saveEntities(params->chunkToSave); + PIXEndNamedEvent(); + PIXBeginNamedEvent(0,"Saving chunk"); + + params->cache->save(params->chunkToSave); + params->chunkToSave->setUnsaved(false); + + PIXEndNamedEvent(); + PIXBeginNamedEvent(0,"Notifying and waiting for next chunk"); + + // Inform the producer thread that we are done with this chunk + params->notificationEvent->Set(); //SetEvent(params->notificationEvent); + + //app.DebugPrintf("Save thread has alerted producer that it is complete\n"); + + // Wait for the producer thread to tell us to go again + params->wakeEvent->WaitForSignal(INFINITE); //WaitForSingleObject(params->wakeEvent,INFINITE); + PIXEndNamedEvent(); + } + + //app.DebugPrintf("Thread is exiting as it has no chunk to process\n"); + + if(!params->useSharedThreadStorage) + { + Compression::ReleaseThreadStorage(); + OldChunkStorage::ReleaseThreadStorage(); + } + + return 0; +} |
