Compare commits

...

11 Commits

Author SHA1 Message Date
MrGadget
5aeeb12fa7
Merge 376a733386 into 1187a59b18 2024-11-10 17:34:32 +05:00
Robin Rolf
1187a59b18
fix: NetworkIdentity component bitmask shifting overflows (#3941)
Some checks failed
Main / Run Unity Tests (push) Has been cancelled
Main / Delete Old Workflow Runs (push) Has been cancelled
Main / Semantic Release (push) Has been cancelled
* Failing test for netid bitmask shifting

* Fix netid bitmask shifting typing

Otherwise it uses ints and overflows when shifting more than 32
2024-11-09 17:45:35 +01:00
mischa
499e4daea3
feat: NetworkTransformHybrid - Hybrid Sync Part 1 (#3937)
Some checks failed
Main / Run Unity Tests (push) Has been cancelled
Main / Delete Old Workflow Runs (push) Has been cancelled
Main / Semantic Release (push) Has been cancelled
* hybrid nt

* fix mrg grid issue; fix unreliable sending just because baseline changed

* comment onserialize baseline

* Update Assets/Mirror/Components/NetworkTransform/NetworkTransformHybrid2022.cs

Co-authored-by: MrGadget <9826063+MrGadget1024@users.noreply.github.com>

* Update Assets/Mirror/Components/NetworkTransform/NetworkTransformHybrid2022.cs

Co-authored-by: MrGadget <9826063+MrGadget1024@users.noreply.github.com>

* Update Assets/Mirror/Components/NetworkTransform/NetworkTransformHybrid2022.cs

Co-authored-by: MrGadget <9826063+MrGadget1024@users.noreply.github.com>

* Update Assets/Mirror/Components/NetworkTransform/NetworkTransformHybrid2022.cs

Co-authored-by: MrGadget <9826063+MrGadget1024@users.noreply.github.com>

* Update Assets/Mirror/Components/NetworkTransform/NetworkTransformHybrid2022.cs

Co-authored-by: MrGadget <9826063+MrGadget1024@users.noreply.github.com>

* Update Assets/Mirror/Components/NetworkTransform/NetworkTransformHybrid2022.cs

Co-authored-by: MrGadget <9826063+MrGadget1024@users.noreply.github.com>

* nthybrid: debug draw data points

* debug draw: drops

* nthybrid: OnServerToClient checks for host mode first to avoid noise!

* nthybrid: OnClientToServer check ordering

* fix: don't apply any hybrid rpcs in host mode, fixes overwriting client's data points

* icon

* syntax

* don't pass Vector3? and QUaternion?

* remove unused

* comments

* cleanup

* cleanups

* comments

* cleanup: remove ContructSnapshot

* syncScale

* comment

* remove custom change

---------

Co-authored-by: MrGadget <9826063+MrGadget1024@users.noreply.github.com>
2024-11-07 17:59:34 +01:00
MrGadget
376a733386 Revert change to CalculateAngularVelocity
Quaternion doesn't have `normalize`
2024-04-15 12:24:49 -04:00
MrGadget
25b6366cc0 Merged master 2024-04-15 12:19:09 -04:00
MrGadget
c4d138aaa0
Update Assets/Mirror/Components/NetworkTransform/NetworkTransformBase.cs
Co-authored-by: mischa <16416509+miwarnec@users.noreply.github.com>
2024-04-15 11:19:40 -04:00
MrGadget
e93e2eae4d Better CalculateAngularVelocity 2024-04-10 12:54:57 -04:00
MrGadget
80f2d1e641 Use GetPositionAndRotation 2024-04-10 11:03:50 -04:00
MrGadget
f8a4e1150f Ensure target is not null 2024-04-10 08:47:03 -04:00
MrGadget
12ffdae3eb virtuals and overrides 2024-04-10 08:33:19 -04:00
MrGadget
bab41931ee feat(NetworkTransformBase): Add Velocity and AngularVelocity 2024-04-10 08:28:33 -04:00
8 changed files with 1758 additions and 15 deletions

View File

@ -156,6 +156,54 @@ protected virtual void Awake()
Configure(); Configure();
} }
// These are in global coordinates for calculating velocity and angular velocity.
Vector3 lastPosition;
Quaternion lastRotation;
public Vector3 velocity { get; internal set; } = Vector3.zero;
public Vector3 angularVelocity { get; internal set; } = Vector3.zero;
protected virtual void Start()
{
// set target to self if none yet
if (target == null) target = transform;
// Set last position and rotation to current values
// so we can calculate velocity and angular velocity.
target.GetPositionAndRotation(out lastPosition, out lastRotation);
}
protected virtual void Update()
{
// set target to self if none yet
if (target == null) target = transform;
// Use global coordinates for velocity and angular velocity.
target.GetPositionAndRotation(out Vector3 pos, out Quaternion rot);
// Update velocity and angular velocity
velocity = (pos - lastPosition) / Time.deltaTime;
CalculateAngularVelocity(rot);
// Update last position and rotation
lastPosition = pos;
lastRotation = rot;
}
void CalculateAngularVelocity(Quaternion currentRot)
{
// calculate angle between two rotations
Quaternion deltaRotation = currentRot * Quaternion.Inverse(lastRotation);
//Quaternion deltaRotation = (currentRot * Quaternion.Inverse(lastRotation)).normalize;
// convert to angle axis
deltaRotation.ToAngleAxis(out float angle, out Vector3 axis);
// we assume the angle is always the shortest path
// we don't need to check for 360 degree rotations
angularVelocity = axis * angle * Mathf.Deg2Rad / Time.deltaTime;
}
// snapshot functions ////////////////////////////////////////////////// // snapshot functions //////////////////////////////////////////////////
// get local/world position // get local/world position
protected virtual Vector3 GetPosition() => protected virtual Vector3 GetPosition() =>
@ -414,6 +462,16 @@ public virtual void ResetState()
// so let's clear the buffers. // so let's clear the buffers.
serverSnapshots.Clear(); serverSnapshots.Clear();
clientSnapshots.Clear(); clientSnapshots.Clear();
// set target to self if none yet
if (target == null) target = transform;
// Reset last position and rotation
target.GetPositionAndRotation(out lastPosition, out lastRotation);
// Reset velocity / angular velocity
velocity = Vector3.zero;
angularVelocity = Vector3.zero;
} }
public virtual void Reset() public virtual void Reset()

