Update wire protocol to version 7 and enhance presence management features
- Bumped the wire protocol version to 7, reflecting significant changes in the communication structure. - Introduced a new presence message type for real-time occupancy updates, including node activity and individual presence states. - Updated the API to include a directory endpoint for fetching short id→name mappings, improving client-side name resolution. - Revised the map snapshot structure to be static, with people and current lessons now handled through the presence stream. - Enhanced client-side handling of presence updates, including UI adjustments to display live occupancy and activity. - Updated documentation to reflect the new protocol features and changes in presence management. - Added tests to validate the new presence functionalities and ensure robust handling of real-time data.
This commit is contained in:
@@ -48,7 +48,7 @@ internal sealed class SchoolWorker
|
||||
private DefCatalog? _catalogSnapshot;
|
||||
private Timetable? _timetableSnapshot;
|
||||
private MapLayout? _mapSnapshot;
|
||||
private OccupancyKey _occupancyKey;
|
||||
private int _presenceAge;
|
||||
private School? _school;
|
||||
private Task? _run;
|
||||
private bool _persistOnStop = true;
|
||||
@@ -308,7 +308,7 @@ internal sealed class SchoolWorker
|
||||
{
|
||||
PublishSnapshot();
|
||||
BroadcastClock();
|
||||
MaybeBroadcastOccupancy(school);
|
||||
MaybeBroadcastPresence(school);
|
||||
}
|
||||
|
||||
FlushSettings();
|
||||
@@ -381,6 +381,7 @@ internal sealed class SchoolWorker
|
||||
open.Client.OpenSchoolId = _id;
|
||||
SendMapSnapshot(open.Client, school);
|
||||
BroadcastClockTo(open.Client, school);
|
||||
SendPresence(open.Client, school);
|
||||
break;
|
||||
|
||||
case WorkerCommand.Close close:
|
||||
@@ -402,6 +403,10 @@ internal sealed class SchoolWorker
|
||||
dirty = true;
|
||||
break;
|
||||
|
||||
case WorkerCommand.SkipEmpty:
|
||||
ApplySkip(school);
|
||||
break;
|
||||
|
||||
case WorkerCommand.GetPerson getPerson:
|
||||
var card = PersonCardReader.Read(school, getPerson.PersonId, getPerson.Locale);
|
||||
getPerson.Result.TrySetResult(
|
||||
@@ -613,7 +618,6 @@ internal sealed class SchoolWorker
|
||||
PersistTimetable(school);
|
||||
}
|
||||
|
||||
RememberOccupancy(school);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -770,11 +774,7 @@ internal sealed class SchoolWorker
|
||||
PublishSnapshot();
|
||||
if (broadcast)
|
||||
{
|
||||
MaybeBroadcastOccupancy(school, force: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
RememberOccupancy(school);
|
||||
BroadcastPresence();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -799,35 +799,41 @@ internal sealed class SchoolWorker
|
||||
}
|
||||
}
|
||||
|
||||
private void RememberOccupancy(School school)
|
||||
private void MaybeBroadcastPresence(School school)
|
||||
{
|
||||
if (school.Catalog is not null)
|
||||
_presenceAge++;
|
||||
var interval = Math.Max(1, _options.TickRate / 2);
|
||||
if (_presenceAge < interval)
|
||||
{
|
||||
_occupancyKey = TimetableClock.Key(school.Catalog, school.Clock.Time, _options.SchoolWeekDays);
|
||||
return;
|
||||
}
|
||||
|
||||
_presenceAge = 0;
|
||||
BroadcastPresence();
|
||||
}
|
||||
|
||||
private void MaybeBroadcastOccupancy(School school, bool force = false)
|
||||
private void ApplySkip(School school)
|
||||
{
|
||||
if (school.Catalog is null)
|
||||
var result = school.TrySkipEmpty();
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var key = TimetableClock.Key(school.Catalog, school.Clock.Time, _options.SchoolWeekDays);
|
||||
if (!force && key == _occupancyKey)
|
||||
if (result.PeopleChanged)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_occupancyKey = key;
|
||||
foreach (var client in _clients.All)
|
||||
{
|
||||
if (client.IsReady && client.OpenSchoolId == _id)
|
||||
PersistPeople();
|
||||
if (school.TimetableDirty)
|
||||
{
|
||||
SendMapSnapshot(client, school);
|
||||
RebuildTimetable(school);
|
||||
}
|
||||
}
|
||||
|
||||
PublishSnapshot();
|
||||
Persist();
|
||||
BroadcastClock();
|
||||
BroadcastPresence();
|
||||
_presenceAge = 0;
|
||||
}
|
||||
|
||||
private bool InstallPeople(School school, DefCatalog catalog, MapLayout map)
|
||||
@@ -987,8 +993,6 @@ internal sealed class SchoolWorker
|
||||
node.Positions);
|
||||
}
|
||||
|
||||
MapOccupancy.Apply(nodes, school, _options.SchoolWeekDays, locale);
|
||||
|
||||
// Sized from the message, not from the inbound frame limit: a map the player enlarged in
|
||||
// the create editor outgrows 8 KiB somewhere past sixty furnished rooms.
|
||||
var message = new ServerMapSnapshotMessage(school.Id, nodes);
|
||||
@@ -997,15 +1001,44 @@ internal sealed class SchoolWorker
|
||||
client.TrySendReliable(frame.AsMemory(0, length));
|
||||
}
|
||||
|
||||
private static void BroadcastClockTo(GameClient client, School school)
|
||||
private void BroadcastClockTo(GameClient client, School school)
|
||||
{
|
||||
var skip = school.PeekSkipEmpty();
|
||||
var frame = new byte[ProtocolCodec.MaxFrameSize];
|
||||
var length = ProtocolCodec.WriteClock(frame, new ServerClockMessage(
|
||||
school.Id,
|
||||
new DateTimeOffset(school.Clock.Time).ToUnixTimeMilliseconds(),
|
||||
school.Clock.IsRunning,
|
||||
(byte)school.Clock.SpeedIndex));
|
||||
(byte)school.Clock.SpeedIndex,
|
||||
skip.Allowed,
|
||||
skip.Time is { } target ? new DateTimeOffset(target).ToUnixTimeMilliseconds() : 0));
|
||||
|
||||
client.TrySend(frame.AsMemory(0, length));
|
||||
}
|
||||
|
||||
private void BroadcastPresence()
|
||||
{
|
||||
var school = _school;
|
||||
if (school is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var client in _clients.All)
|
||||
{
|
||||
if (client.IsReady && client.OpenSchoolId == _id)
|
||||
{
|
||||
SendPresence(client, school);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SendPresence(GameClient client, School school)
|
||||
{
|
||||
var locale = ProtocolConstants.CatalogLocale(client.Locale);
|
||||
var message = PresenceFrame.Build(school, _options.SchoolWeekDays, locale);
|
||||
var frame = new byte[ProtocolCodec.PresenceSize(message)];
|
||||
var length = ProtocolCodec.WritePresence(frame, message);
|
||||
client.TrySendReliable(frame.AsMemory(0, length));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user