Bootstrap lifecycle
SquidStdBootstrap is the entry point that wires up dependency injection and drives the lifecycle of every registered service. The flow is always the same: create, configure, start, stop.
Create
Begin by creating the bootstrap from SquidStdOptions:
var bootstrap = SquidStdBootstrap.Create(new SquidStdOptions
{
ConfigName = "squidstd",
RootDirectory = AppContext.BaseDirectory
});
ConfigName selects the configuration file and RootDirectory anchors relative paths. Create
loads the configuration eagerly - the YAML file, or an empty document when it does not exist
yet - into a standalone SquidStdConfig, before any service registration happens. It also
registers the configuration core: DirectoriesConfig, the logger config section (bound
immediately against that SquidStdConfig) and the config manager. Everything else is
registered explicitly in ConfigureServices. To supply an already-loaded configuration
yourself - useful when values from the file must drive registration decisions - use the
Create(SquidStdConfig, SquidStdOptions) overload; see
two-phase setup.
Managed directories
SquidStdOptions.Directories declares directory names that are created under RootDirectory as soon
as Create runs, before ConfigureServices or StartAsync:
var bootstrap = SquidStdBootstrap.Create(new SquidStdOptions
{
ConfigName = "squidstd",
RootDirectory = AppContext.BaseDirectory,
Directories = ["scripts", "save"]
});
Modules and plugins that need their own managed directory register it against the same
DirectoriesConfig instance, resolved from the container:
var directories = bootstrap.Container.Resolve<DirectoriesConfig>();
var worldDir = directories.RegisterDirectory("world");
Directory names are lower-cased to snake_case on disk (SavedGames becomes saved_games), and
RegisterDirectory is idempotent - registering the same name again just returns the existing path
without creating it twice.
ConfigureServices
Register your services into the DryIoc container. Call RegisterCoreServices() first to bring up the core services, then add the modules you need:
bootstrap.ConfigureServices(container =>
{
return container
.RegisterCoreServices()
.AddSomething();
});
Config bound at registration: every RegisterConfigSection call inside this callback - direct,
or through a RegisterXxx/AddXxx helper - binds its section immediately, so the section
instance is resolvable as soon as the callback returns. There is no need to wait for
StartAsync.
See dependency injection for the container and the AddXxx / RegisterXxx pattern.
OnConfigLoaded
Once, at StartAsync - before the logger is built and services start - the bootstrap applies
any typed config hooks you registered. Use them to inspect or override the already-bound
sections at startup; changes are in-memory only:
var bootstrap = SquidStdBootstrap.Create(o => o.ConfigName = "myapp");
bootstrap.ConfigureServices(c => c.RegisterCoreServices());
bootstrap.OnConfigLoaded<SquidStdLoggerOptions>(o => o.MinimumLevel = LogLevelType.Debug);
await bootstrap.StartAsync();
The effective order is: sections bind at registration (during ConfigureServices), config
hooks apply once at StartAsync entry, the logger is configured, then services start. Hooks
are re-applied whenever you call IConfigManagerService.Load() for an explicit reload, so
overrides are never lost across a reload. To receive the whole configuration manager once
every typed hook has run, use bootstrap.OnConfigReady(cfg => ...). See
Inspecting and overriding loaded configuration for more examples.
Migrating to 0.15: explicit core services
Up to 0.14, SquidStdBootstrap.Create registered every core service on creation. From 0.15 the bootstrap registers only the configuration core - DirectoriesConfig, the logger config section and the config manager. The remaining core services (JSON serializer, event bus, job system, main-thread dispatcher, timer wheel, metrics collection, secrets) are opted into with the parameterless RegisterCoreServices():
var bootstrap = SquidStdBootstrap.Create(o => o.ConfigName = "myapp");
bootstrap.ConfigureServices(c => c.RegisterCoreServices());
await bootstrap.StartAsync();
If you only need a subset, pick individual services with the granular methods instead - RegisterEventBusService(), RegisterJobSystemService(), RegisterTimerWheelService(), RegisterMainThreadDispatcherService(), RegisterMetricsCollectionService(), RegisterSecretServices(), RegisterDataSerializer().
The RegisterCoreServices(configName, configDirectory) overload is unchanged: it registers the configuration core plus all core services, for standalone containers that do not use a bootstrap.
In ASP.NET Core, pass the registration through the container callback:
builder.UseSquidStd(options => options.ConfigName = "myapp", c => c.RegisterCoreServices());
Start and stop over ISquidStdService
Services implementing ISquidStdService participate in the lifecycle. On StartAsync the config hooks are applied once (mutating the already-bound sections in place) and, if the configuration file does not exist yet, it is written with defaults; then services are started in registration order. On StopAsync they are stopped in reverse order, so dependencies remain available while their dependents shut down.
The bootstrap logs its whole lifecycle: a startup banner with the application name and version (set SquidStdOptions.AppName; it defaults to the entry assembly name and is attached to every event as the Application / ApplicationVersion properties), a registration summary (per-registration detail at Debug), one line per service started with its duration, and the shutdown sequence. A service that fails to stop is logged as a warning and the remaining services are still stopped. Extra Serilog sinks can be plugged by registering ILogEventSink instances in the container before start.
When an event bus is registered, the bootstrap publishes EngineStartingEvent, EngineStartedEvent and EngineStoppedEvent on it during the lifecycle.
EngineStartingEvent is only visible to subscriptions made before start - auto-registered listeners start during the service loop.
sequenceDiagram
participant App
participant Bootstrap as SquidStdBootstrap
participant Svc as ISquidStdService(s)
App->>Bootstrap: Create(options)
App->>Bootstrap: ConfigureServices(container)
App->>Bootstrap: StartAsync()
Bootstrap->>Svc: StartAsync() (in order)
App->>Bootstrap: StopAsync()
Bootstrap->>Svc: StopAsync() (reverse order)
RunAsync for long-running hosts
For long-running hosts, call RunAsync. It starts every service and then blocks until cancellation, stopping services cleanly on shutdown. Resolve dependencies anywhere with bootstrap.Resolve<T>(). See the architecture overview for how the host fits the layers.
RunAsync also completes when a shutdown is requested through the shared lifetime - this is
what the exit command of SquidStd.ConsoleCommands does - and Ctrl+C performs an orderly
shutdown instead of killing the process:
bootstrap.Container.Resolve<ISquidStdLifetime>().RequestShutdown();