Files
RobustToolbox/Robust.Shared/Configuration/NetConfigurationManager.cs
83c2a1be11 [Dependency] source generator part 2 (#6550)
* [Dependency] source generator

No more reflection, no more codegen at runtime

Also various changes to Roslyn helpers to make this easier to write.

Requires all types with dependencies to be partial and not have readonly dependency fields. An analyzer enforces this at warning level, the previous injection strategies have remained in the code *for now* as a fallback.

No fallback is available for [field: Dependency] properties, due to a Roslyn bug.

Code Fixes exist. We love Roslyn

* Apply dependencies generator changes to all code

* Release notes

* Preprocessor got hands

* Handle nullable dependencies

These are bad but gotta deal with it.

* Apply suggestions from code review

Co-authored-by: Moony <moony@hellomouse.net>

* Fine, let's not use collection expressions

---------

Co-authored-by: Moony <moony@hellomouse.net>
2026-05-08 12:38:33 +02:00

203 lines
6.7 KiB
C#

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
{
/// <summary>
/// A networked configuration manager that controls the replication of
/// console variables between client and server.
/// </summary>
[NotContentImplementable]
public interface INetConfigurationManager : IConfigurationManager
{
/// <summary>
/// Sets up the networking for the config manager.
/// </summary>
void SetupNetworking();
/// <summary>
/// Gets the list of networked cvars that need to be sent to when connecting to server or client.
/// </summary>
/// <param name="all">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.</param>
/// <returns></returns>
List<(string name, object value)> GetReplicatedVars(bool all = false);
/// <summary>
/// Called every tick to process any incoming network messages.
/// </summary>
void TickProcessMessages();
/// <summary>
/// Flushes any NwCVar messages in the receive buffer.
/// </summary>
void FlushMessages();
/// <summary>
/// Get a replicated client CVar for a specific client. When used client-side, this simply returns the local cvar.
/// </summary>
/// <typeparam name="T">CVar type.</typeparam>
/// <param name="channel">channel of the connected client.</param>
/// <param name="name">Name of the CVar.</param>
/// <returns>Replicated CVar of the client.</returns>
T GetClientCVar<T>(INetChannel channel, string name);
/// <summary>
/// Get a replicated client CVar for a specific client.
/// </summary>
/// <typeparam name="T">CVar type.</typeparam>
/// <param name="channel">channel of the connected client.</param>
/// <param name="definition">The CVar.</param>
/// <returns>Replicated CVar of the client.</returns>
T GetClientCVar<T>(INetChannel channel, CVarDef<T> definition) where T : notnull
=> GetClientCVar<T>(channel, definition.Name);
}
internal interface INetConfigurationManagerInternal : INetConfigurationManager, IConfigurationManagerInternal
{
}
/// <inheritdoc cref="INetConfigurationManager"/>
internal abstract partial class NetConfigurationManager : ConfigurationManager, INetConfigurationManagerInternal
{
[Dependency] protected INetManager NetManager = null!;
[Dependency] protected IGameTiming Timing = null!;
private readonly List<MsgConVars> _netVarsMessages = new();
protected ISawmill Sawmill = default!;
public override void Shutdown()
{
base.Shutdown();
FlushMessages();
}
/// <inheritdoc />
public virtual void SetupNetworking()
{
Sawmill = Logger.GetSawmill("cfg");
Sawmill.Level = LogLevel.Info;
NetManager.RegisterNetMessage<MsgConVars>(HandleNetVarMessage);
}
protected virtual void HandleNetVarMessage(MsgConVars message)
{
_netVarsMessages.Add(message);
}
/// <inheritdoc />
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<MsgConVars> 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} ).");
}
}
/// <inheritdoc />
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);
/// <inheritdoc />
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;
}
/// <inheritdoc />
public abstract T GetClientCVar<T>(INetChannel channel, string name);
}
}