-
Notifications
You must be signed in to change notification settings - Fork 450
feat: add NetworkObject
and NetworkBehaviour
reference types
#1173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
1a63b90
feat: make networkobjectref not stateful
LukeStampfli b5e5465
fix docs
LukeStampfli 3ab7a7c
standards.py “¯\_(ツ)_/¯“
LukeStampfli b4d70a2
standards.py
LukeStampfli 088c6ea
refactor: standards.py now applied correctly
LukeStampfli 3e0fbd3
Merge remote-tracking branch 'origin/develop' into feature/networkobj…
LukeStampfli 6791a5c
Tests for implict operators and NetworkVariable
LukeStampfli e091e18
Merge remote-tracking branch 'origin/develop' into feature/networkobj…
LukeStampfli 6ad32ba
Merge branch 'develop' into feature/networkobject-ref
0xFA11 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
103 changes: 103 additions & 0 deletions
103
com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
|
||
namespace Unity.Netcode | ||
{ | ||
/// <summary> | ||
/// A helper struct for serializing <see cref="NetworkBehaviour"/>s over the network. Can be used in RPCs and <see cref="NetworkVariable{T}"/>. | ||
/// Note: network ids get recycled by the NetworkManager after a while. So a reference pointing to | ||
/// </summary> | ||
public struct NetworkBehaviourReference : INetworkSerializable, IEquatable<NetworkBehaviourReference> | ||
{ | ||
private NetworkObjectReference m_NetworkObjectReference; | ||
private ushort m_NetworkBehaviourId; | ||
|
||
/// <summary> | ||
/// Creates a new instance of the <see cref="NetworkBehaviourReference{T}"/> struct. | ||
/// </summary> | ||
/// <param name="networkBehaviour">The <see cref="NetworkBehaviour"/> to reference.</param> | ||
/// <exception cref="ArgumentException"></exception> | ||
public NetworkBehaviourReference(NetworkBehaviour networkBehaviour) | ||
{ | ||
if (networkBehaviour == null) | ||
{ | ||
throw new ArgumentNullException(nameof(networkBehaviour)); | ||
} | ||
if (networkBehaviour.NetworkObject == null) | ||
{ | ||
throw new ArgumentException($"Cannot create {nameof(NetworkBehaviourReference)} from {nameof(NetworkBehaviour)} without a {nameof(NetworkObject)}."); | ||
} | ||
|
||
m_NetworkObjectReference = networkBehaviour.NetworkObject; | ||
m_NetworkBehaviourId = networkBehaviour.NetworkBehaviourId; | ||
} | ||
|
||
/// <summary> | ||
/// Tries to get the <see cref="NetworkBehaviour"/> referenced by this reference. | ||
/// </summary> | ||
/// <param name="networkBehaviour">The <see cref="NetworkBehaviour"/> which was found. Null if the corresponding <see cref="NetworkObject"/> was not found.</param> | ||
/// <param name="networkManager">The networkmanager. Uses <see cref="NetworkManager.Singleton"/> to resolve if null.</param> | ||
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns> | ||
public bool TryGet(out NetworkBehaviour networkBehaviour, NetworkManager networkManager = null) | ||
{ | ||
networkBehaviour = GetInternal(this, null); | ||
return networkBehaviour != null; | ||
} | ||
|
||
/// <summary> | ||
/// Tries to get the <see cref="NetworkBehaviour"/> referenced by this reference. | ||
/// </summary> | ||
/// <param name="networkBehaviour">The <see cref="NetworkBehaviour"/> which was found. Null if the corresponding <see cref="NetworkObject"/> was not found.</param> | ||
/// <param name="networkManager">The networkmanager. Uses <see cref="NetworkManager.Singleton"/> to resolve if null.</param> | ||
/// <typeparam name="T">The type of the networkBehaviour for convenience.</typeparam> | ||
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns> | ||
public bool TryGet<T>(out T networkBehaviour, NetworkManager networkManager = null) where T : NetworkBehaviour | ||
{ | ||
networkBehaviour = (T)GetInternal(this, null); | ||
return networkBehaviour != null; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void NetworkSerialize(NetworkSerializer serializer) | ||
{ | ||
m_NetworkObjectReference.NetworkSerialize(serializer); | ||
serializer.Serialize(ref m_NetworkBehaviourId); | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
private static NetworkBehaviour GetInternal(NetworkBehaviourReference networkBehaviourRef, NetworkManager networkManager = null) | ||
{ | ||
if (networkBehaviourRef.m_NetworkObjectReference.TryGet(out NetworkObject networkObject, networkManager)) | ||
{ | ||
return networkObject.GetNetworkBehaviourAtOrderIndex(networkBehaviourRef.m_NetworkBehaviourId); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public bool Equals(NetworkBehaviourReference other) | ||
{ | ||
return m_NetworkObjectReference.Equals(other.m_NetworkObjectReference) && m_NetworkBehaviourId == other.m_NetworkBehaviourId; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override bool Equals(object obj) | ||
{ | ||
return obj is NetworkBehaviourReference other && Equals(other); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override int GetHashCode() | ||
{ | ||
unchecked | ||
{ | ||
return (m_NetworkObjectReference.GetHashCode() * 397) ^ m_NetworkBehaviourId.GetHashCode(); | ||
} | ||
} | ||
|
||
public static implicit operator NetworkBehaviour(NetworkBehaviourReference networkBehaviourRef) => GetInternal(networkBehaviourRef); | ||
|
||
public static implicit operator NetworkBehaviourReference(NetworkBehaviour networkBehaviour) => new NetworkBehaviourReference(networkBehaviour); | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
131 changes: 131 additions & 0 deletions
131
com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
using UnityEngine; | ||
|
||
namespace Unity.Netcode | ||
{ | ||
/// <summary> | ||
/// A helper struct for serializing <see cref="NetworkObject"/>s over the network. Can be used in RPCs and <see cref="NetworkVariable{T}"/>. | ||
/// </summary> | ||
public struct NetworkObjectReference : INetworkSerializable, IEquatable<NetworkObjectReference> | ||
{ | ||
private ulong m_NetworkObjectId; | ||
|
||
/// <summary> | ||
/// The <see cref="NetworkObject.NetworkObjectId"/> of the referenced <see cref="NetworkObject"/>. | ||
/// </summary> | ||
public ulong NetworkObjectId | ||
{ | ||
get => m_NetworkObjectId; | ||
internal set => m_NetworkObjectId = value; | ||
} | ||
|
||
/// <summary> | ||
/// Creates a new instance of the <see cref="NetworkObjectReference"/> struct. | ||
/// </summary> | ||
/// <param name="networkObject">The <see cref="NetworkObject"/> to reference.</param> | ||
/// <exception cref="ArgumentNullException"></exception> | ||
/// <exception cref="ArgumentException"></exception> | ||
public NetworkObjectReference(NetworkObject networkObject) | ||
{ | ||
if (networkObject == null) | ||
{ | ||
throw new ArgumentNullException(nameof(networkObject)); | ||
} | ||
|
||
if (networkObject.IsSpawned == false) | ||
{ | ||
throw new ArgumentException($"{nameof(NetworkObjectReference)} can only be created from spawned {nameof(NetworkObject)}s."); | ||
} | ||
|
||
m_NetworkObjectId = networkObject.NetworkObjectId; | ||
} | ||
|
||
/// <summary> | ||
/// Creates a new instance of the <see cref="NetworkObjectReference"/> struct. | ||
/// </summary> | ||
/// <param name="gameObject">The GameObject from which the <see cref="NetworkObject"/> component will be referenced.</param> | ||
/// <exception cref="ArgumentNullException"></exception> | ||
/// <exception cref="ArgumentException"></exception> | ||
public NetworkObjectReference(GameObject gameObject) | ||
{ | ||
if (gameObject == null) | ||
{ | ||
throw new ArgumentNullException(nameof(gameObject)); | ||
} | ||
|
||
var networkObject = gameObject.GetComponent<NetworkObject>(); | ||
|
||
if (networkObject == null) | ||
{ | ||
throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component."); | ||
} | ||
|
||
if (networkObject.IsSpawned == false) | ||
{ | ||
throw new ArgumentException($"{nameof(NetworkObjectReference)} can only be created from spawned {nameof(NetworkObject)}s."); | ||
} | ||
|
||
m_NetworkObjectId = networkObject.NetworkObjectId; | ||
} | ||
|
||
/// <summary> | ||
/// Tries to get the <see cref="NetworkObject"/> referenced by this reference. | ||
/// </summary> | ||
/// <param name="networkObject">The <see cref="NetworkObject"/> which was found. Null if no object was found.</param> | ||
/// <param name="networkManager">The networkmanager. Uses <see cref="NetworkManager.Singleton"/> to resolve if null.</param> | ||
/// <returns>True if the <see cref="NetworkObject"/> was found; False if the <see cref="NetworkObject"/> was not found. This can happen if the <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns> | ||
public bool TryGet(out NetworkObject networkObject, NetworkManager networkManager = null) | ||
{ | ||
networkObject = Resolve(this, networkManager); | ||
return networkObject != null; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void NetworkSerialize(NetworkSerializer serializer) | ||
{ | ||
serializer.Serialize(ref m_NetworkObjectId); | ||
} | ||
|
||
/// <summary> | ||
/// Resolves the corresponding <see cref="NetworkObject"/> for this reference. | ||
/// </summary> | ||
/// <param name="networkObjectRef">The reference.</param> | ||
/// <param name="networkManager">The networkmanager. Uses <see cref="NetworkManager.Singleton"/> to resolve if null.</param> | ||
/// <returns>The resolves <see cref="NetworkObject"/>. Returns null if the networkobject was not found</returns> | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
private static NetworkObject Resolve(NetworkObjectReference networkObjectRef, NetworkManager networkManager = null) | ||
{ | ||
networkManager = networkManager != null ? networkManager : NetworkManager.Singleton; | ||
networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectRef.m_NetworkObjectId, out NetworkObject networkObject); | ||
|
||
return networkObject; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public bool Equals(NetworkObjectReference other) | ||
{ | ||
return m_NetworkObjectId == other.m_NetworkObjectId; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override bool Equals(object obj) | ||
{ | ||
return obj is NetworkObjectReference other && Equals(other); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override int GetHashCode() | ||
{ | ||
return m_NetworkObjectId.GetHashCode(); | ||
} | ||
|
||
public static implicit operator NetworkObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef); | ||
|
||
public static implicit operator NetworkObjectReference(NetworkObject networkObject) => new NetworkObjectReference(networkObject); | ||
|
||
public static implicit operator GameObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef).gameObject; | ||
|
||
public static implicit operator NetworkObjectReference(GameObject gameObject) => new NetworkObjectReference(gameObject); | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
8 changes: 8 additions & 0 deletions
8
com.unity.netcode.gameobjects/Tests/Runtime/Serialization.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrt:
I think this being a struct, value type and let's say documented with its proper usage etc would be good enough IMHO.
Maybe exposing internal
m_NetworkBehaviourId
over a public get-only ulong property would give more clues about this thing is actually working inside (+ maybe another TryGet API that accepts that ID too?).To be fair, I'd like people to use this like:
MyServerRpc(new NetworkBehaviourReference(MyNetworkBehaviour));
etc (you get the idea).long story short, I wouldn't worry about it too much :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also what about super-generic
GameObject
one where we try to getNetworkObject
orNetworkBehaviour
component from it to serialize over the network? 👀There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can actually do
MyServerRpc(MyNetworkBehaviour)
and the implicit operator will make it work automagically as long as the parameter on the RPC function itself uses the reference.Regarding the GameObject one. I think taking a
NetworkBehaviour
from it would be a bit too far fetched so I'm afraid people might get confused as to why they can send someGameObjectReference
s and not others.NetworkObjectReference
also has implicit operators from/toGameObject
so you can pass an GameObject into an RPC with aNetworkObjectReference
parameter.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh this implicit operator, just saw this :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with your second point too!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually, what about adding a few other RPCs for these implicit operators into tests you implemented in this PR?
that way, they'll be tested + will be good sample code as well — what do you think?