Table of Contents

Class GameSession

Namespace
Valheim.Testing.GameSessions
Assembly
Valheim.Testing.GameSessions.dll

One run's game processes in their roles (#258, decided on #289): an owned dedicated server (Server) or a client that hosts its own world (Host, #258 step 8), and named clients (Clients), each with its in-game handle .Game. A scenario is a function of the session and its plan, (GameSession session, TPlan plan) => Task, and coordinates the actors only through the barriers, each one named Setup step with a bounded wait that names itself when it times out:

Disposing it closes the clients in reverse order, then stops the server, each a Cleanup step: clients before their host. A hosting client is closed after its peers, then its placed world is moved into the evidence only once no client can still host it.

public sealed class GameSession
Inheritance
object
GameSession

Properties

CampaignClients

public IReadOnlyList<string> CampaignClients { get; }

Property Value

IReadOnlyList<string>

Cancellation

Cancelled by Ctrl+C or SIGTERM, and when StartAsync() fails.

public CancellationToken Cancellation { get; }

Property Value

CancellationToken

Clients

The session's clients by name, for example client, or a campaign's client-a and client-b.

public IReadOnlyDictionary<string, ClientActor> Clients { get; }

Property Value

IReadOnlyDictionary<string, ClientActor>

Host

The client that hosts the session's world (a listen server), or null; a session has it or a Server.

public HostingClientActor? Host { get; }

Property Value

HostingClientActor

Interval

How often a scenario's observation waits re-read: one second; scenario tests over scripted worlds shorten it.

public TimeSpan Interval { get; }

Property Value

TimeSpan

Logs

Every actor's kept logs, the server's (or host's) first, for the teardown scan once the processes stopped.

public IReadOnlyList<RunLog> Logs { get; }

Property Value

IReadOnlyList<RunLog>

Output

The run's new output directory: reports, per-boot logs, command records and kept client logs.

public string Output { get; }

Property Value

string

Report

The shared report to which actor setup, scenario and teardown add their steps.

public ScenarioReport Report { get; }

Property Value

ScenarioReport

Server

The owned dedicated server, or null for a session without one.

public ServerActor? Server { get; }

Property Value

ServerActor

Methods

AddLog(RunLog)

Adds a log the teardown scan reads besides the actors' own, for example one a scenario's own tool wrote.

public void AddLog(RunLog log)

Parameters

log RunLog

Barrier(string, Func<CancellationToken, Task>, TimeSpan)

A scenario's own readiness as a named Setup step: wait gets a token that ends after within (or on the session's cancellation); running out is a WaitTimeoutException that names name. Wait on events or read-only observations; never issue a mutation more than once.

public Task Barrier(string name, Func<CancellationToken, Task> wait, TimeSpan within)

Parameters

name string
wait Func<CancellationToken, Task>
within TimeSpan

Returns

Task

ClientHost(string)

The host a campaign client runs on, for reading its files or capturing there.

public IGameHost ClientHost(string campaignClient)

Parameters

campaignClient string

Returns

IGameHost

ClientLog(ClientRunPlan)

An owned client's live BepInEx log on this machine, for a scenario's in-run log reads; null for a campaign's clients (on their hosts) and an attached client (its logs are its operator's).

public string? ClientLog(ClientRunPlan client)

Parameters

client ClientRunPlan

Returns

string

DisposeAsync()

Closes the clients in reverse order, then stops the server, each a Cleanup step; a failed close is recorded and the rest still run. The session's token is cancelled first, so a barrier the scenario left running ends now. A hosting client is closed after its peers: when everything so far passed, it first leaves its world to its menu (the game saves it); an owned one is then stopped, an operator's detached. Its placed world is moved into the evidence only once no client can still host it (it left, or its owned process stopped); otherwise it is left in place and named (hostWorldLeftInPlace).

public ValueTask DisposeAsync()

Returns

ValueTask

Join(string, HeightExpectation?, bool, CrossplayLobby?)

