2 Copyright (c) 2001, Loki software, inc.
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
8 Redistributions of source code must retain the above copyright notice, this list
9 of conditions and the following disclaimer.
11 Redistributions in binary form must reproduce the above copyright notice, this
12 list of conditions and the following disclaimer in the documentation and/or
13 other materials provided with the distribution.
15 Neither the name of Loki software nor the names of its contributors may be used
16 to endorse or promote products derived from this software without specific prior
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
23 DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 //-----------------------------------------------------------------------------
34 // monitoring window for running BSP processes (and possibly various other stuff)
44 #if defined (__linux__) || defined (__APPLE__)
46 #define SOCKET_ERROR -1
55 // Static functions for the SAX callbacks -------------------------------------------------------
57 // utility for saxStartElement below
58 static void abortStream(message_info_t *data)
60 g_pParentWnd->GetWatchBSP()->Reset();
61 // tell there has been an error
62 if (g_pParentWnd->GetWatchBSP()->HasBSPPlugin ())
63 g_BSPFrontendTable.m_pfnEndListen(2);
64 // yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out
65 data->ignore_depth = -1;
69 #include "stream_version.h"
71 static void saxStartElement(message_info_t *data, const xmlChar *name, const xmlChar **attrs)
73 if (data->ignore_depth == 0)
78 data->pGeometry->saxStartElement (data, name, attrs);
82 if (strcmp ((char *)name, "q3map_feedback") == 0)
84 // check the correct version
85 // old q3map don't send a version attribute
86 // the ones we support .. send Q3MAP_STREAM_VERSION
87 if (!attrs[0] || !attrs[1] || (strcmp((char*)attrs[0],"version") != 0))
89 Sys_FPrintf(SYS_ERR, "No stream version given in the feedback stream, this is an old q3map version.\n"
90 "Please turn off monitored compiling if you still wish to use this q3map executable\n");
94 else if (strcmp((char*)attrs[1],Q3MAP_STREAM_VERSION) != 0)
97 "This version of Radiant reads version %s debug streams, I got an incoming connection with version %s\n"
98 "Please make sure your versions of Radiant and q3map are matching.\n", Q3MAP_STREAM_VERSION, (char*)attrs[1]);
103 // we don't treat locally
104 else if (strcmp ((char *)name, "message") == 0)
106 data->msg_level = atoi ((char *)attrs[1]);
108 else if (strcmp ((char *)name, "polyline") == 0)
109 // polyline has a particular status .. right now we only use it for leakfile ..
111 data->bGeometry = true;
112 data->pGeometry = &g_pointfile;
113 data->pGeometry->saxStartElement( data, name, attrs );
115 else if (strcmp ((char *)name, "select") == 0)
117 CSelectMsg *pSelect = new CSelectMsg();
118 data->bGeometry = true;
119 data->pGeometry = pSelect;
120 data->pGeometry->saxStartElement( data, name, attrs );
122 else if (strcmp ((char *)name, "pointmsg") == 0)
124 CPointMsg *pPoint = new CPointMsg();
125 data->bGeometry = true;
126 data->pGeometry = pPoint;
127 data->pGeometry->saxStartElement( data, name, attrs );
129 else if (strcmp ((char *)name, "windingmsg") == 0)
131 CWindingMsg *pWinding = new CWindingMsg();
132 data->bGeometry = true;
133 data->pGeometry = pWinding;
134 data->pGeometry->saxStartElement( data, name, attrs );
138 Sys_FPrintf (SYS_WRN, "WARNING: ignoring unrecognized node in XML stream (%s)\n", name);
139 // we don't recognize this node, jump over it
140 // (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level)
141 data->ignore_depth = data->recurse;
148 static void saxEndElement(message_info_t *data, const xmlChar *name) {
150 // we are out of an ignored chunk
151 if ( data->recurse == data->ignore_depth ) {
152 data->ignore_depth = 0;
155 if ( data->bGeometry ) {
156 data->pGeometry->saxEndElement( data, name );
157 // we add the object to the debug window
158 if ( !data->bGeometry ) {
159 g_DbgDlg.Push( data->pGeometry );
162 if ( data->recurse == data->stop_depth ) {
164 Sys_Printf ("Received error msg .. shutting down..\n");
166 g_pParentWnd->GetWatchBSP()->Reset();
167 // tell there has been an error
168 if ( g_pParentWnd->GetWatchBSP()->HasBSPPlugin() ) {
169 g_BSPFrontendTable.m_pfnEndListen( 2 );
175 static void saxCharacters(message_info_t *data, const xmlChar *ch, int len)
179 data->pGeometry->saxCharacters (data, ch, len);
183 if (data->ignore_depth != 0)
185 // output the message using the level
187 memcpy( buf, ch, len );
189 Sys_FPrintf (data->msg_level, "%s", buf);
190 // if this message has error level flag, we mark the depth to stop the compilation when we get out
191 // we don't set the msg level if we don't stop on leak
192 if (data->msg_level == 3)
194 data->stop_depth = data->recurse-1;
199 static void saxComment(void *ctx, const xmlChar *msg)
201 Sys_Printf("XML comment: %s\n", msg);
204 static void saxWarning(void *ctx, const char *msg, ...)
206 char saxMsgBuffer[4096];
210 vsprintf (saxMsgBuffer, msg, args);
212 Sys_FPrintf(SYS_WRN, "XML warning: %s\n", saxMsgBuffer);
215 static void saxError(void *ctx, const char *msg, ...)
217 char saxMsgBuffer[4096];
221 vsprintf (saxMsgBuffer, msg, args);
223 Sys_FPrintf(SYS_ERR, "XML error: %s\n", saxMsgBuffer);
226 static void saxFatal(void *ctx, const char *msg, ...)
233 vsprintf (buffer, msg, args);
235 Sys_FPrintf(SYS_ERR, "XML fatal error: %s\n", buffer);
238 static xmlSAXHandler saxParser = {
239 0, /* internalSubset */
240 0, /* isStandalone */
241 0, /* hasInternalSubset */
242 0, /* hasExternalSubset */
243 0, /* resolveEntity */
246 0, /* notationDecl */
247 0, /* attributeDecl */
249 0, /* unparsedEntityDecl */
250 0, /* setDocumentLocator */
251 0, /* startDocument */
253 (startElementSAXFunc)saxStartElement, /* startElement */
254 (endElementSAXFunc)saxEndElement, /* endElement */
256 (charactersSAXFunc)saxCharacters, /* characters */
257 0, /* ignorableWhitespace */
258 0, /* processingInstruction */
259 (commentSAXFunc)saxComment, /* comment */
260 (warningSAXFunc)saxWarning, /* warning */
261 (errorSAXFunc)saxError, /* error */
262 (fatalErrorSAXFunc)saxFatal, /* fatalError */
265 // ------------------------------------------------------------------------------------------------
267 CWatchBSP::~CWatchBSP()
278 void CWatchBSP::Reset()
282 Net_Disconnect(m_pInSocket);
287 Net_Disconnect(m_pListenSocket);
288 m_pListenSocket = NULL;
290 if (m_xmlInputBuffer)
292 xmlFreeParserInputBuffer (m_xmlInputBuffer);
293 m_xmlInputBuffer = NULL;
298 bool CWatchBSP::SetupListening()
303 Sys_Printf("ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n");
307 Sys_Printf("Setting up\n");
309 m_pListenSocket = Net_ListenSocket(39000);
310 if (m_pListenSocket == NULL)
312 Sys_Printf("Listening...\n");
316 void CWatchBSP::DoEBeginStep() {
318 if ( !SetupListening() ) {
320 msg = "Failed to get a listening socket on port 39000.\nTry running with BSP monitoring disabled if you can't fix this.\n";
322 gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
325 // set the timer for timeouts and step cancellation
326 g_timer_reset( m_pTimer );
327 g_timer_start( m_pTimer );
329 if ( !m_bBSPPlugin ) {
330 Sys_Printf( "=== running BSP command ===\n%s\n", g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
332 if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true ) ) {
334 msg = "Failed to execute the following command: ";
335 msg += (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
336 msg += "\nCheck that the file exists and that you don't run out of system resources.\n";
338 gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
341 // re-initialise the debug window
342 if ( m_iCurrentStep == 0 ) {
346 m_eState = EBeginStep;
349 void CWatchBSP::RoutineProcessing()
353 TIMEVAL tout = { 0, 0 };
355 #if defined (__linux__) || defined (__APPLE__)
364 // timeout: if we don't get an incoming connection fast enough, go back to idle
365 if ( g_timer_elapsed( m_pTimer, NULL ) > g_PrefsDlg.m_iTimeout )
367 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 );
371 // status == 1 : didn't get the connection
372 g_BSPFrontendTable.m_pfnEndListen(1);
378 if (!m_pListenSocket)
380 Sys_Printf("ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n");
384 // we are not connected yet, accept any incoming connection
385 m_pInSocket = Net_Accept(m_pListenSocket);
388 Sys_Printf("Connected.\n");
389 // prepare the message info struct for diving in
390 memset (&m_message_info, 0, sizeof(message_info_s));
391 // a dumb flag to make sure we init the push parser context when first getting a msg
392 m_bNeedCtxtInit = true;
393 m_eState = EWatching;
401 Sys_Printf("ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n");
405 // select() will identify if the socket needs an update
406 // if the socket is identified that means there's either a message or the connection has been closed/reset/terminated
410 FD_SET(((unsigned int)m_pInSocket->socket), &readfds);
411 // from select man page:
412 // n is the highest-numbered descriptor in any of the three sets, plus 1
413 // (no use on windows)
414 ret = select( m_pInSocket->socket + 1, &readfds, NULL, NULL, &tout );
415 if (ret == SOCKET_ERROR)
417 Sys_Printf("WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n");
418 Sys_Printf("Terminating the connection.\n");
425 // we are non-blocking?? we should never get timeout errors
426 Sys_Printf("WARNING: unexpected timeout expired in CWatchBSP::Processing\n");
427 Sys_Printf("Terminating the connection.\n");
434 // the socket has been identified, there's something (message or disconnection)
435 // see if there's anything in input
436 ret = Net_Receive( m_pInSocket, &msg );
439 // unsigned int size = msg.size; //++timo just a check
440 strcpy (m_xmlBuf, NMSG_ReadString (&msg));
443 m_xmlParserCtxt = NULL;
444 m_xmlParserCtxt = xmlCreatePushParserCtxt (&saxParser, &m_message_info, m_xmlBuf, strlen(m_xmlBuf), NULL);
445 if (m_xmlParserCtxt == NULL)
447 Sys_FPrintf (SYS_ERR, "Failed to create the XML parser (incoming stream began with: %s)\n", m_xmlBuf);
450 m_bNeedCtxtInit = false;
454 xmlParseChunk (m_xmlParserCtxt, m_xmlBuf, strlen(m_xmlBuf), 0);
459 // error or connection closed/reset
460 // NOTE: if we get an error down the XML stream we don't reach here
461 Net_Disconnect( m_pInSocket );
463 Sys_Printf("Connection closed.\n");
467 // let the BSP plugin know that the job is done
468 g_BSPFrontendTable.m_pfnEndListen(0);
471 // move to next step or finish
473 if (m_iCurrentStep < m_pCmd->len )
479 // release the GPtrArray and the strings
482 for (m_iCurrentStep=0; m_iCurrentStep < m_pCmd->len; m_iCurrentStep++ )
484 delete[] (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
486 g_ptr_array_free( m_pCmd, false );
489 // launch the engine .. OMG
490 if (g_PrefsDlg.m_bRunQuake)
492 // do we enter sleep mode before?
493 if (g_PrefsDlg.m_bDoSleep)
495 Sys_Printf("Going into sleep mode..\n");
496 g_pParentWnd->OnSleep();
498 Sys_Printf("Running engine...\n");
500 // build the command line
501 cmd = g_pGameDescription->mEnginePath.GetBuffer();
502 // this is game dependant
503 //!\todo Read the engine binary name from a config file.
504 if (g_pGameDescription->mGameFile == "wolf.game")
506 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
511 #elif defined(__linux__)
513 #elif defined(__APPLE__)
516 #error "WTF are you compiling on"
524 #elif defined(__linux__)
526 #elif defined(__APPLE__)
529 #error "WTF are you compiling on"
532 } else if (g_pGameDescription->mGameFile == "et.game")
536 #elif defined(__linux__)
538 #elif defined(__APPLE__)
541 #error "WTF are you compiling on"
546 else if (g_pGameDescription->mGameFile == "jk2.game")
548 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
553 #elif defined(__linux__)
555 #elif defined(__APPLE__)
558 #error "WTF are you compiling on"
566 #elif defined(__linux__)
568 #elif defined(__APPLE__)
571 #error "WTF are you compiling on"
577 else if (g_pGameDescription->mGameFile == "ja.game")
579 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
584 #elif !defined(__linux__) && !defined(__APPLE__)
585 #error "WTF are you compiling on"
593 #elif !defined(__linux__) && !defined(__APPLE__)
594 #error "WTF are you compiling on"
600 else if (g_pGameDescription->mGameFile == "stvef.game")
602 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
606 cmd += "stvoyHM.exe";
607 #elif defined(__linux__)
609 #elif defined(__APPLE__)
610 cmd += "stvoyHM.app";
612 #error "WTF are you compiling on"
620 #elif defined(__linux__)
622 #elif defined(__APPLE__)
625 #error "WTF are you compiling on"
631 else if (g_pGameDescription->mGameFile == "sof2.game")
633 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
638 #elif defined(__linux__)
640 #elif defined(__APPLE__)
643 #error "WTF are you compiling on"
651 #elif defined(__linux__)
653 #elif defined(__APPLE__)
656 #error "WTF are you compiling on"
662 cmd += g_pGameDescription->mEngine.GetBuffer();
665 // NOTE: we are using unix pathnames and CreateProcess doesn't like / in the program path
666 FindReplace( cmd, "/", "\\" );
669 if ( (g_pGameDescription->mGameFile == "q2.game") || (g_pGameDescription->mGameFile == "heretic2.game") )
671 cmdline = ". +exec radiant.cfg +map ";
672 cmdline += m_sBSPName;
676 cmdline = "+set sv_pure 0 ";
677 // TTimo: a check for vm_* but that's all fine
678 //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
679 if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0')
681 cmdline += "+set fs_game ";
682 cmdline += ValueForKey(g_qeglobals.d_project_entity, "gamename");
685 //!\todo Read the start-map args from a config file.
686 if (g_pGameDescription->mGameFile == "wolf.game")
688 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
691 cmdline += "+devmap ";
692 cmdline += m_sBSPName;
697 cmdline += "+set nextmap \"spdevmap ";
698 cmdline += m_sBSPName;
704 cmdline += "+devmap ";
705 cmdline += m_sBSPName;
709 Sys_Printf("%s %s\n", cmd.GetBuffer(), cmdline.GetBuffer());
712 if (!Q_Exec(cmd.GetBuffer(), (char *)cmdline.GetBuffer(), g_pGameDescription->mEnginePath.GetBuffer(), false))
715 msg = "Failed to execute the following command: ";
716 msg += cmd; msg += cmdline;
718 gtk_MessageBox(g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
731 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, char *sBSPName )
737 m_sBSPName = sBSPName;
738 if (m_eState != EIdle)
740 Sys_Printf("WatchBSP got a monitoring request while not idling...\n");
741 // prompt the user, should we cancel the current process and go ahead?
742 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?",
743 "BSP process monitoring", MB_YESNO ) == IDYES)
745 // disconnect and set EIdle state
754 void CWatchBSP::ExternalListen()
760 // the part of the watchbsp interface we export to plugins
761 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
762 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
763 void WINAPI QERApp_Listen()
765 // open the listening socket
766 g_pParentWnd->GetWatchBSP()->ExternalListen();