[Add] FishNet
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34e749ac19246214591a2e3f58dc6365
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Authenticating
|
||||
{
|
||||
/// <summary>
|
||||
/// When inherited from this can be used to create a custom authentication process before clients may communicate with the server.
|
||||
/// </summary>
|
||||
public abstract class Authenticator : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// True if this authenticator has been intiialzied.
|
||||
/// </summary>
|
||||
public bool Initialized { get; private set; }
|
||||
#endregion
|
||||
|
||||
#region Protected.
|
||||
/// <summary>
|
||||
/// NetworkManager for this Authenticator.
|
||||
/// </summary>
|
||||
protected NetworkManager NetworkManager { get; private set; }
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Called when authenticator has concluded a result for a connection. Boolean is true if authentication passed, false if failed.
|
||||
/// Server listens for this event automatically.
|
||||
/// </summary>
|
||||
public abstract event Action<NetworkConnection, bool> OnAuthenticationResult;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this script for use.
|
||||
/// </summary>
|
||||
/// <param name = "networkManager"></param>
|
||||
public virtual void InitializeOnce(NetworkManager networkManager)
|
||||
{
|
||||
NetworkManager = networkManager;
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on the server immediately after a client connects. Can be used to send data to the client for authentication.
|
||||
/// </summary>
|
||||
/// <param name = "connection">Connection which is not yet authenticated.</param>
|
||||
public virtual void OnRemoteConnection(NetworkConnection connection) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9adfb82407774645a1f455ceb9298f9
|
||||
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/Authenticating/Authenticator.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7601d0cb4fcf4ef468b1faeee7bbf3f0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 634f0556bc4961f44846882dac5d1c7f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,196 @@
|
||||
// using FishNet.Connection; // remove on v5
|
||||
// using FishNet.Serializing;
|
||||
// using FishNet.Transporting;
|
||||
// using GameKit.Dependencies.Utilities;
|
||||
// using System;
|
||||
// using System.Collections.Generic;
|
||||
// using System.Diagnostics;
|
||||
|
||||
// namespace FishNet.Broadcast.Helping
|
||||
// {
|
||||
// internal static class BroadcastHelper
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// Gets the key for a broadcast type.
|
||||
// /// </summary>
|
||||
// /// <typeparam name="T"></typeparam>
|
||||
// /// <param name="broadcastType"></param>
|
||||
// /// <returns></returns>
|
||||
// internal static ushort GetKey<T>()
|
||||
// {
|
||||
// return typeof(T).FullName.GetStableHashU16();
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Implemented by server and client broadcast handlers.
|
||||
// /// </summary>
|
||||
// public abstract class BroadcastHandlerBase
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// Current index when iterating invokes.
|
||||
// /// This value will be -1 when not iterating.
|
||||
// /// </summary>
|
||||
// protected int IteratingIndex;
|
||||
|
||||
// public abstract void RegisterHandler(object obj);
|
||||
// public abstract void UnregisterHandler(object obj);
|
||||
// public virtual void InvokeHandlers(PooledReader reader, Channel channel) { }
|
||||
// public virtual void InvokeHandlers(NetworkConnection conn, PooledReader reader, Channel channel) { }
|
||||
// public virtual bool RequireAuthentication => false;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Handles broadcasts received on server, from clients.
|
||||
// /// </summary>
|
||||
// internal class ClientBroadcastHandler<T> : BroadcastHandlerBase
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// Action handlers for the broadcast.
|
||||
// /// </summary>
|
||||
// private List<Action<NetworkConnection, T, Channel>> _handlers = new List<Action<NetworkConnection, T, Channel>>();
|
||||
// /// <summary>
|
||||
// /// True to require authentication for the broadcast type.
|
||||
// /// </summary>
|
||||
// private bool _requireAuthentication;
|
||||
|
||||
// public ClientBroadcastHandler(bool requireAuthentication)
|
||||
// {
|
||||
// _requireAuthentication = requireAuthentication;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Invokes handlers after reading broadcast.
|
||||
// /// </summary>
|
||||
// /// <returns>True if a rebuild was required.</returns>
|
||||
// public override void InvokeHandlers(NetworkConnection conn, PooledReader reader, Channel channel)
|
||||
// {
|
||||
// T result = reader.Read<T>();
|
||||
// for (base.IteratingIndex = 0; base.IteratingIndex < _handlers.Count; base.IteratingIndex++)
|
||||
// {
|
||||
// Action<NetworkConnection, T, Channel> item = _handlers[base.IteratingIndex];
|
||||
// if (item != null)
|
||||
// {
|
||||
// item.Invoke(conn, result, channel);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// _handlers.RemoveAt(base.IteratingIndex);
|
||||
// base.IteratingIndex--;
|
||||
// }
|
||||
// }
|
||||
|
||||
// base.IteratingIndex = -1;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Adds a handler for this type.
|
||||
// /// </summary>
|
||||
// public override void RegisterHandler(object obj)
|
||||
// {
|
||||
// Action<NetworkConnection, T, Channel> handler = (Action<NetworkConnection, T, Channel>)obj;
|
||||
// _handlers.AddUnique(handler);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Removes a handler from this type.
|
||||
// /// </summary>
|
||||
// /// <param name="handler"></param>
|
||||
// public override void UnregisterHandler(object obj)
|
||||
// {
|
||||
// Action<NetworkConnection, T, Channel> handler = (Action<NetworkConnection, T, Channel>)obj;
|
||||
// int indexOf = _handlers.IndexOf(handler);
|
||||
// // Not registered.
|
||||
// if (indexOf == -1)
|
||||
// return;
|
||||
|
||||
// /* Has already been iterated over, need to subtract
|
||||
// * 1 from iteratingIndex to accomodate
|
||||
// * for the entry about to be removed. */
|
||||
// if (base.IteratingIndex >= 0 && (indexOf <= base.IteratingIndex))
|
||||
// base.IteratingIndex--;
|
||||
|
||||
// // Remove entry.
|
||||
// _handlers.RemoveAt(indexOf);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// True to require authentication for the broadcast type.
|
||||
// /// </summary>
|
||||
// public override bool RequireAuthentication => _requireAuthentication;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Handles broadcasts received on client, from server.
|
||||
// /// </summary>
|
||||
// internal class ServerBroadcastHandler<T> : BroadcastHandlerBase
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// Action handlers for the broadcast.
|
||||
// /// Even though List lookups are slower this allows easy adding and removing of entries during iteration.
|
||||
// /// </summary>
|
||||
// private List<Action<T, Channel>> _handlers = new List<Action<T, Channel>>();
|
||||
|
||||
// /// <summary>
|
||||
// /// Invokes handlers after reading broadcast.
|
||||
// /// </summary>
|
||||
// /// <returns>True if a rebuild was required.</returns>
|
||||
// public override void InvokeHandlers(PooledReader reader, Channel channel)
|
||||
// {
|
||||
// T result = reader.Read<T>();
|
||||
// for (base.IteratingIndex = 0; base.IteratingIndex < _handlers.Count; base.IteratingIndex++)
|
||||
// {
|
||||
// Action<T, Channel> item = _handlers[base.IteratingIndex];
|
||||
// if (item != null)
|
||||
// {
|
||||
// item.Invoke(result, channel);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// _handlers.RemoveAt(base.IteratingIndex);
|
||||
// base.IteratingIndex--;
|
||||
// }
|
||||
// }
|
||||
|
||||
// base.IteratingIndex = -1;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Adds a handler for this type.
|
||||
// /// </summary>
|
||||
// public override void RegisterHandler(object obj)
|
||||
// {
|
||||
// Action<T, Channel> handler = (Action<T, Channel>)obj;
|
||||
// _handlers.AddUnique(handler);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Removes a handler from this type.
|
||||
// /// </summary>
|
||||
// /// <param name="handler"></param>
|
||||
// public override void UnregisterHandler(object obj)
|
||||
// {
|
||||
// Action<T, Channel> handler = (Action<T, Channel>)obj;
|
||||
// int indexOf = _handlers.IndexOf(handler);
|
||||
// // Not registered.
|
||||
// if (indexOf == -1)
|
||||
// return;
|
||||
|
||||
// /* Has already been iterated over, need to subtract
|
||||
// * 1 from iteratingIndex to accomodate
|
||||
// * for the entry about to be removed. */
|
||||
// if (base.IteratingIndex >= 0 && (indexOf <= base.IteratingIndex))
|
||||
// base.IteratingIndex--;
|
||||
|
||||
// //Remove entry.
|
||||
// _handlers.RemoveAt(indexOf);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// True to require authentication for the broadcast type.
|
||||
// /// </summary>
|
||||
// public override bool RequireAuthentication => false;
|
||||
// }
|
||||
|
||||
//}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c314af08d630630449b7b7af740b9c7d
|
||||
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/Broadcast/Helping/BroadcastHelpers.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace FishNet.Broadcast
|
||||
{
|
||||
/// <summary>
|
||||
/// Include this interface on types intended to be used with Broadcast.
|
||||
/// </summary>
|
||||
public interface IBroadcast { }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88ec864df25feed49bdcdab7f880531d
|
||||
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/Broadcast/IBroadcast.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afabc0828ac7433468e40b80f5c9524c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,76 @@
|
||||
using FishNet.Utility;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
|
||||
|
||||
namespace FishNet.CodeGenerating
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows a SyncType to be mutable.
|
||||
/// </summary>
|
||||
public class AllowMutableSyncTypeAttribute : Attribute { }
|
||||
|
||||
/// <summary>
|
||||
/// Type will be included in auto serializer creation.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true, AllowMultiple = false)]
|
||||
public class IncludeSerializationAttribute : Attribute { }
|
||||
|
||||
/// <summary>
|
||||
/// Type will be excluded from auto serializer creation.
|
||||
/// </summary>
|
||||
public class ExcludeSerializationAttribute : Attribute { }
|
||||
|
||||
/// <summary>
|
||||
/// Method will not be considered a writer or reader.
|
||||
/// </summary>
|
||||
public class NotSerializerAttribute : Attribute { }
|
||||
|
||||
/// <summary>
|
||||
/// Method or type will be made public by codegen.
|
||||
/// </summary>
|
||||
internal class MakePublicAttribute : Attribute { }
|
||||
|
||||
/// <summary>
|
||||
/// Method is a comparer for a value type.
|
||||
/// </summary>
|
||||
public class CustomComparerAttribute : Attribute { }
|
||||
|
||||
/// <summary>
|
||||
/// Used on a type when you want a custom serializer to be global across all assemblies.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = true, AllowMultiple = false)]
|
||||
public class UseGlobalCustomSerializerAttribute : Attribute { }
|
||||
|
||||
/// <summary>
|
||||
/// Uses built-in caches to retrieve read classes rather than initializing a new instance.
|
||||
/// This attribute is primarily for internal use and may change at anytime without notice.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
||||
public class ReadUnallocatedAttribute : Attribute { }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a method is the default writer for a type. The first non-extension parameter indicates the type this writer is for.
|
||||
/// This attribute is primarily for internal use and may change at anytime without notice.
|
||||
/// </summary>
|
||||
public class DefaultWriterAttribute : Attribute { }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a method is the default reader for a type. The return type indicates what type the reader is for.
|
||||
/// This attribute is primarily for internal use and may change at anytime without notice.
|
||||
/// </summary>
|
||||
public class DefaultReaderAttribute : Attribute { }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a method is a delta writer. The first non-extension parameter indicates the type this writer is for.
|
||||
/// This attribute is primarily for internal use and may change at anytime without notice.
|
||||
/// </summary>
|
||||
public class DefaultDeltaWriterAttribute : Attribute { }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a method is a delta reader. The return type indicates what type the reader is for.
|
||||
/// This attribute is primarily for internal use and may change at anytime without notice.
|
||||
/// </summary>
|
||||
public class DefaultDeltaReaderAttribute : Attribute { }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1581268482fee3b489c2ba9e8cd2b293
|
||||
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/CodeGenerating/Attributes.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1 @@
|
||||
{"StripReleaseBuilds":true}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f1ece47c2d48194ea4827bf592a2279
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Config.json
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bfcb6f3f32d11a4296ade8dc07d56bb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,292 @@
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Utility.Performance;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// A byte buffer that automatically resizes.
|
||||
/// </summary>
|
||||
internal class ByteBuffer
|
||||
{
|
||||
/// <summary>
|
||||
/// How many more bytes may fit into the buffer.
|
||||
/// </summary>
|
||||
internal int Remaining => Size - Length;
|
||||
/// <summary>
|
||||
/// Buffer data.
|
||||
/// </summary>
|
||||
internal byte[] Data { get; private set; }
|
||||
/// <summary>
|
||||
/// How many bytes currently into Data. This will include the reserve.
|
||||
/// </summary>
|
||||
internal int Length { get; private set; }
|
||||
/// <summary>
|
||||
/// Size of the buffer. Data.Length may exceed this value as it uses a pooled array.
|
||||
/// </summary>
|
||||
internal int Size { get; private set; }
|
||||
/// <summary>
|
||||
/// True if data has been written.
|
||||
/// </summary>
|
||||
internal bool HasData { get; private set; }
|
||||
/// <summary>
|
||||
/// Bytes to reserve when resetting.
|
||||
/// </summary>
|
||||
private int _reserve;
|
||||
|
||||
internal ByteBuffer(int size, int reserve = 0)
|
||||
{
|
||||
Data = ByteArrayPool.Retrieve(size);
|
||||
Size = size;
|
||||
_reserve = reserve;
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Data != null)
|
||||
ByteArrayPool.Store(Data);
|
||||
Data = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets instance without clearing Data.
|
||||
/// </summary>
|
||||
internal void Reset()
|
||||
{
|
||||
Length = _reserve;
|
||||
HasData = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies segments without error checking, including tick for the first time data is added.
|
||||
/// </summary>
|
||||
/// <param name = "segment"></param>
|
||||
internal void CopySegment(uint tick, ArraySegment<byte> segment)
|
||||
{
|
||||
/* If data has not been written to buffer yet
|
||||
* then write tick to the start. */
|
||||
if (!HasData)
|
||||
{
|
||||
int pos = 0;
|
||||
Writer.WriteUInt32Unpacked(Data, tick, ref pos);
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(segment.Array, segment.Offset, Data, Length, segment.Count);
|
||||
Length += segment.Count;
|
||||
HasData = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies segments without error checking.
|
||||
/// </summary>
|
||||
/// <param name = "segment"></param>
|
||||
internal void CopySegment(ArraySegment<byte> segment)
|
||||
{
|
||||
Buffer.BlockCopy(segment.Array, segment.Offset, Data, Length, segment.Count);
|
||||
Length += segment.Count;
|
||||
HasData = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal class PacketBundle
|
||||
{
|
||||
/// <summary>
|
||||
/// True if data has been written.
|
||||
/// </summary>
|
||||
internal bool HasData => _buffers[0].HasData || (!_isSendLastBundle && _sendLastBundle.HasData);
|
||||
/// <summary>
|
||||
/// All buffers written. Collection is not cleared when reset but rather the index in which to write is.
|
||||
/// </summary>
|
||||
private List<ByteBuffer> _buffers = new();
|
||||
/// <summary>
|
||||
/// Buffer which is being written to.
|
||||
/// </summary>
|
||||
private int _bufferIndex;
|
||||
/// <summary>
|
||||
/// Maximum size packet the transport can handle.
|
||||
/// </summary>
|
||||
private int _maximumTransportUnit;
|
||||
/// <summary>
|
||||
/// Number of buffers written to. Will return 0 if nothing has been written.
|
||||
/// </summary>
|
||||
public int WrittenBuffers => !HasData ? 0 : _bufferIndex + 1;
|
||||
/// <summary>
|
||||
/// Number of bytes to reserve at the beginning of each buffer.
|
||||
/// </summary>
|
||||
private int _reserve;
|
||||
/// <summary>
|
||||
/// NetworkManager this is for.
|
||||
/// </summary>
|
||||
private NetworkManager _networkManager;
|
||||
/// <summary>
|
||||
/// Packet bundle to use for last enqueued data.
|
||||
/// </summary>
|
||||
private PacketBundle _sendLastBundle;
|
||||
/// <summary>
|
||||
/// True if being used as an sendLast bundle.
|
||||
/// </summary>
|
||||
private bool _isSendLastBundle;
|
||||
|
||||
internal PacketBundle(NetworkManager manager, int mtu, int reserve = 0, DataOrderType orderType = DataOrderType.Default)
|
||||
{
|
||||
_isSendLastBundle = orderType == DataOrderType.Last;
|
||||
// If this is not the send last packetbundle then make a new one.
|
||||
if (!_isSendLastBundle)
|
||||
_sendLastBundle = new(manager, mtu, reserve, DataOrderType.Last);
|
||||
|
||||
_networkManager = manager;
|
||||
_maximumTransportUnit = mtu;
|
||||
/* Allow bytes for the tick.
|
||||
* Modify reserve after making sendLast bundle
|
||||
* so that the wrong reserve is not passed into
|
||||
* the sendLast bundle. */
|
||||
reserve += TransportManager.UNPACKED_TICK_LENGTH;
|
||||
_reserve = reserve;
|
||||
// Add buffer requires the right reserve so call after setting.
|
||||
AddBuffer();
|
||||
|
||||
Reset(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < _buffers.Count; i++)
|
||||
_buffers[i].Dispose();
|
||||
|
||||
_sendLastBundle?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a buffer using current settings.
|
||||
/// </summary>
|
||||
private ByteBuffer AddBuffer()
|
||||
{
|
||||
ByteBuffer ba = new(_maximumTransportUnit, _reserve);
|
||||
_buffers.Add(ba);
|
||||
return ba;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets using current settings.
|
||||
/// </summary>
|
||||
internal void Reset(bool resetSendLast)
|
||||
{
|
||||
_bufferIndex = 0;
|
||||
|
||||
for (int i = 0; i < _buffers.Count; i++)
|
||||
_buffers[i].Reset();
|
||||
|
||||
if (resetSendLast)
|
||||
_sendLastBundle.Reset(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a segment to this packet bundle using the current WriteIndex.
|
||||
/// </summary>
|
||||
/// <param name = "forceNewBuffer">True to force data into a new buffer.</param>
|
||||
internal void Write(ArraySegment<byte> segment, bool forceNewBuffer = false, DataOrderType orderType = DataOrderType.Default)
|
||||
{
|
||||
/* If not the send last bundle and to send data last
|
||||
* then send using the send last bundle. */
|
||||
if (!_isSendLastBundle && orderType == DataOrderType.Last)
|
||||
{
|
||||
_sendLastBundle.Write(segment, forceNewBuffer, orderType);
|
||||
return;
|
||||
}
|
||||
|
||||
// Nothing to be written.
|
||||
if (segment.Count == 0)
|
||||
return;
|
||||
|
||||
/* If the segment count is larger than the mtu then
|
||||
* something went wrong. Nothing should call this method
|
||||
* directly except the TransportManager, which will automatically
|
||||
* split packets that exceed MTU into reliable ordered. */
|
||||
if (segment.Count > _maximumTransportUnit)
|
||||
{
|
||||
_networkManager.LogError($"Segment is length of {segment.Count} while MTU is {_maximumTransportUnit}. Packet was not split properly and will not be sent.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ByteBuffer ba = _buffers[_bufferIndex];
|
||||
/* Make a new buffer if...
|
||||
* forcing a new buffer and data has already been written to the current.
|
||||
* or---
|
||||
* segment.Count is more than what is remaining in the buffer. */
|
||||
bool useNewBuffer = (forceNewBuffer && ba.Length > _reserve) || segment.Count > ba.Remaining;
|
||||
if (useNewBuffer)
|
||||
{
|
||||
_bufferIndex++;
|
||||
// If need to make a new buffer then do so.
|
||||
if (_buffers.Count <= _bufferIndex)
|
||||
{
|
||||
ba = AddBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
ba = _buffers[_bufferIndex];
|
||||
ba.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
uint tick = _networkManager.TimeManager.LocalTick;
|
||||
ba.CopySegment(tick, segment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the packetBundle for send last.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal PacketBundle GetSendLastBundle() => _sendLastBundle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a buffer for the specified index. Returns true and outputs the buffer if it was successfully found.
|
||||
/// </summary>
|
||||
/// <param name = "index">Index of the buffer to retrieve.</param>
|
||||
/// <param name = "bb">Buffer retrieved from the list. Null if the specified buffer was not found.</param>
|
||||
internal bool GetBuffer(int index, out ByteBuffer bb)
|
||||
{
|
||||
bb = null;
|
||||
|
||||
if (index >= _buffers.Count || index < 0)
|
||||
{
|
||||
_networkManager.LogError($"Index of {index} is out of bounds. There are {_buffers.Count} available.");
|
||||
return false;
|
||||
}
|
||||
if (index > _bufferIndex)
|
||||
{
|
||||
_networkManager.LogError($"Index of {index} exceeds the number of written buffers. There are {WrittenBuffers} written buffers.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bb = _buffers[index];
|
||||
return bb.HasData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a PacketBundle for a channel. ResetPackets must be called afterwards.
|
||||
/// </summary>
|
||||
/// <param name = "channel"></param>
|
||||
/// <returns>True if PacketBundle is valid on the index and contains data.</returns>
|
||||
internal static bool GetPacketBundle(int channel, List<PacketBundle> bundles, out PacketBundle mtuBuffer)
|
||||
{
|
||||
// Out of bounds.
|
||||
if (channel >= bundles.Count)
|
||||
{
|
||||
mtuBuffer = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
mtuBuffer = bundles[channel];
|
||||
return mtuBuffer.HasData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb2f3ce9b5ac27f40b7daa9364fb4d60
|
||||
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/Connection/Buffer.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,216 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FishNet.Managing.Timing
|
||||
{
|
||||
public class EstimatedTick
|
||||
{
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// How to handle old ticks, specifically related to EstimatedTick.
|
||||
/// </summary>
|
||||
public enum OldTickOption : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Completely ignore old ticks.
|
||||
/// </summary>
|
||||
Discard = 0,
|
||||
/// <summary>
|
||||
/// Set LastRemoteTick but do not update RemoteTick.
|
||||
/// </summary>
|
||||
SetLastRemoteTick = 1,
|
||||
/// <summary>
|
||||
/// Set LastRemoteTick and RemoteTick.
|
||||
/// </summary>
|
||||
SetRemoteTick = 2
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Local tick when this was last updated.
|
||||
/// </summary>
|
||||
public uint LocalTick { get; private set; } = TimeManager.UNSET_TICK;
|
||||
/// <summary>
|
||||
/// Last remote tick this was updated with that was not out of order or a duplicate.
|
||||
/// </summary>
|
||||
public uint RemoteTick { get; private set; } = TimeManager.UNSET_TICK;
|
||||
/// <summary>
|
||||
/// Last remote tick received regardless if it was out of order or a duplicate.
|
||||
/// </summary>
|
||||
public uint LastRemoteTick { get; private set; } = TimeManager.UNSET_TICK;
|
||||
/// <summary>
|
||||
/// True if LastRemoteTick is equal to RemoteTick.
|
||||
/// This would indicate that the LastRemoteTick did not arrive out of order.
|
||||
/// </summary>
|
||||
public bool IsLastRemoteTickOrdered => LastRemoteTick == RemoteTick;
|
||||
/// <summary>
|
||||
/// True if value is unset.
|
||||
/// </summary>
|
||||
// Only need to check one value for unset as they all would be if not set.
|
||||
public bool IsUnset => LocalTick == TimeManager.UNSET_TICK;
|
||||
/// <summary>
|
||||
/// Last TimeManager specified during an Update call.
|
||||
/// </summary>
|
||||
private TimeManager _updateTimeManager;
|
||||
/// <summary>
|
||||
/// LocalTick when Value was last reset.
|
||||
/// </summary>
|
||||
private uint _valueLocalTick = TimeManager.UNSET_TICK;
|
||||
|
||||
/// <summary>
|
||||
/// Number of ticks LocalTick is being current LocalTick.
|
||||
/// </summary>
|
||||
public uint LocalTickDifference(TimeManager tm = null)
|
||||
{
|
||||
if (!TryAssignTimeManager(ref tm))
|
||||
return TimeManager.UNSET_TICK;
|
||||
|
||||
long value = tm.LocalTick - LocalTick;
|
||||
// Shouldn't be possible to be less than 0.
|
||||
if (value < 0)
|
||||
return TimeManager.UNSET_TICK;
|
||||
else if (value > uint.MaxValue)
|
||||
value = uint.MaxValue;
|
||||
|
||||
return (uint)value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if values were updated this tick.
|
||||
/// </summary>
|
||||
public bool IsCurrent(TimeManager tm = null)
|
||||
{
|
||||
if (!TryAssignTimeManager(ref tm))
|
||||
return false;
|
||||
|
||||
return !IsUnset && LocalTick == tm.LocalTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current estimated value.
|
||||
/// </summary>
|
||||
/// <param name = "nm">NetworkManager to use. When null default value will be returned.</param>
|
||||
public uint Value(TimeManager tm = null)
|
||||
{
|
||||
if (!TryAssignTimeManager(ref tm))
|
||||
return TimeManager.UNSET_TICK;
|
||||
|
||||
return Value(out _, tm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current estimated value. Outputs if value is current.
|
||||
/// </summary>
|
||||
/// <param name = "nm">NetworkManager to use. When null default value will be returned.</param>
|
||||
/// <param name = "isCurrent">True if the value was updated this local tick.</param>
|
||||
public uint Value(out bool isCurrent, TimeManager tm = null)
|
||||
{
|
||||
// Default value.
|
||||
isCurrent = false;
|
||||
|
||||
if (!TryAssignTimeManager(ref tm))
|
||||
return TimeManager.UNSET_TICK;
|
||||
if (IsUnset)
|
||||
return TimeManager.UNSET_TICK;
|
||||
|
||||
isCurrent = IsCurrent(tm);
|
||||
|
||||
uint diff = tm.LocalTick - _valueLocalTick;
|
||||
return diff + RemoteTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this EstimatedTick with values.
|
||||
/// </summary>
|
||||
public void Initialize(TimeManager tm, uint remoteTick = 0, uint lastRemoteTick = 0, uint localTick = 0)
|
||||
{
|
||||
_updateTimeManager = tm;
|
||||
RemoteTick = remoteTick;
|
||||
LastRemoteTick = lastRemoteTick;
|
||||
LocalTick = localTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates values.
|
||||
/// </summary>
|
||||
/// <param name = "tm">TimeManager to use.</param>
|
||||
/// <param name = "remoteTick">Remote tick being updated.</param>
|
||||
/// <param name = "oldTickOption">How to handle remoteTick if it is old.</param>
|
||||
/// ///
|
||||
/// <param name = "resetValue">True to reset Value based on this information. False will allow Value to continue to to estimate tick based on the last reset.</param>
|
||||
/// <returns>True if was able to update values.</returns>
|
||||
public bool Update(TimeManager tm, uint remoteTick, OldTickOption oldTickOption = OldTickOption.Discard, bool resetValue = true)
|
||||
{
|
||||
_updateTimeManager = tm;
|
||||
// Always set LastRemoteTick even if out of order.
|
||||
LastRemoteTick = remoteTick;
|
||||
// If cannot update with old values return.
|
||||
if (oldTickOption != OldTickOption.SetRemoteTick && remoteTick <= RemoteTick)
|
||||
return false;
|
||||
|
||||
// nm is assumed set here.
|
||||
LocalTick = tm.LocalTick;
|
||||
if (resetValue)
|
||||
_valueLocalTick = LocalTick;
|
||||
RemoteTick = remoteTick;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates values.
|
||||
/// </summary>
|
||||
/// <param name = "remoteTick">Remote tick being updated.</param>
|
||||
/// <param name = "oldTickOption">How to handle remoteTick if it is old.</param>
|
||||
/// <param name = "resetValue">True to reset Value based on this information. False will allow Value to continue to to estimate tick based on the last reset.</param>
|
||||
/// <returns>True if was able to update values.</returns>
|
||||
public bool Update(uint remoteTick, OldTickOption oldTickOption = OldTickOption.Discard, bool resetValue = true)
|
||||
{
|
||||
TimeManager tm = null;
|
||||
if (!TryAssignTimeManager(ref tm))
|
||||
return false;
|
||||
|
||||
return Update(tm, remoteTick, oldTickOption);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates Value based on current ticks.
|
||||
/// This is typically used when you want to control when Value is reset through the Update methods.
|
||||
/// </summary>
|
||||
public void UpdateValue()
|
||||
{
|
||||
_valueLocalTick = LocalTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a TimeManager reference to UpdateTimeManager if was null.
|
||||
/// </summary>
|
||||
/// <returns>True if the reference has value or was assigned value. False if the reference remains null.</returns>
|
||||
private bool TryAssignTimeManager(ref TimeManager tm)
|
||||
{
|
||||
if (tm == null)
|
||||
tm = _updateTimeManager;
|
||||
|
||||
return tm != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets values to unset and clears the NetworkManager.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
ResetTicks();
|
||||
_updateTimeManager = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets only tick values, leaving type references.
|
||||
/// </summary>
|
||||
public void ResetTicks()
|
||||
{
|
||||
LocalTick = TimeManager.UNSET_TICK;
|
||||
RemoteTick = TimeManager.UNSET_TICK;
|
||||
LastRemoteTick = TimeManager.UNSET_TICK;
|
||||
_valueLocalTick = TimeManager.UNSET_TICK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36df408e03d0cab4ab728897541c2bf2
|
||||
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/Connection/EstimatedTick.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,108 @@
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Object;
|
||||
using FishNet.Transporting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Connection
|
||||
{
|
||||
public partial class NetworkConnection
|
||||
{
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// PacketBundles to send to this connection. An entry will be made for each channel.
|
||||
/// </summary>
|
||||
private List<PacketBundle> _toClientBundles = new();
|
||||
/// <summary>
|
||||
/// True if this object has been dirtied.
|
||||
/// </summary>
|
||||
private bool _serverDirtied;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this script.
|
||||
/// </summary>
|
||||
private void InitializeBuffer()
|
||||
{
|
||||
for (byte i = 0; i < TransportManager.CHANNEL_COUNT; i++)
|
||||
{
|
||||
int mtu = NetworkManager.TransportManager.GetLowestMTU(i);
|
||||
_toClientBundles.Add(new(NetworkManager, mtu));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a broadcast to this connection.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of broadcast to send.</typeparam>
|
||||
/// <param name = "message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
|
||||
/// <param name = "requireAuthenticated">True if the client must be authenticated for this broadcast to send.</param>
|
||||
/// <param name = "channel">Channel to send on.</param>
|
||||
public void Broadcast<T>(T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
|
||||
{
|
||||
if (!IsActive)
|
||||
NetworkManager.LogError($"Connection is not valid, cannot send broadcast.");
|
||||
else
|
||||
NetworkManager.ServerManager.Broadcast(this, message, requireAuthenticated, channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends data from the server to a client.
|
||||
/// </summary>
|
||||
/// <param name = "forceNewBuffer">True to force data into a new buffer.</param>
|
||||
internal void SendToClient(byte channel, ArraySegment<byte> segment, bool forceNewBuffer = false, DataOrderType orderType = DataOrderType.Default)
|
||||
{
|
||||
// Cannot send data when disconnecting.
|
||||
if (Disconnecting)
|
||||
return;
|
||||
|
||||
if (!IsActive)
|
||||
{
|
||||
NetworkManager.LogWarning($"Data cannot be sent to connection {ClientId} because it is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
// If channel is out of bounds then default to the first channel.
|
||||
if (channel >= _toClientBundles.Count)
|
||||
channel = 0;
|
||||
|
||||
_toClientBundles[channel].Write(segment, forceNewBuffer, orderType);
|
||||
ServerDirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a PacketBundle for a channel. ResetPackets must be called afterwards.
|
||||
/// </summary>
|
||||
/// <param name = "channel"></param>
|
||||
/// <returns>True if PacketBundle is valid on the index and contains data.</returns>
|
||||
internal bool GetPacketBundle(int channel, out PacketBundle packetBundle)
|
||||
{
|
||||
return PacketBundle.GetPacketBundle(channel, _toClientBundles, out packetBundle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the server has data to send to this connection.
|
||||
/// </summary>
|
||||
private void ServerDirty()
|
||||
{
|
||||
bool wasDirty = _serverDirtied;
|
||||
_serverDirtied = true;
|
||||
|
||||
// If not yet dirty then tell transport manager this is dirty.
|
||||
if (!wasDirty)
|
||||
NetworkManager.TransportManager.ServerDirty(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets that there is data to send.
|
||||
/// </summary>
|
||||
internal void ResetServerDirty()
|
||||
{
|
||||
_serverDirtied = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5bc17ee5bac499347a6fbad9dd24b7b0
|
||||
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/Connection/NetworkConnection.Buffer.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,92 @@
|
||||
using FishNet.Component.Observing;
|
||||
using FishNet.Managing;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for a connected client used to perform actions on and gather information for the declared client.
|
||||
/// </summary>
|
||||
public partial class NetworkConnection
|
||||
{
|
||||
#region Internal.
|
||||
/// <summary>
|
||||
/// Current GridEntry this connection is in.
|
||||
/// </summary>
|
||||
internal GridEntry HashGridEntry = HashGrid.EmptyGridEntry;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// HashGrid for the NetworkManager on this connection.
|
||||
/// </summary>
|
||||
private HashGrid _hashGrid;
|
||||
/// <summary>
|
||||
/// Last unscaled time the HashGrid position was updated with this connections Objects.
|
||||
/// </summary>
|
||||
private float _nextHashGridUpdateTime;
|
||||
/// <summary>
|
||||
/// Current GridPosition this connection is in.
|
||||
/// </summary>
|
||||
private Vector2Int _hashGridPosition = HashGrid.UnsetGridPosition;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Called when the FirstObject changes for this connection.
|
||||
/// </summary>
|
||||
private void Observers_FirstObjectChanged()
|
||||
{
|
||||
UpdateHashGridPositions(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this for use.
|
||||
/// </summary>
|
||||
private void Observers_Initialize(NetworkManager nm)
|
||||
{
|
||||
nm.TryGetInstance(out _hashGrid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the HashGridPosition value for FirstObject.
|
||||
/// </summary>
|
||||
internal void UpdateHashGridPositions(bool force)
|
||||
{
|
||||
if (_hashGrid == null)
|
||||
return;
|
||||
|
||||
float unscaledTime = Time.unscaledTime;
|
||||
// Not enough time has passed to update.
|
||||
if (!force && unscaledTime < _nextHashGridUpdateTime)
|
||||
return;
|
||||
|
||||
const float updateInterval = 1f;
|
||||
_nextHashGridUpdateTime = unscaledTime + updateInterval;
|
||||
|
||||
if (FirstObject == null)
|
||||
{
|
||||
HashGridEntry = HashGrid.EmptyGridEntry;
|
||||
_hashGridPosition = HashGrid.UnsetGridPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2Int newPosition = _hashGrid.GetHashGridPosition(FirstObject);
|
||||
if (newPosition != _hashGridPosition)
|
||||
{
|
||||
_hashGridPosition = newPosition;
|
||||
HashGridEntry = _hashGrid.GetGridEntry(newPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets values.
|
||||
/// </summary>
|
||||
private void Observers_Reset()
|
||||
{
|
||||
_hashGrid = null;
|
||||
_hashGridPosition = HashGrid.UnsetGridPosition;
|
||||
_nextHashGridUpdateTime = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1d1fe830d38ad043bd5e616cf6386ed
|
||||
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/Connection/NetworkConnection.Observers.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,102 @@
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Timing;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for a connected client used to perform actions on and gather information for the declared client.
|
||||
/// </summary>
|
||||
public partial class NetworkConnection
|
||||
{
|
||||
#pragma warning disable CS0414
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Last tick this connection sent a ping.
|
||||
/// </summary>
|
||||
private uint _lastPingTick;
|
||||
///// <summary>
|
||||
///// Number of times client has excessively sent a ping.
|
||||
///// </summary>
|
||||
// private float _excessivePingCount;
|
||||
/// <summary>
|
||||
/// Ticks expected between each ping.
|
||||
/// </summary>
|
||||
private uint _requiredPingTicks;
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
/// <summary>
|
||||
/// Number of times a ping may occur excessively before server will punish connection.
|
||||
/// </summary>
|
||||
private const byte EXCESSIVE_PING_LIMIT = 10;
|
||||
#endregion
|
||||
|
||||
#pragma warning restore CS0414
|
||||
/// <summary>
|
||||
/// Initializes for ping.
|
||||
/// </summary>
|
||||
private void InitializePing()
|
||||
{
|
||||
// Give the client some room for error.
|
||||
float requiredInterval = NetworkManager.TimeManager.PingInterval * 0.85f;
|
||||
// Round down so required ticks is lower.
|
||||
_requiredPingTicks = NetworkManager.TimeManager.TimeToTicks(requiredInterval, TickRounding.RoundDown);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets PingPong values.
|
||||
/// </summary>
|
||||
private void ResetPingPong()
|
||||
{
|
||||
// _excessivePingCount = 0;
|
||||
_lastPingTick = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a ping is received from this connection. Returns if can respond to ping.
|
||||
/// </summary>
|
||||
/// <returns>True to respond to ping, false to kick connection.</returns>
|
||||
internal bool CanPingPong()
|
||||
{
|
||||
/* Only check ping conditions in build. Editors are prone to pausing which can
|
||||
* improperly kick clients. */
|
||||
TimeManager tm = NetworkManager == null ? InstanceFinder.TimeManager : NetworkManager.TimeManager;
|
||||
/* Server FPS is running low, timing isn't reliable enough to kick clients.
|
||||
* Respond with clients ping and remove infractions just in case the
|
||||
* client received some from other server instabilities. */
|
||||
if (tm.HasMultipleTicksOccurred(timeSinceMultipleTicks: 1f))
|
||||
{
|
||||
// _excessivePingCount = 0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint currentTick = tm.Tick;
|
||||
uint difference = currentTick - _lastPingTick;
|
||||
_lastPingTick = currentTick;
|
||||
|
||||
// Ping sent too quickly.
|
||||
if (difference < _requiredPingTicks)
|
||||
{
|
||||
// _excessivePingCount += 1f;
|
||||
////Ping limit hit.
|
||||
// if (_excessivePingCount >= EXCESSIVE_PING_LIMIT)
|
||||
// {
|
||||
// NetworkManager.LogWarning($"Kicked connectionId {ClientId} for excessive pings.");
|
||||
// Disconnect(true);
|
||||
// }
|
||||
|
||||
// Return to not send pong back.
|
||||
return false;
|
||||
}
|
||||
// Ping isnt too fast.
|
||||
else
|
||||
{
|
||||
// _excessivePingCount = UnityEngine.Mathf.Max(0f, _excessivePingCount - 0.5f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20633bf6995f6534ba2b27e1eab3054d
|
||||
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/Connection/NetworkConnection.PingPong.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,114 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Predicting;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FishNet.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for a connected client used to perform actions on and gather information for the declared client.
|
||||
/// </summary>
|
||||
public partial class NetworkConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// Approximate replicate tick on the server for this connection.
|
||||
/// This also contains the last set value for local and remote.
|
||||
/// </summary>
|
||||
public EstimatedTick ReplicateTick { get; private set; } = new();
|
||||
/// <summary>
|
||||
/// Writers for states.
|
||||
/// </summary>
|
||||
internal List<PooledWriter> PredictionStateWriters = new();
|
||||
internal void Prediction_Initialize(NetworkManager manager, bool asServer) { }
|
||||
|
||||
/// <summary>
|
||||
/// Writes a prediction state.
|
||||
/// </summary>
|
||||
/// <param name = "data"></param>
|
||||
internal void WriteState(PooledWriter data)
|
||||
{
|
||||
#if !DEVELOPMENT
|
||||
// Do not send states to clientHost.
|
||||
if (IsLocalClient)
|
||||
return;
|
||||
#endif
|
||||
|
||||
TimeManager timeManager = NetworkManager.TimeManager;
|
||||
TransportManager transportManager = NetworkManager.TransportManager;
|
||||
uint ticksBehind = IsLocalClient ? 0 : PacketTick.LocalTickDifference(timeManager);
|
||||
/* If it's been a really long while the client could just be setting up
|
||||
* or dropping. Only send if they've communicated within 5 seconds. */
|
||||
if (ticksBehind > timeManager.TickRate * 5)
|
||||
return;
|
||||
|
||||
int mtu = transportManager.GetLowestMTU((byte)Channel.Unreliable);
|
||||
PooledWriter stateWriter;
|
||||
int writerCount = PredictionStateWriters.Count;
|
||||
/* Conditions to create a new writer are:
|
||||
* - writer does not exist yet.
|
||||
* - data length + currentWriter length > mtu */
|
||||
Channel channel = Channel.Unreliable;
|
||||
if (writerCount > 0)
|
||||
transportManager.CheckSetReliableChannel(data.Length + PredictionStateWriters[writerCount - 1].Length, ref channel);
|
||||
/* If no writers or if channel would be forced reliable.
|
||||
*
|
||||
* By checking if channel would be reliable this is
|
||||
* essentially asking if (current written + new data) would
|
||||
* exceed mtu. When it would get a new writer to try
|
||||
* and favor unreliable. Emphasis on try, because if some
|
||||
* really unlikely chance the data was really large it would
|
||||
* still send on reliable down the line. */
|
||||
if (writerCount == 0 || channel == Channel.Reliable)
|
||||
{
|
||||
stateWriter = WriterPool.Retrieve(mtu);
|
||||
PredictionStateWriters.Add(stateWriter);
|
||||
stateWriter.Skip(PredictionManager.STATE_HEADER_RESERVE_LENGTH);
|
||||
/// 2 PacketId.
|
||||
/// 4 Last replicate tick run for connection.
|
||||
/// 4 Length unpacked.
|
||||
}
|
||||
else
|
||||
{
|
||||
stateWriter = PredictionStateWriters[writerCount - 1];
|
||||
}
|
||||
|
||||
stateWriter.WriteArraySegment(data.GetArraySegment());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores prediction writers to be re-used later.
|
||||
/// </summary>
|
||||
internal void StorePredictionStateWriters()
|
||||
{
|
||||
for (int i = 0; i < PredictionStateWriters.Count; i++)
|
||||
WriterPool.Store(PredictionStateWriters[i]);
|
||||
|
||||
PredictionStateWriters.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the last tick a NetworkBehaviour replicated with.
|
||||
/// </summary>
|
||||
/// <param name = "setUnordered">True to set unordered value, false to set ordered.</param>
|
||||
internal void SetReplicateTick(uint value, EstimatedTick.OldTickOption oldTickOption = EstimatedTick.OldTickOption.Discard)
|
||||
{
|
||||
ReplicateTick.Update(value, oldTickOption);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets NetworkConnection.
|
||||
/// </summary>
|
||||
private void Prediction_Reset()
|
||||
{
|
||||
StorePredictionStateWriters();
|
||||
ReplicateTick.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0068eb93a417a44b9151b61a08d8547
|
||||
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/Connection/NetworkConnection.Prediction.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,81 @@
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Serializing;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for a connected client used to perform actions on and gather information for the declared client.
|
||||
/// </summary>
|
||||
public partial class NetworkConnection
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Returns true if this connection is a clientHost.
|
||||
/// </summary>
|
||||
public bool IsHost => NetworkManager == null ? false : NetworkManager.IsServerStarted && this == NetworkManager.ClientManager.Connection;
|
||||
/// <summary>
|
||||
/// Returns if this connection is for the local client.
|
||||
/// </summary>
|
||||
public bool IsLocalClient => NetworkManager == null ? false : NetworkManager.ClientManager.Connection == this;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns the address of this connection.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetAddress()
|
||||
{
|
||||
if (!IsValid)
|
||||
return string.Empty;
|
||||
if (NetworkManager == null)
|
||||
return string.Empty;
|
||||
|
||||
return NetworkManager.TransportManager.Transport.GetConnectionAddress(ClientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kicks a connection immediately while invoking OnClientKick.
|
||||
/// </summary>
|
||||
/// <param name = "kickReason">Reason client is being kicked.</param>
|
||||
/// <param name = "loggingType">How to print logging as.</param>
|
||||
/// <param name = "log">Optional message to be debug logged.</param>
|
||||
public void Kick(KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "")
|
||||
{
|
||||
if (CanKick())
|
||||
NetworkManager.ServerManager.Kick(this, kickReason, loggingType, log);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kicks a connection immediately while invoking OnClientKick.
|
||||
/// </summary>
|
||||
/// <param name = "reader">Reader to clear before kicking.</param>
|
||||
/// <param name = "kickReason">Reason client is being kicked.</param>
|
||||
/// <param name = "loggingType">How to print logging as.</param>
|
||||
/// <param name = "log">Optional message to be debug logged.</param>
|
||||
public void Kick(Reader reader, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "")
|
||||
{
|
||||
if (CanKick())
|
||||
NetworkManager.ServerManager.Kick(this, reader, kickReason, loggingType, log);
|
||||
}
|
||||
|
||||
private bool CanKick()
|
||||
{
|
||||
// Connection isn't valid, calling kick on an empty connection.
|
||||
if (!IsValid)
|
||||
return false;
|
||||
|
||||
// Should never happen.
|
||||
if (NetworkManager == null)
|
||||
{
|
||||
NetworkManager = InstanceFinder.NetworkManager;
|
||||
NetworkManager.LogError($"NetworkManager was not set for connection {ToString()}. InstanceFinder has been used.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d45abc242399194b85e6c16bcb3676b
|
||||
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/Connection/NetworkConnection.QOL.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,487 @@
|
||||
using FishNet.Component.Observing;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Object;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.Transporting;
|
||||
using UnityEngine.SceneManagement;
|
||||
using static FishNet.Managing.Timing.EstimatedTick;
|
||||
|
||||
namespace FishNet.Connection
|
||||
{
|
||||
public static class NetworkConnectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// True if this connection is valid. An invalid connection indicates no client is set for this reference.
|
||||
/// Null references can be used with this method.
|
||||
/// </summary>
|
||||
public static bool IsValid(this NetworkConnection c)
|
||||
{
|
||||
if (c == null)
|
||||
return false;
|
||||
|
||||
return c.IsValid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstraction of a NetworkConnection's broadcasting duties
|
||||
/// </summary>
|
||||
public interface INetworkConnectionBroadcaster
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a broadcast to this connection.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of broadcast to send.</typeparam>
|
||||
/// <param name = "message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
|
||||
/// <param name = "requireAuthenticated">True if the client must be authenticated for this broadcast to send.</param>
|
||||
/// <param name = "channel">Channel to send on.</param>
|
||||
void Broadcast<T>(T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A container for a connected client used to perform actions on and gather information for the declared client.
|
||||
/// </summary>
|
||||
public partial class NetworkConnection : IResettable, IEquatable<NetworkConnection>, INetworkConnectionBroadcaster
|
||||
{
|
||||
#region Internal.
|
||||
/// <summary>
|
||||
/// Tick when Disconnecting was set.
|
||||
/// </summary>
|
||||
internal uint DisconnectingTick { get; private set; }
|
||||
/// <summary>
|
||||
/// ObjectIds to use for predicted spawning.
|
||||
/// </summary>
|
||||
internal Queue<int> PredictedObjectIds = new();
|
||||
/// <summary>
|
||||
/// True if the client has sent the same version that the server is on.
|
||||
/// </summary>
|
||||
internal bool HasSentVersion;
|
||||
/// <summary>
|
||||
/// LocalTick of the server when this connection was established. This value is not set for clients.
|
||||
/// </summary>
|
||||
internal uint ServerConnectionTick;
|
||||
#endregion
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Called after this connection has loaded start scenes. Boolean will be true if asServer. Available to this connection and server.
|
||||
/// </summary>
|
||||
public event Action<NetworkConnection, bool> OnLoadedStartScenes;
|
||||
/// <summary>
|
||||
/// Called after connection gains ownership of an object, and after the object has been added to Objects. Available to this connection and server.
|
||||
/// </summary>
|
||||
public event Action<NetworkObject> OnObjectAdded;
|
||||
/// <summary>
|
||||
/// Called after connection loses ownership of an object, and after the object has been removed from Objects. Available to this connection and server.
|
||||
/// </summary>
|
||||
public event Action<NetworkObject> OnObjectRemoved;
|
||||
/// <summary>
|
||||
/// NetworkManager managing this class.
|
||||
/// </summary>
|
||||
public NetworkManager NetworkManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if connection has loaded start scenes. Available to this connection and server.
|
||||
/// </summary>
|
||||
public bool LoadedStartScenes() => _loadedStartScenesAsServer || _loadedStartScenesAsClient;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public bool LoadedStartScenes(bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
return _loadedStartScenesAsServer;
|
||||
else
|
||||
return _loadedStartScenesAsClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TransportIndex this connection is on.
|
||||
/// For security reasons this value will be unset on clients if this is not their connection.
|
||||
/// This is not yet used.
|
||||
/// </summary>
|
||||
public int TransportIndex { get; private set; } = -1;
|
||||
/// <summary>
|
||||
/// True if this connection is authenticated. Only available to server.
|
||||
/// </summary>
|
||||
public bool IsAuthenticated { get; private set; }
|
||||
[Obsolete("Use IsAuthenticated.")] // Remove in V5
|
||||
public bool Authenticated
|
||||
{
|
||||
get => IsAuthenticated;
|
||||
set => IsAuthenticated = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// True if this connection IsValid and not Disconnecting.
|
||||
/// </summary>
|
||||
public bool IsActive => ClientId >= 0 && !Disconnecting;
|
||||
/// <summary>
|
||||
/// True if this connection is valid. An invalid connection indicates no client is set for this reference.
|
||||
/// </summary>
|
||||
public bool IsValid => ClientId >= 0;
|
||||
/// <summary>
|
||||
/// Unique Id for this connection.
|
||||
/// </summary>
|
||||
public int ClientId = -1;
|
||||
/// <summary>
|
||||
/// Objects owned by this connection. Available to this connection and server.
|
||||
/// </summary>
|
||||
public HashSet<NetworkObject> Objects = new();
|
||||
/// <summary>
|
||||
/// The first object within Objects.
|
||||
/// </summary>
|
||||
public NetworkObject FirstObject { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets a custom FirstObject. This connection must be owner of the specified object.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
public void SetFirstObject(NetworkObject nob)
|
||||
{
|
||||
// Invalid object.
|
||||
if (!Objects.Contains(nob))
|
||||
{
|
||||
string errMessage = $"FirstObject for {ClientId} cannot be set to {nob.name} as it's not within Objects for this connection.";
|
||||
NetworkManager.LogError(errMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
FirstObject = nob;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scenes this connection is in. Available to this connection and server.
|
||||
/// </summary>
|
||||
public HashSet<Scene> Scenes { get; private set; } = new();
|
||||
/// <summary>
|
||||
/// True if this connection is being disconnected. Only available to server.
|
||||
/// </summary>
|
||||
public bool Disconnecting { get; private set; }
|
||||
/// <summary>
|
||||
/// Custom data associated with this connection which may be modified by the user.
|
||||
/// The value of this field are not synchronized over the network.
|
||||
/// </summary>
|
||||
public object CustomData = null;
|
||||
/// <summary>
|
||||
/// Tick of the last packet received from this connection which was not out of order.
|
||||
/// This value is only available on the server.
|
||||
/// </summary>
|
||||
public EstimatedTick PacketTick { get; private set; } = new();
|
||||
/// <summary>
|
||||
/// Approximate local tick as it is on this connection.
|
||||
/// This also contains the last set value for local and remote.
|
||||
/// </summary>
|
||||
public EstimatedTick LocalTick { get; private set; } = new();
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// True if loaded start scenes as server.
|
||||
/// </summary>
|
||||
private bool _loadedStartScenesAsServer;
|
||||
/// <summary>
|
||||
/// True if loaded start scenes as client.
|
||||
/// </summary>
|
||||
private bool _loadedStartScenesAsClient;
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
/// <summary>
|
||||
/// Value used when ClientId has not been set.
|
||||
/// </summary>
|
||||
public const int UNSET_CLIENTID_VALUE = -1;
|
||||
/// <summary>
|
||||
/// Maximum value a ClientId can be.
|
||||
/// </summary>
|
||||
public const int MAXIMUM_CLIENTID_VALUE = int.MaxValue;
|
||||
/// <summary>
|
||||
/// Maximum value a ClientId can be excluding simulated value.
|
||||
/// </summary>
|
||||
public const int MAXIMUM_CLIENTID_WITHOUT_SIMULATED_VALUE = int.MaxValue - 1;
|
||||
/// <summary>
|
||||
/// Value to use as a ClientId when simulating a local client without actually using a socket.
|
||||
/// </summary>
|
||||
public const int SIMULATED_CLIENTID_VALUE = int.MaxValue;
|
||||
/// <summary>
|
||||
/// Number of bytes to reserve for a connectionId if writing the value uncompressed.
|
||||
/// </summary>
|
||||
public const int CLIENTID_UNCOMPRESSED_RESERVE_LENGTH = 4;
|
||||
#endregion
|
||||
|
||||
#region Comparers.
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is NetworkConnection nc)
|
||||
return nc.ClientId == ClientId;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Equals(NetworkConnection nc)
|
||||
{
|
||||
if (nc is null)
|
||||
return false;
|
||||
// If either is -1 Id.
|
||||
if (ClientId == UNSET_CLIENTID_VALUE || nc.ClientId == UNSET_CLIENTID_VALUE)
|
||||
return false;
|
||||
// Same object.
|
||||
if (ReferenceEquals(this, nc))
|
||||
return true;
|
||||
|
||||
return ClientId == nc.ClientId;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return ClientId;
|
||||
}
|
||||
|
||||
public static bool operator ==(NetworkConnection a, NetworkConnection b)
|
||||
{
|
||||
if (a is null && b is null)
|
||||
return true;
|
||||
if (a is null && !(b is null))
|
||||
return false;
|
||||
|
||||
return b == null ? a.Equals(b) : b.Equals(a);
|
||||
}
|
||||
|
||||
public static bool operator !=(NetworkConnection a, NetworkConnection b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
#endregion
|
||||
|
||||
[APIExclude]
|
||||
public NetworkConnection() { }
|
||||
|
||||
[APIExclude]
|
||||
public NetworkConnection(NetworkManager manager, int clientId, int transportIndex, bool asServer)
|
||||
{
|
||||
Initialize(manager, clientId, transportIndex, asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs data about this connection as a string.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
int clientId = ClientId;
|
||||
string ip = NetworkManager != null ? NetworkManager.TransportManager.Transport.GetConnectionAddress(clientId) : "Unset";
|
||||
return $"Id [{ClientId}] Address [{ip}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this for use.
|
||||
/// </summary>
|
||||
private void Initialize(NetworkManager nm, int clientId, int transportIndex, bool asServer)
|
||||
{
|
||||
NetworkManager = nm;
|
||||
LocalTick.Initialize(nm.TimeManager);
|
||||
PacketTick.Initialize(nm.TimeManager);
|
||||
if (asServer)
|
||||
ServerConnectionTick = nm.TimeManager.LocalTick;
|
||||
TransportIndex = transportIndex;
|
||||
ClientId = clientId;
|
||||
/* Set PacketTick to current values so
|
||||
* that timeouts and other things around
|
||||
* first packet do not occur due to an unset value. */
|
||||
PacketTick.Update(nm.TimeManager, 0, OldTickOption.SetLastRemoteTick);
|
||||
Observers_Initialize(nm);
|
||||
Prediction_Initialize(nm, asServer);
|
||||
// Only the server uses the ping and buffer.
|
||||
if (asServer)
|
||||
{
|
||||
InitializeBuffer();
|
||||
InitializePing();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets Disconnecting boolean for this connection.
|
||||
/// </summary>
|
||||
internal void SetDisconnecting(bool value)
|
||||
{
|
||||
Disconnecting = value;
|
||||
if (Disconnecting)
|
||||
DisconnectingTick = NetworkManager.TimeManager.LocalTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects this connection.
|
||||
/// </summary>
|
||||
/// <param name = "immediately">True to disconnect immediately. False to send any pending data first.</param>
|
||||
public void Disconnect(bool immediately)
|
||||
{
|
||||
if (!IsValid)
|
||||
{
|
||||
NetworkManager.LogWarning($"Disconnect called on an invalid connection.");
|
||||
return;
|
||||
}
|
||||
if (Disconnecting)
|
||||
{
|
||||
NetworkManager.LogWarning($"ClientId {ClientId} is already disconnecting.");
|
||||
return;
|
||||
}
|
||||
|
||||
SetDisconnecting(true);
|
||||
// If immediately then force disconnect through transport.
|
||||
if (immediately)
|
||||
NetworkManager.TransportManager.Transport.StopConnection(ClientId, true);
|
||||
// Otherwise mark dirty so server will push out any pending information, and then disconnect.
|
||||
else
|
||||
ServerDirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if just loaded start scenes and sets them as loaded if not.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal bool SetLoadedStartScenes(bool asServer)
|
||||
{
|
||||
bool loadedToCheck = asServer ? _loadedStartScenesAsServer : _loadedStartScenesAsClient;
|
||||
// Result becomes true if not yet loaded start scenes.
|
||||
bool result = !loadedToCheck;
|
||||
if (asServer)
|
||||
_loadedStartScenesAsServer = true;
|
||||
else
|
||||
_loadedStartScenesAsClient = true;
|
||||
|
||||
OnLoadedStartScenes?.Invoke(this, asServer);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets connection as authenticated.
|
||||
/// </summary>
|
||||
internal void ConnectionAuthenticated()
|
||||
{
|
||||
IsAuthenticated = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds to Objects owned by this connection.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
internal void AddObject(NetworkObject nob)
|
||||
{
|
||||
if (!IsValid)
|
||||
return;
|
||||
|
||||
Objects.Add(nob);
|
||||
// If adding the first object then set new FirstObject.
|
||||
if (Objects.Count == 1)
|
||||
SetFirstObject();
|
||||
|
||||
OnObjectAdded?.Invoke(nob);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes from Objects owned by this connection.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
internal void RemoveObject(NetworkObject nob)
|
||||
{
|
||||
if (!IsValid)
|
||||
{
|
||||
ClearObjects();
|
||||
return;
|
||||
}
|
||||
|
||||
Objects.Remove(nob);
|
||||
// If removing the first object then set a new one.
|
||||
if (nob == FirstObject)
|
||||
SetFirstObject();
|
||||
|
||||
OnObjectRemoved?.Invoke(nob);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all Objects.
|
||||
/// </summary>
|
||||
private void ClearObjects()
|
||||
{
|
||||
Objects.Clear();
|
||||
FirstObject = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets FirstObject using the first element in Objects.
|
||||
/// </summary>
|
||||
private void SetFirstObject()
|
||||
{
|
||||
if (Objects.Count == 0)
|
||||
{
|
||||
FirstObject = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (NetworkObject nob in Objects)
|
||||
{
|
||||
FirstObject = nob;
|
||||
Observers_FirstObjectChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a scene to this connections Scenes.
|
||||
/// </summary>
|
||||
/// <param name = "scene"></param>
|
||||
/// <returns></returns>
|
||||
internal bool AddToScene(Scene scene)
|
||||
{
|
||||
return Scenes.Add(scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a scene to this connections Scenes.
|
||||
/// </summary>
|
||||
/// <param name = "scene"></param>
|
||||
/// <returns></returns>
|
||||
internal bool RemoveFromScene(Scene scene)
|
||||
{
|
||||
return Scenes.Remove(scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all states for re-use.
|
||||
/// </summary>
|
||||
public void ResetState()
|
||||
{
|
||||
MatchCondition.RemoveFromMatchesWithoutRebuild(this, NetworkManager);
|
||||
|
||||
foreach (PacketBundle p in _toClientBundles)
|
||||
p.Dispose();
|
||||
_toClientBundles.Clear();
|
||||
|
||||
ServerConnectionTick = 0;
|
||||
PacketTick.Reset();
|
||||
LocalTick.Reset();
|
||||
TransportIndex = -1;
|
||||
ClientId = -1;
|
||||
ClearObjects();
|
||||
IsAuthenticated = false;
|
||||
HasSentVersion = false;
|
||||
NetworkManager = null;
|
||||
_loadedStartScenesAsClient = false;
|
||||
_loadedStartScenesAsServer = false;
|
||||
SetDisconnecting(false);
|
||||
Scenes.Clear();
|
||||
PredictedObjectIds.Clear();
|
||||
ResetPingPong();
|
||||
Observers_Reset();
|
||||
Prediction_Reset();
|
||||
}
|
||||
|
||||
public void InitializeState() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01c7bfc71e29621408451fa2fa6b1a0b
|
||||
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/Connection/NetworkConnection.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FishNet.Managing.Timing { }
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 139cb2731a4787e47919ef19010d7953
|
||||
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/Connection/OldTickOption.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3eb13a869216f634eb89b030d30bf879
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace FishNet.Documenting
|
||||
{
|
||||
public class APIExcludeAttribute : Attribute { }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d101aaaeb244ac48bfe2d7d05308c1c
|
||||
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/Documenting/Attributes.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 652ea22ae970f014187104e2330ca034
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -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 @@
|
||||
//
|
||||
@@ -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:
|
||||
Binary file not shown.
@@ -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.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user