added sv_threaded cvar, the server can now be moved to another thread
authorhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Mon, 17 Oct 2011 17:02:52 +0000 (17:02 +0000)
committerhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Mon, 17 Oct 2011 17:02:52 +0000 (17:02 +0000)
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

17 files changed:
cl_demo.c
cl_parse.c
cmd.c
cmd.h
console.c
csprogs.c
fs.c
host.c
keys.c
netconn.c
prvm_cmds.c
server.h
sv_main.c
sv_user.c
svvm_cmds.c
vid_wgl.c
zone.c

index ea10bff..6778917 100644 (file)
--- 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);
index b0554e8..1055b94 100644 (file)
@@ -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 e894a8c..1ce6be7 100644 (file)
--- 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 cb96aba..722fea6 100644 (file)
--- 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)
index b604fe1..2f7e99b 100644 (file)
--- a/console.c
+++ b/console.c
@@ -25,6 +25,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #include <time.h>
 
 #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);
 }
 
 /*
index 8181505..77e3a16 100644 (file)
--- 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 4288ab5..f0fbc4e 100644 (file)
--- 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 f2e66e3..cedb41b 100644 (file)
--- 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 f88ea5a..28f74a4 100644 (file)
--- 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));
 
index c6b3ea0..160803f 100755 (executable)
--- 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;
 }
 
index 2ed1d37..1837090 100644 (file)
@@ -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;
 }
 
index 6eb6057..d62a736 100644 (file)
--- 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
 
index 0e19f3a..db86c8f 100644 (file)
--- 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);
+}
index 7b59de9..431bdff 100644 (file)
--- 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:
index 7440b80..acc1d18 100644 (file)
@@ -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;
 }
 
index d30c6a7..3f7d177 100644 (file)
--- 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 72432d5..d14b0ec 100644 (file)
--- 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 <windows.h>
@@ -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)