Initial Commit
This commit is contained in:
parent
53eb92e9af
commit
270ab7d11f
15341 changed files with 700234 additions and 0 deletions
|
@ -0,0 +1,108 @@
|
|||
# Changelog
|
||||
This file documents all notable changes to this package. Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
|
||||
|
||||
## [0.1.0] - 2021-03-23
|
||||
|
||||
This is the initial experimental Unity MLAPI Package, v0.1.0.
|
||||
|
||||
### New Features
|
||||
|
||||
- Refactored a new standard for Remote Procedure Call (RPC) in MLAPI which provides increased performance, significantly reduced boilerplate code, and extensibility for future-proofed code. MLAPI RPC includes `ServerRpc` and `ClientRpc` to execute logic on the server and client-side. This provides a single performant unified RPC solution, replacing MLAPI Convenience and Performance RPC (see [here](#removed-features)).
|
||||
- Added standarized serialization types, including built-in and custom serialization flows. See [RFC #2](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0002-serializable-types.md) for details.
|
||||
- `INetworkSerializable` interface replaces `IBitWritable`.
|
||||
- Added `NetworkSerializer`..., which is the main aggregator that implements serialization code for built-in supported types and holds `NetworkReader` and `NetworkWriter` instances internally.
|
||||
- Added a Network Update Loop infrastructure that aids Netcode systems to update (such as RPC queue and transport) outside of the standard `MonoBehaviour` event cycle. See [RFC #8](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0008-network-update-loop.md) and the following details:
|
||||
|
||||
- It uses Unity's [low-level Player Loop API](https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoop.html) and allows for registering `INetworkUpdateSystem`s with `NetworkUpdate` methods to be executed at specific `NetworkUpdateStage`s, which may also be before or after `MonoBehaviour`-driven game logic execution.
|
||||
- You will typically interact with `NetworkUpdateLoop` for registration and `INetworkUpdateSystem` for implementation.
|
||||
- `NetworkVariable`s are now tick-based using the `NetworkTickSystem`, tracking time through network interactions and syncs.
|
||||
|
||||
- Added message batching to handle consecutive RPC requests sent to the same client. `RpcBatcher` sends batches based on requests from the `RpcQueueProcessing`, by batch size threshold or immediately.
|
||||
- [GitHub 494](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/494): Added a constraint to allow one `NetworkObject` per `GameObject`, set through the `DisallowMultipleComponent` attribute.
|
||||
- Integrated MLAPI with the Unity Profiler for versions 2020.2 and later:
|
||||
|
||||
- Added new profiler modules for MLAPI that report important network data.
|
||||
- Attached the profiler to a remote player to view network data over the wire.
|
||||
|
||||
- A test project is available for building and experimenting with MLAPI features. This project is available in the MLAPI GitHub [testproject folder](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/tree/release/0.1.0/testproject).
|
||||
- Added a [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) new GitHub repository to accept extensions from the MLAPI community. Current extensions include moved MLAPI features for lag compensation (useful for Server Authoritative actions) and `TrackedObject`.
|
||||
|
||||
### Changes
|
||||
|
||||
- [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): MLAPI now uses the Unity Package Manager for installation management.
|
||||
- Added functionality and usability to `NetworkVariable`, previously called `NetworkVar`. Updates enhance options and fully replace the need for `SyncedVar`s.
|
||||
- [GitHub 507](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/507): Reimplemented `NetworkAnimator`, which synchronizes animation states for networked objects.
|
||||
- GitHub [444](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/444) and [455](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/455): Channels are now represented as bytes instead of strings.
|
||||
|
||||
For users of previous versions of MLAPI, this release renames APIs due to refactoring. All obsolete marked APIs have been removed as per [GitHub 513](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/513) and [GitHub 514](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/514).
|
||||
|
||||
| Previous MLAPI Versions | V 0.1.0 Name |
|
||||
| -- | -- |
|
||||
| `NetworkingManager` | `NetworkManager` |
|
||||
| `NetworkedObject` | `NetworkObject` |
|
||||
| `NetworkedBehaviour` | `NetworkBehaviour` |
|
||||
| `NetworkedClient` | `NetworkClient` |
|
||||
| `NetworkedPrefab` | `NetworkPrefab` |
|
||||
| `NetworkedVar` | `NetworkVariable` |
|
||||
| `NetworkedTransform` | `NetworkTransform` |
|
||||
| `NetworkedAnimator` | `NetworkAnimator` |
|
||||
| `NetworkedAnimatorEditor` | `NetworkAnimatorEditor` |
|
||||
| `NetworkedNavMeshAgent` | `NetworkNavMeshAgent` |
|
||||
| `SpawnManager` | `NetworkSpawnManager` |
|
||||
| `BitStream` | `NetworkBuffer` |
|
||||
| `BitReader` | `NetworkReader` |
|
||||
| `BitWriter` | `NetworkWriter` |
|
||||
| `NetEventType` | `NetworkEventType` |
|
||||
| `ChannelType` | `NetworkDelivery` |
|
||||
| `Channel` | `NetworkChannel` |
|
||||
| `Transport` | `NetworkTransport` |
|
||||
| `NetworkedDictionary` | `NetworkDictionary` |
|
||||
| `NetworkedList` | `NetworkList` |
|
||||
| `NetworkedSet` | `NetworkSet` |
|
||||
| `MLAPIConstants` | `NetworkConstants` |
|
||||
| `UnetTransport` | `UNetTransport` |
|
||||
|
||||
### Fixes
|
||||
|
||||
- [GitHub 460](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/460): Fixed an issue for RPC where the host-server was not receiving RPCs from the host-client and vice versa without the loopback flag set in `NetworkingManager`.
|
||||
- Fixed an issue where data in the Profiler was incorrectly aggregated and drawn, which caused the profiler data to increment indefinitely instead of resetting each frame.
|
||||
- Fixed an issue the client soft-synced causing PlayMode client-only scene transition issues, caused when running the client in the editor and the host as a release build. Users may have encountered a soft sync of `NetworkedInstanceId` issues in the `SpawnManager.ClientCollectSoftSyncSceneObjectSweep` method.
|
||||
- [GitHub 458](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/458): Fixed serialization issues in `NetworkList` and `NetworkDictionary` when running in Server mode.
|
||||
- [GitHub 498](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/498): Fixed numerical precision issues to prevent not a number (NaN) quaternions.
|
||||
- [GitHub 438](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/438): Fixed booleans by reaching or writing bytes instead of bits.
|
||||
- [GitHub 519](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/519): Fixed an issue where calling `Shutdown()` before making `NetworkManager.Singleton = null` is null on `NetworkManager.OnDestroy()`.
|
||||
|
||||
### Removed features
|
||||
|
||||
With a new release of MLAPI in Unity, some features have been removed:
|
||||
|
||||
* SyncVars have been removed from MLAPI. Use `NetworkVariable`s in place of this functionality. <!-- MTT54 -->
|
||||
* [GitHub 527](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/527): Lag compensation systems and `TrackedObject` have moved to the new [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) repo.
|
||||
* [GitHub 509](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/509): Encryption has been removed from MLAPI. The `Encryption` option in `NetworkConfig` on the `NetworkingManager` is not available in this release. This change will not block game creation or running. A current replacement for this functionality is not available, and may be developed in future releases. See the following changes:
|
||||
|
||||
* Removed `SecuritySendFlags` from all APIs.
|
||||
* Removed encryption, cryptography, and certificate configurations from APIs including `NetworkManager` and `NetworkConfig`.
|
||||
* Removed "hail handshake", including `NetworkManager` implementation and `NetworkConstants` entries.
|
||||
* Modified `RpcQueue` and `RpcBatcher` internals to remove encryption and authentication from reading and writing.
|
||||
|
||||
* Removed the previous MLAPI Profiler editor window from Unity versions 2020.2 and later.
|
||||
* Removed previous MLAPI Convenience and Performance RPC APIs with the new standard RPC API. See [RFC #1](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0001-std-rpc-api.md) for details.
|
||||
* [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): Removed the MLAPI Installer.
|
||||
|
||||
## Known issues
|
||||
|
||||
* `NetworkNavMeshAgent` does not synchronize mesh data, Agent Size, Steering, Obstacle Avoidance, or Path Finding settings. It only synchronizes the destination and velocity, not the path to the destination.
|
||||
* For `RPC`, methods with a `ClientRpc` or `ServerRpc` suffix which are not marked with [ServerRpc] or [ClientRpc] will cause a compiler error.
|
||||
* For `NetworkAnimator`, Animator Overrides are not supported. Triggers do not work.
|
||||
* For `NetworkVariable`, the `NetworkDictionary` `List` and `Set` must use the `reliableSequenced` channel.
|
||||
* `NetworkObjects`s are supported but when spawning a prefab with nested child network objects you have to manually call spawn on them
|
||||
* `NetworkTransform` have the following issues:
|
||||
* Replicated objects may have jitter.
|
||||
* The owner is always authoritative about the object's position.
|
||||
* Scale is not synchronized.
|
||||
* Connection Approval is not called on the host client.
|
||||
* For `NamedMessages`, always use `NetworkBuffer` as the underlying stream for sending named and unnamed messages.
|
||||
* For `NetworkManager`, connection management is limited. Use `IsServer`, `IsClient`, `IsConnectedClient`, or other code to check if MLAPI connected correctly.
|
||||
|
||||
## [0.0.1-preview.1] - 2020-12-20
|
||||
This was an internally-only-used version of the Unity MLAPI Package
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d8f80d04925759d41a7bbee0259b3c39
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0388707d01c6e18409986ad2fadf6faa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bbb4974b4302f435b9f4663c64d8f803
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,196 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MLAPI.Messaging;
|
||||
using MLAPI.Serialization;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
using UnityEngine;
|
||||
|
||||
#if !UNITY_2019_4_OR_NEWER
|
||||
#error MLAPI requires Unity 2019.4 or newer
|
||||
#endif
|
||||
|
||||
namespace MLAPI.Editor.CodeGen
|
||||
{
|
||||
internal static class CodeGenHelpers
|
||||
{
|
||||
public const string RuntimeAssemblyName = "Unity.Multiplayer.MLAPI.Runtime";
|
||||
|
||||
public static readonly string NetworkBehaviour_FullName = typeof(NetworkBehaviour).FullName;
|
||||
public static readonly string ServerRpcAttribute_FullName = typeof(ServerRpcAttribute).FullName;
|
||||
public static readonly string ClientRpcAttribute_FullName = typeof(ClientRpcAttribute).FullName;
|
||||
public static readonly string ServerRpcParams_FullName = typeof(ServerRpcParams).FullName;
|
||||
public static readonly string ClientRpcParams_FullName = typeof(ClientRpcParams).FullName;
|
||||
public static readonly string INetworkSerializable_FullName = typeof(INetworkSerializable).FullName;
|
||||
public static readonly string INetworkSerializable_NetworkSerialize_Name = nameof(INetworkSerializable.NetworkSerialize);
|
||||
public static readonly string UnityColor_FullName = typeof(Color).FullName;
|
||||
public static readonly string UnityColor32_FullName = typeof(Color32).FullName;
|
||||
public static readonly string UnityVector2_FullName = typeof(Vector2).FullName;
|
||||
public static readonly string UnityVector3_FullName = typeof(Vector3).FullName;
|
||||
public static readonly string UnityVector4_FullName = typeof(Vector4).FullName;
|
||||
public static readonly string UnityQuaternion_FullName = typeof(Quaternion).FullName;
|
||||
public static readonly string UnityRay_FullName = typeof(Ray).FullName;
|
||||
public static readonly string UnityRay2D_FullName = typeof(Ray2D).FullName;
|
||||
|
||||
public static uint Hash(this MethodDefinition methodDefinition)
|
||||
{
|
||||
var sigArr = Encoding.UTF8.GetBytes($"{methodDefinition.Module.Name} / {methodDefinition.FullName}");
|
||||
var sigLen = sigArr.Length;
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* sigPtr = sigArr)
|
||||
{
|
||||
return XXHash.Hash32(sigPtr, sigLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsSubclassOf(this TypeDefinition typeDefinition, string ClassTypeFullName)
|
||||
{
|
||||
if (!typeDefinition.IsClass) return false;
|
||||
|
||||
var baseTypeRef = typeDefinition.BaseType;
|
||||
while (baseTypeRef != null)
|
||||
{
|
||||
if (baseTypeRef.FullName == ClassTypeFullName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
baseTypeRef = baseTypeRef.Resolve().BaseType;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasInterface(this TypeReference typeReference, string InterfaceTypeFullName)
|
||||
{
|
||||
if (typeReference.IsArray) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var typeDef = typeReference.Resolve();
|
||||
var typeFaces = typeDef.Interfaces;
|
||||
return typeFaces.Any(iface => iface.InterfaceType.FullName == InterfaceTypeFullName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsSerializable(this TypeReference typeReference)
|
||||
{
|
||||
var typeSystem = typeReference.Module.TypeSystem;
|
||||
|
||||
// C# primitives
|
||||
if (typeReference == typeSystem.Boolean) return true;
|
||||
if (typeReference == typeSystem.Char) return true;
|
||||
if (typeReference == typeSystem.SByte) return true;
|
||||
if (typeReference == typeSystem.Byte) return true;
|
||||
if (typeReference == typeSystem.Int16) return true;
|
||||
if (typeReference == typeSystem.UInt16) return true;
|
||||
if (typeReference == typeSystem.Int32) return true;
|
||||
if (typeReference == typeSystem.UInt32) return true;
|
||||
if (typeReference == typeSystem.Int64) return true;
|
||||
if (typeReference == typeSystem.UInt64) return true;
|
||||
if (typeReference == typeSystem.Single) return true;
|
||||
if (typeReference == typeSystem.Double) return true;
|
||||
if (typeReference == typeSystem.String) return true;
|
||||
|
||||
// Unity primitives
|
||||
if (typeReference.FullName == UnityColor_FullName) return true;
|
||||
if (typeReference.FullName == UnityColor32_FullName) return true;
|
||||
if (typeReference.FullName == UnityVector2_FullName) return true;
|
||||
if (typeReference.FullName == UnityVector3_FullName) return true;
|
||||
if (typeReference.FullName == UnityVector4_FullName) return true;
|
||||
if (typeReference.FullName == UnityQuaternion_FullName) return true;
|
||||
if (typeReference.FullName == UnityRay_FullName) return true;
|
||||
if (typeReference.FullName == UnityRay2D_FullName) return true;
|
||||
|
||||
// Enum
|
||||
if (typeReference.GetEnumAsInt() != null) return true;
|
||||
|
||||
// INetworkSerializable
|
||||
if (typeReference.HasInterface(INetworkSerializable_FullName)) return true;
|
||||
|
||||
// Static array
|
||||
if (typeReference.IsArray) return typeReference.GetElementType().IsSerializable();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static TypeReference GetEnumAsInt(this TypeReference typeReference)
|
||||
{
|
||||
if (typeReference.IsArray) return null;
|
||||
|
||||
try
|
||||
{
|
||||
var typeDef = typeReference.Resolve();
|
||||
return typeDef.IsEnum ? typeDef.GetEnumUnderlyingType() : null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddError(this List<DiagnosticMessage> diagnostics, string message)
|
||||
{
|
||||
diagnostics.AddError((SequencePoint)null, message);
|
||||
}
|
||||
|
||||
public static void AddError(this List<DiagnosticMessage> diagnostics, MethodDefinition methodDefinition, string message)
|
||||
{
|
||||
diagnostics.AddError(methodDefinition.DebugInformation.SequencePoints.FirstOrDefault(), message);
|
||||
}
|
||||
|
||||
public static void AddError(this List<DiagnosticMessage> diagnostics, SequencePoint sequencePoint, string message)
|
||||
{
|
||||
diagnostics.Add(new DiagnosticMessage
|
||||
{
|
||||
DiagnosticType = DiagnosticType.Error,
|
||||
File = sequencePoint?.Document.Url.Replace($"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}", ""),
|
||||
Line = sequencePoint?.StartLine ?? 0,
|
||||
Column = sequencePoint?.StartColumn ?? 0,
|
||||
MessageData = $" - {message}"
|
||||
});
|
||||
}
|
||||
|
||||
public static AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
var assemblyResolver = new PostProcessorAssemblyResolver(compiledAssembly);
|
||||
var readerParameters = new ReaderParameters
|
||||
{
|
||||
SymbolStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData),
|
||||
SymbolReaderProvider = new PortablePdbReaderProvider(),
|
||||
AssemblyResolver = assemblyResolver,
|
||||
ReflectionImporterProvider = new PostProcessorReflectionImporterProvider(),
|
||||
ReadingMode = ReadingMode.Immediate
|
||||
};
|
||||
|
||||
var assemblyDefinition = AssemblyDefinition.ReadAssembly(new MemoryStream(compiledAssembly.InMemoryAssembly.PeData), readerParameters);
|
||||
|
||||
//apparently, it will happen that when we ask to resolve a type that lives inside MLAPI.Runtime, and we
|
||||
//are also postprocessing MLAPI.Runtime, type resolving will fail, because we do not actually try to resolve
|
||||
//inside the assembly we are processing. Let's make sure we do that, so that we can use postprocessor features inside
|
||||
//MLAPI.Runtime itself as well.
|
||||
assemblyResolver.AddAssemblyDefinitionBeingOperatedOn(assemblyDefinition);
|
||||
|
||||
return assemblyDefinition;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0e5541b3bca0e43b48c2e694fffef5b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,42 @@
|
|||
#if !UNITY_2020_2_OR_NEWER
|
||||
using System.IO;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
|
||||
namespace MLAPI.Editor.CodeGen
|
||||
{
|
||||
internal class ILPostProcessCompiledAssembly : ICompiledAssembly
|
||||
{
|
||||
private readonly string m_AssemblyFilename;
|
||||
private readonly string m_OutputPath;
|
||||
private InMemoryAssembly m_InMemoryAssembly;
|
||||
|
||||
public ILPostProcessCompiledAssembly(string asmName, string[] refs, string[] defines, string outputPath)
|
||||
{
|
||||
m_AssemblyFilename = asmName;
|
||||
Name = Path.GetFileNameWithoutExtension(m_AssemblyFilename);
|
||||
References = refs;
|
||||
Defines = defines;
|
||||
m_OutputPath = outputPath;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public string[] References { get; }
|
||||
public string[] Defines { get; }
|
||||
|
||||
public InMemoryAssembly InMemoryAssembly
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_InMemoryAssembly == null)
|
||||
{
|
||||
m_InMemoryAssembly = new InMemoryAssembly(
|
||||
File.ReadAllBytes(Path.Combine(m_OutputPath, m_AssemblyFilename)),
|
||||
File.ReadAllBytes(Path.Combine(m_OutputPath, $"{Path.GetFileNameWithoutExtension(m_AssemblyFilename)}.pdb")));
|
||||
}
|
||||
|
||||
return m_InMemoryAssembly;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c63d199856aa44f4581ec4de75bf3f44
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,13 @@
|
|||
#if !UNITY_2020_2_OR_NEWER
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
|
||||
namespace MLAPI.Editor.CodeGen
|
||||
{
|
||||
public abstract class ILPostProcessor
|
||||
{
|
||||
public abstract bool WillProcess(ICompiledAssembly compiledAssembly);
|
||||
public abstract ILPostProcessResult Process(ICompiledAssembly compiledAssembly);
|
||||
public abstract ILPostProcessor GetInstance();
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cbffd9b273517da4a9c4a3218771e0b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,239 @@
|
|||
#if !UNITY_2020_2_OR_NEWER
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Compilation;
|
||||
using UnityEngine;
|
||||
|
||||
using Assembly = System.Reflection.Assembly;
|
||||
|
||||
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
|
||||
|
||||
namespace MLAPI.Editor.CodeGen
|
||||
{
|
||||
// There is a behaviour difference between 2019.4 and 2020+ codegen
|
||||
// that essentially does checking on the existence of ILPP vs if a CodeGen assembly
|
||||
// is present. So in order to make sure ILPP runs properly in 2019.4 from a clean
|
||||
// import of the project we add this dummy ILPP which forces the callback to made
|
||||
// and meets the internal ScriptCompilation pipeline requirements
|
||||
internal sealed class ILPP2019CodegenWorkaround : ILPPInterface
|
||||
{
|
||||
public override ILPPInterface GetInstance()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
|
||||
|
||||
}
|
||||
|
||||
internal static class ILPostProcessProgram
|
||||
{
|
||||
private static ILPostProcessor[] s_ILPostProcessors { get; set; }
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
private static void OnInitializeOnLoad()
|
||||
{
|
||||
CompilationPipeline.assemblyCompilationFinished += OnCompilationFinished;
|
||||
s_ILPostProcessors = FindAllPostProcessors();
|
||||
}
|
||||
|
||||
private static ILPostProcessor[] FindAllPostProcessors()
|
||||
{
|
||||
var typesDerivedFrom = TypeCache.GetTypesDerivedFrom<ILPostProcessor>();
|
||||
var localILPostProcessors = new List<ILPostProcessor>(typesDerivedFrom.Count);
|
||||
|
||||
foreach (var typeCollection in typesDerivedFrom)
|
||||
{
|
||||
try
|
||||
{
|
||||
localILPostProcessors.Add((ILPostProcessor)Activator.CreateInstance(typeCollection));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogError($"Could not create {nameof(ILPostProcessor)} ({typeCollection.FullName}):{Environment.NewLine}{exception.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
// Default sort by type full name
|
||||
localILPostProcessors.Sort((left, right) => string.Compare(left.GetType().FullName, right.GetType().FullName, StringComparison.Ordinal));
|
||||
|
||||
return localILPostProcessors.ToArray();
|
||||
}
|
||||
|
||||
private static void OnCompilationFinished(string targetAssembly, CompilerMessage[] messages)
|
||||
{
|
||||
if (messages.Length > 0)
|
||||
{
|
||||
if (messages.Any(msg => msg.type == CompilerMessageType.Error))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Should not run on the editor only assemblies
|
||||
if (targetAssembly.Contains("-Editor") || targetAssembly.Contains(".Editor"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Should not run on Unity Engine modules but we can run on the MLAPI Runtime DLL
|
||||
if ((targetAssembly.Contains("com.unity") || Path.GetFileName(targetAssembly).StartsWith("Unity")) && !targetAssembly.Contains("Unity.Multiplayer."))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Debug.Log($"Running MLAPI ILPP on {targetAssembly}");
|
||||
|
||||
var outputDirectory = $"{Application.dataPath}/../{Path.GetDirectoryName(targetAssembly)}";
|
||||
var unityEngine = string.Empty;
|
||||
var mlapiRuntimeAssemblyPath = string.Empty;
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
var usesMLAPI = false;
|
||||
var foundThisAssembly = false;
|
||||
|
||||
var depenencyPaths = new List<string>();
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
// Find the assembly currently being compiled from domain assembly list and check if it's using unet
|
||||
if (assembly.GetName().Name == Path.GetFileNameWithoutExtension(targetAssembly))
|
||||
{
|
||||
foundThisAssembly = true;
|
||||
foreach (var dependency in assembly.GetReferencedAssemblies())
|
||||
{
|
||||
// Since this assembly is already loaded in the domain this is a no-op and returns the
|
||||
// already loaded assembly
|
||||
depenencyPaths.Add(Assembly.Load(dependency).Location);
|
||||
if (dependency.Name.Contains(CodeGenHelpers.RuntimeAssemblyName))
|
||||
{
|
||||
usesMLAPI = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (assembly.Location.Contains("UnityEngine.CoreModule"))
|
||||
{
|
||||
unityEngine = assembly.Location;
|
||||
}
|
||||
|
||||
if (assembly.Location.Contains(CodeGenHelpers.RuntimeAssemblyName))
|
||||
{
|
||||
mlapiRuntimeAssemblyPath = assembly.Location;
|
||||
}
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
// in memory assembly, can't get location
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundThisAssembly)
|
||||
{
|
||||
// Target assembly not found in current domain, trying to load it to check references
|
||||
// will lead to trouble in the build pipeline, so lets assume it should go to weaver.
|
||||
// Add all assemblies in current domain to dependency list since there could be a
|
||||
// dependency lurking there (there might be generated assemblies so ignore file not found exceptions).
|
||||
// (can happen in runtime test framework on editor platform and when doing full library reimport)
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!(assembly.ManifestModule is System.Reflection.Emit.ModuleBuilder))
|
||||
{
|
||||
depenencyPaths.Add(Assembly.Load(assembly.GetName().Name).Location);
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
usesMLAPI = true;
|
||||
}
|
||||
|
||||
// We check if we are the MLAPI!
|
||||
if (!usesMLAPI)
|
||||
{
|
||||
// we shall also check and see if it we are ourself
|
||||
usesMLAPI = targetAssembly.Contains(CodeGenHelpers.RuntimeAssemblyName);
|
||||
}
|
||||
|
||||
if (!usesMLAPI)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(unityEngine))
|
||||
{
|
||||
Debug.LogError("Failed to find UnityEngine assembly");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(mlapiRuntimeAssemblyPath))
|
||||
{
|
||||
Debug.LogError("Failed to find mlapi runtime assembly");
|
||||
return;
|
||||
}
|
||||
|
||||
var assemblyPathName = Path.GetFileName(targetAssembly);
|
||||
|
||||
var targetCompiledAssembly = new ILPostProcessCompiledAssembly(assemblyPathName, depenencyPaths.ToArray(), null, outputDirectory);
|
||||
|
||||
void WriteAssembly(InMemoryAssembly inMemoryAssembly, string outputPath, string assName)
|
||||
{
|
||||
if (inMemoryAssembly == null)
|
||||
{
|
||||
throw new ArgumentException("InMemoryAssembly has never been accessed or modified");
|
||||
}
|
||||
|
||||
var asmPath = Path.Combine(outputPath, assName);
|
||||
var pdbFileName = $"{Path.GetFileNameWithoutExtension(assName)}.pdb";
|
||||
var pdbPath = Path.Combine(outputPath, pdbFileName);
|
||||
|
||||
File.WriteAllBytes(asmPath, inMemoryAssembly.PeData);
|
||||
File.WriteAllBytes(pdbPath, inMemoryAssembly.PdbData);
|
||||
}
|
||||
|
||||
foreach (var i in s_ILPostProcessors)
|
||||
{
|
||||
var result = i.Process(targetCompiledAssembly);
|
||||
if (result == null) continue;
|
||||
|
||||
if (result.Diagnostics.Count > 0)
|
||||
{
|
||||
Debug.LogError($"{nameof(ILPostProcessor)} - {i.GetType().Name} failed to run on {targetCompiledAssembly.Name}");
|
||||
|
||||
foreach (var message in result.Diagnostics)
|
||||
{
|
||||
switch (message.DiagnosticType)
|
||||
{
|
||||
case DiagnosticType.Error:
|
||||
Debug.LogError($"{nameof(ILPostProcessor)} Error - {message.MessageData} {message.File}:{message.Line}");
|
||||
break;
|
||||
case DiagnosticType.Warning:
|
||||
Debug.LogWarning($"{nameof(ILPostProcessor)} Warning - {message.MessageData} {message.File}:{message.Line}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// we now need to write out the result?
|
||||
WriteAssembly(result.InMemoryAssembly, outputDirectory, assemblyPathName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 72c7d2bd3d748db4d988e2204fe1083e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cf1c8b78182704372820a586c1c91d97
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,139 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Mono.Cecil;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
|
||||
namespace MLAPI.Editor.CodeGen
|
||||
{
|
||||
internal class PostProcessorAssemblyResolver : IAssemblyResolver
|
||||
{
|
||||
private readonly string[] m_AssemblyReferences;
|
||||
private readonly Dictionary<string, AssemblyDefinition> m_AssemblyCache = new Dictionary<string, AssemblyDefinition>();
|
||||
private readonly ICompiledAssembly m_CompiledAssembly;
|
||||
private AssemblyDefinition m_SelfAssembly;
|
||||
|
||||
public PostProcessorAssemblyResolver(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
m_CompiledAssembly = compiledAssembly;
|
||||
m_AssemblyReferences = compiledAssembly.References;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public AssemblyDefinition Resolve(AssemblyNameReference name) => Resolve(name, new ReaderParameters(ReadingMode.Deferred));
|
||||
|
||||
public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
|
||||
{
|
||||
lock (m_AssemblyCache)
|
||||
{
|
||||
if (name.Name == m_CompiledAssembly.Name)
|
||||
{
|
||||
return m_SelfAssembly;
|
||||
}
|
||||
|
||||
var fileName = FindFile(name);
|
||||
if (fileName == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var lastWriteTime = File.GetLastWriteTime(fileName);
|
||||
var cacheKey = $"{fileName}{lastWriteTime}";
|
||||
if (m_AssemblyCache.TryGetValue(cacheKey, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
parameters.AssemblyResolver = this;
|
||||
|
||||
var ms = MemoryStreamFor(fileName);
|
||||
var pdb = $"{fileName}.pdb";
|
||||
if (File.Exists(pdb))
|
||||
{
|
||||
parameters.SymbolStream = MemoryStreamFor(pdb);
|
||||
}
|
||||
|
||||
var assemblyDefinition = AssemblyDefinition.ReadAssembly(ms, parameters);
|
||||
m_AssemblyCache.Add(cacheKey, assemblyDefinition);
|
||||
|
||||
return assemblyDefinition;
|
||||
}
|
||||
}
|
||||
|
||||
private string FindFile(AssemblyNameReference name)
|
||||
{
|
||||
var fileName = m_AssemblyReferences.FirstOrDefault(r => Path.GetFileName(r) == $"{name.Name}.dll");
|
||||
if (fileName != null)
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
// perhaps the type comes from an exe instead
|
||||
fileName = m_AssemblyReferences.FirstOrDefault(r => Path.GetFileName(r) == $"{name.Name}.exe");
|
||||
if (fileName != null)
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
//Unfortunately the current ICompiledAssembly API only provides direct references.
|
||||
//It is very much possible that a postprocessor ends up investigating a type in a directly
|
||||
//referenced assembly, that contains a field that is not in a directly referenced assembly.
|
||||
//if we don't do anything special for that situation, it will fail to resolve. We should fix this
|
||||
//in the ILPostProcessing API. As a workaround, we rely on the fact here that the indirect references
|
||||
//are always located next to direct references, so we search in all directories of direct references we
|
||||
//got passed, and if we find the file in there, we resolve to it.
|
||||
return m_AssemblyReferences
|
||||
.Select(Path.GetDirectoryName)
|
||||
.Distinct()
|
||||
.Select(parentDir => Path.Combine(parentDir, $"{name.Name}.dll"))
|
||||
.FirstOrDefault(File.Exists);
|
||||
}
|
||||
|
||||
private static MemoryStream MemoryStreamFor(string fileName)
|
||||
{
|
||||
return Retry(10, TimeSpan.FromSeconds(1), () =>
|
||||
{
|
||||
byte[] byteArray;
|
||||
using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
byteArray = new byte[fs.Length];
|
||||
var readLength = fs.Read(byteArray, 0, (int)fs.Length);
|
||||
if (readLength != fs.Length)
|
||||
{
|
||||
throw new InvalidOperationException("File read length is not full length of file.");
|
||||
}
|
||||
}
|
||||
|
||||
return new MemoryStream(byteArray);
|
||||
});
|
||||
}
|
||||
|
||||
private static MemoryStream Retry(int retryCount, TimeSpan waitTime, Func<MemoryStream> func)
|
||||
{
|
||||
try
|
||||
{
|
||||
return func();
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
if (retryCount == 0)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Caught IO Exception, trying {retryCount} more times");
|
||||
Thread.Sleep(waitTime);
|
||||
|
||||
return Retry(retryCount - 1, waitTime, func);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddAssemblyDefinitionBeingOperatedOn(AssemblyDefinition assemblyDefinition)
|
||||
{
|
||||
m_SelfAssembly = assemblyDefinition;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2c247f4266b2864eb96e6a9ae6557d31
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,22 @@
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace MLAPI.Editor.CodeGen
|
||||
{
|
||||
internal class PostProcessorReflectionImporter : DefaultReflectionImporter
|
||||
{
|
||||
private const string k_SystemPrivateCoreLib = "System.Private.CoreLib";
|
||||
private readonly AssemblyNameReference m_CorrectCorlib;
|
||||
|
||||
public PostProcessorReflectionImporter(ModuleDefinition module) : base(module)
|
||||
{
|
||||
m_CorrectCorlib = module.AssemblyReferences.FirstOrDefault(a => a.Name == "mscorlib" || a.Name == "netstandard" || a.Name == k_SystemPrivateCoreLib);
|
||||
}
|
||||
|
||||
public override AssemblyNameReference ImportReference(AssemblyName reference)
|
||||
{
|
||||
return m_CorrectCorlib != null && reference.Name == k_SystemPrivateCoreLib ? m_CorrectCorlib : base.ImportReference(reference);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 484e8ad8c4dde382ea67036b32935ef1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,12 @@
|
|||
using Mono.Cecil;
|
||||
|
||||
namespace MLAPI.Editor.CodeGen
|
||||
{
|
||||
internal class PostProcessorReflectionImporterProvider : IReflectionImporterProvider
|
||||
{
|
||||
public IReflectionImporter GetReflectionImporter(ModuleDefinition moduleDefinition)
|
||||
{
|
||||
return new PostProcessorReflectionImporter(moduleDefinition);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f9273a5dad109ab0783891e36c983080
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,118 @@
|
|||
#if UNITY_2020_2_OR_NEWER
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
|
||||
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
|
||||
|
||||
namespace MLAPI.Editor.CodeGen
|
||||
{
|
||||
internal sealed class RuntimeAccessModifiersILPP : ILPPInterface
|
||||
{
|
||||
public override ILPPInterface GetInstance() => this;
|
||||
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName;
|
||||
|
||||
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
|
||||
|
||||
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
if (!WillProcess(compiledAssembly)) return null;
|
||||
m_Diagnostics.Clear();
|
||||
|
||||
// read
|
||||
var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly);
|
||||
if (assemblyDefinition == null)
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot read MLAPI Runtime assembly definition: {compiledAssembly.Name}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// process
|
||||
var mainModule = assemblyDefinition.MainModule;
|
||||
if (mainModule != null)
|
||||
{
|
||||
foreach (var typeDefinition in mainModule.Types)
|
||||
{
|
||||
if (!typeDefinition.IsClass) continue;
|
||||
|
||||
switch (typeDefinition.Name)
|
||||
{
|
||||
case nameof(NetworkManager):
|
||||
ProcessNetworkManager(typeDefinition);
|
||||
break;
|
||||
case nameof(NetworkBehaviour):
|
||||
ProcessNetworkBehaviour(typeDefinition);
|
||||
break;
|
||||
case nameof(Messaging.__RpcParams):
|
||||
typeDefinition.IsPublic = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else m_Diagnostics.AddError($"Cannot get main module from MLAPI Runtime assembly definition: {compiledAssembly.Name}");
|
||||
|
||||
// write
|
||||
var pe = new MemoryStream();
|
||||
var pdb = new MemoryStream();
|
||||
|
||||
var writerParameters = new WriterParameters
|
||||
{
|
||||
SymbolWriterProvider = new PortablePdbWriterProvider(),
|
||||
SymbolStream = pdb,
|
||||
WriteSymbols = true
|
||||
};
|
||||
|
||||
assemblyDefinition.Write(pe, writerParameters);
|
||||
|
||||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
|
||||
}
|
||||
|
||||
private void ProcessNetworkManager(TypeDefinition typeDefinition)
|
||||
{
|
||||
foreach (var fieldDefinition in typeDefinition.Fields)
|
||||
{
|
||||
if (fieldDefinition.Name == nameof(NetworkManager.__ntable))
|
||||
{
|
||||
fieldDefinition.IsPublic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
|
||||
{
|
||||
foreach (var nestedType in typeDefinition.NestedTypes)
|
||||
{
|
||||
if (nestedType.Name == nameof(NetworkBehaviour.__NExec))
|
||||
{
|
||||
nestedType.IsNestedFamily = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var fieldDefinition in typeDefinition.Fields)
|
||||
{
|
||||
if (fieldDefinition.Name == nameof(NetworkBehaviour.__nexec))
|
||||
{
|
||||
fieldDefinition.IsFamily = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var methodDefinition in typeDefinition.Methods)
|
||||
{
|
||||
switch (methodDefinition.Name)
|
||||
{
|
||||
case nameof(NetworkBehaviour.__beginSendServerRpc):
|
||||
case nameof(NetworkBehaviour.__endSendServerRpc):
|
||||
case nameof(NetworkBehaviour.__beginSendClientRpc):
|
||||
case nameof(NetworkBehaviour.__endSendClientRpc):
|
||||
methodDefinition.IsFamily = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2c9f2f4b03d774432be69d4c2f53bd2d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "Unity.Multiplayer.MLAPI.Editor.CodeGen",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"Unity.Multiplayer.MLAPI.Runtime"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"Mono.Cecil.dll",
|
||||
"Mono.Cecil.Mdb.dll",
|
||||
"Mono.Cecil.Pdb.dll",
|
||||
"Mono.Cecil.Rocks.dll"
|
||||
],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fe4fa159f4a96442ba22af67ddf20c65
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2c61e8fe9a68a486fbbc3128d233ded2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015, 2016 Sedat Kapanoglu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cf89ecbf6f9954c8ea6d0848b1e79d87
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,291 @@
|
|||
// <copyright file="XXHash.cs" company="Sedat Kapanoglu">
|
||||
// Copyright (c) 2015-2019 Sedat Kapanoglu
|
||||
// MIT License (see LICENSE file for details)
|
||||
// </copyright>
|
||||
|
||||
// @mfatihmar (Unity): Modified for Unity support
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MLAPI.Editor.CodeGen
|
||||
{
|
||||
/// <summary>
|
||||
/// XXHash implementation.
|
||||
/// </summary>
|
||||
internal static class XXHash
|
||||
{
|
||||
private const ulong k_Prime64v1 = 11400714785074694791ul;
|
||||
private const ulong k_Prime64v2 = 14029467366897019727ul;
|
||||
private const ulong k_Prime64v3 = 1609587929392839161ul;
|
||||
private const ulong k_Prime64v4 = 9650029242287828579ul;
|
||||
private const ulong k_Prime64v5 = 2870177450012600261ul;
|
||||
|
||||
private const uint k_Prime32v1 = 2654435761u;
|
||||
private const uint k_Prime32v2 = 2246822519u;
|
||||
private const uint k_Prime32v3 = 3266489917u;
|
||||
private const uint k_Prime32v4 = 668265263u;
|
||||
private const uint k_Prime32v5 = 374761393u;
|
||||
|
||||
/// <summary>
|
||||
/// Generate a 32-bit xxHash value.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Input buffer.</param>
|
||||
/// <param name="bufferLength">Input buffer length.</param>
|
||||
/// <param name="seed">Optional seed.</param>
|
||||
/// <returns>32-bit hash value.</returns>
|
||||
public static unsafe uint Hash32(byte* buffer, int bufferLength, uint seed = 0)
|
||||
{
|
||||
const int stripeLength = 16;
|
||||
|
||||
int len = bufferLength;
|
||||
int remainingLen = len;
|
||||
uint acc;
|
||||
|
||||
byte* pInput = buffer;
|
||||
if (len >= stripeLength)
|
||||
{
|
||||
uint acc1 = seed + k_Prime32v1 + k_Prime32v2;
|
||||
uint acc2 = seed + k_Prime32v2;
|
||||
uint acc3 = seed;
|
||||
uint acc4 = seed - k_Prime32v1;
|
||||
|
||||
do
|
||||
{
|
||||
acc = processStripe32(ref pInput, ref acc1, ref acc2, ref acc3, ref acc4);
|
||||
remainingLen -= stripeLength;
|
||||
} while (remainingLen >= stripeLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
acc = seed + k_Prime32v5;
|
||||
}
|
||||
|
||||
acc += (uint)len;
|
||||
acc = processRemaining32(pInput, acc, remainingLen);
|
||||
|
||||
return avalanche32(acc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a 64-bit xxHash value.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Input buffer.</param>
|
||||
/// <param name="bufferLength">Input buffer length.</param>
|
||||
/// <param name="seed">Optional seed.</param>
|
||||
/// <returns>Computed 64-bit hash value.</returns>
|
||||
public static unsafe ulong Hash64(byte* buffer, int bufferLength, ulong seed = 0)
|
||||
{
|
||||
const int stripeLength = 32;
|
||||
|
||||
int len = bufferLength;
|
||||
int remainingLen = len;
|
||||
ulong acc;
|
||||
|
||||
byte* pInput = buffer;
|
||||
if (len >= stripeLength)
|
||||
{
|
||||
ulong acc1 = seed + k_Prime64v1 + k_Prime64v2;
|
||||
ulong acc2 = seed + k_Prime64v2;
|
||||
ulong acc3 = seed;
|
||||
ulong acc4 = seed - k_Prime64v1;
|
||||
|
||||
do
|
||||
{
|
||||
acc = processStripe64(ref pInput, ref acc1, ref acc2, ref acc3, ref acc4);
|
||||
remainingLen -= stripeLength;
|
||||
} while (remainingLen >= stripeLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
acc = seed + k_Prime64v5;
|
||||
}
|
||||
|
||||
acc += (ulong)len;
|
||||
acc = processRemaining64(pInput, acc, remainingLen);
|
||||
|
||||
|
||||
return avalanche64(acc);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe ulong processStripe64(
|
||||
ref byte* pInput,
|
||||
ref ulong acc1,
|
||||
ref ulong acc2,
|
||||
ref ulong acc3,
|
||||
ref ulong acc4)
|
||||
{
|
||||
processLane64(ref acc1, ref pInput);
|
||||
processLane64(ref acc2, ref pInput);
|
||||
processLane64(ref acc3, ref pInput);
|
||||
processLane64(ref acc4, ref pInput);
|
||||
|
||||
ulong acc = Bits.RotateLeft(acc1, 1)
|
||||
+ Bits.RotateLeft(acc2, 7)
|
||||
+ Bits.RotateLeft(acc3, 12)
|
||||
+ Bits.RotateLeft(acc4, 18);
|
||||
|
||||
mergeAccumulator64(ref acc, acc1);
|
||||
mergeAccumulator64(ref acc, acc2);
|
||||
mergeAccumulator64(ref acc, acc3);
|
||||
mergeAccumulator64(ref acc, acc4);
|
||||
return acc;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe void processLane64(ref ulong accn, ref byte* pInput)
|
||||
{
|
||||
ulong lane = *(ulong*)pInput;
|
||||
accn = round64(accn, lane);
|
||||
pInput += 8;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe ulong processRemaining64(
|
||||
byte* pInput,
|
||||
ulong acc,
|
||||
int remainingLen)
|
||||
{
|
||||
for (ulong lane; remainingLen >= 8; remainingLen -= 8, pInput += 8)
|
||||
{
|
||||
lane = *(ulong*)pInput;
|
||||
|
||||
acc ^= round64(0, lane);
|
||||
acc = Bits.RotateLeft(acc, 27) * k_Prime64v1;
|
||||
acc += k_Prime64v4;
|
||||
}
|
||||
|
||||
for (uint lane32; remainingLen >= 4; remainingLen -= 4, pInput += 4)
|
||||
{
|
||||
lane32 = *(uint*)pInput;
|
||||
|
||||
acc ^= lane32 * k_Prime64v1;
|
||||
acc = Bits.RotateLeft(acc, 23) * k_Prime64v2;
|
||||
acc += k_Prime64v3;
|
||||
}
|
||||
|
||||
for (byte lane8; remainingLen >= 1; remainingLen--, pInput++)
|
||||
{
|
||||
lane8 = *pInput;
|
||||
acc ^= lane8 * k_Prime64v5;
|
||||
acc = Bits.RotateLeft(acc, 11) * k_Prime64v1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong avalanche64(ulong acc)
|
||||
{
|
||||
acc ^= acc >> 33;
|
||||
acc *= k_Prime64v2;
|
||||
acc ^= acc >> 29;
|
||||
acc *= k_Prime64v3;
|
||||
acc ^= acc >> 32;
|
||||
return acc;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong round64(ulong accn, ulong lane)
|
||||
{
|
||||
accn += lane * k_Prime64v2;
|
||||
return Bits.RotateLeft(accn, 31) * k_Prime64v1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void mergeAccumulator64(ref ulong acc, ulong accn)
|
||||
{
|
||||
acc ^= round64(0, accn);
|
||||
acc *= k_Prime64v1;
|
||||
acc += k_Prime64v4;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe uint processStripe32(
|
||||
ref byte* pInput,
|
||||
ref uint acc1,
|
||||
ref uint acc2,
|
||||
ref uint acc3,
|
||||
ref uint acc4)
|
||||
{
|
||||
processLane32(ref pInput, ref acc1);
|
||||
processLane32(ref pInput, ref acc2);
|
||||
processLane32(ref pInput, ref acc3);
|
||||
processLane32(ref pInput, ref acc4);
|
||||
|
||||
return Bits.RotateLeft(acc1, 1)
|
||||
+ Bits.RotateLeft(acc2, 7)
|
||||
+ Bits.RotateLeft(acc3, 12)
|
||||
+ Bits.RotateLeft(acc4, 18);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe void processLane32(ref byte* pInput, ref uint accn)
|
||||
{
|
||||
uint lane = *(uint*)pInput;
|
||||
accn = round32(accn, lane);
|
||||
pInput += 4;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe uint processRemaining32(
|
||||
byte* pInput,
|
||||
uint acc,
|
||||
int remainingLen)
|
||||
{
|
||||
for (uint lane; remainingLen >= 4; remainingLen -= 4, pInput += 4)
|
||||
{
|
||||
lane = *(uint*)pInput;
|
||||
acc += lane * k_Prime32v3;
|
||||
acc = Bits.RotateLeft(acc, 17) * k_Prime32v4;
|
||||
}
|
||||
|
||||
for (byte lane; remainingLen >= 1; remainingLen--, pInput++)
|
||||
{
|
||||
lane = *pInput;
|
||||
acc += lane * k_Prime32v5;
|
||||
acc = Bits.RotateLeft(acc, 11) * k_Prime32v1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint round32(uint accn, uint lane)
|
||||
{
|
||||
accn += lane * k_Prime32v2;
|
||||
accn = Bits.RotateLeft(accn, 13);
|
||||
accn *= k_Prime32v1;
|
||||
return accn;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint avalanche32(uint acc)
|
||||
{
|
||||
acc ^= acc >> 15;
|
||||
acc *= k_Prime32v2;
|
||||
acc ^= acc >> 13;
|
||||
acc *= k_Prime32v3;
|
||||
acc ^= acc >> 16;
|
||||
return acc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bit operations.
|
||||
/// </summary>
|
||||
private static class Bits
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ulong RotateLeft(ulong value, int bits)
|
||||
{
|
||||
return (value << bits) | (value >> (64 - bits));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static uint RotateLeft(uint value, int bits)
|
||||
{
|
||||
return (value << bits) | (value >> (32 - bits));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b5aa7a49e9e694f148d810d34577546b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,361 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using MLAPI.Profiling;
|
||||
using MLAPI.Serialization;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor
|
||||
{
|
||||
public class MLAPIProfiler : EditorWindow
|
||||
{
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
[MenuItem("Window/MLAPI Profiler")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
GetWindow<MLAPIProfiler>();
|
||||
}
|
||||
#endif
|
||||
|
||||
private static GUIStyle s_WrapStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
Color color = EditorStyles.label.normal.textColor;
|
||||
GUIStyle style = EditorStyles.centeredGreyMiniLabel;
|
||||
style.wordWrap = true;
|
||||
style.normal.textColor = color;
|
||||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
private float m_HoverAlpha = 0f;
|
||||
private float m_UpdateDelay = 1f;
|
||||
private int m_CaptureCount = 100;
|
||||
private float m_ShowMax = 0;
|
||||
private float m_ShowMin = 0;
|
||||
private AnimationCurve m_Curve = AnimationCurve.Linear(0, 0, 1, 0);
|
||||
private readonly List<ProfilerTick> m_CurrentTicks = new List<ProfilerTick>();
|
||||
private float m_LastDrawn = 0;
|
||||
|
||||
private class ProfilerContainer
|
||||
{
|
||||
public ProfilerTick[] Ticks;
|
||||
|
||||
public byte[] ToBytes()
|
||||
{
|
||||
var buffer = new NetworkBuffer();
|
||||
var writer = new NetworkWriter(buffer);
|
||||
|
||||
writer.WriteUInt16Packed((ushort)Ticks.Length);
|
||||
|
||||
for (int i = 0; i < Ticks.Length; i++)
|
||||
{
|
||||
Ticks[i].SerializeToStream(buffer);
|
||||
}
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
public static ProfilerContainer FromBytes(byte[] bytes)
|
||||
{
|
||||
var container = new ProfilerContainer();
|
||||
var buffer = new NetworkBuffer(bytes);
|
||||
var reader = new NetworkReader(buffer);
|
||||
var count = reader.ReadUInt16Packed();
|
||||
|
||||
container.Ticks = new ProfilerTick[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
container.Ticks[i] = ProfilerTick.FromStream(buffer);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
private void StopRecording()
|
||||
{
|
||||
NetworkProfiler.Stop();
|
||||
}
|
||||
|
||||
private void StartRecording()
|
||||
{
|
||||
if (NetworkProfiler.IsRunning) StopRecording();
|
||||
|
||||
if (NetworkProfiler.Ticks != null && NetworkProfiler.Ticks.Count >= 2)
|
||||
{
|
||||
m_Curve = AnimationCurve.Constant(NetworkProfiler.Ticks.ElementAt(0).Frame, NetworkProfiler.Ticks.ElementAt(NetworkProfiler.Ticks.Count - 1).Frame, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Curve = AnimationCurve.Constant(0, 1, 0);
|
||||
}
|
||||
|
||||
m_LastDrawn = 0;
|
||||
NetworkProfiler.Start(m_CaptureCount);
|
||||
}
|
||||
|
||||
private void ClearDrawing()
|
||||
{
|
||||
m_CurrentTicks.Clear();
|
||||
m_Curve = AnimationCurve.Constant(0, 1, 0);
|
||||
m_LastDrawn = 0;
|
||||
}
|
||||
|
||||
private void ChangeRecordState()
|
||||
{
|
||||
if (NetworkProfiler.IsRunning) StopRecording();
|
||||
else StartRecording();
|
||||
}
|
||||
|
||||
private TickEvent m_EventHover = null;
|
||||
private double m_LastSetup = 0;
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
bool recording = NetworkProfiler.IsRunning;
|
||||
float deltaTime = (float)(EditorApplication.timeSinceStartup - m_LastSetup);
|
||||
|
||||
m_LastSetup = EditorApplication.timeSinceStartup;
|
||||
|
||||
//Draw top bar
|
||||
EditorGUILayout.BeginVertical();
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
if (GUILayout.Button(recording ? "Stop" : "Capture")) ChangeRecordState();
|
||||
if (GUILayout.Button("Clear")) ClearDrawing();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (GUILayout.Button("Import datafile"))
|
||||
{
|
||||
string path = EditorUtility.OpenFilePanel("Choose a NetworkProfiler file", "", "");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
var ticks = ProfilerContainer.FromBytes(File.ReadAllBytes(path)).Ticks;
|
||||
if (ticks.Length >= 2)
|
||||
{
|
||||
m_Curve = AnimationCurve.Constant(ticks[0].EventId, ticks[(ticks.Length - 1)].EventId, 0);
|
||||
m_ShowMax = ticks.Length;
|
||||
m_ShowMin = ticks.Length - Mathf.Clamp(100, 0, ticks.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Curve = AnimationCurve.Constant(0, 1, 0);
|
||||
}
|
||||
|
||||
m_CurrentTicks.Clear();
|
||||
for (int i = 0; i < ticks.Length; i++)
|
||||
{
|
||||
m_CurrentTicks.Add(ticks[i]);
|
||||
|
||||
uint bytes = 0;
|
||||
if (ticks[i].Events.Count > 0)
|
||||
{
|
||||
for (int j = 0; j < ticks[i].Events.Count; j++)
|
||||
{
|
||||
var tickEvent = ticks[i].Events[j];
|
||||
bytes += tickEvent.Bytes;
|
||||
}
|
||||
}
|
||||
|
||||
m_Curve.AddKey(ticks[i].EventId, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Export datafile"))
|
||||
{
|
||||
int max = (int)m_ShowMax;
|
||||
int min = (int)m_ShowMin;
|
||||
int ticksInRange = max - min;
|
||||
var ticks = new ProfilerTick[ticksInRange];
|
||||
for (int i = min; i < max; i++) ticks[i - min] = m_CurrentTicks[i];
|
||||
string path = EditorUtility.SaveFilePanel("Save NetworkProfiler data", "", "networkProfilerData", "");
|
||||
if (!string.IsNullOrEmpty(path)) File.WriteAllBytes(path, new ProfilerContainer { Ticks = ticks }.ToBytes());
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
float prevHis = m_CaptureCount;
|
||||
m_CaptureCount = EditorGUILayout.DelayedIntField("History count", m_CaptureCount);
|
||||
if (m_CaptureCount <= 0) m_CaptureCount = 1;
|
||||
m_UpdateDelay = EditorGUILayout.Slider("Refresh delay", m_UpdateDelay, 0.1f, 10f);
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
if (prevHis != m_CaptureCount) StartRecording();
|
||||
|
||||
//Cache
|
||||
if (NetworkProfiler.IsRunning)
|
||||
{
|
||||
if (Time.unscaledTime - m_LastDrawn > m_UpdateDelay)
|
||||
{
|
||||
m_LastDrawn = Time.unscaledTime;
|
||||
m_CurrentTicks.Clear();
|
||||
if (NetworkProfiler.Ticks.Count >= 2)
|
||||
{
|
||||
m_Curve = AnimationCurve.Constant(NetworkProfiler.Ticks.ElementAt(0).EventId, NetworkProfiler.Ticks.ElementAt(NetworkProfiler.Ticks.Count - 1).EventId, 0);
|
||||
}
|
||||
|
||||
for (int i = 0; i < NetworkProfiler.Ticks.Count; i++)
|
||||
{
|
||||
var tick = NetworkProfiler.Ticks.ElementAt(i);
|
||||
m_CurrentTicks.Add(tick);
|
||||
|
||||
uint bytes = 0;
|
||||
if (tick.Events.Count > 0)
|
||||
{
|
||||
for (int j = 0; j < tick.Events.Count; j++)
|
||||
{
|
||||
var tickEvent = tick.Events[j];
|
||||
bytes += tickEvent.Bytes;
|
||||
}
|
||||
}
|
||||
|
||||
m_Curve.AddKey(tick.EventId, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Draw Animation curve and slider
|
||||
m_Curve = EditorGUILayout.CurveField(m_Curve);
|
||||
EditorGUILayout.MinMaxSlider(ref m_ShowMin, ref m_ShowMax, 0, m_CurrentTicks.Count);
|
||||
//Verify slider values
|
||||
if (m_ShowMin < 0) m_ShowMin = 0;
|
||||
if (m_ShowMax > m_CurrentTicks.Count) m_ShowMax = m_CurrentTicks.Count;
|
||||
if (m_ShowMin <= 0 && m_ShowMax <= 0)
|
||||
{
|
||||
m_ShowMin = 0;
|
||||
m_ShowMax = m_CurrentTicks.Count;
|
||||
}
|
||||
|
||||
//Draw main board
|
||||
bool hover = false;
|
||||
int nonEmptyTicks = 0;
|
||||
int largestTickCount = 0;
|
||||
int totalTicks = ((int)m_ShowMax - (int)m_ShowMin);
|
||||
|
||||
for (int i = (int)m_ShowMin; i < (int)m_ShowMax; i++)
|
||||
{
|
||||
if (m_CurrentTicks[i].Events.Count > 0) nonEmptyTicks++; //Count non empty ticks
|
||||
if (m_CurrentTicks[i].Events.Count > largestTickCount) largestTickCount = m_CurrentTicks[i].Events.Count; //Get how many events the tick with most events has
|
||||
}
|
||||
|
||||
int emptyTicks = totalTicks - nonEmptyTicks;
|
||||
|
||||
float equalWidth = position.width / totalTicks;
|
||||
float propWidth = equalWidth * 0.3f;
|
||||
float widthPerTick = ((position.width - emptyTicks * propWidth) / nonEmptyTicks);
|
||||
|
||||
float currentX = 0;
|
||||
int emptyStreak = 0;
|
||||
for (int i = (int)m_ShowMin; i < (int)m_ShowMax; i++)
|
||||
{
|
||||
var tick = m_CurrentTicks[i];
|
||||
if (tick.Events.Count == 0 && i != totalTicks - 1)
|
||||
{
|
||||
emptyStreak++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (emptyStreak > 0 || i == totalTicks - 1)
|
||||
{
|
||||
var dataRect = new Rect(currentX, 140f, propWidth * emptyStreak, position.height - 140f);
|
||||
currentX += propWidth * emptyStreak;
|
||||
if (emptyStreak >= 2) EditorGUI.LabelField(new Rect(dataRect.x, dataRect.y, dataRect.width, dataRect.height), emptyStreak.ToString(), s_WrapStyle);
|
||||
emptyStreak = 0;
|
||||
}
|
||||
|
||||
if (tick.Events.Count > 0)
|
||||
{
|
||||
float heightPerEvent = ((position.height - 140f) - (5f * largestTickCount)) / largestTickCount;
|
||||
|
||||
float currentY = 140f;
|
||||
for (int j = 0; j < tick.Events.Count; j++)
|
||||
{
|
||||
var tickEvent = tick.Events[j];
|
||||
var dataRect = new Rect(currentX, currentY, widthPerTick, heightPerEvent);
|
||||
|
||||
if (dataRect.Contains(Event.current.mousePosition))
|
||||
{
|
||||
hover = true;
|
||||
m_EventHover = tickEvent;
|
||||
}
|
||||
|
||||
EditorGUI.DrawRect(dataRect, TickTypeToColor(tickEvent.EventType, true));
|
||||
EditorGUI.LabelField(new Rect(dataRect.x, dataRect.y, dataRect.width, dataRect.height / 2), tickEvent.EventType.ToString(), s_WrapStyle);
|
||||
EditorGUI.LabelField(new Rect(dataRect.x, dataRect.y + dataRect.height / 2, dataRect.width, dataRect.height / 2), tickEvent.Bytes + "B", s_WrapStyle);
|
||||
|
||||
currentY += heightPerEvent + 5f;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.DrawRect(new Rect(currentX, 100, widthPerTick, 40), TickTypeToColor(tick.Type, false));
|
||||
EditorGUI.LabelField(new Rect(currentX, 100, widthPerTick, 20), tick.Type.ToString(), s_WrapStyle);
|
||||
EditorGUI.LabelField(new Rect(currentX, 120, widthPerTick, 20), tick.Frame.ToString(), s_WrapStyle);
|
||||
currentX += widthPerTick;
|
||||
}
|
||||
|
||||
//Calculate alpha
|
||||
if (hover)
|
||||
{
|
||||
m_HoverAlpha += deltaTime * 10f;
|
||||
|
||||
if (m_HoverAlpha > 1f) m_HoverAlpha = 1f;
|
||||
else if (m_HoverAlpha < 0f) m_HoverAlpha = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_HoverAlpha -= deltaTime * 10f;
|
||||
if (m_HoverAlpha > 1f) m_HoverAlpha = 1f;
|
||||
else if (m_HoverAlpha < 0f) m_HoverAlpha = 0f;
|
||||
}
|
||||
|
||||
//Draw hover thingy
|
||||
if (m_EventHover != null)
|
||||
{
|
||||
var rect = new Rect(Event.current.mousePosition, new Vector2(500, 100));
|
||||
EditorGUI.DrawRect(rect, GetEditorColorWithAlpha(m_HoverAlpha));
|
||||
|
||||
float heightPerField = (rect.height - 5) / 4;
|
||||
EditorGUI.LabelField(new Rect(rect.x + 5, rect.y + 5, rect.width, rect.height), "EventType: " + m_EventHover.EventType, GetStyleWithTextAlpha(EditorStyles.label, m_HoverAlpha));
|
||||
EditorGUI.LabelField(new Rect(rect.x + 5, rect.y + heightPerField * 1 + 5, rect.width, rect.height), "Size: " + m_EventHover.Bytes + "B", GetStyleWithTextAlpha(EditorStyles.label, m_HoverAlpha));
|
||||
EditorGUI.LabelField(new Rect(rect.x + 5, rect.y + heightPerField * 2 + 5, rect.width, rect.height), "Channel: " + m_EventHover.ChannelName, GetStyleWithTextAlpha(EditorStyles.label, m_HoverAlpha));
|
||||
EditorGUI.LabelField(new Rect(rect.x + 5, rect.y + heightPerField * 3 + 5, rect.width, rect.height), "MessageType: " + m_EventHover.MessageType, GetStyleWithTextAlpha(EditorStyles.label, m_HoverAlpha));
|
||||
}
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private Color TickTypeToColor(TickType type, bool alpha)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case TickType.Event:
|
||||
return new Color(0.58f, 0f, 0.56f, alpha ? 0.37f : 0.7f);
|
||||
case TickType.Receive:
|
||||
return new Color(0f, 0.85f, 0.85f, alpha ? 0.28f : 0.7f);
|
||||
case TickType.Send:
|
||||
return new Color(0, 0.55f, 1f, alpha ? 0.06f : 0.7f);
|
||||
default:
|
||||
return Color.clear;
|
||||
}
|
||||
}
|
||||
|
||||
private Color EditorColor => EditorGUIUtility.isProSkin ? new Color32(56, 56, 56, 255) : new Color32(194, 194, 194, 255);
|
||||
|
||||
private Color GetEditorColorWithAlpha(float alpha) => EditorGUIUtility.isProSkin ? new Color(0.22f, 0.22f, 0.22f, alpha) : new Color(0.76f, 0.76f, 0.76f, alpha);
|
||||
|
||||
private GUIStyle GetStyleWithTextAlpha(GUIStyle style, float alpha)
|
||||
{
|
||||
Color textColor = style.normal.textColor;
|
||||
textColor.a = alpha;
|
||||
GUIStyle newStyle = new GUIStyle(style);
|
||||
newStyle.normal.textColor = textColor;
|
||||
return newStyle;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 696c1401e8a6b80409a9a02b51cb469c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,116 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MLAPI.Profiling;
|
||||
using Unity.Profiling;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MLAPI
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
internal static class MLAPIProfilerModule
|
||||
{
|
||||
#if UNITY_2020_2_OR_NEWER && ENABLE_PROFILER
|
||||
private const string k_RpcModuleName = "MLAPI RPCs";
|
||||
private const string k_OperationModuleName = "MLAPI Operations";
|
||||
private const string k_MessageModuleName = "MLAPI Messages";
|
||||
|
||||
/// <summary>
|
||||
/// This needs to be in synced with the internal dynamic module structure to provide our own counters
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
private class MLAPIProfilerCounter
|
||||
{
|
||||
// Note: These fields are named this way for internal serialization
|
||||
public string m_Name;
|
||||
public string m_Category;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This needs to be in synced with the internal dynamic module structure to provide our own counters
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
private class MLAPIProfilerModuleData
|
||||
{
|
||||
// Note: These fields are named this way for internal serialization
|
||||
public List<MLAPIProfilerCounter> m_ChartCounters = new List<MLAPIProfilerCounter>();
|
||||
public List<MLAPIProfilerCounter> m_DetailCounters = new List<MLAPIProfilerCounter>();
|
||||
public string m_Name;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class MLAPIModules
|
||||
{
|
||||
// Note: These fields are named this way for internal serialization
|
||||
public List<MLAPIProfilerModuleData> m_Modules;
|
||||
}
|
||||
|
||||
private static List<MLAPIProfilerCounter> CreateRPCCounters() => new List<MLAPIProfilerCounter>()
|
||||
{
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.RpcSent, m_Category = ProfilerCategory.Network.Name },
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.RpcReceived, m_Category = ProfilerCategory.Network.Name },
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.RpcBatchesSent, m_Category = ProfilerCategory.Network.Name },
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.RpcBatchesReceived, m_Category = ProfilerCategory.Network.Name },
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.RpcQueueProcessed, m_Category = ProfilerCategory.Network.Name },
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.RpcInQueueSize, m_Category = ProfilerCategory.Network.Name },
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.RpcOutQueueSize, m_Category = ProfilerCategory.Network.Name },
|
||||
};
|
||||
|
||||
private static List<MLAPIProfilerCounter> CreateOperationsCounters() => new List<MLAPIProfilerCounter>()
|
||||
{
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.Connections, m_Category = ProfilerCategory.Network.Name },
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.ReceiveTickRate, m_Category = ProfilerCategory.Network.Name },
|
||||
};
|
||||
|
||||
private static List<MLAPIProfilerCounter> CreateMessagesCounters() => new List<MLAPIProfilerCounter>()
|
||||
{
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.NamedMessageReceived, m_Category = ProfilerCategory.Network.Name },
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.UnnamedMessageReceived, m_Category = ProfilerCategory.Network.Name },
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.NamedMessageSent, m_Category = ProfilerCategory.Network.Name },
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.UnnamedMessageSent, m_Category = ProfilerCategory.Network.Name },
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.ByteSent, m_Category = ProfilerCategory.Network.Name },
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.ByteReceived, m_Category = ProfilerCategory.Network.Name },
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.NetworkVarUpdates, m_Category = ProfilerCategory.Network.Name },
|
||||
new MLAPIProfilerCounter { m_Name = ProfilerConstants.NetworkVarDeltas, m_Category = ProfilerCategory.Network.Name },
|
||||
};
|
||||
|
||||
private delegate List<MLAPIProfilerCounter> CounterListFactoryDelegate();
|
||||
|
||||
private static bool CreateMLAPIDynamicModule(ref MLAPIModules mlapiModules, string moduleName, CounterListFactoryDelegate counterListFactoryDelegate)
|
||||
{
|
||||
var module = mlapiModules.m_Modules.Find(x => x.m_Name == moduleName);
|
||||
if (module == null)
|
||||
{
|
||||
var newModule = new MLAPIProfilerModuleData
|
||||
{
|
||||
m_Name = moduleName, m_ChartCounters = counterListFactoryDelegate(), m_DetailCounters = counterListFactoryDelegate(),
|
||||
};
|
||||
mlapiModules.m_Modules.Add(newModule);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
static MLAPIProfilerModule()
|
||||
{
|
||||
#if UNITY_2020_2_OR_NEWER && ENABLE_PROFILER
|
||||
var dynamicModulesJson = EditorPrefs.GetString("ProfilerWindow.DynamicModules");
|
||||
var dynamicModules = JsonUtility.FromJson<MLAPIModules>(dynamicModulesJson);
|
||||
|
||||
if (dynamicModules != null)
|
||||
{
|
||||
bool wasCreated = CreateMLAPIDynamicModule(ref dynamicModules, k_RpcModuleName, CreateRPCCounters);
|
||||
wasCreated |= CreateMLAPIDynamicModule(ref dynamicModules, k_OperationModuleName, CreateOperationsCounters);
|
||||
wasCreated |= CreateMLAPIDynamicModule(ref dynamicModules, k_MessageModuleName, CreateMessagesCounters);
|
||||
|
||||
if (wasCreated)
|
||||
{
|
||||
EditorPrefs.SetString("ProfilerWindow.DynamicModules", JsonUtility.ToJson(dynamicModules));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ff21a65efe3f4150b3cba2842f170d7d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using MLAPI.Prototyping;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor
|
||||
{
|
||||
[CustomEditor(typeof(NetworkAnimator), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkAnimatorEditor : Editor
|
||||
{
|
||||
private NetworkAnimator m_Target;
|
||||
|
||||
[NonSerialized]
|
||||
private bool m_Initialized;
|
||||
|
||||
private SerializedProperty m_AnimatorProperty;
|
||||
private GUIContent m_AnimatorLabel;
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (m_Initialized) return;
|
||||
|
||||
m_Initialized = true;
|
||||
m_Target = target as NetworkAnimator;
|
||||
|
||||
m_AnimatorProperty = serializedObject.FindProperty("m_Animator");
|
||||
m_AnimatorLabel = new GUIContent("Animator", "The Animator component to synchronize.");
|
||||
}
|
||||
|
||||
private void DrawControls()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_AnimatorProperty, m_AnimatorLabel);
|
||||
if (EditorGUI.EndChangeCheck()) m_Target.ResetTrackedParams();
|
||||
|
||||
var animator = m_Target.Animator;
|
||||
if (animator == null) return;
|
||||
|
||||
var animatorController = animator.runtimeAnimatorController as AnimatorController;
|
||||
if (animatorController == null) return;
|
||||
|
||||
EditorGUI.indentLevel += 1;
|
||||
var showWarning = false;
|
||||
{
|
||||
int paramIndex = 0;
|
||||
foreach (var animParam in animatorController.parameters)
|
||||
{
|
||||
if (paramIndex >= 32)
|
||||
{
|
||||
showWarning = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bool wasTracking = m_Target.GetParamTracking(paramIndex);
|
||||
bool isTracking = EditorGUILayout.Toggle(animParam.name, wasTracking);
|
||||
if (isTracking != wasTracking)
|
||||
{
|
||||
m_Target.SetParamTracking(paramIndex, isTracking);
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
|
||||
paramIndex++;
|
||||
}
|
||||
}
|
||||
if (showWarning) EditorGUILayout.HelpBox("NetworkAnimator can only select between the first 32 parameters in a mecanim controller", MessageType.Warning);
|
||||
EditorGUI.indentLevel -= 1;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
Initialize();
|
||||
serializedObject.Update();
|
||||
DrawControls();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 26e973580f9aacc46863bde0a99665e6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using MLAPI;
|
||||
using MLAPI.NetworkVariable;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor
|
||||
{
|
||||
[CustomEditor(typeof(NetworkBehaviour), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkBehaviourEditor : Editor
|
||||
{
|
||||
private bool m_Initialized;
|
||||
private readonly List<string> m_NetworkVariableNames = new List<string>();
|
||||
private readonly Dictionary<string, FieldInfo> m_NetworkVariableFields = new Dictionary<string, FieldInfo>();
|
||||
private readonly Dictionary<string, object> m_NetworkVariableObjects = new Dictionary<string, object>();
|
||||
|
||||
private GUIContent m_NetworkVariableLabelGuiContent;
|
||||
|
||||
private void Init(MonoScript script)
|
||||
{
|
||||
m_Initialized = true;
|
||||
|
||||
m_NetworkVariableNames.Clear();
|
||||
m_NetworkVariableFields.Clear();
|
||||
m_NetworkVariableObjects.Clear();
|
||||
|
||||
m_NetworkVariableLabelGuiContent = new GUIContent("NetworkVariable", "This variable is a NetworkVariable. It can not be serialized and can only be changed during runtime.");
|
||||
|
||||
var fields = script.GetClass().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
{
|
||||
var ft = fields[i].FieldType;
|
||||
if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkVariable<>) && !fields[i].IsDefined(typeof(HideInInspector), true))
|
||||
{
|
||||
m_NetworkVariableNames.Add(fields[i].Name);
|
||||
m_NetworkVariableFields.Add(fields[i].Name, fields[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderNetworkVariable(int index)
|
||||
{
|
||||
if (!m_NetworkVariableFields.ContainsKey(m_NetworkVariableNames[index]))
|
||||
{
|
||||
serializedObject.Update();
|
||||
var scriptProperty = serializedObject.FindProperty("m_Script");
|
||||
if (scriptProperty == null) return;
|
||||
|
||||
var targetScript = scriptProperty.objectReferenceValue as MonoScript;
|
||||
Init(targetScript);
|
||||
}
|
||||
|
||||
object value = m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target);
|
||||
if (value == null)
|
||||
{
|
||||
var fieldType = m_NetworkVariableFields[m_NetworkVariableNames[index]].FieldType;
|
||||
var networkVariable = (INetworkVariable)Activator.CreateInstance(fieldType, true);
|
||||
m_NetworkVariableFields[m_NetworkVariableNames[index]].SetValue(target, networkVariable);
|
||||
}
|
||||
|
||||
var type = m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target).GetType();
|
||||
var genericType = type.GetGenericArguments()[0];
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (genericType == typeof(string))
|
||||
{
|
||||
var networkVariable = (NetworkVariable<string>)m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target);
|
||||
networkVariable.Value = EditorGUILayout.TextField(m_NetworkVariableNames[index], networkVariable.Value);
|
||||
}
|
||||
else if (genericType.IsValueType)
|
||||
{
|
||||
var method = typeof(NetworkBehaviourEditor).GetMethod("RenderNetworkVariableValueType", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic);
|
||||
var genericMethod = method.MakeGenericMethod(genericType);
|
||||
genericMethod.Invoke(this, new[] { (object)index });
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField("Type not renderable");
|
||||
}
|
||||
|
||||
GUILayout.Label(m_NetworkVariableLabelGuiContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(m_NetworkVariableLabelGuiContent).x));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void RenderNetworkVariableValueType<T>(int index) where T : struct
|
||||
{
|
||||
var networkVariable = (NetworkVariable<T>)m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target);
|
||||
var type = typeof(T);
|
||||
object val = networkVariable.Value;
|
||||
string name = m_NetworkVariableNames[index];
|
||||
|
||||
if (NetworkManager.Singleton != null && NetworkManager.Singleton.IsListening)
|
||||
{
|
||||
if (type == typeof(int)) val = EditorGUILayout.IntField(name, (int)val);
|
||||
else if (type == typeof(uint)) val = (uint)EditorGUILayout.LongField(name, (long)((uint)val));
|
||||
else if (type == typeof(short)) val = (short)EditorGUILayout.IntField(name, (int)((short)val));
|
||||
else if (type == typeof(ushort)) val = (ushort)EditorGUILayout.IntField(name, (int)((ushort)val));
|
||||
else if (type == typeof(sbyte)) val = (sbyte)EditorGUILayout.IntField(name, (int)((sbyte)val));
|
||||
else if (type == typeof(byte)) val = (byte)EditorGUILayout.IntField(name, (int)((byte)val));
|
||||
else if (type == typeof(long)) val = EditorGUILayout.LongField(name, (long)val);
|
||||
else if (type == typeof(ulong)) val = (ulong)EditorGUILayout.LongField(name, (long)((ulong)val));
|
||||
else if (type == typeof(bool)) val = EditorGUILayout.Toggle(name, (bool)val);
|
||||
else if (type == typeof(string)) val = EditorGUILayout.TextField(name, (string)val);
|
||||
else if (type.IsEnum) val = EditorGUILayout.EnumPopup(name, (Enum)val);
|
||||
else EditorGUILayout.LabelField("Type not renderable");
|
||||
|
||||
networkVariable.Value = (T)val;
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField(name, EditorStyles.wordWrappedLabel);
|
||||
EditorGUILayout.SelectableLabel(val.ToString(), EditorStyles.wordWrappedLabel);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (!m_Initialized)
|
||||
{
|
||||
serializedObject.Update();
|
||||
var scriptProperty = serializedObject.FindProperty("m_Script");
|
||||
if (scriptProperty == null) return;
|
||||
|
||||
var targetScript = scriptProperty.objectReferenceValue as MonoScript;
|
||||
Init(targetScript);
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
serializedObject.Update();
|
||||
|
||||
for (int i = 0; i < m_NetworkVariableNames.Count; i++)
|
||||
{
|
||||
RenderNetworkVariable(i);
|
||||
}
|
||||
|
||||
var property = serializedObject.GetIterator();
|
||||
bool expanded = true;
|
||||
while (property.NextVisible(expanded))
|
||||
{
|
||||
if (property.propertyType == SerializedPropertyType.ObjectReference)
|
||||
{
|
||||
if (property.name == "m_Script") EditorGUI.BeginDisabledGroup(true);
|
||||
|
||||
EditorGUILayout.PropertyField(property, true);
|
||||
|
||||
if (property.name == "m_Script") EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(property, true);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
expanded = false;
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
EditorGUI.EndChangeCheck();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a4a5c9c08a4038e449fd259764bc663f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,408 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditorInternal;
|
||||
using MLAPI;
|
||||
using MLAPI.Transports;
|
||||
|
||||
[CustomEditor(typeof(NetworkManager), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkManagerEditor : Editor
|
||||
{
|
||||
// Properties
|
||||
private SerializedProperty m_DontDestroyOnLoadProperty;
|
||||
private SerializedProperty m_RunInBackgroundProperty;
|
||||
private SerializedProperty m_LogLevelProperty;
|
||||
|
||||
// NetworkConfig
|
||||
private SerializedProperty m_NetworkConfigProperty;
|
||||
|
||||
// NetworkConfig fields
|
||||
private SerializedProperty m_ProtocolVersionProperty;
|
||||
private SerializedProperty m_AllowRuntimeSceneChangesProperty;
|
||||
private SerializedProperty m_NetworkTransportProperty;
|
||||
private SerializedProperty m_ReceiveTickrateProperty;
|
||||
private SerializedProperty m_NetworkTickIntervalSecProperty;
|
||||
private SerializedProperty m_MaxReceiveEventsPerTickRateProperty;
|
||||
private SerializedProperty m_EventTickrateProperty;
|
||||
private SerializedProperty m_MaxObjectUpdatesPerTickProperty;
|
||||
private SerializedProperty m_ClientConnectionBufferTimeoutProperty;
|
||||
private SerializedProperty m_ConnectionApprovalProperty;
|
||||
private SerializedProperty m_EnableTimeResyncProperty;
|
||||
private SerializedProperty m_TimeResyncIntervalProperty;
|
||||
private SerializedProperty m_EnableNetworkVariableProperty;
|
||||
private SerializedProperty m_EnsureNetworkVariableLengthSafetyProperty;
|
||||
private SerializedProperty m_CreatePlayerPrefabProperty;
|
||||
private SerializedProperty m_ForceSamePrefabsProperty;
|
||||
private SerializedProperty m_UsePrefabSyncProperty;
|
||||
private SerializedProperty m_EnableSceneManagementProperty;
|
||||
private SerializedProperty m_RecycleNetworkIdsProperty;
|
||||
private SerializedProperty m_NetworkIdRecycleDelayProperty;
|
||||
private SerializedProperty m_RpcHashSizeProperty;
|
||||
private SerializedProperty m_LoadSceneTimeOutProperty;
|
||||
private SerializedProperty m_EnableMessageBufferingProperty;
|
||||
private SerializedProperty m_MessageBufferTimeoutProperty;
|
||||
|
||||
private ReorderableList m_NetworkPrefabsList;
|
||||
private ReorderableList m_RegisteredScenesList;
|
||||
|
||||
private NetworkManager m_NetworkManager;
|
||||
private bool m_Initialized;
|
||||
|
||||
private readonly List<Type> m_TransportTypes = new List<Type>();
|
||||
private string[] m_TransportNames = { "Select transport..." };
|
||||
|
||||
private void ReloadTransports()
|
||||
{
|
||||
m_TransportTypes.Clear();
|
||||
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
var types = assembly.GetTypes();
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (type.IsSubclassOf(typeof(NetworkTransport)))
|
||||
{
|
||||
m_TransportTypes.Add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_TransportNames = new string[m_TransportTypes.Count + 1];
|
||||
m_TransportNames[0] = "Select transport...";
|
||||
|
||||
for (int i = 0; i < m_TransportTypes.Count; i++)
|
||||
{
|
||||
m_TransportNames[i + 1] = m_TransportTypes[i].Name;
|
||||
}
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
if (m_Initialized) return;
|
||||
|
||||
m_Initialized = true;
|
||||
m_NetworkManager = (NetworkManager)target;
|
||||
|
||||
// Base properties
|
||||
m_DontDestroyOnLoadProperty = serializedObject.FindProperty("DontDestroy");
|
||||
m_RunInBackgroundProperty = serializedObject.FindProperty("RunInBackground");
|
||||
m_LogLevelProperty = serializedObject.FindProperty("LogLevel");
|
||||
m_NetworkConfigProperty = serializedObject.FindProperty("NetworkConfig");
|
||||
|
||||
// NetworkConfig properties
|
||||
m_ProtocolVersionProperty = m_NetworkConfigProperty.FindPropertyRelative("ProtocolVersion");
|
||||
m_AllowRuntimeSceneChangesProperty = m_NetworkConfigProperty.FindPropertyRelative("AllowRuntimeSceneChanges");
|
||||
m_NetworkTransportProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTransport");
|
||||
m_ReceiveTickrateProperty = m_NetworkConfigProperty.FindPropertyRelative("ReceiveTickrate");
|
||||
m_NetworkTickIntervalSecProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTickIntervalSec");
|
||||
m_MaxReceiveEventsPerTickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("MaxReceiveEventsPerTickRate");
|
||||
m_EventTickrateProperty = m_NetworkConfigProperty.FindPropertyRelative("EventTickrate");
|
||||
m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout");
|
||||
m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval");
|
||||
m_EnableTimeResyncProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableTimeResync");
|
||||
m_TimeResyncIntervalProperty = m_NetworkConfigProperty.FindPropertyRelative("TimeResyncInterval");
|
||||
m_EnableNetworkVariableProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableNetworkVariable");
|
||||
m_EnsureNetworkVariableLengthSafetyProperty = m_NetworkConfigProperty.FindPropertyRelative("EnsureNetworkVariableLengthSafety");
|
||||
m_CreatePlayerPrefabProperty = m_NetworkConfigProperty.FindPropertyRelative("CreatePlayerPrefab");
|
||||
m_ForceSamePrefabsProperty = m_NetworkConfigProperty.FindPropertyRelative("ForceSamePrefabs");
|
||||
m_UsePrefabSyncProperty = m_NetworkConfigProperty.FindPropertyRelative("UsePrefabSync");
|
||||
m_EnableSceneManagementProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableSceneManagement");
|
||||
m_RecycleNetworkIdsProperty = m_NetworkConfigProperty.FindPropertyRelative("RecycleNetworkIds");
|
||||
m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay");
|
||||
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
|
||||
m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut");
|
||||
m_EnableMessageBufferingProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableMessageBuffering");
|
||||
m_MessageBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("MessageBufferTimeout");
|
||||
|
||||
|
||||
ReloadTransports();
|
||||
}
|
||||
|
||||
private void CheckNullProperties()
|
||||
{
|
||||
// Base properties
|
||||
m_DontDestroyOnLoadProperty = serializedObject.FindProperty("DontDestroy");
|
||||
m_RunInBackgroundProperty = serializedObject.FindProperty("RunInBackground");
|
||||
m_LogLevelProperty = serializedObject.FindProperty("LogLevel");
|
||||
m_NetworkConfigProperty = serializedObject.FindProperty("NetworkConfig");
|
||||
|
||||
// NetworkConfig properties
|
||||
m_ProtocolVersionProperty = m_NetworkConfigProperty.FindPropertyRelative("ProtocolVersion");
|
||||
m_AllowRuntimeSceneChangesProperty = m_NetworkConfigProperty.FindPropertyRelative("AllowRuntimeSceneChanges");
|
||||
m_NetworkTransportProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTransport");
|
||||
m_ReceiveTickrateProperty = m_NetworkConfigProperty.FindPropertyRelative("ReceiveTickrate");
|
||||
m_NetworkTickIntervalSecProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTickIntervalSec");
|
||||
m_MaxReceiveEventsPerTickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("MaxReceiveEventsPerTickRate");
|
||||
m_EventTickrateProperty = m_NetworkConfigProperty.FindPropertyRelative("EventTickrate");
|
||||
m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout");
|
||||
m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval");
|
||||
m_EnableTimeResyncProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableTimeResync");
|
||||
m_TimeResyncIntervalProperty = m_NetworkConfigProperty.FindPropertyRelative("TimeResyncInterval");
|
||||
m_EnableNetworkVariableProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableNetworkVariable");
|
||||
m_EnsureNetworkVariableLengthSafetyProperty = m_NetworkConfigProperty.FindPropertyRelative("EnsureNetworkVariableLengthSafety");
|
||||
m_CreatePlayerPrefabProperty = m_NetworkConfigProperty.FindPropertyRelative("CreatePlayerPrefab");
|
||||
m_ForceSamePrefabsProperty = m_NetworkConfigProperty.FindPropertyRelative("ForceSamePrefabs");
|
||||
m_UsePrefabSyncProperty = m_NetworkConfigProperty.FindPropertyRelative("UsePrefabSync");
|
||||
m_EnableSceneManagementProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableSceneManagement");
|
||||
m_RecycleNetworkIdsProperty = m_NetworkConfigProperty.FindPropertyRelative("RecycleNetworkIds");
|
||||
m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay");
|
||||
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
|
||||
m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut");
|
||||
m_EnableMessageBufferingProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableMessageBuffering");
|
||||
m_MessageBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("MessageBufferTimeout");
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_NetworkPrefabsList = new ReorderableList(serializedObject, serializedObject.FindProperty("NetworkConfig").FindPropertyRelative("NetworkPrefabs"), true, true, true, true);
|
||||
m_NetworkPrefabsList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
|
||||
{
|
||||
var element = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
int firstLabelWidth = 50;
|
||||
int secondLabelWidth = 140;
|
||||
float secondFieldWidth = 10;
|
||||
int reduceFirstWidth = 45;
|
||||
|
||||
EditorGUI.LabelField(new Rect(rect.x, rect.y, firstLabelWidth, EditorGUIUtility.singleLineHeight), "Prefab");
|
||||
EditorGUI.PropertyField(new Rect(rect.x + firstLabelWidth, rect.y, rect.width - firstLabelWidth - secondLabelWidth - secondFieldWidth - reduceFirstWidth,
|
||||
EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("Prefab"), GUIContent.none);
|
||||
|
||||
EditorGUI.LabelField(new Rect(rect.width - secondLabelWidth - secondFieldWidth, rect.y, secondLabelWidth, EditorGUIUtility.singleLineHeight), "Default Player Prefab");
|
||||
|
||||
int playerPrefabIndex = -1;
|
||||
|
||||
for (int i = 0; i < m_NetworkManager.NetworkConfig.NetworkPrefabs.Count; i++)
|
||||
{
|
||||
if (m_NetworkManager.NetworkConfig.NetworkPrefabs[i].PlayerPrefab)
|
||||
{
|
||||
playerPrefabIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope(playerPrefabIndex != -1 && playerPrefabIndex != index))
|
||||
{
|
||||
EditorGUI.PropertyField(new Rect(rect.width - secondFieldWidth, rect.y, secondFieldWidth,
|
||||
EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("PlayerPrefab"), GUIContent.none);
|
||||
}
|
||||
};
|
||||
|
||||
m_NetworkPrefabsList.drawHeaderCallback = (Rect rect) => { EditorGUI.LabelField(rect, "NetworkPrefabs"); };
|
||||
|
||||
|
||||
m_RegisteredScenesList = new ReorderableList(serializedObject, serializedObject.FindProperty("NetworkConfig").FindPropertyRelative("RegisteredScenes"), true, true, true, true);
|
||||
m_RegisteredScenesList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
|
||||
{
|
||||
var element = m_RegisteredScenesList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
int firstLabelWidth = 50;
|
||||
int padding = 20;
|
||||
|
||||
EditorGUI.LabelField(new Rect(rect.x, rect.y, firstLabelWidth, EditorGUIUtility.singleLineHeight), "Name");
|
||||
EditorGUI.PropertyField(new Rect(rect.x + firstLabelWidth, rect.y, rect.width - firstLabelWidth - padding,
|
||||
EditorGUIUtility.singleLineHeight), element, GUIContent.none);
|
||||
};
|
||||
|
||||
m_RegisteredScenesList.drawHeaderCallback = (Rect rect) => { EditorGUI.LabelField(rect, "Registered Scene Names"); };
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
Init();
|
||||
CheckNullProperties();
|
||||
|
||||
{
|
||||
var iterator = serializedObject.GetIterator();
|
||||
|
||||
for (bool enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false)
|
||||
{
|
||||
using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath))
|
||||
{
|
||||
EditorGUILayout.PropertyField(iterator, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient)
|
||||
{
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(m_DontDestroyOnLoadProperty);
|
||||
EditorGUILayout.PropertyField(m_RunInBackgroundProperty);
|
||||
EditorGUILayout.PropertyField(m_LogLevelProperty);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
m_NetworkPrefabsList.DoLayoutList();
|
||||
|
||||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.EnableSceneManagement))
|
||||
{
|
||||
m_RegisteredScenesList.DoLayoutList();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
|
||||
EditorGUILayout.LabelField("General", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_ProtocolVersionProperty);
|
||||
|
||||
EditorGUILayout.PropertyField(m_NetworkTransportProperty);
|
||||
|
||||
if (m_NetworkTransportProperty.objectReferenceValue == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("You have no transport selected. A transport is required for the MLAPI to work. Which one do you want?", MessageType.Warning);
|
||||
|
||||
int selection = EditorGUILayout.Popup(0, m_TransportNames);
|
||||
|
||||
if (selection > 0)
|
||||
{
|
||||
ReloadTransports();
|
||||
|
||||
var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]);
|
||||
|
||||
if (transportComponent == null)
|
||||
{
|
||||
transportComponent = m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]);
|
||||
}
|
||||
|
||||
m_NetworkTransportProperty.objectReferenceValue = transportComponent;
|
||||
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(m_EnableTimeResyncProperty);
|
||||
|
||||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.EnableTimeResync))
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_TimeResyncIntervalProperty);
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("Performance", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_ReceiveTickrateProperty);
|
||||
EditorGUILayout.PropertyField(m_NetworkTickIntervalSecProperty);
|
||||
EditorGUILayout.PropertyField(m_MaxReceiveEventsPerTickRateProperty);
|
||||
EditorGUILayout.PropertyField(m_EventTickrateProperty);
|
||||
EditorGUILayout.PropertyField(m_EnableNetworkVariableProperty);
|
||||
|
||||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.EnableNetworkVariable))
|
||||
{
|
||||
if (m_MaxObjectUpdatesPerTickProperty != null)
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_MaxObjectUpdatesPerTickProperty);
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(m_EnsureNetworkVariableLengthSafetyProperty);
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("Connection", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_ConnectionApprovalProperty);
|
||||
|
||||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.ConnectionApproval))
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_ClientConnectionBufferTimeoutProperty);
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("Spawning", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_CreatePlayerPrefabProperty);
|
||||
EditorGUILayout.PropertyField(m_ForceSamePrefabsProperty);
|
||||
|
||||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.EnableSceneManagement))
|
||||
{
|
||||
bool value = m_NetworkManager.NetworkConfig.UsePrefabSync;
|
||||
|
||||
if (!m_NetworkManager.NetworkConfig.EnableSceneManagement)
|
||||
{
|
||||
m_UsePrefabSyncProperty.boolValue = true;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(m_UsePrefabSyncProperty);
|
||||
|
||||
if (!m_NetworkManager.NetworkConfig.EnableSceneManagement)
|
||||
{
|
||||
m_UsePrefabSyncProperty.boolValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(m_RecycleNetworkIdsProperty);
|
||||
|
||||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.RecycleNetworkIds))
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_NetworkIdRecycleDelayProperty);
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(m_EnableMessageBufferingProperty);
|
||||
|
||||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.EnableMessageBuffering))
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_MessageBufferTimeoutProperty);
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("Bandwidth", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_RpcHashSizeProperty);
|
||||
|
||||
EditorGUILayout.LabelField("Scene Management", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_EnableSceneManagementProperty);
|
||||
|
||||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.EnableSceneManagement))
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_LoadSceneTimeOutProperty);
|
||||
EditorGUILayout.PropertyField(m_AllowRuntimeSceneChangesProperty);
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
|
||||
// Start buttons below
|
||||
{
|
||||
string buttonDisabledReasonSuffix = "";
|
||||
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
buttonDisabledReasonSuffix = ". This can only be done in play mode";
|
||||
GUI.enabled = false;
|
||||
}
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Start Host", "Starts a host instance" + buttonDisabledReasonSuffix)))
|
||||
{
|
||||
m_NetworkManager.StartHost();
|
||||
}
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Start Server", "Starts a server instance" + buttonDisabledReasonSuffix)))
|
||||
{
|
||||
m_NetworkManager.StartServer();
|
||||
}
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Start Client", "Starts a client instance" + buttonDisabledReasonSuffix)))
|
||||
{
|
||||
m_NetworkManager.StartClient();
|
||||
}
|
||||
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
GUI.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string instanceType = string.Empty;
|
||||
|
||||
if (m_NetworkManager.IsHost) instanceType = "Host";
|
||||
else if (m_NetworkManager.IsServer) instanceType = "Server";
|
||||
else if (m_NetworkManager.IsClient) instanceType = "Client";
|
||||
|
||||
EditorGUILayout.HelpBox("You cannot edit the NetworkConfig when a " + instanceType + " is running.", MessageType.Info);
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Stop " + instanceType, "Stops the " + instanceType + " instance.")))
|
||||
{
|
||||
if (m_NetworkManager.IsHost) m_NetworkManager.StopHost();
|
||||
else if (m_NetworkManager.IsServer) m_NetworkManager.StopServer();
|
||||
else if (m_NetworkManager.IsClient) m_NetworkManager.StopClient();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 74a8f011a324b7642b69098fe57bf635
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,87 @@
|
|||
using System.Collections.Generic;
|
||||
using MLAPI;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor
|
||||
{
|
||||
[CustomEditor(typeof(NetworkObject), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkObjectEditor : Editor
|
||||
{
|
||||
private bool m_Initialized;
|
||||
private NetworkObject m_NetworkObject;
|
||||
private bool m_ShowObservers;
|
||||
|
||||
private void Init()
|
||||
{
|
||||
if (m_Initialized) return;
|
||||
|
||||
m_Initialized = true;
|
||||
m_NetworkObject = (NetworkObject)target;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
Init();
|
||||
|
||||
if (!m_NetworkObject.IsSpawned && NetworkManager.Singleton != null && NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField(new GUIContent("Spawn", "Spawns the object across the network"));
|
||||
if (GUILayout.Toggle(false, "Spawn", EditorStyles.miniButtonLeft))
|
||||
{
|
||||
m_NetworkObject.Spawn();
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
else if (m_NetworkObject.IsSpawned)
|
||||
{
|
||||
EditorGUILayout.LabelField("PrefabHashGenerator: ", m_NetworkObject.PrefabHashGenerator, EditorStyles.label);
|
||||
EditorGUILayout.LabelField("PrefabHash: ", m_NetworkObject.PrefabHash.ToString(), EditorStyles.label);
|
||||
EditorGUILayout.LabelField("InstanceId: ", m_NetworkObject.NetworkInstanceId.ToString(), EditorStyles.label);
|
||||
EditorGUILayout.LabelField("NetworkId: ", m_NetworkObject.NetworkObjectId.ToString(), EditorStyles.label);
|
||||
EditorGUILayout.LabelField("OwnerId: ", m_NetworkObject.OwnerClientId.ToString(), EditorStyles.label);
|
||||
EditorGUILayout.LabelField("IsSpawned: ", m_NetworkObject.IsSpawned.ToString(), EditorStyles.label);
|
||||
EditorGUILayout.LabelField("IsLocalPlayer: ", m_NetworkObject.IsLocalPlayer.ToString(), EditorStyles.label);
|
||||
EditorGUILayout.LabelField("IsOwner: ", m_NetworkObject.IsOwner.ToString(), EditorStyles.label);
|
||||
EditorGUILayout.LabelField("IsOwnedByServer: ", m_NetworkObject.IsOwnedByServer.ToString(), EditorStyles.label);
|
||||
EditorGUILayout.LabelField("IsPlayerObject: ", m_NetworkObject.IsPlayerObject.ToString(), EditorStyles.label);
|
||||
EditorGUILayout.LabelField("IsSceneObject: ", (m_NetworkObject.IsSceneObject == null ? "Null" : m_NetworkObject.IsSceneObject.Value.ToString()), EditorStyles.label);
|
||||
|
||||
if (NetworkManager.Singleton != null && NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
m_ShowObservers = EditorGUILayout.Foldout(m_ShowObservers, "Observers");
|
||||
|
||||
if (m_ShowObservers)
|
||||
{
|
||||
HashSet<ulong>.Enumerator observerClientIds = m_NetworkObject.GetObservers();
|
||||
|
||||
EditorGUI.indentLevel += 1;
|
||||
|
||||
while (observerClientIds.MoveNext())
|
||||
{
|
||||
if (NetworkManager.Singleton.ConnectedClients[observerClientIds.Current].PlayerObject != null)
|
||||
{
|
||||
EditorGUILayout.ObjectField("ClientId: " + observerClientIds.Current, NetworkManager.Singleton.ConnectedClients[observerClientIds.Current].PlayerObject, typeof(GameObject), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.TextField("ClientId: " + observerClientIds.Current, EditorStyles.label);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
EditorGUILayout.LabelField("PrefabHash: ", m_NetworkObject.PrefabHash.ToString(), EditorStyles.label);
|
||||
EditorGUILayout.LabelField("InstanceId: ", m_NetworkObject.NetworkInstanceId.ToString(), EditorStyles.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 36e4b519d287d0f4e8bfb7d088a9275f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,67 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MLAPI;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor
|
||||
{
|
||||
public class NetworkScenePostProcess : MonoBehaviour
|
||||
{
|
||||
[PostProcessScene(int.MaxValue)]
|
||||
public static void ProcessScene()
|
||||
{
|
||||
// find all scene objects that have not been spawned yet
|
||||
// TODO: long term, replace the means of finding candidate objects to repace FindObjectsOfType
|
||||
var traverseSortedObjects = FindObjectsOfType<NetworkObject>().Where(x => x.IsSceneObject == null).ToList();
|
||||
|
||||
traverseSortedObjects.Sort((x, y) =>
|
||||
{
|
||||
List<int> xSiblingIndex = x.TraversedSiblingIndex();
|
||||
List<int> ySiblingIndex = y.TraversedSiblingIndex();
|
||||
|
||||
while (xSiblingIndex.Count > 0 && ySiblingIndex.Count > 0)
|
||||
{
|
||||
if (xSiblingIndex[0] < ySiblingIndex[0])
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (xSiblingIndex[0] > ySiblingIndex[0])
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
xSiblingIndex.RemoveAt(0);
|
||||
ySiblingIndex.RemoveAt(0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
for (ulong i = 0; i < (ulong)traverseSortedObjects.Count; i++)
|
||||
{
|
||||
traverseSortedObjects[(int)i].NetworkInstanceId = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class PrefabHelpers
|
||||
{
|
||||
internal static List<int> TraversedSiblingIndex(this NetworkObject networkObject)
|
||||
{
|
||||
var paths = new List<int>();
|
||||
var transform = networkObject.transform;
|
||||
|
||||
while (transform != null)
|
||||
{
|
||||
paths.Add(transform.GetSiblingIndex());
|
||||
transform = transform.parent;
|
||||
}
|
||||
|
||||
paths.Reverse();
|
||||
|
||||
return paths;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b8ffd6f352aba584ba0835e2f4221caa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "Unity.Multiplayer.MLAPI.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"Unity.Multiplayer.MLAPI.Runtime",
|
||||
"Unity.Multiplayer.MLAPI.Prototyping"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9f4f5bf029cebb64f983b7bdc29f62a1
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Unity Technologies
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a850895ba88aa114f804fefa31faf4d2
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4f9f645c043894b81ab326fe4703f4c8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,308 @@
|
|||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using MLAPI.Messaging;
|
||||
using MLAPI.Serialization;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MLAPI.Prototyping
|
||||
{
|
||||
/// <summary>
|
||||
/// A prototype component for syncing animations
|
||||
/// </summary>
|
||||
[AddComponentMenu("MLAPI/NetworkAnimator")]
|
||||
public class NetworkAnimator : NetworkBehaviour
|
||||
{
|
||||
private struct AnimParams : INetworkSerializable
|
||||
{
|
||||
public Dictionary<int, (AnimatorControllerParameterType Type, object Boxed)> Parameters;
|
||||
|
||||
public void NetworkSerialize(NetworkSerializer serializer)
|
||||
{
|
||||
int paramCount = serializer.IsReading ? 0 : Parameters.Count;
|
||||
serializer.Serialize(ref paramCount);
|
||||
|
||||
var paramArray = serializer.IsReading ? new KeyValuePair<int, (AnimatorControllerParameterType Type, object Boxed)>[paramCount] : Parameters.ToArray();
|
||||
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++)
|
||||
{
|
||||
int paramId = serializer.IsReading ? 0 : paramArray[paramIndex].Key;
|
||||
serializer.Serialize(ref paramId);
|
||||
|
||||
byte paramType = serializer.IsReading ? (byte)0 : (byte)paramArray[paramIndex].Value.Type;
|
||||
serializer.Serialize(ref paramType);
|
||||
|
||||
object paramBoxed = null;
|
||||
switch (paramType)
|
||||
{
|
||||
case (byte)AnimatorControllerParameterType.Float:
|
||||
float paramFloat = serializer.IsReading ? 0 : (float)paramArray[paramIndex].Value.Boxed;
|
||||
serializer.Serialize(ref paramFloat);
|
||||
paramBoxed = paramFloat;
|
||||
break;
|
||||
case (byte)AnimatorControllerParameterType.Int:
|
||||
int paramInt = serializer.IsReading ? 0 : (int)paramArray[paramIndex].Value.Boxed;
|
||||
serializer.Serialize(ref paramInt);
|
||||
paramBoxed = paramInt;
|
||||
break;
|
||||
case (byte)AnimatorControllerParameterType.Bool:
|
||||
bool paramBool = serializer.IsReading ? false : (bool)paramArray[paramIndex].Value.Boxed;
|
||||
serializer.Serialize(ref paramBool);
|
||||
paramBoxed = paramBool;
|
||||
break;
|
||||
}
|
||||
|
||||
if (serializer.IsReading)
|
||||
{
|
||||
paramArray[paramIndex] = new KeyValuePair<int, (AnimatorControllerParameterType, object)>(paramId, ((AnimatorControllerParameterType)paramType, paramBoxed));
|
||||
}
|
||||
}
|
||||
|
||||
if (serializer.IsReading)
|
||||
{
|
||||
Parameters = paramArray.ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float SendRate = 0.1f;
|
||||
|
||||
[SerializeField]
|
||||
private Animator m_Animator;
|
||||
|
||||
public Animator Animator => m_Animator;
|
||||
|
||||
[HideInInspector]
|
||||
[SerializeField]
|
||||
private uint m_TrackedParamFlags = 0;
|
||||
|
||||
public void SetParamTracking(int paramIndex, bool isTracking)
|
||||
{
|
||||
if (paramIndex >= 32) return;
|
||||
|
||||
if (isTracking)
|
||||
{
|
||||
m_TrackedParamFlags |= (uint)(1 << paramIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_TrackedParamFlags &= (uint)~(1 << paramIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetParamTracking(int paramIndex)
|
||||
{
|
||||
if (paramIndex >= 32) return false;
|
||||
|
||||
return (m_TrackedParamFlags & (uint)(1 << paramIndex)) != 0;
|
||||
}
|
||||
|
||||
public void ResetTrackedParams()
|
||||
{
|
||||
m_TrackedParamFlags = 0;
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (!IsOwner) return;
|
||||
|
||||
if (CheckSendRate())
|
||||
{
|
||||
SendTrackedParams();
|
||||
}
|
||||
|
||||
if (CheckStateChange(out int animStateHash, out float animStateTime))
|
||||
{
|
||||
SendAllParamsAndState(animStateHash, animStateTime);
|
||||
}
|
||||
}
|
||||
|
||||
private float m_NextSendTime = 0.0f;
|
||||
|
||||
private bool CheckSendRate()
|
||||
{
|
||||
var networkTime = NetworkManager.Singleton.NetworkTime;
|
||||
if (SendRate != 0 && m_NextSendTime < networkTime)
|
||||
{
|
||||
m_NextSendTime = networkTime + SendRate;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int m_LastAnimStateHash = 0;
|
||||
|
||||
private bool CheckStateChange(out int outAnimStateHash, out float outAnimStateTime)
|
||||
{
|
||||
var animStateInfo = Animator.GetCurrentAnimatorStateInfo(0);
|
||||
var animStateHash = animStateInfo.fullPathHash;
|
||||
var animStateTime = animStateInfo.normalizedTime;
|
||||
if (animStateHash != m_LastAnimStateHash)
|
||||
{
|
||||
m_LastAnimStateHash = animStateHash;
|
||||
|
||||
outAnimStateHash = animStateHash;
|
||||
outAnimStateTime = animStateTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
outAnimStateHash = 0;
|
||||
outAnimStateTime = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
private AnimParams GetAnimParams(bool trackedOnly = false)
|
||||
{
|
||||
var animParams = new AnimParams();
|
||||
animParams.Parameters = new Dictionary<int, (AnimatorControllerParameterType, object)>(32);
|
||||
for (int paramIndex = 0; paramIndex < 32 && paramIndex < Animator.parameters.Length; paramIndex++)
|
||||
{
|
||||
if (trackedOnly && !GetParamTracking(paramIndex)) continue;
|
||||
|
||||
var animParam = Animator.parameters[paramIndex];
|
||||
var animParamHash = animParam.nameHash;
|
||||
var animParamType = animParam.type;
|
||||
|
||||
object animParamBoxed = null;
|
||||
switch (animParamType)
|
||||
{
|
||||
case AnimatorControllerParameterType.Float:
|
||||
animParamBoxed = Animator.GetFloat(animParamHash);
|
||||
break;
|
||||
case AnimatorControllerParameterType.Int:
|
||||
animParamBoxed = Animator.GetInteger(animParamHash);
|
||||
break;
|
||||
case AnimatorControllerParameterType.Bool:
|
||||
animParamBoxed = Animator.GetBool(animParamHash);
|
||||
break;
|
||||
}
|
||||
|
||||
animParams.Parameters.Add(animParamHash, (animParamType, animParamBoxed));
|
||||
}
|
||||
|
||||
return animParams;
|
||||
}
|
||||
|
||||
private void SetAnimParams(AnimParams animParams)
|
||||
{
|
||||
foreach (var animParam in animParams.Parameters)
|
||||
{
|
||||
switch (animParam.Value.Type)
|
||||
{
|
||||
case AnimatorControllerParameterType.Float:
|
||||
Animator.SetFloat(animParam.Key, (float)animParam.Value.Boxed);
|
||||
break;
|
||||
case AnimatorControllerParameterType.Int:
|
||||
Animator.SetInteger(animParam.Key, (int)animParam.Value.Boxed);
|
||||
break;
|
||||
case AnimatorControllerParameterType.Bool:
|
||||
Animator.SetBool(animParam.Key, (bool)animParam.Value.Boxed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SendTrackedParams()
|
||||
{
|
||||
var animParams = GetAnimParams( /* trackedOnly = */ true);
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
var clientRpcParams = new ClientRpcParams
|
||||
{
|
||||
Send = new ClientRpcSendParams
|
||||
{
|
||||
TargetClientIds = NetworkManager.Singleton.ConnectedClientsList
|
||||
.Where(c => c.ClientId != NetworkManager.Singleton.ServerClientId)
|
||||
.Select(c => c.ClientId)
|
||||
.ToArray()
|
||||
}
|
||||
};
|
||||
UpdateTrackedParamsClientRpc(animParams, clientRpcParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateTrackedParamsServerRpc(animParams);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendAllParamsAndState(int animStateHash, float animStateTime)
|
||||
{
|
||||
var animParams = GetAnimParams();
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
var clientRpcParams = new ClientRpcParams
|
||||
{
|
||||
Send = new ClientRpcSendParams
|
||||
{
|
||||
TargetClientIds = NetworkManager.Singleton.ConnectedClientsList
|
||||
.Where(c => c.ClientId != NetworkManager.Singleton.ServerClientId)
|
||||
.Select(c => c.ClientId)
|
||||
.ToArray()
|
||||
}
|
||||
};
|
||||
UpdateAnimStateClientRpc(animStateHash, animStateTime, clientRpcParams);
|
||||
UpdateTrackedParamsClientRpc(animParams, clientRpcParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateAnimStateServerRpc(animStateHash, animStateTime);
|
||||
UpdateTrackedParamsServerRpc(animParams);
|
||||
}
|
||||
}
|
||||
|
||||
[ServerRpc]
|
||||
private void UpdateTrackedParamsServerRpc(AnimParams animParams, ServerRpcParams serverRpcParams = default)
|
||||
{
|
||||
if (IsOwner) return;
|
||||
SetAnimParams(animParams);
|
||||
|
||||
var clientRpcParams = new ClientRpcParams
|
||||
{
|
||||
Send = new ClientRpcSendParams
|
||||
{
|
||||
TargetClientIds = NetworkManager.Singleton.ConnectedClientsList
|
||||
.Where(c => c.ClientId != serverRpcParams.Receive.SenderClientId)
|
||||
.Select(c => c.ClientId)
|
||||
.ToArray()
|
||||
}
|
||||
};
|
||||
UpdateTrackedParamsClientRpc(animParams, clientRpcParams);
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
private void UpdateTrackedParamsClientRpc(AnimParams animParams, ClientRpcParams clientRpcParams = default)
|
||||
{
|
||||
if (IsOwner) return;
|
||||
SetAnimParams(animParams);
|
||||
}
|
||||
|
||||
[ServerRpc]
|
||||
private void UpdateAnimStateServerRpc(int animStateHash, float animStateTime, ServerRpcParams serverRpcParams = default)
|
||||
{
|
||||
if (IsOwner) return;
|
||||
|
||||
Animator.Play(animStateHash, 0, animStateTime);
|
||||
|
||||
var clientRpcParams = new ClientRpcParams
|
||||
{
|
||||
Send = new ClientRpcSendParams
|
||||
{
|
||||
TargetClientIds = NetworkManager.Singleton.ConnectedClientsList
|
||||
.Where(c => c.ClientId != serverRpcParams.Receive.SenderClientId)
|
||||
.Select(c => c.ClientId)
|
||||
.ToArray()
|
||||
}
|
||||
};
|
||||
UpdateAnimStateClientRpc(animStateHash, animStateTime, clientRpcParams);
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
private void UpdateAnimStateClientRpc(int animStateHash, float animStateTime, ClientRpcParams clientRpcParams = default)
|
||||
{
|
||||
if (IsOwner) return;
|
||||
|
||||
Animator.Play(animStateHash, 0, animStateTime);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e8d0727d5ae3244e3b569694d3912374
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,118 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
using MLAPI.Connection;
|
||||
using MLAPI.Messaging;
|
||||
|
||||
namespace MLAPI.Prototyping
|
||||
{
|
||||
/// <summary>
|
||||
/// A prototype component for syncing NavMeshAgents
|
||||
/// </summary>
|
||||
[AddComponentMenu("MLAPI/NetworkNavMeshAgent")]
|
||||
[RequireComponent(typeof(NavMeshAgent))]
|
||||
public class NetworkNavMeshAgent : NetworkBehaviour
|
||||
{
|
||||
private NavMeshAgent m_Agent;
|
||||
|
||||
/// <summary>
|
||||
/// Is proximity enabled
|
||||
/// </summary>
|
||||
public bool EnableProximity = false;
|
||||
|
||||
/// <summary>
|
||||
/// The proximity range
|
||||
/// </summary>
|
||||
public float ProximityRange = 50f;
|
||||
|
||||
/// <summary>
|
||||
/// The delay in seconds between corrections
|
||||
/// </summary>
|
||||
public float CorrectionDelay = 3f;
|
||||
|
||||
//TODO rephrase.
|
||||
/// <summary>
|
||||
/// The percentage to lerp on corrections
|
||||
/// </summary>
|
||||
[Tooltip("Everytime a correction packet is received. This is the percentage (between 0 & 1) that we will move towards the goal.")]
|
||||
public float DriftCorrectionPercentage = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// Should we warp on destination change
|
||||
/// </summary>
|
||||
public bool WarpOnDestinationChange = false;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_Agent = GetComponent<NavMeshAgent>();
|
||||
}
|
||||
|
||||
private Vector3 m_LastDestination = Vector3.zero;
|
||||
private float m_LastCorrectionTime = 0f;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsOwner) return;
|
||||
|
||||
if (m_Agent.destination != m_LastDestination)
|
||||
{
|
||||
m_LastDestination = m_Agent.destination;
|
||||
if (!EnableProximity)
|
||||
{
|
||||
OnNavMeshStateUpdateClientRpc(m_Agent.destination, m_Agent.velocity, transform.position);
|
||||
}
|
||||
else
|
||||
{
|
||||
var proximityClients = new List<ulong>();
|
||||
foreach (KeyValuePair<ulong, NetworkClient> client in NetworkManager.Singleton.ConnectedClients)
|
||||
{
|
||||
if (client.Value.PlayerObject == null || Vector3.Distance(client.Value.PlayerObject.transform.position, transform.position) <= ProximityRange)
|
||||
{
|
||||
proximityClients.Add(client.Key);
|
||||
}
|
||||
}
|
||||
|
||||
OnNavMeshStateUpdateClientRpc(m_Agent.destination, m_Agent.velocity, transform.position, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = proximityClients.ToArray() } });
|
||||
}
|
||||
}
|
||||
|
||||
if (NetworkManager.Singleton.NetworkTime - m_LastCorrectionTime >= CorrectionDelay)
|
||||
{
|
||||
if (!EnableProximity)
|
||||
{
|
||||
OnNavMeshCorrectionUpdateClientRpc(m_Agent.velocity, transform.position);
|
||||
}
|
||||
else
|
||||
{
|
||||
var proximityClients = new List<ulong>();
|
||||
foreach (KeyValuePair<ulong, NetworkClient> client in NetworkManager.Singleton.ConnectedClients)
|
||||
{
|
||||
if (client.Value.PlayerObject == null || Vector3.Distance(client.Value.PlayerObject.transform.position, transform.position) <= ProximityRange)
|
||||
{
|
||||
proximityClients.Add(client.Key);
|
||||
}
|
||||
}
|
||||
|
||||
OnNavMeshCorrectionUpdateClientRpc(m_Agent.velocity, transform.position, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = proximityClients.ToArray() } });
|
||||
}
|
||||
|
||||
m_LastCorrectionTime = NetworkManager.Singleton.NetworkTime;
|
||||
}
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
private void OnNavMeshStateUpdateClientRpc(Vector3 destination, Vector3 velocity, Vector3 position, ClientRpcParams rpcParams = default)
|
||||
{
|
||||
m_Agent.Warp(WarpOnDestinationChange ? position : Vector3.Lerp(transform.position, position, DriftCorrectionPercentage));
|
||||
m_Agent.SetDestination(destination);
|
||||
m_Agent.velocity = velocity;
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
private void OnNavMeshCorrectionUpdateClientRpc(Vector3 velocity, Vector3 position, ClientRpcParams rpcParams = default)
|
||||
{
|
||||
m_Agent.Warp(Vector3.Lerp(transform.position, position, DriftCorrectionPercentage));
|
||||
m_Agent.velocity = velocity;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 43b86c65774f4494ba4e5878d5df9bcd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,351 @@
|
|||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using MLAPI.Messaging;
|
||||
|
||||
namespace MLAPI.Prototyping
|
||||
{
|
||||
/// <summary>
|
||||
/// A prototype component for syncing transforms
|
||||
/// </summary>
|
||||
[AddComponentMenu("MLAPI/NetworkTransform")]
|
||||
public class NetworkTransform : NetworkBehaviour
|
||||
{
|
||||
internal class ClientSendInfo
|
||||
{
|
||||
public float LastSent;
|
||||
public Vector3? LastMissedPosition;
|
||||
public Quaternion? LastMissedRotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The base amount of sends per seconds to use when range is disabled
|
||||
/// </summary>
|
||||
[Range(0, 120)]
|
||||
public float FixedSendsPerSecond = 20f;
|
||||
|
||||
/// <summary>
|
||||
/// Is the sends per second assumed to be the same across all instances
|
||||
/// </summary>
|
||||
[Tooltip("This assumes that the SendsPerSecond is synced across clients")]
|
||||
public bool AssumeSyncedSends = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enable interpolation
|
||||
/// </summary>
|
||||
[Tooltip("This requires AssumeSyncedSends to be true")]
|
||||
public bool InterpolatePosition = true;
|
||||
|
||||
/// <summary>
|
||||
/// The distance before snaping to the position
|
||||
/// </summary>
|
||||
[Tooltip("The transform will snap if the distance is greater than this distance")]
|
||||
public float SnapDistance = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// Should the server interpolate
|
||||
/// </summary>
|
||||
public bool InterpolateServer = true;
|
||||
|
||||
/// <summary>
|
||||
/// The min meters to move before a send is sent
|
||||
/// </summary>
|
||||
public float MinMeters = 0.15f;
|
||||
|
||||
/// <summary>
|
||||
/// The min degrees to rotate before a send it sent
|
||||
/// </summary>
|
||||
public float MinDegrees = 1.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Enables extrapolation
|
||||
/// </summary>
|
||||
public bool ExtrapolatePosition = false;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of expected send rates to extrapolate over when awaiting new packets.
|
||||
/// A higher value will result in continued extrapolation after an object has stopped moving
|
||||
/// </summary>
|
||||
public float MaxSendsToExtrapolate = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The channel to send the data on
|
||||
/// </summary>
|
||||
[Tooltip("The channel to send the data on. Uses the default channel if left unspecified")]
|
||||
public string Channel = null;
|
||||
|
||||
private float m_LerpTime;
|
||||
private Vector3 m_LerpStartPos;
|
||||
private Quaternion m_LerpStartRot;
|
||||
private Vector3 m_LerpEndPos;
|
||||
private Quaternion m_LerpEndRot;
|
||||
|
||||
private float m_LastSendTime;
|
||||
private Vector3 m_LastSentPos;
|
||||
private Quaternion m_LastSentRot;
|
||||
|
||||
private float m_LastReceiveTime;
|
||||
|
||||
/// <summary>
|
||||
/// Enables range based send rate
|
||||
/// </summary>
|
||||
public bool EnableRange;
|
||||
|
||||
/// <summary>
|
||||
/// Checks for missed sends without provocation. Provocation being a client inside it's normal SendRate
|
||||
/// </summary>
|
||||
public bool EnableNonProvokedResendChecks;
|
||||
|
||||
/// <summary>
|
||||
/// The curve to use to calculate the send rate
|
||||
/// </summary>
|
||||
public AnimationCurve DistanceSendrate = AnimationCurve.Constant(0, 500, 20);
|
||||
|
||||
private readonly Dictionary<ulong, ClientSendInfo> m_ClientSendInfo = new Dictionary<ulong, ClientSendInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// The delegate used to check if a move is valid
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client id the move is being validated for</param>
|
||||
/// <param name="oldPos">The previous position</param>
|
||||
/// <param name="newPos">The new requested position</param>
|
||||
/// <returns>Returns Whether or not the move is valid</returns>
|
||||
public delegate bool MoveValidationDelegate(ulong clientId, Vector3 oldPos, Vector3 newPos);
|
||||
|
||||
/// <summary>
|
||||
/// If set, moves will only be accepted if the custom delegate returns true
|
||||
/// </summary>
|
||||
public MoveValidationDelegate IsMoveValidDelegate = null;
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (!AssumeSyncedSends && InterpolatePosition)
|
||||
InterpolatePosition = false;
|
||||
if (InterpolateServer && !InterpolatePosition)
|
||||
InterpolateServer = false;
|
||||
if (MinDegrees < 0)
|
||||
MinDegrees = 0;
|
||||
if (MinMeters < 0)
|
||||
MinMeters = 0;
|
||||
if (EnableNonProvokedResendChecks && !EnableRange)
|
||||
EnableNonProvokedResendChecks = false;
|
||||
}
|
||||
|
||||
private float GetTimeForLerp(Vector3 pos1, Vector3 pos2)
|
||||
{
|
||||
return 1f / DistanceSendrate.Evaluate(Vector3.Distance(pos1, pos2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers message handlers
|
||||
/// </summary>
|
||||
public override void NetworkStart()
|
||||
{
|
||||
m_LastSentRot = transform.rotation;
|
||||
m_LastSentPos = transform.position;
|
||||
|
||||
m_LerpStartPos = transform.position;
|
||||
m_LerpStartRot = transform.rotation;
|
||||
|
||||
m_LerpEndPos = transform.position;
|
||||
m_LerpEndRot = transform.rotation;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (IsOwner)
|
||||
{
|
||||
if (NetworkManager.Singleton.NetworkTime - m_LastSendTime >= (1f / FixedSendsPerSecond) && (Vector3.Distance(transform.position, m_LastSentPos) > MinMeters || Quaternion.Angle(transform.rotation, m_LastSentRot) > MinDegrees))
|
||||
{
|
||||
m_LastSendTime = NetworkManager.Singleton.NetworkTime;
|
||||
m_LastSentPos = transform.position;
|
||||
m_LastSentRot = transform.rotation;
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
ApplyTransformClientRpc(transform.position, transform.rotation.eulerAngles,
|
||||
new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = NetworkManager.Singleton.ConnectedClientsList.Where(c => c.ClientId != OwnerClientId).Select(c => c.ClientId).ToArray() } });
|
||||
}
|
||||
else
|
||||
{
|
||||
SubmitTransformServerRpc(transform.position, transform.rotation.eulerAngles);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//If we are server and interpolation is turned on for server OR we are not server and interpolation is turned on
|
||||
if ((IsServer && InterpolateServer && InterpolatePosition) || (!IsServer && InterpolatePosition))
|
||||
{
|
||||
if (Vector3.Distance(transform.position, m_LerpEndPos) > SnapDistance)
|
||||
{
|
||||
//Snap, set T to 1 (100% of the lerp)
|
||||
m_LerpTime = 1f;
|
||||
}
|
||||
|
||||
float sendDelay = (IsServer || !EnableRange || !AssumeSyncedSends || NetworkManager.Singleton.ConnectedClients[NetworkManager.Singleton.LocalClientId].PlayerObject == null) ? (1f / FixedSendsPerSecond) : GetTimeForLerp(transform.position, NetworkManager.Singleton.ConnectedClients[NetworkManager.Singleton.LocalClientId].PlayerObject.transform.position);
|
||||
m_LerpTime += Time.unscaledDeltaTime / sendDelay;
|
||||
|
||||
if (ExtrapolatePosition && Time.unscaledTime - m_LastReceiveTime < sendDelay * MaxSendsToExtrapolate)
|
||||
transform.position = Vector3.LerpUnclamped(m_LerpStartPos, m_LerpEndPos, m_LerpTime);
|
||||
else
|
||||
transform.position = Vector3.Lerp(m_LerpStartPos, m_LerpEndPos, m_LerpTime);
|
||||
|
||||
if (ExtrapolatePosition && Time.unscaledTime - m_LastReceiveTime < sendDelay * MaxSendsToExtrapolate)
|
||||
transform.rotation = Quaternion.SlerpUnclamped(m_LerpStartRot, m_LerpEndRot, m_LerpTime);
|
||||
else
|
||||
transform.rotation = Quaternion.Slerp(m_LerpStartRot, m_LerpEndRot, m_LerpTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsServer && EnableRange && EnableNonProvokedResendChecks)
|
||||
CheckForMissedSends();
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
private void ApplyTransformClientRpc(Vector3 position, Vector3 eulerAngles, ClientRpcParams rpcParams = default)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
ApplyTransformInternal(position, Quaternion.Euler(eulerAngles));
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyTransformInternal(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
if (InterpolatePosition && (!IsServer || InterpolateServer))
|
||||
{
|
||||
m_LastReceiveTime = Time.unscaledTime;
|
||||
m_LerpStartPos = transform.position;
|
||||
m_LerpStartRot = transform.rotation;
|
||||
m_LerpEndPos = position;
|
||||
m_LerpEndRot = rotation;
|
||||
m_LerpTime = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.position = position;
|
||||
transform.rotation = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
[ServerRpc]
|
||||
private void SubmitTransformServerRpc(Vector3 position, Vector3 eulerAngles, ServerRpcParams rpcParams = default)
|
||||
{
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
if (IsMoveValidDelegate != null && !IsMoveValidDelegate(rpcParams.Receive.SenderClientId, m_LerpEndPos, position))
|
||||
{
|
||||
//Invalid move!
|
||||
//TODO: Add rubber band (just a message telling them to go back)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsClient)
|
||||
{
|
||||
// Dedicated server
|
||||
ApplyTransformInternal(position, Quaternion.Euler(eulerAngles));
|
||||
}
|
||||
|
||||
if (EnableRange)
|
||||
{
|
||||
for (int i = 0; i < NetworkManager.Singleton.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
if (!m_ClientSendInfo.ContainsKey(NetworkManager.Singleton.ConnectedClientsList[i].ClientId))
|
||||
{
|
||||
m_ClientSendInfo.Add(NetworkManager.Singleton.ConnectedClientsList[i].ClientId, new ClientSendInfo()
|
||||
{
|
||||
LastMissedPosition = null,
|
||||
LastMissedRotation = null,
|
||||
LastSent = 0
|
||||
});
|
||||
}
|
||||
|
||||
ClientSendInfo info = m_ClientSendInfo[NetworkManager.Singleton.ConnectedClientsList[i].ClientId];
|
||||
Vector3? receiverPosition = NetworkManager.Singleton.ConnectedClientsList[i].PlayerObject == null ? null : new Vector3?(NetworkManager.Singleton.ConnectedClientsList[i].PlayerObject.transform.position);
|
||||
Vector3? senderPosition = NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject == null ? null : new Vector3?(NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject.transform.position);
|
||||
|
||||
if ((receiverPosition == null || senderPosition == null && NetworkManager.Singleton.NetworkTime - info.LastSent >= (1f / FixedSendsPerSecond)) || NetworkManager.Singleton.NetworkTime - info.LastSent >= GetTimeForLerp(receiverPosition.Value, senderPosition.Value))
|
||||
{
|
||||
info.LastSent = NetworkManager.Singleton.NetworkTime;
|
||||
info.LastMissedPosition = null;
|
||||
info.LastMissedRotation = null;
|
||||
|
||||
ApplyTransformClientRpc(position, eulerAngles,
|
||||
new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = new[] { NetworkManager.Singleton.ConnectedClientsList[i].ClientId } } });
|
||||
}
|
||||
else
|
||||
{
|
||||
info.LastMissedPosition = position;
|
||||
info.LastMissedRotation = Quaternion.Euler(eulerAngles);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyTransformClientRpc(position, eulerAngles,
|
||||
new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = NetworkManager.Singleton.ConnectedClientsList.Where(c => c.ClientId != OwnerClientId).Select(c => c.ClientId).ToArray() } });
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckForMissedSends()
|
||||
{
|
||||
for (int i = 0; i < NetworkManager.Singleton.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
if (!m_ClientSendInfo.ContainsKey(NetworkManager.Singleton.ConnectedClientsList[i].ClientId))
|
||||
{
|
||||
m_ClientSendInfo.Add(NetworkManager.Singleton.ConnectedClientsList[i].ClientId, new ClientSendInfo()
|
||||
{
|
||||
LastMissedPosition = null,
|
||||
LastMissedRotation = null,
|
||||
LastSent = 0
|
||||
});
|
||||
}
|
||||
|
||||
ClientSendInfo info = m_ClientSendInfo[NetworkManager.Singleton.ConnectedClientsList[i].ClientId];
|
||||
Vector3? receiverPosition = NetworkManager.Singleton.ConnectedClientsList[i].PlayerObject == null ? null : new Vector3?(NetworkManager.Singleton.ConnectedClientsList[i].PlayerObject.transform.position);
|
||||
Vector3? senderPosition = NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject == null ? null : new Vector3?(NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject.transform.position);
|
||||
|
||||
if ((receiverPosition == null || senderPosition == null && NetworkManager.Singleton.NetworkTime - info.LastSent >= (1f / FixedSendsPerSecond)) || NetworkManager.Singleton.NetworkTime - info.LastSent >= GetTimeForLerp(receiverPosition.Value, senderPosition.Value))
|
||||
{
|
||||
/* why is this??? ->*/
|
||||
Vector3? pos = NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject == null ? null : new Vector3?(NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject.transform.position);
|
||||
/* why is this??? ->*/
|
||||
Vector3? rot = NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject == null ? null : new Vector3?(NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject.transform.rotation.eulerAngles);
|
||||
|
||||
if (info.LastMissedPosition != null && info.LastMissedRotation != null)
|
||||
{
|
||||
info.LastSent = NetworkManager.Singleton.NetworkTime;
|
||||
|
||||
ApplyTransformClientRpc(info.LastMissedPosition.Value, info.LastMissedRotation.Value.eulerAngles,
|
||||
new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = new[] { NetworkManager.Singleton.ConnectedClientsList[i].ClientId } } });
|
||||
|
||||
info.LastMissedPosition = null;
|
||||
info.LastMissedRotation = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports the transform to the given position and rotation
|
||||
/// </summary>
|
||||
/// <param name="position">The position to teleport to</param>
|
||||
/// <param name="rotation">The rotation to teleport to</param>
|
||||
public void Teleport(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
if (InterpolateServer && IsServer || IsClient)
|
||||
{
|
||||
m_LerpStartPos = position;
|
||||
m_LerpStartRot = rotation;
|
||||
m_LerpEndPos = position;
|
||||
m_LerpEndRot = rotation;
|
||||
m_LerpTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e96cb6065543e43c4a752faaa1468eb1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "Unity.Multiplayer.MLAPI.Prototyping",
|
||||
"rootNamespace": "MLAPI.Prototyping",
|
||||
"references": [
|
||||
"Unity.Multiplayer.MLAPI.Runtime"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": []
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3b8ed52f1b5c64994af4c4e0aa4b6c4b
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,83 @@
|
|||
[](https://mlapi.network/)
|
||||
|
||||
[](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/releases/latest)
|
||||
[](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/releases)
|
||||
|
||||
[](https://forum.unity.com/forums/multiplayer.26/)
|
||||
[](https://discord.gg/FM8SE9E)
|
||||
|
||||
|
||||
[](https://github.com/MidLevel/MLAPI/blob/master/LICENSE)
|
||||
[](https://docs-multiplayer.unity3d.com/)
|
||||
[](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction)
|
||||
|
||||
|
||||
The Unity MLAPI (Mid level API) is a framework that simplifies building networked games in Unity. It offers **low level** access to core networking while at the same time providing **high level** abstractions. The MLAPI aims to remove the repetitive tasks and reduces the network code dramatically, no matter how many of the **modular** features you use.
|
||||
|
||||
### Getting Started
|
||||
To get started, check the [Multiplayer Docs Site](https://docs-multiplayer.unity3d.com/).
|
||||
|
||||
### Community and Feedback
|
||||
For general questions, networking advice or discussions about MLAPI, please join our [Discord Community](https://discord.gg/FM8SE9E) or create a post in the [Unity Multiplayer Forum](https://forum.unity.com/forums/multiplayer.26/).
|
||||
|
||||
### Compatibility
|
||||
The MLAPI supports all major Unity platforms. To use the WebGL platform a custom WebGL transport based on web sockets is needed.
|
||||
|
||||
MLAPI is compatible with Unity 2019 and newer versions.
|
||||
|
||||
### Development
|
||||
We follow the [Gitflow Workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow). The master branch contains our latest stable release version while the develop branch tracks our current work.
|
||||
|
||||
This repository is broken into multiple components, each one implemented as a Unity Package.
|
||||
```
|
||||
.
|
||||
├── com.unity.multiplayer.mlapi # The core netcode SDK unity package (source + tests)
|
||||
└── testproject # A Unity project with various test implementations & scenes which exercise the features in the above package(s).
|
||||
```
|
||||
|
||||
### Contributing
|
||||
The MLAPI is an open-source project and we encourage and welcome
|
||||
contributions. If you wish to contribute, be sure to review our
|
||||
[contribution guidelines](CONTRIBUTING.md)
|
||||
|
||||
### Issues and missing features
|
||||
If you have an issue, bug or feature request, please follow the information in our [contribution guidelines](CONTRIBUTING.md) to submit an issue.
|
||||
|
||||
### Example
|
||||
Here is a sample MonoBehaviour showing a chat script where everyone can write and read from. This shows the basis of the MLAPI and the abstractions it adds.
|
||||
|
||||
```csharp
|
||||
public class Chat : NetworkBehaviour
|
||||
{
|
||||
private NetworkList<string> ChatMessages = new NetworkList<string>(new MLAPI.NetworkVariable.NetworkVariableSettings()
|
||||
{
|
||||
ReadPermission = MLAPI.NetworkVariable.NetworkVariablePermission.Everyone,
|
||||
WritePermission = MLAPI.NetworkVariable.NetworkVariablePermission.Everyone,
|
||||
SendTickrate = 5
|
||||
}, new List<string>());
|
||||
|
||||
private string textField = "";
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (IsClient)
|
||||
{
|
||||
textField = GUILayout.TextField(textField, GUILayout.Width(200));
|
||||
|
||||
if (GUILayout.Button("Send") && !string.IsNullOrWhiteSpace(textField))
|
||||
{
|
||||
ChatMessages.Add(textField);
|
||||
textField = "";
|
||||
}
|
||||
|
||||
for (int i = ChatMessages.Count - 1; i >= 0; i--)
|
||||
{
|
||||
GUILayout.Label(ChatMessages[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### License
|
||||
[MIT License](LICENSE)
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7a8193086d1cd7d4dadc6a324430350b
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c83905999e4e71246b5a3ebf2565faf7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,10 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
#if UNITY_2020_2_OR_NEWER && UNITY_EDITOR
|
||||
[assembly: InternalsVisibleTo("Unity.Multiplayer.MLAPI.Editor.CodeGen")]
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[assembly: InternalsVisibleTo("Unity.Multiplayer.MLAPI.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Multiplayer.MLAPI.RuntimeTests")]
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c9fd74adf4a0f6e479be3978543dc6a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f2ef964afcae91248b2298b479ed1b53
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
|
||||
namespace MLAPI.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue with a fixed size
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the queue</typeparam>
|
||||
public sealed class FixedQueue<T>
|
||||
{
|
||||
private readonly T[] m_Queue;
|
||||
private int m_QueueCount = 0;
|
||||
private int m_QueueStart;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of enqueued objects
|
||||
/// </summary>
|
||||
public int Count => m_QueueCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at a given virtual index
|
||||
/// </summary>
|
||||
/// <param name="index">The virtual index to get the item from</param>
|
||||
/// <returns>The element at the virtual index</returns>
|
||||
public T this[int index] => m_Queue[(m_QueueStart + index) % m_Queue.Length];
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new FixedQueue with a given size
|
||||
/// </summary>
|
||||
/// <param name="maxSize">The size of the queue</param>
|
||||
public FixedQueue(int maxSize)
|
||||
{
|
||||
m_Queue = new T[maxSize];
|
||||
m_QueueStart = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues an object
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
/// <returns></returns>
|
||||
public bool Enqueue(T t)
|
||||
{
|
||||
m_Queue[(m_QueueStart + m_QueueCount) % m_Queue.Length] = t;
|
||||
if (++m_QueueCount > m_Queue.Length)
|
||||
{
|
||||
--m_QueueCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dequeues an object
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public T Dequeue()
|
||||
{
|
||||
if (--m_QueueCount == -1) throw new IndexOutOfRangeException("Cannot dequeue empty queue!");
|
||||
T res = m_Queue[m_QueueStart];
|
||||
m_QueueStart = (m_QueueStart + 1) % m_Queue.Length;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at a given virtual index
|
||||
/// </summary>
|
||||
/// <param name="index">The virtual index to get the item from</param>
|
||||
/// <returns>The element at the virtual index</returns>
|
||||
public T ElementAt(int index) => m_Queue[(m_QueueStart + index) % m_Queue.Length];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a8514b4eca0c7044d9b92faf9407ec93
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d3cc7700dfd03ee4397858710461d179
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,25 @@
|
|||
namespace MLAPI.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the length of a var int encoded hash
|
||||
/// Note that the HashSize does not say anything about the actual final output due to the var int encoding
|
||||
/// It just says how many bytes the maximum will be
|
||||
/// </summary>
|
||||
public enum HashSize
|
||||
{
|
||||
/// <summary>
|
||||
/// Two byte hash
|
||||
/// </summary>
|
||||
VarIntTwoBytes,
|
||||
|
||||
/// <summary>
|
||||
/// Four byte hash
|
||||
/// </summary>
|
||||
VarIntFourBytes,
|
||||
|
||||
/// <summary>
|
||||
/// Eight byte hash
|
||||
/// </summary>
|
||||
VarIntEightBytes
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5ae94548754e0a0409da85c0e3235bb4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,354 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using MLAPI.Transports;
|
||||
using MLAPI.Hashing;
|
||||
using MLAPI.Serialization;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace MLAPI.Configuration
|
||||
{
|
||||
[Serializable]
|
||||
internal class NullableBoolSerializable
|
||||
{
|
||||
[SerializeField]
|
||||
public ulong Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The configuration object used to start server, client and hosts
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The protocol version. Different versions doesn't talk to each other.
|
||||
/// </summary>
|
||||
[Tooltip("Use this to make two builds incompatible with each other")]
|
||||
public ushort ProtocolVersion = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The transport hosts the sever uses
|
||||
/// </summary>
|
||||
[Tooltip("The NetworkTransport to use")]
|
||||
public NetworkTransport NetworkTransport = null;
|
||||
|
||||
/// <summary>
|
||||
/// A list of SceneNames that can be used during networked games.
|
||||
/// </summary>
|
||||
[Tooltip("The Scenes that can be switched to by the server")]
|
||||
public List<string> RegisteredScenes = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not runtime scene changes should be allowed and expected.
|
||||
/// If this is true, clients with different initial configurations will not work together.
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not runtime scene changes should be allowed and expected.\n " +
|
||||
"If this is true, clients with different initial configurations will not work together.")]
|
||||
public bool AllowRuntimeSceneChanges = false;
|
||||
|
||||
/// <summary>
|
||||
/// A list of spawnable prefabs
|
||||
/// </summary>
|
||||
[Tooltip("The prefabs that can be spawned across the network")]
|
||||
[FormerlySerializedAs("NetworkedPrefabs")]
|
||||
public List<NetworkPrefab> NetworkPrefabs = new List<NetworkPrefab>();
|
||||
|
||||
/// <summary>
|
||||
/// The default player prefab
|
||||
/// </summary>
|
||||
[SerializeReference]
|
||||
internal NullableBoolSerializable PlayerPrefabHash;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not a player object should be created by default. This value can be overriden on a case by case basis with ConnectionApproval.
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not a player object should be created by default. This value can be overriden on a case by case basis with ConnectionApproval.")]
|
||||
public bool CreatePlayerPrefab = true;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of times per second the receive queue is emptied and all messages inside are processed.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of times per second the receive queue is emptied from pending incoming messages")]
|
||||
public int ReceiveTickrate = 64;
|
||||
|
||||
/// <summary>
|
||||
/// Duration in seconds between network ticks.
|
||||
/// </summary>
|
||||
[Tooltip("Duration in seconds between network ticks")]
|
||||
public float NetworkTickIntervalSec = 0.050f;
|
||||
|
||||
/// <summary>
|
||||
/// The max amount of messages to process per ReceiveTickrate. This is to prevent flooding.
|
||||
/// </summary>
|
||||
[Tooltip("The maximum amount of Receive events to poll per Receive tick. This is to prevent flooding and freezing on the server")]
|
||||
public int MaxReceiveEventsPerTickRate = 500;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of times per second internal frame events will occur, e.g. send checking.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of times per second the internal event loop will run. This includes for example NetworkVariable checking.")]
|
||||
public int EventTickrate = 64;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of seconds to wait for handshake to complete before timing out a client
|
||||
/// </summary>
|
||||
[Tooltip("The amount of seconds to wait for the handshake to complete before the client times out")]
|
||||
public int ClientConnectionBufferTimeout = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to use connection approval
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not to force clients to be approved before they connect")]
|
||||
public bool ConnectionApproval = false;
|
||||
|
||||
/// <summary>
|
||||
/// The data to send during connection which can be used to decide on if a client should get accepted
|
||||
/// </summary>
|
||||
[Tooltip("The connection data sent along with connection requests")]
|
||||
public byte[] ConnectionData = new byte[0];
|
||||
|
||||
/// <summary>
|
||||
/// If your logic uses the NetworkTime, this should probably be turned off. If however it's needed to maximize accuracy, this is recommended to be turned on
|
||||
/// </summary>
|
||||
[Tooltip("Enable this to resync the NetworkTime after the initial sync")]
|
||||
public bool EnableTimeResync = false;
|
||||
|
||||
/// <summary>
|
||||
/// If time resync is turned on, this specifies the interval between syncs in seconds.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of seconds between resyncs of NetworkTime, if enabled")]
|
||||
public int TimeResyncInterval = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to enable the NetworkVariable system. This system runs in the Update loop and will degrade performance, but it can be a huge convenience.
|
||||
/// Only turn it off if you have no need for the NetworkVariable system.
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not to enable the NetworkVariable system")]
|
||||
public bool EnableNetworkVariable = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to ensure that NetworkVariables can be read even if a client accidentally writes where its not allowed to. This costs some CPU and bandwdith.
|
||||
/// </summary>
|
||||
[Tooltip("Ensures that NetworkVariables can be read even if a client accidental writes where its not allowed to. This will cost some CPU time and bandwidth")]
|
||||
public bool EnsureNetworkVariableLengthSafety = false;
|
||||
|
||||
/// <summary>
|
||||
/// Enables scene management. This will allow network scene switches and automatic scene diff corrections upon connect.
|
||||
/// SoftSynced scene objects wont work with this disabled. That means that disabling SceneManagement also enables PrefabSync.
|
||||
/// </summary>
|
||||
[Tooltip("Enables scene management. This will allow network scene switches and automatic scene diff corrections upon connect.\n" +
|
||||
"SoftSynced scene objects wont work with this disabled. That means that disabling SceneManagement also enables PrefabSync.")]
|
||||
public bool EnableSceneManagement = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the MLAPI should check for differences in the prefabs at connection.
|
||||
/// If you dynamically add prefabs at runtime, turn this OFF
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not the MLAPI should check for differences in the prefab lists at connection")]
|
||||
public bool ForceSamePrefabs = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, all NetworkObjects need to be prefabs and all scene objects will be replaced on server side which causes all serialization to be lost. Useful for multi project setups
|
||||
/// If false, Only non scene objects have to be prefabs. Scene objects will be matched using their PrefabInstanceId which can be precomputed globally for a scene at build time. Useful for single projects
|
||||
/// </summary>
|
||||
[Tooltip("If true, all NetworkObjects need to be prefabs and all scene objects will be replaced on server side which causes all serialization to be lost. Useful for multi project setups\n" +
|
||||
"If false, Only non scene objects have to be prefabs. Scene objects will be matched using their PrefabInstanceId which can be precomputed globally for a scene at build time. Useful for single projects")]
|
||||
public bool UsePrefabSync = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, NetworkIds will be reused after the NetworkIdRecycleDelay.
|
||||
/// </summary>
|
||||
[Tooltip("If true, NetworkIds will be reused after the NetworkIdRecycleDelay")]
|
||||
public bool RecycleNetworkIds = true;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of seconds a NetworkId has to be unused in order for it to be reused.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of seconds a NetworkId has to unused in order for it to be reused")]
|
||||
public float NetworkIdRecycleDelay = 120f;
|
||||
|
||||
/// <summary>
|
||||
/// Decides how many bytes to use for Rpc messaging. Leave this to 2 bytes unless you are facing hash collisions
|
||||
/// </summary>
|
||||
[Tooltip("The maximum amount of bytes to use for RPC messages. Leave this to 2 unless you are facing hash collisions")]
|
||||
public HashSize RpcHashSize = HashSize.VarIntTwoBytes;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of seconds to wait on all clients to load requested scene before the SwitchSceneProgress onComplete callback, that waits for all clients to complete loading, is called anyway.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of seconds to wait for all clients to load a requested scene")]
|
||||
public int LoadSceneTimeOut = 120;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not message buffering should be enabled. This will resolve most out of order messages during spawn.
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not message buffering should be enabled. This will resolve most out of order messages during spawn")]
|
||||
public bool EnableMessageBuffering = true;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped")]
|
||||
public float MessageBufferTimeout = 20f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to enable network logs.
|
||||
/// </summary>
|
||||
public bool EnableNetworkLogs = true;
|
||||
|
||||
private void Sort()
|
||||
{
|
||||
RegisteredScenes.Sort(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a base64 encoded version of the config
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string ToBase64()
|
||||
{
|
||||
NetworkConfig config = this;
|
||||
using (var buffer = PooledNetworkBuffer.Get())
|
||||
using (var writer = PooledNetworkWriter.Get(buffer))
|
||||
{
|
||||
writer.WriteUInt16Packed(config.ProtocolVersion);
|
||||
writer.WriteUInt16Packed((ushort)config.RegisteredScenes.Count);
|
||||
|
||||
for (int i = 0; i < config.RegisteredScenes.Count; i++)
|
||||
{
|
||||
writer.WriteString(config.RegisteredScenes[i]);
|
||||
}
|
||||
|
||||
writer.WriteInt32Packed(config.ReceiveTickrate);
|
||||
writer.WriteInt32Packed(config.MaxReceiveEventsPerTickRate);
|
||||
writer.WriteInt32Packed(config.EventTickrate);
|
||||
writer.WriteInt32Packed(config.ClientConnectionBufferTimeout);
|
||||
writer.WriteBool(config.ConnectionApproval);
|
||||
writer.WriteInt32Packed(config.LoadSceneTimeOut);
|
||||
writer.WriteBool(config.EnableTimeResync);
|
||||
writer.WriteBool(config.EnsureNetworkVariableLengthSafety);
|
||||
writer.WriteBits((byte)config.RpcHashSize, 2);
|
||||
writer.WriteBool(ForceSamePrefabs);
|
||||
writer.WriteBool(UsePrefabSync);
|
||||
writer.WriteBool(EnableSceneManagement);
|
||||
writer.WriteBool(RecycleNetworkIds);
|
||||
writer.WriteSinglePacked(NetworkIdRecycleDelay);
|
||||
writer.WriteBool(EnableNetworkVariable);
|
||||
writer.WriteBool(AllowRuntimeSceneChanges);
|
||||
writer.WriteBool(EnableNetworkLogs);
|
||||
buffer.PadBuffer();
|
||||
|
||||
return Convert.ToBase64String(buffer.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the NetworkConfig data with that from a base64 encoded version
|
||||
/// </summary>
|
||||
/// <param name="base64">The base64 encoded version</param>
|
||||
public void FromBase64(string base64)
|
||||
{
|
||||
NetworkConfig config = this;
|
||||
byte[] binary = Convert.FromBase64String(base64);
|
||||
using (var buffer = new NetworkBuffer(binary))
|
||||
using (var reader = PooledNetworkReader.Get(buffer))
|
||||
{
|
||||
config.ProtocolVersion = reader.ReadUInt16Packed();
|
||||
|
||||
ushort sceneCount = reader.ReadUInt16Packed();
|
||||
config.RegisteredScenes.Clear();
|
||||
|
||||
for (int i = 0; i < sceneCount; i++)
|
||||
{
|
||||
config.RegisteredScenes.Add(reader.ReadString().ToString());
|
||||
}
|
||||
|
||||
config.ReceiveTickrate = reader.ReadInt32Packed();
|
||||
config.MaxReceiveEventsPerTickRate = reader.ReadInt32Packed();
|
||||
config.EventTickrate = reader.ReadInt32Packed();
|
||||
config.ClientConnectionBufferTimeout = reader.ReadInt32Packed();
|
||||
config.ConnectionApproval = reader.ReadBool();
|
||||
config.LoadSceneTimeOut = reader.ReadInt32Packed();
|
||||
config.EnableTimeResync = reader.ReadBool();
|
||||
config.EnsureNetworkVariableLengthSafety = reader.ReadBool();
|
||||
config.RpcHashSize = (HashSize)reader.ReadBits(2);
|
||||
config.ForceSamePrefabs = reader.ReadBool();
|
||||
config.UsePrefabSync = reader.ReadBool();
|
||||
config.EnableSceneManagement = reader.ReadBool();
|
||||
config.RecycleNetworkIds = reader.ReadBool();
|
||||
config.NetworkIdRecycleDelay = reader.ReadSinglePacked();
|
||||
config.EnableNetworkVariable = reader.ReadBool();
|
||||
config.AllowRuntimeSceneChanges = reader.ReadBool();
|
||||
config.EnableNetworkLogs = reader.ReadBool();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ulong? m_ConfigHash = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a SHA256 hash of parts of the NetworkConfig instance
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
/// <returns></returns>
|
||||
public ulong GetConfig(bool cache = true)
|
||||
{
|
||||
if (m_ConfigHash != null && cache) return m_ConfigHash.Value;
|
||||
|
||||
Sort();
|
||||
|
||||
using (var buffer = PooledNetworkBuffer.Get())
|
||||
using (var writer = PooledNetworkWriter.Get(buffer))
|
||||
{
|
||||
writer.WriteUInt16Packed(ProtocolVersion);
|
||||
writer.WriteString(NetworkConstants.PROTOCOL_VERSION);
|
||||
|
||||
if (EnableSceneManagement && !AllowRuntimeSceneChanges)
|
||||
{
|
||||
for (int i = 0; i < RegisteredScenes.Count; i++)
|
||||
{
|
||||
writer.WriteString(RegisteredScenes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (ForceSamePrefabs)
|
||||
{
|
||||
var sortedPrefabList = NetworkPrefabs.OrderBy(x => x.Hash).ToList();
|
||||
for (int i = 0; i < sortedPrefabList.Count; i++)
|
||||
{
|
||||
writer.WriteUInt64Packed(sortedPrefabList[i].Hash);
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteBool(EnableNetworkVariable);
|
||||
writer.WriteBool(ForceSamePrefabs);
|
||||
writer.WriteBool(UsePrefabSync);
|
||||
writer.WriteBool(EnableSceneManagement);
|
||||
writer.WriteBool(EnsureNetworkVariableLengthSafety);
|
||||
writer.WriteBits((byte)RpcHashSize, 2);
|
||||
buffer.PadBuffer();
|
||||
|
||||
if (cache)
|
||||
{
|
||||
m_ConfigHash = buffer.ToArray().GetStableHash64();
|
||||
return m_ConfigHash.Value;
|
||||
}
|
||||
|
||||
return buffer.ToArray().GetStableHash64();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares a SHA256 hash with the current NetworkConfig instances hash
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <returns></returns>
|
||||
public bool CompareConfig(ulong hash)
|
||||
{
|
||||
return hash == GetConfig();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1b056e7faa4d1cb4aac7bc304c765c3b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,66 @@
|
|||
namespace MLAPI.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// A static class containing MLAPI constants
|
||||
/// </summary>
|
||||
internal static class NetworkConstants
|
||||
{
|
||||
internal const string PROTOCOL_VERSION = "13.0.0";
|
||||
|
||||
internal const byte CONNECTION_REQUEST = 3;
|
||||
internal const byte CONNECTION_APPROVED = 4;
|
||||
internal const byte ADD_OBJECT = 5;
|
||||
internal const byte DESTROY_OBJECT = 6;
|
||||
internal const byte SWITCH_SCENE = 7;
|
||||
internal const byte CLIENT_SWITCH_SCENE_COMPLETED = 8;
|
||||
internal const byte CHANGE_OWNER = 9;
|
||||
internal const byte ADD_OBJECTS = 10;
|
||||
internal const byte TIME_SYNC = 11;
|
||||
internal const byte NETWORK_VARIABLE_DELTA = 12;
|
||||
internal const byte NETWORK_VARIABLE_UPDATE = 13;
|
||||
internal const byte UNNAMED_MESSAGE = 20;
|
||||
internal const byte DESTROY_OBJECTS = 21;
|
||||
internal const byte NAMED_MESSAGE = 22;
|
||||
internal const byte SERVER_LOG = 23;
|
||||
internal const byte SERVER_RPC = 30;
|
||||
internal const byte CLIENT_RPC = 31;
|
||||
internal const byte INVALID = 32;
|
||||
|
||||
internal static readonly string[] MESSAGE_NAMES =
|
||||
{
|
||||
"", // 0
|
||||
"",
|
||||
"",
|
||||
"CONNECTION_REQUEST",
|
||||
"CONNECTION_APPROVED",
|
||||
"ADD_OBJECT",
|
||||
"DESTROY_OBJECT",
|
||||
"SWITCH_SCENE",
|
||||
"CLIENT_SWITCH_SCENE_COMPLETED",
|
||||
"CHANGE_OWNER",
|
||||
"ADD_OBJECTS",
|
||||
"TIME_SYNC",
|
||||
"NETWORK_VARIABLE_DELTA",
|
||||
"NETWORK_VARIABLE_UPDATE",
|
||||
"",
|
||||
"",
|
||||
"", // 16
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"UNNAMED_MESSAGE",
|
||||
"DESTROY_OBJECTS",
|
||||
"NAMED_MESSAGE",
|
||||
"SERVER_LOG",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"SERVER_RPC",
|
||||
"CLIENT_RPC",
|
||||
"INVALID" // 32
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dae82900f88f6bb4a90c29d431f2b45a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using MLAPI.Logging;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MLAPI.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// A class that represents a NetworkPrefab
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkPrefab
|
||||
{
|
||||
internal ulong Hash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Prefab == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} is not assigned");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
var networkObject = Prefab.GetComponent<NetworkObject>();
|
||||
if (networkObject == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {Prefab.name} does not have a {nameof(NetworkObject)}");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return networkObject.PrefabHash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The gameobject of the prefab
|
||||
/// </summary>
|
||||
public GameObject Prefab;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this is a playerPrefab
|
||||
/// </summary>
|
||||
public bool PlayerPrefab;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 10e0511afda4e7743b2cd7c9cf95e0ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c7155f1167eb96846aab2132b37c2815
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,25 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MLAPI.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// A NetworkClient
|
||||
/// </summary>
|
||||
public class NetworkClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The ClientId of the NetworkClient
|
||||
/// </summary>
|
||||
public ulong ClientId;
|
||||
|
||||
/// <summary>
|
||||
/// The PlayerObject of the Client
|
||||
/// </summary>
|
||||
public NetworkObject PlayerObject;
|
||||
|
||||
/// <summary>
|
||||
/// The NetworkObject's owned by this Client
|
||||
/// </summary>
|
||||
public readonly List<NetworkObject> OwnedObjects = new List<NetworkObject>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 23ca4d14911834b41a761c62fb23773e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,29 @@
|
|||
namespace MLAPI.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// A class representing a client that is currently in the process of connecting
|
||||
/// </summary>
|
||||
public class PendingClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The ClientId of the client
|
||||
/// </summary>
|
||||
public ulong ClientId;
|
||||
|
||||
/// <summary>
|
||||
/// The state of the connection process for the client
|
||||
/// </summary>
|
||||
public State ConnectionState;
|
||||
|
||||
/// <summary>
|
||||
/// The states of a connection
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
/// <summary>
|
||||
/// Client is in the process of doing the connection handshake
|
||||
/// </summary>
|
||||
PendingConnection
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2ffbc5b303de84a4196e24f503752218
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: eb92f4010a5924b408aca753b55bd5ae
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,956 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using UnityEngine;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using MLAPI.Configuration;
|
||||
using MLAPI.Logging;
|
||||
using MLAPI.Messaging;
|
||||
using MLAPI.NetworkVariable;
|
||||
using MLAPI.Profiling;
|
||||
using MLAPI.Reflection;
|
||||
using MLAPI.Serialization;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Spawning;
|
||||
using MLAPI.Transports;
|
||||
using Unity.Profiling;
|
||||
|
||||
namespace MLAPI
|
||||
{
|
||||
/// <summary>
|
||||
/// The base class to override to write network code. Inherits MonoBehaviour
|
||||
/// </summary>
|
||||
public abstract class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal enum __NExec
|
||||
#else
|
||||
[Obsolete("Please do not use, will no longer be exposed in the future versions (framework internal)")]
|
||||
public enum __NExec
|
||||
#endif
|
||||
{
|
||||
None = 0,
|
||||
Server = 1,
|
||||
Client = 2
|
||||
}
|
||||
|
||||
#pragma warning disable 414
|
||||
[NonSerialized]
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal __NExec __nexec = __NExec.None;
|
||||
#else
|
||||
[Obsolete("Please do not use, will no longer be exposed in the future versions (framework internal)")]
|
||||
public __NExec __nexec = __NExec.None;
|
||||
#endif
|
||||
#pragma warning restore 414
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal NetworkSerializer __beginSendServerRpc(ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
|
||||
#else
|
||||
[Obsolete("Please do not use, will no longer be exposed in the future versions (framework internal)")]
|
||||
public NetworkSerializer __beginSendServerRpc(ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
|
||||
#endif
|
||||
{
|
||||
PooledNetworkWriter writer;
|
||||
|
||||
var rpcQueueContainer = NetworkManager.Singleton.RpcQueueContainer;
|
||||
var isUsingBatching = rpcQueueContainer.IsUsingBatching();
|
||||
var transportChannel = rpcDelivery == RpcDelivery.Reliable ? NetworkChannel.ReliableRpc : NetworkChannel.UnreliableRpc;
|
||||
|
||||
if (IsHost)
|
||||
{
|
||||
writer = rpcQueueContainer.BeginAddQueueItemToFrame(RpcQueueContainer.QueueItemType.ServerRpc, Time.realtimeSinceStartup, transportChannel,
|
||||
NetworkManager.Singleton.ServerClientId, null, RpcQueueHistoryFrame.QueueFrameType.Inbound, serverRpcParams.Send.UpdateStage);
|
||||
|
||||
if (!isUsingBatching)
|
||||
{
|
||||
writer.WriteByte(NetworkConstants.SERVER_RPC); // MessageType
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer = rpcQueueContainer.BeginAddQueueItemToFrame(RpcQueueContainer.QueueItemType.ServerRpc, Time.realtimeSinceStartup, transportChannel,
|
||||
NetworkManager.Singleton.ServerClientId, null, RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
|
||||
if (!isUsingBatching)
|
||||
{
|
||||
writer.WriteByte(NetworkConstants.SERVER_RPC); // MessageType
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteUInt64Packed(NetworkObjectId); // NetworkObjectId
|
||||
writer.WriteUInt16Packed(NetworkBehaviourId); // NetworkBehaviourId
|
||||
writer.WriteByte((byte)serverRpcParams.Send.UpdateStage); // NetworkUpdateStage
|
||||
|
||||
return writer.Serializer;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal void __endSendServerRpc(NetworkSerializer serializer, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
|
||||
#else
|
||||
[Obsolete("Please do not use, will no longer be exposed in the future versions (framework internal)")]
|
||||
public void __endSendServerRpc(NetworkSerializer serializer, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
|
||||
#endif
|
||||
{
|
||||
if (serializer == null) return;
|
||||
|
||||
var rpcQueueContainer = NetworkManager.Singleton.RpcQueueContainer;
|
||||
if (IsHost)
|
||||
{
|
||||
rpcQueueContainer.EndAddQueueItemToFrame(serializer.Writer, RpcQueueHistoryFrame.QueueFrameType.Inbound, serverRpcParams.Send.UpdateStage);
|
||||
}
|
||||
else
|
||||
{
|
||||
rpcQueueContainer.EndAddQueueItemToFrame(serializer.Writer, RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal NetworkSerializer __beginSendClientRpc(ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
|
||||
#else
|
||||
[Obsolete("Please do not use, will no longer be exposed in the future versions (framework internal)")]
|
||||
public NetworkSerializer __beginSendClientRpc(ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
|
||||
#endif
|
||||
{
|
||||
PooledNetworkWriter writer;
|
||||
|
||||
// This will start a new queue item entry and will then return the writer to the current frame's stream
|
||||
var rpcQueueContainer = NetworkManager.Singleton.RpcQueueContainer;
|
||||
var isUsingBatching = rpcQueueContainer.IsUsingBatching();
|
||||
var transportChannel = rpcDelivery == RpcDelivery.Reliable ? NetworkChannel.ReliableRpc : NetworkChannel.UnreliableRpc;
|
||||
|
||||
ulong[] ClientIds = clientRpcParams.Send.TargetClientIds ?? NetworkManager.Singleton.ConnectedClientsList.Select(c => c.ClientId).ToArray();
|
||||
if (clientRpcParams.Send.TargetClientIds != null && clientRpcParams.Send.TargetClientIds.Length == 0)
|
||||
{
|
||||
ClientIds = NetworkManager.Singleton.ConnectedClientsList.Select(c => c.ClientId).ToArray();
|
||||
}
|
||||
|
||||
//NOTES ON BELOW CHANGES:
|
||||
//The following checks for IsHost and whether the host client id is part of the clients to recieve the RPC
|
||||
//Is part of a patch-fix to handle looping back RPCs into the next frame's inbound queue.
|
||||
//!!! This code is temporary and will change (soon) when NetworkSerializer can be configured for mutliple NetworkWriters!!!
|
||||
var ContainsServerClientId = ClientIds.Contains(NetworkManager.Singleton.ServerClientId);
|
||||
if (IsHost && ContainsServerClientId)
|
||||
{
|
||||
//Always write to the next frame's inbound queue
|
||||
writer = rpcQueueContainer.BeginAddQueueItemToFrame(RpcQueueContainer.QueueItemType.ClientRpc, Time.realtimeSinceStartup, transportChannel,
|
||||
NetworkManager.Singleton.ServerClientId, null, RpcQueueHistoryFrame.QueueFrameType.Inbound, clientRpcParams.Send.UpdateStage);
|
||||
|
||||
//Handle sending to the other clients, if so the above notes explain why this code is here (a temporary patch-fix)
|
||||
if (ClientIds.Length > 1)
|
||||
{
|
||||
//Set the loopback frame
|
||||
rpcQueueContainer.SetLoopBackFrameItem(clientRpcParams.Send.UpdateStage);
|
||||
|
||||
//Switch to the outbound queue
|
||||
writer = rpcQueueContainer.BeginAddQueueItemToFrame(RpcQueueContainer.QueueItemType.ClientRpc, Time.realtimeSinceStartup, transportChannel, NetworkObjectId,
|
||||
ClientIds, RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
|
||||
|
||||
if (!isUsingBatching)
|
||||
{
|
||||
writer.WriteByte(NetworkConstants.CLIENT_RPC); // MessageType
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isUsingBatching)
|
||||
{
|
||||
writer.WriteByte(NetworkConstants.CLIENT_RPC); // MessageType
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer = rpcQueueContainer.BeginAddQueueItemToFrame(RpcQueueContainer.QueueItemType.ClientRpc, Time.realtimeSinceStartup, transportChannel, NetworkObjectId,
|
||||
ClientIds, RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
|
||||
|
||||
if (!isUsingBatching)
|
||||
{
|
||||
writer.WriteByte(NetworkConstants.CLIENT_RPC); // MessageType
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteUInt64Packed(NetworkObjectId); // NetworkObjectId
|
||||
writer.WriteUInt16Packed(NetworkBehaviourId); // NetworkBehaviourId
|
||||
writer.WriteByte((byte)clientRpcParams.Send.UpdateStage); // NetworkUpdateStage
|
||||
|
||||
return writer.Serializer;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal void __endSendClientRpc(NetworkSerializer serializer, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
|
||||
#else
|
||||
[Obsolete("Please do not use, will no longer be exposed in the future versions (framework internal)")]
|
||||
public void __endSendClientRpc(NetworkSerializer serializer, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
|
||||
#endif
|
||||
{
|
||||
if (serializer == null) return;
|
||||
|
||||
var rpcQueueContainer = NetworkManager.Singleton.RpcQueueContainer;
|
||||
|
||||
if (IsHost)
|
||||
{
|
||||
ulong[] ClientIds = clientRpcParams.Send.TargetClientIds ?? NetworkManager.Singleton.ConnectedClientsList.Select(c => c.ClientId).ToArray();
|
||||
if (clientRpcParams.Send.TargetClientIds != null && clientRpcParams.Send.TargetClientIds.Length == 0)
|
||||
{
|
||||
ClientIds = NetworkManager.Singleton.ConnectedClientsList.Select(c => c.ClientId).ToArray();
|
||||
}
|
||||
|
||||
var ContainsServerClientId = ClientIds.Contains(NetworkManager.Singleton.ServerClientId);
|
||||
if (ContainsServerClientId && ClientIds.Length == 1)
|
||||
{
|
||||
rpcQueueContainer.EndAddQueueItemToFrame(serializer.Writer, RpcQueueHistoryFrame.QueueFrameType.Inbound, clientRpcParams.Send.UpdateStage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rpcQueueContainer.EndAddQueueItemToFrame(serializer.Writer, RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkManager that owns this NetworkBehaviour instance
|
||||
/// </summary>
|
||||
public NetworkManager NetworkManager => NetworkObject.NetworkManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is the the personal clients player object
|
||||
/// </summary>
|
||||
public bool IsLocalPlayer => NetworkObject.IsLocalPlayer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is owned by the local player or if the object is the local player object
|
||||
/// </summary>
|
||||
public bool IsOwner => NetworkObject.IsOwner;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as server
|
||||
/// </summary>
|
||||
protected static bool IsServer => IsRunning && NetworkManager.Singleton.IsServer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as client
|
||||
/// </summary>
|
||||
protected bool IsClient => IsRunning && NetworkManager.Singleton.IsClient;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as Host, I.E Server and Client
|
||||
/// </summary>
|
||||
protected bool IsHost => IsRunning && NetworkManager.Singleton.IsHost;
|
||||
|
||||
private static bool IsRunning => NetworkManager.Singleton != null && NetworkManager.Singleton.IsListening;
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not the object has a owner
|
||||
/// </summary>
|
||||
public bool IsOwnedByServer => NetworkObject.IsOwnedByServer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkObject that owns this NetworkBehaviour instance
|
||||
/// </summary>
|
||||
public NetworkObject NetworkObject
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_NetworkObject == null)
|
||||
{
|
||||
m_NetworkObject = GetComponentInParent<NetworkObject>();
|
||||
}
|
||||
|
||||
if (m_NetworkObject == null)
|
||||
{
|
||||
throw new NullReferenceException($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
|
||||
}
|
||||
|
||||
return m_NetworkObject;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not this NetworkBehaviour instance has a NetworkObject owner.
|
||||
/// </summary>
|
||||
public bool HasNetworkObject
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_NetworkObject == null)
|
||||
{
|
||||
m_NetworkObject = GetComponentInParent<NetworkObject>();
|
||||
}
|
||||
|
||||
return m_NetworkObject != null;
|
||||
}
|
||||
}
|
||||
|
||||
private NetworkObject m_NetworkObject = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkId of the NetworkObject that owns this NetworkBehaviour
|
||||
/// </summary>
|
||||
public ulong NetworkObjectId => NetworkObject.NetworkObjectId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets NetworkId for this NetworkBehaviour from the owner NetworkObject
|
||||
/// </summary>
|
||||
public ushort NetworkBehaviourId => NetworkObject.GetNetworkBehaviourOrderIndex(this);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a the NetworkBehaviour with a given BehaviourId for the current NetworkObject
|
||||
/// </summary>
|
||||
/// <param name="behaviourId">The behaviourId to return</param>
|
||||
/// <returns>Returns NetworkBehaviour with given behaviourId</returns>
|
||||
protected NetworkBehaviour GetNetworkBehaviour(ushort behaviourId)
|
||||
{
|
||||
return NetworkObject.GetNetworkBehaviourAtOrderIndex(behaviourId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ClientId that owns the NetworkObject
|
||||
/// </summary>
|
||||
public ulong OwnerClientId => NetworkObject.OwnerClientId;
|
||||
|
||||
internal bool NetworkStartInvoked = false;
|
||||
internal bool InternalNetworkStartInvoked = false;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the network tick at the NetworkBehaviourUpdate time
|
||||
/// This allows sending NetworkVariables not more often than once per network tick, regardless of the update rate
|
||||
/// </summary>
|
||||
public static ushort CurrentTick { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when message handlers are ready to be registered and the network is setup
|
||||
/// </summary>
|
||||
public virtual void NetworkStart() { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when message handlers are ready to be registered and the network is setup. Provides a Payload if it was provided
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream containing the spawn payload</param>
|
||||
public virtual void NetworkStart(Stream stream)
|
||||
{
|
||||
NetworkStart();
|
||||
}
|
||||
|
||||
internal void InternalNetworkStart()
|
||||
{
|
||||
InitializeVariables();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the local client gains ownership of this object
|
||||
/// </summary>
|
||||
public virtual void OnGainedOwnership() { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when we loose ownership of this object
|
||||
/// </summary>
|
||||
public virtual void OnLostOwnership() { }
|
||||
|
||||
#region NetworkVariable
|
||||
|
||||
private bool m_VarInit = false;
|
||||
|
||||
private readonly List<HashSet<int>> m_ChannelMappedNetworkVariableIndexes = new List<HashSet<int>>();
|
||||
private readonly List<NetworkChannel> m_ChannelsForNetworkVariableGroups = new List<NetworkChannel>();
|
||||
internal readonly List<INetworkVariable> NetworkVariableFields = new List<INetworkVariable>();
|
||||
|
||||
private static HashSet<NetworkObject> s_Touched = new HashSet<NetworkObject>();
|
||||
private static Dictionary<Type, FieldInfo[]> s_FieldTypes = new Dictionary<Type, FieldInfo[]>();
|
||||
|
||||
private static FieldInfo[] GetFieldInfoForType(Type type)
|
||||
{
|
||||
if (!s_FieldTypes.ContainsKey(type))
|
||||
{
|
||||
s_FieldTypes.Add(type, GetFieldInfoForTypeRecursive(type));
|
||||
}
|
||||
|
||||
return s_FieldTypes[type];
|
||||
}
|
||||
|
||||
private static FieldInfo[] GetFieldInfoForTypeRecursive(Type type, List<FieldInfo> list = null)
|
||||
{
|
||||
if (list == null)
|
||||
{
|
||||
list = new List<FieldInfo>();
|
||||
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
}
|
||||
else
|
||||
{
|
||||
list.AddRange(type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
}
|
||||
|
||||
if (type.BaseType != null && type.BaseType != typeof(NetworkBehaviour))
|
||||
{
|
||||
return GetFieldInfoForTypeRecursive(type.BaseType, list);
|
||||
}
|
||||
|
||||
return list.OrderBy(x => x.Name, StringComparer.Ordinal).ToArray();
|
||||
}
|
||||
|
||||
internal void InitializeVariables()
|
||||
{
|
||||
if (m_VarInit) return;
|
||||
m_VarInit = true;
|
||||
|
||||
FieldInfo[] sortedFields = GetFieldInfoForType(GetType());
|
||||
|
||||
for (int i = 0; i < sortedFields.Length; i++)
|
||||
{
|
||||
Type fieldType = sortedFields[i].FieldType;
|
||||
|
||||
if (fieldType.HasInterface(typeof(INetworkVariable)))
|
||||
{
|
||||
INetworkVariable instance = (INetworkVariable)sortedFields[i].GetValue(this);
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
instance = (INetworkVariable)Activator.CreateInstance(fieldType, true);
|
||||
sortedFields[i].SetValue(this, instance);
|
||||
}
|
||||
|
||||
instance.SetNetworkBehaviour(this);
|
||||
NetworkVariableFields.Add(instance);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Create index map for channels
|
||||
var firstLevelIndex = new Dictionary<NetworkChannel, int>();
|
||||
int secondLevelCounter = 0;
|
||||
|
||||
for (int i = 0; i < NetworkVariableFields.Count; i++)
|
||||
{
|
||||
NetworkChannel networkChannel = NetworkVariableFields[i].GetChannel();
|
||||
|
||||
if (!firstLevelIndex.ContainsKey(networkChannel))
|
||||
{
|
||||
firstLevelIndex.Add(networkChannel, secondLevelCounter);
|
||||
m_ChannelsForNetworkVariableGroups.Add(networkChannel);
|
||||
secondLevelCounter++;
|
||||
}
|
||||
|
||||
if (firstLevelIndex[networkChannel] >= m_ChannelMappedNetworkVariableIndexes.Count)
|
||||
{
|
||||
m_ChannelMappedNetworkVariableIndexes.Add(new HashSet<int>());
|
||||
}
|
||||
|
||||
m_ChannelMappedNetworkVariableIndexes[firstLevelIndex[networkChannel]].Add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
private static ProfilerMarker s_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}");
|
||||
#endif
|
||||
|
||||
internal static void NetworkBehaviourUpdate()
|
||||
{
|
||||
// Do not execute NetworkBehaviourUpdate more than once per network tick
|
||||
ushort tick = NetworkManager.Singleton.NetworkTickSystem.GetTick();
|
||||
if (tick == CurrentTick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentTick = tick;
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_NetworkBehaviourUpdate.Begin();
|
||||
#endif
|
||||
try
|
||||
{
|
||||
if (IsServer)
|
||||
{
|
||||
s_Touched.Clear();
|
||||
for (int i = 0; i < NetworkManager.Singleton.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
var client = NetworkManager.Singleton.ConnectedClientsList[i];
|
||||
var spawnedObjs = NetworkSpawnManager.SpawnedObjectsList;
|
||||
s_Touched.UnionWith(spawnedObjs);
|
||||
foreach (var sobj in spawnedObjs)
|
||||
{
|
||||
// Sync just the variables for just the objects this client sees
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now, reset all the no-longer-dirty variables
|
||||
foreach (var sobj in s_Touched)
|
||||
{
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// when client updates the sever, it tells it about all its objects
|
||||
foreach (var sobj in NetworkSpawnManager.SpawnedObjectsList)
|
||||
{
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].VariableUpdate(NetworkManager.Singleton.ServerClientId);
|
||||
}
|
||||
}
|
||||
|
||||
// Now, reset all the no-longer-dirty variables
|
||||
foreach (var sobj in NetworkSpawnManager.SpawnedObjectsList)
|
||||
{
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_NetworkBehaviourUpdate.End();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal void PreNetworkVariableWrite()
|
||||
{
|
||||
// reset our "which variables got written" data
|
||||
m_NetworkVariableIndexesToReset.Clear();
|
||||
m_NetworkVariableIndexesToResetSet.Clear();
|
||||
}
|
||||
|
||||
internal void PostNetworkVariableWrite()
|
||||
{
|
||||
// mark any variables we wrote as no longer dirty
|
||||
for (int i = 0; i < m_NetworkVariableIndexesToReset.Count; i++)
|
||||
{
|
||||
NetworkVariableFields[m_NetworkVariableIndexesToReset[i]].ResetDirty();
|
||||
}
|
||||
}
|
||||
|
||||
internal void VariableUpdate(ulong clientId)
|
||||
{
|
||||
if (!m_VarInit) InitializeVariables();
|
||||
|
||||
PreNetworkVariableWrite();
|
||||
NetworkVariableUpdate(clientId);
|
||||
}
|
||||
|
||||
private readonly List<int> m_NetworkVariableIndexesToReset = new List<int>();
|
||||
private readonly HashSet<int> m_NetworkVariableIndexesToResetSet = new HashSet<int>();
|
||||
|
||||
private void NetworkVariableUpdate(ulong clientId)
|
||||
{
|
||||
if (!CouldHaveDirtyNetworkVariables()) return;
|
||||
|
||||
for (int j = 0; j < m_ChannelMappedNetworkVariableIndexes.Count; j++)
|
||||
{
|
||||
using (var buffer = PooledNetworkBuffer.Get())
|
||||
{
|
||||
using (var writer = PooledNetworkWriter.Get(buffer))
|
||||
{
|
||||
writer.WriteUInt64Packed(NetworkObjectId);
|
||||
writer.WriteUInt16Packed(NetworkObject.GetNetworkBehaviourOrderIndex(this));
|
||||
|
||||
// Write the current tick frame
|
||||
// todo: this is currently done per channel, per tick. The snapshot system might improve on this
|
||||
writer.WriteUInt16Packed(CurrentTick);
|
||||
|
||||
bool writtenAny = false;
|
||||
for (int k = 0; k < NetworkVariableFields.Count; k++)
|
||||
{
|
||||
if (!m_ChannelMappedNetworkVariableIndexes[j].Contains(k))
|
||||
{
|
||||
// This var does not belong to the currently iterating channel group.
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
writer.WriteUInt16Packed(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteBool(false);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isDirty = NetworkVariableFields[k].IsDirty(); // cache this here. You never know what operations users will do in the dirty methods
|
||||
|
||||
// if I'm dirty AND a client, write (server always has all permissions)
|
||||
// if I'm dirty AND the server AND the client can read me, send.
|
||||
bool shouldWrite = isDirty && (!IsServer || NetworkVariableFields[k].CanClientRead(clientId));
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (!shouldWrite)
|
||||
{
|
||||
writer.WriteUInt16Packed(0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteBool(shouldWrite);
|
||||
}
|
||||
|
||||
if (shouldWrite)
|
||||
{
|
||||
writtenAny = true;
|
||||
|
||||
// write the network tick at which this NetworkVariable was modified remotely
|
||||
// this will allow lag-compensation
|
||||
writer.WriteUInt16Packed(NetworkVariableFields[k].RemoteTick);
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
using (var varBuffer = PooledNetworkBuffer.Get())
|
||||
{
|
||||
NetworkVariableFields[k].WriteDelta(varBuffer);
|
||||
varBuffer.PadBuffer();
|
||||
|
||||
writer.WriteUInt16Packed((ushort)varBuffer.Length);
|
||||
buffer.CopyFrom(varBuffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkVariableFields[k].WriteDelta(buffer);
|
||||
}
|
||||
|
||||
if (!m_NetworkVariableIndexesToResetSet.Contains(k))
|
||||
{
|
||||
m_NetworkVariableIndexesToResetSet.Add(k);
|
||||
m_NetworkVariableIndexesToReset.Add(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (writtenAny)
|
||||
{
|
||||
InternalMessageSender.Send(clientId, NetworkConstants.NETWORK_VARIABLE_DELTA, m_ChannelsForNetworkVariableGroups[j], buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CouldHaveDirtyNetworkVariables()
|
||||
{
|
||||
// TODO: There should be a better way by reading one dirty variable vs. 'n'
|
||||
for (int i = 0; i < NetworkVariableFields.Count; i++)
|
||||
{
|
||||
if (NetworkVariableFields[i].IsDirty()) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static void HandleNetworkVariableDeltas(List<INetworkVariable> networkVariableList, Stream stream, ulong clientId, NetworkBehaviour logInstance)
|
||||
{
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
// read the remote network tick at which this variable was written.
|
||||
ushort remoteTick = reader.ReadUInt16Packed();
|
||||
|
||||
for (int i = 0; i < networkVariableList.Count; i++)
|
||||
{
|
||||
ushort varSize = 0;
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
varSize = reader.ReadUInt16Packed();
|
||||
|
||||
if (varSize == 0) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!reader.ReadBool()) continue;
|
||||
}
|
||||
|
||||
if (IsServer && !networkVariableList[i].CanClientWrite(clientId))
|
||||
{
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Client wrote to {nameof(NetworkVariable)} without permission. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
NetworkLog.LogError($"[{networkVariableList[i].GetType().Name}]");
|
||||
}
|
||||
|
||||
stream.Position += varSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
//This client wrote somewhere they are not allowed. This is critical
|
||||
//We can't just skip this field. Because we don't actually know how to dummy read
|
||||
//That is, we don't know how many bytes to skip. Because the interface doesn't have a
|
||||
//Read that gives us the value. Only a Read that applies the value straight away
|
||||
//A dummy read COULD be added to the interface for this situation, but it's just being too nice.
|
||||
//This is after all a developer fault. A critical error should be fine.
|
||||
// - TwoTen
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogError($"Client wrote to {nameof(NetworkVariable)} without permission. No more variables can be read. This is critical. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
NetworkLog.LogError($"[{networkVariableList[i].GetType().Name}]");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// read the local network tick at which this variable was written.
|
||||
// if this var was updated from our machine, this local tick will be locally valid
|
||||
ushort localTick = reader.ReadUInt16Packed();
|
||||
|
||||
long readStartPos = stream.Position;
|
||||
|
||||
networkVariableList[i].ReadDelta(stream, IsServer, localTick, remoteTick);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.NetworkVarDeltas);
|
||||
|
||||
ProfilerStatManager.NetworkVarsRcvd.Record();
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
(stream as NetworkBuffer).SkipPadBits();
|
||||
|
||||
if (stream.Position > (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var delta read too far. {stream.Position - (readStartPos + varSize)} bytes. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
}
|
||||
|
||||
stream.Position = readStartPos + varSize;
|
||||
}
|
||||
else if (stream.Position < (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var delta read too little. {(readStartPos + varSize) - stream.Position} bytes. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
}
|
||||
|
||||
stream.Position = readStartPos + varSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void HandleNetworkVariableUpdate(List<INetworkVariable> networkVariableList, Stream stream, ulong clientId, NetworkBehaviour logInstance)
|
||||
{
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
for (int i = 0; i < networkVariableList.Count; i++)
|
||||
{
|
||||
ushort varSize = 0;
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
varSize = reader.ReadUInt16Packed();
|
||||
|
||||
if (varSize == 0) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!reader.ReadBool()) continue;
|
||||
}
|
||||
|
||||
if (IsServer && !networkVariableList[i].CanClientWrite(clientId))
|
||||
{
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Client wrote to {nameof(NetworkVariable)} without permission. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
}
|
||||
|
||||
stream.Position += varSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
//This client wrote somewhere they are not allowed. This is critical
|
||||
//We can't just skip this field. Because we don't actually know how to dummy read
|
||||
//That is, we don't know how many bytes to skip. Because the interface doesn't have a
|
||||
//Read that gives us the value. Only a Read that applies the value straight away
|
||||
//A dummy read COULD be added to the interface for this situation, but it's just being too nice.
|
||||
//This is after all a developer fault. A critical error should be fine.
|
||||
// - TwoTen
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogError($"Client wrote to {nameof(NetworkVariable)} without permission. No more variables can be read. This is critical. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
long readStartPos = stream.Position;
|
||||
|
||||
networkVariableList[i].ReadField(stream, NetworkTickSystem.NoTick, NetworkTickSystem.NoTick);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.NetworkVarUpdates);
|
||||
|
||||
ProfilerStatManager.NetworkVarsRcvd.Record();
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (stream is NetworkBuffer networkBuffer)
|
||||
{
|
||||
networkBuffer.SkipPadBits();
|
||||
}
|
||||
|
||||
if (stream.Position > (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var update read too far. {stream.Position - (readStartPos + varSize)} bytes. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
}
|
||||
|
||||
stream.Position = readStartPos + varSize;
|
||||
}
|
||||
else if (stream.Position < (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var update read too little. {(readStartPos + varSize) - stream.Position} bytes. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
}
|
||||
|
||||
stream.Position = readStartPos + varSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal static void WriteNetworkVariableData(List<INetworkVariable> networkVariableList, Stream stream, ulong clientId)
|
||||
{
|
||||
if (networkVariableList.Count == 0) return;
|
||||
|
||||
using (var writer = PooledNetworkWriter.Get(stream))
|
||||
{
|
||||
for (int j = 0; j < networkVariableList.Count; j++)
|
||||
{
|
||||
bool canClientRead = networkVariableList[j].CanClientRead(clientId);
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (!canClientRead)
|
||||
{
|
||||
writer.WriteUInt16Packed(0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteBool(canClientRead);
|
||||
}
|
||||
|
||||
if (canClientRead)
|
||||
{
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
using (var varBuffer = PooledNetworkBuffer.Get())
|
||||
{
|
||||
networkVariableList[j].WriteField(varBuffer);
|
||||
varBuffer.PadBuffer();
|
||||
|
||||
writer.WriteUInt16Packed((ushort)varBuffer.Length);
|
||||
varBuffer.CopyTo(stream);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
networkVariableList[j].WriteField(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SetNetworkVariableData(List<INetworkVariable> networkVariableList, Stream stream)
|
||||
{
|
||||
if (networkVariableList.Count == 0) return;
|
||||
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
for (int j = 0; j < networkVariableList.Count; j++)
|
||||
{
|
||||
ushort varSize = 0;
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
varSize = reader.ReadUInt16Packed();
|
||||
|
||||
if (varSize == 0) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!reader.ReadBool()) continue;
|
||||
}
|
||||
|
||||
long readStartPos = stream.Position;
|
||||
|
||||
networkVariableList[j].ReadField(stream, NetworkTickSystem.NoTick, NetworkTickSystem.NoTick);
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (stream is NetworkBuffer networkBuffer)
|
||||
{
|
||||
networkBuffer.SkipPadBits();
|
||||
}
|
||||
|
||||
if (stream.Position > (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var data read too far. {stream.Position - (readStartPos + varSize)} bytes.");
|
||||
}
|
||||
|
||||
stream.Position = readStartPos + varSize;
|
||||
}
|
||||
else if (stream.Position < (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var data read too little. {(readStartPos + varSize) - stream.Position} bytes.");
|
||||
}
|
||||
|
||||
stream.Position = readStartPos + varSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local instance of a object with a given NetworkId
|
||||
/// </summary>
|
||||
/// <param name="networkId"></param>
|
||||
/// <returns></returns>
|
||||
protected NetworkObject GetNetworkObject(ulong networkId) => NetworkSpawnManager.SpawnedObjects.ContainsKey(networkId) ? NetworkSpawnManager.SpawnedObjects[networkId] : null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c8ea6ec00590bd44a983c228bcaee727
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 593a2fe42fa9d37498c96f9a383b6521
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,571 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using MLAPI.Configuration;
|
||||
using MLAPI.Exceptions;
|
||||
using MLAPI.Hashing;
|
||||
using MLAPI.Logging;
|
||||
using MLAPI.Messaging;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Spawning;
|
||||
using MLAPI.Transports;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MLAPI
|
||||
{
|
||||
/// <summary>
|
||||
/// A component used to identify that a GameObject in the network
|
||||
/// </summary>
|
||||
[AddComponentMenu("MLAPI/NetworkObject", -99)]
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class NetworkObject : MonoBehaviour
|
||||
{
|
||||
private void OnValidate()
|
||||
{
|
||||
// Set this so the hash can be serialized on Scene objects. For prefabs, they are generated at runtime.
|
||||
ValidateHash();
|
||||
}
|
||||
|
||||
internal void ValidateHash()
|
||||
{
|
||||
if (string.IsNullOrEmpty(PrefabHashGenerator))
|
||||
{
|
||||
PrefabHashGenerator = gameObject.name;
|
||||
}
|
||||
|
||||
PrefabHash = PrefabHashGenerator.GetStableHash64();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkManager that owns this NetworkObject instance
|
||||
/// </summary>
|
||||
public NetworkManager NetworkManager => NetworkManager.Singleton;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique Id of this object that is synced across the network
|
||||
/// </summary>
|
||||
public ulong NetworkObjectId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ClientId of the owner of this NetworkObject
|
||||
/// </summary>
|
||||
public ulong OwnerClientId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OwnerClientIdInternal == null)
|
||||
{
|
||||
return NetworkManager.Singleton != null ? NetworkManager.Singleton.ServerClientId : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return OwnerClientIdInternal.Value;
|
||||
}
|
||||
}
|
||||
internal set
|
||||
{
|
||||
if (NetworkManager.Singleton != null && value == NetworkManager.Singleton.ServerClientId)
|
||||
{
|
||||
OwnerClientIdInternal = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
OwnerClientIdInternal = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal ulong? OwnerClientIdInternal = null;
|
||||
|
||||
/// <summary>
|
||||
/// InstanceId is the id that is unique to the object and scene for a scene object when UsePrefabSync is false.
|
||||
/// If UsePrefabSync is true or if it's used on non scene objects, this has no effect.
|
||||
/// Should not be set manually
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
[SerializeField]
|
||||
public ulong NetworkInstanceId;
|
||||
|
||||
/// <summary>
|
||||
/// The Prefab unique hash. This should not be set my the user but rather changed by editing the PrefabHashGenerator.
|
||||
/// It has to be the same for all instances of a prefab
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
[SerializeField]
|
||||
public ulong PrefabHash;
|
||||
|
||||
/// <summary>
|
||||
/// The generator used to change the PrefabHash. This should be set the same for all instances of a prefab.
|
||||
/// It has to be unique in relation to other prefabs
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public string PrefabHashGenerator;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the object will always be replicated as root on clients and the parent will be ignored.
|
||||
/// </summary>
|
||||
public bool AlwaysReplicateAsRoot;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if this object is a player object
|
||||
/// </summary>
|
||||
public bool IsPlayerObject { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is the the personal clients player object
|
||||
/// </summary>
|
||||
public bool IsLocalPlayer => NetworkManager.Singleton != null && IsPlayerObject && OwnerClientId == NetworkManager.Singleton.LocalClientId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is owned by the local player or if the object is the local player object
|
||||
/// </summary>
|
||||
public bool IsOwner => NetworkManager.Singleton != null && OwnerClientId == NetworkManager.Singleton.LocalClientId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not the object is owned by anyone
|
||||
/// </summary>
|
||||
public bool IsOwnedByServer => NetworkManager.Singleton != null && OwnerClientId == NetworkManager.Singleton.ServerClientId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object has yet been spawned across the network
|
||||
/// </summary>
|
||||
public bool IsSpawned { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is a SceneObject, null if it's not yet spawned but is a scene object.
|
||||
/// </summary>
|
||||
public bool? IsSceneObject { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not the object should be automatically removed when the scene is unloaded.
|
||||
/// </summary>
|
||||
public bool DestroyWithScene { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for checking visibility
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId to check visibility for</param>
|
||||
public delegate bool VisibilityDelegate(ulong clientId);
|
||||
|
||||
/// <summary>
|
||||
/// Delegate invoked when the MLAPI needs to know if the object should be visible to a client, if null it will assume true
|
||||
/// </summary>
|
||||
public VisibilityDelegate CheckObjectVisibility = null;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for checking spawn options
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId to check spawn options for</param>
|
||||
public delegate bool SpawnDelegate(ulong clientId);
|
||||
|
||||
/// <summary>
|
||||
/// Delegate invoked when the MLAPI needs to know if it should include the transform when spawning the object, if null it will assume true
|
||||
/// </summary>
|
||||
public SpawnDelegate IncludeTransformWhenSpawning = null;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to destroy this object if it's owner is destroyed.
|
||||
/// If false, the objects ownership will be given to the server.
|
||||
/// </summary>
|
||||
public bool DontDestroyWithOwner;
|
||||
|
||||
internal readonly HashSet<ulong> m_Observers = new HashSet<ulong>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns Observers enumerator
|
||||
/// </summary>
|
||||
/// <returns>Observers enumerator</returns>
|
||||
public HashSet<ulong>.Enumerator GetObservers()
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
return m_Observers.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this object is visible to a specific client
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId of the client</param>
|
||||
/// <returns>True if the client knows about the object</returns>
|
||||
public bool IsNetworkVisibleTo(ulong clientId)
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
return m_Observers.Contains(clientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a previously hidden object to a client
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client to show the object to</param>
|
||||
/// <param name="payload">An optional payload to send as part of the spawn</param>
|
||||
public void NetworkShow(ulong clientId, Stream payload = null)
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
if (!NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
throw new NotServerException("Only server can change visibility");
|
||||
}
|
||||
|
||||
if (m_Observers.Contains(clientId))
|
||||
{
|
||||
throw new VisibilityChangeException("The object is already visible");
|
||||
}
|
||||
|
||||
// Send spawn call
|
||||
m_Observers.Add(clientId);
|
||||
|
||||
NetworkSpawnManager.SendSpawnCallForObject(clientId, this, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a list of previously hidden objects to a client
|
||||
/// </summary>
|
||||
/// <param name="networkObjects">The objects to show</param>
|
||||
/// <param name="clientId">The client to show the objects to</param>
|
||||
/// <param name="payload">An optional payload to send as part of the spawns</param>
|
||||
public static void NetworkShow(List<NetworkObject> networkObjects, ulong clientId, Stream payload = null)
|
||||
{
|
||||
if (!NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
throw new NotServerException("Only server can change visibility");
|
||||
}
|
||||
|
||||
// Do the safety loop first to prevent putting the MLAPI in an invalid state.
|
||||
for (int i = 0; i < networkObjects.Count; i++)
|
||||
{
|
||||
if (!networkObjects[i].IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
if (networkObjects[i].m_Observers.Contains(clientId))
|
||||
{
|
||||
throw new VisibilityChangeException($"{nameof(NetworkObject)} with NetworkId: {networkObjects[i].NetworkObjectId} is already visible");
|
||||
}
|
||||
}
|
||||
|
||||
using (var buffer = PooledNetworkBuffer.Get())
|
||||
using (var writer = PooledNetworkWriter.Get(buffer))
|
||||
{
|
||||
writer.WriteUInt16Packed((ushort)networkObjects.Count);
|
||||
|
||||
for (int i = 0; i < networkObjects.Count; i++)
|
||||
{
|
||||
// Send spawn call
|
||||
networkObjects[i].m_Observers.Add(clientId);
|
||||
|
||||
NetworkSpawnManager.WriteSpawnCallForObject(buffer, clientId, networkObjects[i], payload);
|
||||
}
|
||||
|
||||
InternalMessageSender.Send(clientId, NetworkConstants.ADD_OBJECTS, NetworkChannel.Internal, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides a object from a specific client
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client to hide the object for</param>
|
||||
public void NetworkHide(ulong clientId)
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
if (!NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
throw new NotServerException("Only server can change visibility");
|
||||
}
|
||||
|
||||
if (!m_Observers.Contains(clientId))
|
||||
{
|
||||
throw new VisibilityChangeException("The object is already hidden");
|
||||
}
|
||||
|
||||
if (clientId == NetworkManager.Singleton.ServerClientId)
|
||||
{
|
||||
throw new VisibilityChangeException("Cannot hide an object from the server");
|
||||
}
|
||||
|
||||
|
||||
// Send destroy call
|
||||
m_Observers.Remove(clientId);
|
||||
|
||||
using (var buffer = PooledNetworkBuffer.Get())
|
||||
using (var writer = PooledNetworkWriter.Get(buffer))
|
||||
{
|
||||
writer.WriteUInt64Packed(NetworkObjectId);
|
||||
|
||||
InternalMessageSender.Send(clientId, NetworkConstants.DESTROY_OBJECT, NetworkChannel.Internal, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides a list of objects from a client
|
||||
/// </summary>
|
||||
/// <param name="networkObjects">The objects to hide</param>
|
||||
/// <param name="clientId">The client to hide the objects from</param>
|
||||
public static void NetworkHide(List<NetworkObject> networkObjects, ulong clientId)
|
||||
{
|
||||
if (!NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
throw new NotServerException("Only server can change visibility");
|
||||
}
|
||||
|
||||
if (clientId == NetworkManager.Singleton.ServerClientId)
|
||||
{
|
||||
throw new VisibilityChangeException("Cannot hide an object from the server");
|
||||
}
|
||||
|
||||
// Do the safety loop first to prevent putting the MLAPI in an invalid state.
|
||||
for (int i = 0; i < networkObjects.Count; i++)
|
||||
{
|
||||
if (!networkObjects[i].IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
if (!networkObjects[i].m_Observers.Contains(clientId))
|
||||
{
|
||||
throw new VisibilityChangeException($"{nameof(NetworkObject)} with {nameof(NetworkObjectId)}: {networkObjects[i].NetworkObjectId} is already hidden");
|
||||
}
|
||||
}
|
||||
|
||||
using (var buffer = PooledNetworkBuffer.Get())
|
||||
using (var writer = PooledNetworkWriter.Get(buffer))
|
||||
{
|
||||
writer.WriteUInt16Packed((ushort)networkObjects.Count);
|
||||
|
||||
for (int i = 0; i < networkObjects.Count; i++)
|
||||
{
|
||||
// Send destroy call
|
||||
networkObjects[i].m_Observers.Remove(clientId);
|
||||
|
||||
writer.WriteUInt64Packed(networkObjects[i].NetworkObjectId);
|
||||
}
|
||||
|
||||
InternalMessageSender.Send(clientId, NetworkConstants.DESTROY_OBJECTS, NetworkChannel.Internal, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (NetworkManager.Singleton != null)
|
||||
{
|
||||
NetworkSpawnManager.OnDestroyObject(NetworkObjectId, false);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SpawnInternal(Stream spawnPayload, bool destroyWithScene , ulong? ownerClientId, bool playerObject)
|
||||
{
|
||||
if (!NetworkManager.Singleton.IsListening)
|
||||
{
|
||||
throw new NotListeningException($"{nameof(NetworkManager)} isn't listening, start a server or host before spawning objects.");
|
||||
}
|
||||
|
||||
if (!NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
throw new NotServerException($"Only server can spawn {nameof(NetworkObject)}s");
|
||||
}
|
||||
|
||||
if (spawnPayload != null) spawnPayload.Position = 0;
|
||||
|
||||
NetworkSpawnManager.SpawnNetworkObjectLocally(this, NetworkSpawnManager.GetNetworkObjectId(), false, playerObject, ownerClientId, spawnPayload, spawnPayload != null, spawnPayload == null ? 0 : (int)spawnPayload.Length, false, destroyWithScene);
|
||||
|
||||
for (int i = 0; i < NetworkManager.Singleton.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
if (m_Observers.Contains(NetworkManager.Singleton.ConnectedClientsList[i].ClientId))
|
||||
{
|
||||
NetworkSpawnManager.SendSpawnCallForObject(NetworkManager.Singleton.ConnectedClientsList[i].ClientId, this, spawnPayload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns this GameObject across the network. Can only be called from the Server
|
||||
/// </summary>
|
||||
/// <param name="spawnPayload">The writer containing the spawn payload</param>
|
||||
/// <param name="destroyWithScene">Should the object be destroyd when the scene is changed</param>
|
||||
public void Spawn(Stream spawnPayload = null, bool destroyWithScene = false)
|
||||
{
|
||||
SpawnInternal(spawnPayload, destroyWithScene, null, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns an object across the network with a given owner. Can only be called from server
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId to own the object</param>
|
||||
/// <param name="spawnPayload">The writer containing the spawn payload</param>
|
||||
/// <param name="destroyWithScene">Should the object be destroyd when the scene is changed</param>
|
||||
public void SpawnWithOwnership(ulong clientId, Stream spawnPayload = null, bool destroyWithScene = false)
|
||||
{
|
||||
SpawnInternal(spawnPayload, destroyWithScene, clientId, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns an object across the network and makes it the player object for the given client
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId whos player object this is</param>
|
||||
/// <param name="spawnPayload">The writer containing the spawn payload</param>
|
||||
/// <param name="destroyWithScene">Should the object be destroyd when the scene is changed</param>
|
||||
public void SpawnAsPlayerObject(ulong clientId, Stream spawnPayload = null, bool destroyWithScene = false)
|
||||
{
|
||||
SpawnInternal(spawnPayload, destroyWithScene, clientId, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns this GameObject and destroys it for other clients. This should be used if the object should be kept on the server
|
||||
/// </summary>
|
||||
public void Despawn(bool destroy = false)
|
||||
{
|
||||
NetworkSpawnManager.DespawnObject(this, destroy);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes all ownership of an object from any client. Can only be called from server
|
||||
/// </summary>
|
||||
public void RemoveOwnership()
|
||||
{
|
||||
NetworkSpawnManager.RemoveOwnership(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the owner of the object. Can only be called from server
|
||||
/// </summary>
|
||||
/// <param name="newOwnerClientId">The new owner clientId</param>
|
||||
public void ChangeOwnership(ulong newOwnerClientId)
|
||||
{
|
||||
NetworkSpawnManager.ChangeOwnership(this, newOwnerClientId);
|
||||
}
|
||||
|
||||
internal void InvokeBehaviourOnLostOwnership()
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].OnLostOwnership();
|
||||
}
|
||||
}
|
||||
|
||||
internal void InvokeBehaviourOnGainedOwnership()
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].OnGainedOwnership();
|
||||
}
|
||||
}
|
||||
|
||||
internal void ResetNetworkStartInvoked()
|
||||
{
|
||||
if (ChildNetworkBehaviours != null)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].NetworkStartInvoked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void InvokeBehaviourNetworkSpawn(Stream stream)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
//We check if we are it's NetworkObject owner incase a NetworkObject exists as a child of our NetworkObject
|
||||
if (!ChildNetworkBehaviours[i].NetworkStartInvoked)
|
||||
{
|
||||
if (!ChildNetworkBehaviours[i].InternalNetworkStartInvoked)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InternalNetworkStart();
|
||||
ChildNetworkBehaviours[i].InternalNetworkStartInvoked = true;
|
||||
}
|
||||
|
||||
ChildNetworkBehaviours[i].NetworkStart(stream);
|
||||
ChildNetworkBehaviours[i].NetworkStartInvoked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<NetworkBehaviour> m_ChildNetworkBehaviours;
|
||||
|
||||
internal List<NetworkBehaviour> ChildNetworkBehaviours
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ChildNetworkBehaviours != null)
|
||||
{
|
||||
return m_ChildNetworkBehaviours;
|
||||
}
|
||||
|
||||
m_ChildNetworkBehaviours = new List<NetworkBehaviour>();
|
||||
var networkBehaviours = GetComponentsInChildren<NetworkBehaviour>(true);
|
||||
for (int i = 0; i < networkBehaviours.Length; i++)
|
||||
{
|
||||
if (networkBehaviours[i].NetworkObject == this)
|
||||
{
|
||||
m_ChildNetworkBehaviours.Add(networkBehaviours[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return m_ChildNetworkBehaviours;
|
||||
}
|
||||
}
|
||||
|
||||
internal void WriteNetworkVariableData(Stream stream, ulong clientId)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InitializeVariables();
|
||||
NetworkBehaviour.WriteNetworkVariableData(ChildNetworkBehaviours[i].NetworkVariableFields, stream, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetNetworkVariableData(Stream stream)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InitializeVariables();
|
||||
NetworkBehaviour.SetNetworkVariableData(ChildNetworkBehaviours[i].NetworkVariableFields, stream);
|
||||
}
|
||||
}
|
||||
|
||||
internal ushort GetNetworkBehaviourOrderIndex(NetworkBehaviour instance)
|
||||
{
|
||||
for (ushort i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
if (ChildNetworkBehaviours[i] == instance)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index)
|
||||
{
|
||||
if (index >= ChildNetworkBehaviours.Count)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogError($"Behaviour index was out of bounds. Did you mess up the order of your {nameof(NetworkBehaviour)}s?");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return ChildNetworkBehaviours[index];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d5a57f767e5e46a458fc5d3c628d0cbb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MLAPI
|
||||
{
|
||||
// todo: This is a pretty minimal tick system. It will be improved in the future
|
||||
// It currently relies on Time.unscaledTime and, as such, will start suffering
|
||||
// numerical precision issues after 2^23 ticks have passed (float have 23 bits mantissa)
|
||||
// For future releases, we'll need to improve on this, probably by leveraging FixedUpdate
|
||||
|
||||
public class NetworkTickSystem : INetworkUpdateSystem, IDisposable
|
||||
{
|
||||
private const float k_DefaultTickIntervalSec = 0.05f; // Defaults to 20 ticks second
|
||||
private readonly float m_TickIntervalSec; // Duration of a tick in seconds
|
||||
private int m_NetworkTickCount; // How many network ticks have passed?
|
||||
|
||||
// special value to indicate "No tick information"
|
||||
public const ushort NoTick = ushort.MaxValue;
|
||||
|
||||
// Number of ticks over which the tick number wraps back to 0
|
||||
public const ushort TickPeriod = NoTick - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// Defaults to k_DefaultTickIntervalSec if no tick duration is specified
|
||||
/// </summary>
|
||||
/// <param name="tickIntervalSec">Duration of a network tick</param>
|
||||
public NetworkTickSystem(float tickIntervalSec = k_DefaultTickIntervalSec)
|
||||
{
|
||||
this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
|
||||
|
||||
//Assure we don't specify a value less than or equal to zero for tick frequency
|
||||
m_TickIntervalSec = (tickIntervalSec <= 0f) ? k_DefaultTickIntervalSec : tickIntervalSec;
|
||||
|
||||
// ticks might not start at 0, so let's update right away at construction
|
||||
UpdateNetworkTick();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.UnregisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetTick
|
||||
/// Gets the current network tick (non-fractional, wrapping around)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ushort GetTick()
|
||||
{
|
||||
return (ushort)(m_NetworkTickCount % TickPeriod);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetNetworkTime
|
||||
/// Network time is calculated from m_NetworkTickCount and m_TickIntervalSec (tick frequency)
|
||||
/// </summary>
|
||||
/// <returns>Network Time</returns>
|
||||
public float GetNetworkTime()
|
||||
{
|
||||
return m_NetworkTickCount * m_TickIntervalSec;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UpdateNetworkTick
|
||||
/// Called each network loop update during the PreUpdate stage
|
||||
/// </summary>
|
||||
private void UpdateNetworkTick()
|
||||
{
|
||||
m_NetworkTickCount = (int)(Time.unscaledTime / m_TickIntervalSec);
|
||||
}
|
||||
|
||||
public void NetworkUpdate(NetworkUpdateStage updateStage)
|
||||
{
|
||||
switch (updateStage)
|
||||
{
|
||||
case NetworkUpdateStage.EarlyUpdate:
|
||||
UpdateNetworkTick();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d802d09776f224e44af7dc71f09779d6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,398 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.LowLevel;
|
||||
using UnityEngine.PlayerLoop;
|
||||
|
||||
namespace MLAPI
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the required interface of a network update system being executed by the network update loop.
|
||||
/// </summary>
|
||||
public interface INetworkUpdateSystem
|
||||
{
|
||||
void NetworkUpdate(NetworkUpdateStage updateStage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines network update stages being executed by the network update loop.
|
||||
/// </summary>
|
||||
public enum NetworkUpdateStage : byte
|
||||
{
|
||||
Initialization = 1,
|
||||
EarlyUpdate = 2,
|
||||
FixedUpdate = 3,
|
||||
PreUpdate = 4,
|
||||
Update = 0, // Default
|
||||
PreLateUpdate = 5,
|
||||
PostLateUpdate = 6
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the network update loop injected into low-level player loop in Unity.
|
||||
/// </summary>
|
||||
public static class NetworkUpdateLoop
|
||||
{
|
||||
private static Dictionary<NetworkUpdateStage, HashSet<INetworkUpdateSystem>> s_UpdateSystem_Sets;
|
||||
private static Dictionary<NetworkUpdateStage, INetworkUpdateSystem[]> s_UpdateSystem_Arrays;
|
||||
private const int k_UpdateSystem_InitialArrayCapacity = 1024;
|
||||
|
||||
static NetworkUpdateLoop()
|
||||
{
|
||||
s_UpdateSystem_Sets = new Dictionary<NetworkUpdateStage, HashSet<INetworkUpdateSystem>>();
|
||||
s_UpdateSystem_Arrays = new Dictionary<NetworkUpdateStage, INetworkUpdateSystem[]>();
|
||||
|
||||
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
|
||||
{
|
||||
s_UpdateSystem_Sets.Add(updateStage, new HashSet<INetworkUpdateSystem>());
|
||||
s_UpdateSystem_Arrays.Add(updateStage, new INetworkUpdateSystem[k_UpdateSystem_InitialArrayCapacity]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a network update system to be executed in all network update stages.
|
||||
/// </summary>
|
||||
public static void RegisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem)
|
||||
{
|
||||
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
|
||||
{
|
||||
RegisterNetworkUpdate(updateSystem, updateStage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a network update system to be executed in a specific network update stage.
|
||||
/// </summary>
|
||||
public static void RegisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update)
|
||||
{
|
||||
var sysSet = s_UpdateSystem_Sets[updateStage];
|
||||
if (!sysSet.Contains(updateSystem))
|
||||
{
|
||||
sysSet.Add(updateSystem);
|
||||
|
||||
int setLen = sysSet.Count;
|
||||
var sysArr = s_UpdateSystem_Arrays[updateStage];
|
||||
int arrLen = sysArr.Length;
|
||||
|
||||
if (setLen > arrLen)
|
||||
{
|
||||
// double capacity
|
||||
sysArr = s_UpdateSystem_Arrays[updateStage] = new INetworkUpdateSystem[arrLen *= 2];
|
||||
}
|
||||
|
||||
sysSet.CopyTo(sysArr);
|
||||
|
||||
if (setLen < arrLen)
|
||||
{
|
||||
// null terminator
|
||||
sysArr[setLen] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a network update system from all network update stages.
|
||||
/// </summary>
|
||||
public static void UnregisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem)
|
||||
{
|
||||
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
|
||||
{
|
||||
UnregisterNetworkUpdate(updateSystem, updateStage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a network update system from a specific network update stage.
|
||||
/// </summary>
|
||||
public static void UnregisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update)
|
||||
{
|
||||
var sysSet = s_UpdateSystem_Sets[updateStage];
|
||||
if (sysSet.Contains(updateSystem))
|
||||
{
|
||||
sysSet.Remove(updateSystem);
|
||||
|
||||
int setLen = sysSet.Count;
|
||||
var sysArr = s_UpdateSystem_Arrays[updateStage];
|
||||
int arrLen = sysArr.Length;
|
||||
|
||||
sysSet.CopyTo(sysArr);
|
||||
|
||||
if (setLen < arrLen)
|
||||
{
|
||||
// null terminator
|
||||
sysArr[setLen] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current network update stage being executed.
|
||||
/// </summary>
|
||||
public static NetworkUpdateStage UpdateStage;
|
||||
|
||||
private static void RunNetworkUpdateStage(NetworkUpdateStage updateStage)
|
||||
{
|
||||
UpdateStage = updateStage;
|
||||
|
||||
var sysArr = s_UpdateSystem_Arrays[updateStage];
|
||||
int arrLen = sysArr.Length;
|
||||
for (int curIdx = 0; curIdx < arrLen; curIdx++)
|
||||
{
|
||||
var curSys = sysArr[curIdx];
|
||||
if (curSys == null)
|
||||
{
|
||||
// null terminator
|
||||
break;
|
||||
}
|
||||
|
||||
curSys.NetworkUpdate(updateStage);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkInitialization
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkInitialization),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.Initialization)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkEarlyUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkEarlyUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.EarlyUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkFixedUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkFixedUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.FixedUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkPreUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkPreUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PreUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.Update)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkPreLateUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkPreLateUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PreLateUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkPostLateUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkPostLateUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PostLateUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void Initialize()
|
||||
{
|
||||
UnregisterLoopSystems();
|
||||
RegisterLoopSystems();
|
||||
}
|
||||
|
||||
private enum LoopSystemPosition
|
||||
{
|
||||
After,
|
||||
Before
|
||||
}
|
||||
|
||||
private static bool TryAddLoopSystem(ref PlayerLoopSystem parentLoopSystem, PlayerLoopSystem childLoopSystem, Type anchorSystemType, LoopSystemPosition loopSystemPosition)
|
||||
{
|
||||
int systemPosition = -1;
|
||||
if (anchorSystemType != null)
|
||||
{
|
||||
for (int i = 0; i < parentLoopSystem.subSystemList.Length; i++)
|
||||
{
|
||||
var subsystem = parentLoopSystem.subSystemList[i];
|
||||
if (subsystem.type == anchorSystemType)
|
||||
{
|
||||
systemPosition = loopSystemPosition == LoopSystemPosition.After ? i + 1 : i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
systemPosition = loopSystemPosition == LoopSystemPosition.After ? parentLoopSystem.subSystemList.Length : 0;
|
||||
}
|
||||
|
||||
if (systemPosition == -1) return false;
|
||||
|
||||
var newSubsystemList = new PlayerLoopSystem[parentLoopSystem.subSystemList.Length + 1];
|
||||
|
||||
// begin = systemsBefore + systemsAfter
|
||||
// + systemsBefore
|
||||
if (systemPosition > 0) Array.Copy(parentLoopSystem.subSystemList, newSubsystemList, systemPosition);
|
||||
// + childSystem
|
||||
newSubsystemList[systemPosition] = childLoopSystem;
|
||||
// + systemsAfter
|
||||
if (systemPosition < parentLoopSystem.subSystemList.Length) Array.Copy(parentLoopSystem.subSystemList, systemPosition, newSubsystemList, systemPosition + 1, parentLoopSystem.subSystemList.Length - systemPosition);
|
||||
// end = systemsBefore + childSystem + systemsAfter
|
||||
|
||||
parentLoopSystem.subSystemList = newSubsystemList;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryRemoveLoopSystem(ref PlayerLoopSystem parentLoopSystem, Type childSystemType)
|
||||
{
|
||||
int systemPosition = -1;
|
||||
for (int i = 0; i < parentLoopSystem.subSystemList.Length; i++)
|
||||
{
|
||||
var subsystem = parentLoopSystem.subSystemList[i];
|
||||
if (subsystem.type == childSystemType)
|
||||
{
|
||||
systemPosition = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (systemPosition == -1) return false;
|
||||
|
||||
var newSubsystemList = new PlayerLoopSystem[parentLoopSystem.subSystemList.Length - 1];
|
||||
|
||||
// begin = systemsBefore + childSystem + systemsAfter
|
||||
// + systemsBefore
|
||||
if (systemPosition > 0) Array.Copy(parentLoopSystem.subSystemList, newSubsystemList, systemPosition);
|
||||
// + systemsAfter
|
||||
if (systemPosition < parentLoopSystem.subSystemList.Length - 1) Array.Copy(parentLoopSystem.subSystemList, systemPosition + 1, newSubsystemList, systemPosition, parentLoopSystem.subSystemList.Length - systemPosition - 1);
|
||||
// end = systemsBefore + systemsAfter
|
||||
|
||||
parentLoopSystem.subSystemList = newSubsystemList;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void RegisterLoopSystems()
|
||||
{
|
||||
var rootPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
|
||||
|
||||
for (int i = 0; i < rootPlayerLoop.subSystemList.Length; i++)
|
||||
{
|
||||
ref var currentSystem = ref rootPlayerLoop.subSystemList[i];
|
||||
|
||||
if (currentSystem.type == typeof(Initialization))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkInitialization.CreateLoopSystem(), null, LoopSystemPosition.After);
|
||||
}
|
||||
else if (currentSystem.type == typeof(EarlyUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkEarlyUpdate.CreateLoopSystem(), typeof(EarlyUpdate.ScriptRunDelayedStartupFrame), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(FixedUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkFixedUpdate.CreateLoopSystem(), typeof(FixedUpdate.ScriptRunBehaviourFixedUpdate), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(PreUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkPreUpdate.CreateLoopSystem(), typeof(PreUpdate.PhysicsUpdate), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(Update))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkUpdate.CreateLoopSystem(), typeof(Update.ScriptRunBehaviourUpdate), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(PreLateUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkPreLateUpdate.CreateLoopSystem(), typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(PostLateUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkPostLateUpdate.CreateLoopSystem(), typeof(PostLateUpdate.PlayerSendFrameComplete), LoopSystemPosition.After);
|
||||
}
|
||||
}
|
||||
|
||||
PlayerLoop.SetPlayerLoop(rootPlayerLoop);
|
||||
}
|
||||
|
||||
internal static void UnregisterLoopSystems()
|
||||
{
|
||||
var rootPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
|
||||
|
||||
for (int i = 0; i < rootPlayerLoop.subSystemList.Length; i++)
|
||||
{
|
||||
ref var currentSystem = ref rootPlayerLoop.subSystemList[i];
|
||||
|
||||
if (currentSystem.type == typeof(Initialization))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkInitialization));
|
||||
}
|
||||
else if (currentSystem.type == typeof(EarlyUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkEarlyUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(FixedUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkFixedUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(PreUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(Update))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(PreLateUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreLateUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(PostLateUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPostLateUpdate));
|
||||
}
|
||||
}
|
||||
|
||||
PlayerLoop.SetPlayerLoop(rootPlayerLoop);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0cd9c24b9acfd4e82a71c795f37235c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2643fec0e8509fd409e5aceeba931323
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace MLAPI.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when the operation can only be done on the server
|
||||
/// </summary>
|
||||
public class NetworkConfigurationException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a NetworkConfigurationException
|
||||
/// </summary>
|
||||
public NetworkConfigurationException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NetworkConfigurationException with a message
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
public NetworkConfigurationException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NetworkConfigurationException with a message and a inner exception
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
/// <param name="inner">The inner exception</param>
|
||||
public NetworkConfigurationException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f50fae9dd20afbd438766cc330b188a4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace MLAPI.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when the operation require NetworkManager to be listening.
|
||||
/// </summary>
|
||||
public class NotListeningException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a NotListeningException
|
||||
/// </summary>
|
||||
public NotListeningException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NotListeningException with a message
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
public NotListeningException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NotListeningException with a message and a inner exception
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
/// <param name="inner">The inner exception</param>
|
||||
public NotListeningException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dc58b328162929140aeb42e6a4a9354b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace MLAPI.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when the operation can only be done on the server
|
||||
/// </summary>
|
||||
public class NotServerException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a NotServerException
|
||||
/// </summary>
|
||||
public NotServerException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NotServerException with a message
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
public NotServerException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NotServerException with a message and a inner exception
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
/// <param name="inner">The inner exception</param>
|
||||
public NotServerException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 751e55ce1418ecd4e9b08fef9d8e9591
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
|
||||
namespace MLAPI.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when an object is not yet spawned
|
||||
/// </summary>
|
||||
public class SpawnStateException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a SpawnStateException
|
||||
/// </summary>
|
||||
public SpawnStateException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a SpawnStateException with a message
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
public SpawnStateException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a SpawnStateException with a message and a inner exception
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
/// <param name="inner">The inner exception</param>
|
||||
public SpawnStateException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
public class InvalidChannelException : Exception
|
||||
{
|
||||
public InvalidChannelException(string message) : base(message) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 70c2a851ae78c6e49b34c7e4ca46305f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace MLAPI.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when a visibility change fails
|
||||
/// </summary>
|
||||
public class VisibilityChangeException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a VisibilityChangeException
|
||||
/// </summary>
|
||||
public VisibilityChangeException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a VisibilityChangeException with a message
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
public VisibilityChangeException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a VisibilityChangeException with a message and a inner exception
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
/// <param name="inner">The inner exception</param>
|
||||
public VisibilityChangeException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 85d754bbea7d74a48918ebe05833e7ba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue