Skip to content

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

Merged
merged 25 commits into from
Apr 23, 2021

Conversation

NoelStephensUnity
Copy link
Collaborator

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.

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,
Copy link
Contributor

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

Copy link
Collaborator Author

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;
Copy link
Contributor

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

Copy link
Collaborator Author

@NoelStephensUnity NoelStephensUnity Apr 17, 2021

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
@NoelStephensUnity NoelStephensUnity marked this pull request as ready for review April 20, 2021 15:38
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
Copy link
Contributor

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.

Copy link
Collaborator Author

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

  1. 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.
  2. Just break the NetworkManager configuration that would require anyone updating to populate the PlayerPrefab setting (i.e. no backwards-forwards compatibility)
  3. 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.

Copy link
Contributor

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

Copy link
Contributor

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)
Copy link
Contributor

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

Copy link
Collaborator Author

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)
Copy link
Contributor

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?

Copy link
Collaborator Author

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.

Copy link
Contributor

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.");
Copy link
Contributor

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?

Copy link
Collaborator Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Collaborator Author

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)}?");
Copy link
Contributor

@mattwalsh-unity mattwalsh-unity Apr 20, 2021

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.

Copy link
Collaborator Author

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")}!");
Copy link
Contributor

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.

Copy link
Collaborator Author

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

Copy link
Contributor

@mattwalsh-unity mattwalsh-unity left a 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.
Comment on lines 187 to 193
/// <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>
Copy link
Contributor

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

Suggested change
/// <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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yay!

Copy link
Contributor

@0xFA11 0xFA11 left a 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.
Comment on lines 7 to 19
public enum NetworkPrefabOverride
{
None,
Prefab,
Hash
}

/// <summary>
/// Class that represents a NetworkPrefab
/// </summary>
[Serializable]
public class NetworkPrefab
{
Copy link
Contributor

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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;
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Defautlt to the standard NetworkPrefab.Prefab's NetworkObject first
// Default to the standard NetworkPrefab.Prefab's NetworkObject first

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Now check to see if it has an override

over-commenting, isn't it obvious?

Copy link
Collaborator Author

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.

Copy link
Contributor

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

Comment on lines 288 to 298
// 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();
}
Copy link
Contributor

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?

Copy link
Collaborator Author

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!");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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!.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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!.");

Copy link
Contributor

@0xFA11 0xFA11 left a 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 :)

@NoelStephensUnity
Copy link
Collaborator Author

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...
Of course, I am of the mind set where providing a reasonable amount of comments for new regions of code will help guide anyone new to that region of code. Will review through the comments and see if there are any blatantly obvious areas where comments are not needed.

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
Copy link
Contributor

@0xFA11 0xFA11 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@0xFA11 0xFA11 enabled auto-merge (squash) April 23, 2021 01:29
@0xFA11 0xFA11 merged commit 6998fd4 into develop Apr 23, 2021
@0xFA11 0xFA11 deleted the feat/mtt640 branch April 23, 2021 04:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants