Files
doom-dm/Engine/src/EngineBuilder.cs
2025-01-07 06:25:09 +03:00

266 lines
7.9 KiB
C#

using Engine.Graphics;
using Engine.Input;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes;
namespace Engine;
/// <summary>
/// Provides a builder for creating and configuring an instance of the <see cref="Engine"/> class.
/// </summary>
public sealed class EngineBuilder
{
/// <summary>
/// The title of the application window.
/// </summary>
private string _title = "";
/// <summary>
/// Indicates whether the engine should run in headless mode.
/// </summary>
private bool _headless;
/// <summary>
/// The width of the rendering window.
/// </summary>
private int _width = 1;
/// <summary>
/// The height of the rendering window.
/// </summary>
private int _height = 1;
/// <summary>
/// The path to the asset folder.
/// </summary>
private string _assetFolder = "./asset";
/// <summary>
/// The path to the data folder.
/// </summary>
private string _dataFolder = "./data";
/// <summary>
/// The input handler factory.
/// </summary>
private Func<Engine, IInputHandler>? _inputHandlerFunc;
/// <summary>
/// The presenter factory.
/// </summary>
private Func<Engine, IPresenter>? _presenterFunc;
// Logging
/// <summary>
/// Indicates whether to log to the console.
/// </summary>
private bool _logToConsole;
/// <summary>
/// Indicates whether to log to a file.
/// </summary>
private bool _logToFile;
/// <summary>
/// The path to the log file.
/// </summary>
private string? _logFilePath;
/// <summary>
/// The log level.
/// </summary>
private LogEventLevel _logLevel = LogEventLevel.Information;
/// <summary>
/// Sets the title of the engine window.
/// </summary>
/// <param name="parTitle">The title to use for the engine window.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder Title(string parTitle)
{
_title = parTitle;
return this;
}
/// <summary>
/// Configures the engine to run in headless mode.
/// </summary>
/// <param name="parHeadless">Indicates whether to enable headless mode. Defaults to <c>true</c>.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder Headless(bool parHeadless = true)
{
_headless = parHeadless;
return this;
}
/// <summary>
/// Sets the width of the engine window.
/// </summary>
/// <param name="parWidth">The width in pixels. Must be greater than zero.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder Width(int parWidth)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parWidth);
_width = parWidth;
return this;
}
/// <summary>
/// Sets the height of the engine window.
/// </summary>
/// <param name="parHeight">The height in pixels. Must be greater than zero.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder Height(int parHeight)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parHeight);
_height = parHeight;
return this;
}
/// <summary>
/// Sets the folder path for assets used by the engine.
/// </summary>
/// <param name="parAssetFolder">The folder path containing asset files.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder AssetFolder(string parAssetFolder)
{
_assetFolder = parAssetFolder;
return this;
}
/// <summary>
/// Sets the folder path for data files used by the engine.
/// </summary>
/// <param name="parDataFolder">The folder path containing data files.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder DataFolder(string parDataFolder)
{
_dataFolder = parDataFolder;
return this;
}
/// <summary>
/// Specifies the input handler to be used by the engine.
/// </summary>
/// <param name="parInputHandlerFunc">A function that creates an input handler for the engine.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder InputHandler(Func<Engine, IInputHandler> parInputHandlerFunc)
{
_inputHandlerFunc = parInputHandlerFunc;
return this;
}
/// <summary>
/// Specifies the presenter to be used by the engine.
/// </summary>
/// <param name="parPresenterFunc">A function that creates a presenter for the engine.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder Presenter(Func<Engine, IPresenter> parPresenterFunc)
{
_presenterFunc = parPresenterFunc;
return this;
}
/// <summary>
/// Configures logging to output to the console.
/// </summary>
/// <param name="parLogToConsole">Indicates whether to enable console logging. Defaults to <c>true</c>.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder LogToConsole(bool parLogToConsole = true)
{
_logToConsole = parLogToConsole;
return this;
}
/// <summary>
/// Configures logging to output to a file.
/// </summary>
/// <param name="parLogToFile">Indicates whether to enable file logging. Defaults to <c>true</c>.</param>
/// <param name="parLogFilePath">The path of the log file. Cannot be <c>null</c> if file logging is enabled.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="parLogFilePath"/> is <c>null</c> when <paramref name="parLogToFile"/> is <c>true</c>.</exception>
public EngineBuilder LogToFile(bool parLogToFile = true, string? parLogFilePath = null)
{
if (parLogToFile && parLogFilePath == null)
{
throw new ArgumentNullException(nameof(parLogFilePath));
}
_logToFile = parLogToFile;
_logFilePath = parLogFilePath;
return this;
}
/// <summary>
/// Sets the minimum log level for logging.
/// </summary>
/// <param name="parLogLevel">The minimum level of log events to capture.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder LogLevel(LogEventLevel parLogLevel)
{
_logLevel = parLogLevel;
return this;
}
/// <summary>
/// Builds and returns a new instance of the <see cref="Engine"/> class based on the configured settings.
/// </summary>
/// <returns>A fully configured instance of <see cref="Engine"/>.</returns>
public Engine Build()
{
var logger = BuildLogger();
var engine = new Engine(_width, _height, _headless, _title, _assetFolder, _dataFolder, logger);
var presenter = _presenterFunc?.Invoke(engine);
if (presenter != null)
{
engine.Presenter = presenter;
}
var inputHandler = _inputHandlerFunc?.Invoke(engine);
if (inputHandler != null)
{
engine.InputHandler = inputHandler;
}
return engine;
}
/// <summary>
/// Configures and builds a logger based on the current logging settings.
/// </summary>
/// <returns>A configured instance of <see cref="Logger"/>.</returns>
private Logger BuildLogger()
{
const string template =
"[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [{ThreadName,-15:l}:{ThreadId,-4:d4}] [{SourceContext:l}] {Message:lj}{NewLine}{Exception}";
var loggerConfiguration = new LoggerConfiguration()
.MinimumLevel.Is(_logLevel)
.Enrich.WithThreadName()
.Enrich.WithThreadId()
.Enrich.FromLogContext();
if (_logToConsole)
{
loggerConfiguration.WriteTo.Console(
outputTemplate: template,
theme: AnsiConsoleTheme.Literate
);
}
if (_logToFile)
{
loggerConfiguration.WriteTo.File(
_logFilePath!,
outputTemplate: template
);
}
return loggerConfiguration.CreateLogger();
}
}