-
Notifications
You must be signed in to change notification settings - Fork 450
feat: NetworkObject Parenting #855
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
Conversation
if (!NetworkManager.IsServer) | ||
{ | ||
transform.parent = m_CachedParent; | ||
Debug.LogException(new NotServerException($"Only the server can reparent {nameof(NetworkObject)}s")); |
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.
how would this work with a client authoritative NetworkTransform?
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.
NetworkTransform
is a NetworkBehaviour
and a component — similarly, NetworkBehaviour
conceptually a component to NetworkObject
. So, in this context, we're talking about reparenting a NetworkObject
, not a NetworkTransform
or anything.
NetworkTransform
could still replicate its state (pos,rot,sca etc.) however it wants. If it requires a parental change, it has to ask server, more specifically NetworkObject
to change its parent.
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.
mmm so the NetworkTransform has to wait a full RTT for the server to confirm the parent change?
if my character is jumping on the back of a moving predicted car, I wouldn't be able to use parenting client side until RTT ms? I'd have a full RTT where my character would jitter around on the car until parenting is activated? that's not good.
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 could have 2 NetworkObject
s and enable one, disable the other and keep going without any issues and potentially even without any parenting during the transition.
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.
So if I have 100 players and 100 cars, each car would need 100 disabled players as children on them in case the player jumps on them? And I'd need to copy my player state to each of these cars?
That doesn't seem very users first :(
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 can say, it's not in our scope or in any of our acceptance criteria etc.
- you're explaining a specific scenario we may or may not support (which could be a good user-need to start with)
- there is no authority model in MLAPI other than Ownership, except the ad-hoc
NetworkTransform
internal one — and I'm hesitant to expand the scope of this feature/delivery.
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.
MLAPI has supported client authority for a while though. It's not formalized like we're aiming to do it, but it's still supported. Dropping that support is kind of a big deal.
I'm guessing since you're arguing against it there's a good technical reason for not supporting client authority in that reparenting case?
To be noted, I'm not talking about shared authority, more about owner authority, where only an owner could change the parent, but that change could come either from the client or from the server. I understand with shared authority it can become a mess very fast, but would it be that complex to allow clients changing parenting 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.
MLAPI has supported client authority for a while though.
where specifically?
Dropping that support is kind of a big deal.
checkout the ownership code, look into NetworkObject::ChangeOwnership(ulong newOwnerClientId)
API.
following your definition, MLAPI already dropped client authority long before then.
I'm guessing since you're arguing against it there's a good technical reason for not supporting client authority in that reparenting case?
I'm not arguing against anything here. I'm double-checking your assumptions and understandings.
To be noted, I'm not talking about shared authority, more about owner authority, where only an owner could change the parent, but that change could come either from the client or from the server. I understand with shared authority it can become a mess very fast, but would it be that complex to allow clients changing parenting too?
ownership model (IsOwner
, OwnerClientId
, ChangeOwnership()
etc.) does not play a role here. the code user writes on the server-side can decide to change with regards to the object's owner or regardless of it, we do not assume or try to guess. however, ChangeOwnership()
API works only on the server-side because the server is the ultimate source of truth. we are following a very similar concept here: which NetworkObject
is parent/child of which NetworkObject
is determined, controlled and stored by the server — depending on what user writes, it may or may not be affected by ownership or any other arbitrary conditions but the execution must happen on the server-side as it is for ownership.
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.
So since AutoObjectParentSync is a per-NetworkObject property, could one enable the kind of client-authoritative parenting @SamuelBellomo is describing by controlling this flag on the desired objects?
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 opt-out from AutoObjectParentSync
and do whatever you want (the goal of this flag was to give experienced developers full-control and get out their way, as outlined in the RFC), yes. however, client-driven/authoritative object parenting sounds like a completely different feature to me (and I'd even argue if that's needed given that we're a client-server model).
About adding samples for this feature, we already have assets for a character moving a barrel around, we'd just need to add this feature in boss room. |
{ | ||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error) | ||
{ | ||
NetworkLog.LogError($"Invalid parenting, {nameof(NetworkObject)} moved under a non-{nameof(NetworkObject)} parent, will fix and move to the scene root"); |
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.
Why not cancel the reparenting and use the cached parent? It'd be consistent with the other error handling we have above
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, I think you're right! :)
} | ||
else | ||
{ | ||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error) |
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.
it's not really part of your review, but this if should really be part of NetworkLog.LogError
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.
to me, I'd throw away the NetworkLog
entirely LOL
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.
meh I do like the developer level logs. I agree eventually we should have network traces to replace them, but it's a good thing we can enable/disable them.
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.
we already have developer-level logs, it's called Debug.Log
suite and if you were to look at NetworkLog
closely, it's just a thin layer on top of Debug.Log
which serves very little value IMHO.
using (var writer = PooledNetworkWriter.Get(buffer)) | ||
{ | ||
writer.WriteUInt64Packed(NetworkObjectId); | ||
WriteNetworkParenting(writer, m_IsReparented, m_LatestParent); |
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.
If we reparent and move (NetworkTransform) in the same frame, do we have a guarantee the reparent and move will happen at the same time and in the right order client side?
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.
we (still) have no guarantees or message delivery orders. well, we kind of have but it is very implicit and opaque. if you were to use the same reliable ordered fragmented channel (or whatever it's called), you could guarantee that they'd be in order — but, that's very subtle and not reliable IMHO.
so, the answer is yes but actually no. also NetworkObject
parenting doesn't necessarily look into NetworkTransform
compatibility since it's lower-level than NetworkTransform
. if things break and/or don't look good, we might even consider reworking NetworkTransform
after NetworkObject
changes based on new rules and nuances.
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.
there's plans to have grouping for netvars, enabling a guarantee these netvars will be sent together. @jeffreyrainy for visibility.
using netvars would help in this case.
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'm not a part of NetVars convos nor snapshotting convos. I'm living in my own silo here and delivering what I'm supposed to deliver.
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.
So assuming we have that grouping, that'd make it great to use with this no?
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.
again, I have no idea what you folks are talking about or planning. however, I'd still be opinionated about avoiding coupling as much as possible. I'm also hesitant to suggest any architectural or technical changes either in netvars/snapshots space or MLAPI in general. if you have anything fancy in your mind, I'm all ears.
var targetClientId = NetworkManager.ConnectedClientsList[i].ClientId; | ||
if (Observers.Contains(targetClientId)) | ||
{ | ||
NetworkManager.MessageSender.Send(targetClientId, NetworkConstants.PARENT_SYNC, NetworkChannel.Internal, buffer); |
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.
out of curiosity, why aren't we using netvars to track this?
Custom messages makes this harder to track in any eventual AoI / Prediction / Server side rewind, no?
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 don't think NetVar is the right tool for this job.
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.
Any reason why not?
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.
Custom messages makes this harder to track in any eventual AoI / Prediction / Server side rewind, no?
what you see inline above is not a "custom message", custom messages are something completely different.
Any reason why not?
I'd argue why so?
NetVar is being checked every-frame, does some other things and will do some other things (that I don't fully understand yet, maybe I don't have to?) which is not needed here because we precisely control when parent changes and know exactly when it happens.
NetworkObject
parenting could live without NetVar coupling and I believe de-coupling is a good thing.
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 need to look more closely but I don't want to forget this thought...so with this change, is the NetworkTransform always sending the relative-to-parent or global position as before?
Regardless, my intuition is suggesting that I suggest this - what if we atomically sent either just a position change (if the parent hasn't changed, as today) or parent change plus position/rot change. That way you never have the weird case where the parent changed with a mis-matched transform position causing jumping, etc. This parenting only applies to transform (yes?) so it doesn't seem like a bolt on.
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.
things could've done better for sure but things are being done this way due to our current MLAPI architecture (NetworkObject-NetworkBehaviour relationship, their relation to scene hierarchy and NetworkTransform doing transform-related stuff. I think both of you know where I stand in terms of NO, NB & NT — NO & NB should not even form any transform-based hierarchy at all, if any at all).
apart from that, we can still do things in NetworkTransform
realm. we do have a OnNetworkObjectParentChanged()
callback when NetworkObject
reparenting happens (on both server and client sides). that could be used to switch between local vs world relative positioning or any other custom optimization at NetworkTransform
-level.
I added a placeholder method implementation into NetworkTransform.cs
where I put a comment saying:
// TODO: handle parent NetworkObject change and potentially optimize pos/rot/scale replication?
that'd hopefully guide us later in the future (if and) when we decide to discover optimization possibilities on NetworkTransform
.
return TrySetParent(parent.GetComponent<NetworkObject>(), worldPositionStays); | ||
} | ||
|
||
public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) |
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.
So, we want to silently fail in these cases or what about throwing an exception?
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 don't think returning a bool
back from a method called TryXXX
is silencing fail cases. it is pretty much like .NET Dictionary.TryAdd()
API.
I had some thoughts as I did my first read through...
|
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 don't have specific changes yet, this is my signal to help gain more understanding before the commit
network spawn/despawn happens on NetworkShow & NetworkHide — we might follow the same logic here. obv, I think we need a bigger picture and plan but for the time being, we could rely on this behaviour (NetworkObjects are being re-serialized & replicated on network spawn due to NetworkShow()).
this is a good question that I'd like to answer differently: I think this implementation should not even exist at this level (NetworkObject-NetworkBehaviour level) but here we are. until we land AOI, filtering, prioritization, NetworkObject-Behaviour-Transform- changes, I'd see this feature exist as a temporary solution (to deliver on the user-need in the roadmap).
owner can't despawn an object, it can just call ServerRpcs on it (and potentially apply ownership rules on other features in the future). apart from that, parenting doesn't care. A might be owned by Client X and child object B might be owned by Client Y then everything would still work just fine. no limitations or restrictions between ownership and parenting here.
|
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.
This looks real nice Fatih.
Like the updates to the comments...the new parent changed notification callback...and your test is insane!
// Move child NetworkObjects to the root when parent NetworkObject is destroyed | ||
foreach (var networkObject in SpawnedObjectsList) | ||
{ | ||
var (isReparented, latestParent) = networkObject.GetNetworkParenting(); | ||
if (isReparented && latestParent == networkObjectId) | ||
{ | ||
networkObject.gameObject.transform.parent = null; | ||
|
||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) | ||
{ | ||
NetworkLog.LogWarning($"{nameof(NetworkObject)} #{networkObject.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObjectId} is destroyed"); | ||
} | ||
} | ||
} |
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.
…o be fixed by OrphanChildren mechanism
com.unity.multiplayer.mlapi/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs
Outdated
Show resolved
Hide resolved
@@ -221,6 +213,11 @@ void SetupVar<T>(NetworkVariable<T> v, T initialValue, ref T oldVal) | |||
} | |||
} | |||
|
|||
public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) | |||
{ | |||
// TODO: handle parent NetworkObject change and potentially optimize pos/rot/scale replication? |
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.
* develop: (21 commits) feat: NetworkObject Parenting (#855) refactor: move RpcMethodId serialization from ILPP to Core (#910) fix: NetworkPrefabs container's elements invalidated in the NetworkManager after relaunching Unity Project (#905) feat!: OnNetworkSpawn / OnNetworkDespawn (#865) feat: Add missing XMLdoc comment (#897) refactor: upgrade ILPP backend, drop 2019.4 support, rename types/fields (#895) fix: do not access/render runtime info if not playing in the editor (#898) feat: Add name property for network variables (#891) chore: delete PhilTestResults.xml (#894) feat: MultiInstanceHelpers to use fixed FrameRate by default (#893) test: General MultiInstanceHelper improvements (#885) refactor: isKinematic set to true for rigid bodies of non-authorized instances (#886) docs: adding more info to help debug on network transform error message (#892) feat: Add RPC Name Lookup Table Provided by NetworkBehaviourILPP (#875) fix: remove OnClientConnectedCallback registration from StatsDisplay (#882) feat: Add profiling decorator pattern (#878) refactor: Removing dead code for NETWORK_VARIABLE_UPDATE (#880) fix: update package version to 0.2.0 because of unity minversion change (#881) docs: Fix typo in changelog version title docs: Hotfix Changelog for 0.1.1 and manual update (#873) ... # Conflicts: # com.unity.multiplayer.mlapi/Runtime/Core/NetworkBehaviour.cs # com.unity.multiplayer.mlapi/Runtime/Core/NetworkManager.cs # com.unity.multiplayer.mlapi/Tests/Runtime/BaseMultiInstanceTest.cs # com.unity.multiplayer.mlapi/Tests/Runtime/BaseMultiInstanceTest.cs.meta
* develop: (67 commits) feat: NetworkObject Parenting (#855) refactor: move RpcMethodId serialization from ILPP to Core (#910) fix: NetworkPrefabs container's elements invalidated in the NetworkManager after relaunching Unity Project (#905) feat!: OnNetworkSpawn / OnNetworkDespawn (#865) feat: Add missing XMLdoc comment (#897) refactor: upgrade ILPP backend, drop 2019.4 support, rename types/fields (#895) fix: do not access/render runtime info if not playing in the editor (#898) feat: Add name property for network variables (#891) chore: delete PhilTestResults.xml (#894) feat: MultiInstanceHelpers to use fixed FrameRate by default (#893) test: General MultiInstanceHelper improvements (#885) refactor: isKinematic set to true for rigid bodies of non-authorized instances (#886) docs: adding more info to help debug on network transform error message (#892) feat: Add RPC Name Lookup Table Provided by NetworkBehaviourILPP (#875) fix: remove OnClientConnectedCallback registration from StatsDisplay (#882) feat: Add profiling decorator pattern (#878) refactor: Removing dead code for NETWORK_VARIABLE_UPDATE (#880) fix: update package version to 0.2.0 because of unity minversion change (#881) docs: Fix typo in changelog version title docs: Hotfix Changelog for 0.1.1 and manual update (#873) ... # Conflicts: # com.unity.multiplayer.mlapi/Tests/Runtime/com.unity.multiplayer.mlapi.runtimetests.asmdef # testproject/ProjectSettings/EditorBuildSettings.asset
This PR implements RFC#17: NetworkObject Parenting (tracked by Issue #876)
PR also includes minor refactors and cleanups around the codebase.