diff --git a/source/lib/res/graphics/unifont.cpp b/source/lib/res/graphics/unifont.cpp index 49e3d63f05..35342e6e6d 100644 --- a/source/lib/res/graphics/unifont.cpp +++ b/source/lib/res/graphics/unifont.cpp @@ -205,6 +205,13 @@ Handle unifont_load(const VfsPath& pathname, size_t flags) LibError unifont_unload(Handle& h) { + H_DEREF(h, UniFont, f); + + // unbind ourself, so people will get errors if + // they draw more text without binding a new font + if (BoundGlyphs == f->glyphs) + BoundGlyphs = NULL; + return h_free(h, H_UniFont); } diff --git a/source/ps/CConsole.cpp b/source/ps/CConsole.cpp index 66f8c872b7..41d9f34615 100644 --- a/source/ps/CConsole.cpp +++ b/source/ps/CConsole.cpp @@ -32,6 +32,7 @@ #include "network/NetServer.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" +#include "ps/Font.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Pyrogenesis.h" @@ -194,6 +195,9 @@ void CConsole::Render() { if (! (m_bVisible || m_bToggle) ) return; + CFont font(CONSOLE_FONT); + font.Bind(); + // animation: slide in from top of screen const float MaxY = m_fHeight; const float DeltaY = (1.0f - m_fVisibleFrac) * MaxY; diff --git a/source/ps/CLogger.cpp b/source/ps/CLogger.cpp index a1f109a1cc..40fb1810aa 100644 --- a/source/ps/CLogger.cpp +++ b/source/ps/CLogger.cpp @@ -20,13 +20,23 @@ #include "CLogger.h" #include "CConsole.h" #include "ConfigDB.h" +#include "lib/ogl.h" #include "lib/path_util.h" +#include "lib/timer.h" #include "lib/utf8.h" +#include "lib/res/graphics/unifont.h" #include "lib/sysdep/sysdep.h" +#include "ps/Font.h" #include #include +static const double RENDER_TIMEOUT = 5.0; // seconds before messages are deleted +static const double RENDER_TIMEOUT_RATE = 10.0; // number of timed-out messages deleted per second +static const size_t RENDER_LIMIT = 20; // maximum messages on screen at once + +extern int g_xres, g_yres; + // Set up a default logger that throws everything away, because that's // better than crashing. (This is particularly useful for unit tests which // don't care about any log output.) @@ -74,6 +84,10 @@ CLogger::CLogger(std::wostream* mainLog, std::wostream* interestingLog, bool tak m_OwnsStreams = takeOwnership; m_UseDebugPrintf = useDebugPrintf; + m_RenderLastEraseTime = -1.0; + // this is called too early to allow us to call timer_Time(), + // so we'll fill in the initial value later + Init(); } @@ -126,6 +140,9 @@ void CLogger::WriteMessage(const wchar_t* message) *m_MainLog << L"

" << message << L"

\n"; m_MainLog->flush(); + // Don't do this since it results in too much noise: +// RenderedMessage r = { Normal, timer_Time(), message }; +// m_RenderMessages.push_back(r); } void CLogger::WriteError(const wchar_t* message) @@ -140,6 +157,9 @@ void CLogger::WriteError(const wchar_t* message) *m_MainLog << L"

ERROR: "<< message << L"

\n"; m_MainLog->flush(); + + RenderedMessage r = { Error, timer_Time(), message }; + m_RenderMessages.push_back(r); } void CLogger::WriteWarning(const wchar_t* message) @@ -154,6 +174,9 @@ void CLogger::WriteWarning(const wchar_t* message) *m_MainLog << L"

WARNING: "<< message << L"

\n"; m_MainLog->flush(); + + RenderedMessage r = { Warning, timer_Time(), message }; + m_RenderMessages.push_back(r); } // Sends the message to the appropriate piece of code @@ -260,6 +283,81 @@ void CLogger::LogError(const wchar_t* fmt, ...) WriteError(buffer); } +void CLogger::Render() +{ + CleanupRenderQueue(); + + CFont font(L"mono-stroke-10"); + int lineSpacing = font.GetLineSpacing(); + font.Bind(); + + glPushMatrix(); + + glScalef(1.0f, -1.0f, 1.0f); + glTranslatef(4.0f, 4.0f + (float)lineSpacing - g_yres, 0.0f); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + for (std::deque::iterator it = m_RenderMessages.begin(); it != m_RenderMessages.end(); ++it) + { + const wchar_t* type; + if (it->method == Normal) + { + type = L"info"; + glColor3f(0.0f, 0.8f, 0.0f); + } + else if (it->method == Warning) + { + type = L"warning"; + glColor3f(1.0f, 1.0f, 0.0f); + } + else + { + type = L"error"; + glColor3f(1.0f, 0.0f, 0.0f); + } + glPushMatrix(); + + glwprintf(L"[%8.3f] %ls: ", it->time, type); + // Display the actual message in white so it's more readable + glColor3f(1.0f, 1.0f, 1.0f); + glwprintf(L"%ls", it->message.c_str()); + + glPopMatrix(); + glTranslatef(0.f, (float)lineSpacing, 0.f); + } + + glDisable(GL_BLEND); + + glPopMatrix(); +} + +void CLogger::CleanupRenderQueue() +{ + if (m_RenderMessages.empty()) + return; + + double now = timer_Time(); + + // Initialise the timer on the first call (since we can't do it in the ctor) + if (m_RenderLastEraseTime == -1.0) + m_RenderLastEraseTime = now; + + // Delete old messages, approximately at the given rate limit (and at most one per frame) + if (now - m_RenderLastEraseTime > 1.0/RENDER_TIMEOUT_RATE) + { + if (m_RenderMessages[0].time + RENDER_TIMEOUT < now) + { + m_RenderMessages.pop_front(); + m_RenderLastEraseTime = now; + } + } + + // If there's still too many then delete the oldest + if (m_RenderMessages.size() > RENDER_LIMIT) + m_RenderMessages.erase(m_RenderMessages.begin(), m_RenderMessages.end() - RENDER_LIMIT); +} TestLogger::TestLogger() { diff --git a/source/ps/CLogger.h b/source/ps/CLogger.h index c638e54d68..21045a1c9d 100644 --- a/source/ps/CLogger.h +++ b/source/ps/CLogger.h @@ -73,6 +73,9 @@ public: void LogMessage(const wchar_t* fmt, ...) WPRINTF_ARGS(2); void LogWarning(const wchar_t* fmt, ...) WPRINTF_ARGS(2); void LogError(const wchar_t* fmt, ...) WPRINTF_ARGS(2); + + // Render recent log messages onto the screen + void Render(); private: void Init(); @@ -80,6 +83,9 @@ private: // -- This function has not been removed because the build would break. void LogUsingMethod(ELogMethod method, const wchar_t* message); + // Delete old timed-out entries from the list of text to render + void CleanupRenderQueue(); + // the output streams std::wostream* m_MainLog; std::wostream* m_InterestingLog; @@ -97,6 +103,15 @@ private: // Used to remember LogOnce messages std::set m_LoggedOnce; + // Used for Render() + struct RenderedMessage + { + ELogMethod method; + double time; + std::wstring message; + }; + std::deque m_RenderMessages; + double m_RenderLastEraseTime; }; /** diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index c4a18fafd8..1fd335b51f 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -238,39 +238,36 @@ void Render() ogl_WarnIfError(); - PROFILE_START( "render fonts" ); - MICROLOG(L"render fonts"); - // overlay mode + // set up overlay mode glPushAttrib(GL_ENABLE_BIT); glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); + glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); - glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); - PROFILE_END( "render fonts" ); - ogl_WarnIfError(); // Temp GUI message GeeTODO - MICROLOG(L"render GUI"); - PROFILE_START( "render gui" ); + PROFILE_START("render gui"); if(g_DoRenderGui) g_GUI->Draw(); - PROFILE_END( "render gui" ); + PROFILE_END("render gui"); ogl_WarnIfError(); // Particle Engine Updating CParticleEngine::GetInstance()->UpdateEmitters(); + ogl_WarnIfError(); + // Text: // Use the GL_ALPHA texture as the alpha channel with a flat colouring @@ -280,26 +277,25 @@ void Render() glEnable(GL_TEXTURE_2D); // -- GL - ogl_WarnIfError(); + glLoadIdentity(); - { - PROFILE( "render console" ); - glLoadIdentity(); - - MICROLOG(L"render console"); - CFont font(CONSOLE_FONT); - font.Bind(); - g_Console->Render(); - } + PROFILE_START("render console"); + g_Console->Render(); + PROFILE_END("render console"); ogl_WarnIfError(); + PROFILE_START("render logger"); + g_Logger->Render(); + PROFILE_END("render logger"); + + ogl_WarnIfError(); // Profile information - PROFILE_START( "render profiling" ); + PROFILE_START("render profiling"); g_ProfileViewer.RenderProfile(); - PROFILE_END( "render profiling" ); + PROFILE_END("render profiling"); ogl_WarnIfError(); diff --git a/source/scriptinterface/ScriptInterface.cpp b/source/scriptinterface/ScriptInterface.cpp index 8cc72fdcd2..4dfb373385 100644 --- a/source/scriptinterface/ScriptInterface.cpp +++ b/source/scriptinterface/ScriptInterface.cpp @@ -101,16 +101,37 @@ void ErrorReporter(JSContext* UNUSED(cx), const char* message, JSErrorReport* re // Functions in the global namespace: -JSBool print(JSContext* cx, JSObject* UNUSED(obj), uintN argc, jsval* argv, jsval* UNUSED(rval)) +JSBool print(JSContext* cx, uintN argc, jsval* vp) { for (uintN i = 0; i < argc; ++i) { std::string str; - if (!ScriptInterface::FromJSVal(cx, argv[i], str)) + if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[i], str)) return JS_FALSE; printf("%s", str.c_str()); } fflush(stdout); + JS_SET_RVAL(cx, vp, JSVAL_VOID); + return JS_TRUE; +} + +JSBool warn(JSContext* cx, uintN UNUSED(argc), jsval* vp) +{ + std::wstring str; + if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], str)) + return JS_FALSE; + LOGWARNING(L"%ls", str.c_str()); + JS_SET_RVAL(cx, vp, JSVAL_VOID); + return JS_TRUE; +} + +JSBool error(JSContext* cx, uintN UNUSED(argc), jsval* vp) +{ + std::wstring str; + if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], str)) + return JS_FALSE; + LOGERROR(L"%ls", str.c_str()); + JS_SET_RVAL(cx, vp, JSVAL_VOID); return JS_TRUE; } @@ -227,7 +248,9 @@ ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, JSContex m_nativeScope = JS_DefineObject(m_cx, m_glob, nativeScopeName, NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineFunction(m_cx, m_glob, "print", ::print, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); + JS_DefineFunction(m_cx, m_glob, "print", (JSNative)::print, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSFUN_FAST_NATIVE); + JS_DefineFunction(m_cx, m_glob, "warn", (JSNative)::warn, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSFUN_FAST_NATIVE); + JS_DefineFunction(m_cx, m_glob, "error", (JSNative)::error, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSFUN_FAST_NATIVE); } ScriptInterface_impl::~ScriptInterface_impl()