From e82f61256abc2943b6f751145666376cfcb1fca8 Mon Sep 17 00:00:00 2001 From: Shpoike Date: Fri, 8 Nov 2024 14:43:32 +0000 Subject: [PATCH] Add 'setrenderer sv' for linux, without needing to be started from a terminal. --- engine/client/sys_linux.c | 122 +++++++++++++++++++++++++++++++++++++- engine/common/cmd.c | 3 +- engine/common/common.c | 52 ++++++++++++---- engine/common/net_wins.c | 2 +- engine/common/sys.h | 3 + 5 files changed, 166 insertions(+), 16 deletions(-) diff --git a/engine/client/sys_linux.c b/engine/client/sys_linux.c index 2f3e28b0d..a764cc665 100644 --- a/engine/client/sys_linux.c +++ b/engine/client/sys_linux.c @@ -73,6 +73,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. static int noconinput = 0; static int nostdout = 0; +static FILE *dedcon; //replaces our stdin/out when set. +static int dedconproc = -1; + extern int isPlugin; int sys_parentleft; int sys_parenttop; @@ -86,10 +89,77 @@ static void Sys_InitClock(void); qboolean Sys_InitTerminal (void) //we either have one or we don't. { - return isatty(STDIN_FILENO); + if (isatty(STDIN_FILENO)) + return true; //already attached to a terminal in some form. don't need another. + + if (dedcon) //already got one open... shouldn't really happen. future paranoia. + return true; + else + { + int pty = posix_openpt(O_RDWR|O_NOCTTY); + if (pty >= 0) + { + int fd; + char *slavename, window[64], buf[64]; + grantpt(pty); + unlockpt(pty); + slavename = ptsname(pty); + dedcon = fopen(slavename, "r+e"); + if (dedcon) + { + snprintf(buf, sizeof buf, "-S%s/%d", strrchr(slavename,'/')+1, pty); + dedconproc = fork(); + if(!dedconproc) + { //oh hey, we're the child. + execlp("xterm", "xterm", buf, (char *)0); + _exit(1); + } + close(pty); //can close it now, we'll keep track of it with our slave fd + if (dedconproc >= 0) + { //if the xterm fails, does this EPIPE properly? + fgets(window, sizeof window, dedcon); + //printf("window: %s\n", window); + + //switch input to non-blocking, so we can actually still do stuff... + fd = fileno(dedcon); + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); + + #ifdef HAVE_EPOLL + { + extern int epoll_fd; + if (epoll_fd >= 0) + { + struct epoll_event event = {EPOLLIN, {NULL}}; + epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event); + } + } + #else + //FIXME: NET_Sleep needs to wake up on dedcon input + #endif + return true; + } + //else fork failed. + fclose(dedcon); + dedcon = NULL; + } + close(pty); + } + } + return false; //nope, soz } void Sys_CloseTerminal (void) { + if (dedcon) + { +#ifdef HAVE_EPOLL + extern int epoll_fd; + if (epoll_fd >= 0) + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fileno(dedcon), NULL); //don't get confused if the FD# is later reopened as something else... +#endif + fclose(dedcon); + dedcon = NULL; + } + dedconproc = -1; } void Sys_RecentServer(char *command, char *target, char *title, char *desc) @@ -186,7 +256,9 @@ void Sys_Printf (char *fmt, ...) } #endif - if (nostdout) + if (dedcon) + out = dedcon; + else if (nostdout) { #ifdef _DEBUG out = stderr; @@ -1137,6 +1209,27 @@ char *Sys_ConsoleInput(void) static char text[256]; char *nl; + if (dedcon) + { + int e; + if (fgets(text, sizeof(text), dedcon)) + return text; + e = errno; + switch(e) + { + case EAGAIN: + case EINTR: //not meant to be blocking, but can still be interrupted. + break; //cos we made it non-blocking. + case EPIPE: //not seen, but possible I guess. + case EIO: //can happen if the other end dies. + Sys_CloseTerminal(); + return "quit"; //kill the server if we were actually using the terminal... or at least try not to run silently. + default: + Sys_Printf(CON_WARNING "fgets errno %i\n", e); + break; + } + } + if (noconinput) return NULL; @@ -1350,6 +1443,29 @@ static void SigCont(int code) fcntl(STDIN_FILENO, F_SETFL, fl | FNDELAY); noconinput &= ~2; } +static void SigChldTerminalDied(int code, void *data) +{ //our terminal dieded? restart again (this shouldn't loop infinitely...) + Sys_CloseTerminal(); + Cbuf_AddText ("vid_renderer \"\"; vid_restart\n", RESTRICT_LOCAL); +} +static void SigChld(int code) +{ + int wstat; + pid_t pid; + + for(;;) + { + pid = wait3 (&wstat, WNOHANG, (struct rusage *)NULL); + if (pid == -1) + return; //error + else if (pid == 0) + return; //nothing left to report (linux seems to like errors instead) + else if (pid == dedconproc) + Cmd_AddTimer(0, SigChldTerminalDied, 0, NULL, 0); +// else //forked subserver? we use pipes to track when it dies. +// printf ("Return code: %d\n", wstat); + } +} #endif int main (int c, const char **v) { @@ -1362,7 +1478,7 @@ int main (int c, const char **v) #ifdef _POSIX_C_SOURCE signal(SIGTTIN, SIG_IGN); //have to ignore this if we want to not lock up when running backgrounded. signal(SIGCONT, SigCont); - signal(SIGCHLD, SIG_IGN); //mapcluster stuff might leak zombie processes if we don't do this. + signal(SIGCHLD, SigChld); //mapcluster stuff might leak zombie processes if we don't do this. #endif diff --git a/engine/common/cmd.c b/engine/common/cmd.c index ed49eba71..4d4cfc7d7 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -254,8 +254,7 @@ void Cmd_AddTimer(float delay, void(*callback)(int iarg, void *data), int iarg, n->timer = realtime + delay; - n->next = cmdtimers; - cmdtimers = n; + FTE_Atomic_Insert(cmdtimers, n, n->next); } static void Cmd_In_Callback(int iarg, void *data) { diff --git a/engine/common/common.c b/engine/common/common.c index b977f8d07..25be3befd 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -6364,6 +6364,9 @@ static int COM_WorkerThread(void *arg) } static void Sys_ErrorThread(void *ctx, void *data, size_t a, size_t b) { + if (ctx) + COM_WorkerSync_WorkerStopped(ctx, NULL, a, b); + //posted to main thread from a worker. Sys_Error("%s", (const char*)data); } @@ -6383,12 +6386,12 @@ void COM_WorkerAbort(char *message) if (com_worker[us].thread && Sys_IsThread(com_worker[us].thread)) { group = WG_LOADER; - COM_AddWork(WG_MAIN, COM_WorkerSync_WorkerStopped, &com_worker[us], NULL, 0, group); + COM_InsertWork(WG_MAIN, Sys_ErrorThread, &com_worker[us], Z_StrDup(message), 0, group); break; } - //now tell the main thread that it should be crashing, and why. - COM_AddWork(WG_MAIN, Sys_ErrorThread, NULL, Z_StrDup(message), 0, 0); + if (us == WORKERTHREADS) //don't know who it was. + COM_AddWork(WG_MAIN, Sys_ErrorThread, NULL, Z_StrDup(message), 0, 0); Sys_ThreadAbort(); } @@ -6416,11 +6419,10 @@ void COM_DestroyWorkerThread(void) while(COM_DoWork(WG_LOADER, false)) //finish any work that got posted to it that it neglected to finish. ; + COM_WorkerFullSync(); while(COM_DoWork(WG_MAIN, false)) ; - COM_WorkerFullSync(); - for (i = 0; i < WG_COUNT; i++) { if (com_workercondition[i]) @@ -6435,33 +6437,35 @@ void COM_DestroyWorkerThread(void) //Dangerous: stops workers WITHOUT flushing their queue. Be SURE to 'unlock' to start them up again. void COM_WorkerLock(void) { +#define NOFLUSH 0x40000000 int i; if (!com_liveworkers[WG_LOADER]) return; //nothing to do. - //add a fake worker and ask workers to die + //don't let liveworkers become 0 (so the main thread doesn't flush any pending work) and ask workers to die Sys_LockConditional(com_workercondition[WG_LOADER]); - com_liveworkers[WG_LOADER] += 1; + com_liveworkers[WG_LOADER] |= NOFLUSH; for (i = 0; i < WORKERTHREADS; i++) com_worker[i].request = WR_DIE; //flag them all to die Sys_ConditionBroadcast(com_workercondition[WG_LOADER]); //and make sure they ALL wake up to check their new death values. Sys_UnlockConditional(com_workercondition[WG_LOADER]); //wait for the workers to stop (leaving their work, because of our fake worker) - while(com_liveworkers[WG_LOADER]>1) + while((com_liveworkers[WG_LOADER]&~NOFLUSH)>0) { if (!COM_DoWork(WG_MAIN, false)) //need to check this to know they're done. COM_DoWork(WG_LOADER, false); //might as well, while we're waiting. } - //remove our fake worker now... + //remove our flush-blocker now... Sys_LockConditional(com_workercondition[WG_LOADER]); - com_liveworkers[WG_LOADER] -= 1; + com_liveworkers[WG_LOADER] &= ~NOFLUSH; Sys_UnlockConditional(com_workercondition[WG_LOADER]); } //called after COM_WorkerLock void COM_WorkerUnlock(void) { + qboolean restarted = false; int i; for (i = 0; i < WORKERTHREADS; i++) { @@ -6473,8 +6477,14 @@ void COM_WorkerUnlock(void) { com_worker[i].request = WR_NONE; com_worker[i].thread = Sys_CreateThread(va("loadworker_%i", i), COM_WorkerThread, &com_worker[i], 0, 256*1024); + if (com_worker[i].thread) + restarted = true; } } + + if (!restarted) + while (COM_DoWork(WG_LOADER, false)) + ; } //fully flushes ALL pending work. @@ -6709,6 +6719,19 @@ static void COM_InitWorkerThread(void) Cvar_ForceCallback(&worker_count); } +qboolean FTE_AtomicPtr_ConditionalReplace(qint32_t *ptr, qint32_t old, qint32_t new) +{ + Sys_LockMutex(com_resourcemutex); + if (*ptr == old) + { + *ptr = new; + Sys_UnlockMutex(com_resourcemutex); + return true; + } + Sys_UnlockMutex(com_resourcemutex); + return false; +} + qint32_t FTE_Atomic32Mutex_Add(qint32_t *ptr, qint32_t change) { qint32_t r; @@ -6724,6 +6747,15 @@ qint32_t FTE_Atomic32Mutex_Add(qint32_t *ptr, qint32_t change) r = (*ptr += change); return r; } +qboolean FTE_AtomicPtr_ConditionalReplace(qint32_t *ptr, qint32_t old, qint32_t new) +{ //hope it ain't threaded + if (*ptr == old) + { + *ptr = new; + return true; + } + return false; +} #endif /* diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index 47f9a9659..15dcf01b6 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -93,7 +93,7 @@ static cvar_t net_enable_kexlobby = CVARD("net_enable_"KEXLOBBY, "0", "If en //#endif #ifdef HAVE_EPOLL -static int epoll_fd = -1; +int epoll_fd = -1; #endif void NET_GetLocalAddress (int socket, netadr_t *out); diff --git a/engine/common/sys.h b/engine/common/sys.h index c709b0343..e0c4c4f6a 100644 --- a/engine/common/sys.h +++ b/engine/common/sys.h @@ -108,14 +108,17 @@ qboolean Sys_GetDesktopParameters(int *width, int *height, int *bpp, int *refres #define qatomic32_t qint32_t #define FTE_Atomic32_Inc(ptr) __sync_add_and_fetch(ptr, 1) //returns the AFTER the operation. #define FTE_Atomic32_Dec(ptr) __sync_add_and_fetch(ptr, -1) //returns the AFTER the operation. + #define FTE_Atomic_Insert(head, newnode, newnodenext) do newnodenext = head; while(!__sync_bool_compare_and_swap(&head, newnodenext, newnode)) //atomically insert into a linked list, being sure to not corrupt the pointers #elif defined(_WIN32) #define qatomic32_t long #define FTE_Atomic32_Inc(ptr) _InterlockedIncrement(ptr) #define FTE_Atomic32_Dec(ptr) _InterlockedDecrement(ptr) + #define FTE_Atomic_Insert(head, newnode, newnodenext) do newnodenext = head; while(newnodenext != _InterlockedCompareExchangePointer(&head, newnode, newnodenext)) #else #define qatomic32_t qint32_t #define FTE_Atomic32_Inc(ptr) FTE_Atomic32Mutex_Add(ptr, 1) #define FTE_Atomic32_Dec(ptr) FTE_Atomic32Mutex_Add(ptr, -1) + #define FTE_Atomic_Insert(head, newnode, newnodenext) do newnodenext = head; while(!FTE_AtomicPtr_ConditionalReplace(&head, newnodenext, newnode)) #endif