X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=radiant%2Fwatchbsp.cpp;h=bb65e57337b331c5161a7f894e9c00111481899b;hb=1c26c420f1b5dabd39b492ab017447172ef5a6c8;hp=29ce371e36353e60b366991f544212f662b865fb;hpb=830125fad042fad35dc029b6eb57c8156ad7e176;p=xonotic%2Fnetradiant.git diff --git a/radiant/watchbsp.cpp b/radiant/watchbsp.cpp index 29ce371e..bb65e573 100644 --- a/radiant/watchbsp.cpp +++ b/radiant/watchbsp.cpp @@ -33,34 +33,206 @@ // DESCRIPTION: // monitoring window for running BSP processes (and possibly various other stuff) -#include "stdafx.h" #include "watchbsp.h" +#include "globaldefs.h" + +#include + +#include "cmdlib.h" +#include "convert.h" +#include "string/string.h" +#include "stream/stringstream.h" + +#include "gtkutil/messagebox.h" +#include "xmlstuff.h" +#include "console.h" +#include "preferences.h" +#include "points.h" #include "feedback.h" +#include "mainframe.h" +#include "sockets.h" -#ifdef _WIN32 -#include -#endif +void message_flush( message_info_t* self ){ + Sys_Print( self->msg_level, self->m_buffer, self->m_length ); + self->m_length = 0; +} -#if defined ( __linux__ ) || defined ( __APPLE__ ) -#include -#define SOCKET_ERROR -1 -#endif +void message_print( message_info_t* self, const char* characters, std::size_t length ){ + const char* end = characters + length; + while ( characters != end ) + { + std::size_t space = message_info_t::bufsize - 1 - self->m_length; + if ( space == 0 ) { + message_flush( self ); + } + else + { + std::size_t size = std::min( space, std::size_t( end - characters ) ); + memcpy( self->m_buffer + self->m_length, characters, size ); + self->m_length += size; + characters += size; + } + } +} -#ifdef __APPLE__ -#include -#endif -#include +#include +#include +#include "xmlstuff.h" + +class CWatchBSP +{ +private: + // a flag we have set to true when using an external BSP plugin + // the resulting code with that is a bit dirty, cleaner solution would be to seperate the succession of commands from the listening loop + // (in two seperate classes probably) + bool m_bBSPPlugin; + + // EIdle: we are not listening + // DoMonitoringLoop will change state to EBeginStep + // EBeginStep: the socket is up for listening, we are expecting incoming connection + // incoming connection will change state to EWatching + // EWatching: we have a connection, monitor it + // connection closed will see if we start a new step (EBeginStep) or launch Quake3 and end (EIdle) + enum EWatchBSPState { EIdle, EBeginStep, EWatching } m_eState; + socket_t *m_pListenSocket; + socket_t *m_pInSocket; + netmessage_t msg; + GPtrArray *m_pCmd; + // used to timeout EBeginStep + GTimer *m_pTimer; + std::size_t m_iCurrentStep; + // name of the map so we can run the engine + char *m_sBSPName; + // buffer we use in push mode to receive data directly from the network + xmlParserInputBufferPtr m_xmlInputBuffer; + xmlParserInputPtr m_xmlInput; + xmlParserCtxtPtr m_xmlParserCtxt; + // call this to switch the set listening mode + bool SetupListening(); + // start a new EBeginStep + void DoEBeginStep(); + // the xml and sax parser state + char m_xmlBuf[MAX_NETMESSAGE]; + bool m_bNeedCtxtInit; + message_info_t m_message_info; + +public: + CWatchBSP(){ + m_pCmd = 0; + m_bBSPPlugin = false; + m_pListenSocket = NULL; + m_pInSocket = NULL; + m_eState = EIdle; + m_pTimer = g_timer_new(); + m_sBSPName = NULL; + m_xmlInputBuffer = NULL; + m_bNeedCtxtInit = true; + } + virtual ~CWatchBSP(){ + EndMonitoringLoop(); + Net_Shutdown(); + + g_timer_destroy( m_pTimer ); + } + + bool HasBSPPlugin() const + { return m_bBSPPlugin; } + + // called regularly to keep listening + void RoutineProcessing(); + // start a monitoring loop with the following steps + void DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName ); + void EndMonitoringLoop(){ + Reset(); + if ( m_sBSPName ) { + string_release( m_sBSPName, string_length( m_sBSPName ) ); + m_sBSPName = 0; + } + if ( m_pCmd ) { + g_ptr_array_free( m_pCmd, TRUE ); + m_pCmd = 0; + } + } + // close everything - may be called from the outside to abort the process + void Reset(); + // start a listening loop for an external process, possibly a BSP plugin + void ExternalListen(); +}; + +CWatchBSP* g_pWatchBSP; + +// watch the BSP process through network connections +// true: trigger the BSP steps one by one and monitor them through the network +// false: create a BAT / .sh file and execute it. don't bother monitoring it. +bool g_WatchBSP_Enabled = true; +// do we stop the compilation process if we come accross a leak? +bool g_WatchBSP_LeakStop = true; +bool g_WatchBSP_RunQuake = false; +// store prefs setting for automatic sleep mode activation +bool g_WatchBSP_DoSleep = true; +// timeout when beginning a step (in seconds) +// if we don't get a connection quick enough we assume something failed and go back to idling +int g_WatchBSP_Timeout = 10; + + +void Build_constructPreferences( PreferencesPage& page ){ + ui::CheckButton monitorbsp = page.appendCheckBox( "", "Enable Build Process Monitoring", g_WatchBSP_Enabled ); + ui::CheckButton leakstop = page.appendCheckBox( "", "Stop Compilation on Leak", g_WatchBSP_LeakStop ); + ui::CheckButton runengine = page.appendCheckBox( "", "Run Engine After Compile", g_WatchBSP_RunQuake ); + ui::CheckButton sleep = page.appendCheckBox ( "", "Sleep When Running the Engine", g_WatchBSP_DoSleep ); + Widget_connectToggleDependency( leakstop, monitorbsp ); + Widget_connectToggleDependency( runengine, monitorbsp ); + Widget_connectToggleDependency( sleep, runengine ); +} +void Build_constructPage( PreferenceGroup& group ){ + PreferencesPage page( group.createPage( "Build", "Build Preferences" ) ); + Build_constructPreferences( page ); +} +void Build_registerPreferencesPage(){ + PreferencesDialog_addSettingsPage( makeCallbackF(Build_constructPage) ); +} + +#include "preferencesystem.h" +#include "stringio.h" + +void BuildMonitor_Construct(){ + g_pWatchBSP = new CWatchBSP(); + + g_WatchBSP_Enabled = !string_equal( g_pGameDescription->getKeyValue( "no_bsp_monitor" ), "1" ); + + GlobalPreferenceSystem().registerPreference( "WatchBSP", make_property_string( g_WatchBSP_Enabled ) ); + GlobalPreferenceSystem().registerPreference( "RunQuake2Run", make_property_string( g_WatchBSP_RunQuake ) ); + GlobalPreferenceSystem().registerPreference( "LeakStop", make_property_string( g_WatchBSP_LeakStop ) ); + GlobalPreferenceSystem().registerPreference( "SleepMode", make_property_string( g_WatchBSP_DoSleep ) ); + + Build_registerPreferencesPage(); +} + +void BuildMonitor_Destroy(){ + delete g_pWatchBSP; +} + +CWatchBSP *GetWatchBSP(){ + return g_pWatchBSP; +} + +void BuildMonitor_Run( GPtrArray* commands, const char* mapName ){ + GetWatchBSP()->DoMonitoringLoop( commands, mapName ); +} + // Static functions for the SAX callbacks ------------------------------------------------------- // utility for saxStartElement below static void abortStream( message_info_t *data ){ - g_pParentWnd->GetWatchBSP()->Reset(); + GetWatchBSP()->EndMonitoringLoop(); // tell there has been an error - if ( g_pParentWnd->GetWatchBSP()->HasBSPPlugin() ) { +#if 0 + if ( GetWatchBSP()->HasBSPPlugin() ) { g_BSPFrontendTable.m_pfnEndListen( 2 ); } +#endif // yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out data->ignore_depth = -1; data->recurse++; @@ -69,62 +241,79 @@ static void abortStream( message_info_t *data ){ #include "stream_version.h" static void saxStartElement( message_info_t *data, const xmlChar *name, const xmlChar **attrs ){ +#if 0 + globalOutputStream() << "<" << name; + if ( attrs != 0 ) { + for ( const xmlChar** p = attrs; *p != 0; p += 2 ) + { + globalOutputStream() << " " << p[0] << "=" << makeQuoted( p[1] ); + } + } + globalOutputStream() << ">\n"; +#endif + if ( data->ignore_depth == 0 ) { - if ( data->bGeometry ) { + if ( data->pGeometry != 0 ) { // we have a handler data->pGeometry->saxStartElement( data, name, attrs ); } else { - if ( strcmp( (char *)name, "q3map_feedback" ) == 0 ) { + if ( strcmp( reinterpret_cast( name ), "q3map_feedback" ) == 0 ) { // check the correct version // old q3map don't send a version attribute // the ones we support .. send Q3MAP_STREAM_VERSION - if ( !attrs[0] || !attrs[1] || ( strcmp( (char*)attrs[0],"version" ) != 0 ) ) { - Sys_FPrintf( SYS_ERR, "No stream version given in the feedback stream, this is an old q3map version.\n" - "Please turn off monitored compiling if you still wish to use this q3map executable\n" ); + if ( !attrs[0] || !attrs[1] || ( strcmp( reinterpret_cast( attrs[0] ), "version" ) != 0 ) ) { + message_flush( data ); + globalErrorStream() << "No stream version given in the feedback stream, this is an old q3map version.\n" + "Please turn off monitored compiling if you still wish to use this q3map executable\n"; abortStream( data ); return; } - else if ( strcmp( (char*)attrs[1],Q3MAP_STREAM_VERSION ) != 0 ) { - Sys_FPrintf( SYS_ERR, - "This version of Radiant reads version %s debug streams, I got an incoming connection with version %s\n" - "Please make sure your versions of Radiant and q3map are matching.\n", Q3MAP_STREAM_VERSION, (char*)attrs[1] ); + else if ( strcmp( reinterpret_cast( attrs[1] ), Q3MAP_STREAM_VERSION ) != 0 ) { + message_flush( data ); + globalErrorStream() << + "This version of " RADIANT_NAME " reads version " Q3MAP_STREAM_VERSION " debug streams, I got an incoming connection with version " << reinterpret_cast( attrs[1] ) << "\n" + "Please make sure your versions of " RADIANT_NAME " and q3map are matching.\n"; abortStream( data ); return; } } // we don't treat locally - else if ( strcmp( (char *)name, "message" ) == 0 ) { - data->msg_level = atoi( (char *)attrs[1] ); + else if ( strcmp( reinterpret_cast( name ), "message" ) == 0 ) { + int msg_level = atoi( reinterpret_cast( attrs[1] ) ); + if ( msg_level != data->msg_level ) { + message_flush( data ); + data->msg_level = msg_level; + } } - else if ( strcmp( (char *)name, "polyline" ) == 0 ) { + else if ( strcmp( reinterpret_cast( name ), "polyline" ) == 0 ) { // polyline has a particular status .. right now we only use it for leakfile .. - data->bGeometry = true; + data->geometry_depth = data->recurse; data->pGeometry = &g_pointfile; data->pGeometry->saxStartElement( data, name, attrs ); } - else if ( strcmp( (char *)name, "select" ) == 0 ) { + else if ( strcmp( reinterpret_cast( name ), "select" ) == 0 ) { CSelectMsg *pSelect = new CSelectMsg(); - data->bGeometry = true; + data->geometry_depth = data->recurse; data->pGeometry = pSelect; data->pGeometry->saxStartElement( data, name, attrs ); } - else if ( strcmp( (char *)name, "pointmsg" ) == 0 ) { + else if ( strcmp( reinterpret_cast( name ), "pointmsg" ) == 0 ) { CPointMsg *pPoint = new CPointMsg(); - data->bGeometry = true; + data->geometry_depth = data->recurse; data->pGeometry = pPoint; data->pGeometry->saxStartElement( data, name, attrs ); } - else if ( strcmp( (char *)name, "windingmsg" ) == 0 ) { + else if ( strcmp( reinterpret_cast( name ), "windingmsg" ) == 0 ) { CWindingMsg *pWinding = new CWindingMsg(); - data->bGeometry = true; + data->geometry_depth = data->recurse; data->pGeometry = pWinding; data->pGeometry->saxStartElement( data, name, attrs ); } else { - Sys_FPrintf( SYS_WRN, "WARNING: ignoring unrecognized node in XML stream (%s)\n", name ); + globalErrorStream() << "Warning: ignoring unrecognized node in XML stream (" << reinterpret_cast( name ) << ")\n"; // we don't recognize this node, jump over it // (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level) data->ignore_depth = data->recurse; @@ -134,57 +323,81 @@ static void saxStartElement( message_info_t *data, const xmlChar *name, const xm data->recurse++; } -static void saxEndElement( message_info_t *data, const xmlChar *name ) { +static void saxEndElement( message_info_t *data, const xmlChar *name ){ +#if 0 + globalOutputStream() << "<" << name << "/>\n"; +#endif + data->recurse--; // we are out of an ignored chunk if ( data->recurse == data->ignore_depth ) { data->ignore_depth = 0; return; } - if ( data->bGeometry ) { + if ( data->pGeometry != 0 ) { data->pGeometry->saxEndElement( data, name ); // we add the object to the debug window - if ( !data->bGeometry ) { + if ( data->geometry_depth == data->recurse ) { g_DbgDlg.Push( data->pGeometry ); + data->pGeometry = 0; } } if ( data->recurse == data->stop_depth ) { -#ifdef _DEBUG - Sys_Printf( "Received error msg .. shutting down..\n" ); + message_flush( data ); +#if GDEF_DEBUG + globalOutputStream() << "Received error msg .. shutting down..\n"; #endif - g_pParentWnd->GetWatchBSP()->Reset(); + GetWatchBSP()->EndMonitoringLoop(); // tell there has been an error - if ( g_pParentWnd->GetWatchBSP()->HasBSPPlugin() ) { +#if 0 + if ( GetWatchBSP()->HasBSPPlugin() ) { g_BSPFrontendTable.m_pfnEndListen( 2 ); } +#endif return; } } -static void saxCharacters( message_info_t *data, const xmlChar *ch, int len ){ - if ( data->bGeometry ) { - data->pGeometry->saxCharacters( data, ch, len ); +class MessageOutputStream : public TextOutputStream +{ + message_info_t* m_data; +public: + MessageOutputStream( message_info_t* data ) : m_data( data ){ } - else - { - if ( data->ignore_depth != 0 ) { - return; + + std::size_t write( const char* buffer, std::size_t length ){ + if ( m_data->pGeometry != 0 ) { + m_data->pGeometry->saxCharacters( m_data, reinterpret_cast( buffer ), int(length) ); } - // output the message using the level - char buf[1024]; - memcpy( buf, ch, len ); - buf[len] = '\0'; - Sys_FPrintf( data->msg_level, "%s", buf ); - // if this message has error level flag, we mark the depth to stop the compilation when we get out - // we don't set the msg level if we don't stop on leak - if ( data->msg_level == 3 ) { - data->stop_depth = data->recurse - 1; + else + { + if ( m_data->ignore_depth == 0 ) { + // output the message using the level + message_print( m_data, buffer, length ); + // if this message has error level flag, we mark the depth to stop the compilation when we get out + // we don't set the msg level if we don't stop on leak + if ( m_data->msg_level == 3 ) { + m_data->stop_depth = m_data->recurse - 1; + } + } } + + return length; } +}; + +template +inline MessageOutputStream& operator<<( MessageOutputStream& ostream, const T& t ){ + return ostream_write( ostream, t ); +} + +static void saxCharacters( message_info_t *data, const xmlChar *ch, int len ){ + MessageOutputStream ostream( data ); + ostream << StringRange( reinterpret_cast( ch ), reinterpret_cast( ch + len ) ); } static void saxComment( void *ctx, const xmlChar *msg ){ - Sys_Printf( "XML comment: %s\n", msg ); + globalOutputStream() << "XML comment: " << reinterpret_cast( msg ) << "\n"; } static void saxWarning( void *ctx, const char *msg, ... ){ @@ -194,7 +407,7 @@ static void saxWarning( void *ctx, const char *msg, ... ){ va_start( args, msg ); vsprintf( saxMsgBuffer, msg, args ); va_end( args ); - Sys_FPrintf( SYS_WRN, "XML warning: %s\n", saxMsgBuffer ); + globalOutputStream() << "XML warning: " << saxMsgBuffer << "\n"; } static void saxError( void *ctx, const char *msg, ... ){ @@ -204,7 +417,7 @@ static void saxError( void *ctx, const char *msg, ... ){ va_start( args, msg ); vsprintf( saxMsgBuffer, msg, args ); va_end( args ); - Sys_FPrintf( SYS_ERR, "XML error: %s\n", saxMsgBuffer ); + globalErrorStream() << "XML error: " << saxMsgBuffer << "\n"; } static void saxFatal( void *ctx, const char *msg, ... ){ @@ -215,7 +428,7 @@ static void saxFatal( void *ctx, const char *msg, ... ){ va_start( args, msg ); vsprintf( buffer, msg, args ); va_end( args ); - Sys_FPrintf( SYS_ERR, "XML fatal error: %s\n", buffer ); + globalErrorStream() << "XML fatal error: " << buffer << "\n"; } static xmlSAXHandler saxParser = { @@ -243,17 +456,23 @@ static xmlSAXHandler saxParser = { (warningSAXFunc)saxWarning, /* warning */ (errorSAXFunc)saxError, /* error */ (fatalErrorSAXFunc)saxFatal, /* fatalError */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 }; // ------------------------------------------------------------------------------------------------ -CWatchBSP::~CWatchBSP(){ - Reset(); - if ( m_sBSPName ) { - delete[] m_sBSPName; - m_sBSPName = NULL; - } - Net_Shutdown(); + +guint s_routine_id; +static gint watchbsp_routine( gpointer data ){ + reinterpret_cast( data )->RoutineProcessing(); + return TRUE; } void CWatchBSP::Reset(){ @@ -270,36 +489,34 @@ void CWatchBSP::Reset(){ m_xmlInputBuffer = NULL; } m_eState = EIdle; + if ( s_routine_id ) { + g_source_remove( s_routine_id ); + } } bool CWatchBSP::SetupListening(){ -#ifdef _DEBUG +#if GDEF_DEBUG if ( m_pListenSocket ) { - Sys_Printf( "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n" ); + globalOutputStream() << "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n"; return false; } #endif - Sys_Printf( "Setting up\n" ); - if ( !Net_Setup() ) { - return false; - } - + globalOutputStream() << "Setting up\n"; + Net_Setup(); m_pListenSocket = Net_ListenSocket( 39000 ); if ( m_pListenSocket == NULL ) { return false; } - - Sys_Printf( "Listening...\n" ); + globalOutputStream() << "Listening...\n"; return true; } -void CWatchBSP::DoEBeginStep() { +void CWatchBSP::DoEBeginStep(){ Reset(); - if ( !SetupListening() ) { - CString msg; - msg = "Failed to get a listening socket on port 39000.\nTry running with BSP monitoring disabled if you can't fix this.\n"; - Sys_Printf( msg ); - gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR ); + if ( SetupListening() == false ) { + 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"; + globalOutputStream() << msg; + ui::alert( MainFrame_getWindow(), msg, "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error ); return; } // set the timer for timeouts and step cancellation @@ -307,15 +524,16 @@ void CWatchBSP::DoEBeginStep() { g_timer_start( m_pTimer ); if ( !m_bBSPPlugin ) { - Sys_Printf( "=== running BSP command ===\n%s\n", g_ptr_array_index( m_pCmd, m_iCurrentStep ) ); - - if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true ) ) { - CString msg; - msg = "Failed to execute the following command: "; - msg += (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ); - msg += "\nCheck that the file exists and that you don't run out of system resources.\n"; - Sys_Printf( msg ); - gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR ); + globalOutputStream() << "=== running build command ===\n" + << static_cast( g_ptr_array_index( m_pCmd, m_iCurrentStep ) ) << "\n"; + + if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true, false ) ) { + StringOutputStream msg( 256 ); + msg << "Failed to execute the following command: "; + msg << reinterpret_cast( g_ptr_array_index( m_pCmd, m_iCurrentStep ) ); + msg << "\nCheck that the file exists and that you don't run out of system resources.\n"; + globalOutputStream() << msg.c_str(); + ui::alert( MainFrame_getWindow(), msg.c_str(), "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error ); return; } // re-initialise the debug window @@ -324,83 +542,123 @@ void CWatchBSP::DoEBeginStep() { } } m_eState = EBeginStep; + s_routine_id = g_timeout_add( 25, watchbsp_routine, this ); } -void CWatchBSP::RoutineProcessing(){ - // used for select() -#ifdef _WIN32 - TIMEVAL tout = { 0, 0 }; -#endif -#if defined ( __linux__ ) || defined ( __APPLE__ ) - timeval tout; - tout.tv_sec = 0; - tout.tv_usec = 0; + +#if GDEF_OS_WINDOWS +const char *ENGINE_ATTRIBUTE = "engine_win32"; +const char *MP_ENGINE_ATTRIBUTE = "mp_engine_win32"; +#elif GDEF_OS_LINUX || GDEF_OS_BSD +const char *ENGINE_ATTRIBUTE = "engine_linux"; +const char *MP_ENGINE_ATTRIBUTE = "mp_engine_linux"; +#elif GDEF_OS_MACOS +const char *ENGINE_ATTRIBUTE = "engine_macos"; +const char *MP_ENGINE_ATTRIBUTE = "mp_engine_macos"; +#else +#error "unsupported platform" #endif +class RunEngineConfiguration +{ +public: + const char* executable; + const char* mp_executable; + bool do_sp_mp; + + RunEngineConfiguration() : + executable( g_pGameDescription->getRequiredKeyValue( ENGINE_ATTRIBUTE ) ), + mp_executable( g_pGameDescription->getKeyValue( MP_ENGINE_ATTRIBUTE ) ){ + do_sp_mp = !string_empty( mp_executable ); + } +}; + +inline void GlobalGameDescription_string_write_mapparameter( StringOutputStream& string, const char* mapname ){ + if ( g_pGameDescription->mGameType == "q2" + || g_pGameDescription->mGameType == "heretic2" ) { + string << ". +exec radiant.cfg +map " << mapname; + } + else + { + string << "+set sv_pure 0 "; + // TTimo: a check for vm_* but that's all fine + //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 "; + const char* fs_game = gamename_get(); + if ( !string_equal( fs_game, basegame_get() ) ) { + string << "+set fs_game " << fs_game << " "; + } + if ( g_pGameDescription->mGameType == "wolf" ) { + //|| g_pGameDescription->mGameType == "et") + if ( string_equal( gamemode_get(), "mp" ) ) { + // MP + string << "+devmap " << mapname; + } + else + { + // SP + string << "+set nextmap \"spdevmap " << mapname << "\""; + } + } + else + { + string << "+devmap " << mapname; + } + } +} + + +void CWatchBSP::RoutineProcessing(){ switch ( m_eState ) { case EBeginStep: // timeout: if we don't get an incoming connection fast enough, go back to idle - if ( g_timer_elapsed( m_pTimer, NULL ) > g_PrefsDlg.m_iTimeout ) { - gtk_MessageBox( g_pParentWnd->m_pWidget, "The connection timed out, assuming the BSP process failed\nMake sure you are using a networked version of Q3Map?\nOtherwise you need to disable BSP Monitoring in prefs.", "BSP process monitoring", MB_OK ); - Reset(); + if ( g_timer_elapsed( m_pTimer, NULL ) > g_WatchBSP_Timeout ) { + 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 ); + EndMonitoringLoop(); +#if 0 if ( m_bBSPPlugin ) { // status == 1 : didn't get the connection g_BSPFrontendTable.m_pfnEndListen( 1 ); } +#endif return; } -#ifdef _DEBUG +#if GDEF_DEBUG // some debug checks if ( !m_pListenSocket ) { - Sys_Printf( "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n" ); + globalErrorStream() << "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n"; return; } #endif // we are not connected yet, accept any incoming connection m_pInSocket = Net_Accept( m_pListenSocket ); if ( m_pInSocket ) { - Sys_Printf( "Connected.\n" ); + globalOutputStream() << "Connected.\n"; // prepare the message info struct for diving in - memset( &m_message_info, 0, sizeof( message_info_s ) ); + memset( &m_message_info, 0, sizeof( message_info_t ) ); // a dumb flag to make sure we init the push parser context when first getting a msg m_bNeedCtxtInit = true; m_eState = EWatching; } break; case EWatching: -#ifdef _DEBUG + { +#if GDEF_DEBUG // some debug checks if ( !m_pInSocket ) { - Sys_Printf( "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n" ); + globalErrorStream() << "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n"; return; } #endif - // select() will identify if the socket needs an update - // if the socket is identified that means there's either a message or the connection has been closed/reset/terminated - fd_set readfds; - int ret; - FD_ZERO( &readfds ); - FD_SET( ( (unsigned int)m_pInSocket->socket ), &readfds ); - // from select man page: - // n is the highest-numbered descriptor in any of the three sets, plus 1 - // (no use on windows) - ret = select( m_pInSocket->socket + 1, &readfds, NULL, NULL, &tout ); - if ( ret == SOCKET_ERROR ) { - Sys_Printf( "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n" ); - Sys_Printf( "Terminating the connection.\n" ); - Reset(); - return; - } -#ifdef _DEBUG + + int ret = Net_Wait( m_pInSocket, 0, 0 ); if ( ret == -1 ) { - // we are non-blocking?? we should never get timeout errors - Sys_Printf( "WARNING: unexpected timeout expired in CWatchBSP::Processing\n" ); - Sys_Printf( "Terminating the connection.\n" ); - Reset(); + globalOutputStream() << "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n"; + globalOutputStream() << "Terminating the connection.\n"; + EndMonitoringLoop(); return; } -#endif + if ( ret == 1 ) { // the socket has been identified, there's something (message or disconnection) // see if there's anything in input @@ -410,31 +668,35 @@ void CWatchBSP::RoutineProcessing(){ strcpy( m_xmlBuf, NMSG_ReadString( &msg ) ); if ( m_bNeedCtxtInit ) { m_xmlParserCtxt = NULL; - m_xmlParserCtxt = xmlCreatePushParserCtxt( &saxParser, &m_message_info, m_xmlBuf, strlen( m_xmlBuf ), NULL ); + m_xmlParserCtxt = xmlCreatePushParserCtxt( &saxParser, &m_message_info, m_xmlBuf, static_cast( strlen( m_xmlBuf ) ), NULL ); + if ( m_xmlParserCtxt == NULL ) { - Sys_FPrintf( SYS_ERR, "Failed to create the XML parser (incoming stream began with: %s)\n", m_xmlBuf ); - Reset(); + globalErrorStream() << "Failed to create the XML parser (incoming stream began with: " << m_xmlBuf << ")\n"; + EndMonitoringLoop(); } m_bNeedCtxtInit = false; } else { - xmlParseChunk( m_xmlParserCtxt, m_xmlBuf, strlen( m_xmlBuf ), 0 ); + xmlParseChunk( m_xmlParserCtxt, m_xmlBuf, static_cast( strlen( m_xmlBuf ) ), 0 ); } } else { + message_flush( &m_message_info ); // error or connection closed/reset // NOTE: if we get an error down the XML stream we don't reach here Net_Disconnect( m_pInSocket ); m_pInSocket = NULL; - Sys_Printf( "Connection closed.\n" ); + globalOutputStream() << "Connection closed.\n"; +#if 0 if ( m_bBSPPlugin ) { - Reset(); + EndMonitoringLoop(); // let the BSP plugin know that the job is done g_BSPFrontendTable.m_pfnEndListen( 0 ); return; } +#endif // move to next step or finish m_iCurrentStep++; if ( m_iCurrentStep < m_pCmd->len ) { @@ -442,114 +704,83 @@ void CWatchBSP::RoutineProcessing(){ } else { - // release the GPtrArray and the strings - if ( m_pCmd != NULL ) { - for ( m_iCurrentStep = 0; m_iCurrentStep < m_pCmd->len; m_iCurrentStep++ ) - { - delete[] (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ); - } - g_ptr_array_free( m_pCmd, false ); - } - m_pCmd = NULL; // launch the engine .. OMG - if ( g_PrefsDlg.m_bRunQuake ) { + if ( g_WatchBSP_RunQuake ) { +#if 0 // do we enter sleep mode before? - if ( g_PrefsDlg.m_bDoSleep ) { - Sys_Printf( "Going into sleep mode..\n" ); + if ( g_WatchBSP_DoSleep ) { + globalOutputStream() << "Going into sleep mode..\n"; g_pParentWnd->OnSleep(); } - Sys_Printf( "Running engine...\n" ); - Str cmd; +#endif + globalOutputStream() << "Running engine...\n"; + StringOutputStream cmd( 256 ); // build the command line - cmd = g_pGameDescription->mEnginePath.GetBuffer(); + cmd << EnginePath_get(); // this is game dependant - if ( !strcmp( ValueForKey( g_qeglobals.d_project_entity, "gamemode" ),"mp" ) ) { - // MP - cmd += g_pGameDescription->mMultiplayerEngine.GetBuffer(); - } - else - { - // SP - cmd += g_pGameDescription->mEngine.GetBuffer(); - } -#ifdef _WIN32 - // NOTE: we are using unix pathnames and CreateProcess doesn't like / in the program path - // FIXME: This isn't true anymore, doesn't it? - FindReplace( cmd, "/", "\\" ); -#endif - Str cmdline; - if ( g_pGameDescription->quake2 ) { - cmdline = ". +exec radiant.cfg +map "; - cmdline += m_sBSPName; - } - else - { - cmdline = "+set sv_pure 0 "; - // TTimo: a check for vm_* but that's all fine - //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 "; - if ( *ValueForKey( g_qeglobals.d_project_entity, "gamename" ) != '\0' ) { - cmdline += "+set fs_game "; - cmdline += ValueForKey( g_qeglobals.d_project_entity, "gamename" ); - cmdline += " "; - } - //!\todo Read the start-map args from a config file. - if ( g_pGameDescription->mGameFile == "wolf.game" ) { - if ( !strcmp( ValueForKey( g_qeglobals.d_project_entity, "gamemode" ),"mp" ) ) { - // MP - cmdline += "+devmap "; - cmdline += m_sBSPName; - } - else - { - // SP - cmdline += "+set nextmap \"spdevmap "; - cmdline += m_sBSPName; - cmdline += "\""; - } + + RunEngineConfiguration engineConfig; + + if ( engineConfig.do_sp_mp ) { + if ( string_equal( gamemode_get(), "mp" ) ) { + cmd << engineConfig.mp_executable; } else { - cmdline += "+devmap "; - cmdline += m_sBSPName; + cmd << engineConfig.executable; } } + else + { + cmd << engineConfig.executable; + } + + StringOutputStream cmdline; - Sys_Printf( "%s %s\n", cmd.GetBuffer(), cmdline.GetBuffer() ); + GlobalGameDescription_string_write_mapparameter( cmdline, m_sBSPName ); + + globalOutputStream() << cmd.c_str() << " " << cmdline.c_str() << "\n"; // execute now - if ( !Q_Exec( cmd.GetBuffer(), (char *)cmdline.GetBuffer(), g_pGameDescription->mEnginePath.GetBuffer(), false ) ) { - CString msg; - msg = "Failed to execute the following command: "; - msg += cmd; msg += cmdline; - Sys_Printf( msg ); - gtk_MessageBox( g_pParentWnd->m_pWidget, msg, "BSP monitoring", MB_OK | MB_ICONERROR ); + if ( !Q_Exec( cmd.c_str(), (char *)cmdline.c_str(), EnginePath_get(), false, false ) ) { + StringOutputStream msg; + msg << "Failed to execute the following command: " << cmd.c_str() << cmdline.c_str(); + globalOutputStream() << msg.c_str(); + ui::alert( MainFrame_getWindow(), msg.c_str(), "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error ); } } - Reset(); + EndMonitoringLoop(); } } } - break; + } + break; default: break; } } -void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, char *sBSPName ){ - if ( m_sBSPName ) { - delete[] m_sBSPName; +GPtrArray* str_ptr_array_clone( GPtrArray* array ){ + GPtrArray* cloned = g_ptr_array_sized_new( array->len ); + for ( guint i = 0; i < array->len; ++i ) + { + g_ptr_array_add( cloned, g_strdup( (char*)g_ptr_array_index( array, i ) ) ); } - m_sBSPName = sBSPName; + return cloned; +} + +void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName ){ + m_sBSPName = string_clone( sBSPName ); if ( m_eState != EIdle ) { - Sys_Printf( "WatchBSP got a monitoring request while not idling...\n" ); + globalOutputStream() << "WatchBSP got a monitoring request while not idling...\n"; // prompt the user, should we cancel the current process and go ahead? - if ( gtk_MessageBox( g_pParentWnd->m_pWidget, "I am already monitoring a BSP process.\nDo you want me to override and start a new compilation?", - "BSP process monitoring", MB_YESNO ) == IDYES ) { + if ( ui::alert( MainFrame_getWindow(), "I am already monitoring a Build process.\nDo you want me to override and start a new compilation?", + "Build process monitoring", ui::alert_type::YESNO ) == ui::alert_response::YES ) { // disconnect and set EIdle state Reset(); } } - m_pCmd = pCmd; + m_pCmd = str_ptr_array_clone( pCmd ); m_iCurrentStep = 0; DoEBeginStep(); } @@ -562,7 +793,7 @@ void CWatchBSP::ExternalListen(){ // the part of the watchbsp interface we export to plugins // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final) -void WINAPI QERApp_Listen(){ +void QERApp_Listen(){ // open the listening socket - g_pParentWnd->GetWatchBSP()->ExternalListen(); + GetWatchBSP()->ExternalListen(); }