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}";
}
}
}