This commit is contained in:
bwmp 2026-03-02 12:57:00 +00:00 committed by GitHub
commit 89d083d9fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 393 additions and 640 deletions

View file

@ -77,13 +77,15 @@ C4JThread *LevelRenderer::rebuildThreads[MAX_CHUNK_REBUILD_THREADS];
C4JThread::EventArray *LevelRenderer::s_rebuildCompleteEvents; C4JThread::EventArray *LevelRenderer::s_rebuildCompleteEvents;
C4JThread::Event *LevelRenderer::s_activationEventA[MAX_CHUNK_REBUILD_THREADS]; C4JThread::Event *LevelRenderer::s_activationEventA[MAX_CHUNK_REBUILD_THREADS];
// This defines the maximum size of renderable level, must be big enough to cope with actual size of level + view distance at each side // For infinite worlds the overworld AND nether render sizes use the rolling view window.
// so that we can render the "infinite" sea at the edges. Currently defined as: // Nether is also effectively infinite with _LARGE_WORLDS; End remains a fixed small island.
const int overworldSize = LEVEL_MAX_WIDTH + LevelRenderer::PLAYER_VIEW_DISTANCE + LevelRenderer::PLAYER_VIEW_DISTANCE; const int netherSize = LevelRenderer::PLAYER_VIEW_DISTANCE * 2; // rolling window, same as overworld
const int netherSize = HELL_LEVEL_MAX_WIDTH + 2; // 4J Stu - The plus 2 is really just to make our total chunk count a multiple of 8 for the flags, we will never see these in the nether
const int endSize = END_LEVEL_MAX_WIDTH; const int endSize = END_LEVEL_MAX_WIDTH;
const int LevelRenderer::MAX_LEVEL_RENDER_SIZE[3] = { overworldSize, netherSize, endSize }; // MAX_LEVEL_RENDER_SIZE[0] is overridden at runtime in getGlobalIndexForChunk / getGlobalChunkCount.
const int LevelRenderer::DIMENSION_OFFSETS[3] = { 0, (overworldSize * overworldSize * CHUNK_Y_COUNT) , (overworldSize * overworldSize * CHUNK_Y_COUNT) + ( netherSize * netherSize * CHUNK_Y_COUNT ) }; // Use PLAYER_VIEW_DISTANCE*2 as the compile-time upper bound for static array sizing only.
const int overworldSizeMax = LevelRenderer::PLAYER_VIEW_DISTANCE * 2;
const int LevelRenderer::MAX_LEVEL_RENDER_SIZE[3] = { overworldSizeMax, netherSize, endSize };
const int LevelRenderer::DIMENSION_OFFSETS[3] = { 0, (overworldSizeMax * overworldSizeMax * CHUNK_Y_COUNT), (overworldSizeMax * overworldSizeMax * CHUNK_Y_COUNT) + (netherSize * netherSize * CHUNK_Y_COUNT) };
#else #else
// This defines the maximum size of renderable level, must be big enough to cope with actual size of level + view distance at each side // This defines the maximum size of renderable level, must be big enough to cope with actual size of level + view distance at each side
// so that we can render the "infinite" sea at the edges. Currently defined as: // so that we can render the "infinite" sea at the edges. Currently defined as:
@ -3236,21 +3238,44 @@ int LevelRenderer::getGlobalIndexForChunk(int x, int y, int z, Level *level)
int LevelRenderer::getGlobalIndexForChunk(int x, int y, int z, int dimensionId) int LevelRenderer::getGlobalIndexForChunk(int x, int y, int z, int dimensionId)
{ {
int dimIdx = getDimensionIndexFromId(dimensionId); int dimIdx = getDimensionIndexFromId(dimensionId);
int xx = ( x / CHUNK_XZSIZE ) + ( MAX_LEVEL_RENDER_SIZE[dimIdx] / 2 );
int yy = y / CHUNK_SIZE;
int zz = ( z / CHUNK_XZSIZE ) + ( MAX_LEVEL_RENDER_SIZE[dimIdx] / 2 );
if( ( xx < 0 ) || ( xx >= MAX_LEVEL_RENDER_SIZE[dimIdx] ) ) return -1; int yy = y / CHUNK_SIZE;
if( ( zz < 0 ) || ( zz >= MAX_LEVEL_RENDER_SIZE[dimIdx] ) ) return -1;
if( ( yy < 0 ) || ( yy >= CHUNK_Y_COUNT ) ) return -1; if( ( yy < 0 ) || ( yy >= CHUNK_Y_COUNT ) ) return -1;
int dimOffset = DIMENSION_OFFSETS[dimIdx]; if( dimIdx == 0 )
{
int offset = dimOffset; // Offset caused by current dimension // Overworld: use rolling window modulo so the index is always within [0, xChunks*yChunks*zChunks)
offset += ( zz * MAX_LEVEL_RENDER_SIZE[dimIdx] + xx ) * CHUNK_Y_COUNT; // Offset by x/z pos // Chunk::levelRenderer is the singleton LevelRenderer instance.
offset += yy; // Offset by y pos LevelRenderer *lr = Chunk::levelRenderer;
if( lr == NULL || lr->xChunks == 0 ) return -1;
return offset; int sz = lr->xChunks; // xChunks == zChunks
int xx = ((x / CHUNK_XZSIZE) % sz + sz) % sz;
int zz = ((z / CHUNK_XZSIZE) % sz + sz) % sz;
int offset = ( zz * sz + xx ) * CHUNK_Y_COUNT + yy;
return offset;
}
else if( dimIdx == 1 )
{
// Nether: also rolling window (infinite nether with _LARGE_WORLDS)
int sz = MAX_LEVEL_RENDER_SIZE[1]; // = PLAYER_VIEW_DISTANCE * 2
int xx = ((x / CHUNK_XZSIZE) % sz + sz) % sz;
int zz = ((z / CHUNK_XZSIZE) % sz + sz) % sz;
int dimOffset = DIMENSION_OFFSETS[1];
int offset = dimOffset + ( zz * sz + xx ) * CHUNK_Y_COUNT + yy;
return offset;
}
else
{
// End: fixed small world, use centre-offset indexing as before
int sz = MAX_LEVEL_RENDER_SIZE[dimIdx];
int xx = ( x / CHUNK_XZSIZE ) + ( sz / 2 );
int zz = ( z / CHUNK_XZSIZE ) + ( sz / 2 );
if( ( xx < 0 ) || ( xx >= sz ) ) return -1;
if( ( zz < 0 ) || ( zz >= sz ) ) return -1;
int dimOffset = DIMENSION_OFFSETS[dimIdx];
int offset = dimOffset + ( zz * sz + xx ) * CHUNK_Y_COUNT + yy;
return offset;
}
} }
bool LevelRenderer::isGlobalIndexInSameDimension( int idx, Level *level) bool LevelRenderer::isGlobalIndexInSameDimension( int idx, Level *level)
@ -3264,14 +3289,19 @@ bool LevelRenderer::isGlobalIndexInSameDimension( int idx, Level *level)
int LevelRenderer::getGlobalChunkCount() int LevelRenderer::getGlobalChunkCount()
{ {
return ( MAX_LEVEL_RENDER_SIZE[0] * MAX_LEVEL_RENDER_SIZE[0] * CHUNK_Y_COUNT ) + // Overworld uses the rolling view window (xChunks x yChunks x zChunks).
( MAX_LEVEL_RENDER_SIZE[1] * MAX_LEVEL_RENDER_SIZE[1] * CHUNK_Y_COUNT ) + // Nether and End are fixed small sizes.
( MAX_LEVEL_RENDER_SIZE[2] * MAX_LEVEL_RENDER_SIZE[2] * CHUNK_Y_COUNT ); LevelRenderer *lr = Chunk::levelRenderer;
int overworldCount = ( lr && lr->xChunks > 0 ) ? ( lr->xChunks * CHUNK_Y_COUNT * lr->zChunks ) : ( overworldSizeMax * overworldSizeMax * CHUNK_Y_COUNT );
return overworldCount
+ ( MAX_LEVEL_RENDER_SIZE[1] * MAX_LEVEL_RENDER_SIZE[1] * CHUNK_Y_COUNT )
+ ( MAX_LEVEL_RENDER_SIZE[2] * MAX_LEVEL_RENDER_SIZE[2] * CHUNK_Y_COUNT );
} }
int LevelRenderer::getGlobalChunkCountForOverworld() int LevelRenderer::getGlobalChunkCountForOverworld()
{ {
return ( MAX_LEVEL_RENDER_SIZE[0] * MAX_LEVEL_RENDER_SIZE[0] * CHUNK_Y_COUNT ); LevelRenderer *lr = Chunk::levelRenderer;
return ( lr && lr->xChunks > 0 ) ? ( lr->xChunks * CHUNK_Y_COUNT * lr->zChunks ) : ( overworldSizeMax * overworldSizeMax * CHUNK_Y_COUNT );
} }
unsigned char LevelRenderer::getGlobalChunkFlags(int x, int y, int z, Level *level) unsigned char LevelRenderer::getGlobalChunkFlags(int x, int y, int z, Level *level)

View file

