diff --git a/docs/core-concepts/entities-components.md b/docs/entity-component-system/entities-components.md similarity index 100% rename from docs/core-concepts/entities-components.md rename to docs/entity-component-system/entities-components.md diff --git a/docs/core-concepts/events.md b/docs/entity-component-system/events.md similarity index 99% rename from docs/core-concepts/events.md rename to docs/entity-component-system/events.md index 48064d3..91f4b2e 100644 --- a/docs/core-concepts/events.md +++ b/docs/entity-component-system/events.md @@ -166,8 +166,6 @@ Middleware allows you to intercept and modify event handling behavior. Middlewar You can use middleware to log all events, validate data before handlers run, check permissions, or apply other centralized logic across events. Configure middleware in your `Startup` class using the : ```csharp -using SampSharp.Entities; - public class MyMiddleware { private readonly EventDelegate _next; diff --git a/docs/core-concepts/index.md b/docs/entity-component-system/overview.md similarity index 100% rename from docs/core-concepts/index.md rename to docs/entity-component-system/overview.md diff --git a/docs/core-concepts/systems.md b/docs/entity-component-system/systems.md similarity index 99% rename from docs/core-concepts/systems.md rename to docs/entity-component-system/systems.md index d26e33b..ab73552 100644 --- a/docs/core-concepts/systems.md +++ b/docs/entity-component-system/systems.md @@ -76,7 +76,6 @@ To disable automatic loading, call `DisableDefaultSystemsLoading()` in your `Sta ```csharp using Microsoft.Extensions.DependencyInjection; -using SampSharp.Entities; public class Startup : IEcsStartup { diff --git a/docs/getting-started.md b/docs/getting-started.md deleted file mode 100644 index d3717e6..0000000 --- a/docs/getting-started.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -redirect_url: quick-start.html ---- diff --git a/docs/quick-start.md b/docs/getting-started/quick-start.md similarity index 100% rename from docs/quick-start.md rename to docs/getting-started/quick-start.md diff --git a/docs/host-configuration/configuration.md b/docs/host-configuration/configuration.md new file mode 100644 index 0000000..d8f7088 --- /dev/null +++ b/docs/host-configuration/configuration.md @@ -0,0 +1,141 @@ +--- +title: Configuration +uid: configuration +--- + +# Configuration + +SampSharp exposes two ways to read configuration values: + +- **** — typed access to the open.mp server configuration (the values defined in `config.json`). +- **`Microsoft.Extensions.Configuration.IConfiguration`** — the standard .NET configuration abstraction, populated from several sources including the open.mp config, environment variables, and JSON files. + +Both are registered automatically and available through [dependency injection](xref:systems#dependency-injection). Use `IConfigService` when you specifically need open.mp's typed accessors; use `IConfiguration` for everything else, especially when binding settings to your own classes via the options pattern. + +## IConfigService — typed open.mp config + +`IConfigService` wraps open.mp's native config API. Each getter is type-checked: if the key is missing or holds a different type, the call returns `null` (or, for `GetStrings`, an empty array). + +```csharp +public class WelcomeSystem(IConfigService config) : ISystem +{ + [Event] + public void OnGameModeInit() + { + var hostname = config.GetString("name") ?? "SA-MP Server"; + var maxPlayers = config.GetInt("max_players") ?? 50; + } +} +``` + +Other members: + +- `GetBool`, `GetFloat`, `GetStrings(key)` — typed accessors for the remaining value types. +- `GetValueType(key)` — returns the `ConfigOptionType` of a key (`Int`, `String`, `Float`, `Bool`, or `Strings`). +- `GetOptions()` — every key open.mp currently knows about, with its type. Useful for discovery and diagnostics. + +This service reads only from open.mp's config — it does **not** see values added through `appsettings.json` or environment variables. For unified access across all sources, use `IConfiguration`. + +## IConfiguration — unified configuration + +SampSharp builds an `IConfiguration` at startup and registers it as a singleton. Inject it directly, or bind sections to a typed options class. + +```csharp +public class MyService(IConfiguration configuration) +{ + private readonly string? _connectionString = configuration["Database:ConnectionString"]; +} +``` + +### Sources and precedence + +The configuration pipeline registers the following sources. Later sources override earlier ones, so the list reads from lowest to highest precedence: + +1. **open.mp config** — every key from `config.json`, surfaced through SampSharp's config provider. +2. **Environment variables** — every process environment variable. As usual in .NET configuration, `__` represents the section separator (so `Database__ConnectionString` corresponds to `Database:ConnectionString`). +3. **`appsettings.json`** — loaded from the directory containing your gamemode assembly, with hot-reload enabled. +4. **`appsettings.{environment}.json`** — same directory, only loaded when an environment name is set (see [Environments](#environments) below). +5. **Custom sources** — anything you add via `ConfigureAppConfiguration` on the host builder. + +So a key set in `appsettings.json` overrides the same key from open.mp's config; a value passed via an environment variable beats the open.mp config but loses to `appsettings.json`. + +### How open.mp keys appear in IConfiguration + +open.mp keys use dotted names with snake_case segments (for example `network.public_addr`). SampSharp transforms each key when surfacing it through `IConfiguration`: + +- Dots (`.`) become colons (`:`), so each segment becomes a configuration section. +- Underscores (`_`) are removed. +- String-array values become indexed children: `key:0`, `key:1`, … + +So open.mp's `network.public_addr` is available at `configuration["network:publicaddr"]`, and `artwork.enable` becomes `configuration["artwork:enable"]`. + +### Environments + +If an environment name is set, SampSharp loads `appsettings.{environment}.json` on top of the base `appsettings.json`. The name is resolved from, in order: + +1. The `environment` key in `config.json`. +2. The `environment` process environment variable. + +For example, to switch between local and production settings, add `appsettings.Development.json` and `appsettings.Production.json` next to your gamemode assembly, then set `environment` to `Development` or `Production` in `config.json` (or set the `environment` environment variable when launching the server). + +### Extending with ConfigureAppConfiguration + +To add other configuration sources — a different JSON file, an INI file, a key-value store, a secrets provider — use `ConfigureAppConfiguration` on the host builder. The callback runs after SampSharp's built-in sources, so anything you add wins. + +```csharp +public void Initialize(IStartupContext context) +{ + context.UseEntities() + .ConfigureAppConfiguration(builder => + { + builder.AddJsonFile("secrets.json", optional: true, reloadOnChange: true); + builder.AddEnvironmentVariables(prefix: "MYAPP_"); + }); +} +``` + +## Binding to typed options + +Use the standard .NET options pattern to bind a configuration section to a strongly-typed class. + +```csharp +public class DatabaseOptions +{ + public string ConnectionString { get; set; } = ""; + public int CommandTimeout { get; set; } = 30; +} +``` + +Register the binding from `ConfigureServices`. provides an overload that supplies the `IConfiguration` you need: + +```csharp +public class Startup : IEcsStartup +{ + public void Initialize(IStartupContext context) + { + context.UseEntities(); + } + + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + services.Configure(configuration.GetSection("Database")); + } + + public void Configure(IEcsBuilder builder) { } +} +``` + +Then, in any system or service, inject `IOptions` to read the bound values — or `IOptionsMonitor` to pick up changes from sources that support hot-reload (such as `appsettings.json`). + +```csharp +public class GameSystem(IOptions options) : ISystem +{ + private readonly DatabaseOptions _db = options.Value; + + [Event] + public void OnGameModeInit() + { + // use _db.ConnectionString + } +} +``` diff --git a/docs/host-configuration/logging.md b/docs/host-configuration/logging.md new file mode 100644 index 0000000..6ac8d8d --- /dev/null +++ b/docs/host-configuration/logging.md @@ -0,0 +1,116 @@ +--- +title: Logging +uid: logging +--- + +# Logging + +SampSharp uses the standard `Microsoft.Extensions.Logging` abstraction. Inject `ILogger` into any system or service to write log messages — they're forwarded to the open.mp logger that the framework registers automatically. + +```csharp +public class JoinAuditSystem(ILogger logger) : ISystem +{ + [Event] + public void OnPlayerConnect(Player player) + { + logger.LogInformation("{Name} connected from {Ip}", player.Name, player.Ip); + } +} +``` + +## What's registered by default + +When you call `UseEntities()`, SampSharp configures logging with: + +- The open.mp logger provider — writes through open.mp's native logging infrastructure so messages appear alongside server logs and use the same formatting and routing. +- Configuration binding from the `Logging` section of the [unified configuration](xref:configuration) — log levels, category filters, and provider options come from `appsettings.json` (or any other source) without extra wiring. + +You can layer additional providers (Serilog, console, file, …) on top via `ConfigureLogging` on the host builder. + +## Configuring log levels + +Set log levels in `appsettings.json` using the standard `Logging:LogLevel` schema: + +```json +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "MyGameMode.Combat": "Debug" + } + } +} +``` + +Categories are matched by prefix, so `MyGameMode.Combat` covers every `ILogger` whose category name starts with `MyGameMode.Combat`. The default category for `ILogger` is the type's full name. + +For the full schema — provider-specific overrides, filter precedence, and setting levels through environment variables or other configuration sources — see Microsoft's [Configure logging](https://learn.microsoft.com/dotnet/core/extensions/logging/overview#configure-logging) reference. + +## Tuning the open.mp logger + +open.mp has four severity levels — `Debug`, `Message`, `Warning`, and `Error` — while .NET defines six (`Trace`, `Debug`, `Information`, `Warning`, `Error`, `Critical`). SampSharp's open.mp logger maps each .NET level to an open.mp level. The defaults are: + +| .NET level | open.mp level | +|---------------|---------------| +| `Trace` | `Message` | +| `Debug` | `Message` | +| `Information` | `Message` | +| `Warning` | `Warning` | +| `Error` | `Error` | +| `Critical` | `Error` | + +The mapping can be overridden by configuring the open.mp provider (its `[ProviderAlias]` is `Omp`): + +```json +{ + "Logging": { + "Omp": { + "DebugLevel": "Debug", + "InformationLevel": "Message", + "WarningLevel": "Warning", + "ErrorLevel": "Error" + } + } +} +``` + +In this example, `logger.LogDebug(...)` surfaces as a `Debug` message in open.mp instead of a regular `Message`. Each property accepts `Debug`, `Message`, `Warning`, or `Error`. See for the full list of properties. + +> [!WARNING] +> open.mp's `Debug` level is only written by debug builds of the open.mp server, which you'd have to compile yourself. The official open.mp and SampSharp distributions ship release builds only, so in practice anything you route to `Debug` is silently dropped on every server users actually run. Treat `Debug` as effectively a no-op unless you know you're running a self-built debug server, and map anything you want to be visible in production to `Message`, `Warning`, or `Error`. + +You can also filter messages routed through the open.mp provider only — for example, to send `Trace` to open.mp while leaving the default `Information` filter in place for other providers: + +```json +{ + "Logging": { + "Omp": { + "LogLevel": { + "Default": "Trace" + } + } + } +} +``` + +## Adding more providers + +`ConfigureLogging` on the host builder gives you the standard `ILoggingBuilder`, so anything you'd register in an ASP.NET Core or generic-host app works the same way: + +```csharp +public void Initialize(IStartupContext context) +{ + context.UseEntities() + .ConfigureLogging(logging => + { + logging.SetMinimumLevel(LogLevel.Information); + logging.AddFile("logs/gamemode-{Date}.log"); // e.g. Serilog.Extensions.Logging.File + }); +} +``` + +The open.mp provider is added before your callback runs, so your own providers run alongside it — log messages are forwarded to every registered provider. + +> [!NOTE] +> The open.mp logger provider is intentionally minimal: it formats the category name and message and sends them to open.mp. For richer output (structured properties, sinks, enrichers), pair it with a logging framework like Serilog or NLog through its `Microsoft.Extensions.Logging` integration. diff --git a/docs/core-concepts/startup.md b/docs/host-configuration/startup.md similarity index 78% rename from docs/core-concepts/startup.md rename to docs/host-configuration/startup.md index c9177c8..f05e0d9 100644 --- a/docs/core-concepts/startup.md +++ b/docs/host-configuration/startup.md @@ -1,18 +1,19 @@ --- -title: Startup and configuration +title: Startup uid: startup --- -# Startup and configuration +# Startup Every SampSharp gamemode begins with a `Startup` class that implements . SampSharp creates an instance of this class when the gamemode loads and uses it to wire up the ECS framework, register services into the DI container, and configure event handling. +This article focuses on the startup lifecycle and the ECS host builder. Application configuration sources (open.mp config, `appsettings.json`, environment variables) are covered in [Configuration](xref:configuration), and logging setup is covered in [Logging](xref:logging). + The project template generates a minimal startup: ```csharp +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using SampSharp.Entities; -using SampSharp.Entities.SAMP.Commands; using SampSharp.OpenMp.Core; public class Startup : IEcsStartup @@ -22,7 +23,7 @@ public class Startup : IEcsStartup context.UseEntities().UseCommands(); } - public void ConfigureServices(IServiceCollection services) + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { } @@ -32,12 +33,15 @@ public class Startup : IEcsStartup } ``` +> [!NOTE] +> `SampSharp.Sdk` provides global `using`s for `SampSharp.Entities`, `SampSharp.Entities.SAMP`, and `SampSharp.Entities.SAMP.Commands`, so the snippets in the rest of these docs leave them out. Add them back explicitly if you're not using the SDK. + The three methods run at different points during startup: | Method | Runs | Used for | |---|---|---| | `Initialize(IStartupContext)` | Earliest. Has access to the open.mp host. | Calling `UseEntities()` and adding feature modules (`UseCommands`, custom hosts). | -| `ConfigureServices(IServiceCollection)` | After the service collection is created, before the provider is built. | Adding your own services to dependency injection. | +| `ConfigureServices(IServiceCollection, IConfiguration)` | After the service collection is created, before the provider is built. | Adding your own services to dependency injection. | | `Configure(IEcsBuilder)` | After the service provider is built, just before `OnGameModeInit` fires. | Final pre-launch work that needs resolved services — preloading data, warming up caches, kicking off background services. | ## Initialize and the ECS host builder @@ -49,7 +53,6 @@ public void Initialize(IStartupContext context) { context.UseEntities() .UseCommands() - .ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Information)) .ConfigureUnhandledExceptionHandler((sp, where, ex) => { var log = sp.GetRequiredService>(); @@ -62,7 +65,8 @@ The host builder supports the following configuration: - **`Configure(Action)`** — schedules a callback that runs after the service provider is built, just before `OnGameModeInit` fires. The same hook as the `Configure` method on `IEcsStartup`, but exposed on the host builder so a feature module can register its own pre-launch work. - **`ConfigureServices(Action)`** — register services. Useful when a feature module wants to add its own services on top of yours. There's also an overload that exposes the `SampSharpEnvironment` if you need it. -- **`ConfigureLogging(Action)`** — set log levels, add custom providers (Serilog, file logging, etc.). open.mp's console logger is added automatically. +- **`ConfigureLogging(Action)`** — set log levels, add custom providers (Serilog, file logging, etc.). open.mp's console logger is added automatically. See [Logging](xref:logging) for details. +- **`ConfigureAppConfiguration(Action)`** — add or override sources for the unified `IConfiguration`. See [Configuration](xref:configuration) for the full source list and precedence. - **`ConfigureUnhandledExceptionHandler(UnhandledExceptionHandler)`** — replace the default handler for uncaught exceptions thrown from event handlers, systems, timers, etc. The default writes the exception to the configured logger; override it to forward to an error tracker or take other action. - **`UseServiceProviderFactory(IServiceProviderFactory)`** — swap out the default Microsoft DI container for an alternative such as Autofac, Lamar, or DryIoc. - **`DisableDefaultSystemsLoading()`** — by default SampSharp scans the entry assembly and registers every `ISystem` it finds. Call this to opt out and register systems manually with `services.AddSystem()`. @@ -96,14 +100,15 @@ public static class MyFeatureExtensions ## ConfigureServices -The `ConfigureServices(IServiceCollection)` method on `IEcsStartup` is where you register your own services for [dependency injection](xref:systems#dependency-injection) into systems and event handlers: +The `ConfigureServices` method on `IEcsStartup` is where you register your own services for [dependency injection](xref:systems#dependency-injection) into systems and event handlers. The `IConfiguration` parameter gives you access to all configuration sources at registration time (see [Configuration](xref:configuration)): ```csharp -public void ConfigureServices(IServiceCollection services) +public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { services.AddSingleton(); services.AddSingleton(); - services.AddDbContext(o => o.UseSqlite("Data Source=game.db")); + services.AddDbContext(o => + o.UseSqlite(configuration["Database:ConnectionString"])); } ``` @@ -140,7 +145,8 @@ When you call `UseEntities()`, SampSharp automatically registers a baseline of s - **Core infrastructure** — , , , and an `IUnhandledExceptionHandler`. - **Built-in systems** — `TimerSystem` (exposed as ) and `TickingSystem`. - **SAMP services** — , , , , plus all the open.mp event handlers that translate native callbacks into SampSharp events. -- **Logging** — `ILogger` backed by open.mp's console logger. Add more providers via `ConfigureLogging`. +- **Logging** — `ILogger` backed by open.mp's console logger. See [Logging](xref:logging). +- **Configuration** — an `IConfiguration` populated from the open.mp config, environment variables, and `appsettings.json`. See [Configuration](xref:configuration). - **Auto-discovered systems** — every public `ISystem` type in the entry assembly, unless you call `DisableDefaultSystemsLoading()`. Feature modules (like `UseCommands`) layer on top of this baseline. diff --git a/docs/host-configuration/toc.yml b/docs/host-configuration/toc.yml new file mode 100644 index 0000000..c3e6177 --- /dev/null +++ b/docs/host-configuration/toc.yml @@ -0,0 +1,4 @@ +items: +- href: startup.md +- href: configuration.md +- href: logging.md diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index b72acf8..0000000 --- a/docs/index.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -redirect_url: quick-start.html ---- \ No newline at end of file diff --git a/docs/toc.yml b/docs/toc.yml index f4442e9..b2d75dc 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -1,12 +1,13 @@ items: - name: Getting Started -- href: quick-start.md -- href: core-concepts/index.md +- href: getting-started/quick-start.md +- href: entity-component-system/overview.md name: Entity-Component-System -- href: core-concepts/entities-components.md -- href: core-concepts/systems.md -- href: core-concepts/events.md -- href: core-concepts/startup.md +- href: entity-component-system/entities-components.md +- href: entity-component-system/systems.md +- href: entity-component-system/events.md +- name: Host configuration + href: host-configuration/toc.yml - name: Features - href: features/command-system.md - href: features/dialog-menus.md diff --git a/index.md b/index.md index 0bb0e0f..69e87e3 100644 --- a/index.md +++ b/index.md @@ -144,7 +144,7 @@ class PlayerSystem : ISystem Ready to build? Check out the resources below: -- **[Documentation](~/docs/index.md)** — Learn the framework concepts and architecture +- **[Documentation](xref:quick-start)** — Learn the framework concepts and architecture - **[Sample Projects](https://github.com/SampSharp/samples)** — Real-world examples to learn from --- diff --git a/sampsharp-src b/sampsharp-src index b9a63ef..b826511 160000 --- a/sampsharp-src +++ b/sampsharp-src @@ -1 +1 @@ -Subproject commit b9a63efcf7e990468b211c4876defd48b445788f +Subproject commit b826511cf6bcd744ca0630d1c37082abaf8a91f8