KnockBox.Core
1.0.0-prerelease
dotnet add package KnockBox.Core --version 1.0.0-prerelease
NuGet\Install-Package KnockBox.Core -Version 1.0.0-prerelease
<PackageReference Include="KnockBox.Core" Version="1.0.0-prerelease" />
<PackageVersion Include="KnockBox.Core" Version="1.0.0-prerelease" />
<PackageReference Include="KnockBox.Core" />
paket add KnockBox.Core --version 1.0.0-prerelease
#r "nuget: KnockBox.Core, 1.0.0-prerelease"
#:package KnockBox.Core@1.0.0-prerelease
#addin nuget:?package=KnockBox.Core&version=1.0.0-prerelease&prerelease
#tool nuget:?package=KnockBox.Core&version=1.0.0-prerelease&prerelease
KnockBox.Core
Contract package for KnockBox game plugins.
KnockBox is a Blazor Server host that loads party games as runtime-discovered plugins. Every game plugin is a Razor Class Library that references only this package — the host loads the plugin into its own AssemblyLoadContext at startup and resolves shared contracts (the types in this package, the BCL, logging/DI abstractions) against the default ALC so type identity is preserved across the host/plugin boundary.
Who references this? Every game plugin project. Who does NOT reference this? The host — the host references
KnockBox.Platformwhich transitively depends onKnockBox.Core.
Getting started
The fastest path is to scaffold a new plugin from the companion template:
dotnet new install KnockBox.Templates
dotnet new knockbox-game -n MyGame --routeIdentifier my-game
The template generates three projects (MyGame, MyGame.DevHost, MyGame.Tests) and wires them together. Every generated file carries inline comments explaining what it does and where your own code goes.
If you'd rather wire up by hand, a minimal plugin is three types:
using KnockBox.Core.Primitives.Returns;
using KnockBox.Core.Plugins;
using KnockBox.Core.Services.Logic.Games.Engines.Shared;
using KnockBox.Core.Services.State.Games.Shared;
using KnockBox.Core.Services.State.Users;
using Microsoft.Extensions.DependencyInjection;
// 1. The module — plugin entry point. Must have a public parameterless ctor.
public sealed class MyGameModule : IGameModule
{
public string Name => "My Game";
public string Description => "A tiny example game.";
public string RouteIdentifier => "my-game"; // must match your @page route
public void RegisterServices(IServiceCollection services)
=> services.AddGameEngine<MyGameEngine>(RouteIdentifier);
public RenderFragment GetButtonContent() => b => { /* home-page tile */ };
}
// 2. The state — one instance per lobby.
public sealed class MyGameState(User host, ILogger<MyGameState> logger)
: AbstractGameState(host, logger)
{
public int Round { get; private set; }
internal void AdvanceRound() => Execute(() => Round++); // mutations via Execute
}
// 3. The engine — stateless singleton.
public sealed class MyGameEngine(ILogger<MyGameEngine> logger,
ILogger<MyGameState> stateLogger)
: AbstractGameEngine(minPlayers: 2, maxPlayers: 8)
{
public override Task<ValueResult<AbstractGameState>> CreateStateAsync(
User host, CancellationToken ct = default)
{
var state = new MyGameState(host, stateLogger);
state.UpdateJoinableStatus(true);
return Task.FromResult<ValueResult<AbstractGameState>>(state);
}
public override Task<Result> StartAsync(
User host, AbstractGameState state, CancellationToken ct = default)
{
if (state is not MyGameState s || host != s.Host)
return Task.FromResult(Result.FromError("Only the host can start."));
return Task.FromResult(s.Execute(() => s.UpdateJoinableStatus(false)));
}
}
Then add a Razor page at /room/my-game/{ObfuscatedRoomCode} that injects the engine, subscribes to state.StateChangedEventManager, and renders the UI.
What's in this package
| Type / namespace | What it's for |
|---|---|
IGameModule |
Plugin entry point. Exactly one per plugin assembly. |
AbstractGameEngine |
Base class for game engines (DI singleton; stateless). |
AbstractGameState |
Base class for per-room state. Owns the Execute lock. |
GameModuleServiceCollectionExtensions.AddGameEngine<T> |
Registers an engine as both a singleton and a keyed AbstractGameEngine. |
DisposableComponent |
Base for Razor pages. Provides ComponentDetached and a virtual Dispose. |
Result / ValueResult<T> / ValueResult<T,TError> |
Failure-returning types used across engines and services. |
User, IUserService |
Current user identity (scoped per Blazor circuit). |
IGameSessionService |
Active-session accessor (survives a 1-minute disconnect grace period). |
INavigationService |
Typed navigation (ToHome, ToGame, GetJoinUri). |
IThreadSafeEventManager / ThreadSafeEventManager |
Snapshot-based event dispatch used by AbstractGameState.StateChangedEventManager. |
FiniteStateMachine<TContext,TCommand>, IGameState<,>, ITimedGameState<,> |
Optional FSM scaffolding for phase-driven games. |
TurnManager |
Helper for games that take strict turns. |
IPhasedGameState<T>, IConfigurableGameState<T>, IFsmContextGameState<T>, IPlayerTrackedGameState<T> |
Marker interfaces for advanced state patterns. |
The concurrency contract
Every mutation on AbstractGameState must flow through Execute(Action) or ExecuteAsync(Func<Task>). The base class:
- Acquires the state's
SemaphoreSlim(1,1). - Runs your lambda.
- Releases the semaphore.
- Fires
StateChangedEventManagerafter the lock is released — so subscribers (e.g., disconnect handlers) can safely re-enterExecutewithout deadlocking.
For serialized non-mutating reads use WithExclusiveRead / WithExclusiveReadAsync — those do not fire a notification.
The PlayerUnregistered event is also raised outside the Execute lock, so your handler can call Execute (e.g., "advance the turn on disconnect") safely.
Developer reference
A full end-to-end guide — scaffolding, state, engine, Razor, DevHost, tests, shipping, advanced patterns — lives at:
https://github.com/jcub1011/KnockBox/blob/main/docs/making-a-game-plugin.md
Related packages
KnockBox.Templates—dotnet newtemplate pack that scaffolds a plugin + dev host + tests in one command.KnockBox.Platform— hosting SDK. Reference this from host projects, never from plugins.
License
MIT. See LICENSE.txt in the repository.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.AspNetCore.Components.Web (>= 10.0.5)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on KnockBox.Core:
| Package | Downloads |
|---|---|
|
KnockBox.Platform
Runtime host for KnockBox party game plugins. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-prerelease | 261 | 4/23/2026 |
| 0.2.1-beta | 98 | 4/16/2026 |