// 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);
break;
case qw_svc_sellscreen:
- Cmd_ExecuteString ("help", src_command);
+ Cmd_ExecuteString ("help", src_command, true);
break;
case qw_svc_smallkick:
break;
case svc_sellscreen:
- Cmd_ExecuteString ("help", src_command);
+ Cmd_ExecuteString ("help", src_command, true);
break;
case svc_hidelmp:
if (gamemode == GAME_TENEBRAE)
// cmd.c -- Quake script command processing module
#include "quakedef.h"
+#include "thread.h"
typedef struct cmdalias_s
{
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);
+}
/*
============
{
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();
}
char *temp;
int templen;
+ Cbuf_LockThreadMutex();
+
// copy off any commands still remaining in the exec buffer
templen = cmd_text.cursize;
if (templen)
SZ_Write (&cmd_text, (const unsigned char *)temp, templen);
Mem_Free (temp);
}
+
+ Cbuf_UnlockThreadMutex();
}
/*
qboolean quotes;
char *comment;
+ Cbuf_LockThreadMutex();
+ SV_LockThreadMutex();
+
// LordHavoc: making sure the tokenizebuffer doesn't get filled up by repeated crashes
cmd_tokenizebufferpos = 0;
)
{
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)
break;
}
}
+
+ SV_UnlockThreadMutex();
+ Cbuf_UnlockThreadMutex();
}
/*
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)
*/
void Cmd_Shutdown(void)
{
+ if (cmd_text_mutex)
+ Thread_DestroyMutex(cmd_text_mutex);
+ cmd_text_mutex = NULL;
+
Mem_FreePool(&cmd_mempool);
}
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;
// 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)
if (!strcasecmp (cmd_argv[0],cmd->name))
{
if (cmd->csqcfunc && CL_VM_ConsoleCommand (text)) //[515]: csqc
- return;
+ goto done;
switch (src)
{
case src_command:
if (cmd->clientfunction)
{
cmd->clientfunction ();
- cmd_tokenizebufferpos = oldpos;
- return;
+ goto done;
}
break;
}
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
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();
}
/// 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)
#include <time.h>
#include "quakedef.h"
+#include "thread.h"
// for u8_encodech
#include "ft2.h"
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)
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);
}
/*
{
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;
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;
}
/*
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)
break;
}
}
+ if (con_mutex) Thread_UnlockMutex(con_mutex);
}
/*! The translation table between the graphical font and plain ASCII --KB */
static int index = 0;
static char line[MAX_INPUTLINE];
+ if (con_mutex)
+ Thread_LockMutex(con_mutex);
+
for (;*msg;msg++)
{
Con_Rcon_AddChar(*msg);
mask = 0;
}
}
+
+ if (con_mutex)
+ Thread_UnlockMutex(con_mutex);
}
/*
int numChatlines;
int chatpos;
+ if (con_mutex) Thread_LockMutex(con_mutex);
ConBuffer_FixTimes(&con);
numChatlines = con_chat.integer;
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);
}
/*
if (lines <= 0)
return;
+ if (con_mutex) Thread_LockMutex(con_mutex);
+
if (con_backscroll < 0)
con_backscroll = 0;
Con_DrawInput ();
r_draw2d_force = false;
+ if (con_mutex) Thread_UnlockMutex(con_mutex);
}
/*
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;
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')
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;
}
#endif
#include "quakedef.h"
+#include "thread.h"
#include "fs.h"
#include "wad.h"
*/
mempool_t *fs_mempool;
+void *fs_mutex = NULL;
searchpath_t *fs_searchpaths = NULL;
const char *const fs_checkgamedir_missing = "missing";
// generate the searchpath
FS_Rescan();
+
+ if (Thread_HasThreads())
+ fs_mutex = Thread_CreateMutex();
}
void FS_Init_Commands(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)
*/
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;
}
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))
continue;
}
+ // limit the frametime steps to no more than 100ms each
+ if (cl_timer > 0.1)
+ cl_timer = 0.1;
+
R_TimeReport("---");
//-------------------
//-------------------
// 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);
}
}
+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;
Con_DPrint("========Initialized=========\n");
//Host_StartVideo();
+
+ if (cls.state != ca_dedicated)
+ SV_StartThread();
}
VID_Shutdown();
}
+ SV_StopThread();
Thread_Shutdown();
Cmd_Shutdown();
Key_Shutdown();
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));
*/
#include "quakedef.h"
+#include "thread.h"
#include "lhnet.h"
// for secure rcon authentication
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"};
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)
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];
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)
}
net_message.maxsize = sizeof(net_message_buf);
net_message.cursize = 0;
LHNET_Init();
+ if (Thread_HasThreads())
+ netconn_mutex = Thread_CreateMutex();
}
void NetConn_Shutdown(void)
NetConn_CloseClientPorts();
NetConn_CloseServerPorts();
LHNET_Shutdown();
+ if (netconn_mutex)
+ Thread_DestroyMutex(netconn_mutex);
+ netconn_mutex = NULL;
}
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;
}
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;
//=============================================================================
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
#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);
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"};
Cvar_RegisterVariable (&sys_ticrate);
Cvar_RegisterVariable (&teamplay);
Cvar_RegisterVariable (&timelimit);
+ Cvar_RegisterVariable (&sv_threaded);
Cvar_RegisterVariable (&saved1);
Cvar_RegisterVariable (&saved2);
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);
}
}
+// SV_LockThreadMutex();
+
if (cls.state != ca_dedicated)
{
SCR_BeginLoadingPlaque();
if (!worldmodel || !worldmodel->TraceBox)
{
Con_Printf("Couldn't load map %s\n", modelname);
+ SV_UnlockThreadMutex();
return;
}
NetConn_Heartbeat (2);
SV_VM_End();
+
+// SV_UnlockThreadMutex();
}
/////////////////////////////////////////////////////
{
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);
+}
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;
vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
}
else
- Cmd_ExecuteString (s, src_client);
+ Cmd_ExecuteString (s, src_client, true);
break;
clc_stringcmd_invalid:
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;
}
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;
// Z_zone.c
#include "quakedef.h"
+#include "thread.h"
#ifdef WIN32
#include <windows.h>
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
}
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)
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;
// 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
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)
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);
{
// Mem_FreePool (&zonemempool);
// Mem_FreePool (&tempmempool);
+
+ if (mem_mutex)
+ Thread_DestroyMutex(mem_mutex);
+ mem_mutex = NULL;
}
void Memory_Init_Commands (void)