ddnet/src/engine/client/client.h
bors[bot] 5e7ec64292
Merge #5486
5486: Switch to loading screen, when map creation takes too long r=def- a=Jupeyy

Same as #4941 

It doesn't directly fix the issue described in #5478, it does however not trigger it anymore. it's still a bug in our code unrelated  to this. This just triggered the UB from the issue as  `@ardadem` confirmed, SDL received a SDL_QUIT (`[2022-06-23 20:26:31][test]: sdl closed my client, but why xd`)

Now we have a confirmed case that making the window unresponsive can create weird behavior. Similar to the other pr switch to a loading screen after 500ms (this time without menu background map tho, since the menu background map calls the same code).

We really need some fancy loading screen for such situations, even tho they are rare xD

## Checklist

- [x] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Jupeyy <jupjopjap@gmail.com>
2022-06-30 14:36:58 +00:00

543 lines
17 KiB
C++

/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef ENGINE_CLIENT_CLIENT_H
#define ENGINE_CLIENT_CLIENT_H
#include <list>
#include <memory>
#include <base/hash.h>
#include <engine/client.h>
#include <engine/client/checksum.h>
#include <engine/client/friends.h>
#include <engine/client/ghost.h>
#include <engine/client/serverbrowser.h>
#include <engine/client/updater.h>
#include <engine/editor.h>
#include <engine/engine.h>
#include <engine/graphics.h>
#include <engine/shared/config.h>
#include <engine/shared/demo.h>
#include <engine/shared/fifo.h>
#include <engine/shared/http.h>
#include <engine/shared/network.h>
#include <engine/warning.h>
class CDemoEdit;
class IDemoRecorder;
class CMsgPacker;
class CUnpacker;
class IConfigManager;
class IDiscord;
class IEngineInput;
class IEngineMap;
class IEngineSound;
class IFriends;
class ISteam;
class IStorage;
class IUpdater;
#define CONNECTLINK "ddnet:"
class CGraph
{
public:
enum
{
// restrictions: Must be power of two
MAX_VALUES = 128,
};
float m_Min, m_Max;
float m_MinRange, m_MaxRange;
float m_aValues[MAX_VALUES];
float m_aColors[MAX_VALUES][3];
int m_Index;
void Init(float Min, float Max);
void ScaleMax();
void ScaleMin();
void Add(float v, float r, float g, float b);
bool InsertAt(int i, float v, float r, float g, float b);
void Render(IGraphics *pGraphics, IGraphics::CTextureHandle FontTexture, float x, float y, float w, float h, const char *pDescription);
};
class CSmoothTime
{
int64_t m_Snap;
int64_t m_Current;
int64_t m_Target;
int64_t m_SnapMargin;
int64_t m_CurrentMargin;
int64_t m_TargetMargin;
CGraph m_Graph;
int m_SpikeCounter;
float m_aAdjustSpeed[2]; // 0 = down, 1 = up
public:
void Init(int64_t Target);
void SetAdjustSpeed(int Direction, float Value);
int64_t Get(int64_t Now);
void UpdateInt(int64_t Target);
void Update(CGraph *pGraph, int64_t Target, int TimeLeft, int AdjustDirection);
int64_t GetMargin(int64_t Now);
void UpdateMargin(int64_t TargetMargin);
};
class CServerCapabilities
{
public:
bool m_ChatTimeoutCode;
bool m_AnyPlayerFlag;
bool m_PingEx;
bool m_AllowDummy;
bool m_SyncWeaponInput;
};
class CClient : public IClient, public CDemoPlayer::IListener
{
// needed interfaces
IEngine *m_pEngine;
IEditor *m_pEditor;
IEngineInput *m_pInput;
IEngineGraphics *m_pGraphics;
IEngineSound *m_pSound;
IGameClient *m_pGameClient;
IEngineMap *m_pMap;
IConfigManager *m_pConfigManager;
CConfig *m_pConfig;
IConsole *m_pConsole;
IStorage *m_pStorage;
IUpdater *m_pUpdater;
IDiscord *m_pDiscord;
ISteam *m_pSteam;
CNetClient m_NetClient[NUM_CONNS];
CDemoPlayer m_DemoPlayer;
CDemoRecorder m_DemoRecorder[RECORDER_MAX];
CDemoEditor m_DemoEditor;
CGhostRecorder m_GhostRecorder;
CGhostLoader m_GhostLoader;
CServerBrowser m_ServerBrowser;
CUpdater m_Updater;
CFriends m_Friends;
CFriends m_Foes;
char m_aConnectAddressStr[256];
CUuid m_ConnectionID;
bool m_HaveGlobalTcpAddr = false;
NETADDR m_GlobalTcpAddr;
uint64_t m_SnapshotParts[NUM_DUMMIES];
int64_t m_LocalStartTime;
IGraphics::CTextureHandle m_DebugFont;
int m_DebugSoundIndex = 0;
int64_t m_LastRenderTime;
float m_RenderFrameTimeLow;
float m_RenderFrameTimeHigh;
int m_RenderFrames;
NETADDR m_ServerAddress;
int m_SnapCrcErrors;
bool m_AutoScreenshotRecycle;
bool m_AutoStatScreenshotRecycle;
bool m_AutoCSVRecycle;
bool m_EditorActive;
bool m_SoundInitFailed;
bool m_ResortServerBrowser;
int m_AckGameTick[NUM_DUMMIES];
int m_CurrentRecvTick[NUM_DUMMIES];
int m_RconAuthed[NUM_DUMMIES];
char m_RconPassword[32];
int m_UseTempRconCommands;
char m_Password[32];
bool m_SendPassword;
bool m_ButtonRender = false;
// version-checking
char m_aVersionStr[10];
// pinging
int64_t m_PingStartTime;
char m_aCurrentMap[IO_MAX_PATH_LENGTH];
char m_aCurrentMapPath[IO_MAX_PATH_LENGTH];
char m_aTimeoutCodes[NUM_DUMMIES][32];
bool m_CodeRunAfterJoin[NUM_DUMMIES];
bool m_GenerateTimeoutSeed;
//
char m_aCmdConnect[256];
char m_aCmdPlayDemo[IO_MAX_PATH_LENGTH];
char m_aCmdEditMap[IO_MAX_PATH_LENGTH];
// map download
std::shared_ptr<CHttpRequest> m_pMapdownloadTask;
char m_aMapdownloadFilename[256];
char m_aMapdownloadFilenameTemp[256];
char m_aMapdownloadName[256];
IOHANDLE m_MapdownloadFileTemp;
int m_MapdownloadChunk;
int m_MapdownloadCrc;
int m_MapdownloadAmount;
int m_MapdownloadTotalsize;
bool m_MapdownloadSha256Present;
SHA256_DIGEST m_MapdownloadSha256;
bool m_MapDetailsPresent;
char m_aMapDetailsName[256];
int m_MapDetailsCrc;
SHA256_DIGEST m_MapDetailsSha256;
char m_aDDNetInfoTmp[64];
std::shared_ptr<CHttpRequest> m_pDDNetInfoTask;
// time
CSmoothTime m_GameTime[NUM_DUMMIES];
CSmoothTime m_PredictedTime;
// input
struct // TODO: handle input better
{
int m_aData[MAX_INPUT_SIZE]; // the input data
int m_Tick; // the tick that the input is for
int64_t m_PredictedTime; // prediction latency when we sent this input
int64_t m_PredictionMargin; // prediction margin when we sent this input
int64_t m_Time;
} m_aInputs[NUM_DUMMIES][200];
int m_CurrentInput[NUM_DUMMIES];
bool m_LastDummy;
bool m_DummySendConnInfo;
// graphs
CGraph m_InputtimeMarginGraph;
CGraph m_GametimeMarginGraph;
CGraph m_FpsGraph;
// the game snapshots are modifiable by the game
CSnapshotStorage m_SnapshotStorage[NUM_DUMMIES];
CSnapshotStorage::CHolder *m_aSnapshots[NUM_DUMMIES][NUM_SNAPSHOT_TYPES];
int m_ReceivedSnapshots[NUM_DUMMIES];
char m_aSnapshotIncomingData[NUM_DUMMIES][CSnapshot::MAX_SIZE];
int m_SnapshotIncomingDataSize[NUM_DUMMIES];
CSnapshotStorage::CHolder m_aDemorecSnapshotHolders[NUM_SNAPSHOT_TYPES];
char *m_aDemorecSnapshotData[NUM_SNAPSHOT_TYPES][2][CSnapshot::MAX_SIZE];
CSnapshotDelta m_SnapshotDelta;
std::list<std::shared_ptr<CDemoEdit>> m_EditJobs;
//
bool m_CanReceiveServerCapabilities;
bool m_ServerSentCapabilities;
CServerCapabilities m_ServerCapabilities;
bool ShouldSendChatTimeoutCodeHeuristic();
CServerInfo m_CurrentServerInfo;
int64_t m_CurrentServerInfoRequestTime; // >= 0 should request, == -1 got info
int m_CurrentServerPingInfoType;
int m_CurrentServerPingBasicToken;
int m_CurrentServerPingToken;
CUuid m_CurrentServerPingUuid;
int64_t m_CurrentServerCurrentPingTime; // >= 0 request running
int64_t m_CurrentServerNextPingTime; // >= 0 should request
// version info
struct CVersionInfo
{
enum
{
STATE_INIT = 0,
STATE_START,
STATE_READY,
};
int m_State;
class CHostLookup m_VersionServeraddr;
} m_VersionInfo;
static void GraphicsThreadProxy(void *pThis) { ((CClient *)pThis)->GraphicsThread(); }
void GraphicsThread();
std::vector<SWarning> m_vWarnings;
#if defined(CONF_FAMILY_UNIX)
CFifo m_Fifo;
#endif
IOHANDLE m_BenchmarkFile;
int64_t m_BenchmarkStopTime;
CChecksum m_Checksum;
int m_OwnExecutableSize = 0;
IOHANDLE m_OwnExecutable;
void UpdateDemoIntraTimers();
int MaxLatencyTicks() const;
int PredictionMargin() const;
public:
IEngine *Engine() { return m_pEngine; }
IEngineGraphics *Graphics() { return m_pGraphics; }
IEngineInput *Input() { return m_pInput; }
IEngineSound *Sound() { return m_pSound; }
IGameClient *GameClient() { return m_pGameClient; }
IConfigManager *ConfigManager() { return m_pConfigManager; }
CConfig *Config() { return m_pConfig; }
IStorage *Storage() { return m_pStorage; }
IUpdater *Updater() { return m_pUpdater; }
IDiscord *Discord() { return m_pDiscord; }
ISteam *Steam() { return m_pSteam; }
CClient();
// ----- send functions -----
int SendMsg(int Conn, CMsgPacker *pMsg, int Flags) override;
// Send via the currently active client (main/dummy)
int SendMsgActive(CMsgPacker *pMsg, int Flags) override;
void SendInfo();
void SendEnterGame(int Conn);
void SendReady();
void SendMapRequest();
bool RconAuthed() const override { return m_RconAuthed[g_Config.m_ClDummy] != 0; }
bool UseTempRconCommands() const override { return m_UseTempRconCommands != 0; }
void RconAuth(const char *pName, const char *pPassword) override;
void Rcon(const char *pCmd) override;
bool ConnectionProblems() const override;
bool SoundInitFailed() const override { return m_SoundInitFailed; }
IGraphics::CTextureHandle GetDebugFont() const override { return m_DebugFont; }
void DirectInput(int *pInput, int Size);
void SendInput();
// TODO: OPT: do this a lot smarter!
int *GetInput(int Tick, int IsDummy) const override;
const char *LatestVersion() const override;
// ------ state handling -----
void SetState(EClientState s);
// called when the map is loaded and we should init for a new round
void OnEnterGame(bool Dummy);
void EnterGame(int Conn) override;
void Connect(const char *pAddress, const char *pPassword = nullptr) override;
void DisconnectWithReason(const char *pReason);
void Disconnect() override;
void DummyDisconnect(const char *pReason) override;
void DummyConnect() override;
bool DummyConnected() override;
bool DummyConnecting() override;
bool DummyAllowed() override;
int m_DummyConnected;
int m_LastDummyConnectTime;
void GetServerInfo(CServerInfo *pServerInfo) const override;
void ServerInfoRequest();
int LoadData();
// ---
int GetPredictionTime() override;
void *SnapGetItem(int SnapID, int Index, CSnapItem *pItem) const override;
int SnapItemSize(int SnapID, int Index) const override;
void SnapInvalidateItem(int SnapID, int Index) override;
void *SnapFindItem(int SnapID, int Type, int ID) const override;
int SnapNumItems(int SnapID) const override;
void SnapSetStaticsize(int ItemType, int Size) override;
void Render();
void DebugRender();
void Restart() override;
void Quit() override;
const char *PlayerName() const override;
const char *DummyName() const override;
const char *ErrorString() const override;
const char *LoadMap(const char *pName, const char *pFilename, SHA256_DIGEST *pWantedSha256, unsigned WantedCrc);
const char *LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedSha256, int WantedCrc);
void ProcessConnlessPacket(CNetChunk *pPacket);
void ProcessServerInfo(int Type, NETADDR *pFrom, const void *pData, int DataSize);
void ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy);
int UnpackAndValidateSnapshot(CSnapshot *pFrom, CSnapshot *pTo);
void ResetMapDownload();
void FinishMapDownload();
void RequestDDNetInfo() override;
void ResetDDNetInfo();
bool IsDDNetInfoChanged();
void FinishDDNetInfo();
void LoadDDNetInfo();
NETADDR ServerAddress() const override { return m_ServerAddress; }
const char *ConnectAddressString() const override { return m_aConnectAddressStr; }
const char *MapDownloadName() const override { return m_aMapdownloadName; }
int MapDownloadAmount() const override { return !m_pMapdownloadTask ? m_MapdownloadAmount : (int)m_pMapdownloadTask->Current(); }
int MapDownloadTotalsize() const override { return !m_pMapdownloadTask ? m_MapdownloadTotalsize : (int)m_pMapdownloadTask->Size(); }
void PumpNetwork();
void OnDemoPlayerSnapshot(void *pData, int Size) override;
void OnDemoPlayerMessage(void *pData, int Size) override;
void Update();
void RegisterInterfaces();
void InitInterfaces();
void Run();
bool CtrlShiftKey(int Key, bool &Last);
static void Con_Connect(IConsole::IResult *pResult, void *pUserData);
static void Con_Disconnect(IConsole::IResult *pResult, void *pUserData);
static void Con_DummyConnect(IConsole::IResult *pResult, void *pUserData);
static void Con_DummyDisconnect(IConsole::IResult *pResult, void *pUserData);
static void Con_DummyResetInput(IConsole::IResult *pResult, void *pUserData);
static void Con_Quit(IConsole::IResult *pResult, void *pUserData);
static void Con_DemoPlay(IConsole::IResult *pResult, void *pUserData);
static void Con_DemoSpeed(IConsole::IResult *pResult, void *pUserData);
static void Con_Minimize(IConsole::IResult *pResult, void *pUserData);
static void Con_Ping(IConsole::IResult *pResult, void *pUserData);
static void Con_Screenshot(IConsole::IResult *pResult, void *pUserData);
static void Con_Reset(IConsole::IResult *pResult, void *pUserData);
#if defined(CONF_VIDEORECORDER)
static void StartVideo(IConsole::IResult *pResult, void *pUserData, const char *pVideoName);
static void Con_StartVideo(IConsole::IResult *pResult, void *pUserData);
static void Con_StopVideo(IConsole::IResult *pResult, void *pUserData);
const char *DemoPlayer_Render(const char *pFilename, int StorageType, const char *pVideoName, int SpeedIndex) override;
#endif
static void Con_Rcon(IConsole::IResult *pResult, void *pUserData);
static void Con_RconAuth(IConsole::IResult *pResult, void *pUserData);
static void Con_RconLogin(IConsole::IResult *pResult, void *pUserData);
static void Con_AddFavorite(IConsole::IResult *pResult, void *pUserData);
static void Con_RemoveFavorite(IConsole::IResult *pResult, void *pUserData);
static void Con_Play(IConsole::IResult *pResult, void *pUserData);
static void Con_Record(IConsole::IResult *pResult, void *pUserData);
static void Con_StopRecord(IConsole::IResult *pResult, void *pUserData);
static void Con_AddDemoMarker(IConsole::IResult *pResult, void *pUserData);
static void Con_BenchmarkQuit(IConsole::IResult *pResult, void *pUserData);
static void ConchainServerBrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
static void ConchainFullscreen(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
static void ConchainWindowBordered(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
static void ConchainWindowScreen(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
static void ConchainWindowVSync(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
static void ConchainTimeoutSeed(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
static void ConchainLoglevel(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
static void ConchainPassword(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
static void ConchainReplays(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
static void Con_DemoSlice(IConsole::IResult *pResult, void *pUserData);
static void Con_DemoSliceBegin(IConsole::IResult *pResult, void *pUserData);
static void Con_DemoSliceEnd(IConsole::IResult *pResult, void *pUserData);
static void Con_SaveReplay(IConsole::IResult *pResult, void *pUserData);
void RegisterCommands();
const char *DemoPlayer_Play(const char *pFilename, int StorageType) override;
void DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int Recorder) override;
void DemoRecorder_HandleAutoStart() override;
void DemoRecorder_StartReplayRecorder();
void DemoRecorder_Stop(int Recorder, bool RemoveFile = false) override;
void DemoRecorder_AddDemoMarker(int Recorder);
IDemoRecorder *DemoRecorder(int Recorder) override;
void AutoScreenshot_Start() override;
void AutoStatScreenshot_Start() override;
void AutoScreenshot_Cleanup();
void AutoStatScreenshot_Cleanup();
void AutoCSV_Start() override;
void AutoCSV_Cleanup();
void ServerBrowserUpdate() override;
void HandleConnectAddress(const NETADDR *pAddr);
void HandleConnectLink(const char *pLink);
void HandleDemoPath(const char *pPath);
void HandleMapPath(const char *pPath);
virtual void InitChecksum();
virtual int HandleChecksum(int Conn, CUuid Uuid, CUnpacker *pUnpacker);
// gfx
void SwitchWindowScreen(int Index) override;
void SetWindowParams(int FullscreenMode, bool IsBorderless, bool AllowResizing) override;
void ToggleWindowVSync() override;
void LoadFont() override;
void Notify(const char *pTitle, const char *pMessage) override;
void BenchmarkQuit(int Seconds, const char *pFilename);
void UpdateAndSwap() override;
// DDRace
void GenerateTimeoutSeed() override;
void GenerateTimeoutCodes();
int GetCurrentRaceTime() override;
const char *GetCurrentMap() const override;
const char *GetCurrentMapPath() const override;
SHA256_DIGEST GetCurrentMapSha256() const override;
unsigned GetCurrentMapCrc() const override;
void RaceRecord_Start(const char *pFilename) override;
void RaceRecord_Stop() override;
bool RaceRecord_IsRecording() override;
void DemoSliceBegin() override;
void DemoSliceEnd() override;
void DemoSlice(const char *pDstPath, CLIENTFUNC_FILTER pfnFilter, void *pUser) override;
virtual void SaveReplay(int Length, const char *pFilename = "");
bool EditorHasUnsavedData() const override { return m_pEditor->HasUnsavedData(); }
IFriends *Foes() override { return &m_Foes; }
void GetSmoothTick(int *pSmoothTick, float *pSmoothIntraTick, float MixAmount) override;
SWarning *GetCurWarning() override;
CChecksumData *ChecksumData() override { return &m_Checksum.m_Data; }
bool InfoTaskRunning() override { return m_pDDNetInfoTask != nullptr; }
int UdpConnectivity(int NetType) override;
};
#endif