Skip to content

Commit

Permalink
Simplify server stepping and dtime calculation
Browse files Browse the repository at this point in the history
Old:
* game or dedicated_server_step increment server dtime counter. ServerThread
  runs in a loop in which it receives for at least 30 ms, and then reads and
  resets the dtime counter. If the counter is > 0, it does a server-step
  (AsyncRunStep).
* There are aliasing effects. E.g. in singleplayer the server-step dtimes are
  0ms (0 frames, server-step skipped), 16ms (1 frame) and 33ms (2 frames), and
  the actual times between the server-steps are >30ms or >60ms.

New:
* ServerThread receives (with timeout) until next step shall start (or longer
  if there are more packets), and calculates dtime itself.
* In singleplayer, Game tells the server if the game is paused or unfocused.

Effects:
* Shorter server dtimes are possible (i.e. <30ms).
* No dtime value aliasing.
* Server-step dtime is decoupled from client-step dtime (i.e. low client fps
  doesn't cause coarse server-steps).
  Note that pause and unfocus are now handled explicitly. But the results are
  similar to before.
  • Loading branch information
Desour committed Mar 29, 2023
1 parent 0f496f1 commit 86c651e
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 85 deletions.
23 changes: 10 additions & 13 deletions src/client/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1269,8 +1269,7 @@ void Game::run()
resumeAnimation();
}

