KnockBox.Core 1.0.0-prerelease

This is a prerelease version of KnockBox.Core.
dotnet add package KnockBox.Core --version 1.0.0-prerelease
                    
NuGet\Install-Package KnockBox.Core -Version 1.0.0-prerelease
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="KnockBox.Core" Version="1.0.0-prerelease" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="KnockBox.Core" Version="1.0.0-prerelease" />
                    
Directory.Packages.props
<PackageReference Include="KnockBox.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add KnockBox.Core --version 1.0.0-prerelease
                    
#r "nuget: KnockBox.Core, 1.0.0-prerelease"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package KnockBox.Core@1.0.0-prerelease
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=KnockBox.Core&version=1.0.0-prerelease&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=KnockBox.Core&version=1.0.0-prerelease&prerelease
                    
Install as a Cake Tool

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.Platform which transitively depends on KnockBox.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:

  1. Acquires the state's SemaphoreSlim(1,1).
  2. Runs your lambda.
  3. Releases the semaphore.
  4. Fires StateChangedEventManager after the lock is released — so subscribers (e.g., disconnect handlers) can safely re-enter Execute without 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

  • KnockBox.Templates — dotnet new template 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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