]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/watchbsp.cpp
Merge branch 'mschwan/gdef-inline-fix' into '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 #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 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 #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;
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 ) {
493                 g_source_remove( s_routine_id );
494         }
495 }
496
497 bool CWatchBSP::SetupListening(){
498 #if GDEF_DEBUG
499         if ( m_pListenSocket ) {
500                 globalOutputStream() << "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n";
501                 return false;
502         }
503 #endif
504         globalOutputStream() << "Setting up\n";
505         Net_Setup();
506         m_pListenSocket = Net_ListenSocket( 39000 );
507         if ( m_pListenSocket == NULL ) {
508                 return false;
509         }
510         globalOutputStream() << "Listening...\n";
511         return true;
512 }
513
514 void CWatchBSP::DoEBeginStep(){
515         Reset();
516         if ( SetupListening() == false ) {
517                 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";
518                 globalOutputStream() << msg;
519                 ui::alert( MainFrame_getWindow(), msg, "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error );
520                 return;
521         }
522         // set the timer for timeouts and step cancellation
523         g_timer_reset( m_pTimer );
524         g_timer_start( m_pTimer );
525
526         if ( !m_bBSPPlugin ) {
527                 globalOutputStream() << "=== running build command ===\n"
528                                                          << static_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) ) << "\n";
529
530                 if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true, false ) ) {
531                         StringOutputStream msg( 256 );
532                         msg << "Failed to execute the following command: ";
533                         msg << reinterpret_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
534                         msg << "\nCheck that the file exists and that you don't run out of system resources.\n";
535                         globalOutputStream() << msg.c_str();
536                         ui::alert( MainFrame_getWindow(), msg.c_str(), "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error );
537                         return;
538                 }
539                 // re-initialise the debug window
540                 if ( m_iCurrentStep == 0 ) {
541                         g_DbgDlg.Init();
542                 }
543         }
544         m_eState = EBeginStep;
545         s_routine_id = g_timeout_add( 25, watchbsp_routine, this );
546 }
547
548
549 #if GDEF_OS_WINDOWS
550 const char *ENGINE_ATTRIBUTE = "engine_win32";
551 const char *MP_ENGINE_ATTRIBUTE = "mp_engine_win32";
552 #elif GDEF_OS_LINUX || GDEF_OS_BSD
553 const char *ENGINE_ATTRIBUTE = "engine_linux";
554 const char *MP_ENGINE_ATTRIBUTE = "mp_engine_linux";
555 #elif GDEF_OS_MACOS
556 const char *ENGINE_ATTRIBUTE = "engine_macos";
557 const char *MP_ENGINE_ATTRIBUTE = "mp_engine_macos";
558 #else
559 #error "unsupported platform"
560 #endif
561
562 class RunEngineConfiguration
563 {
564 public:
565         const char* executable;
566         const char* mp_executable;
567         bool do_sp_mp;
568
569         RunEngineConfiguration() :
570                 executable( g_pGameDescription->getRequiredKeyValue( ENGINE_ATTRIBUTE ) ),
571                 mp_executable( g_pGameDescription->getKeyValue( MP_ENGINE_ATTRIBUTE ) ){
572                 do_sp_mp = !string_empty( mp_executable );
573         }
574 };
575
576 inline void GlobalGameDescription_string_write_mapparameter( StringOutputStream& string, const char* mapname ){
577         if ( g_pGameDescription->mGameType == "q2"
578                  || g_pGameDescription->mGameType == "heretic2" ) {
579                 string << ". +exec radiant.cfg +map " << mapname;
580         }
581         else
582         {
583                 string << "+set sv_pure 0 ";
584                 // TTimo: a check for vm_* but that's all fine
585                 //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
586                 const char* fs_game = gamename_get();
587                 if ( !string_equal( fs_game, basegame_get() ) ) {
588                         string << "+set fs_game " << fs_game << " ";
589                 }
590                 if ( g_pGameDescription->mGameType == "wolf" ) {
591                         //|| g_pGameDescription->mGameType == "et")
592                         if ( string_equal( gamemode_get(), "mp" ) ) {
593                                 // MP
594                                 string << "+devmap " << mapname;
595                         }
596                         else
597                         {
598                                 // SP
599                                 string << "+set nextmap \"spdevmap " << mapname << "\"";
600                         }
601                 }
602                 else
603                 {
604                         string << "+devmap " << mapname;
605                 }
606         }
607 }
608
609
610 void CWatchBSP::RoutineProcessing(){
611         switch ( m_eState )
612         {
613         case EBeginStep:
614                 // timeout: if we don't get an incoming connection fast enough, go back to idle
615                 if ( g_timer_elapsed( m_pTimer, NULL ) > g_WatchBSP_Timeout ) {
616                         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 );
617                         EndMonitoringLoop();
618 #if 0
619                         if ( m_bBSPPlugin ) {
620                                 // status == 1 : didn't get the connection
621                                 g_BSPFrontendTable.m_pfnEndListen( 1 );
622                         }
623 #endif
624                         return;
625                 }
626 #if GDEF_DEBUG
627                 // some debug checks
628                 if ( !m_pListenSocket ) {
629                         globalErrorStream() << "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n";
630                         return;
631                 }
632 #endif
633                 // we are not connected yet, accept any incoming connection
634                 m_pInSocket = Net_Accept( m_pListenSocket );
635                 if ( m_pInSocket ) {
636                         globalOutputStream() << "Connected.\n";
637                         // prepare the message info struct for diving in
638                         memset( &m_message_info, 0, sizeof( message_info_t ) );
639                         // a dumb flag to make sure we init the push parser context when first getting a msg
640                         m_bNeedCtxtInit = true;
641                         m_eState = EWatching;
642                 }
643                 break;
644         case EWatching:
645         {
646 #if GDEF_DEBUG
647                 // some debug checks
648                 if ( !m_pInSocket ) {
649                         globalErrorStream() << "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n";
650                         return;
651                 }
652 #endif
653
654                 int ret = Net_Wait( m_pInSocket, 0, 0 );
655                 if ( ret == -1 ) {
656                         globalOutputStream() << "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n";
657                         globalOutputStream() << "Terminating the connection.\n";
658                         EndMonitoringLoop();
659                         return;
660                 }
661
662                 if ( ret == 1 ) {
663                         // the socket has been identified, there's something (message or disconnection)
664                         // see if there's anything in input
665                         ret = Net_Receive( m_pInSocket, &msg );
666                         if ( ret > 0 ) {
667                                 //        unsigned int size = msg.size; //++timo just a check
668                                 strcpy( m_xmlBuf, NMSG_ReadString( &msg ) );
669                                 if ( m_bNeedCtxtInit ) {
670                                         m_xmlParserCtxt = NULL;
671                                         m_xmlParserCtxt = xmlCreatePushParserCtxt( &saxParser, &m_message_info, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), NULL );
672
673                                         if ( m_xmlParserCtxt == NULL ) {
674                                                 globalErrorStream() << "Failed to create the XML parser (incoming stream began with: " << m_xmlBuf << ")\n";
675                                                 EndMonitoringLoop();
676                                         }
677                                         m_bNeedCtxtInit = false;
678                                 }
679                                 else
680                                 {
681                                         xmlParseChunk( m_xmlParserCtxt, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), 0 );
682                                 }
683                         }
684                         else
685                         {
686                                 message_flush( &m_message_info );
687                                 // error or connection closed/reset
688                                 // NOTE: if we get an error down the XML stream we don't reach here
689                                 Net_Disconnect( m_pInSocket );
690                                 m_pInSocket = NULL;
691                                 globalOutputStream() << "Connection closed.\n";
692 #if 0
693                                 if ( m_bBSPPlugin ) {
694                                         EndMonitoringLoop();
695                                         // let the BSP plugin know that the job is done
696                                         g_BSPFrontendTable.m_pfnEndListen( 0 );
697                                         return;
698                                 }
699 #endif
700                                 // move to next step or finish
701                                 m_iCurrentStep++;
702                                 if ( m_iCurrentStep < m_pCmd->len ) {
703                                         DoEBeginStep();
704                                 }
705                                 else
706                                 {
707                                         // launch the engine .. OMG
708                                         if ( g_WatchBSP_RunQuake ) {
709 #if 0
710                                                 // do we enter sleep mode before?
711                                                 if ( g_WatchBSP_DoSleep ) {
712                                                         globalOutputStream() << "Going into sleep mode..\n";
713                                                         g_pParentWnd->OnSleep();
714                                                 }
715 #endif
716                                                 globalOutputStream() << "Running engine...\n";
717                                                 StringOutputStream cmd( 256 );
718                                                 // build the command line
719                                                 cmd << EnginePath_get();
720                                                 // this is game dependant
721
722                                                 RunEngineConfiguration engineConfig;
723
724                                                 if ( engineConfig.do_sp_mp ) {
725                                                         if ( string_equal( gamemode_get(), "mp" ) ) {
726                                                                 cmd << engineConfig.mp_executable;
727                                                         }
728                                                         else
729                                                         {
730                                                                 cmd << engineConfig.executable;
731                                                         }
732                                                 }
733                                                 else
734                                                 {
735                                                         cmd << engineConfig.executable;
736                                                 }
737
738                                                 StringOutputStream cmdline;
739
740                                                 GlobalGameDescription_string_write_mapparameter( cmdline, m_sBSPName );
741
742                                                 globalOutputStream() << cmd.c_str() << " " << cmdline.c_str() << "\n";
743
744                                                 // execute now
745                                                 if ( !Q_Exec( cmd.c_str(), (char *)cmdline.c_str(), EnginePath_get(), false, false ) ) {
746                                                         StringOutputStream msg;
747                                                         msg << "Failed to execute the following command: " << cmd.c_str() << cmdline.c_str();
748                                                         globalOutputStream() << msg.c_str();
749                                                         ui::alert( MainFrame_getWindow(), msg.c_str(), "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error );
750                                                 }
751                                         }
752                                         EndMonitoringLoop();
753                                 }
754                         }
755                 }
756         }
757         break;
758         default:
759                 break;
760         }
761 }
762
763 GPtrArray* str_ptr_array_clone( GPtrArray* array ){
764         GPtrArray* cloned = g_ptr_array_sized_new( array->len );
765         for ( guint i = 0; i < array->len; ++i )
766         {
767                 g_ptr_array_add( cloned, g_strdup( (char*)g_ptr_array_index( array, i ) ) );
768         }
769         return cloned;
770 }
771
772 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName ){
773         m_sBSPName = string_clone( sBSPName );
774         if ( m_eState != EIdle ) {
775                 globalOutputStream() << "WatchBSP got a monitoring request while not idling...\n";
776                 // prompt the user, should we cancel the current process and go ahead?
777                 if ( ui::alert( MainFrame_getWindow(), "I am already monitoring a Build process.\nDo you want me to override and start a new compilation?",
778                                                          "Build process monitoring", ui::alert_type::YESNO ) == ui::alert_response::YES ) {
779                         // disconnect and set EIdle state
780                         Reset();
781                 }
782         }
783         m_pCmd = str_ptr_array_clone( pCmd );
784         m_iCurrentStep = 0;
785         DoEBeginStep();
786 }
787
788 void CWatchBSP::ExternalListen(){
789         m_bBSPPlugin = true;
790         DoEBeginStep();
791 }
792
793 // the part of the watchbsp interface we export to plugins
794 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
795 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
796 void QERApp_Listen(){
797         // open the listening socket
798         GetWatchBSP()->ExternalListen();
799 }