if (!m_is_paused)
step(dtime);
step(dtime);
processClientEvents(&cam_view_target);
updateDebugState();
updateCamera(dtime);
Expand Down Expand Up @@ -1653,10 +1652,7 @@ bool Game::connectToServer(const GameStartData &start_data,
fps_control.limit(device, &dtime);

// Update client and server
client->step(dtime);

if (server != NULL)
server->step(dtime);
step(dtime);

// End condition
if (client->getState() == LC_Init) {
Expand Down Expand Up @@ -1715,10 +1711,7 @@ bool Game::getServerContent(bool *aborted)
fps_control.limit(device, &dtime);

// Update client and server
client->step(dtime);

if (server != NULL)
server->step(dtime);
step(dtime);

// End condition
if (client->mediaReceived() && client->itemdefReceived() &&
Expand Down Expand Up @@ -2702,10 +2695,14 @@ void Game::updatePlayerControl(const CameraOrientation &cam)

inline void Game::step(f32 dtime)
{
if (server)
server->step(dtime);
if (server) {
server->step();
server->setSingleplayerData(Server::SingleplayerData{m_is_paused,
!device->isWindowFocused() || g_menumgr.pausesGame()});
}

client->step(dtime);
if (!m_is_paused)
client->step(dtime);
}

static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
Expand Down
8 changes: 4 additions & 4 deletions src/network/connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1418,15 +1418,15 @@ void Connection::Disconnect()
putCommand(ConnectionCommand::disconnect());
}

bool Connection::Receive(NetworkPacket *pkt, u32 timeout)
bool Connection::ReceiveTimeoutMs(NetworkPacket *pkt, u32 timeout_ms)
{
/*
Note that this function can potentially wait infinitely if non-data
events keep happening before the timeout expires.
This is not considered to be a problem (is it?)
*/
for(;;) {
ConnectionEventPtr e_ptr = waitEvent(timeout);
ConnectionEventPtr e_ptr = waitEvent(timeout_ms);
const ConnectionEvent &e = *e_ptr;

if (e.type != CONNEVENT_NONE) {
Expand Down Expand Up @@ -1467,14 +1467,14 @@ bool Connection::Receive(NetworkPacket *pkt, u32 timeout)

void Connection::Receive(NetworkPacket *pkt)
{
bool any = Receive(pkt, m_bc_receive_timeout);
bool any = ReceiveTimeoutMs(pkt, m_bc_receive_timeout);
if (!any)
throw NoIncomingDataException("No incoming data");
}

bool Connection::TryReceive(NetworkPacket *pkt)
{
return Receive(pkt, 0);
return ReceiveTimeoutMs(pkt, 0);
}

void Connection::Send(session_t peer_id, u8 channelnum,
Expand Down
7 changes: 3 additions & 4 deletions src/network/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ class Connection
~Connection();

/* Interface */
ConnectionEventPtr waitEvent(u32 timeout_ms);
ConnectionEventPtr waitEvent(u32 timeout_us);

void putCommand(ConnectionCommandPtr c);

Expand All @@ -714,7 +714,8 @@ class Connection
void Connect(Address address);
bool Connected();
void Disconnect();
void Receive(NetworkPacket* pkt);
bool ReceiveTimeoutMs(NetworkPacket *pkt, u32 timeout_ms);
void Receive(NetworkPacket *pkt);
bool TryReceive(NetworkPacket *pkt);
void Send(session_t peer_id, u8 channelnum, NetworkPacket *pkt, bool reliable);
session_t GetPeerID() const { return m_peer_id; }
Expand Down Expand Up @@ -747,8 +748,6 @@ class Connection
// Command queue: user -> SendThread
MutexedQueue<ConnectionCommandPtr> m_command_queue;

bool Receive(NetworkPacket *pkt, u32 timeout);

void putEvent(ConnectionEventPtr e);

void TriggerSend();
Expand Down
25 changes: 20 additions & 5 deletions src/porting.h
Original file line number Diff line number Diff line change
Expand Up @@ -269,19 +269,34 @@ inline u64 getTime(TimePrecision prec)
FATAL_ERROR("Called getTime with invalid time precision");
}

/** Calculate the delta of two values of a monotonically growing counter.
*
* @param old_ctr old value
* @param new_ctr new value
* @return non-negative delta value
*/
inline u64 getDeltaMGCtr(u64 old_ctr, u64 new_ctr)
{
if (new_ctr >= old_ctr)
return new_ctr - old_ctr;
else
return old_ctr - new_ctr; // overflow occurred
}

/**
* Delta calculation function arguments.
* @param old_time_ms old time for delta calculation
* @param new_time_ms new time for delta calculation
* @return positive delta value
* @return non-negative delta value
*/
inline u64 getDeltaMs(u64 old_time_ms, u64 new_time_ms)
{
if (new_time_ms >= old_time_ms) {
return (new_time_ms - old_time_ms);
}
return getDeltaMGCtr(old_time_ms, new_time_ms);
}

return (old_time_ms - new_time_ms);
inline u64 getDeltaUs(u64 old_time_us, u64 new_time_us)
{
return getDeltaMGCtr(old_time_us, new_time_us);
}

inline const char *getPlatformName()
Expand Down
112 changes: 61 additions & 51 deletions src/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,25 +104,44 @@ void *ServerThread::run()
/*
* The real business of the server happens on the ServerThread.
* How this works:
* AsyncRunStep() runs an actual server step as soon as enough time has
* passed (dedicated_server_loop keeps track of that).
* Receive() blocks at least(!) 30ms waiting for a packet (so this loop
* doesn't busy wait) and will process any remaining packets.
* AsyncRunStep() (which runs the actual server step) is called at the
* server-step frequency. Receive() is used for waiting between the steps.
*/

const float dedicated_server_step = g_settings->getFloat("dedicated_server_step");
const float singleplayer_server_step = 1.0f / g_settings->getFloat("fps_max");
const float singleplayer_server_step_unfocused = 1.0f / g_settings->getFloat("fps_max_unfocused");

try {
m_server->AsyncRunStep(true);
m_server->AsyncRunStep(0.0f, true);
} catch (con::ConnectionBindFailed &e) {
m_server->setAsyncFatalError(e.what());
} catch (LuaError &e) {
m_server->setAsyncFatalError(e);
}

float dtime = 0.0f;

while (!stopRequested()) {
u64 t0 = porting::getTimeUs();

float target_steplen = dedicated_server_step;
bool is_paused = false;

if (m_server->isSingleplayer()) {
Server::SingleplayerData spdata = m_server->getSingleplayerData();
target_steplen = spdata.unfocused ?
singleplayer_server_step_unfocused : singleplayer_server_step;
is_paused = spdata.paused;
}

try {
m_server->AsyncRunStep();
if (!is_paused)
m_server->AsyncRunStep(dtime);

m_server->Receive();
float remaining_time = target_steplen
- 1.0e-6f * (float)porting::getDeltaUs(t0, porting::getTimeUs());
m_server->Receive(remaining_time);

} catch (con::PeerNotFoundException &e) {
infostream<<"Server: PeerNotFoundException"<<std::endl;
Expand All @@ -132,6 +151,8 @@ void *ServerThread::run()
} catch (LuaError &e) {
m_server->setAsyncFatalError(e);
}

dtime = 1.0e-6f * (float)porting::getDeltaUs(t0, porting::getTimeUs());
}

END_DEBUG_EXCEPTION_HANDLER
Expand Down Expand Up @@ -567,15 +588,8 @@ void Server::stop()
infostream<<"Server: Threads stopped"<<std::endl;
}

void Server::step(float dtime)
void Server::step()
{
// Limit a bit
if (dtime > 2.0)
dtime = 2.0;
{
MutexAutoLock lock(m_step_dtime_mutex);
m_step_dtime += dtime;
}
// Throw if fatal error occurred in thread
std::string async_err = m_async_fatal_error.get();
if (!async_err.empty()) {
Expand All @@ -588,30 +602,18 @@ void Server::step(float dtime)
}
}

void Server::AsyncRunStep(bool initial_step)
void Server::AsyncRunStep(float dtime, bool initial_step)
{

float dtime;
{
MutexAutoLock lock1(m_step_dtime_mutex);
dtime = m_step_dtime;
}

{
// Send blocks to clients
SendBlocks(dtime);
}

if((dtime < 0.001) && !initial_step)
if ((dtime < 0.001f) && !initial_step)
return;

ScopeProfiler sp(g_profiler, "Server::AsyncRunStep()", SPT_AVG);

{
MutexAutoLock lock1(m_step_dtime_mutex);
m_step_dtime -= dtime;
}

/*
Update uptime
*/
Expand Down Expand Up @@ -1041,26 +1043,21 @@ void Server::AsyncRunStep(bool initial_step)
m_shutdown_state.tick(dtime, this);
}

void Server::Receive()
void Server::Receive(float timeout)
{
const u64 t0 = porting::getTimeUs();
const float timeout_us = timeout * 1.0e6f;
auto remaining_time_us = [&]() -> float {
return timeout_us - (float)porting::getDeltaUs(t0, porting::getTimeUs());
};

NetworkPacket pkt;
session_t peer_id;
bool first = true;
for (;;) {
pkt.clear();
peer_id = 0;
try {
/*
In the first iteration *wait* for a packet, afterwards process
all packets that are immediately available (no waiting).
*/
if (first) {
m_con->Receive(&pkt);
first = false;
} else {
if (!m_con->TryReceive(&pkt))
return;
}
m_con->ReceiveTimeoutMs(&pkt, (u32)std::max(0.0f, remaining_time_us()) / 1000);

peer_id = pkt.getPeerId();
m_packet_recv_counter->increment();
Expand All @@ -1079,7 +1076,10 @@ void Server::Receive()
} catch (const con::PeerNotFoundException &e) {
// Do nothing
} catch (const con::NoIncomingDataException &e) {
return;
// Already break if there's 1ms left, as ReceiveTimeoutMs is too coarse
// and a faster server-step is better than busy waiting.
if (remaining_time_us() < 1000.0f)
break;
}
}
}
Expand Down Expand Up @@ -3741,6 +3741,18 @@ std::string Server::getBuiltinLuaPath()
return porting::path_share + DIR_DELIM + "builtin";
}

void Server::setSingleplayerData(SingleplayerData spdata)
{
MutexAutoLock m_singpleplayer_data_mutex;
m_singpleplayer_data = spdata;
}

Server::SingleplayerData Server::getSingleplayerData()
{
MutexAutoLock m_singpleplayer_data_mutex;
return m_singpleplayer_data;
}

v3f Server::findSpawnPos()
{
ServerMap &map = m_env->getServerMap();
Expand Down Expand Up @@ -3895,21 +3907,19 @@ void dedicated_server_loop(Server &server, bool &kill)

IntervalLimiter m_profiler_interval;

static thread_local const float steplen =
g_settings->getFloat("dedicated_server_step");
static thread_local const float profiler_print_interval =
g_settings->getFloat("profiler_print_interval");
const float steplen = g_settings->getFloat("dedicated_server_step");
const float profiler_print_interval = g_settings->getFloat("profiler_print_interval");

/*
* The dedicated server loop only does time-keeping (in Server::step) and
* provides a way to main.cpp to kill the server externally (bool &kill).
* The dedicated server loop only provides a way to main.cpp to kill the
* server externally (bool &kill).
*/

for(;;) {
// This is kind of a hack but can be done like this
// because server.step() is very light
sleep_ms((int)(steplen*1000.0));
server.step(steplen);
sleep_ms((int)(steplen*1000.0f));
server.step();

if (server.isShutdownRequested() || kill)
break;
Expand Down

0 comments on commit 86c651e

Please sign in to comment.