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");
311 m_pListenSocket = Net_ListenSocket(39000);
312 if (m_pListenSocket == NULL)
315 Sys_Printf("Listening...\n");
319 void CWatchBSP::DoEBeginStep() {
321 if ( !SetupListening() ) {
323 msg = "Failed to get a listening socket on port 39000.\nTry running with BSP monitoring disabled if you can't fix this.\n";
325 gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
328 // set the timer for timeouts and step cancellation
329 g_timer_reset( m_pTimer );
330 g_timer_start( m_pTimer );
332 if ( !m_bBSPPlugin ) {
333 Sys_Printf( "=== running BSP command ===\n%s\n", g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
335 if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true ) ) {
337 msg = "Failed to execute the following command: ";
338 msg += (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
339 msg += "\nCheck that the file exists and that you don't run out of system resources.\n";
341 gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
344 // re-initialise the debug window
345 if ( m_iCurrentStep == 0 ) {
349 m_eState = EBeginStep;
352 void CWatchBSP::RoutineProcessing()
356 TIMEVAL tout = { 0, 0 };
358 #if defined (__linux__) || defined (__APPLE__)
367 // timeout: if we don't get an incoming connection fast enough, go back to idle
368 if ( g_timer_elapsed( m_pTimer, NULL ) > g_PrefsDlg.m_iTimeout )
370 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 );
374 // status == 1 : didn't get the connection
375 g_BSPFrontendTable.m_pfnEndListen(1);
381 if (!m_pListenSocket)
383 Sys_Printf("ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n");
387 // we are not connected yet, accept any incoming connection
388 m_pInSocket = Net_Accept(m_pListenSocket);
391 Sys_Printf("Connected.\n");
392 // prepare the message info struct for diving in
393 memset (&m_message_info, 0, sizeof(message_info_s));
394 // a dumb flag to make sure we init the push parser context when first getting a msg
395 m_bNeedCtxtInit = true;
396 m_eState = EWatching;
404 Sys_Printf("ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n");
408 // select() will identify if the socket needs an update
409 // if the socket is identified that means there's either a message or the connection has been closed/reset/terminated
413 FD_SET(((unsigned int)m_pInSocket->socket), &readfds);
414 // from select man page:
415 // n is the highest-numbered descriptor in any of the three sets, plus 1
416 // (no use on windows)
417 ret = select( m_pInSocket->socket + 1, &readfds, NULL, NULL, &tout );
418 if (ret == SOCKET_ERROR)
420 Sys_Printf("WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n");
421 Sys_Printf("Terminating the connection.\n");
428 // we are non-blocking?? we should never get timeout errors
429 Sys_Printf("WARNING: unexpected timeout expired in CWatchBSP::Processing\n");
430 Sys_Printf("Terminating the connection.\n");
437 // the socket has been identified, there's something (message or disconnection)
438 // see if there's anything in input
439 ret = Net_Receive( m_pInSocket, &msg );
442 // unsigned int size = msg.size; //++timo just a check
443 strcpy (m_xmlBuf, NMSG_ReadString (&msg));
446 m_xmlParserCtxt = NULL;
447 m_xmlParserCtxt = xmlCreatePushParserCtxt (&saxParser, &m_message_info, m_xmlBuf, strlen(m_xmlBuf), NULL);
448 if (m_xmlParserCtxt == NULL)
450 Sys_FPrintf (SYS_ERR, "Failed to create the XML parser (incoming stream began with: %s)\n", m_xmlBuf);
453 m_bNeedCtxtInit = false;
457 xmlParseChunk (m_xmlParserCtxt, m_xmlBuf, strlen(m_xmlBuf), 0);
462 // error or connection closed/reset
463 // NOTE: if we get an error down the XML stream we don't reach here
464 Net_Disconnect( m_pInSocket );
466 Sys_Printf("Connection closed.\n");
470 // let the BSP plugin know that the job is done
471 g_BSPFrontendTable.m_pfnEndListen(0);
474 // move to next step or finish
476 if (m_iCurrentStep < m_pCmd->len )
482 // release the GPtrArray and the strings
485 for (m_iCurrentStep=0; m_iCurrentStep < m_pCmd->len; m_iCurrentStep++ )
487 delete[] (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
489 g_ptr_array_free( m_pCmd, false );
492 // launch the engine .. OMG
493 if (g_PrefsDlg.m_bRunQuake)
495 // do we enter sleep mode before?
496 if (g_PrefsDlg.m_bDoSleep)
498 Sys_Printf("Going into sleep mode..\n");
499 g_pParentWnd->OnSleep();
501 Sys_Printf("Running engine...\n");
503 // build the command line
504 cmd = g_pGameDescription->mEnginePath.GetBuffer();
505 // this is game dependant
506 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
509 cmd += g_pGameDescription->mMultiplayerEngine.GetBuffer();
514 cmd += g_pGameDescription->mEngine.GetBuffer();
517 // NOTE: we are using unix pathnames and CreateProcess doesn't like / in the program path
518 // FIXME: This isn't true anymore, doesn't it?
519 FindReplace( cmd, "/", "\\" );
522 if ( g_pGameDescription->quake2 )
524 cmdline = ". +exec radiant.cfg +map ";
525 cmdline += m_sBSPName;
529 cmdline = "+set sv_pure 0 ";
530 // TTimo: a check for vm_* but that's all fine
531 //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
532 if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0')
534 cmdline += "+set fs_game ";
535 cmdline += ValueForKey(g_qeglobals.d_project_entity, "gamename");
538 //!\todo Read the start-map args from a config file.
539 if (g_pGameDescription->mGameFile == "wolf.game")
541 if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"),"mp"))
544 cmdline += "+devmap ";
545 cmdline += m_sBSPName;
550 cmdline += "+set nextmap \"spdevmap ";
551 cmdline += m_sBSPName;
557 cmdline += "+devmap ";
558 cmdline += m_sBSPName;
562 Sys_Printf("%s %s\n", cmd.GetBuffer(), cmdline.GetBuffer());
565 if (!Q_Exec(cmd.GetBuffer(), (char *)cmdline.GetBuffer(), g_pGameDescription->mEnginePath.GetBuffer(), false))
568 msg = "Failed to execute the following command: ";
569 msg += cmd; msg += cmdline;
571 gtk_MessageBox(g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
584 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, char *sBSPName )
590 m_sBSPName = sBSPName;
591 if (m_eState != EIdle)
593 Sys_Printf("WatchBSP got a monitoring request while not idling...\n");
594 // prompt the user, should we cancel the current process and go ahead?
595 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?",
596 "BSP process monitoring", MB_YESNO ) == IDYES)
598 // disconnect and set EIdle state
607 void CWatchBSP::ExternalListen()
613 // the part of the watchbsp interface we export to plugins
614 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
615 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
616 void WINAPI QERApp_Listen()
618 // open the listening socket
619 g_pParentWnd->GetWatchBSP()->ExternalListen();