]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/watchbsp.cpp
radiant: fix xy/yz/xz layout
[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         g_pWatchBSP = NULL;
215 }
216
217 CWatchBSP *GetWatchBSP(){
218         return g_pWatchBSP;
219 }
220
221 void BuildMonitor_Run( GPtrArray* commands, const char* mapName ){
222         GetWatchBSP()->DoMonitoringLoop( commands, mapName );
223 }
224
225
226 // Static functions for the SAX callbacks -------------------------------------------------------
227
228 // utility for saxStartElement below
229 static void abortStream( message_info_t *data ){
230         GetWatchBSP()->EndMonitoringLoop();
231         // tell there has been an error
232 #if 0
233         if ( GetWatchBSP()->HasBSPPlugin() ) {
234                 g_BSPFrontendTable.m_pfnEndListen( 2 );
235         }
236 #endif
237         // yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out
238         data->ignore_depth = -1;
239         data->recurse++;
240 }
241
242 #include "stream_version.h"
243
244 static void saxStartElement( message_info_t *data, const xmlChar *name, const xmlChar **attrs ){
245 #if 0
246         globalOutputStream() << "<" << name;
247         if ( attrs != 0 ) {
248                 for ( const xmlChar** p = attrs; *p != 0; p += 2 )
249                 {
250                         globalOutputStream() << " " << p[0] << "=" << makeQuoted( p[1] );
251                 }
252         }
253         globalOutputStream() << ">\n";
254 #endif
255
256         if ( data->ignore_depth == 0 ) {
257                 if ( data->pGeometry != 0 ) {
258                         // we have a handler
259                         data->pGeometry->saxStartElement( data, name, attrs );
260                 }
261                 else
262                 {
263                         if ( strcmp( reinterpret_cast<const char*>( name ), "q3map_feedback" ) == 0 ) {
264                                 // check the correct version
265                                 // old q3map don't send a version attribute
266                                 // the ones we support .. send Q3MAP_STREAM_VERSION
267                                 if ( !attrs[0] || !attrs[1] || ( strcmp( reinterpret_cast<const char*>( attrs[0] ), "version" ) != 0 ) ) {
268                                         message_flush( data );
269                                         globalErrorStream() << "No stream version given in the feedback stream, this is an old q3map version.\n"
270                                                                                    "Please turn off monitored compiling if you still wish to use this q3map executable\n";
271                                         abortStream( data );
272                                         return;
273                                 }
274                                 else if ( strcmp( reinterpret_cast<const char*>( attrs[1] ), Q3MAP_STREAM_VERSION ) != 0 ) {
275                                         message_flush( data );
276                                         globalErrorStream() <<
277                                         "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"
278                                                                                                                                                                                                                                                                                                                                                                                            "Please make sure your versions of " RADIANT_NAME " and q3map are matching.\n";
279                                         abortStream( data );
280                                         return;
281                                 }
282                         }
283                         // we don't treat locally
284                         else if ( strcmp( reinterpret_cast<const char*>( name ), "message" ) == 0 ) {
285                                 int msg_level = atoi( reinterpret_cast<const char*>( attrs[1] ) );
286                                 if ( msg_level != data->msg_level ) {
287                                         message_flush( data );
288                                         data->msg_level = msg_level;
289                                 }
290                         }
291                         else if ( strcmp( reinterpret_cast<const char*>( name ), "polyline" ) == 0 ) {
292                                 // polyline has a particular status .. right now we only use it for leakfile ..
293                                 data->geometry_depth = data->recurse;
294                                 data->pGeometry = &g_pointfile;
295                                 data->pGeometry->saxStartElement( data, name, attrs );
296                         }
297                         else if ( strcmp( reinterpret_cast<const char*>( name ), "select" ) == 0 ) {
298                                 CSelectMsg *pSelect = new CSelectMsg();
299                                 data->geometry_depth = data->recurse;
300                                 data->pGeometry = pSelect;
301                                 data->pGeometry->saxStartElement( data, name, attrs );
302                         }
303                         else if ( strcmp( reinterpret_cast<const char*>( name ), "pointmsg" ) == 0 ) {
304                                 CPointMsg *pPoint = new CPointMsg();
305                                 data->geometry_depth = data->recurse;
306                                 data->pGeometry = pPoint;
307                                 data->pGeometry->saxStartElement( data, name, attrs );
308                         }
309                         else if ( strcmp( reinterpret_cast<const char*>( name ), "windingmsg" ) == 0 ) {
310                                 CWindingMsg *pWinding = new CWindingMsg();
311                                 data->geometry_depth = data->recurse;
312                                 data->pGeometry = pWinding;
313                                 data->pGeometry->saxStartElement( data, name, attrs );
314                         }
315                         else
316                         {
317                                 globalErrorStream() << "Warning: ignoring unrecognized node in XML stream (" << reinterpret_cast<const char*>( name ) << ")\n";
318                                 // we don't recognize this node, jump over it
319                                 // (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level)
320                                 data->ignore_depth = data->recurse;
321                         }
322                 }
323         }
324         data->recurse++;
325 }
326
327 static void saxEndElement( message_info_t *data, const xmlChar *name ){
328 #if 0
329         globalOutputStream() << "<" << name << "/>\n";
330 #endif
331
332         data->recurse--;
333         // we are out of an ignored chunk
334         if ( data->recurse == data->ignore_depth ) {
335                 data->ignore_depth = 0;
336                 return;
337         }
338         if ( data->pGeometry != 0 ) {
339                 data->pGeometry->saxEndElement( data, name );
340                 // we add the object to the debug window
341                 if ( data->geometry_depth == data->recurse ) {
342                         g_DbgDlg.Push( data->pGeometry );
343                         data->pGeometry = 0;
344                 }
345         }
346         if ( data->recurse == data->stop_depth ) {
347                 message_flush( data );
348 #if GDEF_DEBUG
349                 globalOutputStream() << "Received error msg .. shutting down..\n";
350 #endif
351                 GetWatchBSP()->EndMonitoringLoop();
352                 // tell there has been an error
353 #if 0
354                 if ( GetWatchBSP()->HasBSPPlugin() ) {
355                         g_BSPFrontendTable.m_pfnEndListen( 2 );
356                 }
357 #endif
358                 return;
359         }
360 }
361
362 class MessageOutputStream : public TextOutputStream
363 {
364         message_info_t* m_data;
365 public:
366         MessageOutputStream( message_info_t* data ) : m_data( data ){
367         }
368
369         std::size_t write( const char* buffer, std::size_t length ){
370                 if ( m_data->pGeometry != 0 ) {
371                         m_data->pGeometry->saxCharacters( m_data, reinterpret_cast<const xmlChar*>( buffer ), int(length) );
372                 }
373                 else
374                 {
375                         if ( m_data->ignore_depth == 0 ) {
376                                 // output the message using the level
377                                 message_print( m_data, buffer, length );
378                                 // if this message has error level flag, we mark the depth to stop the compilation when we get out
379                                 // we don't set the msg level if we don't stop on leak
380                                 if ( m_data->msg_level == 3 ) {
381                                         m_data->stop_depth = m_data->recurse - 1;
382                                 }
383                         }
384                 }
385
386                 return length;
387         }
388 };
389
390 template<typename T>
391 inline MessageOutputStream& operator<<( MessageOutputStream& ostream, const T& t ){
392         return ostream_write( ostream, t );
393 }
394
395 static void saxCharacters( message_info_t *data, const xmlChar *ch, int len ){
396         MessageOutputStream ostream( data );
397         ostream << StringRange( reinterpret_cast<const char*>( ch ), reinterpret_cast<const char*>( ch + len ) );
398 }
399
400 static void saxComment( void *ctx, const xmlChar *msg ){
401         globalOutputStream() << "XML comment: " << reinterpret_cast<const char*>( msg ) << "\n";
402 }
403
404 static void saxWarning( void *ctx, const char *msg, ... ){
405         char saxMsgBuffer[4096];
406         va_list args;
407
408         va_start( args, msg );
409         vsprintf( saxMsgBuffer, msg, args );
410         va_end( args );
411         globalOutputStream() << "XML warning: " << saxMsgBuffer << "\n";
412 }
413
414 static void saxError( void *ctx, const char *msg, ... ){
415         char saxMsgBuffer[4096];
416         va_list args;
417
418         va_start( args, msg );
419         vsprintf( saxMsgBuffer, msg, args );
420         va_end( args );
421         globalErrorStream() << "XML error: " << saxMsgBuffer << "\n";
422 }
423
424 static void saxFatal( void *ctx, const char *msg, ... ){
425         char buffer[4096];
426
427         va_list args;
428
429         va_start( args, msg );
430         vsprintf( buffer, msg, args );
431         va_end( args );
432         globalErrorStream() << "XML fatal error: " << buffer << "\n";
433 }
434
435 static xmlSAXHandler saxParser = {
436         0, /* internalSubset */
437         0, /* isStandalone */
438         0, /* hasInternalSubset */
439         0, /* hasExternalSubset */
440         0, /* resolveEntity */
441         0, /* getEntity */
442         0, /* entityDecl */
443         0, /* notationDecl */
444         0, /* attributeDecl */
445         0, /* elementDecl */
446         0, /* unparsedEntityDecl */
447         0, /* setDocumentLocator */
448         0, /* startDocument */
449         0, /* endDocument */
450         (startElementSAXFunc)saxStartElement, /* startElement */
451         (endElementSAXFunc)saxEndElement, /* endElement */
452         0, /* reference */
453         (charactersSAXFunc)saxCharacters, /* characters */
454         0, /* ignorableWhitespace */
455         0, /* processingInstruction */
456         (commentSAXFunc)saxComment, /* comment */
457         (warningSAXFunc)saxWarning, /* warning */
458         (errorSAXFunc)saxError, /* error */
459         (fatalErrorSAXFunc)saxFatal, /* fatalError */
460         0,
461         0,
462         0,
463         0,
464         0,
465         0,
466         0,
467         0
468 };
469
470 // ------------------------------------------------------------------------------------------------
471
472
473 guint s_routine_id = 0;
474 static gint watchbsp_routine( gpointer data ){
475         reinterpret_cast<CWatchBSP*>( data )->RoutineProcessing();
476         return TRUE;
477 }
478
479 void CWatchBSP::Reset(){
480         if ( m_pInSocket ) {
481                 Net_Disconnect( m_pInSocket );
482                 m_pInSocket = NULL;
483         }
484         if ( m_pListenSocket ) {
485                 Net_Disconnect( m_pListenSocket );
486                 m_pListenSocket = NULL;
487         }
488         if ( m_xmlInputBuffer ) {
489                 xmlFreeParserInputBuffer( m_xmlInputBuffer );
490                 m_xmlInputBuffer = NULL;
491         }
492         m_eState = EIdle;
493         if ( s_routine_id != 0 ) {
494                 g_source_remove( s_routine_id );
495                 s_routine_id = 0;
496         }
497 }
498
499 bool CWatchBSP::SetupListening(){
500 #if GDEF_DEBUG
501         if ( m_pListenSocket ) {
502                 globalOutputStream() << "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n";
503                 return false;
504         }
505 #endif
506         globalOutputStream() << "Setting up\n";
507         Net_Setup();
508         m_pListenSocket = Net_ListenSocket( 39000 );
509         if ( m_pListenSocket == NULL ) {
510                 return false;
511         }
512         globalOutputStream() << "Listening...\n";
513         return true;
514 }
515
516 void CWatchBSP::DoEBeginStep(){
517         Reset();
518         if ( SetupListening() == false ) {
519                 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";
520                 globalOutputStream() << msg;
521                 ui::alert( MainFrame_getWindow(), msg, "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error );
522                 return;
523         }
524         // set the timer for timeouts and step cancellation
525         g_timer_reset( m_pTimer );
526         g_timer_start( m_pTimer );
527
528         if ( !m_bBSPPlugin ) {
529                 globalOutputStream() << "=== running build command ===\n"
530                                                          << static_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) ) << "\n";
531
532                 if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true, false ) ) {
533                         StringOutputStream msg( 256 );
534                         msg << "Failed to execute the following command: ";
535                         msg << reinterpret_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
536                         msg << "\nCheck that the file exists and that you don't run out of system resources.\n";
537                         globalOutputStream() << msg.c_str();
538                         ui::alert( MainFrame_getWindow(), msg.c_str(), "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error );
539                         return;
540                 }
541                 // re-initialise the debug window
542                 if ( m_iCurrentStep == 0 ) {
543                         g_DbgDlg.Init();
544                 }
545         }
546         m_eState = EBeginStep;
547         s_routine_id = g_timeout_add( 25, watchbsp_routine, this );
548 }
549
550
551 #if GDEF_OS_WINDOWS
552 const char *ENGINE_ATTRIBUTE = "engine_win32";
553 const char *MP_ENGINE_ATTRIBUTE = "mp_engine_win32";
554 #elif GDEF_OS_LINUX || GDEF_OS_BSD
555 const char *ENGINE_ATTRIBUTE = "engine_linux";
556 const char *MP_ENGINE_ATTRIBUTE = "mp_engine_linux";
557 #elif GDEF_OS_MACOS
558 const char *ENGINE_ATTRIBUTE = "engine_macos";
559 const char *MP_ENGINE_ATTRIBUTE = "mp_engine_macos";
560 #else
561 #error "unsupported platform"
562 #endif
563
564 class RunEngineConfiguration
565 {
566 public:
567         const char* executable;
568         const char* mp_executable;
569         bool do_sp_mp;
570
571         RunEngineConfiguration() :
572                 executable( g_pGameDescription->getRequiredKeyValue( ENGINE_ATTRIBUTE ) ),
573                 mp_executable( g_pGameDescription->getKeyValue( MP_ENGINE_ATTRIBUTE ) ){
574                 do_sp_mp = !string_empty( mp_executable );
575         }
576 };
577
578 inline void GlobalGameDescription_string_write_mapparameter( StringOutputStream& string, const char* mapname ){
579         if ( g_pGameDescription->mGameType == "q2"
580                  || g_pGameDescription->mGameType == "heretic2" ) {
581                 string << ". +exec radiant.cfg +map " << mapname;
582         }
583         else
584         {
585                 string << "+set sv_pure 0 ";
586                 // TTimo: a check for vm_* but that's all fine
587                 //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
588                 const char* fs_game = gamename_get();
589                 if ( !string_equal( fs_game, basegame_get() ) ) {
590                         string << "+set fs_game " << fs_game << " ";
591                 }
592                 if ( g_pGameDescription->mGameType == "wolf" ) {
593                         //|| g_pGameDescription->mGameType == "et")
594                         if ( string_equal( gamemode_get(), "mp" ) ) {
595                                 // MP
596                                 string << "+devmap " << mapname;
597                         }
598                         else
599                         {
600                                 // SP
601                                 string << "+set nextmap \"spdevmap " << mapname << "\"";
602                         }
603                 }
604                 else
605                 {
606                         string << "+devmap " << mapname;
607                 }
608         }
609 }
610
611
612 void CWatchBSP::RoutineProcessing(){
613         switch ( m_eState )
614         {
615         case EBeginStep:
616                 // timeout: if we don't get an incoming connection fast enough, go back to idle
617                 if ( g_timer_elapsed( m_pTimer, NULL ) > g_WatchBSP_Timeout ) {
618                         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 );
619                         EndMonitoringLoop();
620 #if 0
621                         if ( m_bBSPPlugin ) {
622                                 // status == 1 : didn't get the connection
623                                 g_BSPFrontendTable.m_pfnEndListen( 1 );
624                         }
625 #endif
626                         return;
627                 }
628 #if GDEF_DEBUG
629                 // some debug checks
630                 if ( !m_pListenSocket ) {
631                         globalErrorStream() << "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n";
632                         return;
633                 }
634 #endif
635                 // we are not connected yet, accept any incoming connection
636                 m_pInSocket = Net_Accept( m_pListenSocket );
637                 if ( m_pInSocket ) {
638                         globalOutputStream() << "Connected.\n";
639                         // prepare the message info struct for diving in
640                         memset( &m_message_info, 0, sizeof( message_info_t ) );
641                         // a dumb flag to make sure we init the push parser context when first getting a msg
642                         m_bNeedCtxtInit = true;
643                         m_eState = EWatching;
644                 }
645                 break;
646         case EWatching:
647         {
648 #if GDEF_DEBUG
649                 // some debug checks
650                 if ( !m_pInSocket ) {
651                         globalErrorStream() << "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n";
652                         return;
653                 }
654 #endif
655
656                 int ret = Net_Wait( m_pInSocket, 0, 0 );
657                 if ( ret == -1 ) {
658                         globalOutputStream() << "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n";
659                         globalOutputStream() << "Terminating the connection.\n";
660                         EndMonitoringLoop();
661                         return;
662                 }
663
664                 if ( ret == 1 ) {
665                         // the socket has been identified, there's something (message or disconnection)
666                         // see if there's anything in input
667                         ret = Net_Receive( m_pInSocket, &msg );
668                         if ( ret > 0 ) {
669                                 //        unsigned int size = msg.size; //++timo just a check
670                                 strcpy( m_xmlBuf, NMSG_ReadString( &msg ) );
671                                 if ( m_bNeedCtxtInit ) {
672                                         m_xmlParserCtxt = NULL;
673                                         m_xmlParserCtxt = xmlCreatePushParserCtxt( &saxParser, &m_message_info, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), NULL );
674
675                                         if ( m_xmlParserCtxt == NULL ) {
676                                                 globalErrorStream() << "Failed to create the XML parser (incoming stream began with: " << m_xmlBuf << ")\n";
677                                                 EndMonitoringLoop();
678                                         }
679                                         m_bNeedCtxtInit = false;
680                                 }
681                                 else
682                                 {
683                                         xmlParseChunk( m_xmlParserCtxt, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), 0 );
684                                 }
685                         }
686                         else
687                         {
688                                 message_flush( &m_message_info );
689                                 // error or connection closed/reset
690                                 // NOTE: if we get an error down the XML stream we don't reach here
691                                 Net_Disconnect( m_pInSocket );
692                                 m_pInSocket = NULL;
693                                 globalOutputStream() << "Connection closed.\n";
694 #if 0
695                                 if ( m_bBSPPlugin ) {
696                                         EndMonitoringLoop();
697                                         // let the BSP plugin know that the job is done
698                                         g_BSPFrontendTable.m_pfnEndListen( 0 );
699                                         return;
700                                 }
701 #endif
702                                 // move to next step or finish
703                                 m_iCurrentStep++;
704                                 if ( m_iCurrentStep < m_pCmd->len ) {
705                                         DoEBeginStep();
706                                 }
707                                 else
708                                 {
709                                         // launch the engine .. OMG
710                                         if ( g_WatchBSP_RunQuake ) {
711 #if 0
712                                                 // do we enter sleep mode before?
713                                                 if ( g_WatchBSP_DoSleep ) {
714                                                         globalOutputStream() << "Going into sleep mode..\n";
715                                                         g_pParentWnd->OnSleep();
716                                                 }
717 #endif
718                                                 globalOutputStream() << "Running engine...\n";
719                                                 StringOutputStream cmd( 256 );
720                                                 // build the command line
721                                                 cmd << EnginePath_get();
722                                                 // this is game dependant
723
724                                                 RunEngineConfiguration engineConfig;
725
726                                                 if ( engineConfig.do_sp_mp ) {
727                                                         if ( string_equal( gamemode_get(), "mp" ) ) {
728                                                                 cmd << engineConfig.mp_executable;
729                                                         }
730                                                         else
731                                                         {
732                                                                 cmd << engineConfig.executable;
733                                                         }
734                                                 }
735                                                 else
736                                                 {
737                                                         cmd << engineConfig.executable;
738                                                 }
739
740                                                 StringOutputStream cmdline;
741
742                                                 GlobalGameDescription_string_write_mapparameter( cmdline, m_sBSPName );
743
744                                                 globalOutputStream() << cmd.c_str() << " " << cmdline.c_str() << "\n";
745
746                                                 // execute now
747                                                 if ( !Q_Exec( cmd.c_str(), (char *)cmdline.c_str(), EnginePath_get(), false, false ) ) {
748                                                         StringOutputStream msg;
749                                                         msg << "Failed to execute the following command: " << cmd.c_str() << cmdline.c_str();
750                                                         globalOutputStream() << msg.c_str();
751                                                         ui::alert( MainFrame_getWindow(), msg.c_str(), "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error );
752                                                 }
753                                         }
754                                         EndMonitoringLoop();
755                                 }
756                         }
757                 }
758         }
759         break;
760         default:
761                 break;
762         }
763 }
764
765 GPtrArray* str_ptr_array_clone( GPtrArray* array ){
766         GPtrArray* cloned = g_ptr_array_sized_new( array->len );
767         for ( guint i = 0; i < array->len; ++i )
768         {
769                 g_ptr_array_add( cloned, g_strdup( (char*)g_ptr_array_index( array, i ) ) );
770         }
771         return cloned;
772 }
773
774 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName ){
775         m_sBSPName = string_clone( sBSPName );
776         if ( m_eState != EIdle ) {
777                 globalOutputStream() << "WatchBSP got a monitoring request while not idling...\n";
778                 // prompt the user, should we cancel the current process and go ahead?
779                 if ( ui::alert( MainFrame_getWindow(), "I am already monitoring a Build process.\nDo you want me to override and start a new compilation?",
780                                                          "Build process monitoring", ui::alert_type::YESNO ) == ui::alert_response::YES ) {
781                         // disconnect and set EIdle state
782                         Reset();
783                 }
784         }
785         m_pCmd = str_ptr_array_clone( pCmd );
786         m_iCurrentStep = 0;
787         DoEBeginStep();
788 }
789
790 void CWatchBSP::ExternalListen(){
791         m_bBSPPlugin = true;
792         DoEBeginStep();
793 }
794
795 // the part of the watchbsp interface we export to plugins
796 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
797 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
798 void QERApp_Listen(){
799         // open the listening socket
800         GetWatchBSP()->ExternalListen();
801 }