diff --git a/NitroxClient/ClientAutoFacRegistrar.cs b/NitroxClient/ClientAutoFacRegistrar.cs index 333f44a7e9..f085cfb0b2 100644 --- a/NitroxClient/ClientAutoFacRegistrar.cs +++ b/NitroxClient/ClientAutoFacRegistrar.cs @@ -6,6 +6,7 @@ using NitroxClient.Communication.MultiplayerSession; using NitroxClient.Communication.NetworkingLayer.LiteNetLib; using NitroxClient.Communication.Packets.Processors.Abstract; +using NitroxClient.Debuggers; using NitroxClient.GameLogic; using NitroxClient.GameLogic.Bases; using NitroxClient.GameLogic.ChatUI; @@ -24,6 +25,7 @@ namespace NitroxClient { public class ClientAutoFacRegistrar : IAutoFacRegistrar { + private static readonly Assembly currentAssembly = Assembly.GetExecutingAssembly(); private readonly IModule[] modules; public ClientAutoFacRegistrar(params IModule[] modules) @@ -46,31 +48,37 @@ public void RegisterDependencies(ContainerBuilder containerBuilder) private static void RegisterCoreDependencies(ContainerBuilder containerBuilder) { - containerBuilder.Register(c => new NitroxProtobufSerializer("NitroxModel.dll")); + containerBuilder.RegisterAssemblyTypes(currentAssembly) + .AssignableTo() + .As() + .AsSelf() + .SingleInstance(); + containerBuilder.Register(c => new NitroxProtobufSerializer($"{nameof(NitroxModel)}.dll")); + containerBuilder.RegisterType() - .As() - .SingleInstance(); + .As() + .SingleInstance(); containerBuilder.RegisterType().SingleInstance(); containerBuilder.RegisterType() - .As() - .As() - .InstancePerLifetimeScope(); + .As() + .As() + .InstancePerLifetimeScope(); containerBuilder.RegisterType() - .As() - .InstancePerLifetimeScope(); + .As() + .InstancePerLifetimeScope(); containerBuilder.RegisterType() - .AsSelf() //Would like to deprecate this registration at some point and just work through an abstraction. - .As() - .InstancePerLifetimeScope(); + .AsSelf() //Would like to deprecate this registration at some point and just work through an abstraction. + .As() + .InstancePerLifetimeScope(); containerBuilder.RegisterType() - .As() - .InstancePerLifetimeScope(); + .As() + .InstancePerLifetimeScope(); containerBuilder.RegisterType().InstancePerLifetimeScope(); containerBuilder.RegisterType().InstancePerLifetimeScope(); @@ -109,7 +117,7 @@ private static void RegisterCoreDependencies(ContainerBuilder containerBuilder) private void RegisterPacketProcessors(ContainerBuilder containerBuilder) { containerBuilder - .RegisterAssemblyTypes(Assembly.GetAssembly(GetType())) + .RegisterAssemblyTypes(currentAssembly) .AsClosedTypesOf(typeof(ClientPacketProcessor<>)) .InstancePerLifetimeScope(); } @@ -117,7 +125,7 @@ private void RegisterPacketProcessors(ContainerBuilder containerBuilder) private void RegisterColorSwapManagers(ContainerBuilder containerBuilder) { containerBuilder - .RegisterAssemblyTypes(Assembly.GetAssembly(GetType())) + .RegisterAssemblyTypes(currentAssembly) .AssignableTo() .As() .InstancePerLifetimeScope(); @@ -126,7 +134,7 @@ private void RegisterColorSwapManagers(ContainerBuilder containerBuilder) private void RegisterInitialSyncProcessors(ContainerBuilder containerBuilder) { containerBuilder - .RegisterAssemblyTypes(Assembly.GetAssembly(GetType())) + .RegisterAssemblyTypes(currentAssembly) .AssignableTo() .As() .InstancePerLifetimeScope(); diff --git a/NitroxClient/Communication/MultiplayerSession/MultiplayerSessionManager.cs b/NitroxClient/Communication/MultiplayerSession/MultiplayerSessionManager.cs index 9f34264f94..3bf6adac56 100644 --- a/NitroxClient/Communication/MultiplayerSession/MultiplayerSessionManager.cs +++ b/NitroxClient/Communication/MultiplayerSession/MultiplayerSessionManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using NitroxClient.Communication.Abstract; using NitroxClient.Communication.MultiplayerSession.ConnectionState; +using NitroxClient.Debuggers; using NitroxClient.GameLogic; using NitroxModel; using NitroxModel.Helper; diff --git a/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs b/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs index fe1656de69..adea7bba35 100644 --- a/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs +++ b/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs @@ -2,6 +2,7 @@ using LiteNetLib; using LiteNetLib.Utils; using NitroxClient.Communication.Abstract; +using NitroxClient.Debuggers; using NitroxClient.MonoBehaviours.Gui.InGame; using NitroxModel.Core; using NitroxModel.Logger; @@ -15,14 +16,16 @@ public class LiteNetLibClient : IClient public bool IsConnected { get; private set; } private readonly NetPacketProcessor netPacketProcessor = new NetPacketProcessor(); - private AutoResetEvent connectedEvent = new AutoResetEvent(false); + private readonly AutoResetEvent connectedEvent = new AutoResetEvent(false); private readonly PacketReceiver packetReceiver; + private readonly NetworkDebugger networkDebugger; private NetManager client; - public LiteNetLibClient() + public LiteNetLibClient(PacketReceiver packetReceiver, NetworkDebugger networkDebugger) { - packetReceiver = NitroxServiceLocator.LocateService(); + this.packetReceiver = packetReceiver; + this.networkDebugger = networkDebugger; } public void Start(string ipAddress, int serverPort) @@ -50,6 +53,7 @@ public void Start(string ipAddress, int serverPort) public void Send(Packet packet) { + networkDebugger?.PacketSent(packet); client.SendToAll(netPacketProcessor.Write(packet.ToWrapperPacket()), NitroxDeliveryMethod.ToLiteNetLib(packet.DeliveryMethod)); client.Flush(); } diff --git a/NitroxClient/Communication/PacketReceiver.cs b/NitroxClient/Communication/PacketReceiver.cs index f86bc49af5..ad14549a11 100644 --- a/NitroxClient/Communication/PacketReceiver.cs +++ b/NitroxClient/Communication/PacketReceiver.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using NitroxClient.Debuggers; using NitroxModel.Packets; namespace NitroxClient.Communication @@ -6,17 +7,20 @@ namespace NitroxClient.Communication // TODO: Spinlocks don't seem to be necessary here, but I don't know for certain. public class PacketReceiver { + private readonly NetworkDebugger networkDebugger; private readonly Queue receivedPackets; - public PacketReceiver() + public PacketReceiver(NetworkDebugger networkDebugger = null) { receivedPackets = new Queue(); + this.networkDebugger = networkDebugger; } public void PacketReceived(Packet packet) { lock (receivedPackets) { + networkDebugger?.PacketReceived(packet); receivedPackets.Enqueue(packet); } } diff --git a/NitroxClient/Debuggers/NetworkDebugger.cs b/NitroxClient/Debuggers/NetworkDebugger.cs index cd3b2f6d5e..9f62767a78 100644 --- a/NitroxClient/Debuggers/NetworkDebugger.cs +++ b/NitroxClient/Debuggers/NetworkDebugger.cs @@ -1,20 +1,236 @@ -using UnityEngine; +using System; +using System.Collections.Generic; +using System.Linq; +using NitroxClient.Unity.Helper; +using NitroxModel.Packets; +using UnityEngine; namespace NitroxClient.Debuggers { public class NetworkDebugger : BaseDebugger { - public NetworkDebugger() : base(400, null, KeyCode.N, true, false, false, GUISkinCreationOptions.DERIVEDCOPY) + private const int PACKET_STORED_COUNT = 100; + private readonly Dictionary countByType = new Dictionary(); + + private readonly List filter = new List { nameof(Movement), nameof(EntityTransformUpdates) }; + private readonly List packets = new List(PACKET_STORED_COUNT); + + // vs blacklist + private bool isWhitelist; + private int receivedCount; + private Vector2 scrollPosition; + private int sentCount; + + public NetworkDebugger() : base(600, null, KeyCode.N, true, false, false, GUISkinCreationOptions.DERIVEDCOPY) + { + ActiveTab = AddTab("All", RenderTabPackets); + AddTab("Sent", RenderTabSentPackets); + AddTab("Received", RenderTabReceivedPackets); + AddTab("Type Count", RenderTabTypeCount); + AddTab("Filter", RenderTabFilter); + } + + public void PacketSent(Packet packet) { - ActiveTab = AddTab("Packets", RenderTabPackets); + AddPacket(packet, true); + sentCount++; + } + + public void PacketReceived(Packet packet) + { + AddPacket(packet, false); + receivedCount++; + } + + protected override void OnSetSkin(GUISkin skin) + { + base.OnSetSkin(skin); + + skin.SetCustomStyle("packet-type-down", + skin.label, + s => + { + s.normal = new GUIStyleState { textColor = Color.green }; + s.fontStyle = FontStyle.Bold; + s.alignment = TextAnchor.MiddleLeft; + }); + + skin.SetCustomStyle("packet-type-up", + skin.label, + s => + { + s.normal = new GUIStyleState { textColor = Color.red }; + s.fontStyle = FontStyle.Bold; + s.alignment = TextAnchor.MiddleLeft; + }); } private void RenderTabPackets() { using (new GUILayout.VerticalScope("Box")) { - GUILayout.Label("TODO: In/out-coming packets log", "header"); + GUILayout.Label($"Sent: {sentCount} - Received: {receivedCount}"); + + scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Height(300)); + RenderPacketList(ToRender.BOTH); + GUILayout.EndScrollView(); } } + + private void RenderTabSentPackets() + { + using (new GUILayout.VerticalScope("Box")) + { + GUILayout.Label($"Sent: {sentCount} - Received: {receivedCount}"); + + scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Height(300)); + RenderPacketList(ToRender.SENT); + GUILayout.EndScrollView(); + } + } + + private void RenderTabReceivedPackets() + { + using (new GUILayout.VerticalScope("Box")) + { + GUILayout.Label($"Sent: {sentCount} - Received: {receivedCount}"); + + scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Height(300)); + RenderPacketList(ToRender.RECEIVED); + GUILayout.EndScrollView(); + } + } + + private void RenderTabTypeCount() + { + using (new GUILayout.VerticalScope("Box")) + { + GUILayout.Label($"Sent: {sentCount} - Received: {receivedCount}"); + + scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Height(300)); + foreach (KeyValuePair kv in countByType.OrderBy(e => -e.Value)) // descending + { + GUILayout.Label($"{kv.Key.Name}: {kv.Value}"); + } + GUILayout.EndScrollView(); + } + } + + private void RenderTabFilter() + { + using (new GUILayout.VerticalScope("Box")) + { + GUILayout.Label($"Sent: {sentCount} - Received: {receivedCount}"); + using (new GUILayout.HorizontalScope()) + { + isWhitelist = GUILayout.Toggle(isWhitelist, "Is Whitelist"); + if (GUILayout.Button("Clear")) + { + filter.Clear(); + } + } + + scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Height(300)); + for (int i = 0; i < filter.Count; i++) + { + filter[i] = GUILayout.TextField(filter[i]); + } + string n = GUILayout.TextField(""); + if (n != "") + { + filter.Add(n); + } + GUILayout.EndScrollView(); + } + } + + private void RenderPacketList(ToRender toRender) + { + bool isSentList = toRender.HasFlag(ToRender.SENT); + bool isReceiveList = toRender.HasFlag(ToRender.RECEIVED); + PacketPrefixer prefixer = isSentList && isReceiveList ? (PacketPrefixer)PacketDirectionPrefixer : PacketNoopPrefixer; + + for (int i = packets.Count - 1; i >= 0; i--) + { + PacketDebugWrapper wrapper = packets[i]; + if (wrapper.IsSent && !isSentList) + { + continue; + } + if (!wrapper.IsSent && !isReceiveList) + { + continue; + } + + using (new GUILayout.VerticalScope("Box")) + { + using (new GUILayout.HorizontalScope()) + { + wrapper.ShowDetails = GUILayout.Toggle(wrapper.ShowDetails, "", GUILayout.Width(20), GUILayout.Height(20)); + GUILayout.Label($"{prefixer(wrapper)}{wrapper.Packet.GetType().FullName}", wrapper.IsSent ? "packet-type-up" : "packet-type-down"); + + packets[i] = wrapper; // Store again because value-type + } + + if (wrapper.ShowDetails) + { + IShortString hasShortString = wrapper.Packet as IShortString; + GUILayout.Label(hasShortString != null ? hasShortString.ToShortString() : wrapper.Packet.ToString()); + } + } + } + } + + private void AddPacket(Packet packet, bool isSent) + { + Type packetType = packet.GetType(); + if (isWhitelist == filter.Contains(packetType.Name, StringComparer.InvariantCultureIgnoreCase)) + { + packets.Add(new PacketDebugWrapper(packet, isSent, false)); + if (packets.Count > PACKET_STORED_COUNT) + { + packets.RemoveAt(0); + } + } + + int count; + if (countByType.TryGetValue(packetType, out count)) + { + countByType[packetType] = count + 1; + } + else + { + countByType.Add(packetType, 1); + } + } + + private string PacketDirectionPrefixer(PacketDebugWrapper wrapper) => $"{(wrapper.IsSent ? "↑" : "↓")} - "; + + private string PacketNoopPrefixer(PacketDebugWrapper wraper) => ""; + + private delegate string PacketPrefixer(PacketDebugWrapper wrapper); + + private struct PacketDebugWrapper + { + public readonly Packet Packet; + public readonly bool IsSent; + + public bool ShowDetails { get; set; } + + public PacketDebugWrapper(Packet packet, bool isSent, bool showDetails) + { + IsSent = isSent; + Packet = packet; + ShowDetails = showDetails; + } + } + + [Flags] + private enum ToRender + { + SENT = 1, + RECEIVED = 2, + BOTH = SENT | RECEIVED + } } } diff --git a/NitroxClient/GameLogic/LocalPlayer.cs b/NitroxClient/GameLogic/LocalPlayer.cs index ff897f5c8e..6ec2356eda 100644 --- a/NitroxClient/GameLogic/LocalPlayer.cs +++ b/NitroxClient/GameLogic/LocalPlayer.cs @@ -49,7 +49,6 @@ public void BroadcastStats(float oxygen, float maxOxygen, float health, float fo public void UpdateLocation(Vector3 location, Vector3 velocity, Quaternion bodyRotation, Quaternion aimingRotation, Optional vehicle) { Movement movement; - if (vehicle.HasValue) { movement = new VehicleMovement(multiplayerSession.Reservation.PlayerId, vehicle.Value); diff --git a/NitroxClient/MonoBehaviours/NitroxDebugManager.cs b/NitroxClient/MonoBehaviours/NitroxDebugManager.cs index dbff84db7f..662402b270 100644 --- a/NitroxClient/MonoBehaviours/NitroxDebugManager.cs +++ b/NitroxClient/MonoBehaviours/NitroxDebugManager.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using System.Linq; using NitroxClient.Debuggers; +using NitroxModel.Core; using NitroxModel.Logger; using UnityEngine; using UnityEngine.SceneManagement; @@ -16,12 +18,7 @@ public class NitroxDebugManager : MonoBehaviour private NitroxDebugManager() { - Debuggers = new List - { - new SceneDebugger(), - new NetworkDebugger(), - new EntityDebugger() - }; + Debuggers = NitroxServiceLocator.LocateServicePreLifetime>().ToList(); } public static void ToggleCursor() diff --git a/NitroxModel-Subnautica/NitroxModel-Subnautica.csproj b/NitroxModel-Subnautica/NitroxModel-Subnautica.csproj index ae1b6db16d..5729eed69a 100644 --- a/NitroxModel-Subnautica/NitroxModel-Subnautica.csproj +++ b/NitroxModel-Subnautica/NitroxModel-Subnautica.csproj @@ -42,7 +42,10 @@ ..\packages\Autofac.2.6.3.862\lib\NET35\Autofac.Configuration.dll - + + ..\packages\LitJson.0.16.0\lib\net40\LitJSON.dll + True + ..\lib\protobuf-net.dll diff --git a/NitroxModel-Subnautica/packages.config b/NitroxModel-Subnautica/packages.config index ee27163160..500de98d0f 100644 --- a/NitroxModel-Subnautica/packages.config +++ b/NitroxModel-Subnautica/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/NitroxModel/NitroxModel.csproj b/NitroxModel/NitroxModel.csproj index a572f7c956..d7f2219ab1 100644 --- a/NitroxModel/NitroxModel.csproj +++ b/NitroxModel/NitroxModel.csproj @@ -279,6 +279,7 @@ + diff --git a/NitroxModel/Packets/IShortString.cs b/NitroxModel/Packets/IShortString.cs new file mode 100644 index 0000000000..b255a557e4 --- /dev/null +++ b/NitroxModel/Packets/IShortString.cs @@ -0,0 +1,11 @@ +namespace NitroxModel.Packets +{ + public interface IShortString + { + /// + /// Gets a short summary of the packet to quickly see what's in it. + /// + /// A short summary of the packet data as string. + string ToShortString(); + } +} diff --git a/NitroxModel/Packets/InitialPlayerSync.cs b/NitroxModel/Packets/InitialPlayerSync.cs index 9cef83cca6..866a1db6f7 100644 --- a/NitroxModel/Packets/InitialPlayerSync.cs +++ b/NitroxModel/Packets/InitialPlayerSync.cs @@ -9,7 +9,7 @@ namespace NitroxModel.Packets { [Serializable] - public class InitialPlayerSync : Packet + public class InitialPlayerSync : Packet, IShortString { public NitroxId AssignedEscapePodId; public List EscapePodsData { get; } @@ -76,5 +76,13 @@ public override string ToString() { return "[InitialPlayerSync - EquippedItems: " + EquippedItems + " BasePieces: " + BasePieces + " Vehicles: " + Vehicles + " InventoryItems: " + InventoryItems + " PDAData: " + PDAData + " StoryGoalData: " + StoryGoalData + "]"; } + + public string ToShortString() + { + return $"Equipped items count: {EquippedItems.Count}\n" + + $"Base pieces count: {BasePieces.Count}\n" + + $"Vehicles count: {Vehicles.Count}\n" + + $"Inventory items count: {InventoryItems.Count}"; + } } } diff --git a/NitroxModel/Packets/MultiplayerSessionPolicyRequest.cs b/NitroxModel/Packets/MultiplayerSessionPolicyRequest.cs index 18dd27c0c3..67f4d34489 100644 --- a/NitroxModel/Packets/MultiplayerSessionPolicyRequest.cs +++ b/NitroxModel/Packets/MultiplayerSessionPolicyRequest.cs @@ -2,8 +2,10 @@ namespace NitroxModel.Packets { - // This is a packet that we use to "ping" a server to let it know that we'd like more information - // on the current requirements to submit a reservation to the ongoing game session. + /// + /// This is a packet that we use to "ping" a server to let it know that we'd like more information + /// on the current requirements to submit a reservation to the ongoing game session. + /// [Serializable] public class MultiplayerSessionPolicyRequest : CorrelatedPacket { diff --git a/NitroxServer/ServerAutoFacRegistrar.cs b/NitroxServer/ServerAutoFacRegistrar.cs index a1b577bf61..2d4753a858 100644 --- a/NitroxServer/ServerAutoFacRegistrar.cs +++ b/NitroxServer/ServerAutoFacRegistrar.cs @@ -1,7 +1,6 @@ using System.Reflection; using Autofac; using NitroxModel.Core; -using System.Reflection; using NitroxServer.GameLogic; using NitroxServer.GameLogic.Entities; using NitroxServer.Serialization.World;