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