Merge branch 'Erdroy-lowlevel-networking'
This commit is contained in:
@@ -25,6 +25,7 @@ public class Engine : EngineModule
|
||||
options.PublicDependencies.Add("Input");
|
||||
options.PublicDependencies.Add("Level");
|
||||
options.PublicDependencies.Add("Navigation");
|
||||
options.PublicDependencies.Add("Networking");
|
||||
options.PublicDependencies.Add("Physics");
|
||||
options.PublicDependencies.Add("Particles");
|
||||
options.PublicDependencies.Add("Scripting");
|
||||
|
||||
250
Source/Engine/Networking/Drivers/ENetDriver.cpp
Normal file
250
Source/Engine/Networking/Drivers/ENetDriver.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
//
|
||||
// TODO: Check defines so we can disable ENet
|
||||
|
||||
#include "ENetDriver.h"
|
||||
|
||||
#include "Engine/Networking/NetworkConfig.h"
|
||||
#include "Engine/Networking/NetworkChannelType.h"
|
||||
#include "Engine/Networking/NetworkEvent.h"
|
||||
#include "Engine/Networking/NetworkPeer.h"
|
||||
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
#define ENET_IMPLEMENTATION
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#include <enet/enet.h>
|
||||
|
||||
#undef _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#undef SendMessage
|
||||
|
||||
ENetPacketFlag ChannelTypeToPacketFlag(const NetworkChannelType channel)
|
||||
{
|
||||
int flag = 0; // Maybe use ENET_PACKET_FLAG_NO_ALLOCATE?
|
||||
|
||||
// Add reliable flag when it is "reliable" channel
|
||||
if(channel > NetworkChannelType::UnreliableOrdered)
|
||||
flag |= ENET_PACKET_FLAG_RELIABLE;
|
||||
|
||||
// Use unsequenced flag when the flag is unreliable. We have to sequence all other packets.
|
||||
if(channel == NetworkChannelType::Unreliable)
|
||||
flag |= ENET_PACKET_FLAG_UNSEQUENCED;
|
||||
|
||||
// Note that all reliable channels are exactly the same. TODO: How to handle unordered reliable packets...?
|
||||
|
||||
return static_cast<ENetPacketFlag>(flag);
|
||||
}
|
||||
|
||||
void SendPacketToPeer(ENetPeer* peer, const NetworkChannelType channelType, const NetworkMessage& message)
|
||||
{
|
||||
// Covert our channel type to the internal ENet packet flags
|
||||
const ENetPacketFlag flag = ChannelTypeToPacketFlag(channelType);
|
||||
|
||||
// This will copy the data into the packet when ENET_PACKET_FLAG_NO_ALLOCATE is not set.
|
||||
// Tho, we cannot use it, because we're releasing the message right after the send - and the packet might not
|
||||
// be sent, yet. To avoid data corruption, we're just using the copy method. We might fix that later, but I'll take
|
||||
// the smaller risk.
|
||||
ENetPacket* packet = enet_packet_create(message.Buffer, message.Length, flag);
|
||||
|
||||
// And send it!
|
||||
enet_peer_send (peer, 0, packet);
|
||||
|
||||
// TODO: To reduce latency, we can use `enet_host_flush` to flush all packets. Maybe some API, like NetworkManager::FlushQueues()?
|
||||
}
|
||||
|
||||
void ENetDriver::Initialize(NetworkPeer* host, const NetworkConfig& config)
|
||||
{
|
||||
_networkHost = host;
|
||||
_config = config;
|
||||
_peerMap = Dictionary<uint32, void*>();
|
||||
|
||||
if (enet_initialize () != 0) {
|
||||
LOG(Error, "Failed to initialize ENet driver!");
|
||||
}
|
||||
|
||||
LOG(Info, "Initialized ENet driver!");
|
||||
}
|
||||
|
||||
void ENetDriver::Dispose()
|
||||
{
|
||||
if(_peer)
|
||||
enet_peer_disconnect_now((ENetPeer*)_peer, 0);
|
||||
enet_host_destroy((ENetHost*)_host);
|
||||
|
||||
enet_deinitialize();
|
||||
|
||||
_peerMap.Clear();
|
||||
_peerMap = {};
|
||||
|
||||
_peer = nullptr;
|
||||
_host = nullptr;
|
||||
|
||||
LOG(Info, "ENet driver stopped!");
|
||||
}
|
||||
|
||||
bool ENetDriver::Listen()
|
||||
{
|
||||
ENetAddress address = {0};
|
||||
address.port = _config.Port;
|
||||
address.host = ENET_HOST_ANY;
|
||||
|
||||
// Set host address if needed
|
||||
if(_config.Address != String("any"))
|
||||
enet_address_set_host(&address, _config.Address.ToStringAnsi().GetText());
|
||||
|
||||
// Create ENet host
|
||||
_host = enet_host_create(&address, _config.ConnectionsLimit, 1, 0, 0);
|
||||
if(_host == nullptr)
|
||||
{
|
||||
LOG(Error, "Failed to initialize ENet host!");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG(Info, "Created ENet server!");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ENetDriver::Connect()
|
||||
{
|
||||
LOG(Info, "Connecting using ENet...");
|
||||
|
||||
ENetAddress address = {0};
|
||||
address.port = _config.Port;
|
||||
enet_address_set_host(&address, _config.Address.ToStringAnsi().GetText());
|
||||
|
||||
// Create ENet host
|
||||
_host = enet_host_create(nullptr, 1, 1, 0, 0);
|
||||
if(_host == nullptr)
|
||||
{
|
||||
LOG(Error, "Failed to initialize ENet host!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create ENet peer/connect to the server
|
||||
_peer = enet_host_connect((ENetHost*)_host, &address, 1, 0);
|
||||
if(_peer == nullptr)
|
||||
{
|
||||
LOG(Error, "Failed to create ENet host!");
|
||||
enet_host_destroy((ENetHost*)_host);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ENetDriver::Disconnect()
|
||||
{
|
||||
ASSERT(_peer != nullptr);
|
||||
|
||||
if(_peer)
|
||||
{
|
||||
enet_peer_disconnect_now((ENetPeer*)_peer, 0);
|
||||
_peer = nullptr;
|
||||
|
||||
LOG(Info, "Disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
void ENetDriver::Disconnect(const NetworkConnection& connection)
|
||||
{
|
||||
const int connectionId = connection.ConnectionId;
|
||||
|
||||
void* peer = nullptr;
|
||||
if(_peerMap.TryGet(connectionId, peer))
|
||||
{
|
||||
enet_peer_disconnect_now((ENetPeer*)_peer, 0);
|
||||
_peerMap.Remove(connectionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(Error, "Failed to kick connection({0}). ENetPeer not found!", connection.ConnectionId);
|
||||
}
|
||||
}
|
||||
|
||||
bool ENetDriver::PopEvent(NetworkEvent* eventPtr)
|
||||
{
|
||||
ENetEvent event;
|
||||
const int result = enet_host_service((ENetHost*)_host, &event, 0);
|
||||
|
||||
if(result < 0)
|
||||
LOG(Error, "Failed to check ENet events!");
|
||||
|
||||
if(result > 0)
|
||||
{
|
||||
// Copy sender data
|
||||
const uint32 connectionId = enet_peer_get_id(event.peer);
|
||||
eventPtr->Sender = NetworkConnection();
|
||||
eventPtr->Sender.ConnectionId = connectionId;
|
||||
|
||||
switch(event.type)
|
||||
{
|
||||
case ENET_EVENT_TYPE_CONNECT:
|
||||
eventPtr->EventType = NetworkEventType::Connected;
|
||||
|
||||
if(IsServer())
|
||||
_peerMap.Add(connectionId, event.peer);
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
eventPtr->EventType = NetworkEventType::Disconnected;
|
||||
|
||||
if(IsServer())
|
||||
_peerMap.Remove(connectionId);
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
|
||||
eventPtr->EventType = NetworkEventType::Timeout;
|
||||
|
||||
if(IsServer())
|
||||
_peerMap.Remove(connectionId);
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
eventPtr->EventType = NetworkEventType::Message;
|
||||
|
||||
// Acquire message and copy message data
|
||||
eventPtr->Message = _networkHost->CreateMessage();
|
||||
eventPtr->Message.Length = event.packet->dataLength;
|
||||
Memory::CopyItems(eventPtr->Message.Buffer, event.packet->data, event.packet->dataLength);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
return true; // Event
|
||||
}
|
||||
|
||||
return false; // No events
|
||||
}
|
||||
|
||||
void ENetDriver::SendMessage(const NetworkChannelType channelType, const NetworkMessage& message)
|
||||
{
|
||||
ASSERT(IsServer() == false);
|
||||
|
||||
SendPacketToPeer((ENetPeer*)_peer, channelType, message);
|
||||
}
|
||||
|
||||
void ENetDriver::SendMessage(NetworkChannelType channelType, const NetworkMessage& message, NetworkConnection target)
|
||||
{
|
||||
ASSERT(IsServer());
|
||||
|
||||
ENetPeer* peer = *(ENetPeer**)_peerMap.TryGet(target.ConnectionId);
|
||||
ASSERT(peer != nullptr);
|
||||
ASSERT(peer->state == ENET_PEER_STATE_CONNECTED);
|
||||
|
||||
SendPacketToPeer(peer, channelType, message);
|
||||
}
|
||||
|
||||
void ENetDriver::SendMessage(const NetworkChannelType channelType, const NetworkMessage& message, const Array<NetworkConnection, HeapAllocation>& targets)
|
||||
{
|
||||
ASSERT(IsServer());
|
||||
|
||||
for(NetworkConnection target : targets)
|
||||
{
|
||||
ENetPeer* peer = *(ENetPeer**)_peerMap.TryGet(target.ConnectionId);
|
||||
ASSERT(peer != nullptr);
|
||||
ASSERT(peer->state == ENET_PEER_STATE_CONNECTED);
|
||||
|
||||
SendPacketToPeer(peer, channelType, message);
|
||||
}
|
||||
}
|
||||
48
Source/Engine/Networking/Drivers/ENetDriver.h
Normal file
48
Source/Engine/Networking/Drivers/ENetDriver.h
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Networking/Types.h"
|
||||
#include "Engine/Networking/INetworkDriver.h"
|
||||
#include "Engine/Networking/NetworkConnection.h"
|
||||
#include "Engine/Networking/NetworkConfig.h"
|
||||
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
|
||||
/// <summary>
|
||||
/// Low-level network transport interface implementation based on ENet library.
|
||||
/// </summary>
|
||||
API_CLASS(Namespace="FlaxEngine.Networking", Sealed) class FLAXENGINE_API ENetDriver : public INetworkDriver
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(ENetDriver);
|
||||
public:
|
||||
void Initialize(NetworkPeer* host, const NetworkConfig& config) override;
|
||||
void Dispose() override;
|
||||
|
||||
bool Listen() override;
|
||||
bool Connect() override;
|
||||
void Disconnect() override;
|
||||
void Disconnect(const NetworkConnection& connection) override;
|
||||
|
||||
bool PopEvent(NetworkEvent* eventPtr) override;
|
||||
|
||||
void SendMessage(NetworkChannelType channelType, const NetworkMessage& message) override;
|
||||
void SendMessage(NetworkChannelType channelType, const NetworkMessage& message, NetworkConnection target) override;
|
||||
void SendMessage(NetworkChannelType channelType, const NetworkMessage& message, const Array<NetworkConnection, HeapAllocation>& targets) override;
|
||||
|
||||
private:
|
||||
bool IsServer() const
|
||||
{
|
||||
return _host != nullptr && _peer == nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
NetworkConfig _config;
|
||||
NetworkPeer* _networkHost;
|
||||
void* _host = nullptr;
|
||||
void* _peer = nullptr;
|
||||
|
||||
Dictionary<uint32, void*> _peerMap;
|
||||
};
|
||||
|
||||
110
Source/Engine/Networking/INetworkDriver.h
Normal file
110
Source/Engine/Networking/INetworkDriver.h
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
#include "Types.h"
|
||||
|
||||
/// <summary>
|
||||
/// Basic interface for the low-level network transport/driver.
|
||||
/// </summary>
|
||||
API_INTERFACE(Namespace="FlaxEngine.Networking") class FLAXENGINE_API INetworkDriver
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(INetworkDriver);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="INetworkDriver"/> class.
|
||||
/// </summary>
|
||||
virtual ~INetworkDriver() = default;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the instance of this network driver using given configuration.
|
||||
/// </summary>
|
||||
/// <param name="host">The peer that this driver has been assigned to.</param>
|
||||
/// <param name="config">The network config to use to configure this driver.</param>
|
||||
virtual void Initialize(NetworkPeer* host, const NetworkConfig& config) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Disposes this driver making it no longer usable.
|
||||
/// Reserved for resource deallocation etc.
|
||||
/// </summary>
|
||||
virtual void Dispose() = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Starts listening for incoming connections.
|
||||
/// Once this is called, this driver becomes a server.
|
||||
/// </summary>
|
||||
/// <returns>True when succeeded.</returns>
|
||||
virtual bool Listen() = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Starts connection handshake with the end point specified in the <seealso cref="NetworkConfig"/> structure.
|
||||
/// Once this is called, this driver becomes a client.
|
||||
/// </summary>
|
||||
/// <returns>True when succeeded.</returns>
|
||||
virtual bool Connect() = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects from the server.
|
||||
/// </summary>
|
||||
/// <remarks>Can be used only by the client!</remarks>
|
||||
virtual void Disconnect() = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects given connection from the server.
|
||||
/// </summary>
|
||||
/// <remarks>Can be used only by the server!</remarks>
|
||||
virtual void Disconnect(const NetworkConnection& connection) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to pop an network event from the queue.
|
||||
/// </summary>
|
||||
/// <param name="eventPtr">The pointer to event structure.</param>
|
||||
/// <returns>True when succeeded and the event can be processed.</returns>
|
||||
virtual bool PopEvent(NetworkEvent* eventPtr) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Sends given message over specified channel to the server.
|
||||
/// </summary>
|
||||
/// <param name="channelType">The channel to send the message over.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <remarks>Can be used only by the client!</remarks>
|
||||
/// <remarks>
|
||||
/// Do not recycle the message after calling this.
|
||||
/// This function automatically recycles the message.
|
||||
virtual void SendMessage(NetworkChannelType channelType, const NetworkMessage& message) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Sends given message over specified channel to the given client connection (target).
|
||||
/// </summary>
|
||||
/// <param name="channelType">The channel to send the message over.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="target">The client connection to send the message to.</param>
|
||||
/// <remarks>Can be used only by the server!</remarks>
|
||||
/// <remarks>
|
||||
/// Do not recycle the message after calling this.
|
||||
/// This function automatically recycles the message.
|
||||
/// </remarks>
|
||||
virtual void SendMessage(NetworkChannelType channelType, const NetworkMessage& message, NetworkConnection target) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Sends given message over specified channel to the given client connection (target).
|
||||
/// </summary>
|
||||
/// <param name="channelType">The channel to send the message over.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="targets">The connections list to send the message to.</param>
|
||||
/// <remarks>Can be used only by the server!</remarks>
|
||||
/// <remarks>
|
||||
/// Do not recycle the message after calling this.
|
||||
/// This function automatically recycles the message.
|
||||
/// </remarks>
|
||||
virtual void SendMessage(NetworkChannelType channelType, const NetworkMessage& message, const Array<NetworkConnection, HeapAllocation>& targets) = 0;
|
||||
|
||||
// TODO: Stats API
|
||||
// TODO: Simulation API
|
||||
|
||||
};
|
||||
40
Source/Engine/Networking/NetworkChannelType.h
Normal file
40
Source/Engine/Networking/NetworkChannelType.h
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Config.h"
|
||||
|
||||
/// <summary>
|
||||
/// The low-level network channel type for message sending.
|
||||
/// </summary>
|
||||
API_ENUM(Namespace="FlaxEngine.Networking") enum class NetworkChannelType
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid channel type.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Unreliable channel type.
|
||||
/// Messages can be lost or arrive out-of-order.
|
||||
/// </summary>
|
||||
Unreliable,
|
||||
|
||||
/// <summary>
|
||||
/// Unreliable-ordered channel type.
|
||||
/// Messages can be lost but always arrive in order.
|
||||
/// </summary>
|
||||
UnreliableOrdered,
|
||||
|
||||
/// <summary>
|
||||
/// Reliable channel type.
|
||||
/// Messages won't be lost but may arrive out-of-order.
|
||||
/// </summary>
|
||||
Reliable,
|
||||
|
||||
/// <summary>
|
||||
/// Reliable-ordered channel type.
|
||||
/// Messages won't be lost and always arrive in order.
|
||||
/// </summary>
|
||||
ReliableOrdered
|
||||
};
|
||||
75
Source/Engine/Networking/NetworkConfig.h
Normal file
75
Source/Engine/Networking/NetworkConfig.h
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Platform/Network.h"
|
||||
|
||||
/// <summary>
|
||||
/// Network driver implementations enum.
|
||||
/// </summary>
|
||||
API_ENUM(Namespace="FlaxEngine.Networking") enum class NetworkDriverType
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid network driver implementation.
|
||||
/// </summary>
|
||||
Undefined = 0,
|
||||
|
||||
/// <summary>
|
||||
/// ENet library based network driver implementation.
|
||||
/// </summary>
|
||||
ENet
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Low-level network configuration structure. Provides settings for the network driver and all internal components.
|
||||
/// </summary>
|
||||
API_STRUCT(Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkConfig
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkConfig);
|
||||
public:
|
||||
/// <summary>
|
||||
/// The network driver that will be used to create the peer.
|
||||
/// To allow two peers to connect, they must use the same host.
|
||||
/// </summary>
|
||||
API_FIELD()
|
||||
NetworkDriverType NetworkDriverType = NetworkDriverType::ENet;
|
||||
// TODO: Expose INetworkDriver as a ref not enum, when C++/C# interfaces are done.
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The upper limit on how many peers can join when we're listening.
|
||||
/// </summary>
|
||||
API_FIELD()
|
||||
uint16 ConnectionsLimit = 32;
|
||||
|
||||
/// <summary>
|
||||
/// Address used to connect to or listen at.
|
||||
/// </summary>
|
||||
/// <remarks>Set it to "any" when you want to listen at all available addresses.</remarks>
|
||||
/// <remarks>Only IPv4 is supported.</remarks>
|
||||
API_FIELD()
|
||||
String Address = String("127.0.0.1");
|
||||
|
||||
/// <summary>
|
||||
/// The port to connect to or listen at.
|
||||
/// </summary>
|
||||
API_FIELD()
|
||||
uint16 Port = 7777;
|
||||
|
||||
/// <summary>
|
||||
/// The size of a message buffer in bytes.
|
||||
/// Should be lower than the MTU (maximal transmission unit) - typically 1500 bytes.
|
||||
/// </summary>
|
||||
API_FIELD()
|
||||
uint16 MessageSize = 1500;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of pooled messages that can be used at once (receiving and sending!).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Creating more messages than this limit will result in a crash!
|
||||
/// This should be tweaked manually to fit the needs (adjusting this value will increase/decrease memory usage)!
|
||||
/// </remarks>
|
||||
API_FIELD()
|
||||
uint16 MessagePoolSize = 2048;
|
||||
};
|
||||
20
Source/Engine/Networking/NetworkConnection.h
Normal file
20
Source/Engine/Networking/NetworkConnection.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
|
||||
/// <summary>
|
||||
/// Network connection structure - used to identify connected peers when we're listening.
|
||||
/// </summary>
|
||||
API_STRUCT(Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkConnection
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkConnection);
|
||||
public:
|
||||
/// <summary>
|
||||
/// The identifier of the connection.
|
||||
/// </summary>
|
||||
/// <remarks>Used by network driver implementations.</remarks>
|
||||
API_FIELD()
|
||||
uint32 ConnectionId;
|
||||
};
|
||||
66
Source/Engine/Networking/NetworkEvent.h
Normal file
66
Source/Engine/Networking/NetworkEvent.h
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "NetworkConnection.h"
|
||||
#include "NetworkMessage.h"
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
|
||||
/// <summary>
|
||||
/// Network event type enum contains all possible events that can be returned by PopEvent function.
|
||||
/// </summary>
|
||||
API_ENUM(Namespace="FlaxEngine.Networking") enum class NetworkEventType
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid network event type.
|
||||
/// </summary>
|
||||
Undefined = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Event "connected" - client connected to our server or we've connected to the server.
|
||||
/// </summary>
|
||||
Connected,
|
||||
|
||||
/// <summary>
|
||||
/// Event "disconnected" - client disconnected from our server or we've been kicked from the server.
|
||||
/// </summary>
|
||||
Disconnected,
|
||||
|
||||
/// <summary>
|
||||
/// Event "disconnected" - client got a timeout from our server or we've list the connection to the server.
|
||||
/// </summary>
|
||||
Timeout,
|
||||
|
||||
/// <summary>
|
||||
/// Event "message" - message received from some client or the server.
|
||||
/// </summary>
|
||||
Message
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Network event structure that wraps all data needed to identify and process it.
|
||||
/// </summary>
|
||||
API_STRUCT(Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkEvent
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkEvent);
|
||||
public:
|
||||
/// <summary>
|
||||
/// The type of the received event.
|
||||
/// </summary>
|
||||
API_FIELD();
|
||||
NetworkEventType EventType;
|
||||
|
||||
/// <summary>
|
||||
/// The message when this event is an "message" event - not valid in any other cases.
|
||||
/// If this is an message-event, make sure to return the message using RecycleMessage function of the peer after processing it!
|
||||
/// </summary>
|
||||
API_FIELD();
|
||||
NetworkMessage Message;
|
||||
|
||||
/// <summary>
|
||||
/// The connected of the client that has sent message, connected, disconnected or got a timeout.
|
||||
/// </summary>
|
||||
/// <remarks>Only valid when event has been received on server-peer.</remarks>
|
||||
API_FIELD();
|
||||
NetworkConnection Sender;
|
||||
};
|
||||
365
Source/Engine/Networking/NetworkMessage.cs
Normal file
365
Source/Engine/Networking/NetworkMessage.cs
Normal file
@@ -0,0 +1,365 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using FlaxEngine.Assertions;
|
||||
|
||||
namespace FlaxEngine.Networking
|
||||
{
|
||||
public unsafe partial struct NetworkMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes raw bytes into the message.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes that will be written.</param>
|
||||
/// <param name="length">The amount of bytes to write from the bytes pointer.</param>
|
||||
public void WriteBytes(byte* bytes, int length)
|
||||
{
|
||||
Assert.IsTrue(Position + length <= BufferSize, $"Could not write data of length {length} into message with id={MessageId}! Current write position={Position}");
|
||||
Utils.MemoryCopy(new IntPtr(bytes), new IntPtr(Buffer + Position), (ulong)length);
|
||||
Position += (uint)length;
|
||||
Length = Position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads raw bytes from the message into the given byte array.
|
||||
/// </summary>
|
||||
/// <param name="buffer">
|
||||
/// The buffer pointer that will be used to store the bytes.
|
||||
/// Should be of the same length as length or longer.
|
||||
/// </param>
|
||||
/// <param name="length">The minimal amount of bytes that the buffer contains.</param>
|
||||
public void ReadBytes(byte* buffer, int length)
|
||||
{
|
||||
Assert.IsTrue(Position + length <= Length, $"Could not read data of length {length} from message with id={MessageId} and size of {Length}B! Current read position={Position}");
|
||||
Utils.MemoryCopy(new IntPtr(Buffer + Position), new IntPtr(buffer), (ulong)length);
|
||||
Position += (uint)length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes raw bytes into the message.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes that will be written.</param>
|
||||
/// <param name="length">The amount of bytes to write from the bytes array.</param>
|
||||
public void WriteBytes(byte[] bytes, int length)
|
||||
{
|
||||
fixed (byte* bytesPtr = bytes)
|
||||
{
|
||||
WriteBytes(bytesPtr, length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads raw bytes from the message into the given byte array.
|
||||
/// </summary>
|
||||
/// <param name="buffer">
|
||||
/// The buffer that will be used to store the bytes.
|
||||
/// Should be of the same length as length or longer.
|
||||
/// </param>
|
||||
/// <param name="length">The minimal amount of bytes that the buffer contains.</param>
|
||||
public void ReadBytes(byte[] buffer, int length)
|
||||
{
|
||||
fixed (byte* bufferPtr = buffer)
|
||||
{
|
||||
ReadBytes(bufferPtr, length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="UInt64"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteInt64(long value)
|
||||
{
|
||||
WriteBytes((byte*)&value, sizeof(long));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="UInt64"/> from the message.
|
||||
/// </summary>
|
||||
public long ReadInt64()
|
||||
{
|
||||
long value = 0;
|
||||
ReadBytes((byte*)&value, sizeof(long));
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="UInt32"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteInt32(int value)
|
||||
{
|
||||
WriteBytes((byte*)&value, sizeof(int));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="UInt32"/> from the message.
|
||||
/// </summary>
|
||||
public int ReadInt32()
|
||||
{
|
||||
int value = 0;
|
||||
ReadBytes((byte*)&value, sizeof(int));
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="UInt16"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteInt16(short value)
|
||||
{
|
||||
WriteBytes((byte*)&value, sizeof(short));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="UInt16"/> from the message.
|
||||
/// </summary>
|
||||
public short ReadInt16()
|
||||
{
|
||||
short value = 0;
|
||||
ReadBytes((byte*)&value, sizeof(short));
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="SByte"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteSByte(sbyte value)
|
||||
{
|
||||
WriteBytes((byte*)&value, sizeof(sbyte));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="SByte"/> from the message.
|
||||
/// </summary>
|
||||
public sbyte ReadSByte()
|
||||
{
|
||||
sbyte value = 0;
|
||||
ReadBytes((byte*)&value, sizeof(sbyte));
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="UInt64"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteUInt64(ulong value)
|
||||
{
|
||||
WriteBytes((byte*)&value, sizeof(ulong));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="UInt64"/> from the message.
|
||||
/// </summary>
|
||||
public ulong ReadUInt64()
|
||||
{
|
||||
ulong value = 0;
|
||||
ReadBytes((byte*)&value, sizeof(ulong));
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="UInt32"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteUInt32(uint value)
|
||||
{
|
||||
WriteBytes((byte*)&value, sizeof(uint));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="UInt32"/> from the message.
|
||||
/// </summary>
|
||||
public uint ReadUInt32()
|
||||
{
|
||||
uint value = 0;
|
||||
ReadBytes((byte*)&value, sizeof(uint));
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="UInt16"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteUInt16(ushort value)
|
||||
{
|
||||
WriteBytes((byte*)&value, sizeof(ushort));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="UInt16"/> from the message.
|
||||
/// </summary>
|
||||
public ushort ReadUInt16()
|
||||
{
|
||||
ushort value = 0;
|
||||
ReadBytes((byte*)&value, sizeof(ushort));
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="Byte"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteByte(byte value)
|
||||
{
|
||||
WriteBytes(&value, sizeof(byte));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="Byte"/> from the message.
|
||||
/// </summary>
|
||||
public byte ReadByte()
|
||||
{
|
||||
byte value = 0;
|
||||
ReadBytes(&value, sizeof(byte));
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="Single"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteSingle(float value)
|
||||
{
|
||||
WriteBytes((byte*)&value, sizeof(float));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="Single"/> from the message.
|
||||
/// </summary>
|
||||
public float ReadSingle()
|
||||
{
|
||||
float value = 0.0f;
|
||||
ReadBytes((byte*)&value, sizeof(float));
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="Double"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteDouble(double value)
|
||||
{
|
||||
WriteBytes((byte*)&value, sizeof(double));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="Double"/> from the message.
|
||||
/// </summary>
|
||||
public double ReadDouble()
|
||||
{
|
||||
double value = 0.0;
|
||||
ReadBytes((byte*)&value, sizeof(double));
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="string"/> into the message. UTF-16 encoded.
|
||||
/// </summary>
|
||||
public void WriteString(string value)
|
||||
{
|
||||
// Note: Make sure that this is consistent with the C++ message API!
|
||||
|
||||
var data = Encoding.Unicode.GetBytes(value);
|
||||
WriteUInt16((ushort)data.Length); // TODO: Use 1-byte length when possible
|
||||
WriteBytes(data, data.Length * sizeof(ushort));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="string"/> from the message. UTF-16 encoded.
|
||||
/// </summary>
|
||||
public string ReadString()
|
||||
{
|
||||
// Note: Make sure that this is consistent with the C++ message API!
|
||||
|
||||
var stringLength = ReadUInt16(); // In chars
|
||||
var stringSize = stringLength * sizeof(ushort); // In bytes
|
||||
var bytes = stackalloc char[stringSize];
|
||||
ReadBytes((byte*)bytes, stringSize);
|
||||
return new string(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="Vector2"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteVector2(Vector2 value)
|
||||
{
|
||||
WriteSingle(value.X);
|
||||
WriteSingle(value.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="Vector2"/> from the message.
|
||||
/// </summary>
|
||||
public Vector2 ReadVector2()
|
||||
{
|
||||
return new Vector2(ReadSingle(), ReadSingle());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="Vector3"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteVector3(Vector3 value)
|
||||
{
|
||||
WriteSingle(value.X);
|
||||
WriteSingle(value.Y);
|
||||
WriteSingle(value.Z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="Vector3"/> from the message.
|
||||
/// </summary>
|
||||
public Vector3 ReadVector3()
|
||||
{
|
||||
return new Vector3(ReadSingle(), ReadSingle(), ReadSingle());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="Vector4"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteVector4(Vector4 value)
|
||||
{
|
||||
WriteSingle(value.X);
|
||||
WriteSingle(value.Y);
|
||||
WriteSingle(value.Z);
|
||||
WriteSingle(value.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="Vector4"/> from the message.
|
||||
/// </summary>
|
||||
public Vector4 ReadVector4()
|
||||
{
|
||||
return new Vector4(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="Quaternion"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteQuaternion(Quaternion value)
|
||||
{
|
||||
WriteSingle(value.X);
|
||||
WriteSingle(value.Y);
|
||||
WriteSingle(value.Z);
|
||||
WriteSingle(value.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="Quaternion"/> from the message.
|
||||
/// </summary>
|
||||
public Quaternion ReadQuaternion()
|
||||
{
|
||||
return new Quaternion(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type <see cref="Boolean"/> into the message.
|
||||
/// </summary>
|
||||
public void WriteBoolean(bool value)
|
||||
{
|
||||
WriteBytes((byte*)&value, sizeof(bool));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type <see cref="Boolean"/> from the message.
|
||||
/// </summary>
|
||||
public bool ReadBoolean()
|
||||
{
|
||||
bool value = default;
|
||||
ReadBytes((byte*)&value, sizeof(bool));
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
213
Source/Engine/Networking/NetworkMessage.h
Normal file
213
Source/Engine/Networking/NetworkMessage.h
Normal file
@@ -0,0 +1,213 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Math/Vector2.h"
|
||||
#include "Engine/Core/Math/Vector3.h"
|
||||
#include "Engine/Core/Math/Vector4.h"
|
||||
#include "Engine/Core/Math/Quaternion.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
|
||||
/// <summary>
|
||||
/// Network message structure. Provides raw data writing and reading to the message buffer.
|
||||
/// </summary>
|
||||
API_STRUCT(Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkMessage
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkMessage);
|
||||
public:
|
||||
/// <summary>
|
||||
/// The raw message buffer.
|
||||
/// </summary>
|
||||
API_FIELD()
|
||||
uint8* Buffer = nullptr;
|
||||
|
||||
/// <summary>
|
||||
/// The unique, internal message identifier.
|
||||
/// </summary>
|
||||
API_FIELD()
|
||||
uint32 MessageId = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The size in bytes of the buffer that this message has.
|
||||
/// </summary>
|
||||
API_FIELD()
|
||||
uint32 BufferSize = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The length in bytes of this message.
|
||||
/// </summary>
|
||||
API_FIELD()
|
||||
uint32 Length = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The position in bytes in buffer where the next read/write will occur.
|
||||
/// </summary>
|
||||
API_FIELD()
|
||||
uint32 Position = 0;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes default values of the <seealso cref="NetworkMessage"/> structure.
|
||||
/// </summary>
|
||||
NetworkMessage() = default;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes values of the <seealso cref="NetworkMessage"/> structure.
|
||||
/// </summary>
|
||||
NetworkMessage(uint8* buffer, uint32 messageId, uint32 bufferSize, uint32 length, uint32 position) :
|
||||
Buffer(buffer), MessageId(messageId), BufferSize(bufferSize), Length(length), Position(position)
|
||||
{ }
|
||||
|
||||
~NetworkMessage() = default;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Writes raw bytes into the message.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes that will be written.</param>
|
||||
/// <param name="numBytes">The amount of bytes to write from the bytes pointer.</param>
|
||||
FORCE_INLINE void WriteBytes(uint8* bytes, const int numBytes)
|
||||
{
|
||||
ASSERT(Position + numBytes < BufferSize);
|
||||
Platform::MemoryCopy(Buffer + Position, bytes, numBytes);
|
||||
Position += numBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads raw bytes from the message into the given byte array.
|
||||
/// </summary>
|
||||
/// <param name="bytes">
|
||||
/// The buffer pointer that will be used to store the bytes.
|
||||
/// Should be of the same length as length or longer.
|
||||
/// </param>
|
||||
/// <param name="numBytes">The minimal amount of bytes that the buffer contains.</param>
|
||||
FORCE_INLINE void ReadBytes(uint8* bytes, const int numBytes)
|
||||
{
|
||||
ASSERT(Position + numBytes < BufferSize);
|
||||
Platform::MemoryCopy(bytes, Buffer + Position, numBytes);
|
||||
Position += numBytes;
|
||||
}
|
||||
|
||||
#define DECL_READWRITE(type, name) \
|
||||
FORCE_INLINE void Write##name(type value) { WriteBytes(reinterpret_cast<uint8*>(&value), sizeof(type)); } \
|
||||
FORCE_INLINE type Read##name() { type value = 0; ReadBytes(reinterpret_cast<uint8*>(&value), sizeof(type)); return value; }
|
||||
|
||||
public:
|
||||
DECL_READWRITE(int8, Int8)
|
||||
DECL_READWRITE(uint8, UInt8)
|
||||
DECL_READWRITE(int16, Int16)
|
||||
DECL_READWRITE(uint16, UInt16)
|
||||
DECL_READWRITE(int32, Int32)
|
||||
DECL_READWRITE(uint32, UInt32)
|
||||
DECL_READWRITE(int64, Int64)
|
||||
DECL_READWRITE(uint64, UInt64)
|
||||
DECL_READWRITE(float, Single)
|
||||
DECL_READWRITE(double, Double)
|
||||
DECL_READWRITE(bool, Boolean)
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Writes data of type Vector2 into the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE void WriteVector2(const Vector2& value)
|
||||
{
|
||||
WriteSingle(value.X);
|
||||
WriteSingle(value.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type Vector2 from the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE Vector2 ReadVector2()
|
||||
{
|
||||
return Vector2(ReadSingle(), ReadSingle());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type Vector3 into the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE void WriteVector3(const Vector3& value)
|
||||
{
|
||||
WriteSingle(value.X);
|
||||
WriteSingle(value.Y);
|
||||
WriteSingle(value.Z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type Vector3 from the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE Vector3 ReadVector3()
|
||||
{
|
||||
return Vector3(ReadSingle(), ReadSingle(), ReadSingle());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type Vector4 into the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE void WriteVector4(const Vector4& value)
|
||||
{
|
||||
WriteSingle(value.X);
|
||||
WriteSingle(value.Y);
|
||||
WriteSingle(value.Z);
|
||||
WriteSingle(value.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type Vector4 from the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE Vector4 ReadVector4()
|
||||
{
|
||||
return Vector4(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data of type Quaternion into the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE void WriteQuaternion(const Quaternion& value)
|
||||
{
|
||||
WriteSingle(value.X);
|
||||
WriteSingle(value.Y);
|
||||
WriteSingle(value.Z);
|
||||
WriteSingle(value.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type Quaternion from the message.
|
||||
/// </summary>
|
||||
FORCE_INLINE Quaternion ReadQuaternion()
|
||||
{
|
||||
return Quaternion(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Writes data of type String into the message. UTF-16 encoded.
|
||||
/// </summary>
|
||||
FORCE_INLINE void WriteString(const String& value)
|
||||
{
|
||||
WriteUInt16(value.Length()); // TODO: Use 1-byte length when possible
|
||||
WriteBytes((uint8*)value.Get(), value.Length() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and returns data of type String from the message. UTF-16 encoded.
|
||||
/// </summary>
|
||||
FORCE_INLINE String ReadString()
|
||||
{
|
||||
uint16 length = ReadUInt16();
|
||||
String value;
|
||||
value.Resize(length);
|
||||
ReadBytes((uint8*)value.Get(), length * sizeof(wchar_t));
|
||||
return value;
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Returns true if the message is valid for reading or writing.
|
||||
/// </summary>
|
||||
bool IsValid() const
|
||||
{
|
||||
return Buffer != nullptr && BufferSize > 0;
|
||||
}
|
||||
};
|
||||
192
Source/Engine/Networking/NetworkPeer.cpp
Normal file
192
Source/Engine/Networking/NetworkPeer.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "NetworkPeer.h"
|
||||
#include "NetworkEvent.h"
|
||||
|
||||
#include "Drivers/ENetDriver.h"
|
||||
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Platform/CPUInfo.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
Array<NetworkPeer*, HeapAllocation> Peers;
|
||||
uint32 LastHostId = 0;
|
||||
}
|
||||
|
||||
void NetworkPeer::Initialize(const NetworkConfig& config)
|
||||
{
|
||||
Config = config;
|
||||
|
||||
ASSERT(NetworkDriver == nullptr);
|
||||
ASSERT(Config.NetworkDriverType != NetworkDriverType::Undefined);
|
||||
ASSERT(Config.ConnectionsLimit > 0);
|
||||
ASSERT(Config.MessageSize > 32); // TODO: Adjust this, not sure what the lowest limit should be.
|
||||
ASSERT(Config.MessagePoolSize > 128);
|
||||
|
||||
// TODO: Dynamic message pool allocation
|
||||
// Setup messages
|
||||
CreateMessageBuffers();
|
||||
MessagePool.Clear();
|
||||
|
||||
// Warmup message pool
|
||||
for (uint32 messageId = Config.MessagePoolSize; messageId > 0; messageId --)
|
||||
MessagePool.Push(messageId);
|
||||
|
||||
// Setup network driver
|
||||
NetworkDriver = New<ENetDriver>();
|
||||
NetworkDriver->Initialize(this, Config);
|
||||
|
||||
LOG(Info, "NetworkManager initialized using driver = {0}", static_cast<int>(Config.NetworkDriverType));
|
||||
}
|
||||
|
||||
void NetworkPeer::Shutdown()
|
||||
{
|
||||
NetworkDriver->Dispose();
|
||||
Delete(NetworkDriver);
|
||||
DisposeMessageBuffers();
|
||||
|
||||
NetworkDriver = nullptr;
|
||||
}
|
||||
|
||||
void NetworkPeer::CreateMessageBuffers()
|
||||
{
|
||||
ASSERT(MessageBuffer == nullptr);
|
||||
|
||||
const uint32 pageSize = Platform::GetCPUInfo().PageSize;
|
||||
|
||||
// Calculate total size in bytes
|
||||
const uint64 totalSize = static_cast<uint64>(Config.MessagePoolSize) * Config.MessageSize;
|
||||
|
||||
// Calculate the amount of pages that we need
|
||||
const uint32 numPages = totalSize > pageSize ? Math::CeilToInt(totalSize / static_cast<float>(pageSize)) : 1;
|
||||
|
||||
MessageBuffer = static_cast<uint8*>(Platform::AllocatePages(numPages, pageSize));
|
||||
Platform::MemorySet(MessageBuffer, 0, numPages * pageSize);
|
||||
}
|
||||
|
||||
void NetworkPeer::DisposeMessageBuffers()
|
||||
{
|
||||
ASSERT(MessageBuffer != nullptr);
|
||||
|
||||
Platform::FreePages(MessageBuffer);
|
||||
MessageBuffer = nullptr;
|
||||
}
|
||||
|
||||
bool NetworkPeer::Listen()
|
||||
{
|
||||
LOG(Info, "NetworkManager starting to listen on address = {0}:{1}", Config.Address, Config.Port);
|
||||
return NetworkDriver->Listen();
|
||||
}
|
||||
|
||||
bool NetworkPeer::Connect()
|
||||
{
|
||||
LOG(Info, "Connecting to {0}:{1}...", Config.Address, Config.Port);
|
||||
return NetworkDriver->Connect();
|
||||
}
|
||||
|
||||
void NetworkPeer::Disconnect()
|
||||
{
|
||||
LOG(Info, "Disconnecting...");
|
||||
NetworkDriver->Disconnect();
|
||||
}
|
||||
|
||||
void NetworkPeer::Disconnect(const NetworkConnection& connection)
|
||||
{
|
||||
LOG(Info, "Disconnecting connection with id = {0}...", connection.ConnectionId);
|
||||
NetworkDriver->Disconnect(connection);
|
||||
}
|
||||
|
||||
bool NetworkPeer::PopEvent(NetworkEvent& eventRef)
|
||||
{
|
||||
return NetworkDriver->PopEvent(&eventRef);
|
||||
}
|
||||
|
||||
NetworkMessage NetworkPeer::CreateMessage()
|
||||
{
|
||||
const uint32 messageId = MessagePool.Pop();
|
||||
uint8* messageBuffer = GetMessageBuffer(messageId);
|
||||
|
||||
return NetworkMessage(messageBuffer, messageId, Config.MessageSize, 0, 0);
|
||||
}
|
||||
|
||||
void NetworkPeer::RecycleMessage(const NetworkMessage& message)
|
||||
{
|
||||
ASSERT(message.IsValid());
|
||||
#ifdef BUILD_DEBUG
|
||||
ASSERT(MessagePool.Contains(message.MessageId) == false);
|
||||
#endif
|
||||
|
||||
// Return the message id
|
||||
MessagePool.Push(message.MessageId);
|
||||
}
|
||||
|
||||
NetworkMessage NetworkPeer::BeginSendMessage()
|
||||
{
|
||||
return CreateMessage();
|
||||
}
|
||||
|
||||
void NetworkPeer::AbortSendMessage(const NetworkMessage& message)
|
||||
{
|
||||
ASSERT(message.IsValid());
|
||||
RecycleMessage(message);
|
||||
}
|
||||
|
||||
bool NetworkPeer::EndSendMessage(const NetworkChannelType channelType, const NetworkMessage& message)
|
||||
{
|
||||
ASSERT(message.IsValid());
|
||||
|
||||
NetworkDriver->SendMessage(channelType, message);
|
||||
|
||||
RecycleMessage(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NetworkPeer::EndSendMessage(const NetworkChannelType channelType, const NetworkMessage& message, const NetworkConnection& target)
|
||||
{
|
||||
ASSERT(message.IsValid());
|
||||
|
||||
NetworkDriver->SendMessage(channelType, message, target);
|
||||
|
||||
RecycleMessage(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NetworkPeer::EndSendMessage(const NetworkChannelType channelType, const NetworkMessage& message, const Array<NetworkConnection>& targets)
|
||||
{
|
||||
ASSERT(message.IsValid());
|
||||
|
||||
NetworkDriver->SendMessage(channelType, message, targets);
|
||||
|
||||
RecycleMessage(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
NetworkPeer* NetworkPeer::CreatePeer(const NetworkConfig& config)
|
||||
{
|
||||
// Validate the address for listen/connect
|
||||
NetworkEndPoint endPoint = {};
|
||||
const bool isValidEndPoint = NetworkBase::CreateEndPoint(config.Address, String("7777"), NetworkIPVersion::IPv4, endPoint, false);
|
||||
ASSERT(config.Address == String("any") || isValidEndPoint);
|
||||
|
||||
// Alloc new host
|
||||
Peers.Add(New<NetworkPeer>());
|
||||
NetworkPeer* host = Peers.Last();
|
||||
host->HostId = LastHostId++;
|
||||
|
||||
// Initialize the host
|
||||
host->Initialize(config);
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
void NetworkPeer::ShutdownPeer(NetworkPeer* peer)
|
||||
{
|
||||
ASSERT(peer->IsValid());
|
||||
peer->Shutdown();
|
||||
peer->HostId = -1;
|
||||
Peers.Remove(peer);
|
||||
|
||||
Delete(peer);
|
||||
}
|
||||
194
Source/Engine/Networking/NetworkPeer.h
Normal file
194
Source/Engine/Networking/NetworkPeer.h
Normal file
@@ -0,0 +1,194 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
#include "Engine/Scripting/ScriptingObjectReference.h"
|
||||
#include "Types.h"
|
||||
#include "NetworkConfig.h"
|
||||
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
/// <summary>
|
||||
/// Low-level network peer class. Provides server-client communication functions, message processing and sending.
|
||||
/// </summary>
|
||||
API_CLASS(sealed, NoSpawn, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkPeer final : public PersistentScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(NetworkPeer);
|
||||
friend class NetworkManager;
|
||||
public:
|
||||
int HostId = -1;
|
||||
NetworkConfig Config;
|
||||
INetworkDriver* NetworkDriver = nullptr;
|
||||
|
||||
uint8* MessageBuffer = nullptr;
|
||||
Array<uint32, HeapAllocation> MessagePool;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NetworkPeer"/> class.
|
||||
/// </summary>
|
||||
NetworkPeer() : PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
void Initialize(const NetworkConfig& config);
|
||||
void Shutdown();
|
||||
|
||||
private:
|
||||
void CreateMessageBuffers();
|
||||
void DisposeMessageBuffers();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Starts listening for incoming connections.
|
||||
/// Once this is called, this peer becomes a server.
|
||||
/// </summary>
|
||||
/// <returns>True when succeeded.</returns>
|
||||
API_FUNCTION()
|
||||
bool Listen();
|
||||
|
||||
/// <summary>
|
||||
/// Starts connection handshake with the end point specified in the <seealso cref="NetworkConfig"/> structure.
|
||||
/// Once this is called, this peer becomes a client.
|
||||
/// </summary>
|
||||
/// <returns>True when succeeded.</returns>
|
||||
API_FUNCTION()
|
||||
bool Connect();
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects from the server.
|
||||
/// </summary>
|
||||
/// <remarks>Can be used only by the client!</remarks>
|
||||
API_FUNCTION()
|
||||
void Disconnect();
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects given connection from the server.
|
||||
/// </summary>
|
||||
/// <remarks>Can be used only by the server!</remarks>
|
||||
API_FUNCTION()
|
||||
void Disconnect(const NetworkConnection& connection);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to pop an network event from the queue.
|
||||
/// </summary>
|
||||
/// <param name="eventRef">The reference to event structure.</param>
|
||||
/// <returns>True when succeeded and the event can be processed.</returns>
|
||||
/// <remarks>If this returns message event, make sure to recycle the message using <see cref="RecycleMessage"/> function after processing it!</remarks>
|
||||
API_FUNCTION()
|
||||
bool PopEvent(API_PARAM(out) NetworkEvent& eventRef);
|
||||
|
||||
/// <summary>
|
||||
/// Acquires new message from the pool.
|
||||
/// Cannot acquire more messages than the limit specified in the <seealso cref="NetworkConfig"/> structure.
|
||||
/// </summary>
|
||||
/// <returns>The acquired message.</returns>
|
||||
/// <remarks>Make sure to recycle the message to this peer once it is no longer needed!</remarks>
|
||||
API_FUNCTION()
|
||||
NetworkMessage CreateMessage();
|
||||
|
||||
/// <summary>
|
||||
/// Returns given message to the pool.
|
||||
/// </summary>
|
||||
/// <remarks>Make sure that this message belongs to the peer and has not been recycled already (debug build checks for this)!</remarks>
|
||||
API_FUNCTION()
|
||||
void RecycleMessage(const NetworkMessage& message);
|
||||
|
||||
/// <summary>
|
||||
/// Acquires new message from the pool and setups it for sending.
|
||||
/// </summary>
|
||||
/// <returns>The acquired message.</returns>
|
||||
API_FUNCTION()
|
||||
NetworkMessage BeginSendMessage();
|
||||
|
||||
/// <summary>
|
||||
/// Aborts given message send. This effectively deinitializes the message and returns it to the pool.
|
||||
/// </summary>
|
||||
/// <param name="message">The message.</param>
|
||||
API_FUNCTION()
|
||||
void AbortSendMessage(const NetworkMessage& message);
|
||||
|
||||
/// <summary>
|
||||
/// Sends given message over specified channel to the server.
|
||||
/// </summary>
|
||||
/// <param name="channelType">The channel to send the message over.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <remarks>Can be used only by the client!</remarks>
|
||||
/// <remarks>
|
||||
/// Do not recycle the message after calling this.
|
||||
/// This function automatically recycles the message.
|
||||
/// </remarks>
|
||||
API_FUNCTION()
|
||||
bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message);
|
||||
|
||||
/// <summary>
|
||||
/// Sends given message over specified channel to the given client connection (target).
|
||||
/// </summary>
|
||||
/// <param name="channelType">The channel to send the message over.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="target">The client connection to send the message to.</param>
|
||||
/// <remarks>Can be used only by the server!</remarks>
|
||||
/// <remarks>
|
||||
/// Do not recycle the message after calling this.
|
||||
/// This function automatically recycles the message.
|
||||
/// </remarks>
|
||||
API_FUNCTION()
|
||||
bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message, const NetworkConnection& target);
|
||||
|
||||
/// <summary>
|
||||
/// Sends given message over specified channel to the given client connection (target).
|
||||
/// </summary>
|
||||
/// <param name="channelType">The channel to send the message over.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="targets">The connections list to send the message to.</param>
|
||||
/// <remarks>Can be used only by the server!</remarks>
|
||||
/// <remarks>
|
||||
/// Do not recycle the message after calling this.
|
||||
/// This function automatically recycles the message.
|
||||
/// </remarks>
|
||||
API_FUNCTION()
|
||||
bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message, const Array<NetworkConnection, HeapAllocation>& targets);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Creates new peer using given configuration.
|
||||
/// </summary>
|
||||
/// <param name="config">The configuration to create and setup new peer.</param>
|
||||
/// <returns>The peer.</returns>
|
||||
/// <remarks>Peer should be destroyed using <see cref="ShutdownPeer"/> once it is no longer in use.</remarks>
|
||||
API_FUNCTION()
|
||||
static NetworkPeer* CreatePeer(const NetworkConfig& config);
|
||||
|
||||
/// <summary>
|
||||
/// Shutdowns and destroys given peer.
|
||||
/// </summary>
|
||||
/// <param name="peer">The peer to destroy.</param>
|
||||
API_FUNCTION()
|
||||
static void ShutdownPeer(NetworkPeer* peer);
|
||||
|
||||
public:
|
||||
bool IsValid() const
|
||||
{
|
||||
return NetworkDriver != nullptr && HostId >= 0;
|
||||
}
|
||||
|
||||
uint8* GetMessageBuffer(const uint32 messageId) const
|
||||
{
|
||||
// Calculate and return the buffer slice using previously calculated slice.
|
||||
return MessageBuffer + Config.MessageSize * messageId;
|
||||
}
|
||||
|
||||
public:
|
||||
FORCE_INLINE bool operator==(const NetworkPeer& other) const
|
||||
{
|
||||
return HostId == other.HostId;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator!=(const NetworkPeer& other) const
|
||||
{
|
||||
return HostId != other.HostId;
|
||||
}
|
||||
};
|
||||
|
||||
18
Source/Engine/Networking/Networking.Build.cs
Normal file
18
Source/Engine/Networking/Networking.Build.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using Flax.Build;
|
||||
using Flax.Build.NativeCpp;
|
||||
|
||||
/// <summary>
|
||||
/// Networking module.
|
||||
/// </summary>
|
||||
public class Networking : EngineModule
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Setup(BuildOptions options)
|
||||
{
|
||||
base.Setup(options);
|
||||
|
||||
options.PublicDefinitions.Add("COMPILE_WITH_NETWORKING");
|
||||
}
|
||||
}
|
||||
14
Source/Engine/Networking/Types.h
Normal file
14
Source/Engine/Networking/Types.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
enum class NetworkChannelType;
|
||||
enum class NetworkEventType;
|
||||
|
||||
class INetworkDriver;
|
||||
class NetworkPeer;
|
||||
|
||||
struct NetworkEvent;
|
||||
struct NetworkConnection;
|
||||
struct NetworkMessage;
|
||||
struct NetworkConfig;
|
||||
22
Source/ThirdParty/enet/enet License.txt
vendored
Normal file
22
Source/ThirdParty/enet/enet License.txt
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2002-2016 Lee Salzman
|
||||
Copyright (c) 2017-2021 Vladyslav Hrytsenko, Dominik Madarász
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
32
Source/ThirdParty/enet/enet.Build.cs
vendored
Normal file
32
Source/ThirdParty/enet/enet.Build.cs
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.IO;
|
||||
using Flax.Build;
|
||||
using Flax.Build.NativeCpp;
|
||||
|
||||
/// <summary>
|
||||
/// https://github.com/zpl-c/enet
|
||||
/// </summary>
|
||||
public class ENet : DepsModule
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
|
||||
LicenseType = LicenseTypes.MIT;
|
||||
LicenseFilePath = "enet License.txt";
|
||||
|
||||
// Merge third-party modules into engine binary
|
||||
BinaryModuleName = "FlaxEngine";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Setup(BuildOptions options)
|
||||
{
|
||||
base.Setup(options);
|
||||
|
||||
var depsRoot = options.DepsFolder;
|
||||
options.PublicDefinitions.Add("ENET");
|
||||
}
|
||||
}
|
||||
6086
Source/ThirdParty/enet/enet.h
vendored
Normal file
6086
Source/ThirdParty/enet/enet.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user