Skip to content

refactor: replacing GameNetPortal with state machine [MTT-1742] [MTT-3501] [MTT-3502] #666

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 87 commits into from
Jul 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
decf8bf
Adding ConnectionManager skeleton
LPLafontaineB May 24, 2022
f774161
initial state machine implementation
LPLafontaineB May 25, 2022
40f7167
simplifying initial spawn logic
LPLafontaineB May 26, 2022
0ab59b2
Adding disconnect reason handling client-side
LPLafontaineB May 26, 2022
0382392
using state machine's monobehaviour for coroutines
LPLafontaineB May 26, 2022
2d3b0bb
replacing abstract methods with empty virtual ones
LPLafontaineB May 26, 2022
314f670
implementing reconnecting state
LPLafontaineB May 26, 2022
385fff2
simplifying reconnect state
LPLafontaineB May 26, 2022
5263998
removing implementation without state machine
LPLafontaineB May 26, 2022
fb212ec
cleanup
LPLafontaineB May 26, 2022
f923235
cleanup
LPLafontaineB May 26, 2022
bedb352
removing duplicated call
LPLafontaineB May 27, 2022
6237f47
fix: making sure reconnecting state goes to offline state once attemp…
LPLafontaineB May 27, 2022
523e077
fixing user-requested shutdown
LPLafontaineB May 27, 2022
ac49f7c
Used inheritance to simplify Reconnecting state
LPLafontaineB May 30, 2022
c7eaf27
removing unneeded using directives
LPLafontaineB May 30, 2022
5a808c0
removing unneeded method
LPLafontaineB May 30, 2022
0461c0b
small bug fixes
LPLafontaineB May 30, 2022
6b3ac0f
reorganized DI so that only states get injected in
LPLafontaineB May 30, 2022
fe1880e
cleaning up
LPLafontaineB May 30, 2022
109833d
removing GameNetPortal
LPLafontaineB May 30, 2022
64f61d1
removing clientScene from connection payload
LPLafontaineB May 31, 2022
2516936
cleaning up old implementation
LPLafontaineB Jun 2, 2022
40b66e6
deregistering to connectionApproval callback
LPLafontaineB Jun 3, 2022
7bc15ca
replacing enum with static members of ConnectionState
LPLafontaineB Jun 7, 2022
efcc4b8
formatting
LPLafontaineB Jun 7, 2022
3912002
simplified state initializations
LPLafontaineB Jun 7, 2022
d95e29b
formatting
LPLafontaineB Jun 7, 2022
a929a1a
Removing DisconnectReason
LPLafontaineB Jun 7, 2022
aa9b3a3
Adding comments
LPLafontaineB Jun 7, 2022
700b739
more formatting
LPLafontaineB Jun 7, 2022
c19c1bc
More comments
LPLafontaineB Jun 7, 2022
0356b9a
Removing references to AvatarRegistry in ConnectionManager
LPLafontaineB Jun 8, 2022
256f0ae
updating changelog
LPLafontaineB Jun 8, 2022
cd56a35
fixing error in changelog
LPLafontaineB Jun 8, 2022
cd7b918
Renamed connection states to clarify them
LPLafontaineB Jun 9, 2022
80fd94d
improved clarity of debug message when changing states
LPLafontaineB Jun 9, 2022
ce0696a
moving quitGameSession responsibility from ApplicationController to C…
LPLafontaineB Jun 9, 2022
45de54c
adjusting delays before shutdown/approval denial
LPLafontaineB Jun 9, 2022
9904023
moved disconnect reason handling responsibility to connection states
LPLafontaineB Jun 10, 2022
2cf47ba
fixed handling of connection denied by approval callback
LPLafontaineB Jun 10, 2022
cb481ab
cleaning up comments
LPLafontaineB Jun 10, 2022
3e01a71
Apply suggestions from code review
LPLafontaineB Jun 15, 2022
b7513ab
Merge branch 'develop' into lplb/gamenetportal-refactor
LPLafontaineB Jun 15, 2022
f0288b6
Added StartingHostState
LPLafontaineB Jun 15, 2022
714d33d
renaming unused parameters
LPLafontaineB Jun 15, 2022
c5f01a8
Moving relay allocation/join responsibility to Connecting/StartingHos…
LPLafontaineB Jun 21, 2022
e4a567a
Not rethrowing exception if lobby not found while attempting to leave it
LPLafontaineB Jun 21, 2022
a35968e
removed IsServer to avoid silencing potential errors
LPLafontaineB Jun 21, 2022
02a4fe4
moving method description to class that actually implements it
LPLafontaineB Jun 21, 2022
053d05f
moved state transitions in StartingHost to be more explicit
LPLafontaineB Jun 21, 2022
e2b1423
removed unnecessary deletion of lobby when host shuts down
LPLafontaineB Jun 21, 2022
def793d
renaming method
LPLafontaineB Jun 22, 2022
2db5499
Moving state change from public method to callback from the states
LPLafontaineB Jun 22, 2022
54fc503
moving shutdown to OfflineState's Enter method
LPLafontaineB Jun 22, 2022
f99bd5d
Adding tests base
LPLafontaineB Jun 23, 2022
d9ab783
Merge branch 'develop' into lplb/gamenetportal-refactor
LPLafontaineB Jun 23, 2022
8ee8cfe
Adding testables to manifest
LPLafontaineB Jun 23, 2022
e521195
Updating connection approval with pre.10 changes
LPLafontaineB Jun 23, 2022
01a0eb2
updating class descriptions of states
LPLafontaineB Jun 23, 2022
470fcde
updating class descriptions of states (part 2)
LPLafontaineB Jun 23, 2022
4aca034
Moving connection management to new assembly
LPLafontaineB Jun 27, 2022
51c4ff4
removing conditional operator from StateChangeRequest so that it trig…
LPLafontaineB Jun 29, 2022
cc84ca9
Added explanatory comment to CurrentState setter
LPLafontaineB Jun 29, 2022
be3cfff
renaming k_MaxLobbyPlayers and setting as serialized field
LPLafontaineB Jun 29, 2022
8f04c12
Changed states from static fields in ConnectionState to non-static fi…
LPLafontaineB Jun 29, 2022
d37e6e6
removing IsHost check in StartingHostState
LPLafontaineB Jun 30, 2022
afcffbe
removing obsolete method from ClientCharSelectState
LPLafontaineB Jun 30, 2022
29e6e5a
renaming GameNetPortal GameObject in Startup scene
LPLafontaineB Jun 30, 2022
e729b92
made connection states internal to simplify state change flow while k…
LPLafontaineB Jun 30, 2022
37dca8c
removing test base (will add proper tests in a further PR)
LPLafontaineB Jun 30, 2022
9ff4cd6
Revert "Adding testables to manifest"
LPLafontaineB Jun 30, 2022
7303007
set max number of players in lobby based on field in ConnectionManager
LPLafontaineB Jun 30, 2022
d26d52b
reverting change to ApplicationController
LPLafontaineB Jun 30, 2022
87c173d
Merge branch 'develop' into lplb/gamenetportal-refactor
LPLafontaineB Jul 5, 2022
4e052b4
fixed conflicts with startup scene
LPLafontaineB Jul 5, 2022
7498702
fixed conflicts in asmdefs
LPLafontaineB Jul 5, 2022
f733ae3
replacing use of old DI with VContainer
LPLafontaineB Jul 5, 2022
b9afaca
Fixing conflicts in ServerBossRoomState
LPLafontaineB Jul 5, 2022
30815f7
Fixed injection in ConnectionStates
LPLafontaineB Jul 6, 2022
2d5cd2a
Made ClientConnectingState's m_LocalLobby protected so it could be in…
LPLafontaineB Jul 6, 2022
f5debcd
removing unneeded using directives
LPLafontaineB Jul 6, 2022
ec046e5
updated changelog
LPLafontaineB Jul 6, 2022
b949e49
removing extra whitespaces
LPLafontaineB Jul 6, 2022
de9f67f
using plain text instead of guids for asmdef
LPLafontaineB Jul 6, 2022
d6ee26d
adding ConnectStatus publisher as a field of base ConnectionState
LPLafontaineB Jul 6, 2022
73abe8f
Merge branch 'develop' into lplb/gamenetportal-refactor
LPLafontaineB Jul 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 4 additions & 30 deletions Assets/Prefabs/GameNetPortal.prefab
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ GameObject:
serializedVersion: 6
m_Component:
- component: {fileID: 5062837533140207352}
- component: {fileID: 5062837533140207350}
- component: {fileID: 5062837533140207353}
- component: {fileID: 5062837533140207351}
- component: {fileID: 1984353324962460586}
m_Layer: 0
m_Name: GameNetPortal
m_TagString: GameNetPortal
Expand All @@ -29,11 +27,12 @@ Transform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &5062837533140207350
--- !u!114 &1984353324962460586
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
Expand All @@ -42,34 +41,9 @@ MonoBehaviour:
m_GameObject: {fileID: 5062837533140207349}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7ba4a1d598313d94a81a08ceae147b57, type: 3}
m_Script: {fileID: 11500000, guid: bf51ea31bbc84cc3aa883cc5f1caae06, type: 3}
m_Name:
m_EditorClassIdentifier:
m_NetworkManager: {fileID: 0}
m_AvatarRegistry: {fileID: 11400000, guid: 48d17d764bff6c643a3dc035fb71c979, type: 2}
PlayerName:
--- !u!114 &5062837533140207353
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5062837533140207349}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5bd31edc1b1fd1342b3bf8193a5a03b2, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &5062837533140207351
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5062837533140207349}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 54b0859a1da930541adf0b2d1bb8d977, type: 3}
m_Name:
m_EditorClassIdentifier:
m_GameState: {fileID: 7151198093957655792, guid: 3af96a32a84bcf74d9538fa7af973c97, type: 3}
4 changes: 2 additions & 2 deletions Assets/Scenes/Startup.unity
Git LFS file not shown
30 changes: 2 additions & 28 deletions Assets/Scripts/ApplicationLifecycle/ApplicationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@
using System.Collections;
using BossRoom.Scripts.Shared.Net.UnityServices.Auth;
using Unity.Multiplayer.Samples.BossRoom.ApplicationLifecycle.Messages;
using Unity.Multiplayer.Samples.BossRoom.Client;
using Unity.Multiplayer.Samples.BossRoom.Server;
using Unity.Multiplayer.Samples.BossRoom.Shared.Infrastructure;
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Infrastructure;
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Lobbies;
using Unity.Multiplayer.Samples.Utilities;
using UnityEngine;
using UnityEngine.SceneManagement;
using VContainer;
Expand All @@ -22,9 +19,7 @@ namespace Unity.Multiplayer.Samples.BossRoom.Shared
public class ApplicationController : LifetimeScope
{
[SerializeField] UpdateRunner m_UpdateRunner;
[SerializeField] GameNetPortal m_GameNetPortal;
[SerializeField] ClientGameNetPortal m_ClientNetPortal;
[SerializeField] ServerGameNetPortal m_ServerGameNetPortal;
[SerializeField] ConnectionManager m_ConnectionManager;

LocalLobby m_LocalLobby;
LobbyServiceFacade m_LobbyServiceFacade;
Expand All @@ -35,9 +30,7 @@ protected override void Configure(IContainerBuilder builder)
{
base.Configure(builder);
builder.RegisterComponent(m_UpdateRunner);
builder.RegisterComponent(m_GameNetPortal);
builder.RegisterComponent(m_ClientNetPortal);
builder.RegisterComponent(m_ServerGameNetPortal);
builder.RegisterComponent(m_ConnectionManager);

//the following singletons represent the local representations of the lobby that we're in and the user that we are
//they can persist longer than the lifetime of the UI in MainMenu where we set up the lobby that we create or join
Expand All @@ -48,7 +41,6 @@ protected override void Configure(IContainerBuilder builder)

//these message channels are essential and persist for the lifetime of the lobby and relay services
// Registering as instance to prevent code stripping on iOS
builder.RegisterInstance(new MessageChannel<QuitGameSessionMessage>()).AsImplementedInterfaces();
builder.RegisterInstance(new MessageChannel<QuitApplicationMessage>()).AsImplementedInterfaces();
builder.RegisterInstance(new MessageChannel<UnityServiceErrorMessage>()).AsImplementedInterfaces();
builder.RegisterInstance(new MessageChannel<ConnectStatus>()).AsImplementedInterfaces();
Expand Down Expand Up @@ -80,11 +72,9 @@ private void Start()
m_LocalLobby = Container.Resolve<LocalLobby>();
m_LobbyServiceFacade = Container.Resolve<LobbyServiceFacade>();

var quitGameSessionSub = Container.Resolve<ISubscriber<QuitGameSessionMessage>>();
var quitApplicationSub = Container.Resolve<ISubscriber<QuitApplicationMessage>>();

var subHandles = new DisposableGroup();
subHandles.Add(quitGameSessionSub.Subscribe(LeaveSession));
subHandles.Add(quitApplicationSub.Subscribe(QuitGame));
m_Subscriptions = subHandles;

Expand Down Expand Up @@ -131,22 +121,6 @@ private bool OnWantToQuit()
return canQuit;
}

private void LeaveSession(QuitGameSessionMessage msg)
{
m_LobbyServiceFacade.EndTracking();

if (msg.UserRequested)
{
// first disconnect then return to menu
var gameNetPortal = GameNetPortal.Instance;
if (gameNetPortal != null)
{
gameNetPortal.RequestDisconnect();
}
}
SceneLoaderWrapper.Instance.LoadScene("MainMenu", useNetworkSceneManager: false);
}

private void QuitGame(QuitApplicationMessage msg)
{
#if UNITY_EDITOR
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"Unity.Collections",
"Unity.Multiplayer.Samples.Utilities",
"Unity.BossRoom.Utils",
"Unity.BossRoom.ConnectionManagement",
"VContainer",
"VContainer.EnableCodeGen"
],
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

205 changes: 205 additions & 0 deletions Assets/Scripts/ConnectionManagement/ConnectionManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using VContainer;

namespace Unity.Multiplayer.Samples.BossRoom
{
public enum ConnectStatus
{
Undefined,
Success, //client successfully connected. This may also be a successful reconnect.
ServerFull, //can't join, server is already at capacity.
LoggedInAgain, //logged in on a separate client, causing this one to be kicked out.
UserRequestedDisconnect, //Intentional Disconnect triggered by the user.
GenericDisconnect, //server disconnected, but no specific reason given.
Reconnecting, //client lost connection and is attempting to reconnect.
IncompatibleBuildType, //client build type is incompatible with server.
HostEndedSession, //host intentionally ended the session.
StartHostFailed, // server failed to bind
StartClientFailed // failed to connect to server and/or invalid network endpoint
}

public struct ReconnectMessage
{
public int CurrentAttempt;
public int MaxAttempt;

public ReconnectMessage(int currentAttempt, int maxAttempt)
{
CurrentAttempt = currentAttempt;
MaxAttempt = maxAttempt;
}
}

public struct ConnectionEventMessage : INetworkSerializeByMemcpy
{
public ConnectStatus ConnectStatus;
public FixedPlayerName PlayerName;
}

[Serializable]
public class ConnectionPayload
{
public string playerId;
public string playerName;
public bool isDebug;
}

/// <summary>
/// This state machine handles connection through the NetworkManager. It is responsible for listening to
/// NetworkManger callbacks and other outside calls and redirecting them to the current ConnectionState object.
/// </summary>
public class ConnectionManager : MonoBehaviour
{
ConnectionState m_CurrentState;

[SerializeField]
NetworkManager m_NetworkManager;
public NetworkManager NetworkManager => m_NetworkManager;

[SerializeField]
NetworkObject m_GameState;
public NetworkObject GameState => m_GameState;

[Inject]
IObjectResolver m_Resolver;

public int MaxConnectedPlayers = 8;

internal readonly OfflineState m_Offline = new OfflineState();
internal readonly ClientConnectingState m_ClientConnecting = new ClientConnectingState();
internal readonly ClientConnectedState m_ClientConnected = new ClientConnectedState();
internal readonly ClientReconnectingState m_ClientReconnecting = new ClientReconnectingState();
internal readonly DisconnectingWithReasonState m_DisconnectingWithReason = new DisconnectingWithReasonState();
internal readonly StartingHostState m_StartingHost = new StartingHostState();
internal readonly HostingState m_Hosting = new HostingState();

void Awake()
{
DontDestroyOnLoad(gameObject);
}

void Start()
{
List<ConnectionState> states = new() { m_Offline, m_ClientConnecting, m_ClientConnected, m_ClientReconnecting, m_DisconnectingWithReason, m_StartingHost, m_Hosting };
foreach (var connectionState in states)
{
m_Resolver.Inject(connectionState);
}

m_CurrentState = m_Offline;

NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback;
NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
NetworkManager.OnServerStarted += OnServerStarted;
NetworkManager.ConnectionApprovalCallback += ApprovalCheck;
}

void OnDestroy()
{
NetworkManager.OnClientConnectedCallback -= OnClientConnectedCallback;
NetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback;
NetworkManager.OnServerStarted -= OnServerStarted;
NetworkManager.ConnectionApprovalCallback -= ApprovalCheck;

}

internal void ChangeState(ConnectionState nextState)
{
Debug.Log($"Changed connection state from {m_CurrentState.GetType().Name} to {nextState.GetType().Name}.");

if (m_CurrentState != null)
{
m_CurrentState.Exit();
}
m_CurrentState = nextState;
m_CurrentState.Enter();
}

void OnClientDisconnectCallback(ulong clientId)
{
m_CurrentState.OnClientDisconnect(clientId);
}

void OnClientConnectedCallback(ulong clientId)
{
m_CurrentState.OnClientConnected(clientId);
}

void OnServerStarted()
{
m_CurrentState.OnServerStarted();
}

void ApprovalCheck(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response)
{
m_CurrentState.ApprovalCheck(request, response);
}

public void StartClientLobby(string playerName)
{
m_CurrentState.StartClientLobby(playerName);
}

public void StartClientIp(string playerName, string ipaddress, int port)
{
m_CurrentState.StartClientIP(playerName, ipaddress, port);
}

public void StartHostLobby(string playerName)
{
m_CurrentState.StartHostLobby(playerName);
}

public void StartHostIp(string playerName, string ipaddress, int port)
{
m_CurrentState.StartHostIP(playerName, ipaddress, port);
}

public void RequestShutdown()
{
m_CurrentState.OnUserRequestedShutdown();
}

/// <summary>
/// Registers the message handler for custom named messages. This should only be done once StartClient has been
/// called (start client will initialize NetworkSceneManager and CustomMessagingManager)
/// </summary>
public void RegisterCustomMessages()
{
NetworkManager.CustomMessagingManager.RegisterNamedMessageHandler(nameof(ReceiveServerToClientSetDisconnectReason_CustomMessage), ReceiveServerToClientSetDisconnectReason_CustomMessage);
}

void ReceiveServerToClientSetDisconnectReason_CustomMessage(ulong clientID, FastBufferReader reader)
{
reader.ReadValueSafe(out ConnectStatus status);
m_CurrentState.OnDisconnectReasonReceived(status);
}

/// <summary>
/// Sends a DisconnectReason to all connected clients. This should only be done on the server, prior to disconnecting the clients.
/// </summary>
/// <param name="status"> The reason for the upcoming disconnect.</param>
public static void SendServerToAllClientsSetDisconnectReason(ConnectStatus status)
{
var writer = new FastBufferWriter(sizeof(ConnectStatus), Allocator.Temp);
writer.WriteValueSafe(status);
NetworkManager.Singleton.CustomMessagingManager.SendNamedMessageToAll(nameof(ReceiveServerToClientSetDisconnectReason_CustomMessage), writer);
}

/// <summary>
/// Sends a DisconnectReason to the indicated client. This should only be done on the server, prior to disconnecting the client.
/// </summary>
/// <param name="clientID"> id of the client to send to </param>
/// <param name="status"> The reason for the upcoming disconnect.</param>
public static void SendServerToClientSetDisconnectReason(ulong clientID, ConnectStatus status)
{
var writer = new FastBufferWriter(sizeof(ConnectStatus), Allocator.Temp);
writer.WriteValueSafe(status);
NetworkManager.Singleton.CustomMessagingManager.SendNamedMessage(nameof(ReceiveServerToClientSetDisconnectReason_CustomMessage), clientID, writer);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Assets/Scripts/ConnectionManagement/ConnectionState.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading