doom3-bfg/neo/framework/Common_printf.cpp

617 lines
14 KiB
C++

/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "Common_local.h"
idCVar com_logFile( "logFile", "0", CVAR_SYSTEM | CVAR_NOCHEAT, "1 = buffer log, 2 = flush after each print", 0, 2, idCmdSystem::ArgCompletion_Integer<0, 2> );
idCVar com_logFileName( "logFileName", "qconsole.log", CVAR_SYSTEM | CVAR_NOCHEAT, "name of log file, if empty, qconsole.log will be used" );
idCVar com_timestampPrints( "com_timestampPrints", "0", CVAR_SYSTEM, "print time with each console print, 1 = msec, 2 = sec", 0, 2, idCmdSystem::ArgCompletion_Integer<0, 2> );
#ifndef ID_RETAIL
idCVar com_printFilter( "com_printFilter", "", CVAR_SYSTEM, "only print lines that contain this, add multiple filters with a ; delimeter" );
#endif
/*
==================
idCommonLocal::BeginRedirect
==================
*/
void idCommonLocal::BeginRedirect( char* buffer, int buffersize, void ( *flush )( const char* ) )
{
if( !buffer || !buffersize || !flush )
{
return;
}
rd_buffer = buffer;
rd_buffersize = buffersize;
rd_flush = flush;
*rd_buffer = 0;
}
/*
==================
idCommonLocal::EndRedirect
==================
*/
void idCommonLocal::EndRedirect()
{
if( rd_flush && rd_buffer[ 0 ] )
{
rd_flush( rd_buffer );
}
rd_buffer = NULL;
rd_buffersize = 0;
rd_flush = NULL;
}
/*
==================
idCommonLocal::CloseLogFile
==================
*/
void idCommonLocal::CloseLogFile()
{
if( logFile )
{
com_logFile.SetBool( false ); // make sure no further VPrintf attempts to open the log file again
fileSystem->CloseFile( logFile );
logFile = NULL;
}
}
/*
==================
idCommonLocal::SetRefreshOnPrint
==================
*/
void idCommonLocal::SetRefreshOnPrint( bool set )
{
com_refreshOnPrint = set;
}
/*
==================
idCommonLocal::VPrintf
A raw string should NEVER be passed as fmt, because of "%f" type crashes.
==================
*/
void idCommonLocal::VPrintf( const char* fmt, va_list args )
{
static bool logFileFailed = false;
// if the cvar system is not initialized
if( !cvarSystem->IsInitialized() )
{
return;
}
// optionally put a timestamp at the beginning of each print,
// so we can see how long different init sections are taking
int timeLength = 0;
char msg[MAX_PRINT_MSG_SIZE];
msg[ 0 ] = '\0';
if( com_timestampPrints.GetInteger() )
{
int t = Sys_Milliseconds();
if( com_timestampPrints.GetInteger() == 1 )
{
sprintf( msg, "[%5.2f]", t * 0.001f );
}
else
{
sprintf( msg, "[%i]", t );
}
}
timeLength = strlen( msg );
// don't overflow
if( idStr::vsnPrintf( msg + timeLength, MAX_PRINT_MSG_SIZE - timeLength - 1, fmt, args ) < 0 )
{
msg[sizeof( msg ) - 2] = '\n';
msg[sizeof( msg ) - 1] = '\0'; // avoid output garbling
Sys_Printf( "idCommon::VPrintf: truncated to %d characters\n", strlen( msg ) - 1 );
}
if( rd_buffer )
{
if( ( int )( strlen( msg ) + strlen( rd_buffer ) ) > ( rd_buffersize - 1 ) )
{
rd_flush( rd_buffer );
*rd_buffer = 0;
}
strcat( rd_buffer, msg );
return;
}
#ifndef ID_RETAIL
if( com_printFilter.GetString() != NULL && com_printFilter.GetString()[ 0 ] != '\0' )
{
idStrStatic< 4096 > filterBuf = com_printFilter.GetString();
idStrStatic< 4096 > msgBuf = msg;
filterBuf.ToLower();
msgBuf.ToLower();
char* sp = strtok( &filterBuf[ 0 ], ";" );
bool p = false;
for( ; sp != NULL ; )
{
if( strstr( msgBuf, sp ) != NULL )
{
p = true;
break;
}
sp = strtok( NULL, ";" );
}
if( !p )
{
return;
}
}
#endif
if( !idLib::IsMainThread() )
{
OutputDebugString( msg );
return;
}
// echo to console buffer
console->Print( msg );
// remove any color codes
idStr::RemoveColors( msg );
// echo to dedicated console and early console
Sys_Printf( "%s", msg );
// print to script debugger server
// DebuggerServerPrint( msg );
#if 0 // !@#
#if defined(_DEBUG) && defined(WIN32)
if( strlen( msg ) < 512 )
{
TRACE( msg );
}
#endif
#endif
// logFile
if( com_logFile.GetInteger() && !logFileFailed && fileSystem->IsInitialized() )
{
static bool recursing;
if( !logFile && !recursing )
{
const char* fileName = com_logFileName.GetString()[0] ? com_logFileName.GetString() : "qconsole.log";
// fileSystem->OpenFileWrite can cause recursive prints into here
recursing = true;
logFile = fileSystem->OpenFileWrite( fileName );
if( !logFile )
{
logFileFailed = true;
FatalError( "failed to open log file '%s'\n", fileName );
}
recursing = false;
if( com_logFile.GetInteger() > 1 )
{
// force it to not buffer so we get valid
// data even if we are crashing
logFile->ForceFlush();
}
time_t aclock;
time( &aclock );
struct tm* newtime = localtime( &aclock );
Printf( "log file '%s' opened on %s\n", fileName, asctime( newtime ) );
}
if( logFile )
{
logFile->Write( msg, strlen( msg ) );
logFile->Flush(); // ForceFlush doesn't help a whole lot
}
}
// don't trigger any updates if we are in the process of doing a fatal error
if( com_errorEntered != ERP_FATAL )
{
// update the console if we are in a long-running command, like dmap
if( com_refreshOnPrint )
{
const bool captureToImage = false;
UpdateScreen( captureToImage );
}
}
}
/*
==================
idCommonLocal::Printf
Both client and server can use this, and it will output to the appropriate place.
A raw string should NEVER be passed as fmt, because of "%f" type crashers.
==================
*/
void idCommonLocal::Printf( const char* fmt, ... )
{
va_list argptr;
va_start( argptr, fmt );
VPrintf( fmt, argptr );
va_end( argptr );
}
/*
==================
idCommonLocal::DPrintf
prints message that only shows up if the "developer" cvar is set
==================
*/
void idCommonLocal::DPrintf( const char* fmt, ... )
{
va_list argptr;
char msg[MAX_PRINT_MSG_SIZE];
if( !cvarSystem->IsInitialized() || !com_developer.GetBool() )
{
return; // don't confuse non-developers with techie stuff...
}
va_start( argptr, fmt );
idStr::vsnPrintf( msg, sizeof( msg ), fmt, argptr );
va_end( argptr );
msg[sizeof( msg ) - 1] = '\0';
// never refresh the screen, which could cause reentrency problems
bool temp = com_refreshOnPrint;
com_refreshOnPrint = false;
Printf( S_COLOR_RED"%s", msg );
com_refreshOnPrint = temp;
}
/*
==================
idCommonLocal::DWarning
prints warning message in yellow that only shows up if the "developer" cvar is set
==================
*/
void idCommonLocal::DWarning( const char* fmt, ... )
{
va_list argptr;
char msg[MAX_PRINT_MSG_SIZE];
if( !com_developer.GetBool() )
{
return; // don't confuse non-developers with techie stuff...
}
va_start( argptr, fmt );
idStr::vsnPrintf( msg, sizeof( msg ), fmt, argptr );
va_end( argptr );
msg[sizeof( msg ) - 1] = '\0';
Printf( S_COLOR_YELLOW"WARNING: %s\n", msg );
}
/*
==================
idCommonLocal::Warning
prints WARNING %s and adds the warning message to a queue to be printed later on
==================
*/
void idCommonLocal::Warning( const char* fmt, ... )
{
va_list argptr;
char msg[MAX_PRINT_MSG_SIZE];
if( !idLib::IsMainThread() )
{
return; // not thread safe!
}
va_start( argptr, fmt );
idStr::vsnPrintf( msg, sizeof( msg ), fmt, argptr );
va_end( argptr );
msg[sizeof( msg ) - 1] = 0;
Printf( S_COLOR_YELLOW "WARNING: " S_COLOR_RED "%s\n", msg );
if( warningList.Num() < MAX_WARNING_LIST )
{
warningList.AddUnique( msg );
}
}
/*
==================
idCommonLocal::PrintWarnings
==================
*/
void idCommonLocal::PrintWarnings()
{
int i;
if( !warningList.Num() )
{
return;
}
Printf( "------------- Warnings ---------------\n" );
Printf( "during %s...\n", warningCaption.c_str() );
for( i = 0; i < warningList.Num(); i++ )
{
Printf( S_COLOR_YELLOW "WARNING: " S_COLOR_RED "%s\n", warningList[i].c_str() );
}
if( warningList.Num() )
{
if( warningList.Num() >= MAX_WARNING_LIST )
{
Printf( "more than %d warnings\n", MAX_WARNING_LIST );
}
else
{
Printf( "%d warnings\n", warningList.Num() );
}
}
}
/*
==================
idCommonLocal::ClearWarnings
==================
*/
void idCommonLocal::ClearWarnings( const char* reason )
{
warningCaption = reason;
warningList.Clear();
}
/*
==================
idCommonLocal::DumpWarnings
==================
*/
void idCommonLocal::DumpWarnings()
{
int i;
idFile* warningFile;
if( !warningList.Num() )
{
return;
}
warningFile = fileSystem->OpenFileWrite( "warnings.txt", "fs_savepath" );
if( warningFile )
{
warningFile->Printf( "------------- Warnings ---------------\n\n" );
warningFile->Printf( "during %s...\n", warningCaption.c_str() );
for( i = 0; i < warningList.Num(); i++ )
{
warningList[i].RemoveColors();
warningFile->Printf( "WARNING: %s\n", warningList[i].c_str() );
}
if( warningList.Num() >= MAX_WARNING_LIST )
{
warningFile->Printf( "\nmore than %d warnings!\n", MAX_WARNING_LIST );
}
else
{
warningFile->Printf( "\n%d warnings.\n", warningList.Num() );
}
warningFile->Printf( "\n\n-------------- Errors ---------------\n\n" );
for( i = 0; i < errorList.Num(); i++ )
{
errorList[i].RemoveColors();
warningFile->Printf( "ERROR: %s", errorList[i].c_str() );
}
warningFile->ForceFlush();
fileSystem->CloseFile( warningFile );
#ifndef ID_DEBUG
idStr osPath;
osPath = fileSystem->RelativePathToOSPath( "warnings.txt", "fs_savepath" );
WinExec( va( "Notepad.exe %s", osPath.c_str() ), SW_SHOW );
#endif
}
}
/*
==================
idCommonLocal::Error
==================
*/
void idCommonLocal::Error( const char* fmt, ... )
{
va_list argptr;
static int lastErrorTime;
static int errorCount;
int currentTime;
errorParm_t code = ERP_DROP;
// always turn this off after an error
com_refreshOnPrint = false;
if( com_productionMode.GetInteger() == 3 )
{
Sys_Quit();
}
// when we are running automated scripts, make sure we
// know if anything failed
if( cvarSystem->GetCVarInteger( "fs_copyfiles" ) )
{
code = ERP_FATAL;
}
// if we don't have GL running, make it a fatal error
if( !renderSystem->IsOpenGLRunning() )
{
code = ERP_FATAL;
}
// if we got a recursive error, make it fatal
if( com_errorEntered )
{
// if we are recursively erroring while exiting
// from a fatal error, just kill the entire
// process immediately, which will prevent a
// full screen rendering window covering the
// error dialog
if( com_errorEntered == ERP_FATAL )
{
Sys_Quit();
}
code = ERP_FATAL;
}
// if we are getting a solid stream of ERP_DROP, do an ERP_FATAL
currentTime = Sys_Milliseconds();
if( currentTime - lastErrorTime < 100 )
{
if( ++errorCount > 3 )
{
code = ERP_FATAL;
}
}
else
{
errorCount = 0;
}
lastErrorTime = currentTime;
com_errorEntered = code;
va_start( argptr, fmt );
idStr::vsnPrintf( errorMessage, sizeof( errorMessage ), fmt, argptr );
va_end( argptr );
errorMessage[sizeof( errorMessage ) - 1] = '\0';
// copy the error message to the clip board
Sys_SetClipboardData( errorMessage );
// add the message to the error list
errorList.AddUnique( errorMessage );
Stop();
if( code == ERP_DISCONNECT )
{
com_errorEntered = ERP_NONE;
throw idException( errorMessage );
}
else if( code == ERP_DROP )
{
Printf( "********************\nERROR: %s\n********************\n", errorMessage );
com_errorEntered = ERP_NONE;
throw idException( errorMessage );
}
else
{
Printf( "********************\nERROR: %s\n********************\n", errorMessage );
}
if( cvarSystem->GetCVarBool( "r_fullscreen" ) )
{
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart partial windowed\n" );
}
Sys_Error( "%s", errorMessage );
}
/*
==================
idCommonLocal::FatalError
Dump out of the game to a system dialog
==================
*/
void idCommonLocal::FatalError( const char* fmt, ... )
{
va_list argptr;
if( com_productionMode.GetInteger() == 3 )
{
Sys_Quit();
}
// if we got a recursive error, make it fatal
if( com_errorEntered )
{
// if we are recursively erroring while exiting
// from a fatal error, just kill the entire
// process immediately, which will prevent a
// full screen rendering window covering the
// error dialog
Sys_Printf( "FATAL: recursed fatal error:\n%s\n", errorMessage );
va_start( argptr, fmt );
idStr::vsnPrintf( errorMessage, sizeof( errorMessage ), fmt, argptr );
va_end( argptr );
errorMessage[sizeof( errorMessage ) - 1] = '\0';
Sys_Printf( "%s\n", errorMessage );
// write the console to a log file?
Sys_Quit();
}
com_errorEntered = ERP_FATAL;
va_start( argptr, fmt );
idStr::vsnPrintf( errorMessage, sizeof( errorMessage ), fmt, argptr );
va_end( argptr );
errorMessage[sizeof( errorMessage ) - 1] = '\0';
if( cvarSystem->GetCVarBool( "r_fullscreen" ) )
{
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart partial windowed\n" );
}
Sys_SetFatalError( errorMessage );
Sys_Error( "%s", errorMessage );
}