The barrier "client X in world <uid>": the server accepts connections, then client joins it once (JoinWorld(ClientRunPlan, string, CrossplayLobby?, bool, CancellationToken): by address, or the crossplay lobby; the world pinned and awaited; protect enables god, ghost and debug protection), within the client plan's join time. With arrival, the player then stands there (Arrive(GameActor?, GameActor, HeightExpectation, TimeSpan, CancellationToken, bool, bool)) within its arrival time, and the arrival (its support reading and teleport timing) is returned for the scenario's evidence; otherwise null.

public Task<PlayerPlacement.TeleportArrival?> Join(string client, HeightExpectation? arrival = null, bool protect = true, CrossplayLobby? lobby = null)

Parameters

client string
arrival HeightExpectation
protect bool
lobby CrossplayLobby

Returns

Task<PlayerPlacement.TeleportArrival>

JoinAll(CrossplayLobby?)

Every client joins, one after another in name order (Join(string, HeightExpectation?, bool, CrossplayLobby?), protected, no arrival point; a crossplay server's lobby; a host's peers by its identity).

public Task JoinAll(CrossplayLobby? lobby = null)

Parameters

lobby CrossplayLobby

Returns

Task

OpenClient(ClientRunPlan, string?, string?)

Opens a client of plan through a new ClientActor and returns its session; its logs join Logs, and one the scenario leaves open is closed at teardown before the server stops. In a campaign, name names the campaign's client (left out when it declares one), which starts on its assigned host on its leased Steam identity; otherwise the client opens on this machine. A campaign's client, and any client opened by name, writes its evidence into its own folder (Valheim.Testing.GameSessions.GameSession.ActorOutput(string, string)). With directory, its evidence goes to that subdirectory of Output instead, so a second client never overwrites the first one's. Until the scenarios declare their clients, this is how a scenario opens one. A client opened into its own directory and no name is named by its directory in the report.

public ClientSession OpenClient(ClientRunPlan plan, string? name = null, string? directory = null)

Parameters

plan ClientRunPlan
name string
directory string

Returns

ClientSession

OpenClientsAsync(IReadOnlyDictionary<string, ClientRunPlan>)

Opens the named campaign clients at once (OpenClient(ClientRunPlan, string?, string?) for each), each on its own host, Steam lease, install and character; all have opened before this returns. If one fails, every client that opened is closed and the first failure is rethrown (not the cancellation it caused in the others).

public Task<IReadOnlyDictionary<string, ClientSession>> OpenClientsAsync(IReadOnlyDictionary<string, ClientRunPlan> clients)

Parameters

clients IReadOnlyDictionary<string, ClientRunPlan>

Returns

Task<IReadOnlyDictionary<string, ClientSession>>

Rejoin(string, HeightExpectation?, bool, CrossplayLobby?)

The barrier "client X back at its menu", then Join(string, HeightExpectation?, bool, CrossplayLobby?) again: the client leaves the world (pins verified at the menu again) and rejoins the server's current boot, or the host's world. It first waits, within the client's join time, for the client to settle where it is (SessionControl: idle at its menu, or in the world with its player): a client the world already dropped (a host's restart disconnects its peers) is only re-pinned at its menu and recorded in rejoinedFromMenu; a leave is issued only from a loaded world.

public Task<PlayerPlacement.TeleportArrival?> Rejoin(string client, HeightExpectation? arrival = null, bool protect = true, CrossplayLobby? lobby = null)

Parameters

client string
arrival HeightExpectation
protect bool
lobby CrossplayLobby

Returns

Task<PlayerPlacement.TeleportArrival>

ServerJoinable()

The barrier "the server accepts game connections", within the server's startup deadline; with a hosting client, "the host's world accepts game connections" (WaitUntilJoinable(GameActor)), within its plan's join time.

public Task ServerJoinable()

Returns

Task

StartAsync()

Starts every actor at once: the server's boot ("start and verify owned dedicated fixture") and each client to its menu ("client X at its menu, plugins pinned"), each a Setup step. When one fails, the session's token is cancelled so the others end their waits, every start settles, the session is disposed (clients closed, then the server stopped, each a Cleanup step), and the first failure is rethrown.

public Task StartAsync()

Returns

Task