Skip to content

Experimental/singleton removal merge #495

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

Closed
wants to merge 10 commits into from

Conversation

mattwalsh-unity
Copy link
Contributor

This is a draft PR so we can have a place to discuss the singleton removal

@unity-cla-assistant
Copy link

unity-cla-assistant commented Feb 24, 2021

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@mattwalsh-unity mattwalsh-unity requested review from TwoTenPvP, 0xFA11 and JesseOlmer and removed request for TwoTenPvP February 24, 2021 18:46
@mattwalsh-unity mattwalsh-unity marked this pull request as draft February 24, 2021 18:48
Comment on lines +7 to +8
// TODO: put this back!!!
/*namespace MLAPI.EditorTests
Copy link
Contributor

Choose a reason for hiding this comment

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

this is definitely not acceptable :)
I did this because I can no longer use var inReader = PooledBitReader.Get(inStream) API

Choose a reason for hiding this comment

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

I've fixed this is the new equivalent file (NetworkSerializerTests), although my solution requires a NetworkManager to be present in the test scene.

Comment on lines 643 to +645
public void SetSingleton()
{
Singleton = this;

if (OnSingletonReady != null)
OnSingletonReady();
OnSingletonReady?.Invoke();
Copy link
Contributor

Choose a reason for hiding this comment

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

this function and OnSingletonReady event should be gone, probably.

Choose a reason for hiding this comment

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

yes, removed it.

{
using (PooledBitReader reader = PooledBitReader.Get(stream))
using (var reader = networkingManager.PooledBitReaders.GetReader(stream))
Copy link
Contributor

Choose a reason for hiding this comment

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

I get the singleton-side change. But, out of curiosity, what's the rationale for using var instead of the type ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's kind of like the 'auto' specifier in C++.

/// </summary>
public static NetworkingManager GetAnyNetworkingManager()
{
return FindObjectOfType<NetworkingManager>();
Copy link
Contributor

Choose a reason for hiding this comment

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

While I get this is just editor code FindObjectOfType is one of the worst functions ever! Also assume we have more than one manager in a scene that has different settings does that matter ?

Copy link
Contributor

Choose a reason for hiding this comment

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

We had similar discussions over at the RFC-side in case you'd like to contribute :)

if(NetworkingManager.Singleton != null && NetworkingManager.Singleton.IsServer)

//FIXME: Singleton Conversion, get all of them and be sure to get the server!
NetworkingManager manager = NetworkingManagerEditor.GetNetworkingManager(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

So I think generally in the non-singleton case I would expect the editors to show only useful data if the actual object being selected is a NetworkManager... I just don't think that picking the first thing you see if actually useful.

Also FindObjectOfType doesn't guarantee order so you could call it more than once and get different objects which doesn't seem like the desired behavior either.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is probably the #1 unresolved issue in this addition

Copy link
Contributor

Choose a reason for hiding this comment

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

I called out something similar on the RFC-side, inlining my suggestions here too:

Rather than finding NetworkManager with GameObject.FindWithTag, it might be useful to have a static NetworkManager[] array globally where we could quickly iterate over and find the one we're looking for.

Similar to what I said above, we could store NetworkManager instances in a static NetworkManager[] container to avoid Object.FindObjectOfType call.

(response to Matt's Dictionary idea)
oh, static array or hashmap, the main idea being avoiding gameobject hierarchy traversal with Object.FindObjectOfType because seems like it could be unnecessary with a faster centralized static container lookup.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I think one way or another we'll want a central table of NMs

/// </summary>
public NetworkingManager NetworkManager => NetworkingManager.Singleton;
public NetworkingManager NetManager { get; internal set; }
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did we change the name to NetManager ?

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah, we shouldn't change that like this — we're going to have a renaming RFC soon where we will specifically discuss naming types.

Choose a reason for hiding this comment

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

these are all normalized to NetworkManager now.

return;
}

NetworkingManager networkingManager = networkedObjects[0].NetManager;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it guaranteed that every NetworkObject will have the same NetManager?

Copy link
Contributor Author

@mattwalsh-unity mattwalsh-unity Feb 26, 2021

Choose a reason for hiding this comment

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

@TwoTenPvP this (and the non-static NetworkShow) are not in use. Are these worth keeping around?

If so, we could make this an instance method off of NetworkedManager and if you pass in a NetworkedObject that doesn't belong to this NM, just skip it or throw an exception.

Or, it could be a static method off of NM. It really doesn't seem to have much to to with NetworkedObject. @wackoisgod not every NetworkObject has to have the same NM; but as written it would just show the pile of objects passed in regardless of NM.

Choose a reason for hiding this comment

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

It was very difficult for me to envision a scenario where you would hand this method a heterogenous list of NetworkObjects, belonging to multiple different NetworkManagers. It seemed like something that could be handled by specifying that as a method precondition in the method's comments, which I've added in my latest changes.

That said, I was anticipating these methods would go away in favor of matt's new visibility work.

@wackoisgod
Copy link
Contributor

wackoisgod commented Feb 25, 2021

So I am concerned about that var name change from NetworkManager to NetManager on things as that is a breaking change

container.ticks[i] = ProfilerTick.FromStream(stream);
}
{
container.ticks[i] = ProfilerTick.FromStream(manager, stream);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same story here, in the FromStream lambda, you can first read the NM id, then de-ref and not need to pass this in.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also was able to break this dep same was as above

@@ -279,7 +282,7 @@ public void FromBase64(string base64)
byte[] binary = Convert.FromBase64String(base64);
using (BitStream stream = new BitStream(binary))
{
using (PooledBitReader reader = PooledBitReader.Get(stream))
using (PooledBitReader reader = NetManager.PooledBitReaders.GetReader(stream))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

How come this has to hang off of NetManager?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed to be a static again

@@ -334,12 +341,19 @@ public void NetworkHide(ulong clientId)
/// <param name="clientId">The client to hide the objects from</param>
public static void NetworkHide(List<NetworkedObject> networkedObjects, ulong clientId)
{
if (!NetworkingManager.Singleton.IsServer)
if( networkedObjects.Count == 0 )
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same story as NetworkShow, above

{
throw new NotServerException("Only the server can perform lag compensation");
if( !simulatedObjects[0].NetObject.NetManager.IsServer )
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh boy, I guess you could however unlikely, have a collection of simulatedObjects were some are from one NM that is set up as a server and some are as a client. I haven't worked with LagCompensationManager though.

Is this telling us that this should be an instance method? There should be an LCM per NM?

{
if (!NetworkingManager.Singleton.IsServer)
if (!networkingManager.IsServer)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, again, maybe this is telling us this should be an instance method.

@@ -10,69 +10,76 @@

namespace MLAPI.Messaging
{
internal static class InternalMessageSender
internal class InternalMessageSender
Copy link
Contributor Author

@mattwalsh-unity mattwalsh-unity Feb 26, 2021

Choose a reason for hiding this comment

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

We could leave this class static (and stateless) and just pass in NM in each send. What does the room think? That's more like what the UnnamedMessageSender does

/// <summary>
/// The singleton instance of the NetworkingManager
/// </summary>
public static NetworkingManager Singleton { get; private set; }
Copy link
Contributor

@0xFA11 0xFA11 Feb 26, 2021

Choose a reason for hiding this comment

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

I think we should get rid of this singleton boss but this PR looks like a mess to me.
(I know this is not a very constructive feedback, sorry).

There are lots of things going on here:

  • NetManager/NetworkManager, NetObject and other naming inconsistencies
  • Assumptions like:
  • Controversial changes like pinning PooledBitReader, InternalMessageSender etc to NetworkManager
    • using (PooledBitReader reader = NetManager.PooledBitReaders.GetReader(stream))
  • other fundamental behavioral changes which could be counted as high risks

Overall, I believe we need to redo/rework singleton removal again and think about design changes more carefully.

However, if I should consider this PR only as a draft that's never going to be merged into develop, then I'm fine :)

This is a draft PR so we can have a place to discuss the singleton removal

I wanted to double-check the statement above.

{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_CleanBuffer.Begin();
#endif
foreach (KeyValuePair<ulong, Queue<BufferedMessage>> pair in bufferQueues)
{
while (pair.Value.Count > 0 && Time.realtimeSinceStartup - pair.Value.Peek().bufferTime >= NetworkingManager.Singleton.NetworkConfig.MessageBufferTimeout)
while (pair.Value.Count > 0 && Time.realtimeSinceStartup - pair.Value.Peek().bufferTime >= messageBufferTimeout)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we think we want to have per-NM messageBufferTimeouts or could we simplify and have one shared static one?

@@ -30,45 +30,47 @@ public static class CustomMessagingManager
/// </summary>
public static event UnnamedMessageDelegate OnUnnamedMessage;

internal static void InvokeUnnamedMessage(ulong clientId, Stream stream)
internal static void InvokeUnnamedMessage(NetworkingManager networkingManager, ulong clientId, Stream stream)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I look at this function I really wonder what if we mad this an instance method off of NetworkingManager? It's weird to have a static method that takes an instance and then does most of its work with that instance.

@@ -23,19 +23,25 @@ internal class RpcQueueProcessor
// Batcher object used to manage the RPC batching on the send side
private readonly RpcBatcher m_RpcBatcher = new RpcBatcher();
private const int k_BatchThreshold = 512;
private NetworkingManager m_NetManager;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

So it appears we have an NM instance in this class so that:

  • we can get an NM-specific RPC Queue Container, which I have some doubts on is the right abstraction
  • we can call NM:InvokeRPC (and, in turn, InvokeRPC is only in NM because it needs to find the right spawn manager, and maybe there's a cleaner way to do that)
  • we can call NM:MessageSender.Send()

/// <param name="stream">The stream containing the ProfilerTick data</param>
/// <returns>The ProfilerTick with data read from the stream</returns>
public static ProfilerTick FromStream(Stream stream)
public static ProfilerTick FromStream(NetworkingManager manager, Stream stream)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

NM is being passed in so that ProfilerTickData can get an NM so that it can get a PooledBitReader, which - not to belabor the point - should be detached from NM

@@ -55,15 +55,19 @@ public class SceneSwitchProgress
/// The callback invoked when a client is done loading the scene.
/// </summary>
public event OnClientLoadedSceneDelegate OnClientLoadedScene;

private NetworkingManager networkingManager;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

inconsistent, is netManager everywhere else


NetworkEventType eventType = relayTransport.ReceiveFromHost(hostId, out int connectionId, out int channelId, messageBuffer, messageBuffer.Length, out int receivedSize, out byte error);

if(eventType != NetworkEventType.Nothing )
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Did you mean to leave this in?

Copy link
Contributor Author

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

Mostly want to have a discussion on where we can break the NetworkManager dependencies

@@ -186,9 +184,10 @@ public object ReadObjectPacked(Type type)
{
ulong networkId = ReadUInt64Packed();

if (networkingManager.SpawnManager.SpawnedObjects.ContainsKey(networkId))
var netManager = SpawnManager.SpawnedObjectsByNetworkingManager[networkId];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

After talking to @TwoTenPvP, I've come to see this is probably not correct code. Instead, I intend to remove this table and instead pass the NM into ReadObjectPacked.

{
foreach (var manager in FindObjectsOfType<NetworkingManager>())
{
if (manager.IsServer == isServer)
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

no, that's for the NetworkedObjectEditor.cs in the Editor, not at Runtime I believe, no?

@dwoodruffsf
Copy link

Just a heads up that I have updated this branch to fix all merge conflicts with develop. The branch is mergeable (at least as of this comment), but for some reasons, neither my latest changes, nor the merge status, is showing properly in this PR (maybe because this is Matt's PR, and it still represents the merge at the last commit that Matt made it?).

@dwoodruffsf
Copy link

Follow on note: I have finished an integration test against the BossRoom sample (which turned up a ton of problems!) I have fixed all of them, and I now think this PR could probably be taken (or rather a new one, from the latest commit on this branch). A couple of interesting things did change:

  1. Added ActiveScene, FindObjectsOfTypeInScene, and FindGameObjectsWithTagInScene methods to NetworkManager. These are worth looking at; they encapsulate the weirdness of what it means to say a NetworkManager is "associated" with a scene, and have usability considerations, particularly for devs that are setting DontDestroyOnLoad on their NetworkManagers.

  2. Made NetworkWriterPool symmetric with NetworkReaderPool; now they both live on NetworkManager, even though NetworkWriterPool doesn't really need NetworkManager for anything except logging. This is basically the opposite direction that Matt was going where he was trying to break the dependency between NetworkManager and the serialization layer. What would you guys think about just removing the Read/Write(GameObject) magic? This seems like a low-value feature to me, and it's causing lots of problems with this refactor--without it we could easily make the NetworkReader/Writer pools static, without hitting the concerns I expressed to Matt in a side thread.

@TwoTenPvP
Copy link
Contributor

@mattwalsh-unity Can this be closed?

@JesseOlmer
Copy link
Contributor

@mattwalsh-unity can we close this?

@0xFA11 0xFA11 deleted the experimental/singleton-removal-merge branch May 19, 2021 17:04
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.

8 participants