X-Git-Url: https://de.git.xonotic.org/?a=blobdiff_plain;f=radiant%2Fwatchbsp.cpp;h=2c85e57abdafeee9c71ee0b92db862ba4d04d0ca;hb=0d8ed0a52313b378bbe98e71ae7a10f9d1f44b0f;hp=08e3ce313a2edede8427a6c9d63f937eb9130f6f;hpb=80378101101ca1762bbf5638a9e3566893096d8a;p=xonotic%2Fnetradiant.git diff --git a/radiant/watchbsp.cpp b/radiant/watchbsp.cpp index 08e3ce31..2c85e57a 100644 --- a/radiant/watchbsp.cpp +++ b/radiant/watchbsp.cpp @@ -1,774 +1,770 @@ -/* -Copyright (c) 2001, Loki software, inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of Loki software nor the names of its contributors may be used -to endorse or promote products derived from this software without specific prior -written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY -DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -//----------------------------------------------------------------------------- -// -// DESCRIPTION: -// monitoring window for running BSP processes (and possibly various other stuff) - -#include "stdafx.h" -#include "watchbsp.h" -#include "feedback.h" - -#ifdef _WIN32 -#include -#endif - -#if defined (__linux__) || defined (__APPLE__) -#include -#define SOCKET_ERROR -1 -#endif - -#ifdef __APPLE__ -#include -#endif - -#include - -// Static functions for the SAX callbacks ------------------------------------------------------- - -// utility for saxStartElement below -static void abortStream(message_info_t *data) -{ - g_pParentWnd->GetWatchBSP()->Reset(); - // tell there has been an error - if (g_pParentWnd->GetWatchBSP()->HasBSPPlugin ()) - g_BSPFrontendTable.m_pfnEndListen(2); - // yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out - data->ignore_depth = -1; - data->recurse++; -} - -#include "stream_version.h" - -static void saxStartElement(message_info_t *data, const xmlChar *name, const xmlChar **attrs) -{ - if (data->ignore_depth == 0) - { - if (data->bGeometry) - // we have a handler - { - data->pGeometry->saxStartElement (data, name, attrs); - } - else - { - if (strcmp ((char *)name, "q3map_feedback") == 0) - { - // check the correct version - // old q3map don't send a version attribute - // the ones we support .. send Q3MAP_STREAM_VERSION - if (!attrs[0] || !attrs[1] || (strcmp((char*)attrs[0],"version") != 0)) - { - Sys_FPrintf(SYS_ERR, "No stream version given in the feedback stream, this is an old q3map version.\n" - "Please turn off monitored compiling if you still wish to use this q3map executable\n"); - abortStream(data); - return; - } - else if (strcmp((char*)attrs[1],Q3MAP_STREAM_VERSION) != 0) - { - Sys_FPrintf(SYS_ERR, - "This version of Radiant reads version %s debug streams, I got an incoming connection with version %s\n" - "Please make sure your versions of Radiant and q3map are matching.\n", Q3MAP_STREAM_VERSION, (char*)attrs[1]); - abortStream(data); - return; - } - } - // we don't treat locally - else if (strcmp ((char *)name, "message") == 0) - { - data->msg_level = atoi ((char *)attrs[1]); - } - else if (strcmp ((char *)name, "polyline") == 0) - // polyline has a particular status .. right now we only use it for leakfile .. - { - data->bGeometry = true; - data->pGeometry = &g_pointfile; - data->pGeometry->saxStartElement (data, name, attrs); - } - else if (strcmp ((char *)name, "select") == 0) - { - CSelectMsg *pSelect = new CSelectMsg(); - data->bGeometry = true; - data->pGeometry = pSelect; - data->pGeometry->saxStartElement (data, name, attrs); - } - else if (strcmp ((char *)name, "pointmsg") == 0) - { - CPointMsg *pPoint = new CPointMsg(); - data->bGeometry = true; - data->pGeometry = pPoint; - data->pGeometry->saxStartElement (data, name, attrs); - } - else if (strcmp ((char *)name, "windingmsg") == 0) - { - CWindingMsg *pWinding = new CWindingMsg(); - data->bGeometry = true; - data->pGeometry = pWinding; - data->pGeometry->saxStartElement (data, name, attrs); - } - else - { - Sys_FPrintf (SYS_WRN, "WARNING: ignoring unrecognized node in XML stream (%s)\n", name); - // we don't recognize this node, jump over it - // (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level) - data->ignore_depth = data->recurse; - } - } - } - data->recurse++; -} - -static void saxEndElement(message_info_t *data, const xmlChar *name) -{ - data->recurse--; - // we are out of an ignored chunk - if (data->recurse == data->ignore_depth) - { - data->ignore_depth = 0; - return; - } - if (data->bGeometry) - { - data->pGeometry->saxEndElement (data, name); - // we add the object to the debug window - if (!data->bGeometry) - { - g_DbgDlg.Push (data->pGeometry); - } - } - if (data->recurse == data->stop_depth) - { -#ifdef _DEBUG - Sys_Printf ("Received error msg .. shutting down..\n"); -#endif - g_pParentWnd->GetWatchBSP()->Reset(); - // tell there has been an error - if (g_pParentWnd->GetWatchBSP()->HasBSPPlugin ()) - g_BSPFrontendTable.m_pfnEndListen(2); - return; - } -} - -static void saxCharacters(message_info_t *data, const xmlChar *ch, int len) -{ - if (data->bGeometry) - { - data->pGeometry->saxCharacters (data, ch, len); - } - else - { - if (data->ignore_depth != 0) - return; - // output the message using the level - char buf[1024]; - memcpy( buf, ch, len ); - buf[len] = '\0'; - Sys_FPrintf (data->msg_level, "%s", buf); - // if this message has error level flag, we mark the depth to stop the compilation when we get out - // we don't set the msg level if we don't stop on leak - if (data->msg_level == 3) - { - data->stop_depth = data->recurse-1; - } - } -} - -static void saxComment(void *ctx, const xmlChar *msg) -{ - Sys_Printf("XML comment: %s\n", msg); -} - -static void saxWarning(void *ctx, const char *msg, ...) -{ - char saxMsgBuffer[4096]; - va_list args; - - va_start(args, msg); - vsprintf (saxMsgBuffer, msg, args); - va_end(args); - Sys_FPrintf(SYS_WRN, "XML warning: %s\n", saxMsgBuffer); -} - -static void saxError(void *ctx, const char *msg, ...) -{ - char saxMsgBuffer[4096]; - va_list args; - - va_start(args, msg); - vsprintf (saxMsgBuffer, msg, args); - va_end(args); - Sys_FPrintf(SYS_ERR, "XML error: %s\n", saxMsgBuffer); -} - -static void saxFatal(void *ctx, const char *msg, ...) -{ - char buffer[4096]; - - va_list args; - - va_start(args, msg); - vsprintf (buffer, msg, args); - va_end(args); - Sys_FPrintf(SYS_ERR, "XML fatal error: %s\n", buffer); -} - -static xmlSAXHandler saxParser = { - 0, /* internalSubset */ - 0, /* isStandalone */ - 0, /* hasInternalSubset */ - 0, /* hasExternalSubset */ - 0, /* resolveEntity */ - 0, /* getEntity */ - 0, /* entityDecl */ - 0, /* notationDecl */ - 0, /* attributeDecl */ - 0, /* elementDecl */ - 0, /* unparsedEntityDecl */ - 0, /* setDocumentLocator */ - 0, /* startDocument */ - 0, /* endDocument */ - (startElementSAXFunc)saxStartElement, /* startElement */ - (endElementSAXFunc)saxEndElement, /* endElement */ - 0, /* reference */ - (charactersSAXFunc)saxCharacters, /* characters */ - 0, /* ignorableWhitespace */ - 0, /* processingInstruction */ - (commentSAXFunc)saxComment, /* comment */ - (warningSAXFunc)saxWarning, /* warning */ - (errorSAXFunc)saxError, /* error */ - (fatalErrorSAXFunc)saxFatal, /* fatalError */ -}; - -// ------------------------------------------------------------------------------------------------ - -CWatchBSP::~CWatchBSP() -{ - Reset(); - if (m_sBSPName) - { - delete[] m_sBSPName; - m_sBSPName = NULL; - } - Net_Shutdown(); -} - -void CWatchBSP::Reset() -{ - if (m_pInSocket) - { - Net_Disconnect(m_pInSocket); - m_pInSocket = NULL; - } - if (m_pListenSocket) - { - Net_Disconnect(m_pListenSocket); - m_pListenSocket = NULL; - } - if (m_xmlInputBuffer) - { - xmlFreeParserInputBuffer (m_xmlInputBuffer); - m_xmlInputBuffer = NULL; - } - m_eState = EIdle; -} - -bool CWatchBSP::SetupListening() -{ -#ifdef _DEBUG - if (m_pListenSocket) - { - Sys_Printf("ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n"); - return false; - } -#endif - Sys_Printf("Setting up\n"); - Net_Setup(); - m_pListenSocket = Net_ListenSocket(39000); - if (m_pListenSocket == NULL) - return false; - Sys_Printf("Listening...\n"); - return true; -} - -void CWatchBSP::DoEBeginStep() -{ - Reset(); - if (SetupListening() == false) - { - CString msg; - msg = "Failed to get a listening socket on port 39000.\nTry running with BSP monitoring disabled if you can't fix this.\n"; - Sys_Printf (msg); - gtk_MessageBox (g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR); - return; - } - // set the timer for timeouts and step cancellation - g_timer_reset( m_pTimer ); - g_timer_start( m_pTimer ); - - if (!m_bBSPPlugin) - { - Sys_Printf("=== running BSP command ===\n%s\n", g_ptr_array_index( m_pCmd, m_iCurrentStep ) ); - - if (!Q_Exec(NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true )) - { - CString msg; - msg = "Failed to execute the following command: "; - msg += (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ); - msg += "\nCheck that the file exists and that you don't run out of system resources.\n"; - Sys_Printf(msg); - gtk_MessageBox(g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR ); - return; - } - // re-initialise the debug window - if (m_iCurrentStep == 0) - g_DbgDlg.Init(); - } - m_eState = EBeginStep; -} - -void CWatchBSP::RoutineProcessing() -{ - // used for select() -#ifdef _WIN32 - TIMEVAL tout = { 0, 0 }; -#endif -#if defined (__linux__) || defined (__APPLE__) - timeval tout; - tout.tv_sec = 0; - tout.tv_usec = 0; -#endif - - switch (m_eState) - { - case EBeginStep: - // timeout: if we don't get an incoming connection fast enough, go back to idle - if ( g_timer_elapsed( m_pTimer, NULL ) > g_PrefsDlg.m_iTimeout ) - { - gtk_MessageBox(g_pParentWnd->m_pWidget, "The connection timed out, assuming the BSP process failed\nMake sure you are using a networked version of Q3Map?\nOtherwise you need to disable BSP Monitoring in prefs.", "BSP process monitoring", MB_OK ); - Reset(); - if (m_bBSPPlugin) - { - // status == 1 : didn't get the connection - g_BSPFrontendTable.m_pfnEndListen(1); - } - return; - } -#ifdef _DEBUG - // some debug checks - if (!m_pListenSocket) - { - Sys_Printf("ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n"); - return; - } -#endif - // we are not connected yet, accept any incoming connection - m_pInSocket = Net_Accept(m_pListenSocket); - if (m_pInSocket) - { - Sys_Printf("Connected.\n"); - // prepare the message info struct for diving in - memset (&m_message_info, 0, sizeof(message_info_s)); - // a dumb flag to make sure we init the push parser context when first getting a msg - m_bNeedCtxtInit = true; - m_eState = EWatching; - } - break; - case EWatching: -#ifdef _DEBUG - // some debug checks - if (!m_pInSocket) - { - Sys_Printf("ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n"); - return; - } -#endif - // select() will identify if the socket needs an update - // if the socket is identified that means there's either a message or the connection has been closed/reset/terminated - fd_set readfds; - int ret; - FD_ZERO(&readfds); - FD_SET(((unsigned int)m_pInSocket->socket), &readfds); - // from select man page: - // n is the highest-numbered descriptor in any of the three sets, plus 1 - // (no use on windows) - ret = select( m_pInSocket->socket + 1, &readfds, NULL, NULL, &tout ); - if (ret == SOCKET_ERROR) - { - Sys_Printf("WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n"); - Sys_Printf("Terminating the connection.\n"); - Reset(); - return; - } -#ifdef _DEBUG - if (ret == -1) - { - // we are non-blocking?? we should never get timeout errors - Sys_Printf("WARNING: unexpected timeout expired in CWatchBSP::Processing\n"); - Sys_Printf("Terminating the connection.\n"); - Reset(); - return; - } -#endif - if (ret == 1) - { - // the socket has been identified, there's something (message or disconnection) - // see if there's anything in input - ret = Net_Receive( m_pInSocket, &msg ); - if (ret > 0) - { - // unsigned int size = msg.size; //++timo just a check - strcpy (m_xmlBuf, NMSG_ReadString (&msg)); - if (m_bNeedCtxtInit) - { - m_xmlParserCtxt = NULL; - m_xmlParserCtxt = xmlCreatePushParserCtxt (&saxParser, &m_message_info, m_xmlBuf, strlen(m_xmlBuf), NULL); - if (m_xmlParserCtxt == NULL) - { - Sys_FPrintf (SYS_ERR, "Failed to create the XML parser (incoming stream began with: %s)\n", m_xmlBuf); - Reset(); - } - m_bNeedCtxtInit = false; - } - else - { - xmlParseChunk (m_xmlParserCtxt, m_xmlBuf, strlen(m_xmlBuf), 0); - } - } - else - { - // error or connection closed/reset - // NOTE: if we get an error down the XML stream we don't reach here - Net_Disconnect( m_pInSocket ); - m_pInSocket = NULL; - Sys_Printf("Connection closed.\n"); - if (m_bBSPPlugin) - { - Reset(); - // let the BSP plugin know that the job is done - g_BSPFrontendTable.m_pfnEndListen(0); - return; - } - // move to next step or finish - m_iCurrentStep++; - if (m_iCurrentStep < m_pCmd->len ) - { - DoEBeginStep(); - } - else - { - // release the GPtrArray and the strings - if (m_pCmd != NULL) - { - for (m_iCurrentStep=0; m_iCurrentStep < m_pCmd->len; m_iCurrentStep++ ) - { - delete[] (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ); - } - g_ptr_array_free( m_pCmd, false ); - } - m_pCmd = NULL; - // launch the engine .. OMG - if (g_PrefsDlg.m_bRunQuake) - { - // do we enter sleep mode before? - if (g_PrefsDlg.m_bDoSleep) - { - Sys_Printf("Going into sleep mode..\n"); - g_pParentWnd->OnSleep(); - } - Sys_Printf("Running engine...\n"); - Str cmd; - // build the command line - cmd = g_pGameDescription->mEnginePath.GetBuffer(); - // this is game dependant - //!\todo Read the engine binary name from a config file. - if (g_pGameDescription->mGameFile == "wolf.game") - { - if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp")) - { - // MP -#if defined(WIN32) - cmd += "WolfMP.exe"; -#elif defined(__linux__) - cmd += "wolfmp"; -#elif defined(__APPLE__) - cmd += "wolfmp.app"; -#else -#error "WTF are you compiling on" -#endif - } - else - { - // SP -#if defined(WIN32) - cmd += "WolfSP.exe"; -#elif defined(__linux__) - cmd += "wolfsp"; -#elif defined(__APPLE__) - cmd += "wolfsp.app"; -#else -#error "WTF are you compiling on" -#endif - } - } else if (g_pGameDescription->mGameFile == "et.game") - { -#if defined(WIN32) - cmd += "et.exe"; -#elif defined(__linux__) - cmd += "et"; -#elif defined(__APPLE__) - cmd += "et.app"; -#else -#error "WTF are you compiling on" -#endif - } - // RIANT - // JK2 HACK - else if (g_pGameDescription->mGameFile == "jk2.game") - { - if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp")) - { - // MP -#if defined(WIN32) - cmd += "jk2MP.exe"; -#elif defined(__linux__) - cmd += "jk2mp"; -#elif defined(__APPLE__) - cmd += "jk2mp.app"; -#else -#error "WTF are you compiling on" -#endif - } - else - { - // SP -#if defined(WIN32) - cmd += "jk2SP.exe"; -#elif defined(__linux__) - cmd += "jk2sp"; -#elif defined(__APPLE__) - cmd += "jk2sp.app"; -#else -#error "WTF are you compiling on" -#endif - } - } - // TTimo - // JA HACK - else if (g_pGameDescription->mGameFile == "ja.game") - { - if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp")) - { - // MP -#if defined(WIN32) - cmd += "jamp.exe"; -#elif !defined(__linux__) && !defined(__APPLE__) -#error "WTF are you compiling on" -#endif - } - else - { - // SP -#if defined(WIN32) - cmd += "jasp.exe"; -#elif !defined(__linux__) && !defined(__APPLE__) -#error "WTF are you compiling on" -#endif - } - } - // RIANT - // STVEF HACK - else if (g_pGameDescription->mGameFile == "stvef.game") - { - if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp")) - { - // MP -#if defined(WIN32) - cmd += "stvoyHM.exe"; -#elif defined(__linux__) - cmd += "stvoyHM"; -#elif defined(__APPLE__) - cmd += "stvoyHM.app"; -#else -#error "WTF are you compiling on" -#endif - } - else - { - // SP -#if defined(WIN32) - cmd += "stvoy.exe"; -#elif defined(__linux__) - cmd += "stvoy"; -#elif defined(__APPLE__) - cmd += "stvoy.app"; -#else -#error "WTF are you compiling on" -#endif - } - } - // RIANT - // SOF2 HACK - else if (g_pGameDescription->mGameFile == "sof2.game") - { - if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp")) - { - // MP -#if defined(WIN32) - cmd += "sof2MP.exe"; -#elif defined(__linux__) - cmd += "b00gus"; -#elif defined(__APPLE__) - cmd += "sof2MP.app"; -#else -#error "WTF are you compiling on" -#endif - } - else - { - // SP -#if defined(WIN32) - cmd += "sof2.exe"; -#elif defined(__linux__) - cmd += "b00gus"; -#elif defined(__APPLE__) - cmd += "sof2.app"; -#else -#error "WTF are you compiling on" -#endif - } - } - else - { - cmd += g_pGameDescription->mEngine.GetBuffer(); - } -#ifdef _WIN32 - // NOTE: we are using unix pathnames and CreateProcess doesn't like / in the program path - FindReplace( cmd, "/", "\\" ); -#endif - Str cmdline; - if ( (g_pGameDescription->mGameFile == "q2.game") || (g_pGameDescription->mGameFile == "heretic2.game") ) - { - cmdline = ". +exec radiant.cfg +map "; - cmdline += m_sBSPName; - } - else - { - cmdline = "+set sv_pure 0 "; - // TTimo: a check for vm_* but that's all fine - //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 "; - if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0') - { - cmdline += "+set fs_game "; - cmdline += ValueForKey(g_qeglobals.d_project_entity, "gamename"); - cmdline += " "; - } - //!\todo Read the start-map args from a config file. - if (g_pGameDescription->mGameFile == "wolf.game") - { - if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp")) - { - // MP - cmdline += "+devmap "; - cmdline += m_sBSPName; - } - else - { - // SP - cmdline += "+set nextmap \"spdevmap "; - cmdline += m_sBSPName; - cmdline += "\""; - } - } - else - { - cmdline += "+devmap "; - cmdline += m_sBSPName; - } - } - - Sys_Printf("%s %s\n", cmd.GetBuffer(), cmdline.GetBuffer()); - - // execute now - if (!Q_Exec(cmd.GetBuffer(), (char *)cmdline.GetBuffer(), g_pGameDescription->mEnginePath.GetBuffer(), false)) - { - CString msg; - msg = "Failed to execute the following command: "; - msg += cmd; msg += cmdline; - Sys_Printf(msg); - gtk_MessageBox(g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR ); - } - } - Reset(); - } - } - } - break; - default: - break; - } -} - -void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, char *sBSPName ) -{ - if (m_sBSPName) - { - delete[] m_sBSPName; - } - m_sBSPName = sBSPName; - if (m_eState != EIdle) - { - Sys_Printf("WatchBSP got a monitoring request while not idling...\n"); - // prompt the user, should we cancel the current process and go ahead? - if (gtk_MessageBox(g_pParentWnd->m_pWidget, "I am already monitoring a BSP process.\nDo you want me to override and start a new compilation?", - "BSP process monitoring", MB_YESNO ) == IDYES) - { - // disconnect and set EIdle state - Reset(); - } - } - m_pCmd = pCmd; - m_iCurrentStep = 0; - DoEBeginStep(); -} - -void CWatchBSP::ExternalListen() -{ - m_bBSPPlugin = true; - DoEBeginStep (); -} - -// the part of the watchbsp interface we export to plugins -// NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level -// for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final) -void WINAPI QERApp_Listen() -{ - // open the listening socket - g_pParentWnd->GetWatchBSP()->ExternalListen(); -} +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +//----------------------------------------------------------------------------- +// +// DESCRIPTION: +// monitoring window for running BSP processes (and possibly various other stuff) + +#include "stdafx.h" +#include "watchbsp.h" +#include "feedback.h" + +#ifdef _WIN32 +#include +#endif + +#if defined (__linux__) || defined (__APPLE__) +#include +#define SOCKET_ERROR -1 +#endif + +#ifdef __APPLE__ +#include +#endif + +#include + +// Static functions for the SAX callbacks ------------------------------------------------------- + +// utility for saxStartElement below +static void abortStream(message_info_t *data) +{ + g_pParentWnd->GetWatchBSP()->Reset(); + // tell there has been an error + if (g_pParentWnd->GetWatchBSP()->HasBSPPlugin ()) + g_BSPFrontendTable.m_pfnEndListen(2); + // yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out + data->ignore_depth = -1; + data->recurse++; +} + +#include "stream_version.h" + +static void saxStartElement(message_info_t *data, const xmlChar *name, const xmlChar **attrs) +{ + if (data->ignore_depth == 0) + { + if (data->bGeometry) + // we have a handler + { + data->pGeometry->saxStartElement (data, name, attrs); + } + else + { + if (strcmp ((char *)name, "q3map_feedback") == 0) + { + // check the correct version + // old q3map don't send a version attribute + // the ones we support .. send Q3MAP_STREAM_VERSION + if (!attrs[0] || !attrs[1] || (strcmp((char*)attrs[0],"version") != 0)) + { + Sys_FPrintf(SYS_ERR, "No stream version given in the feedback stream, this is an old q3map version.\n" + "Please turn off monitored compiling if you still wish to use this q3map executable\n"); + abortStream(data); + return; + } + else if (strcmp((char*)attrs[1],Q3MAP_STREAM_VERSION) != 0) + { + Sys_FPrintf(SYS_ERR, + "This version of Radiant reads version %s debug streams, I got an incoming connection with version %s\n" + "Please make sure your versions of Radiant and q3map are matching.\n", Q3MAP_STREAM_VERSION, (char*)attrs[1]); + abortStream(data); + return; + } + } + // we don't treat locally + else if (strcmp ((char *)name, "message") == 0) + { + data->msg_level = atoi ((char *)attrs[1]); + } + else if (strcmp ((char *)name, "polyline") == 0) + // polyline has a particular status .. right now we only use it for leakfile .. + { + data->bGeometry = true; + data->pGeometry = &g_pointfile; + data->pGeometry->saxStartElement( data, name, attrs ); + } + else if (strcmp ((char *)name, "select") == 0) + { + CSelectMsg *pSelect = new CSelectMsg(); + data->bGeometry = true; + data->pGeometry = pSelect; + data->pGeometry->saxStartElement( data, name, attrs ); + } + else if (strcmp ((char *)name, "pointmsg") == 0) + { + CPointMsg *pPoint = new CPointMsg(); + data->bGeometry = true; + data->pGeometry = pPoint; + data->pGeometry->saxStartElement( data, name, attrs ); + } + else if (strcmp ((char *)name, "windingmsg") == 0) + { + CWindingMsg *pWinding = new CWindingMsg(); + data->bGeometry = true; + data->pGeometry = pWinding; + data->pGeometry->saxStartElement( data, name, attrs ); + } + else + { + Sys_FPrintf (SYS_WRN, "WARNING: ignoring unrecognized node in XML stream (%s)\n", name); + // we don't recognize this node, jump over it + // (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level) + data->ignore_depth = data->recurse; + } + } + } + data->recurse++; +} + +static void saxEndElement(message_info_t *data, const xmlChar *name) { + data->recurse--; + // we are out of an ignored chunk + if ( data->recurse == data->ignore_depth ) { + data->ignore_depth = 0; + return; + } + if ( data->bGeometry ) { + data->pGeometry->saxEndElement( data, name ); + // we add the object to the debug window + if ( !data->bGeometry ) { + g_DbgDlg.Push( data->pGeometry ); + } + } + if ( data->recurse == data->stop_depth ) { +#ifdef _DEBUG + Sys_Printf ("Received error msg .. shutting down..\n"); +#endif + g_pParentWnd->GetWatchBSP()->Reset(); + // tell there has been an error + if ( g_pParentWnd->GetWatchBSP()->HasBSPPlugin() ) { + g_BSPFrontendTable.m_pfnEndListen( 2 ); + } + return; + } +} + +static void saxCharacters(message_info_t *data, const xmlChar *ch, int len) +{ + if (data->bGeometry) + { + data->pGeometry->saxCharacters (data, ch, len); + } + else + { + if (data->ignore_depth != 0) + return; + // output the message using the level + char buf[1024]; + memcpy( buf, ch, len ); + buf[len] = '\0'; + Sys_FPrintf (data->msg_level, "%s", buf); + // if this message has error level flag, we mark the depth to stop the compilation when we get out + // we don't set the msg level if we don't stop on leak + if (data->msg_level == 3) + { + data->stop_depth = data->recurse-1; + } + } +} + +static void saxComment(void *ctx, const xmlChar *msg) +{ + Sys_Printf("XML comment: %s\n", msg); +} + +static void saxWarning(void *ctx, const char *msg, ...) +{ + char saxMsgBuffer[4096]; + va_list args; + + va_start(args, msg); + vsprintf (saxMsgBuffer, msg, args); + va_end(args); + Sys_FPrintf(SYS_WRN, "XML warning: %s\n", saxMsgBuffer); +} + +static void saxError(void *ctx, const char *msg, ...) +{ + char saxMsgBuffer[4096]; + va_list args; + + va_start(args, msg); + vsprintf (saxMsgBuffer, msg, args); + va_end(args); + Sys_FPrintf(SYS_ERR, "XML error: %s\n", saxMsgBuffer); +} + +static void saxFatal(void *ctx, const char *msg, ...) +{ + char buffer[4096]; + + va_list args; + + va_start(args, msg); + vsprintf (buffer, msg, args); + va_end(args); + Sys_FPrintf(SYS_ERR, "XML fatal error: %s\n", buffer); +} + +static xmlSAXHandler saxParser = { + 0, /* internalSubset */ + 0, /* isStandalone */ + 0, /* hasInternalSubset */ + 0, /* hasExternalSubset */ + 0, /* resolveEntity */ + 0, /* getEntity */ + 0, /* entityDecl */ + 0, /* notationDecl */ + 0, /* attributeDecl */ + 0, /* elementDecl */ + 0, /* unparsedEntityDecl */ + 0, /* setDocumentLocator */ + 0, /* startDocument */ + 0, /* endDocument */ + (startElementSAXFunc)saxStartElement, /* startElement */ + (endElementSAXFunc)saxEndElement, /* endElement */ + 0, /* reference */ + (charactersSAXFunc)saxCharacters, /* characters */ + 0, /* ignorableWhitespace */ + 0, /* processingInstruction */ + (commentSAXFunc)saxComment, /* comment */ + (warningSAXFunc)saxWarning, /* warning */ + (errorSAXFunc)saxError, /* error */ + (fatalErrorSAXFunc)saxFatal, /* fatalError */ +}; + +// ------------------------------------------------------------------------------------------------ + +CWatchBSP::~CWatchBSP() +{ + Reset(); + if (m_sBSPName) + { + delete[] m_sBSPName; + m_sBSPName = NULL; + } + Net_Shutdown(); +} + +void CWatchBSP::Reset() +{ + if (m_pInSocket) + { + Net_Disconnect(m_pInSocket); + m_pInSocket = NULL; + } + if (m_pListenSocket) + { + Net_Disconnect(m_pListenSocket); + m_pListenSocket = NULL; + } + if (m_xmlInputBuffer) + { + xmlFreeParserInputBuffer (m_xmlInputBuffer); + m_xmlInputBuffer = NULL; + } + m_eState = EIdle; +} + +bool CWatchBSP::SetupListening() +{ +#ifdef _DEBUG + if (m_pListenSocket) + { + Sys_Printf("ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n"); + return false; + } +#endif + Sys_Printf("Setting up\n"); + if( !Net_Setup() ) + return false; + + m_pListenSocket = Net_ListenSocket(39000); + if (m_pListenSocket == NULL) + return false; + + Sys_Printf("Listening...\n"); + return true; +} + +void CWatchBSP::DoEBeginStep() { + Reset(); + if ( !SetupListening() ) { + CString msg; + msg = "Failed to get a listening socket on port 39000.\nTry running with BSP monitoring disabled if you can't fix this.\n"; + Sys_Printf( msg ); + gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR ); + return; + } + // set the timer for timeouts and step cancellation + g_timer_reset( m_pTimer ); + g_timer_start( m_pTimer ); + + if ( !m_bBSPPlugin ) { + Sys_Printf( "=== running BSP command ===\n%s\n", g_ptr_array_index( m_pCmd, m_iCurrentStep ) ); + + if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true ) ) { + CString msg; + msg = "Failed to execute the following command: "; + msg += (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ); + msg += "\nCheck that the file exists and that you don't run out of system resources.\n"; + Sys_Printf( msg ); + gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR ); + return; + } + // re-initialise the debug window + if ( m_iCurrentStep == 0 ) { + g_DbgDlg.Init(); + } + } + m_eState = EBeginStep; +} + +void CWatchBSP::RoutineProcessing() +{ + // used for select() +#ifdef _WIN32 + TIMEVAL tout = { 0, 0 }; +#endif +#if defined (__linux__) || defined (__APPLE__) + timeval tout; + tout.tv_sec = 0; + tout.tv_usec = 0; +#endif + + switch (m_eState) + { + case EBeginStep: + // timeout: if we don't get an incoming connection fast enough, go back to idle + if ( g_timer_elapsed( m_pTimer, NULL ) > g_PrefsDlg.m_iTimeout ) + { + gtk_MessageBox(g_pParentWnd->m_pWidget, "The connection timed out, assuming the BSP process failed\nMake sure you are using a networked version of Q3Map?\nOtherwise you need to disable BSP Monitoring in prefs.", "BSP process monitoring", MB_OK ); + Reset(); + if (m_bBSPPlugin) + { + // status == 1 : didn't get the connection + g_BSPFrontendTable.m_pfnEndListen(1); + } + return; + } +#ifdef _DEBUG + // some debug checks + if (!m_pListenSocket) + { + Sys_Printf("ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n"); + return; + } +#endif + // we are not connected yet, accept any incoming connection + m_pInSocket = Net_Accept(m_pListenSocket); + if (m_pInSocket) + { + Sys_Printf("Connected.\n"); + // prepare the message info struct for diving in + memset (&m_message_info, 0, sizeof(message_info_s)); + // a dumb flag to make sure we init the push parser context when first getting a msg + m_bNeedCtxtInit = true; + m_eState = EWatching; + } + break; + case EWatching: +#ifdef _DEBUG + // some debug checks + if (!m_pInSocket) + { + Sys_Printf("ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n"); + return; + } +#endif + // select() will identify if the socket needs an update + // if the socket is identified that means there's either a message or the connection has been closed/reset/terminated + fd_set readfds; + int ret; + FD_ZERO(&readfds); + FD_SET(((unsigned int)m_pInSocket->socket), &readfds); + // from select man page: + // n is the highest-numbered descriptor in any of the three sets, plus 1 + // (no use on windows) + ret = select( m_pInSocket->socket + 1, &readfds, NULL, NULL, &tout ); + if (ret == SOCKET_ERROR) + { + Sys_Printf("WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n"); + Sys_Printf("Terminating the connection.\n"); + Reset(); + return; + } +#ifdef _DEBUG + if (ret == -1) + { + // we are non-blocking?? we should never get timeout errors + Sys_Printf("WARNING: unexpected timeout expired in CWatchBSP::Processing\n"); + Sys_Printf("Terminating the connection.\n"); + Reset(); + return; + } +#endif + if (ret == 1) + { + // the socket has been identified, there's something (message or disconnection) + // see if there's anything in input + ret = Net_Receive( m_pInSocket, &msg ); + if (ret > 0) + { + // unsigned int size = msg.size; //++timo just a check + strcpy (m_xmlBuf, NMSG_ReadString (&msg)); + if (m_bNeedCtxtInit) + { + m_xmlParserCtxt = NULL; + m_xmlParserCtxt = xmlCreatePushParserCtxt (&saxParser, &m_message_info, m_xmlBuf, strlen(m_xmlBuf), NULL); + if (m_xmlParserCtxt == NULL) + { + Sys_FPrintf (SYS_ERR, "Failed to create the XML parser (incoming stream began with: %s)\n", m_xmlBuf); + Reset(); + } + m_bNeedCtxtInit = false; + } + else + { + xmlParseChunk (m_xmlParserCtxt, m_xmlBuf, strlen(m_xmlBuf), 0); + } + } + else + { + // error or connection closed/reset + // NOTE: if we get an error down the XML stream we don't reach here + Net_Disconnect( m_pInSocket ); + m_pInSocket = NULL; + Sys_Printf("Connection closed.\n"); + if (m_bBSPPlugin) + { + Reset(); + // let the BSP plugin know that the job is done + g_BSPFrontendTable.m_pfnEndListen(0); + return; + } + // move to next step or finish + m_iCurrentStep++; + if (m_iCurrentStep < m_pCmd->len ) + { + DoEBeginStep(); + } + else + { + // release the GPtrArray and the strings + if (m_pCmd != NULL) + { + for (m_iCurrentStep=0; m_iCurrentStep < m_pCmd->len; m_iCurrentStep++ ) + { + delete[] (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ); + } + g_ptr_array_free( m_pCmd, false ); + } + m_pCmd = NULL; + // launch the engine .. OMG + if (g_PrefsDlg.m_bRunQuake) + { + // do we enter sleep mode before? + if (g_PrefsDlg.m_bDoSleep) + { + Sys_Printf("Going into sleep mode..\n"); + g_pParentWnd->OnSleep(); + } + Sys_Printf("Running engine...\n"); + Str cmd; + // build the command line + cmd = g_pGameDescription->mEnginePath.GetBuffer(); + // this is game dependant + //!\todo Read the engine binary name from a config file. + if (g_pGameDescription->mGameFile == "wolf.game") + { + if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp")) + { + // MP +#if defined(WIN32) + cmd += "WolfMP.exe"; +#elif defined(__linux__) + cmd += "wolfmp"; +#elif defined(__APPLE__) + cmd += "wolfmp.app"; +#else +#error "WTF are you compiling on" +#endif + } + else + { + // SP +#if defined(WIN32) + cmd += "WolfSP.exe"; +#elif defined(__linux__) + cmd += "wolfsp"; +#elif defined(__APPLE__) + cmd += "wolfsp.app"; +#else +#error "WTF are you compiling on" +#endif + } + } else if (g_pGameDescription->mGameFile == "et.game") + { +#if defined(WIN32) + cmd += "et.exe"; +#elif defined(__linux__) + cmd += "et"; +#elif defined(__APPLE__) + cmd += "et.app"; +#else +#error "WTF are you compiling on" +#endif + } + // RIANT + // JK2 HACK + else if (g_pGameDescription->mGameFile == "jk2.game") + { + if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp")) + { + // MP +#if defined(WIN32) + cmd += "jk2MP.exe"; +#elif defined(__linux__) + cmd += "jk2mp"; +#elif defined(__APPLE__) + cmd += "jk2mp.app"; +#else +#error "WTF are you compiling on" +#endif + } + else + { + // SP +#if defined(WIN32) + cmd += "jk2SP.exe"; +#elif defined(__linux__) + cmd += "jk2sp"; +#elif defined(__APPLE__) + cmd += "jk2sp.app"; +#else +#error "WTF are you compiling on" +#endif + } + } + // TTimo + // JA HACK + else if (g_pGameDescription->mGameFile == "ja.game") + { + if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp")) + { + // MP +#if defined(WIN32) + cmd += "jamp.exe"; +#elif !defined(__linux__) && !defined(__APPLE__) +#error "WTF are you compiling on" +#endif + } + else + { + // SP +#if defined(WIN32) + cmd += "jasp.exe"; +#elif !defined(__linux__) && !defined(__APPLE__) +#error "WTF are you compiling on" +#endif + } + } + // RIANT + // STVEF HACK + else if (g_pGameDescription->mGameFile == "stvef.game") + { + if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp")) + { + // MP +#if defined(WIN32) + cmd += "stvoyHM.exe"; +#elif defined(__linux__) + cmd += "stvoyHM"; +#elif defined(__APPLE__) + cmd += "stvoyHM.app"; +#else +#error "WTF are you compiling on" +#endif + } + else + { + // SP +#if defined(WIN32) + cmd += "stvoy.exe"; +#elif defined(__linux__) + cmd += "stvoy"; +#elif defined(__APPLE__) + cmd += "stvoy.app"; +#else +#error "WTF are you compiling on" +#endif + } + } + // RIANT + // SOF2 HACK + else if (g_pGameDescription->mGameFile == "sof2.game") + { + if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp")) + { + // MP +#if defined(WIN32) + cmd += "sof2MP.exe"; +#elif defined(__linux__) + cmd += "b00gus"; +#elif defined(__APPLE__) + cmd += "sof2MP.app"; +#else +#error "WTF are you compiling on" +#endif + } + else + { + // SP +#if defined(WIN32) + cmd += "sof2.exe"; +#elif defined(__linux__) + cmd += "b00gus"; +#elif defined(__APPLE__) + cmd += "sof2.app"; +#else +#error "WTF are you compiling on" +#endif + } + } + else + { + cmd += g_pGameDescription->mEngine.GetBuffer(); + } +#ifdef _WIN32 + // NOTE: we are using unix pathnames and CreateProcess doesn't like / in the program path + FindReplace( cmd, "/", "\\" ); +#endif + Str cmdline; + if ( (g_pGameDescription->mGameFile == "q2.game") || (g_pGameDescription->mGameFile == "heretic2.game") ) + { + cmdline = ". +exec radiant.cfg +map "; + cmdline += m_sBSPName; + } + else + { + cmdline = "+set sv_pure 0 "; + // TTimo: a check for vm_* but that's all fine + //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 "; + if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0') + { + cmdline += "+set fs_game "; + cmdline += ValueForKey(g_qeglobals.d_project_entity, "gamename"); + cmdline += " "; + } + //!\todo Read the start-map args from a config file. + if (g_pGameDescription->mGameFile == "wolf.game") + { + if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp")) + { + // MP + cmdline += "+devmap "; + cmdline += m_sBSPName; + } + else + { + // SP + cmdline += "+set nextmap \"spdevmap "; + cmdline += m_sBSPName; + cmdline += "\""; + } + } + else + { + cmdline += "+devmap "; + cmdline += m_sBSPName; + } + } + + Sys_Printf("%s %s\n", cmd.GetBuffer(), cmdline.GetBuffer()); + + // execute now + if (!Q_Exec(cmd.GetBuffer(), (char *)cmdline.GetBuffer(), g_pGameDescription->mEnginePath.GetBuffer(), false)) + { + CString msg; + msg = "Failed to execute the following command: "; + msg += cmd; msg += cmdline; + Sys_Printf(msg); + gtk_MessageBox(g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR ); + } + } + Reset(); + } + } + } + break; + default: + break; + } +} + +void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, char *sBSPName ) +{ + if (m_sBSPName) + { + delete[] m_sBSPName; + } + m_sBSPName = sBSPName; + if (m_eState != EIdle) + { + Sys_Printf("WatchBSP got a monitoring request while not idling...\n"); + // prompt the user, should we cancel the current process and go ahead? + if (gtk_MessageBox(g_pParentWnd->m_pWidget, "I am already monitoring a BSP process.\nDo you want me to override and start a new compilation?", + "BSP process monitoring", MB_YESNO ) == IDYES) + { + // disconnect and set EIdle state + Reset(); + } + } + m_pCmd = pCmd; + m_iCurrentStep = 0; + DoEBeginStep(); +} + +void CWatchBSP::ExternalListen() +{ + m_bBSPPlugin = true; + DoEBeginStep (); +} + +// the part of the watchbsp interface we export to plugins +// NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level +// for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final) +void WINAPI QERApp_Listen() +{ + // open the listening socket + g_pParentWnd->GetWatchBSP()->ExternalListen(); +}