File diff suppressed because it is too large Load Diff

View File

@ -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:

View File

@ -43,13 +43,15 @@ public class NetworkTransformReliable : NetworkTransformBase
protected TransformSnapshot last; protected TransformSnapshot last;
// update ////////////////////////////////////////////////////////////// // update //////////////////////////////////////////////////////////////
void Update() protected override void Update()
{ {
// if server then always sync to others. // if server then always sync to others.
if (isServer) UpdateServer(); if (isServer) UpdateServer();
// 'else if' because host mode shouldn't send anything to server. // 'else if' because host mode shouldn't send anything to server.
// it is the server. don't overwrite anything there. // it is the server. don't overwrite anything there.
else if (isClient) UpdateClient(); else if (isClient) UpdateClient();
base.Update();
} }
void LateUpdate() void LateUpdate()

View File

@ -27,13 +27,15 @@ public class NetworkTransformUnreliable : NetworkTransformBase
// update ////////////////////////////////////////////////////////////// // update //////////////////////////////////////////////////////////////
// Update applies interpolation // Update applies interpolation
void Update() protected override void Update()
{ {
if (isServer) UpdateServerInterpolation(); if (isServer) UpdateServerInterpolation();
// for all other clients (and for local player if !authority), // for all other clients (and for local player if !authority),
// we need to apply snapshots from the buffer. // we need to apply snapshots from the buffer.
// 'else if' because host mode shouldn't interpolate client // 'else if' because host mode shouldn't interpolate client
else if (isClient && !IsClientWithAuthority) UpdateClientInterpolation(); else if (isClient && !IsClientWithAuthority) UpdateClientInterpolation();
base.Update();
} }
// LateUpdate broadcasts. // LateUpdate broadcasts.

View File

@ -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;
} }

View File

@ -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)

View File

@ -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;
@ -42,7 +43,7 @@ public void SerializeAndDeserializeAll()
); );
// set sync modes // set sync modes
serverOwnerComp.syncMode = clientOwnerComp.syncMode = SyncMode.Owner; serverOwnerComp.syncMode = clientOwnerComp.syncMode = SyncMode.Owner;
serverObserversComp.syncMode = clientObserversComp.syncMode = SyncMode.Observers; serverObserversComp.syncMode = clientObserversComp.syncMode = SyncMode.Observers;
// set unique values on server components // set unique values on server components
@ -65,10 +66,127 @@ public void SerializeAndDeserializeAll()
// deserialize client object with OBSERVERS payload // deserialize client object with OBSERVERS payload
reader = new NetworkReader(observersWriter.ToArray()); reader = new NetworkReader(observersWriter.ToArray());
clientIdentity.DeserializeClient(reader, true); clientIdentity.DeserializeClient(reader, true);
Assert.That(clientOwnerComp.value, Is.EqualTo(null)); // owner mode shouldn't be in data Assert.That(clientOwnerComp.value, Is.EqualTo(null)); // owner mode shouldn't be in data
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]
@ -150,20 +268,20 @@ public void TooManyComponents()
public void ErrorCorrection() public void ErrorCorrection()
{ {
int original = 0x12345678; int original = 0x12345678;
byte safety = 0x78; // last byte byte safety = 0x78; // last byte
// correct size shouldn't be corrected // correct size shouldn't be corrected
Assert.That(NetworkBehaviour.ErrorCorrection(original + 0, safety), Is.EqualTo(original)); Assert.That(NetworkBehaviour.ErrorCorrection(original + 0, safety), Is.EqualTo(original));
// read a little too much // read a little too much
Assert.That(NetworkBehaviour.ErrorCorrection(original + 1, safety), Is.EqualTo(original)); Assert.That(NetworkBehaviour.ErrorCorrection(original + 1, safety), Is.EqualTo(original));
Assert.That(NetworkBehaviour.ErrorCorrection(original + 2, safety), Is.EqualTo(original)); Assert.That(NetworkBehaviour.ErrorCorrection(original + 2, safety), Is.EqualTo(original));
Assert.That(NetworkBehaviour.ErrorCorrection(original + 42, safety), Is.EqualTo(original)); Assert.That(NetworkBehaviour.ErrorCorrection(original + 42, safety), Is.EqualTo(original));
// read a little too less // read a little too less
Assert.That(NetworkBehaviour.ErrorCorrection(original - 1, safety), Is.EqualTo(original)); Assert.That(NetworkBehaviour.ErrorCorrection(original - 1, safety), Is.EqualTo(original));
Assert.That(NetworkBehaviour.ErrorCorrection(original - 2, safety), Is.EqualTo(original)); Assert.That(NetworkBehaviour.ErrorCorrection(original - 2, safety), Is.EqualTo(original));
Assert.That(NetworkBehaviour.ErrorCorrection(original - 42, safety), Is.EqualTo(original)); Assert.That(NetworkBehaviour.ErrorCorrection(original - 42, safety), Is.EqualTo(original));
// reading way too much / less is expected to fail. // reading way too much / less is expected to fail.
// we can only correct the last byte, not more. // we can only correct the last byte, not more.