Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 <xref:SampSharp.Entities.IEcsBuilder>:

```csharp
using SampSharp.Entities;

public class MyMiddleware
{
private readonly EventDelegate _next;
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
3 changes: 0 additions & 3 deletions docs/getting-started.md

This file was deleted.

File renamed without changes.
141 changes: 141 additions & 0 deletions docs/host-configuration/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
---
title: Configuration
uid: configuration
---

# Configuration

SampSharp exposes two ways to read configuration values:

- **<xref:SampSharp.Entities.SAMP.IConfigService>** — 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`. <xref:SampSharp.Entities.IEcsStartup> 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<DatabaseOptions>(configuration.GetSection("Database"));
}

public void Configure(IEcsBuilder builder) { }
}
```

Then, in any system or service, inject `IOptions<DatabaseOptions>` to read the bound values — or `IOptionsMonitor<T>` to pick up changes from sources that support hot-reload (such as `appsettings.json`).

```csharp
public class GameSystem(IOptions<DatabaseOptions> options) : ISystem
{
private readonly DatabaseOptions _db = options.Value;

[Event]
public void OnGameModeInit()
{
// use _db.ConnectionString
}
}
```
116 changes: 116 additions & 0 deletions docs/host-configuration/logging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
title: Logging
uid: logging
---

# Logging

SampSharp uses the standard `Microsoft.Extensions.Logging` abstraction. Inject `ILogger<T>` 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<JoinAuditSystem> 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<T>` whose category name starts with `MyGameMode.Combat`. The default category for `ILogger<T>` 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 <xref:SampSharp.Entities.OmpLoggerOptions> 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.
Original file line number Diff line number Diff line change
@@ -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 <xref:SampSharp.Entities.IEcsStartup>. 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
Expand All @@ -22,7 +23,7 @@ public class Startup : IEcsStartup
context.UseEntities().UseCommands();
}

public void ConfigureServices(IServiceCollection services)
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
}

Expand All @@ -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
Expand All @@ -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<ILogger<Startup>>();
Expand All @@ -62,7 +65,8 @@ The host builder supports the following configuration:

- **`Configure(Action<IEcsBuilder>)`** — 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<IServiceCollection>)`** — 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<ILoggingBuilder>)`** — set log levels, add custom providers (Serilog, file logging, etc.). open.mp's console logger is added automatically.
- **`ConfigureLogging(Action<ILoggingBuilder>)`** — 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<IConfigurationBuilder>)`** — 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<T>(IServiceProviderFactory<T>)`** — 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<T>()`.
Expand Down Expand Up @@ -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<IBankService, BankService>();
services.AddSingleton<IPersistenceService, SqlitePersistenceService>();
services.AddDbContext<GameDbContext>(o => o.UseSqlite("Data Source=game.db"));
services.AddDbContext<GameDbContext>(o =>
o.UseSqlite(configuration["Database:ConnectionString"]));
}
```

Expand Down Expand Up @@ -140,7 +145,8 @@ When you call `UseEntities()`, SampSharp automatically registers a baseline of s
- **Core infrastructure** — <xref:SampSharp.Entities.IEntityManager>, <xref:SampSharp.Entities.IEventDispatcher>, <xref:SampSharp.Entities.ISystemRegistry>, and an `IUnhandledExceptionHandler`.
- **Built-in systems** — `TimerSystem` (exposed as <xref:SampSharp.Entities.ITimerService>) and `TickingSystem`.
- **SAMP services** — <xref:SampSharp.Entities.SAMP.IWorldService>, <xref:SampSharp.Entities.SAMP.IServerService>, <xref:SampSharp.Entities.SAMP.IDialogService>, <xref:SampSharp.Entities.SAMP.INpcService>, plus all the open.mp event handlers that translate native callbacks into SampSharp events.
- **Logging** — `ILogger<T>` backed by open.mp's console logger. Add more providers via `ConfigureLogging`.
- **Logging** — `ILogger<T>` 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.
4 changes: 4 additions & 0 deletions docs/host-configuration/toc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
items:
- href: startup.md
- href: configuration.md
- href: logging.md
3 changes: 0 additions & 3 deletions docs/index.md

This file was deleted.

Loading
Loading