the game where you go into mines and start crafting! but for consoles (forked directly from smartcmd's github)
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}