V2/Infrastructure/RuntimeContracts.cs

Runtime contracts and descriptors for the Hexagon V2 system. Defines schema registration/collection (SchemaSourceCollector, IHexSchemaSource), runtime descriptor records (HexSchemaRuntimeDescriptor, HexHostRuntimeContext, HexClientRuntimeContext) and interfaces for host transport and host application used by the game's runtime and networking layer.

Networking
#nullable enable

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Hexagon.V2.Application;
using Hexagon.V2.Client;
using Hexagon.V2.Composition;
using Hexagon.V2.Domain;
using Hexagon.V2.Kernel;
using Hexagon.V2.Kernel.Schema;
using Hexagon.V2.Networking;
using Hexagon.V2.Persistence;
using Hexagon.V2.Runtime;
using Sandbox;

namespace Hexagon.V2.Infrastructure;

public sealed class SchemaSourceCollector
{
	private readonly Dictionary<string, HexSchemaRuntimeDescriptor> _registrations =
		new( StringComparer.Ordinal );
	private readonly List<string> _errors = new();

	public IReadOnlyList<string> Errors => _errors;

	public void Register( HexSchemaRuntimeDescriptor descriptor )
	{
		if ( descriptor is null )
		{
			_errors.Add( "Schema runtime descriptor cannot be null." );
			return;
		}
		if ( !_registrations.TryAdd( descriptor.Schema.Id, descriptor ) )
			_errors.Add( $"Schema runtime ID '{descriptor.Schema.Id}' was registered more than once." );
	}

	public OperationResult<HexSchemaRuntimeDescriptor> Require( string schemaId )
	{
		if ( _errors.Count > 0 )
			return OperationResult<HexSchemaRuntimeDescriptor>.Failure(
				ErrorCode.DuplicateRegistration, string.Join( " ", _errors ) );
		return _registrations.TryGetValue( schemaId, out var descriptor )
			? OperationResult<HexSchemaRuntimeDescriptor>.Success( descriptor )
			: OperationResult<HexSchemaRuntimeDescriptor>.Failure(
				ErrorCode.UnknownDefinition, $"Schema runtime '{schemaId}' is not mounted." );
	}
}

/// <summary>
/// Explicit scene-scoped schema source. Game packages implement this on a
/// GameObjectSystem; no assembly scanning or reflected construction is used.
/// </summary>
public interface IHexSchemaSource : ISceneEvent<IHexSchemaSource>
{
	void CollectSchemas( SchemaSourceCollector collector );
}

public sealed record HexSchemaRuntimeDescriptor
{
	public required IHexSchema Schema { get; init; }
	public required SchemaPersistenceInvariantProfile PersistenceInvariants { get; init; }
	public IReadOnlyList<IPersistedTypeCodec> PersistenceCodecs { get; init; } =
		Array.Empty<IPersistedTypeCodec>();
	/// <summary>
	/// Creates the game-owned durable storage adapter. Hexagon defines and consumes the
	/// persistence protocol, while the standalone game owns any raw operating-system access.
	/// </summary>
	public required Func<IPersistenceStorage> CreatePersistenceStorage { get; init; }
	public required Func<HexHostRuntimeContext, IHexHostApplication> CreateHostApplication { get; init; }
	public Action<HexClientRuntimeContext>? ConfigureClient { get; init; }
}

public sealed record HexHostRuntimeContext(
	Scene Scene,
	CompiledSchema Schema,
	IPersistenceProvider Persistence,
	DomainRepositories Repositories,
	ITypedConfigurationStore Configuration,
	IHexHostTransport Transport,
	string PersistenceRoot,
	string VerificationProbe );

public sealed record HexClientRuntimeContext(
	Scene Scene,
	HexClientStore Store,
	HexClientController Controller );

public interface IHexHostTransport
{
	void SendOperationResult(
		Connection recipient,
		ClientSessionScope scope,
		CommandRequestId requestId,
		OperationResult result );
	void SendCharacterList( Connection recipient, CharacterListSnapshot snapshot );
	void SendClientState(
		Connection recipient,
		PlayerPublicSnapshot publicSnapshot,
		PlayerPrivateSnapshot? privateSnapshot,
		PlayerRosterSnapshot roster,
		IReadOnlyList<SchemaViewSnapshot> schemaViews,
		IReadOnlyList<Hexagon.V2.Networking.InventorySnapshot> inventories,
		ActionProgressSnapshot? activeAction );
	void SendChat(
		Connection recipient,
		long revision,
		IReadOnlyList<ChatMessageSnapshot> messages );
}

public interface IHexHostApplication : IAsyncDisposable
{
	ValueTask<OperationResult> InitializeAsync( CancellationToken cancellationToken = default );
	void Connected( RpcActor actor );
	void Disconnected( RpcActor actor );
	CharacterRecord? FindActiveCharacter( ConnectionId connectionId );
	ValueTask<OperationResult> HandleCommandAsync(
		RpcActor actor,
		ClientCommand command,
		CancellationToken cancellationToken = default );
	OperationResult CompleteQuiescedShutdown( PersistenceShutdownResult persistenceShutdown );
}