From cfd4100c6e387a72d344b5c22d0e693c1349f341 Mon Sep 17 00:00:00 2001 From: tosh Date: Sun, 19 May 2013 14:52:01 +0200 Subject: [PATCH] revises log display --- interface/src/Application.cpp | 2 +- interface/src/Log.cpp | 505 ++++++++++++++++++---------------- interface/src/Log.h | 99 +++---- 3 files changed, 310 insertions(+), 296 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1f34ed3c09..3f9186946a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -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) { diff --git a/interface/src/Log.cpp b/interface/src/Log.cpp index 5b50074444..af3b2f90fb 100644 --- a/interface/src/Log.cpp +++ b/interface/src/Log.cpp @@ -8,313 +8,356 @@ #include "Log.h" -#include "InterfaceConfig.h" #include #include +#include + +#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; } diff --git a/interface/src/Log.h b/interface/src/Log.h index 17c693142c..55c22064a8 100644 --- a/interface/src/Log.h +++ b/interface/src/Log.h @@ -11,87 +11,58 @@ #include #include -#include -#include - -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