{* UltraStar Deluxe - Karaoke Game * * UltraStar Deluxe is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program 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. * * This program 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 this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * $URL$ * $Id$ *} unit UConsole; interface {$IFDEF FPC} {$MODE Delphi} {$ENDIF} {$DEFINE SYNCHRONIZE_CONSOLE} procedure ConsoleWriteLn(const msg: string); implementation uses // Do not include the unit 'Libc', it also defines TRTLCriticalSection, // use the TRTLCriticalSection {$IFDEF MSWINDOWS} Windows, {$ENDIF} SysUtils, SyncObjs, Classes; procedure _ConsoleWriteLn(const aString: string); {$IFDEF HasInline}inline;{$ENDIF} forward; {$IFDEF SYNCHRONIZE_CONSOLE} type {** * FPC uses threadvars (TLS) managed by FPC for console output locking. * Using WriteLn() from external threads (like in SDL callbacks) * will crash the program as those threadvars have never been initialized. * Access to the console by two non-managed threads in Delphi also causes a * crash. * * The solution is to create an FPC/Delphi-managed thread which has the TLS data * and use it to handle the console-output (hence it is called Console-Handler) * This must be a thread managed by FPC/Delphi, otherwise (e.g. SDL-thread) * it will crash when using Writeln. *} TConsoleHandler = class(TThread) private fMessageList: TStringList; fConsoleCriticalSection: TCriticalSection; fConsoleEvent: TEvent; public constructor Create(); destructor Destroy(); override; procedure WriteLn(const msg: string); protected procedure Execute(); override; end; var ConsoleHandler: TConsoleHandler; constructor TConsoleHandler.Create(); begin inherited Create(false); // init thread-safe output fMessageList := TStringList.Create(); fConsoleCriticalSection := TCriticalSection.Create; fConsoleEvent := TEvent.Create(nil, false, false, 'ConsoleEvent'); end; destructor TConsoleHandler.Destroy(); begin // terminate console-handler fConsoleCriticalSection.Enter; Terminate; fConsoleEvent.SetEvent(); fConsoleCriticalSection.Leave(); WaitFor(); // free data fConsoleCriticalSection.Free; fConsoleEvent.Free; fMessageList.Free; inherited; end; {* * The console-handlers main-function. *} procedure TConsoleHandler.Execute(); var i: integer; begin while (true) do begin // wait for new output or quit-request fConsoleEvent.WaitFor(INFINITE); fConsoleCriticalSection.Enter; try // output pending messages for i := 0 to fMessageList.Count - 1 do begin _ConsoleWriteLn(fMessageList[i]); end; fMessageList.Clear(); // use local quit-variable to avoid accessing // ConsoleQuit outside of the critical section if (Terminated) then Break; finally fConsoleEvent.ResetEvent(); fConsoleCriticalSection.Leave; end; end; end; procedure TConsoleHandler.WriteLn(const msg: string); begin // TODO: check for the main-thread and use a simple _ConsoleWriteLn() then? //GetCurrentThreadThreadId(); fConsoleCriticalSection.Enter; try fMessageList.Add(msg); fConsoleEvent.SetEvent(); finally fConsoleCriticalSection.Leave; end; end; {$ENDIF} (* * Write to console if one is available. * It checks if a console is available before output so it will not * crash on windows if none is available. * Do not use this function directly because it is not thread-safe, * use ConsoleWriteLn() instead. *) procedure _ConsoleWriteLn(const aString: string); {$IFDEF HasInline}inline;{$ENDIF} begin {$IFDEF MSWINDOWS} // sanity check to avoid crashes with writeln() if (IsConsole) then begin {$ENDIF} Writeln(aString); {$IFDEF MSWINDOWS} end; {$ENDIF} end; procedure ConsoleWriteLn(const msg: string); begin {$IFDEF CONSOLE} {$IFDEF SYNCHRONIZE_CONSOLE} ConsoleHandler.WriteLn(msg); {$ELSE} _ConsoleWriteLn(msg); {$ENDIF} {$ENDIF} end; procedure InitConsoleOutput(); begin {$IFDEF SYNCHRONIZE_CONSOLE} ConsoleHandler := TConsoleHandler.Create; {$ENDIF} end; procedure FinalizeConsoleOutput(); begin {$IFDEF SYNCHRONIZE_CONSOLE} ConsoleHandler.Free; {$ENDIF} end; initialization InitConsoleOutput(); finalization FinalizeConsoleOutput(); end.