[Add] FishNet

This commit is contained in:
2026-03-30 20:11:57 +07:00
parent ee793a3361
commit c22c08753a
1797 changed files with 197950 additions and 1 deletions
@@ -0,0 +1,39 @@
#if UNITY_EDITOR
using System.IO;
using UnityEditor;
using UnityEditor.Callbacks;
namespace FishNet.Editing
{
/* When creating builds this will place an empty file within
* the build folder.
*
* The file contains absolutely no information, and is used by our partners to identify how many of their customers are using
* Fish-Networking.
*
* While this file is not required, you may delete the file and/or this code, we request that you please
* consider keeping the file present as it helps keep FishNet free. */
public class BuildIdentifier
{
[PostProcessBuild(1)]
public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject)
{
/* Previously only server builds were included, but it makes sense to include
* in all builds for when used with client-auth relays. */
string buildPath = Path.GetDirectoryName(pathToBuiltProject);
if (buildPath == null)
return;
// Try to create the empty file.
try
{
string filePath = Path.Combine(buildPath, "FishNet.SDK.Id");
File.WriteAllText(filePath, string.Empty);
}
finally { }
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 390372a39ccc82a4d8bf9ca660da578a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/BuildIdentifier.cs
uploadId: 866910
@@ -0,0 +1,101 @@
#if UNITY_EDITOR
using FishNet.Configuring;
using System.IO;
using UnityEngine;
using System.Xml.Serialization;
using FishNet.Editing.PrefabCollectionGenerator;
using UnityEditor.Compilation;
using UnityEditor.Build.Reporting;
using UnityEditor;
using UnityEditor.Build;
namespace FishNet.Configuring
{
public class CodeStripping
{
/// <summary>
/// True if making a release build for client.
/// </summary>
public static bool ReleasingForClient => Configuration.Configurations.CodeStripping.IsBuilding && !Configuration.Configurations.CodeStripping.IsHeadless && !Configuration.Configurations.CodeStripping.IsDevelopment;
/// <summary>
/// True if making a release build for server.
/// </summary>
public static bool ReleasingForServer => Configuration.Configurations.CodeStripping.IsBuilding && Configuration.Configurations.CodeStripping.IsHeadless && !Configuration.Configurations.CodeStripping.IsDevelopment;
/// <summary>
/// Returns if to remove server logic.
/// </summary>
/// <returns></returns>
public static bool RemoveServerLogic
{
get
{
/* This is to protect non pro users from enabling this
* without the extra logic code. */
#pragma warning disable CS0162 // Unreachable code detected
return false;
#pragma warning restore CS0162 // Unreachable code detected
}
}
/// <summary>
/// True if building and stripping is enabled.
/// </summary>
public static bool StripBuild
{
get
{
/* This is to protect non pro users from enabling this
* without the extra logic code. */
#pragma warning disable CS0162 // Unreachable code detected
return false;
#pragma warning restore CS0162 // Unreachable code detected
}
}
/// <summary>
/// Technique to strip methods.
/// </summary>
public static StrippingTypes StrippingType => (StrippingTypes)Configuration.Configurations.CodeStripping.StrippingType;
private static object _compilationContext;
public int callbackOrder => 0;
public void OnPreprocessBuild(BuildReport report)
{
Generator.IgnorePostProcess = true;
Generator.GenerateFull();
CompilationPipeline.compilationStarted += CompilationPipelineOnCompilationStarted;
CompilationPipeline.compilationFinished += CompilationPipelineOnCompilationFinished;
}
/* Solution for builds ending with errors and not triggering OnPostprocessBuild.
* Link: https://gamedev.stackexchange.com/questions/181611/custom-build-failure-callback
*/
private void CompilationPipelineOnCompilationStarted(object compilationContext)
{
_compilationContext = compilationContext;
}
private void CompilationPipelineOnCompilationFinished(object compilationContext)
{
if (compilationContext != _compilationContext)
return;
_compilationContext = null;
CompilationPipeline.compilationStarted -= CompilationPipelineOnCompilationStarted;
CompilationPipeline.compilationFinished -= CompilationPipelineOnCompilationFinished;
// BuildingEnded();
}
private void BuildingEnded()
{
Generator.IgnorePostProcess = false;
}
public void OnPostprocessBuild(BuildReport report)
{
BuildingEnded();
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 0c7409efe2f84e7428d5c6c97ed7d32e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/CodeStripping.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f10fbb8567e7a7749831005ce1e1ee4f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,92 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace FishNet.Editing.Beta
{
public class BetaModeMenu : MonoBehaviour
{
#region const.
private const string STABLE_RECURSIVE_DESPAWNS_DEFINE = "FISHNET_STABLE_RECURSIVE_DESPAWNS";
private const string THREADED_TICKSMOOTHERS_DEFINE = "FISHNET_THREADED_TICKSMOOTHERS";
private const string THREADED_COLLIDER_ROLLBACK_DEFINE = "FISHNET_THREADED_COLLIDER_ROLLBACK";
#endregion
#region Beta Recursive Despawns
#if FISHNET_STABLE_RECURSIVE_DESPAWNS
[MenuItem("Tools/Fish-Networking/Beta/Enable Recursive Despawns", false, -1101)]
private static void EnableBetaRecursiveDespawns() => SetBetaRecursiveDespawns(useStable: false);
#else
[MenuItem("Tools/Fish-Networking/Beta/Disable Recursive Despawns", false, -1101)]
private static void DisableBetaRecursiveDespawns() => SetBetaRecursiveDespawns(useStable: true);
#endif
private static void SetBetaRecursiveDespawns(bool useStable)
{
bool result = DeveloperMenu.RemoveOrAddDefine(STABLE_RECURSIVE_DESPAWNS_DEFINE, removeDefine: !useStable);
if (result)
Debug.LogWarning($"Beta Recursive Despawns are now {GetBetaEnabledText(useStable)}.");
}
#endregion
#region Beta ThreadedSmothers
/* Changes by https://github.com/belplaton
* Content: Threaded TickSmoothers
* Migrating the network interpolation system for the graphical world to a multithreaded Unity Jobs + Burst implementation. */
#if FISHNET_THREADED_TICKSMOOTHERS
[MenuItem("Tools/Fish-Networking/Beta/Disable Threaded TickSmoothers", false, -1101)]
private static void DisableBetaThreadedSmoothers() => SetBetaThreadedSmoothers(useStable: true);
#else
[MenuItem("Tools/Fish-Networking/Beta/Enable Threaded TickSmoothers", false, -1101)]
private static void EnableBetaThreadedSmoothers()
{
#if UNITYMATHEMATICS || UNITYMATHEMATICS_131 || UNITYMATHEMATICS_132
SetBetaThreadedSmoothers(useStable: false);
#else
Debug.LogError($"You must install the package com.unity.mathematics to use Beta Threaded TickSmoothers.");
#endif
}
#endif
private static void SetBetaThreadedSmoothers(bool useStable)
{
bool result = DeveloperMenu.RemoveOrAddDefine(THREADED_TICKSMOOTHERS_DEFINE, removeDefine: useStable);
if (result)
Debug.LogWarning($"Beta Threaded TickSmoothers are now {GetBetaEnabledText(useStable)}.");
}
#endregion
#region Beta Threaded Collider Rollback
/* Changes by https://github.com/belplaton
* Content: Threaded Collider Rollback
* Migrating collider rollback -- commonly used for hitbox tracing -- to a multithreaded Unity Jobs + Burst implementation. */
#if FISHNET_THREADED_COLLIDER_ROLLBACK
[MenuItem("Tools/Fish-Networking/Beta/Disable Threaded Collider Rollback", false, -1101)]
private static void DisableBetaThreadedColliderRollback() => SetBetaThreadedColliderRollback(useStable: true);
#else
[MenuItem("Tools/Fish-Networking/Beta/Enable Threaded Collider Rollback", false, -1101)]
private static void EnableBetaThreadedColliderRollback()
{
#if UNITYMATHEMATICS || UNITYMATHEMATICS_131 || UNITYMATHEMATICS_132
SetBetaThreadedColliderRollback(useStable: false);
#else
Debug.LogError($"You must install the package com.unity.mathematics to use Beta Threaded Collider Rollhack..");
#endif
}
#endif
private static void SetBetaThreadedColliderRollback(bool useStable)
{
bool result = DeveloperMenu.RemoveOrAddDefine(THREADED_COLLIDER_ROLLBACK_DEFINE, removeDefine: useStable);
if (result)
Debug.LogWarning($"Beta Threaded Collider Rollbacks are now {GetBetaEnabledText(useStable)}.");
}
#endregion
private static string GetBetaEnabledText(bool useStable)
{
return useStable ? "disabled" : "enabled";
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ecf03df72c983164586a22e695d17905
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Configuring/BetaModeMenu.cs
uploadId: 866910
@@ -0,0 +1,146 @@
#if UNITY_EDITOR
using FishNet.Editing.PrefabCollectionGenerator;
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using UnityEditor;
using UnityEngine;
namespace FishNet.Configuring
{
public enum StrippingTypes : int
{
Redirect = 0,
Empty_Experimental = 1
}
public enum SearchScopeType : int
{
EntireProject = 0,
SpecificFolders = 1
}
public class CreateNewNetworkBehaviourConfigurations
{
public string templateDirectoryPath = "Assets";
}
public class PrefabGeneratorConfigurations
{
public bool Enabled = true;
public bool LogToConsole = true;
public bool FullRebuild = false;
public bool SpawnableOnly = true;
public bool SaveChanges = true;
public string DefaultPrefabObjectsPath = Path.Combine("Assets", "DefaultPrefabObjects.asset");
internal string DefaultPrefabObjectsPath_Platform => Generator.GetPlatformPath(DefaultPrefabObjectsPath);
public int SearchScope = (int)SearchScopeType.EntireProject;
public List<string> ExcludedFolders = new();
public List<string> IncludedFolders = new();
}
public class CodeStrippingConfigurations
{
public bool IsBuilding = false;
public bool IsDevelopment = false;
public bool IsHeadless = false;
public bool StripReleaseBuilds = false;
public int StrippingType = (int)StrippingTypes.Redirect;
}
public class ConfigurationData
{
// Non serialized doesn't really do anything, its just for me.
[NonSerialized]
public bool Loaded;
public PrefabGeneratorConfigurations PrefabGenerator = new();
public CodeStrippingConfigurations CodeStripping = new();
public CreateNewNetworkBehaviourConfigurations CreateNewNetworkBehaviour = new();
}
public static class ConfigurationDataExtension
{
/// <summary>
/// Returns if a differs from b.
/// </summary>
public static bool HasChanged(this ConfigurationData a, ConfigurationData b)
{
return a.CodeStripping.StripReleaseBuilds != b.CodeStripping.StripReleaseBuilds;
}
/// <summary>
/// Copies all values from source to target.
/// </summary>
public static void CopyTo(this ConfigurationData source, ConfigurationData target)
{
target.CodeStripping.StripReleaseBuilds = source.CodeStripping.StripReleaseBuilds;
}
/// <summary>
/// Writes a configuration data.
/// </summary>
public static void Write(this ConfigurationData cd, bool refreshAssetDatabase)
{
/* Why is this a thing you ask? Because Unity makes it VERY difficult to read values from
* memory during builds since on some Unity versions the building application is on a different
* processor. In result instead of using memory to read configurationdata the values
* must be written to disk then load the disk values as needed.
*
* Fortunatelly the file is extremely small and this does not occur often at all. The disk read
* will occur once per script save, and once per assembly when building. */
try
{
string path = Configuration.GetAssetsPath(Configuration.CONFIG_FILE_NAME);
XmlSerializer serializer = new(typeof(ConfigurationData));
TextWriter writer = new StreamWriter(path);
serializer.Serialize(writer, cd);
writer.Close();
#if UNITY_EDITOR
if (refreshAssetDatabase)
{
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
#endif
}
catch (Exception ex)
{
throw new($"An error occurred while writing ConfigurationData. Message: {ex.Message}");
}
}
/// <summary>
/// Writes a configuration data.
/// </summary>
public static void Write(this ConfigurationData cd, string path, bool refreshAssetDatabase)
{
/* Why is this a thing you ask? Because Unity makes it VERY difficult to read values from
* memory during builds since on some Unity versions the building application is on a different
* processor. In result instead of using memory to read configurationdata the values
* must be written to disk then load the disk values as needed.
*
* Fortunatelly the file is extremely small and this does not occur often at all. The disk read
* will occur once per script save, and once per assembly when building. */
try
{
XmlSerializer serializer = new(typeof(ConfigurationData));
TextWriter writer = new StreamWriter(path);
serializer.Serialize(writer, cd);
writer.Close();
#if UNITY_EDITOR
if (refreshAssetDatabase)
{
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
#endif
}
catch (Exception ex)
{
throw new($"An error occurred while writing ConfigurationData. Message: {ex.Message}");
}
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4be37e1b0afd29944ad4fa0b92ed8c7e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Configuring/ConfigurationData.cs
uploadId: 866910
@@ -0,0 +1,112 @@
#if UNITY_EDITOR
using FishNet.Editing.PrefabCollectionGenerator;
using FishNet.Object;
using FishNet.Utility.Extension;
using GameKit.Dependencies.Utilities;
using System.Collections.Generic;
using FishNet.Configuring.EditorCloning;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace FishNet.Editing
{
public class ConfigurationEditor : EditorWindow
{
[MenuItem("Tools/Fish-Networking/Configuration", false, 0)]
public static void ShowConfiguration()
{
SettingsService.OpenProjectSettings("Project/Fish-Networking/Configuration");
}
}
public class DeveloperMenu : MonoBehaviour
{
#region const.
private const string QOL_ATTRIBUTES_DEFINE = "DISABLE_QOL_ATTRIBUTES";
private const string DEVELOPER_ONLY_WARNING = "If you are not a developer or were not instructed to do this by a developer things are likely to break. You have been warned.";
#endregion
#region QOL Attributes
#if DISABLE_QOL_ATTRIBUTES
[MenuItem("Tools/Fish-Networking/Utility/Quality of Life Attributes/Enable", false, -999)]
private static void EnableQOLAttributes()
{
bool result = RemoveOrAddDefine(QOL_ATTRIBUTES_DEFINE, removeDefine: true);
if (result)
Debug.LogWarning($"Quality of Life Attributes have been enabled.");
}
#else
[MenuItem("Tools/Fish-Networking/Utility/Quality of Life Attributes/Disable", false, 0)]
private static void DisableQOLAttributes()
{
bool result = RemoveOrAddDefine(QOL_ATTRIBUTES_DEFINE, removeDefine: false);
if (result)
Debug.LogWarning($"Quality of Life Attributes have been disabled. {DEVELOPER_ONLY_WARNING}");
}
#endif
#endregion
internal static bool RemoveOrAddDefine(string define, bool removeDefine)
{
#if UNITY_6000_1_OR_NEWER
NamedBuildTarget activeTarget = NamedBuildTarget.FromBuildTargetGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
#endif
#if UNITY_6000_1_OR_NEWER
string currentDefines = PlayerSettings.GetScriptingDefineSymbols(activeTarget);
#else
string currentDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
#endif
HashSet<string> definesHs = new();
string[] currentArr = currentDefines.Split(';');
// Add any define which doesn't contain MIRROR.
foreach (string item in currentArr)
definesHs.Add(item);
int startingCount = definesHs.Count;
if (removeDefine)
definesHs.Remove(define);
else
definesHs.Add(define);
bool modified = definesHs.Count != startingCount;
if (modified)
{
string changedDefines = string.Join(";", definesHs);
#if UNITY_6000_1_OR_NEWER
PlayerSettings.SetScriptingDefineSymbols(activeTarget, changedDefines);
#else
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, changedDefines);
#endif
}
return modified;
}
}
public class RefreshDefaultPrefabsMenu : MonoBehaviour
{
/// <summary>
/// Rebuilds the DefaultPrefabsCollection file.
/// </summary>
[MenuItem("Tools/Fish-Networking/Utility/Refresh Default Prefabs", false, 300)]
public static void RebuildDefaultPrefabs()
{
if (!CloneChecker.CanGenerateFiles())
{
Debug.Log("Skipping prefab generation as clone settings does not allow it.");
return;
}
Debug.Log("Refreshing default prefabs.");
Generator.GenerateFull(null, true);
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8135b3a4c31cfb74896f1e9e77059c89
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Configuring/ConfigurationEditor.cs
uploadId: 866910
@@ -0,0 +1,91 @@
#if UNITY_EDITOR
using System.IO;
using System.Xml.Serialization;
using UnityEngine;
using UnityEditor.Compilation;
using UnityEditor.Build.Reporting;
using UnityEditor;
using UnityEditor.Build;
namespace FishNet.Configuring
{
public class Configuration
{
/// <summary>
/// </summary>
private static ConfigurationData _configurations;
/// <summary>
/// ConfigurationData to use.
/// </summary>
public static ConfigurationData Configurations
{
get
{
if (_configurations == null)
_configurations = LoadConfigurationData();
if (_configurations == null)
throw new("Fish-Networking Configurations could not be loaded. Certain features such as code-stripping may not function.");
return _configurations;
}
private set { _configurations = value; }
}
/// <summary>
/// File name for configuration disk data.
/// </summary>
public const string CONFIG_FILE_NAME = "FishNet.Config.XML";
/// <summary>
/// Returns the path for the configuration file.
/// </summary>
/// <returns></returns>
internal static string GetAssetsPath(string additional = "")
{
string a = Path.Combine(Directory.GetCurrentDirectory(), "Assets");
if (additional != "")
a = Path.Combine(a, additional);
return a;
}
/// <summary>
/// Returns FishNetworking ConfigurationData.
/// </summary>
/// <returns></returns>
internal static ConfigurationData LoadConfigurationData()
{
// return new ConfigurationData();
if (_configurations == null || !_configurations.Loaded)
{
string configPath = GetAssetsPath(CONFIG_FILE_NAME);
// string configPath = string.Empty;
// File is on disk.
if (File.Exists(configPath))
{
FileStream fs = null;
try
{
XmlSerializer serializer = new(typeof(ConfigurationData));
fs = new(configPath, FileMode.Open, FileAccess.Read, FileShare.Read);
_configurations = (ConfigurationData)serializer.Deserialize(fs);
}
finally
{
fs?.Close();
}
_configurations.Loaded = true;
}
else
{
// If null then make a new instance.
if (_configurations == null)
_configurations = new();
// Don't unset loaded, if its true then it should have proper info.
// _configurationData.Loaded = false;
}
}
return _configurations;
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8d05bf07ec9af2c46a1fe6c24871cccb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Configuring/Configuring.cs
uploadId: 866910
@@ -0,0 +1,61 @@
#if UNITY_EDITOR
using System;
using FishNet.Configuring;
using FishNet.Configuring.EditorCloning;
using FishNet.Managing;
using UnityEditor;
using UnityEngine;
namespace FishNet.Editing
{
/// <summary>
/// Contributed by YarnCat! Thank you!
/// </summary>
[InitializeOnLoad]
public class DelayedEditorTasks : EditorWindow
{
private static double _startTime = double.MinValue;
static DelayedEditorTasks()
{
if (CloneChecker.IsMultiplayerClone(out _))
return;
const string startupCheckString = "FishNetDelayedEditorTasks";
if (SessionState.GetBool(startupCheckString, false))
return;
_startTime = EditorApplication.timeSinceStartup;
EditorApplication.update += CheckRunTasks;
SessionState.SetBool(startupCheckString, true);
}
private static void CheckRunTasks()
{
if (EditorApplication.timeSinceStartup - _startTime < 1f)
return;
EditorApplication.update -= CheckRunTasks;
LogFeedbackLink();
// First time use, no other actions should be done.
if (FishNetGettingStartedEditor.ShowGettingStarted())
return;
ReviewReminderEditor.CheckRemindToReview();
}
private static void LogFeedbackLink()
{
// Only log the link when editor opens.
if (Time.realtimeSinceStartup < 10f)
{
string msg = $"Thank you for using Fish-Networking! If you have any feedback -- be suggestions, documentation, or performance related, let us know through our anonymous Google feedback form!{Environment.NewLine}" + @"<color=#67d419>https://forms.gle/1g13VY4KKMnEqpkp6</color>";
Debug.Log(msg);
}
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: eb3b5223de134ee41bc1ad462ce897f2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Configuring/DelayedEditorTasks.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 14146fca20e58f94987416948b38d79d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,91 @@
using UnityEngine;
namespace FishNet.Configuring.EditorCloning
{
public static class CloneChecker
{
/// <summary>
/// Returns true if this editor is a multiplayer clone.
/// </summary>
/// <returns></returns>
public static bool IsMultiplayerClone(out EditorCloneType editorCloneType)
{
if (IsUnityMultiplayerModeClone())
{
editorCloneType = EditorCloneType.UnityMultiplayer;
return true;
}
if (IsParrelSyncClone())
{
editorCloneType = EditorCloneType.ParrelSync;
return true;
}
editorCloneType = EditorCloneType.None;
return false;
}
/// <summary>
/// Returns true if ParrelSync clone with file modification enabled, or if not a clone.
/// </summary>
/// <returns></returns>
public static bool CanGenerateFiles()
{
//Not a clone.
if (!IsMultiplayerClone(out EditorCloneType cloneType))
return true;
//A clone, but not parrelsync.
if (cloneType != EditorCloneType.ParrelSync)
return false;
return CanParrelSyncSetData();
}
/// <summary>
/// Uses preprocessors to determine if ParrelSync and can set data.
/// </summary>
/// <returns></returns>
private static bool CanParrelSyncSetData()
{
#if PARRELSYNC && UNITY_EDITOR
bool areSetsBlocked = ParrelSync.Preferences.AssetModPref.Value;
return !areSetsBlocked;
#else
return false;
#endif
}
/// <summary>
/// Returns true if is a ParrelSync clone.
/// </summary>
public static bool IsParrelSyncClone()
{
#if PARRELSYNC && UNITY_EDITOR
return ParrelSync.ClonesManager.IsClone();
#else
return false;
#endif
}
/// <summary>
/// Returns true if a Unity MultiplayerMode clone.
/// </summary>
/// <returns></returns>
public static bool IsUnityMultiplayerModeClone()
{
return Application.dataPath.ToLower().Contains("library/vp/");
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: f63e9ae39a4fa65409189b5d587c5197
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Configuring/EditorCloning/CloneChecker.cs
uploadId: 866910
@@ -0,0 +1,9 @@
namespace FishNet.Configuring.EditorCloning
{
public enum EditorCloneType
{
None = 0,
UnityMultiplayer = 1,
ParrelSync = 2,
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 9ef534ed79b320b4c8961e3666159de1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Configuring/EditorCloning/EditorCloneType.cs
uploadId: 866910
@@ -0,0 +1,137 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace FishNet.Editing
{
/// <summary>
/// Contributed by YarnCat! Thank you!
/// </summary>
public class FishNetGettingStartedEditor : EditorWindow
{
private Texture2D _fishnetLogo, _reviewButtonBg, _reviewButtonBgHover;
private GUIStyle _labelStyle, _reviewButtonStyle;
private const string SHOWED_GETTING_STARTED = "ShowedFishNetGettingStarted";
[MenuItem("Tools/Fish-Networking/Getting Started", isValidateFunction: false, 9999)]
public static void GettingStartedMenu()
{
FishNetGettingStartedEditor window = (FishNetGettingStartedEditor)GetWindow(typeof(FishNetGettingStartedEditor));
window.position = new(0, 0, 320, 355);
Rect mainPos;
mainPos = EditorGUIUtility.GetMainWindowPosition();
Rect pos = window.position;
float w = (mainPos.width - pos.width) * 0.5f;
float h = (mainPos.height - pos.height) * 0.5f;
pos.x = mainPos.x + w;
pos.y = mainPos.y + h;
window.position = pos;
window._fishnetLogo = (Texture2D)AssetDatabase.LoadAssetAtPath("Assets/FishNet/Runtime/Editor/Textures/UI/Logo_With_Text.png", typeof(Texture));
window._labelStyle = new("label");
window._labelStyle.fontSize = 24;
window._labelStyle.wordWrap = true;
// window.labelStyle.alignment = TextAnchor.MiddleCenter;
window._labelStyle.normal.textColor = new Color32(74, 195, 255, 255);
window._reviewButtonBg = MakeBackgroundTexture(1, 1, new Color32(52, 111, 255, 255));
window._reviewButtonBgHover = MakeBackgroundTexture(1, 1, new Color32(99, 153, 255, 255));
window._reviewButtonStyle = new("button");
window._reviewButtonStyle.fontSize = 18;
window._reviewButtonStyle.fontStyle = FontStyle.Bold;
window._reviewButtonStyle.normal.background = window._reviewButtonBg;
window._reviewButtonStyle.active.background = window._reviewButtonBgHover;
window._reviewButtonStyle.focused.background = window._reviewButtonBgHover;
window._reviewButtonStyle.onFocused.background = window._reviewButtonBgHover;
window._reviewButtonStyle.hover.background = window._reviewButtonBgHover;
window._reviewButtonStyle.onHover.background = window._reviewButtonBgHover;
window._reviewButtonStyle.alignment = TextAnchor.MiddleCenter;
window._reviewButtonStyle.normal.textColor = new(1, 1, 1, 1);
}
internal static bool ShowGettingStarted()
{
bool shown = EditorPrefs.GetBool(SHOWED_GETTING_STARTED, false);
if (!shown)
{
EditorPrefs.SetBool(SHOWED_GETTING_STARTED, true);
ReviewReminderEditor.ResetDateTimeReminded();
GettingStartedMenu();
return true;
}
return false;
}
private void OnGUI()
{
GUILayout.Box(_fishnetLogo, GUILayout.Width(position.width), GUILayout.Height(128));
GUILayout.Space(20);
GUILayout.Label("Have you considered leaving us a review?", _labelStyle, GUILayout.Width(280));
GUILayout.Space(10);
if (GUILayout.Button("Leave us a review!", _reviewButtonStyle))
{
Application.OpenURL("https://assetstore.unity.com/packages/tools/network/fish-net-networking-evolved-207815");
}
GUILayout.Space(20);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Documentation", GUILayout.Width(position.width * 0.485f)))
{
Application.OpenURL("https://fish-networking.gitbook.io/docs/");
}
if (GUILayout.Button("Discord", GUILayout.Width(position.width * 0.485f)))
{
Application.OpenURL("https://discord.gg/Ta9HgDh4Hj");
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("FishNet Pro", GUILayout.Width(position.width * 0.485f)))
{
Application.OpenURL("https://fish-networking.gitbook.io/docs/master/pro");
}
if (GUILayout.Button("Github", GUILayout.Width(position.width * 0.485f)))
{
Application.OpenURL("https://github.com/FirstGearGames/FishNet");
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Pro Downloads", GUILayout.Width(position.width * 0.485f)))
{
Application.OpenURL("https://www.fish-networking.com/");
}
// if (GUILayout.Button("Examples", GUILayout.Width(this.position.width * 0.485f)))
// {
// Application.OpenURL("https://fish-networking.gitbook.io/docs/manual/tutorials/example-projects");
//}
EditorGUILayout.EndHorizontal();
//GUILayout.Space(20);
//_showOnStartupSelected = EditorGUILayout.Popup("Show on Startup", _showOnStartupSelected, showOnStartupOptions);
}
//private string[] showOnStartupOptions = new string[] { "Always", "On new version", "Never", };
//private int _showOnStartupSelected = 1;
private static Texture2D MakeBackgroundTexture(int width, int height, Color color)
{
Color[] pixels = new Color[width * height];
for (int i = 0; i < pixels.Length; i++)
pixels[i] = color;
Texture2D backgroundTexture = new(width, height);
backgroundTexture.SetPixels(pixels);
backgroundTexture.Apply();
return backgroundTexture;
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 335aec9a9dce4944994cb57ac704ba5a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Configuring/FIshNetGettingStartedEditor.cs
uploadId: 866910
@@ -0,0 +1,458 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using System.Linq;
using FishNet.Editing.PrefabCollectionGenerator;
using FishNet.Object;
using FishNet.Utility.Extension;
using GameKit.Dependencies.Utilities;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityScene = UnityEngine.SceneManagement.Scene;
using UnitySceneManagement = UnityEngine.SceneManagement;
namespace FishNet.Editing
{
/// <summary>
/// Contributed by YarnCat! Thank you!
/// </summary>
public class ReserializeNetworkObjectsEditor : EditorWindow
{
/// <summary>
/// True if currently iterating.
/// </summary>
[System.NonSerialized]
internal static bool IsRunning;
private enum ReserializeSceneType : int
{
AllScenes = 0,
OpenScenes = 1,
SelectedScenes = 2,
BuildScenes = 3
}
private struct OpenScene
{
public UnityScene Scene;
public string Path;
public OpenScene(UnityScene scene)
{
Scene = scene;
Path = scene.path;
}
}
private Texture2D _fishnetLogo;
private Texture2D _buttonBg;
private Texture2D _buttonBgHover;
private GUIStyle _upgradeRequiredStyle;
private GUIStyle _instructionsStyle;
private GUIStyle _buttonStyle;
private bool _loaded;
private bool _iteratePrefabs;
private bool _iterateScenes;
private ReserializeSceneType _sceneReserializeType = ReserializeSceneType.OpenScenes;
private bool _enabledOnlyBuildScenes = true;
private const string UPGRADE_PART_COLOR = "cd61ff";
private const string UPGRADE_COMPLETE_COLOR = "32e66e";
private const string PREFS_PREFIX = "FishNetReserialize";
private static ReserializeNetworkObjectsEditor _window;
[MenuItem("Tools/Fish-Networking/Utility/Reserialize NetworkObjects", false, 400)]
internal static void ReserializeNetworkObjects()
{
if (ApplicationState.IsPlaying())
{
Debug.LogError($"NetworkObjects cannot be reserialized while in play mode.");
return;
}
InitializeWindow();
}
private static void InitializeWindow()
{
if (_window != null)
return;
_window = (ReserializeNetworkObjectsEditor)GetWindow(typeof(ReserializeNetworkObjectsEditor));
_window.position = new(0f, 0f, 550f, 300f);
Rect mainPos;
mainPos = EditorGUIUtility.GetMainWindowPosition();
Rect pos = _window.position;
float w = (mainPos.width - pos.width) * 0.5f;
float h = (mainPos.height - pos.height) * 0.5f;
pos.x = mainPos.x + w;
pos.y = mainPos.y + h;
_window.position = pos;
}
private static void StyleWindow()
{
if (_window == null)
return;
_window._fishnetLogo = (Texture2D)AssetDatabase.LoadAssetAtPath("Assets/FishNet/Runtime/Editor/Textures/UI/Logo_With_Text.png", typeof(Texture));
_window._upgradeRequiredStyle = new("label");
_window._upgradeRequiredStyle.fontSize = 20;
_window._upgradeRequiredStyle.wordWrap = true;
_window._upgradeRequiredStyle.alignment = TextAnchor.MiddleCenter;
_window._upgradeRequiredStyle.normal.textColor = new Color32(255, 102, 102, 255);
_window._instructionsStyle = new("label");
_window._instructionsStyle.fontSize = 14;
_window._instructionsStyle.wordWrap = true;
_window._instructionsStyle.alignment = TextAnchor.MiddleCenter;
_window._instructionsStyle.normal.textColor = new Color32(255, 255, 255, 255);
_window._instructionsStyle.hover.textColor = new Color32(255, 255, 255, 255);
_window._buttonBg = MakeBackgroundTexture(1, 1, new Color32(52, 111, 255, 255));
_window._buttonBgHover = MakeBackgroundTexture(1, 1, new Color32(99, 153, 255, 255));
_window._buttonStyle = new("button");
_window._buttonStyle.fontSize = 18;
_window._buttonStyle.fontStyle = FontStyle.Bold;
_window._buttonStyle.normal.background = _window._buttonBg;
_window._buttonStyle.active.background = _window._buttonBgHover;
_window._buttonStyle.focused.background = _window._buttonBgHover;
_window._buttonStyle.onFocused.background = _window._buttonBgHover;
_window._buttonStyle.hover.background = _window._buttonBgHover;
_window._buttonStyle.onHover.background = _window._buttonBgHover;
_window._buttonStyle.alignment = TextAnchor.MiddleCenter;
_window._buttonStyle.normal.textColor = new(1, 1, 1, 1);
}
private void OnGUI()
{
// If not yet loaded then set last used values.
if (!_loaded)
{
LoadLastValues();
_loaded = true;
}
float thisWidth = position.width;
StyleWindow();
// Starting values.
Vector2 requiredSize = new(position.width, 160f);
GUILayout.Box(_fishnetLogo, GUILayout.Width(requiredSize.x), GUILayout.Height(requiredSize.y));
GUILayout.Space(8f);
EditorGUILayout.BeginHorizontal();
GUILayout.Space(5f);
CreateInformationLabel("Use this window to refresh serialized values on all NetworkObject prefabs and scene NetworkObjects.");
EditorGUILayout.EndHorizontal();
GUILayout.Space(8f);
EditorGUILayout.BeginHorizontal();
GUILayout.Space(30f);
_iteratePrefabs = EditorGUILayout.Toggle("Reserialize Prefabs", _iteratePrefabs);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
// Some dumb reason Unity moves the checkbox further when using nested settings.
float rebuildScenesSpacing = _iterateScenes ? 27f : 30f;
GUILayout.Space(rebuildScenesSpacing);
EditorGUILayout.BeginVertical();
_iterateScenes = EditorGUILayout.Toggle("Reserialize Scenes", _iterateScenes);
if (_iterateScenes)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Space(15f);
_sceneReserializeType = (ReserializeSceneType)EditorGUILayout.EnumPopup("Targeted Scenes", _sceneReserializeType);
EditorGUILayout.EndHorizontal();
requiredSize.y += 20f;
if (_sceneReserializeType == ReserializeSceneType.BuildScenes)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Space(30f);
_enabledOnlyBuildScenes = EditorGUILayout.Toggle("Enabled Only", _enabledOnlyBuildScenes);
EditorGUILayout.EndHorizontal();
requiredSize.y += 18f;
}
if (_sceneReserializeType != ReserializeSceneType.OpenScenes)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Space(30f);
EditorGUILayout.HelpBox("This operation will open and close targeted scene one at a time. Your current open scenes will be closed and re-opened without saving.", MessageType.Warning);
EditorGUILayout.EndHorizontal();
requiredSize.y += 40f;
}
}
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
requiredSize.y += 80f;
GUILayout.Space(8f);
EditorGUILayout.BeginHorizontal();
if (!_iteratePrefabs && !_iterateScenes)
GUI.enabled = false;
if (GUILayout.Button("Run Task"))
{
IsRunning = true;
SaveLastValues();
ReserializeProjectPrefabs();
ReserializeScenes();
LogColoredText($"Task complete.", UPGRADE_COMPLETE_COLOR);
_iteratePrefabs = false;
_iterateScenes = false;
IsRunning = false;
}
GUI.enabled = true;
EditorGUILayout.EndHorizontal();
minSize = requiredSize;
maxSize = minSize;
void CreateInformationLabel(string text, FontStyle? style = null)
{
EditorGUILayout.BeginHorizontal();
FontStyle firstStyle = _instructionsStyle.fontStyle;
if (style != null)
_instructionsStyle.fontStyle = style.Value;
GUILayout.Label(text, _instructionsStyle, GUILayout.Width(thisWidth * 0.95f));
_instructionsStyle.fontStyle = firstStyle;
EditorGUILayout.EndHorizontal();
requiredSize.y += 55f;
}
}
private void LoadLastValues()
{
_iteratePrefabs = EditorPrefs.GetBool($"{PREFS_PREFIX}{nameof(_iteratePrefabs)}", defaultValue: false);
_iterateScenes = EditorPrefs.GetBool($"{PREFS_PREFIX}{nameof(_iterateScenes)}", defaultValue: false);
_sceneReserializeType = (ReserializeSceneType)EditorPrefs.GetInt($"{PREFS_PREFIX}{nameof(_sceneReserializeType)}", defaultValue: (int)ReserializeSceneType.OpenScenes);
_enabledOnlyBuildScenes = EditorPrefs.GetBool($"{PREFS_PREFIX}{nameof(_enabledOnlyBuildScenes)}", defaultValue: true);
}
private void SaveLastValues()
{
EditorPrefs.SetBool($"{PREFS_PREFIX}{nameof(_iteratePrefabs)}", _iteratePrefabs);
EditorPrefs.SetBool($"{PREFS_PREFIX}{nameof(_iterateScenes)}", _iterateScenes);
EditorPrefs.SetInt($"{PREFS_PREFIX}{nameof(_sceneReserializeType)}", (int)_sceneReserializeType);
EditorPrefs.SetBool($"{PREFS_PREFIX}{nameof(_enabledOnlyBuildScenes)}", _enabledOnlyBuildScenes);
}
private void ReserializeProjectPrefabs()
{
if (!_iteratePrefabs)
return;
int checkedObjects = 0;
int duplicateNetworkObjectsRemoved = 0;
bool modified = false;
List<NetworkObject> networkObjects = Generator.GetNetworkObjects(settings: null);
foreach (NetworkObject nob in networkObjects)
{
checkedObjects++;
duplicateNetworkObjectsRemoved += nob.RemoveDuplicateNetworkObjects();
nob.ReserializeEditorSetValues(setWasActiveDuringEdit: true, setSceneId: false);
EditorUtility.SetDirty(nob);
modified = true;
}
if (modified)
AssetDatabase.SaveAssets();
Debug.Log($"Reserialized {checkedObjects} NetworkObject prefabs. Removed {duplicateNetworkObjectsRemoved} duplicate NetworkObject components.");
}
private void ReserializeScenes()
{
if (!_iterateScenes)
return;
int duplicateNetworkObjectsRemoved = 0;
int checkedObjects = 0;
int checkedScenes = 0;
int changedObjects = 0;
List<OpenScene> openScenes = GetOpenScenes();
// If running for open scenes only.
if (_sceneReserializeType == ReserializeSceneType.OpenScenes)
{
ReserializeScenes(openScenes, ref checkedScenes, ref checkedObjects, ref changedObjects, ref duplicateNetworkObjectsRemoved);
}
// Running on multiple scenes.
else
{
// When working on multiple scenes make sure open scenes are not dirty to prevent data loss.
foreach (OpenScene os in openScenes)
{
if (os.Scene.isDirty)
{
Debug.LogError($"One or more open scenes are dirty. To prevent data loss scene reserialization will not complete. Ensure all open scenes are saved before continuing.");
return;
}
}
List<SceneAsset> targetedScenes;
if (_sceneReserializeType == ReserializeSceneType.SelectedScenes)
{
targetedScenes = Selection.GetFiltered<SceneAsset>(SelectionMode.Assets).ToList();
}
else if (_sceneReserializeType == ReserializeSceneType.AllScenes)
{
targetedScenes = new();
string[] scenePaths = Generator.GetProjectFiles("Assets", "unity", new(), recursive: true);
foreach (string path in scenePaths)
{
SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(path);
if (sceneAsset != null)
targetedScenes.Add(sceneAsset);
}
}
else if (_sceneReserializeType == ReserializeSceneType.BuildScenes)
{
targetedScenes = new();
EditorBuildSettingsScene[] buildScenes = EditorBuildSettings.scenes;
foreach (EditorBuildSettingsScene bs in buildScenes)
{
if (_enabledOnlyBuildScenes && !bs.enabled)
continue;
SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(bs.path);
if (sceneAsset != null)
targetedScenes.Add(sceneAsset);
}
}
else
{
Debug.LogError($"Unsupported {nameof(ReserializeSceneType)} type {_sceneReserializeType}.");
return;
}
ReserializeScenes(targetedScenes, ref checkedScenes, ref checkedObjects, ref changedObjects, ref duplicateNetworkObjectsRemoved);
// Reopen original scenes.
for (int i = 0; i < openScenes.Count; i++)
{
string path = openScenes[i].Path;
/* Make sure asset exists before trying to reopen scene.
* Its possible the dev had a scene open that wasn't saved, which
* would otherwise result in an error here. */
SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(path);
if (sceneAsset != null)
{
OpenSceneMode mode = i == 0 ? OpenSceneMode.Single : OpenSceneMode.Additive;
EditorSceneManager.OpenScene(path, mode);
}
}
}
if (changedObjects > 0)
AssetDatabase.SaveAssets();
string saveText = _sceneReserializeType == ReserializeSceneType.OpenScenes && changedObjects > 0 ? " Please save your open scenes." : string.Empty;
Debug.Log($"Checked {checkedObjects} NetworkObjects over {checkedScenes} scenes. {changedObjects} sceneIds were generated. {duplicateNetworkObjectsRemoved} duplicate NetworkObject components were removed. {saveText}");
LogColoredText($"Scene NetworkObjects refreshed.", UPGRADE_PART_COLOR);
List<OpenScene> GetOpenScenes()
{
List<OpenScene> result = new();
int sceneCount = UnitySceneManagement.SceneManager.sceneCount;
for (int i = 0; i < sceneCount; i++)
{
UnityScene scene = UnitySceneManagement.SceneManager.GetSceneAt(i);
if (scene.isLoaded)
result.Add(new(scene));
}
return result;
}
}
/// <summary>
/// Refreshes NetworkObjects for specified scenes.
/// </summary>
private static void ReserializeScenes(List<SceneAsset> sceneAssets, ref int checkedScenes, ref int checkedObjects, ref int changedObjects, ref int duplicateNetworkObjectsRemoved)
{
foreach (SceneAsset sa in sceneAssets)
{
string path = AssetDatabase.GetAssetPath(sa);
UnityScene scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Single);
List<NetworkObject> foundNobs = NetworkObject.CreateSceneId(scene, force: true, out int changed);
foreach (NetworkObject n in foundNobs)
{
duplicateNetworkObjectsRemoved += n.RemoveDuplicateNetworkObjects();
n.ReserializeEditorSetValues(setWasActiveDuringEdit: true, setSceneId: false);
}
EditorSceneManager.SaveScene(scene);
checkedScenes++;
checkedObjects += foundNobs.Count;
changedObjects += changed;
}
}
/// <summary>
/// Refreshes NetworkObjects in OpenScenes.
/// </summary>
private static void ReserializeScenes(List<OpenScene> openScenes, ref int checkedScenes, ref int checkedObjects, ref int changedObjects, ref int duplicateNetworkObjectsRemoved)
{
foreach (OpenScene os in openScenes)
{
List<NetworkObject> foundNobs = NetworkObject.CreateSceneId(os.Scene, force: true, out int changed);
foreach (NetworkObject n in foundNobs)
{
duplicateNetworkObjectsRemoved += n.RemoveDuplicateNetworkObjects();
n.ReserializeEditorSetValues(setWasActiveDuringEdit: true, setSceneId: false);
}
checkedScenes++;
checkedObjects += foundNobs.Count;
changedObjects += changed;
}
}
private static void LogColoredText(string txt, string hexColor)
{
Debug.Log($"<color=#{hexColor}>{txt}</color>");
}
private static Texture2D MakeBackgroundTexture(int width, int height, Color color)
{
Color[] pixels = new Color[width * height];
for (int i = 0; i < pixels.Length; i++)
pixels[i] = color;
Texture2D backgroundTexture = new(width, height);
backgroundTexture.SetPixels(pixels);
backgroundTexture.Apply();
return backgroundTexture;
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: d0f2e650746c37949a9dd7b96e7c6f65
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Configuring/ReserializeNetworkObjectsEditor.cs
uploadId: 866910
@@ -0,0 +1,165 @@
#if UNITY_EDITOR
using System;
using FishNet.Configuring;
using UnityEditor;
using UnityEngine;
namespace FishNet.Editing
{
/// <summary>
/// Contributed by YarnCat! Thank you!
/// </summary>
public class ReviewReminderEditor : EditorWindow
{
private Texture2D _fishnetLogo, _reviewButtonBg, _reviewButtonBgHover;
private GUIStyle _labelStyle, _reviewButtonStyle;
private const string DATETIME_REMINDED = "ReviewDateTimeReminded";
private const string CHECK_REMIND_COUNT = "CheckRemindCount";
private const string IS_ENABLED = "ReminderEnabled";
private static ReviewReminderEditor _window;
internal static void CheckRemindToReview()
{
bool reminderEnabled = EditorPrefs.GetBool(IS_ENABLED, true);
if (!reminderEnabled)
return;
/* Require at least two opens and 10 days
* to be passed before reminding. */
int checkRemindCount = EditorPrefs.GetInt(CHECK_REMIND_COUNT, 0) + 1;
EditorPrefs.SetInt(CHECK_REMIND_COUNT, checkRemindCount);
// Not enough checks.
if (checkRemindCount < 2)
return;
string dtStr = EditorPrefs.GetString(DATETIME_REMINDED, string.Empty);
// Somehow got cleared. Reset.
if (string.IsNullOrWhiteSpace(dtStr))
{
ResetDateTimeReminded();
return;
}
long binary;
//Failed to parse.
if (!long.TryParse(dtStr, out binary))
{
ResetDateTimeReminded();
return;
}
//Not enough time passed.
DateTime dt = DateTime.FromBinary(binary);
if ((DateTime.Now - dt).TotalDays < 10)
return;
//If here then the reminder can be shown.
EditorPrefs.SetInt(CHECK_REMIND_COUNT, 0);
ResetDateTimeReminded();
ShowReminder();
}
internal static void ResetDateTimeReminded()
{
EditorPrefs.SetString(DATETIME_REMINDED, DateTime.Now.ToBinary().ToString());
}
private static void ShowReminder()
{
InitializeWindow();
}
private static void InitializeWindow()
{
if (_window != null)
return;
_window = (ReviewReminderEditor)GetWindow(typeof(ReviewReminderEditor));
_window.position = new(0f, 0f, 320f, 300f);
Rect mainPos;
mainPos = EditorGUIUtility.GetMainWindowPosition();
Rect pos = _window.position;
float w = (mainPos.width - pos.width) * 0.5f;
float h = (mainPos.height - pos.height) * 0.5f;
pos.x = mainPos.x + w;
pos.y = mainPos.y + h;
_window.position = pos;
}
private static void StyleWindow()
{
InitializeWindow();
_window._fishnetLogo = (Texture2D)AssetDatabase.LoadAssetAtPath("Assets/FishNet/Runtime/Editor/Textures/UI/Logo_With_Text.png", typeof(Texture));
_window._labelStyle = new("label");
_window._labelStyle.fontSize = 24;
_window._labelStyle.wordWrap = true;
//window.labelStyle.alignment = TextAnchor.MiddleCenter;
_window._labelStyle.normal.textColor = new Color32(74, 195, 255, 255);
_window._reviewButtonBg = MakeBackgroundTexture(1, 1, new Color32(52, 111, 255, 255));
_window._reviewButtonBgHover = MakeBackgroundTexture(1, 1, new Color32(99, 153, 255, 255));
_window._reviewButtonStyle = new("button");
_window._reviewButtonStyle.fontSize = 18;
_window._reviewButtonStyle.fontStyle = FontStyle.Bold;
_window._reviewButtonStyle.normal.background = _window._reviewButtonBg;
_window._reviewButtonStyle.active.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.focused.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.onFocused.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.hover.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.onHover.background = _window._reviewButtonBgHover;
_window._reviewButtonStyle.alignment = TextAnchor.MiddleCenter;
_window._reviewButtonStyle.normal.textColor = new(1, 1, 1, 1);
}
private void OnGUI()
{
float thisWidth = position.width;
StyleWindow();
GUILayout.Box(_fishnetLogo, GUILayout.Width(position.width), GUILayout.Height(160f));
EditorGUILayout.BeginHorizontal();
GUILayout.Space(8f);
GUILayout.Label("Have you considered leaving us a review?", _labelStyle, GUILayout.Width(thisWidth * 0.95f));
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Don't Ask Again", GUILayout.Width(position.width)))
{
Close();
EditorPrefs.SetBool(IS_ENABLED, false);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Ask Later", GUILayout.Width(position.width)))
{
Close();
//Application.OpenURL("https://discord.gg/Ta9HgDh4Hj");
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Leave A Review", GUILayout.Width(position.width)))
{
Close();
EditorPrefs.SetBool(IS_ENABLED, false);
Application.OpenURL("https://assetstore.unity.com/packages/tools/network/fish-net-networking-evolved-207815");
}
EditorGUILayout.EndHorizontal();
//GUILayout.Space(20);
//_showOnStartupSelected = EditorGUILayout.Popup("Show on Startup", _showOnStartupSelected, showOnStartupOptions);
}
private static Texture2D MakeBackgroundTexture(int width, int height, Color color)
{
Color[] pixels = new Color[width * height];
for (int i = 0; i < pixels.Length; i++)
pixels[i] = color;
Texture2D backgroundTexture = new(width, height);
backgroundTexture.SetPixels(pixels);
backgroundTexture.Apply();
return backgroundTexture;
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4260206b6a57e4243b56437f8f283084
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Configuring/ReviewReminderEditor.cs
uploadId: 866910
@@ -0,0 +1,85 @@
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using FishNet.Configuring;
using UnitySettingsProviderAttribute = UnityEditor.SettingsProviderAttribute;
using UnitySettingsProvider = UnityEditor.SettingsProvider;
using System.Collections.Generic;
namespace FishNet.Configuring.Editing
{
internal static class SettingsProvider
{
private static Vector2 _scrollView;
[UnitySettingsProvider]
private static UnitySettingsProvider Create()
{
return new("Project/Fish-Networking/Configuration", SettingsScope.Project)
{
label = "Configuration",
guiHandler = OnGUI,
keywords = new string[]
{
"Fish",
"Networking",
"Configuration"
}
};
}
private static void OnGUI(string searchContext)
{
ConfigurationData configuration = Configuration.LoadConfigurationData();
if (configuration == null)
{
EditorGUILayout.HelpBox("Unable to load configuration data.", MessageType.Error);
return;
}
EditorGUI.BeginChangeCheck();
GUIStyle scrollViewStyle = new()
{
padding = new(10, 10, 10, 10)
};
_scrollView = GUILayout.BeginScrollView(_scrollView, scrollViewStyle);
EditorGUILayout.BeginHorizontal();
GUIStyle toggleStyle = new(EditorStyles.toggle)
{
richText = true
};
configuration.CodeStripping.StripReleaseBuilds = GUILayout.Toggle(configuration.CodeStripping.StripReleaseBuilds, $"{ObjectNames.NicifyVariableName(nameof(configuration.CodeStripping.StripReleaseBuilds))} <color=yellow>(Pro Only)</color>", toggleStyle);
EditorGUILayout.EndHorizontal();
if (configuration.CodeStripping.StripReleaseBuilds)
{
EditorGUI.indentLevel++;
// Stripping Method.
List<string> enumStrings = new();
foreach (string item in System.Enum.GetNames(typeof(StrippingTypes)))
enumStrings.Add(item);
configuration.CodeStripping.StrippingType = EditorGUILayout.Popup($"{ObjectNames.NicifyVariableName(nameof(configuration.CodeStripping.StrippingType))}", (int)configuration.CodeStripping.StrippingType, enumStrings.ToArray());
EditorGUILayout.HelpBox("Development builds will not have code stripped. Additionally, if you plan to run as host disable code stripping.", MessageType.Warning);
EditorGUI.indentLevel--;
}
GUILayout.EndScrollView();
if (EditorGUI.EndChangeCheck())
Configuration.Configurations.Write(true);
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b3d7d3c45d53dea4e8a0a7da73d64021
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Configuring/SettingsProvider.cs
uploadId: 866910
@@ -0,0 +1,11 @@
using FishNet.Documenting;
namespace FishNet.Editing
{
[APIExclude]
public static class EditingConstants
{
public const string PRO_ASSETS_LOCKED_TEXT = "Fields marked with * are only active with Fish-Networking Pro.";
public const string PRO_ASSETS_UNLOCKED_TEXT = "Thank you for supporting Fish-Networking! Pro asset features are unlocked.";
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 1d2683b3becd2c5488c1f338972d49e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Constants.cs
uploadId: 866910
@@ -0,0 +1,2 @@
// Remove in V5
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 2bd002f6c85dd4341bcaf163eaaa3ddf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/DefaultPrefabsFinder.cs
uploadId: 866910
+198
View File
@@ -0,0 +1,198 @@
#if UNITY_EDITOR
using FishNet.Utility;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace FishNet.Editing
{
public static class Finding
{
#region Private.
/// <summary>
/// Path where the FishNet.Runtime assembly is.
/// </summary>
[System.NonSerialized]
private static string _fishNetRuntimePath = string.Empty;
/// <summary>
/// Path where the FishNet.Generated assembly is.
/// </summary>
private static string _fishNetGeneratedPath = string.Empty;
#endregion
/// <summary>
/// Sets FishNet assembly paths.
/// </summary>
/// <param name = "error"></param>
private static void UpdateFishNetPaths()
{
if (_fishNetGeneratedPath != string.Empty && _fishNetRuntimePath != string.Empty)
return;
string[] guids = AssetDatabase.FindAssets("t:asmdef", new string[] { "Assets" });
string[] objectPaths = new string[guids.Length];
for (int i = 0; i < guids.Length; i++)
objectPaths[i] = AssetDatabase.GUIDToAssetPath(guids[i]);
string runtimeName = (UtilityConstants.RUNTIME_ASSEMBLY_NAME + ".asmdef").ToLower();
string generatedName = (UtilityConstants.GENERATED_ASSEMBLY_NAME + ".asmdef").ToLower();
/* Find all network managers which use Single prefab linking
* as well all network object prefabs. */
foreach (string item in objectPaths)
{
// Found directory to create object in.
if (item.ToLower().Contains(runtimeName))
_fishNetRuntimePath = System.IO.Path.GetDirectoryName(item);
else if (item.ToLower().Contains(generatedName))
_fishNetGeneratedPath = System.IO.Path.GetDirectoryName(item);
if (_fishNetGeneratedPath != string.Empty && _fishNetRuntimePath != string.Empty)
return;
}
}
/// <summary>
/// Gets all GameObjects in Assets and optionally scenes.
/// </summary>
/// <typeparam name = "T"></typeparam>
/// <returns></returns>
public static List<GameObject> GetGameObjects(bool userAssemblies, bool fishNetAssembly, bool includeScenes, string[] ignoredPaths = null)
{
List<GameObject> results = new();
string[] guids;
string[] objectPaths;
UpdateFishNetPaths();
guids = AssetDatabase.FindAssets("t:GameObject", null);
objectPaths = new string[guids.Length];
for (int i = 0; i < guids.Length; i++)
objectPaths[i] = AssetDatabase.GUIDToAssetPath(guids[i]);
foreach (string item in objectPaths)
{
bool inFishNet = item.Contains(_fishNetRuntimePath);
if (inFishNet && !fishNetAssembly)
continue;
if (!inFishNet && !userAssemblies)
continue;
if (ignoredPaths != null)
{
bool ignore = false;
foreach (string path in ignoredPaths)
{
if (item.Contains(path))
{
ignore = true;
break;
}
}
if (ignore)
continue;
}
GameObject go = (GameObject)AssetDatabase.LoadAssetAtPath(item, typeof(GameObject));
results.Add(go);
}
if (includeScenes)
results.AddRange(GetSceneGameObjects());
return results;
}
/// <summary>
/// Gets all GameObjects in all open scenes.
/// </summary>
/// <returns></returns>
private static List<GameObject> GetSceneGameObjects()
{
List<GameObject> results = new();
for (int i = 0; i < SceneManager.sceneCount; i++)
results.AddRange(GetSceneGameObjects(SceneManager.GetSceneAt(i)));
return results;
}
/// <summary>
/// Gets all GameObjects in a scene.
/// </summary>
private static List<GameObject> GetSceneGameObjects(Scene s)
{
List<GameObject> results = new();
List<Transform> buffer = new();
// Iterate all root objects for the scene.
GameObject[] gos = s.GetRootGameObjects();
for (int i = 0; i < gos.Length; i++)
{
/* Get GameObjects within children of each
* root object then add them to the cache. */
gos[i].GetComponentsInChildren(true, buffer);
foreach (Transform t in buffer)
results.Add(t.gameObject);
}
return results;
}
/// <summary>
/// Gets created ScriptableObjects of T.
/// </summary>
/// <typeparam name = "T"></typeparam>
/// <returns></returns>
public static List<UnityEngine.Object> GetScriptableObjects<T>(bool fishNetAssembly, bool breakOnFirst = false)
{
System.Type tType = typeof(T);
List<UnityEngine.Object> results = new();
string[] guids = AssetDatabase.FindAssets("t:ScriptableObject", new string[] { "Assets" });
string[] objectPaths = new string[guids.Length];
for (int i = 0; i < guids.Length; i++)
objectPaths[i] = AssetDatabase.GUIDToAssetPath(guids[i]);
/* This might be faster than using directory comparers.
* Don't really care since this occurs only at edit. */
List<string> fishNetPaths = new();
fishNetPaths.Add(_fishNetGeneratedPath.Replace(@"/", @"\"));
fishNetPaths.Add(_fishNetGeneratedPath.Replace(@"\", @"/"));
fishNetPaths.Add(_fishNetRuntimePath.Replace(@"/", @"\"));
fishNetPaths.Add(_fishNetRuntimePath.Replace(@"\", @"/"));
/* Find all network managers which use Single prefab linking
* as well all network object prefabs. */
foreach (string item in objectPaths)
{
// This will skip hidden unity types.
if (!item.EndsWith(".asset"))
continue;
if (fishNetAssembly)
{
bool found = false;
foreach (string path in fishNetPaths)
{
if (item.Contains(path))
{
found = true;
break;
}
}
if (!found)
continue;
}
UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath(item, tType);
if (obj != null && tType != null && obj.GetType() == tType)
{
results.Add(obj);
if (breakOnFirst)
return results;
}
}
return results;
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b777233d19062274f9eec6a982d8ff37
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Finding.cs
uploadId: 866910
@@ -0,0 +1,57 @@
#if UNITY_EDITOR
using System;
using System.IO;
using UnityEditor;
using UnityEngine;
namespace FishNet.Editing
{
/* When you import playeveryware's EOS asset, it force installs NGO, which creates
* a lot of issues for anyone not using NGO. This script will block the force installation. */
public class ForceInstallPreventor : AssetPostprocessor
{
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
// No need to continue if nothing was imported.
if (importedAssets == null || importedAssets.Length == 0)
return;
EditorApplication.LockReloadAssemblies();
foreach (string path in importedAssets)
CheckTargetPath(path);
/* We don't have a way to know if the user intentionally
* had domain locked so we just have to unlock it and hope
* we aren't messing up users settings.
*
* Worse case scenario this will only happen when the forceware
* is removed.
*
* There is a 'didDomainReload' boolean override for this
* method, but it does not seem to reflect the information
* we need.
* */
EditorApplication.UnlockReloadAssemblies();
}
private void OnPreprocessAsset()
{
CheckTargetPath(assetImporter.assetPath);
}
private static void CheckTargetPath(string path)
{
if (!path.Contains("PackageInstallHelper_Netcode", StringComparison.CurrentCultureIgnoreCase))
return;
try
{
File.Delete(path);
Debug.Log($"Fish-Networking prevented PlayEveryWare from forcefully installing Netcode for GameObjects.");
}
finally { }
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7a0f9a4fe4875c54fa7820bc76908204
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/ForceInstallPreventor.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 085eef268cecafb4abf2e2810428f9f3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 9b35764a2e39a0844b01aa7e1b690c8d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/NetworkProfiler/NetworkProfilerWindow.cs
uploadId: 866910
@@ -0,0 +1,302 @@
using System.Collections.Generic;
using FishNet.Managing;
using FishNet.Managing.Statistic;
using FishNet.Managing.Timing;
using FishNet.Transporting;
using GameKit.Dependencies.Utilities;
using UnityEngine;
namespace FishNet.Editing
{
/// <summary>
/// Used to resize a window.
/// </summary>
internal struct WindowResizeData
{
public readonly Vector2 CursorStartPosition;
public readonly Vector2 WindowStartHeight;
public readonly bool IsValid;
public WindowResizeData(Vector2 cursorPosition, Vector2 windowHeight)
{
CursorStartPosition = cursorPosition;
WindowStartHeight = windowHeight;
IsValid = true;
}
}
/// <summary>
/// Used to store Inbound and Outbound traffic details.
/// </summary>
public class BidirectionalNetworkTraffic : IResettable
{
/// <summary>
/// Received traffic.
/// </summary>
internal NetworkTraffic InboundTraffic;
/// <summary>
/// Sent traffic.
/// </summary>
internal NetworkTraffic OutboundTraffic;
/// <summary>
/// Creates a clone of this class using cache.
/// </summary>
/// <returns></returns>
public BidirectionalNetworkTraffic CloneUsingCache()
{
if (InboundTraffic == null)
{
NetworkManagerExtensions.LogError($"One or more NetworkTraffic values is null. {nameof(BidirectionalNetworkTraffic)} cannot be cloned.");
return null;
}
BidirectionalNetworkTraffic traffic = ResettableObjectCaches<BidirectionalNetworkTraffic>.Retrieve();
traffic.InboundTraffic = InboundTraffic;
traffic.OutboundTraffic = OutboundTraffic;
return traffic;
}
/// <summary>
/// Re-initializes by calling ResetState, then InitializeState.
/// </summary>
public void Reinitialize()
{
ResetState();
InitializeState();
}
public void ResetState()
{
ResettableObjectCaches<NetworkTraffic>.StoreAndDefault(ref InboundTraffic);
ResettableObjectCaches<NetworkTraffic>.StoreAndDefault(ref OutboundTraffic);
}
public void InitializeState()
{
InboundTraffic = ResettableObjectCaches<NetworkTraffic>.Retrieve();
OutboundTraffic = ResettableObjectCaches<NetworkTraffic>.Retrieve();
}
}
internal class NetworkTraffic : IResettable
{
#region Types.
/// <summary>
/// Information about a single packet.
/// </summary>
public struct Packet
{
/// <summary>
/// Details about the packet, such as method or class name.
/// </summary>
/// <remarks>This may be empty.</remarks>
public string Details;
/// <summary>
/// Bytes used.
/// </summary>
public ulong Bytes;
/// <summary>
/// Originating GameObject.
/// </summary>
/// <remarks>GameObject is used rather than a script reference because we do not want to risk unintentionally holding a script in memory. Unity will automatically clean up GameObjects, so they are safe to reference.</remarks>
public GameObject GameObject;
public Packet(ulong bytes) : this(details: string.Empty, bytes, gameObject: null) { }
public Packet(string details, ulong bytes) : this(details, bytes, gameObject: null) { }
public Packet(ulong bytes, GameObject gameObject) : this(details: string.Empty, bytes, gameObject) { }
public Packet(string details, ulong bytes, GameObject gameObject)
{
Details = details;
Bytes = bytes;
GameObject = gameObject;
}
}
/// <summary>
/// Container for multiple Packets of the same type.
/// </summary>
public class PacketGroup : IResettable
{
/// <summary>
/// PacketId of this metric.
/// </summary>
public PacketId PacketId { get; private set; } = PacketId.Unset;
/// <summary>
/// Bytes of all packets using PacketId.
/// </summary>
public ulong Bytes { get; private set; }
/// <summary>
/// Percent Bytes is when compared against Bytes of other PacketMetrics.
/// </summary>
/// <remarks>This can only be completed after all Packet entries for each PacketId are added.</remarks>
public float Percent { get; private set; }
/// <summary>
/// True if PacketId is for unspecified packets.
/// </summary>
public bool IsUnspecifiedPacketId => PacketId == NetworkTrafficStatistics.UNSPECIFIED_PACKETID;
/// <summary>
/// Currently added packets.
/// </summary>
private List<Packet> _packets = new();
public void Initialize(PacketId packetId)
{
PacketId = packetId;
}
// public void Initialize(PacketId packetId, ulong bytes) => Initialize(packetId, details: string.Empty, bytes, gameObject: null);
// public void Initialize(PacketId packetId, ulong bytes, GameObject gameObject) => Initialize(packetId, details: string.Empty, bytes, gameObject);
// public void Initialize(PacketId packetId, string details, ulong bytes) => Initialize(packetId, details, bytes, gameObject: null);
// public void Initialize(PacketId packetId, string details, ulong bytes, GameObject gameObject)
// {
// PacketId = packetId;
//
// _packets.Add(new(details, bytes, gameObject));
// }
/// <summary>
/// Adds traffic from a specified packetId.
/// </summary>
public void AddPacket(string details, ulong bytes, GameObject gameObject)
{
Bytes += bytes;
_packets.Add(new(details, bytes, gameObject));
}
/// <summary>
/// Sets Percent using Bytes against allPacketGroupBytes.
/// </summary>
public void SetPercent(ulong allPacketGroupBytes)
{
//Prevent divide by 0.
if (Bytes == 0)
Percent = 0;
else
Percent = (float)Bytes / allPacketGroupBytes;
}
public void ResetState()
{
PacketId = PacketId.Unset;
Bytes = 0;
Percent = 0f;
_packets.Clear();
}
public void InitializeState() { }
}
#endregion
/// <summary>
/// PacketGroup for each PacketId processed.
/// </summary>
private Dictionary<PacketId, PacketGroup> _packetGroups;
/// <summary>
/// Total bytes for all packets.
/// </summary>
public ulong Bytes;
/// <summary>
/// Adds traffic from a specified packetId.
/// </summary>
public void AddPacketIdData(PacketId packetId, string details, ulong bytes, GameObject gameObject) => LAddPacketId(packetId, details, bytes, gameObject);
/// <summary>
/// Adds traffic from a specified packetId.
/// </summary>
public void AddSocketData(ulong bytes)
{
LAddPacketId(NetworkTrafficStatistics.UNSPECIFIED_PACKETID, details: string.Empty, bytes, gameObject: null);
Bytes += bytes;
}
/// <summary>
/// Adds traffic to a PackerGroup.
/// </summary>
private void LAddPacketId(PacketId packetId, string details, ulong bytes, GameObject gameObject)
{
if (!_packetGroups.TryGetValue(packetId, out PacketGroup packetGroup))
{
packetGroup = ResettableObjectCaches<PacketGroup>.Retrieve();
packetGroup.Initialize(packetId);
_packetGroups[packetId] = packetGroup;
}
packetGroup.AddPacket(details, bytes, gameObject);
}
/// <summary>
/// Calculates and sets Percentage value on each PacketGroup.
/// </summary>
/// <remarks>This should only be called after all PacketGroup entries have been created.</remarks>
public void SetPacketGroupPercentages()
{
//Field would probably get cached at runtime during iteration but let's be certain.
ulong bytes = Bytes;
foreach (PacketGroup pg in _packetGroups.Values)
pg.SetPercent(bytes);
}
public void ResetState()
{
Bytes = 0;
ResettableT2CollectionCaches<PacketId, PacketGroup>.StoreAndDefault(ref _packetGroups);
}
public void InitializeState()
{
_packetGroups = ResettableT2CollectionCaches<PacketId, PacketGroup>.RetrieveDictionary();
}
}
/// <summary>
/// Data for a profiled tick.
/// </summary>
internal class ProfiledTickData : IResettable
{
/// <summary>
/// Tick this is for.
/// </summary>
public uint Tick;
/// <summary>
/// Traffic collection for the server.
/// </summary>
public BidirectionalNetworkTraffic ServerTraffic;
/// <summary>
/// Traffic collection for the client.
/// </summary>
public BidirectionalNetworkTraffic ClientTraffic;
/// <summary>
/// Initializes and returns if successful.
/// </summary>
public bool TryInitialize(uint tick, BidirectionalNetworkTraffic serverTraffic, BidirectionalNetworkTraffic clientTraffic)
{
Tick = tick;
ServerTraffic = serverTraffic.CloneUsingCache();
ClientTraffic = clientTraffic.CloneUsingCache();
return ServerTraffic != null && ClientTraffic != null;
}
/// <summary>
/// Resets all values and stores to caches as needed.
/// </summary>
public void ResetState()
{
Tick = TimeManager.UNSET_TICK;
ResettableObjectCaches<BidirectionalNetworkTraffic>.StoreAndDefault(ref ServerTraffic);
ResettableObjectCaches<BidirectionalNetworkTraffic>.StoreAndDefault(ref ClientTraffic);
}
public void InitializeState() { }
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8e4a30af35ccc06478592b32c8f12540
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/NetworkProfiler/Types.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 564bc17539291c64683b5b20c06eda8b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,64 @@
#if UNITY_EDITOR
using FishNet.Configuring;
using System;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
using File = System.IO.File;
namespace FishNet.Editing.NewNetworkBehaviourScript
{
internal sealed class CreateNewNetworkBehaviour : MonoBehaviour
{
private const string TEMPLATE_CLASS_NAME = "NewNetworkBehaviourTemplate";
public static string TemplatePath => Path.Combine(Configuration.Configurations.CreateNewNetworkBehaviour.templateDirectoryPath, $"{TEMPLATE_CLASS_NAME}.txt");
[MenuItem("Assets/Create/FishNet/NetworkBehaviour Script", false, -220)]
private static void CreateNewAsset()
{
try
{
EnsureTemplateExists();
ProjectWindowUtil.CreateScriptAssetFromTemplateFile(TemplatePath, $"{TEMPLATE_CLASS_NAME}.cs");
}
catch (Exception e)
{
Debug.LogError($"An exception occurred while trying to copy the NetworkBehaviour template. {e.Message}");
}
}
public static void EnsureTemplateExists()
{
try
{
if (!File.Exists(TemplatePath))
{
string fileContent = GetNewTemplateText();
File.WriteAllText(TemplatePath, fileContent);
}
}
catch (Exception e)
{
Debug.LogError($"An exception occurred while trying to create the NetworkBehaviour template. {e.Message}");
}
}
private static string GetNewTemplateText()
{
StringBuilder sb = new();
sb.AppendLine("using UnityEngine;");
sb.AppendLine("using FishNet.Object;");
sb.AppendLine();
sb.AppendLine("public class NewNetworkBehaviourTemplate : NetworkBehaviour");
sb.AppendLine("{");
sb.AppendLine(" private void Awake() { }");
sb.AppendLine();
sb.AppendLine(" private void Update() { }");
sb.AppendLine("}");
return sb.ToString();
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: bd7764ab379082b4f9889c73b9133da9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/NewNetworkBehaviour/CreateNewNetworkBehaviour.cs
uploadId: 866910
@@ -0,0 +1,102 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using UnitySettingsProviderAttribute = UnityEditor.SettingsProviderAttribute;
using UnitySettingsProvider = UnityEditor.SettingsProvider;
using FishNet.Configuring;
using System.IO;
using System;
using System.Text.RegularExpressions;
namespace FishNet.Editing.NewNetworkBehaviourScript
{
internal static class SettingsProvider
{
private static CreateNewNetworkBehaviourConfigurations _settings;
private static GUIContent _folderIcon;
private static readonly Regex SlashRegex = new(@"[\\// ]");
[UnitySettingsProvider]
private static UnitySettingsProvider Create()
{
return new("Project/Fish-Networking/New NetworkBehaviour Template", SettingsScope.Project)
{
label = "New NetworkBehaviour Template",
guiHandler = OnGUI,
keywords = new string[]
{
"Fish",
"Networking",
"CreateNewNetworkBehaviour",
"Template"
}
};
}
private static void OnGUI(string searchContext)
{
if (_settings == null)
_settings = Configuration.Configurations.CreateNewNetworkBehaviour;
if (_folderIcon == null)
_folderIcon = EditorGUIUtility.IconContent("d_FolderOpened Icon");
EditorGUI.BeginChangeCheck();
GUILayoutOption iconWidthConstraint = GUILayout.MaxWidth(32.0f);
GUILayoutOption iconHeightConstraint = GUILayout.MaxHeight(EditorGUIUtility.singleLineHeight);
if (GUILayout.Button("Edit template"))
{
CreateNewNetworkBehaviour.EnsureTemplateExists();
try
{
System.Diagnostics.Process.Start(CreateNewNetworkBehaviour.TemplatePath);
}
catch (Exception e)
{
Debug.LogError($"An issue occurred while trying to launch the NetworkBehaviour template. {e.Message}");
}
}
GUILayout.Space(20);
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Template directory: ", GUILayout.MaxWidth(150));
string newDirectoryPath;
newDirectoryPath = EditorGUILayout.DelayedTextField(_settings.templateDirectoryPath, GUILayout.MaxWidth(600));
if (newDirectoryPath.StartsWith("Assets") && Directory.Exists(newDirectoryPath))
{
_settings.templateDirectoryPath = newDirectoryPath;
}
else
{
EditorWindow.focusedWindow.ShowNotification(new($"Directory must be inside the Assets folder."), 2);
}
if (GUILayout.Button(_folderIcon, iconHeightConstraint, iconWidthConstraint))
{
newDirectoryPath = EditorUtility.OpenFolderPanel("Select template directory", _settings.templateDirectoryPath, "");
}
if (newDirectoryPath.StartsWith(Application.dataPath, StringComparison.OrdinalIgnoreCase))
{
newDirectoryPath = SlashRegex.Replace(newDirectoryPath.Remove(0, Path.GetDirectoryName(Application.dataPath).Length + 1), Path.DirectorySeparatorChar.ToString());
_settings.templateDirectoryPath = newDirectoryPath;
}
else if (!newDirectoryPath.StartsWith(Application.dataPath, StringComparison.OrdinalIgnoreCase) && !newDirectoryPath.StartsWith("Assets"))
{
EditorWindow.focusedWindow.ShowNotification(new($"Directory must be inside the Assets folder."), 2);
}
EditorGUILayout.EndHorizontal();
// EditorGUILayout.HelpBox("By default MonoBehaviour script template will be copied", MessageType.Info);
if (EditorGUI.EndChangeCheck())
Configuration.Configurations.Write(true);
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ec84d908960b66b448f94582e747689b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/NewNetworkBehaviour/SettingsProvider.cs
uploadId: 866910
@@ -0,0 +1,49 @@
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
namespace FishNet.Editing
{
[InitializeOnLoad]
public class PlayModeTracker
{
static PlayModeTracker()
{
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
~PlayModeTracker()
{
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
}
/// <summary>
/// DateTime when the editor last exited playmode.
/// </summary>
private static DateTime _quitTime = DateTime.MaxValue;
/// <summary>
/// True if the editor has exited playmode within past.
/// </summary>
/// <param name = "past"></param>
/// <returns></returns>
internal static bool QuitRecently(float past)
{
past *= 1000;
return (DateTime.Now - _quitTime).TotalMilliseconds < past;
}
private static void OnPlayModeStateChanged(PlayModeStateChange stateChange)
{
switch (stateChange)
{
case PlayModeStateChange.ExitingPlayMode:
_quitTime = DateTime.Now;
break;
}
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: d4a1d20c6a03a524ab21c7aebed106d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/PlayModeTracker.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a26a5fd0a07d6964faab3ddc4d7454ee
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,697 @@
#if UNITY_EDITOR
using System;
using FishNet.Configuring;
using FishNet.Managing;
using FishNet.Managing.Object;
using FishNet.Object;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using FishNet.Configuring.EditorCloning;
using UnityEditor;
using UnityEngine;
using UnityDebug = UnityEngine.Debug;
namespace FishNet.Editing.PrefabCollectionGenerator
{
internal sealed class Generator : AssetPostprocessor
{
public Generator()
{
if (!_subscribed)
{
_subscribed = true;
EditorApplication.update += OnEditorUpdate;
}
}
~Generator()
{
if (_subscribed)
{
_subscribed = false;
EditorApplication.update -= OnEditorUpdate;
}
}
#region Types.
internal readonly struct SpecifiedFolder
{
public readonly string Path;
public readonly bool Recursive;
public SpecifiedFolder(string path, bool recursive)
{
Path = path;
Recursive = recursive;
}
}
#endregion
#region Public.
/// <summary>
/// True to ignore post process changes.
/// </summary>
public static bool IgnorePostProcess = false;
#endregion
#region Private.
/// <summary>
/// Last asset to import when there was only one imported asset and no other changes.
/// </summary>
private static string _lastSingleImportedAsset = string.Empty;
/// <summary>
/// Cached DefaultPrefabObjects reference.
/// </summary>
private static DefaultPrefabObjects _cachedDefaultPrefabs;
/// <summary>
/// True to refresh prefabs next update.
/// </summary>
private static bool _retryRefreshDefaultPrefabs;
/// <summary>
/// True if already subscribed to EditorApplication.Update.
/// </summary>
private static bool _subscribed;
/// <summary>
/// True if ran once since editor started.
/// </summary>
[System.NonSerialized]
private static bool _ranOnce;
/// <summary>
/// Last paths of updated nobs during a changed update.
/// </summary>
[System.NonSerialized]
private static List<string> _lastUpdatedNamePaths = new();
/// <summary>
/// Last frame changed was updated.
/// </summary>
[System.NonSerialized]
private static int _lastUpdatedFrame = -1;
/// <summary>
/// Length of assets strings during the last update.
/// </summary>
[System.NonSerialized]
private static int _lastUpdatedLengths = -1;
#endregion
internal static string[] GetProjectFiles(string startingPath, string fileExtension, List<string> excludedPaths, bool recursive)
{
// starting path is excluded.
if (excludedPaths.Contains(startingPath))
return new string[0];
// Folders remaining to be iterated.
List<string> enumeratedCollection = new() { startingPath };
// Only check other directories if recursive.
if (recursive)
{
// Find all folders which aren't excluded.
for (int i = 0; i < enumeratedCollection.Count; i++)
{
string[] allFolders = Directory.GetDirectories(enumeratedCollection[i], "*", SearchOption.TopDirectoryOnly);
for (int z = 0; z < allFolders.Length; z++)
{
string current = allFolders[z];
// Not excluded.
if (!excludedPaths.Contains(current))
enumeratedCollection.Add(current);
}
}
}
// Valid prefab files.
List<string> results = new();
// Build files from folders.
int count = enumeratedCollection.Count;
for (int i = 0; i < count; i++)
{
string[] r = Directory.GetFiles(enumeratedCollection[i], "*.prefab", SearchOption.TopDirectoryOnly);
results.AddRange(r);
}
return results.ToArray();
}
/// <summary>
/// Returns a message to attach to logs if objects were dirtied.
/// </summary>
private static string GetDirtiedMessage(PrefabGeneratorConfigurations settings, bool dirtied)
{
if (!settings.SaveChanges && dirtied)
return " One or more NetworkObjects were dirtied. Please save your project.";
else
return string.Empty;
}
/// <summary>
/// Updates prefabs by using only changed information.
/// </summary>
public static void GenerateChanged(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, PrefabGeneratorConfigurations settings = null)
{
if (!CloneChecker.CanGenerateFiles())
{
UnityDebug.Log("Skipping prefab generation as clone settings does not allow it.");
return;
}
// Do not run if the reserializer is currently running.
if (ReserializeNetworkObjectsEditor.IsRunning)
return;
if (settings == null)
settings = Configuration.Configurations.PrefabGenerator;
if (!settings.Enabled)
return;
bool log = settings.LogToConsole;
Stopwatch sw = log ? Stopwatch.StartNew() : null;
DefaultPrefabObjects prefabCollection = GetDefaultPrefabObjects(settings);
// No need to error if nto found, GetDefaultPrefabObjects will.
if (prefabCollection == null)
return;
int assetsLength = importedAssets.Length + deletedAssets.Length + movedAssets.Length + movedFromAssetPaths.Length;
List<string> changedNobPaths = new();
System.Type goType = typeof(GameObject);
IterateAssetCollection(importedAssets);
IterateAssetCollection(movedAssets);
// True if dirtied by changes.
bool dirtied;
// First remove null entries.
int startCount = prefabCollection.GetObjectCount();
prefabCollection.RemoveNull();
dirtied = prefabCollection.GetObjectCount() != startCount;
// First index which new objects will be added to.
int firstAddIndex = prefabCollection.GetObjectCount() - 1;
// Iterates strings adding prefabs to collection.
void IterateAssetCollection(string[] c)
{
foreach (string item in c)
{
System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(item);
if (assetType != goType)
continue;
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(item);
if (CanAddNetworkObject(nob, settings))
{
changedNobPaths.Add(item);
prefabCollection.AddObject(nob, true);
dirtied = true;
}
}
}
// To prevent out of range.
if (firstAddIndex < 0 || firstAddIndex >= prefabCollection.GetObjectCount())
firstAddIndex = 0;
dirtied |= prefabCollection.SetAssetPathHashes(firstAddIndex);
if (log && dirtied)
UnityDebug.Log($"Default prefab generator updated prefabs in {sw.ElapsedMilliseconds}ms.{GetDirtiedMessage(settings, dirtied)}");
// Check for redundancy.
int frameCount = Time.frameCount;
int changedCount = changedNobPaths.Count;
if (frameCount == _lastUpdatedFrame && assetsLength == _lastUpdatedLengths && changedCount == _lastUpdatedNamePaths.Count && changedCount > 0)
{
bool allMatch = true;
for (int i = 0; i < changedCount; i++)
{
if (changedNobPaths[i] != _lastUpdatedNamePaths[i])
{
allMatch = false;
break;
}
}
/* If the import results are the same as the last attempt, on the same frame
* then there is likely an issue saving the assets. */
if (allMatch)
{
// Unset dirtied to prevent a save.
dirtied = false;
// Log this no matter what, it's critical.
UnityDebug.LogError($"Default prefab generator had a problem saving one or more assets. " + $"This usually occurs when the assets cannot be saved due to missing scripts or serialization errors. " + $"Please see above any prefabs which could not save any make corrections.");
}
}
// Set last values.
_lastUpdatedFrame = Time.frameCount;
_lastUpdatedNamePaths = changedNobPaths;
_lastUpdatedLengths = assetsLength;
EditorUtility.SetDirty(prefabCollection);
if (dirtied && settings.SaveChanges)
AssetDatabase.SaveAssets();
}
/// <summary>
/// Gets NetworkObjects from folders using settings.
/// </summary>
internal static List<NetworkObject> GetNetworkObjects(PrefabGeneratorConfigurations settings = null)
{
List<SpecifiedFolder> folders = GetSpecifiedFolders(settings);
if (folders == null)
return new();
return GetNetworkObjects(folders, settings);
}
/// <summary>
/// Gets specified folders to check for prefab generation. This may include excluded paths. Null is returned on error.
/// </summary>
internal static List<SpecifiedFolder> GetSpecifiedFolders(PrefabGeneratorConfigurations settings = null)
{
settings = GetSettingsIfNull(settings);
List<string> folderStrs;
if (settings.SearchScope == (int)SearchScopeType.EntireProject)
{
folderStrs = new();
folderStrs.Add("Assets*");
}
else if (settings.SearchScope == (int)SearchScopeType.SpecificFolders)
{
folderStrs = settings.IncludedFolders.ToList();
}
else
{
UnityDebug.LogError($"{settings.SearchScope} is not handled; folder paths cannot be found.");
return null;
}
return GetSpecifiedFolders(folderStrs);
}
/// <summary>
/// Gets all NetworkObjects in specified folders while ignoring any excluded paths.
/// </summary>
internal static List<NetworkObject> GetNetworkObjects(SpecifiedFolder specifiedFolder, PrefabGeneratorConfigurations settings = null)
{
List<string> excludedPaths = GetSettingsIfNull(settings).ExcludedFolders;
List<NetworkObject> foundNobs = new();
foreach (string path in GetProjectFiles(specifiedFolder.Path, "prefab", excludedPaths, specifiedFolder.Recursive))
{
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(path);
if (CanAddNetworkObject(nob, settings))
foundNobs.Add(nob);
}
return foundNobs;
}
/// <summary>
/// Gets all NetworkObjects in specified folders while ignoring any excluded paths.
/// </summary>
internal static List<NetworkObject> GetNetworkObjects(List<SpecifiedFolder> specifiedFolders, PrefabGeneratorConfigurations settings = null)
{
List<NetworkObject> foundNobs = new();
foreach (SpecifiedFolder sf in specifiedFolders)
foundNobs.AddRange(GetNetworkObjects(sf, settings));
foundNobs = foundNobs.OrderBy(nob => nob.AssetPathHash).ToList();
return foundNobs;
}
/// <summary>
/// Generates prefabs by iterating all files within settings parameters.
/// </summary>
public static void GenerateFull(PrefabGeneratorConfigurations settings = null, bool forced = false, bool initializeAdded = true)
{
if (!CloneChecker.CanGenerateFiles())
{
UnityDebug.Log("Skipping prefab generation as clone settings does not allow it.");
return;
}
// Do not run if the re-serializer is currently running.
if (ReserializeNetworkObjectsEditor.IsRunning)
{
UnityDebug.LogError($"Cannot generate default prefabs when ReserializeNetworkObjectsEditor is running");
return;
}
settings = GetSettingsIfNull(settings);
if (!forced && !settings.Enabled)
return;
bool log = settings.LogToConsole;
Stopwatch sw = log ? Stopwatch.StartNew() : null;
DefaultPrefabObjects prefabCollection = GetDefaultPrefabObjects(settings);
// No need to error if not found, GetDefaultPrefabObjects will throw.
if (prefabCollection == null)
return;
List<NetworkObject> foundNobs = GetNetworkObjects(settings);
// Clear and add built list.
prefabCollection.Clear();
prefabCollection.AddObjects(foundNobs, checkForDuplicates: false, initializeAdded);
bool dirtied = prefabCollection.SetAssetPathHashes(0);
int newCount = prefabCollection.GetObjectCount();
if (log)
{
string dirtiedMessage = newCount > 0 ? GetDirtiedMessage(settings, dirtied) : string.Empty;
UnityDebug.Log($"Default prefab generator found {newCount} prefabs in {sw.ElapsedMilliseconds}ms.{dirtiedMessage}");
}
// Only set dirty if and save if prefabs were found.
if (newCount > 0)
{
EditorUtility.SetDirty(prefabCollection);
if (settings.SaveChanges)
AssetDatabase.SaveAssets();
}
}
/// <summary>
/// Returns settings parameter if not null, otherwise returns settings from configuration.
/// </summary>
internal static PrefabGeneratorConfigurations GetSettingsIfNull(PrefabGeneratorConfigurations settings) => settings == null ? Configuration.Configurations.PrefabGenerator : settings;
/// <summary>
/// Iterates folders building them into SpecifiedFolders.
/// </summary>
internal static List<SpecifiedFolder> GetSpecifiedFolders(List<string> strFolders)
{
List<SpecifiedFolder> results = new();
// Remove astericks.
foreach (string path in strFolders)
{
int pLength = path.Length;
if (pLength == 0)
continue;
bool recursive;
string p;
// If the last character indicates resursive.
if (path.Substring(pLength - 1, 1) == "*")
{
p = path.Substring(0, pLength - 1);
recursive = true;
}
else
{
p = path;
recursive = false;
}
p = GetPlatformPath(p);
// Path does not exist.
if (!Directory.Exists(p))
continue;
results.Add(new(p, recursive));
}
RemoveOverlappingFolders(results);
return results;
// Removes paths which may overlap each other, such as sub directories.
static void RemoveOverlappingFolders(List<SpecifiedFolder> specifiedFolders)
{
for (int z = 0; z < specifiedFolders.Count; z++)
{
for (int i = 0; i < specifiedFolders.Count; i++)
{
// Do not check against self.
if (i == z)
continue;
string zPath = GetPathWithSeparator(specifiedFolders[z].Path);
string iPath = GetPathWithSeparator(specifiedFolders[i].Path);
// Duplicate.
if (zPath.Equals(iPath, System.StringComparison.OrdinalIgnoreCase))
{
UnityDebug.LogError($"The same path is specified multiple times in the DefaultPrefabGenerator settings. Remove the duplicate to clear this error.");
specifiedFolders.RemoveAt(i);
break;
}
/* We are checking if i can be within
* z. This is only possible if i is longer
* than z. */
if (iPath.Length < zPath.Length)
continue;
/* Do not need to check if not recursive.
* Only recursive needs to be checked because
* a shorter recursive path could contain
* a longer path. */
if (!specifiedFolders[z].Recursive)
continue;
// // Compare paths.
// string zPath = GetPathWithSeparator(specifiedFolders[z].Path);
// string iPath = specifiedFolders[i].Path.Substring(0, zPath.Length);
string iPathRecursiveCheck = iPath.Substring(0, zPath.Length);
// If paths match.
if (iPathRecursiveCheck.Equals(zPath, System.StringComparison.OrdinalIgnoreCase))
{
UnityDebug.LogError($"Path {specifiedFolders[i].Path} is included within recursive path {specifiedFolders[z].Path}. Remove path {specifiedFolders[i].Path} to clear this error.");
specifiedFolders.RemoveAt(i);
break;
}
}
}
string GetPathWithSeparator(string txt)
{
return txt.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
}
}
}
internal static string GetPlatformPath(string path)
{
if (string.IsNullOrEmpty(path))
return path;
path = path.Replace(@"\"[0], Path.DirectorySeparatorChar);
path = path.Replace(@"/"[0], Path.DirectorySeparatorChar);
return path;
}
/// <summary>
/// Returns the DefaultPrefabObjects file.
/// </summary>
internal static DefaultPrefabObjects GetDefaultPrefabObjects(PrefabGeneratorConfigurations settings = null)
{
if (settings == null)
settings = Configuration.Configurations.PrefabGenerator;
// If not using default prefabs then exit early.
if (!settings.Enabled)
return null;
// Load the prefab collection
string defaultPrefabsPath = settings.DefaultPrefabObjectsPath_Platform;
string fullDefaultPrefabsPath = defaultPrefabsPath.Length > 0 ? Path.GetFullPath(defaultPrefabsPath) : string.Empty;
// If cached prefabs is not the same path as assetPath.
if (_cachedDefaultPrefabs != null)
{
string unityAssetPath = AssetDatabase.GetAssetPath(_cachedDefaultPrefabs);
string fullCachedPath = unityAssetPath.Length > 0 ? Path.GetFullPath(unityAssetPath) : string.Empty;
if (fullCachedPath != fullDefaultPrefabsPath)
_cachedDefaultPrefabs = null;
}
// If cached is null try to get it.
if (_cachedDefaultPrefabs == null)
{
// Only try to load it if file exist.
if (File.Exists(fullDefaultPrefabsPath))
{
_cachedDefaultPrefabs = AssetDatabase.LoadAssetAtPath<DefaultPrefabObjects>(defaultPrefabsPath);
if (_cachedDefaultPrefabs == null)
{
// If already retried then throw an error.
if (_retryRefreshDefaultPrefabs)
{
UnityDebug.LogError("DefaultPrefabObjects file exists but it could not be loaded by Unity. Use the Fish-Networking menu -> Utility -> Refresh Default Prefabs to refresh prefabs.");
}
else
{
UnityDebug.Log("DefaultPrefabObjects file exists but it could not be loaded by Unity. Trying to reload the file next frame.");
_retryRefreshDefaultPrefabs = true;
}
return null;
}
}
}
// Will be true also if not a clone.
if (CloneChecker.CanGenerateFiles())
CreateDefaultPrefabsAssetIfNeeded();
// Creates DefaultPrefabs asset if missing or not set.
void CreateDefaultPrefabsAssetIfNeeded()
{
if (_cachedDefaultPrefabs == null)
{
string fullPath = Path.GetFullPath(defaultPrefabsPath);
UnityDebug.Log($"Creating a new DefaultPrefabsObject at {fullPath}.");
string directory = Path.GetDirectoryName(fullPath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
AssetDatabase.Refresh();
}
_cachedDefaultPrefabs = ScriptableObject.CreateInstance<DefaultPrefabObjects>();
AssetDatabase.CreateAsset(_cachedDefaultPrefabs, defaultPrefabsPath);
AssetDatabase.SaveAssets();
}
}
if (_cachedDefaultPrefabs != null && _retryRefreshDefaultPrefabs)
UnityDebug.Log("DefaultPrefabObjects found on the second iteration.");
return _cachedDefaultPrefabs;
}
/// <summary>
/// Called every frame the editor updates.
/// </summary>
private static void OnEditorUpdate()
{
if (!_retryRefreshDefaultPrefabs)
return;
GenerateFull();
_retryRefreshDefaultPrefabs = false;
}
/// <summary>
/// Called by Unity when assets are modified.
/// </summary>
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
if (Application.isPlaying)
return;
// If retrying next frame don't bother updating, next frame will do a full refresh.
if (_retryRefreshDefaultPrefabs)
return;
// Post process is being ignored. Could be temporary or user has disabled this feature.
if (IgnorePostProcess)
return;
/* Don't iterate if updating or compiling as that could cause an infinite loop
* due to the prefabs being generated during an update, which causes the update
* to start over, which causes the generator to run again, which... you get the idea. */
if (EditorApplication.isCompiling)
return;
DefaultPrefabObjects prefabCollection = GetDefaultPrefabObjects();
if (prefabCollection == null)
return;
PrefabGeneratorConfigurations settings = Configuration.Configurations.PrefabGenerator;
if (prefabCollection.GetObjectCount() == 0)
{
// If there are no prefabs then do a full rebuild. Odds of there being none are pretty much nill.
GenerateFull(settings);
}
else
{
int totalChanges = importedAssets.Length + deletedAssets.Length + movedAssets.Length + movedFromAssetPaths.Length;
// Nothing has changed. This shouldn't occur but unity is funny so we're going to check anyway.
if (totalChanges == 0)
return;
// Normalizes path.
string dpoPath = Path.GetFullPath(settings.DefaultPrefabObjectsPath_Platform);
// If total changes is 1 and the only changed file is the default prefab collection then do nothing.
if (totalChanges == 1)
{
// Do not need to check movedFromAssetPaths because that's not possible for this check.
if ((importedAssets.Length == 1 && Path.GetFullPath(importedAssets[0]) == dpoPath) || (deletedAssets.Length == 1 && Path.GetFullPath(deletedAssets[0]) == dpoPath) || (movedAssets.Length == 1 && Path.GetFullPath(movedAssets[0]) == dpoPath))
return;
/* If the only change is an import then check if the imported file
* is the same as the last, and if so check into returning early.
* For some reason occasionally when files are saved unity runs postprocess
* multiple times on the same file. */
string imported = importedAssets.Length == 1 ? importedAssets[0] : null;
if (imported != null && imported == _lastSingleImportedAsset)
{
// If here then the file is the same. Make sure it's already in the collection before returning.
System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(imported);
// Not a gameObject, no reason to continue.
if (assetType != typeof(GameObject))
return;
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(imported);
// If is a networked object.
if (CanAddNetworkObject(nob, settings))
{
// Already added!
if (prefabCollection.Prefabs.Contains(nob))
return;
}
}
else if (imported != null)
{
_lastSingleImportedAsset = imported;
}
}
bool fullRebuild = settings.FullRebuild;
/* If updating FN. This needs to be done a better way.
* Parsing the actual version file would be better.
* I'll get to it next release. */
if (!_ranOnce)
{
_ranOnce = true;
fullRebuild = true;
}
// Other conditions which a full rebuild may be required.
else if (!fullRebuild)
{
const string fishnetVersionSave = "fishnet_version";
string savedVersion = EditorPrefs.GetString(fishnetVersionSave, string.Empty);
if (savedVersion != NetworkManager.FISHNET_VERSION)
{
fullRebuild = true;
EditorPrefs.SetString(fishnetVersionSave, NetworkManager.FISHNET_VERSION);
}
}
if (fullRebuild)
GenerateFull(settings);
else
GenerateChanged(importedAssets, deletedAssets, movedAssets, movedFromAssetPaths, settings);
}
}
/// <summary>
/// Returns true if a NetworkObject can be added to DefaultPrefabs.
/// </summary>
private static bool CanAddNetworkObject(NetworkObject networkObject, PrefabGeneratorConfigurations settings)
{
settings = GetSettingsIfNull(settings);
return networkObject != null && (networkObject.GetIsSpawnable() || !settings.SpawnableOnly);
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 68e990388e202d54aa0fe9e7aa8cc716
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/PrefabCollectionGenerator/Generator.cs
uploadId: 866910
@@ -0,0 +1,235 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using UnitySettingsProviderAttribute = UnityEditor.SettingsProviderAttribute;
using UnitySettingsProvider = UnityEditor.SettingsProvider;
using FishNet.Configuring;
using System.Linq;
namespace FishNet.Editing.PrefabCollectionGenerator
{
internal static class SettingsProvider
{
private static readonly Regex SlashRegex = new(@"[\\// ]");
private static PrefabGeneratorConfigurations _settings;
private static GUIContent _folderIcon;
private static GUIContent _deleteIcon;
private static Vector2 _scrollVector;
private static bool _showFolders;
[UnitySettingsProvider]
private static UnitySettingsProvider Create()
{
return new("Project/Fish-Networking/Prefab Objects Generator", SettingsScope.Project)
{
label = "Prefab Objects Generator",
guiHandler = OnGUI,
keywords = new string[]
{
"Fish",
"Networking",
"Prefab",
"Objects",
"Generator"
}
};
}
private static void OnGUI(string searchContext)
{
if (_settings == null)
_settings = Configuration.Configurations.PrefabGenerator;
if (_folderIcon == null)
_folderIcon = EditorGUIUtility.IconContent("d_FolderOpened Icon");
if (_deleteIcon == null)
_deleteIcon = EditorGUIUtility.IconContent("P4_DeletedLocal");
EditorGUI.BeginChangeCheck();
GUIStyle scrollViewStyle = new()
{
padding = new(10, 10, 10, 10)
};
_scrollVector = EditorGUILayout.BeginScrollView(_scrollVector, scrollViewStyle);
_settings.Enabled = EditorGUILayout.Toggle(ObjectNames.NicifyVariableName(nameof(_settings.Enabled)), _settings.Enabled);
_settings.LogToConsole = EditorGUILayout.Toggle(ObjectNames.NicifyVariableName(nameof(_settings.LogToConsole)), _settings.LogToConsole);
_settings.FullRebuild = EditorGUILayout.Toggle(ObjectNames.NicifyVariableName(nameof(_settings.FullRebuild)), _settings.FullRebuild);
_settings.SpawnableOnly = EditorGUILayout.Toggle(ObjectNames.NicifyVariableName(nameof(_settings.SpawnableOnly)), _settings.SpawnableOnly);
_settings.SaveChanges = EditorGUILayout.Toggle(ObjectNames.NicifyVariableName(nameof(_settings.SaveChanges)), _settings.SaveChanges);
GUILayoutOption iconWidthConstraint = GUILayout.MaxWidth(32.0f);
GUILayoutOption iconHeightConstraint = GUILayout.MaxHeight(EditorGUIUtility.singleLineHeight);
EditorGUILayout.BeginHorizontal();
string oldAssetPath = _settings.DefaultPrefabObjectsPath;
string newAssetPath = EditorGUILayout.DelayedTextField(ObjectNames.NicifyVariableName(nameof(_settings.DefaultPrefabObjectsPath)), oldAssetPath);
if (GUILayout.Button(_folderIcon, iconWidthConstraint, iconHeightConstraint))
{
if (TrySaveFilePathInsideAssetsFolder(null, Application.dataPath, "DefaultPrefabObjects", "asset", out string result))
newAssetPath = result;
else
EditorWindow.focusedWindow.ShowNotification(new($"{ObjectNames.NicifyVariableName(nameof(_settings.DefaultPrefabObjectsPath))} must be inside the Assets folder."));
}
if (!newAssetPath.Equals(oldAssetPath, StringComparison.OrdinalIgnoreCase))
{
if (newAssetPath.StartsWith($"Assets{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase))
{
if (File.Exists(newAssetPath))
{
EditorWindow.focusedWindow.ShowNotification(new("Another asset already exists at the new path."));
}
else
{
Generator.IgnorePostProcess = true;
if (File.Exists(oldAssetPath))
AssetDatabase.MoveAsset(oldAssetPath, newAssetPath);
_settings.DefaultPrefabObjectsPath = newAssetPath;
Generator.IgnorePostProcess = false;
}
}
else
{
EditorWindow.focusedWindow.ShowNotification(new($"{ObjectNames.NicifyVariableName(nameof(_settings.DefaultPrefabObjectsPath))} must be inside the Assets folder."));
}
}
EditorGUILayout.EndHorizontal();
int currentSearchScope = _settings.SearchScope;
SearchScopeType searchScopeType = (SearchScopeType)EditorGUILayout.EnumPopup(ValueToSearchScope(_settings.SearchScope));
_settings.SearchScope = (int)searchScopeType;
SearchScopeType ValueToSearchScope(int value) => (SearchScopeType)value;
if (_settings.SearchScope == (int)SearchScopeType.EntireProject)
{
EditorGUILayout.HelpBox("Searching the entire project for prefabs can become very slow. Consider switching the search scope to specific folders instead.", MessageType.Warning);
if (GUILayout.Button("Switch"))
_settings.SearchScope = (int)SearchScopeType.SpecificFolders;
}
// If search scope changed then update prefabs.
if (currentSearchScope != _settings.SearchScope && (SearchScopeType)_settings.SearchScope == SearchScopeType.EntireProject)
Generator.GenerateFull();
List<string> folders = null;
string foldersName = null;
if (_settings.SearchScope == (int)SearchScopeType.EntireProject)
{
folders = _settings.ExcludedFolders;
foldersName = ObjectNames.NicifyVariableName(nameof(_settings.ExcludedFolders));
}
else if (_settings.SearchScope == (int)SearchScopeType.SpecificFolders)
{
folders = _settings.IncludedFolders;
foldersName = ObjectNames.NicifyVariableName(nameof(_settings.IncludedFolders));
}
string folderName = foldersName.Substring(0, foldersName.Length - 1);
if ((_showFolders = EditorGUILayout.Foldout(_showFolders, $"{foldersName} ({folders.Count})")) && folders != null)
{
EditorGUI.indentLevel++;
for (int i = 0; i < folders.Count; i++)
{
EditorGUILayout.BeginHorizontal();
string oldFolder = folders[i];
string newFolder = SlashRegex.Replace(EditorGUILayout.DelayedTextField(oldFolder), Path.DirectorySeparatorChar.ToString());
if (!newFolder.Equals(oldFolder, StringComparison.OrdinalIgnoreCase))
{
if (newFolder.StartsWith($"Assets{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase))
folders[i] = newFolder;
else
EditorWindow.focusedWindow.ShowNotification(new($"{folderName} must be inside the Assets folder."));
}
if (GUILayout.Button(_folderIcon, iconWidthConstraint, iconHeightConstraint))
{
if (TryOpenFolderPathInsideAssetsFolder(null, Application.dataPath, null, out string result))
folders[i] = result;
else
EditorWindow.focusedWindow.ShowNotification(new($"{folderName} must be inside the Assets folder."));
}
if (GUILayout.Button(_deleteIcon, iconWidthConstraint, iconHeightConstraint))
folders.RemoveAt(i);
EditorGUILayout.EndHorizontal();
}
EditorGUI.indentLevel--;
if (_settings.SearchScope == (int)SearchScopeType.SpecificFolders)
EditorGUILayout.HelpBox("You can include subfolders by appending an asterisk (*) to a path.", MessageType.None);
if (GUILayout.Button("Browse"))
{
if (TryOpenFolderPathInsideAssetsFolder(null, Application.dataPath, null, out string result))
{
folders.Add(result);
}
else
{
EditorWindow.focusedWindow.ShowNotification(new($"{folderName} must be inside the Assets folder."));
}
}
}
if (EditorGUI.EndChangeCheck())
Configuration.Configurations.Write(true);
if (GUILayout.Button("Generate"))
Generator.GenerateFull();
EditorGUILayout.HelpBox("Consider pressing 'Generate' after changing the settings.", MessageType.Info);
EditorGUILayout.EndScrollView();
}
private static bool TrySaveFilePathInsideAssetsFolder(string title, string directory, string name, string extension, out string result)
{
result = null;
string selectedPath = EditorUtility.SaveFilePanel(title, directory, name, extension);
if (selectedPath.StartsWith(Application.dataPath, StringComparison.OrdinalIgnoreCase))
{
result = SlashRegex.Replace(selectedPath.Remove(0, Path.GetDirectoryName(Application.dataPath).Length + 1), Path.DirectorySeparatorChar.ToString());
return true;
}
return false;
}
private static bool TryOpenFolderPathInsideAssetsFolder(string title, string folder, string name, out string result)
{
result = null;
string selectedPath = EditorUtility.OpenFolderPanel(title, folder, name);
if (selectedPath.StartsWith(Application.dataPath, StringComparison.OrdinalIgnoreCase))
{
result = SlashRegex.Replace(selectedPath.Remove(0, Path.GetDirectoryName(Application.dataPath).Length + 1), Path.DirectorySeparatorChar.ToString());
return true;
}
return false;
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7451fcc5eeb5b89468ab2ce22f678b26
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/PrefabCollectionGenerator/SettingsProvider.cs
uploadId: 866910
@@ -0,0 +1,96 @@
#if UNITY_EDITOR
using FishNet.Managing;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Build;
using UnityEngine;
namespace FishNet
{
internal static class ScriptingDefines
{
[InitializeOnLoadMethod]
public static void AddDefineSymbols()
{
// Get data about current target group
bool standaloneAndServer = false;
BuildTarget buildTarget = EditorUserBuildSettings.activeBuildTarget;
BuildTargetGroup buildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget);
if (buildTargetGroup == BuildTargetGroup.Standalone)
{
StandaloneBuildSubtarget standaloneSubTarget = EditorUserBuildSettings.standaloneBuildSubtarget;
if (standaloneSubTarget == StandaloneBuildSubtarget.Server)
standaloneAndServer = true;
}
// Prepare named target, depending on above stuff
NamedBuildTarget namedBuildTarget;
if (standaloneAndServer)
namedBuildTarget = NamedBuildTarget.Server;
else
namedBuildTarget = NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup);
string currentDefines = PlayerSettings.GetScriptingDefineSymbols(namedBuildTarget);
/* Convert current defines into a hashset. This is so we can
* determine if any of our defines were added. Only save playersettings
* when a define is added. */
HashSet<string> definesHs = new();
string[] currentArr = currentDefines.Split(';');
// Add current defines into hs.
foreach (string item in currentArr)
definesHs.Add(item);
string proDefine = "FISHNET_PRO";
string versionPrefix = "FISHNET_V";
string[] currentVersionSplit = NetworkManager.FISHNET_VERSION.Split(".");
string thisVersion = $"{versionPrefix}{currentVersionSplit[0]}";
string[] fishNetDefines = new string[]
{
"FISHNET",
thisVersion,
};
bool modified = false;
// Now add FN defines.
foreach (string item in fishNetDefines)
modified |= definesHs.Add(item);
// Remove old prediction defines.
modified |= definesHs.Remove("PREDICTION_V2");
/* Remove pro define if not on pro. This might look a little
* funny because the code below varies depending on if pro or not. */
#pragma warning disable CS0162 // Unreachable code detected
modified |= definesHs.Remove(proDefine);
#pragma warning restore CS0162 // Unreachable code detected
List<string> definesToRemove = new();
int versionPrefixLength = versionPrefix.Length;
// Remove old versions.
foreach (string item in definesHs)
{
// Do not remove this version.
if (item == thisVersion)
continue;
// If length is possible to be a version prefix and is so then remove it.
if (item.Length >= versionPrefixLength && item.Substring(0, versionPrefixLength) == versionPrefix)
definesToRemove.Add(item);
}
modified |= definesToRemove.Count > 0;
foreach (string item in definesToRemove)
{
definesHs.Remove(item);
Debug.Log($"Removed unused Fish-Networking define {item}.");
}
if (modified)
{
Debug.Log("Added or removed Fish-Networking defines within player settings.");
string changedDefines = string.Join(";", definesHs);
PlayerSettings.SetScriptingDefineSymbols(namedBuildTarget, changedDefines);
}
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 893e5074d486a0e4aaf7803436fef791
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/ScriptingDefines.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3e64b45ed42e33641b9cc1f6ee7e2975
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e3fad7777e2bfd04f8ad002a7797348c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,99 @@
fileFormatVersion: 2
guid: bf9191e2e07d29749bca3a1ae44e4bc8
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Textures/Icon/fishnet_light.png
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b390700058f059c4987901fdd16c2578
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
@@ -0,0 +1,142 @@
fileFormatVersion: 2
guid: 3ae205103debe66408545096e23a3780
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Textures/UI/Client_Text.png
uploadId: 866910
Binary file not shown.
@@ -0,0 +1,142 @@
fileFormatVersion: 2
guid: 9f392fdb366cd42408f283227989684c
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Textures/UI/FishNet_Text.png
uploadId: 866910
@@ -0,0 +1,142 @@
fileFormatVersion: 2
guid: 5ea1cf1e0e57aff4e9ad3cd4246b0e80
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
cookieLightType: 1
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Textures/UI/Logo_With_Text.png
uploadId: 866910
Binary file not shown.
@@ -0,0 +1,142 @@
fileFormatVersion: 2
guid: cc43c1385315d554ba6b0a5cad49aa19
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Textures/UI/Server_Text.png
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a4ef7ba8f76fa854cb4799bbe62695c0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,2 @@
// Remove V5
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c502df40457bd364cbaa553a2ce7f04e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Editor/Upgrading/UpgradeFromV3ToV4Menu.cs
uploadId: 866910