Skip to content

feat: Addressables Redux#1882

Merged
ShadauxCat merged 16 commits intodevelopfrom
feat/addrssables_redux
Apr 25, 2022
Merged

feat: Addressables Redux#1882
ShadauxCat merged 16 commits intodevelopfrom
feat/addrssables_redux

Conversation

@ShadauxCat
Copy link
Copy Markdown
Collaborator

@ShadauxCat ShadauxCat commented Apr 18, 2022

As opposed to the previous addressables PR, this one focuses on unblocking users from being able to make their own addressables implementations. This PR adds two things:

  1. It adds support for modifying the prefab list at runtime (including after startup when NetworkConfig.ForceSamePrefabs is false)
  2. It adds error handling to gracefully handle race conditions when a spawn arrives before the asset is fully loaded, by means of a 1 second timeout. This is not intended to be relied on (e.g., should not be used as a way to trigger loading assets after receiving spawns) but is intended to support cases where slow disk or network result in good faith efforts to load the asset in advance being slowed down to the point of missing the moment of spawn.

In this implementation, it's up to the user to load their own addressables in whatever way they choose, and tell the SDK about them after the addressable has been loaded into a GameObject with a NetworkObject component. When this is done dynamically at runtime, it's also up to the user (using whatever methods are appropriate for their game - deterministic loading when changing levels, RPCs, etc) to ensure both server and client have the required assets before the server sends a spawn message to the client. We will not make any attempt to trigger the client to load something just because the server did, as that may not be desirable in all cases.

Changelog

  • Added: Prefabs can now be added to the SDK at runtime (i.e., from an addressable asset). If ForceSamePrefabs is false, this can happen after a connection has been formed.
  • Added: When ForceSamePrefabs is false, a configurable delay (default 1 second) has been introduced to gracefully handle race conditions where a spawn call has been received for an object whose prefab is still being loaded.

Testing and Documentation

  • Includes unit tests.
  • Includes integration tests.
  • Documentation changes will be added in a separate PR.

@ShadauxCat ShadauxCat requested a review from a team as a code owner April 18, 2022 23:20
- Disable deferring spawn messages when `ForceSamePrefabs` is true because there's no point
Comment thread com.unity.netcode.gameobjects/CHANGELOG.md Outdated
[Tooltip("The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped")]
public float MessageBufferTimeout = 20f;
[Tooltip("The amount of time a message should be buffered if the asset or object needed to process it doesn't exist yet. If the asset is not added/object is not spawned within this time, it will be dropped")]
public float SpawnTimeout = 1f;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

perhaps?

Suggested change
public float SpawnTimeout = 1f;
public float SpawnTimeoutSec = 1f;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I started to name it this, actually, but we have other time values in NetworkConfig that don't specify units. I'm more in favor of consistency here unless we change them all to specify their units - though that'd still be inconsistent with Unity as a whole, since none of the Time properties have a Sec or Seconds suffix.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

