Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use multiple threads to generate navmeshes #2955

Merged
merged 8 commits into from Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions pkg/unvanquished_src.dpkdir/ui/server_setup.rml
Expand Up @@ -235,6 +235,18 @@
<input cvar="g_bot_infiniteMomentum" type="checkbox" />
<h3> Ignore momentum </h3>
</row>
<h2>Navmesh generation</h2>
<row>
<input cvar="cg_navgenOnLoad" type="checkbox" />
<h3>Generate navmeshes at load time</h3>
</row>
<row>
<input type="range" min="1" max="16" step="1" cvar="cg_navgenMaxThreads"/>
<h3>Number of worker threads</h3>
<p class="inline">
Current: <inlinecvar cvar="cg_navgenMaxThreads" type="number" format="%.0f"/>
</p>
</row>
</panel>

<!-- HUMANS BOT CONFIG -->
Expand Down
45 changes: 17 additions & 28 deletions src/cgame/cg_main.cpp
Expand Up @@ -158,6 +158,10 @@ Cvar::Cvar<float> cg_motionblurMinSpeed("cg_motionblurMinSpeed", "minimum speed
Cvar::Cvar<bool> cg_spawnEffects("cg_spawnEffects", "desaturate world view when dead or spawning", Cvar::NONE, true);

static Cvar::Cvar<bool> cg_navgenOnLoad("cg_navgenOnLoad", "generate navmeshes when starting a local game", Cvar::NONE, true);
// hardware_concurrency() miraculously works in NaCl.
static Cvar::Cvar<int> cg_navgenMaxThreads(
slipher marked this conversation as resolved.
Show resolved Hide resolved
"cg_navgenMaxThreads", "Maximum number of threads to use when generating navmeshes",
Cvar::NONE, std::max(1, int(std::thread::hardware_concurrency()) - 1));

// search 'fovCvar' to find usage of these (names come from config files)
// 0 means use global FOV setting
Expand Down Expand Up @@ -1075,7 +1079,7 @@ bool CG_ClientIsReady( int clientNum )
static void GenerateNavmeshes()
{
std::string mapName = Cvar::GetValue( "mapname" );
std::vector<class_t> missing;
std::bitset<PCL_NUM_CLASSES> missing;
NavgenConfig config = ReadNavgenConfig( mapName );
bool reduceTypes;
if ( !Cvar::ParseCvarValue( Cvar::GetValue( "g_bot_navmeshReduceTypes" ), reduceTypes ) )
Expand All @@ -1089,19 +1093,19 @@ static void GenerateNavmeshes()
std::string filename = NavmeshFilename( mapName, species );
if ( BG_FOpenGameOrPakPath( filename, f ) < 0 )
{
missing.push_back( species );
missing.set(species);
continue;
}
NavMeshSetHeader header;
std::string error = GetNavmeshHeader( f, config, header, mapName );
if ( !error.empty() )
{
Log::Notice( "Existing navmesh file %s can't be used: %s", filename, error );
missing.push_back( species );
missing.set(species);
}
trap_FS_FCloseFile( f );
}
if ( missing.empty() )
if ( !missing.any() )
{
return;
}
Expand All @@ -1113,30 +1117,16 @@ static void GenerateNavmeshes()
trap_UpdateScreen();

NavmeshGenerator navgen;
navgen.Init( mapName );
float classesCompleted = 0.3; // Assume that Init() is 0.3 times as much work as generating 1 species
// and assume that each species takes the same amount of time, which is actually completely wrong:
// smaller ones take much longer
float classesTotal = classesCompleted + missing.size();
for ( class_t species : missing )
{
cg.loadingText =
Str::Format( "%s — %s", message, BG_ClassModelConfig( species )->humanName );
cg.loadingFraction = classesCompleted / classesTotal;
trap_UpdateScreen();

navgen.StartGeneration( species );
do
navgen.LoadMapAndEnqueueTasks( mapName, missing );
navgen.StartBackgroundThreads( cg_navgenMaxThreads.Get() );
navgen.WaitInMainThread( []( float progress ) {
if ( progress >= cg.navmeshLoadingFraction + 0.01f )
{
float fraction = ( classesCompleted + navgen.SpeciesFractionCompleted() ) / classesTotal;
if ( fraction - cg.navmeshLoadingFraction > 0.01 )
{
cg.navmeshLoadingFraction = fraction;
trap_UpdateScreen();
}
} while ( !navgen.Step() );
++classesCompleted;
}
cg.navmeshLoadingFraction = progress;
trap_UpdateScreen();
}
} );

