Add HSchool.Content project for JSONC definitions, catalog, and map validation. Update solution structure to include new content and tests projects. Enhance school management to support mod packs and map instances, ensuring proper loading and validation. Revise documentation to reflect these changes and update tests for new functionality.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Channels;
|
||||
using HSchool.Content;
|
||||
using HSchool.Protocol;
|
||||
using HSchool.Server.Net;
|
||||
using HSchool.Simulation;
|
||||
@@ -8,8 +9,8 @@ namespace HSchool.Server.Game;
|
||||
|
||||
/// <summary>
|
||||
/// Dedicated thread for one school: fixed-step clock, that school's Arch world, that school's
|
||||
/// save file. Awaits are resolved with <c>GetResult</c> so <see cref="School.Tick"/> stays on
|
||||
/// this thread instead of hopping back onto the pool.
|
||||
/// frozen catalog, that school's save file. Awaits are resolved with <c>GetResult</c> so
|
||||
/// <see cref="School.Tick"/> stays on this thread instead of hopping back onto the pool.
|
||||
/// </summary>
|
||||
internal sealed class SchoolWorker
|
||||
{
|
||||
@@ -19,12 +20,15 @@ internal sealed class SchoolWorker
|
||||
private readonly ClientRegistry _clients;
|
||||
private readonly GameMetrics _metrics;
|
||||
private readonly SchoolStore _store;
|
||||
private readonly ModContent _mods;
|
||||
private readonly ILogger _logger;
|
||||
private readonly Channel<WorkerCommand> _mailbox = Channel.CreateUnbounded<WorkerCommand>(
|
||||
new UnboundedChannelOptions { SingleReader = true, SingleWriter = false });
|
||||
private readonly TaskCompletionSource _started = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
private readonly CancellationTokenSource _stopping = new();
|
||||
private readonly bool _isNew;
|
||||
private readonly IReadOnlyList<string>? _modIds;
|
||||
private readonly MapLayout? _savedMap;
|
||||
|
||||
private readonly int _id;
|
||||
private readonly string _name;
|
||||
@@ -44,10 +48,13 @@ internal sealed class SchoolWorker
|
||||
bool running,
|
||||
int speedIndex,
|
||||
bool isNew,
|
||||
IReadOnlyList<string>? modIds,
|
||||
MapLayout? savedMap,
|
||||
SimulationOptions options,
|
||||
ClientRegistry clients,
|
||||
GameMetrics metrics,
|
||||
SchoolStore store,
|
||||
ModContent mods,
|
||||
ILogger logger)
|
||||
{
|
||||
_id = id;
|
||||
@@ -56,10 +63,13 @@ internal sealed class SchoolWorker
|
||||
_running = running;
|
||||
_speedIndex = speedIndex;
|
||||
_isNew = isNew;
|
||||
_modIds = modIds;
|
||||
_savedMap = savedMap;
|
||||
_options = options;
|
||||
_clients = clients;
|
||||
_metrics = metrics;
|
||||
_store = store;
|
||||
_mods = mods;
|
||||
_logger = logger;
|
||||
_snapshot = new SchoolState(id, name, time, running, (byte)speedIndex);
|
||||
}
|
||||
@@ -113,6 +123,11 @@ internal sealed class SchoolWorker
|
||||
{
|
||||
RunLoop(_stopping.Token);
|
||||
}
|
||||
catch (SchoolContentUnavailableException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "School {SchoolId} was not started; the save file is unchanged.", _id);
|
||||
_started.TrySetException(ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "School {SchoolId} worker died.", _id);
|
||||
@@ -122,9 +137,30 @@ internal sealed class SchoolWorker
|
||||
|
||||
private void RunLoop(CancellationToken cancellationToken)
|
||||
{
|
||||
var packIds = _mods.NormalizePackIds(_modIds);
|
||||
foreach (var packId in packIds)
|
||||
{
|
||||
if (!_mods.PackExists(packId))
|
||||
{
|
||||
throw new SchoolContentUnavailableException(
|
||||
$"School {_id} needs mod '{packId}', but that folder is missing.");
|
||||
}
|
||||
}
|
||||
|
||||
var catalog = _mods.LoadCatalog(packIds, _logger);
|
||||
var map = _mods.LoadMap(packIds, _savedMap);
|
||||
try
|
||||
{
|
||||
MapValidator.Validate(map, catalog);
|
||||
}
|
||||
catch (MapValidationException ex)
|
||||
{
|
||||
throw new SchoolContentUnavailableException(ex.Message, ex);
|
||||
}
|
||||
|
||||
var school = _isNew
|
||||
? School.Create(_id, _name, _time)
|
||||
: School.Load(_id, _name, _time, _running, _speedIndex);
|
||||
? School.Create(_id, _name, _time, catalog, map)
|
||||
: School.Load(_id, _name, _time, _running, _speedIndex, catalog, map);
|
||||
|
||||
_school = school;
|
||||
PublishSnapshot();
|
||||
@@ -298,13 +334,17 @@ internal sealed class SchoolWorker
|
||||
return;
|
||||
}
|
||||
|
||||
_store.Save(new SchoolSave(
|
||||
SchoolStore.CurrentFormat,
|
||||
school.Id,
|
||||
school.Name,
|
||||
school.Clock.Time,
|
||||
school.Clock.IsRunning,
|
||||
school.Clock.SpeedIndex));
|
||||
_store.Save(new SchoolSave
|
||||
{
|
||||
Format = SchoolStore.CurrentFormat,
|
||||
Id = school.Id,
|
||||
Name = school.Name,
|
||||
GameTime = school.Clock.Time,
|
||||
Running = school.Clock.IsRunning,
|
||||
SpeedIndex = school.Clock.SpeedIndex,
|
||||
ModIds = school.Catalog?.PackIds,
|
||||
Map = school.Map,
|
||||
});
|
||||
}
|
||||
|
||||
private void BroadcastClock()
|
||||
|
||||
Reference in New Issue
Block a user