okay :(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should we add changelog entries like "removed MessageBufferTimeout from NetworkConfig" and rephrase other added changelog entry to mention SpawnTimeout somehow there too?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I don't think it's really worth mentioning the removal of MessageBufferTimeout given that the message buffers were removed before we ever released 1.0-pre, so that config was already dead and had no function. I removed it because it was dead code.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'll update the changelog to mention SpawnTimeout though.

/// Components currently supported by ComponentFactory:
/// - IDeferredMessageManager
/// </summary>
internal static class ComponentFactory
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is this implementation required or good to have?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This specific implementation isn't required but the tests need to have some ability to replace the DeferredMessageManager with a test-specific version. It's either a factory method that can be replaced like this, or NetworkManager constructs the default one and the test then assigns a new one... which works in this case, but may not work in other cases. This is just one possible implementation of Dependency Injection, but Dependency Injection as a pattern is really important for good testable code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

hmm, can this live under Tests/ ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No, it's still needed in the runtime, otherwise NetworkManager doesn't know how to create the DeferredMessageManager. :) The idea is that this provides default construction logic (which is what's used at runtime) but allows it to be overridden (which is used for tests - though this pattern could also be used to let users provide their own implementations of specific components, but that's a discussion we'd need to have about if we want to support that, so I left it internal for now.)

Comment thread testproject/ProjectSettings/BurstAotSettings_StandaloneWindows.json Outdated
Comment on lines +203 to +219
public class DeferredMessageTestNetworkVariableComponent : NetworkBehaviour
{
public NetworkVariable<int> TestNetworkVariable = new NetworkVariable<int>();
}

public class DeferredMessageTestRpcAndNetworkVariableComponent : NetworkBehaviour
{
public bool ClientRpcCalled;

[ClientRpc]
public void SendTestClientRpc()
{
ClientRpcCalled = true;
}

public NetworkVariable<int> TestNetworkVariable = new NetworkVariable<int>();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what was the rationale behind separating these two? could we have one comp with NetVar & RPC in it?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Test isolation. If a NetworkVariable is present, NetworkVariableDelta messages will get sent. Makes it hard for a test to reliably verify that the message that was captured was the RPC message.

Comment thread com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs Outdated
Comment thread com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs Outdated
Comment thread com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs Outdated
Comment thread com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs Outdated
@ShadauxCat ShadauxCat mentioned this pull request Apr 19, 2022
ShadauxCat and others added 2 commits April 19, 2022 14:50
Copy link
Copy Markdown
Member

@NoelStephensUnity NoelStephensUnity left a comment

Choose a reason for hiding this comment

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

Other than it could use more comments in various places.
The over-all PR looks good, passes in stand-alone, and fixes other edge case scenarios with the deferred messages addition! (yay)
LGTM!

@mattwalsh-unity
Copy link
Copy Markdown
Contributor

Seems like there are 2 things going on here, the new IDeferredMessageManager / ComponentFactory elements, then the addressable stuff. Can this be in 2 commits?

@mattwalsh-unity
Copy link
Copy Markdown
Contributor

So on the per-NetworkObject queueing of messages...say you have a horse and rider game objects, and they interact in script code, and just the horse comes in late. If you have object inter-dependencies, but only queue one of them, what happens?

I guess I'm intimating, can we safely assume that we can have per-object queues and unwind them independently and expect things to work consistently?

@ShadauxCat
Copy link
Copy Markdown
Collaborator Author

Seems like there are 2 things going on here, the new IDeferredMessageManager / ComponentFactory elements, then the addressable stuff. Can this be in 2 commits?

This PR isn't functional without ComponentFactory (it's a requirement for the testing to function), and without this PR, ComponentFactory would be a single empty and unused class. I wouldn't want to put ComponentFactory in without something using it, personally.

@ShadauxCat
Copy link
Copy Markdown
Collaborator Author

So on the per-NetworkObject queueing of messages...say you have a horse and rider game objects, and they interact in script code, and just the horse comes in late. If you have object inter-dependencies, but only queue one of them, what happens?

I guess I'm intimating, can we safely assume that we can have per-object queues and unwind them independently and expect things to work consistently?

I would say if that's something that can happen, it's a problem user code has to handle (i.e., the rider needs to verify the horse exists before it does any gameplay logic on it). There's no way we can know that the rider depends on the horse, and I don't think we should try to predict and solve every gameplay problem for our users. We just need to document that this can happen and make sure our users are aware of that.

Comment thread com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs Outdated
Comment thread com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs Outdated
Comment thread com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs Outdated

triggers[key].TriggerData.Add(new TriggerData
{
Reader = new FastBufferReader(reader.GetUnsafePtr(), Allocator.Persistent, reader.Length),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I am not sure about this, but doesn't this allocate each time you are "deferring" a message?

Copy link
Copy Markdown
Collaborator Author

@ShadauxCat ShadauxCat Apr 21, 2022

Choose a reason for hiding this comment

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

Not in the way you're thinking. FastBufferReader is a struct so it's allocated on the stack. It contains only an unsafe pointer so its size is only one word (so copying it to pass it around is as fast as passing a long around). Its underlying data is allocated using Unity's native allocators (same ones used for NativeList and NativeArray) - so yes, there's an allocation, but it's a low-level malloc and doesn't create garbage.

It's unavoidable though because the existing reader was allocated with Allocator.TempJob and those have a maximum lifetime of 4 frames. Since the deferred messages can last up to 60 frames (or more if NetworkConfig.SpawnTimeout is increased) it has to be moved to Allocator.Persistent. The whole buffer copy isn't ideal... But this is here to handle an edge-case scenario so it's not like it'll be happening constantly.

…t/addrssables_redux

# Conflicts:
#	com.unity.netcode.gameobjects/CHANGELOG.md
- Reverted unintentional file change
- Addressed more review feedback
@0xFA11
Copy link
Copy Markdown
Contributor

0xFA11 commented Apr 21, 2022

you could also re-save (re-serialize) these scene files to cause Unity replace MessageBufferTimeout with SpawnTimeout

image

0xFA11
0xFA11 previously approved these changes Apr 21, 2022
@0xFA11 0xFA11 dismissed their stale review April 21, 2022 23:56

I need to understand a few things a little better.

Comment on lines +527 to +528
var sourcePrefabGlobalObjectIdHash = (uint)0;
var targetPrefabGlobalObjectIdHash = (uint)0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I can't quite follow objectidhash changes here and below. I'll try to grab you some time to discuss and understand better.

@ShadauxCat ShadauxCat closed this Apr 22, 2022
@ShadauxCat ShadauxCat reopened this Apr 22, 2022
@ShadauxCat
Copy link
Copy Markdown
Collaborator Author

Whoops. Did not mean to push the close button.

@ShadauxCat ShadauxCat requested a review from Cosmin-B April 22, 2022 19:22
@ShadauxCat ShadauxCat enabled auto-merge (squash) April 25, 2022 22:57
@ShadauxCat ShadauxCat merged commit 81290ba into develop Apr 25, 2022
@ShadauxCat ShadauxCat deleted the feat/addrssables_redux branch April 25, 2022 23:53
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.

6 participants