From 1868f6714c488a2ee70416ebd335ffe3d79fa665 Mon Sep 17 00:00:00 2001 From: Antoine Fontaine Date: Mon, 22 Mar 2021 02:10:47 +0100 Subject: [PATCH] radiant/console: make logging thread-safe --- radiant/console.cpp | 133 +++++++++++++++++++++++++++----------------- radiant/console.h | 3 +- 2 files changed, 83 insertions(+), 53 deletions(-) diff --git a/radiant/console.cpp b/radiant/console.cpp index e6479cb1..290e4429 100644 --- a/radiant/console.cpp +++ b/radiant/console.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "gtkutil/accelerator.h" #include "gtkutil/messagebox.h" @@ -45,6 +46,13 @@ FILE* g_hLogFile; bool g_Console_enableLogging = false; +struct Gtk_Idle_Print_Data { + int level; + char *buf; + std::size_t length; + bool contains_newline; +}; + // called whenever we need to open/close/check the console log file void Sys_LogFile( bool enable ){ if ( enable && !g_hLogFile ) { @@ -146,6 +154,69 @@ std::size_t write( const char* buffer, std::size_t length ){ } }; +// This function is meant to be used with gtk_idle_add. It will free its argument. +static gboolean Gtk_Idle_Print( gpointer data ){ + Gtk_Idle_Print_Data *args = reinterpret_cast(data); + g_assert(g_console); + + auto buffer = gtk_text_view_get_buffer( g_console ); + + GtkTextIter iter; + gtk_text_buffer_get_end_iter( buffer, &iter ); + + static auto end = gtk_text_buffer_create_mark( buffer, "end", &iter, FALSE ); + + const GdkColor yellow = { 0, 0xb0ff, 0xb0ff, 0x0000 }; + const GdkColor red = { 0, 0xffff, 0x0000, 0x0000 }; + + static auto error_tag = gtk_text_buffer_create_tag( buffer, "red_foreground", "foreground-gdk", &red, NULL ); + static auto warning_tag = gtk_text_buffer_create_tag( buffer, "yellow_foreground", "foreground-gdk", &yellow, NULL ); + static auto standard_tag = gtk_text_buffer_create_tag( buffer, "black_foreground", NULL ); + GtkTextTag* tag; + switch ( args->level ) + { + case SYS_WRN: + tag = warning_tag; + break; + case SYS_ERR: + tag = error_tag; + break; + case SYS_STD: + case SYS_VRB: + default: + tag = standard_tag; + break; + } + + { + GtkTextBufferOutputStream textBuffer( buffer, &iter, tag ); + if ( !globalCharacterSet().isUTF8() ) { + BufferedTextOutputStream buffered( textBuffer ); + buffered << StringRange( args->buf, args->buf + args->length ); + } + else + { + textBuffer << StringRange( args->buf, args->buf + args->length ); + } + } + + // update console widget immediatly if we're doing something time-consuming + if ( args->contains_newline ) { + gtk_text_view_scroll_mark_onscreen( g_console, end ); + + if ( !ScreenUpdates_Enabled() && gtk_widget_get_realized( g_console ) ) { + ScreenUpdates_process(); + } + } + + free( args->buf ); + free( args ); + + return FALSE; // call this once, not repeatedly +} + +// Print logs to the in-game console and/or to the log file. +// This function is thread safe. std::size_t Sys_Print( int level, const char* buf, std::size_t length ){ bool contains_newline = std::find( buf, buf + length, '\n' ) != buf + length; @@ -154,66 +225,24 @@ std::size_t Sys_Print( int level, const char* buf, std::size_t length ){ } if ( g_hLogFile != 0 ) { + // prevent parallel write + static std::mutex log_file_mutex; + std::lock_guard guard(log_file_mutex); + fwrite( buf, 1, length, g_hLogFile ); if ( contains_newline ) { fflush( g_hLogFile ); } } - if ( level != SYS_NOCON ) { - if ( g_console ) { - auto buffer = gtk_text_view_get_buffer( g_console ); - - GtkTextIter iter; - gtk_text_buffer_get_end_iter( buffer, &iter ); - - static auto end = gtk_text_buffer_create_mark( buffer, "end", &iter, FALSE ); - - const GdkColor yellow = { 0, 0xb0ff, 0xb0ff, 0x0000 }; - const GdkColor red = { 0, 0xffff, 0x0000, 0x0000 }; - - static auto error_tag = gtk_text_buffer_create_tag( buffer, "red_foreground", "foreground-gdk", &red, NULL ); - static auto warning_tag = gtk_text_buffer_create_tag( buffer, "yellow_foreground", "foreground-gdk", &yellow, NULL ); - static auto standard_tag = gtk_text_buffer_create_tag( buffer, "black_foreground", NULL ); - GtkTextTag* tag; - switch ( level ) - { - case SYS_WRN: - tag = warning_tag; - break; - case SYS_ERR: - tag = error_tag; - break; - case SYS_STD: - case SYS_VRB: - default: - tag = standard_tag; - break; - } - - - { - GtkTextBufferOutputStream textBuffer( buffer, &iter, tag ); - if ( !globalCharacterSet().isUTF8() ) { - BufferedTextOutputStream buffered( textBuffer ); - buffered << StringRange( buf, buf + length ); - } - else - { - textBuffer << StringRange( buf, buf + length ); - } - } - - // update console widget immediatly if we're doing something time-consuming - if ( contains_newline ) { - gtk_text_view_scroll_mark_onscreen( g_console, end ); - - if ( !ScreenUpdates_Enabled() && gtk_widget_get_realized( g_console ) ) { - ScreenUpdates_process(); - } - } + if ( level != SYS_NOCON && g_console ) { + auto data = reinterpret_cast( malloc( sizeof(struct Gtk_Idle_Print_Data) ) ); + if (data != nullptr) { + *data = { level, g_strndup(buf, length), length, contains_newline }; + gdk_threads_add_idle(Gtk_Idle_Print, (gpointer)data); } } + return length; } diff --git a/radiant/console.h b/radiant/console.h index 8bbe71e1..44d63f10 100644 --- a/radiant/console.h +++ b/radiant/console.h @@ -31,13 +31,14 @@ #define SYS_ERR 3 ///< error #define SYS_NOCON 4 ///< no console, only print to the file (useful whenever Sys_Printf and output IS the problem) -std::size_t Sys_Print( int level, const char* buf, std::size_t length ); class TextOutputStream; TextOutputStream& getSysPrintOutputStream(); TextOutputStream& getSysPrintErrorStream(); ui::Widget Console_constructWindow( ui::Window toplevel ); +std::size_t Sys_Print( int level, const char* buf, std::size_t length ); + // will open/close the log file based on the parameter void Sys_LogFile( bool enable ); extern bool g_Console_enableLogging; -- 2.39.2