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