2012-02-13 21:47:03 +00:00
// WorldStorage.cpp
// Implements the cWorldStorage class representing the chunk loading / saving thread
// To add a new storage schema, implement a cWSSchema descendant and add it to cWorldStorage::InitSchemas()
# include "Globals.h"
# include "WorldStorage.h"
# include "WSSCompact.h"
2012-03-07 11:28:24 +00:00
# include "WSSAnvil.h"
2012-02-13 21:47:03 +00:00
# include "cWorld.h"
# include "cChunkGenerator.h"
2012-02-16 13:42:35 +00:00
# include "cEntity.h"
# include "cBlockEntity.h"
# include "BlockID.h"
2012-02-13 21:47:03 +00:00
/// Example storage schema - forgets all chunks ;)
class cWSSForgetful :
public cWSSchema
{
public :
2012-03-23 21:12:48 +00:00
cWSSForgetful ( cWSInterface * a_WSI ) : cWSSchema ( a_WSI ) { }
2012-02-13 21:47:03 +00:00
protected :
// cWSSchema overrides:
2012-02-16 13:42:35 +00:00
virtual bool LoadChunk ( const cChunkCoords & a_Chunk ) override { return false ; }
virtual bool SaveChunk ( const cChunkCoords & a_Chunk ) override { return true ; }
2012-02-13 21:47:03 +00:00
virtual const AString GetName ( void ) const override { return " forgetful " ; }
} ;
2012-02-16 13:42:35 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cJsonChunkSerializer:
cJsonChunkSerializer : : cJsonChunkSerializer ( void ) :
m_HasJsonData ( false )
{
m_Root [ " Chests " ] = m_AllChests ;
m_Root [ " Furnaces " ] = m_AllFurnaces ;
m_Root [ " Signs " ] = m_AllSigns ;
}
void cJsonChunkSerializer : : Entity ( cEntity * a_Entity )
{
// TODO: a_Entity->SaveToJson(m_Root);
}
void cJsonChunkSerializer : : BlockEntity ( cBlockEntity * a_BlockEntity )
{
const char * SaveInto = NULL ;
switch ( a_BlockEntity - > GetBlockType ( ) )
{
case E_BLOCK_CHEST : SaveInto = " Chests " ; break ;
case E_BLOCK_FURNACE : SaveInto = " Furnaces " ; break ;
case E_BLOCK_SIGN_POST : SaveInto = " Signs " ; break ;
case E_BLOCK_WALLSIGN : SaveInto = " Signs " ; break ;
default :
{
2012-02-19 23:00:00 +00:00
ASSERT ( ! " Unhandled blocktype in BlockEntities list while saving to JSON " ) ;
2012-02-16 13:42:35 +00:00
break ;
}
} // switch (BlockEntity->GetBlockType())
if ( SaveInto = = NULL )
{
return ;
}
Json : : Value val ;
a_BlockEntity - > SaveToJson ( val ) ;
m_Root [ SaveInto ] . append ( val ) ;
m_HasJsonData = true ;
}
2012-02-13 21:47:03 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cWorldStorage:
cWorldStorage : : cWorldStorage ( void ) :
super ( " cWorldStorage " ) ,
2012-03-23 21:12:48 +00:00
m_WSI ( NULL ) ,
2012-02-13 21:47:03 +00:00
m_SaveSchema ( NULL )
{
}
cWorldStorage : : ~ cWorldStorage ( )
{
for ( cWSSchemaList : : iterator itr = m_Schemas . begin ( ) ; itr ! = m_Schemas . end ( ) ; + + itr )
{
delete * itr ;
} // for itr - m_Schemas[]
m_LoadQueue . clear ( ) ;
m_SaveQueue . clear ( ) ;
}
2012-03-23 21:12:48 +00:00
bool cWorldStorage : : Start ( cWSInterface * a_WSI , const AString & a_StorageSchemaName )
2012-02-13 21:47:03 +00:00
{
2012-03-23 21:12:48 +00:00
m_WSI = a_WSI ;
2012-02-13 21:47:03 +00:00
m_StorageSchemaName = a_StorageSchemaName ;
InitSchemas ( ) ;
return super : : Start ( ) ;
}
void cWorldStorage : : WaitForFinish ( void )
{
LOG ( " Waiting for the world storage to finish saving " ) ;
2012-02-15 21:35:59 +00:00
{
// Cancel all loading requests:
2012-02-18 17:53:22 +00:00
cCSLock Lock ( m_CSQueues ) ;
2012-02-15 21:35:59 +00:00
m_LoadQueue . clear ( ) ;
}
2012-02-13 21:47:03 +00:00
2012-03-13 21:13:34 +00:00
// Wait for the saving to finish:
WaitForQueuesEmpty ( ) ;
2012-02-13 21:47:03 +00:00
// Wait for the thread to finish:
2012-03-10 17:37:00 +00:00
m_ShouldTerminate = true ;
2012-02-13 21:47:03 +00:00
m_Event . Set ( ) ;
2012-02-18 17:53:22 +00:00
m_evtRemoved . Set ( ) ; // Wake up anybody waiting in the WaitForQueuesEmpty() method
2012-02-13 21:47:03 +00:00
super : : Wait ( ) ;
2012-03-10 17:37:00 +00:00
LOG ( " World storage thread finished " ) ;
2012-02-13 21:47:03 +00:00
}
2012-02-18 17:53:22 +00:00
void cWorldStorage : : WaitForQueuesEmpty ( void )
{
cCSLock Lock ( m_CSQueues ) ;
2012-03-10 17:37:00 +00:00
while ( ! m_ShouldTerminate & & ( ! m_LoadQueue . empty ( ) | | ! m_SaveQueue . empty ( ) ) )
2012-02-18 17:53:22 +00:00
{
cCSUnlock Unlock ( Lock ) ;
m_evtRemoved . Wait ( ) ;
}
}
int cWorldStorage : : GetLoadQueueLength ( void )
{
cCSLock Lock ( m_CSQueues ) ;
return ( int ) m_LoadQueue . size ( ) ;
}
int cWorldStorage : : GetSaveQueueLength ( void )
{
cCSLock Lock ( m_CSQueues ) ;
return ( int ) m_SaveQueue . size ( ) ;
}
2012-02-26 16:46:23 +00:00
void cWorldStorage : : QueueLoadChunk ( int a_ChunkX , int a_ChunkY , int a_ChunkZ , bool a_Generate )
2012-02-13 21:47:03 +00:00
{
// Queues the chunk for loading; if not loaded, the chunk will be generated
2012-02-26 16:46:23 +00:00
{
2012-02-28 16:59:59 +00:00
cCSLock Lock ( m_CSQueues ) ;
// Check if already in the queue:
for ( sChunkLoadQueue : : iterator itr = m_LoadQueue . begin ( ) ; itr ! = m_LoadQueue . end ( ) ; + + itr )
2012-02-26 16:46:23 +00:00
{
2012-02-28 16:59:59 +00:00
if ( ( itr - > m_ChunkX = = a_ChunkX ) & & ( itr - > m_ChunkY = = a_ChunkY ) & & ( itr - > m_ChunkZ = = a_ChunkZ ) & & ( itr - > m_Generate = = a_Generate ) )
{
return ;
}
2012-02-26 16:46:23 +00:00
}
2012-02-28 16:59:59 +00:00
m_LoadQueue . push_back ( sChunkLoad ( a_ChunkX , a_ChunkY , a_ChunkZ , a_Generate ) ) ;
2012-02-26 16:46:23 +00:00
}
2012-02-28 16:59:59 +00:00
2012-02-13 21:47:03 +00:00
m_Event . Set ( ) ;
}
2012-02-17 17:56:25 +00:00
void cWorldStorage : : QueueSaveChunk ( int a_ChunkX , int a_ChunkY , int a_ChunkZ )
2012-02-13 21:47:03 +00:00
{
2012-02-28 16:59:59 +00:00
{
cCSLock Lock ( m_CSQueues ) ;
m_SaveQueue . remove ( cChunkCoords ( a_ChunkX , a_ChunkY , a_ChunkZ ) ) ; // Don't add twice
m_SaveQueue . push_back ( cChunkCoords ( a_ChunkX , a_ChunkY , a_ChunkZ ) ) ;
}
2012-02-13 21:47:03 +00:00
m_Event . Set ( ) ;
}
2012-02-26 16:46:23 +00:00
void cWorldStorage : : UnqueueLoad ( int a_ChunkX , int a_ChunkY , int a_ChunkZ )
2012-02-13 21:47:03 +00:00
{
2012-02-18 17:53:22 +00:00
cCSLock Lock ( m_CSQueues ) ;
2012-02-26 16:46:23 +00:00
for ( sChunkLoadQueue : : iterator itr = m_LoadQueue . begin ( ) ; itr ! = m_LoadQueue . end ( ) ; + + itr )
{
if ( ( itr - > m_ChunkX ! = a_ChunkX ) | | ( itr - > m_ChunkY ! = a_ChunkY ) | | ( itr - > m_ChunkZ ! = a_ChunkZ ) )
{
continue ;
}
m_LoadQueue . erase ( itr ) ;
2012-02-28 16:59:59 +00:00
Lock . Unlock ( ) ;
2012-02-26 16:46:23 +00:00
m_evtRemoved . Set ( ) ;
return ;
} // for itr - m_LoadQueue[]
2012-02-13 21:47:03 +00:00
}
2012-02-16 13:42:35 +00:00
void cWorldStorage : : UnqueueSave ( const cChunkCoords & a_Chunk )
2012-02-13 21:47:03 +00:00
{
2012-02-28 16:59:59 +00:00
{
cCSLock Lock ( m_CSQueues ) ;
m_SaveQueue . remove ( a_Chunk ) ;
}
2012-02-18 17:53:22 +00:00
m_evtRemoved . Set ( ) ;
2012-02-13 21:47:03 +00:00
}
void cWorldStorage : : InitSchemas ( void )
{
// The first schema added is considered the default
2012-03-23 21:12:48 +00:00
m_Schemas . push_back ( new cWSSCompact ( m_WSI ) ) ;
m_Schemas . push_back ( new cWSSAnvil ( m_WSI ) ) ;
m_Schemas . push_back ( new cWSSForgetful ( m_WSI ) ) ;
2012-02-13 21:47:03 +00:00
// Add new schemas here
2012-03-12 19:43:25 +00:00
if ( NoCaseCompare ( m_StorageSchemaName , " default " ) = = 0 )
2012-02-13 21:47:03 +00:00
{
m_SaveSchema = m_Schemas . front ( ) ;
return ;
}
for ( cWSSchemaList : : iterator itr = m_Schemas . begin ( ) ; itr ! = m_Schemas . end ( ) ; + + itr )
{
2012-03-12 19:43:25 +00:00
if ( NoCaseCompare ( ( * itr ) - > GetName ( ) , m_StorageSchemaName ) = = 0 )
2012-02-13 21:47:03 +00:00
{
m_SaveSchema = * itr ;
return ;
}
} // for itr - m_Schemas[]
// Unknown schema selected, let the admin know:
2012-03-23 21:12:48 +00:00
LOGWARNING ( " World \" %s \" : Unknown storage schema name \" %s \" . Using default. Available schemas: " , m_WSI - > WSIGetFolder ( ) . c_str ( ) , m_StorageSchemaName . c_str ( ) ) ;
2012-02-13 21:47:03 +00:00
for ( cWSSchemaList : : iterator itr = m_Schemas . begin ( ) ; itr ! = m_Schemas . end ( ) ; + + itr )
{
LOGWARNING ( " \t \" %s \" " , ( * itr ) - > GetName ( ) . c_str ( ) ) ;
}
m_SaveSchema = m_Schemas . front ( ) ;
}
void cWorldStorage : : Execute ( void )
{
2012-03-10 17:37:00 +00:00
while ( ! m_ShouldTerminate )
2012-02-13 21:47:03 +00:00
{
m_Event . Wait ( ) ;
// Process both queues until they are empty again:
bool HasMore ;
do
{
HasMore = false ;
2012-03-10 17:37:00 +00:00
if ( m_ShouldTerminate )
2012-02-13 21:47:03 +00:00
{
return ;
}
2012-02-16 13:42:35 +00:00
HasMore = LoadOneChunk ( ) ;
HasMore = HasMore | SaveOneChunk ( ) ;
2012-02-18 17:53:22 +00:00
m_evtRemoved . Set ( ) ;
2012-02-13 21:47:03 +00:00
} while ( HasMore ) ;
}
}
2012-02-16 13:42:35 +00:00
bool cWorldStorage : : LoadOneChunk ( void )
2012-02-13 21:47:03 +00:00
{
2012-02-26 16:46:23 +00:00
sChunkLoad ToLoad ( 0 , 0 , 0 , false ) ;
2012-02-16 13:42:35 +00:00
bool HasMore ;
bool ShouldLoad = false ;
{
2012-02-18 17:53:22 +00:00
cCSLock Lock ( m_CSQueues ) ;
2012-02-16 13:42:35 +00:00
if ( m_LoadQueue . size ( ) > 0 )
{
ToLoad = m_LoadQueue . front ( ) ;
m_LoadQueue . pop_front ( ) ;
ShouldLoad = true ;
}
HasMore = ( m_LoadQueue . size ( ) > 0 ) ;
}
2012-02-28 10:45:53 +00:00
if ( ShouldLoad & & ! LoadChunk ( ToLoad . m_ChunkX , ToLoad . m_ChunkY , ToLoad . m_ChunkZ ) )
2012-02-16 13:42:35 +00:00
{
2012-02-26 16:46:23 +00:00
if ( ToLoad . m_Generate )
{
// The chunk couldn't be loaded, generate it:
2012-03-23 21:12:48 +00:00
m_WSI - > WSIGenerateChunk ( ToLoad . m_ChunkX , ToLoad . m_ChunkY , ToLoad . m_ChunkZ ) ;
2012-02-26 16:46:23 +00:00
}
else
{
// TODO: Notify the world that the load has failed:
// m_World->ChunkLoadFailed(ToLoad.m_ChunkX, ToLoad.m_ChunkY, ToLoad.m_ChunkZ);
}
2012-02-16 13:42:35 +00:00
}
return HasMore ;
}
bool cWorldStorage : : SaveOneChunk ( void )
{
2012-02-17 17:56:25 +00:00
cChunkCoords Save ( 0 , 0 , 0 ) ;
2012-02-16 13:42:35 +00:00
bool HasMore ;
bool ShouldSave = false ;
{
2012-02-18 17:53:22 +00:00
cCSLock Lock ( m_CSQueues ) ;
2012-02-16 13:42:35 +00:00
if ( m_SaveQueue . size ( ) > 0 )
{
Save = m_SaveQueue . front ( ) ;
m_SaveQueue . pop_front ( ) ;
ShouldSave = true ;
}
HasMore = ( m_SaveQueue . size ( ) > 0 ) ;
}
2012-03-23 21:12:48 +00:00
if ( ShouldSave & & m_WSI - > WSIIsChunkValid ( Save . m_ChunkX , Save . m_ChunkY , Save . m_ChunkZ ) )
2012-02-16 13:42:35 +00:00
{
2012-03-23 21:12:48 +00:00
m_WSI - > WSIMarkChunkSaving ( Save . m_ChunkX , Save . m_ChunkY , Save . m_ChunkZ ) ;
2012-02-16 16:50:11 +00:00
if ( m_SaveSchema - > SaveChunk ( Save ) )
{
2012-03-23 21:12:48 +00:00
m_WSI - > WSIMarkChunkSaved ( Save . m_ChunkX , Save . m_ChunkY , Save . m_ChunkZ ) ;
2012-02-16 16:50:11 +00:00
}
else
{
2012-02-17 17:56:25 +00:00
LOGWARNING ( " Cannot save chunk [%d, %d, %d] " , Save . m_ChunkX , Save . m_ChunkY , Save . m_ChunkZ ) ;
2012-02-16 16:50:11 +00:00
}
2012-02-16 13:42:35 +00:00
}
return HasMore ;
}
2012-02-28 10:45:53 +00:00
bool cWorldStorage : : LoadChunk ( int a_ChunkX , int a_ChunkY , int a_ChunkZ )
2012-02-16 13:42:35 +00:00
{
2012-03-23 21:12:48 +00:00
if ( m_WSI - > WSIIsChunkValid ( a_ChunkX , a_ChunkY , a_ChunkZ ) )
2012-02-13 21:47:03 +00:00
{
// Already loaded (can happen, since the queue is async)
return true ;
}
2012-02-28 10:45:53 +00:00
cChunkCoords Coords ( a_ChunkX , a_ChunkY , a_ChunkZ ) ;
// First try the schema that is used for saving
if ( m_SaveSchema - > LoadChunk ( Coords ) )
2012-02-13 21:47:03 +00:00
{
return true ;
}
2012-02-28 10:45:53 +00:00
// If it didn't have the chunk, try all the other schemas:
2012-02-13 21:47:03 +00:00
for ( cWSSchemaList : : iterator itr = m_Schemas . begin ( ) ; itr ! = m_Schemas . end ( ) ; + + itr )
{
2012-02-28 10:45:53 +00:00
if ( ( ( * itr ) ! = m_SaveSchema ) & & ( * itr ) - > LoadChunk ( Coords ) )
2012-02-13 21:47:03 +00:00
{
return true ;
}
}
2012-02-28 12:11:14 +00:00
// Notify the chunk owner that the chunk failed to load (sets cChunk::m_HasLoadFailed to true):
2012-03-23 21:12:48 +00:00
m_WSI - > WSIChunkLoadFailed ( a_ChunkX , a_ChunkY , a_ChunkZ ) ;
2012-02-28 12:11:14 +00:00
2012-02-13 21:47:03 +00:00
return false ;
}
2012-02-16 13:42:35 +00:00