1
0

Optimized chunk loader

This commit is contained in:
Howaner 2014-10-02 23:50:41 +02:00
parent ecef85574d
commit 382e014ebc
7 changed files with 93 additions and 105 deletions

View File

@ -192,40 +192,37 @@ void cChunkSender::SendChunk(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Clien
ASSERT(m_World != NULL); ASSERT(m_World != NULL);
// Ask the client if it still wants the chunk: // Ask the client if it still wants the chunk:
if (a_Client != NULL) if ((a_Client != NULL) && !a_Client->WantsSendChunk(a_ChunkX, a_ChunkZ))
{ {
if (!a_Client->WantsSendChunk(a_ChunkX, a_ChunkZ)) return;
{
return;
}
} }
// If the chunk has no clients, no need to packetize it: // If the chunk has no clients, no need to packetize it:
if (!m_World->HasChunkAnyClients(a_ChunkX, a_ChunkZ)) if (!m_World->HasChunkAnyClients(a_ChunkX, a_ChunkZ))
{ {
return; return;
} }
// If the chunk is not valid, do nothing - whoever needs it has queued it for loading / generating // If the chunk is not valid, do nothing - whoever needs it has queued it for loading / generating
if (!m_World->IsChunkValid(a_ChunkX, a_ChunkZ)) if (!m_World->IsChunkValid(a_ChunkX, a_ChunkZ))
{ {
return; return;
} }
// If the chunk is not lighted, queue it for relighting and get notified when it's ready: // If the chunk is not lighted, queue it for relighting and get notified when it's ready:
if (!m_World->IsChunkLighted(a_ChunkX, a_ChunkZ)) if (!m_World->IsChunkLighted(a_ChunkX, a_ChunkZ))
{ {
m_World->QueueLightChunk(a_ChunkX, a_ChunkZ, &m_Notify); m_World->QueueLightChunk(a_ChunkX, a_ChunkZ, &m_Notify);
return; return;
} }
// Query and prepare chunk data: // Query and prepare chunk data:
if (!m_World->GetChunkData(a_ChunkX, a_ChunkZ, *this)) if (!m_World->GetChunkData(a_ChunkX, a_ChunkZ, *this))
{ {
return; return;
} }
cChunkDataSerializer Data(m_BlockTypes, m_BlockMetas, m_BlockLight, m_BlockSkyLight, m_BiomeMap); cChunkDataSerializer Data(m_BlockTypes, m_BlockMetas, m_BlockLight, m_BlockSkyLight, m_BiomeMap);
// Send: // Send:
if (a_Client == NULL) if (a_Client == NULL)
{ {
@ -235,7 +232,7 @@ void cChunkSender::SendChunk(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Clien
{ {
a_Client->SendChunkData(a_ChunkX, a_ChunkZ, Data); a_Client->SendChunkData(a_ChunkX, a_ChunkZ, Data);
} }
// Send block-entity packets: // Send block-entity packets:
for (sBlockCoords::iterator itr = m_BlockEntities.begin(); itr != m_BlockEntities.end(); ++itr) for (sBlockCoords::iterator itr = m_BlockEntities.begin(); itr != m_BlockEntities.end(); ++itr)
{ {
@ -249,7 +246,7 @@ void cChunkSender::SendChunk(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Clien
} }
} // for itr - m_Packets[] } // for itr - m_Packets[]
m_BlockEntities.clear(); m_BlockEntities.clear();
// TODO: Send entity spawn packets // TODO: Send entity spawn packets
} }

View File

@ -70,8 +70,6 @@ cClientHandle::cClientHandle(const cSocket * a_Socket, int a_ViewDistance) :
m_OutgoingData(64 KiB), m_OutgoingData(64 KiB),
m_Player(NULL), m_Player(NULL),
m_HasSentDC(false), m_HasSentDC(false),
m_LastStreamedChunkX(0x7fffffff), // bogus chunk coords to force streaming upon login
m_LastStreamedChunkZ(0x7fffffff),
m_TimeSinceLastPacket(0), m_TimeSinceLastPacket(0),
m_Ping(1000), m_Ping(1000),
m_PingID(1), m_PingID(1),
@ -401,53 +399,84 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID,
void cClientHandle::StreamChunks(void) void cClientHandle::StreamNextChunk(void)
{ {
if ((m_State < csAuthenticated) || (m_State >= csDestroying)) if ((m_State < csAuthenticated) || (m_State >= csDestroying))
{ {
return; return;
} }
ASSERT(m_Player != NULL); Vector3d LookVector = m_Player->GetLookVector();
LookVector.Normalize();
Vector3d Position = m_Player->GetEyePosition();
for (size_t Range = 0; Range < (size_t)m_ViewDistance; Range++)
{
Vector3d Vector = Position + LookVector * cChunkDef::Width * Range;
int RangeX, RangeZ = 0;
cChunkDef::BlockToChunk((int)std::floor(Vector.x), (int)std::floor(Vector.z), RangeX, RangeZ);
for (size_t X = 0; X < 6; X++)
{
for (size_t Z = 0; Z < 6; Z++)
{
int ChunkX = RangeX + ((X >= 3) ? (2 - X) : X);
int ChunkZ = RangeZ + ((Z >= 3) ? (2 - Z) : Z);
cChunkCoords Coords(ChunkX, ChunkZ);
// If the chunk already loading/loaded -> skip
{
cCSLock Lock(m_CSChunkLists);
if (
(std::find(m_ChunksToSend.begin(), m_ChunksToSend.end(), Coords) != m_ChunksToSend.end()) ||
(std::find(m_LoadedChunks.begin(), m_LoadedChunks.end(), Coords) != m_LoadedChunks.end())
)
{
continue;
}
}
// Unloaded chunk found -> Send it to the client.
StreamChunk(ChunkX, ChunkZ);
return;
}
}
}
}
void cClientHandle::UnloadOutOfRangeChunks(void)
{
int ChunkPosX = FAST_FLOOR_DIV((int)m_Player->GetPosX(), cChunkDef::Width); int ChunkPosX = FAST_FLOOR_DIV((int)m_Player->GetPosX(), cChunkDef::Width);
int ChunkPosZ = FAST_FLOOR_DIV((int)m_Player->GetPosZ(), cChunkDef::Width); int ChunkPosZ = FAST_FLOOR_DIV((int)m_Player->GetPosZ(), cChunkDef::Width);
if ((ChunkPosX == m_LastStreamedChunkX) && (ChunkPosZ == m_LastStreamedChunkZ))
{
// Already streamed for this position
return;
}
m_LastStreamedChunkX = ChunkPosX;
m_LastStreamedChunkZ = ChunkPosZ;
LOGD("Streaming chunks centered on [%d, %d], view distance %d", ChunkPosX, ChunkPosZ, m_ViewDistance);
cWorld * World = m_Player->GetWorld();
ASSERT(World != NULL);
// Remove all loaded chunks that are no longer in range; deferred to out-of-CS: cChunkCoordsList ChunksToRemove;
cChunkCoordsList RemoveChunks;
{ {
cCSLock Lock(m_CSChunkLists); cCSLock Lock(m_CSChunkLists);
for (cChunkCoordsList::iterator itr = m_LoadedChunks.begin(); itr != m_LoadedChunks.end();) for (cChunkCoordsList::iterator itr = m_LoadedChunks.begin(); itr != m_LoadedChunks.end();)
{ {
int RelX = (*itr).m_ChunkX - ChunkPosX; int DiffX = Diff((*itr).m_ChunkX, ChunkPosX);
int RelZ = (*itr).m_ChunkZ - ChunkPosZ; int DiffZ = Diff((*itr).m_ChunkZ, ChunkPosZ);
if ((RelX > m_ViewDistance) || (RelX < -m_ViewDistance) || (RelZ > m_ViewDistance) || (RelZ < -m_ViewDistance)) if ((DiffX > m_ViewDistance) || (DiffZ > m_ViewDistance))
{ {
RemoveChunks.push_back(*itr); ChunksToRemove.push_back(*itr);
itr = m_LoadedChunks.erase(itr); itr = m_LoadedChunks.erase(itr);
} }
else else
{ {
++itr; ++itr;
} }
} // for itr - m_LoadedChunks[] }
for (cChunkCoordsList::iterator itr = m_ChunksToSend.begin(); itr != m_ChunksToSend.end();) for (cChunkCoordsList::iterator itr = m_ChunksToSend.begin(); itr != m_ChunksToSend.end();)
{ {
int RelX = (*itr).m_ChunkX - ChunkPosX; int DiffX = Diff((*itr).m_ChunkX, ChunkPosX);
int RelZ = (*itr).m_ChunkZ - ChunkPosZ; int DiffZ = Diff((*itr).m_ChunkZ, ChunkPosZ);
if ((RelX > m_ViewDistance) || (RelX < -m_ViewDistance) || (RelZ > m_ViewDistance) || (RelZ < -m_ViewDistance)) if ((DiffX > m_ViewDistance) || (DiffZ > m_ViewDistance))
{ {
itr = m_ChunksToSend.erase(itr); itr = m_ChunksToSend.erase(itr);
} }
@ -455,51 +484,20 @@ void cClientHandle::StreamChunks(void)
{ {
++itr; ++itr;
} }
} // for itr - m_ChunksToSend[] }
} }
for (cChunkCoordsList::iterator itr = RemoveChunks.begin(); itr != RemoveChunks.end(); ++itr)
for (cChunkCoordsList::iterator itr = ChunksToRemove.begin(); itr != ChunksToRemove.end(); ++itr)
{ {
World->RemoveChunkClient(itr->m_ChunkX, itr->m_ChunkZ, this); m_Player->GetWorld()->RemoveChunkClient(itr->m_ChunkX, itr->m_ChunkZ, this);
m_Protocol->SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ); m_Protocol->SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ);
} // for itr - RemoveChunks[] }
// Add all chunks that are in range and not yet in m_LoadedChunks:
// Queue these smartly - from the center out to the edge
for (int d = 0; d <= m_ViewDistance; ++d) // cycle through (square) distance, from nearest to furthest
{
// For each distance add chunks in a hollow square centered around current position:
for (int i = -d; i <= d; ++i)
{
StreamChunk(ChunkPosX + d, ChunkPosZ + i);
StreamChunk(ChunkPosX - d, ChunkPosZ + i);
} // for i
for (int i = -d + 1; i < d; ++i)
{
StreamChunk(ChunkPosX + i, ChunkPosZ + d);
StreamChunk(ChunkPosX + i, ChunkPosZ - d);
} // for i
} // for d
// Touch chunks GENERATEDISTANCE ahead to let them generate:
for (int d = m_ViewDistance + 1; d <= m_ViewDistance + GENERATEDISTANCE; ++d) // cycle through (square) distance, from nearest to furthest
{
// For each distance touch chunks in a hollow square centered around current position:
for (int i = -d; i <= d; ++i)
{
World->TouchChunk(ChunkPosX + d, ChunkPosZ + i);
World->TouchChunk(ChunkPosX - d, ChunkPosZ + i);
} // for i
for (int i = -d + 1; i < d; ++i)
{
World->TouchChunk(ChunkPosX + i, ChunkPosZ + d);
World->TouchChunk(ChunkPosX + i, ChunkPosZ - d);
} // for i
} // for d
} }
void cClientHandle::StreamChunk(int a_ChunkX, int a_ChunkZ) void cClientHandle::StreamChunk(int a_ChunkX, int a_ChunkZ)
{ {
if (m_State >= csDestroying) if (m_State >= csDestroying)
@ -539,11 +537,6 @@ void cClientHandle::RemoveFromAllChunks()
cCSLock Lock(m_CSChunkLists); cCSLock Lock(m_CSChunkLists);
m_LoadedChunks.clear(); m_LoadedChunks.clear();
m_ChunksToSend.clear(); m_ChunksToSend.clear();
// Also reset the LastStreamedChunk coords to bogus coords,
// so that all chunks are streamed in subsequent StreamChunks() call (FS #407)
m_LastStreamedChunkX = 0x7fffffff;
m_LastStreamedChunkZ = 0x7fffffff;
} }
} }
@ -1858,10 +1851,7 @@ void cClientHandle::RemoveFromWorld(void)
{ {
m_Protocol->SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ); m_Protocol->SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ);
} // for itr - Chunks[] } // for itr - Chunks[]
// Here, we set last streamed values to bogus ones so everything is resent
m_LastStreamedChunkX = 0x7fffffff;
m_LastStreamedChunkZ = 0x7fffffff;
m_HasSentPlayerChunk = false; m_HasSentPlayerChunk = false;
} }
@ -1907,7 +1897,7 @@ void cClientHandle::Tick(float a_Dt)
{ {
return; return;
} }
// If the chunk the player's in was just sent, spawn the player: // If the chunk the player's in was just sent, spawn the player:
if (m_HasSentPlayerChunk && (m_State == csDownloadingWorld)) if (m_HasSentPlayerChunk && (m_State == csDownloadingWorld))
{ {
@ -1928,6 +1918,17 @@ void cClientHandle::Tick(float a_Dt)
} }
} }
if ((m_State >= csAuthenticated) && (m_State < csDestroying))
{
StreamNextChunk(); // Streams the next chunk
// Unload all chunks that are out of the view distance (all 2 seconds)
if ((m_Player->GetWorld()->GetWorldAge() % 40) == 0)
{
UnloadOutOfRangeChunks();
}
}
// Handle block break animation: // Handle block break animation:
if (m_BlockDigAnimStage > -1) if (m_BlockDigAnimStage > -1)
{ {
@ -1964,7 +1965,7 @@ void cClientHandle::ServerTick(float a_Dt)
if (m_State == csAuthenticated) if (m_State == csAuthenticated)
{ {
StreamChunks(); StreamNextChunk();
// Remove the client handle from the server, it will be ticked from its cPlayer object from now on // Remove the client handle from the server, it will be ticked from its cPlayer object from now on
cRoot::Get()->GetServer()->ClientMovedToWorld(this); cRoot::Get()->GetServer()->ClientMovedToWorld(this);
@ -2723,18 +2724,8 @@ void cClientHandle::SetUsername( const AString & a_Username)
void cClientHandle::SetViewDistance(int a_ViewDistance) void cClientHandle::SetViewDistance(int a_ViewDistance)
{ {
if (a_ViewDistance < MIN_VIEW_DISTANCE) m_ViewDistance = Clamp(a_ViewDistance, MIN_VIEW_DISTANCE, MAX_VIEW_DISTANCE);
{ LOGD("Setted %s's view distance to %i", GetUsername().c_str(), m_ViewDistance);
a_ViewDistance = MIN_VIEW_DISTANCE;
}
if (a_ViewDistance > MAX_VIEW_DISTANCE)
{
a_ViewDistance = MAX_VIEW_DISTANCE;
}
m_ViewDistance = a_ViewDistance;
// Need to re-stream chunks for the change to become apparent:
StreamChunks();
} }

View File

@ -113,7 +113,11 @@ public:
/** Authenticates the specified user, called by cAuthenticator */ /** Authenticates the specified user, called by cAuthenticator */
void Authenticate(const AString & a_Name, const AString & a_UUID, const Json::Value & a_Properties); void Authenticate(const AString & a_Name, const AString & a_UUID, const Json::Value & a_Properties);
void StreamChunks(void); /** This function sends a new unloaded chunk to the player. */
void StreamNextChunk(void);
/** Remove all loaded chunks that are no longer in range */
void UnloadOutOfRangeChunks(void);
// Removes the client from all chunks. Used when switching worlds or destroying the player // Removes the client from all chunks. Used when switching worlds or destroying the player
void RemoveFromAllChunks(void); void RemoveFromAllChunks(void);
@ -352,10 +356,6 @@ private:
cPlayer * m_Player; cPlayer * m_Player;
bool m_HasSentDC; ///< True if a D/C packet has been sent in either direction bool m_HasSentDC; ///< True if a D/C packet has been sent in either direction
// Chunk position when the last StreamChunks() was called; used to avoid re-streaming while in the same chunk
int m_LastStreamedChunkX;
int m_LastStreamedChunkZ;
/** Seconds since the last packet data was received (updated in Tick(), reset in DataReceived()) */ /** Seconds since the last packet data was received (updated in Tick(), reset in DataReceived()) */
float m_TimeSinceLastPacket; float m_TimeSinceLastPacket;

View File

@ -235,7 +235,6 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk)
CanMove = false; CanMove = false;
TeleportToCoords(m_LastPos.x, m_LastPos.y, m_LastPos.z); TeleportToCoords(m_LastPos.x, m_LastPos.y, m_LastPos.z);
} }
m_ClientHandle->StreamChunks();
} }
if (CanMove) if (CanMove)