cg.loadingNavmesh = false;
}

Expand Down Expand Up @@ -1349,7 +1339,6 @@ void CG_Init( int serverMessageNum, int clientNum, const glconfig_t& gl, const G
CG_Rocket_LoadHuds();
CG_LoadBeaconsConfig();

CG_UpdateLoadingStep( LOAD_GLSL );
trap_S_EndRegistration();

if ( cg_navgenOnLoad.Get() && Cvar::GetValue( "sv_running" ) == "1" )
Expand Down
6 changes: 6 additions & 0 deletions src/sgame/sg_admin.cpp
Expand Up @@ -6215,6 +6215,12 @@ bool G_admin_navgen( gentity_t* ent )
return false;
}

if ( navMeshLoaded == navMeshStatus_t::GENERATING )
{
ADMP( QQ( "^3navgen:^* navmesh generation is already running" ) );
return false;
}

std::string mapName = Cvar::GetValue( "mapname" );
std::bitset<PCL_NUM_CLASSES> targets;

Expand Down
120 changes: 62 additions & 58 deletions src/sgame/sg_bot_nav.cpp
Expand Up @@ -49,28 +49,18 @@ Navigation Mesh Generation
===========================
*/

static Cvar::Cvar<int> g_bot_navgen_maxThreads(
"g_bot_navgen_maxThreads", "Number of background threads to use when generating navmeshes. 0 for main thread",
Cvar::NONE, 1);

// blocks the main thread!
void G_BlockingGenerateNavmesh( std::bitset<PCL_NUM_CLASSES> classes )
{
std::string mapName = Cvar::GetValue( "mapname" );
NavmeshGenerator navgen;

for ( int i = PCL_NONE; ++i < PCL_NUM_CLASSES; )
{
if ( !classes[ i ] )
{
continue;
}

navgen.Init( mapName );
navgen.StartGeneration( Util::enum_cast<class_t>( i ) );

while ( !navgen.Step() )
{
// ping the engine with a useless message so that it does not think the sgame VM has hung
Cvar::GetValue( "x" );
}
}
navgen.LoadMapAndEnqueueTasks( mapName, classes );
navgen.StartBackgroundThreads( g_bot_navgen_maxThreads.Get() );
navgen.WaitInMainThread( []( float ) {} );
}

// TODO: Latch(), when supported in gamelogic
Expand All @@ -79,52 +69,36 @@ Cvar::Cvar<bool> g_bot_navmeshReduceTypes(
Cvar::NONE, false);

static Cvar::Range<Cvar::Cvar<int>> msecPerFrame(
"g_bot_navgen_msecPerFrame", "time budget per frame for navmesh generation",
"g_bot_navgen_msecPerFrame", "time budget per frame for single-threaded navmesh generation",
Cvar::NONE, 20, 1, 1500 );

static Cvar::Cvar<int> frameToggle("g_bot_navgen_frame", "FOR INTERNAL USE", Cvar::NONE, 0);
static Cvar::Cvar<bool> g_bot_autocrouch("g_bot_autocrouch", "whether bots should crouch when they detect an obstacle", Cvar::NONE, true);

static NavmeshGenerator navgen;
static std::vector<class_t> navgenQueue;
static class_t generatingNow;
bool usingBackgroundThreads;
static std::unique_ptr<NavgenTask> generatingNow; // used if generating on the main thread
static int nextLogTime;

static void G_BotBackgroundNavgenShutdown()
{
navgen.~NavmeshGenerator();
new (&navgen) NavmeshGenerator();
navgenQueue.clear();
generatingNow = PCL_NONE;
generatingNow.reset();
}

