]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/watchbsp.cpp
uncrustify! now the code is only ugly on the *inside*
[xonotic/netradiant.git] / radiant / watchbsp.cpp
1 /*
2    Copyright (c) 2001, Loki software, inc.
3    All rights reserved.
4
5    Redistribution and use in source and binary forms, with or without modification,
6    are permitted provided that the following conditions are met:
7
8    Redistributions of source code must retain the above copyright notice, this list
9    of conditions and the following disclaimer.
10
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.
14
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
17    written permission.
18
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.
29  */
30
31 //-----------------------------------------------------------------------------
32 //
33 // DESCRIPTION:
34 // monitoring window for running BSP processes (and possibly various other stuff)
35
36 #include "stdafx.h"
37 #include "watchbsp.h"
38 #include "feedback.h"
39
40 #ifdef _WIN32
41 #include <winsock2.h>
42 #endif
43
44 #if defined ( __linux__ ) || defined ( __APPLE__ )
45 #include <sys/time.h>
46 #define SOCKET_ERROR -1
47 #endif
48
49 #ifdef __APPLE__
50 #include <unistd.h>
51 #endif
52
53 #include <assert.h>
54
55 // Static functions for the SAX callbacks -------------------------------------------------------
56
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 );
63         }
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;
66         data->recurse++;
67 }
68
69 #include "stream_version.h"
70
71 static void saxStartElement( message_info_t *data, const xmlChar *name, const xmlChar **attrs ){
72         if ( data->ignore_depth == 0 ) {
73                 if ( data->bGeometry ) {
74                         // we have a handler
75                         data->pGeometry->saxStartElement( data, name, attrs );
76                 }
77                 else
78                 {
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" );
86                                         abortStream( data );
87                                         return;
88                                 }
89                                 else if ( strcmp( (char*)attrs[1],Q3MAP_STREAM_VERSION ) != 0 ) {
90                                         Sys_FPrintf( SYS_ERR,
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] );
93                                         abortStream( data );
94                                         return;
95                                 }
96                         }
97                         // we don't treat locally
98                         else if ( strcmp( (char *)name, "message" ) == 0 ) {
99                                 data->msg_level = atoi( (char *)attrs[1] );
100                         }
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 );
106                         }
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 );
112                         }
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 );
118                         }
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 );
124                         }
125                         else
126                         {
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;
131                         }
132                 }
133         }
134         data->recurse++;
135 }
136
137 static void saxEndElement( message_info_t *data, const xmlChar *name ) {
138         data->recurse--;
139         // we are out of an ignored chunk
140         if ( data->recurse == data->ignore_depth ) {
141                 data->ignore_depth = 0;
142                 return;
143         }
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 );
149                 }
150         }
151         if ( data->recurse == data->stop_depth ) {
152 #ifdef _DEBUG
153                 Sys_Printf( "Received error msg .. shutting down..\n" );
154 #endif
155                 g_pParentWnd->GetWatchBSP()->Reset();
156                 // tell there has been an error
157                 if ( g_pParentWnd->GetWatchBSP()->HasBSPPlugin() ) {
158                         g_BSPFrontendTable.m_pfnEndListen( 2 );
159                 }
160                 return;
161         }
162 }
163
164 static void saxCharacters( message_info_t *data, const xmlChar *ch, int len ){
165         if ( data->bGeometry ) {
166                 data->pGeometry->saxCharacters( data, ch, len );
167         }
168         else
169         {
170                 if ( data->ignore_depth != 0 ) {
171                         return;
172                 }
173                 // output the message using the level
174                 char buf[1024];
175                 memcpy( buf, ch, len );
176                 buf[len] = '\0';
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;
182                 }
183         }
184 }
185
186 static void saxComment( void *ctx, const xmlChar *msg ){
187         Sys_Printf( "XML comment: %s\n", msg );
188 }
189
190 static void saxWarning( void *ctx, const char *msg, ... ){
191         char saxMsgBuffer[4096];
192         va_list args;
193
194         va_start( args, msg );
195         vsprintf( saxMsgBuffer, msg, args );
196         va_end( args );
197         Sys_FPrintf( SYS_WRN, "XML warning: %s\n", saxMsgBuffer );
198 }
199
200 static void saxError( void *ctx, const char *msg, ... ){
201         char saxMsgBuffer[4096];
202         va_list args;
203
204         va_start( args, msg );
205         vsprintf( saxMsgBuffer, msg, args );
206         va_end( args );
207         Sys_FPrintf( SYS_ERR, "XML error: %s\n", saxMsgBuffer );
208 }
209
210 static void saxFatal( void *ctx, const char *msg, ... ){
211         char buffer[4096];
212
213         va_list args;
214
215         va_start( args, msg );
216         vsprintf( buffer, msg, args );
217         va_end( args );
218         Sys_FPrintf( SYS_ERR, "XML fatal error: %s\n", buffer );
219 }
220
221 static xmlSAXHandler saxParser = {
222         0, /* internalSubset */
223         0, /* isStandalone */
224         0, /* hasInternalSubset */
225         0, /* hasExternalSubset */
226         0, /* resolveEntity */
227         0, /* getEntity */
228         0, /* entityDecl */
229         0, /* notationDecl */
230         0, /* attributeDecl */
231         0, /* elementDecl */
232         0, /* unparsedEntityDecl */
233         0, /* setDocumentLocator */
234         0, /* startDocument */
235         0, /* endDocument */
236         (startElementSAXFunc)saxStartElement, /* startElement */
237         (endElementSAXFunc)saxEndElement, /* endElement */
238         0, /* reference */
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 */
246 };
247
248 // ------------------------------------------------------------------------------------------------
249
250 CWatchBSP::~CWatchBSP(){
251         Reset();
252         if ( m_sBSPName ) {
253                 delete[] m_sBSPName;
254                 m_sBSPName = NULL;
255         }
256         Net_Shutdown();
257 }
258
259 void CWatchBSP::Reset(){
260         if ( m_pInSocket ) {
261                 Net_Disconnect( m_pInSocket );
262                 m_pInSocket = NULL;
263         }
264         if ( m_pListenSocket ) {
265                 Net_Disconnect( m_pListenSocket );
266                 m_pListenSocket = NULL;
267         }
268         if ( m_xmlInputBuffer ) {
269                 xmlFreeParserInputBuffer( m_xmlInputBuffer );
270                 m_xmlInputBuffer = NULL;
271         }
272         m_eState = EIdle;
273 }
274
275 bool CWatchBSP::SetupListening(){
276 #ifdef _DEBUG
277         if ( m_pListenSocket ) {
278                 Sys_Printf( "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n" );
279                 return false;
280         }
281 #endif
282         Sys_Printf( "Setting up\n" );
283         if ( !Net_Setup() ) {
284                 return false;
285         }
286
287         m_pListenSocket = Net_ListenSocket( 39000 );
288         if ( m_pListenSocket == NULL ) {
289                 return false;
290         }
291
292         Sys_Printf( "Listening...\n" );
293         return true;
294 }
295
296 void CWatchBSP::DoEBeginStep() {
297         Reset();
298         if ( !SetupListening() ) {
299                 CString msg;
300                 msg = "Failed to get a listening socket on port 39000.\nTry running with BSP monitoring disabled if you can't fix this.\n";
301                 Sys_Printf( msg );
302                 gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR );
303                 return;
304         }
305         // set the timer for timeouts and step cancellation
306         g_timer_reset( m_pTimer );
307         g_timer_start( m_pTimer );
308
309         if ( !m_bBSPPlugin ) {
310                 Sys_Printf( "=== running BSP command ===\n%s\n", g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
311
312                 if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true ) ) {
313                         CString msg;
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";
317                         Sys_Printf( msg );
318                         gtk_MessageBox( g_pParentWnd->m_pWidget,  msg, "BSP monitoring", MB_OK | MB_ICONERROR );
319                         return;
320                 }
321                 // re-initialise the debug window
322                 if ( m_iCurrentStep == 0 ) {
323                         g_DbgDlg.Init();
324                 }
325         }
326         m_eState = EBeginStep;
327 }
328
329 void CWatchBSP::RoutineProcessing(){
330         // used for select()
331 #ifdef _WIN32
332         TIMEVAL tout = { 0, 0 };
333 #endif
334 #if defined ( __linux__ ) || defined ( __APPLE__ )
335         timeval tout;
336         tout.tv_sec = 0;
337         tout.tv_usec = 0;
338 #endif
339
340         switch ( m_eState )
341         {
342         case EBeginStep:
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 );
346                         Reset();
347                         if ( m_bBSPPlugin ) {
348                                 // status == 1 : didn't get the connection
349                                 g_BSPFrontendTable.m_pfnEndListen( 1 );
350                         }
351                         return;
352                 }
353 #ifdef _DEBUG
354                 // some debug checks
355                 if ( !m_pListenSocket ) {
356                         Sys_Printf( "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n" );
357                         return;
358                 }
359 #endif
360                 // we are not connected yet, accept any incoming connection
361                 m_pInSocket = Net_Accept( m_pListenSocket );
362                 if ( m_pInSocket ) {
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;
369                 }
370                 break;
371         case EWatching:
372 #ifdef _DEBUG
373                 // some debug checks
374                 if ( !m_pInSocket ) {
375                         Sys_Printf( "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n" );
376                         return;
377                 }
378 #endif
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
381                 fd_set readfds;
382                 int ret;
383                 FD_ZERO( &readfds );
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" );
392                         Reset();
393                         return;
394                 }
395 #ifdef _DEBUG
396                 if ( ret == -1 ) {
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" );
400                         Reset();
401                         return;
402                 }
403 #endif
404                 if ( ret == 1 ) {
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 );
408                         if ( ret > 0 ) {
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 );
416                                                 Reset();
417                                         }
418                                         m_bNeedCtxtInit = false;
419                                 }
420                                 else
421                                 {
422                                         xmlParseChunk( m_xmlParserCtxt, m_xmlBuf, strlen( m_xmlBuf ), 0 );
423                                 }
424                         }
425                         else
426                         {
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 );
430                                 m_pInSocket = NULL;
431                                 Sys_Printf( "Connection closed.\n" );
432                                 if ( m_bBSPPlugin ) {
433                                         Reset();
434                                         // let the BSP plugin know that the job is done
435                                         g_BSPFrontendTable.m_pfnEndListen( 0 );
436                                         return;
437                                 }
438                                 // move to next step or finish
439                                 m_iCurrentStep++;
440                                 if ( m_iCurrentStep < m_pCmd->len ) {
441                                         DoEBeginStep();
442                                 }
443                                 else
444                                 {
445                                         // release the GPtrArray and the strings
446                                         if ( m_pCmd != NULL ) {
447                                                 for ( m_iCurrentStep = 0; m_iCurrentStep < m_pCmd->len; m_iCurrentStep++ )
448                                                 {
449                                                         delete[] (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep );
450                                                 }
451                                                 g_ptr_array_free( m_pCmd, false );
452                                         }
453                                         m_pCmd = NULL;
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();
460                                                 }
461                                                 Sys_Printf( "Running engine...\n" );
462                                                 Str cmd;
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" ) ) {
467                                                         // MP
468                                                         cmd += g_pGameDescription->mMultiplayerEngine.GetBuffer();
469                                                 }
470                                                 else
471                                                 {
472                                                         // SP
473                                                         cmd += g_pGameDescription->mEngine.GetBuffer();
474                                                 }
475 #ifdef _WIN32
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, "/", "\\" );
479 #endif
480                                                 Str cmdline;
481                                                 if ( g_pGameDescription->quake2 ) {
482                                                         cmdline = ". +exec radiant.cfg +map ";
483                                                         cmdline += m_sBSPName;
484                                                 }
485                                                 else
486                                                 {
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" );
493                                                                 cmdline += " ";
494                                                         }
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" ) ) {
498                                                                         // MP
499                                                                         cmdline += "+devmap ";
500                                                                         cmdline += m_sBSPName;
501                                                                 }
502                                                                 else
503                                                                 {
504                                                                         // SP
505                                                                         cmdline += "+set nextmap \"spdevmap ";
506                                                                         cmdline += m_sBSPName;
507                                                                         cmdline += "\"";
508                                                                 }
509                                                         }
510                                                         else
511                                                         {
512                                                                 cmdline += "+devmap ";
513                                                                 cmdline += m_sBSPName;
514                                                         }
515                                                 }
516
517                                                 Sys_Printf( "%s %s\n", cmd.GetBuffer(), cmdline.GetBuffer() );
518
519                                                 // execute now
520                                                 if ( !Q_Exec( cmd.GetBuffer(), (char *)cmdline.GetBuffer(), g_pGameDescription->mEnginePath.GetBuffer(), false ) ) {
521                                                         CString msg;
522                                                         msg = "Failed to execute the following command: ";
523                                                         msg += cmd; msg += cmdline;
524                                                         Sys_Printf( msg );
525                                                         gtk_MessageBox( g_pParentWnd->m_pWidget,  msg, "BSP monitoring", MB_OK | MB_ICONERROR );
526                                                 }
527                                         }
528                                         Reset();
529                                 }
530                         }
531                 }
532                 break;
533         default:
534                 break;
535         }
536 }
537
538 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, char *sBSPName ){
539         if ( m_sBSPName ) {
540                 delete[] m_sBSPName;
541         }
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
549                         Reset();
550                 }
551         }
552         m_pCmd = pCmd;
553         m_iCurrentStep = 0;
554         DoEBeginStep();
555 }
556
557 void CWatchBSP::ExternalListen(){
558         m_bBSPPlugin = true;
559         DoEBeginStep();
560 }
561
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();
568 }