warzone2100/lib/framework/debug.c

367 lines
8.9 KiB
C

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2007 Warzone Resurrection Project
Warzone 2100 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 2 of the License, or
(at your option) any later version.
Warzone 2100 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 Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* Debug.c
*
* Various debugging output functions.
*
*/
#include <string.h>
#include <stdio.h>
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#define WIN32_EXTRA_LEAN
#include <windows.h>
#endif
#include "frame.h"
#include "frameint.h"
#define MAX_FILENAME_SIZE 200
#define MAX_LEN_LOG_LINE 512
#define MAX_LEN_DEBUG_PART 12
char last_called_script_event[MAX_EVENT_NAME_LEN];
static debug_callback * callbackRegistry = NULL;
static BOOL enabled_debug_parts[LOG_LAST];
/* This list _must_ match the enum in debug.h! */
static const char *code_part_names[] = {
"all",
"main",
"sound",
"video",
"wz",
"3d",
"texture",
"net",
"memory",
"warning",
"error",
"never",
"script",
"movement",
"attack",
"fog",
"last"
};
/**********************************************************************
cat_snprintf is like a combination of snprintf and strlcat;
it does snprintf to the end of an existing string.
Like mystrlcat, n is the total length available for str, including
existing contents and trailing nul. If there is no extra room
available in str, does not change the string.
Also like mystrlcat, returns the final length that str would have
had without truncation. I.e., if return is >= n, truncation occurred.
**********************************************************************/
static int cat_snprintf(char *str, size_t n, const char *format, ...)
{
size_t len;
int ret;
va_list ap;
assert(format != NULL);
assert(str != NULL);
assert(n > 0);
len = strlen(str);
assert(len < n);
va_start(ap, format);
ret = vsnprintf(str + len, n - len, format, ap);
va_end(ap);
return (int) (ret + len);
}
/**
* Convert code_part names to enum. Case insensitive.
*
* \return Codepart number or LOG_LAST if can't match.
*/
static int code_part_from_str(const char *str)
{
int i;
for (i = 0; i < LOG_LAST; i++) {
if (strcasecmp(code_part_names[i], str) == 0) {
return i;
}
}
return LOG_LAST;
}
/**
* Callback for outputing to stderr
*
* \param data Ignored. Use NULL.
* \param outputBuffer Buffer containing the preprocessed text to output.
*/
void debug_callback_stderr( WZ_DECL_UNUSED void ** data, const char * outputBuffer )
{
if ( !strchr( outputBuffer, '\n' ) ) {
fprintf( stderr, "%s\n", outputBuffer );
} else {
fprintf( stderr, "%s", outputBuffer );
}
}
/**
* Callback for outputting to a win32 debugger
*
* \param data Ignored. Use NULL.
* \param outputBuffer Buffer containing the preprocessed text to output.
*/
#if defined WIN32 && defined DEBUG
void debug_callback_win32debug( void ** data, const char * outputBuffer )
{
char tmpStr[MAX_LEN_LOG_LINE];
strcpy( tmpStr, outputBuffer );
if ( !strchr( tmpStr, '\n' ) ) {
strcat( tmpStr, "\n" );
}
OutputDebugStringA( tmpStr );
}
#endif // WIN32
/**
* Callback for outputing to a file
*
* \param data Filehandle to output to.
* \param outputBuffer Buffer containing the preprocessed text to output.
*/
void debug_callback_file( void ** data, const char * outputBuffer )
{
FILE * logfile = (FILE*)*data;
if ( !strchr( outputBuffer, '\n' ) ) {
fprintf( logfile, "%s\n", outputBuffer );
} else {
fprintf( logfile, "%s", outputBuffer );
}
}
/**
* Setup the file callback
*
* Sets data to the filehandle opened for the filename found in data.
*
* \param[in,out] data In: The filename to output to.
* Out: The filehandle.
*/
void debug_callback_file_init( void ** data )
{
const char * filename = (const char *)*data;
FILE * logfile = NULL;
assert( strlen( filename ) < MAX_FILENAME_SIZE );
logfile = fopen( filename, "a" );
if (!logfile) {
fprintf( stderr, "Could not open %s for appending!\n", filename );
} else {
setbuf( logfile, NULL );
fprintf( logfile, "\n--- Starting log ---\n" );
*data = logfile;
}
}
/**
* Shutdown the file callback
*
* Closes the logfile.
*
* \param data The filehandle to close.
*/
void debug_callback_file_exit( void ** data )
{
FILE * logfile = (FILE*)*data;
fclose( logfile );
*data = NULL;
}
void debug_init(void)
{
int count = 0;
while (strcmp(code_part_names[count], "last") != 0) {
count++;
}
if (count != LOG_LAST) {
fprintf( stderr, "LOG_LAST != last; whoever edited the debug code last "
"did a mistake.\n" );
fprintf( stderr, "Always edit both the enum in debug.h and the string "
"list in debug.c!\n" );
exit(1);
}
memset( enabled_debug_parts, FALSE, sizeof(enabled_debug_parts) );
enabled_debug_parts[LOG_ERROR] = TRUE;
#ifdef DEBUG
enabled_debug_parts[LOG_WARNING] = TRUE;
#endif
}
void debug_exit(void)
{
debug_callback * curCallback = callbackRegistry, * tmpCallback = NULL;
while ( curCallback )
{
if ( curCallback->exit )
curCallback->exit( &curCallback->data );
tmpCallback = curCallback->next;
free( curCallback );
curCallback = tmpCallback;
}
callbackRegistry = NULL;
}
void debug_register_callback( debug_callback_fn callback, debug_callback_init init, debug_callback_exit exit, void * data )
{
debug_callback * curCallback = callbackRegistry, * tmpCallback = NULL;
tmpCallback = (debug_callback*)malloc(sizeof(debug_callback));
tmpCallback->next = NULL;
tmpCallback->callback = callback;
tmpCallback->init = init;
tmpCallback->exit = exit;
tmpCallback->data = data;
if ( tmpCallback->init )
tmpCallback->init( &tmpCallback->data );
if ( !curCallback )
{
callbackRegistry = tmpCallback;
return;
}
while ( curCallback->next )
curCallback = curCallback->next;
curCallback->next = tmpCallback;
}
BOOL debug_enable_switch(const char *str)
{
int part = code_part_from_str(str);
if (part != LOG_LAST) {
enabled_debug_parts[part] = !enabled_debug_parts[part];
}
if (part == LOG_ALL) {
memset(enabled_debug_parts, TRUE, sizeof(enabled_debug_parts));
}
return (part != LOG_LAST);
}
void debug( code_part part, const char *str, ... )
{
va_list ap;
static char inputBuffer[2][MAX_LEN_LOG_LINE];
static char outputBuffer[MAX_LEN_LOG_LINE+MAX_LEN_DEBUG_PART];
static BOOL useInputBuffer1 = FALSE;
debug_callback * curCallback = callbackRegistry;
static unsigned int repeated = 0; /* times current message repeated */
static unsigned int next = 2; /* next total to print update */
static unsigned int prev = 0; /* total on last update */
/* Not enabled debugging for this part? Punt! */
if (!enabled_debug_parts[part]) {
return;
}
va_start(ap, str);
vsnprintf( useInputBuffer1 ? inputBuffer[1] : inputBuffer[0], MAX_LEN_LOG_LINE, str, ap );
va_end(ap);
if ( strncmp( inputBuffer[0], inputBuffer[1], MAX_LEN_LOG_LINE - 1 ) == 0 ) {
// Received again the same line
repeated++;
if (repeated == next) {
snprintf( outputBuffer, sizeof(outputBuffer), "last message repeated %d times", repeated - prev );
if (repeated > 2) {
cat_snprintf( outputBuffer, sizeof(outputBuffer), " (total %d repeats)", repeated );
}
while (curCallback)
{
curCallback->callback( &curCallback->data, outputBuffer );
curCallback = curCallback->next;
}
curCallback = callbackRegistry;
prev = repeated;
next *= 2;
}
} else {
// Received another line, cleanup the old
if (repeated > 0 && repeated != prev && repeated != 1) {
/* just repeat the previous message when only one repeat occurred */
snprintf( outputBuffer, sizeof(outputBuffer), "last message repeated %d times", repeated - prev );
if (repeated > 2) {
cat_snprintf( outputBuffer, sizeof(outputBuffer), " (total %d repeats)", repeated );
}
while (curCallback)
{
curCallback->callback( &curCallback->data, outputBuffer );
curCallback = curCallback->next;
}
curCallback = callbackRegistry;
}
repeated = 0;
next = 2;
prev = 0;
}
if (!repeated)
{
// Assemble the outputBuffer:
sprintf( outputBuffer, "%s:", code_part_names[part] );
memset( outputBuffer + strlen( code_part_names[part] ) + 1, ' ', MAX_LEN_DEBUG_PART - strlen( code_part_names[part] ) - 1 ); // Fill with whitespaces
snprintf( outputBuffer + MAX_LEN_DEBUG_PART, MAX_LEN_LOG_LINE, useInputBuffer1 ? inputBuffer[1] : inputBuffer[0] ); // Append the message
while (curCallback)
{
curCallback->callback( &curCallback->data, outputBuffer );
curCallback = curCallback->next;
}
}
useInputBuffer1 = !useInputBuffer1; // Swap buffers
}