XRoom_Unity/Assets/Photon/PhotonVoice/Code/Editor/UnityVoiceClientEditor.cs
2025-05-31 10:20:20 +03:30

467 lines
26 KiB
C#

namespace Photon.Voice.Unity.Editor
{
using ExitGames.Client.Photon;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Unity;
using Realtime;
[CustomEditor(typeof(UnityVoiceClient), true)]
public class UnityVoiceClientEditor : VoiceConnectionEditor
{
private SerializedProperty useVoiceAppSettingsSp;
protected override void OnEnable()
{
base.OnEnable();
this.useVoiceAppSettingsSp = this.serializedObject.FindProperty("UseVoiceAppSettings");
}
protected override void DisplayAppSettings()
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(this.useVoiceAppSettingsSp, new GUIContent("Use Voice App Settings", "Use App Settings From Voice's PhotonServerSettings"));
if (GUILayout.Button("VoiceAppSettings", EditorStyles.miniButton, GUILayout.Width(150)))
{
Selection.objects = new Object[] { global::Photon.Voice.PhotonAppSettings.Instance };
EditorGUIUtility.PingObject(global::Photon.Voice.PhotonAppSettings.Instance);
}
EditorGUILayout.EndHorizontal();
if (!this.useVoiceAppSettingsSp.boolValue)
{
EditorGUI.indentLevel++;
base.DisplayAppSettings();
EditorGUI.indentLevel--;
}
}
}
public class VoiceConnectionEditor : Editor
{
private VoiceConnection connection;
private SerializedProperty settingsSp;
private SerializedProperty runInBackgroundSp;
private SerializedProperty keepAliveInBackgroundSp;
private SerializedProperty applyDontDestroyOnLoadSp;
private SerializedProperty statsResetInterval;
private SerializedProperty primaryRecorderSp;
private SerializedProperty usePrimaryRecorderSp;
private SerializedProperty speakerPrefabSp;
private SerializedProperty cppCompatibilityModeSp;
protected virtual void OnEnable()
{
this.connection = this.target as VoiceConnection;
this.settingsSp = this.serializedObject.FindProperty("Settings");
this.runInBackgroundSp = this.serializedObject.FindProperty("runInBackground");
this.keepAliveInBackgroundSp = this.serializedObject.FindProperty("KeepAliveInBackground");
this.applyDontDestroyOnLoadSp = this.serializedObject.FindProperty("ApplyDontDestroyOnLoad");
this.statsResetInterval = this.serializedObject.FindProperty("statsResetInterval");
this.primaryRecorderSp = this.serializedObject.FindProperty("primaryRecorder");
if (this.primaryRecorderSp == null) // [FormerlySerializedAs("PrimaryRecorder")]
{
this.primaryRecorderSp = this.serializedObject.FindProperty("PrimaryRecorder");
}
this.usePrimaryRecorderSp = this.serializedObject.FindProperty("usePrimaryRecorder");
this.speakerPrefabSp = this.serializedObject.FindProperty("speakerPrefab");
this.cppCompatibilityModeSp = this.serializedObject.FindProperty("cppCompatibilityMode");
}
public override void OnInspectorGUI()
{
this.serializedObject.UpdateIfRequiredOrScript();
EditorGUI.BeginChangeCheck();
VoiceLogger.EditorVoiceLoggerOnInspectorGUI(this.connection.gameObject);
if (!PhotonVoiceEditorUtils.IsInTheSceneInPlayMode(this.connection.gameObject))
{
this.DisplayAppSettings();
}
this.ShowHeader();
EditorGUILayout.PropertyField(this.statsResetInterval, new GUIContent("Stats Reset Interval (ms)", "time [ms] between statistics calculations"));
if (!PhotonVoiceEditorUtils.IsInTheSceneInPlayMode(this.connection.gameObject))
{
EditorGUILayout.PropertyField(this.runInBackgroundSp, new GUIContent("Run In Background", "Sets Unity's Application.runInBackground: Should the application keep running when the application is in the background?"));
EditorGUILayout.PropertyField(this.keepAliveInBackgroundSp, new GUIContent("Background Timeout (ms)", "Defines for how long the Fallback Thread should keep the connection, before it may time out as usual."));
EditorGUILayout.PropertyField(this.applyDontDestroyOnLoadSp, new GUIContent("Don't Destroy On Load", "Persists the GameObject across scenes using Unity's GameObject.DontDestroyOnLoad"));
if (this.applyDontDestroyOnLoadSp.boolValue && !PhotonVoiceEditorUtils.IsPrefab(this.connection.gameObject))
{
if (this.connection.transform.parent != null)
{
EditorGUILayout.HelpBox("DontDestroyOnLoad only works for root GameObjects or components on root GameObjects.", MessageType.Warning);
if (GUILayout.Button("Detach"))
{
this.connection.transform.parent = null;
}
}
}
EditorGUILayout.PropertyField(this.primaryRecorderSp,
new GUIContent("Primary Recorder", "Main Recorder to be used for transmission by default"));
if (!this.connection.AlwaysUsePrimaryRecorder)
{
EditorGUILayout.PropertyField(this.usePrimaryRecorderSp, new GUIContent("Use Primary Recorder", "Use primary recorder directly by Voice Client"));
}
GameObject prefab = this.speakerPrefabSp.objectReferenceValue as GameObject;
if (prefab != null && prefab.GetComponentInChildren<Speaker>() == null)
{
EditorGUILayout.HelpBox("Speaker prefab needs to have a Speaker component in the hierarchy.", MessageType.Warning);
}
this.speakerPrefabSp.objectReferenceValue = EditorGUILayout.ObjectField(new GUIContent("Speaker Prefab",
"Prefab that contains Speaker component to be instantiated when receiving a new remote audio source info"), prefab,
typeof(GameObject), false) as GameObject;
EditorGUILayout.PropertyField(this.cppCompatibilityModeSp, new GUIContent("C++ API Compatibility Mode", "Use a protocol compatible with Photon Voice C++ API"));
}
if (EditorGUI.EndChangeCheck())
{
this.serializedObject.ApplyModifiedProperties();
}
if (PhotonVoiceEditorUtils.IsInTheSceneInPlayMode(this.connection.gameObject))
{
this.DisplayVoiceStats();
this.DisplayDebugInfo(this.connection.Client);
this.DisplayCachedVoiceInfo();
this.DisplayTrafficStats(this.connection.Client.LoadBalancingPeer);
if (connection.Client.State == ClientState.PeerCreated || connection.Client.State == ClientState.Disconnected)
{
if (GUILayout.Button("Connect"))
{
connection.ConnectUsingSettings();
}
}
if (connection.Client.State == ClientState.Joined)
{
if (GUILayout.Button("Disconnect"))
{
connection.Client.Disconnect();
}
}
}
}
private bool showVoiceStats;
private bool showPlayersList;
private bool showDebugInfo = true;
private bool showCachedVoices;
private bool showTrafficStats;
protected virtual void DisplayVoiceStats()
{
this.showVoiceStats =
EditorGUILayout.Foldout(this.showVoiceStats, new GUIContent("Voice Frames Stats", "Show frames stats"));
if (this.showVoiceStats)
{
this.DrawLabel("Frames Received /s", this.connection.FramesReceivedPerSecond.ToString());
this.DrawLabel("Frames Lost /s", this.connection.FramesLostPerSecond.ToString());
this.DrawLabel("Frames Lost %", this.connection.FramesLostPercent.ToString());
}
}
protected virtual void DisplayDebugInfo(LoadBalancingClient client)
{
this.showDebugInfo = EditorGUILayout.Foldout(this.showDebugInfo, new GUIContent("Client Debug Info", "Debug info for Photon client"));
if (this.showDebugInfo)
{
EditorGUI.indentLevel++;
this.DrawLabel("Client State", client.State.ToString());
if (!string.IsNullOrEmpty(client.AppId))
{
this.DrawLabel("AppId", client.AppId);
}
if (!string.IsNullOrEmpty(client.AppVersion))
{
this.DrawLabel("AppVersion", client.AppVersion);
}
if (!string.IsNullOrEmpty(client.CloudRegion))
{
this.DrawLabel("Current Cloud Region", client.CloudRegion);
}
if (client.IsConnected)
{
this.DrawLabel("Current Server Address", client.CurrentServerAddress);
}
if (client.InRoom)
{
this.DrawLabel("Room Name", client.CurrentRoom.Name);
this.showPlayersList = EditorGUILayout.Foldout(this.showPlayersList, new GUIContent("Players List", "List of players joined to the room"));
if (this.showPlayersList)
{
EditorGUI.indentLevel++;
foreach (Player player in client.CurrentRoom.Players.Values)
{
this.DisplayPlayerDebugInfo(player);
EditorGUILayout.LabelField(string.Empty, GUI.skin.horizontalSlider);
}
EditorGUI.indentLevel--;
}
}
EditorGUI.indentLevel--;
}
}
protected virtual void DisplayPlayerDebugInfo(Player player)
{
this.DrawLabel("Actor Number", player.ActorNumber.ToString());
if (!string.IsNullOrEmpty(player.UserId))
{
this.DrawLabel("UserId", player.UserId);
}
if (!string.IsNullOrEmpty(player.NickName))
{
this.DrawLabel("NickName", player.NickName);
}
if (player.IsMasterClient)
{
EditorGUILayout.LabelField("Master Client");
}
if (player.IsLocal)
{
EditorGUILayout.LabelField("Local");
}
if (player.IsInactive)
{
EditorGUILayout.LabelField("Inactive");
}
}
protected virtual void DisplayCachedVoiceInfo()
{
this.showCachedVoices =
EditorGUILayout.Foldout(this.showCachedVoices, new GUIContent("Cached Remote Voices' Info", "Show remote voices info cached by local client"));
if (this.showCachedVoices)
{
List<RemoteVoiceLink> cachedVoices = this.connection.CachedRemoteVoices;
Speaker[] speakers = FindObjectsOfType<Speaker>();
for (int i = 0; i < cachedVoices.Count; i++)
{
//VoiceInfo info = cachedVoices[i].Info;
EditorGUI.indentLevel++;
this.DrawLabel("Voice #", cachedVoices[i].VoiceId.ToString());
this.DrawLabel("Player #", cachedVoices[i].PlayerId.ToString());
this.DrawLabel("Channel #", cachedVoices[i].ChannelId.ToString());
if (cachedVoices[i].VoiceInfo.UserData != null)
{
this.DrawLabel("UserData: ", cachedVoices[i].VoiceInfo.UserData.ToString());
}
bool linked = false;
for (int j = 0; j < speakers.Length; j++)
{
Speaker speaker = speakers[j];
if (speaker.IsLinked && speaker.RemoteVoice.PlayerId == cachedVoices[i].PlayerId &&
speaker.RemoteVoice.VoiceId == cachedVoices[i].VoiceId)
{
linked = true;
EditorGUILayout.ObjectField(new GUIContent("Linked Speaker"), speaker, typeof(Speaker), false);
break;
}
}
if (!linked)
{
EditorGUILayout.LabelField("Not Linked");
}
EditorGUILayout.LabelField(string.Empty, GUI.skin.horizontalSlider);
EditorGUI.indentLevel--;
}
}
}
// inspired by PhotonVoiceStatsGui.TrafficStatsWindow
protected virtual void DisplayTrafficStats(LoadBalancingPeer peer)
{
this.showTrafficStats = EditorGUILayout.Foldout(this.showTrafficStats, new GUIContent("Traffic Stats", "Traffic Statistics for Photon Client"));
if (this.showTrafficStats)
{
GUILayout.Label(string.Format("RTT (ping): {0}[+/-{1}]ms, last={2}ms", peer.RoundTripTime, peer.RoundTripTimeVariance, peer.LastRoundTripTime));
//GUILayout.Label(string.Format("{0}ms since last ACK sent, {1}ms since last sent, {2}ms since last received", peer.ConnectionTime - peer.LastSendAckTime, peer.ConnectionTime - peer.LastSendOutgoingTime, peer.ConnectionTime - peer.TimestampOfLastSocketReceive)); //add
GUILayout.Label(string.Format("Reliable Commands Resent: {0}", peer.ResentReliableCommands));
//GUILayout.Label(string.Format("last operation={0}B current dispatch:{1}B", peer.ByteCountLastOperation, peer.ByteCountCurrentDispatch));
//GUILayout.Label(string.Format("Packets Lost: by challenge={0} by CRC={1}", peer.PacketLossByChallenge, peer.PacketLossByCrc));
//GUILayout.Label(string.Format("Total Traffic: In={0} - {1} Out={2} - {3}", this.FormatSize(peer.BytesIn, ti:string.Empty), this.FormatSize(this.connection.BytesReceivedPerSecond), this.FormatSize(peer.BytesOut, ti:string.Empty), this.FormatSize(this.connection.BytesSentPerSecond)));
GUILayout.Label(string.Format("Total Traffic: In={0} Out={1}", this.FormatSize(peer.BytesIn, ti: string.Empty), this.FormatSize(peer.BytesOut, ti: string.Empty)));
peer.TrafficStatsEnabled = EditorGUILayout.Toggle(new GUIContent("Advanced", "Enable or disable traffic Statistics for Photon Peer"), peer.TrafficStatsEnabled);
if (peer.TrafficStatsEnabled)
{
long elapsedSeconds = peer.TrafficStatsElapsedMs / 1000;
if (elapsedSeconds == 0)
{
elapsedSeconds = 1;
}
GUILayout.Label(string.Format("Time elapsed: {0} seconds", elapsedSeconds));
this.DisplayTrafficStatsGameLevel(peer.TrafficStatsGameLevel, elapsedSeconds);
TrafficStats trafficStats = peer.TrafficStatsIncoming;
GUILayout.Label(string.Format("Protocol: {0} Package Header Size={1}B", peer.TransportProtocol, trafficStats.PackageHeaderSize));
EditorGUILayout.LabelField("Commands/Packets Incoming", EditorStyles.boldLabel);
this.DisplayTrafficStats(/*peer, */trafficStats, elapsedSeconds);
EditorGUILayout.LabelField("Commands/Packets Outgoing", EditorStyles.boldLabel);
trafficStats = peer.TrafficStatsOutgoing;
this.DisplayTrafficStats(/*peer, */trafficStats, elapsedSeconds);
if (GUILayout.Button("Reset"))
{
peer.TrafficStatsReset();
}
}
}
}
private void DisplayTrafficStats(/*PhotonPeer peer, */TrafficStats trafficStats, long elapsedSeconds)
{
GUILayout.Label(string.Format("\tControl Commands: #={0} a#={1}/s s={2} as={3}", trafficStats.ControlCommandCount, trafficStats.ControlCommandCount / elapsedSeconds, this.FormatSize(trafficStats.ControlCommandBytes, ti: string.Empty), this.FormatSize(trafficStats.ControlCommandBytes / elapsedSeconds)));
GUILayout.Label(string.Format("\tFragment Commands: #={0} a#={1}/s s={2} as={3}", trafficStats.FragmentCommandCount, trafficStats.FragmentCommandCount / elapsedSeconds, this.FormatSize(trafficStats.FragmentCommandBytes, ti: string.Empty), this.FormatSize(trafficStats.FragmentCommandBytes / elapsedSeconds)));
GUILayout.Label(string.Format("\tReliable Commands: #={0} a#={1}/s s={2} as={3}", trafficStats.ReliableCommandCount, trafficStats.ReliableCommandCount / elapsedSeconds, this.FormatSize(trafficStats.ReliableCommandBytes, ti: string.Empty), this.FormatSize(trafficStats.ReliableCommandCount / elapsedSeconds)));
GUILayout.Label(string.Format("\tUnreliable Commands: #={0} a#={1}/s s={2} as={3}", trafficStats.UnreliableCommandCount, trafficStats.UnreliableCommandCount / elapsedSeconds, this.FormatSize(trafficStats.UnreliableCommandBytes, ti: string.Empty), this.FormatSize(trafficStats.UnreliableCommandBytes / elapsedSeconds)));
GUILayout.Label(string.Format("\tTotal Commands: #={0} a#={1}/s s={2} as={3}", trafficStats.TotalCommandCount, trafficStats.TotalCommandCount / elapsedSeconds, this.FormatSize(trafficStats.TotalCommandBytes, ti: string.Empty), this.FormatSize(trafficStats.TotalCommandBytes / elapsedSeconds)));
GUILayout.Label(string.Format("\tTotal Packets: #={0} a#={1}/s s={2} as={3}", trafficStats.TotalPacketCount, trafficStats.TotalPacketCount / elapsedSeconds, this.FormatSize(trafficStats.TotalPacketBytes, ti: string.Empty), this.FormatSize(trafficStats.TotalPacketBytes / elapsedSeconds)));
GUILayout.Label(string.Format("\tTotal Commands in Packets: {0}", trafficStats.TotalCommandsInPackets));
//GUILayout.Label(string.Format("\t{0}ms since last ACK", peer.ConnectionTime - trafficStats.TimestampOfLastAck));
//GUILayout.Label(string.Format("\t{0} ms since last reliable Command", peer.ConnectionTime - trafficStats.TimestampOfLastReliableCommand));
}
private void DisplayTrafficStatsGameLevel(TrafficStatsGameLevel gls, long elapsedSeconds)
{
GUILayout.Label("In Game", EditorStyles.boldLabel);
GUILayout.Label(string.Format("\tmax. delta between\n\t\tsend: {0,4}ms \n\t\tdispatch: {1,4}ms \n\tlongest dispatch for: \n\t\tev({3}):{2,3}ms \n\t\top({5}):{4,3}ms",
gls.LongestDeltaBetweenSending,
gls.LongestDeltaBetweenDispatching,
gls.LongestEventCallback,
gls.LongestEventCallbackCode,
gls.LongestOpResponseCallback,
gls.LongestOpResponseCallbackOpCode));
GUILayout.Label("\tMessages", EditorStyles.boldLabel);
GUILayout.Label(string.Format("\t\tTotal: Out {0,4}msg | In {1,4}msg | Sum {2,4}msg",
gls.TotalOutgoingMessageCount,
gls.TotalIncomingMessageCount,
gls.TotalMessageCount));
GUILayout.Label(string.Format("\t\tAverage: Out {0,4}msg/s | In {1,4}msg/s | Sum {2,4}msg/s",
gls.TotalOutgoingMessageCount / elapsedSeconds,
gls.TotalIncomingMessageCount / elapsedSeconds,
gls.TotalMessageCount / elapsedSeconds));
}
private void DrawLabel(string prefix, string text)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel(prefix);
EditorGUILayout.LabelField(text);
EditorGUILayout.EndHorizontal();
}
protected virtual void DisplayAppSettings()
{
this.connection.ShowSettings = EditorGUILayout.Foldout(this.connection.ShowSettings, new GUIContent("App Settings", "Settings to be used by this Voice Client"));
if (this.connection.ShowSettings)
{
EditorGUILayout.BeginHorizontal();
SerializedProperty sP = this.settingsSp.FindPropertyRelative("AppIdVoice");
EditorGUILayout.PropertyField(sP);
string appId = sP.stringValue;
string url = "https://dashboard.photonengine.com/en-US/PublicCloud";
if (!string.IsNullOrEmpty(appId))
{
url = string.Concat("https://dashboard.photonengine.com/en-US/App/Manage/", appId);
}
if (GUILayout.Button("Dashboard", EditorStyles.miniButton, GUILayout.Width(90)))
{
Application.OpenURL(url);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.PropertyField(this.settingsSp.FindPropertyRelative("AppVersion"));
EditorGUILayout.PropertyField(this.settingsSp.FindPropertyRelative("UseNameServer"), new GUIContent("Use Name Server", "Photon Cloud requires this checked.\nUncheck for Photon Server SDK (OnPremises)."));
EditorGUILayout.PropertyField(this.settingsSp.FindPropertyRelative("FixedRegion"), new GUIContent("Fixed Region", "Photon Cloud setting, needs a Name Server.\nDefine one region to always connect to.\nLeave empty to use the best region from a server-side region list."));
EditorGUILayout.PropertyField(this.settingsSp.FindPropertyRelative("Server"), new GUIContent("Server", "Typically empty for Photon Cloud.\nFor Photon Server, enter your host name or IP. Also uncheck \"Use Name Server\" for older Photon Server versions."));
EditorGUILayout.PropertyField(this.settingsSp.FindPropertyRelative("Port"), new GUIContent("Port", "Use 0 for Photon Cloud.\nOnPremise uses 5055 for UDP and 4530 for TCP."));
EditorGUILayout.PropertyField(this.settingsSp.FindPropertyRelative("ProxyServer"), new GUIContent("Proxy Server", "HTTP Proxy Server for WebSocket connection. See LoadBalancingClient.ProxyServerAddress for options."));
EditorGUILayout.PropertyField(this.settingsSp.FindPropertyRelative("Protocol"), new GUIContent("Protocol", "Use UDP where possible.\nWSS works on WebGL and Xbox exports.\nDefine WEBSOCKET for use on other platforms."));
EditorGUILayout.PropertyField(this.settingsSp.FindPropertyRelative("EnableProtocolFallback"), new GUIContent("Protocol Fallback", "Automatically try another network protocol, if initial connect fails.\nWill use default Name Server ports."));
EditorGUILayout.PropertyField(this.settingsSp.FindPropertyRelative("EnableLobbyStatistics"), new GUIContent("Lobby Statistics", "When using multiple room lists (lobbies), the server can send info about their usage."));
EditorGUILayout.PropertyField(this.settingsSp.FindPropertyRelative("NetworkLogging"), new GUIContent("Network Logging", "Log level for the Photon libraries."));
#region Best Region Box
GUIStyle verticalBoxStyle = new GUIStyle("HelpBox") { margin = new RectOffset(16, 0, 0, 0) };
EditorGUILayout.BeginVertical(verticalBoxStyle);
string prefLabel;
const string notAvailableLabel = "n/a";
string bestRegionSummaryInPrefs = this.connection.BestRegionSummaryInPreferences;
if (!string.IsNullOrEmpty(bestRegionSummaryInPrefs))
{
string[] regionsPrefsList = bestRegionSummaryInPrefs.Split(';');
if (regionsPrefsList.Length < 2 || string.IsNullOrEmpty(regionsPrefsList[0]) || string.IsNullOrEmpty(regionsPrefsList[1]))
{
prefLabel = notAvailableLabel;
}
else
{
prefLabel = string.Format("'{0}' ping:{1}ms ", regionsPrefsList[0], regionsPrefsList[1]);
}
}
else
{
prefLabel = notAvailableLabel;
}
EditorGUI.indentLevel--;
EditorGUILayout.LabelField(new GUIContent(string.Concat("Best Region Preference: ", prefLabel), "Best region is used if Fixed Region is empty."));
EditorGUI.indentLevel++;
EditorGUILayout.BeginHorizontal();
Rect resetRect = EditorGUILayout.GetControlRect(GUILayout.MinWidth(64));
Rect editRect = EditorGUILayout.GetControlRect(GUILayout.MinWidth(64));
if (GUI.Button(resetRect, "Reset", EditorStyles.miniButton))
{
this.connection.BestRegionSummaryInPreferences = null;
}
if (!string.IsNullOrEmpty(appId) && GUI.Button(editRect, "Edit Regions WhiteList", EditorStyles.miniButton))
{
url = string.Concat("https://dashboard.photonengine.com/en-US/App/RegionsWhitelistEdit/", appId);
Application.OpenURL(url);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
#endregion Best Region Box
}
}
protected virtual void ShowHeader()
{
}
private string FormatSize(float bytes, string u = "B", string ti = "/s")
{
const long kb = 1024;
const long mb = kb * 1024;
const long gb = mb * 1024;
const long tb = gb * 1024;
long m = 1;
if (bytes >= tb)
{
m = tb;
u = "TB";
}
else if (bytes >= gb)
{
m = gb;
u = "GB";
}
else if (bytes >= mb)
{
m = mb;
u = "MB";
}
else if (bytes >= kb)
{
m = kb;
u = "KB";
}
return string.Format("{0:0.0}{1}{2}", bytes / m, u, ti);
}
}
}