revises log display

This commit is contained in:
tosh 2013-05-19 14:52:01 +02:00
parent 2120ab9b1e
commit cfd4100c6e
3 changed files with 310 additions and 296 deletions

View file

@ -1690,7 +1690,7 @@ void Application::displayOverlay() {
glPointSize(1.0f);
if (_renderStatsOn->isChecked()) { displayStats(); }
if (_logOn->isChecked()) { logger.render(_glWidget->width(), _glWidget->height()); }
if (_logOn->isChecked()) { logdisplay::Render(_glWidget->width(), _glWidget->height()); }
// Show chat entry field
if (_chatEntryOn) {

View file

@ -8,313 +8,356 @@
#include "Log.h"
#include "InterfaceConfig.h"
#include <string.h>
#include <stdarg.h>
#include <pthread.h>
#include "InterfaceConfig.h"
#include "Util.h"
#include "ui/TextRenderer.h"
namespace {
// anonymous namespace - everything in here only exists within this very .cpp file
// just as 'static' on every effective line in plain C
unsigned const CHARACTER_BUFFER_SIZE = 16384; // number of character that are buffered
unsigned const LINE_BUFFER_SIZE = 256; // number of lines that are buffered
unsigned const MAX_MESSAGE_LENGTH = 512; // maximum number of characters for a message
namespace logdisplay {
const char* FONT_FAMILY = SANS_FONT_FAMILY;
bool const TEXT_MONOSPACED = true;
class Logger {
public:
Logger();
~Logger();
float const TEXT_RED = 0.7f;
float const TEXT_GREEN = 0.6f;
float const TEXT_BLUE = 1.0f;
inline void render(unsigned screenWidth, unsigned screenHeight);
// magic constants from the GLUT spec
// http://www.opengl.org/resources/libraries/glut/spec3/node78.html
// ultimately this stuff should be in Util.h??
float const CHAR_UP = 119.05f;
float const CHAR_DOWN = 33.33f;
float const CHAR_WIDTH = 104.76f;
// derived values
float const CHAR_HEIGHT = CHAR_UP + CHAR_DOWN;
float const CHAR_FRACT_BASELINE = CHAR_DOWN / CHAR_HEIGHT;
inline void setStream(FILE* stream);
inline void setLogWidth(unsigned pixels);
inline void setCharacterSize(unsigned width, unsigned height);
// unsigned integer division rounded towards infinity
unsigned divRoundUp(unsigned l, unsigned r) { return (l + r - 1) / r; }
}
// format, eventually forward, and add the log message (called by printLog)
inline int vprint(char const* fmt, va_list);
Log::Log(FILE* tPipeTo, unsigned bufferedLines,
unsigned defaultLogWidth, unsigned defaultCharWidth, unsigned defaultCharHeight) :
private:
// don't copy/assign
Logger(Logger const&); // = delete;
Logger& operator=(Logger const&); // = delete;
_stream(tPipeTo),
_chars(0l),
_lines(0l),
_logWidth(defaultLogWidth) {
// add formatted message for console diplay (called by vprint)
inline void addMessage(char const*);
pthread_mutex_init(& _mutex, 0l);
TextRenderer _textRenderer;
FILE* _stream; // FILE as secondary destination for log messages
char* _chars; // character buffer base address
char* _charsEnd; // character buffer, exclusive end
char** _lines; // line buffer base address
char** _linesEnd; // line buffer, exclusive end
// allocate twice as much (so we have spare space for a copy not to block
// logging from other threads during 'render')
_chars = new char[CHARACTER_BUFFER_SIZE * 2];
_charsEnd = _chars + CHARACTER_BUFFER_SIZE;
_lines = new char*[LINE_BUFFER_SIZE * 2];
_linesEnd = _lines + LINE_BUFFER_SIZE;
char* _writePos; // character position to write to
char* _writeLineStartPos; // character position where line being written starts
char** _lastLinePos; // last line in the log
unsigned _writtenInLine; // character counter for line wrapping
unsigned _lineLength; // number of characters before line wrap
// initialize the log to all empty lines
_chars[0] = '\0';
_writePos = _chars;
_writeLineStartPos = _chars;
_lastLinePos = _lines;
_writtenInLine = 0;
memset(_lines, 0, LINE_BUFFER_SIZE * sizeof(char*));
unsigned _logWidth; // width of the log in pixels
unsigned _charWidth; // width of a character in pixels
unsigned _charHeight; // height of a character in pixels
setCharacterSize(defaultCharWidth, defaultCharHeight);
}
pthread_mutex_t _mutex;
};
Log::~Log() {
//
// Initialization / state management
//
delete[] _chars;
delete[] _lines;
}
Logger::Logger() :
inline void Log::addMessage(char const* ptr) {
_textRenderer(MONO_FONT_FAMILY, -1, -1, false, TextRenderer::SHADOW_EFFECT),
_stream(DEFAULT_STREAM),
_chars(0l),
_lines(0l),
_logWidth(DEFAULT_CONSOLE_WIDTH) {
// precondition: mutex is locked so noone gets in our way
pthread_mutex_init(& _mutex, 0l);
// T-pipe, if requested
if (_stream != 0l) {
fprintf(_stream, "%s", ptr);
// allocate twice as much (so we have spare space for a copy not to block
// logging from other threads during 'render')
_chars = new char[CHARACTER_BUFFER_SIZE * 2];
_charsEnd = _chars + CHARACTER_BUFFER_SIZE;
_lines = new char*[LINE_BUFFER_SIZE * 2];
_linesEnd = _lines + LINE_BUFFER_SIZE;
// initialize the log to all empty lines
_chars[0] = '\0';
_writePos = _chars;
_writeLineStartPos = _chars;
_lastLinePos = _lines;
_writtenInLine = 0;
memset(_lines, 0, LINE_BUFFER_SIZE * sizeof(char*));
setCharacterSize(DEFAULT_CHAR_WIDTH, DEFAULT_CHAR_HEIGHT);
}
while (*ptr != '\0') {
// process the characters
char c = *ptr++;
if (c == '\t') {
Logger::~Logger() {
// found TAB -> write SPACE
c = ' ';
delete[] _chars;
delete[] _lines;
}
} else if (c == '\n') {
inline void Logger::setStream(FILE* stream) {
// found LF -> write NUL (c == '\0' tells us to wrap, below)
c = '\0';
}
*_writePos++ = c;
pthread_mutex_lock(& _mutex);
_stream = stream;
pthread_mutex_unlock(& _mutex);
}
if (_writePos == _charsEnd) {
// reached the end of the circular character buffer? -> start over
_writePos = _chars;
inline void Logger::setLogWidth(unsigned pixels) {
pthread_mutex_lock(& _mutex);
_logWidth = pixels;
_lineLength = _logWidth / _charWidth;
pthread_mutex_unlock(& _mutex);
}
inline void Logger::setCharacterSize(unsigned width, unsigned height) {
pthread_mutex_lock(& _mutex);
_charWidth = width;
_charHeight = height;
_lineLength = _logWidth / _charWidth;
pthread_mutex_unlock(& _mutex);
}
//
// Logging
//
inline int Logger::vprint(char const* fmt, va_list args) {
pthread_mutex_lock(& _mutex);
// print to buffer
char buf[MAX_MESSAGE_LENGTH];
int n = vsnprintf(buf, MAX_MESSAGE_LENGTH, fmt, args);
if (n > 0) {
// all fine? log the message
addMessage(buf);
} else {
// error? -> mutter on stream or stderr
fprintf(_stream != 0l ? _stream : stderr,
"Log: Failed to log message with format string = \"%s\".\n", fmt);
}
if (++_writtenInLine >= _lineLength || c == '\0') {
pthread_mutex_unlock(& _mutex);
return n;
}
// new line? store its start to the line buffer and mark next line as empty
++_lastLinePos;
if (_lastLinePos == _linesEnd) {
_lastLinePos = _lines;
_lastLinePos[1] = 0l;
} else if (_lastLinePos + 1 != _linesEnd) {
_lastLinePos[1] = 0l;
} else {
_lines[0] = 0l;
}
*_lastLinePos = _writeLineStartPos;
inline void Logger::addMessage(char const* ptr) {
// debug mode: make sure all line pointers we write here are valid
assert(! (_lastLinePos < _lines || _lastLinePos >= _linesEnd));
assert(! (*_lastLinePos < _chars || *_lastLinePos >= _charsEnd));
// precondition: mutex is locked so noone gets in our way
// terminate line, unless done already
if (c != '\0') {
*_writePos++ = '\0';
// T-pipe, if requested
if (_stream != 0l) {
fprintf(_stream, "%s", ptr);
}
if (_writePos == _charsEnd) {
_writePos = _chars;
}
while (*ptr != '\0') {
// process the characters
char c = *ptr++;
if (c == '\t') {
// found TAB -> write SPACE
c = ' ';
} else if (c == '\n') {
// found LF -> write NUL (c == '\0' tells us to wrap, below)
c = '\0';
}
*_writePos++ = c;
if (_writePos == _charsEnd) {
// reached the end of the circular character buffer? -> start over
_writePos = _chars;
}
// remember start position in character buffer for next line and reset character count
_writeLineStartPos = _writePos;
_writtenInLine = 0;
if (++_writtenInLine >= _lineLength || c == '\0') {
// new line? store its start to the line buffer and mark next line as empty
++_lastLinePos;
if (_lastLinePos == _linesEnd) {
_lastLinePos = _lines;
_lastLinePos[1] = 0l;
} else if (_lastLinePos + 1 != _linesEnd) {
_lastLinePos[1] = 0l;
} else {
_lines[0] = 0l;
}
*_lastLinePos = _writeLineStartPos;
// debug mode: make sure all line pointers we write here are valid
assert(! (_lastLinePos < _lines || _lastLinePos >= _linesEnd));
assert(! (*_lastLinePos < _chars || *_lastLinePos >= _charsEnd));
// terminate line, unless done already
if (c != '\0') {
*_writePos++ = '\0';
if (_writePos == _charsEnd) {
_writePos = _chars;
}
}
// remember start position in character buffer for next line and reset character count
_writeLineStartPos = _writePos;
_writtenInLine = 0;
}
}
}
}
//
// Rendering
//
int Log::vprint(char const* fmt, va_list args) {
pthread_mutex_lock(& _mutex);
inline void Logger::render(unsigned screenWidth, unsigned screenHeight) {
// print to buffer
char buf[MAX_MESSAGE_LENGTH];
int n = vsnprintf(buf, MAX_MESSAGE_LENGTH, fmt, args);
if (n > 0) {
// rendering might take some time, so create a local copy of the portion we need
// instead of having to hold the mutex all the time
pthread_mutex_lock(& _mutex);
// all fine? log the message
addMessage(buf);
// determine number of visible lines (integer division rounded up)
unsigned showLines = (screenHeight + _charHeight - 1) / _charHeight;
} else {
char** lastLine = _lastLinePos;
char** firstLine = _lastLinePos;
// error? -> mutter on stream or stderr
fprintf(_stream != 0l ? _stream : stderr,
"Log: Failed to log message with format string = \"%s\".\n", fmt);
}
if (! *lastLine) {
// empty log
pthread_mutex_unlock(& _mutex);
return;
}
pthread_mutex_unlock(& _mutex);
return n;
}
// scan for first line
for (int n = 2; n <= showLines; ++n) {
void Log::operator()(char const* fmt, ...) {
char** prevFirstLine = firstLine;
--firstLine;
if (firstLine < _lines) {
firstLine = _linesEnd - 1;
}
if (! *firstLine) {
firstLine = prevFirstLine;
showLines = n - 1;
break;
}
va_list args;
va_start(args,fmt);
vprint(fmt, args);
va_end(args);
}
// debug mode: make sure all line pointers we find here are valid
assert(! (firstLine < _lines || firstLine >= _linesEnd));
assert(! (*firstLine < _chars || *firstLine >= _charsEnd));
}
void Log::setLogWidth(unsigned pixels) {
// copy the line buffer portion into a contiguous region at _linesEnd
if (firstLine <= lastLine) {
pthread_mutex_lock(& _mutex);
_logWidth = pixels;
_lineLength = _logWidth / _charWidth;
pthread_mutex_unlock(& _mutex);
}
memcpy(_linesEnd, firstLine, showLines * sizeof(char*));
void Log::setCharacterSize(unsigned width, unsigned height) {
} else {
pthread_mutex_lock(& _mutex);
_charWidth = width;
_charHeight = height;
_charYoffset = height * CHAR_FRACT_BASELINE;
_charScale = float(width) / CHAR_WIDTH;
_charAspect = (height * CHAR_WIDTH) / (width * CHAR_HEIGHT);
_lineLength = _logWidth / _charWidth;
pthread_mutex_unlock(& _mutex);
}
unsigned atEnd = _linesEnd - firstLine;
memcpy(_linesEnd, firstLine, atEnd * sizeof(char*));
memcpy(_linesEnd + atEnd, _lines, (showLines - atEnd) * sizeof(char*));
}
static TextRenderer* textRenderer() {
static TextRenderer* renderer = new TextRenderer(FONT_FAMILY, -1, -1, false, TextRenderer::SHADOW_EFFECT);
return renderer;
}
// copy relevant char buffer portion and determine information to remap the pointers
char* firstChar = *firstLine;
char* lastChar = *lastLine + strlen(*lastLine) + 1;
ptrdiff_t charOffset = _charsEnd - firstChar, charOffsetBeforeFirst = 0;
if (firstChar <= lastChar) {
void Log::render(unsigned screenWidth, unsigned screenHeight) {
memcpy(_charsEnd, firstChar, lastChar - firstChar + 1);
// rendering might take some time, so create a local copy of the portion we need
// instead of having to hold the mutex all the time
pthread_mutex_lock(& _mutex);
} else {
// determine number of visible lines
unsigned showLines = divRoundUp(screenHeight, _charHeight);
unsigned atEnd = _charsEnd - firstChar;
memcpy(_charsEnd, firstChar, atEnd);
memcpy(_charsEnd + atEnd, _chars, lastChar + 1 - _chars);
char** lastLine = _lastLinePos;
char** firstLine = _lastLinePos;
charOffsetBeforeFirst = _charsEnd + atEnd - _chars;
}
if (! *lastLine) {
// empty log
// determine geometry information from font metrics
QFontMetrics const& fontMetrics = _textRenderer.metrics();
int yStep = fontMetrics.lineSpacing();
// scale
float xScale = float(_charWidth) / fontMetrics.width('*');
float yScale = float(_charHeight) / yStep;
// scaled translation
int xStart = int((screenWidth - _logWidth) / xScale);
int yStart = screenHeight / yScale - fontMetrics.descent();
// first line to render
char** line = _linesEnd + showLines;
// ok, now the lock can be released - we have all we need
// and won't hold it while talking to OpenGL
pthread_mutex_unlock(& _mutex);
return;
}
// scan for first line
for (int n = 2; n <= showLines; ++n) {
glPushMatrix();
glScalef(xScale, yScale, 1.0f);
for (int y = yStart; y > 0; y -= yStep) {
char** prevFirstLine = firstLine;
--firstLine;
if (firstLine < _lines) {
firstLine = _linesEnd - 1;
// debug mode: check line pointer is valid
assert(! (line < _linesEnd || line >= _linesEnd + (_linesEnd - _lines)));
// get character pointer
if (--line < _linesEnd) {
break;
}
char* chars = *line;
// debug mode: check char pointer we find is valid
assert(! (chars < _chars || chars >= _charsEnd));
// remap character pointer it to copied buffer
chars += chars >= firstChar ? charOffset : charOffsetBeforeFirst;
// debug mode: check char pointer is still valid (in new range)
assert(! (chars < _charsEnd || chars >= _charsEnd + (_charsEnd - _chars)));
// render the string
glColor3f(TEXT_COLOR_RED, TEXT_COLOR_GREEN, TEXT_COLOR_BLUE);
_textRenderer.draw(xStart, y, chars);
//fprintf(stderr, "Logger::render, message = \"%s\"\n", chars);
}
if (! *firstLine) {
firstLine = prevFirstLine;
showLines = n - 1;
break;
}
// debug mode: make sure all line pointers we find here are valid
assert(! (firstLine < _lines || firstLine >= _linesEnd));
assert(! (*firstLine < _chars || *firstLine >= _charsEnd));
glPopMatrix();
}
// copy the line buffer portion into a contiguous region at _linesEnd
if (firstLine <= lastLine) {
memcpy(_linesEnd, firstLine, showLines * sizeof(char*));
//
// There's one Logger and it exists globally...
//
Logger logger;
} else {
// Entrypoints
void Render(unsigned screenWidth, unsigned screenHeight) { logger.render(screenWidth, screenHeight); }
void SetStream(FILE* stream) { logger.setStream(stream); }
void SetLogWidth(unsigned pixels) { logger.setLogWidth(pixels); }
void SetCharacterSize(unsigned width, unsigned height) { logger.setCharacterSize(width, height); }
unsigned atEnd = _linesEnd - firstLine;
memcpy(_linesEnd, firstLine, atEnd * sizeof(char*));
memcpy(_linesEnd + atEnd, _lines, (showLines - atEnd) * sizeof(char*));
}
} // namespace logdisplay
// copy relevant char buffer portion and determine information to remap the pointers
char* firstChar = *firstLine;
char* lastChar = *lastLine + strlen(*lastLine) + 1;
ptrdiff_t charOffset = _charsEnd - firstChar, charOffsetBeforeFirst = 0;
if (firstChar <= lastChar) {
memcpy(_charsEnd, firstChar, lastChar - firstChar + 1);
} else {
unsigned atEnd = _charsEnd - firstChar;
memcpy(_charsEnd, firstChar, atEnd);
memcpy(_charsEnd + atEnd, _chars, lastChar + 1 - _chars);
charOffsetBeforeFirst = _charsEnd + atEnd - _chars;
}
// get values for rendering
int yStep = textRenderer()->metrics().lineSpacing();
int yStart = screenHeight - textRenderer()->metrics().descent();
// render text
char** line = _linesEnd + showLines;
int x = screenWidth - _logWidth;
pthread_mutex_unlock(& _mutex);
// ok, we got all we need
for (int y = yStart; y > 0; y -= yStep) {
// debug mode: check line pointer is valid
assert(! (line < _linesEnd || line >= _linesEnd + (_linesEnd - _lines)));
// get character pointer
if (--line < _linesEnd) {
break;
}
char* chars = *line;
// debug mode: check char pointer we find is valid
assert(! (chars < _chars || chars >= _charsEnd));
// remap character pointer it to copied buffer
chars += chars >= firstChar ? charOffset : charOffsetBeforeFirst;
// debug mode: check char pointer is still valid (in new range)
assert(! (chars < _charsEnd || chars >= _charsEnd + (_charsEnd - _chars)));
// render the string
glColor3f(TEXT_RED, TEXT_GREEN, TEXT_BLUE);
textRenderer()->draw(x, y, chars);
//fprintf(stderr, "Log::render, message = \"%s\"\n", chars);
}
}
Log logger;
int printLog(char const* fmt, ...) {
int result;
va_list args;
va_start(args,fmt);
result = logger.vprint(fmt, args);
result = logdisplay::logger.vprint(fmt, args);
va_end(args);
return result;
}

View file

@ -11,87 +11,58 @@
#include <stdio.h>
#include <stdarg.h>
#include <glm/glm.hpp>
#include <pthread.h>
class Log;
//
// Call it as you would call 'printf'.
// Log function. Call it as you would call 'printf'.
//
int printLog(char const* fmt, ...);
//
// Logging control.
//
namespace logdisplay {
void Render(unsigned screenWidth, unsigned screenHeight);
// settings
static float const TEXT_COLOR_RED = 0.7f;
static float const TEXT_COLOR_GREEN = 0.6f;
static float const TEXT_COLOR_BLUE = 1.0f;
static FILE* DEFAULT_STREAM = stdout;
static unsigned const DEFAULT_CHAR_WIDTH = 7;
static unsigned const DEFAULT_CHAR_HEIGHT = 16;
static unsigned const DEFAULT_CONSOLE_WIDTH = 400;
void SetStream(FILE* stream);
void SetLogWidth(unsigned pixels);
void SetCharacterSize(unsigned width, unsigned height);
// limits
unsigned const CHARACTER_BUFFER_SIZE = 16384; // number of character that are buffered
unsigned const LINE_BUFFER_SIZE = 256; // number of lines that are buffered
unsigned const MAX_MESSAGE_LENGTH = 512; // maximum number of characters for a message
}
//
// Macro to log OpenGl errors.
// Example: oglLog( glPushMatrix() );
// Example: printGlError( glPushMatrix() );
//
#define oGlLog(stmt) \
#define printLogGlError(stmt) \
stmt; \
{ \
GLenum e = glGetError(); \
if (e != GL_NO_ERROR) { \
printLog(__FILE__ ":" oGlLog_stringize(__LINE__) \
printLog(__FILE__ ":" printLogGlError_stringize(__LINE__) \
" [OpenGL] %s\n", gluErrorString(e)); \
} \
} \
(void) 0
#define oGlLog_stringize(x) oGlLog_stringize_i(x)
#define oGlLog_stringize_i(x) # x
//
// Global instance.
//
extern Log logger;
//
// Logging subsystem.
//
class Log {
FILE* _stream;
char* _chars;
char* _charsEnd;
char** _lines;
char** _linesEnd;
char* _writePos; // character position to write to
char* _writeLineStartPos; // character position where line being written starts
char** _lastLinePos; // last line in the log
unsigned _writtenInLine; // character counter for line wrapping
unsigned _lineLength; // number of characters before line wrap
unsigned _logWidth; // width of the log in pixels
unsigned _charWidth; // width of a character in pixels
unsigned _charHeight; // height of a character in pixels
unsigned _charYoffset; // baseline offset in pixels
float _charScale; // scale factor
float _charAspect; // aspect (h/w)
pthread_mutex_t _mutex;
public:
explicit Log(FILE* tPipeTo = stdout, unsigned bufferedLines = 1024,
unsigned defaultLogWidth = 400, unsigned defaultCharWidth = 6, unsigned defaultCharHeight = 20);
~Log();
void setLogWidth(unsigned pixels);
void setCharacterSize(unsigned width, unsigned height);
void render(unsigned screenWidth, unsigned screenHeight);
void operator()(char const* fmt, ...);
int vprint(char const* fmt, va_list);
private:
// don't copy/assign
Log(Log const&); // = delete;
Log& operator=(Log const&); // = delete;
inline void addMessage(char const*);
friend class LogStream; // for optional iostream-style interface that has to be #included separately
};
#define printLogGlError_stringize(x) printLogGlError_stringize_i(x)
#define printLogGlError_stringize_i(x) # x
#endif