Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ jobs:
path: Docs/Generated_HTML

deploy:
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
needs: docs
environment:
name: github-pages
Expand Down
1 change: 1 addition & 0 deletions Source/Engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ add_subdirectory(Asset)
add_subdirectory(Audio)
add_subdirectory(Core)
add_subdirectory(Input)
add_subdirectory(Network)
add_subdirectory(Physics)
add_subdirectory(Platform)
add_subdirectory(Renderer)
Expand Down
27 changes: 27 additions & 0 deletions Source/Engine/Network/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: MIT

file(GLOB_RECURSE PRIVATE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*/Private/*.cpp)
file(GLOB_RECURSE PUBLIC_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/*/Public/*.cppm)
file(GLOB_RECURSE PRIVATE_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/*/Private/*.cppm)

if(WIN32)
file(GLOB_RECURSE WINDOWS_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/Platform/Windows/*.cpp)
list(APPEND PRIVATE_SOURCES ${WINDOWS_SOURCES})
elseif(LINUX)
file(GLOB_RECURSE LINUX_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/Platform/Linux/*.cpp)
list(APPEND PRIVATE_SOURCES ${LINUX_SOURCES})
endif()

target_sources(NexusEngine PRIVATE ${PRIVATE_SOURCES})
target_sources(NexusEngine PUBLIC
FILE_SET engine_network_modules TYPE CXX_MODULES
FILES ${PUBLIC_MODULES}
)
target_sources(NexusEngine PUBLIC
FILE_SET engine_network_modules_prv TYPE CXX_MODULES
FILES ${PRIVATE_MODULES}
)

if(WIN32)
target_link_libraries(NexusEngine PRIVATE ws2_32)
endif()
21 changes: 21 additions & 0 deletions Source/Engine/Network/Common/Private/NetworkAddress.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT

module NE.Engine.Network.Common.NetworkAddress;

import NE.Engine.Core.Types;

import std;

namespace Nexus::Network {
[[nodiscard]] const std::string& NetworkAddress::Host() const noexcept {
return m_host;
}

[[nodiscard]] uint16 NetworkAddress::Port() const noexcept {
return m_port;
}

[[nodiscard]] bool NetworkAddress::operator==(const NetworkAddress& other) const noexcept {
return m_port == other.m_port && m_host == other.m_host;
}
} // namespace Nexus::Network
35 changes: 35 additions & 0 deletions Source/Engine/Network/Common/Public/NetworkAddress.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT

module;

#include <Platform/DLL/Export.hpp>

export module NE.Engine.Network.Common.NetworkAddress;

import NE.Engine.Core.Types;

import std;

export namespace Nexus::Network {
class NEXUS_API NetworkAddress {
public:
NetworkAddress() = default;

NetworkAddress(std::string host, uint16 port) : m_host(std::move(host)), m_port(port) {}

[[nodiscard]] const std::string& Host() const noexcept;

[[nodiscard]] uint16 Port() const noexcept;

[[nodiscard]] bool operator==(const NetworkAddress& other) const noexcept;

private:
std::string m_host;
uint16 m_port = 0;
};
} // namespace Nexus::Network

template <>
struct NEXUS_API std::hash<Nexus::Network::NetworkAddress>;

#include "NetworkAddress.inl"
15 changes: 15 additions & 0 deletions Source/Engine/Network/Common/Public/NetworkAddress.inl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT

#pragma once

template <>
struct NEXUS_API std::hash<Nexus::Network::NetworkAddress> {
Nexus::usize operator()(const Nexus::Network::NetworkAddress& address) const noexcept {
const Nexus::usize h1 = std::hash<std::string>{}(address.Host());
const Nexus::usize h2 = std::hash<Nexus::uint16>{}(address.Port());

// 64-bit mix inspired by the standard hash-combine pattern.
static constexpr auto PHI = 0x9e3779b97f4a7c15ULL;
return h1 ^ (h2 + static_cast<Nexus::usize>(PHI) + (h1 << 6) + (h1 >> 2));
}
};
41 changes: 41 additions & 0 deletions Source/Engine/Network/Common/Public/NetworkError.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT

module;

#include <Platform/DLL/Export.hpp>

export module NE.Engine.Network.Common.NetworkError;

import std;

export namespace Nexus::Network {
enum class NetworkErrorCode {
None,
WouldBlock,
ConnectionInProgress,
ConnectionClosed,
ConnectionReset,
ConnectionAborted,
ConnectionRefused,
NotConnected,
AddressInUse,
HostUnreachable,
NetworkUnreachable,
Timeout,
MessageTooLarge,
InvalidAddress,
PermissionDenied,
InvalidOperation,
Unknown,
};

struct NEXUS_API NetworkError {
NetworkErrorCode code = NetworkErrorCode::None;
std::string message;
int platformErrno = 0;

[[nodiscard]] explicit operator bool() const noexcept {
return code != NetworkErrorCode::None;
}
};
} // namespace Nexus::Network
74 changes: 74 additions & 0 deletions Source/Engine/Network/Manager/Private/NetworkManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: MIT

module NE.Engine.Network.Manager;

import NE.Engine.Network.Common.NetworkAddress;
import NE.Engine.Network.Common.NetworkError;
import NE.Engine.Network.Protocol.PacketDispatcher;
import NE.Engine.Network.Packet;
import NE.Engine.Network.PacketWriter;
import NE.Engine.Network.TCP.TCPClient;
import NE.Engine.Network.UDP.UDPSocket;

import NE.Engine.Core.Types;

import std;

namespace Nexus::Network {
bool NetworkManager::StartClient(const NetworkAddress& server) {
m_serverAddress = server;

const bool tcpStarted = m_tcp.Connect(server);
const bool udpStarted = m_udp.Bind(server.Port()); // binding to 0 for an ephemeral port also works

m_lastConnectAttempt = std::chrono::steady_clock::now();

return tcpStarted || udpStarted;
}

void NetworkManager::RegisterHandler(PacketType type, PacketDispatcher::Handler handler) {
m_dispatcher.Register(type, std::move(handler));
}

bool NetworkManager::SendReliable(std::vector<byte> framedPacket) {
return m_tcp.Send(std::move(framedPacket));
}

bool NetworkManager::SendUnreliable(std::vector<byte> framedPacket) {
return m_udp.Send(m_serverAddress, framedPacket);
}

PacketWriter NetworkManager::MakeWriter(PacketType type) {
return PacketWriter(type, m_nextSequence++);
}

bool NetworkManager::IsConnected() const noexcept {
return m_tcp.IsConnected();
}

bool NetworkManager::IsConnecting() const noexcept {
return m_tcp.IsConnecting();
}

void NetworkManager::Update() {
// If we're neither connected nor connecting, try to reconnect periodically.
const auto now = std::chrono::steady_clock::now();
if (!IsConnected() && !IsConnecting()) {
if (now - m_lastConnectAttempt >= m_connectRetryInterval) {
m_tcp.Connect(m_serverAddress);
m_lastConnectAttempt = now;
}
}

for (auto& frame : m_tcp.Poll())
DispatchIncoming(frame);

std::optional<NetworkError> udpError;
while (auto message = m_udp.Receive(udpError))
DispatchIncoming(message->data);
}

void NetworkManager::DispatchIncoming(std::span<const byte> frame) const {
m_dispatcher.Dispatch(frame);
}
} // namespace Nexus::Network
54 changes: 54 additions & 0 deletions Source/Engine/Network/Manager/Public/NetworkManager.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT

module;

#include <Platform/DLL/Export.hpp>

export module NE.Engine.Network.Manager;

import NE.Engine.Network.Common.NetworkAddress;
import NE.Engine.Network.Protocol.PacketDispatcher;
import NE.Engine.Network.Packet;
import NE.Engine.Network.PacketWriter;
import NE.Engine.Network.TCP.TCPClient;
import NE.Engine.Network.UDP.UDPSocket;

import NE.Engine.Core.Types;

import std;

export namespace Nexus::Network {
class NEXUS_API NetworkManager {
public:
bool StartClient(const NetworkAddress& server);

void RegisterHandler(PacketType type, PacketDispatcher::Handler handler);

[[nodiscard]] bool SendReliable(std::vector<byte> framedPacket);

[[nodiscard]] bool SendUnreliable(std::vector<byte> framedPacket);

[[nodiscard]] PacketWriter MakeWriter(PacketType type);

[[nodiscard]] bool IsConnected() const noexcept;

[[nodiscard]] bool IsConnecting() const noexcept;

/// Call once per frame from the main loop.
void Update();

private:
void DispatchIncoming(std::span<const byte> frame) const;

private:
TCPClient m_tcp;
UDPSocket m_udp;
NetworkAddress m_serverAddress;
PacketDispatcher m_dispatcher;
uint32 m_nextSequence = 0;

// Reconnection state
std::chrono::steady_clock::time_point m_lastConnectAttempt = std::chrono::steady_clock::now();
std::chrono::milliseconds m_connectRetryInterval = std::chrono::milliseconds(1000);
};
} // namespace Nexus::Network
Loading