]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/watchbsp.cpp
Merge commit '833d947959e424126907d552840203a557097e8f' into master-merge
[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 #include "globaldefs.h"
38
39 #include <algorithm>
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 <uilib/uilib.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         ui::CheckButton monitorbsp = page.appendCheckBox( "", "Enable Build Process Monitoring", g_WatchBSP_Enabled );
181         ui::CheckButton leakstop = page.appendCheckBox( "", "Stop Compilation on Leak", g_WatchBSP_LeakStop );
182         ui::CheckButton runengine = page.appendCheckBox( "", "Run Engine After Compile", g_WatchBSP_RunQuake );
183         ui::CheckButton 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( makeCallbackF(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", make_property_string( g_WatchBSP_Enabled ) );
205         GlobalPreferenceSystem().registerPreference( "RunQuake2Run", make_property_string( g_WatchBSP_RunQuake ) );
206         GlobalPreferenceSystem().registerPreference( "LeakStop", make_property_string( g_WatchBSP_LeakStop ) );
207         GlobalPreferenceSystem().registerPreference( "SleepMode", make_property_string( 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_NAME " 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_NAME " 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 #if GDEF_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
368         std::size_t write( const char* buffer, std::size_t length ){
369                 if ( m_data->pGeometry != 0 ) {
370                         m_data->pGeometry->saxCharacters( m_data, reinterpret_cast<const xmlChar*>( buffer ), int(length) );
371                 }
372                 else
373                 {
374                         if ( m_data->ignore_depth == 0 ) {
375                                 // output the message using the level
376                                 message_print( m_data, buffer, length );
377                                 // if this message has error level flag, we mark the depth to stop the compilation when we get out
378                                 // we don't set the msg level if we don't stop on leak
379                                 if ( m_data->msg_level == 3 ) {
380                                         m_data->stop_depth = m_data->recurse - 1;
381                                 }
382                         }
383                 }
384
385                 return length;
386         }
387 };
388
389 template<typename T>
390 inline MessageOutputStream& operator<<( MessageOutputStream& ostream, const T& t ){
391         return ostream_write( ostream, t );
392 }
393
394 static void saxCharacters( message_info_t *data, const xmlChar *ch, int len ){
395         MessageOutputStream ostream( data );
396         ostream << StringRange( reinterpret_cast<const char*>( ch ), reinterpret_cast<const char*>( ch + len ) );
397 }
398
399 static void saxComment( void *ctx, const xmlChar *msg ){
400         globalOutputStream() << "XML comment: " << reinterpret_cast<const char*>( msg ) << "\n";
401 }
402
403 static void saxWarning( void *ctx, const char *msg, ... ){
404         char saxMsgBuffer[4096];
405         va_list args;
406
407         va_start( args, msg );
408         vsprintf( saxMsgBuffer, msg, args );
409         va_end( args );
410         globalOutputStream() << "XML warning: " << saxMsgBuffer << "\n";
411 }
412
413 static void saxError( void *ctx, const char *msg, ... ){
414         char saxMsgBuffer[4096];
415         va_list args;
416
417         va_start( args, msg );
418         vsprintf( saxMsgBuffer, msg, args );
419         va_end( args );
420         globalErrorStream() << "XML error: " << saxMsgBuffer << "\n";
421 }
422
423 static void saxFatal( void *ctx, const char *msg, ... ){
424         char buffer[4096];
425
426         va_list args;
427
428         va_start( args, msg );
429         vsprintf( buffer, msg, args );
430         va_end( args );
431         globalErrorStream() << "XML fatal error: " << buffer << "\n";
432 }
433
434 static xmlSAXHandler saxParser = {
435         0, /* internalSubset */
436         0, /* isStandalone */
437         0, /* hasInternalSubset */
438         0, /* hasExternalSubset */
439         0, /* resolveEntity */
440         0, /* getEntity */
441         0, /* entityDecl */
442         0, /* notationDecl */
443         0, /* attributeDecl */
444         0, /* elementDecl */
445         0, /* unparsedEntityDecl */
446         0, /* setDocumentLocator */
447         0, /* startDocument */
448         0, /* endDocument */
449         (startElementSAXFunc)saxStartElement, /* startElement */
450         (endElementSAXFunc)saxEndElement, /* endElement */
451         0, /* reference */
452         (charactersSAXFunc)saxCharacters, /* characters */
453         0, /* ignorableWhitespace */
454         0, /* processingInstruction */
455         (commentSAXFunc)saxComment, /* comment */
456         (warningSAXFunc)saxWarning, /* warning */
457         (errorSAXFunc)saxError, /* error */
458         (fatalErrorSAXFunc)saxFatal, /* fatalError */
459         0,
460         0,
461         0,
462         0,
463         0,
464         0,
465         0,
466         0
467 };
468
469 // ------------------------------------------------------------------------------------------------
470
471
472 guint s_routine_id = 0;
473 static gint watchbsp_routine( gpointer data ){
474         reinterpret_cast<CWatchBSP*>( data )->RoutineProcessing();
475         return TRUE;
476 }
477
478 void CWatchBSP::Reset(){
479         if ( m_pInSocket ) {
480                 Net_Disconnect( m_pInSocket );
481                 m_pInSocket = NULL;
482         }
483         if ( m_pListenSocket ) {
484                 Net_Disconnect( m_pListenSocket );
485                 m_pListenSocket = NULL;
486         }
487         if ( m_xmlInputBuffer ) {
488                 xmlFreeParserInputBuffer( m_xmlInputBuffer );
489                 m_xmlInputBuffer = NULL;
490         }
491         m_eState = EIdle;
492         if ( s_routine_id != 0 ) {
493                 g_source_remove( s_routine_id );
494                 s_routine_id = 0;
495         }
496 }
497
498 bool CWatchBSP::SetupListening(){
499 #if GDEF_DEBUG
500         if ( m_pListenSocket ) {
501                 globalOutputStream() << "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n";
502                 return false;
503         }
504 #endif
505         globalOutputStream() << "Setting up\n";
506         Net_Setup();
507         m_pListenSocket = Net_ListenSocket( 39000 );
508         if ( m_pListenSocket == NULL ) {
509                 return false;
510         }
511         globalOutputStream() << "Listening...\n";
512         return true;
513 }
514
515 void CWatchBSP::DoEBeginStep(){
516         Reset();
517         if ( SetupListening() == false ) {
518                 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";
519                 globalOutputStream() << msg;
520                 ui::alert( MainFrame_getWindow(), msg, "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error );
521                 return;
522         }
523         // set the timer for timeouts and step cancellation
524         g_timer_reset( m_pTimer );
525         g_timer_start( m_pTimer );
526
527         if ( !m_bBSPPlugin ) {
528                 globalOutputStream() << "=== running build command ===\n"
529                                                          << static_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) ) << "\n";
530
531                 if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true, false ) ) {
532                         StringOutputStream msg( 256 );
533                         msg << "Failed to execute the following command: ";
534                         msg << reinterpret_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
535                         msg << "\nCheck that the file exists and that you don't run out of system resources.\n";
536                         globalOutputStream() << msg.c_str();
537                         ui::alert( MainFrame_getWindow(), msg.c_str(), "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error );
538                         return;
539                 }
540                 // re-initialise the debug window
541                 if ( m_iCurrentStep == 0 ) {
542                         g_DbgDlg.Init();
543                 }
544         }
545         m_eState = EBeginStep;
546         s_routine_id = g_timeout_add( 25, watchbsp_routine, this );
547 }
548
549
550 #if GDEF_OS_WINDOWS
551 const char *ENGINE_ATTRIBUTE = "engine_win32";
552 const char *MP_ENGINE_ATTRIBUTE = "mp_engine_win32";
553 #elif GDEF_OS_LINUX || GDEF_OS_BSD
554 const char *ENGINE_ATTRIBUTE = "engine_linux";
555 const char *MP_ENGINE_ATTRIBUTE = "mp_engine_linux";
556 #elif GDEF_OS_MACOS
557 const char *ENGINE_ATTRIBUTE = "engine_macos";
558 const char *MP_ENGINE_ATTRIBUTE = "mp_engine_macos";
559 #else
560 #error "unsupported platform"
561 #endif
562
563 class RunEngineConfiguration
564 {
565 public:
566         const char* executable;
567         const char* mp_executable;
568         bool do_sp_mp;
569
570         RunEngineConfiguration() :
571                 executable( g_pGameDescription->getRequiredKeyValue( ENGINE_ATTRIBUTE ) ),
572                 mp_executable( g_pGameDescription->getKeyValue( MP_ENGINE_ATTRIBUTE ) ){
573                 do_sp_mp = !string_empty( mp_executable );
574         }
575 };
576
577 inline void GlobalGameDescription_string_write_mapparameter( StringOutputStream& string, const char* mapname ){
578         if ( g_pGameDescription->mGameType == "q2"
579                  || g_pGameDescription->mGameType == "heretic2" ) {
580                 string << ". +exec radiant.cfg +map " << mapname;
581         }
582         else
583         {
584                 string << "+set sv_pure 0 ";
585                 // TTimo: a check for vm_* but that's all fine
586                 //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
587                 const char* fs_game = gamename_get();
588                 if ( !string_equal( fs_game, basegame_get() ) ) {
589                         string << "+set fs_game " << fs_game << " ";
590                 }
591                 if ( g_pGameDescription->mGameType == "wolf" ) {
592                         //|| g_pGameDescription->mGameType == "et")
593                         if ( string_equal( gamemode_get(), "mp" ) ) {
594                                 // MP
595                                 string << "+devmap " << mapname;
596                         }
597                         else
598                         {
599                                 // SP
600                                 string << "+set nextmap \"spdevmap " << mapname << "\"";
601                         }
602                 }
603                 else
604                 {
605                         string << "+devmap " << mapname;
606                 }
607         }
608 }
609
610
611 void CWatchBSP::RoutineProcessing(){
612         switch ( m_eState )
613         {
614         case EBeginStep:
615                 // timeout: if we don't get an incoming connection fast enough, go back to idle
616                 if ( g_timer_elapsed( m_pTimer, NULL ) > g_WatchBSP_Timeout ) {
617                         ui::alert( 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", ui::alert_type::OK );
618                         EndMonitoringLoop();
619 #if 0
620                         if ( m_bBSPPlugin ) {
621                                 // status == 1 : didn't get the connection
622                                 g_BSPFrontendTable.m_pfnEndListen( 1 );
623                         }
624 #endif
625                         return;
626                 }
627 #if GDEF_DEBUG
628                 // some debug checks
629                 if ( !m_pListenSocket ) {
630                         globalErrorStream() << "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n";
631                         return;
632                 }
633 #endif
634                 // we are not connected yet, accept any incoming connection
635                 m_pInSocket = Net_Accept( m_pListenSocket );
636                 if ( m_pInSocket ) {
637                         globalOutputStream() << "Connected.\n";
638                         // prepare the message info struct for diving in
639                         memset( &m_message_info, 0, sizeof( message_info_t ) );
640                         // a dumb flag to make sure we init the push parser context when first getting a msg
641                         m_bNeedCtxtInit = true;
642                         m_eState = EWatching;
643                 }
644                 break;
645         case EWatching:
646         {
647 #if GDEF_DEBUG
648                 // some debug checks
649                 if ( !m_pInSocket ) {
650                         globalErrorStream() << "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n";
651                         return;
652                 }
653 #endif
654
655                 int ret = Net_Wait( m_pInSocket, 0, 0 );
656                 if ( ret == -1 ) {
657                         globalOutputStream() << "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n";
658                         globalOutputStream() << "Terminating the connection.\n";
659                         EndMonitoringLoop();
660                         return;
661                 }
662
663                 if ( ret == 1 ) {
664                         // the socket has been identified, there's something (message or disconnection)
665                         // see if there's anything in input
666                         ret = Net_Receive( m_pInSocket, &msg );
667                         if ( ret > 0 ) {
668                                 //        unsigned int size = msg.size; //++timo just a check
669                                 strcpy( m_xmlBuf, NMSG_ReadString( &msg ) );
670                                 if ( m_bNeedCtxtInit ) {
671                                         m_xmlParserCtxt = NULL;
672                                         m_xmlParserCtxt = xmlCreatePushParserCtxt( &saxParser, &m_message_info, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), NULL );
673
674                                         if ( m_xmlParserCtxt == NULL ) {
675                                                 globalErrorStream() << "Failed to create the XML parser (incoming stream began with: " << m_xmlBuf << ")\n";
676                                                 EndMonitoringLoop();
677                                         }
678                                         m_bNeedCtxtInit = false;
679                                 }
680                                 else
681                                 {
682                                         xmlParseChunk( m_xmlParserCtxt, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), 0 );
683                                 }
684                         }
685                         else
686                         {
687                                 message_flush( &m_message_info );
688                                 // error or connection closed/reset
689                                 // NOTE: if we get an error down the XML stream we don't reach here
690                                 Net_Disconnect( m_pInSocket );
691                                 m_pInSocket = NULL;
692                                 globalOutputStream() << "Connection closed.\n";
693 #if 0
694                                 if ( m_bBSPPlugin ) {
695                                         EndMonitoringLoop();
696                                         // let the BSP plugin know that the job is done
697                                         g_BSPFrontendTable.m_pfnEndListen( 0 );
698                                         return;
699                                 }
700 #endif
701                                 // move to next step or finish
702                                 m_iCurrentStep++;
703                                 if ( m_iCurrentStep < m_pCmd->len ) {
704                                         DoEBeginStep();
705                                 }
706                                 else
707                                 {
708                                         // launch the engine .. OMG
709                                         if ( g_WatchBSP_RunQuake ) {
710 #if 0
711                                                 // do we enter sleep mode before?
712                                                 if ( g_WatchBSP_DoSleep ) {
713                                                         globalOutputStream() << "Going into sleep mode..\n";
714                                                         g_pParentWnd->OnSleep();
715                                                 }
716 #endif
717                                                 globalOutputStream() << "Running engine...\n";
718                                                 StringOutputStream cmd( 256 );
719                                                 // build the command line
720                                                 cmd << EnginePath_get();
721                                                 // this is game dependant
722
723                                                 RunEngineConfiguration engineConfig;
724
725                                                 if ( engineConfig.do_sp_mp ) {
726                                                         if ( string_equal( gamemode_get(), "mp" ) ) {
727                                                                 cmd << engineConfig.mp_executable;
728                                                         }
729                                                         else
730                                                         {
731                                                                 cmd << engineConfig.executable;
732                                                         }
733                                                 }
734                                                 else
735                                                 {
736                                                         cmd << engineConfig.executable;
737                                                 }
738
739                                                 StringOutputStream cmdline;
740
741                                                 GlobalGameDescription_string_write_mapparameter( cmdline, m_sBSPName );
742
743                                                 globalOutputStream() << cmd.c_str() << " " << cmdline.c_str() << "\n";
744
745                                                 // execute now
746                                                 if ( !Q_Exec( cmd.c_str(), (char *)cmdline.c_str(), EnginePath_get(), false, false ) ) {
747                                                         StringOutputStream msg;
748                                                         msg << "Failed to execute the following command: " << cmd.c_str() << cmdline.c_str();
749                                                         globalOutputStream() << msg.c_str();
750                                                         ui::alert( MainFrame_getWindow(), msg.c_str(), "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error );
751                                                 }
752                                         }
753                                         EndMonitoringLoop();
754                                 }
755                         }
756                 }
757         }
758         break;
759         default:
760                 break;
761         }
762 }
763
764 GPtrArray* str_ptr_array_clone( GPtrArray* array ){
765         GPtrArray* cloned = g_ptr_array_sized_new( array->len );
766         for ( guint i = 0; i < array->len; ++i )
767         {
768                 g_ptr_array_add( cloned, g_strdup( (char*)g_ptr_array_index( array, i ) ) );
769         }
770         return cloned;
771 }
772
773 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName ){
774         m_sBSPName = string_clone( sBSPName );
775         if ( m_eState != EIdle ) {
776                 globalOutputStream() << "WatchBSP got a monitoring request while not idling...\n";
777                 // prompt the user, should we cancel the current process and go ahead?
778                 if ( ui::alert( MainFrame_getWindow(), "I am already monitoring a Build process.\nDo you want me to override and start a new compilation?",
779                                                          "Build process monitoring", ui::alert_type::YESNO ) == ui::alert_response::YES ) {
780                         // disconnect and set EIdle state
781                         Reset();
782                 }
783         }
784         m_pCmd = str_ptr_array_clone( pCmd );
785         m_iCurrentStep = 0;
786         DoEBeginStep();
787 }
788
789 void CWatchBSP::ExternalListen(){
790         m_bBSPPlugin = true;
791         DoEBeginStep();
792 }
793
794 // the part of the watchbsp interface we export to plugins
795 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
796 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
797 void QERApp_Listen(){
798         // open the listening socket
799         GetWatchBSP()->ExternalListen();
800 }