2012-06-14 13:06:06 +00:00
#include "Globals.h"
2012-09-23 20:14:04 +00:00
#include "ChunkGenerator.h"
2012-09-23 22:09:57 +00:00
#include "../World.h"
2012-09-23 20:14:04 +00:00
#include "../../iniFile/iniFile.h"
2012-09-23 22:09:57 +00:00
#include "../Root.h"
#include "../PluginManager.h"
2013-01-25 10:12:29 +00:00
#include "ChunkDesc.h"
#include "ComposableGenerator.h"
2013-04-27 13:38:40 +00:00
#include "Noise3DGenerator.h"
2012-06-14 13:06:06 +00:00
/// If the generation queue size exceeds this number, a warning will be output
const int QUEUE_WARNING_LIMIT = 1000 ;
/// If the generation queue size exceeds this number, chunks with no clients will be skipped
const int QUEUE_SKIP_LIMIT = 500 ;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cChunkGenerator:
2013-01-25 10:12:29 +00:00
cChunkGenerator :: cChunkGenerator ( void ) :
super ( "cChunkGenerator" ),
m_World ( NULL ),
m_Generator ( NULL )
2012-06-14 13:06:06 +00:00
{
}
cChunkGenerator ::~ cChunkGenerator ()
{
Stop ();
}
bool cChunkGenerator :: Start ( cWorld * a_World , cIniFile & a_IniFile )
{
MTRand rnd ;
m_World = a_World ;
m_Seed = a_IniFile . GetValueSetI ( "Seed" , "Seed" , rnd . randInt ());
2013-01-25 10:12:29 +00:00
AString GeneratorName = a_IniFile . GetValueSet ( "Generator" , "Generator" , "Composable" );
2012-06-14 13:06:06 +00:00
2013-04-27 13:38:40 +00:00
if ( NoCaseCompare ( GeneratorName , "Noise3D" ) == 0 )
2013-01-24 12:15:36 +00:00
{
2013-04-27 13:38:40 +00:00
m_Generator = new cNoise3DGenerator ( * this );
2013-01-24 12:15:36 +00:00
}
2012-06-14 13:06:06 +00:00
else
{
2013-01-25 10:12:29 +00:00
if ( NoCaseCompare ( GeneratorName , "composable" ) != 0 )
2012-06-14 13:06:06 +00:00
{
2013-01-25 10:12:29 +00:00
LOGWARN ( "[Generator]::Generator value \" %s \" not recognized, using \" Composable \" ." , GeneratorName . c_str ());
2012-06-14 13:06:06 +00:00
}
2013-01-25 10:12:29 +00:00
m_Generator = new cComposableGenerator ( * this );
2012-06-14 13:06:06 +00:00
}
2013-01-25 10:12:29 +00:00
if ( m_Generator == NULL )
2012-06-14 13:06:06 +00:00
{
2013-01-25 10:12:29 +00:00
LOGERROR ( "Generator could not start, aborting the server" );
return false ;
2012-06-14 13:06:06 +00:00
}
2013-01-25 10:12:29 +00:00
m_Generator -> Initialize ( a_World , a_IniFile );
2012-06-14 13:06:06 +00:00
2013-01-25 10:12:29 +00:00
a_IniFile . WriteFile ();
return super :: Start ();
2012-06-14 13:06:06 +00:00
}
2013-01-25 10:12:29 +00:00
void cChunkGenerator :: Stop ( void )
2012-06-14 13:06:06 +00:00
{
2013-01-25 10:12:29 +00:00
m_ShouldTerminate = true ;
m_Event . Set ();
m_evtRemoved . Set (); // Wake up anybody waiting for empty queue
Wait ();
2012-06-14 13:06:06 +00:00
2013-01-25 10:12:29 +00:00
delete m_Generator ;
m_Generator = NULL ;
2012-06-14 13:06:06 +00:00
}
void cChunkGenerator :: QueueGenerateChunk ( int a_ChunkX , int a_ChunkY , int a_ChunkZ )
{
{
cCSLock Lock ( m_CS );
// Check if it is already in the queue:
for ( cChunkCoordsList :: iterator itr = m_Queue . begin (); itr != m_Queue . end (); ++ itr )
{
if (( itr -> m_ChunkX == a_ChunkX ) && ( itr -> m_ChunkY == a_ChunkY ) && ( itr -> m_ChunkZ == a_ChunkZ ))
{
// Already in the queue, bail out
return ;
}
} // for itr - m_Queue[]
// Add to queue, issue a warning if too many:
if ( m_Queue . size () >= QUEUE_WARNING_LIMIT )
{
LOGWARN ( "WARNING: Adding chunk [%i, %i] to generation queue; Queue is too big! (%i)" , a_ChunkX , a_ChunkZ , m_Queue . size ());
}
m_Queue . push_back ( cChunkCoords ( a_ChunkX , a_ChunkY , a_ChunkZ ));
}
m_Event . Set ();
}
void cChunkGenerator :: GenerateBiomes ( int a_ChunkX , int a_ChunkZ , cChunkDef :: BiomeMap & a_BiomeMap )
{
2013-01-25 10:12:29 +00:00
if ( m_Generator != NULL )
2012-09-01 21:37:41 +00:00
{
2013-01-25 10:12:29 +00:00
m_Generator -> GenerateBiomes ( a_ChunkX , a_ChunkZ , a_BiomeMap );
2012-09-01 21:37:41 +00:00
}
2012-06-14 13:06:06 +00:00
}
void cChunkGenerator :: WaitForQueueEmpty ( void )
{
cCSLock Lock ( m_CS );
while ( ! m_ShouldTerminate && ! m_Queue . empty ())
{
cCSUnlock Unlock ( Lock );
m_evtRemoved . Wait ();
}
}
int cChunkGenerator :: GetQueueLength ( void )
{
cCSLock Lock ( m_CS );
return ( int ) m_Queue . size ();
}
EMCSBiome cChunkGenerator :: GetBiomeAt ( int a_BlockX , int a_BlockZ )
{
2013-01-25 10:12:29 +00:00
ASSERT ( m_Generator != NULL );
return m_Generator -> GetBiomeAt ( a_BlockX , a_BlockZ );
}
BLOCKTYPE cChunkGenerator :: GetIniBlock ( cIniFile & a_IniFile , const AString & a_SectionName , const AString & a_ValueName , const AString & a_Default )
{
AString BlockType = a_IniFile . GetValueSet ( a_SectionName , a_ValueName , a_Default );
BLOCKTYPE Block = BlockStringToType ( BlockType );
if ( Block < 0 )
{
LOGWARN ( "[&s].%s Could not parse block value \" %s \" . Using default: \" %s \" ." , a_SectionName . c_str (), a_ValueName . c_str (), BlockType . c_str (), a_Default . c_str ());
return BlockStringToType ( a_Default );
}
return Block ;
2012-06-14 13:06:06 +00:00
}
void cChunkGenerator :: Execute ( void )
{
2012-07-29 17:51:04 +00:00
// To be able to display performance information, the generator counts the chunks generated.
// When the queue gets empty, the count is reset, so that waiting for the queue is not counted into the total time.
int NumChunksGenerated = 0 ; // Number of chunks generated since the queue was last empty
clock_t GenerationStart = clock (); // Clock tick when the queue started to fill
clock_t LastReportTick = clock (); // Clock tick of the last report made (so that performance isn't reported too often)
2012-06-14 13:06:06 +00:00
while ( ! m_ShouldTerminate )
{
cCSLock Lock ( m_CS );
while ( m_Queue . size () == 0 )
{
2012-07-29 17:51:04 +00:00
if (( NumChunksGenerated > 16 ) && ( clock () - LastReportTick > CLOCKS_PER_SEC ))
{
LOG ( "Chunk generator performance: %.2f ch/s (%d ch total)" ,
( double ) NumChunksGenerated * CLOCKS_PER_SEC / ( clock () - GenerationStart ),
NumChunksGenerated
);
}
2012-06-14 13:06:06 +00:00
cCSUnlock Unlock ( Lock );
m_Event . Wait ();
if ( m_ShouldTerminate )
{
return ;
}
2012-07-29 17:51:04 +00:00
NumChunksGenerated = 0 ;
GenerationStart = clock ();
LastReportTick = clock ();
2012-06-14 13:06:06 +00:00
}
cChunkCoords coords = m_Queue . front (); // Get next coord from queue
m_Queue . erase ( m_Queue . begin () ); // Remove coordinate from queue
bool SkipEnabled = ( m_Queue . size () > QUEUE_SKIP_LIMIT );
Lock . Unlock (); // Unlock ASAP
m_evtRemoved . Set ();
2012-07-29 17:51:04 +00:00
// Display perf info once in a while:
if (( NumChunksGenerated > 16 ) && ( clock () - LastReportTick > 2 * CLOCKS_PER_SEC ))
{
LOG ( "Chunk generator performance: %.2f ch/s (%d ch total)" ,
( double ) NumChunksGenerated * CLOCKS_PER_SEC / ( clock () - GenerationStart ),
NumChunksGenerated
);
LastReportTick = clock ();
}
2012-06-14 13:06:06 +00:00
// Hack for regenerating chunks: if Y != 0, the chunk is considered invalid, even if it has its data set
2013-04-13 21:02:10 +00:00
if (( coords . m_ChunkY == 0 ) && m_World -> IsChunkValid ( coords . m_ChunkX , coords . m_ChunkZ ))
2012-06-14 13:06:06 +00:00
{
LOGD ( "Chunk [%d, %d] already generated, skipping generation" , coords . m_ChunkX , coords . m_ChunkZ );
// Already generated, ignore request
continue ;
}
2013-04-13 21:02:10 +00:00
if ( SkipEnabled && ! m_World -> HasChunkAnyClients ( coords . m_ChunkX , coords . m_ChunkZ ))
2012-06-14 13:06:06 +00:00
{
2013-04-13 21:02:10 +00:00
LOGWARNING ( "Chunk generator overloaded, skipping chunk [%d, %d]" , coords . m_ChunkX , coords . m_ChunkZ );
2012-06-14 13:06:06 +00:00
continue ;
}
LOGD ( "Generating chunk [%d, %d, %d]" , coords . m_ChunkX , coords . m_ChunkY , coords . m_ChunkZ );
DoGenerate ( coords . m_ChunkX , coords . m_ChunkY , coords . m_ChunkZ );
// Save the chunk right after generating, so that we don't have to generate it again on next run
m_World -> GetStorage (). QueueSaveChunk ( coords . m_ChunkX , coords . m_ChunkY , coords . m_ChunkZ );
2012-07-29 17:51:04 +00:00
NumChunksGenerated ++ ;
2012-06-14 13:06:06 +00:00
} // while (!bStop)
}
void cChunkGenerator :: DoGenerate ( int a_ChunkX , int a_ChunkY , int a_ChunkZ )
{
2013-02-08 20:57:42 +00:00
cChunkDesc ChunkDesc ( a_ChunkX , a_ChunkZ );
2013-02-05 19:57:22 +00:00
cRoot :: Get () -> GetPluginManager () -> CallHookChunkGenerating ( m_World , a_ChunkX , a_ChunkZ , & ChunkDesc );
2013-02-08 16:01:44 +00:00
m_Generator -> DoGenerate ( a_ChunkX , a_ChunkZ , ChunkDesc );
2013-02-05 19:57:22 +00:00
cRoot :: Get () -> GetPluginManager () -> CallHookChunkGenerated ( m_World , a_ChunkX , a_ChunkZ , & ChunkDesc );
2012-06-14 13:06:06 +00:00
2013-05-05 19:56:45 +00:00
#ifdef _DEBUG
// Verify that the generator has produced valid data:
ChunkDesc . VerifyHeightmap ();
#endif
2013-03-19 08:32:02 +00:00
cChunkDef :: BlockNibbles BlockMetas ;
ChunkDesc . CompressBlockMetas ( BlockMetas );
2012-06-14 13:06:06 +00:00
m_World -> SetChunkData (
2013-04-13 21:02:10 +00:00
a_ChunkX , a_ChunkZ ,
2013-03-19 08:32:02 +00:00
ChunkDesc . GetBlockTypes (), BlockMetas ,
2012-06-14 13:06:06 +00:00
NULL , NULL , // We don't have lighting, chunk will be lighted when needed
2013-02-08 16:01:44 +00:00
& ChunkDesc . GetHeightMap (), & ChunkDesc . GetBiomeMap (),
ChunkDesc . GetEntities (), ChunkDesc . GetBlockEntities (),
2012-06-14 13:06:06 +00:00
true
);
}
2013-01-25 10:12:29 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cChunkGenerator::cGenerator:
cChunkGenerator :: cGenerator :: cGenerator ( cChunkGenerator & a_ChunkGenerator ) :
m_ChunkGenerator ( a_ChunkGenerator )
{
}
void cChunkGenerator :: cGenerator :: Initialize ( cWorld * a_World , cIniFile & a_IniFile )
{
m_World = a_World ;
UNUSED ( a_IniFile );
}
EMCSBiome cChunkGenerator :: cGenerator :: GetBiomeAt ( int a_BlockX , int a_BlockZ )
{
cChunkDef :: BiomeMap Biomes ;
int Y = 0 ;
int ChunkX , ChunkZ ;
cWorld :: AbsoluteToRelative ( a_BlockX , Y , a_BlockZ , ChunkX , Y , ChunkZ );
GenerateBiomes ( ChunkX , ChunkZ , Biomes );
return cChunkDef :: GetBiome ( Biomes , a_BlockX , a_BlockZ );
}