void G_BotBackgroundNavgen()
// Returns true if done
static bool MainThreadBackgroundNavgen()
{
if ( navgenQueue.empty() )
{
return;
}

ASSERT_EQ( navMeshLoaded, navMeshStatus_t::GENERATING );

int stopTime = Sys::Milliseconds() + msecPerFrame.Get();

navgen.Init( Cvar::GetValue( "mapname" ) );

if ( generatingNow == PCL_NONE )
{
class_t next = navgenQueue.back();
generatingNow = next;
navgen.StartGeneration( next );
}

if ( level.time > nextLogTime )
if ( !generatingNow )
{
std::string percent = std::to_string( int(navgen.SpeciesFractionCompleted() * 100) );
trap_SendServerCommand( -1, va( "print_tr %s %s %s",
QQ( N_( "Server: generating bot navigation mesh for $1$... $2$%" ) ),
BG_Class( generatingNow )->name, percent.c_str() ) );
nextLogTime = level.time + 10000;
generatingNow = navgen.PopTask();
if ( !generatingNow )
{
return true;
}
}

// HACK: if the game simulation time gets behind the real time, the server runs a bunch of
Expand All @@ -136,23 +110,44 @@ void G_BotBackgroundNavgen()
static int lastToggle = -12345;
if ( lastToggle == frameToggle.Get() )
{
return;
return false;
}
lastToggle = frameToggle.Get();
trap_SendConsoleCommand( "toggle g_bot_navgen_frame" );

while ( Sys::Milliseconds() < stopTime )
{
if ( navgen.Step() )
if ( navgen.Step( *generatingNow ) )
{
ASSERT_EQ( generatingNow, navgenQueue.back() );
navgenQueue.pop_back();
generatingNow = PCL_NONE;
generatingNow->context.DoMainThreadTasks();
generatingNow.reset();
break;
}
}

if ( navgenQueue.empty() ) // finished?
return false;
}

void G_BotBackgroundNavgen()
{
if ( navMeshLoaded != navMeshStatus_t::GENERATING )
{
return;
}

bool done;

if ( usingBackgroundThreads )
{
done = navgen.ThreadsDone();
navgen.HandleFinishedTasks();
}
else
{
done = MainThreadBackgroundNavgen();
}

if ( done )
{
G_BotBackgroundNavgenShutdown();

Expand All @@ -170,6 +165,13 @@ void G_BotBackgroundNavgen()
// Bots on spectate will now join in their think function
}
}
else if ( level.time > nextLogTime )
{
std::string percent = std::to_string( int(navgen.FractionComplete() * 100) );
trap_SendServerCommand( -1, va( "print_tr %s %s",
QQ( N_( "Server: generating bot navigation meshes... $1$%" ) ), percent.c_str() ) );
nextLogTime = level.time + 10000;
}
}

/*
Expand All @@ -196,7 +198,8 @@ void G_BotNavInit( int generateNeeded )
}

std::bitset<PCL_NUM_CLASSES> missing;
NavgenConfig config = ReadNavgenConfig( Cvar::GetValue( "mapname" ) );
std::string mapName = Cvar::GetValue( "mapname" );
NavgenConfig config = ReadNavgenConfig( mapName );

for ( class_t i : RequiredNavmeshes( g_bot_navmeshReduceTypes.Get() ) )
{
Expand Down Expand Up @@ -232,14 +235,15 @@ void G_BotNavInit( int generateNeeded )
}
else
{
ASSERT( navgenQueue.empty() );
for ( int i = PCL_NUM_CLASSES; --i > PCL_NONE; )
ASSERT( !generatingNow );
navgen.LoadMapAndEnqueueTasks( mapName, missing );
usingBackgroundThreads = g_bot_navgen_maxThreads.Get() > 0;

if ( usingBackgroundThreads )
{
if ( missing[ i ] )
{
navgenQueue.push_back( Util::enum_cast<class_t>( i ) );
}
navgen.StartBackgroundThreads( g_bot_navgen_maxThreads.Get() );
}

navMeshLoaded = navMeshStatus_t::GENERATING;
return;
}
Expand Down