using System;
using System.Collections.Generic;
using Robust.Shared.Collections;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.Configuration
{
///
/// A networked configuration manager that controls the replication of
/// console variables between client and server.
///
[NotContentImplementable]
public interface INetConfigurationManager : IConfigurationManager
{
///
/// Sets up the networking for the config manager.
///
void SetupNetworking();
///
/// Gets the list of networked cvars that need to be sent to when connecting to server or client.
///
/// If true, includes all replicated cvars. I.e., clients would include cvars that were
/// received from the server, instead of only the ones that need to be sent to the server.
///
List<(string name, object value)> GetReplicatedVars(bool all = false);
///
/// Called every tick to process any incoming network messages.
///
void TickProcessMessages();
///
/// Flushes any NwCVar messages in the receive buffer.
///
void FlushMessages();
///
/// Get a replicated client CVar for a specific client. When used client-side, this simply returns the local cvar.
///
/// CVar type.
/// channel of the connected client.
/// Name of the CVar.
/// Replicated CVar of the client.
T GetClientCVar(INetChannel channel, string name);
///
/// Get a replicated client CVar for a specific client.
///
/// CVar type.
/// channel of the connected client.
/// The CVar.
/// Replicated CVar of the client.
T GetClientCVar(INetChannel channel, CVarDef definition) where T : notnull
=> GetClientCVar(channel, definition.Name);
}
internal interface INetConfigurationManagerInternal : INetConfigurationManager, IConfigurationManagerInternal
{
}
///
internal abstract partial class NetConfigurationManager : ConfigurationManager, INetConfigurationManagerInternal
{
[Dependency] protected INetManager NetManager = null!;
[Dependency] protected IGameTiming Timing = null!;
private readonly List _netVarsMessages = new();
protected ISawmill Sawmill = default!;
public override void Shutdown()
{
base.Shutdown();
FlushMessages();
}
///
public virtual void SetupNetworking()
{
Sawmill = Logger.GetSawmill("cfg");
Sawmill.Level = LogLevel.Info;
NetManager.RegisterNetMessage(HandleNetVarMessage);
}
protected virtual void HandleNetVarMessage(MsgConVars message)
{
_netVarsMessages.Add(message);
}
///
public void TickProcessMessages()
{
if (!Timing.InSimulation || Timing.InPrediction)
return;
// _netVarsMessages is not in any particular ordering.
// Copy any messages to apply to a separate list so we can sort before going through it.
ValueList toApply = default;
for (var i = 0; i < _netVarsMessages.Count; i++)
{
var msg = _netVarsMessages[i];
if (msg.Tick > Timing.CurTick)
continue;
toApply.Add(msg);
_netVarsMessages.RemoveSwap(i);
i--;
}
if (toApply.Count == 0)
return;
toApply.Sort((a, b) => a.Tick.CompareTo(b.Tick));
foreach (var msg in toApply)
{
ApplyNetVarChange(msg.MsgChannel, msg.NetworkedVars, msg.Tick);
if(msg.Tick != default && msg.Tick < Timing.CurTick)
Sawmill.Warning($"{msg.MsgChannel}: Received late nwVar message ({msg.Tick} < {Timing.CurTick} ).");
}
}
///
public void FlushMessages()
{
_netVarsMessages.Sort(((a, b) => a.Tick.Value.CompareTo(b.Tick.Value)));
foreach (var msg in _netVarsMessages)
{
ApplyNetVarChange(msg.MsgChannel, msg.NetworkedVars, msg.Tick);
}
_netVarsMessages.Clear();
}
protected abstract void ApplyNetVarChange(
INetChannel msgChannel,
List<(string name, object value)> networkedVars,
GameTick tick);
///
public void SyncConnectingClient(INetChannel client)
{
DebugTools.Assert(NetManager.IsConnected);
DebugTools.Assert(NetManager.IsServer);
Sawmill.Info($"{client}: Sending server info...");
var msg = new MsgConVars();
msg.Tick = Timing.CurTick;
msg.NetworkedVars = GetReplicatedVars();
NetManager.ServerSendMessage(msg, client);
}
public List<(string name, object value)> GetReplicatedVars(bool all = false)
{
using var _ = Lock.ReadGuard();
var nwVars = new List<(string name, object value)>();
foreach (var cVar in _configVars.Values)
{
if (!cVar.Registered)
continue;
if ((cVar.Flags & CVar.REPLICATED) == 0)
continue;
if (!all)
{
if (NetManager.IsClient)
{
if ((cVar.Flags & CVar.SERVER) != 0)
continue;
}
else if ((cVar.Flags & CVar.CLIENT) != 0)
continue;
}
nwVars.Add((cVar.Name, GetConfigVarValue(cVar)));
Sawmill.Debug($"name={cVar.Name}, val={(cVar.Value ?? cVar.DefaultValue)}");
}
return nwVars;
}
///
public abstract T GetClientCVar(INetChannel channel, string name);
}
}