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