the game where you go into mines and start crafting! but for consoles (forked directly from smartcmd's github)
at main 1052 lines 36 kB view raw
1#include "stdafx.h" 2#include "ServerChunkCache.h" 3#include "ServerLevel.h" 4#include "MinecraftServer.h" 5#include "..\Minecraft.World\net.minecraft.world.level.h" 6#include "..\Minecraft.World\net.minecraft.world.level.dimension.h" 7#include "..\Minecraft.World\net.minecraft.world.level.storage.h" 8#include "..\Minecraft.World\net.minecraft.world.level.chunk.h" 9#include "..\Minecraft.World\Pos.h" 10#include "..\Minecraft.World\ProgressListener.h" 11#include "..\Minecraft.World\ThreadName.h" 12#include "..\Minecraft.World\compression.h" 13#include "..\Minecraft.World\OldChunkStorage.h" 14#include "..\Minecraft.World\Tile.h" 15 16ServerChunkCache::ServerChunkCache(ServerLevel *level, ChunkStorage *storage, ChunkSource *source) 17{ 18 XZSIZE = source->m_XZSize; // 4J Added 19 XZOFFSET = XZSIZE/2; // 4J Added 20 21 autoCreate = false; // 4J added 22 23 emptyChunk = new EmptyLevelChunk(level, byteArray( Level::CHUNK_TILE_COUNT ), 0, 0); 24 25 this->level = level; 26 this->storage = storage; 27 this->source = source; 28 this->m_XZSize = source->m_XZSize; 29 30 this->cache = new LevelChunk *[XZSIZE * XZSIZE]; 31 memset(this->cache, 0, XZSIZE * XZSIZE * sizeof(LevelChunk *)); 32 33#ifdef _LARGE_WORLDS 34 m_unloadedCache = new LevelChunk *[XZSIZE * XZSIZE]; 35 memset(m_unloadedCache, 0, XZSIZE * XZSIZE * sizeof(LevelChunk *)); 36#endif 37 38 InitializeCriticalSectionAndSpinCount(&m_csLoadCreate,4000); 39} 40 41// 4J-PB added 42ServerChunkCache::~ServerChunkCache() 43{ 44 storage->WaitForAll(); // MGH - added to fix crash bug 175183 45 delete emptyChunk; 46 delete cache; 47 delete source; 48 49#ifdef _LARGE_WORLDS 50 for(unsigned int i = 0; i < XZSIZE * XZSIZE; ++i) 51 { 52 delete m_unloadedCache[i]; 53 } 54 delete m_unloadedCache; 55#endif 56 57 AUTO_VAR(itEnd, m_loadedChunkList.end()); 58 for (AUTO_VAR(it, m_loadedChunkList.begin()); it != itEnd; it++) 59 delete *it; 60 DeleteCriticalSection(&m_csLoadCreate); 61} 62 63bool ServerChunkCache::hasChunk(int x, int z) 64{ 65 int ix = x + XZOFFSET; 66 int iz = z + XZOFFSET; 67 // Check we're in range of the stored level 68 // 4J Stu - Request for chunks outside the range always return an emptyChunk, so just return true here to say we have it 69 // If we return false entities less than 2 chunks from the edge do not tick properly due to them requiring a certain radius 70 // of chunks around them when they tick 71 if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return true; 72 if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return true; 73 int idx = ix * XZSIZE + iz; 74 LevelChunk *lc = cache[idx]; 75 if( lc == NULL ) return false; 76 return true; 77} 78 79vector<LevelChunk *> *ServerChunkCache::getLoadedChunkList() 80{ 81 return &m_loadedChunkList; 82} 83 84void ServerChunkCache::drop(int x, int z) 85{ 86 // 4J - we're not dropping things anymore now that we have a fixed sized cache 87#ifdef _LARGE_WORLDS 88 89 bool canDrop = false; 90// if (level->dimension->mayRespawn()) 91// { 92// Pos *spawnPos = level->getSharedSpawnPos(); 93// int xd = x * 16 + 8 - spawnPos->x; 94// int zd = z * 16 + 8 - spawnPos->z; 95// delete spawnPos; 96// int r = 128; 97// if (xd < -r || xd > r || zd < -r || zd > r) 98// { 99// canDrop = true; 100//} 101// } 102// else 103 { 104 canDrop = true; 105 } 106 if(canDrop) 107 { 108 int ix = x + XZOFFSET; 109 int iz = z + XZOFFSET; 110 // Check we're in range of the stored level 111 if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return; 112 if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return; 113 int idx = ix * XZSIZE + iz; 114 LevelChunk *chunk = cache[idx]; 115 116 if(chunk) 117 { 118 m_toDrop.push_back(chunk); 119 } 120 } 121#endif 122} 123 124void ServerChunkCache::dropAll() 125{ 126#ifdef _LARGE_WORLDS 127 for (LevelChunk *chunk : m_loadedChunkList) 128 { 129 drop(chunk->x, chunk->z); 130} 131#endif 132} 133 134// 4J - this is the original (and virtual) interface to create 135LevelChunk *ServerChunkCache::create(int x, int z) 136{ 137 return create(x, z, false); 138} 139 140LevelChunk *ServerChunkCache::create(int x, int z, bool asyncPostProcess) // 4J - added extra parameter 141{ 142 int ix = x + XZOFFSET; 143 int iz = z + XZOFFSET; 144 // Check we're in range of the stored level 145 if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return emptyChunk; 146 if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return emptyChunk; 147 int idx = ix * XZSIZE + iz; 148 149 LevelChunk *chunk = cache[idx]; 150 LevelChunk *lastChunk = chunk; 151 152 if( ( chunk == NULL ) || ( chunk->x != x ) || ( chunk->z != z ) ) 153 { 154 EnterCriticalSection(&m_csLoadCreate); 155 chunk = load(x, z); 156 if (chunk == NULL) 157 { 158 if (source == NULL) 159 { 160 chunk = emptyChunk; 161 } 162 else 163 { 164 chunk = source->getChunk(x, z); 165 } 166 } 167 if (chunk != NULL) 168 { 169 chunk->load(); 170 } 171 172 LeaveCriticalSection(&m_csLoadCreate); 173 174#if ( defined _WIN64 || defined __LP64__ ) 175 if( InterlockedCompareExchangeRelease64((LONG64 *)&cache[idx],(LONG64)chunk,(LONG64)lastChunk) == (LONG64)lastChunk ) 176#else 177 if( InterlockedCompareExchangeRelease((LONG *)&cache[idx],(LONG)chunk,(LONG)lastChunk) == (LONG)lastChunk ) 178#endif // _DURANGO 179 { 180 // Successfully updated the cache 181 EnterCriticalSection(&m_csLoadCreate); 182 // 4J - added - this will run a recalcHeightmap if source is a randomlevelsource, which has been split out from source::getChunk so that 183 // 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 184 // they are in fail ServerChunkCache::hasChunk. 185 source->lightChunk(chunk); 186 187 updatePostProcessFlags( x, z ); 188 189 m_loadedChunkList.push_back(chunk); 190 191 // 4J - If post-processing is to be async, then let the server know about requests rather than processing directly here. Note that 192 // these hasChunk() checks appear to be incorrect - the chunks checked by these map out as: 193 // 194 // 1. 2. 3. 4. 195 // oxx xxo ooo ooo 196 // oPx Poo oox xoo 197 // ooo ooo oPx Pxo 198 // 199 // 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 200 // 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 201 // the chunk which is to be processed itself rather than (what I presume to be) the correct position. 202 // Don't think we should change in case it alters level creation. 203 204 if( asyncPostProcess ) 205 { 206 // 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 207 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); 208 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); 209 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); 210 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); 211 } 212 else 213 { 214 chunk->checkPostProcess(this, this, x, z); 215 } 216 217 // 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 218 // 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. 219 // 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 220 221 // 1. 2. 3. 4. 5. 222 // ooooo ooxoo ooooo ooooo ooooo 223 // oxooo oxPxo oooxo ooooo ooxoo 224 // xPToo ooToo ooTPx ooToo oxPxo (in 5th case P and T are same) 225 // oxooo ooooo oooxo oxPxo ooxoo 226 // ooooo ooooo ooooo ooxoo ooooo 227 228 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 ); 229 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); 230 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 ); 231 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); 232 if( hasChunk( x - 1, z ) && hasChunk( x + 1, z ) && hasChunk ( x, z - 1 ) && hasChunk( x, z + 1 ) ) chunk->checkChests( this, x, z ); 233 234 LeaveCriticalSection(&m_csLoadCreate); 235 } 236 else 237 { 238 // Something else must have updated the cache. Return that chunk and discard this one 239 chunk->unload(true); 240 delete chunk; 241 return cache[idx]; 242 } 243 } 244 245#ifdef __PS3__ 246 Sleep(1); 247#endif // __PS3__ 248 return chunk; 249 250} 251 252// 4J Stu - Split out this function so that we get a chunk without loading entities 253// This is used when sharing server chunk data on the main thread 254LevelChunk *ServerChunkCache::getChunk(int x, int z) 255{ 256 int ix = x + XZOFFSET; 257 int iz = z + XZOFFSET; 258 // Check we're in range of the stored level 259 if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return emptyChunk; 260 if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return emptyChunk; 261 int idx = ix * XZSIZE + iz; 262 263 LevelChunk *lc = cache[idx]; 264 if( lc ) 265 { 266 return lc; 267 } 268 269 if( level->isFindingSpawn || autoCreate ) 270 { 271 return create(x, z); 272 } 273 274 return emptyChunk; 275} 276 277#ifdef _LARGE_WORLDS 278// 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: 279// (1) Trying to determine whether the client blocks and data are the same as those on the server, so we can start sharing them 280// (2) Trying to resync the lighting data from the server to the client 281// 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 282LevelChunk *ServerChunkCache::getChunkLoadedOrUnloaded(int x, int z) 283{ 284 int ix = x + XZOFFSET; 285 int iz = z + XZOFFSET; 286 // Check we're in range of the stored level 287 if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return emptyChunk; 288 if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return emptyChunk; 289 int idx = ix * XZSIZE + iz; 290 291 LevelChunk *lc = cache[idx]; 292 if( lc ) 293 { 294 return lc; 295 } 296 297 lc = m_unloadedCache[idx]; 298 if( lc ) 299 { 300 return lc; 301} 302 303 if( level->isFindingSpawn || autoCreate ) 304 { 305 return create(x, z); 306 } 307 308 return emptyChunk; 309} 310#endif 311 312 313// 4J MGH added, for expanding worlds, to kill any player changes and reset the chunk 314#ifdef _LARGE_WORLDS 315void ServerChunkCache::overwriteLevelChunkFromSource(int x, int z) 316{ 317 int ix = x + XZOFFSET; 318 int iz = z + XZOFFSET; 319 // Check we're in range of the stored level 320 if( ( ix < 0 ) || ( ix >= XZSIZE ) ) assert(0); 321 if( ( iz < 0 ) || ( iz >= XZSIZE ) ) assert(0); 322 int idx = ix * XZSIZE + iz; 323 324 LevelChunk *chunk = NULL; 325 chunk = source->getChunk(x, z); 326 assert(chunk); 327 if(chunk) 328 { 329 save(chunk); 330 } 331} 332 333 334 335void ServerChunkCache::updateOverwriteHellChunk(LevelChunk* origChunk, LevelChunk* playerChunk, int xMin, int xMax, int zMin, int zMax) 336{ 337 338 // replace a section of the chunk with the original source data, if it hasn't already changed 339 for(int x=xMin;x<xMax;x++) 340 { 341 for(int z=zMin;z<zMax;z++) 342 { 343 for(int y=0;y<256;y++) 344 { 345 int playerTile = playerChunk->getTile(x,y,z); 346 if(playerTile == Tile::unbreakable_Id) // if the tile is still unbreakable, the player hasn't changed it, so we can replace with the source 347 playerChunk->setTileAndData(x, y, z, origChunk->getTile(x,y,z), origChunk->getData(x,y,z)); 348 } 349 } 350 } 351} 352 353void ServerChunkCache::overwriteHellLevelChunkFromSource(int x, int z, int minVal, int maxVal) 354{ 355 int ix = x + XZOFFSET; 356 int iz = z + XZOFFSET; 357 // Check we're in range of the stored level 358 if( ( ix < 0 ) || ( ix >= XZSIZE ) ) assert(0); 359 if( ( iz < 0 ) || ( iz >= XZSIZE ) ) assert(0); 360 int idx = ix * XZSIZE + iz; 361 autoCreate = true; 362 LevelChunk* playerChunk = getChunk(x, z); 363 autoCreate = false; 364 LevelChunk* origChunk = source->getChunk(x, z); 365 assert(origChunk); 366 if(playerChunk!= emptyChunk) 367 { 368 if(x == minVal) 369 updateOverwriteHellChunk(origChunk, playerChunk, 0, 4, 0, 16); 370 if(x == maxVal) 371 updateOverwriteHellChunk(origChunk, playerChunk, 12, 16, 0, 16); 372 if(z == minVal) 373 updateOverwriteHellChunk(origChunk, playerChunk, 0, 16, 0, 4); 374 if(z == maxVal) 375 updateOverwriteHellChunk(origChunk, playerChunk, 0, 16, 12, 16); 376 } 377 save(playerChunk); 378} 379 380#endif 381 382// 4J Added // 383#ifdef _LARGE_WORLDS 384void ServerChunkCache::dontDrop(int x, int z) 385{ 386 LevelChunk *chunk = getChunk(x,z); 387 m_toDrop.erase(std::remove(m_toDrop.begin(), m_toDrop.end(), chunk), m_toDrop.end()); 388} 389#endif 390 391LevelChunk *ServerChunkCache::load(int x, int z) 392{ 393 if (storage == NULL) return NULL; 394 395 LevelChunk *levelChunk = NULL; 396 397#ifdef _LARGE_WORLDS 398 int ix = x + XZOFFSET; 399 int iz = z + XZOFFSET; 400 int idx = ix * XZSIZE + iz; 401 levelChunk = m_unloadedCache[idx]; 402 m_unloadedCache[idx] = NULL; 403 if(levelChunk == NULL) 404#endif 405 { 406 levelChunk = storage->load(level, x, z); 407 } 408 if (levelChunk != NULL) 409 { 410 levelChunk->lastSaveTime = level->getGameTime(); 411 } 412 return levelChunk; 413} 414 415void ServerChunkCache::saveEntities(LevelChunk *levelChunk) 416{ 417 if (storage == NULL) return; 418 419 storage->saveEntities(level, levelChunk); 420} 421 422void ServerChunkCache::save(LevelChunk *levelChunk) 423{ 424 if (storage == NULL) return; 425 426 levelChunk->lastSaveTime = level->getGameTime(); 427 storage->save(level, levelChunk); 428} 429 430// 4J added 431void ServerChunkCache::updatePostProcessFlag(short flag, int x, int z, int xo, int zo, LevelChunk *lc) 432{ 433 if( hasChunk( x + xo, z + zo ) ) 434 { 435 LevelChunk *lc2 = getChunk(x + xo, z + zo); 436 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) 437 { 438 if( lc2->terrainPopulated & LevelChunk::sTerrainPopulatedFromHere ) 439 { 440 lc->terrainPopulated |= flag; 441 } 442 } 443 else 444 { 445 // The edge - always consider as post-processed 446 lc->terrainPopulated |= flag; 447 } 448 } 449} 450 451// 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 452// affected chunks might not themselves exist, so we need to check the flags also when creating new chunks. 453void ServerChunkCache::updatePostProcessFlags(int x, int z) 454{ 455 LevelChunk *lc = getChunk(x, z); 456 if( lc != emptyChunk ) 457 { 458 // First check if any of our neighbours are post-processed, that should affect OUR flags 459 updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromS, x, z, 0, -1, lc ); 460 updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromSW, x, z, -1, -1, lc ); 461 updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromW, x, z, -1, 0, lc ); 462 updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromNW, x, z, -1, 1, lc ); 463 updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromN, x, z, 0, 1, lc ); 464 updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromNE, x, z, 1, 1, lc ); 465 updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromE, x, z, 1, 0, lc ); 466 updatePostProcessFlag( LevelChunk::sTerrainPopulatedFromSE, x, z, 1, -1, lc ); 467 468 // Then, if WE are post-processed, check that our neighbour's flags are also set 469 if( lc->terrainPopulated & LevelChunk::sTerrainPopulatedFromHere ) 470 { 471 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromW, x + 1, z + 0 ); 472 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromSW, x + 1, z + 1 ); 473 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromS, x + 0, z + 1 ); 474 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromSE, x - 1, z + 1 ); 475 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromE, x - 1, z + 0 ); 476 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromNE, x - 1, z - 1 ); 477 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromN, x + 0, z - 1 ); 478 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromNW, x + 1, z - 1 ); 479 } 480 } 481 482 flagPostProcessComplete(0, x, z); 483} 484 485// 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 486// chunks which can actually set tile tiles in this chunk (sTerrainPopulatedAllAffecting), then this is a good point to compress this chunk. 487// 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 488// lighting things that need all the tiles to be in place in the region into which they might propagate. 489void ServerChunkCache::flagPostProcessComplete(short flag, int x, int z) 490{ 491 // Set any extra flags for this chunk to indicate which neighbours have now had their post-processing done 492 if( !hasChunk(x, z) ) return; 493 494 LevelChunk *lc = level->getChunk( x, z ); 495 if( lc == emptyChunk ) return; 496 497 lc->terrainPopulated |= flag; 498 499 // Are all neighbouring chunks which could actually place tiles on this chunk complete? (This is ones to W, SW, S) 500 if( ( lc->terrainPopulated & LevelChunk::sTerrainPopulatedAllAffecting ) == LevelChunk::sTerrainPopulatedAllAffecting ) 501 { 502 // Do the compression of data & lighting at this point 503 PIXBeginNamedEvent(0,"Compressing lighting/blocks"); 504 505 // Check, using lower blocks as a reference, if we've already compressed - no point doing this multiple times, which 506 // otherwise we will do as we aren't checking for the flags transitioning in the if statement we're in here 507 if( !lc->isLowerBlockStorageCompressed() ) 508 lc->compressBlocks(); 509 if( !lc->isLowerBlockLightStorageCompressed() ) 510 lc->compressLighting(); 511 if( !lc->isLowerDataStorageCompressed() ) 512 lc->compressData(); 513 514 PIXEndNamedEvent(); 515 } 516 517 // Are all neighbouring chunks And this one now post-processed? 518 if( lc->terrainPopulated == LevelChunk::sTerrainPopulatedAllNeighbours ) 519 { 520 // Special lighting patching for schematics first 521 app.processSchematicsLighting(lc); 522 523 // 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 524 PIXBeginNamedEvent(0,"Recheck gaps"); 525 if( lc->level->dimension->id != 1 ) 526 { 527 lc->recheckGaps(true); 528 } 529 PIXEndNamedEvent(); 530 531 // Do a checkLight on any tiles which are lava. 532 PIXBeginNamedEvent(0,"Light lava (this)"); 533 lc->lightLava(); 534 PIXEndNamedEvent(); 535 536 // Flag as now having this post-post-processing stage completed 537 lc->terrainPopulated |= LevelChunk::sTerrainPostPostProcessed; 538 } 539} 540 541void ServerChunkCache::postProcess(ChunkSource *parent, int x, int z ) 542{ 543 LevelChunk *chunk = getChunk(x, z); 544 if ( (chunk->terrainPopulated & LevelChunk::sTerrainPopulatedFromHere) == 0 ) 545 { 546 if (source != NULL) 547 { 548 PIXBeginNamedEvent(0,"Main post processing"); 549 source->postProcess(parent, x, z); 550 PIXEndNamedEvent(); 551 552 chunk->markUnsaved(); 553 } 554 555 // 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 556 // chunks exist as that's determined before post-processing can even run 557 chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromHere; 558 559 // If we are an edge chunk, fill in missing flags from sides that will never post-process 560 if(x == -XZOFFSET) // Furthest west 561 { 562 chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromW; 563 chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromSW; 564 chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromNW; 565 } 566 if(x == (XZOFFSET - 1 )) // Furthest east 567 { 568 chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromE; 569 chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromSE; 570 chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromNE; 571 } 572 if(z == -XZOFFSET) // Furthest south 573 { 574 chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromS; 575 chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromSW; 576 chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromSE; 577 } 578 if(z == (XZOFFSET - 1)) // Furthest north 579 { 580 chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromN; 581 chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromNW; 582 chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromNE; 583 } 584 585 // Set flags for post-processing being complete for neighbouring chunks. This also performs actions if this post-processing completes 586 // a full set of post-processing flags for one of these neighbours. 587 flagPostProcessComplete(0, x, z ); 588 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromW, x + 1, z + 0 ); 589 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromSW, x + 1, z + 1 ); 590 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromS, x + 0, z + 1 ); 591 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromSE, x - 1, z + 1 ); 592 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromE, x - 1, z + 0 ); 593 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromNE, x - 1, z - 1 ); 594 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromN, x + 0, z - 1 ); 595 flagPostProcessComplete(LevelChunk::sTerrainPopulatedFromNW, x + 1, z - 1 ); 596 } 597} 598 599// 4J Added for suspend 600bool ServerChunkCache::saveAllEntities() 601{ 602 PIXBeginNamedEvent(0, "Save all entities"); 603 604 PIXBeginNamedEvent(0, "saving to NBT"); 605 EnterCriticalSection(&m_csLoadCreate); 606 for(AUTO_VAR(it,m_loadedChunkList.begin()); it != m_loadedChunkList.end(); ++it) 607 { 608 storage->saveEntities(level, *it); 609 } 610 LeaveCriticalSection(&m_csLoadCreate); 611 PIXEndNamedEvent(); 612 613 PIXBeginNamedEvent(0,"Flushing"); 614 storage->flush(); 615 PIXEndNamedEvent(); 616 617 PIXEndNamedEvent(); 618 return true; 619} 620 621bool ServerChunkCache::save(bool force, ProgressListener *progressListener) 622{ 623 EnterCriticalSection(&m_csLoadCreate); 624 int saves = 0; 625 626 // 4J - added this to support progressListner 627 int count = 0; 628 if (progressListener != NULL) 629 { 630 AUTO_VAR(itEnd, m_loadedChunkList.end()); 631 for (AUTO_VAR(it, m_loadedChunkList.begin()); it != itEnd; it++) 632 { 633 LevelChunk *chunk = *it; 634 if (chunk->shouldSave(force)) 635 { 636 count++; 637 } 638 } 639 } 640 int cc = 0; 641 642 bool maxSavesReached = false; 643 644 if(!force) 645 { 646 //app.DebugPrintf("Unsaved chunks = %d\n", level->getUnsavedChunkCount() ); 647 // Single threaded implementation for small saves 648 for (unsigned int i = 0; i < m_loadedChunkList.size(); i++) 649 { 650 LevelChunk *chunk = m_loadedChunkList[i]; 651#ifndef SPLIT_SAVES 652 if (force && !chunk->dontSave) saveEntities(chunk); 653#endif 654 if (chunk->shouldSave(force)) 655 { 656 save(chunk); 657 chunk->setUnsaved(false); 658 if (++saves == MAX_SAVES && !force) 659 { 660 LeaveCriticalSection(&m_csLoadCreate); 661 return false; 662 } 663 664 // 4J - added this to support progressListener 665 if (progressListener != NULL) 666 { 667 if (++cc % 10 == 0) 668 { 669 progressListener->progressStagePercentage(cc * 100 / count); 670 } 671 } 672 } 673 } 674 } 675 else 676 { 677#if 1 //_LARGE_WORLDS 678 // 4J Stu - We have multiple for threads for all saving as part of the storage, so use that rather than new threads here 679 680 // Created a roughly sorted list to match the order that the files were created in McRegionChunkStorage::McRegionChunkStorage. 681 // This is to minimise the amount of data that needs to be moved round when creating a new level. 682 683 vector<LevelChunk *> sortedChunkList; 684 685 for( int i = 0; i < m_loadedChunkList.size(); i++ ) 686 { 687 if( ( m_loadedChunkList[i]->x < 0 ) && ( m_loadedChunkList[i]->z < 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); 688 } 689 for( int i = 0; i < m_loadedChunkList.size(); i++ ) 690 { 691 if( ( m_loadedChunkList[i]->x >= 0 ) && ( m_loadedChunkList[i]->z < 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); 692 } 693 for( int i = 0; i < m_loadedChunkList.size(); i++ ) 694 { 695 if( ( m_loadedChunkList[i]->x >= 0 ) && ( m_loadedChunkList[i]->z >= 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); 696 } 697 for( int i = 0; i < m_loadedChunkList.size(); i++ ) 698 { 699 if( ( m_loadedChunkList[i]->x < 0 ) && ( m_loadedChunkList[i]->z >= 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); 700 } 701 702 // Push all the chunks to be saved to the compression threads 703 for (unsigned int i = 0; i < sortedChunkList.size();++i) 704 { 705 LevelChunk *chunk = sortedChunkList[i]; 706 if (force && !chunk->dontSave) saveEntities(chunk); 707 if (chunk->shouldSave(force)) 708 { 709 save(chunk); 710 chunk->setUnsaved(false); 711 if (++saves == MAX_SAVES && !force) 712 { 713 LeaveCriticalSection(&m_csLoadCreate); 714 return false; 715 } 716 717 // 4J - added this to support progressListener 718 if (progressListener != NULL) 719 { 720 if (++cc % 10 == 0) 721 { 722 progressListener->progressStagePercentage(cc * 100 / count); 723 } 724 } 725 } 726 // 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 727 // out of memory when saving after exploring a full map 728 storage->WaitIfTooManyQueuedChunks(); 729 } 730 731 // Wait for the storage threads to be complete 732 storage->WaitForAll(); 733#else 734 // Multithreaded implementation for larger saves 735 736 C4JThread::Event *wakeEvent[3]; // This sets off the threads that are waiting to continue 737 C4JThread::Event *notificationEvent[3]; // These are signalled by the threads to let us know they are complete 738 C4JThread *saveThreads[3]; 739 DWORD threadId[3]; 740 SaveThreadData threadData[3]; 741 ZeroMemory(&threadData[0], sizeof(SaveThreadData)); 742 ZeroMemory(&threadData[1], sizeof(SaveThreadData)); 743 ZeroMemory(&threadData[2], sizeof(SaveThreadData)); 744 745 for(unsigned int i = 0; i < 3; ++i) 746 { 747 saveThreads[i] = NULL; 748 749 threadData[i].cache = this; 750 751 wakeEvent[i] = new C4JThread::Event(); //CreateEvent(NULL,FALSE,FALSE,NULL); 752 threadData[i].wakeEvent = wakeEvent[i]; 753 754 notificationEvent[i] = new C4JThread::Event(); //CreateEvent(NULL,FALSE,FALSE,NULL); 755 threadData[i].notificationEvent = notificationEvent[i]; 756 757 if(i==0) threadData[i].useSharedThreadStorage = true; 758 else threadData[i].useSharedThreadStorage = false; 759 } 760 761 LevelChunk *chunk = NULL; 762 byte workingThreads; 763 bool chunkSet = false; 764 765 // Created a roughly sorted list to match the order that the files were created in McRegionChunkStorage::McRegionChunkStorage. 766 // This is to minimise the amount of data that needs to be moved round when creating a new level. 767 768 vector<LevelChunk *> sortedChunkList; 769 770 for( int i = 0; i < m_loadedChunkList.size(); i++ ) 771 { 772 if( ( m_loadedChunkList[i]->x < 0 ) && ( m_loadedChunkList[i]->z < 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); 773 } 774 for( int i = 0; i < m_loadedChunkList.size(); i++ ) 775 { 776 if( ( m_loadedChunkList[i]->x >= 0 ) && ( m_loadedChunkList[i]->z < 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); 777 } 778 for( int i = 0; i < m_loadedChunkList.size(); i++ ) 779 { 780 if( ( m_loadedChunkList[i]->x >= 0 ) && ( m_loadedChunkList[i]->z >= 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); 781 } 782 for( int i = 0; i < m_loadedChunkList.size(); i++ ) 783 { 784 if( ( m_loadedChunkList[i]->x < 0 ) && ( m_loadedChunkList[i]->z >= 0 ) ) sortedChunkList.push_back(m_loadedChunkList[i]); 785 } 786 787 for (unsigned int i = 0; i < sortedChunkList.size();) 788 { 789 workingThreads = 0; 790 PIXBeginNamedEvent(0,"Setting tasks for save threads\n"); 791 for(unsigned int j = 0; j < 3; ++j) 792 { 793 chunkSet = false; 794 795 while(!chunkSet && i < sortedChunkList.size()) 796 { 797 chunk = sortedChunkList[i]; 798 799 threadData[j].saveEntities = (force && !chunk->dontSave); 800 801 if (chunk->shouldSave(force) || threadData[j].saveEntities) 802 { 803 chunkSet = true; 804 ++workingThreads; 805 806 threadData[j].chunkToSave = chunk; 807 808 //app.DebugPrintf("Chunk to save set for thread %d\n", j); 809 810 if(saveThreads[j] == NULL) 811 { 812 char threadName[256]; 813 sprintf(threadName,"Save thread %d\n",j); 814 SetThreadName(threadId[j], threadName); 815 816 //saveThreads[j] = CreateThread(NULL,0,runSaveThreadProc,&threadData[j],CREATE_SUSPENDED,&threadId[j]); 817 saveThreads[j] = new C4JThread(runSaveThreadProc,(void *)&threadData[j],threadName); 818 819 820 //app.DebugPrintf("Created new thread: %s\n",threadName); 821 822 // Threads 1,3 and 5 are generally idle so use them (this call waits on thread 2) 823 if(j == 0) saveThreads[j]->SetProcessor(CPU_CORE_SAVE_THREAD_A); //XSetThreadProcessor( saveThreads[j], 1); 824 else if(j == 1) saveThreads[j]->SetProcessor(CPU_CORE_SAVE_THREAD_B); //XSetThreadProcessor( saveThreads[j], 3); 825 else if(j == 2) saveThreads[j]->SetProcessor(CPU_CORE_SAVE_THREAD_C); //XSetThreadProcessor( saveThreads[j], 5); 826 827 //ResumeThread( saveThreads[j] ); 828 saveThreads[j]->Run(); 829 } 830 831 if (++saves == MAX_SAVES && !force) 832 { 833 maxSavesReached = true; 834 break; 835 836 //LeaveCriticalSection(&m_csLoadCreate); 837 // TODO Should we be returning from here? Probably not 838 //return false; 839 } 840 841 // 4J - added this to support progressListener 842 if (progressListener != NULL) 843 { 844 if (count > 0 && ++cc % 10 == 0) 845 { 846 progressListener->progressStagePercentage(cc * 100 / count); 847 } 848 } 849 } 850 851 ++i; 852 } 853 854 if( !chunkSet ) 855 { 856 threadData[j].chunkToSave = NULL; 857 //app.DebugPrintf("No chunk to save set for thread %d\n",j); 858 } 859 } 860 PIXEndNamedEvent(); 861 PIXBeginNamedEvent(0,"Waking save threads\n"); 862 // Start the worker threads going 863 for(unsigned int k = 0; k < 3; ++k) 864 { 865 //app.DebugPrintf("Waking save thread %d\n",k); 866 threadData[k].wakeEvent->Set(); //SetEvent(threadData[k].wakeEvent); 867 } 868 PIXEndNamedEvent(); 869 PIXBeginNamedEvent(0,"Waiting for completion of save threads\n"); 870 //app.DebugPrintf("Waiting for %d save thread(s) to complete\n", workingThreads); 871 872 // Wait for the worker threads to complete 873 //WaitForMultipleObjects(workingThreads,notificationEvent,TRUE,INFINITE); 874 // 4J Stu - TODO This isn't ideal as it's not a perfect re-implmentation of the Xbox behaviour 875 for(unsigned int k = 0; k < workingThreads; ++k) 876 { 877 threadData[k].notificationEvent->WaitForSignal(INFINITE); 878 } 879 PIXEndNamedEvent(); 880 if( maxSavesReached ) break; 881 } 882 883 //app.DebugPrintf("Clearing up worker threads\n"); 884 // Stop all the worker threads by giving them nothing to process then telling them to start 885 unsigned char validThreads = 0; 886 for(unsigned int i = 0; i < 3; ++i) 887 { 888 //app.DebugPrintf("Settings chunk to NULL for save thread %d\n", i); 889 threadData[i].chunkToSave = NULL; 890 891 //app.DebugPrintf("Setting wake event for save thread %d\n",i); 892 threadData[i].wakeEvent->Set(); //SetEvent(threadData[i].wakeEvent); 893 894 if(saveThreads[i] != NULL) ++validThreads; 895 } 896 897 //WaitForMultipleObjects(validThreads,saveThreads,TRUE,INFINITE); 898 // 4J Stu - TODO This isn't ideal as it's not a perfect re-implmentation of the Xbox behaviour 899 for(unsigned int k = 0; k < validThreads; ++k) 900 { 901 saveThreads[k]->WaitForCompletion(INFINITE);; 902 } 903 904 for(unsigned int i = 0; i < 3; ++i) 905 { 906 //app.DebugPrintf("Closing handles for save thread %d\n", i); 907 delete threadData[i].wakeEvent; //CloseHandle(threadData[i].wakeEvent); 908 delete threadData[i].notificationEvent; //CloseHandle(threadData[i].notificationEvent); 909 delete saveThreads[i]; //CloseHandle(saveThreads[i]); 910 } 911#endif 912 } 913 914 if (force) 915 { 916 if (storage == NULL) 917 { 918 LeaveCriticalSection(&m_csLoadCreate); 919 return true; 920 } 921 storage->flush(); 922 } 923 924 LeaveCriticalSection(&m_csLoadCreate); 925 return !maxSavesReached; 926 927} 928 929bool ServerChunkCache::tick() 930{ 931 if (!level->noSave) 932 { 933#ifdef _LARGE_WORLDS 934 for (int i = 0; i < 100; i++) 935 { 936 if (!m_toDrop.empty()) 937 { 938 LevelChunk *chunk = m_toDrop.front(); 939 if(!chunk->isUnloaded()) 940 { 941 // Don't unload a chunk that contains a player, as this will cause their entity to be removed from the level itself and they will never tick again. 942 // This can happen if a player moves a long distance in one tick, for example when the server thread has locked up doing something for a while whilst a player 943 // kept moving. In this case, the player is moved in the player chunk map (driven by the network packets being processed for their new position) before the 944 // player's tick is called to remove them from the chunk they used to be in, and add them to their current chunk. This will only be a temporary state and 945 // we should be able to unload the chunk on the next call to this tick. 946 if( !chunk->containsPlayer() ) 947 { 948 save(chunk); 949 saveEntities(chunk); 950 chunk->unload(true); 951 952 //loadedChunks.remove(cp); 953 //loadedChunkList.remove(chunk); 954 AUTO_VAR(it, std::find( m_loadedChunkList.begin(), m_loadedChunkList.end(), chunk) ); 955 if(it != m_loadedChunkList.end()) m_loadedChunkList.erase(it); 956 957 int ix = chunk->x + XZOFFSET; 958 int iz = chunk->z + XZOFFSET; 959 int idx = ix * XZSIZE + iz; 960 m_unloadedCache[idx] = chunk; 961 cache[idx] = NULL; 962 } 963 } 964 m_toDrop.pop_front(); 965 } 966 } 967#endif 968 if (storage != NULL) storage->tick(); 969 } 970 971 return source->tick(); 972 973} 974 975bool ServerChunkCache::shouldSave() 976{ 977 return !level->noSave; 978} 979 980wstring ServerChunkCache::gatherStats() 981{ 982 return L"ServerChunkCache: ";// + _toString<int>(loadedChunks.size()) + L" Drop: " + _toString<int>(toDrop.size()); 983} 984 985vector<Biome::MobSpawnerData *> *ServerChunkCache::getMobsAt(MobCategory *mobCategory, int x, int y, int z) 986{ 987 return source->getMobsAt(mobCategory, x, y, z); 988} 989 990TilePos *ServerChunkCache::findNearestMapFeature(Level *level, const wstring &featureName, int x, int y, int z) 991{ 992 return source->findNearestMapFeature(level, featureName, x, y, z); 993} 994 995void ServerChunkCache::recreateLogicStructuresForChunk(int chunkX, int chunkZ) 996{ 997} 998 999int ServerChunkCache::runSaveThreadProc(LPVOID lpParam) 1000{ 1001 SaveThreadData *params = (SaveThreadData *)lpParam; 1002 1003 if(params->useSharedThreadStorage) 1004 { 1005 Compression::UseDefaultThreadStorage(); 1006 OldChunkStorage::UseDefaultThreadStorage(); 1007 } 1008 else 1009 { 1010 Compression::CreateNewThreadStorage(); 1011 OldChunkStorage::CreateNewThreadStorage(); 1012 } 1013 1014 // Wait for the producer thread to tell us to start 1015 params->wakeEvent->WaitForSignal(INFINITE); //WaitForSingleObject(params->wakeEvent,INFINITE); 1016 1017 //app.DebugPrintf("Save thread has started\n"); 1018 1019 while(params->chunkToSave != NULL) 1020 { 1021 PIXBeginNamedEvent(0,"Saving entities"); 1022 //app.DebugPrintf("Save thread has started processing a chunk\n"); 1023 if (params->saveEntities) params->cache->saveEntities(params->chunkToSave); 1024 PIXEndNamedEvent(); 1025 PIXBeginNamedEvent(0,"Saving chunk"); 1026 1027 params->cache->save(params->chunkToSave); 1028 params->chunkToSave->setUnsaved(false); 1029 1030 PIXEndNamedEvent(); 1031 PIXBeginNamedEvent(0,"Notifying and waiting for next chunk"); 1032 1033 // Inform the producer thread that we are done with this chunk 1034 params->notificationEvent->Set(); //SetEvent(params->notificationEvent); 1035 1036 //app.DebugPrintf("Save thread has alerted producer that it is complete\n"); 1037 1038 // Wait for the producer thread to tell us to go again 1039 params->wakeEvent->WaitForSignal(INFINITE); //WaitForSingleObject(params->wakeEvent,INFINITE); 1040 PIXEndNamedEvent(); 1041 } 1042 1043 //app.DebugPrintf("Thread is exiting as it has no chunk to process\n"); 1044 1045 if(!params->useSharedThreadStorage) 1046 { 1047 Compression::ReleaseThreadStorage(); 1048 OldChunkStorage::ReleaseThreadStorage(); 1049 } 1050 1051 return 0; 1052}