]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/watchbsp.cpp
Add a README
[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 "watchbsp.h"
37
38 #include <algorithm>
39 #include <gtk/gtkmain.h>
40
41 #include "cmdlib.h"
42 #include "convert.h"
43 #include "string/string.h"
44 #include "stream/stringstream.h"
45
46 #include "gtkutil/messagebox.h"
47 #include "xmlstuff.h"
48 #include "console.h"
49 #include "preferences.h"
50 #include "points.h"
51 #include "feedback.h"
52 #include "mainframe.h"
53 #include "sockets.h"
54
55 void message_flush( message_info_t* self ){
56         Sys_Print( self->msg_level, self->m_buffer, self->m_length );
57         self->m_length = 0;
58 }
59
60 void message_print( message_info_t* self, const char* characters, std::size_t length ){
61         const char* end = characters + length;
62         while ( characters != end )
63         {
64                 std::size_t space = message_info_t::bufsize - 1 - self->m_length;
65                 if ( space == 0 ) {
66                         message_flush( self );
67                 }
68                 else
69                 {
70                         std::size_t size = std::min( space, std::size_t( end - characters ) );
71                         memcpy( self->m_buffer + self->m_length, characters, size );
72                         self->m_length += size;
73                         characters += size;
74                 }
75         }
76 }
77
78
79 #include <glib.h>
80 #include "xmlstuff.h"
81
82 class CWatchBSP
83 {
84 private:
85         // a flag we have set to true when using an external BSP plugin
86         // the resulting code with that is a bit dirty, cleaner solution would be to seperate the succession of commands from the listening loop
87         // (in two seperate classes probably)
88         bool m_bBSPPlugin;
89
90         // EIdle: we are not listening
91         //   DoMonitoringLoop will change state to EBeginStep
92         // EBeginStep: the socket is up for listening, we are expecting incoming connection
93         //   incoming connection will change state to EWatching
94         // EWatching: we have a connection, monitor it
95         //   connection closed will see if we start a new step (EBeginStep) or launch Quake3 and end (EIdle)
96         enum EWatchBSPState { EIdle, EBeginStep, EWatching } m_eState;
97         socket_t *m_pListenSocket;
98         socket_t *m_pInSocket;
99         netmessage_t msg;
100         GPtrArray *m_pCmd;
101         // used to timeout EBeginStep
102         GTimer    *m_pTimer;
103         std::size_t m_iCurrentStep;
104         // name of the map so we can run the engine
105         char    *m_sBSPName;
106         // buffer we use in push mode to receive data directly from the network
107         xmlParserInputBufferPtr m_xmlInputBuffer;
108         xmlParserInputPtr m_xmlInput;
109         xmlParserCtxtPtr m_xmlParserCtxt;
110         // call this to switch the set listening mode
111         bool SetupListening();
112         // start a new EBeginStep
113         void DoEBeginStep();
114         // the xml and sax parser state
115         char m_xmlBuf[MAX_NETMESSAGE];
116         bool m_bNeedCtxtInit;
117         message_info_t m_message_info;
118
119 public:
120         CWatchBSP(){
121                 m_pCmd = 0;
122                 m_bBSPPlugin = false;
123                 m_pListenSocket = NULL;
124                 m_pInSocket = NULL;
125                 m_eState = EIdle;
126                 m_pTimer = g_timer_new();
127                 m_sBSPName = NULL;
128                 m_xmlInputBuffer = NULL;
129                 m_bNeedCtxtInit = true;
130         }
131         virtual ~CWatchBSP(){
132                 EndMonitoringLoop();
133                 Net_Shutdown();
134
135                 g_timer_destroy( m_pTimer );
136         }
137
138         bool HasBSPPlugin() const
139         { return m_bBSPPlugin; }
140
141         // called regularly to keep listening
142         void RoutineProcessing();
143         // start a monitoring loop with the following steps
144         void DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName );
145         void EndMonitoringLoop(){
146                 Reset();
147                 if ( m_sBSPName ) {
148                         string_release( m_sBSPName, string_length( m_sBSPName ) );
149                         m_sBSPName = 0;
150                 }
151                 if ( m_pCmd ) {
152                         g_ptr_array_free( m_pCmd, TRUE );
153                         m_pCmd = 0;
154                 }
155         }
156         // close everything - may be called from the outside to abort the process
157         void Reset();
158         // start a listening loop for an external process, possibly a BSP plugin
159         void ExternalListen();
160 };
161
162 CWatchBSP* g_pWatchBSP;
163
164 // watch the BSP process through network connections
165 // true: trigger the BSP steps one by one and monitor them through the network
166 // false: create a BAT / .sh file and execute it. don't bother monitoring it.
167 bool g_WatchBSP_Enabled = true;
168 // do we stop the compilation process if we come accross a leak?
169 bool g_WatchBSP_LeakStop = true;
170 bool g_WatchBSP_RunQuake = false;
171 // store prefs setting for automatic sleep mode activation
172 bool g_WatchBSP_DoSleep = true;
173 // timeout when beginning a step (in seconds)
174 // if we don't get a connection quick enough we assume something failed and go back to idling
175 int g_WatchBSP_Timeout = 10;
176
177
178 void Build_constructPreferences( PreferencesPage& page ){
179         GtkWidget* monitorbsp = page.appendCheckBox( "", "Enable Build Process Monitoring", g_WatchBSP_Enabled );
180         GtkWidget* leakstop = page.appendCheckBox( "", "Stop Compilation on Leak", g_WatchBSP_LeakStop );
181         GtkWidget* runengine = page.appendCheckBox( "", "Run Engine After Compile", g_WatchBSP_RunQuake );
182         GtkWidget* sleep = page.appendCheckBox ( "", "Sleep When Running the Engine", g_WatchBSP_DoSleep );
183         Widget_connectToggleDependency( leakstop, monitorbsp );
184         Widget_connectToggleDependency( runengine, monitorbsp );
185         Widget_connectToggleDependency( sleep, runengine );
186 }
187 void Build_constructPage( PreferenceGroup& group ){
188         PreferencesPage page( group.createPage( "Build", "Build Preferences" ) );
189         Build_constructPreferences( page );
190 }
191 void Build_registerPreferencesPage(){
192         PreferencesDialog_addSettingsPage( FreeCaller1<PreferenceGroup&, Build_constructPage>() );
193 }
194
195 #include "preferencesystem.h"
196 #include "stringio.h"
197
198 void BuildMonitor_Construct(){
199         g_pWatchBSP = new CWatchBSP();
200
201         g_WatchBSP_Enabled = !string_equal( g_pGameDescription->getKeyValue( "no_bsp_monitor" ), "1" );
202
203         GlobalPreferenceSystem().registerPreference( "WatchBSP", BoolImportStringCaller( g_WatchBSP_Enabled ), BoolExportStringCaller( g_WatchBSP_Enabled ) );
204         GlobalPreferenceSystem().registerPreference( "RunQuake2Run", BoolImportStringCaller( g_WatchBSP_RunQuake ), BoolExportStringCaller( g_WatchBSP_RunQuake ) );
205         GlobalPreferenceSystem().registerPreference( "LeakStop", BoolImportStringCaller( g_WatchBSP_LeakStop ), BoolExportStringCaller( g_WatchBSP_LeakStop ) );
206         GlobalPreferenceSystem().registerPreference( "SleepMode", BoolImportStringCaller( g_WatchBSP_DoSleep ), BoolExportStringCaller( g_WatchBSP_DoSleep ) );
207
208         Build_registerPreferencesPage();
209 }
210
211 void BuildMonitor_Destroy(){
212         delete g_pWatchBSP;
213         g_pWatchBSP = nullptr;
214 }
215
216 CWatchBSP *GetWatchBSP(){
217         return g_pWatchBSP;
218 }
219
220 void BuildMonitor_Run( GPtrArray* commands, const char* mapName ){
221         GetWatchBSP()->DoMonitoringLoop( commands, mapName );
222 }
223
224
225 // Static functions for the SAX callbacks -------------------------------------------------------
226
227 // utility for saxStartElement below
228 static void abortStream( message_info_t *data ){
229         GetWatchBSP()->EndMonitoringLoop();
230         // tell there has been an error
231 #if 0
232         if ( GetWatchBSP()->HasBSPPlugin() ) {
233                 g_BSPFrontendTable.m_pfnEndListen( 2 );
234         }
235 #endif
236         // yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out
237         data->ignore_depth = -1;
238         data->recurse++;
239 }
240
241 #include "stream_version.h"
242
243 static void saxStartElement( message_info_t *data, const xmlChar *name, const xmlChar **attrs ){
244 #if 0
245         globalOutputStream() << "<" << name;
246         if ( attrs != 0 ) {
247                 for ( const xmlChar** p = attrs; *p != 0; p += 2 )
248                 {
249                         globalOutputStream() << " " << p[0] << "=" << makeQuoted( p[1] );
250                 }
251         }
252         globalOutputStream() << ">\n";
253 #endif
254
255         if ( data->ignore_depth == 0 ) {
256                 if ( data->pGeometry != 0 ) {
257                         // we have a handler
258                         data->pGeometry->saxStartElement( data, name, attrs );
259                 }
260                 else
261                 {
262                         if ( strcmp( reinterpret_cast<const char*>( name ), "q3map_feedback" ) == 0 ) {
263                                 // check the correct version
264                                 // old q3map don't send a version attribute
265                                 // the ones we support .. send Q3MAP_STREAM_VERSION
266                                 if ( !attrs[0] || !attrs[1] || ( strcmp( reinterpret_cast<const char*>( attrs[0] ), "version" ) != 0 ) ) {
267                                         message_flush( data );
268                                         globalErrorStream() << "No stream version given in the feedback stream, this is an old q3map version.\n"
269                                                                                    "Please turn off monitored compiling if you still wish to use this q3map executable\n";
270                                         abortStream( data );
271                                         return;
272                                 }
273                                 else if ( q3map::stream_version() !=  reinterpret_cast<const char*>( attrs[1] ) ) {
274                                         message_flush( data );
275                                         globalErrorStream() << "This version of Radiant reads version "
276                                                 << q3map::stream_version()
277                                                 << " debug streams, I got an incoming connection with version "
278                                                 << reinterpret_cast<const char*>( attrs[1] )
279                                                 << "\nPlease make sure your versions of Radiant and q3map are matching.\n";
280                                         abortStream( data );
281                                         return;
282                                 }
283                         }
284                         // we don't treat locally
285                         else if ( strcmp( reinterpret_cast<const char*>( name ), "message" ) == 0 ) {
286                                 int msg_level = atoi( reinterpret_cast<const char*>( attrs[1] ) );
287                                 if ( msg_level != data->msg_level ) {
288                                         message_flush( data );
289                                         data->msg_level = msg_level;
290                                 }
291                         }
292                         else if ( strcmp( reinterpret_cast<const char*>( name ), "polyline" ) == 0 ) {
293                                 // polyline has a particular status .. right now we only use it for leakfile ..
294                                 data->geometry_depth = data->recurse;
295                                 data->pGeometry = &g_pointfile;
296                                 data->pGeometry->saxStartElement( data, name, attrs );
297                         }
298                         else if ( strcmp( reinterpret_cast<const char*>( name ), "select" ) == 0 ) {
299                                 CSelectMsg *pSelect = new CSelectMsg();
300                                 data->geometry_depth = data->recurse;
301                                 data->pGeometry = pSelect;
302                                 data->pGeometry->saxStartElement( data, name, attrs );
303                         }
304                         else if ( strcmp( reinterpret_cast<const char*>( name ), "pointmsg" ) == 0 ) {
305                                 CPointMsg *pPoint = new CPointMsg();
306                                 data->geometry_depth = data->recurse;
307                                 data->pGeometry = pPoint;
308                                 data->pGeometry->saxStartElement( data, name, attrs );
309                         }
310                         else if ( strcmp( reinterpret_cast<const char*>( name ), "windingmsg" ) == 0 ) {
311                                 CWindingMsg *pWinding = new CWindingMsg();
312                                 data->geometry_depth = data->recurse;
313                                 data->pGeometry = pWinding;
314                                 data->pGeometry->saxStartElement( data, name, attrs );
315                         }
316                         else
317                         {
318                                 globalErrorStream() << "Warning: ignoring unrecognized node in XML stream (" << reinterpret_cast<const char*>( name ) << ")\n";
319                                 // we don't recognize this node, jump over it
320                                 // (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level)
321                                 data->ignore_depth = data->recurse;
322                         }
323                 }
324         }
325         data->recurse++;
326 }
327
328 static void saxEndElement( message_info_t *data, const xmlChar *name ){
329 #if 0
330         globalOutputStream() << "<" << name << "/>\n";
331 #endif
332
333         data->recurse--;
334         // we are out of an ignored chunk
335         if ( data->recurse == data->ignore_depth ) {
336                 data->ignore_depth = 0;
337                 return;
338         }
339         if ( data->pGeometry != 0 ) {
340                 data->pGeometry->saxEndElement( data, name );
341                 // we add the object to the debug window
342                 if ( data->geometry_depth == data->recurse ) {
343                         g_DbgDlg.Push( data->pGeometry );
344                         data->pGeometry = 0;
345                 }
346         }
347         if ( data->recurse == data->stop_depth ) {
348                 message_flush( data );
349 #ifdef _DEBUG
350                 globalOutputStream() << "Received error msg .. shutting down..\n";
351 #endif
352                 GetWatchBSP()->EndMonitoringLoop();
353                 // tell there has been an error
354 #if 0
355                 if ( GetWatchBSP()->HasBSPPlugin() ) {
356                         g_BSPFrontendTable.m_pfnEndListen( 2 );
357                 }
358 #endif
359                 return;
360         }
361 }
362
363 class MessageOutputStream : public TextOutputStream
364 {
365         message_info_t* m_data;
366 public:
367         MessageOutputStream( message_info_t* data ) : m_data( data ){
368         }
369
370         std::size_t write( const char* buffer, std::size_t length ){
371                 if ( m_data->pGeometry != 0 ) {
372                         m_data->pGeometry->saxCharacters( m_data, reinterpret_cast<const xmlChar*>( buffer ), int(length) );
373                 }
374                 else
375                 {
376                         if ( m_data->ignore_depth == 0 ) {
377                                 // output the message using the level
378                                 message_print( m_data, buffer, length );
379                                 // if this message has error level flag, we mark the depth to stop the compilation when we get out
380                                 // we don't set the msg level if we don't stop on leak
381                                 if ( m_data->msg_level == 3 ) {
382                                         m_data->stop_depth = m_data->recurse - 1;
383                                 }
384                         }
385                 }
386
387                 return length;
388         }
389 };
390
391 template<typename T>
392 inline MessageOutputStream& operator<<( MessageOutputStream& ostream, const T& t ){
393         return ostream_write( ostream, t );
394 }
395
396 static void saxCharacters( message_info_t *data, const xmlChar *ch, int len ){
397         MessageOutputStream ostream( data );
398         ostream << StringRange( reinterpret_cast<const char*>( ch ), reinterpret_cast<const char*>( ch + len ) );
399 }
400
401 static void saxComment( void *ctx, const xmlChar *msg ){
402         globalOutputStream() << "XML comment: " << reinterpret_cast<const char*>( msg ) << "\n";
403 }
404
405 static void saxWarning( void *ctx, const char *msg, ... ){
406         char saxMsgBuffer[4096];
407         va_list args;
408
409         va_start( args, msg );
410         vsprintf( saxMsgBuffer, msg, args );
411         va_end( args );
412         globalOutputStream() << "XML warning: " << saxMsgBuffer << "\n";
413 }
414
415 static void saxError( void *ctx, const char *msg, ... ){
416         char saxMsgBuffer[4096];
417         va_list args;
418
419         va_start( args, msg );
420         vsprintf( saxMsgBuffer, msg, args );
421         va_end( args );
422         globalErrorStream() << "XML error: " << saxMsgBuffer << "\n";
423 }
424
425 static void saxFatal( void *ctx, const char *msg, ... ){
426         char buffer[4096];
427
428         va_list args;
429
430         va_start( args, msg );
431         vsprintf( buffer, msg, args );
432         va_end( args );
433         globalErrorStream() << "XML fatal error: " << buffer << "\n";
434 }
435
436 static xmlSAXHandler saxParser = {
437         0, /* internalSubset */
438         0, /* isStandalone */
439         0, /* hasInternalSubset */
440         0, /* hasExternalSubset */
441         0, /* resolveEntity */
442         0, /* getEntity */
443         0, /* entityDecl */
444         0, /* notationDecl */
445         0, /* attributeDecl */
446         0, /* elementDecl */
447         0, /* unparsedEntityDecl */
448         0, /* setDocumentLocator */
449         0, /* startDocument */
450         0, /* endDocument */
451         (startElementSAXFunc)saxStartElement, /* startElement */
452         (endElementSAXFunc)saxEndElement, /* endElement */
453         0, /* reference */
454         (charactersSAXFunc)saxCharacters, /* characters */
455         0, /* ignorableWhitespace */
456         0, /* processingInstruction */
457         (commentSAXFunc)saxComment, /* comment */
458         (warningSAXFunc)saxWarning, /* warning */
459         (errorSAXFunc)saxError, /* error */
460         (fatalErrorSAXFunc)saxFatal, /* fatalError */
461         0,
462         0,
463         0,
464         0,
465         0,
466         0,
467         0,
468         0
469 };
470
471 // ------------------------------------------------------------------------------------------------
472
473
474 guint s_routine_id;
475 static gint watchbsp_routine( gpointer data ){
476         reinterpret_cast<CWatchBSP*>( data )->RoutineProcessing();
477         return TRUE;
478 }
479
480 void CWatchBSP::Reset(){
481         if ( m_pInSocket ) {
482                 Net_Disconnect( m_pInSocket );
483                 m_pInSocket = NULL;
484         }
485         if ( m_pListenSocket ) {
486                 Net_Disconnect( m_pListenSocket );
487                 m_pListenSocket = NULL;
488         }
489         if ( m_xmlInputBuffer ) {
490                 xmlFreeParserInputBuffer( m_xmlInputBuffer );
491                 m_xmlInputBuffer = NULL;
492         }
493         m_eState = EIdle;
494         if ( s_routine_id ) {
495                 gtk_timeout_remove( s_routine_id );
496                 s_routine_id = 0;
497         }
498 }
499
500 bool CWatchBSP::SetupListening(){
501 #ifdef _DEBUG
502         if ( m_pListenSocket ) {
503                 globalOutputStream() << "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n";
504                 return false;
505         }
506 #endif
507         globalOutputStream() << "Setting up\n";
508         Net_Setup();
509         m_pListenSocket = Net_ListenSocket( 39000 );
510         if ( m_pListenSocket == NULL ) {
511                 return false;
512         }
513         globalOutputStream() << "Listening...\n";
514         return true;
515 }
516
517 void CWatchBSP::DoEBeginStep(){
518         Reset();
519         if ( SetupListening() == false ) {
520                 const char* msg = "Failed to get a listening socket on port 39000.\nTry running with Build monitoring disabled if you can't fix this.\n";
521                 globalOutputStream() << msg;
522                 gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ), msg, "Build monitoring", eMB_OK, eMB_ICONERROR );
523                 return;
524         }
525         // set the timer for timeouts and step cancellation
526         g_timer_reset( m_pTimer );
527         g_timer_start( m_pTimer );
528
529         if ( !m_bBSPPlugin ) {
530                 globalOutputStream() << "=== running build command ===\n"
531                                                          << static_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) ) << "\n";
532
533                 if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true, false ) ) {
534                         StringOutputStream msg( 256 );
535                         msg << "Failed to execute the following command: ";
536                         msg << reinterpret_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
537                         msg << "\nCheck that the file exists and that you don't run out of system resources.\n";
538                         globalOutputStream() << msg.c_str();
539                         gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ), msg.c_str(), "Build monitoring", eMB_OK, eMB_ICONERROR );
540                         return;
541                 }
542                 // re-initialise the debug window
543                 if ( m_iCurrentStep == 0 ) {
544                         g_DbgDlg.Init();
545                 }
546         }
547         m_eState = EBeginStep;
548         s_routine_id = gtk_timeout_add( 25, watchbsp_routine, this );
549 }
550
551
552 #if defined( WIN32 )
553 #define ENGINE_ATTRIBUTE "engine_win32"
554 #define MP_ENGINE_ATTRIBUTE "mp_engine_win32"
555 #elif defined( __linux__ ) || defined ( __FreeBSD__ )
556 #define ENGINE_ATTRIBUTE "engine_linux"
557 #define MP_ENGINE_ATTRIBUTE "mp_engine_linux"
558 #elif defined( __APPLE__ )
559 #define ENGINE_ATTRIBUTE "engine_macos"
560 #define MP_ENGINE_ATTRIBUTE "mp_engine_macos"
561 #else
562 #error "unsupported platform"
563 #endif
564
565 class RunEngineConfiguration
566 {
567 public:
568         const char* executable;
569         const char* mp_executable;
570         bool do_sp_mp;
571
572         RunEngineConfiguration() :
573                 executable( g_pGameDescription->getRequiredKeyValue( ENGINE_ATTRIBUTE ) ),
574                 mp_executable( g_pGameDescription->getKeyValue( MP_ENGINE_ATTRIBUTE ) ){
575                 do_sp_mp = !string_empty( mp_executable );
576         }
577 };
578
579 inline void GlobalGameDescription_string_write_mapparameter( StringOutputStream& string, const char* mapname ){
580         if ( g_pGameDescription->mGameType == "q2"
581                  || g_pGameDescription->mGameType == "heretic2" ) {
582                 string << ". +exec radiant.cfg +map " << mapname;
583         }
584         else
585         {
586                 string << "+set sv_pure 0 ";
587                 // TTimo: a check for vm_* but that's all fine
588                 //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
589                 const char* fs_game = gamename_get();
590                 if ( !string_equal( fs_game, basegame_get() ) ) {
591                         string << "+set fs_game " << fs_game << " ";
592                 }
593                 if ( g_pGameDescription->mGameType == "wolf" ) {
594                         //|| g_pGameDescription->mGameType == "et")
595                         if ( string_equal( gamemode_get(), "mp" ) ) {
596                                 // MP
597                                 string << "+devmap " << mapname;
598                         }
599                         else
600                         {
601                                 // SP
602                                 string << "+set nextmap \"spdevmap " << mapname << "\"";
603                         }
604                 }
605                 else
606                 {
607                         string << "+devmap " << mapname;
608                 }
609         }
610 }
611
612
613 void CWatchBSP::RoutineProcessing(){
614         switch ( m_eState )
615         {
616         case EBeginStep:
617                 // timeout: if we don't get an incoming connection fast enough, go back to idle
618                 if ( g_timer_elapsed( m_pTimer, NULL ) > g_WatchBSP_Timeout ) {
619                         gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ),  "The connection timed out, assuming the build process failed\nMake sure you are using a networked version of Q3Map?\nOtherwise you need to disable BSP Monitoring in prefs.", "BSP process monitoring", eMB_OK );
620                         EndMonitoringLoop();
621 #if 0
622                         if ( m_bBSPPlugin ) {
623                                 // status == 1 : didn't get the connection
624                                 g_BSPFrontendTable.m_pfnEndListen( 1 );
625                         }
626 #endif
627                         return;
628                 }
629 #ifdef _DEBUG
630                 // some debug checks
631                 if ( !m_pListenSocket ) {
632                         globalErrorStream() << "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n";
633                         return;
634                 }
635 #endif
636                 // we are not connected yet, accept any incoming connection
637                 m_pInSocket = Net_Accept( m_pListenSocket );
638                 if ( m_pInSocket ) {
639                         globalOutputStream() << "Connected.\n";
640                         // prepare the message info struct for diving in
641                         memset( &m_message_info, 0, sizeof( message_info_t ) );
642                         // a dumb flag to make sure we init the push parser context when first getting a msg
643                         m_bNeedCtxtInit = true;
644                         m_eState = EWatching;
645                 }
646                 break;
647         case EWatching:
648         {
649 #ifdef _DEBUG
650                 // some debug checks
651                 if ( !m_pInSocket ) {
652                         globalErrorStream() << "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n";
653                         return;
654                 }
655 #endif
656
657                 int ret = Net_Wait( m_pInSocket, 0, 0 );
658                 if ( ret == -1 ) {
659                         globalOutputStream() << "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n";
660                         globalOutputStream() << "Terminating the connection.\n";
661                         EndMonitoringLoop();
662                         return;
663                 }
664
665                 if ( ret == 1 ) {
666                         // the socket has been identified, there's something (message or disconnection)
667                         // see if there's anything in input
668                         ret = Net_Receive( m_pInSocket, &msg );
669                         if ( ret > 0 ) {
670                                 //        unsigned int size = msg.size; //++timo just a check
671                                 strcpy( m_xmlBuf, NMSG_ReadString( &msg ) );
672                                 if ( m_bNeedCtxtInit ) {
673                                         m_xmlParserCtxt = NULL;
674                                         m_xmlParserCtxt = xmlCreatePushParserCtxt( &saxParser, &m_message_info, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), NULL );
675
676                                         if ( m_xmlParserCtxt == NULL ) {
677                                                 globalErrorStream() << "Failed to create the XML parser (incoming stream began with: " << m_xmlBuf << ")\n";
678                                                 EndMonitoringLoop();
679                                         }
680                                         m_bNeedCtxtInit = false;
681                                 }
682                                 else
683                                 {
684                                         xmlParseChunk( m_xmlParserCtxt, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), 0 );
685                                 }
686                         }
687                         else
688                         {
689                                 message_flush( &m_message_info );
690                                 // error or connection closed/reset
691                                 // NOTE: if we get an error down the XML stream we don't reach here
692                                 Net_Disconnect( m_pInSocket );
693                                 m_pInSocket = NULL;
694                                 globalOutputStream() << "Connection closed.\n";
695 #if 0
696                                 if ( m_bBSPPlugin ) {
697                                         EndMonitoringLoop();
698                                         // let the BSP plugin know that the job is done
699                                         g_BSPFrontendTable.m_pfnEndListen( 0 );
700                                         return;
701                                 }
702 #endif
703                                 // move to next step or finish
704                                 m_iCurrentStep++;
705                                 if ( m_iCurrentStep < m_pCmd->len ) {
706                                         DoEBeginStep();
707                                 }
708                                 else
709                                 {
710                                         // launch the engine .. OMG
711                                         if ( g_WatchBSP_RunQuake ) {
712 #if 0
713                                                 // do we enter sleep mode before?
714                                                 if ( g_WatchBSP_DoSleep ) {
715                                                         globalOutputStream() << "Going into sleep mode..\n";
716                                                         g_pParentWnd->OnSleep();
717                                                 }
718 #endif
719                                                 globalOutputStream() << "Running engine...\n";
720                                                 StringOutputStream cmd( 256 );
721                                                 // build the command line
722                                                 cmd << EnginePath_get();
723                                                 // this is game dependant
724
725                                                 RunEngineConfiguration engineConfig;
726
727                                                 if ( engineConfig.do_sp_mp ) {
728                                                         if ( string_equal( gamemode_get(), "mp" ) ) {
729                                                                 cmd << engineConfig.mp_executable;
730                                                         }
731                                                         else
732                                                         {
733                                                                 cmd << engineConfig.executable;
734                                                         }
735                                                 }
736                                                 else
737                                                 {
738                                                         cmd << engineConfig.executable;
739                                                 }
740
741                                                 StringOutputStream cmdline;
742
743                                                 GlobalGameDescription_string_write_mapparameter( cmdline, m_sBSPName );
744
745                                                 globalOutputStream() << cmd.c_str() << " " << cmdline.c_str() << "\n";
746
747                                                 // execute now
748                                                 if ( !Q_Exec( cmd.c_str(), (char *)cmdline.c_str(), EnginePath_get(), false, false ) ) {
749                                                         StringOutputStream msg;
750                                                         msg << "Failed to execute the following command: " << cmd.c_str() << cmdline.c_str();
751                                                         globalOutputStream() << msg.c_str();
752                                                         gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ),  msg.c_str(), "Build monitoring", eMB_OK, eMB_ICONERROR );
753                                                 }
754                                         }
755                                         EndMonitoringLoop();
756                                 }
757                         }
758                 }
759         }
760         break;
761         default:
762                 break;
763         }
764 }
765
766 GPtrArray* str_ptr_array_clone( GPtrArray* array ){
767         GPtrArray* cloned = g_ptr_array_sized_new( array->len );
768         for ( guint i = 0; i < array->len; ++i )
769         {
770                 g_ptr_array_add( cloned, g_strdup( (char*)g_ptr_array_index( array, i ) ) );
771         }
772         return cloned;
773 }
774
775 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName ){
776         m_sBSPName = string_clone( sBSPName );
777         if ( m_eState != EIdle ) {
778                 globalOutputStream() << "WatchBSP got a monitoring request while not idling...\n";
779                 // prompt the user, should we cancel the current process and go ahead?
780                 if ( gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ),  "I am already monitoring a Build process.\nDo you want me to override and start a new compilation?",
781                                                          "Build process monitoring", eMB_YESNO ) == eIDYES ) {
782                         // disconnect and set EIdle state
783                         Reset();
784                 }
785         }
786         m_pCmd = str_ptr_array_clone( pCmd );
787         m_iCurrentStep = 0;
788         DoEBeginStep();
789 }
790
791 void CWatchBSP::ExternalListen(){
792         m_bBSPPlugin = true;
793         DoEBeginStep();
794 }
795
796 // the part of the watchbsp interface we export to plugins
797 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
798 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
799 void QERApp_Listen(){
800         // open the listening socket
801         GetWatchBSP()->ExternalListen();
802 }