-
Notifications
You must be signed in to change notification settings - Fork 20
Optimize performance of bot cloak visibility calculations #1405
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
Optimize performance of bot cloak visibility calculations #1405
Conversation
04d5fc8 to
56318cf
Compare
56318cf to
5baa513
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good for the most part, I left some questions where I wasn't sure what's going on.
Also, I tried to quantify this performance issue with some light profiling, so here's the data. Warning: wall of text ahead.
The data
This was collected on Windows Release mode builds, with 1 real player (myself) and 9 bots (neo_bot_quota 10), using the following patch to make the bots not kill me:
diff --git a/src/game/server/NextBot/Player/NextBotPlayer.h b/src/game/server/NextBot/Player/NextBotPlayer.h
index 2a3c8463..000f0b00 100644
--- a/src/game/server/NextBot/Player/NextBotPlayer.h
+++ b/src/game/server/NextBot/Player/NextBotPlayer.h
@@ -742,50 +742,53 @@ inline void NextBotPlayer< PlayerType >::PhysicsSimulate( void )
{
Update();
- // build button bits
- if ( !m_fireButtonTimer.IsElapsed() )
- m_inputButtons |= IN_ATTACK;
- if ( !m_meleeButtonTimer.IsElapsed() )
- m_inputButtons |= IN_ATTACK2;
+ if (NextBotPlayerMove.GetBool()) {
+ // build button bits
+ if (!m_fireButtonTimer.IsElapsed())
+ m_inputButtons |= IN_ATTACK;
- if ( !m_specialFireButtonTimer.IsElapsed() )
- m_inputButtons |= IN_ATTACK3;
+ if (!m_meleeButtonTimer.IsElapsed())
+ m_inputButtons |= IN_ATTACK2;
- if ( !m_useButtonTimer.IsElapsed() )
- m_inputButtons |= IN_USE;
+ if (!m_specialFireButtonTimer.IsElapsed())
+ m_inputButtons |= IN_ATTACK3;
- if ( !m_reloadButtonTimer.IsElapsed() )
- m_inputButtons |= IN_RELOAD;
+ if (!m_useButtonTimer.IsElapsed())
+ m_inputButtons |= IN_USE;
- if ( !m_forwardButtonTimer.IsElapsed() )
- m_inputButtons |= IN_FORWARD;
+ if (!m_reloadButtonTimer.IsElapsed())
+ m_inputButtons |= IN_RELOAD;
- if ( !m_backwardButtonTimer.IsElapsed() )
- m_inputButtons |= IN_BACK;
+ if (!m_forwardButtonTimer.IsElapsed())
+ m_inputButtons |= IN_FORWARD;
- if ( !m_leftButtonTimer.IsElapsed() )
- m_inputButtons |= IN_MOVELEFT;
+ if (!m_backwardButtonTimer.IsElapsed())
+ m_inputButtons |= IN_BACK;
- if ( !m_rightButtonTimer.IsElapsed() )
- m_inputButtons |= IN_MOVERIGHT;
+ if (!m_leftButtonTimer.IsElapsed())
+ m_inputButtons |= IN_MOVELEFT;
- if ( !m_jumpButtonTimer.IsElapsed() )
- m_inputButtons |= IN_JUMP;
+ if (!m_rightButtonTimer.IsElapsed())
+ m_inputButtons |= IN_MOVERIGHT;
- if ( !m_crouchButtonTimer.IsElapsed() )
- m_inputButtons |= IN_DUCK;
+ if (!m_jumpButtonTimer.IsElapsed())
+ m_inputButtons |= IN_JUMP;
- if ( !m_walkButtonTimer.IsElapsed() )
- m_inputButtons |= IN_SPEED;
+ if (!m_crouchButtonTimer.IsElapsed())
+ m_inputButtons |= IN_DUCK;
+
+ if (!m_walkButtonTimer.IsElapsed())
+ m_inputButtons |= IN_SPEED;
#ifdef NEO
- if ( !m_leanLeftButtonTimer.IsElapsed() )
- m_inputButtons |= IN_LEAN_LEFT;
+ if (!m_leanLeftButtonTimer.IsElapsed())
+ m_inputButtons |= IN_LEAN_LEFT;
- if ( !m_leanRightButtonTimer.IsElapsed() )
- m_inputButtons |= IN_LEAN_RIGHT;
+ if (!m_leanRightButtonTimer.IsElapsed())
+ m_inputButtons |= IN_LEAN_RIGHT;
#endif
+ }
m_prevInputButtons = m_inputButtons;
inputButtons = m_inputButtons;
@@ -854,7 +857,7 @@ inline void NextBotPlayer< PlayerType >::PhysicsSimulate( void )
if ( !NextBotPlayerMove.GetBool() )
{
- inputButtons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT | IN_JUMP );
+ inputButtons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT | IN_JUMP | IN_ATTACK );
forwardSpeed = 0.0f;
strafeSpeed = 0.0f;
verticalSpeed = 0.0f;Then, setting up the map with:
nb_player_move 0;
map ntre_skyline_ctg;
// Join NSF
neo_restart_this 1;
// Spawn as assault
setpos 771 -1411 36; setang 30 0 0;
and sitting there staring at the 5 enemy bots for the duration of one full round (about 3 minutes).
Using vprof
With vprof; console command to toggle profiling on/off, and +showvprof; to show the results.
I also added the budget block:
diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp
index 6cfcd9e2..5dc18ca3 100644
--- a/src/game/server/neo/neo_player.cpp
+++ b/src/game/server/neo/neo_player.cpp
@@ -39,6 +39,8 @@
#include "neo_player_shared.h"
+#include "vprof.h"
+
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
@@ -1162,6 +1164,8 @@ bool CNEO_Player::IsHiddenByFog(CBaseEntity* target) const
//-----------------------------------------------------------------------------
float CNEO_Player::GetFogObscuredRatio(CBaseEntity* target) const
{
+ VPROF_BUDGET(__FUNCTION__, "NextBotExpensive");
+Current master (a7da1a7)
This PR head (5baa513)
Using Visual Studio's CPU Performance Profiler
Current master (a7da1a7)
| Function Name | Total CPU [unit, %] | Self CPU [unit, %] | Module |
|---|---|---|---|
| | - IVision::Update | 243 (0,14 %) | 0 (0,00 %) | server |
| Function Name | Total CPU [unit, %] | Self CPU [unit, %] | Module |
|---|---|---|---|
| | - CNEO_Player::IsHiddenByFog | 12 (0,01 %) | 0 (0,00 %) | server |
| | - CNEO_Player::GetFogObscuredRatio | 3 (0,00 %) | 1 (0,00 %) | server |
| Name | Total CPU [unit, %] | Self CPU [unit, %] | Inlined Method | Inlined Location |
|---|---|---|---|---|
| || + CNEO_Player::GetFogObscuredRatio | 3 (0,00 %) | 1 (0,00 %) | ||
| ||| + neo_player.cpp:1179 | 2 (0,00 %) | 0 (0,00 %) | ||
| |||| - 0x00B815D1 | 2 (0,00 %) | 0 (0,00 %) | ||
| ||| + neo_player.cpp:1286 | 1 (0,00 %) | 1 (0,00 %) | ||
| |||| - 0x00B817C5 | 1 (0,00 %) | 1 (0,00 %) |
This PR head (5baa513)
| Function Name | Total CPU [unit, %] | Self CPU [unit, %] | Module |
|---|---|---|---|
| | - IVision::Update | 251 (0,12 %) | 0 (0,00 %) | server |
| Function Name | Total CPU [unit, %] | Self CPU [unit, %] | Module |
|---|---|---|---|
| | - CNEO_Player::IsHiddenByFog | 30 (0,02 %) | 3 (0,00 %) | server |
| | - CNEO_Player::GetFogObscuredRatio | 1 (0,00 %) | 0 (0,00 %) | server |
| Name | Total CPU [unit, %] | Self CPU [unit, %] | Inlined Method | Inlined Location |
|---|---|---|---|---|
| || + CNEO_Player::GetFogObscuredRatio | 1 (0,00 %) | 0 (0,00 %) | ||
| ||| + neo_player.cpp:1260 | 1 (0,00 %) | 0 (0,00 %) | ||
| |||| - 0x00B818DC | 1 (0,00 %) | 0 (0,00 %) |
Bot call tree (master)
And for extra context, here's a call tree for the current master, down to the bot sensing code:
| Function Name | Total CPU [unit, %] | Self CPU [unit, %] | Module | Category |
|---|---|---|---|---|
| ||||||||||||| + engine.dll!0x00007fffe7e749ef | 17152 (8,12 %) | 0 (0,00 %) | engine | Graphics | Kernel |
| |||||||||||||| + engine.dll!0x00007fffe7e76132 | 17152 (8,12 %) | 1 (0,00 %) | engine | Graphics | Kernel |
| ||||||||||||||| + engine.dll!0x00007fffe7e0caa7 | 14961 (7,08 %) | 0 (0,00 %) | engine | Kernel |
| |||||||||||||||| + engine.dll!0x00007fffe7e0da12 | 14956 (7,08 %) | 0 (0,00 %) | engine | Kernel |
| ||||||||||||||||| + CServerGameDLL::GameFrame | 14951 (7,08 %) | 13 (0,01 %) | server | Kernel |
| |||||||||||||||||| + Physics_RunThinkFunctions | 13367 (6,33 %) | 0 (0,00 %) | server | Kernel |
| ||||||||||||||||||| + Physics_SimulateEntity | 13336 (6,31 %) | 3 (0,00 %) | server | Kernel |
| |||||||||||||||||||| + CNEOBot::PhysicsSimulate | 11617 (5,50 %) | 2 (0,00 %) | server | Kernel |
| ||||||||||||||||||||| + NextBotPlayer<CNEO_Player>::PhysicsSimulate | 11609 (5,49 %) | 25 (0,01 %) | server | Kernel |
| |||||||||||||||||||||| - CBasePlayer::PhysicsSimulate | 10061 (4,76 %) | 11 (0,01 %) | server | Kernel |
| |||||||||||||||||||||| + CNEOBot::Update | 1142 (0,54 %) | 1 (0,00 %) | server | Kernel |
| ||||||||||||||||||||||| + NextBotPlayer<CNEO_Player>::Update | 1140 (0,54 %) | 2 (0,00 %) | server | Kernel |
| |||||||||||||||||||||||| + INextBot::Update | 1136 (0,54 %) | 0 (0,00 %) | server | Kernel |
| ||||||||||||||||||||||||| - Behavior<CNEOBot>::Update | 586 (0,28 %) | 2 (0,00 %) | server | Kernel |
| ||||||||||||||||||||||||| - CNEOBotLocomotion::Update | 307 (0,15 %) | 0 (0,00 %) | server | |
| ||||||||||||||||||||||||| + IVision::Update | 236 (0,11 %) | 0 (0,00 %) | server | Kernel |
| |||||||||||||||||||||||||| + IVision::UpdateKnownEntities | 235 (0,11 %) | 6 (0,00 %) | server | Kernel |
| ||||||||||||||||||||||||||| + CollectVisible::operator() | 170 (0,08 %) | 1 (0,00 %) | server | |
| |||||||||||||||||||||||||||| + CNEOBotVision::IsAbleToSee | 158 (0,07 %) | 0 (0,00 %) | server | |
| ||||||||||||||||||||||||||||| + IVision::IsAbleToSee | 157 (0,07 %) | 1 (0,00 %) | server | |
| |||||||||||||||||||||||||||||| - IVision::IsLineOfSightClearToEntity | 47 (0,02 %) | 3 (0,00 %) | server | |
| |||||||||||||||||||||||||||||| - IVision::IsInFieldOfView | 37 (0,02 %) | 1 (0,00 %) | server | |
| |||||||||||||||||||||||||||||| - INextBot::IsRangeGreaterThan | 34 (0,02 %) | 0 (0,00 %) | server | |
| |||||||||||||||||||||||||||||| - CNavArea::IsPotentiallyVisible | 18 (0,01 %) | 3 (0,00 %) | server | |
| |||||||||||||||||||||||||||||| - CNEO_Player::IsHiddenByFog | 12 (0,01 %) | 0 (0,00 %) | server |
Results
In my (somewhat limited) testing, the game was using under 1% of CPU time in the NextBotPlayer<CNEO_Player>::Update routines. For the IVision::Update path, this was about 0.1%, and for CNEO_Player::IsHiddenByFog about 0.01%. Furthermore at least for my sampling, CNEO_Player::GetFogObscuredRatio was using approximately 0.00% of the CPU time, both before and after this PR.
So at least I can't replicate this report of a performance issue locally. It could be an issue with my setup (more human targets needed? more bots needed? some other variable?), but it may be more likely that the actual hotspot is somewhere else.
In some future playtest, it might be useful for us to run a vprof profiling session over the duration the playtest, and dump out the results to file. I'm not sure if the game has good native utilities for this, but SourceMod is able to produce a dump file using vprof. The documentation for it is somewhat lacking, but for the record: https://wiki.alliedmods.net/SourceMod_Profiler
Or if you're able to reproduce this perf problem locally, then perhaps try to capture it with the VS tooling: https://learn.microsoft.com/en-us/visualstudio/profiling/cpu-usage?view=vs-2022
|
for future reference the convar neo_bot_ignore_real_players should stop the bots from shooting you when testing |
Factor in visibility cache delay with cloak disruption timer. Total 0.5 second effect time, but there can be a delay as long as 200-300ms. before the visibility cache is refreshed.
5baa513 to
1743604
Compare
Code review feedback
This might be kind of a damning indictment of how I came up with these numbers, but it's possible that these three numbers are not exactly representing the same concept but just happen to line up to my eyeballed preference. It just happens to be that a 200ms delay correlates to an average human reaction time, while the delay for the time the thermoptic flash currently neatly rounds to about half a second. Because a delay could be around 66ms, 0.200x2+0.066 about rounds to slightly less than the time I eyeballed that the flash lasts. (My reasoning being that a human would prefer the bots to lose track of them faster than a human would due to subjective feelings of fairness.) All that is to say that it might be possible that these numbers could diverge in the future if for example we change how bot ticks are handled or if we wanted to experiment with how long the cloak flash lasts. |
Description
Toolchain
Linked Issues