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)
151 // we are out of an ignored chunk
152 if (data->recurse == data->ignore_depth)
154 data->ignore_depth = 0;
159 data->pGeometry->saxEndElement (data, name);
160 // we add the object to the debug window
161 if (!data->bGeometry)
163 g_DbgDlg.Push (data->pGeometry);
166 if (data->recurse == data->stop_depth)
169 Sys_Printf ("Received error msg .. shutting down..\n");
171 g_pParentWnd->GetWatchBSP()->Reset();
172 // tell there has been an error
173 if (g_pParentWnd->GetWatchBSP()->HasBSPPlugin ())
174 g_BSPFrontendTable.m_pfnEndListen(2);
179 static void saxCharacters(message_info_t *data, const xmlChar *ch, int len)
183 data->pGeometry->saxCharacters (data, ch, len);
187 if (data->ignore_depth != 0)
189 // output the message using the level
191 memcpy( buf, ch, len );
193 Sys_FPrintf (data->msg_level, "%s", buf);
194 // if this message has error level flag, we mark the depth to stop the compilation when we get out
195 // we don't set the msg level if we don't stop on leak
196 if (data->msg_level == 3)
198 data->stop_depth = data->recurse-1;
203 static void saxComment(void *ctx, const xmlChar *msg)
205 Sys_Printf("XML comment: %s\n", msg);
208 static void saxWarning(void *ctx, const char *msg, ...)
210 char saxMsgBuffer[4096];
214 vsprintf (saxMsgBuffer, msg, args);
216 Sys_FPrintf(SYS_WRN, "XML warning: %s\n", saxMsgBuffer);
219 static void saxError(void *ctx, const char *msg, ...)
221 char saxMsgBuffer[4096];
225 vsprintf (saxMsgBuffer, msg, args);
227 Sys_FPrintf(SYS_ERR, "XML error: %s\n", saxMsgBuffer);
230 static void saxFatal(void *ctx, const char *msg, ...)
237 vsprintf (buffer, msg, args);
239 Sys_FPrintf(SYS_ERR, "XML fatal error: %s\n", buffer);
242 static xmlSAXHandler saxParser = {
243 0, /* internalSubset */
244 0, /* isStandalone */
245 0, /* hasInternalSubset */
246 0, /* hasExternalSubset */
247 0, /* resolveEntity */
250 0, /* notationDecl */
251 0, /* attributeDecl */
253 0, /* unparsedEntityDecl */
254 0, /* setDocumentLocator */
255 0, /* startDocument */
257 (startElementSAXFunc)saxStartElement, /* startElement */
258 (endElementSAXFunc)saxEndElement, /* endElement */
260 (charactersSAXFunc)saxCharacters, /* characters */
261 0, /* ignorableWhitespace */
262 0, /* processingInstruction */
263 (commentSAXFunc)saxComment, /* comment */
264 (warningSAXFunc)saxWarning, /* warning */
265 (errorSAXFunc)saxError, /* error */
266 (fatalErrorSAXFunc)saxFatal, /* fatalError */
269 // ------------------------------------------------------------------------------------------------
271 CWatchBSP::~CWatchBSP()
282 void CWatchBSP::Reset()
286 Net_Disconnect(m_pInSocket);
291 Net_Disconnect(m_pListenSocket);
292 m_pListenSocket = NULL;
294 if (m_xmlInputBuffer)
296 xmlFreeParserInputBuffer (m_xmlInputBuffer);
297 m_xmlInputBuffer = NULL;
302 bool CWatchBSP::SetupListening()
307 Sys_Printf("ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n");
311 Sys_Printf("Setting up\n");
313 m_pListenSocket = Net_ListenSocket(39000);
314 if (m_pListenSocket == NULL)
316 Sys_Printf("Listening...\n");
320 void CWatchBSP::DoEBeginStep()
323 if (SetupListening() == false)
326 msg = "Failed to get a listening socket on port 39000.\nTry running with BSP monitoring disabled if you can't fix this.\n";
328 gtk_MessageBox (g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR);
331 // set the timer for timeouts and step cancellation
332 g_timer_reset( m_pTimer );
333 g_timer_start( m_pTimer );
337 Sys_Printf("=== running BSP command ===\n%s\n", g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
339 if (!Q_Exec(NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true ))
342 msg = "Failed to execute the following command: ";
343 msg += (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
344 msg += "\nCheck that the file exists and that you don't run out of system resources.\n";
346 gtk_MessageBox(g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
349 // re-initialise the debug window
350 if (m_iCurrentStep == 0)
353 m_eState = EBeginStep;
356 void CWatchBSP::RoutineProcessing()
360 TIMEVAL tout = { 0, 0 };
362 #if defined (__linux__) || defined (__APPLE__)
371 // timeout: if we don't get an incoming connection fast enough, go back to idle
372 if ( g_timer_elapsed( m_pTimer, NULL ) > g_PrefsDlg.m_iTimeout )
374 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 );
378 // status == 1 : didn't get the connection
379 g_BSPFrontendTable.m_pfnEndListen(1);
385 if (!m_pListenSocket)
387 Sys_Printf("ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n");
391 // we are not connected yet, accept any incoming connection
392 m_pInSocket = Net_Accept(m_pListenSocket);
395 Sys_Printf("Connected.\n");
396 // prepare the message info struct for diving in
397 memset (&m_message_info, 0, sizeof(message_info_s));
398 // a dumb flag to make sure we init the push parser context when first getting a msg
399 m_bNeedCtxtInit = true;
400 m_eState = EWatching;
408 Sys_Printf("ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n");
412 // select() will identify if the socket needs an update
413 // if the socket is identified that means there's either a message or the connection has been closed/reset/terminated
417 FD_SET(((unsigned int)m_pInSocket->socket), &readfds);
418 // from select man page:
419 // n is the highest-numbered descriptor in any of the three sets, plus 1
420 // (no use on windows)
421 ret = select( m_pInSocket->socket + 1, &readfds, NULL, NULL, &tout );
422 if (ret == SOCKET_ERROR)
424 Sys_Printf("WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n");
425 Sys_Printf("Terminating the connection.\n");
432 // we are non-blocking?? we should never get timeout errors
433 Sys_Printf("WARNING: unexpected timeout expired in CWatchBSP::Processing\n");
434 Sys_Printf("Terminating the connection.\n");
441 // the socket has been identified, there's something (message or disconnection)
442 // see if there's anything in input
443 ret = Net_Receive( m_pInSocket, &msg );
446 // unsigned int size = msg.size; //++timo just a check
447 strcpy (m_xmlBuf, NMSG_ReadString (&msg));
450 m_xmlParserCtxt = NULL;
451 m_xmlParserCtxt = xmlCreatePushParserCtxt (&saxParser, &m_message_info, m_xmlBuf, strlen(m_xmlBuf), NULL);
452 if (m_xmlParserCtxt == NULL)
454 Sys_FPrintf (SYS_ERR, "Failed to create the XML parser (incoming stream began with: %s)\n", m_xmlBuf);
457 m_bNeedCtxtInit = false;
461 xmlParseChunk (m_xmlParserCtxt, m_xmlBuf, strlen(m_xmlBuf), 0);
466 // error or connection closed/reset
467 // NOTE: if we get an error down the XML stream we don't reach here
468 Net_Disconnect( m_pInSocket );
470 Sys_Printf("Connection closed.\n");
474 // let the BSP plugin know that the job is done
475 g_BSPFrontendTable.m_pfnEndListen(0);
478 // move to next step or finish
480 if (m_iCurrentStep < m_pCmd->len )
486 // release the GPtrArray and the strings
489 for (m_iCurrentStep=0; m_iCurrentStep < m_pCmd->len; m_iCurrentStep++ )
491 delete[] (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
493 g_ptr_array_free( m_pCmd, false );
496 // launch the engine .. OMG
497 if (g_PrefsDlg.m_bRunQuake)
499 // do we enter sleep mode before?
500 if (g_PrefsDlg.m_bDoSleep)
502 Sys_Printf("Going into sleep mode..\n");
503 g_pParentWnd->OnSleep();
505 Sys_Printf("Running engine...\n");
507 // build the command line
508 cmd = g_pGameDescription->mEnginePath.GetBuffer();
509 // this is game dependant
510 //!\todo Read the engine binary name from a config file.
511 if (g_pGameDescription->mGameFile == "wolf.game")
513 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
518 #elif defined(__linux__)
520 #elif defined(__APPLE__)
523 #error "WTF are you compiling on"
531 #elif defined(__linux__)
533 #elif defined(__APPLE__)
536 #error "WTF are you compiling on"
539 } else if (g_pGameDescription->mGameFile == "et.game")
543 #elif defined(__linux__)
545 #elif defined(__APPLE__)
548 #error "WTF are you compiling on"
553 else if (g_pGameDescription->mGameFile == "jk2.game")
555 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
560 #elif defined(__linux__)
562 #elif defined(__APPLE__)
565 #error "WTF are you compiling on"
573 #elif defined(__linux__)
575 #elif defined(__APPLE__)
578 #error "WTF are you compiling on"
584 else if (g_pGameDescription->mGameFile == "ja.game")
586 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
591 #elif !defined(__linux__) && !defined(__APPLE__)
592 #error "WTF are you compiling on"
600 #elif !defined(__linux__) && !defined(__APPLE__)
601 #error "WTF are you compiling on"
607 else if (g_pGameDescription->mGameFile == "stvef.game")
609 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
613 cmd += "stvoyHM.exe";
614 #elif defined(__linux__)
616 #elif defined(__APPLE__)
617 cmd += "stvoyHM.app";
619 #error "WTF are you compiling on"
627 #elif defined(__linux__)
629 #elif defined(__APPLE__)
632 #error "WTF are you compiling on"
638 else if (g_pGameDescription->mGameFile == "sof2.game")
640 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
645 #elif defined(__linux__)
647 #elif defined(__APPLE__)
650 #error "WTF are you compiling on"
658 #elif defined(__linux__)
660 #elif defined(__APPLE__)
663 #error "WTF are you compiling on"
669 cmd += g_pGameDescription->mEngine.GetBuffer();
672 // NOTE: we are using unix pathnames and CreateProcess doesn't like / in the program path
673 FindReplace( cmd, "/", "\\" );
676 if ( (g_pGameDescription->mGameFile == "q2.game") || (g_pGameDescription->mGameFile == "heretic2.game") )
678 cmdline = ". +exec radiant.cfg +map ";
679 cmdline += m_sBSPName;
683 cmdline = "+set sv_pure 0 ";
684 // TTimo: a check for vm_* but that's all fine
685 //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
686 if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0')
688 cmdline += "+set fs_game ";
689 cmdline += ValueForKey(g_qeglobals.d_project_entity, "gamename");
692 //!\todo Read the start-map args from a config file.
693 if (g_pGameDescription->mGameFile == "wolf.game")
695 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
698 cmdline += "+devmap ";
699 cmdline += m_sBSPName;
704 cmdline += "+set nextmap \"spdevmap ";
705 cmdline += m_sBSPName;
711 cmdline += "+devmap ";
712 cmdline += m_sBSPName;
716 Sys_Printf("%s %s\n", cmd.GetBuffer(), cmdline.GetBuffer());
719 if (!Q_Exec(cmd.GetBuffer(), (char *)cmdline.GetBuffer(), g_pGameDescription->mEnginePath.GetBuffer(), false))
722 msg = "Failed to execute the following command: ";
723 msg += cmd; msg += cmdline;
725 gtk_MessageBox(g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
738 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, char *sBSPName )
744 m_sBSPName = sBSPName;
745 if (m_eState != EIdle)
747 Sys_Printf("WatchBSP got a monitoring request while not idling...\n");
748 // prompt the user, should we cancel the current process and go ahead?
749 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?",
750 "BSP process monitoring", MB_YESNO ) == IDYES)
752 // disconnect and set EIdle state
761 void CWatchBSP::ExternalListen()
767 // the part of the watchbsp interface we export to plugins
768 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
769 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
770 void WINAPI QERApp_Listen()
772 // open the listening socket
773 g_pParentWnd->GetWatchBSP()->ExternalListen();