aboutsummaryrefslogtreecommitdiffstats
path: root/mediaplugin/src/base/UConsole.pas
blob: 102205ccc99ad14992218ef2bfc2a633027f465c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
{* 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.