diff --git a/.gitignore b/.gitignore index 350a49dde..f55cb4747 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Ignore everything: /Assets/* !/Assets/FishNet +!/Assets/FishNet.meta !.gitignore DefaultPrefabObjects.asset diff --git a/.idea/.idea.FishNet/.idea/.gitignore b/.idea/.idea.FishNet/.idea/.gitignore new file mode 100644 index 000000000..535ce6d11 --- /dev/null +++ b/.idea/.idea.FishNet/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/projectSettingsUpdater.xml +/modules.xml +/contentModel.xml +/.idea.FishNet.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.FishNet/.idea/indexLayout.xml b/.idea/.idea.FishNet/.idea/indexLayout.xml new file mode 100644 index 000000000..7b08163ce --- /dev/null +++ b/.idea/.idea.FishNet/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.FishNet/.idea/vcs.xml b/.idea/.idea.FishNet/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/.idea.FishNet/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Assets/FishNet.meta b/Assets/FishNet.meta new file mode 100644 index 000000000..fe2d4d5c7 --- /dev/null +++ b/Assets/FishNet.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 04c0f770bcb73bc46a75fedc3f97d40d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling.meta b/Assets/FishNet/Runtime/Editor/Profiling.meta new file mode 100644 index 000000000..1723d1eef --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bac8d138d5b64f5196d9bc91fcab4110 +timeCreated: 1751232406 \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Editor/Profiling/ICountRecorderProvider.cs b/Assets/FishNet/Runtime/Editor/Profiling/ICountRecorderProvider.cs new file mode 100644 index 000000000..303337459 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/ICountRecorderProvider.cs @@ -0,0 +1,9 @@ +using FishNet.Utility.Performance.Profiling; + +namespace Fishnet.NetworkProfiler.ModuleGUI +{ + internal interface ICountRecorderProvider + { + CountRecorder GetCountRecorder(); + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/ICountRecorderProvider.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/ICountRecorderProvider.cs.meta new file mode 100644 index 000000000..9aaffa630 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/ICountRecorderProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e7ff307d04db0b64690d8c9692e40d8c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages.meta b/Assets/FishNet/Runtime/Editor/Profiling/Messages.meta new file mode 100644 index 000000000..31bc0e207 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 667ec5fd9bee70844a2b458410782b0a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/Columns.cs b/Assets/FishNet/Runtime/Editor/Profiling/Messages/Columns.cs new file mode 100644 index 000000000..9b64d381b --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/Columns.cs @@ -0,0 +1,90 @@ +using System.Collections; +using System.Collections.Generic; +using Fishnet.NetworkProfiler.ModuleGUI.UITable; + +namespace Fishnet.NetworkProfiler.ModuleGUI.Messages +{ + internal sealed class Columns : IEnumerable + { + private const int EXPAND_WIDTH = 25; + private const int SHORT_NAME_WIDTH = 75; + private const int FULL_NAME_WIDTH = 300; + private const int NAME_WIDTH = 450; + private const int OTHER_WIDTH = 100; + + private readonly ColumnInfo[] _columns; + + public readonly ColumnInfo Expand; + public readonly ColumnInfo FullName; + public readonly ColumnInfo TotalBytes; + public readonly ColumnInfo Count; + public readonly ColumnInfo BytesPerMessage; + //public readonly ColumnInfo NetId; + public readonly ColumnInfo ObjectName; + public readonly ColumnInfo PropertyName; + + public Columns() + { + Expand = new ColumnInfo("+", EXPAND_WIDTH, x => ""); + + FullName = new ColumnInfo("Packet Type", SHORT_NAME_WIDTH, x => x.PacketIdName); + FullName.AddSort(m => m.Name, m => m.PacketIdName); + + TotalBytes = new ColumnInfo("Total Bytes", OTHER_WIDTH, x => x.TotalBytes.ToString()); + TotalBytes.AddSort(m => m.TotalBytes, m => m.TotalBytes); + + Count = new ColumnInfo("Count", OTHER_WIDTH, x => x.Count.ToString()); + Count.AddSort(m => m.TotalCount, m => m.Count); + + BytesPerMessage = new ColumnInfo("Bytes", OTHER_WIDTH, x => x.Bytes.ToString()); + BytesPerMessage.AddSort(null, m => m.Bytes); + + // NetId = new ColumnInfo("Net id", OTHER_WIDTH, x => x.NetId.HasValue ? x.NetId.ToString() : ""); + // NetId.AddSort(null, m => m.NetId.GetValueOrDefault()); + + ObjectName = new ColumnInfo("GameObject Name", NAME_WIDTH, x => x.ObjectName); + ObjectName.AddSort(null, m => m.ObjectName); + + PropertyName = new ColumnInfo("Prop Name (hover for full name)", FULL_NAME_WIDTH, x => RpcShortName(x.RpcName)); + PropertyName.AddSort(null, m => m.RpcName); + // full name in tooltip + PropertyName.AddToolTip(m => m.RpcName); + + _columns = new ColumnInfo[] { + + Expand, + FullName, + TotalBytes, + Count, + BytesPerMessage, + //NetId, + ObjectName, + PropertyName, + }; + } + + private string RpcShortName(string fullName) + { + if (string.IsNullOrEmpty(fullName)) + return string.Empty; + + const char separator = '.'; + + if (fullName.Contains(separator)) + { + var split = fullName.Split(separator); + var count = split.Length; + if (count >= 2) + { + return $"{split[count - 2]}.{split[count - 1]}"; + } + } + + return fullName; + } + + + public IEnumerator GetEnumerator() => ((IEnumerable)_columns).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _columns.GetEnumerator(); + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/Columns.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Messages/Columns.cs.meta new file mode 100644 index 000000000..f838ed023 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/Columns.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 36dff191a3209c444844bbdc74f60cd4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/CounterNames.cs b/Assets/FishNet/Runtime/Editor/Profiling/Messages/CounterNames.cs new file mode 100644 index 000000000..32d92c8ae --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/CounterNames.cs @@ -0,0 +1,16 @@ +namespace Fishnet.NetworkProfiler.ModuleGUI.Messages +{ + internal struct CounterNames + { + public readonly string Count; + public readonly string Bytes; + public readonly string PerSecond; + + public CounterNames(string count, string bytes, string perSecond) + { + Count = count; + Bytes = bytes; + PerSecond = perSecond; + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/CounterNames.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Messages/CounterNames.cs.meta new file mode 100644 index 000000000..3e8299b61 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/CounterNames.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2422d6a68fe533e4c9da13ae30767fcf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/DrawnMessage.cs b/Assets/FishNet/Runtime/Editor/Profiling/Messages/DrawnMessage.cs new file mode 100644 index 000000000..4c3626831 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/DrawnMessage.cs @@ -0,0 +1,11 @@ +using Fishnet.NetworkProfiler.ModuleGUI.UITable; +using FishNet.Utility.Performance.Profiling; + +namespace Fishnet.NetworkProfiler.ModuleGUI.Messages +{ + internal class DrawnMessage + { + public PacketInfo Info; + public Row Row; + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/DrawnMessage.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Messages/DrawnMessage.cs.meta new file mode 100644 index 000000000..2caa2fc0e --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/DrawnMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: df1ae3a59995b1a43b6b76e16d2b8f3a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/Group.cs b/Assets/FishNet/Runtime/Editor/Profiling/Messages/Group.cs new file mode 100644 index 000000000..d8d51c366 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/Group.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using Fishnet.NetworkProfiler.ModuleGUI.UITable; +using FishNet.Utility.Performance.Profiling; +using UnityEditor; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Fishnet.NetworkProfiler.ModuleGUI.Messages +{ + internal class Group + { + public readonly List Rows = new List(); + public readonly string Name; + public readonly List Messages = new List(); + + private readonly Table _table; + private readonly Columns _columns; + + public Row Head; + + public int TotalBytes { get; private set; } + public int TotalCount { get; private set; } + public int Order { get; private set; } + + public bool Expanded { get; private set; } + + public Group(string name, Table table, Columns columns) + { + Name = name; + _table = table; + _columns = columns; + // start at max, then take min each time message is added + Order = int.MaxValue; + } + + public void AddMessage(PacketInfo msg) + { + Messages.Add(new DrawnMessage { Info = msg }); + TotalBytes += msg.TotalBytes; + TotalCount += msg.Count; + Order = Math.Min(Order, msg.Order); + } + + public void ToggleExpand() + { + Expand(!Expanded); + } + + public void Expand(bool expanded) + { + Expanded = expanded; + // create rows if needed + LazyCreateRows(); + foreach (var row in Rows) + { + row.VisualElement.style.display = expanded ? DisplayStyle.Flex : DisplayStyle.None; + } + + // head can be null if ungrouped + Head?.SetText(_columns.Expand, Expanded ? "-" : "+"); + } + + public void LazyCreateRows() + { + // not visible, do nothing till row is expanded + if (!Expanded) + return; + // already created + if (Rows.Count > 0) + return; + + DrawMessages(); + } + + + /// Messages to add to table + /// list to add rows to once created, Can be null + private void DrawMessages() + { + var previous = Head; + var backgroundColor = GetBackgroundColor(); + + foreach (var drawn in Messages) + { + var row = _table.AddRow(previous); + Rows.Add(row); + + // set previous to be new row, so that message are added in order after previous + previous = row; + + drawn.Row = row; + var info = drawn.Info; + DrawMessage(row, info); + + // set color of labels not whole row, otherwise color will be outside of table as well + foreach (var ele in row.GetChildren()) + ele.style.backgroundColor = backgroundColor; + } + } + + private void DrawMessage(Row row, PacketInfo info) + { + foreach (var column in _columns) + { + row.SetText(column, column.TextGetter.Invoke(info)); + if (column.HasToolTip) + { + var label = row.GetLabel(column); + label.tooltip = column.ToolTipGetter.Invoke(info); + } + } + } + + private static Color GetBackgroundColor() + { + // pick color that is lighter/darker than default editor background + // todo check if there is a way to get the real color, or do we have to use `isProSkin`? + return EditorGUIUtility.isProSkin + ? (Color)new Color32(56, 56, 56, 255) / 0.8f + : (Color)new Color32(194, 194, 194, 255) * .8f; + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/Group.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Messages/Group.cs.meta new file mode 100644 index 000000000..dcc56b888 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/Group.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d73a0b136989084c8caed7ae1349219 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/GroupSorter.cs b/Assets/FishNet/Runtime/Editor/Profiling/Messages/GroupSorter.cs new file mode 100644 index 000000000..bd5dcc783 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/GroupSorter.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using Fishnet.NetworkProfiler.ModuleGUI.UITable; +using FishNet.Utility.Performance.Profiling; + +namespace Fishnet.NetworkProfiler.ModuleGUI.Messages +{ + internal struct GroupSorter + { + private readonly Dictionary _grouped; + private readonly Func _sortGroupFunc; + private readonly Func _sortMessageFunc; + + private readonly SortMode _sortMode; + + public GroupSorter(Dictionary grouped, ColumnInfo sortHeader, SortMode sortMode) + { + _grouped = grouped; + _sortMode = sortMode; + + // if header or sort is null, use default + _sortGroupFunc = sortHeader?.SortGroup ?? DefaultGroupSort; + _sortMessageFunc = sortHeader?.SortMessages ?? DefaultMessageSort; + } + + public void Sort() + { + var groups = new List(_grouped.Values); + + // sort all groups and their messages + groups.Sort(CompareGroupSortMode); + foreach (var group in groups) + { + group.Messages.Sort(CompareDrawnSortMode); + } + + // apply sort to table + foreach (var group in groups) + { + // use BringToFront so that each new element is placed after the last one, bring them all to their correct position + + // head might be null if messages are ungrouped + group.Head?.VisualElement.BringToFront(); + foreach (var msg in group.Messages) + { + // row might be null before it is drawn for first time + msg.Row?.VisualElement.BringToFront(); + } + } + } + + private int CompareGroupSortMode(Group x, Group y) + { + var sort = _sortGroupFunc.Invoke(x, y); + return CheckSortMode(sort); + } + + private int CompareDrawnSortMode(DrawnMessage x, DrawnMessage y) + { + var sort = _sortMessageFunc.Invoke(x.Info, y.Info); + return CheckSortMode(sort); + } + + private int CheckSortMode(int sort) + { + // flip order if Descending + if (_sortMode == SortMode.Descending) + return -sort; + + return sort; + } + + public static int Compare(Group x, Group y, Func func) where T : IComparable + { + var xValue = func.Invoke(x); + var yValue = func.Invoke(y); + return xValue.CompareTo(yValue); + } + public static int Compare(PacketInfo x, PacketInfo y, Func func) where T : IComparable + { + var xValue = func.Invoke(x); + var yValue = func.Invoke(y); + return xValue.CompareTo(yValue); + } + + public static int DefaultGroupSort(Group x, Group y) + { + return x.Order.CompareTo(y.Order); + } + public static int DefaultMessageSort(PacketInfo x, PacketInfo y) + { + return x.Order.CompareTo(y.Order); + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/GroupSorter.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Messages/GroupSorter.cs.meta new file mode 100644 index 000000000..ecc245ec3 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/GroupSorter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b13bef8f87b23684bae6fc2453c090b0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/MessageView.cs b/Assets/FishNet/Runtime/Editor/Profiling/Messages/MessageView.cs new file mode 100644 index 000000000..d9826c2c1 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/MessageView.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using Fishnet.NetworkProfiler.ModuleGUI.UITable; +using FishNet.Utility.Performance.Profiling; +using UnityEngine.UIElements; + +namespace Fishnet.NetworkProfiler.ModuleGUI.Messages +{ + internal class MessageView + { + private readonly Columns _columns; + private readonly Table _table; + + /// + /// cache list used to gather messages + /// + private readonly List _messages = new List(); + private readonly Dictionary _grouped = new Dictionary(); + + public event Action OnGroupExpanded; + + public MessageView(Columns columns, TableSorter sorter, VisualElement parent) + { + _columns = columns; + _table = new Table(columns, sorter); + parent.Add(_table.VisualElement); + } + + public void Draw(IEnumerable frames, bool groupMessages) + { + CollectMessages(frames); + GroupMessages(groupMessages); + DrawGroups(groupMessages); + + var expandColumn = _columns.Expand; + var defaultWidth = expandColumn.Width; + var width = groupMessages ? defaultWidth : 0; + _table.ChangeWidth(expandColumn, width, true); + } + + private void CollectMessages(IEnumerable frames) + { + _messages.Clear(); + foreach (var frame in frames) + { + _messages.AddRange(frame.Messages); + } + } + + private void GroupMessages(bool asGroups) + { + _grouped.Clear(); + + foreach (var message in _messages) + { + string name; + if (asGroups) + name = message.PacketIdName; + else + name = "all_messages"; + + if (!_grouped.TryGetValue(name, out var group)) + { + group = new Group(name, _table, _columns); + _grouped[name] = group; + } + + group.AddMessage(message); + } + } + + private void DrawGroups(bool withHeader) + { + foreach (var group in _grouped.Values) + { + if (withHeader) + { + DrawGroupHeader(group); + } + else + { + group.Expand(true); + } + } + } + + private void DrawGroupHeader(Group group) + { + // draw header + var head = _table.AddRow(); + head.SetText(_columns.Expand, group.Expanded ? "-" : "+"); + head.SetText(_columns.FullName, group.Name); + head.SetText(_columns.TotalBytes, group.TotalBytes); + head.SetText(_columns.Count, group.TotalCount); + group.Head = head; + + var expand = head.GetLabel(_columns.Expand); + expand.AddManipulator(new Clickable((evt) => + { + group.ToggleExpand(); + OnGroupExpanded?.Invoke(group, group.Expanded); + })); + + // will lazy create message if expanded + group.Expand(group.Expanded); + } + + public void Sort(ColumnInfo sortHeader, SortMode sortMode) + { + var sorter = new GroupSorter(_grouped, sortHeader, sortMode); + sorter.Sort(); + + if (sortHeader != null) + { + // also set table names, + _table.SetSortHeader(sortHeader, sortMode); + } + } + + public void Clear() + { + _table.Clear(); + } + + public VisualElement AddEmptyRow() + { + var row = _table.AddEmptyRow(); + return row.VisualElement; + } + + public void ExpandMany(List expanded) + { + foreach (var group in _grouped.Values) + { + if (expanded.Contains(group.Name)) + { + group.Expand(true); + } + } + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/MessageView.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Messages/MessageView.cs.meta new file mode 100644 index 000000000..6bfc49b55 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/MessageView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6f417eeb8a30cca4eb1a9301a73dbf10 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/MessageViewController.cs b/Assets/FishNet/Runtime/Editor/Profiling/Messages/MessageViewController.cs new file mode 100644 index 000000000..5f4676b1c --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/MessageViewController.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using Fishnet.NetworkProfiler.ModuleGUI.UITable; +using FishNet.Utility.Performance.Profiling; +//using Mirage.RemoteCalls; +using Unity.Profiling; +using Unity.Profiling.Editor; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Fishnet.NetworkProfiler.ModuleGUI.Messages +{ + internal sealed class MessageViewController : ProfilerModuleViewController + { + private readonly CounterNames _names; + private readonly Columns _columns = new Columns(); + private Label _countLabel; + private Label _bytesLabel; + private Label _perSecondLabel; + private VisualElement _toggleBox; + private Toggle _debugToggle; + private Toggle _groupMsgToggle; + private MessageView _messageView; + private readonly SavedData _savedData; + + public MessageViewController(ProfilerWindow profilerWindow, CounterNames names, SavedData savedData) + : base(profilerWindow) + { + _names = names; + _savedData = savedData; + } + + protected override VisualElement CreateView() + { + // unity doesn't catch errors here so we have to wrap in try/catch + try + { + return CreateViewInternal(); + } + catch (Exception ex) + { + Debug.LogException(ex); + return null; + } + } + + protected override void Dispose(bool disposing) + { + if (!disposing) + return; + + // Unsubscribe from the Profiler window event that we previously subscribed to. + ProfilerWindow.SelectedFrameIndexChanged -= FrameIndexChanged; + + base.Dispose(disposing); + } + + private VisualElement CreateViewInternal() + { + var root = new VisualElement(); + root.style.flexDirection = new StyleEnum(FlexDirection.Row); + root.style.height = Length.Percent(100); + root.style.overflow = Overflow.Hidden; + + var summary = new VisualElement(); + _countLabel = AddLabelWithPadding(summary); + _bytesLabel = AddLabelWithPadding(summary); + _perSecondLabel = AddLabelWithPadding(summary); + _perSecondLabel.tooltip = Names.PER_SECOND_TOOLTIP; + root.Add(summary); + summary.style.height = Length.Percent(100); + summary.style.width = 180; + summary.style.minWidth = 180; + summary.style.maxWidth = 180; + summary.style.borderRightColor = Color.white * .4f;//dark grey + summary.style.borderRightWidth = 3; + + _toggleBox = new VisualElement(); + _toggleBox.style.position = Position.Absolute; + _toggleBox.style.bottom = 5; + _toggleBox.style.left = 5; + _toggleBox.style.unityTextAlign = TextAnchor.LowerLeft; + summary.Add(_toggleBox); + + _groupMsgToggle = new Toggle + { + text = "Group Messages", + tooltip = "Groups Message by type", + value = _savedData.GroupMessages, + }; + _groupMsgToggle.RegisterValueChangedCallback(GroupToggled); + _toggleBox.Add(_groupMsgToggle); + + // todo allow selection of multiple frames + //var frameSlider = new MinMaxSlider(); + //frameSlider.highLimit = 300; + //frameSlider.lowLimit = 1; + //frameSlider.value = Vector2.one; + //frameSlider.RegisterValueChangedCallback(_ => Debug.Log(frameSlider.value)); + //_toggleBox.Add(frameSlider); + + _debugToggle = new Toggle + { + text = "Show Fake Messages", + tooltip = "Adds fakes message to table to debug layout of table", + value = false + }; + _debugToggle.RegisterValueChangedCallback(_ => ReloadData()); + _toggleBox.Add(_debugToggle); +#if MIRAGE_PROFILER_DEBUG + _debugToggle.style.display = DisplayStyle.Flex; +#else + _debugToggle.style.display = DisplayStyle.None; +#endif + + + var sorter = new TableSorter(this); + _messageView = new MessageView(_columns, sorter, root); + _messageView.OnGroupExpanded += OnGroupExpanded; + + // Populate the label with the current data for the selected frame. + ReloadData(); + + // Be notified when the selected frame index in the Profiler Window changes, so we can update the label. + ProfilerWindow.SelectedFrameIndexChanged += FrameIndexChanged; + + return root; + } + + private void GroupToggled(ChangeEvent evt) + { + _savedData.GroupMessages = evt.newValue; + ReloadData(); + } + + private void OnGroupExpanded(Group group, bool expanded) + { + if (expanded) + _savedData.Expanded.Add(group.Name); + else + _savedData.Expanded.Remove(group.Name); + } + + private void FrameIndexChanged(long selectedFrameIndex) => ReloadData(); + + private static Label AddLabelWithPadding(VisualElement parent) + { + var label = new Label() { style = { paddingTop = 8, paddingLeft = 8 } }; + parent.Add(label); + return label; + } + + internal void Sort(SortHeader header) + { + _savedData.SetSortHeader(header); + SortFromSaveData(); + } + + private void SortFromSaveData() + { + var (sortHeader, sortMode) = _savedData.GetSortHeader(_columns); + _messageView.Sort(sortHeader, sortMode); + } + + + private void ReloadData() + { + SetSummary(_countLabel, _names.Count); + SetSummary(_bytesLabel, _names.Bytes); + SetSummary(_perSecondLabel, _names.PerSecond); + + ReloadMessages(); + } + + private void SetSummary(Label label, string counterName) + { + var frame = (int)ProfilerWindow.selectedFrameIndex; + var category = ProfilerCategory.Network.Name; + var value = ProfilerDriver.GetFormattedCounterValue(frame, category, counterName); + + // replace prefix + var display = counterName.Replace("Received", "").Replace("Sent", "").Trim(); + label.text = $"{display}: {value}"; + } + + private void ReloadMessages() + { + const int EditorID = -1; + + _messageView.Clear(); + + var frameIndex = (int)ProfilerWindow.selectedFrameIndex; + // Debug.Log($"ReloadMessages [selected {(int)ProfilerWindow.selectedFrameIndex}]"); + + + if (ProfilerDriver.connectedProfiler != EditorID) + { + AddErrorLabel("Can't read message from player"); + return; + } + + if (!TryGetMessages(frameIndex, out var messages)) + { + AddErrorLabel("Can not load messages! (Message list only visible in play mode)\nIMPORTANT: make sure NetworkProfilerBehaviour is setup in starting scene"); + return; + } + + if (messages.Count == 0) + { + AddInfoLabel("No Messages"); + return; + } + + var frame = new Frame[1] { + new Frame{ Messages = messages }, + }; + _messageView.Draw(frame, _groupMsgToggle.value); + _messageView.ExpandMany(_savedData.Expanded); + SortFromSaveData(); + } + + private bool TryGetMessages(int frameIndex, out List messages) + { + if (_debugToggle.value) + { + messages = GenerateDebugMessages(); + return true; + } + + if (frameIndex == -1) + { + messages = null; + return false; + } + + messages = _savedData.Frames.GetFrame(frameIndex).Messages; + return true; + } + + private static List GenerateDebugMessages() + { + var messages = new List(); + var order = 0; +// for (var i = 0; i < 5; i++) +// { +// messages.Add(NewInfo(order++, new RpcMessage { NetId = (uint)i }, 20 + i, 5)); +// messages.Add(NewInfo(order++, new SpawnMessage { NetId = (uint)i }, 80 + i, 1)); +// messages.Add(NewInfo(order++, new SpawnMessage { NetId = (uint)i }, 60 + i, 4)); +// messages.Add(NewInfo(order++, new NetworkPingMessage { }, 4, 1)); + +// static MessageInfo NewInfo(int order, object msg, int bytes, int count) +// { +//#if MIRAGE_DIAGNOSTIC_INSTANCE +// return new MessageInfo(new NetworkDiagnostics.MessageInfo(null, msg, bytes, count), provider, order); +//#else +// return new MessageInfo(new NetworkDiagnostics.MessageInfo(msg, bytes, count), new NetworkInfoProvider(null), order); +//#endif +// } +// } + + return messages; + } + + private void AddErrorLabel(string message) + { + var parent = _messageView.AddEmptyRow(); + var ele = AddLabelWithPadding(parent); + ele.style.color = Color.red; + ele.text = message; + } + + private void AddInfoLabel(string message) + { + var parent = _messageView.AddEmptyRow(); + var ele = AddLabelWithPadding(parent); + ele.text = message; + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/MessageViewController.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Messages/MessageViewController.cs.meta new file mode 100644 index 000000000..6a3c59a76 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/MessageViewController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b210a69331a49414ca5f6436163f61d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/TableSorter.cs b/Assets/FishNet/Runtime/Editor/Profiling/Messages/TableSorter.cs new file mode 100644 index 000000000..80da5485b --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/TableSorter.cs @@ -0,0 +1,27 @@ +using Fishnet.NetworkProfiler.ModuleGUI.UITable; +using UnityEngine; + +namespace Fishnet.NetworkProfiler.ModuleGUI.Messages +{ + + internal class TableSorter : ITableSorter + { + private readonly MessageViewController _controller; + + public TableSorter(MessageViewController controller) + { + _controller = controller; + } + + public void Sort(Table table, SortHeader header) + { + if (table.ContainsEmptyRows) + { + Debug.LogWarning("Can't sort when there are empty rows"); + return; + } + + _controller.Sort(header); + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Messages/TableSorter.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Messages/TableSorter.cs.meta new file mode 100644 index 000000000..6c4c19a03 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Messages/TableSorter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 658b759dbe63e9c44a14d785a15c862e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/ModuleNames.cs b/Assets/FishNet/Runtime/Editor/Profiling/ModuleNames.cs new file mode 100644 index 000000000..ad149b36c --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/ModuleNames.cs @@ -0,0 +1,12 @@ +namespace Fishnet.NetworkProfiler.ModuleGUI +{ + internal static class ModuleNames + { + // change this based on netcode lib + private const string PREFIX = "FishNet"; + + public const string SERVER = PREFIX + " Server"; + public const string SENT = PREFIX + " Sent"; + public const string RECEIVED = PREFIX + " Received"; + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/ModuleNames.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/ModuleNames.cs.meta new file mode 100644 index 000000000..eeee9a1a1 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/ModuleNames.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1a65943c66af2cb4cb0433f5f3bd73b7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/ReceivedModule.cs b/Assets/FishNet/Runtime/Editor/Profiling/ReceivedModule.cs new file mode 100644 index 000000000..fb6e2623c --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/ReceivedModule.cs @@ -0,0 +1,36 @@ +using Fishnet.NetworkProfiler.ModuleGUI.Messages; +using FishNet.Utility.Performance.Profiling; +using Unity.Profiling.Editor; + +namespace Fishnet.NetworkProfiler.ModuleGUI +{ + [System.Serializable] + [ProfilerModuleMetadata(ModuleNames.RECEIVED)] + public class ReceivedModule : ProfilerModule, ICountRecorderProvider + { + private static readonly ProfilerCounterDescriptor[] counters = new ProfilerCounterDescriptor[] + { + new ProfilerCounterDescriptor(Names.RECEIVED_COUNT, Counters.Category), + new ProfilerCounterDescriptor(Names.RECEIVED_BYTES, Counters.Category), + new ProfilerCounterDescriptor(Names.RECEIVED_PER_SECOND, Counters.Category), + }; + + public ReceivedModule() : base(counters) { } + + public override ProfilerModuleViewController CreateDetailsViewController() + { + var names = new CounterNames( + Names.RECEIVED_COUNT, + Names.RECEIVED_BYTES, + Names.RECEIVED_PER_SECOND + ); + + return new MessageViewController(ProfilerWindow, names, SaveDataLoader.ReceiveData); + } + + CountRecorder ICountRecorderProvider.GetCountRecorder() + { + return NetworkProfilerRecorder._receivedCounter; + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/ReceivedModule.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/ReceivedModule.cs.meta new file mode 100644 index 000000000..e5f993026 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/ReceivedModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 677abdcefedd3144b9784f7a09a647da +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/SavedData.cs b/Assets/FishNet/Runtime/Editor/Profiling/SavedData.cs new file mode 100644 index 000000000..f9183d0d6 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/SavedData.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Fishnet.NetworkProfiler.ModuleGUI.Messages; +using Fishnet.NetworkProfiler.ModuleGUI.UITable; +using FishNet.Utility.Performance.Profiling; +using UnityEditor; +using UnityEngine; + +namespace Fishnet.NetworkProfiler.ModuleGUI +{ + [Serializable] + internal class SavedData + { + /// + /// Message from each frame so they can survive domain reload + /// + public Frames Frames; + + /// + /// Active sort header + /// + public string SortHeader; + + public SortMode SortMode; + + /// + /// Which Message groups are expanded + /// + public List Expanded; + + public bool GroupMessages = true; + + public SavedData() + { + Frames = new Frames(); + + Expanded = new List(); + } + + public (ColumnInfo, SortMode) GetSortHeader(Columns columns) + { + foreach (var c in columns) + { + if (SortHeader == c.Header) + { + return (c, SortMode); + } + } + + return (null, SortMode.None); + } + + public void SetSortHeader(SortHeader header) + { + if (header == null) + { + SortHeader = ""; + } + else + { + SortHeader = header.Info.Header; + SortMode = header.SortMode; + } + } + + public void Clear() + { + foreach (var frame in Frames) + { + frame.Bytes = 0; + frame.Messages.Clear(); + } + } + } + + internal class SaveDataLoader + { + private SavedData _receiveData; + private SavedData _sentData; + private static SaveDataLoader instance; + private static bool isQuitting; + + // private so only we can create one + private SaveDataLoader() + { + NetworkProfilerRecorder.AfterSample += AfterSample; + EditorApplication.quitting += Quitting; + } + + private void Quitting() + { + Console.WriteLine("[Mirage.Profiler] quitting"); + // save and clear references when quitting, + // this is needed because finialize is called after unity dll unloads so causes crash + SaveBoth(); + _receiveData = null; + _sentData = null; + isQuitting = true; + } + + ~SaveDataLoader() + { + NetworkProfilerRecorder.AfterSample -= AfterSample; + + // dont save after quitting, unity might unload their dll and cause crash + if (isQuitting) + return; + + SaveBoth(); + } + + private void SaveBoth() + { + if (_receiveData != null) + Save(GetFullPath("Receive"), _receiveData); + + if (_sentData != null) + Save(GetFullPath("Sent"), _sentData); + } + + private static void AfterSample(int tick) + { + SetFrame(tick, ReceiveData, NetworkProfilerRecorder._receivedCounter); + SetFrame(tick, SentData, NetworkProfilerRecorder._sentCounter); + } + + private static void SetFrame(int tick, SavedData data, CountRecorder counter) + { + var saveFrame = data.Frames.GetFrame(tick); + + // clear old data + saveFrame.Bytes = 0; + saveFrame.Messages.Clear(); + + if (counter == null) + return; + + var counterFrame = counter._frames.GetFrame(tick); + + saveFrame.Bytes = counterFrame.Bytes; + saveFrame.Messages.AddRange(counterFrame.Messages); + } + + public static SavedData ReceiveData + { + get + { + // dont load on quit, it might cause crash if unity dll unloads while savedata is save/loading + if (isQuitting) + return null; + + if (instance == null) + instance = new SaveDataLoader(); + + if (instance._receiveData == null) + { + instance._receiveData = Load(GetFullPath("Receive")); + } + return instance._receiveData; + } + } + + public static SavedData SentData + { + get + { + // dont load on quit, it might cause crash if unity dll unloads while savedata is save/loading + if (isQuitting) + return null; + + if (instance == null) + instance = new SaveDataLoader(); + + if (instance._sentData == null) + { + instance._sentData = Load(GetFullPath("Sent")); + } + return instance._sentData; + } + } + + public static string GetFullPath(string name) + { + var userSettingsFolder = Path.GetFullPath("UserSettings"); + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException(nameof(name)); + + return Path.Join(userSettingsFolder, "Mirage.Profiler", $"{name}.json"); + } + + public static void Save(string path, SavedData data) + { + Console.WriteLine($"[Mirage.Profiler] Save {path}"); + CheckDir(path); + + var text = JsonUtility.ToJson(data); + File.WriteAllText(path, text); + } + + public static SavedData Load(string path) + { + Console.WriteLine($"[Mirage.Profiler] Load {path}"); + CheckDir(path); + + if (File.Exists(path)) + { + var text = File.ReadAllText(path); + var data = JsonUtility.FromJson(text); + Validate(data); + return data; + } + else + { + return new SavedData(); + } + } + + private static void Validate(SavedData data) + { + data.Frames.ValidateSize(); + } + + private static void CheckDir(string path) + { + // check dir exists + var dir = Path.GetDirectoryName(path); + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/SavedData.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/SavedData.cs.meta new file mode 100644 index 000000000..0198a6cde --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/SavedData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6e3608e18fbb66346bc8d15f628ad4f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/SentModule.cs b/Assets/FishNet/Runtime/Editor/Profiling/SentModule.cs new file mode 100644 index 000000000..35807f2ce --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/SentModule.cs @@ -0,0 +1,36 @@ +using Fishnet.NetworkProfiler.ModuleGUI.Messages; +using FishNet.Utility.Performance.Profiling; +using Unity.Profiling.Editor; + +namespace Fishnet.NetworkProfiler.ModuleGUI +{ + [System.Serializable] + [ProfilerModuleMetadata(ModuleNames.SENT)] + public class SentModule : ProfilerModule, ICountRecorderProvider + { + private static readonly ProfilerCounterDescriptor[] counters = new ProfilerCounterDescriptor[] + { + new ProfilerCounterDescriptor(Names.SENT_COUNT, Counters.Category), + new ProfilerCounterDescriptor(Names.SENT_BYTES, Counters.Category), + new ProfilerCounterDescriptor(Names.SENT_PER_SECOND, Counters.Category), + }; + + public SentModule() : base(counters) { } + + public override ProfilerModuleViewController CreateDetailsViewController() + { + var names = new CounterNames( + Names.SENT_COUNT, + Names.SENT_BYTES, + Names.SENT_PER_SECOND + ); + + return new MessageViewController(ProfilerWindow, names, SaveDataLoader.SentData); + } + + CountRecorder ICountRecorderProvider.GetCountRecorder() + { + return NetworkProfilerRecorder._sentCounter; + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/SentModule.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/SentModule.cs.meta new file mode 100644 index 000000000..7dae7f048 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/SentModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5447799e8b54b3044b4fac717c450b46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/ServerModule.cs b/Assets/FishNet/Runtime/Editor/Profiling/ServerModule.cs new file mode 100644 index 000000000..7924d1653 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/ServerModule.cs @@ -0,0 +1,109 @@ +using FishNet.Utility.Performance.Profiling; +using Unity.Profiling; +using Unity.Profiling.Editor; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine.UIElements; + +namespace Fishnet.NetworkProfiler.ModuleGUI +{ + [System.Serializable] + [ProfilerModuleMetadata(ModuleNames.SERVER)] + public class ServerModule : ProfilerModule + { + private static readonly ProfilerCounterDescriptor[] counters = new ProfilerCounterDescriptor[] + { + new ProfilerCounterDescriptor(Names.PLAYER_COUNT, Counters.Category), + new ProfilerCounterDescriptor(Names.AUTHENTICATED_COUNT, Counters.Category), + new ProfilerCounterDescriptor(Names.CHARACTER_COUNT, Counters.Category), + new ProfilerCounterDescriptor(Names.OBJECT_COUNT, Counters.Category), + }; + + public ServerModule() : base(counters) { } + + public override ProfilerModuleViewController CreateDetailsViewController() + { + return new ServerViewController(ProfilerWindow); + } + } + + public abstract class BaseViewController : ProfilerModuleViewController + { + public BaseViewController(ProfilerWindow profilerWindow) : base(profilerWindow) { } + + protected static Label AddLabelWithPadding(VisualElement parent) + { + var label = new Label() { style = { paddingTop = 8, paddingLeft = 8 } }; + parent.Add(label); + return label; + } + + + protected void SetText(Label label, string name) + { + var frame = (int)ProfilerWindow.selectedFrameIndex; + var category = ProfilerCategory.Network.Name; + var value = ProfilerDriver.GetFormattedCounterValue(frame, category, name); + + label.text = $"{name}: {value}"; + } + } + + public sealed class ServerViewController : BaseViewController + { + private Label PlayerLabel; + private Label AuthenticatedLabel; + private Label CharacterLabel; + private Label ObjectLabel; + + public ServerViewController(ProfilerWindow profilerWindow) : base(profilerWindow) { } + + protected override VisualElement CreateView() + { + var root = new VisualElement(); + + PlayerLabel = AddLabelWithPadding(root); + AuthenticatedLabel = AddLabelWithPadding(root); + CharacterLabel = AddLabelWithPadding(root); + ObjectLabel = AddLabelWithPadding(root); + + PlayerLabel.tooltip = Names.PLAYER_COUNT_TOOLTIP; + AuthenticatedLabel.tooltip = Names.AUTHENTICATED_COUNT_TOOLTIP; + CharacterLabel.tooltip = Names.CHARACTER_COUNT_TOOLTIP; + ObjectLabel.tooltip = Names.OBJECT_COUNT_TOOLTIP; + + // Populate the label with the current data for the selected frame. + ReloadData(); + + // Be notified when the selected frame index in the Profiler Window changes, so we can update the label. + ProfilerWindow.SelectedFrameIndexChanged += OnSelectedFrameIndexChanged; + + return root; + } + + private void OnSelectedFrameIndexChanged(long selectedFrameIndex) + { + // Update the label with the current data for the newly selected frame. + ReloadData(); + } + + protected override void Dispose(bool disposing) + { + if (!disposing) + return; + + // Unsubscribe from the Profiler window event that we previously subscribed to. + ProfilerWindow.SelectedFrameIndexChanged -= OnSelectedFrameIndexChanged; + + base.Dispose(disposing); + } + + private void ReloadData() + { + SetText(PlayerLabel, Names.PLAYER_COUNT); + SetText(AuthenticatedLabel, Names.AUTHENTICATED_COUNT); + SetText(CharacterLabel, Names.CHARACTER_COUNT); + SetText(ObjectLabel, Names.OBJECT_COUNT); + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/ServerModule.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/ServerModule.cs.meta new file mode 100644 index 000000000..01d46e394 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/ServerModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b8113ff8f56cc644bb23d2da87f23eab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table.meta b/Assets/FishNet/Runtime/Editor/Profiling/Table.meta new file mode 100644 index 000000000..2dcf6c597 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cc264da8a8fc533418223d921f2f00c8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/ColumnInfo.cs b/Assets/FishNet/Runtime/Editor/Profiling/Table/ColumnInfo.cs new file mode 100644 index 000000000..bca7c911a --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/ColumnInfo.cs @@ -0,0 +1,67 @@ +using System; +using Fishnet.NetworkProfiler.ModuleGUI.Messages; +using FishNet.Utility.Performance.Profiling; + +namespace Fishnet.NetworkProfiler.ModuleGUI.UITable +{ + internal class ColumnInfo + { + public string Header { get; private set; } + public int Width { get; private set; } + + public bool AllowSort { get; private set; } + public Func SortGroup { get; private set; } + public Func SortMessages { get; private set; } + + public Func TextGetter { get; private set; } + + public bool HasToolTip { get; private set; } + public Func ToolTipGetter { get; private set; } + + public ColumnInfo(string header, int width, Func textGetter) + { + Header = header; + Width = width; + TextGetter = textGetter; + } + + /// + /// Enables sorting for column. If sort functions are null they will use default sort from + /// + /// + /// + /// + public void AddSort(Func sortGroup, Func sortMessages) + { + AllowSort = true; + SortGroup = sortGroup ?? GroupSorter.DefaultGroupSort; + SortMessages = sortMessages ?? GroupSorter.DefaultMessageSort; + } + + /// + /// Enables sorting for column. If sort functions are null they will use default sort from + /// Uses member getting to sort via that member + /// + /// + /// + /// + public void AddSort(Func groupGetter, Func messageGetter) where T : IComparable + { + Func sortGroup = groupGetter != null + ? (x, y) => GroupSorter.Compare(x, y, groupGetter) + : null; + + Func sortMessages = messageGetter != null + ? (x, y) => GroupSorter.Compare(x, y, messageGetter) + : null; + + AddSort(sortGroup, sortMessages); + } + + public void AddToolTip(Func getter) + { + HasToolTip = true; + ToolTipGetter = getter; + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/ColumnInfo.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Table/ColumnInfo.cs.meta new file mode 100644 index 000000000..fa911940f --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/ColumnInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b017b2728f784842bc8f62460ab9fbe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/EmptyRow.cs b/Assets/FishNet/Runtime/Editor/Profiling/Table/EmptyRow.cs new file mode 100644 index 000000000..d199e5b72 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/EmptyRow.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine.UIElements; + +namespace Fishnet.NetworkProfiler.ModuleGUI.UITable +{ + internal class EmptyRow : Row + { + public EmptyRow(Table table, Row previous = null) : base(table, previous) { } + + public override Label GetLabel(ColumnInfo column) + { + throw new NotSupportedException("Empty row does not have any columns"); + } + + public override IEnumerable GetChildren() + { + return Enumerable.Empty(); + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/EmptyRow.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Table/EmptyRow.cs.meta new file mode 100644 index 000000000..b04d5eda2 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/EmptyRow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8129d33c5f799524fa10e84110131e69 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/ITableSorter.cs b/Assets/FishNet/Runtime/Editor/Profiling/Table/ITableSorter.cs new file mode 100644 index 000000000..1aa026870 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/ITableSorter.cs @@ -0,0 +1,7 @@ +namespace Fishnet.NetworkProfiler.ModuleGUI.UITable +{ + internal interface ITableSorter + { + void Sort(Table table, SortHeader header); + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/ITableSorter.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Table/ITableSorter.cs.meta new file mode 100644 index 000000000..ed3d524e5 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/ITableSorter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 05c177a4f382419409a2f55de20e046c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/LabelRow.cs b/Assets/FishNet/Runtime/Editor/Profiling/Table/LabelRow.cs new file mode 100644 index 000000000..3bb73e42d --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/LabelRow.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Fishnet.NetworkProfiler.ModuleGUI.UITable +{ + internal class LabelRow : Row + { + private readonly Dictionary _elements = new Dictionary(); + + public LabelRow(Table table, Row previous = null) : base(table, previous) + { + foreach (var header in table.HeaderInfo) + { + var label = CreateLabel(header); + VisualElement.Add(label); + _elements[header] = label; + } + } + + protected virtual Label CreateLabel(ColumnInfo column) + { + var label = new Label(); + SetLabelStyle(column, label); + return label; + } + + protected static void SetLabelStyle(ColumnInfo column, Label label) + { + var style = label.style; + style.width = column.Width; + + style.paddingLeft = 5; + style.paddingRight = 5; + style.paddingTop = 5; + style.paddingBottom = 5; + style.borderRightColor = Color.white * .4f; + style.borderBottomColor = Color.white * .4f; + style.borderBottomWidth = 1; + style.borderRightWidth = 2; + + style.overflow = Overflow.Hidden; + style.textOverflow = TextOverflow.Ellipsis; + } + + public override Label GetLabel(ColumnInfo column) + { + return _elements[column]; + } + + public override IEnumerable GetChildren() + { + return _elements.Values; + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/LabelRow.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Table/LabelRow.cs.meta new file mode 100644 index 000000000..7cc1ace4d --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/LabelRow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e463bd968d1bb0e4cbae5efd09ed24fc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/Row.cs b/Assets/FishNet/Runtime/Editor/Profiling/Table/Row.cs new file mode 100644 index 000000000..12a26579a --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/Row.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using UnityEngine.UIElements; + +namespace Fishnet.NetworkProfiler.ModuleGUI.UITable +{ + internal abstract class Row + { + public Table Table { get; } + public VisualElement VisualElement { get; } + + public Row(Table table, Row previous = null) + { + Table = table; + + VisualElement = new VisualElement(); + VisualElement.style.flexDirection = FlexDirection.Row; + + var parent = table.ScrollView; + if (previous != null) + { + var index = parent.IndexOf(previous.VisualElement); + // insert after previous + parent.Insert(index + 1, VisualElement); + } + else + { + // just add at end + parent.Add(VisualElement); + } + } + + public abstract Label GetLabel(ColumnInfo column); + public abstract IEnumerable GetChildren(); + + public void SetText(ColumnInfo column, object obj) + { + SetText(column, obj.ToString()); + } + public void SetText(ColumnInfo column, string text) + { + var label = GetLabel(column); + label.text = text; + label.tooltip = text; + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/Row.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Table/Row.cs.meta new file mode 100644 index 000000000..98b930f5b --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/Row.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ac8d50f233b70db42a649ca6b7d69010 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/SortHeader.cs b/Assets/FishNet/Runtime/Editor/Profiling/Table/SortHeader.cs new file mode 100644 index 000000000..8f6e7dbb3 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/SortHeader.cs @@ -0,0 +1,63 @@ +using UnityEngine.UIElements; + +namespace Fishnet.NetworkProfiler.ModuleGUI.UITable +{ + internal class SortHeader : Label + { + public const string ARROW_UP = "\u2191"; + public const string ARROW_DOWN = "\u2193"; + + public SortMode SortMode; + + private readonly SortHeaderRow _row; + + private string _nameWithoutSort; + public string NameWithoutSort + { + get + { + // this get should be called before any modifications are made + // so lazy property should be safe here + if (_nameWithoutSort == null) + _nameWithoutSort = text; + + return _nameWithoutSort; + } + } + + public ColumnInfo Info { get; internal set; } + + public SortHeader(SortHeaderRow row) : base() + { + _row = row; + this.AddManipulator(new Clickable(OnClick)); + } + + private void OnClick(EventBase evt) + { + // just flip between Accending and Descending, no going back to none + if (SortMode == SortMode.Accending) + SortMode = SortMode.Descending; + else + SortMode = SortMode.Accending; + + _row.ApplySort(this); + } + + public void UpdateName() + { + switch (SortMode) + { + case SortMode.None: + text = NameWithoutSort; + break; + case SortMode.Accending: + text = ARROW_UP + NameWithoutSort; + break; + case SortMode.Descending: + text = ARROW_DOWN + NameWithoutSort; + break; + } + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/SortHeader.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Table/SortHeader.cs.meta new file mode 100644 index 000000000..913308b1d --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/SortHeader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ab266974aca81aa4abbaefba1e934073 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/SortHeaderRow.cs b/Assets/FishNet/Runtime/Editor/Profiling/Table/SortHeaderRow.cs new file mode 100644 index 000000000..23329ad07 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/SortHeaderRow.cs @@ -0,0 +1,54 @@ +using System; +using UnityEngine.UIElements; + +namespace Fishnet.NetworkProfiler.ModuleGUI.UITable +{ + internal class SortHeaderRow : LabelRow + { + private readonly ITableSorter _sorter; + private SortHeader _currentSort; + + public SortHeaderRow(Table table, ITableSorter sorter) : base(table, null) + { + _sorter = sorter ?? throw new ArgumentNullException(nameof(sorter)); + } + + protected override Label CreateLabel(ColumnInfo column) + { + var label = column.AllowSort + ? new SortHeader(this) + : new Label(); + + SetLabelStyle(column, label); + return label; + } + + /// + /// Update names of header based on sort + /// + /// + public void SetSortHeader(SortHeader sortHeader) + { + // not null or current + if (_currentSort != null && _currentSort != sortHeader) + { + _currentSort.SortMode = SortMode.None; + _currentSort.UpdateName(); + } + + _currentSort = sortHeader; + _currentSort.UpdateName(); + } + + /// + /// Updates names and applies sort to table + /// + /// + public void ApplySort(SortHeader sortHeader) + { + SetSortHeader(sortHeader); + + _sorter.Sort(Table, sortHeader); + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/SortHeaderRow.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Table/SortHeaderRow.cs.meta new file mode 100644 index 000000000..70290d53f --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/SortHeaderRow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4b4301d9972553c4ab486e9b8915f784 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/SortMode.cs b/Assets/FishNet/Runtime/Editor/Profiling/Table/SortMode.cs new file mode 100644 index 000000000..40ebabf95 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/SortMode.cs @@ -0,0 +1,9 @@ +namespace Fishnet.NetworkProfiler.ModuleGUI.UITable +{ + internal enum SortMode + { + None, + Accending, + Descending, + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/SortMode.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Table/SortMode.cs.meta new file mode 100644 index 000000000..8f77f1cd6 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/SortMode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f0d80f37bc0b1a44cbad7667bba04c79 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/Table.cs b/Assets/FishNet/Runtime/Editor/Profiling/Table/Table.cs new file mode 100644 index 000000000..3da599ce1 --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/Table.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Fishnet.NetworkProfiler.ModuleGUI.UITable +{ + internal class Table + { + public readonly VisualElement VisualElement; + public readonly ScrollView ScrollView; + + public readonly SortHeaderRow Header; + public readonly List Rows = new List(); + public readonly IReadOnlyList HeaderInfo; + + public bool ContainsEmptyRows { get; private set; } + + public Table(IEnumerable columns, ITableSorter sorter) + { + // create readonly list from given Enumerable + HeaderInfo = new List(columns); + + // create table root and scroll view + // root can be Horizontal scroll + // Horizontal will also move header, but Vertical keeps header at top + VisualElement = new ScrollView(ScrollViewMode.Horizontal); + // seperate root for contents, so that Horizontal from scroll isn't applied + var root = new VisualElement(); + VisualElement.Add(root); + // using VerticalAndHorizontal fixes header being squashed, not sure why, or if it'll cause future problems + ScrollView = new ScrollView(ScrollViewMode.VerticalAndHorizontal); + + // add header to table root + // header will initialize labels, but we need to set text + Header = new SortHeaderRow(this, sorter); + + // add header and scroll to root + root.Add(Header.VisualElement); + root.Add(ScrollView); + + // add headers + foreach (var c in columns) + { + var ele = Header.GetLabel(c); + ele.text = c.Header; + + if (c.AllowSort) + { + var sortHeader = (SortHeader)ele; + sortHeader.Info = c; + } + + // make header element thicker + var eleStyle = ele.style; + eleStyle.unityFontStyleAndWeight = FontStyle.Bold; + eleStyle.borderBottomWidth = 3; + eleStyle.borderRightWidth = 3; + } + } + + public Row AddRow(Row previous = null) + { + var row = new LabelRow(this, previous); + Rows.Add(row); + return row; + } + + public Row AddEmptyRow(Row previous = null) + { + var row = new EmptyRow(this, previous); + ContainsEmptyRows = true; + Rows.Add(row); + return row; + } + + public void ChangeWidth(ColumnInfo column, int newWidth, bool setVisibility) + { + foreach (var row in Rows) + { + if (row is EmptyRow) + continue; + + var label = row.GetLabel(column); + var style = label.style; + style.width = newWidth; + + if (setVisibility) + style.display = newWidth > 0 ? DisplayStyle.Flex : DisplayStyle.None; + } + } + + /// + /// Removes all rows expect header + /// + public void Clear() + { + ScrollView.Clear(); + Rows.Clear(); + Rows.Add(Header); + ContainsEmptyRows = false; + } + + /// + /// Updates names of sort header + /// + public void SetSortHeader(ColumnInfo info, SortMode mode) + { + var sortBy = (SortHeader)Header.GetLabel(info); + sortBy.SortMode = mode; + Header.SetSortHeader(sortBy); + } + } +} diff --git a/Assets/FishNet/Runtime/Editor/Profiling/Table/Table.cs.meta b/Assets/FishNet/Runtime/Editor/Profiling/Table/Table.cs.meta new file mode 100644 index 000000000..46d81dd7d --- /dev/null +++ b/Assets/FishNet/Runtime/Editor/Profiling/Table/Table.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2a7346de93910944c965b68a39d93082 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/FishNet.Runtime.asmdef b/Assets/FishNet/Runtime/FishNet.Runtime.asmdef index 73a8b851e..898636b60 100644 --- a/Assets/FishNet/Runtime/FishNet.Runtime.asmdef +++ b/Assets/FishNet/Runtime/FishNet.Runtime.asmdef @@ -1,8 +1,10 @@ { "name": "FishNet.Runtime", + "rootNamespace": "", "references": [ "GUID:894a6cc6ed5cd2645bb542978cbed6a9", - "GUID:17013b2a21298c14ea4808251346a38a" + "GUID:17013b2a21298c14ea4808251346a38a", + "GUID:b46779583a009f04ba9f5f31d0e7e6ac" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs b/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs index cdd172db1..94a710b21 100644 --- a/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs +++ b/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs @@ -46,6 +46,10 @@ public sealed partial class ClientManager : MonoBehaviour /// public event Action OnConnectedClients; /// + /// Called when the client processes a packet. + /// + public event Action OnPacketRead; + /// /// True if the client connection is connected to the server. /// public bool Started { get; private set; } @@ -152,6 +156,7 @@ internal void InitializeOnce_Internal(NetworkManager manager) NetworkManager = manager; Objects = new ClientObjects(manager); Objects.SubscribeToSceneLoaded(true); + Objects.OnPacketRead += (args) => OnPacketRead?.Invoke(args); /* Unsubscribe before subscribing. * Shouldn't be an issue but better safe than sorry. */ SubscribeToEvents(false); @@ -400,6 +405,7 @@ internal void ParseReader(PooledReader reader, Channel channel, bool print = fal reader.Initialize(NetworkManager.TransportManager.ProcessIntermediateIncoming(fullMessage, true), NetworkManager, dataSource); else reader.Initialize(fullMessage, NetworkManager, dataSource); + OnPacketRead?.Invoke(new PacketProcessingArgs(null, -1, PacketId.Split, expectedMessages*1500)); } //Not split. else diff --git a/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs b/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs index bc0a96d64..cdc8a6da9 100644 --- a/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs +++ b/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs @@ -32,6 +32,11 @@ public partial class ClientObjects : ManagedObjects /// NetworkObjects which are currently active on the local client. /// internal List LocalClientSpawned = new List(); + + /// + /// Called when the client processes a packet. + /// + public event Action OnPacketRead; #endregion #region Private. @@ -328,6 +333,7 @@ internal void ParseSyncType(PooledReader reader, bool isSyncObject, Channel chan if (nb != null) { + OnPacketRead?.Invoke(new PacketProcessingArgs(nb, (int)reader.PeekByte(), (PacketId)packetId, dataLength)); /* Length of data to be read for syncvars. * This is important because syncvars are never * a set length and data must be read through completion. @@ -369,7 +375,10 @@ internal void ParseReconcileRpc(PooledReader reader, Channel channel) int dataLength = Packets.GetPacketLength((ushort)PacketId.Reconcile, reader, channel); if (nb != null) + { + OnPacketRead?.Invoke(new PacketProcessingArgs(nb, (int)nb.PeekRpcHash(reader), PacketId.Reconcile, dataLength)); nb.OnReconcileRpc(null, reader, channel); + } else SkipDataLength((ushort)PacketId.ObserversRpc, reader, dataLength); } @@ -385,7 +394,10 @@ internal void ParseObserversRpc(PooledReader reader, Channel channel) int dataLength = Packets.GetPacketLength((ushort)PacketId.ObserversRpc, reader, channel); if (nb != null) + { + OnPacketRead?.Invoke(new PacketProcessingArgs(nb, (int)nb.PeekRpcHash(reader), PacketId.ObserversRpc, dataLength)); nb.OnObserversRpc(null, reader, channel); + } else SkipDataLength((ushort)PacketId.ObserversRpc, reader, dataLength); } @@ -400,7 +412,10 @@ internal void ParseTargetRpc(PooledReader reader, Channel channel) int dataLength = Packets.GetPacketLength((ushort)PacketId.TargetRpc, reader, channel); if (nb != null) + { + OnPacketRead?.Invoke(new PacketProcessingArgs(nb, (int)nb.PeekRpcHash(reader), PacketId.TargetRpc, dataLength)); nb.OnTargetRpc(null, reader, channel); + } else SkipDataLength((ushort)PacketId.TargetRpc, reader, dataLength); } diff --git a/Assets/FishNet/Runtime/Object/NetworkBehaviour.RPCs.cs b/Assets/FishNet/Runtime/Object/NetworkBehaviour.RPCs.cs index 995ff30b1..27b363e10 100644 --- a/Assets/FishNet/Runtime/Object/NetworkBehaviour.RPCs.cs +++ b/Assets/FishNet/Runtime/Object/NetworkBehaviour.RPCs.cs @@ -165,6 +165,19 @@ public void ClearBuffedRpcs() bRpc.Writer.Store(); _bufferedRpcs.Clear(); } + + /// + /// Reads a RPC hash. + /// + /// + /// + internal uint PeekRpcHash(PooledReader reader) + { + if (_rpcHashSize == 1) + return reader.PeekByte(); + else + return reader.PeekUInt16(); + } /// /// Reads a RPC hash. diff --git a/Assets/FishNet/Runtime/Serializing/Reader.cs b/Assets/FishNet/Runtime/Serializing/Reader.cs index 077d83acd..131e18dd1 100644 --- a/Assets/FishNet/Runtime/Serializing/Reader.cs +++ b/Assets/FishNet/Runtime/Serializing/Reader.cs @@ -266,6 +266,20 @@ internal byte PeekByte() { return _buffer[Position]; } + + /// + /// Returns the next uint16 to be read. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ushort PeekUInt16() + { + ushort result = 0; + result |= _buffer[Position]; + result |= (ushort)(_buffer[Position+1] << 8); + + return result; + } /// /// Skips a number of bytes in the reader. diff --git a/Assets/FishNet/Runtime/Transporting/EventStructures.cs b/Assets/FishNet/Runtime/Transporting/EventStructures.cs index bbbfc83ed..cab273f0f 100644 --- a/Assets/FishNet/Runtime/Transporting/EventStructures.cs +++ b/Assets/FishNet/Runtime/Transporting/EventStructures.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using FishNet.Object; namespace FishNet.Transporting { @@ -95,6 +96,28 @@ public ClientReceivedDataArgs(ArraySegment data, Channel channel, int tran TransportIndex = transportIndex; } } + + /// + /// Container about data received on the local client. + /// + public struct PacketProcessingArgs + { + public NetworkBehaviour NetworkBehaviour; + + public PacketId PacketId; + + public int DataLength; + + public int PropertyHash; + + public PacketProcessingArgs(NetworkBehaviour nb, int propertyHash, PacketId packetId, int dataLength) + { + NetworkBehaviour = nb; + PacketId = packetId; + DataLength = dataLength; + PropertyHash = propertyHash; + } + } diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling.meta b/Assets/FishNet/Runtime/Utility/Performance/Profiling.meta new file mode 100644 index 000000000..ca902e0fb --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 084aef25b51176a43978f1cfe92272db +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling/CountRecorder.cs b/Assets/FishNet/Runtime/Utility/Performance/Profiling/CountRecorder.cs new file mode 100644 index 000000000..2e3954e45 --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling/CountRecorder.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using FishNet.Transporting; +using Unity.Profiling; +using UnityEngine; + +namespace FishNet.Utility.Performance.Profiling +{ + internal class CountRecorder + { + private readonly ProfilerCounter _profilerCount; + private readonly ProfilerCounter _profilerBytes; + private readonly ProfilerCounter _profilerPerSecond; + private readonly object _instance; + //private readonly INetworkInfoProvider _provider; + internal readonly Frames _frames; + private int _count; + private int _bytes; + private int _perSecond; + private readonly Queue<(float time, int bytes)> _perSecondQueue = new Queue<(float time, int bytes)>(); + private int _frameIndex = -1; + + public CountRecorder(object instance, /*INetworkInfoProvider provider,*/ ProfilerCounter profilerCount, ProfilerCounter profilerBytes, ProfilerCounter profilerPerSecond) + { + //_provider = provider; + _instance = instance; + _profilerCount = profilerCount; + _profilerBytes = profilerBytes; + _profilerPerSecond = profilerPerSecond; + _frames = new Frames(); + } + + public void OnMessage(PacketProcessingArgs args) + { + + // dont record anything if frame index is -1 + // this normally means the profiler has not been activated yet + if (_frameIndex == -1) + return; + + _count ++; + _bytes += args.DataLength; + + var frame = _frames.GetFrame(_frameIndex); + frame.Messages.Add( + new PacketInfo( + args, + frame.Messages.Count)); + frame.Bytes+= args.DataLength; + } + + public void EndFrame(int frameIndex) + { + CaclulatePerSecond(Time.time, _bytes); + _profilerCount.Sample(_count); + _profilerBytes.Sample(_bytes); + _count = 0; + _bytes = 0; + + // +1 so that we set next frame + // otherwise we clear the frame that the savedata wants to grab + _frameIndex = frameIndex + 1; + var frame = _frames.GetFrame(_frameIndex); + frame.Messages.Clear(); + frame.Bytes = 0; + } + + private void CaclulatePerSecond(float now, int bytes) + { + // add new values to sum + _perSecond += bytes; + _perSecondQueue.Enqueue((now, bytes)); + + // remove old bytes from sum + var removeTime = now - 1; + while (_perSecondQueue.Peek().time < removeTime) + { + var removed = _perSecondQueue.Dequeue(); + _perSecond -= removed.bytes; + } + + // record sample after adding/removing value + _profilerPerSecond.Sample(_perSecond); + } + } +} diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling/CountRecorder.cs.meta b/Assets/FishNet/Runtime/Utility/Performance/Profiling/CountRecorder.cs.meta new file mode 100644 index 000000000..dd5874983 --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling/CountRecorder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 16575ee3be2c5e046af4438787d215f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling/Counters.cs b/Assets/FishNet/Runtime/Utility/Performance/Profiling/Counters.cs new file mode 100644 index 000000000..aecffbd05 --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling/Counters.cs @@ -0,0 +1,25 @@ +using Fishnet.NetworkProfiler; +using Unity.Profiling; + +namespace FishNet.Utility.Performance.Profiling +{ + internal static class Counters + { + public static readonly ProfilerCategory Category = ProfilerCategory.Network; + private const ProfilerMarkerDataUnit COUNT = ProfilerMarkerDataUnit.Count; + private const ProfilerMarkerDataUnit BYTES = ProfilerMarkerDataUnit.Bytes; + + public static readonly ProfilerCounter AllPlayersCount = new ProfilerCounter(Category, Names.PLAYER_COUNT, COUNT); + public static readonly ProfilerCounter AuthenticatedPlayersCount = new ProfilerCounter(Category, Names.AUTHENTICATED_COUNT, COUNT); + public static readonly ProfilerCounter CharacterCount = new ProfilerCounter(Category, Names.CHARACTER_COUNT, COUNT); + public static readonly ProfilerCounter ObjectCount = new ProfilerCounter(Category, Names.OBJECT_COUNT, COUNT); + + public static readonly ProfilerCounter SentCount = new ProfilerCounter(Category, Names.SENT_COUNT, COUNT); + public static readonly ProfilerCounter SentBytes = new ProfilerCounter(Category, Names.SENT_BYTES, BYTES); + public static readonly ProfilerCounter SentPerSecond = new ProfilerCounter(Category, Names.SENT_PER_SECOND, BYTES); + + public static readonly ProfilerCounter ReceiveCount = new ProfilerCounter(Category, Names.RECEIVED_COUNT, COUNT); + public static readonly ProfilerCounter ReceiveBytes = new ProfilerCounter(Category, Names.RECEIVED_BYTES, BYTES); + public static readonly ProfilerCounter ReceivePerSecond = new ProfilerCounter(Category, Names.RECEIVED_PER_SECOND, BYTES); + } +} diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling/Counters.cs.meta b/Assets/FishNet/Runtime/Utility/Performance/Profiling/Counters.cs.meta new file mode 100644 index 000000000..325bf4506 --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling/Counters.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 40d8d596129601f4d90f0664cb2557c4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling/Frame.cs b/Assets/FishNet/Runtime/Utility/Performance/Profiling/Frame.cs new file mode 100644 index 000000000..da0be4c95 --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling/Frame.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace FishNet.Utility.Performance.Profiling +{ + [System.Serializable] + public class Frames : IEnumerable + { + [SerializeField] + private Frame[] _frames; + + public Frames() + { + _frames = new Frame[NetworkProfilerRecorder.FRAME_COUNT]; + for (var i = 0; i < _frames.Length; i++) + _frames[i] = new Frame(); + } + + public Frame GetFrame(int frameIndex) + { + return _frames[frameIndex % _frames.Length]; + } + + public IEnumerator GetEnumerator() => ((IEnumerable)_frames).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_frames).GetEnumerator(); + + internal void ValidateSize() + { + if (_frames.Length != NetworkProfilerRecorder.FRAME_COUNT) + { + Array.Resize(ref _frames, NetworkProfilerRecorder.FRAME_COUNT); + } + } + } + + [System.Serializable] + public class Frame + { + public List Messages = new List(); + public int Bytes; + } +} diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling/Frame.cs.meta b/Assets/FishNet/Runtime/Utility/Performance/Profiling/Frame.cs.meta new file mode 100644 index 000000000..cbe424201 --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling/Frame.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b59754fd175ff2345a6788891dddbfcf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling/Names.cs b/Assets/FishNet/Runtime/Utility/Performance/Profiling/Names.cs new file mode 100644 index 000000000..6f849d910 --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling/Names.cs @@ -0,0 +1,25 @@ +namespace FishNet.Utility.Performance.Profiling +{ + public static class Names + { + public const string PLAYER_COUNT = "All Players Count"; + public const string PLAYER_COUNT_TOOLTIP = "Number of players connected to the server"; + public const string AUTHENTICATED_COUNT = "Authenticated Players Count"; + public const string AUTHENTICATED_COUNT_TOOLTIP = "Number of Authenticated players connected to the server, This should be same as total players most of the time"; + public const string CHARACTER_COUNT = "Character Count"; + public const string CHARACTER_COUNT_TOOLTIP = "Number of players with spawned GameObjects"; + + public const string OBJECT_COUNT = "Object Count"; + public const string OBJECT_COUNT_TOOLTIP = "Number of NetworkObjects spawned"; + + public const string SENT_COUNT = "Sent Messages"; + public const string SENT_BYTES = "Sent Bytes"; + public const string SENT_PER_SECOND = "Sent Per Second"; + + public const string RECEIVED_COUNT = "Received Messages"; + public const string RECEIVED_BYTES = "Received Bytes"; + public const string RECEIVED_PER_SECOND = "Received Per Second"; + + public const string PER_SECOND_TOOLTIP = "Sum of Bytes over the previous second"; + } +} diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling/Names.cs.meta b/Assets/FishNet/Runtime/Utility/Performance/Profiling/Names.cs.meta new file mode 100644 index 000000000..3e12e8598 --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling/Names.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 05fd6c7133d9b2e42a8f282465a445d8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling/NetworkProfilerRecorder.cs b/Assets/FishNet/Runtime/Utility/Performance/Profiling/NetworkProfilerRecorder.cs new file mode 100644 index 000000000..7e4768b1b --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling/NetworkProfilerRecorder.cs @@ -0,0 +1,181 @@ +using UnityEngine; +using System.Linq; +using FishNet.Managing.Client; +using FishNet.Managing.Server; +using FishNet.Transporting; + + +#if UNITY_EDITOR +using UnityEditorInternal; +#endif + +namespace FishNet.Utility.Performance.Profiling +{ + [DefaultExecutionOrder(int.MaxValue)] // last + public class NetworkProfilerRecorder : MonoBehaviour + { + // singleton because unity only has 1 profiler + public static NetworkProfilerRecorder Instance { get; private set; } + + public ClientManager Client; + public ServerManager Server; + + internal static CountRecorder _sentCounter; + internal static CountRecorder _receivedCounter; + internal const int FRAME_COUNT = 300; // todo find a way to get real frame count + + public delegate void FrameUpdate(int tick); + public static event FrameUpdate AfterSample; + + private int _lastProcessedFrame = -1; + + private void Start() + { +#if UNITY_EDITOR + _lastProcessedFrame = ProfilerDriver.lastFrameIndex; +#endif + + Debug.Assert(Instance == null); + Instance = this; + DontDestroyOnLoad(this); + + // if (Server != null) + // { + // Server.; + // Server.Stopped.AddListener(ServerStopped); + // } + if (Client == null) + { + Client = InstanceFinder.ClientManager; + } + + if (Client != null) + { + Client.OnClientConnectionState += ClientConnectionStateChanged; + } + } + + private void OnDestroy() + { + //if (_receivedCounter != null) + // NetworkDiagnostics.InMessageEvent -= _receivedCounter.OnMessage; + //if (_sentCounter != null) + // NetworkDiagnostics.OutMessageEvent -= _sentCounter.OnMessage; + + Debug.Assert(Instance == this); + Instance = null; + } + + private void ServerStarted() + { + //if (instance != null) + //{ + // logger.LogWarning($"Already started profiler for different Instance:{instance}"); + // return; + //} + //instance = Server; + + //var provider = new NetworkInfoProvider(Server.World); + //_sentCounter = new CountRecorder(Server, provider, Counters.SentCount, Counters.SentBytes, Counters.SentPerSecond); + //_receivedCounter = new CountRecorder(Server, provider, Counters.ReceiveCount, Counters.ReceiveBytes, Counters.ReceivePerSecond); + //NetworkDiagnostics.InMessageEvent += _receivedCounter.OnMessage; + //NetworkDiagnostics.OutMessageEvent += _sentCounter.OnMessage; + } + + private void ClientConnectionStateChanged(ClientConnectionStateArgs args) + { + switch (args.ConnectionState) + { + case LocalConnectionState.Started: + ClientStarted(); + break; + case LocalConnectionState.Stopped: + ClientStopped(); + break; + } + } + + private void ClientStarted() + { + _sentCounter = new CountRecorder(Client, Counters.ReceiveCount, Counters.ReceiveBytes, Counters.ReceivePerSecond); + _receivedCounter = new CountRecorder(Client, Counters.ReceiveCount, Counters.ReceiveBytes, Counters.ReceivePerSecond); + Client.OnPacketRead += _receivedCounter.OnMessage; + //NetworkDiagnostics.OutMessageEvent += _sentCounter.OnMessage; + } + + private void ServerStopped() + { + //if (instance == (object)Server) + // instance = null; + } + + private void ClientStopped(/*ClientStoppedReason _*/) + { + _receivedCounter = null; + _sentCounter = null; + } + + private void LateUpdate() + { +#if UNITY_EDITOR + if (!ProfilerDriver.enabled) + return; + + // once a frame, ever frame, no matter what lastFrameIndex is + SampleCounts(); + + // unity sometimes skips a profiler frame, because unity + // so we have to check if that happens and then sample the missing frame + while (_lastProcessedFrame < ProfilerDriver.lastFrameIndex) + { + _lastProcessedFrame++; + + //Debug.Log($"Sample: [LateUpdate, enabled { ProfilerDriver.enabled}, first {ProfilerDriver.firstFrameIndex}, last {ProfilerDriver.lastFrameIndex}, lastProcessed {lastProcessedFrame}]"); + + var lastFrame = _lastProcessedFrame; + // not sure why frame is offset, but +2 fixes it + SampleMessages(lastFrame + 2); + } +#else + // in player, just use ProfilerCounter (frameCount only used by messages) + SampleCounts(); + SampleMessages(0); +#endif + } + + /// + /// call this every frame to sample number of players and objects + /// + private void SampleCounts() + { + if (Client != null) + { + Counters.AllPlayersCount.Sample(Client.Clients.Count); + Counters.ObjectCount.Sample(Client.Objects.Spawned.Count); + } else if (Server != null) + { + Counters.AllPlayersCount.Sample(Server.Clients.Count); + Counters.ObjectCount.Sample(Server.Objects.Spawned.Count); + } + } + + /// + /// call this when ProfilerDriver shows it is next frame + /// + /// + private void SampleMessages(int frame) + { + + if (_sentCounter != null) + { + _sentCounter.EndFrame(frame); + } + + if (_receivedCounter != null) + { + _receivedCounter.EndFrame(frame); + } + AfterSample?.Invoke(frame); + } + } +} diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling/NetworkProfilerRecorder.cs.meta b/Assets/FishNet/Runtime/Utility/Performance/Profiling/NetworkProfilerRecorder.cs.meta new file mode 100644 index 000000000..83dd31721 --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling/NetworkProfilerRecorder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bc5b42cbfc38b1c4d86fd25905b19e29 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling/PacketInfo.cs b/Assets/FishNet/Runtime/Utility/Performance/Profiling/PacketInfo.cs new file mode 100644 index 000000000..ec95a08a7 --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling/PacketInfo.cs @@ -0,0 +1,42 @@ +using FishNet.Transporting; +using UnityEngine; + +namespace FishNet.Utility.Performance.Profiling +{ + [System.Serializable] + public class PacketInfo + { + /// + /// Order message was sent/received in frame + /// + [SerializeField] private int _order; + [SerializeField] private int _length; + [SerializeField] private int _count; + [SerializeField] private int _packetId; + [SerializeField] private string _objectName; + [SerializeField] private string _rpcName; + + public int Order => _order; + public int Bytes => _length; + public int Count => _count; + public int TotalBytes => Bytes * Count; + public string ObjectName => _objectName; + public string PacketIdName => ((PacketId)_packetId).ToString(); + public string RpcName => _rpcName; + + public PacketInfo(PacketProcessingArgs args, int order) + { + _order = order; + _length = args.DataLength; + _count = 1; + _packetId = (int)args.PacketId; + if (args.NetworkBehaviour != null) + { + _objectName = args.NetworkBehaviour.name; + // var obj = provider.GetNetworkIdentity(id); + // _objectName = obj != null ? obj.name : null; + _rpcName = PacketInfoProvider.GetPropertyName(args.NetworkBehaviour, args.PropertyHash, args.PacketId); + } + } + } +} diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling/PacketInfo.cs.meta b/Assets/FishNet/Runtime/Utility/Performance/Profiling/PacketInfo.cs.meta new file mode 100644 index 000000000..2d173f0d9 --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling/PacketInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 668dc7c434784214794807fcfecfe185 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling/PacketInfoProvider.cs b/Assets/FishNet/Runtime/Utility/Performance/Profiling/PacketInfoProvider.cs new file mode 100644 index 000000000..95d4820a8 --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling/PacketInfoProvider.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using FishNet.Object; +using FishNet.Object.Prediction; +using FishNet.Object.Synchronizing; +using FishNet.Transporting; + +namespace FishNet.Utility.Performance.Profiling +{ + + public static class PacketInfoProvider + { + private static Dictionary> SyncTypeNamesCache = new(); + private static Dictionary> RpcNamesCache = new(); + + public static string GetPropertyName(NetworkBehaviour nb, int propertyHash, PacketId packetId) + { + string res = ""; + switch (packetId) + { + case PacketId.SyncObject: + case PacketId.SyncVar: + if (SyncTypeNamesCache.TryGetValue(nb.GetType(), out var syncTypeNames)) + { + if (propertyHash < syncTypeNames.Count) + { + res = syncTypeNames[propertyHash]; + } + } + else + { + List typeNamesList = new(); + int syncTypeCount = 0; + foreach (var fieldInfo in nb.GetType().GetFields()) + { + foreach (var customAttribute in fieldInfo.CustomAttributes) + { + if (customAttribute.AttributeType.FullName == typeof(SyncVarAttribute).FullName || + customAttribute.AttributeType.FullName == typeof(SyncObjectAttribute).FullName) + { + if (syncTypeCount == propertyHash) + { + res = fieldInfo.Name; + } + syncTypeCount++; + typeNamesList.Add(fieldInfo.Name); + } + } + } + + SyncTypeNamesCache[nb.GetType()] = typeNamesList; + } + break; + case PacketId.Reconcile: + case PacketId.ObserversRpc: + case PacketId.TargetRpc: + if (RpcNamesCache.TryGetValue(nb.GetType(), out var rpcNames)) + { + if (propertyHash < rpcNames.Count) + { + res = rpcNames[propertyHash]; + } + } + else + { + List rpcNamesList = new(); + int rpcCount = 0; + // Iterate the replicates first, that's what the codegen does + foreach (var methodInfo in nb.GetType().GetMethods()) + { + foreach (var customAttribute in methodInfo.CustomAttributes) + { + if (customAttribute.AttributeType.FullName == typeof(ReplicateAttribute).FullName) + { + rpcNamesList.Add(methodInfo.Name); + rpcCount++; + } + } + } + foreach (var methodInfo in nb.GetType().GetMethods()) + { + foreach (var customAttribute in methodInfo.CustomAttributes) + { + if (customAttribute.AttributeType.FullName == typeof(ObserversRpcAttribute).FullName || + customAttribute.AttributeType.FullName == typeof(TargetRpcAttribute).FullName || + customAttribute.AttributeType.FullName == typeof(ReconcileAttribute).FullName) + { + if (rpcCount == propertyHash) + { + res = methodInfo.Name; + break; + } + rpcCount++; + rpcNamesList.Add(methodInfo.Name); + } + } + } + + RpcNamesCache[nb.GetType()] = rpcNamesList; + } + break; + } + + return res; + } + } +} diff --git a/Assets/FishNet/Runtime/Utility/Performance/Profiling/PacketInfoProvider.cs.meta b/Assets/FishNet/Runtime/Utility/Performance/Profiling/PacketInfoProvider.cs.meta new file mode 100644 index 000000000..ea174bf95 --- /dev/null +++ b/Assets/FishNet/Runtime/Utility/Performance/Profiling/PacketInfoProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8192a6dbec7e3b446bea79c4b0f172be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/VERSION.txt b/Assets/FishNet/VERSION.txt index 03d4bc90f..4dcb6e64e 100644 --- a/Assets/FishNet/VERSION.txt +++ b/Assets/FishNet/VERSION.txt @@ -1 +1 @@ -3.11.18 \ No newline at end of file +3.11.19 \ No newline at end of file diff --git a/Assets/FishNet/package.json b/Assets/FishNet/package.json index 1c9baf12c..830b13cb2 100644 --- a/Assets/FishNet/package.json +++ b/Assets/FishNet/package.json @@ -1,6 +1,6 @@ { "name": "com.firstgeargames.fishnet", - "version": "3.11.18", + "version": "3.11.19", "displayName": "FishNet: Networking Evolved", "description": "A feature-rich Unity networking solution aimed towards reliability, ease of use, efficiency, and flexibility.", "unity": "2021.3", @@ -25,6 +25,7 @@ "url": "http://www.firstgeargames.com" }, "dependencies": { - "com.unity.nuget.newtonsoft-json": "3.2.1" + "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.profiling.core": "1.0.2" } } \ No newline at end of file diff --git a/FishNet.sln b/FishNet.sln index 89338b6ce..7813bf63c 100644 --- a/FishNet.sln +++ b/FishNet.sln @@ -1,60 +1,143 @@  -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FishNet.Runtime", "FishNet.Runtime.csproj", "{69D0C159-BAB4-689B-687E-9E213DFB5A44}" +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FishNet.Runtime", "FishNet.Runtime.csproj", "{59c1d069-b4ba-9b68-687e-9e213dfb5a44}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameKit.Utilities", "GameKit.Utilities.csproj", "{13113C11-58E9-2A2E-DF1A-460F73B189CE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityEditor.UI", "UnityEditor.UI.csproj", "{6ff9d838-069f-4b49-5a35-f70072bbf0bf}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FishNet.Demos", "FishNet.Demos.csproj", "{136A7A12-C446-BC06-2B59-E13B4B55C391}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FishNet.Demos", "FishNet.Demos.csproj", "{127a6a13-46c4-06bc-2b59-e13b4b55c391}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly-CSharp.csproj", "{5ED35FB8-7A21-7C15-DB72-386947C92520}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.TestTools.CodeCoverage.Editor", "Unity.TestTools.CodeCoverage.Editor.csproj", "{2fefaaf8-b5d9-71e6-28d0-ea11b956c5ab}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameKit.Dependencies", "GameKit.Dependencies.csproj", "{6D29C1FC-6376-8F6B-216E-88C8C2471B53}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FishNet.Codegen.Cecil", "FishNet.Codegen.Cecil.csproj", "{537888d1-cf8a-a5f4-08f5-be1b0604d38e}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.FishNet.Codegen", "Unity.FishNet.Codegen.csproj", "{486584D8-1B68-74D0-B2EE-6A18C8D7352A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityEditor.TestRunner", "UnityEditor.TestRunner.csproj", "{75207961-aee3-5a76-7646-e1380f658e8a}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "asset-store-tools-editor", "asset-store-tools-editor.csproj", "{B5A117C4-B458-5028-BA98-4F8E4154A101}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityEngine.TestRunner", "UnityEngine.TestRunner.csproj", "{9cb64bdf-5b52-b4fa-5298-d7fd83a65e4b}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FishNet.Codegen.Cecil", "FishNet.Codegen.Cecil.csproj", "{D1887853-8ACF-F4A5-08F5-BE1B0604D38E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityEngine.UI", "UnityEngine.UI.csproj", "{9aa0345b-376b-0323-106e-cc6cb6d71a2d}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Rider.Editor", "Unity.Rider.Editor.csproj", "{a319fe15-1ae6-fe2a-0a9f-052921a7f1a8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Edgegap", "Edgegap.csproj", "{1ad87cc8-ed38-0ca8-9bc0-9e4d761874cc}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.FishNet.Codegen", "Unity.FishNet.Codegen.csproj", "{d8846548-681b-d074-b2ee-6a18c8d7352a}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.VisualStudio.Editor", "Unity.VisualStudio.Editor.csproj", "{531f45d2-6b02-0890-bdab-1a1cfec120fe}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Settings.Editor", "Unity.Settings.Editor.csproj", "{3c2e65d1-71a9-dcce-9ff0-e462821b8936}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.TestTools.CodeCoverage.Editor.OpenCover.Model", "Unity.TestTools.CodeCoverage.Editor.OpenCover.Model.csproj", "{cb069737-b94e-9a1c-359c-e91e2cc14ccf}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.SysrootPackage.Editor", "Unity.SysrootPackage.Editor.csproj", "{d1b151e9-55af-d0be-5c48-e60322e8547e}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Profiling.Core", "Unity.Profiling.Core.csproj", "{11c38925-f5ac-1a7b-d4ea-7d1dbe8333b9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameKit.Utilities", "GameKit.Utilities.csproj", "{113c1113-e958-2e2a-df1a-460f73b189ce}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Performance.Profile-Analyzer.Editor", "Unity.Performance.Profile-Analyzer.Editor.csproj", "{892dd2ad-d637-2009-2a24-a9e22d565f04}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.VSCode.Editor", "Unity.VSCode.Editor.csproj", "{054be889-cbbb-67ac-ee1c-c274bed62ef9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.EditorCoroutines.Editor", "Unity.EditorCoroutines.Editor.csproj", "{2f4a3448-cdaf-4526-5f2b-e6251050fa57}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameKit.Dependencies", "GameKit.Dependencies.csproj", "{fcc1296d-7663-6b8f-216e-88c8c2471b53}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.TestTools.CodeCoverage.Editor.OpenCover.Mono.Reflection", "Unity.TestTools.CodeCoverage.Editor.OpenCover.Mono.Reflection.csproj", "{e9f11526-f647-3906-4076-b9d63956a2f2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Sysroot.Linux_x86_64", "Unity.Sysroot.Linux_x86_64.csproj", "{e3bb3ef4-4fde-ed23-c68f-bde7736f97e7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Toolchain.Win-x86_64-Linux-x86_64", "Unity.Toolchain.Win-x86_64-Linux-x86_64.csproj", "{dcbe98c1-58fc-f809-893a-f5d2f6943897}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly-CSharp.csproj", "{b85fd35e-217a-157c-db72-386947c92520}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityEngine.UI.Tests", "UnityEngine.UI.Tests.csproj", "{67278720-21fb-059d-f163-359f038172c3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityEditor.UI.EditorTests", "UnityEditor.UI.EditorTests.csproj", "{ce465a3d-fd6c-d3ce-4d58-f60566f39213}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Toolchain.Win-x86_64-Linux-x86_64.EditorTests", "Unity.Toolchain.Win-x86_64-Linux-x86_64.EditorTests.csproj", "{96d65cec-822f-7bc1-485f-2689af7c678a}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Sysroot.Linux_x86_64.EditorTests", "Unity.Sysroot.Linux_x86_64.EditorTests.csproj", "{24a55edd-b57b-5260-7140-c808091ae8c0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.TestTools.CodeCoverage.Editor.Compatibility", "Unity.TestTools.CodeCoverage.Editor.Compatibility.csproj", "{d4aff916-632e-ee01-58e3-a5fe1c465dbf}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Settings.Tests", "Unity.Settings.Tests.csproj", "{bf826988-d794-8d15-17c3-30e002b58d3c}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Performance.Profile-Analyzer.EditorTests", "Unity.Performance.Profile-Analyzer.EditorTests.csproj", "{cb503895-a385-b47b-b7f5-142e6fa90c8e}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Sysroot.EditorTests", "Unity.Sysroot.EditorTests.csproj", "{669fd055-17cb-6645-809a-9f6916f3ff2e}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {69D0C159-BAB4-689B-687E-9E213DFB5A44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {69D0C159-BAB4-689B-687E-9E213DFB5A44}.Debug|Any CPU.Build.0 = Debug|Any CPU - {69D0C159-BAB4-689B-687E-9E213DFB5A44}.Release|Any CPU.ActiveCfg = Release|Any CPU - {69D0C159-BAB4-689B-687E-9E213DFB5A44}.Release|Any CPU.Build.0 = Release|Any CPU - {13113C11-58E9-2A2E-DF1A-460F73B189CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {13113C11-58E9-2A2E-DF1A-460F73B189CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {13113C11-58E9-2A2E-DF1A-460F73B189CE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {13113C11-58E9-2A2E-DF1A-460F73B189CE}.Release|Any CPU.Build.0 = Release|Any CPU - {136A7A12-C446-BC06-2B59-E13B4B55C391}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {136A7A12-C446-BC06-2B59-E13B4B55C391}.Debug|Any CPU.Build.0 = Debug|Any CPU - {136A7A12-C446-BC06-2B59-E13B4B55C391}.Release|Any CPU.ActiveCfg = Release|Any CPU - {136A7A12-C446-BC06-2B59-E13B4B55C391}.Release|Any CPU.Build.0 = Release|Any CPU - {5ED35FB8-7A21-7C15-DB72-386947C92520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5ED35FB8-7A21-7C15-DB72-386947C92520}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5ED35FB8-7A21-7C15-DB72-386947C92520}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5ED35FB8-7A21-7C15-DB72-386947C92520}.Release|Any CPU.Build.0 = Release|Any CPU - {6D29C1FC-6376-8F6B-216E-88C8C2471B53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D29C1FC-6376-8F6B-216E-88C8C2471B53}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D29C1FC-6376-8F6B-216E-88C8C2471B53}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D29C1FC-6376-8F6B-216E-88C8C2471B53}.Release|Any CPU.Build.0 = Release|Any CPU - {486584D8-1B68-74D0-B2EE-6A18C8D7352A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {486584D8-1B68-74D0-B2EE-6A18C8D7352A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {486584D8-1B68-74D0-B2EE-6A18C8D7352A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {486584D8-1B68-74D0-B2EE-6A18C8D7352A}.Release|Any CPU.Build.0 = Release|Any CPU - {B5A117C4-B458-5028-BA98-4F8E4154A101}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B5A117C4-B458-5028-BA98-4F8E4154A101}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B5A117C4-B458-5028-BA98-4F8E4154A101}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B5A117C4-B458-5028-BA98-4F8E4154A101}.Release|Any CPU.Build.0 = Release|Any CPU - {D1887853-8ACF-F4A5-08F5-BE1B0604D38E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D1887853-8ACF-F4A5-08F5-BE1B0604D38E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D1887853-8ACF-F4A5-08F5-BE1B0604D38E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D1887853-8ACF-F4A5-08F5-BE1B0604D38E}.Release|Any CPU.Build.0 = Release|Any CPU + {59c1d069-b4ba-9b68-687e-9e213dfb5a44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59c1d069-b4ba-9b68-687e-9e213dfb5a44}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6ff9d838-069f-4b49-5a35-f70072bbf0bf}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6ff9d838-069f-4b49-5a35-f70072bbf0bf}.Debug|Any CPU.Build.0 = Debug|Any CPU + {127a6a13-46c4-06bc-2b59-e13b4b55c391}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {127a6a13-46c4-06bc-2b59-e13b4b55c391}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2fefaaf8-b5d9-71e6-28d0-ea11b956c5ab}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2fefaaf8-b5d9-71e6-28d0-ea11b956c5ab}.Debug|Any CPU.Build.0 = Debug|Any CPU + {537888d1-cf8a-a5f4-08f5-be1b0604d38e}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {537888d1-cf8a-a5f4-08f5-be1b0604d38e}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75207961-aee3-5a76-7646-e1380f658e8a}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75207961-aee3-5a76-7646-e1380f658e8a}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9cb64bdf-5b52-b4fa-5298-d7fd83a65e4b}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9cb64bdf-5b52-b4fa-5298-d7fd83a65e4b}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9aa0345b-376b-0323-106e-cc6cb6d71a2d}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9aa0345b-376b-0323-106e-cc6cb6d71a2d}.Debug|Any CPU.Build.0 = Debug|Any CPU + {a319fe15-1ae6-fe2a-0a9f-052921a7f1a8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {a319fe15-1ae6-fe2a-0a9f-052921a7f1a8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1ad87cc8-ed38-0ca8-9bc0-9e4d761874cc}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1ad87cc8-ed38-0ca8-9bc0-9e4d761874cc}.Debug|Any CPU.Build.0 = Debug|Any CPU + {d8846548-681b-d074-b2ee-6a18c8d7352a}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {d8846548-681b-d074-b2ee-6a18c8d7352a}.Debug|Any CPU.Build.0 = Debug|Any CPU + {531f45d2-6b02-0890-bdab-1a1cfec120fe}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {531f45d2-6b02-0890-bdab-1a1cfec120fe}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3c2e65d1-71a9-dcce-9ff0-e462821b8936}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3c2e65d1-71a9-dcce-9ff0-e462821b8936}.Debug|Any CPU.Build.0 = Debug|Any CPU + {cb069737-b94e-9a1c-359c-e91e2cc14ccf}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {cb069737-b94e-9a1c-359c-e91e2cc14ccf}.Debug|Any CPU.Build.0 = Debug|Any CPU + {d1b151e9-55af-d0be-5c48-e60322e8547e}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {d1b151e9-55af-d0be-5c48-e60322e8547e}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11c38925-f5ac-1a7b-d4ea-7d1dbe8333b9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11c38925-f5ac-1a7b-d4ea-7d1dbe8333b9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {113c1113-e958-2e2a-df1a-460f73b189ce}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {113c1113-e958-2e2a-df1a-460f73b189ce}.Debug|Any CPU.Build.0 = Debug|Any CPU + {892dd2ad-d637-2009-2a24-a9e22d565f04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {892dd2ad-d637-2009-2a24-a9e22d565f04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {054be889-cbbb-67ac-ee1c-c274bed62ef9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {054be889-cbbb-67ac-ee1c-c274bed62ef9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2f4a3448-cdaf-4526-5f2b-e6251050fa57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2f4a3448-cdaf-4526-5f2b-e6251050fa57}.Debug|Any CPU.Build.0 = Debug|Any CPU + {fcc1296d-7663-6b8f-216e-88c8c2471b53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {fcc1296d-7663-6b8f-216e-88c8c2471b53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {e9f11526-f647-3906-4076-b9d63956a2f2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {e9f11526-f647-3906-4076-b9d63956a2f2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {e3bb3ef4-4fde-ed23-c68f-bde7736f97e7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {e3bb3ef4-4fde-ed23-c68f-bde7736f97e7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {dcbe98c1-58fc-f809-893a-f5d2f6943897}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {dcbe98c1-58fc-f809-893a-f5d2f6943897}.Debug|Any CPU.Build.0 = Debug|Any CPU + {b85fd35e-217a-157c-db72-386947c92520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {b85fd35e-217a-157c-db72-386947c92520}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67278720-21fb-059d-f163-359f038172c3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67278720-21fb-059d-f163-359f038172c3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ce465a3d-fd6c-d3ce-4d58-f60566f39213}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ce465a3d-fd6c-d3ce-4d58-f60566f39213}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96d65cec-822f-7bc1-485f-2689af7c678a}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96d65cec-822f-7bc1-485f-2689af7c678a}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24a55edd-b57b-5260-7140-c808091ae8c0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24a55edd-b57b-5260-7140-c808091ae8c0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {d4aff916-632e-ee01-58e3-a5fe1c465dbf}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {d4aff916-632e-ee01-58e3-a5fe1c465dbf}.Debug|Any CPU.Build.0 = Debug|Any CPU + {bf826988-d794-8d15-17c3-30e002b58d3c}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {bf826988-d794-8d15-17c3-30e002b58d3c}.Debug|Any CPU.Build.0 = Debug|Any CPU + {cb503895-a385-b47b-b7f5-142e6fa90c8e}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {cb503895-a385-b47b-b7f5-142e6fa90c8e}.Debug|Any CPU.Build.0 = Debug|Any CPU + {669fd055-17cb-6645-809a-9f6916f3ff2e}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {669fd055-17cb-6645-809a-9f6916f3ff2e}.Debug|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/package.json b/package.json deleted file mode 100644 index 456c627f1..000000000 --- a/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "com.firstgeargames.fishnet", - "version": "3.11.15", - "displayName": "FishNet: Networking Evolved", - "description": "A feature-rich Unity networking solution aimed towards reliability, ease of use, efficiency, and flexibility.", - "unity": "2019.4", - "documentationUrl": "https://fish-networking.gitbook.io/docs/", - "licensesUrl": "https://github.com/FirstGearGames/FishNet/blob/main/LICENSE.md", - "license": "See LICENSE.txt file", - "keywords": [ - "FishNetworking", - "Networking", - "FishNet", - "Unity Networking", - "Bolt", - "Mirror", - "Photon", - "DarkRift", - "DedicatedServer", - "Fusion", - "Netcode" - ], - "author": { - "name": "FirstGearGames", - "url": "http://www.firstgeargames.com" - } -} \ No newline at end of file diff --git a/packages.config b/packages.config deleted file mode 100644 index d6a8f0776..000000000 --- a/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file