451 lines
15 KiB
C++
451 lines
15 KiB
C++
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
|
|
|
#include "NetworkManager.h"
|
|
#include "NetworkClient.h"
|
|
#include "NetworkPeer.h"
|
|
#include "NetworkEvent.h"
|
|
#include "NetworkChannelType.h"
|
|
#include "NetworkSettings.h"
|
|
#include "NetworkInternal.h"
|
|
#include "FlaxEngine.Gen.h"
|
|
#include "Engine/Core/Log.h"
|
|
#include "Engine/Core/Collections/Array.h"
|
|
#include "Engine/Engine/EngineService.h"
|
|
#include "Engine/Engine/Time.h"
|
|
#include "Engine/Profiler/ProfilerCPU.h"
|
|
#include "Engine/Scripting/Scripting.h"
|
|
|
|
#define NETWORK_PROTOCOL_VERSION 1
|
|
|
|
float NetworkManager::NetworkFPS = 60.0f;
|
|
NetworkPeer* NetworkManager::Peer = nullptr;
|
|
NetworkManagerMode NetworkManager::Mode = NetworkManagerMode::Offline;
|
|
NetworkConnectionState NetworkManager::State = NetworkConnectionState::Offline;
|
|
uint32 NetworkManager::Frame = 0;
|
|
uint32 NetworkManager::LocalClientId = 0;
|
|
NetworkClient* NetworkManager::LocalClient = nullptr;
|
|
Array<NetworkClient*> NetworkManager::Clients;
|
|
Action NetworkManager::StateChanged;
|
|
Delegate<NetworkClientConnectionData&> NetworkManager::ClientConnecting;
|
|
Delegate<NetworkClient*> NetworkManager::ClientConnected;
|
|
Delegate<NetworkClient*> NetworkManager::ClientDisconnected;
|
|
|
|
namespace
|
|
{
|
|
uint32 GameProtocolVersion = 0;
|
|
uint32 NextClientId = 0;
|
|
double LastUpdateTime = 0;
|
|
}
|
|
|
|
PACK_STRUCT(struct NetworkMessageHandshake
|
|
{
|
|
NetworkMessageIDs ID = NetworkMessageIDs::Handshake;
|
|
uint32 EngineBuild;
|
|
uint32 EngineProtocolVersion;
|
|
uint32 GameProtocolVersion;
|
|
byte Platform;
|
|
byte Architecture;
|
|
uint16 PayloadDataSize;
|
|
});
|
|
|
|
PACK_STRUCT(struct NetworkMessageHandshakeReply
|
|
{
|
|
NetworkMessageIDs ID = NetworkMessageIDs::HandshakeReply;
|
|
uint32 ClientId;
|
|
int32 Result;
|
|
});
|
|
|
|
void OnNetworkMessageHandshake(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
|
{
|
|
// Read client connection data
|
|
NetworkMessageHandshake msgData;
|
|
event.Message.ReadStructure(msgData);
|
|
NetworkClientConnectionData connectionData;
|
|
connectionData.Client = client;
|
|
connectionData.Result = 0;
|
|
connectionData.Platform = (PlatformType)msgData.Platform;
|
|
connectionData.Architecture = (ArchitectureType)msgData.Architecture;
|
|
connectionData.PayloadData.Resize(msgData.PayloadDataSize);
|
|
event.Message.ReadBytes(connectionData.PayloadData.Get(), msgData.PayloadDataSize);
|
|
if (msgData.EngineProtocolVersion != NETWORK_PROTOCOL_VERSION ||
|
|
msgData.GameProtocolVersion != GameProtocolVersion)
|
|
{
|
|
connectionData.Result = 1; // Mismatching network protocol version
|
|
}
|
|
NetworkManager::ClientConnecting(connectionData); // Allow server to validate connection
|
|
|
|
// Reply to the handshake message with a result
|
|
NetworkMessageHandshakeReply replyData;
|
|
replyData.Result = connectionData.Result;
|
|
replyData.ClientId = client->ClientId;
|
|
NetworkMessage msgReply = peer->BeginSendMessage();
|
|
msgReply.WriteStructure(replyData);
|
|
peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msgReply, event.Sender);
|
|
|
|
// Update client based on connection result
|
|
if (connectionData.Result != 0)
|
|
{
|
|
LOG(Info, "Connection blocked with result {0} from client id={1}.", connectionData.Result, event.Sender.ConnectionId);
|
|
client->State = NetworkConnectionState::Disconnecting;
|
|
peer->Disconnect(event.Sender);
|
|
client->State = NetworkConnectionState::Disconnected;
|
|
}
|
|
else
|
|
{
|
|
client->State = NetworkConnectionState::Connected;
|
|
LOG(Info, "Client id={0} connected", event.Sender.ConnectionId);
|
|
NetworkManager::ClientConnected(client);
|
|
NetworkInternal::NetworkReplicatorClientConnected(client);
|
|
}
|
|
}
|
|
|
|
void OnNetworkMessageHandshakeReply(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
|
{
|
|
ASSERT_LOW_LAYER(NetworkManager::IsClient());
|
|
NetworkMessageHandshakeReply msgData;
|
|
event.Message.ReadStructure(msgData);
|
|
if (msgData.Result != 0)
|
|
{
|
|
// Server failed to connect with client
|
|
// TODO: feed game with result from msgData.Result
|
|
NetworkManager::Stop();
|
|
return;
|
|
}
|
|
|
|
// Client got connected with server
|
|
NetworkManager::LocalClientId = msgData.ClientId;
|
|
NetworkManager::LocalClient->ClientId = msgData.ClientId;
|
|
NetworkManager::LocalClient->State = NetworkConnectionState::Connected;
|
|
NetworkManager::State = NetworkConnectionState::Connected;
|
|
NetworkManager::StateChanged();
|
|
}
|
|
|
|
namespace
|
|
{
|
|
// Network message handlers table
|
|
void (*MessageHandlers[(int32)NetworkMessageIDs::MAX])(NetworkEvent&, NetworkClient*, NetworkPeer*) =
|
|
{
|
|
nullptr,
|
|
OnNetworkMessageHandshake,
|
|
OnNetworkMessageHandshakeReply,
|
|
NetworkInternal::OnNetworkMessageObjectReplicate,
|
|
NetworkInternal::OnNetworkMessageObjectSpawn,
|
|
NetworkInternal::OnNetworkMessageObjectDespawn,
|
|
NetworkInternal::OnNetworkMessageObjectRole,
|
|
NetworkInternal::OnNetworkMessageObjectRpc,
|
|
};
|
|
}
|
|
|
|
class NetworkManagerService : public EngineService
|
|
{
|
|
public:
|
|
NetworkManagerService()
|
|
: EngineService(TEXT("Network Manager"), 1000)
|
|
{
|
|
}
|
|
|
|
void Update() override;
|
|
|
|
void Dispose() override
|
|
{
|
|
// Ensure to dispose any resources upon exiting
|
|
NetworkManager::Stop();
|
|
}
|
|
};
|
|
|
|
NetworkManagerService NetworkManagerServiceInstance;
|
|
|
|
bool StartPeer()
|
|
{
|
|
PROFILE_CPU();
|
|
ASSERT_LOW_LAYER(!NetworkManager::Peer);
|
|
NetworkManager::State = NetworkConnectionState::Connecting;
|
|
NetworkManager::StateChanged();
|
|
const auto& settings = *NetworkSettings::Get();
|
|
|
|
// Create Network Peer that will use underlying INetworkDriver to send messages over the network
|
|
NetworkConfig networkConfig;
|
|
if (NetworkManager::Mode == NetworkManagerMode::Client)
|
|
{
|
|
// Client
|
|
networkConfig.Address = settings.Address;
|
|
networkConfig.Port = settings.Port;
|
|
networkConfig.ConnectionsLimit = 1;
|
|
}
|
|
else
|
|
{
|
|
// Server or Host
|
|
networkConfig.Address = TEXT("any");
|
|
networkConfig.Port = settings.Port;
|
|
networkConfig.ConnectionsLimit = (uint16)settings.MaxClients;
|
|
}
|
|
const ScriptingTypeHandle networkDriverType = Scripting::FindScriptingType(settings.NetworkDriver);
|
|
if (!networkDriverType)
|
|
{
|
|
LOG(Error, "Unknown Network Driver type {0}", String(settings.NetworkDriver));
|
|
return true;
|
|
}
|
|
networkConfig.NetworkDriver = ScriptingObject::NewObject(networkDriverType);
|
|
NetworkManager::Peer = NetworkPeer::CreatePeer(networkConfig);
|
|
if (!NetworkManager::Peer)
|
|
{
|
|
LOG(Error, "Failed to create Network Peer at {0}:{1}", networkConfig.Address, networkConfig.Port);
|
|
return true;
|
|
}
|
|
NetworkManager::Frame = 0;
|
|
|
|
return false;
|
|
}
|
|
|
|
void StopPeer()
|
|
{
|
|
if (!NetworkManager::Peer)
|
|
return;
|
|
PROFILE_CPU();
|
|
if (NetworkManager::Mode == NetworkManagerMode::Client)
|
|
NetworkManager::Peer->Disconnect();
|
|
NetworkPeer::ShutdownPeer(NetworkManager::Peer);
|
|
NetworkManager::Peer = nullptr;
|
|
}
|
|
|
|
void NetworkSettings::Apply()
|
|
{
|
|
NetworkManager::NetworkFPS = NetworkFPS;
|
|
GameProtocolVersion = ProtocolVersion;
|
|
}
|
|
|
|
NetworkClient::NetworkClient(uint32 id, NetworkConnection connection)
|
|
: ScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
|
, ClientId(id)
|
|
, Connection(connection)
|
|
, State(NetworkConnectionState::Connecting)
|
|
{
|
|
}
|
|
|
|
NetworkClient* NetworkManager::GetClient(const NetworkConnection& connection)
|
|
{
|
|
if (connection.ConnectionId == 0)
|
|
return LocalClient;
|
|
for (NetworkClient* client : Clients)
|
|
{
|
|
if (client->Connection == connection)
|
|
return client;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool NetworkManager::StartServer()
|
|
{
|
|
PROFILE_CPU();
|
|
Stop();
|
|
|
|
LOG(Info, "Starting network manager as server");
|
|
Mode = NetworkManagerMode::Server;
|
|
if (StartPeer())
|
|
return true;
|
|
if (!Peer->Listen())
|
|
{
|
|
Stop();
|
|
return true;
|
|
}
|
|
LocalClientId = ServerClientId;
|
|
NextClientId = ServerClientId + 1;
|
|
|
|
State = NetworkConnectionState::Connected;
|
|
StateChanged();
|
|
return false;
|
|
}
|
|
|
|
bool NetworkManager::StartClient()
|
|
{
|
|
PROFILE_CPU();
|
|
Stop();
|
|
|
|
LOG(Info, "Starting network manager as client");
|
|
Mode = NetworkManagerMode::Client;
|
|
if (StartPeer())
|
|
return true;
|
|
if (!Peer->Connect())
|
|
{
|
|
Stop();
|
|
return true;
|
|
}
|
|
LocalClientId = 0; // Id gets assigned by server later after connection
|
|
NextClientId = 0;
|
|
LocalClient = New<NetworkClient>(LocalClientId, NetworkConnection{ 0 });
|
|
|
|
return false;
|
|
}
|
|
|
|
bool NetworkManager::StartHost()
|
|
{
|
|
PROFILE_CPU();
|
|
Stop();
|
|
|
|
LOG(Info, "Starting network manager as host");
|
|
Mode = NetworkManagerMode::Host;
|
|
if (StartPeer())
|
|
return true;
|
|
if (!Peer->Listen())
|
|
return true;
|
|
LocalClientId = ServerClientId;
|
|
NextClientId = ServerClientId + 1;
|
|
LocalClient = New<NetworkClient>(LocalClientId, NetworkConnection{ 0 });
|
|
|
|
// Auto-connect host
|
|
LocalClient->State = NetworkConnectionState::Connected;
|
|
ClientConnected(LocalClient);
|
|
|
|
State = NetworkConnectionState::Connected;
|
|
StateChanged();
|
|
return false;
|
|
}
|
|
|
|
void NetworkManager::Stop()
|
|
{
|
|
if (Mode == NetworkManagerMode::Offline && State == NetworkConnectionState::Offline)
|
|
return;
|
|
PROFILE_CPU();
|
|
|
|
LOG(Info, "Stopping network manager");
|
|
State = NetworkConnectionState::Disconnecting;
|
|
if (LocalClient)
|
|
LocalClient->State = NetworkConnectionState::Disconnecting;
|
|
for (NetworkClient* client : Clients)
|
|
client->State = NetworkConnectionState::Disconnecting;
|
|
StateChanged();
|
|
|
|
NetworkInternal::NetworkReplicatorClear();
|
|
for (int32 i = Clients.Count() - 1; i >= 0; i--)
|
|
{
|
|
NetworkClient* client = Clients[i];
|
|
ClientDisconnected(client);
|
|
client->State = NetworkConnectionState::Disconnected;
|
|
Delete(client);
|
|
Clients.RemoveAt(i);
|
|
}
|
|
StopPeer();
|
|
if (LocalClient)
|
|
{
|
|
LocalClient->State = NetworkConnectionState::Disconnected;
|
|
Delete(LocalClient);
|
|
LocalClient = nullptr;
|
|
}
|
|
|
|
State = NetworkConnectionState::Disconnected;
|
|
Mode = NetworkManagerMode::Offline;
|
|
StateChanged();
|
|
}
|
|
|
|
void NetworkManagerService::Update()
|
|
{
|
|
const double currentTime = Time::Update.UnscaledTime.GetTotalSeconds();
|
|
const float minDeltaTime = NetworkManager::NetworkFPS > 0 ? 1.0f / NetworkManager::NetworkFPS : 0.0f;
|
|
auto peer = NetworkManager::Peer;
|
|
if (NetworkManager::Mode == NetworkManagerMode::Offline || (float)(currentTime - LastUpdateTime) < minDeltaTime || !peer)
|
|
return;
|
|
PROFILE_CPU();
|
|
LastUpdateTime = currentTime;
|
|
NetworkManager::Frame++;
|
|
NetworkInternal::NetworkReplicatorPreUpdate();
|
|
// TODO: convert into TaskGraphSystems and use async jobs
|
|
|
|
// Process network messages
|
|
NetworkEvent event;
|
|
while (peer->PopEvent(event))
|
|
{
|
|
switch (event.EventType)
|
|
{
|
|
case NetworkEventType::Connected:
|
|
LOG(Info, "Incoming connection with Id={0}", event.Sender.ConnectionId);
|
|
if (NetworkManager::IsClient())
|
|
{
|
|
// Initialize client connection data
|
|
NetworkClientConnectionData connectionData;
|
|
connectionData.Client = NetworkManager::LocalClient;
|
|
connectionData.Result = 0;
|
|
connectionData.Platform = PLATFORM_TYPE;
|
|
connectionData.Architecture = PLATFORM_ARCH;
|
|
NetworkManager::ClientConnecting(connectionData); // Allow client to validate connection or inject custom connection data
|
|
if (connectionData.Result != 0)
|
|
{
|
|
LOG(Info, "Connection blocked with result {0}.", connectionData.Result);
|
|
NetworkManager::Stop();
|
|
break;
|
|
}
|
|
|
|
// Send initial handshake message from client to server
|
|
NetworkMessageHandshake msgData;
|
|
msgData.EngineBuild = FLAXENGINE_VERSION_BUILD;
|
|
msgData.EngineProtocolVersion = NETWORK_PROTOCOL_VERSION;
|
|
msgData.GameProtocolVersion = GameProtocolVersion;
|
|
msgData.Platform = (byte)connectionData.Platform;
|
|
msgData.Architecture = (byte)connectionData.Architecture;
|
|
msgData.PayloadDataSize = (uint16)connectionData.PayloadData.Count();
|
|
NetworkMessage msg = peer->BeginSendMessage();
|
|
msg.WriteStructure(msgData);
|
|
msg.WriteBytes(connectionData.PayloadData.Get(), connectionData.PayloadData.Count());
|
|
peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg);
|
|
}
|
|
else
|
|
{
|
|
// Create incoming client
|
|
auto client = New<NetworkClient>(NextClientId++, event.Sender);
|
|
NetworkManager::Clients.Add(client);
|
|
}
|
|
break;
|
|
case NetworkEventType::Disconnected:
|
|
case NetworkEventType::Timeout:
|
|
LOG(Info, "{1} with Id={0}", event.Sender.ConnectionId, event.EventType == NetworkEventType::Disconnected ? TEXT("Disconnected") : TEXT("Disconnected on timeout"));
|
|
if (NetworkManager::IsClient())
|
|
{
|
|
// Server disconnected from client
|
|
NetworkManager::Stop();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Client disconnected from server/host
|
|
NetworkClient* client = NetworkManager::GetClient(event.Sender);
|
|
if (!client)
|
|
{
|
|
LOG(Error, "Unknown client");
|
|
break;
|
|
}
|
|
client->State = NetworkConnectionState::Disconnecting;
|
|
LOG(Info, "Client id={0} disconnected", event.Sender.ConnectionId);
|
|
NetworkManager::Clients.RemoveKeepOrder(client);
|
|
NetworkInternal::NetworkReplicatorClientDisconnected(client);
|
|
NetworkManager::ClientDisconnected(client);
|
|
client->State = NetworkConnectionState::Disconnected;
|
|
Delete(client);
|
|
}
|
|
break;
|
|
case NetworkEventType::Message:
|
|
{
|
|
// Process network message
|
|
NetworkClient* client = NetworkManager::GetClient(event.Sender);
|
|
if (!client && NetworkManager::Mode != NetworkManagerMode::Client)
|
|
{
|
|
LOG(Error, "Unknown client");
|
|
break;
|
|
}
|
|
uint8 id = *event.Message.Buffer;
|
|
if (id < (uint8)NetworkMessageIDs::MAX)
|
|
{
|
|
MessageHandlers[id](event, client, peer);
|
|
}
|
|
else
|
|
{
|
|
LOG(Warning, "Unknown message id={0} from connection {1}", id, event.Sender.ConnectionId);
|
|
}
|
|
}
|
|
peer->RecycleMessage(event.Message);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update replication
|
|
NetworkInternal::NetworkReplicatorUpdate();
|
|
}
|