Standard input is read in MonitorInputStream().
Code: Select all
// +------------------------------------------------------------------------------+
// | |
// | MadChess is developed by Erik Madsen. Copyright 2014. |
// | MadChess is free software. It is distributed under the GNU General |
// | Public License Version 3 (GPLv3). See License.txt for details. |
// | |
// +------------------------------------------------------------------------------+
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Resources;
using System.Text;
using System.Threading;
namespace MadChess
{
public sealed class UciStream : IDisposable
{
private const int _cacheSizeMegabytes = 128;
private const string _startPositionFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
public const long MovesInfoInterval = 250000;
public const long MovesTimeInterval = 100;
private readonly StringBuilder _stringBuilder;
private readonly Board _board;
private readonly Cache _cache;
private readonly KillerMoves _killerMoves;
private readonly MoveHistory _moveHistory;
private readonly Search _search;
private readonly AutoResetEvent _asyncSignal;
private readonly Queue<string> _asyncQueue;
private readonly object _logLock;
private readonly Stopwatch _mainStopwatch;
private readonly Stopwatch _commandStopwatch;
private Thread _asyncThread;
private FileStream _logStream;
private StreamWriter _logWriter;
private bool _log;
private bool _lastMessageIncludedTimestamp;
private bool _disposed;
public bool Debug;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
public bool Log
{
private get { return _log; }
set
{
_log = value;
if (_log)
{
lock (_logLock)
{
if (_logStream == null)
{
// Create or append to log file.
// Include process ID in log filename to avoid multiple engines interleaving lines in a single log file.
string strEngineLocation = Assembly.GetEntryAssembly().Location;
int intProcessId = Process.GetCurrentProcess().Id;
string strFile = Path.GetFileNameWithoutExtension(strEngineLocation) + "-" + intProcessId + ".log";
_logStream = File.Open(strFile, FileMode.Append, FileAccess.Write, FileShare.Read);
_logWriter = new StreamWriter(_logStream)
{
AutoFlush = true
};
}
}
}
}
}
public UciStream()
{
_stringBuilder = new StringBuilder();
_log = false;
_lastMessageIncludedTimestamp = false;
_disposed = false;
// Create board, cache, killer moves, move history, and search.
_board = new Board();
_cache = new Cache {Capacity = _cacheSizeMegabytes * Cache.PositionsPerMegabyte};
_killerMoves = new KillerMoves(_board, Search.MaxHorizon);
_moveHistory = new MoveHistory(_board);
_search = new Search(this, _board, _cache, _killerMoves, _moveHistory) {MultiPv = 1};
// Create synchronization and diagnostic objects.
_asyncSignal = new AutoResetEvent(false);
_asyncQueue = new Queue<string>();
_logLock = new object();
_mainStopwatch = new Stopwatch();
_commandStopwatch = new Stopwatch();
_mainStopwatch.Start();
}
~UciStream()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool Disposing)
{
if (_disposed) return;
if (Disposing)
{
// Release managed resources.
_search.Dispose();
}
// Release unmanaged resources.
if (_logStream != null)
{
_logStream.Close();
_logStream.Dispose();
}
if (_asyncSignal != null)
{
_asyncSignal.Close();
}
_disposed = true;
}
public void Run()
{
Thread.CurrentThread.Name = "UCI Synchronous";
// Create async thread.
_asyncThread = new Thread(MonitorQueue) { Name = "UCI Asynchronous", IsBackground = true };
_asyncThread.Start();
// Monitor input stream.
MonitorInputStream();
}
private void MonitorInputStream()
{
try
{
do
{
// Read command.
if (Log)
{
LogMessage(CommandDirection.In, "Waiting to read console line", true);
}
string command = Console.ReadLine();
if (Log)
{
// Log command.
LogMessage(CommandDirection.In, command, true);
}
// Dispatch command.
DispatchCommand(command);
} while (true);
}
catch (Exception exception)
{
HandleException(exception);
}
}
private void DispatchCommand(string Command)
{
if (Command == null) return;
// ParseLongAlgebraic command into tokens.
string[] tokens = Tokens.Parse(Command, ' ', '"');
// Do not convert to lowercase because this invalidates FEN strings (where case differentiates white and black pieces).
if (tokens.Length == 0)
{
// No tokens found.
return;
}
// Determine whether to dispatch command on main thread or async thread.
switch (tokens[0].ToLower(CultureInfo.InvariantCulture))
{
case "go":
DispatchOnAsyncThread(tokens);
break;
default:
DispatchOnMainThread(tokens);
break;
}
}
private void DispatchOnMainThread(string[] Tokens)
{
try
{
switch (Tokens[0].ToLower(CultureInfo.InvariantCulture))
{
case "debug":
SetDebug(Tokens);
break;
case "uci":
Uci();
break;
case "isready":
IsReady();
break;
case "setoption":
SetOption(Tokens);
break;
case "ucinewgame":
UciNewGame();
break;
case "position":
Position(Tokens);
break;
case "showboard":
ShowBoard();
break;
case "countmoves":
CountMoves(Tokens);
break;
case "dividemoves":
DivideMoves(Tokens);
break;
case "listmoves":
ListMoves();
break;
case "exchangescore":
ExchangeScore(Tokens);
break;
case "test":
Test();
break;
case "analyzepositions":
AnalyzePositions(Tokens);
break;
case "stop":
Stop();
break;
case "quit":
Quit(0);
break;
default:
WriteMessageLine(Tokens[0] + " command not supported.");
break;
}
WriteMessageLine(null);
}
catch (Exception exception)
{
HandleException(exception);
}
}
private void DispatchOnAsyncThread(string[] Tokens)
{
lock (_asyncQueue)
{
// Queue command.
_asyncQueue.Enqueue(Tokens);
// Signal async queue.
_asyncSignal.Set();
}
}
private void MonitorQueue()
{
try
{
do
{
// Wait for signal.
_asyncSignal.WaitOne();
string[] tokens = null;
lock (_asyncQueue)
{
if (_asyncQueue.Count > 0)
{
tokens = _asyncQueue.Dequeue();
}
}
if ((tokens != null) && (tokens.Length > 0))
{
// Process command.
switch (tokens[0].ToLower(CultureInfo.InvariantCulture))
{
case "go":
Go(tokens);
break;
default:
throw new InvalidOperationException("Cannot process " + tokens[0] + " command on asynchronous thread.");
}
WriteMessageLine(null);
}
} while (true);
}
catch (Exception exception)
{
HandleException(exception);
}
}
private void SetDebug(string[] Tokens)
{
string debug = Tokens[1].ToLower(CultureInfo.InvariantCulture);
Debug = (debug == "on") || (debug == "true");
}
private void Uci()
{
// Engine name and author
WriteMessageLine("id name MadChess 2.0");
WriteMessageLine("id author Erik Madsen");
// Engine options
WriteMessageLine("option name Debug type check default false");
WriteMessageLine("option name Hash type spin default 128 min 0 max 1024");
WriteMessageLine("option name MultiPV type spin default 1 min 1 max " + MadChess.Position.MaxMoves);
WriteMessageLine("option name UCI_AnalyseMode type check default false");
WriteMessageLine("uciok");
}
private void IsReady()
{
WriteMessageLine("readyok");
}
private void SetOption(string[] Tokens)
{
string optionName = Tokens[2];
string optionValue = Tokens[4];
switch (optionName.ToLower(CultureInfo.InvariantCulture))
{
case "debug":
SetDebug(Tokens);
break;
case "hash":
int cacheMegabytes = int.Parse(optionValue);
_cache.Capacity = cacheMegabytes * Cache.PositionsPerMegabyte;
break;
case "multipv":
_search.MultiPv = int.Parse(optionValue);
break;
case "uci_analysemode":
// TODO: Implement analysis mode.
break;
default:
WriteMessageLine(Tokens[0] + " option not supported.");
break;
}
}
private void UciNewGame()
{
// Reset cache.
_cache.Reset();
// Reset killer moves and move history.
_killerMoves.Reset();
_moveHistory.Reset();
}
private void Position(string[] Tokens)
{
// ParseLongAlgebraic FEN.
string fen = Tokens[1].ToLower(CultureInfo.InvariantCulture) == "startpos"
? _startPositionFen
: string.Join(" ", Tokens, 2, Tokens.Length - 2);
// Determine if position specifies moves.
int moveIndex = Tokens.Length;
for (int index = 2; index < Tokens.Length; index++)
{
if (Tokens[index].ToLower(CultureInfo.InvariantCulture) == "moves")
{
// Position specifies moves.
if (index == (Tokens.Length - 1))
{
// Remove empty move list from FEN.
fen = fen.Substring(0, fen.Length - 6);
}
moveIndex = index + 1;
break;
}
}
// Setup position.
_board.SetupPosition(fen);
while (moveIndex <Tokens>= _board.MovesInfoUpdate)
{
// Update move count.
double movesPerSecond = _board.Moves / _commandStopwatch.Elapsed.TotalSeconds;
WriteMessageLine("Counted " + _board.MovesInfoUpdate.ToString("n0") + " moves (" + movesPerSecond.ToString("n0") + " moves per second).");
long intervals = _board.Moves / MovesInfoInterval;
_board.MovesInfoUpdate = MovesInfoInterval * (intervals + 1);
}
if ((Horizon - Depth) == 0)
{
return 1L;
}
// Generate moves.
_board.GenerateMoves(false);
long moves = 0L;
for (int moveIndex = 0; moveIndex < _board.CurrentPosition.MoveIndex; moveIndex++)
{
ulong move = _board.CurrentPosition.Moves[moveIndex];
// Determine if move is legal.
if (!_board.IsMoveLegal(ref move))
{
// Skip illegal move.
continue;
}
// Play move.
_board.PlayMove(move);
// Recurse.
moves += CountMoves(Depth + 1, Horizon);
// Undo move.
_board.UndoMove();
}
return moves;
}
private void DivideMoves(string[] Tokens)
{
int horizon = int.Parse(Tokens[1].Trim());
_board.Moves = 0L;
_board.MovesInfoUpdate = MovesInfoInterval;
_commandStopwatch.Reset();
_commandStopwatch.Start();
// Generate moves.
_board.GenerateMoves(false);
// Count moves for each root move.
long[] rootMoves = new long[_board.RootPosition.MoveIndex];
for (int moveIndex = 0; moveIndex < _board.RootPosition.MoveIndex; moveIndex++)
{
ulong move = _board.RootPosition.Moves[moveIndex];
if (!_board.IsMoveLegal(ref move))
{
// Skip illegal move.
continue;
}
// Play move.
_board.PlayMove(move);
// Count moves.
rootMoves[moveIndex] = CountMoves(0, horizon - 1);
// Undo move.
_board.UndoMove();
}
_commandStopwatch.Stop();
// Display move count for each root move.
int legalMoves = 0;
WriteMessageLine("Root Move Moves");
WriteMessageLine("========= =======");
for (int moveIndex = 0; moveIndex < _board.RootPosition.MoveIndex; moveIndex++)
{
ulong move = _board.RootPosition.Moves[moveIndex];
if (!_board.IsMoveLegal(ref move))
{
// Skip illegal move.
continue;
}
legalMoves++;
WriteMessageLine(Move.ToLongAlgebraic(_board, move).PadRight(9) + " " + rootMoves[moveIndex].ToString(CultureInfo.InvariantCulture).PadLeft(7));
}
WriteMessageLine();
WriteMessageLine(legalMoves + " legal root moves");
}
private void ListMoves()
{
_commandStopwatch.Reset();
_commandStopwatch.Start();
// Get cached position.
CachedPosition cachedPosition = _cache.GetPosition(_board.RootPosition.Key, false);
ulong bestMove = 0ul;
if ((cachedPosition != null) && (cachedPosition.BestMoveFrom != Square.Illegal))
{
// Cached position specifies a best move.
Move.SetFrom(ref bestMove, cachedPosition.BestMoveFrom);
Move.SetTo(ref bestMove, cachedPosition.BestMoveTo);
Move.SetPromotedPiece(ref bestMove, cachedPosition.BestMovePromotedPiece);
}
// Generate and sort moves.
_board.GenerateMoves(false);
_search.SortMovesByPriority(_board.RootPosition.Moves, 0, _board.RootPosition.MoveIndex - 1, true, bestMove, 0);
WriteMessageLine("Rank Move Best Cap Victim Cap Attacker Killer History Check Priority");
WriteMessageLine("==== ===== ==== ========== ============ ====== ======= ===== ====================");
int legalMoveNumber = 0;
for (int moveIndex = 0; moveIndex < _board.RootPosition.MoveIndex; moveIndex++)
{
ulong move = _board.RootPosition.Moves[moveIndex];
// Determine if move is legal.
if (_board.IsMoveLegal(ref move))
{
// Move is legal.
legalMoveNumber++;
}
else
{
// Skip illegal move.
continue;
}
WriteMessage(legalMoveNumber.ToString("00").PadLeft(4) + " ");
WriteMessage(Move.ToLongAlgebraic(_board, move).PadLeft(5) + " ");
WriteMessage(Move.IsBest(move) ? "True " : " ");
WriteMessage(Piece.GetNameFromCode(Move.CaptureVictim(move)).PadLeft(10) + " ");
WriteMessage(Piece.GetNameFromCode(Move.CaptureAttacker(move)).PadLeft(12) + " ");
WriteMessage(Move.Killer(move).ToString(CultureInfo.InvariantCulture).PadLeft(6) + " ");
WriteMessage(Move.History(move).ToString(CultureInfo.InvariantCulture).PadLeft(7) + " ");
WriteMessage(Move.IsCheck(move) ? " True " : " ");
WriteMessage(move.ToString(CultureInfo.InvariantCulture).PadLeft(20));
WriteMessageLine();
}
WriteMessageLine();
WriteMessageLine(legalMoveNumber + " legal moves");
_commandStopwatch.Stop();
}
private void ExchangeScore(string[] Tokens)
{
ulong move = Move.ParseLongAlgebraic(Tokens[1]);
WriteMessageLine(_search.GetExchangeScore(move).ToString(CultureInfo.InvariantCulture));
}
private void Test()
{
// Verify move counts of test positions.
// Thanks to Martin Sedlak for providing test positions and correct legal move counts. See http://talkchess.com/forum/viewtopic.php?t=47318.
Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MadChess.TestPositions.txt");
if (stream == null)
{
throw new MissingManifestResourceException("Failed to load test positions from internal resources.");
}
WriteMessageLine("Number Position Depth Expected Moves Correct Pct");
WriteMessageLine("====== =========================================================================== ===== =========== =========== ======= =====");
_board.Moves = 0L;
_board.MovesInfoUpdate = MovesInfoInterval;
int positions = 0;
int correctPositions = 0;
_commandStopwatch.Reset();
_commandStopwatch.Start();
using (StreamReader reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
// Load position, horizon, and correct move count.
string line = reader.ReadLine();
if (line == null) continue;
positions++;
string[] tokens = Tokens.Parse(line, '|', '"');
string fen = tokens[0];
int horizon = int.Parse(tokens[1], CultureInfo.InvariantCulture);
long expectedMoves = long.Parse(tokens[2], CultureInfo.InvariantCulture);
// Setup position. Preserve move count.
_board.SetupPosition(fen, true);
WriteMessage(positions.ToString().PadLeft(6) + " " + fen.PadLeft(75) + " " + horizon.ToString("0").PadLeft(5) + " " + expectedMoves.ToString("n0").PadLeft(11) + " ");
// Count moves. Do not update move count.
_board.MovesInfoUpdate = long.MaxValue;
long moves = CountMoves(0, horizon);
bool correct = moves == expectedMoves;
if (correct)
{
correctPositions++;
}
double percent = ((100d * correctPositions) / positions);
WriteMessageLine(moves.ToString("n0").PadLeft(11) + " " + correct.ToString().PadLeft(7) + " " + percent.ToString("0.0").PadLeft(5));
}
}
_commandStopwatch.Stop();
// Update move count.
double movesPerSecond = _board.Moves / _commandStopwatch.Elapsed.TotalSeconds;
WriteMessageLine();
WriteMessageLine("Counted " + _board.Moves.ToString("n0") + " moves (" + movesPerSecond.ToString("n0") + " moves per second).");
}
private void AnalyzePositions(string[] Tokens)
{
string file = Tokens[1].Trim();
int moveTimeMilliseconds = int.Parse(Tokens[2].Trim());
int positions = 0;
int correctPositions = 0;
using (StreamReader reader = File.OpenText(file))
{
WriteMessageLine("Number Position Solution Expected Moves Move Correct Pct");
WriteMessageLine("====== =========================================================================== ======== ================ ===== ======= =====");
_board.Moves = 0L;
_board.MovesInfoUpdate = MovesInfoInterval;
_commandStopwatch.Reset();
_commandStopwatch.Start();
while (!reader.EndOfStream)
{
// Load position, horizon, and correct move count.
string line = reader.ReadLine();
if (line == null) continue;
positions++;
string[] tokens = MadChess.Tokens.Parse(line, ' ', '"');
PositionSolution positionSolution = PositionSolution.Unknown;
int solutionIndex = -1;
int expectedMovesIndex = -1;
for (int index = 0; index < tokens.Length; index++)
{
string token = tokens[index].Trim().ToLower();
switch (token)
{
case "bm":
positionSolution = PositionSolution.BestMoves;
solutionIndex = index;
break;
case "am":
positionSolution = PositionSolution.AvoidMoves;
solutionIndex = index;
break;
}
if (token.EndsWith(";"))
{
expectedMovesIndex = index;
break;
}
}
if (solutionIndex == -1)
{
throw new InvalidOperationException("Position does not specify a best moves or avoid moves solution.");
}
if (expectedMovesIndex == -1)
{
throw new InvalidOperationException("Position does not terminate the expected moves with a semicolon.");
}
int correctMoves = expectedMovesIndex - solutionIndex;
string fen = string.Join(" ", tokens, 0, solutionIndex).Trim();
string expectedMovesListStandardAlgebraic = string.Join(" ", tokens, solutionIndex + 1, correctMoves).Trim().TrimEnd(";".ToCharArray());
string[] expectedMovesStandardAlgebraic = expectedMovesListStandardAlgebraic.Split(" ".ToCharArray());
ulong[] expectedMoves = new ulong[expectedMovesStandardAlgebraic.Length];
string[] expectedMovesLongAlgebraic = new string[expectedMovesStandardAlgebraic.Length];
// Setup position.
_board.SetupPosition(fen, true);
for (int moveIndex = 0; moveIndex < expectedMovesStandardAlgebraic.Length; moveIndex++)
{
string expectedMoveStandardAlgebraic = expectedMovesStandardAlgebraic[moveIndex];
ulong expectedMove = Move.ParseStandardAlgebraic(_board, expectedMoveStandardAlgebraic);
expectedMoves[moveIndex] = expectedMove;
expectedMovesLongAlgebraic[moveIndex] = Move.ToLongAlgebraic(_board, expectedMove);
}
string solution = positionSolution == PositionSolution.BestMoves ? "Best" : "Avoid";
WriteMessage(positions.ToString().PadLeft(6) + " " + fen.PadLeft(75) + " " + solution.PadLeft(8) + " " + string.Join(" ", expectedMovesLongAlgebraic).PadLeft(16) + " ");
// Reset search.
UciNewGame();
_search.Reset();
// Find best move. Do not update move count or PV.
_board.MovesInfoUpdate = long.MaxValue;
_search.PvInfoUpdate = false;
_search.MoveTimeSoftLimit = TimeSpan.MaxValue;
_search.MoveTimeHardLimit = TimeSpan.FromMilliseconds(moveTimeMilliseconds);
ulong bestMove = _search.FindBestMove();
// Signal search has stopped.
_search.Signal.Set();
bool correct;
switch (positionSolution)
{
case PositionSolution.BestMoves:
correct = false;
foreach (ulong expectedMove in expectedMoves)
{
if (Move.Equals(bestMove, expectedMove))
{
correct = true;
break;
}
}
break;
case PositionSolution.AvoidMoves:
correct = true;
foreach (ulong expectedMove in expectedMoves)
{
if (Move.Equals(bestMove, expectedMove))
{
correct = false;
break;
}
}
break;
default:
throw new Exception(positionSolution + " position solution not supported.");
}
if (correct)
{
correctPositions++;
}
double percent = ((100d * correctPositions) / positions);
WriteMessageLine(Move.ToLongAlgebraic(_board, bestMove).PadLeft(5) + " " + correct.ToString().PadLeft(7) + " " + percent.ToString("0.0").PadLeft(5));
}
}
_commandStopwatch.Stop();
// Update score.
WriteMessageLine();
WriteMessageLine("Solved " + correctPositions + " of " + positions + " positions in " + _commandStopwatch.Elapsed.TotalSeconds.ToString("0") + " seconds." );
// Update move count.
double movesPerSecond = _board.Moves / _commandStopwatch.Elapsed.TotalSeconds;
WriteMessageLine("Counted " + _board.Moves.ToString("n0") + " moves (" + movesPerSecond.ToString("n0") + " moves per second).");
}
private void Go(string[] Tokens)
{
_commandStopwatch.Reset();
_commandStopwatch.Start();
// Reset search.
_search.Reset();
// Reset killer moves and move history.
_killerMoves.Reset();
_moveHistory.Reset();
for (int tokenIndex = 1; tokenIndex < Tokens.Length; tokenIndex++)
{
string token = Tokens[tokenIndex];
switch (token.ToLower(CultureInfo.InvariantCulture))
{
case "wtime":
_search.WhiteTimeRemaining = TimeSpan.FromMilliseconds(int.Parse(Tokens[tokenIndex + 1], CultureInfo.InvariantCulture));
break;
case "btime":
_search.BlackTimeRemaining = TimeSpan.FromMilliseconds(int.Parse(Tokens[tokenIndex + 1], CultureInfo.InvariantCulture));
break;
case "winc":
_search.WhiteTimeIncrement = TimeSpan.FromMilliseconds(int.Parse(Tokens[tokenIndex + 1], CultureInfo.InvariantCulture));
break;
case "binc":
_search.BlackTimeIncrement = TimeSpan.FromMilliseconds(int.Parse(Tokens[tokenIndex + 1], CultureInfo.InvariantCulture));
break;
case "movestogo":
_search.MovesToTimeControl = int.Parse(Tokens[tokenIndex + 1], CultureInfo.InvariantCulture);
break;
case "depth":
_search.HorizonLimit = int.Parse(Tokens[tokenIndex + 1], CultureInfo.InvariantCulture);
break;
case "nodes":
_search.NodeLimit = long.Parse(Tokens[tokenIndex + 1], CultureInfo.InvariantCulture);
break;
case "mate":
_search.MateInMoves = int.Parse(Tokens[tokenIndex + 1], CultureInfo.InvariantCulture);
break;
case "movetime":
_search.MoveTimeSoftLimit = TimeSpan.MaxValue;
_search.MoveTimeHardLimit = TimeSpan.FromMilliseconds(int.Parse(Tokens[tokenIndex + 1], CultureInfo.InvariantCulture));
break;
case "infinite":
_search.MoveTimeHardLimit = TimeSpan.MaxValue;
break;
}
}
// Find best move.
ulong bestMove = _search.FindBestMove();
// Signal search has stopped.
_commandStopwatch.Stop();
_search.Signal.Set();
// Respond with best move.
WriteMessageLine("bestmove " + Move.ToLongAlgebraic(_board, bestMove));
}
private void Stop()
{
_search.Continue = false;
// Wait for search to complete.
_search.Signal.WaitOne();
}
private void Quit(int ExitCode)
{
Dispose(true);
Environment.Exit(ExitCode);
}
public void WriteMessage(string Message)
{
Console.Write(Message);
if (Log)
{
// Log message.
LogMessage(CommandDirection.Out, Message, false);
}
}
public void WriteMessageLine()
{
Console.WriteLine();
if (Log)
{
// Log message.
LogMessage(CommandDirection.Out, null, true);
}
}
public void WriteMessageLine(string Message)
{
Console.WriteLine(Message);
if (Log)
{
// Log message.
LogMessage(CommandDirection.Out, Message, true);
}
}
private void LogMessage(int Direction, string Message, bool IncludeTimestamp)
{
lock (_logLock)
{
if (_lastMessageIncludedTimestamp)
{
_logWriter.Write(Direction == CommandDirection.In ? " In " : " Out ");
}
_logWriter.Write(Message);
if (IncludeTimestamp)
{
_lastMessageIncludedTimestamp = true;
TimeSpan elapsed = _mainStopwatch.Elapsed;
_logWriter.WriteLine();
_logWriter.Write(elapsed.Hours.ToString("00", CultureInfo.InvariantCulture) + ":" + elapsed.Minutes.ToString("00", CultureInfo.InvariantCulture) + ":" +
elapsed.Seconds.ToString("00", CultureInfo.InvariantCulture) + "." + elapsed.Milliseconds.ToString("000", CultureInfo.InvariantCulture) + " ");
}
else
{
_lastMessageIncludedTimestamp = false;
}
}
}
public void HandleException(Exception Exception)
{
Log = true;
_stringBuilder.Length = 0;
Exception exception = Exception;
do
{
// Display message and write to log.
_stringBuilder.AppendLine(exception.Message);
_stringBuilder.AppendLine();
_stringBuilder.AppendLine(exception.StackTrace);
_stringBuilder.AppendLine();
exception = exception.InnerException;
} while (exception != null);
WriteMessageLine(_stringBuilder.ToString());
Quit(-1);
}
}
}