-
Notifications
You must be signed in to change notification settings - Fork 450
feat!: Network Prefab Overrides, Inspector View, and the default Player Prefab #749
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
This is the first pass changes required to implement MTT-640. The default player prefab is now a directly assigned value as opposed to a checkbox in the network prefab list. The default player prefab has to also be included in the NetworkPrefab list (this is so the default player prefab can be overridden too) NetworkPrefab entries can be overridden and directly assigned alternate prefabs for clients to spawn. The source prefab (what the server uses) can be a prefab reference or a prefab hash.
@@ -4,6 +4,13 @@ | |||
|
|||
namespace MLAPI.Configuration | |||
{ | |||
public enum NetworkPrefabOverride | |||
{ | |||
Unset, |
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.
Nit: I think I would pick 'None' else at first glance sounds like 'Unset' is a 'needs-to-be-initialized' state
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.
Yes, I agree. None is what that should be.
/// This is used typically in multi-project patterns where a separate project contains the | ||
/// source prefab and the GlobalObjectIdHash was copied and pasted into this field. | ||
/// </summary> | ||
public uint OverridingSourceHash; |
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 would think you could derive this from OverridingSourcePrefab and wouldn't need to store it. OR, actually, isn't this the hash that overrides the hash for OverridingSourcePrefab when NetworkPrefabOverride.Hash is enabled? if so the comment consufed me
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.
Yeah... I think just changing the names around might make it clearer?
SourceHashToOverride
SourcePrefabToOverride
OverridingTargetPrefab
This update is the first pass of making this update as painless as possible to the user by "auto-handling" the v0.1.0 NetworkPrefab pattern as well as there is additional "user-bumper" code that assures the assigned default player prefab is always in the NetworkPrefab list.
Removing the auto insert within the editor and doing this at runtime. Added additional check to handle assigning the OverridingSourcePrefab if it is null and the Prefab is not.
This will prevent the scenario where a user might want to delete the default player prefab but also has it assigned within the NetworkPrefab list. This assures that a newly created network prefab list entry is blank and that there are not two entries within the list that have the same global object id hash.
just adding a few comments
Adjusted names of the new NetworkPrefab properties and added additional comments to better clarify some of the "trickery" happening that will help make this a "non-breaking" feature when it comes to existing v0.1.0 NetworkConfigs and the already selected default player prefab.
This will prevent any future client side soft synchronization issues or related issues where only one (or a few) NetworkObjects are misconfigured and the client fails to synchronize (find or instantiate). As opposed to completely breaking the rest of the pending NetworkObjects to be synchronized/instantiated, it will log an error and continue loading. This also has some additional comments and updates to comments for better clarity.
minor change in comments
This removes the CreatePlayerPrefab property from the NetworkConfig and from the NetworkManagerEditor. It is no longer needed as the NetworkConfig.PlayerPrefab property being something other than null tells us that the user wants to have MLAPI automatically spawn the players for them.
removed unused namespace references.
@@ -42,22 +42,41 @@ public class NetworkConfig | |||
public bool AllowRuntimeSceneChanges = false; | |||
|
|||
/// <summary> | |||
/// A list of spawnable prefabs | |||
/// The previously set default player prefab |
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.
Thanks for writing such a detailed comment.
In our UI/UX meeting with @morgane and Ross tomorrow I want to see what their thinking is, but if we have to go through lots of hoops I start to wonder if we should just go with not having a separate PlayerPrefab entry and just have an additional radio button next to each registered prefab to indicate whether you want it to be the player prefab.
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.
Well we have two meetings to really discuss.
There are three paths (ordered in personal preference):
- Provide the ability to not break existing v0.1.0 NetworkManager configurations as it pertains to the default PlayerPrefab. Under this condition, you pointed out a unique scenario where the user had to delete the NetworkPrefab entry marked as Default Player before they could delete the PlayerPrefab setting in the editor (i.e. it would auto-populate back). The PreviousPlayerPrefab is used to detect this specific condition. I am sure there are other ways to do this.
- Just break the NetworkManager configuration that would require anyone updating to populate the PlayerPrefab setting (i.e. no backwards-forwards compatibility)
- Leave the additional field in NetworkPrefab list, which will be set once. This path has never checked for more than one NetworkPrefab being marked as the "Player" prefab...so theoretically one could mark several entries but only one of the entries would apply.
Obviously I am a fan of #1, but we should most definitely discuss.
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.
Yeah, great summary...though on #3 I would (assuming it's possible) see if we could do radio buttons vs. checkboxes
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 are no radio buttons in Unity Editor inspector UI — we could reinvent it with checkboxes by keeping only one of them checked and others disabled.
Added a tooltip for the new NetworkConfig.PlayerPrefab property
//then we want to set the default player prefab. We also want to set the | ||
//previous player prefab value in the event the user decides to delete the | ||
//PlayerPrefab value from within the editor inspector view. | ||
//(See NtworkConfig.PreviousPlayerPrefab comments for more information) |
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.
nit: typo. Also I think the preferred comment style has a space between the // and the text
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.
resolved in 7f2781c
Made sure all added comments (and areas surrounding within reason) have spaces between the forward slashes and the text body of the comment.
//previous player prefab value in the event the user decides to delete the | ||
//PlayerPrefab value from within the editor inspector view. | ||
//(See NtworkConfig.PreviousPlayerPrefab comments for more information) | ||
if (NetworkConfig.PreviousPlayerPrefab == null) |
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 guess I'm kind of on the fence. I like the defensive programming against the old scheme, but I also would like to avoid maintaining the work around code nor have to remember to eventually get rid of it.
Could we just detect the old scheme is in use, then signal the user that they need to re-configure their network prefabs?
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.
Until we implement something like custom serialization with our serialized NetworkConfig class and provide some form of version number to check against (i.e. determine what the version is and then "upgrade" or "downgrade" the configuration), we will have to either break existing configurations or handle things like the above.
Until that happens, we just need to determine what we want to accomplish.
Side Note: We can easily keep track of this by just creating a new Jira story that references the areas that need to be addressed and then assign it to the next milestone after v1.0.0. That way we won't have to remember to remove it as we will already have a task to do so in the not too distant future.
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're already breaking the old NetworkPrefab setup UI and I think it's safe to change API (therefore already serialized/saved assets) this time here in v1.0.0-pre release. we should address this case by case basis. I'll bring this up in our internal discussions.
if (networkObject == null) | ||
{ | ||
//This will prevent one misconfigured issue (or more) from breaking the entire loading process. | ||
Debug.LogError($"Failed to spawn NetowrkObject for Hash {prefabHash}, ignoring and continuing to load."); |
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.
Do you think we should error and continue or assert? Are we really in a recoverable state here or have we put the user in a situation where we haven't hit an exception but the state is hopelessly messed up?
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 we can discuss further in the Thursday meeting, but the big different between allowing the exception to happen vs. gracefully handling each specific case is:
1.) With an exception, you break out of the current stack scope and if there were any remaining and valid NetworkObjects to load you will never load them (i.e. bug reports can vary widely for the same issue focused around one thing failing) and it becomes a complete blocker for developers as they can't test anything else until that one specific issue is resolved.
2.) "Gracefully" handling each specific case will only expose the specific NetworkObjects causing the issue. This helps to greatly narrow down what is broken and what is still working as opposed to just stopping the entire loading process when you encounter 1 NetworkObject that has an issue (i.e. there could be many other valid NetworkObjects entries in the stream and a bug report could be based around any one of the valid NetworkObjects that never loaded on the client side). This also only blocks on the NetworkObject(s) failing, but doesn't block a developer from being able to test everything that does work.
I picked the later of the two as I did find it annoying to not be able to test other things if 1 aspect of MLAPI was broken (specifically soft synchronization issues). More than likely with our new GlobalObjectIdHash pattern this type of thing won't happen.
I used a similar approach when first loading the NetworkPrefab list at runtime:
- Encounter and issue with a NetworkPrefab at runtime (i.e. null or the like)
- log a warning or error depending upon the severity
- remove it from the list of NetworkPrefabs
- continue loading process
There is no harm in this and it allows a developer to at least test what is valid at that moment, and then they can circle back to fixing any errors/warnings next editor session.
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 think this is well said and thought out. I think the sticking point for me is I don't have faith people look at the console for errors, but I know we'll get their attention if it doesn't run. I mean if we really wanted the game to defensively cope and keep on trucking and did some kind of scheme like spawn a built-in-stand-in-can't-fail prefab, then I could live with it. But I think masking errors ultimately makes life worse for users.
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.
both exception logs and error logs appear in the Unity console logs with the exact same UI. if somebody doesn't want to pay attention to console error logs, they are also not paying attention to exception logs — UI-wise there is no difference. I agree with Noel's approach here where we just log an error to console but continue loading/serializing the rest. yeah, we can discuss internally 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.
This is resolved/removed and will be handled via PR-756.
{ | ||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error) | ||
{ | ||
NetworkLog.LogError($"Failed to create object locally. [{nameof(prefabHash)}={prefabHash}]. Hash could not be found. Is the prefab registered?"); | ||
NetworkLog.LogError($"Failed to create object locally. [{nameof(prefabHash)}={prefabHash}]. {nameof(NetworkPrefab)} could not be found. Is the prefab registered with {nameof(NetworkManager)}?"); |
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.
Similar to above, should we not exit when this happens? Though maybe the caller receives the null and then exits.
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.
See the notes above for NetworkSceneManager.cs
if (NetworkLog.CurrentLogLevel <= LogLevel.Error) | ||
{ | ||
NetworkLog.LogError("Cannot find pending soft sync object. Is the projects the same?"); | ||
NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed NetworkObject soft synchronization failure for Hash: {prefabHash.ToString("X")}!"); |
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.
Similar to above, should we not exit when this happens? Though maybe the caller receives the null and then exits.
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.
See the notes above for NetworkSceneManager.cs
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.
Some questions I want to talk through, but generally looks great
Added some spaces between the forward slashes and comment's text body
This fixes an issue that can occur with serialization and NetworkPrefabOverrideLinks. The policy is to ALWAYS clear this dictionary before building the links dictionary.
/// <param name="softCreate"></param> | ||
/// <param name="prefabHash"></param> | ||
/// <param name="ownerClientId"></param> | ||
/// <param name="parentNetworkId"></param> | ||
/// <param name="position"></param> | ||
/// <param name="rotation"></param> | ||
/// <returns></returns> |
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'd suggest removing these empty blocks if we don't specifically xmldoc them (but still keep the main <summary>
block only).
/// <param name="softCreate"></param> | |
/// <param name="prefabHash"></param> | |
/// <param name="ownerClientId"></param> | |
/// <param name="parentNetworkId"></param> | |
/// <param name="position"></param> | |
/// <param name="rotation"></param> | |
/// <returns></returns> |
@@ -74,7 +74,6 @@ public static bool StartNetworkManager(out NetworkManager networkManager, Networ | |||
|
|||
NetworkManagerObject.NetworkConfig = new Configuration.NetworkConfig | |||
{ | |||
CreatePlayerPrefab = false, |
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.
yay!
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.
looks good to me overall — except some minor tweaks and fixes which could be addressed after our internal discussion.
This removes the NetworkConfig compatibility checks that auto-upgraded a previous NetworkConfig setting to the new pattern. Updated NetworkPrefab according to this change, isPlayer is no longer needed. Added additional code to NetworkPrefab that will return a hash reflective of the override state. Adjusted some comments.
Minor adjustment for the right GlobalObjectIdHash check.
public enum NetworkPrefabOverride | ||
{ | ||
None, | ||
Prefab, | ||
Hash | ||
} | ||
|
||
/// <summary> | ||
/// Class that represents a NetworkPrefab | ||
/// </summary> | ||
[Serializable] | ||
public class NetworkPrefab | ||
{ |
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 reduce surface area even further, I wonder if we could make both enum NetworkPrefabOverride
and class NetworkPrefab
internal
instead of public
. they're not going to be used by framework users on any APIs we have — and [Serializable]
attribute will still serialize NetworkPrefab
instances even if it was private
, it should be safe to hide them behind internal
access modifiers.
also minor: I'd suggest naming NetworkPrefab
as NetworkPrefabConfig
to indicate/communicate more clearly that it's just a config struct.
var networkObject = Prefab.GetComponent<NetworkObject>(); | ||
var prefabGameObject = Prefab; | ||
|
||
switch(Override) |
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.
switch(Override) | |
switch (Override) |
I'm the spaceman :P
Can we kill this internal uint Hash { get { ... } }
property entirely?
It seems possible to me because we already have NetworkPrefabOverrideLinks
now and there should be just a few places that use this getter property directly, no?
(and this is a minor/optional change suggestion, I'm just curious)
@@ -4,9 +4,7 @@ | |||
using System.ComponentModel; | |||
using System.Diagnostics; | |||
using UnityEngine; | |||
using System.Linq; |
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.
❤️
} | ||
else | ||
{ | ||
// Defautlt to the standard NetworkPrefab.Prefab's NetworkObject 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.
// Defautlt to the standard NetworkPrefab.Prefab's NetworkObject first | |
// Default to the standard NetworkPrefab.Prefab's NetworkObject 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.
// Defautlt to the standard NetworkPrefab.Prefab's NetworkObject first |
or this comment line might not be needed at all? :)
|
||
int playerPrefabCount = NetworkConfig.NetworkPrefabs.Count(x => x.IsPlayer); | ||
// Now check to see if it has an override |
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.
// Now check to see if it has an override |
over-commenting, isn't it obvious?
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, comments help guide people as to what is happening.
While it is obvious to us, this level of commenting to someone who is completely new to the SDK is actually useful.
I would rather leave comments in than remove 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.
I disagree :P Let's discuss internally :)
// Add to the NetworkPrefabOverrideLinks or handle a new (blank) entries | ||
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(globalObjectIdHash)) | ||
{ | ||
NetworkConfig.NetworkPrefabOverrideLinks.Add(globalObjectIdHash, NetworkConfig.NetworkPrefabs[i]); | ||
} | ||
else | ||
{ | ||
// This happens when a new duplicate entry is created. | ||
// We just turn it into a new blank entry | ||
NetworkConfig.NetworkPrefabs[i] = new NetworkPrefab(); | ||
} |
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 actually didn't quite get this block.
The first if block looks sensible but the second else block made me confused a little bit :/
Do you want to explain a little bit?
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.
The default behavior for adding new list entries in Unity is that the previous list entry (if any) is basically duplicated.
Without adding additional custom UI elements and/or properties, this basically assures that any duplicate entry and/or any new entry will be a new, empty, NetworkPrefab.
As the comments suggest... :)
else | ||
{ | ||
// This should never happen, but in the case it somehow does log an error and remove the duplicate entry | ||
UnityEngine.Debug.LogError($"{nameof(NetworkPrefab)} (\"{NetworkConfig.NetworkPrefabs[i].Prefab.name}\") has a duplicate GlobalObjectIdHash {networkObject.GlobalObjectIdHash.ToString("X")} entry! Removing entry from list!"); |
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.
UnityEngine.Debug.LogError($"{nameof(NetworkPrefab)} (\"{NetworkConfig.NetworkPrefabs[i].Prefab.name}\") has a duplicate GlobalObjectIdHash {networkObject.GlobalObjectIdHash.ToString("X")} entry! Removing entry from list!"); | |
UnityEngine.Debug.LogError($"{nameof(NetworkPrefab)} (\"{NetworkConfig.NetworkPrefabs[i].Prefab.name}\") has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} {networkObject.GlobalObjectIdHash} entry! Removing entry from list!"); |
embracing nameof
operator.
also we're not using hex representation of GlobalObjectIdHash
on UI (see NetworkObjectEditor
for the reference)
else | ||
{ | ||
// Provide the name of the prefab with issues so the user can more easily find the prefab and fix it | ||
UnityEngine.Debug.LogError($"{nameof(NetworkConfig.PlayerPrefab)} (\"{NetworkConfig.PlayerPrefab.name}\") has no NetworkObject assigned to it!."); |
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.
UnityEngine.Debug.LogError($"{nameof(NetworkConfig.PlayerPrefab)} (\"{NetworkConfig.PlayerPrefab.name}\") has no NetworkObject assigned to it!."); | |
UnityEngine.Debug.LogError($"{nameof(NetworkConfig.PlayerPrefab)} (\"{NetworkConfig.PlayerPrefab.name}\") has no {nameof(NetworkObject)} assigned to it!."); |
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.
almost there! I suggested some minor changes and asked for my better understanding.
also, I'm seeing lots and lots of comments inline which I think doesn't look good in terms of signal-to-noise ratio.
I'd expect inline comments to be explaining something that is not obvious and can't be inferred after reading the code block.
in general, if too many things have to be commented, then there are even bigger concerns I have with code quality in general :)
Actually, I was asked to add comments to the areas you are wanting less comments... |
Made the NetworkPrefab internal as well as NetworkPrefabOverride. Removed GetNetworkPrefabIndexOfHash and GetPrefabHashFromIndex as they were used in the old hash generation pattern. Updated comments a bit.
Removing two checks for the failure to spawn a NetworkObject locally. This will be handled via PR-756: #756
This property is no longer being used and where it was used was replaced by the NetworkConfig.NetworkPrefabOverrideLinks
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 is the first pass changes required to implement MTT-640.
The default player prefab is now a directly assigned value as opposed to a checkbox in the network prefab list.
The default player prefab has to also be included in the NetworkPrefab list (this is so the default player prefab can be overridden too)
NetworkPrefab entries can be overridden and directly assigned alternate prefabs for clients to spawn.
The source prefab (what the server uses) can be a prefab reference or a prefab hash.