@ -10,13 +10,15 @@
#include "..\Minecraft.World\Tile.h" #include "..\Minecraft.World\Tile.h"
#include "..\Minecraft.World\WaterLevelChunk.h" #include "..\Minecraft.World\WaterLevelChunk.h"
// Pack (x,z) chunk coords into a single int64 key
static inline int64_t mpKey(int x, int z) {
return ((int64_t)(unsigned int)x << 32) | (unsigned int)z;
}
MultiPlayerChunkCache::MultiPlayerChunkCache(Level *level) MultiPlayerChunkCache::MultiPlayerChunkCache(Level *level)
{ {
XZSIZE = level->dimension->getXZSize(); // 4J Added // For infinite worlds, m_XZSize is kept for compatibility but the cache is unbounded
XZOFFSET = XZSIZE/2; // 4J Added m_XZSize = level->dimension->getXZSize();
m_XZSize = XZSIZE;
hasData = new bool[XZSIZE * XZSIZE];
memset(hasData, 0, sizeof(bool) * XZSIZE * XZSIZE);
emptyChunk = new EmptyLevelChunk(level, byteArray(16 * 16 * Level::maxBuildHeight), 0, 0); emptyChunk = new EmptyLevelChunk(level, byteArray(16 * 16 * Level::maxBuildHeight), 0, 0);
@ -92,9 +94,8 @@ MultiPlayerChunkCache::MultiPlayerChunkCache(Level *level)
} }
this->level = level; this->level = level;
m_tickCount = 0;
this->cache = new LevelChunk *[XZSIZE * XZSIZE];
memset(this->cache, 0, XZSIZE * XZSIZE * sizeof(LevelChunk *));
InitializeCriticalSectionAndSpinCount(&m_csLoadCreate,4000); InitializeCriticalSectionAndSpinCount(&m_csLoadCreate,4000);
} }
@ -102,13 +103,16 @@ MultiPlayerChunkCache::~MultiPlayerChunkCache()
{ {
delete emptyChunk; delete emptyChunk;
delete waterChunk; delete waterChunk;
delete cache;
delete hasData;
AUTO_VAR(itEnd, loadedChunkList.end()); AUTO_VAR(itEnd, loadedChunkList.end());
for (AUTO_VAR(it, loadedChunkList.begin()); it != itEnd; it++) for (AUTO_VAR(it, loadedChunkList.begin()); it != itEnd; it++)
delete *it; delete *it;
// Delete any chunks still waiting in the deferred-delete queue
for (auto &pd : m_pendingDelete)
delete pd.first;
m_pendingDelete.clear();
DeleteCriticalSection(&m_csLoadCreate); DeleteCriticalSection(&m_csLoadCreate);
} }
@ -122,138 +126,126 @@ bool MultiPlayerChunkCache::hasChunk(int x, int z)
// 4J added - find out if we actually really do have a chunk in our cache // 4J added - find out if we actually really do have a chunk in our cache
bool MultiPlayerChunkCache::reallyHasChunk(int x, int z) bool MultiPlayerChunkCache::reallyHasChunk(int x, int z)
{ {
int ix = x + XZOFFSET; int64_t key = mpKey(x, z);
int iz = z + XZOFFSET; EnterCriticalSection(&m_csLoadCreate);
// Check we're in range of the stored level - if we aren't, then consider that we do have that chunk as we'll be able to use the water chunk there auto it = m_chunkMap.find(key);
if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return true; if (it == m_chunkMap.end() || it->second == NULL)
if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return true;
int idx = ix * XZSIZE + iz;
LevelChunk *chunk = cache[idx];
if( chunk == NULL )
{ {
LeaveCriticalSection(&m_csLoadCreate);
return false; return false;
} }
return hasData[idx]; auto itd = m_hasDataMap.find(key);
bool result = (itd != m_hasDataMap.end() && itd->second);
LeaveCriticalSection(&m_csLoadCreate);
return result;
} }
void MultiPlayerChunkCache::drop(int x, int z) void MultiPlayerChunkCache::drop(int x, int z)
{ {
// 4J Stu - We do want to drop any entities in the chunks, especially for the case when a player is dead as they will // 4J Stu - We do want to drop any entities in the chunks, especially for the case when a player is dead as they will
// not get the RemoveEntity packet if an entity is removed. // not get the RemoveEntity packet if an entity is removed.
LevelChunk *chunk = getChunk(x, z); EnterCriticalSection(&m_csLoadCreate);
if (!chunk->isEmpty()) int64_t key = mpKey(x, z);
auto it = m_chunkMap.find(key);
if (it != m_chunkMap.end() && it->second != NULL)
{ {
// Added parameter here specifies that we don't want to delete tile entities, as they won't get recreated unless they've got update packets LevelChunk *chunk = it->second;
// The tile entities are in general only created on the client by virtue of the chunk rebuild if (!chunk->isEmpty())
chunk->unload(false); {
// Added parameter here specifies that we don't want to delete tile entities, as they won't get recreated unless they've got update packets
// The tile entities are in general only created on the client by virtue of the chunk rebuild
chunk->unload(false);
}
// 4J - We just want to clear out the entities in the chunk, but everything else should be valid // Remove from maps so getChunk() will return emptyChunk for this position
chunk->loaded = true; m_chunkMap.erase(it);
} m_hasDataMap.erase(key);
auto lit = std::find(loadedChunkList.begin(), loadedChunkList.end(), chunk);
if (lit != loadedChunkList.end()) loadedChunkList.erase(lit);
// Defer actual deletion: rebuild threads may still hold a raw LevelChunk* from
// a getChunkAt() call that returned this chunk just before we removed it.
// The chunk stays alive for DELETE_DELAY_TICKS so any in-flight rebuild finishes safely.
m_pendingDelete.push_back(std::make_pair(chunk, m_tickCount + DELETE_DELAY_TICKS));
}
LeaveCriticalSection(&m_csLoadCreate);
} }
LevelChunk *MultiPlayerChunkCache::create(int x, int z) LevelChunk *MultiPlayerChunkCache::create(int x, int z)
{ {
int ix = x + XZOFFSET; int64_t key = mpKey(x, z);
int iz = z + XZOFFSET;
// Check we're in range of the stored level
if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return ( waterChunk ? waterChunk : emptyChunk );
if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return ( waterChunk ? waterChunk : emptyChunk );
int idx = ix * XZSIZE + iz;
LevelChunk *chunk = cache[idx];
LevelChunk *lastChunk = chunk;
if( chunk == NULL ) EnterCriticalSection(&m_csLoadCreate);
{ {
EnterCriticalSection(&m_csLoadCreate); auto it = m_chunkMap.find(key);
if (it != m_chunkMap.end() && it->second != NULL)
//LevelChunk *chunk;
if( g_NetworkManager.IsHost() ) // force here to disable sharing of data
{ {
// 4J-JEV: We are about to use shared data, abort if the server is stopped and the data is deleted. LevelChunk *existing = it->second;
if (MinecraftServer::getInstance()->serverHalted()) return NULL; existing->load();
// If we're the host, then don't create the chunk, share data from the server's copy
#ifdef _LARGE_WORLDS
LevelChunk *serverChunk = MinecraftServer::getInstance()->getLevel(level->dimension->id)->cache->getChunkLoadedOrUnloaded(x,z);
#else
LevelChunk *serverChunk = MinecraftServer::getInstance()->getLevel(level->dimension->id)->cache->getChunk(x,z);
#endif
chunk = new LevelChunk(level, x, z, serverChunk);
// Let renderer know that this chunk has been created - it might have made render data from the EmptyChunk if it got to a chunk before the server sent it
level->setTilesDirty( x * 16 , 0 , z * 16 , x * 16 + 15, 127, z * 16 + 15);
hasData[idx] = true;
}
else
{
// Passing an empty array into the LevelChunk ctor, which it now detects and sets up the chunk as compressed & empty
byteArray bytes;
chunk = new LevelChunk(level, bytes, x, z);
// 4J - changed to use new methods for lighting
chunk->setSkyLightDataAllBright();
// Arrays::fill(chunk->skyLight->data, (byte) 255);
}
chunk->loaded = true;
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
{
// If we're sharing with the server, we'll need to calculate our heightmap now, which isn't shared. If we aren't sharing with the server,
// then this will be calculated when the chunk data arrives.
if( g_NetworkManager.IsHost() )
{
chunk->recalcHeightmapOnly();
}
// Successfully updated the cache
EnterCriticalSection(&m_csLoadCreate);
loadedChunkList.push_back(chunk);
LeaveCriticalSection(&m_csLoadCreate); LeaveCriticalSection(&m_csLoadCreate);
return existing;
} }
else }
LevelChunk *chunk = NULL;
if( g_NetworkManager.IsHost() ) // force here to disable sharing of data
{
// 4J-JEV: We are about to use shared data, abort if the server is stopped and the data is deleted.
if (MinecraftServer::getInstance()->serverHalted())
{ {
// Something else must have updated the cache. Return that chunk and discard this one. This really shouldn't be happening LeaveCriticalSection(&m_csLoadCreate);
// in multiplayer return NULL;
delete chunk;
return cache[idx];
} }
// If we're the host, then don't create the chunk, share data from the server's copy
#ifdef _LARGE_WORLDS
LevelChunk *serverChunk = MinecraftServer::getInstance()->getLevel(level->dimension->id)->cache->getChunkLoadedOrUnloaded(x,z);
#else
LevelChunk *serverChunk = MinecraftServer::getInstance()->getLevel(level->dimension->id)->cache->getChunk(x,z);
#endif
chunk = new LevelChunk(level, x, z, serverChunk);
// Let renderer know that this chunk has been created - it might have made render data from the EmptyChunk if it got to a chunk before the server sent it
level->setTilesDirty( x * 16 , 0 , z * 16 , x * 16 + 15, 127, z * 16 + 15);
m_hasDataMap[key] = true;
} }
else else
{ {
chunk->load(); // Passing an empty array into the LevelChunk ctor, which it now detects and sets up the chunk as compressed & empty
byteArray bytes;
chunk = new LevelChunk(level, bytes, x, z);
// 4J - changed to use new methods for lighting
chunk->setSkyLightDataAllBright();
} }
chunk->loaded = true;
// Insert into hash map
m_chunkMap[key] = chunk;
// If we're sharing with the server, we'll need to calculate our heightmap now, which isn't shared.
if( g_NetworkManager.IsHost() )
{
chunk->recalcHeightmapOnly();
}
loadedChunkList.push_back(chunk);
LeaveCriticalSection(&m_csLoadCreate);
return chunk; return chunk;
} }
LevelChunk *MultiPlayerChunkCache::getChunk(int x, int z) LevelChunk *MultiPlayerChunkCache::getChunk(int x, int z)
{ {
int ix = x + XZOFFSET; EnterCriticalSection(&m_csLoadCreate);
int iz = z + XZOFFSET; auto it = m_chunkMap.find(mpKey(x, z));
// Check we're in range of the stored level LevelChunk *chunk = (it != m_chunkMap.end() && it->second != NULL) ? it->second : NULL;
if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return ( waterChunk ? waterChunk : emptyChunk ); LeaveCriticalSection(&m_csLoadCreate);
if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return ( waterChunk ? waterChunk : emptyChunk ); return chunk != NULL ? chunk : emptyChunk;
int idx = ix * XZSIZE + iz;
LevelChunk *chunk = cache[idx];
if( chunk == NULL )
{
return emptyChunk;
}
else
{
return chunk;
}
} }
bool MultiPlayerChunkCache::save(bool force, ProgressListener *progressListener) bool MultiPlayerChunkCache::save(bool force, ProgressListener *progressListener)
@ -263,6 +255,13 @@ bool MultiPlayerChunkCache::save(bool force, ProgressListener *progressListener)
bool MultiPlayerChunkCache::tick() bool MultiPlayerChunkCache::tick()
{ {
m_tickCount++;
while (!m_pendingDelete.empty() && m_pendingDelete.front().second <= m_tickCount)
{
delete m_pendingDelete.front().first;
m_pendingDelete.pop_front();
}
return false; return false;
} }
@ -296,11 +295,7 @@ wstring MultiPlayerChunkCache::gatherStats()
void MultiPlayerChunkCache::dataReceived(int x, int z) void MultiPlayerChunkCache::dataReceived(int x, int z)
{ {
int ix = x + XZOFFSET; EnterCriticalSection(&m_csLoadCreate);
int iz = z + XZOFFSET; m_hasDataMap[mpKey(x, z)] = true;
// Check we're in range of the stored level LeaveCriticalSection(&m_csLoadCreate);
if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return; }
if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return;
int idx = ix * XZSIZE + iz;
hasData[idx] = true;
}

