mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
Compare commits
17 Commits
29ee1317e4
...
cf83f90251
Author | SHA1 | Date | |
---|---|---|---|
|
cf83f90251 | ||
|
451c297a43 | ||
|
1e04c9833b | ||
|
1187a59b18 | ||
|
499e4daea3 | ||
|
b0fa0c50b9 | ||
|
d04e0b1f06 | ||
|
d09f5ef008 | ||
|
a14d094bc5 | ||
|
ae9ecf3483 | ||
|
bf49b6c923 | ||
|
477d430a3f | ||
|
12ce588711 | ||
|
ebdd36f6fc | ||
|
5ede69c025 | ||
|
338c0634a5 | ||
|
7e1156a4d6 |
@ -0,0 +1,369 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Mirror;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class FastSpatialInterestManagement : InterestManagementBase
|
||||||
|
{
|
||||||
|
Vector2Int InvalidPosition => new Vector2Int(int.MaxValue,
|
||||||
|
int.MaxValue);
|
||||||
|
|
||||||
|
|
||||||
|
[Tooltip("The maximum range that objects will be visible at.")]
|
||||||
|
public int visRange = 30;
|
||||||
|
|
||||||
|
[Tooltip("Rebuild all every 'rebuildInterval' seconds.")]
|
||||||
|
public float rebuildInterval = 1;
|
||||||
|
|
||||||
|
double lastRebuildTime;
|
||||||
|
|
||||||
|
// we use a 9 neighbour grid.
|
||||||
|
// so we always see in a distance of 2 grids.
|
||||||
|
// for example, our own grid and then one on top / below / left / right.
|
||||||
|
//
|
||||||
|
// this means that grid resolution needs to be distance / 2.
|
||||||
|
// so for example, for distance = 30 we see 2 cells = 15 * 2 distance.
|
||||||
|
//
|
||||||
|
// on first sight, it seems we need distance / 3 (we see left/us/right).
|
||||||
|
// but that's not the case.
|
||||||
|
// resolution would be 10, and we only see 1 cell far, so 10+10=20.
|
||||||
|
int TileSize => visRange / 2;
|
||||||
|
|
||||||
|
// the grid
|
||||||
|
Dictionary<Vector2Int, HashSet<NetworkIdentity>> grid =
|
||||||
|
new Dictionary<Vector2Int, HashSet<NetworkIdentity>>();
|
||||||
|
|
||||||
|
class Tracked
|
||||||
|
{
|
||||||
|
public Vector2Int Position;
|
||||||
|
public Transform Transform;
|
||||||
|
public NetworkIdentity Identity;
|
||||||
|
public Visibility PreviousVisibility;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Vector2Int GridPosition(int tileSize)
|
||||||
|
{
|
||||||
|
Vector3 transformPos = Transform.position;
|
||||||
|
return Vector2Int.RoundToInt(new Vector2(transformPos.x, transformPos.z) / tileSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<NetworkIdentity, Tracked> trackedIdentities = new Dictionary<NetworkIdentity, Tracked>();
|
||||||
|
|
||||||
|
public override void Rebuild(NetworkIdentity identity, bool initialize)
|
||||||
|
{
|
||||||
|
// do nothing, we rebuild globally and individually in OnSpawned
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnCheckObserver(NetworkIdentity identity, NetworkConnectionToClient newObserver)
|
||||||
|
{
|
||||||
|
// do nothing, we rebuild globally and individually in OnSpawned
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void LateUpdate()
|
||||||
|
{
|
||||||
|
// only on server
|
||||||
|
if (!NetworkServer.active) return;
|
||||||
|
|
||||||
|
// rebuild all spawned entities' observers every 'interval'
|
||||||
|
// this will call OnRebuildObservers which then returns the
|
||||||
|
// observers at grid[position] for each entity.
|
||||||
|
if (NetworkTime.localTime >= lastRebuildTime + rebuildInterval)
|
||||||
|
{
|
||||||
|
RebuildAll();
|
||||||
|
lastRebuildTime = NetworkTime.localTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When a new identity is spawned
|
||||||
|
public override void OnSpawned(NetworkIdentity identity)
|
||||||
|
{
|
||||||
|
// host visibility shim to make sure unseen entities are hidden, we initialize actual visibility later
|
||||||
|
if (NetworkClient.active)
|
||||||
|
{
|
||||||
|
SetHostVisibility(identity, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (identity.connectionToClient != null)
|
||||||
|
{
|
||||||
|
// client always sees itself
|
||||||
|
AddObserver(identity.connectionToClient, identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
Tracked tracked = new Tracked
|
||||||
|
{
|
||||||
|
Transform = identity.transform, Identity = identity, PreviousVisibility = identity.visibility,
|
||||||
|
};
|
||||||
|
|
||||||
|
// set initial position
|
||||||
|
tracked.Position = tracked.GridPosition(TileSize);
|
||||||
|
// add to tracked
|
||||||
|
trackedIdentities.Add(identity, tracked);
|
||||||
|
// initialize in grid
|
||||||
|
RebuildAdd(identity, InvalidPosition, tracked.Position, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// when an identity is despawned/destroyed
|
||||||
|
public override void OnDestroyed(NetworkIdentity identity)
|
||||||
|
{
|
||||||
|
Tracked obj = trackedIdentities[identity];
|
||||||
|
trackedIdentities.Remove(identity);
|
||||||
|
|
||||||
|
// observers are cleaned up automatically when destroying, we just need to remove it from our grid
|
||||||
|
grid[obj.Position].Remove(identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildAll()
|
||||||
|
{
|
||||||
|
// loop over all identities and check if their position has changed
|
||||||
|
foreach (Tracked tracked in trackedIdentities.Values)
|
||||||
|
{
|
||||||
|
// Check if visibility has changed, this should usually be false
|
||||||
|
bool visibilityChanged = tracked.Identity.visibility != tracked.PreviousVisibility;
|
||||||
|
// Visibility change *to* default needs to be handled before the normal grid update
|
||||||
|
// since observers are manipulated in RebuildAdd/RebuildRemove if visibility == Default
|
||||||
|
if (visibilityChanged && tracked.Identity.visibility == Visibility.Default)
|
||||||
|
{
|
||||||
|
switch (tracked.PreviousVisibility)
|
||||||
|
{
|
||||||
|
case Visibility.ForceHidden:
|
||||||
|
// Hidden To Default
|
||||||
|
AddObserversHiddenToDefault(tracked.Identity, tracked.Position);
|
||||||
|
break;
|
||||||
|
case Visibility.ForceShown:
|
||||||
|
// Shown To Default
|
||||||
|
RemoveObserversShownToDefault(tracked.Identity, tracked.Position);
|
||||||
|
break;
|
||||||
|
case Visibility.Default:
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2Int currentPosition = tracked.GridPosition(TileSize);
|
||||||
|
// if the position changed, move the identity in the grid and update observers accordingly
|
||||||
|
if (currentPosition != tracked.Position)
|
||||||
|
{
|
||||||
|
Vector2Int oldPosition = tracked.Position;
|
||||||
|
tracked.Position = currentPosition;
|
||||||
|
// First remove from old grid position
|
||||||
|
RebuildRemove(tracked.Identity, oldPosition, currentPosition);
|
||||||
|
// Then add to new grid position
|
||||||
|
RebuildAdd(tracked.Identity, oldPosition, currentPosition, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// after updating the grid, if the visibility has changed
|
||||||
|
if (visibilityChanged)
|
||||||
|
{
|
||||||
|
switch (tracked.Identity.visibility)
|
||||||
|
{
|
||||||
|
case Visibility.ForceHidden:
|
||||||
|
ClearObservers(tracked.Identity);
|
||||||
|
break;
|
||||||
|
case Visibility.ForceShown:
|
||||||
|
AddObserversAllReady(tracked.Identity);
|
||||||
|
break;
|
||||||
|
case Visibility.Default:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
tracked.PreviousVisibility = tracked.Identity.visibility;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildRemove(NetworkIdentity changedIdentity, Vector2Int oldPosition, Vector2Int newPosition)
|
||||||
|
{
|
||||||
|
// sanity check
|
||||||
|
if (!grid[oldPosition].Remove(changedIdentity))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("changedIdentity was not in the provided grid");
|
||||||
|
}
|
||||||
|
|
||||||
|
// for all tiles the changedIdentity could see at the old position
|
||||||
|
for (int x = -1; x <= 1; x++)
|
||||||
|
{
|
||||||
|
for (int y = -1; y <= 1; y++)
|
||||||
|
{
|
||||||
|
Vector2Int tilePos = oldPosition + new Vector2Int(x, y);
|
||||||
|
// Skip grid tiles that are still visible
|
||||||
|
if (Mathf.Abs(tilePos.x - newPosition.x) <= 1 &&
|
||||||
|
Mathf.Abs(tilePos.y - newPosition.y) <= 1)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!grid.TryGetValue(tilePos, out HashSet<NetworkIdentity> tile))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (NetworkIdentity gridIdentity in tile)
|
||||||
|
{
|
||||||
|
if (gridIdentity == changedIdentity)
|
||||||
|
{
|
||||||
|
// Don't do anything with yourself
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we only modify observers here if the visibility is default, ForceShown/ForceHidden are handled in RebuildAll
|
||||||
|
|
||||||
|
// if the gridIdentity is a player, it can't see changedIdentity anymore
|
||||||
|
if (gridIdentity.connectionToClient != null && changedIdentity.visibility == Visibility.Default)
|
||||||
|
{
|
||||||
|
RemoveObserver(gridIdentity.connectionToClient, changedIdentity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the changedIdentity is a player, it can't see gridIdentity anymore
|
||||||
|
if (changedIdentity.connectionToClient != null && gridIdentity.visibility == Visibility.Default)
|
||||||
|
{
|
||||||
|
RemoveObserver(changedIdentity.connectionToClient, gridIdentity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildAdd(NetworkIdentity changedIdentity, Vector2Int oldPosition, Vector2Int newPosition,
|
||||||
|
bool initialize)
|
||||||
|
{
|
||||||
|
// for all tiles the changedIdentity now sees at the new position
|
||||||
|
for (int x = -1; x <= 1; x++)
|
||||||
|
{
|
||||||
|
for (int y = -1; y <= 1; y++)
|
||||||
|
{
|
||||||
|
Vector2Int tilePos = newPosition + new Vector2Int(x, y);
|
||||||
|
|
||||||
|
// Skip grid tiles that were already visible before moving
|
||||||
|
if (!initialize && (Mathf.Abs(tilePos.x - oldPosition.x) <= 1 &&
|
||||||
|
Mathf.Abs(tilePos.y - oldPosition.y) <= 1))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!grid.TryGetValue(tilePos, out HashSet<NetworkIdentity> tile))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (NetworkIdentity gridIdentity in tile)
|
||||||
|
{
|
||||||
|
if (gridIdentity == changedIdentity)
|
||||||
|
{
|
||||||
|
// Don't do anything with yourself
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we only modify observers here if the visibility is default, ForceShown/ForceHidden are handled in RebuildAll
|
||||||
|
|
||||||
|
// if the gridIdentity is a player, it can now see changedIdentity
|
||||||
|
if (gridIdentity.connectionToClient != null && changedIdentity.visibility == Visibility.Default)
|
||||||
|
{
|
||||||
|
AddObserver(gridIdentity.connectionToClient, changedIdentity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the changedIdentity is a player, it can now see gridIdentity
|
||||||
|
if (changedIdentity.connectionToClient != null && gridIdentity.visibility == Visibility.Default)
|
||||||
|
{
|
||||||
|
AddObserver(changedIdentity.connectionToClient, gridIdentity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add ourselves to the new grid position
|
||||||
|
if (!grid.TryGetValue(newPosition, out HashSet<NetworkIdentity> addTile))
|
||||||
|
{
|
||||||
|
addTile = new HashSet<NetworkIdentity>();
|
||||||
|
grid[newPosition] = addTile;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!addTile.Add(changedIdentity))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("identity was already in the grid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds observers to the NI, but not the other way around. This is used when a NI changes from ForceHidden to Default
|
||||||
|
private void AddObserversHiddenToDefault(NetworkIdentity changed, Vector2Int gridPosition)
|
||||||
|
{
|
||||||
|
// for all tiles around the changedIdentity
|
||||||
|
for (int x = -1; x <= 1; x++)
|
||||||
|
{
|
||||||
|
for (int y = -1; y <= 1; y++)
|
||||||
|
{
|
||||||
|
Vector2Int tilePos = gridPosition + new Vector2Int(x, y);
|
||||||
|
if (!grid.TryGetValue(tilePos, out HashSet<NetworkIdentity> tile))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (NetworkIdentity gridIdentity in tile)
|
||||||
|
{
|
||||||
|
if (gridIdentity == changed)
|
||||||
|
{
|
||||||
|
// Don't do anything with yourself
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the gridIdentity is a player, it can now see changedIdentity
|
||||||
|
if (gridIdentity.connectionToClient != null)
|
||||||
|
{
|
||||||
|
AddObserver(gridIdentity.connectionToClient, changed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temp hashset to avoid runtime allocation
|
||||||
|
private HashSet<NetworkConnectionToClient> tempShownToDefaultSet = new HashSet<NetworkConnectionToClient>();
|
||||||
|
|
||||||
|
/// Removes observers from the NI, but doesn't change observing. This is used when a NI changes from ForceShown to Default
|
||||||
|
private void RemoveObserversShownToDefault(NetworkIdentity changedIdentity, Vector2Int gridPosition)
|
||||||
|
{
|
||||||
|
tempShownToDefaultSet.Clear();
|
||||||
|
// copy over all current connections that are seeing the NI
|
||||||
|
foreach (NetworkConnectionToClient observer in changedIdentity.observers.Values)
|
||||||
|
{
|
||||||
|
tempShownToDefaultSet.Add(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// for all tiles around the changedIdentity, remove any connections that can still see it
|
||||||
|
for (int x = -1; x <= 1; x++)
|
||||||
|
{
|
||||||
|
for (int y = -1; y <= 1; y++)
|
||||||
|
{
|
||||||
|
Vector2Int tilePos = gridPosition + new Vector2Int(x, y);
|
||||||
|
if (!grid.TryGetValue(tilePos, out HashSet<NetworkIdentity> tile))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (NetworkIdentity gridIdentity in tile)
|
||||||
|
{
|
||||||
|
// if the gridIdentity is a player, it can see changedIdentity
|
||||||
|
// (also yourself! don't need the extra check here)
|
||||||
|
if (gridIdentity.connectionToClient != null)
|
||||||
|
{
|
||||||
|
tempShownToDefaultSet.Remove(gridIdentity.connectionToClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// any left over connections can't see changedIdentity - thus need removing
|
||||||
|
foreach (NetworkConnectionToClient connection in tempShownToDefaultSet)
|
||||||
|
{
|
||||||
|
RemoveObserver(connection, changedIdentity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear when done
|
||||||
|
tempShownToDefaultSet.Clear();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 86996600b40a4c41bb300ac0aed33189
|
||||||
|
timeCreated: 1676129828
|
1501
Assets/Mirror/Components/NetworkTransform/NetworkTransformHybrid.cs
Normal file
1501
Assets/Mirror/Components/NetworkTransform/NetworkTransformHybrid.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8f63ea2e505fd484193fb31c5c55ca73
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -48,28 +48,28 @@ public class TargetRpcAttribute : Attribute
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prevents clients from running this method.
|
/// Only an active server will run this method.
|
||||||
/// <para>Prints a warning if a client tries to execute this method.</para>
|
/// <para>Prints a warning if a client or in-active server tries to execute this method.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
public class ServerAttribute : Attribute {}
|
public class ServerAttribute : Attribute {}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prevents clients from running this method.
|
/// Only an active server will run this method.
|
||||||
/// <para>No warning is thrown.</para>
|
/// <para>No warning is thrown.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
public class ServerCallbackAttribute : Attribute {}
|
public class ServerCallbackAttribute : Attribute {}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prevents the server from running this method.
|
/// Only an active client will run this method.
|
||||||
/// <para>Prints a warning if the server tries to execute this method.</para>
|
/// <para>Prints a warning if the server or in-active client tries to execute this method.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
public class ClientAttribute : Attribute {}
|
public class ClientAttribute : Attribute {}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prevents the server from running this method.
|
/// Only an active client will run this method.
|
||||||
/// <para>No warning is printed.</para>
|
/// <para>No warning is printed.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// interest management component for custom solutions like
|
// interest management component for custom solutions like
|
||||||
// distance based, spatial hashing, raycast based, etc.
|
// distance based, spatial hashing, raycast based, etc.
|
||||||
// low level base class allows for low level spatial hashing etc., which is 3-5x faster.
|
// low level base class allows for low level spatial hashing etc., which is 3-5x faster.
|
||||||
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Mirror
|
namespace Mirror
|
||||||
@ -73,5 +74,39 @@ protected void RemoveObserver(NetworkConnectionToClient connection, NetworkIdent
|
|||||||
connection.RemoveFromObserving(identity, false);
|
connection.RemoveFromObserving(identity, false);
|
||||||
identity.observers.Remove(connection.connectionId);
|
identity.observers.Remove(connection.connectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For ForceShown: Makes sure all ready connections (that aren't already) are added to observers
|
||||||
|
protected void AddObserversAllReady(NetworkIdentity identity)
|
||||||
|
{
|
||||||
|
foreach (NetworkConnectionToClient connection in NetworkServer.connections.Values)
|
||||||
|
{
|
||||||
|
if (connection.isReady && !identity.observers.ContainsKey(connection.connectionId))
|
||||||
|
{
|
||||||
|
connection.AddToObserving(identity);
|
||||||
|
identity.observers.Add(connection.connectionId, connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For ForceHidden: Removes all observers from this identity
|
||||||
|
protected void ClearObservers(NetworkIdentity identity)
|
||||||
|
{
|
||||||
|
foreach (NetworkConnectionToClient connection in identity.observers.Values)
|
||||||
|
{
|
||||||
|
// Don't remove the client from observing its owned objects
|
||||||
|
if (connection != identity.connectionToClient)
|
||||||
|
{
|
||||||
|
connection.RemoveFromObserving(identity, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear..
|
||||||
|
identity.observers.Clear();
|
||||||
|
// If the object is owned by a client, add it's connection back to the observing set
|
||||||
|
if (identity.connectionToClient != null)
|
||||||
|
{
|
||||||
|
identity.observers.Add(identity.connectionToClient.connectionId, identity.connectionToClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -863,7 +863,7 @@ internal void OnStopLocalPlayer()
|
|||||||
for (int i = 0; i < components.Length; ++i)
|
for (int i = 0; i < components.Length; ++i)
|
||||||
{
|
{
|
||||||
NetworkBehaviour component = components[i];
|
NetworkBehaviour component = components[i];
|
||||||
ulong nthBit = (1u << i);
|
ulong nthBit = 1ul << i;
|
||||||
|
|
||||||
bool dirty = component.IsDirty();
|
bool dirty = component.IsDirty();
|
||||||
|
|
||||||
@ -910,7 +910,7 @@ ulong ClientDirtyMask()
|
|||||||
|
|
||||||
// on client, only consider owned components with SyncDirection to server
|
// on client, only consider owned components with SyncDirection to server
|
||||||
NetworkBehaviour component = components[i];
|
NetworkBehaviour component = components[i];
|
||||||
ulong nthBit = (1u << i);
|
ulong nthBit = 1ul << i;
|
||||||
|
|
||||||
if (isOwned && component.syncDirection == SyncDirection.ClientToServer)
|
if (isOwned && component.syncDirection == SyncDirection.ClientToServer)
|
||||||
{
|
{
|
||||||
@ -928,7 +928,7 @@ ulong ClientDirtyMask()
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal static bool IsDirty(ulong mask, int index)
|
internal static bool IsDirty(ulong mask, int index)
|
||||||
{
|
{
|
||||||
ulong nthBit = (ulong)(1 << index);
|
ulong nthBit = 1ul << index;
|
||||||
return (mask & nthBit) != 0;
|
return (mask & nthBit) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// base class for networking tests to make things easier.
|
// base class for networking tests to make things easier.
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -81,6 +82,18 @@ protected void CreateNetworked(out GameObject go, out NetworkIdentity identity)
|
|||||||
instantiated.Add(go);
|
instantiated.Add(go);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void CreateNetworked(out GameObject go, out NetworkIdentity identity, Action<NetworkIdentity> preAwake)
|
||||||
|
{
|
||||||
|
go = new GameObject();
|
||||||
|
identity = go.AddComponent<NetworkIdentity>();
|
||||||
|
preAwake(identity);
|
||||||
|
// Awake is only called in play mode.
|
||||||
|
// call manually for initialization.
|
||||||
|
identity.Awake();
|
||||||
|
// track
|
||||||
|
instantiated.Add(go);
|
||||||
|
}
|
||||||
|
|
||||||
// create GameObject + NetworkIdentity + NetworkBehaviour<T>
|
// create GameObject + NetworkIdentity + NetworkBehaviour<T>
|
||||||
// add to tracker list if needed (useful for cleanups afterwards)
|
// add to tracker list if needed (useful for cleanups afterwards)
|
||||||
protected void CreateNetworked<T>(out GameObject go, out NetworkIdentity identity, out T component)
|
protected void CreateNetworked<T>(out GameObject go, out NetworkIdentity identity, out T component)
|
||||||
@ -269,6 +282,44 @@ protected void CreateNetworkedAndSpawn(
|
|||||||
Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId));
|
Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN
|
||||||
|
// => preAwake callbacks can be used to add network behaviours to the NI
|
||||||
|
// => ownerConnection can be NetworkServer.localConnection if needed.
|
||||||
|
// => returns objects from client and from server.
|
||||||
|
// will be same in host mode.
|
||||||
|
protected void CreateNetworkedAndSpawn(
|
||||||
|
out GameObject serverGO, out NetworkIdentity serverIdentity, Action<NetworkIdentity> serverPreAwake,
|
||||||
|
out GameObject clientGO, out NetworkIdentity clientIdentity, Action<NetworkIdentity> clientPreAwake,
|
||||||
|
NetworkConnectionToClient ownerConnection = null)
|
||||||
|
{
|
||||||
|
// server & client need to be active before spawning
|
||||||
|
Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning.");
|
||||||
|
Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning.");
|
||||||
|
|
||||||
|
// create one on server, one on client
|
||||||
|
// (spawning has to find it on client, it doesn't create it)
|
||||||
|
CreateNetworked(out serverGO, out serverIdentity, serverPreAwake);
|
||||||
|
CreateNetworked(out clientGO, out clientIdentity, clientPreAwake);
|
||||||
|
|
||||||
|
// give both a scene id and register it on client for spawnables
|
||||||
|
clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverGO.GetHashCode();
|
||||||
|
NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity;
|
||||||
|
|
||||||
|
// spawn
|
||||||
|
NetworkServer.Spawn(serverGO, ownerConnection);
|
||||||
|
ProcessMessages();
|
||||||
|
|
||||||
|
// double check isServer/isClient. avoids debugging headaches.
|
||||||
|
Assert.That(serverIdentity.isServer, Is.True);
|
||||||
|
Assert.That(clientIdentity.isClient, Is.True);
|
||||||
|
|
||||||
|
// double check that we have authority if we passed an owner connection
|
||||||
|
if (ownerConnection != null)
|
||||||
|
Debug.Assert(clientIdentity.isOwned == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results");
|
||||||
|
|
||||||
|
// make sure the client really spawned it.
|
||||||
|
Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId));
|
||||||
|
}
|
||||||
// create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN
|
// create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN
|
||||||
// => ownerConnection can be NetworkServer.localConnection if needed.
|
// => ownerConnection can be NetworkServer.localConnection if needed.
|
||||||
protected void CreateNetworkedAndSpawn<T>(out GameObject go, out NetworkIdentity identity, out T component, NetworkConnectionToClient ownerConnection = null)
|
protected void CreateNetworkedAndSpawn<T>(out GameObject go, out NetworkIdentity identity, out T component, NetworkConnectionToClient ownerConnection = null)
|
||||||
|
@ -0,0 +1,223 @@
|
|||||||
|
// TODO: test despawning
|
||||||
|
// TODO: test non-player ni's
|
||||||
|
// TODO: test grid size changing at runtime
|
||||||
|
using System;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Mirror.Tests.InterestManagement
|
||||||
|
{
|
||||||
|
public class InterestManagementTests_FastSpatialHashing : MirrorEditModeTest
|
||||||
|
{
|
||||||
|
FastSpatialInterestManagement aoi;
|
||||||
|
|
||||||
|
protected NetworkIdentity CreateNI(Action<NetworkIdentity> prespawn = null)
|
||||||
|
{
|
||||||
|
CreateNetworked(out var gameObject, out var identity);
|
||||||
|
prespawn?.Invoke(identity);
|
||||||
|
NetworkServer.Spawn(gameObject);
|
||||||
|
return identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected NetworkIdentity CreatePlayerNI(int connectionId, Action<NetworkIdentity> prespawn = null)
|
||||||
|
{
|
||||||
|
CreateNetworked(out var gameObject, out var identity);
|
||||||
|
prespawn?.Invoke(identity);
|
||||||
|
NetworkConnectionToClient connection = new NetworkConnectionToClient(connectionId);
|
||||||
|
connection.isAuthenticated = true;
|
||||||
|
connection.identity = identity;
|
||||||
|
NetworkServer.connections.Add(connectionId, connection);
|
||||||
|
|
||||||
|
NetworkServer.Spawn(gameObject, connection);
|
||||||
|
NetworkServer.SetClientReady(connection); // AddPlayerForConnection also calls this!
|
||||||
|
return identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public override void SetUp()
|
||||||
|
{
|
||||||
|
base.SetUp();
|
||||||
|
// need to start server so that interest management works
|
||||||
|
NetworkServer.Listen(10);
|
||||||
|
|
||||||
|
aoi = holder.AddComponent<FastSpatialInterestManagement>();
|
||||||
|
aoi.visRange = 10;
|
||||||
|
aoi.rebuildInterval = -1; // rebuild every call :)
|
||||||
|
// setup server aoi since InterestManagement Awake isn't called
|
||||||
|
NetworkServer.aoi = aoi;
|
||||||
|
}
|
||||||
|
|
||||||
|
[TearDown]
|
||||||
|
public override void TearDown()
|
||||||
|
{
|
||||||
|
foreach (GameObject go in instantiated)
|
||||||
|
{
|
||||||
|
if (go.TryGetComponent(out NetworkIdentity ni))
|
||||||
|
{
|
||||||
|
// set isServer is false. otherwise Destroy instead of
|
||||||
|
// DestroyImmediate is called internally, giving an error in Editor
|
||||||
|
ni.isServer = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear connections first. calling OnDisconnect wouldn't work since
|
||||||
|
// we have no real clients.
|
||||||
|
NetworkServer.connections.Clear();
|
||||||
|
|
||||||
|
base.TearDown();
|
||||||
|
// clear server aoi again
|
||||||
|
NetworkServer.aoi = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertSelfVisible(NetworkIdentity id)
|
||||||
|
{
|
||||||
|
// identities ALWAYS see themselves, if they have a player
|
||||||
|
if (id.connectionToClient != null)
|
||||||
|
{
|
||||||
|
Assert.That(id.observers.ContainsKey(id.connectionToClient.connectionId), Is.True);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ForceHidden()
|
||||||
|
{
|
||||||
|
// A and B are at (0,0,0) so within range!
|
||||||
|
var a = CreatePlayerNI(1, ni => ni.visibility = Visibility.ForceHidden);
|
||||||
|
var b = CreatePlayerNI(2);
|
||||||
|
|
||||||
|
// no rebuild required here due to initial state :)
|
||||||
|
|
||||||
|
AssertSelfVisible(a);
|
||||||
|
AssertSelfVisible(b);
|
||||||
|
// A should not be seen by B because A is force hidden
|
||||||
|
Assert.That(a.observers.ContainsKey(b.connectionToClient.connectionId), Is.False);
|
||||||
|
// B should be seen by A
|
||||||
|
Assert.That(b.observers.ContainsKey(a.connectionToClient.connectionId), Is.True);
|
||||||
|
|
||||||
|
// If we now set a to default, and rebuild, they should both see each other!
|
||||||
|
a.visibility = Visibility.Default;
|
||||||
|
aoi.LateUpdate();
|
||||||
|
AssertSelfVisible(a);
|
||||||
|
AssertSelfVisible(b);
|
||||||
|
Assert.That(a.observers.ContainsKey(b.connectionToClient.connectionId), Is.True);
|
||||||
|
Assert.That(b.observers.ContainsKey(a.connectionToClient.connectionId), Is.True);
|
||||||
|
// If we now set both hidden, and rebuild, they both won't see each other!
|
||||||
|
a.visibility = Visibility.ForceHidden;
|
||||||
|
b.visibility = Visibility.ForceHidden;
|
||||||
|
aoi.LateUpdate();
|
||||||
|
AssertSelfVisible(a);
|
||||||
|
AssertSelfVisible(b);
|
||||||
|
Assert.That(a.observers.ContainsKey(b.connectionToClient.connectionId), Is.False);
|
||||||
|
Assert.That(b.observers.ContainsKey(a.connectionToClient.connectionId), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ForceShown()
|
||||||
|
{
|
||||||
|
// A and B are at (0,0,0) so within range!
|
||||||
|
var a = CreatePlayerNI(1, ni => ni.visibility = Visibility.ForceShown);
|
||||||
|
var b = CreatePlayerNI(2);
|
||||||
|
|
||||||
|
// no rebuild required here due to initial state :)
|
||||||
|
AssertSelfVisible(a);
|
||||||
|
AssertSelfVisible(b);
|
||||||
|
// A&B should see each other
|
||||||
|
Assert.That(a.observers.ContainsKey(b.connectionToClient.connectionId), Is.True);
|
||||||
|
Assert.That(b.observers.ContainsKey(a.connectionToClient.connectionId), Is.True);
|
||||||
|
aoi.LateUpdate();
|
||||||
|
// rebuild doesnt change that
|
||||||
|
// no rebuild required here due to initial state :)
|
||||||
|
AssertSelfVisible(a);
|
||||||
|
AssertSelfVisible(b);
|
||||||
|
// A&B should see each other
|
||||||
|
Assert.That(a.observers.ContainsKey(b.connectionToClient.connectionId), Is.True);
|
||||||
|
Assert.That(b.observers.ContainsKey(a.connectionToClient.connectionId), Is.True);
|
||||||
|
|
||||||
|
// If we now move A out of range of B
|
||||||
|
a.transform.position = new Vector3(aoi.visRange * 100, 0, 0);
|
||||||
|
aoi.LateUpdate();
|
||||||
|
AssertSelfVisible(a);
|
||||||
|
AssertSelfVisible(b);
|
||||||
|
|
||||||
|
// a will be seen by B still
|
||||||
|
Assert.That(a.observers.ContainsKey(b.connectionToClient.connectionId), Is.True);
|
||||||
|
// But B is out of range of A
|
||||||
|
Assert.That(b.observers.ContainsKey(a.connectionToClient.connectionId), Is.False);
|
||||||
|
|
||||||
|
|
||||||
|
// B to ForceShown:
|
||||||
|
b.visibility = Visibility.ForceShown;
|
||||||
|
aoi.LateUpdate();
|
||||||
|
AssertSelfVisible(a);
|
||||||
|
AssertSelfVisible(b);
|
||||||
|
// A&B should see each other
|
||||||
|
Assert.That(a.observers.ContainsKey(b.connectionToClient.connectionId), Is.True);
|
||||||
|
Assert.That(b.observers.ContainsKey(a.connectionToClient.connectionId), Is.True);
|
||||||
|
// A&B to default
|
||||||
|
a.visibility = Visibility.Default;
|
||||||
|
b.visibility = Visibility.Default;
|
||||||
|
aoi.LateUpdate();
|
||||||
|
AssertSelfVisible(a);
|
||||||
|
AssertSelfVisible(b);
|
||||||
|
// and they can't see each other anymore
|
||||||
|
Assert.That(a.observers.ContainsKey(b.connectionToClient.connectionId), Is.False);
|
||||||
|
Assert.That(b.observers.ContainsKey(a.connectionToClient.connectionId), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void InRangeInitial_To_OutRange()
|
||||||
|
{
|
||||||
|
// A and B are at (0,0,0) so within range!
|
||||||
|
var a = CreatePlayerNI(1);
|
||||||
|
var b = CreatePlayerNI(2);
|
||||||
|
AssertSelfVisible(a);
|
||||||
|
AssertSelfVisible(b);
|
||||||
|
// both should see each other because they are in range
|
||||||
|
Assert.That(a.observers.ContainsKey(b.connectionToClient.connectionId), Is.True);
|
||||||
|
Assert.That(b.observers.ContainsKey(a.connectionToClient.connectionId), Is.True);
|
||||||
|
// update won't change that
|
||||||
|
aoi.LateUpdate();
|
||||||
|
AssertSelfVisible(a);
|
||||||
|
AssertSelfVisible(b);
|
||||||
|
Assert.That(a.observers.ContainsKey(b.connectionToClient.connectionId), Is.True);
|
||||||
|
Assert.That(b.observers.ContainsKey(a.connectionToClient.connectionId), Is.True);
|
||||||
|
// move out of range
|
||||||
|
a.transform.position = new Vector3(aoi.visRange * 100, 0, 0);
|
||||||
|
aoi.LateUpdate();
|
||||||
|
AssertSelfVisible(a);
|
||||||
|
AssertSelfVisible(b);
|
||||||
|
// and they'll see not each other anymore
|
||||||
|
Assert.That(a.observers.ContainsKey(b.connectionToClient.connectionId), Is.False);
|
||||||
|
Assert.That(b.observers.ContainsKey(a.connectionToClient.connectionId), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OutRangeInitial_To_InRange()
|
||||||
|
{
|
||||||
|
// A and B are not in range
|
||||||
|
var a = CreatePlayerNI(1,
|
||||||
|
ni => ni.transform.position = new Vector3(aoi.visRange * 100, 0, 0));
|
||||||
|
var b = CreatePlayerNI(2);
|
||||||
|
|
||||||
|
AssertSelfVisible(a);
|
||||||
|
AssertSelfVisible(b);
|
||||||
|
// both should not see each other because they aren't in range
|
||||||
|
Assert.That(a.observers.ContainsKey(b.connectionToClient.connectionId), Is.False);
|
||||||
|
Assert.That(b.observers.ContainsKey(a.connectionToClient.connectionId), Is.False);
|
||||||
|
aoi.LateUpdate();
|
||||||
|
AssertSelfVisible(a);
|
||||||
|
AssertSelfVisible(b);
|
||||||
|
// update won't change that
|
||||||
|
Assert.That(a.observers.ContainsKey(b.connectionToClient.connectionId), Is.False);
|
||||||
|
Assert.That(b.observers.ContainsKey(a.connectionToClient.connectionId), Is.False);
|
||||||
|
// move into range
|
||||||
|
a.transform.position = Vector3.zero;
|
||||||
|
aoi.LateUpdate();
|
||||||
|
AssertSelfVisible(a);
|
||||||
|
AssertSelfVisible(b);
|
||||||
|
// and they'll see each other
|
||||||
|
Assert.That(a.observers.ContainsKey(b.connectionToClient.connectionId), Is.True);
|
||||||
|
Assert.That(b.observers.ContainsKey(a.connectionToClient.connectionId), Is.True);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d74fcd64861c40f8856d3479137f49f2
|
||||||
|
timeCreated: 1676129959
|
@ -1,4 +1,5 @@
|
|||||||
// OnDe/SerializeSafely tests.
|
// OnDe/SerializeSafely tests.
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Mirror.Tests.EditorBehaviours.NetworkIdentities;
|
using Mirror.Tests.EditorBehaviours.NetworkIdentities;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -69,6 +70,123 @@ public void SerializeAndDeserializeAll()
|
|||||||
Assert.That(clientObserversComp.value, Is.EqualTo(42)); // observers mode should be in data
|
Assert.That(clientObserversComp.value, Is.EqualTo(42)); // observers mode should be in data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test serialize -> deserialize of any supported number of components
|
||||||
|
[Test]
|
||||||
|
public void SerializeAndDeserializeN([NUnit.Framework.Range(1, 64)] int numberOfNBs)
|
||||||
|
{
|
||||||
|
List<SerializeTest1NetworkBehaviour> serverNBs = new List<SerializeTest1NetworkBehaviour>();
|
||||||
|
List<SerializeTest1NetworkBehaviour> clientNBs = new List<SerializeTest1NetworkBehaviour>();
|
||||||
|
// need two of both versions so we can serialize -> deserialize
|
||||||
|
CreateNetworkedAndSpawn(
|
||||||
|
out _, out NetworkIdentity serverIdentity, ni =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numberOfNBs; i++)
|
||||||
|
{
|
||||||
|
SerializeTest1NetworkBehaviour nb = ni.gameObject.AddComponent<SerializeTest1NetworkBehaviour>();
|
||||||
|
nb.syncInterval = 0;
|
||||||
|
nb.syncMode = SyncMode.Observers;
|
||||||
|
serverNBs.Add(nb);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
out _, out NetworkIdentity clientIdentity, ni =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numberOfNBs; i++)
|
||||||
|
{
|
||||||
|
SerializeTest1NetworkBehaviour nb = ni.gameObject.AddComponent<SerializeTest1NetworkBehaviour>();
|
||||||
|
nb.syncInterval = 0;
|
||||||
|
nb.syncMode = SyncMode.Observers;
|
||||||
|
clientNBs.Add(nb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// INITIAL SYNC
|
||||||
|
// set unique values on server components
|
||||||
|
for (int i = 0; i < serverNBs.Count; i++)
|
||||||
|
{
|
||||||
|
serverNBs[i].value = (i + 1) * 3;
|
||||||
|
serverNBs[i].SetDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize server object
|
||||||
|
serverIdentity.SerializeServer(true, ownerWriter, observersWriter);
|
||||||
|
|
||||||
|
// deserialize client object with OBSERVERS payload
|
||||||
|
NetworkReader reader = new NetworkReader(observersWriter.ToArray());
|
||||||
|
clientIdentity.DeserializeClient(reader, true);
|
||||||
|
for (int i = 0; i < clientNBs.Count; i++)
|
||||||
|
{
|
||||||
|
int expected = (i + 1) * 3;
|
||||||
|
Assert.That(clientNBs[i].value, Is.EqualTo(expected), $"Expected the clientNBs[{i}] to have a value of {expected}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear dirty bits for incremental sync
|
||||||
|
foreach (SerializeTest1NetworkBehaviour serverNB in serverNBs)
|
||||||
|
serverNB.ClearAllDirtyBits();
|
||||||
|
|
||||||
|
// INCREMENTAL SYNC ALL
|
||||||
|
// set unique values on server components
|
||||||
|
for (int i = 0; i < serverNBs.Count; i++)
|
||||||
|
{
|
||||||
|
serverNBs[i].value = (i + 1) * 11;
|
||||||
|
serverNBs[i].SetDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
ownerWriter.Reset();
|
||||||
|
observersWriter.Reset();
|
||||||
|
// serialize server object
|
||||||
|
serverIdentity.SerializeServer(false, ownerWriter, observersWriter);
|
||||||
|
|
||||||
|
// deserialize client object with OBSERVERS payload
|
||||||
|
reader = new NetworkReader(observersWriter.ToArray());
|
||||||
|
clientIdentity.DeserializeClient(reader, false);
|
||||||
|
for (int i = 0; i < clientNBs.Count; i++)
|
||||||
|
{
|
||||||
|
int expected = (i + 1) * 11;
|
||||||
|
Assert.That(clientNBs[i].value, Is.EqualTo(expected), $"Expected the clientNBs[{i}] to have a value of {expected}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear dirty bits for incremental sync
|
||||||
|
foreach (SerializeTest1NetworkBehaviour serverNB in serverNBs)
|
||||||
|
serverNB.ClearAllDirtyBits();
|
||||||
|
|
||||||
|
// INCREMENTAL SYNC INDIVIDUAL
|
||||||
|
for (int i = 0; i < numberOfNBs; i++)
|
||||||
|
{
|
||||||
|
// reset all client nbs
|
||||||
|
foreach (SerializeTest1NetworkBehaviour clientNB in clientNBs)
|
||||||
|
clientNB.value = 0;
|
||||||
|
|
||||||
|
int expected = (i + 1) * 7;
|
||||||
|
|
||||||
|
// set unique value on server components
|
||||||
|
serverNBs[i].value = expected;
|
||||||
|
serverNBs[i].SetDirty();
|
||||||
|
|
||||||
|
ownerWriter.Reset();
|
||||||
|
observersWriter.Reset();
|
||||||
|
// serialize server object
|
||||||
|
serverIdentity.SerializeServer(false, ownerWriter, observersWriter);
|
||||||
|
|
||||||
|
// deserialize client object with OBSERVERS payload
|
||||||
|
reader = new NetworkReader(observersWriter.ToArray());
|
||||||
|
clientIdentity.DeserializeClient(reader, false);
|
||||||
|
for (int index = 0; index < clientNBs.Count; index++)
|
||||||
|
{
|
||||||
|
SerializeTest1NetworkBehaviour clientNB = clientNBs[index];
|
||||||
|
if (index == i)
|
||||||
|
{
|
||||||
|
Assert.That(clientNB.value, Is.EqualTo(expected), $"Expected the clientNBs[{index}] to have a value of {expected}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.That(clientNB.value, Is.EqualTo(0), $"Expected the clientNBs[{index}] to have a value of 0 since we're not syncing that index (on sync of #{i})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// serialization should work even if a component throws an exception.
|
// serialization should work even if a component throws an exception.
|
||||||
// so if first component throws, second should still be serialized fine.
|
// so if first component throws, second should still be serialized fine.
|
||||||
[Test]
|
[Test]
|
||||||
|
Loading…
Reference in New Issue
Block a user