diff --git a/src/client-test/Main.cpp b/src/client-test/Main.cpp index ea17d935..56463929 100644 --- a/src/client-test/Main.cpp +++ b/src/client-test/Main.cpp @@ -6,7 +6,8 @@ import Network; int main(int, char*[]) { - auto&& client = Client::create("127.0.0.1", 10666); + auto&& client = Client::create( + "127.0.0.1", 10666, [](auto, auto) {}, [](auto, auto) {}); if (!client) { std::println(std::cerr, "{}", client.error()); diff --git a/src/lib-network/src/Client.ixx b/src/lib-network/src/Client.ixx index d29f5f4e..495ea1c9 100644 --- a/src/lib-network/src/Client.ixx +++ b/src/lib-network/src/Client.ixx @@ -7,31 +7,57 @@ module; #include #include #include +#include #include #include #include export module Client; -import Message; +import ClientMessage; +import ServerMessage; import Error; import Memory; -export class Client +export struct PlayerConfig +{ + std::string name; +}; + +export using UpdatePlayerConfigCallback = + std::function; +export using UpdatePlayerInputCallback = + std::function; + +export class [[nodiscard]] Client { public: - Client(const sf::IpAddress& address, unsigned short port) - : remoteAddress(address), remotePort(port) + Client( + const sf::IpAddress& address, + unsigned short port, + UpdatePlayerConfigCallback updatePlayerConfigCallback, + UpdatePlayerInputCallback updatePlayerInputCallback) + : remoteAddress(address) + , remotePort(port) + , updatePlayerConfigCallback(updatePlayerConfigCallback) + , updatePlayerInputCallback(updatePlayerInputCallback) { } public: - static [[nodiscard]] std::expected - create(const sf::IpAddress& address, unsigned short port) + static [[nodiscard]] std::expected create( + const sf::IpAddress& address, + unsigned short port, + UpdatePlayerConfigCallback updatePlayerConfigCallback, + UpdatePlayerInputCallback updatePlayerInputCallback) { try { - auto&& client = Client(address, port); + auto&& client = Client( + address, + port, + updatePlayerConfigCallback, + updatePlayerInputCallback); if (auto&& result = client.bindToAnyPort(); !result) return std::unexpected(result.error()); @@ -47,6 +73,43 @@ public: } } + void readIncomingPackets() + { + sf::Packet packet; + sf::IpAddress address; + unsigned short port; + + while (socket->receive(packet, address, port) + == sf::Socket::Status::Done) + { + auto&& result = + handleIncomingMessage(ServerMessage::fromPacket(packet)); + } + } + + void updatePlayerName(const std::string& newName) + { // TODO: this + } + + ExpectSuccess sendMapReadySignal() + { + auto&& packet = ClientMessage { .type = ClientMessageType::MapLoaded, + .clientId = myClientId } + .toPacket(); + if (socket->send(packet, remoteAddress, remotePort) + != sf::Socket::Status::Done) + { + return std::unexpected( + std::format("Could not send MapLoaded message to server")); + } + + return ReturnFlag::Success; + } + + void sendInputUpdate(const std::string& json) + { // TODO: this + } + private: ExpectSuccess bindToAnyPort() { @@ -69,12 +132,14 @@ private: auto&& message = getConnectResponse(); if (!message) return std::unexpected(message.error()); - return message->playerId; + return message->clientId; } ExpectSuccess sendConnectPacket() { - auto&& packet = Message { .type = MessageType::Connect }.toPacket(); + auto&& packet = + ClientMessage { .type = ClientMessageType::ConnectionRequest } + .toPacket(); if (socket->send(packet, remoteAddress, remotePort) != sf::Socket::Status::Done) { @@ -83,34 +148,62 @@ private: remoteAddress.toString(), remotePort)); } + + return ReturnFlag::Success; } - std::expected getConnectResponse() + std::expected getConnectResponse() { - auto&& packet = sf::Packet(); - if (socket->receive(packet, remoteAddress, remotePort) - != sf::Socket::Status::Done) + sf::Packet packet; + sf::IpAddress address; + unsigned short port; + + if (socket->receive(packet, address, port) != sf::Socket::Status::Done) { return std::unexpected( std::format("Got no response from remote server")); } - auto&& message = Message::parseMessage(packet); - if (message.type != MessageType::ConnectConfirmed) + auto&& message = ServerMessage::fromPacket(packet); + if (message.type != ServerMessageType::ConnectionAccepted) { return std::unexpected(std::format( "Expected ConnectConfirmed from the server, got {}", - static_cast>( + static_cast>( message.type))); } return message; } + ExpectSuccess handleIncomingMessage(const ServerMessage& message) + { + switch (message.type) + { + using enum ServerMessageType; + + case ConnectionAccepted: + case ConnectionRefused: + case LobbyCommited: + case StartGame: + case UpdateInput: + break; + default: + return std::unexpected(std::format( + "Got invalid message code {}", + static_cast>( + message.type))); + } + + return ReturnFlag::Success; + } + private: sf::IpAddress remoteAddress; unsigned short remotePort; mem::Box socket; unsigned short myPort; PlayerIdType myClientId; + UpdatePlayerConfigCallback updatePlayerConfigCallback; + UpdatePlayerInputCallback updatePlayerInputCallback; }; diff --git a/src/lib-network/src/ClientMessage.ixx b/src/lib-network/src/ClientMessage.ixx new file mode 100644 index 00000000..798d4205 --- /dev/null +++ b/src/lib-network/src/ClientMessage.ixx @@ -0,0 +1,40 @@ +module; + +#include +#include + +export module ClientMessage; + +export import ClientMessageType; +export import NetworkTypes; + +export struct [[nodiscard]] ClientMessage +{ + ClientMessageType type; + PlayerIdType clientId; + std::string clientName; + std::string jsonData; // either map configuration JSON or input JSON + + // tick? + + static ClientMessage fromPacket(sf::Packet& packet) + { + std::underlying_type_t messageType; + + packet >> messageType; + + ClientMessage message; + message.type = static_cast(messageType); + packet >> message.clientId; + + return message; + } + + [[nodiscard]] sf::Packet toPacket() const + { + sf::Packet packet; + packet << static_cast>(type); + packet << clientId; + return packet; + } +}; diff --git a/src/lib-network/src/ClientMessageType.ixx b/src/lib-network/src/ClientMessageType.ixx new file mode 100644 index 00000000..2149817e --- /dev/null +++ b/src/lib-network/src/ClientMessageType.ixx @@ -0,0 +1,15 @@ +module; + +#include + +export module ClientMessageType; + +export enum class [[nodiscard]] ClientMessageType : uint8_t { + ConnectionRequest, + PeerSettingsUpdate, + GameSettingsUpdate, + CommitLobby, + MapLoaded, + ReportInput, + Disconnect +}; diff --git a/src/lib-network/src/Message.ixx b/src/lib-network/src/Message.ixx deleted file mode 100644 index 421bc2c1..00000000 --- a/src/lib-network/src/Message.ixx +++ /dev/null @@ -1,99 +0,0 @@ -module; - -#include -#include - -export module Message; - -export -{ - using PlayerIdType = std::uint8_t; - using ChecksumType = std::uint64_t; - - enum class [[nodiscard]] ClientMessageType : uint8_t - { - ConnectionRequest, - PeerSettingsUpdate, - GameSettingsUpdate, - CommitLobby, - MapLoaded, - ReportInput, - Disconnect - }; - - struct [[nodiscard]] ClientMessage - { - }; - - enum class [[nodiscard]] ServerMessageType : uint8_t - { - ConnectionAccepted, - ConnectionRefused, - LobbyCommited, - StartGame, - UpdateInput - }; - - struct [[nodiscard]] ServerMessage - { - }; - - enum class MessageType : std::uint8_t - { - Connect, - ConnectConfirmed, - ConnectionRefused, - PeerSettingsUpdate, - GameSettingsUpdate, - Update, - Disconnect - }; - - struct [[nodiscard]] Message - { - MessageType type; - PlayerIdType playerId; - std::string playerName; - std::string inputJson; - std::size_t tick; - ChecksumType checksum; - - static Message parseMessage(sf::Packet& packet); - - [[nodiscard]] sf::Packet toPacket() const; - }; - - sf::Packet& operator<<(sf::Packet& packet, const Message& message) - { - return packet << message.playerId - << static_cast>( - message.type) - << message.playerName << message.inputJson << message.tick - << message.checksum; - } - - sf::Packet& operator>>(sf::Packet& packet, Message& message) - { - std::underlying_type_t type; - packet >> message.playerId >> type >> message.playerName - >> message.inputJson >> message.tick >> message.checksum; - message.type = static_cast(type); - return packet; - } -} - -module :private; - -Message Message::parseMessage(sf::Packet& packet) -{ - auto&& result = Message(); - packet >> result; - return result; -} - -sf::Packet Message::toPacket() const -{ - auto&& packet = sf::Packet(); - packet << *this; - return packet; -} diff --git a/src/lib-network/src/Network.ixx b/src/lib-network/src/Network.ixx index 4ca150f0..36525a92 100644 --- a/src/lib-network/src/Network.ixx +++ b/src/lib-network/src/Network.ixx @@ -1,6 +1,5 @@ export module Network; -export import Message; export import ClientData; export import Client; -export import Server; +export import Server; \ No newline at end of file diff --git a/src/lib-network/src/NetworkTypes.ixx b/src/lib-network/src/NetworkTypes.ixx new file mode 100644 index 00000000..3229bd4e --- /dev/null +++ b/src/lib-network/src/NetworkTypes.ixx @@ -0,0 +1,8 @@ +module; + +#include + +export module NetworkTypes; + +export using PlayerIdType = std::uint8_t; +export using ChecksumType = std::uint64_t; diff --git a/src/lib-network/src/Server.ixx b/src/lib-network/src/Server.ixx index 4adab13a..6ac36e61 100644 --- a/src/lib-network/src/Server.ixx +++ b/src/lib-network/src/Server.ixx @@ -12,7 +12,8 @@ module; export module Server; -import Message; +import ServerMessage; +import ClientMessage; import ClientData; import Error; @@ -38,47 +39,61 @@ public: public: void startLoop() + { + // Read all incoming messages + while (true) + { + update(); + } + } + + void update() { socket.setBlocking(false); sf::IpAddress remoteAddress; unsigned short remotePort; sf::Packet packet; - Message message; - // Read all incoming messages - while (true) + while (socket.receive(packet, remoteAddress, remotePort) + == sf::Socket::Status::Done) { - while (socket.receive(packet, remoteAddress, remotePort) - == sf::Socket::Status::Done) - { - handleMessage( - Message::parseMessage(packet), remoteAddress, remotePort); - } - - // Send them back to all clients - // TODO: + handleMessage( + ClientMessage::fromPacket(packet), remoteAddress, remotePort); } + + // Send them back to all clients + // TODO: } private: void handleMessage( - const Message& message, + const ClientMessage& message, const sf::IpAddress& address, unsigned short port) { switch (message.type) { - case MessageType::Connect: + using enum ClientMessageType; + case ConnectionRequest: if (auto&& result = handleConnectionAttempt(address, port); !result) { std::println(std::cerr, "Server: {}", result.error()); } break; - case MessageType::Update: - std::println("Update"); + case PeerSettingsUpdate: + case GameSettingsUpdate: + case CommitLobby: + case MapLoaded: + case ReportInput: + case Disconnect: + std::println("Other message"); break; - case MessageType::Disconnect: - std::println("Disconnect"); + default: + std::println( + std::cerr, + "Unknown message {}", + static_cast>( + message.type)); break; } } @@ -99,7 +114,8 @@ private: std::println("Server: Already at full capacity, refusing new peer"); auto&& packet = - Message { .type = MessageType::ConnectionRefused }.toPacket(); + ServerMessage { .type = ServerMessageType::ConnectionRefused } + .toPacket(); if (socket.send(packet, address, port) != sf::Socket::Status::Done) { return std::unexpected(std::format( @@ -119,7 +135,8 @@ private: address.toString(), port); auto&& packet = - Message { .type = MessageType::ConnectionRefused }.toPacket(); + ServerMessage { .type = ServerMessageType::ConnectionRefused } + .toPacket(); if (socket.send(packet, address, port) != sf::Socket::Status::Done) { return std::unexpected(std::format( @@ -136,7 +153,8 @@ private: { auto&& newId = static_cast(registeredClients.size()); auto&& packet = - Message { .type = MessageType::ConnectConfirmed, .playerId = newId } + ServerMessage { .type = ServerMessageType::ConnectionAccepted, + .clientId = newId } .toPacket(); std::println( "Registering new client at {}:{} with ID: {}", @@ -153,6 +171,10 @@ private: } registeredClients[address.toInteger()] = newId; + if (newId == 0) + { + adminAddress = address.toInteger(); + } return ReturnFlag::Success; } @@ -160,6 +182,6 @@ private: private: const unsigned short MAX_CLIENT_COUNT; sf::UdpSocket socket; - std::deque connectedClients; std::map registeredClients; + sf::Uint32 adminAddress; // first connected client is the admin }; diff --git a/src/lib-network/src/ServerMessage.ixx b/src/lib-network/src/ServerMessage.ixx new file mode 100644 index 00000000..0671b95c --- /dev/null +++ b/src/lib-network/src/ServerMessage.ixx @@ -0,0 +1,36 @@ +module; + +#include +#include + +export module ServerMessage; + +export import ServerMessageType; +export import NetworkTypes; + +export struct [[nodiscard]] ServerMessage +{ + ServerMessageType type; + PlayerIdType clientId; + + static ServerMessage fromPacket(sf::Packet& packet) + { + std::underlying_type_t messageType; + + packet >> messageType; + + ServerMessage message; + message.type = static_cast(messageType); + packet >> message.clientId; + + return message; + } + + [[nodiscard]] sf::Packet toPacket() const + { + sf::Packet packet; + packet << static_cast>(type); + packet << clientId; + return packet; + } +}; diff --git a/src/lib-network/src/ServerMessageType.ixx b/src/lib-network/src/ServerMessageType.ixx new file mode 100644 index 00000000..38614182 --- /dev/null +++ b/src/lib-network/src/ServerMessageType.ixx @@ -0,0 +1,13 @@ +module; + +#include + +export module ServerMessageType; + +export enum class [[nodiscard]] ServerMessageType : uint8_t { + ConnectionAccepted, + ConnectionRefused, + LobbyCommited, + StartGame, // after all clients reported map loaded + UpdateInput +};