View file

@ -2,11 +2,14 @@
#include "..\Minecraft.World\net.minecraft.world.level.h" #include "..\Minecraft.World\net.minecraft.world.level.h"
#include "..\Minecraft.World\net.minecraft.world.level.chunk.h" #include "..\Minecraft.World\net.minecraft.world.level.chunk.h"
#include "..\Minecraft.World\RandomLevelSource.h" #include "..\Minecraft.World\RandomLevelSource.h"
#include <unordered_map>
#include <deque>
using namespace std; using namespace std;
class ServerChunkCache; class ServerChunkCache;
// 4J - various alterations here to make this thread safe, and operate as a fixed sized cache // 4J - various alterations here to make this thread safe
// Modified for infinite worlds: flat array cache replaced with unordered_map
class MultiPlayerChunkCache : public ChunkSource class MultiPlayerChunkCache : public ChunkSource
{ {
friend class LevelRenderer; friend class LevelRenderer;
@ -16,13 +19,17 @@ private:
vector<LevelChunk *> loadedChunkList; vector<LevelChunk *> loadedChunkList;
LevelChunk **cache; unordered_map<int64_t, LevelChunk *> m_chunkMap;
unordered_map<int64_t, bool> m_hasDataMap;
// 4J - added for multithreaded support // 4J - added for multithreaded support
CRITICAL_SECTION m_csLoadCreate; CRITICAL_SECTION m_csLoadCreate;
// 4J - size of cache is defined by size of one side - must be even
int XZSIZE; // Deferred deletion: chunks removed from the map but kept alive briefly
int XZOFFSET; // so any in-flight rebuild thread that already obtained the pointer can finish.
bool *hasData; // Each entry stores {chunk, tickAtWhichToDelete}.
static const int DELETE_DELAY_TICKS = 20; // ~1 second at 20 tps
deque<pair<LevelChunk *, int>> m_pendingDelete;
int m_tickCount;
Level *level; Level *level;
@ -43,5 +50,9 @@ public:
virtual TilePos *findNearestMapFeature(Level *level, const wstring &featureName, int x, int y, int z); virtual TilePos *findNearestMapFeature(Level *level, const wstring &featureName, int x, int y, int z);
virtual void dataReceived(int x, int z); // 4J added virtual void dataReceived(int x, int z); // 4J added
virtual LevelChunk **getCache() { return cache; } // 4J added // getCache() is no longer meaningful for infinite worlds; returns NULL
virtual LevelChunk **getCache() { return NULL; }
// Expose loaded chunk list for iteration (infinite-worlds replacement for coordinate scanning)
const vector<LevelChunk *>& getLoadedChunkList() const { return loadedChunkList; }
}; };

View file

