// base class for networking tests to make things easier. using System.Collections.Generic; using System.Linq; using NUnit.Framework; using UnityEngine; namespace Mirror.Tests { // inherited by MirrorEditModeTest / MirrorPlayModeTest // to call SetUp/TearDown by [SetUp]/[UnitySetUp] as needed public abstract class MirrorTest { // keep track of networked GameObjects so we don't have to clean them // up manually each time. // CreateNetworked() adds to the list automatically. public List instantiated; // we usually need the memory transport public GameObject holder; public MemoryTransport transport; public virtual void SetUp() { instantiated = new List(); // need a holder GO. with name for easier debugging. holder = new GameObject("MirrorTest.holder"); // need a transport to send & receive Transport.activeTransport = transport = holder.AddComponent(); } public virtual void TearDown() { NetworkClient.Shutdown(); NetworkServer.Shutdown(); // some tests might modify NetworkServer.connections without ever // starting the server. // NetworkServer.Shutdown() only clears connections if it was started. // so let's do it manually for proper test cleanup here. NetworkServer.connections.Clear(); foreach (GameObject go in instantiated) if (go != null) GameObject.DestroyImmediate(go); GameObject.DestroyImmediate(transport.gameObject); Transport.activeTransport = null; NetworkManager.singleton = null; } // create a tracked GameObject for tests without Networkidentity // add to tracker list if needed (useful for cleanups afterwards) protected void CreateGameObject(out GameObject go) { go = new GameObject(); // track instantiated.Add(go); } // create GameObject + MonoBehaviour // add to tracker list if needed (useful for cleanups afterwards) protected void CreateGameObject(out GameObject go, out T component) where T : MonoBehaviour { CreateGameObject(out go); component = go.AddComponent(); } // create GameObject + NetworkIdentity // add to tracker list if needed (useful for cleanups afterwards) protected void CreateNetworked(out GameObject go, out NetworkIdentity identity) { go = new GameObject(); identity = go.AddComponent(); // Awake is only called in play mode. // call manually for initialization. identity.Awake(); // track instantiated.Add(go); } // create GameObject + NetworkIdentity + NetworkBehaviour // add to tracker list if needed (useful for cleanups afterwards) protected void CreateNetworked(out GameObject go, out NetworkIdentity identity, out T component) where T : NetworkBehaviour { go = new GameObject(); identity = go.AddComponent(); component = go.AddComponent(); // always set syncinterval = 0 for immediate testing component.syncInterval = 0; // Awake is only called in play mode. // call manually for initialization. identity.Awake(); // track instantiated.Add(go); } // create GameObject + NetworkIdentity + 2x NetworkBehaviour // add to tracker list if needed (useful for cleanups afterwards) protected void CreateNetworked(out GameObject go, out NetworkIdentity identity, out T componentA, out U componentB) where T : NetworkBehaviour where U : NetworkBehaviour { go = new GameObject(); identity = go.AddComponent(); componentA = go.AddComponent(); componentB = go.AddComponent(); // always set syncinterval = 0 for immediate testing componentA.syncInterval = 0; componentB.syncInterval = 0; // Awake is only called in play mode. // call manually for initialization. identity.Awake(); // track instantiated.Add(go); } // create GameObject + NetworkIdentity + 2x NetworkBehaviour // add to tracker list if needed (useful for cleanups afterwards) protected void CreateNetworked(out GameObject go, out NetworkIdentity identity, out T componentA, out U componentB, out V componentC) where T : NetworkBehaviour where U : NetworkBehaviour where V : NetworkBehaviour { go = new GameObject(); identity = go.AddComponent(); componentA = go.AddComponent(); componentB = go.AddComponent(); componentC = go.AddComponent(); // always set syncinterval = 0 for immediate testing componentA.syncInterval = 0; componentB.syncInterval = 0; componentC.syncInterval = 0; // Awake is only called in play mode. // call manually for initialization. identity.Awake(); // track instantiated.Add(go); } // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN // => ownerConnection can be NetworkServer.localConnection if needed. protected void CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity identity, NetworkConnectionToClient ownerConnection = null) { // server & client need to be active before spawning Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); CreateNetworked(out go, out identity); // spawn NetworkServer.Spawn(go, ownerConnection); ProcessMessages(); } // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN // => ownerConnection can be NetworkServer.localConnection if needed. // => returns objects from client and from server. // will be same in host mode. protected void CreateNetworkedAndSpawn( out GameObject serverGO, out NetworkIdentity serverIdentity, out GameObject clientGO, out NetworkIdentity clientIdentity, NetworkConnectionToClient ownerConnection = null) { // server & client need to be active before spawning Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); // create one on server, one on client // (spawning has to find it on client, it doesn't create it) CreateNetworked(out serverGO, out serverIdentity); CreateNetworked(out clientGO, out clientIdentity); // give both a scene id and register it on client for spawnables clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverGO.GetHashCode(); NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; // spawn NetworkServer.Spawn(serverGO, ownerConnection); ProcessMessages(); // double check isServer/isClient. avoids debugging headaches. Assert.That(serverIdentity.isServer, Is.True); Assert.That(clientIdentity.isClient, Is.True); // double check that we have authority if we passed an owner connection if (ownerConnection != null) Debug.Assert(clientIdentity.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); // make sure the client really spawned it. Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); } // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN // => ownerConnection can be NetworkServer.localConnection if needed. protected void CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity identity, out T component, NetworkConnectionToClient ownerConnection = null) where T : NetworkBehaviour { // server & client need to be active before spawning Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); CreateNetworked(out go, out identity, out component); // spawn NetworkServer.Spawn(go, ownerConnection); ProcessMessages(); // double check that we have authority if we passed an owner connection if (ownerConnection != null) Debug.Assert(component.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); } // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN // => ownerConnection can be NetworkServer.localConnection if needed. // => returns objects from client and from server. // will be same in host mode. protected void CreateNetworkedAndSpawn( out GameObject serverGO, out NetworkIdentity serverIdentity, out T serverComponent, out GameObject clientGO, out NetworkIdentity clientIdentity, out T clientComponent, NetworkConnectionToClient ownerConnection = null) where T : NetworkBehaviour { // server & client need to be active before spawning Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); // create one on server, one on client // (spawning has to find it on client, it doesn't create it) CreateNetworked(out serverGO, out serverIdentity, out serverComponent); CreateNetworked(out clientGO, out clientIdentity, out clientComponent); // give both a scene id and register it on client for spawnables clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverGO.GetHashCode(); NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; // spawn NetworkServer.Spawn(serverGO, ownerConnection); ProcessMessages(); // double check isServer/isClient. avoids debugging headaches. Assert.That(serverIdentity.isServer, Is.True); Assert.That(clientIdentity.isClient, Is.True); // double check that we have authority if we passed an owner connection if (ownerConnection != null) Debug.Assert(clientComponent.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); // make sure the client really spawned it. Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); } // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN // => ownerConnection can be NetworkServer.localConnection if needed. protected void CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity identity, out T componentA, out U componentB, NetworkConnectionToClient ownerConnection = null) where T : NetworkBehaviour where U : NetworkBehaviour { // server & client need to be active before spawning Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); CreateNetworked(out go, out identity, out componentA, out componentB); // spawn NetworkServer.Spawn(go, ownerConnection); ProcessMessages(); // double check that we have authority if we passed an owner connection if (ownerConnection != null) { Debug.Assert(componentA.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); Debug.Assert(componentB.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); } } // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN // => ownerConnection can be NetworkServer.localConnection if needed. // => returns objects from client and from server. // will be same in host mode. protected void CreateNetworkedAndSpawn( out GameObject serverGO, out NetworkIdentity serverIdentity, out T serverComponentA, out U serverComponentB, out GameObject clientGO, out NetworkIdentity clientIdentity, out T clientComponentA, out U clientComponentB, NetworkConnectionToClient ownerConnection = null) where T : NetworkBehaviour where U : NetworkBehaviour { // server & client need to be active before spawning Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); // create one on server, one on client // (spawning has to find it on client, it doesn't create it) CreateNetworked(out serverGO, out serverIdentity, out serverComponentA, out serverComponentB); CreateNetworked(out clientGO, out clientIdentity, out clientComponentA, out clientComponentB); // give both a scene id and register it on client for spawnables clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverGO.GetHashCode(); NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; // spawn NetworkServer.Spawn(serverGO, ownerConnection); ProcessMessages(); // double check isServer/isClient. avoids debugging headaches. Assert.That(serverIdentity.isServer, Is.True); Assert.That(clientIdentity.isClient, Is.True); // double check that we have authority if we passed an owner connection if (ownerConnection != null) { Debug.Assert(clientComponentA.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); Debug.Assert(clientComponentB.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); } // make sure the client really spawned it. Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); } // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN // => ownerConnection can be NetworkServer.localConnection if needed. protected void CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity identity, out T componentA, out U componentB, out V componentC, NetworkConnectionToClient ownerConnection = null) where T : NetworkBehaviour where U : NetworkBehaviour where V : NetworkBehaviour { // server & client need to be active before spawning Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); CreateNetworked(out go, out identity, out componentA, out componentB, out componentC); // spawn NetworkServer.Spawn(go, ownerConnection); ProcessMessages(); // double check that we have authority if we passed an owner connection if (ownerConnection != null) { Debug.Assert(componentA.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); Debug.Assert(componentB.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); Debug.Assert(componentC.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); } } // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN // => ownerConnection can be NetworkServer.localConnection if needed. // => returns objects from client and from server. // will be same in host mode. protected void CreateNetworkedAndSpawn( out GameObject serverGO, out NetworkIdentity serverIdentity, out T serverComponentA, out U serverComponentB, out V serverComponentC, out GameObject clientGO, out NetworkIdentity clientIdentity, out T clientComponentA, out U clientComponentB, out V clientComponentC, NetworkConnectionToClient ownerConnection = null) where T : NetworkBehaviour where U : NetworkBehaviour where V : NetworkBehaviour { // server & client need to be active before spawning Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); // create one on server, one on client // (spawning has to find it on client, it doesn't create it) CreateNetworked(out serverGO, out serverIdentity, out serverComponentA, out serverComponentB, out serverComponentC); CreateNetworked(out clientGO, out clientIdentity, out clientComponentA, out clientComponentB, out clientComponentC); // give both a scene id and register it on client for spawnables clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverGO.GetHashCode(); NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; // spawn NetworkServer.Spawn(serverGO, ownerConnection); ProcessMessages(); // double check isServer/isClient. avoids debugging headaches. Assert.That(serverIdentity.isServer, Is.True); Assert.That(clientIdentity.isClient, Is.True); // double check that we have authority if we passed an owner connection if (ownerConnection != null) { Debug.Assert(clientComponentA.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); Debug.Assert(clientComponentB.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); Debug.Assert(clientComponentC.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); } // make sure the client really spawned it. Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); } // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN PLAYER. // often times, we really need a player object for the client to receive // certain messages. protected void CreateNetworkedAndSpawnPlayer(out GameObject go, out NetworkIdentity identity, NetworkConnectionToClient ownerConnection) { // server & client need to be active before spawning Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); // create a networked object CreateNetworked(out go, out identity); // add as player & process spawn message on client. NetworkServer.AddPlayerForConnection(ownerConnection, go); ProcessMessages(); } // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN PLAYER. // often times, we really need a player object for the client to receive // certain messages. // => returns objects from client and from server. // will be same in host mode. protected void CreateNetworkedAndSpawnPlayer( out GameObject serverGO, out NetworkIdentity serverIdentity, out GameObject clientGO, out NetworkIdentity clientIdentity, NetworkConnectionToClient ownerConnection) { // server & client need to be active before spawning Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); // create one on server, one on client // (spawning has to find it on client, it doesn't create it) CreateNetworked(out serverGO, out serverIdentity); CreateNetworked(out clientGO, out clientIdentity); // give both a scene id and register it on client for spawnables clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverGO.GetHashCode(); NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; // IMPORTANT: OnSpawn finds 'sceneId' in .spawnableObjects. // only those who are ConsiderForSpawn() are in there. // for scene objects to be considered, they need to be disabled. // (it'll be active by the time we return here) clientGO.SetActive(false); // add as player & process spawn message on client. NetworkServer.AddPlayerForConnection(ownerConnection, serverGO); ProcessMessages(); // double check isServer/isClient. avoids debugging headaches. Assert.That(serverIdentity.isServer, Is.True); Assert.That(clientIdentity.isClient, Is.True); // make sure the client really spawned it. Assert.That(clientGO.activeSelf, Is.True); Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); // double check that client object's isClient is really true! // previously a test magically failed because isClient was false // even though it should've been true! Assert.That(clientIdentity.isClient, Is.True); } // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN PLAYER. // often times, we really need a player object for the client to receive // certain messages. protected void CreateNetworkedAndSpawnPlayer(out GameObject go, out NetworkIdentity identity, out T component, NetworkConnectionToClient ownerConnection) where T : NetworkBehaviour { // server & client need to be active before spawning Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); // create a networked object CreateNetworked(out go, out identity, out component); // add as player & process spawn message on client. NetworkServer.AddPlayerForConnection(ownerConnection, go); ProcessMessages(); } // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN PLAYER. // often times, we really need a player object for the client to receive // certain messages. // => returns objects from client and from server. // will be same in host mode. protected void CreateNetworkedAndSpawnPlayer( out GameObject serverGO, out NetworkIdentity serverIdentity, out T serverComponent, out GameObject clientGO, out NetworkIdentity clientIdentity, out T clientComponent, NetworkConnectionToClient ownerConnection) where T : NetworkBehaviour { // server & client need to be active before spawning Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); // create one on server, one on client // (spawning has to find it on client, it doesn't create it) CreateNetworked(out serverGO, out serverIdentity, out serverComponent); CreateNetworked(out clientGO, out clientIdentity, out clientComponent); // give both a scene id and register it on client for spawnables clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverGO.GetHashCode(); NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; // IMPORTANT: OnSpawn finds 'sceneId' in .spawnableObjects. // only those who are ConsiderForSpawn() are in there. // for scene objects to be considered, they need to be disabled. // (it'll be active by the time we return here) clientGO.SetActive(false); // add as player & process spawn message on client. NetworkServer.AddPlayerForConnection(ownerConnection, serverGO); ProcessMessages(); // double check isServer/isClient. avoids debugging headaches. Assert.That(serverIdentity.isServer, Is.True); Assert.That(clientIdentity.isClient, Is.True); // make sure the client really spawned it. Assert.That(clientGO.activeSelf, Is.True); Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); } // create GameObject + NetworkIdentity + NetworkBehaviours & SPAWN PLAYER. // often times, we really need a player object for the client to receive // certain messages. // => returns objects from client and from server. // will be same in host mode. protected void CreateNetworkedAndSpawnPlayer( out GameObject serverGO, out NetworkIdentity serverIdentity, out T serverComponentA, out U serverComponentB, out GameObject clientGO, out NetworkIdentity clientIdentity, out T clientComponentA, out U clientComponentB, NetworkConnectionToClient ownerConnection) where T : NetworkBehaviour where U : NetworkBehaviour { // server & client need to be active before spawning Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); // create one on server, one on client // (spawning has to find it on client, it doesn't create it) CreateNetworked(out serverGO, out serverIdentity, out serverComponentA, out serverComponentB); CreateNetworked(out clientGO, out clientIdentity, out clientComponentA, out clientComponentB); // give both a scene id and register it on client for spawnables clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverGO.GetHashCode(); NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; // IMPORTANT: OnSpawn finds 'sceneId' in .spawnableObjects. // only those who are ConsiderForSpawn() are in there. // for scene objects to be considered, they need to be disabled. // (it'll be active by the time we return here) clientGO.SetActive(false); // add as player & process spawn message on client. NetworkServer.AddPlayerForConnection(ownerConnection, serverGO); ProcessMessages(); // double check isServer/isClient. avoids debugging headaches. Assert.That(serverIdentity.isServer, Is.True); Assert.That(clientIdentity.isClient, Is.True); // make sure the client really spawned it. Assert.That(clientGO.activeSelf, Is.True); Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); } // fully connect client to local server // gives out the server's connection to client for convenience if needed protected void ConnectClientBlocking(out NetworkConnectionToClient connectionToClient) { NetworkClient.Connect("127.0.0.1"); UpdateTransport(); Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); connectionToClient = NetworkServer.connections.Values.First(); // set isSpawnFinished flag. // so that any Spawn() calls will call OnStartClient and set isClient=true // for the client objects. // otherwise this would only happen after AddPlayerForConnection. // but not all tests have a player. NetworkClient.isSpawnFinished = true; } // fully connect client to local server & authenticate protected void ConnectClientBlockingAuthenticated(out NetworkConnectionToClient connectionToClient) { ConnectClientBlocking(out connectionToClient); // authenticate server & client connections connectionToClient.isAuthenticated = true; NetworkClient.connection.isAuthenticated = true; } // fully connect client to local server & authenticate & set read protected void ConnectClientBlockingAuthenticatedAndReady(out NetworkConnectionToClient connectionToClient) { ConnectClientBlocking(out connectionToClient); // authenticate server & client connections connectionToClient.isAuthenticated = true; NetworkClient.connection.isAuthenticated = true; // set ready NetworkClient.Ready(); ProcessMessages(); Assert.That(connectionToClient.isReady, Is.True); } // fully connect HOST client to local server // sets NetworkServer.localConnection / NetworkClient.connection. protected void ConnectHostClientBlocking() { NetworkClient.ConnectHost(); NetworkClient.ConnectLocalServer(); UpdateTransport(); Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); // set isSpawnFinished flag. // so that any Spawn() calls will call OnStartClient and set isClient=true // for the client objects. // otherwise this would only happen after AddPlayerForConnection. // but not all tests have a player. NetworkClient.isSpawnFinished = true; } // fully connect client to local server & authenticate & set read protected void ConnectHostClientBlockingAuthenticatedAndReady() { ConnectHostClientBlocking(); // authenticate server & client connections NetworkServer.localConnection.isAuthenticated = true; NetworkClient.connection.isAuthenticated = true; // set ready NetworkClient.Ready(); ProcessMessages(); Assert.That(NetworkServer.localConnection.isReady, Is.True); } protected void UpdateTransport() { transport.ClientEarlyUpdate(); transport.ServerEarlyUpdate(); } protected void ProcessMessages() { // server & client need to be active Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); // update server & client so batched messages are flushed NetworkClient.NetworkLateUpdate(); NetworkServer.NetworkLateUpdate(); // update transport so sent messages are received UpdateTransport(); } // helper function to create local connection pair protected void CreateLocalConnectionPair(out LocalConnectionToClient connectionToClient, out LocalConnectionToServer connectionToServer) { connectionToClient = new LocalConnectionToClient(); connectionToServer = new LocalConnectionToServer(); connectionToClient.connectionToServer = connectionToServer; connectionToServer.connectionToClient = connectionToClient; } } }