View File

@ -1901,6 +1901,7 @@ void cProtocol172::HandlePacketClientSettings(cByteBuffer & a_ByteBuffer)
HANDLE_READ(a_ByteBuffer, ReadByte, Byte, ShowCape); HANDLE_READ(a_ByteBuffer, ReadByte, Byte, ShowCape);
m_Client->SetLocale(Locale); m_Client->SetLocale(Locale);
m_Client->SetViewDistance(ViewDistance);
// TODO: Do anything with the other values. // TODO: Do anything with the other values.
} }

View File

@ -2160,6 +2160,7 @@ void cProtocol180::HandlePacketClientSettings(cByteBuffer & a_ByteBuffer)
HANDLE_READ(a_ByteBuffer, ReadChar, char, SkinFlags); HANDLE_READ(a_ByteBuffer, ReadChar, char, SkinFlags);
m_Client->SetLocale(Locale); m_Client->SetLocale(Locale);
m_Client->SetViewDistance(ViewDistance);
// TODO: Handle other values // TODO: Handle other values
} }

View File

@ -3491,7 +3491,6 @@ void cWorld::AddQueuedPlayers(void)
cClientHandle * Client = (*itr)->GetClientHandle(); cClientHandle * Client = (*itr)->GetClientHandle();
if (Client != NULL) if (Client != NULL)
{ {
Client->StreamChunks();
Client->SendPlayerMoveLook(); Client->SendPlayerMoveLook();
Client->SendHealth(); Client->SendHealth();
Client->SendWholeInventory(*(*itr)->GetWindow()); Client->SendWholeInventory(*(*itr)->GetWindow());