From 27b75893ba46058b5ca680f3c2aaae7233fe4270 Mon Sep 17 00:00:00 2001 From: havoc Date: Mon, 17 Oct 2011 17:02:52 +0000 Subject: [PATCH] added sv_threaded cvar, the server can now be moved to another thread and execute in parallel with the client code THIS IS EXPERIMENTAL AND MAY CRASH IF USED this required adding mutex's to many subsystems... git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@11437 d7cf8633-e32d-0410-b094-e92efae38249 --- cl_demo.c | 2 +- cl_parse.c | 4 +- cmd.c | 74 ++++++++++----- cmd.h | 2 +- console.c | 24 +++++ csprogs.c | 6 +- fs.c | 14 ++- host.c | 264 ++++++++++++++++++++++++++++++++++++++++++++++++---- keys.c | 2 +- netconn.c | 20 +++- prvm_cmds.c | 2 +- server.h | 11 +++ sv_main.c | 198 ++++++++++++++++++++++++++++++++++++++- sv_user.c | 4 +- svvm_cmds.c | 2 +- vid_wgl.c | 4 +- zone.c | 18 ++++ 17 files changed, 590 insertions(+), 61 deletions(-) diff --git a/cl_demo.c b/cl_demo.c index ea10bff6..67789177 100644 --- a/cl_demo.c +++ b/cl_demo.c @@ -367,7 +367,7 @@ void CL_Record_f (void) // start the map up if (c > 2) - Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command); + Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command, false); // open the demo file Con_Printf("recording to %s.\n", name); diff --git a/cl_parse.c b/cl_parse.c index b0554e86..1055b947 100644 --- a/cl_parse.c +++ b/cl_parse.c @@ -3618,7 +3618,7 @@ void CL_ParseServerMessage(void) break; case qw_svc_sellscreen: - Cmd_ExecuteString ("help", src_command); + Cmd_ExecuteString ("help", src_command, true); break; case qw_svc_smallkick: @@ -4124,7 +4124,7 @@ void CL_ParseServerMessage(void) break; case svc_sellscreen: - Cmd_ExecuteString ("help", src_command); + Cmd_ExecuteString ("help", src_command, true); break; case svc_hidelmp: if (gamemode == GAME_TENEBRAE) diff --git a/cmd.c b/cmd.c index e894a8c2..1ce6be78 100644 --- a/cmd.c +++ b/cmd.c @@ -20,6 +20,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // cmd.c -- Quake script command processing module #include "quakedef.h" +#include "thread.h" typedef struct cmdalias_s { @@ -175,6 +176,19 @@ static void Cmd_Centerprint_f (void) static sizebuf_t cmd_text; static unsigned char cmd_text_buf[CMDBUFSIZE]; +void *cmd_text_mutex = NULL; + +static void Cbuf_LockThreadMutex(void) +{ + if (cmd_text_mutex) + Thread_LockMutex(cmd_text_mutex); +} + +static void Cbuf_UnlockThreadMutex(void) +{ + if (cmd_text_mutex) + Thread_UnlockMutex(cmd_text_mutex); +} /* ============ @@ -187,15 +201,14 @@ void Cbuf_AddText (const char *text) { int l; - l = (int)strlen (text); + l = (int)strlen(text); + Cbuf_LockThreadMutex(); if (cmd_text.cursize + l >= cmd_text.maxsize) - { Con_Print("Cbuf_AddText: overflow\n"); - return; - } - - SZ_Write (&cmd_text, (const unsigned char *)text, (int)strlen (text)); + else + SZ_Write(&cmd_text, (const unsigned char *)text, (int)strlen (text)); + Cbuf_UnlockThreadMutex(); } @@ -213,6 +226,8 @@ void Cbuf_InsertText (const char *text) char *temp; int templen; + Cbuf_LockThreadMutex(); + // copy off any commands still remaining in the exec buffer templen = cmd_text.cursize; if (templen) @@ -233,6 +248,8 @@ void Cbuf_InsertText (const char *text) SZ_Write (&cmd_text, (const unsigned char *)temp, templen); Mem_Free (temp); } + + Cbuf_UnlockThreadMutex(); } /* @@ -286,6 +303,9 @@ void Cbuf_Execute (void) qboolean quotes; char *comment; + Cbuf_LockThreadMutex(); + SV_LockThreadMutex(); + // LordHavoc: making sure the tokenizebuffer doesn't get filled up by repeated crashes cmd_tokenizebufferpos = 0; @@ -362,11 +382,11 @@ void Cbuf_Execute (void) ) { Cmd_PreprocessString( line, preprocessed, sizeof(preprocessed), NULL ); - Cmd_ExecuteString (preprocessed, src_command); + Cmd_ExecuteString (preprocessed, src_command, false); } else { - Cmd_ExecuteString (line, src_command); + Cmd_ExecuteString (line, src_command, false); } if (cmd_wait) @@ -376,6 +396,9 @@ void Cbuf_Execute (void) break; } } + + SV_UnlockThreadMutex(); + Cbuf_UnlockThreadMutex(); } /* @@ -1222,6 +1245,9 @@ void Cmd_Init (void) cmd_text.data = cmd_text_buf; cmd_text.maxsize = sizeof(cmd_text_buf); cmd_text.cursize = 0; + + if (Thread_HasThreads()) + cmd_text_mutex = Thread_CreateMutex(); } void Cmd_Init_Commands (void) @@ -1269,6 +1295,10 @@ Cmd_Shutdown */ void Cmd_Shutdown(void) { + if (cmd_text_mutex) + Thread_DestroyMutex(cmd_text_mutex); + cmd_text_mutex = NULL; + Mem_FreePool(&cmd_mempool); } @@ -1646,13 +1676,15 @@ A complete command line has been parsed, so try to execute it FIXME: lookupnoadd the token to speed search? ============ */ -void Cmd_ExecuteString (const char *text, cmd_source_t src) +void Cmd_ExecuteString (const char *text, cmd_source_t src, qboolean lockmutex) { int oldpos; int found; cmd_function_t *cmd; cmdalias_t *a; + if (lockmutex) + Cbuf_LockThreadMutex(); oldpos = cmd_tokenizebufferpos; cmd_source = src; found = false; @@ -1661,10 +1693,7 @@ void Cmd_ExecuteString (const char *text, cmd_source_t src) // execute the command line if (!Cmd_Argc()) - { - cmd_tokenizebufferpos = oldpos; - return; // no tokens - } + goto done; // no tokens // check functions for (cmd=cmd_functions ; cmd ; cmd=cmd->next) @@ -1672,7 +1701,7 @@ void Cmd_ExecuteString (const char *text, cmd_source_t src) if (!strcasecmp (cmd_argv[0],cmd->name)) { if (cmd->csqcfunc && CL_VM_ConsoleCommand (text)) //[515]: csqc - return; + goto done; switch (src) { case src_command: @@ -1696,8 +1725,7 @@ void Cmd_ExecuteString (const char *text, cmd_source_t src) if (cmd->clientfunction) { cmd->clientfunction (); - cmd_tokenizebufferpos = oldpos; - return; + goto done; } break; } @@ -1710,8 +1738,7 @@ command_found: if (cmd_source == src_client) { Con_Printf("player \"%s\" tried to %s\n", host_client->name, text); - cmd_tokenizebufferpos = oldpos; - return; + goto done; } // check alias @@ -1720,22 +1747,21 @@ command_found: if (!strcasecmp (cmd_argv[0], a->name)) { Cmd_ExecuteAlias(a); - cmd_tokenizebufferpos = oldpos; - return; + goto done; } } if(found) // if the command was hooked and found, all is good - { - cmd_tokenizebufferpos = oldpos; - return; - } + goto done; // check cvars if (!Cvar_Command () && host_framecount > 0) Con_Printf("Unknown command \"%s\"\n", Cmd_Argv(0)); +done: cmd_tokenizebufferpos = oldpos; + if (lockmutex) + Cbuf_UnlockThreadMutex(); } diff --git a/cmd.h b/cmd.h index cb96abaa..722fea65 100644 --- a/cmd.h +++ b/cmd.h @@ -142,7 +142,7 @@ int Cmd_CheckParm (const char *parm); /// Parses a single line of text into arguments and tries to execute it. /// The text can come from the command buffer, a remote client, or stdin. -void Cmd_ExecuteString (const char *text, cmd_source_t src); +void Cmd_ExecuteString (const char *text, cmd_source_t src, qboolean lockmutex); /// adds the string as a clc_stringcmd to the client message. /// (used when there is no reason to generate a local command to do it) diff --git a/console.c b/console.c index b604fe13..2f7e99b7 100644 --- a/console.c +++ b/console.c @@ -25,6 +25,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include "quakedef.h" +#include "thread.h" // for u8_encodech #include "ft2.h" @@ -35,6 +36,7 @@ float con_cursorspeed = 4; int con_backscroll; conbuffer_t con; +void *con_mutex = NULL; #define CON_LINES(i) CONBUFFER_LINES(&con, i) #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con) @@ -726,17 +728,21 @@ void Con_ConDump_f (void) Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1)); return; } + if (con_mutex) Thread_LockMutex(con_mutex); for(i = 0; i < CON_LINES_COUNT; ++i) { FS_Write(file, CON_LINES(i).start, CON_LINES(i).len); FS_Write(file, "\n", 1); } + if (con_mutex) Thread_UnlockMutex(con_mutex); FS_Close(file); } void Con_Clear_f (void) { + if (con_mutex) Thread_LockMutex(con_mutex); ConBuffer_Clear(&con); + if (con_mutex) Thread_UnlockMutex(con_mutex); } /* @@ -748,6 +754,8 @@ void Con_Init (void) { con_linewidth = 80; ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool); + if (Thread_HasThreads()) + con_mutex = Thread_CreateMutex(); // Allocate a log queue, this will be freed after configs are parsed logq_size = MAX_INPUTLINE; @@ -804,7 +812,10 @@ void Con_Init (void) void Con_Shutdown (void) { + if (con_mutex) Thread_LockMutex(con_mutex); ConBuffer_Shutdown(&con); + if (con_mutex) Thread_UnlockMutex(con_mutex); + if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL; } /* @@ -829,6 +840,7 @@ void Con_PrintToHistory(const char *txt, int mask) if(!con.text) // FIXME uses a non-abstracted property of con return; + if (con_mutex) Thread_LockMutex(con_mutex); for(; *txt; ++txt) { if(cr_pending) @@ -859,6 +871,7 @@ void Con_PrintToHistory(const char *txt, int mask) break; } } + if (con_mutex) Thread_UnlockMutex(con_mutex); } /*! The translation table between the graphical font and plain ASCII --KB */ @@ -1057,6 +1070,9 @@ void Con_MaskPrint(int additionalmask, const char *msg) static int index = 0; static char line[MAX_INPUTLINE]; + if (con_mutex) + Thread_LockMutex(con_mutex); + for (;*msg;msg++) { Con_Rcon_AddChar(*msg); @@ -1323,6 +1339,9 @@ void Con_MaskPrint(int additionalmask, const char *msg) mask = 0; } } + + if (con_mutex) + Thread_UnlockMutex(con_mutex); } /* @@ -1668,6 +1687,7 @@ void Con_DrawNotify (void) int numChatlines; int chatpos; + if (con_mutex) Thread_LockMutex(con_mutex); ConBuffer_FixTimes(&con); numChatlines = con_chat.integer; @@ -1758,6 +1778,7 @@ void Con_DrawNotify (void) x = min(xr, x); DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT); } + if (con_mutex) Thread_UnlockMutex(con_mutex); } /* @@ -1876,6 +1897,8 @@ void Con_DrawConsole (int lines) if (lines <= 0) return; + if (con_mutex) Thread_LockMutex(con_mutex); + if (con_backscroll < 0) con_backscroll = 0; @@ -1982,6 +2005,7 @@ void Con_DrawConsole (int lines) Con_DrawInput (); r_draw2d_force = false; + if (con_mutex) Thread_UnlockMutex(con_mutex); } /* diff --git a/csprogs.c b/csprogs.c index 8181505b..77e3a16b 100644 --- a/csprogs.c +++ b/csprogs.c @@ -559,7 +559,7 @@ void CL_VM_Parse_StuffCmd (const char *msg) int sizeflags = csqc_progcrc.flags; csqc_progcrc.flags &= ~CVAR_READONLY; csqc_progsize.flags &= ~CVAR_READONLY; - Cmd_ExecuteString (msg, src_command); + Cmd_ExecuteString (msg, src_command, true); csqc_progcrc.flags = crcflags; csqc_progsize.flags = sizeflags; return; @@ -592,7 +592,7 @@ void CL_VM_Parse_StuffCmd (const char *msg) l = sizeof(buf) - 1; strlcpy(buf, p, l + 1); // strlcpy needs a + 1 as it includes the newline! - Cmd_ExecuteString(buf, src_command); + Cmd_ExecuteString(buf, src_command, true); p += l; if(*p == '\n') @@ -600,7 +600,7 @@ void CL_VM_Parse_StuffCmd (const char *msg) else break; // end of string or overflow } - Cmd_ExecuteString("curl --clear_autodownload", src_command); // don't inhibit CSQC loading + Cmd_ExecuteString("curl --clear_autodownload", src_command, true); // don't inhibit CSQC loading return; } diff --git a/fs.c b/fs.c index 4288ab5d..f0fbc4e1 100644 --- a/fs.c +++ b/fs.c @@ -44,6 +44,7 @@ #endif #include "quakedef.h" +#include "thread.h" #include "fs.h" #include "wad.h" @@ -332,6 +333,7 @@ VARIABLES */ mempool_t *fs_mempool; +void *fs_mutex = NULL; searchpath_t *fs_searchpaths = NULL; const char *const fs_checkgamedir_missing = "missing"; @@ -2067,6 +2069,9 @@ void FS_Init (void) // generate the searchpath FS_Rescan(); + + if (Thread_HasThreads()) + fs_mutex = Thread_CreateMutex(); } void FS_Init_Commands(void) @@ -2101,6 +2106,9 @@ void FS_Shutdown (void) Sys_UnloadLibrary (&shell32_dll); Sys_UnloadLibrary (&ole32_dll); #endif + + if (fs_mutex) + Thread_DestroyMutex(fs_mutex); } int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking) @@ -2595,13 +2603,17 @@ Open a file. The syntax is the same as fopen */ qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet) { + qfile_t *result = NULL; if (FS_CheckNastyPath(filepath, false)) { Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false"); return NULL; } - return FS_OpenReadFile (filepath, quiet, false, 16); + if (fs_mutex) Thread_LockMutex(fs_mutex); + result = FS_OpenReadFile (filepath, quiet, false, 16); + if (fs_mutex) Thread_UnlockMutex(fs_mutex); + return result; } diff --git a/host.c b/host.c index f2e66e3a..cedb41b4 100644 --- a/host.c +++ b/host.c @@ -624,30 +624,16 @@ const char *Host_TimingReport(void) return va("%.1f%% CPU, %.2f%% lost, offset avg %.1fms, max %.1fms, sdev %.1fms", svs.perf_cpuload * 100, svs.perf_lost * 100, svs.perf_offset_avg * 1000, svs.perf_offset_max * 1000, svs.perf_offset_sdev * 1000); } -/* -================== -Host_Frame - -Runs all active servers -================== -*/ -static void Host_Init(void); -void Host_Main(void) +void Host_Mingled(void) { double time1 = 0; double time2 = 0; double time3 = 0; - double cl_timer, sv_timer; + double cl_timer = 0, sv_timer = 0; double clframetime, deltarealtime, oldrealtime; double wait; int pass1, pass2, pass3, i; - Host_Init(); - - cl_timer = 0; - sv_timer = 0; - - realtime = host_starttime = Sys_DoubleTime(); for (;;) { if (setjmp(host_abortframe)) @@ -764,6 +750,10 @@ void Host_Main(void) continue; } + // limit the frametime steps to no more than 100ms each + if (cl_timer > 0.1) + cl_timer = 0.1; + R_TimeReport("---"); //------------------- @@ -773,8 +763,6 @@ void Host_Main(void) //------------------- // limit the frametime steps to no more than 100ms each - if (cl_timer > 0.1) - cl_timer = 0.1; if (sv_timer > 0.1) { svs.perf_acc_lost += (sv_timer - 0.1); @@ -1023,6 +1011,242 @@ void Host_Main(void) } } +void Host_Threaded(void) +{ + double time1 = 0; + double time2 = 0; + double time3 = 0; + double cl_timer = 0; + double clframetime, deltarealtime, oldrealtime; + double wait; + int pass1, pass2, pass3; + + for (;;) + { + if (setjmp(host_abortframe)) + { + SCR_ClearLoadingScreen(false); + continue; // something bad happened, or the server disconnected + } + + oldrealtime = realtime; + realtime = Sys_DoubleTime(); + + deltarealtime = realtime - oldrealtime; + cl_timer += deltarealtime; + + if (slowmo.value < 0.00001 && slowmo.value != 0) + Cvar_SetValue("slowmo", 0); + if (host_framerate.value < 0.00001 && host_framerate.value != 0) + Cvar_SetValue("host_framerate", 0); + + // keep the random time dependent, but not when playing demos/benchmarking + if(!*sv_random_seed.string && !cls.demoplayback) + rand(); + + cl.islocalgame = NetConn_IsLocalGame(); + + // get new key events + Key_EventQueue_Unblock(); + SndSys_SendKeyEvents(); + Sys_SendKeyEvents(); + + NetConn_UpdateSockets(); + + Log_DestBuffer_Flush(); + + Curl_Run(); + + // check for commands typed to the host + Host_GetConsoleCommands(); + + // process console commands +// R_TimeReport("preconsole"); + CL_VM_PreventInformationLeaks(); + Cbuf_Execute(); +// R_TimeReport("console"); + + //Con_Printf("%6.0f %6.0f\n", cl_timer * 1000000.0, sv_timer * 1000000.0); + + // if the accumulators haven't become positive yet, wait a while + wait = cl_timer * -1000000.0; + + if (!cls.timedemo && wait >= 1) + { + double time0; + + if(host_maxwait.value <= 0) + wait = min(wait, 1000000.0); + else + wait = min(wait, host_maxwait.value * 1000.0); + if(wait < 1) + wait = 1; // because we cast to int + + time0 = Sys_DoubleTime(); + Sys_Sleep((int)wait); +// R_TimeReport("sleep"); + continue; + } + + // limit the frametime steps to no more than 100ms each + if (cl_timer > 0.1) + cl_timer = 0.1; + + R_TimeReport("---"); + + if (cl_timer > 0 || cls.timedemo || ((vid_activewindow ? cl_maxfps : cl_maxidlefps).value < 1)) + { + R_TimeReport("---"); + Collision_Cache_NewFrame(); + R_TimeReport("collisioncache"); + // decide the simulation time + if (cls.capturevideo.active) + { + //*** + if (cls.capturevideo.realtime) + clframetime = cl.realframetime = max(cl_timer, 1.0 / cls.capturevideo.framerate); + else + { + clframetime = 1.0 / cls.capturevideo.framerate; + cl.realframetime = max(cl_timer, clframetime); + } + } + else if (vid_activewindow && cl_maxfps.value >= 1 && !cls.timedemo) + { + clframetime = cl.realframetime = max(cl_timer, 1.0 / cl_maxfps.value); + // when running slow, we need to sleep to keep input responsive + wait = bound(0, cl_maxfps_alwayssleep.value * 1000, 100000); + if (wait > 0) + Sys_Sleep((int)wait); + } + else if (!vid_activewindow && cl_maxidlefps.value >= 1 && !cls.timedemo) + clframetime = cl.realframetime = max(cl_timer, 1.0 / cl_maxidlefps.value); + else + clframetime = cl.realframetime = cl_timer; + + // apply slowmo scaling + clframetime *= cl.movevars_timescale; + // scale playback speed of demos by slowmo cvar + if (cls.demoplayback) + { + clframetime *= slowmo.value; + // if demo playback is paused, don't advance time at all + if (cls.demopaused) + clframetime = 0; + } + + // host_framerate overrides all else + if (host_framerate.value) + clframetime = host_framerate.value; + + if (cl.paused || (cl.islocalgame && (key_dest != key_game || key_consoleactive || cl.csqc_paused))) + clframetime = 0; + + if (cls.timedemo) + clframetime = cl.realframetime = cl_timer; + + // deduct the frame time from the accumulator + cl_timer -= cl.realframetime; + + cl.oldtime = cl.time; + cl.time += clframetime; + + // update video + if (host_speeds.integer) + time1 = Sys_DoubleTime(); + R_TimeReport("pre-input"); + + // Collect input into cmd + CL_Input(); + + R_TimeReport("input"); + + // check for new packets + NetConn_ClientFrame(); + + // read a new frame from a demo if needed + CL_ReadDemoMessage(); + R_TimeReport("clientnetwork"); + + // now that packets have been read, send input to server + CL_SendMove(); + R_TimeReport("sendmove"); + + // update client world (interpolate entities, create trails, etc) + CL_UpdateWorld(); + R_TimeReport("lerpworld"); + + CL_Video_Frame(); + + R_TimeReport("client"); + + CL_UpdateScreen(); + R_TimeReport("render"); + + if (host_speeds.integer) + time2 = Sys_DoubleTime(); + + // update audio + if(cl.csqc_usecsqclistener) + { + S_Update(&cl.csqc_listenermatrix); + cl.csqc_usecsqclistener = false; + } + else + S_Update(&r_refdef.view.matrix); + + CDAudio_Update(); + R_TimeReport("audio"); + + // reset gathering of mouse input + in_mouse_x = in_mouse_y = 0; + + if (host_speeds.integer) + { + pass1 = (int)((time1 - time3)*1000000); + time3 = Sys_DoubleTime(); + pass2 = (int)((time2 - time1)*1000000); + pass3 = (int)((time3 - time2)*1000000); + Con_Printf("%6ius total %6ius other %6ius gfx %6ius snd\n", + pass1+pass2+pass3, pass1, pass2, pass3); + } + } + +#if MEMPARANOIA + Mem_CheckSentinelsGlobal(); +#else + if (developer_memorydebug.integer) + Mem_CheckSentinelsGlobal(); +#endif + + // if there is some time remaining from this frame, reset the timers + if (cl_timer >= 0) + cl_timer = 0; + + host_framecount++; + } +} + +/* +================== +Host_Frame + +Runs all active servers +================== +*/ +static void Host_Init(void); +void Host_Main(void) +{ + Host_Init(); + + realtime = host_starttime = Sys_DoubleTime(); + + if (svs.threaded) + Host_Threaded(); + else + Host_Mingled(); +} + //============================================================================ qboolean vid_opened = false; @@ -1258,6 +1482,9 @@ static void Host_Init (void) Con_DPrint("========Initialized=========\n"); //Host_StartVideo(); + + if (cls.state != ca_dedicated) + SV_StartThread(); } @@ -1317,6 +1544,7 @@ void Host_Shutdown(void) VID_Shutdown(); } + SV_StopThread(); Thread_Shutdown(); Cmd_Shutdown(); Key_Shutdown(); diff --git a/keys.c b/keys.c index f88ea5a4..28f74a42 100644 --- a/keys.c +++ b/keys.c @@ -1200,7 +1200,7 @@ Key_Message (int key, int ascii) if (key == K_ENTER || ascii == 10 || ascii == 13) { if(chat_mode < 0) - Cmd_ExecuteString(chat_buffer, src_command); // not Cbuf_AddText to allow semiclons in args; however, this allows no variables then. Use aliases! + Cmd_ExecuteString(chat_buffer, src_command, true); // not Cbuf_AddText to allow semiclons in args; however, this allows no variables then. Use aliases! else Cmd_ForwardStringToServer(va("%s %s", chat_mode ? "say_team" : "say ", chat_buffer)); diff --git a/netconn.c b/netconn.c index c6b3ea03..160803f8 100755 --- a/netconn.c +++ b/netconn.c @@ -21,6 +21,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" +#include "thread.h" #include "lhnet.h" // for secure rcon authentication @@ -135,6 +136,7 @@ static lhnetsocket_t *sv_sockets[16]; netconn_t *netconn_list = NULL; mempool_t *netconn_mempool = NULL; +void *netconn_mutex = NULL; cvar_t cl_netport = {0, "cl_port", "0", "forces client to use chosen port number if not 0"}; cvar_t sv_netport = {0, "port", "26000", "server port for players to connect to"}; @@ -597,8 +599,13 @@ void ServerList_QueryList(qboolean resetcache, qboolean querydp, qboolean queryq int NetConn_Read(lhnetsocket_t *mysocket, void *data, int maxlength, lhnetaddress_t *peeraddress) { - int length = LHNET_Read(mysocket, data, maxlength, peeraddress); + int length; int i; + if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex) + Thread_LockMutex(netconn_mutex); + length = LHNET_Read(mysocket, data, maxlength, peeraddress); + if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex) + Thread_UnlockMutex(netconn_mutex); if (length == 0) return 0; if (cl_netpacketloss_receive.integer) @@ -629,7 +636,11 @@ int NetConn_Write(lhnetsocket_t *mysocket, const void *data, int length, const l for (i = 0;i < cl_numsockets;i++) if (cl_sockets[i] == mysocket && (rand() % 100) < cl_netpacketloss_send.integer) return length; + if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex) + Thread_LockMutex(netconn_mutex); ret = LHNET_Write(mysocket, data, length, peeraddress); + if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex) + Thread_UnlockMutex(netconn_mutex); if (developer_networking.integer) { char addressstring[128], addressstring2[128]; @@ -2683,7 +2694,7 @@ void RCon_Execute(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, const ch if(l) { client_t *host_client_save = host_client; - Cmd_ExecuteString(s, src_command); + Cmd_ExecuteString(s, src_command, true); host_client = host_client_save; // in case it is a command that changes host_client (like restart) } @@ -3655,6 +3666,8 @@ void NetConn_Init(void) net_message.maxsize = sizeof(net_message_buf); net_message.cursize = 0; LHNET_Init(); + if (Thread_HasThreads()) + netconn_mutex = Thread_CreateMutex(); } void NetConn_Shutdown(void) @@ -3662,5 +3675,8 @@ void NetConn_Shutdown(void) NetConn_CloseClientPorts(); NetConn_CloseServerPorts(); LHNET_Shutdown(); + if (netconn_mutex) + Thread_DestroyMutex(netconn_mutex); + netconn_mutex = NULL; } diff --git a/prvm_cmds.c b/prvm_cmds.c index 2ed1d378..1837090d 100644 --- a/prvm_cmds.c +++ b/prvm_cmds.c @@ -2565,7 +2565,7 @@ void VM_clcommand (void) temp_client = host_client; host_client = svs.clients + i; - Cmd_ExecuteString (PRVM_G_STRING(OFS_PARM1), src_client); + Cmd_ExecuteString (PRVM_G_STRING(OFS_PARM1), src_client, true); host_client = temp_client; } diff --git a/server.h b/server.h index 6eb60579..d62a7366 100644 --- a/server.h +++ b/server.h @@ -53,6 +53,12 @@ typedef struct server_static_s unsigned char *csqc_progdata; size_t csqc_progsize_deflated; unsigned char *csqc_progdata_deflated; + + // independent server thread (when running client) + qboolean threaded; // true if server is running on separate thread + qboolean volatile threadstop; + void *threadmutex; + void *thread; } server_static_t; //============================================================================= @@ -584,5 +590,10 @@ const char *Host_TimingReport(void); ///< for output in Host_Status_f int SV_GetPitchSign(prvm_edict_t *ent); void SV_GetEntityMatrix (prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatrix); +void SV_StartThread(void); +void SV_StopThread(void); +void SV_LockThreadMutex(void); +void SV_UnlockThreadMutex(void); + #endif diff --git a/sv_main.c b/sv_main.c index 0e19f3a0..db86c8f0 100644 --- a/sv_main.c +++ b/sv_main.c @@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "sv_demo.h" #include "libcurl.h" #include "csprogs.h" +#include "thread.h" static void SV_SaveEntFile_f(void); static void SV_StartDownload_f(void); @@ -155,6 +156,7 @@ cvar_t sv_areadebug = {0, "sv_areadebug", "0", "disables physics culling for deb cvar_t sys_ticrate = {CVAR_SAVE, "sys_ticrate","0.0138889", "how long a server frame is in seconds, 0.05 is 20fps server rate, 0.1 is 10fps (can not be set higher than 0.1), 0 runs as many server frames as possible (makes games against bots a little smoother, overwhelms network players), 0.0138889 matches QuakeWorld physics"}; cvar_t teamplay = {CVAR_NOTIFY, "teamplay","0", "teamplay mode, values depend on mod but typically 0 = no teams, 1 = no team damage no self damage, 2 = team damage and self damage, some mods support 3 = no team damage but can damage self"}; cvar_t timelimit = {CVAR_NOTIFY, "timelimit","0", "ends level at this time (in minutes)"}; +cvar_t sv_threaded = {0, "sv_threaded", "0", "enables a separate thread for server code, improving performance, especially when hosting a game while playing, EXPERIMENTAL, may be crashy"}; cvar_t saved1 = {CVAR_SAVE, "saved1", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; cvar_t saved2 = {CVAR_SAVE, "saved2", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; @@ -563,6 +565,7 @@ void SV_Init (void) Cvar_RegisterVariable (&sys_ticrate); Cvar_RegisterVariable (&teamplay); Cvar_RegisterVariable (&timelimit); + Cvar_RegisterVariable (&sv_threaded); Cvar_RegisterVariable (&saved1); Cvar_RegisterVariable (&saved2); @@ -2831,9 +2834,23 @@ int SV_ModelIndex(const char *s, int precachemode) if (precachemode == 1) Con_Printf("SV_ModelIndex(\"%s\"): not precached (fix your code), precaching anyway\n", filename); strlcpy(sv.model_precache[i], filename, sizeof(sv.model_precache[i])); - sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, s[0] == '*' ? sv.worldname : NULL); - if (sv.state != ss_loading) + if (sv.state == ss_loading) { + // running from SV_SpawnServer which is launched from the client console command interpreter + sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, s[0] == '*' ? sv.worldname : NULL); + } + else + { + if (svs.threaded) + { + // this is running on the server thread, we can't load a model here (it would crash on renderer calls), so only look it up, the svc_precache will cause it to be loaded when it reaches the client + sv.models[i] = Mod_FindName (sv.model_precache[i], s[0] == '*' ? sv.worldname : NULL); + } + else + { + // running single threaded, so we can load the model here + sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, s[0] == '*' ? sv.worldname : NULL); + } MSG_WriteByte(&sv.reliable_datagram, svc_precache); MSG_WriteShort(&sv.reliable_datagram, i); MSG_WriteString(&sv.reliable_datagram, filename); @@ -3190,6 +3207,8 @@ void SV_SpawnServer (const char *server) } } +// SV_LockThreadMutex(); + if (cls.state != ca_dedicated) { SCR_BeginLoadingPlaque(); @@ -3216,6 +3235,7 @@ void SV_SpawnServer (const char *server) if (!worldmodel || !worldmodel->TraceBox) { Con_Printf("Couldn't load map %s\n", modelname); + SV_UnlockThreadMutex(); return; } @@ -3463,6 +3483,8 @@ void SV_SpawnServer (const char *server) NetConn_Heartbeat (2); SV_VM_End(); + +// SV_UnlockThreadMutex(); } ///////////////////////////////////////////////////// @@ -3823,3 +3845,175 @@ void SV_VM_End(void) { PRVM_End; } + +extern cvar_t host_maxwait; +extern cvar_t host_framerate; +int SV_ThreadFunc(void *voiddata) +{ + double sv_timer = 0; + double sv_deltarealtime, sv_oldrealtime, sv_realtime; + double wait; + int i; + sv_realtime = Sys_DoubleTime(); + while (!svs.threadstop) + { + // FIXME: we need to handle Host_Error in the server thread somehow +// if (setjmp(sv_abortframe)) +// continue; // something bad happened in the server game + + sv_oldrealtime = sv_realtime; + sv_realtime = Sys_DoubleTime(); + + sv_deltarealtime = sv_realtime - sv_oldrealtime; + sv_timer += sv_deltarealtime; + + svs.perf_acc_realtime += sv_deltarealtime; + + // Look for clients who have spawned + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + if(host_client->spawned) + if(host_client->netconnection) + break; + if(i == svs.maxclients) + { + // Nobody is looking? Then we won't do timing... + // Instead, reset it to zero + svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0; + } + else if(svs.perf_acc_realtime > 5) + { + svs.perf_cpuload = 1 - svs.perf_acc_sleeptime / svs.perf_acc_realtime; + svs.perf_lost = svs.perf_acc_lost / svs.perf_acc_realtime; + if(svs.perf_acc_offset_samples > 0) + { + svs.perf_offset_max = svs.perf_acc_offset_max; + svs.perf_offset_avg = svs.perf_acc_offset / svs.perf_acc_offset_samples; + svs.perf_offset_sdev = sqrt(svs.perf_acc_offset_squared / svs.perf_acc_offset_samples - svs.perf_offset_avg * svs.perf_offset_avg); + } + if(svs.perf_lost > 0 && developer_extra.integer) + Con_DPrintf("Server can't keep up: %s\n", Host_TimingReport()); + svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0; + } + + // get new packets + if (sv.active) + NetConn_ServerFrame(); + + // if the accumulators haven't become positive yet, wait a while + wait = sv_timer * -1000000.0; + if (wait >= 1) + { + double time0; + if (host_maxwait.value <= 0) + wait = min(wait, 1000000.0); + else + wait = min(wait, host_maxwait.value * 1000.0); + if(wait < 1) + wait = 1; // because we cast to int + time0 = Sys_DoubleTime(); + Sys_Sleep((int)wait); + svs.perf_acc_sleeptime += Sys_DoubleTime() - time0; + continue; + } + + if (sv.active && sv_timer > 0) + { + // execute one server frame + double advancetime; + float offset; + + if (sys_ticrate.value <= 0) + advancetime = min(sv_timer, 0.1); // don't step more than 100ms + else + advancetime = sys_ticrate.value; + + if(advancetime > 0) + { + offset = sv_timer + (Sys_DoubleTime() - sv_realtime); // LordHavoc: FIXME: I don't understand this line + ++svs.perf_acc_offset_samples; + svs.perf_acc_offset += offset; + svs.perf_acc_offset_squared += offset * offset; + if(svs.perf_acc_offset_max < offset) + svs.perf_acc_offset_max = offset; + } + + // at this point we start doing real server work, and must block on any client activity pertaining to the server (such as executing SV_SpawnServer) + SV_LockThreadMutex(); + + // only advance time if not paused + // the game also pauses in singleplayer when menu or console is used + sv.frametime = advancetime * slowmo.value; + if (host_framerate.value) + sv.frametime = host_framerate.value; + if (sv.paused || (cl.islocalgame && (key_dest != key_game || key_consoleactive || cl.csqc_paused))) + sv.frametime = 0; + + sv_timer -= advancetime; + + // setup the VM frame + SV_VM_Begin(); + + // move things around and think unless paused + if (sv.frametime) + SV_Physics(); + + // send all messages to the clients + SV_SendClientMessages(); + + if (sv.paused == 1 && sv_realtime > sv.pausedstart && sv.pausedstart > 0) + { + prog->globals.generic[OFS_PARM0] = sv_realtime - sv.pausedstart; + PRVM_ExecuteProgram(PRVM_serverfunction(SV_PausedTic), "QC function SV_PausedTic is missing"); + } + + // end the server VM frame + SV_VM_End(); + + // send an heartbeat if enough time has passed since the last one + NetConn_Heartbeat(0); + + // at this point we start doing real server work, and must block on any client activity pertaining to the server (such as executing SV_SpawnServer) + SV_UnlockThreadMutex(); + } + + // if there is some time remaining from this frame, reset the timers + if (sv_timer >= 0) + { + svs.perf_acc_lost += sv_timer; + sv_timer = 0; + } + } + return 0; +} + +void SV_StartThread(void) +{ + if (!sv_threaded.integer || !Thread_HasThreads()) + return; + svs.threaded = true; + svs.threadstop = false; + svs.threadmutex = Thread_CreateMutex(); + svs.thread = Thread_CreateThread(SV_ThreadFunc, NULL); +} + +void SV_StopThread(void) +{ + if (!svs.threaded) + return; + svs.threadstop = true; + Thread_WaitThread(svs.thread, 0); + Thread_DestroyMutex(svs.threadmutex); + svs.threaded = false; +} + +void SV_LockThreadMutex(void) +{ + if (svs.threaded) + Thread_LockMutex(svs.threadmutex); +} + +void SV_UnlockThreadMutex(void) +{ + if (svs.threaded) + Thread_UnlockMutex(svs.threadmutex); +} diff --git a/sv_user.c b/sv_user.c index 7b59de99..431bdff0 100644 --- a/sv_user.c +++ b/sv_user.c @@ -862,7 +862,7 @@ void SV_ReadClientMessage(void) if (strncasecmp(s, "spawn", 5) == 0 || strncasecmp(s, "begin", 5) == 0 || strncasecmp(s, "prespawn", 8) == 0) - Cmd_ExecuteString (s, src_client); + Cmd_ExecuteString (s, src_client, true); else if (PRVM_serverfunction(SV_ParseClientCommand)) { int restorevm_tempstringsbuf_cursize; @@ -873,7 +873,7 @@ void SV_ReadClientMessage(void) vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; } else - Cmd_ExecuteString (s, src_client); + Cmd_ExecuteString (s, src_client, true); break; clc_stringcmd_invalid: diff --git a/svvm_cmds.c b/svvm_cmds.c index 7440b805..acc1d184 100644 --- a/svvm_cmds.c +++ b/svvm_cmds.c @@ -2315,7 +2315,7 @@ static void VM_SV_clientcommand (void) temp_client = host_client; host_client = svs.clients + i; - Cmd_ExecuteString (PRVM_G_STRING(OFS_PARM1), src_client); + Cmd_ExecuteString (PRVM_G_STRING(OFS_PARM1), src_client, true); host_client = temp_client; } diff --git a/vid_wgl.c b/vid_wgl.c index d30c6a75..3f7d177d 100644 --- a/vid_wgl.c +++ b/vid_wgl.c @@ -1900,8 +1900,8 @@ void VID_Shutdown (void) if (vid_begunscene) IDirect3DDevice9_EndScene(vid_d3d9dev); vid_begunscene = false; -// Cmd_ExecuteString("r_texturestats", src_command); -// Cmd_ExecuteString("memlist", src_command); +// Cmd_ExecuteString("r_texturestats", src_command, true); +// Cmd_ExecuteString("memlist", src_command, true); IDirect3DDevice9_Release(vid_d3d9dev); } vid_d3d9dev = NULL; diff --git a/zone.c b/zone.c index 72432d55..d14b0ec9 100644 --- a/zone.c +++ b/zone.c @@ -20,6 +20,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // Z_zone.c #include "quakedef.h" +#include "thread.h" #ifdef WIN32 #include @@ -37,6 +38,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. unsigned int sentinel_seed; qboolean mem_bigendian = false; +void *mem_mutex = NULL; // LordHavoc: enables our own low-level allocator (instead of malloc) #define MEMCLUMPING 0 @@ -330,6 +332,8 @@ void *_Mem_Alloc(mempool_t *pool, void *olddata, size_t size, size_t alignment, } if (pool == NULL) Sys_Error("Mem_Alloc: pool == NULL (alloc at %s:%i)", filename, fileline); + if (mem_mutex) + Thread_LockMutex(mem_mutex); if (developer_memory.integer) Con_DPrintf("Mem_Alloc: pool %s, file %s:%i, size %i bytes\n", pool->name, filename, fileline, (int)size); //if (developer.integer > 0 && developer_memorydebug.integer) @@ -367,6 +371,9 @@ void *_Mem_Alloc(mempool_t *pool, void *olddata, size_t size, size_t alignment, if (mem->next) mem->next->prev = mem; + if (mem_mutex) + Thread_UnlockMutex(mem_mutex); + // copy the shared portion in the case of a realloc, then memset the rest sharedsize = 0; remainsize = size; @@ -405,6 +412,8 @@ static void _Mem_FreeBlock(memheader_t *mem, const char *filename, int fileline) // unlink memheader from doubly linked list if ((mem->prev ? mem->prev->next != mem : pool->chain != mem) || (mem->next && mem->next->prev != mem)) Sys_Error("Mem_Free: not allocated or double freed (free at %s:%i)", filename, fileline); + if (mem_mutex) + Thread_LockMutex(mem_mutex); if (mem->prev) mem->prev->next = mem->next; else @@ -417,6 +426,8 @@ static void _Mem_FreeBlock(memheader_t *mem, const char *filename, int fileline) pool->totalsize -= size; pool->realsize -= realsize; Clump_FreeBlock(mem->baseaddress, realsize); + if (mem_mutex) + Thread_UnlockMutex(mem_mutex); } void _Mem_Free(void *data, const char *filename, int fileline) @@ -864,6 +875,9 @@ void Memory_Init (void) u.s = 0x100; mem_bigendian = u.b[0] != 0; + if (Thread_HasThreads()) + mem_mutex = Thread_CreateMutex(); + sentinel_seed = rand(); poolchain = NULL; tempmempool = Mem_AllocPool("Temporary Memory", POOLFLAG_TEMP, NULL); @@ -874,6 +888,10 @@ void Memory_Shutdown (void) { // Mem_FreePool (&zonemempool); // Mem_FreePool (&tempmempool); + + if (mem_mutex) + Thread_DestroyMutex(mem_mutex); + mem_mutex = NULL; } void Memory_Init_Commands (void) -- 2.39.2