-
Notifications
You must be signed in to change notification settings - Fork 5
Singleton Removal #11
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
- Feature Name: `singleton-remove` | ||
- Start Date: 2021-02-11 | ||
- RFC PR: [RFC#11](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/pull/11) | ||
- Issue: [Unity-Technologies/com.unity.multiplayer#0000](https://github.com/Unity-Technologies/com.unity.multiplayer/issues/0000) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
This change removes the `NetworkingManager.Singleton` static, as well as the majority of other static state in MLAPI. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
The purpose of the change is to allow multiple instances of the MLAPI Networking Stack to co-exist in the same process. This enables: | ||
- in-process servers. This allows developers to architect single-player and host-mode versions of their multiplayer game identically to the dedicated-server version of the same game. | ||
- integration testing. Both games that use MLAPI and MLAPI itself can write full server/client tests that run entirely in the scope of a single process. | ||
- clients talking to multiple servers simultaneously. This is expected to be a requirement for MMO support. | ||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
MLAPI supports having multiple `NetworkingManager`s running concurrently. There are a few things to keep in mind when doing so: | ||
- The simplest case is to only have one `NetworkingManager` per scene. This is what you do to support an in-process server: load a "server" version of your scene, and then call `StartServer` on the `NetworkingManager` in it. All "server" physics calculations must then be performed via the `PhysicsScene` associated with the server scene. | ||
mattwalsh-unity marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, does the scene teardown automatically tear down its associated NetworkingManager? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think if you haven't marked the NetworkingManager to be DontDestroyOnLoad, then the NetworkingManager gets destroyed when its scene goes away. I believe the NetworkingManager does sensibly handle this, but if it doesn't, it wouldn't be hard to make it clean itself up in its |
||
- If you have multiple `NetworkingManager`s in one scene (for example, to talk to two different servers at once), then you should take care that all placed `NetworkedObject`s be wired to the `NetworkingManager` they are associated with via a serialized field. | ||
- `NetworkedObject`s spawned by a `NetworkingManager` will always be bound to the `NetworkingManager` that created them, and be placed in the same scene as their parent `NetworkingManager`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little bit curious about inter- Having said that, more persistent/resilient/reliable network ID generation would greatly help in this area as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, the only use-case I can think of for two NetworkManagers sharing the same NetworkObject is pretty advanced: the "seamless world" problem of having two adjacent blocks, managed by two different servers, and supporting a networked object crossing from server A to server B. I agree that would be nice to support at the MLAPI level, and probably would require a globally unique networkID. |
||
- If launching an in-process server, be sure to only call `StartClient` after the `OnServerStarted` callback has fired. | ||
- Currently only the ENet Transport is supported. UNet shares a common static at the Unity level (`NetworkTransport`), which prevents it from being fully separated. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This may be a showstopper; we can't live without UNet at least initially (and it's included in the SDK). ENet support is actually not in the current scope, and actually is only in the core SDK right now due to an oversight (though we certainly want Enet to work) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, how about the UTP impl? |
||
- We have removed the old `NetworkingManager.Singleton` method. For migrators, if you are writing code that in a component sitting on a `NetworkedObject`, you can simply get the `NetworkedObject`s `NetManager` instead. For other code, you will need to adopt your own strategy (for example, finding the `NetworkingManager` in the same scene as yourself via [`GameObject.FindWithTag`](https://docs.unity3d.com/ScriptReference/GameObject.FindWithTag.html)). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For consistenct, NetManager probably should be NetworkingManager There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 (at least for now). We have plans to rename/refactor API a little bit very soon, we would change some names but at least for now, let's keep it consistent with existing namings. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than finding |
||
- Note that the MLAPI Profiler doesn't support profiling multiple `NetworkingManager`s simultaneously. If run when multiple `NetworkingManager`s are active, it will profile the first `NetworkingManager` it finds. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Long term we're migrating to the Unity profiler, this should be ok, but good to call it out. I suppose the profiler could pop a warning in the log if > 1 NetworkingManagers are in play? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update, we have committed to eliminating the MLAPI profiler for March |
||
|
||
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
`NetworkingManager.Singleton` is removed. `SpawnManager`, `InternalMessageSender`, `InternalMessageHandler`, `NetworkSceneManager`, and `BitReaderPool` have all been made non-static, and hang off the `NetworkingManager` that owns them. | ||
|
||
`NetworkedObject`s have a `NetManager` field that is stamped on them at their moment of creation (when spawned by MLAPI), or should already be set in editor (for placed objects). If still unset by the time `NetworkedObject.Start` runs, the `NetworkedObject` takes the first `NetworkingManager` it finds in the same scene as itself. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this implicit behavior is somewhat shaky IMHO. I wouldn't expect There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I could live with this. I was tempted to suggest it assert loud and proud rather than magically fixing the NetworkingManager (NM), but then I think "when won't I want an object I instantiated in the scene to not be managed by that scene". I am curious though how you think one would set the NM of an object in the editor? Would the NMs be enumerable to where you could see a pickable list in the editor? Or would the setting of the NM need to be in code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, But having said that, this work is not required nor essential so we could address object-manager linking in the future and keep this auto-register idea around for now. I do not want to extend the scope here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My approach changed a little bit when I actually came to implement this. Now it's the NetworkManager that stamps itself on the NetworkObject (via Importantly, the NetworkManager won't stamp itself on a NetworkObject that is already claimed by a NetworkManager. Right now, this creates a worrying order-dependency for startup. If you have a "Chat" NetworkManager and a "Game" NetworkManager, you better hope the "Game" one starts up first! I think a good enhancement would be a |
||
|
||
A few things have been left static. `BitWriterPool` doesn't make use of `NetworkingManager` at all, and has consequently been left shared. The `LogLevel` has also been left static, as it is a bother to thread through into some of the places where it is used (however, I can see the argument for biting the bullet and doing so, as it would be nice to see whether log lines were generated from the server or client, respectively). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds like Server/Client/Host origin could be an (optional?) parameter in the log method signatures? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I really was missing that in my initial test. I have implemented it in this round. I maintained static "LogInfo" etc methods to be used when there was no other choice, but preferred non-static versions everywhere possible--these add the tag you mention, which makes the logging a lot more legible. |
||
|
||
For editor code, there is a new static method: `NetworkingManagerEditor.GetAnyNetworkingManager` that just wraps a [`FindObjectOfType`](https://docs.unity3d.com/ScriptReference/Object.FindObjectOfType.html) call. This is the replacement for editor code that needed to operate on "the NetworkingManager" (back when there was only one). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to what I said above, we could store There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Say we have that static array...if it's just a numeric array, does referencing it work? Like would one specify NetworkManager[2] vs. NetworkManager[1]? OTOH, you could have the key be a string and then you could get to the NM via NetworkManager["chat"] or NetworkManager[""] for the default. @dwoodruffsf thoughts on this central static array? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, static array or hashmap, the main idea being avoiding gameobject hierarchy traversal with |
||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
Singletons are convenient. For code that operates in the context of a `NetworkedObject`, this isn't a loss, but for any other code, it requires more thought. For games that have multiple `NetworkingManager`s (particularly in the same scene), there is no recourse to making users think harder about _which_ `NetworkingManager` they want to interact with. However, for games that don't make use of multiple `NetworkingManager`s, it creates a pain point with no upside. I've considered if we should just leave the `NetworkingManager.Singleton` in place (with the first `NetworkingManager` to claim it "winning"), just to maintain continuity for users that don't need this feature. However, there is a risk of this creating bugs and confusion for users who do start down the path of multiple `NetworkingManager`s. | ||
|
||
This also makes the editor facilities feel a bit more halfbaked. It would be nice if the MLAPIProfiler could provide feedback for all the `NetworkingManager` instances that are running, but this will entail more work. | ||
|
||
# Rationale and alternatives | ||
[rationale-and-alternatives]: #rationale-and-alternatives | ||
|
||
There is no alternative design that covers all use cases of removing MLAPI singleton dependence. An architecturally similar approach to supporting host-based games is to have the client start up a separate, out-of-process server, but this will increase the minspec of the Host machine, because it effectively doubles its memory requirements. There is no good solution to talking to multiple servers simultaneously via MLAPI. It may be possible to live without this feature when writing MMOs, as a common pattern is to have the client only talk to a single gateway server in production; however, forcing this pattern onto development is undesirable, as configuring a local gateway server can be nontrivial. | ||
|
||
# Prior art | ||
[prior-art]: #prior-art | ||
|
||
There is no specific prior art to cite, but avoiding static global state is a common software development recommendation, as it reduces system isolation and flexibility. | ||
|
||
# Unresolved questions | ||
[unresolved-questions]: #unresolved-questions | ||
mattwalsh-unity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
- Should we maintain a `NetworkingManager.Singleton` for the convenience of people not using this feature? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd highly argue against having 2 ways of doing things — my vote is NO :P There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, agree @MFatihMAR though we could argue if we have the central singleton thing that they might be tempted to replace NetworkingManager.Singleton with NetworkingManager.NetworkManager[0] |
||
- Should we force `NetworkLog` to need a `NetworkingManager` context? This makes it less convenient, but it means the logs could append `[SERVER]` or `[CLIENT]` tags. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I commented above, I'd suggest making Server/Client/Host tag a parameter and call-sites would specify that. |
||
- Are there any "must-do" changes to how I've handled MLAPIProfiler in particular, or is the current compromise of profiling the first-found `NetworkingManager` acceptable? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
# Future possibilities | ||
[future-possibilities]: #future-possibilities | ||
|
||
This is a fairly self-contained feature (really somewhere between a feature and a code refactor). While it enables new use patterns mentioned above, I don't have anything specific to bring up 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.
So usability-wise, one still creates an empty object, then adds the NetworkingManager component to it, yes?
Looking at scale demo, most of the singleton accesses are on IsClient / IsServer. So would is code...
....turn into...
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.
that's right--you'd have the NetworkingManager you wanted to interact with dragged in as serialized field.