You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

256 lines
6.4 KiB

/**
* @file nthread.cpp
*
* Implementation of functions for managing game ticks.
*/
#include "nthread.h"
#include <fmt/core.h>
#include "diablo.h"
#include "engine/demomode.h"
#include "gmenu.h"
#include "storm/storm_net.hpp"
#include "utils/sdl_mutex.h"
#include "utils/sdl_thread.h"
#include "utils/str_cat.hpp"
namespace devilution {
uint8_t sgbNetUpdateRate;
size_t gdwMsgLenTbl[MAX_PLRS];
uint32_t gdwTurnsInTransit;
uintptr_t glpMsgTbl[MAX_PLRS];
uint32_t gdwLargestMsgSize;
uint32_t gdwNormalMsgSize;
int last_tick;
uint8_t ProgressToNextGameTick = 0;
namespace {
SdlMutex MemCrit;
bool nthread_should_run;
int8_t sgbSyncCountdown;
uint32_t turn_upper_bit;
bool sgbTicsOutOfSync;
int8_t sgbPacketCountdown;
bool sgbThreadIsRunning;
SdlThread Thread;
void NthreadHandler()
{
if (!nthread_should_run) {
return;
}
while (true) {
MemCrit.lock();
if (!nthread_should_run) {
MemCrit.unlock();
break;
}
nthread_send_and_recv_turn(0, 0);
int delta = gnTickDelay;
if (nthread_recv_turns())
delta = last_tick - SDL_GetTicks();
MemCrit.unlock();
if (delta > 0)
SDL_Delay(delta);
if (!nthread_should_run)
return;
}
}
} // namespace
void nthread_terminate_game(const char *pszFcn)
{
uint32_t sErr = SErrGetLastError();
if (sErr == STORM_ERROR_INVALID_PLAYER) {
return;
}
if (sErr != STORM_ERROR_GAME_TERMINATED && sErr != STORM_ERROR_NOT_IN_GAME) {
app_fatal(StrCat(pszFcn, ":\n", pszFcn));
}
gbGameDestroyed = true;
}
uint32_t nthread_send_and_recv_turn(uint32_t curTurn, int turnDelta)
{
uint32_t curTurnsInTransit;
if (!SNetGetTurnsInTransit(&curTurnsInTransit)) {
nthread_terminate_game("SNetGetTurnsInTransit");
return 0;
}
while (curTurnsInTransit++ < gdwTurnsInTransit) {
uint32_t turnTmp = turn_upper_bit | (curTurn & 0x7FFFFFFF);
turn_upper_bit = 0;
uint32_t turn = turnTmp;
if (!SNetSendTurn((char *)&turn, sizeof(turn))) {
nthread_terminate_game("SNetSendTurn");
return 0;
}
curTurn += turnDelta;
if (curTurn >= 0x7FFFFFFF)
curTurn &= 0xFFFF;
}
return curTurn;
}
bool nthread_recv_turns(bool *pfSendAsync)
{
if (pfSendAsync != nullptr)
*pfSendAsync = false;
sgbPacketCountdown--;
if (sgbPacketCountdown > 0) {
last_tick += gnTickDelay;
return true;
}
sgbSyncCountdown--;
sgbPacketCountdown = sgbNetUpdateRate;
if (sgbSyncCountdown != 0) {
if (pfSendAsync != nullptr)
*pfSendAsync = true;
last_tick += gnTickDelay;
return true;
}
if (!SNetReceiveTurns(MAX_PLRS, (char **)glpMsgTbl, gdwMsgLenTbl, &player_state[0])) {
if (SErrGetLastError() != STORM_ERROR_NO_MESSAGES_WAITING)
nthread_terminate_game("SNetReceiveTurns");
sgbTicsOutOfSync = false;
sgbSyncCountdown = 1;
sgbPacketCountdown = 1;
return false;
}
if (!sgbTicsOutOfSync) {
sgbTicsOutOfSync = true;
last_tick = SDL_GetTicks();
}
sgbSyncCountdown = 4;
multi_msg_countdown();
if (pfSendAsync != nullptr)
*pfSendAsync = true;
last_tick += gnTickDelay;
return true;
}
void nthread_set_turn_upper_bit()
{
turn_upper_bit = 0x80000000;
}
void nthread_start(bool setTurnUpperBit)
{
last_tick = SDL_GetTicks();
sgbPacketCountdown = 1;
sgbSyncCountdown = 1;
sgbTicsOutOfSync = true;
if (setTurnUpperBit)
nthread_set_turn_upper_bit();
else
turn_upper_bit = 0;
_SNETCAPS caps;
caps.size = 36;
SNetGetProviderCaps(&caps);
gdwTurnsInTransit = caps.defaultturnsintransit;
if (gdwTurnsInTransit == 0)
gdwTurnsInTransit = 1;
if (caps.defaultturnssec <= 20 && caps.defaultturnssec != 0)
sgbNetUpdateRate = 20 / caps.defaultturnssec;
else
sgbNetUpdateRate = 1;
uint32_t largestMsgSize = 512;
if (caps.maxmessagesize < 0x200)
largestMsgSize = caps.maxmessagesize;
gdwLargestMsgSize = largestMsgSize;
gdwNormalMsgSize = caps.bytessec * sgbNetUpdateRate / 20;
gdwNormalMsgSize *= 3;
gdwNormalMsgSize >>= 2;
if (caps.maxplayers > MAX_PLRS)
caps.maxplayers = MAX_PLRS;
gdwNormalMsgSize /= caps.maxplayers;
while (gdwNormalMsgSize < 0x80) {
gdwNormalMsgSize *= 2;
sgbNetUpdateRate *= 2;
}
if (gdwNormalMsgSize > largestMsgSize)
gdwNormalMsgSize = largestMsgSize;
if (gbIsMultiplayer) {
sgbThreadIsRunning = false;
MemCrit.lock();
nthread_should_run = true;
Thread = SdlThread { NthreadHandler };
}
}
void nthread_cleanup()
{
nthread_should_run = false;
gdwTurnsInTransit = 0;
gdwNormalMsgSize = 0;
gdwLargestMsgSize = 0;
if (Thread.joinable() && Thread.get_id() != this_sdl_thread::get_id()) {
if (!sgbThreadIsRunning)
MemCrit.unlock();
Thread.join();
}
}
void nthread_ignore_mutex(bool bStart)
{
if (!Thread.joinable())
return;
if (bStart)
MemCrit.unlock();
else
MemCrit.lock();
sgbThreadIsRunning = bStart;
}
bool nthread_has_500ms_passed()
{
int currentTickCount = SDL_GetTicks();
int ticksElapsed = currentTickCount - last_tick;
// Check if we missed multiple game ticks (> 10)
if (ticksElapsed > gnTickDelay * 10) {
bool resetLastTick = true;
if (gbIsMultiplayer) {
for (size_t i = 0; i < Players.size(); i++) {
if ((player_state[i] & PS_CONNECTED) != 0 && i != MyPlayerId) {
// Reset last tick is not allowed when other players are connected, cause the elapsed time is needed to sync the game ticks between the clients
resetLastTick = false;
break;
}
}
}
if (resetLastTick) {
// Reset last tick to avoid caught up of all missed game ticks (game speed is dramatically increased for a short time)
last_tick = currentTickCount;
ticksElapsed = 0;
}
}
return ticksElapsed >= 0;
}
void nthread_UpdateProgressToNextGameTick()
{
if (!gbRunGame || PauseMode != 0 || (!gbIsMultiplayer && gmenu_is_active()) || !gbProcessPlayers || demo::IsRunning()) // if game is not running or paused there is no next gametick in the near future
return;
int currentTickCount = SDL_GetTicks();
int ticksMissing = last_tick - currentTickCount;
if (ticksMissing <= 0) {
ProgressToNextGameTick = AnimationInfo::baseValueFraction; // game tick is due
return;
}
int ticksAdvanced = gnTickDelay - ticksMissing;
int32_t fraction = ticksAdvanced * AnimationInfo::baseValueFraction / gnTickDelay;
fraction = clamp<int32_t>(fraction, 0, AnimationInfo::baseValueFraction);
ProgressToNextGameTick = static_cast<uint8_t>(fraction);
}
} // namespace devilution