2012-06-14 09:06:06 -04:00
# include "Globals.h"
2012-09-23 16:14:04 -04:00
# include "ChunkGenerator.h"
2014-10-23 09:15:10 -04:00
# include "../IniFile.h"
2013-01-25 05:12:29 -05:00
# include "ChunkDesc.h"
# include "ComposableGenerator.h"
2013-04-27 09:38:40 -04:00
# include "Noise3DGenerator.h"
2014-10-19 09:10:18 -04:00
# include "FastRandom.h"
2012-06-14 09:06:06 -04:00
2015-07-31 10:49:10 -04:00
/** If the generation queue size exceeds this number, a warning will be output */
2013-11-16 13:55:49 -05:00
const unsigned int QUEUE_WARNING_LIMIT = 1000 ;
2012-06-14 09:06:06 -04:00
2015-07-31 10:49:10 -04:00
/** If the generation queue size exceeds this number, chunks with no clients will be skipped */
2013-11-16 13:55:49 -05:00
const unsigned int QUEUE_SKIP_LIMIT = 500 ;
2012-06-14 09:06:06 -04:00
2014-07-17 16:15:34 -04:00
////////////////////////////////////////////////////////////////////////////////
2012-06-14 09:06:06 -04:00
// cChunkGenerator:
2013-01-25 05:12:29 -05:00
cChunkGenerator : : cChunkGenerator ( void ) :
super ( " cChunkGenerator " ) ,
2014-08-21 16:39:53 -04:00
m_Seed ( 0 ) , // Will be overwritten by the actual generator
2014-10-20 16:55:07 -04:00
m_Generator ( nullptr ) ,
m_PluginInterface ( nullptr ) ,
m_ChunkSink ( nullptr )
2012-06-14 09:06:06 -04:00
{
}
cChunkGenerator : : ~ cChunkGenerator ( )
{
Stop ( ) ;
}
2014-01-10 16:22:54 -05:00
bool cChunkGenerator : : Start ( cPluginInterface & a_PluginInterface , cChunkSink & a_ChunkSink , cIniFile & a_IniFile )
2012-06-14 09:06:06 -04:00
{
2014-01-10 16:22:54 -05:00
m_PluginInterface = & a_PluginInterface ;
m_ChunkSink = & a_ChunkSink ;
2014-09-03 17:02:00 -04:00
// Get the seed; create a new one and log it if not found in the INI file:
if ( a_IniFile . HasValue ( " Seed " , " Seed " ) )
{
m_Seed = a_IniFile . GetValueI ( " Seed " , " Seed " ) ;
}
else
{
MTRand rnd ;
m_Seed = rnd . randInt ( ) ;
LOGINFO ( " Chosen a new random seed for world: %d " , m_Seed ) ;
a_IniFile . SetValueI ( " Seed " , " Seed " , m_Seed ) ;
}
// Get the generator engine based on the INI file settings:
2013-01-25 05:12:29 -05:00
AString GeneratorName = a_IniFile . GetValueSet ( " Generator " , " Generator " , " Composable " ) ;
2013-04-27 09:38:40 -04:00
if ( NoCaseCompare ( GeneratorName , " Noise3D " ) = = 0 )
2013-01-24 07:15:36 -05:00
{
2013-04-27 09:38:40 -04:00
m_Generator = new cNoise3DGenerator ( * this ) ;
2013-01-24 07:15:36 -05:00
}
2012-06-14 09:06:06 -04:00
else
{
2013-01-25 05:12:29 -05:00
if ( NoCaseCompare ( GeneratorName , " composable " ) ! = 0 )
2012-06-14 09:06:06 -04:00
{
2013-01-25 05:12:29 -05:00
LOGWARN ( " [Generator]::Generator value \" %s \" not recognized, using \" Composable \" . " , GeneratorName . c_str ( ) ) ;
2012-06-14 09:06:06 -04:00
}
2013-01-25 05:12:29 -05:00
m_Generator = new cComposableGenerator ( * this ) ;
2012-06-14 09:06:06 -04:00
}
2014-10-20 16:55:07 -04:00
if ( m_Generator = = nullptr )
2012-06-14 09:06:06 -04:00
{
2013-01-25 05:12:29 -05:00
LOGERROR ( " Generator could not start, aborting the server " ) ;
return false ;
2012-06-14 09:06:06 -04:00
}
2013-11-16 13:55:49 -05:00
2014-01-10 16:22:54 -05:00
m_Generator - > Initialize ( a_IniFile ) ;
2013-11-16 13:55:49 -05:00
2013-01-25 05:12:29 -05:00
return super : : Start ( ) ;
2012-06-14 09:06:06 -04:00
}
2013-01-25 05:12:29 -05:00
void cChunkGenerator : : Stop ( void )
2012-06-14 09:06:06 -04:00
{
2013-01-25 05:12:29 -05:00
m_ShouldTerminate = true ;
m_Event . Set ( ) ;
m_evtRemoved . Set ( ) ; // Wake up anybody waiting for empty queue
Wait ( ) ;
2012-06-14 09:06:06 -04:00
2013-01-25 05:12:29 -05:00
delete m_Generator ;
2014-10-20 16:55:07 -04:00
m_Generator = nullptr ;
2012-06-14 09:06:06 -04:00
}
2014-12-10 16:35:16 -05:00
void cChunkGenerator : : QueueGenerateChunk ( int a_ChunkX , int a_ChunkZ , bool a_ForceGenerate , cChunkCoordCallback * a_Callback )
2012-06-14 09:06:06 -04:00
{
2014-09-05 17:26:00 -04:00
ASSERT ( m_ChunkSink - > IsChunkQueued ( a_ChunkX , a_ChunkZ ) ) ;
2012-06-14 09:06:06 -04:00
{
cCSLock Lock ( m_CS ) ;
2013-11-16 13:55:49 -05:00
2012-06-14 09:06:06 -04:00
// Add to queue, issue a warning if too many:
if ( m_Queue . size ( ) > = QUEUE_WARNING_LIMIT )
{
2014-03-12 13:34:50 -04:00
LOGWARN ( " WARNING: Adding chunk [%i, %i] to generation queue; Queue is too big! ( " SIZE_T_FMT " ) " , a_ChunkX , a_ChunkZ , m_Queue . size ( ) ) ;
2012-06-14 09:06:06 -04:00
}
2014-12-10 16:35:16 -05:00
m_Queue . push_back ( cQueueItem { a_ChunkX , a_ChunkZ , a_ForceGenerate , a_Callback } ) ;
2012-06-14 09:06:06 -04:00
}
2013-11-16 13:55:49 -05:00
2012-06-14 09:06:06 -04:00
m_Event . Set ( ) ;
}
void cChunkGenerator : : GenerateBiomes ( int a_ChunkX , int a_ChunkZ , cChunkDef : : BiomeMap & a_BiomeMap )
{
2014-10-20 16:55:07 -04:00
if ( m_Generator ! = nullptr )
2012-09-01 17:37:41 -04:00
{
2013-01-25 05:12:29 -05:00
m_Generator - > GenerateBiomes ( a_ChunkX , a_ChunkZ , a_BiomeMap ) ;
2012-09-01 17:37:41 -04:00
}
2012-06-14 09:06:06 -04: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 ) ;
2015-07-29 11:04:03 -04:00
return static_cast < int > ( m_Queue . size ( ) ) ;
2012-06-14 09:06:06 -04:00
}
EMCSBiome cChunkGenerator : : GetBiomeAt ( int a_BlockX , int a_BlockZ )
{
2014-10-20 16:55:07 -04:00
ASSERT ( m_Generator ! = nullptr ) ;
2013-01-25 05:12:29 -05:00
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 ) ;
2014-11-27 15:19:52 -05:00
int Block = BlockStringToType ( BlockType ) ;
2013-01-25 05:12:29 -05:00
if ( Block < 0 )
{
2014-07-19 08:53:41 -04:00
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 ( ) ) ;
2014-11-27 15:19:52 -05:00
return static_cast < BLOCKTYPE > ( BlockStringToType ( a_Default ) ) ;
2013-01-25 05:12:29 -05:00
}
2014-11-27 15:19:52 -05:00
return static_cast < BLOCKTYPE > ( Block ) ;
2012-06-14 09:06:06 -04:00
}
void cChunkGenerator : : Execute ( void )
{
2012-07-29 13:51:04 -04: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)
2013-11-16 13:55:49 -05:00
2012-06-14 09:06:06 -04:00
while ( ! m_ShouldTerminate )
{
cCSLock Lock ( m_CS ) ;
2014-01-24 18:56:19 -05:00
while ( m_Queue . empty ( ) )
2012-06-14 09:06:06 -04:00
{
2012-07-29 13:51:04 -04:00
if ( ( NumChunksGenerated > 16 ) & & ( clock ( ) - LastReportTick > CLOCKS_PER_SEC ) )
{
2015-05-09 03:25:09 -04:00
LOG ( " Chunk generator performance: %.2f ch / sec (%d ch total) " ,
2015-07-29 11:04:03 -04:00
static_cast < double > ( NumChunksGenerated ) * CLOCKS_PER_SEC / ( clock ( ) - GenerationStart ) ,
2012-07-29 13:51:04 -04:00
NumChunksGenerated
) ;
}
2012-06-14 09:06:06 -04:00
cCSUnlock Unlock ( Lock ) ;
m_Event . Wait ( ) ;
if ( m_ShouldTerminate )
{
return ;
}
2012-07-29 13:51:04 -04:00
NumChunksGenerated = 0 ;
GenerationStart = clock ( ) ;
LastReportTick = clock ( ) ;
2012-06-14 09:06:06 -04:00
}
2013-11-16 13:55:49 -05:00
2014-01-24 18:56:19 -05:00
if ( m_Queue . empty ( ) )
{
2014-01-25 09:42:26 -05:00
// Sometimes the queue remains empty
// If so, we can't do any front() operations on it!
2014-01-24 18:56:19 -05:00
continue ;
}
2014-12-10 16:35:16 -05:00
cQueueItem item = m_Queue . front ( ) ; // Get next chunk from the queue
2012-06-14 09:06:06 -04:00
bool SkipEnabled = ( m_Queue . size ( ) > QUEUE_SKIP_LIMIT ) ;
2014-12-10 16:35:16 -05:00
m_Queue . erase ( m_Queue . begin ( ) ) ; // Remove the item from the queue
2014-07-17 17:15:53 -04:00
Lock . Unlock ( ) ; // Unlock ASAP
2012-06-14 09:06:06 -04:00
m_evtRemoved . Set ( ) ;
2012-07-29 13:51:04 -04:00
// Display perf info once in a while:
if ( ( NumChunksGenerated > 16 ) & & ( clock ( ) - LastReportTick > 2 * CLOCKS_PER_SEC ) )
{
2015-05-09 03:25:09 -04:00
LOG ( " Chunk generator performance: %.2f ch / sec (%d ch total) " ,
2015-07-29 11:04:03 -04:00
static_cast < double > ( NumChunksGenerated ) * CLOCKS_PER_SEC / ( clock ( ) - GenerationStart ) ,
2012-07-29 13:51:04 -04:00
NumChunksGenerated
) ;
LastReportTick = clock ( ) ;
}
2014-12-10 16:35:16 -05:00
// Skip the chunk if it's already generated and regeneration is not forced:
if ( ! item . m_ForceGenerate & & m_ChunkSink - > IsChunkValid ( item . m_ChunkX , item . m_ChunkZ ) )
2014-09-02 18:14:51 -04:00
{
2014-12-10 16:35:16 -05:00
LOGD ( " Chunk [%d, %d] already generated, skipping generation " , item . m_ChunkX , item . m_ChunkZ ) ;
if ( item . m_Callback ! = nullptr )
{
item . m_Callback - > Call ( item . m_ChunkX , item . m_ChunkZ ) ;
}
2014-09-02 18:14:51 -04:00
continue ;
}
2014-12-10 16:35:16 -05:00
// Skip the chunk if the generator is overloaded:
if ( SkipEnabled & & ! m_ChunkSink - > HasChunkAnyClients ( item . m_ChunkX , item . m_ChunkZ ) )
2012-06-14 09:06:06 -04:00
{
2014-12-10 16:35:16 -05:00
LOGWARNING ( " Chunk generator overloaded, skipping chunk [%d, %d] " , item . m_ChunkX , item . m_ChunkZ ) ;
if ( item . m_Callback ! = nullptr )
{
item . m_Callback - > Call ( item . m_ChunkX , item . m_ChunkZ ) ;
}
2012-06-14 09:06:06 -04:00
continue ;
}
2013-11-16 13:55:49 -05:00
2014-12-10 16:35:16 -05:00
// Generate the chunk:
LOGD ( " Generating chunk [%d, %d] " , item . m_ChunkX , item . m_ChunkZ ) ;
DoGenerate ( item . m_ChunkX , item . m_ChunkZ ) ;
if ( item . m_Callback ! = nullptr )
{
item . m_Callback - > Call ( item . m_ChunkX , item . m_ChunkZ ) ;
}
2012-07-29 13:51:04 -04:00
NumChunksGenerated + + ;
2012-06-14 09:06:06 -04:00
} // while (!bStop)
}
2014-08-28 05:36:35 -04:00
void cChunkGenerator : : DoGenerate ( int a_ChunkX , int a_ChunkZ )
2012-06-14 09:06:06 -04:00
{
2014-10-20 16:55:07 -04:00
ASSERT ( m_PluginInterface ! = nullptr ) ;
ASSERT ( m_ChunkSink ! = nullptr ) ;
2014-09-05 16:16:48 -04:00
ASSERT ( m_ChunkSink - > IsChunkQueued ( a_ChunkX , a_ChunkZ ) ) ;
2013-02-08 15:57:42 -05:00
cChunkDesc ChunkDesc ( a_ChunkX , a_ChunkZ ) ;
2014-01-10 16:22:54 -05:00
m_PluginInterface - > CallHookChunkGenerating ( ChunkDesc ) ;
2013-02-08 11:01:44 -05:00
m_Generator - > DoGenerate ( a_ChunkX , a_ChunkZ , ChunkDesc ) ;
2014-01-10 16:22:54 -05:00
m_PluginInterface - > CallHookChunkGenerated ( ChunkDesc ) ;
2013-11-16 13:55:49 -05:00
2013-05-05 15:56:45 -04:00
# ifdef _DEBUG
// Verify that the generator has produced valid data:
ChunkDesc . VerifyHeightmap ( ) ;
# endif
2013-11-16 13:55:49 -05:00
2014-01-10 16:22:54 -05:00
m_ChunkSink - > OnChunkGenerated ( ChunkDesc ) ;
2012-06-14 09:06:06 -04:00
}
2013-01-25 05:12:29 -05:00
2014-07-17 16:15:34 -04:00
////////////////////////////////////////////////////////////////////////////////
2013-01-25 05:12:29 -05:00
// cChunkGenerator::cGenerator:
cChunkGenerator : : cGenerator : : cGenerator ( cChunkGenerator & a_ChunkGenerator ) :
m_ChunkGenerator ( a_ChunkGenerator )
{
}
2014-01-10 16:22:54 -05:00
void cChunkGenerator : : cGenerator : : Initialize ( cIniFile & a_IniFile )
2013-01-25 05:12:29 -05:00
{
UNUSED ( a_IniFile ) ;
}
EMCSBiome cChunkGenerator : : cGenerator : : GetBiomeAt ( int a_BlockX , int a_BlockZ )
{
cChunkDef : : BiomeMap Biomes ;
int Y = 0 ;
int ChunkX , ChunkZ ;
2014-01-10 16:22:54 -05:00
cChunkDef : : AbsoluteToRelative ( a_BlockX , Y , a_BlockZ , ChunkX , ChunkZ ) ;
2013-01-25 05:12:29 -05:00
GenerateBiomes ( ChunkX , ChunkZ , Biomes ) ;
return cChunkDef : : GetBiome ( Biomes , a_BlockX , a_BlockZ ) ;
}