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 ){
59 g_pParentWnd->GetWatchBSP()->Reset();
60 // tell there has been an error
61 if ( g_pParentWnd->GetWatchBSP()->HasBSPPlugin() ) {
62 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 ){
72 if ( data->ignore_depth == 0 ) {
73 if ( data->bGeometry ) {
75 data->pGeometry->saxStartElement( data, name, attrs );
79 if ( strcmp( (char *)name, "q3map_feedback" ) == 0 ) {
80 // check the correct version
81 // old q3map don't send a version attribute
82 // the ones we support .. send Q3MAP_STREAM_VERSION
83 if ( !attrs[0] || !attrs[1] || ( strcmp( (char*)attrs[0],"version" ) != 0 ) ) {
84 Sys_FPrintf( SYS_ERR, "No stream version given in the feedback stream, this is an old q3map version.\n"
85 "Please turn off monitored compiling if you still wish to use this q3map executable\n" );
89 else if ( strcmp( (char*)attrs[1],Q3MAP_STREAM_VERSION ) != 0 ) {
91 "This version of Radiant reads version %s debug streams, I got an incoming connection with version %s\n"
92 "Please make sure your versions of Radiant and q3map are matching.\n", Q3MAP_STREAM_VERSION, (char*)attrs[1] );
97 // we don't treat locally
98 else if ( strcmp( (char *)name, "message" ) == 0 ) {
99 data->msg_level = atoi( (char *)attrs[1] );
101 else if ( strcmp( (char *)name, "polyline" ) == 0 ) {
102 // polyline has a particular status .. right now we only use it for leakfile ..
103 data->bGeometry = true;
104 data->pGeometry = &g_pointfile;
105 data->pGeometry->saxStartElement( data, name, attrs );
107 else if ( strcmp( (char *)name, "select" ) == 0 ) {
108 CSelectMsg *pSelect = new CSelectMsg();
109 data->bGeometry = true;
110 data->pGeometry = pSelect;
111 data->pGeometry->saxStartElement( data, name, attrs );
113 else if ( strcmp( (char *)name, "pointmsg" ) == 0 ) {
114 CPointMsg *pPoint = new CPointMsg();
115 data->bGeometry = true;
116 data->pGeometry = pPoint;
117 data->pGeometry->saxStartElement( data, name, attrs );
119 else if ( strcmp( (char *)name, "windingmsg" ) == 0 ) {
120 CWindingMsg *pWinding = new CWindingMsg();
121 data->bGeometry = true;
122 data->pGeometry = pWinding;
123 data->pGeometry->saxStartElement( data, name, attrs );
127 Sys_FPrintf( SYS_WRN, "WARNING: ignoring unrecognized node in XML stream (%s)\n", name );
128 // we don't recognize this node, jump over it
129 // (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level)
130 data->ignore_depth = data->recurse;
137 static void saxEndElement( message_info_t *data, const xmlChar *name ) {
139 // we are out of an ignored chunk
140 if ( data->recurse == data->ignore_depth ) {
141 data->ignore_depth = 0;
144 if ( data->bGeometry ) {
145 data->pGeometry->saxEndElement( data, name );
146 // we add the object to the debug window
147 if ( !data->bGeometry ) {
148 g_DbgDlg.Push( data->pGeometry );
151 if ( data->recurse == data->stop_depth ) {
153 Sys_Printf( "Received error msg .. shutting down..\n" );
155 g_pParentWnd->GetWatchBSP()->Reset();
156 // tell there has been an error
157 if ( g_pParentWnd->GetWatchBSP()->HasBSPPlugin() ) {
158 g_BSPFrontendTable.m_pfnEndListen( 2 );
164 static void saxCharacters( message_info_t *data, const xmlChar *ch, int len ){
165 if ( data->bGeometry ) {
166 data->pGeometry->saxCharacters( data, ch, len );
170 if ( data->ignore_depth != 0 ) {
173 // output the message using the level
175 memcpy( buf, ch, len );
177 Sys_FPrintf( data->msg_level, "%s", buf );
178 // if this message has error level flag, we mark the depth to stop the compilation when we get out
179 // we don't set the msg level if we don't stop on leak
180 if ( data->msg_level == 3 ) {
181 data->stop_depth = data->recurse - 1;
186 static void saxComment( void *ctx, const xmlChar *msg ){
187 Sys_Printf( "XML comment: %s\n", msg );
190 static void saxWarning( void *ctx, const char *msg, ... ){
191 char saxMsgBuffer[4096];
194 va_start( args, msg );
195 vsprintf( saxMsgBuffer, msg, args );
197 Sys_FPrintf( SYS_WRN, "XML warning: %s\n", saxMsgBuffer );
200 static void saxError( void *ctx, const char *msg, ... ){
201 char saxMsgBuffer[4096];
204 va_start( args, msg );
205 vsprintf( saxMsgBuffer, msg, args );
207 Sys_FPrintf( SYS_ERR, "XML error: %s\n", saxMsgBuffer );
210 static void saxFatal( void *ctx, const char *msg, ... ){
215 va_start( args, msg );
216 vsprintf( buffer, msg, args );
218 Sys_FPrintf( SYS_ERR, "XML fatal error: %s\n", buffer );
221 static xmlSAXHandler saxParser = {
222 0, /* internalSubset */
223 0, /* isStandalone */
224 0, /* hasInternalSubset */
225 0, /* hasExternalSubset */
226 0, /* resolveEntity */
229 0, /* notationDecl */
230 0, /* attributeDecl */
232 0, /* unparsedEntityDecl */
233 0, /* setDocumentLocator */
234 0, /* startDocument */
236 (startElementSAXFunc)saxStartElement, /* startElement */
237 (endElementSAXFunc)saxEndElement, /* endElement */
239 (charactersSAXFunc)saxCharacters, /* characters */
240 0, /* ignorableWhitespace */
241 0, /* processingInstruction */
242 (commentSAXFunc)saxComment, /* comment */
243 (warningSAXFunc)saxWarning, /* warning */
244 (errorSAXFunc)saxError, /* error */
245 (fatalErrorSAXFunc)saxFatal, /* fatalError */
248 // ------------------------------------------------------------------------------------------------
250 CWatchBSP::~CWatchBSP(){
259 void CWatchBSP::Reset(){
261 Net_Disconnect( m_pInSocket );
264 if ( m_pListenSocket ) {
265 Net_Disconnect( m_pListenSocket );
266 m_pListenSocket = NULL;
268 if ( m_xmlInputBuffer ) {
269 xmlFreeParserInputBuffer( m_xmlInputBuffer );
270 m_xmlInputBuffer = NULL;
275 bool CWatchBSP::SetupListening(){
277 if ( m_pListenSocket ) {
278 Sys_Printf( "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n" );
282 Sys_Printf( "Setting up\n" );
283 if ( !Net_Setup() ) {
287 m_pListenSocket = Net_ListenSocket( 39000 );
288 if ( m_pListenSocket == NULL ) {
292 Sys_Printf( "Listening...\n" );
296 void CWatchBSP::DoEBeginStep() {
298 if ( !SetupListening() ) {
300 msg = "Failed to get a listening socket on port 39000.\nTry running with BSP monitoring disabled if you can't fix this.\n";
302 gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
305 // set the timer for timeouts and step cancellation
306 g_timer_reset( m_pTimer );
307 g_timer_start( m_pTimer );
309 if ( !m_bBSPPlugin ) {
310 Sys_Printf( "=== running BSP command ===\n%s\n", g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
312 if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true ) ) {
314 msg = "Failed to execute the following command: ";
315 msg += (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
316 msg += "\nCheck that the file exists and that you don't run out of system resources.\n";
318 gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
321 // re-initialise the debug window
322 if ( m_iCurrentStep == 0 ) {
326 m_eState = EBeginStep;
329 void CWatchBSP::RoutineProcessing(){
332 TIMEVAL tout = { 0, 0 };
334 #if defined ( __linux__ ) || defined ( __APPLE__ )
343 // timeout: if we don't get an incoming connection fast enough, go back to idle
344 if ( g_timer_elapsed( m_pTimer, NULL ) > g_PrefsDlg.m_iTimeout ) {
345 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 );
347 if ( m_bBSPPlugin ) {
348 // status == 1 : didn't get the connection
349 g_BSPFrontendTable.m_pfnEndListen( 1 );
355 if ( !m_pListenSocket ) {
356 Sys_Printf( "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n" );
360 // we are not connected yet, accept any incoming connection
361 m_pInSocket = Net_Accept( m_pListenSocket );
363 Sys_Printf( "Connected.\n" );
364 // prepare the message info struct for diving in
365 memset( &m_message_info, 0, sizeof( message_info_s ) );
366 // a dumb flag to make sure we init the push parser context when first getting a msg
367 m_bNeedCtxtInit = true;
368 m_eState = EWatching;
374 if ( !m_pInSocket ) {
375 Sys_Printf( "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n" );
379 // select() will identify if the socket needs an update
380 // if the socket is identified that means there's either a message or the connection has been closed/reset/terminated
384 FD_SET( ( (unsigned int)m_pInSocket->socket ), &readfds );
385 // from select man page:
386 // n is the highest-numbered descriptor in any of the three sets, plus 1
387 // (no use on windows)
388 ret = select( m_pInSocket->socket + 1, &readfds, NULL, NULL, &tout );
389 if ( ret == SOCKET_ERROR ) {
390 Sys_Printf( "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n" );
391 Sys_Printf( "Terminating the connection.\n" );
397 // we are non-blocking?? we should never get timeout errors
398 Sys_Printf( "WARNING: unexpected timeout expired in CWatchBSP::Processing\n" );
399 Sys_Printf( "Terminating the connection.\n" );
405 // the socket has been identified, there's something (message or disconnection)
406 // see if there's anything in input
407 ret = Net_Receive( m_pInSocket, &msg );
409 // unsigned int size = msg.size; //++timo just a check
410 strcpy( m_xmlBuf, NMSG_ReadString( &msg ) );
411 if ( m_bNeedCtxtInit ) {
412 m_xmlParserCtxt = NULL;
413 m_xmlParserCtxt = xmlCreatePushParserCtxt( &saxParser, &m_message_info, m_xmlBuf, strlen( m_xmlBuf ), NULL );
414 if ( m_xmlParserCtxt == NULL ) {
415 Sys_FPrintf( SYS_ERR, "Failed to create the XML parser (incoming stream began with: %s)\n", m_xmlBuf );
418 m_bNeedCtxtInit = false;
422 xmlParseChunk( m_xmlParserCtxt, m_xmlBuf, strlen( m_xmlBuf ), 0 );
427 // error or connection closed/reset
428 // NOTE: if we get an error down the XML stream we don't reach here
429 Net_Disconnect( m_pInSocket );
431 Sys_Printf( "Connection closed.\n" );
432 if ( m_bBSPPlugin ) {
434 // let the BSP plugin know that the job is done
435 g_BSPFrontendTable.m_pfnEndListen( 0 );
438 // move to next step or finish
440 if ( m_iCurrentStep < m_pCmd->len ) {
445 // release the GPtrArray and the strings
446 if ( m_pCmd != NULL ) {
447 for ( m_iCurrentStep = 0; m_iCurrentStep < m_pCmd->len; m_iCurrentStep++ )
449 delete[] (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
451 g_ptr_array_free( m_pCmd, false );
454 // launch the engine .. OMG
455 if ( g_PrefsDlg.m_bRunQuake ) {
456 // do we enter sleep mode before?
457 if ( g_PrefsDlg.m_bDoSleep ) {
458 Sys_Printf( "Going into sleep mode..\n" );
459 g_pParentWnd->OnSleep();
461 Sys_Printf( "Running engine...\n" );
463 // build the command line
464 cmd = g_pGameDescription->mEnginePath.GetBuffer();
465 // this is game dependant
466 if ( !strcmp( ValueForKey( g_qeglobals.d_project_entity, "gamemode" ),"mp" ) ) {
468 cmd += g_pGameDescription->mMultiplayerEngine.GetBuffer();
473 cmd += g_pGameDescription->mEngine.GetBuffer();
476 // NOTE: we are using unix pathnames and CreateProcess doesn't like / in the program path
477 // FIXME: This isn't true anymore, doesn't it?
478 FindReplace( cmd, "/", "\\" );
481 if ( g_pGameDescription->quake2 ) {
482 cmdline = ". +exec radiant.cfg +map ";
483 cmdline += m_sBSPName;
487 cmdline = "+set sv_pure 0 ";
488 // TTimo: a check for vm_* but that's all fine
489 //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
490 if ( *ValueForKey( g_qeglobals.d_project_entity, "gamename" ) != '\0' ) {
491 cmdline += "+set fs_game ";
492 cmdline += ValueForKey( g_qeglobals.d_project_entity, "gamename" );
495 //!\todo Read the start-map args from a config file.
496 if ( g_pGameDescription->mGameFile == "wolf.game" ) {
497 if ( !strcmp( ValueForKey( g_qeglobals.d_project_entity, "gamemode" ),"mp" ) ) {
499 cmdline += "+devmap ";
500 cmdline += m_sBSPName;
505 cmdline += "+set nextmap \"spdevmap ";
506 cmdline += m_sBSPName;
512 cmdline += "+devmap ";
513 cmdline += m_sBSPName;
517 Sys_Printf( "%s %s\n", cmd.GetBuffer(), cmdline.GetBuffer() );
520 if ( !Q_Exec( cmd.GetBuffer(), (char *)cmdline.GetBuffer(), g_pGameDescription->mEnginePath.GetBuffer(), false ) ) {
522 msg = "Failed to execute the following command: ";
523 msg += cmd; msg += cmdline;
525 gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
538 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, char *sBSPName ){
542 m_sBSPName = sBSPName;
543 if ( m_eState != EIdle ) {
544 Sys_Printf( "WatchBSP got a monitoring request while not idling...\n" );
545 // prompt the user, should we cancel the current process and go ahead?
546 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?",
547 "BSP process monitoring", MB_YESNO ) == IDYES ) {
548 // disconnect and set EIdle state
557 void CWatchBSP::ExternalListen(){
562 // the part of the watchbsp interface we export to plugins
563 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
564 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
565 void WINAPI QERApp_Listen(){
566 // open the listening socket
567 g_pParentWnd->GetWatchBSP()->ExternalListen();