@ -30,9 +30,7 @@ MultiPlayerLevel::MultiPlayerLevel(ClientConnection *connection, LevelSettings *
// 4J - this this used to be called in parent ctor via a virtual fn // 4J - this this used to be called in parent ctor via a virtual fn
chunkSource = createChunkSource(); chunkSource = createChunkSource();
// 4J - optimisation - keep direct reference of underlying cache here // chunkSourceCache/chunkSourceXZSize removed: infinite worlds use virtual dispatch
chunkSourceCache = chunkSource->getCache();
chunkSourceXZSize = chunkSource->m_XZSize;
// This also used to be called in parent ctor, but can't be called until chunkSource is created. Call now if required. // This also used to be called in parent ctor, but can't be called until chunkSource is created. Call now if required.
if (!levelData->isInitialized()) if (!levelData->isInitialized())
@ -56,10 +54,8 @@ MultiPlayerLevel::MultiPlayerLevel(ClientConnection *connection, LevelSettings *
{ {
this->savedDataStorage = connection->savedDataStorage; this->savedDataStorage = connection->savedDataStorage;
} }
unshareCheckX = 0; unshareCheckIdx = 0;
unshareCheckZ = 0; compressCheckIdx = 0;
compressCheckX = 0;
compressCheckZ = 0;
// 4J Added, as there are some times when we don't want to add tile updates to the updatesToReset vector // 4J Added, as there are some times when we don't want to add tile updates to the updatesToReset vector
m_bEnableResetChanges = true; m_bEnableResetChanges = true;
@ -156,55 +152,42 @@ void MultiPlayerLevel::tick()
// 4J - added this section. Each tick we'll check a different block, and force it to share data if it has been // 4J - added this section. Each tick we'll check a different block, and force it to share data if it has been
// more than 2 minutes since we last wanted to unshare it. This shouldn't really ever happen, and is added // more than 2 minutes since we last wanted to unshare it. This shouldn't really ever happen, and is added
// here as a safe guard against accumulated memory leaks should a lot of chunks become unshared over time. // here as a safe guard against accumulated memory leaks should a lot of chunks become unshared over time.
int ls = dimension->getXZSize(); // Infinite-worlds fix: iterate loaded chunk list directly instead of scanning up to
// ls=1,875,000 coordinate slots.
// Unshare check: visit one loaded chunk per tick, cycling through all loaded chunks.
if( g_NetworkManager.IsHost() ) if( g_NetworkManager.IsHost() )
{ {
if( Level::reallyHasChunk(unshareCheckX - ( ls / 2), unshareCheckZ - ( ls / 2 ) ) ) const vector<LevelChunk *>& loaded = chunkCache->getLoadedChunkList();
int n = (int)loaded.size();
if( n > 0 )
{ {
LevelChunk *lc = Level::getChunk(unshareCheckX - ( ls / 2), unshareCheckZ - ( ls / 2 )); if( unshareCheckIdx >= n ) unshareCheckIdx = 0;
if( g_NetworkManager.IsHost() ) LevelChunk *lc = loaded[unshareCheckIdx];
{ if( lc )
lc->startSharingTilesAndData(1000 * 60 * 2); lc->startSharingTilesAndData(1000 * 60 * 2);
} unshareCheckIdx = ( unshareCheckIdx + 1 ) % n;
}
unshareCheckX++;
if( unshareCheckX >= ls )
{
unshareCheckX = 0;
unshareCheckZ++;
if( unshareCheckZ >= ls )
{
unshareCheckZ = 0;
}
} }
} }
// 4J added - also similar thing tosee if we can compress the lighting in any of these chunks. This is slightly different // 4J added - also similar thing to see if we can compress the lighting in any of these chunks.
// as it does try to make sure that at least one chunk has something done to it. // Infinite-worlds fix: step through loadedChunkList (O(1) per tick) instead of scanning
// a 1,875,000-wide coordinate grid cuz thats stupid.
// At most loop round at least one row the chunks, so we should be able to at least find a non-empty chunk to do something with in 2.7 seconds of ticks, and process the whole thing in about 2.4 minutes.
for( int i = 0; i < ls; i++ )
{ {
compressCheckX++; const vector<LevelChunk *>& loaded = chunkCache->getLoadedChunkList();
if( compressCheckX >= ls ) int n = (int)loaded.size();
if( n > 0 )
{ {
compressCheckX = 0; if( compressCheckIdx >= n ) compressCheckIdx = 0;
compressCheckZ++; LevelChunk *lc = loaded[compressCheckIdx];
if( compressCheckZ >= ls ) if( lc )
{ {
compressCheckZ = 0; lc->compressLighting();
lc->compressBlocks();
lc->compressData();
} }
} compressCheckIdx = ( compressCheckIdx + 1 ) % n;
if( Level::reallyHasChunk(compressCheckX - ( ls / 2), compressCheckZ - ( ls / 2 ) ) )
{
LevelChunk *lc = Level::getChunk(compressCheckX - ( ls / 2), compressCheckZ - ( ls / 2 ));
lc->compressLighting();
lc->compressBlocks();
lc->compressData();
break;
} }
} }

View file

@ -30,10 +30,8 @@ public:
void enableResetChanges(bool enable) { m_bEnableResetChanges = enable; } // 4J Added void enableResetChanges(bool enable) { m_bEnableResetChanges = enable; } // 4J Added
private: private:
int unshareCheckX; // 4J - added int unshareCheckIdx; // 4J - index into loadedChunkList for unshare cycling (infinite-worlds fix)
int unshareCheckZ; // 4J - added int compressCheckIdx; // 4J - index into loadedChunkList for compress cycling (infinite-worlds fix)
int compressCheckX; // 4J - added
int compressCheckZ; // 4J - added
vector<ClientConnection *> connections; // 4J Stu - Made this a vector as we can have more than one local connection vector<ClientConnection *> connections; // 4J Stu - Made this a vector as we can have more than one local connection
MultiPlayerChunkCache *chunkCache; MultiPlayerChunkCache *chunkCache;
Minecraft *minecraft; Minecraft *minecraft;

View file

@ -14,9 +14,6 @@
ServerChunkCache::ServerChunkCache(ServerLevel *level, ChunkStorage *storage, ChunkSource *source) ServerChunkCache::ServerChunkCache(ServerLevel *level, ChunkStorage *storage, ChunkSource *source)
{ {
XZSIZE = source->m_XZSize; // 4J Added
XZOFFSET = XZSIZE/2; // 4J Added
autoCreate = false; // 4J added autoCreate = false; // 4J added
emptyChunk = new EmptyLevelChunk(level, byteArray( Level::CHUNK_TILE_COUNT ), 0, 0); emptyChunk = new EmptyLevelChunk(level, byteArray( Level::CHUNK_TILE_COUNT ), 0, 0);
@ -24,16 +21,10 @@ ServerChunkCache::ServerChunkCache(ServerLevel *level, ChunkStorage *storage, Ch
this->level = level; this->level = level;
this->storage = storage; this->storage = storage;
this->source = source; this->source = source;
// For infinite worlds, m_XZSize is no longer used for cache sizing.
// Keep it at a large value so code that reads it (e.g. dimension getXZSize) still works.
this->m_XZSize = source->m_XZSize; 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); InitializeCriticalSectionAndSpinCount(&m_csLoadCreate,4000);
} }
@ -41,15 +32,12 @@ ServerChunkCache::ServerChunkCache(ServerLevel *level, ChunkStorage *storage, Ch
ServerChunkCache::~ServerChunkCache() ServerChunkCache::~ServerChunkCache()
{ {
delete emptyChunk; delete emptyChunk;
delete cache;
delete source; delete source;
#ifdef _LARGE_WORLDS #ifdef _LARGE_WORLDS
for(unsigned int i = 0; i < XZSIZE * XZSIZE; ++i) for (auto &kv : m_unloadedMap)
{ delete kv.second;
delete m_unloadedCache[i]; m_unloadedMap.clear();
}
delete m_unloadedCache;
#endif #endif
AUTO_VAR(itEnd, m_loadedChunkList.end()); AUTO_VAR(itEnd, m_loadedChunkList.end());
@ -60,18 +48,12 @@ ServerChunkCache::~ServerChunkCache()
bool ServerChunkCache::hasChunk(int x, int z) bool ServerChunkCache::hasChunk(int x, int z)
{ {
int ix = x + XZOFFSET; // Infinite worlds: any coordinate is valid; check if chunk is loaded
int iz = z + XZOFFSET; EnterCriticalSection(&m_csLoadCreate);
// Check we're in range of the stored level auto it = m_chunkMap.find(chunkKey(x, z));
// 4J Stu - Request for chunks outside the range always return an emptyChunk, so just return true here to say we have it bool result = (it != m_chunkMap.end() && it->second != NULL);
// If we return false entities less than 2 chunks from the edge do not tick properly due to them requiring a certain radius LeaveCriticalSection(&m_csLoadCreate);
// of chunks around them when they tick return result;
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() vector<LevelChunk *> *ServerChunkCache::getLoadedChunkList()
@ -81,40 +63,12 @@ vector<LevelChunk *> *ServerChunkCache::getLoadedChunkList()
void ServerChunkCache::drop(int x, int z) 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 #ifdef _LARGE_WORLDS
int64_t key = chunkKey(x, z);
bool canDrop = false; auto it = m_chunkMap.find(key);
// if (level->dimension->mayRespawn()) if (it != m_chunkMap.end() && it->second != NULL)
// {
// 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; m_toDrop.push_back(it->second);
}
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 #endif
} }
@ -137,108 +91,71 @@ LevelChunk *ServerChunkCache::create(int x, int z)
LevelChunk *ServerChunkCache::create(int x, int z, bool asyncPostProcess) // 4J - added extra parameter LevelChunk *ServerChunkCache::create(int x, int z, bool asyncPostProcess) // 4J - added extra parameter
{ {
int ix = x + XZOFFSET; int64_t key = chunkKey(x, z);
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]; EnterCriticalSection(&m_csLoadCreate);
LevelChunk *lastChunk = chunk;
if( ( chunk == NULL ) || ( chunk->x != x ) || ( chunk->z != z ) ) // Check under lock
{ {
EnterCriticalSection(&m_csLoadCreate); auto it = m_chunkMap.find(key);
chunk = load(x, z); if (it != m_chunkMap.end() && it->second != NULL)
if (chunk == NULL)
{ {
if (source == NULL) LevelChunk *existing = it->second;
{
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); LeaveCriticalSection(&m_csLoadCreate);
return existing;
} }
}
LevelChunk *chunk = load(x, z);
if (chunk == NULL)
{
if (source == NULL)
{
chunk = emptyChunk;
}
else else
{ {
// Something else must have updated the cache. Return that chunk and discard this one chunk = source->getChunk(x, z);
chunk->unload(true); }
delete chunk;
return cache[idx];
}
} }
if (chunk != NULL)
{
chunk->load();
}
m_chunkMap[key] = chunk;
// 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.
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.
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);
#ifdef __PS3__ #ifdef __PS3__
Sleep(1); Sleep(1);
@ -251,24 +168,14 @@ LevelChunk *ServerChunkCache::create(int x, int z, bool asyncPostProcess) // 4J
// This is used when sharing server chunk data on the main thread // This is used when sharing server chunk data on the main thread
LevelChunk *ServerChunkCache::getChunk(int x, int z) LevelChunk *ServerChunkCache::getChunk(int x, int z)
{ {
int ix = x + XZOFFSET; EnterCriticalSection(&m_csLoadCreate);
int iz = z + XZOFFSET; auto it = m_chunkMap.find(chunkKey(x, z));
// Check we're in range of the stored level LevelChunk *chunk = (it != m_chunkMap.end() && it->second != NULL) ? it->second : NULL;
if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return emptyChunk; LeaveCriticalSection(&m_csLoadCreate);
if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return emptyChunk;
int idx = ix * XZSIZE + iz;
LevelChunk *lc = cache[idx]; if (chunk != NULL) return chunk;
if( lc ) if (level->isFindingSpawn || autoCreate)
{
return lc;
}
if( level->isFindingSpawn || autoCreate )
{
return create(x, z); return create(x, z);
}
return emptyChunk; return emptyChunk;
} }
@ -279,29 +186,28 @@ LevelChunk *ServerChunkCache::getChunk(int x, int z)
// 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 // 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) LevelChunk *ServerChunkCache::getChunkLoadedOrUnloaded(int x, int z)
{ {
int ix = x + XZOFFSET; int64_t key = chunkKey(x, z);
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]; EnterCriticalSection(&m_csLoadCreate);
if( lc ) auto it = m_chunkMap.find(key);
if (it != m_chunkMap.end() && it->second != NULL)
{ {
return lc; LevelChunk *chunk = it->second;
LeaveCriticalSection(&m_csLoadCreate);
return chunk;
} }
lc = m_unloadedCache[idx]; auto it2 = m_unloadedMap.find(key);
if( lc ) if (it2 != m_unloadedMap.end() && it2->second != NULL)
{ {
return lc; LevelChunk *chunk = it2->second;
} LeaveCriticalSection(&m_csLoadCreate);
return chunk;
}
LeaveCriticalSection(&m_csLoadCreate);
if( level->isFindingSpawn || autoCreate ) if( level->isFindingSpawn || autoCreate )
{
return create(x, z); return create(x, z);
}
return emptyChunk; return emptyChunk;
} }
@ -311,7 +217,7 @@ LevelChunk *ServerChunkCache::getChunkLoadedOrUnloaded(int x, int z)
#ifdef _LARGE_WORLDS #ifdef _LARGE_WORLDS
void ServerChunkCache::dontDrop(int x, int z) void ServerChunkCache::dontDrop(int x, int z)
{ {
LevelChunk *chunk = getChunk(x,z); LevelChunk *chunk = getChunk(x, z);
m_toDrop.erase(std::remove(m_toDrop.begin(), m_toDrop.end(), chunk), m_toDrop.end()); m_toDrop.erase(std::remove(m_toDrop.begin(), m_toDrop.end(), chunk), m_toDrop.end());
} }
#endif #endif
@ -323,12 +229,15 @@ LevelChunk *ServerChunkCache::load(int x, int z)
LevelChunk *levelChunk = NULL; LevelChunk *levelChunk = NULL;
#ifdef _LARGE_WORLDS #ifdef _LARGE_WORLDS
int ix = x + XZOFFSET; // Check the in-memory unloaded cache first before going to disk
int iz = z + XZOFFSET; int64_t key = chunkKey(x, z);
int idx = ix * XZSIZE + iz; auto it = m_unloadedMap.find(key);
levelChunk = m_unloadedCache[idx]; if (it != m_unloadedMap.end())
m_unloadedCache[idx] = NULL; {
if(levelChunk == NULL) levelChunk = it->second;
m_unloadedMap.erase(it);
}
if (levelChunk == NULL)
#endif #endif
{ {
levelChunk = storage->load(level, x, z); levelChunk = storage->load(level, x, z);
@ -484,31 +393,7 @@ void ServerChunkCache::postProcess(ChunkSource *parent, int x, int z )
// chunks exist as that's determined before post-processing can even run // chunks exist as that's determined before post-processing can even run
chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromHere; chunk->terrainPopulated |= LevelChunk::sTerrainPopulatedFromHere;
// If we are an edge chunk, fill in missing flags from sides that will never post-process // Infinite worlds: no fixed world edges, so no edge-chunk special-casing needed.
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 // 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. // a full set of post-processing flags for one of these neighbours.
@ -866,20 +751,19 @@ bool ServerChunkCache::tick()
LevelChunk *chunk = m_toDrop.front(); LevelChunk *chunk = m_toDrop.front();
if(!chunk->isUnloaded()) if(!chunk->isUnloaded())
{ {
save(chunk); save(chunk);
saveEntities(chunk); saveEntities(chunk);
chunk->unload(true); chunk->unload(true);
//loadedChunks.remove(cp); EnterCriticalSection(&m_csLoadCreate);
//loadedChunkList.remove(chunk); AUTO_VAR(it, std::find( m_loadedChunkList.begin(), m_loadedChunkList.end(), chunk) );
AUTO_VAR(it, std::find( m_loadedChunkList.begin(), m_loadedChunkList.end(), chunk) ); if(it != m_loadedChunkList.end()) m_loadedChunkList.erase(it);
if(it != m_loadedChunkList.end()) m_loadedChunkList.erase(it);
int ix = chunk->x + XZOFFSET; int64_t key = chunkKey(chunk->x, chunk->z);
int iz = chunk->z + XZOFFSET; // Move from live map to unloaded map; data stays in RAM
int idx = ix * XZSIZE + iz; m_unloadedMap[key] = chunk;
m_unloadedCache[idx] = chunk; m_chunkMap.erase(key);
cache[idx] = NULL; LeaveCriticalSection(&m_csLoadCreate);
} }
m_toDrop.pop_front(); m_toDrop.pop_front();
} }

View file

@ -5,9 +5,15 @@
#include "..\Minecraft.World\JavaIntHash.h" #include "..\Minecraft.World\JavaIntHash.h"
#include "..\Minecraft.World\RandomLevelSource.h" #include "..\Minecraft.World\RandomLevelSource.h"
#include "..\Minecraft.World\C4JThread.h" #include "..\Minecraft.World\C4JThread.h"
#include <unordered_map>
using namespace std; using namespace std;
class ServerLevel; class ServerLevel;
// Helper: pack (x,z) chunk coords into a single int64 key
static inline int64_t chunkKey(int x, int z) {
return ((int64_t)(unsigned int)x << 32) | (unsigned int)z;
}
class ServerChunkCache : public ChunkSource class ServerChunkCache : public ChunkSource
{ {
@ -20,20 +26,19 @@ private:
public: public:
bool autoCreate; bool autoCreate;
private: private:
LevelChunk **cache; // Infinite-world chunk cache: maps packed (x,z) -> LevelChunk*
unordered_map<int64_t, LevelChunk *> m_chunkMap;
// Secondary map for unloaded-but-retained chunks (_LARGE_WORLDS streaming)
unordered_map<int64_t, LevelChunk *> m_unloadedMap;
vector<LevelChunk *> m_loadedChunkList; vector<LevelChunk *> m_loadedChunkList;
ServerLevel *level; ServerLevel *level;
#ifdef _LARGE_WORLDS #ifdef _LARGE_WORLDS
deque<LevelChunk *> m_toDrop; deque<LevelChunk *> m_toDrop;
LevelChunk **m_unloadedCache;
#endif #endif
// 4J - added for multithreaded support // 4J - added for multithreaded support
CRITICAL_SECTION m_csLoadCreate; CRITICAL_SECTION m_csLoadCreate;
// 4J - size of cache is defined by size of one side - must be even
int XZSIZE;
int XZOFFSET;
public: public:
ServerChunkCache(ServerLevel *level, ChunkStorage *storage, ChunkSource *source); ServerChunkCache(ServerLevel *level, ChunkStorage *storage, ChunkSource *source);
@ -48,7 +53,8 @@ public:
#ifdef _LARGE_WORLDS #ifdef _LARGE_WORLDS
LevelChunk *getChunkLoadedOrUnloaded(int x, int z); // 4J added LevelChunk *getChunkLoadedOrUnloaded(int x, int z); // 4J added
#endif #endif
virtual LevelChunk **getCache() { return cache; } // 4J added // getCache() is no longer meaningful for infinite worlds; returns NULL
virtual LevelChunk **getCache() { return NULL; }
// 4J-JEV Added; Remove chunk from the toDrop queue. // 4J-JEV Added; Remove chunk from the toDrop queue.
#ifdef _LARGE_WORLDS #ifdef _LARGE_WORLDS

View file

@ -101,9 +101,7 @@ ServerLevel::ServerLevel(MinecraftServer *server, shared_ptr<LevelStorage>levelS
// 4J - this this used to be called in parent ctor via a virtual fn // 4J - this this used to be called in parent ctor via a virtual fn
chunkSource = createChunkSource(); chunkSource = createChunkSource();
// 4J - optimisation - keep direct reference of underlying cache here // chunkSourceCache/chunkSourceXZSize removed: infinite worlds use virtual dispatch
chunkSourceCache = chunkSource->getCache();
chunkSourceXZSize = chunkSource->m_XZSize;
// 4J - The listener used to be added in MinecraftServer::loadLevel but we need it to be set up before we do the next couple of things, or else chunks get loaded before we have the entity tracker set up to listen to them // 4J - The listener used to be added in MinecraftServer::loadLevel but we need it to be set up before we do the next couple of things, or else chunks get loaded before we have the entity tracker set up to listen to them
this->server = server; this->server = server;
@ -758,8 +756,8 @@ void ServerLevel::setInitialSpawn(LevelSettings *levelSettings)
int xSpawn = 0; // (Level.MAX_LEVEL_SIZE - 100) * 0; int xSpawn = 0; // (Level.MAX_LEVEL_SIZE - 100) * 0;
int ySpawn = dimension->getSpawnYPosition(); int ySpawn = dimension->getSpawnYPosition();
int zSpawn = 0; // (Level.MAX_LEVEL_SIZE - 100) * 0; int zSpawn = 0; // (Level.MAX_LEVEL_SIZE - 100) * 0;
int minXZ = - (dimension->getXZSize() * 16 ) / 2; int minXZ = -Level::MAX_LEVEL_SIZE;
int maxXZ = (dimension->getXZSize() * 16 ) / 2 - 1; int maxXZ = Level::MAX_LEVEL_SIZE - 1;
if (findBiome != NULL) if (findBiome != NULL)
{ {

View file

@ -65,8 +65,8 @@ ServerPlayer::ServerPlayer(MinecraftServer *server, Level *level, const wstring&
int attemptCount = 0; int attemptCount = 0;
int xx2, yy2, zz2; int xx2, yy2, zz2;
int minXZ = - (level->dimension->getXZSize() * 16 ) / 2; int minXZ = -Level::MAX_LEVEL_SIZE;
int maxXZ = (level->dimension->getXZSize() * 16 ) / 2 - 1; int maxXZ = Level::MAX_LEVEL_SIZE - 1;
bool playerNear = false; bool playerNear = false;
do do

View file

@ -4,10 +4,13 @@
class ProgressListener; class ProgressListener;
class TilePos; class TilePos;
// The maximum number of chunks that we can store // The maximum number of chunks for the world.
// For infinite worlds (_LARGE_WORLDS), this is kept at a very large value
// so that structure generation code (villages, strongholds, etc.) still works.
// The actual chunk cache is now unbounded (hash map); this constant is only
// used for structure placement limits and similar range checks.
#ifdef _LARGE_WORLDS #ifdef _LARGE_WORLDS
// 4J Stu - Our default map (at zoom level 3) is 1024x1024 blocks (or 64 chunks) #define LEVEL_MAX_WIDTH (1875000) // 30,000,000 blocks / 16 = effectively infinite kinda :3
#define LEVEL_MAX_WIDTH (5*64) //(6*54)
#else #else
#define LEVEL_MAX_WIDTH 54 #define LEVEL_MAX_WIDTH 54
#endif #endif

View file

@ -160,64 +160,16 @@ void CustomLevelSource::prepareHeights(int xOffs, int zOffs, byteArray blocks)
int mapHeight = m_heightmapOverride[mapIndex]; int mapHeight = m_heightmapOverride[mapIndex];
waterHeight = m_waterheightOverride[mapIndex]; waterHeight = m_waterheightOverride[mapIndex];
//app.DebugPrintf("MapHeight = %d, y = %d\n", mapHeight, yc * CHUNK_HEIGHT + y); //app.DebugPrintf("MapHeight = %d, y = %d\n", mapHeight, yc * CHUNK_HEIGHT + y);
/////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////
// 4J - add this chunk of code to make land "fall-off" at the edges of int tileId = 0;
// a finite world - size of that world is currently hard-coded in here if (yc * CHUNK_HEIGHT + y < mapHeight)
const int worldSize = m_XZSize * 16; {
const int falloffStart = 32; // chunks away from edge were we start doing fall-off tileId = (byte) Tile::rock_Id;
const float falloffMax = 128.0f; // max value we need to get to falloff by the edge of the map }
else if (yc * CHUNK_HEIGHT + y < waterHeight)
int xxx = ( ( xOffs * 16 ) + x + ( xc * CHUNK_WIDTH ) ); {
int zzz = ( ( zOffs * 16 ) + z + ( zc * CHUNK_WIDTH ) ); tileId = (byte) Tile::calmWater_Id;
}
// Get distance to edges of world in x
int xxx0 = xxx + ( worldSize / 2 );
if( xxx0 < 0 ) xxx0 = 0;
int xxx1 = ( ( worldSize / 2 ) - 1 ) - xxx;
if( xxx1 < 0 ) xxx1 = 0;
// Get distance to edges of world in z
int zzz0 = zzz + ( worldSize / 2 );
if( zzz0 < 0 ) zzz0 = 0;
int zzz1 = ( ( worldSize / 2 ) - 1 ) - zzz;
if( zzz1 < 0 ) zzz1 = 0;
// Get min distance to any edge
int emin = xxx0;
if (xxx1 < emin ) emin = xxx1;
if (zzz0 < emin ) emin = zzz0;
if (zzz1 < emin ) emin = zzz1;
float comp = 0.0f;
// Calculate how much we want the world to fall away, if we're in the defined region to do so
if( emin < falloffStart )
{
int falloff = falloffStart - emin;
comp = ((float)falloff / (float)falloffStart ) * falloffMax;
}
// 4J - end of extra code
///////////////////////////////////////////////////////////////////
int tileId = 0;
// 4J - this comparison used to just be with 0.0f but is now varied by block above
if (yc * CHUNK_HEIGHT + y < mapHeight)
{
tileId = (byte) Tile::rock_Id;
}
else if (yc * CHUNK_HEIGHT + y < waterHeight)
{
tileId = (byte) Tile::calmWater_Id;
}
// 4J - more extra code to make sure that the column at the edge of the world is just water & rock, to match the infinite sea that
// continues on after the edge of the world.
if( emin == 0 )
{
// This matches code in MultiPlayerChunkCache that makes the geometry which continues at the edge of the world
if( yc * CHUNK_HEIGHT + y <= ( level->getSeaLevel() - 10 ) ) tileId = Tile::rock_Id;
else if( yc * CHUNK_HEIGHT + y < level->getSeaLevel() ) tileId = Tile::calmWater_Id;
}
int indexY = (yc * CHUNK_HEIGHT + y); int indexY = (yc * CHUNK_HEIGHT + y);
int offsAdjustment = 0; int offsAdjustment = 0;

View file

@ -138,9 +138,10 @@ void Fireball::tick()
} }
else else
{ {
// 4J-PB - TU9 bug fix - fireballs can hit the edge of the world, and stay there // 4J-PB - TU9 bug fix - fireballs can hit the edge of the world, and stay there
int minXZ = - (level->dimension->getXZSize() * 16 ) / 2; // Use MAX_LEVEL_SIZE for infinite world support
int maxXZ = (level->dimension->getXZSize() * 16 ) / 2 - 1; int minXZ = -Level::MAX_LEVEL_SIZE;
int maxXZ = Level::MAX_LEVEL_SIZE - 1;
if ((x<=minXZ) || (x>=maxXZ) || (z<=minXZ) || (z>=maxXZ)) if ((x<=minXZ) || (x>=maxXZ) || (z<=minXZ) || (z>=maxXZ))
{ {

View file

@ -1273,19 +1273,8 @@ int Level::getBrightnessPropagate(LightLayer::variety layer, int x, int y, int z
int Level::getBrightness(LightLayer::variety layer, int x, int y, int z) int Level::getBrightness(LightLayer::variety layer, int x, int y, int z)
{ {
// 4J - optimised. Not doing checks on x/z that are no longer necessary, and directly checking the cache within // Infinite worlds: use virtual dispatch through chunkSource (no fixed-size flat array cache)
// the ServerChunkCache/MultiplayerChunkCache rather than going through wrappers & virtual functions. LevelChunk *c = chunkSource->getChunk(x >> 4, z >> 4);
int xc = x >> 4;
int zc = z >> 4;
int ix = xc + (chunkSourceXZSize/2);
int iz = zc + (chunkSourceXZSize/2);
if( ( ix < 0 ) || ( ix >= chunkSourceXZSize ) ) return 0;
if( ( iz < 0 ) || ( iz >= chunkSourceXZSize ) ) return 0;
int idx = ix * chunkSourceXZSize + iz;
LevelChunk *c = chunkSourceCache[idx];
if( c == NULL ) return (int)layer; if( c == NULL ) return (int)layer;
if (y < 0) y = 0; if (y < 0) y = 0;
@ -1294,7 +1283,7 @@ int Level::getBrightness(LightLayer::variety layer, int x, int y, int z)
return c->getBrightness(layer, x & 15, y, z & 15); return c->getBrightness(layer, x & 15, y, z & 15);
} }
// 4J added as optimisation - if all the neighbouring brightesses are going to be in the one chunk, just get // 4J added as optimisation - if all the neighbouring brightnesses are going to be in the one chunk, just get
// the level chunk once // the level chunk once
void Level::getNeighbourBrightnesses(int *brightnesses, LightLayer::variety layer, int x, int y, int z) void Level::getNeighbourBrightnesses(int *brightnesses, LightLayer::variety layer, int x, int y, int z)
{ {
@ -1302,7 +1291,7 @@ void Level::getNeighbourBrightnesses(int *brightnesses, LightLayer::variety laye
( ( ( z & 15 ) == 0 ) || ( ( z & 15 ) == 15 ) ) || ( ( ( z & 15 ) == 0 ) || ( ( z & 15 ) == 15 ) ) ||
( ( y <= 0 ) || ( y >= 127 ) ) ) ( ( y <= 0 ) || ( y >= 127 ) ) )
{ {
// We're spanning more than one chunk, just fall back on original java method here // We're spanning more than one chunk, just fall back on original method here
brightnesses[0] = getBrightness(layer, x - 1, y, z); brightnesses[0] = getBrightness(layer, x - 1, y, z);
brightnesses[1] = getBrightness(layer, x + 1, y, z); brightnesses[1] = getBrightness(layer, x + 1, y, z);
brightnesses[2] = getBrightness(layer, x, y - 1, z); brightnesses[2] = getBrightness(layer, x, y - 1, z);
@ -1312,42 +1301,17 @@ void Level::getNeighbourBrightnesses(int *brightnesses, LightLayer::variety laye
} }
else else
{ {
// All in one chunk - just get the chunk once, and do a single call to get the results // All in one chunk - get the chunk once via virtual dispatch
int xc = x >> 4; LevelChunk *c = chunkSource->getChunk(x >> 4, z >> 4);
int zc = z >> 4;
int ix = xc + (chunkSourceXZSize/2);
int iz = zc + (chunkSourceXZSize/2);
// 4J Stu - The java LightLayer was an enum class type with a member "surrounding" which is what we
// were returning here. Surrounding has the same value as the enum value in our C++ code, so just cast
// it to an int
if( ( ( ix < 0 ) || ( ix >= chunkSourceXZSize ) ) ||
( ( iz < 0 ) || ( iz >= chunkSourceXZSize ) ) )
{
for( int i = 0; i < 6; i++ )
{
brightnesses[i] = (int)layer;
}
return;
}
int idx = ix * chunkSourceXZSize + iz;
LevelChunk *c = chunkSourceCache[idx];
// 4J Stu - The java LightLayer was an enum class type with a member "surrounding" which is what we
// were returning here. Surrounding has the same value as the enum value in our C++ code, so just cast
// it to an int
if( c == NULL ) if( c == NULL )
{ {
for( int i = 0; i < 6; i++ ) for( int i = 0; i < 6; i++ )
{
brightnesses[i] = (int)layer; brightnesses[i] = (int)layer;
}
return; return;
} }
// Single call to the levelchunk too to avoid overhead of virtual fn calls // Single call to the levelchunk to get all 6 neighbour brightnesses
c->getNeighbourBrightnesses(brightnesses, layer, x & 15, y, z & 15); c->getNeighbourBrightnesses(brightnesses, layer, x & 15, y, z & 15);
} }
} }
@ -1833,8 +1797,8 @@ AABBList *Level::getCubes(shared_ptr<Entity> source, AABB *box, bool noEntities,
int z0 = Mth::floor(box->z0); int z0 = Mth::floor(box->z0);
int z1 = Mth::floor(box->z1 + 1); int z1 = Mth::floor(box->z1 + 1);
int maxxz = ( dimension->getXZSize() * 16 ) / 2; int maxxz = MAX_LEVEL_SIZE;
int minxz = -maxxz; int minxz = -MAX_LEVEL_SIZE;
for (int x = x0; x < x1; x++) for (int x = x0; x < x1; x++)
for (int z = z0; z < z1; z++) for (int z = z0; z < z1; z++)
{ {
@ -3466,14 +3430,6 @@ void Level::checkLight(LightLayer::variety layer, int xc, int yc, int zc, bool f
//int darktcc = 0; //int darktcc = 0;
// 4J - added
int minXZ = - (dimension->getXZSize() * 16 ) / 2;
int maxXZ = (dimension->getXZSize() * 16 ) / 2 - 1;
if( ( xc > maxXZ ) || ( xc < minXZ ) || ( zc > maxXZ ) || ( zc < minXZ ) )
{
LeaveCriticalSection(&m_checkLightCS);
return;
}
// Lock 128K of cache (containing all the lighting cache + first 112K of toCheck array) on L2 to try and stop any cached data getting knocked out of L2 by other non-cached reads (or vice-versa) // Lock 128K of cache (containing all the lighting cache + first 112K of toCheck array) on L2 to try and stop any cached data getting knocked out of L2 by other non-cached reads (or vice-versa)
// if( cache ) XLockL2(XLOCKL2_INDEX_TITLE, cache, 128 * 1024, XLOCKL2_LOCK_SIZE_1_WAY, 0 ); // if( cache ) XLockL2(XLOCKL2_INDEX_TITLE, cache, 128 * 1024, XLOCKL2_LOCK_SIZE_1_WAY, 0 );
@ -3587,9 +3543,6 @@ void Level::checkLight(LightLayer::variety layer, int xc, int yc, int zc, bool f
int xx = x + ((j / 2) % 3 / 2) * flip; int xx = x + ((j / 2) % 3 / 2) * flip;
int yy = y + ((j / 2 + 1) % 3 / 2) * flip; int yy = y + ((j / 2 + 1) % 3 / 2) * flip;
int zz = z + ((j / 2 + 2) % 3 / 2) * flip; int zz = z + ((j / 2 + 2) % 3 / 2) * flip;
// 4J - added - don't let this lighting creep out of the normal fixed world and into the infinite water chunks beyond
if( ( xx > maxXZ ) || ( xx < minXZ ) || ( zz > maxXZ ) || ( zz < minXZ ) ) continue;
if( ( yy < 0 ) || ( yy >= maxBuildHeight ) ) continue; if( ( yy < 0 ) || ( yy >= maxBuildHeight ) ) continue;
o = getBrightnessCached(cache, layer, xx, yy, zz); o = getBrightnessCached(cache, layer, xx, yy, zz);
@ -3678,13 +3631,13 @@ void Level::checkLight(LightLayer::variety layer, int xc, int yc, int zc, bool f
if (zd < 0) zd = -zd; if (zd < 0) zd = -zd;
if (xd + yd + zd < 17 && tcc < (32 * 32 * 32) - 6) // 4J - 32 * 32 * 32 was toCheck.length if (xd + yd + zd < 17 && tcc < (32 * 32 * 32) - 6) // 4J - 32 * 32 * 32 was toCheck.length
{ {
// 4J - added extra checks here to stop lighting updates moving out of the actual fixed world and into the infinite water chunks // Infinite worlds: propagate lighting in all directions without boundary restrictions
if( ( x - 1 ) >= minXZ ) { if (getBrightnessCached(cache, layer, x - 1, y, z) < expected) toCheck[tcc++] = (((x - 1 - xc) + 32)) + (((y - yc) + 32) << 6) + (((z - zc) + 32) << 12); } if (getBrightnessCached(cache, layer, x - 1, y, z) < expected) toCheck[tcc++] = (((x - 1 - xc) + 32)) + (((y - yc) + 32) << 6) + (((z - zc) + 32) << 12);
if( ( x + 1 ) <= maxXZ ) { if (getBrightnessCached(cache, layer, x + 1, y, z) < expected) toCheck[tcc++] = (((x + 1 - xc) + 32)) + (((y - yc) + 32) << 6) + (((z - zc) + 32) << 12); } if (getBrightnessCached(cache, layer, x + 1, y, z) < expected) toCheck[tcc++] = (((x + 1 - xc) + 32)) + (((y - yc) + 32) << 6) + (((z - zc) + 32) << 12);
if( ( y - 1 ) >= 0 ) { if (getBrightnessCached(cache, layer, x, y - 1, z) < expected) toCheck[tcc++] = (((x - xc) + 32)) + (((y - 1 - yc) + 32) << 6) + (((z - zc) + 32) << 12); } if( ( y - 1 ) >= 0 ) { if (getBrightnessCached(cache, layer, x, y - 1, z) < expected) toCheck[tcc++] = (((x - xc) + 32)) + (((y - 1 - yc) + 32) << 6) + (((z - zc) + 32) << 12); }
if( ( y + 1 ) < maxBuildHeight ) { if (getBrightnessCached(cache, layer, x, y + 1, z) < expected) toCheck[tcc++] = (((x - xc) + 32)) + (((y + 1 - yc) + 32) << 6) + (((z - zc) + 32) << 12); } if( ( y + 1 ) < maxBuildHeight ) { if (getBrightnessCached(cache, layer, x, y + 1, z) < expected) toCheck[tcc++] = (((x - xc) + 32)) + (((y + 1 - yc) + 32) << 6) + (((z - zc) + 32) << 12); }
if( ( z - 1 ) >= minXZ ) { if (getBrightnessCached(cache, layer, x, y, z - 1) < expected) toCheck[tcc++] = (((x - xc) + 32)) + (((y - yc) + 32) << 6) + (((z - 1 - zc) + 32) << 12); } if (getBrightnessCached(cache, layer, x, y, z - 1) < expected) toCheck[tcc++] = (((x - xc) + 32)) + (((y - yc) + 32) << 6) + (((z - 1 - zc) + 32) << 12);
if( ( z + 1 ) <= maxXZ ) { if (getBrightnessCached(cache, layer, x, y, z + 1) < expected) toCheck[tcc++] = (((x - xc) + 32)) + (((y - yc) + 32) << 6) + (((z + 1 - zc) + 32) << 12); } if (getBrightnessCached(cache, layer, x, y, z + 1) < expected) toCheck[tcc++] = (((x - xc) + 32)) + (((y - yc) + 32) << 6) + (((z + 1 - zc) + 32) << 12);
} }
} }
} }

View file

@ -498,9 +498,7 @@ public:
__int64 m_timeOfDayOverride; __int64 m_timeOfDayOverride;
// 4J - optimisation - keep direct reference of underlying cache here // Removed chunkSourceCache/chunkSourceXZSize: replaced by virtual dispatch for infinite world support
LevelChunk **chunkSourceCache;
int chunkSourceXZSize;
// 4J - added for implementation of finite limit to number of item entities, tnt and falling block entities // 4J - added for implementation of finite limit to number of item entities, tnt and falling block entities
public: public:

View file

@ -699,9 +699,9 @@ void LevelChunk::recheckGaps(bool bForce)
// to light massive gaps between the height of 0 and whatever heights are in those. // to light massive gaps between the height of 0 and whatever heights are in those.
if( isEmpty() ) return; if( isEmpty() ) return;
// 4J added // 4J added - use MAX_LEVEL_SIZE for infinite world support
int minXZ = - (level->dimension->getXZSize() * 16 ) / 2; int minXZ = -Level::MAX_LEVEL_SIZE;
int maxXZ = (level->dimension->getXZSize() * 16 ) / 2 - 1; int maxXZ = Level::MAX_LEVEL_SIZE - 1;
// 4J - note - this test will currently return true for chunks at the edge of our world. Making further checks inside the loop now to address this issue. // 4J - note - this test will currently return true for chunks at the edge of our world. Making further checks inside the loop now to address this issue.
if (level->hasChunksAt(x * 16 + 8, Level::maxBuildHeight / 2, z * 16 + 8, 16)) if (level->hasChunksAt(x * 16 + 8, Level::maxBuildHeight / 2, z * 16 + 8, 16))

View file

@ -316,15 +316,8 @@ int LiquidTileDynamic::getHighest(Level *level, int x, int y, int z, int current
bool LiquidTileDynamic::canSpreadTo(Level *level, int x, int y, int z) bool LiquidTileDynamic::canSpreadTo(Level *level, int x, int y, int z)
{ {
// 4J added - don't try and spread out of our restricted map. If we don't do this check then tiles at the edge of the world will try and spread outside as the outside tiles report that they contain // 4J added - for infinite worlds, we rely on hasChunkAt to prevent spreading into unloaded chunks
// only air. The fact that this successfully spreads then updates the neighbours of the tile outside of the map, one of which is the original tile just inside the map, which gets set back to being if( !level->hasChunkAt(x, y, z) ) return false;
// dynamic, and added to the pending ticks array.
int xc = x >> 4;
int zc = z >> 4;
int ix = xc + (level->chunkSourceXZSize/2);
int iz = zc + (level->chunkSourceXZSize/2);
if( ( ix < 0 ) || ( ix >= level->chunkSourceXZSize ) ) return false;
if( ( iz < 0 ) || ( iz >= level->chunkSourceXZSize ) ) return false;
Material *target = level->getMaterial(x, y, z); Material *target = level->getMaterial(x, y, z);
if (target == material) return false; if (target == material) return false;

View file

@ -486,11 +486,9 @@ bool PistonBaseTile::canPush(Level *level, int sx, int sy, int sz, int facing)
// out of bounds // out of bounds
return false; return false;
} }
// 4J - added to also check for out of bounds in x/z for our finite world // Infinite worlds: use Level::MAX_LEVEL_SIZE instead of finite world boundary
int minXZ = - (level->dimension->getXZSize() * 16 ) / 2; if( ( cx <= -Level::MAX_LEVEL_SIZE ) || ( cx >= Level::MAX_LEVEL_SIZE ) || ( cz <= -Level::MAX_LEVEL_SIZE ) || ( cz >= Level::MAX_LEVEL_SIZE ) )
int maxXZ = (level->dimension->getXZSize() * 16 ) / 2 - 1;
if( ( cx <= minXZ ) || ( cx >= maxXZ ) || ( cz <= minXZ ) || ( cz >= maxXZ ) )
{ {
return false; return false;
} }
@ -552,11 +550,9 @@ bool PistonBaseTile::createPush(Level *level, int sx, int sy, int sz, int facing
// out of bounds // out of bounds
return false; return false;
} }
// 4J - added to also check for out of bounds in x/z for our finite world // Infinite worlds: use Level::MAX_LEVEL_SIZE instead of finite world boundary
int minXZ = - (level->dimension->getXZSize() * 16 ) / 2; if( ( cx <= -Level::MAX_LEVEL_SIZE ) || ( cx >= Level::MAX_LEVEL_SIZE ) || ( cz <= -Level::MAX_LEVEL_SIZE ) || ( cz >= Level::MAX_LEVEL_SIZE ) )
int maxXZ = (level->dimension->getXZSize() * 16 ) / 2 - 1;
if( ( cx <= minXZ ) || ( cx >= maxXZ ) || ( cz <= minXZ ) || ( cz >= maxXZ ) )
{ {
return false; return false;
} }

View file

@ -154,45 +154,9 @@ void RandomLevelSource::prepareHeights(int xOffs, int zOffs, byteArray blocks)
val -= vala; val -= vala;
for (int z = 0; z < CHUNK_WIDTH; z++) for (int z = 0; z < CHUNK_WIDTH; z++)
{ {
/////////////////////////////////////////////////////////////////// // Infinite worlds: no edge falloff; comp is always 0.0f
// 4J - add this chunk of code to make land "fall-off" at the edges of
// a finite world - size of that world is currently hard-coded in here
const int worldSize = m_XZSize * 16;
const int falloffStart = 32; // chunks away from edge were we start doing fall-off
const float falloffMax = 128.0f; // max value we need to get to falloff by the edge of the map
int xxx = ( ( xOffs * 16 ) + x + ( xc * CHUNK_WIDTH ) );
int zzz = ( ( zOffs * 16 ) + z + ( zc * CHUNK_WIDTH ) );
// Get distance to edges of world in x
int xxx0 = xxx + ( worldSize / 2 );
if( xxx0 < 0 ) xxx0 = 0;
int xxx1 = ( ( worldSize / 2 ) - 1 ) - xxx;
if( xxx1 < 0 ) xxx1 = 0;
// Get distance to edges of world in z
int zzz0 = zzz + ( worldSize / 2 );
if( zzz0 < 0 ) zzz0 = 0;
int zzz1 = ( ( worldSize / 2 ) - 1 ) - zzz;
if( zzz1 < 0 ) zzz1 = 0;
// Get min distance to any edge
int emin = xxx0;
if (xxx1 < emin ) emin = xxx1;
if (zzz0 < emin ) emin = zzz0;
if (zzz1 < emin ) emin = zzz1;
float comp = 0.0f; float comp = 0.0f;
// Calculate how much we want the world to fall away, if we're in the defined region to do so
if( emin < falloffStart )
{
int falloff = falloffStart - emin;
comp = ((float)falloff / (float)falloffStart ) * falloffMax;
}
// 4J - end of extra code
///////////////////////////////////////////////////////////////////
// 4J - slightly rearranged this code (as of java 1.0.1 merge) to better fit with // 4J - slightly rearranged this code (as of java 1.0.1 merge) to better fit with
// changes we've made edge-of-world things - original sets blocks[offs += step] directly // changes we've made edge-of-world things - original sets blocks[offs += step] directly
// here rather than setting a tileId // here rather than setting a tileId
@ -205,17 +169,7 @@ void RandomLevelSource::prepareHeights(int xOffs, int zOffs, byteArray blocks)
else if (yc * CHUNK_HEIGHT + y < waterHeight) else if (yc * CHUNK_HEIGHT + y < waterHeight)
{ {
tileId = (byte) Tile::calmWater_Id; tileId = (byte) Tile::calmWater_Id;
} }
// 4J - more extra code to make sure that the column at the edge of the world is just water & rock, to match the infinite sea that
// continues on after the edge of the world.
if( emin == 0 )
{
// This matches code in MultiPlayerChunkCache that makes the geometry which continues at the edge of the world
if( yc * CHUNK_HEIGHT + y <= ( level->getSeaLevel() - 10 ) ) tileId = Tile::rock_Id;
else if( yc * CHUNK_HEIGHT + y < level->getSeaLevel() ) tileId = Tile::calmWater_Id;
}
blocks[offs += step] = tileId; blocks[offs += step] = tileId;
} }

View file

@ -365,9 +365,8 @@ bool StrongholdPieces::StrongholdPiece::isOkBox(BoundingBox *box, StartPiece *st
if( startRoom != NULL && startRoom->m_level->getOriginalSaveVersion() >= SAVE_FILE_VERSION_MOVED_STRONGHOLD ) if( startRoom != NULL && startRoom->m_level->getOriginalSaveVersion() >= SAVE_FILE_VERSION_MOVED_STRONGHOLD )
{ {
int xzSize = startRoom->m_level->getLevelData()->getXZSize(); int blockMin = -Level::MAX_LEVEL_SIZE + 1;
int blockMin = -( (xzSize << 4) / 2) + 1; int blockMax = Level::MAX_LEVEL_SIZE - 1;
int blockMax = ( (xzSize << 4) / 2 ) - 1;
if(box->x0 <= blockMin) bIsOk = false; if(box->x0 <= blockMin) bIsOk = false;
if(box->z0 <= blockMin) bIsOk = false; if(box->z0 <= blockMin) bIsOk = false;

View file

@ -317,9 +317,8 @@ bool VillagePieces::VillagePiece::isOkBox(BoundingBox *box, StartPiece *startRoo
{ {
if( box->y0 > LOWEST_Y_POSITION ) bIsOk = true; if( box->y0 > LOWEST_Y_POSITION ) bIsOk = true;
int xzSize = startRoom->m_level->getLevelData()->getXZSize(); int blockMin = -Level::MAX_LEVEL_SIZE + 1;
int blockMin = -( (xzSize << 4) / 2) + 1; int blockMax = Level::MAX_LEVEL_SIZE - 1;
int blockMax = ( (xzSize << 4) / 2 ) - 1;
if(box->x0 <= blockMin) bIsOk = false; if(box->x0 <= blockMin) bIsOk = false;
if(box->z0 <= blockMin) bIsOk = false; if(box->z0 <= blockMin) bIsOk = false;