using System.Linq; using Robust.Client.Timing; using Robust.Shared.Timing; namespace Content.Client.UserInterface; /// /// A local buffer for s to manually implement prediction. /// /// /// /// In many current (and future) cases, it is not practically possible to implement prediction for UIs /// by implementing the logic in shared. At the same time, we want to implement prediction for the best user experience /// (and it is sometimes the easiest way to make even a middling user experience). /// /// /// You can queue predicted messages into this class with , /// and then call later from /// to get all messages that are still "ahead" of the latest server state. /// These messages can then manually be "applied" to the latest state received from the server. /// /// /// Note that this system only works if the server is guaranteed to send some kind of update in response to UI messages, /// or at a regular schedule. If it does not, there is no opportunity to error correct the prediction. /// /// public sealed class BuiPredictionState { private readonly BoundUserInterface _parent; private readonly IClientGameTiming _gameTiming; private readonly Queue _queuedMessages = new(); public BuiPredictionState(BoundUserInterface parent, IClientGameTiming gameTiming) { _parent = parent; _gameTiming = gameTiming; } public void SendMessage(BoundUserInterfaceMessage message) { if (_gameTiming.IsFirstTimePredicted) { var messageData = new MessageData { TickSent = _gameTiming.CurTick, Message = message, }; _queuedMessages.Enqueue(messageData); } _parent.SendPredictedMessage(message); } public IEnumerable MessagesToReplay() { var curTick = _gameTiming.LastRealTick; while (_queuedMessages.TryPeek(out var data) && data.TickSent <= curTick) { _queuedMessages.Dequeue(); } if (_queuedMessages.Count == 0) return []; return _queuedMessages.Select(c => c.Message); } private struct MessageData { public GameTick TickSent; public required BoundUserInterfaceMessage Message; public override string ToString() { return $"{Message} @ {TickSent}"; } } }