Merge branch 'phase/35-appropriateness'
ci / server (push) Failing after 3m44s
ci / client (push) Failing after 14s

# Conflicts:
#	docs/phases/README.md
This commit is contained in:
Leonid Pershin
2026-08-20 05:19:33 +03:00
33 changed files with 1895 additions and 26 deletions
+10
View File
@@ -101,4 +101,14 @@ internal abstract record GameCommand
int Day,
int Period,
TaskCompletionSource<TimetableOutcome> Result) : GameCommand;
internal sealed record GetDressRules(
int SchoolId,
TaskCompletionSource<DressRulesOutcome> Result) : GameCommand;
internal sealed record SetDressRules(
int SchoolId,
DressRulePair? PendingStudents,
DressRulePair? PendingStaff,
TaskCompletionSource<DressRulesOutcome> Result) : GameCommand;
}
+24 -2
View File
@@ -225,6 +225,17 @@ internal sealed class GameLoopService(
new WorkerCommand.UnpinLesson(unpin.ClassId, unpin.Subject, unpin.Day, unpin.Period, unpin.Result),
unpin.Result);
break;
case GameCommand.GetDressRules getRules:
HandleDressRules(getRules.SchoolId, new WorkerCommand.GetDressRules(getRules.Result), getRules.Result);
break;
case GameCommand.SetDressRules setRules:
HandleDressRules(
setRules.SchoolId,
new WorkerCommand.SetDressRules(setRules.PendingStudents, setRules.PendingStaff, setRules.Result),
setRules.Result);
break;
}
}
@@ -255,6 +266,14 @@ internal sealed class GameLoopService(
}
}
private void HandleDressRules(int schoolId, WorkerCommand command, TaskCompletionSource<DressRulesOutcome> result)
{
if (!_workers.TryGetValue(schoolId, out var worker) || !worker.Post(command))
{
result.TrySetResult(DressRulesOutcome.Fail(DressRulesError.UnknownSchool));
}
}
private void HandleStaffing(int schoolId, WorkerCommand command, TaskCompletionSource<StaffingOutcome> result)
{
if (!_workers.TryGetValue(schoolId, out var worker) || !worker.Post(command))
@@ -560,7 +579,8 @@ internal sealed class GameLoopService(
save.ClimatePresetId,
save.NativeLanguage,
createSeed: null,
save.Presence);
save.Presence,
save.DressRules);
worker.Start();
try
@@ -611,7 +631,8 @@ internal sealed class GameLoopService(
string? climatePresetId,
string? nativeLanguage,
int? createSeed = null,
IReadOnlyList<PresenceSnapshot>? presence = null) =>
IReadOnlyList<PresenceSnapshot>? presence = null,
SchoolDressRules? dressRules = null) =>
new(
id,
name,
@@ -626,6 +647,7 @@ internal sealed class GameLoopService(
nativeLanguage,
createSeed,
presence,
dressRules,
_options,
clients,
metrics,
+5
View File
@@ -1,5 +1,6 @@
using System.Text.Json;
using HSchool.Content;
using HSchool.Content;
using HSchool.People;
using HSchool.Schedule;
using HSchool.Simulation;
@@ -36,6 +37,8 @@ internal sealed class SchoolSave
public string? NativeLanguage { get; init; }
public IReadOnlyList<PresenceSnapshot>? Presence { get; init; }
public SchoolDressRules? DressRules { get; init; }
}
/// <summary>Allocates school ids that survive a process restart.</summary>
@@ -188,6 +191,7 @@ internal sealed class SchoolStore
NameSetId = save.NameSetId,
NativeLanguage = save.NativeLanguage,
Presence = save.Presence,
DressRules = save.DressRules,
});
}
catch (Exception ex)
@@ -233,6 +237,7 @@ internal sealed class SchoolStore
NameSetId = save.NameSetId,
NativeLanguage = save.NativeLanguage,
Presence = save.Presence,
DressRules = save.DressRules,
};
}
+40
View File
@@ -37,6 +37,7 @@ internal sealed class SchoolWorker
private string? _nativeLanguage;
private readonly int? _createSeed;
private readonly IReadOnlyList<PresenceSnapshot>? _savedPresence;
private readonly SchoolDressRules? _savedDressRules;
private readonly Action<int> _onFailed;
private readonly int _id;
@@ -72,6 +73,7 @@ internal sealed class SchoolWorker
string? nativeLanguage,
int? createSeed,
IReadOnlyList<PresenceSnapshot>? savedPresence,
SchoolDressRules? savedDressRules,
SimulationOptions options,
ClientRegistry clients,
GameMetrics metrics,
@@ -93,6 +95,7 @@ internal sealed class SchoolWorker
_nativeLanguage = nativeLanguage;
_createSeed = createSeed;
_savedPresence = savedPresence;
_savedDressRules = savedDressRules;
_options = options;
_clients = clients;
_metrics = metrics;
@@ -248,6 +251,7 @@ internal sealed class SchoolWorker
}
_school = school;
school.DressRules = _savedDressRules ?? new SchoolDressRules();
PublishSnapshot();
if (_isNew)
@@ -458,6 +462,29 @@ internal sealed class SchoolWorker
unpin.Result.TrySetResult(
ApplyUnpin(school, unpin.ClassId, unpin.Subject, unpin.Day, unpin.Period));
break;
case WorkerCommand.GetDressRules getRules:
getRules.Result.TrySetResult(DressRulesOutcome.Ok(school.DressRules));
break;
case WorkerCommand.SetDressRules setRules:
{
var next = school.DressRules;
if (setRules.PendingStudents is { } students)
{
next = next with { PendingStudents = students };
}
if (setRules.PendingStaff is { } staff)
{
next = next with { PendingStaff = staff };
}
school.DressRules = next;
setRules.Result.TrySetResult(DressRulesOutcome.Ok(school.DressRules));
dirty = true;
break;
}
}
}
catch (Exception ex)
@@ -511,6 +538,12 @@ internal sealed class SchoolWorker
case WorkerCommand.UnpinLesson unpin:
unpin.Result.TrySetResult(TimetableOutcome.Fail(TimetableError.UnknownSchool));
break;
case WorkerCommand.GetDressRules getRules:
getRules.Result.TrySetResult(DressRulesOutcome.Fail(DressRulesError.UnknownSchool));
break;
case WorkerCommand.SetDressRules setRules:
setRules.Result.TrySetResult(DressRulesOutcome.Fail(DressRulesError.UnknownSchool));
break;
}
}
@@ -542,6 +575,12 @@ internal sealed class SchoolWorker
case WorkerCommand.UnpinLesson unpin:
unpin.Result.TrySetException(exception);
break;
case WorkerCommand.GetDressRules getRules:
getRules.Result.TrySetException(exception);
break;
case WorkerCommand.SetDressRules setRules:
setRules.Result.TrySetException(exception);
break;
}
}
@@ -1014,6 +1053,7 @@ internal sealed class SchoolWorker
ClimatePresetId = school.ClimatePresetId,
NativeLanguage = _nativeLanguage,
Presence = school.CapturePresence(),
DressRules = school.DressRules,
});
}
catch (Exception ex)
+8
View File
@@ -1,3 +1,4 @@
using HSchool.Content;
using HSchool.People;
using HSchool.Server.Api;
using HSchool.Server.Net;
@@ -63,4 +64,11 @@ internal abstract record WorkerCommand
int Day,
int Period,
TaskCompletionSource<TimetableOutcome> Result) : WorkerCommand;
internal sealed record GetDressRules(TaskCompletionSource<DressRulesOutcome> Result) : WorkerCommand;
internal sealed record SetDressRules(
DressRulePair? PendingStudents,
DressRulePair? PendingStaff,
TaskCompletionSource<DressRulesOutcome> Result) : WorkerCommand;
}