From 748b9c76ef105fecbca9b432fcee30e2fbdbcc5a Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Fri, 13 Sep 2019 01:04:02 +0200 Subject: [PATCH] Working on netcode --- .flintlock | 10 ++ flint.lua | 29 +++++ network-client/src/client.cpp | 173 ++++++++++++++++++++---- network-server/src/server.cpp | 217 +++++++++++++++++++++++-------- network-shared/include/packets.h | 7 +- 5 files changed, 360 insertions(+), 76 deletions(-) diff --git a/.flintlock b/.flintlock index 0f1c6ce..4422938 100644 --- a/.flintlock +++ b/.flintlock @@ -4,3 +4,13 @@ base_url = https://downloads.mtgames.nl/release/flint [flint.py-plugins] +[git-vulkan-headers] +url = https://github.com/KhronosGroup/Vulkan-Headers +revision = master +commit = 42ad3f90faec009b9435383ee89910846d6a91ba + +[git-glfw] +url = https://github.com/glfw/glfw +revision = 3.3-stable +commit = b1309dd42a72c8f7cd58a6f75329c4328679aed2 + diff --git a/flint.lua b/flint.lua index 9965193..9b35110 100644 --- a/flint.lua +++ b/flint.lua @@ -48,6 +48,32 @@ executable "test" hook(step.PRE_BUILD, codegen, "test/include", "components.h") include(config.paths.build .. "/generated/") +git("https://github.com/glfw/glfw", "3.3-stable", "glfw") +lib "glfw" + include "@glfw@/include" + + src "@glfw@/src/{context,init,input,monitor,window}.c" + src "@glfw@/src/egl_context.c" + if config.platform.target == "linux" then + src "@glfw@/src/glx_context.c" + src "@glfw@/src/x11_{init,monitor,window}.c" + src "@glfw@/src/posix_{time,thread}.c" + src "@glfw@/src/linux_joystick.c" + src "@glfw@/src/xkb_unicode.c" + src "@glfw@/src/osmesa_context.c" + + define "_GLFW_X11" + -- @todo This is needed for strdup to work + define "_POSIX_C_SOURCE=200809L" + + link("dl", "X11", "Xrandr", "Xinerama", "Xcursor", "GL", "GLU") + elseif config.platform.target == "windows" then + end + + lang "c11" + + warnings(false) + lib "network-shared" path "network-shared" @@ -68,6 +94,9 @@ executable "client" path "network-client" dependency "network-shared" + dependency "glfw" + + threads() run_dir "test" run_target "test" diff --git a/network-client/src/client.cpp b/network-client/src/client.cpp index 6ae243a..819733f 100644 --- a/network-client/src/client.cpp +++ b/network-client/src/client.cpp @@ -12,7 +12,51 @@ #include "packets.h" #include "debug.h" +#include +#include +#include + +#include + +// template +// class ThreadQueue { +// public: +// void push(const T& value) { +// std::lock_guard lock(_mutex); +// _queue.push(value); +// } +// +// void pop() { +// std::lock_guard lock(_mutex); +// _queue.pop(); +// } +// +// void front() { +// +// } +// +// private: +// std::queue _queue; +// std::mutex _mutex; +// }; + int main() { + std::queue> thread_queue; + + GLFWwindow* window; + + if (!glfwInit()) { + return -1; + } + + window = glfwCreateWindow(640, 480, "Network Client", nullptr, nullptr); + if (!window) { + glfwTerminate(); + return -1; + } + + glfwMakeContextCurrent(window); + ecs::Manager manager; generated::init(); @@ -28,6 +72,14 @@ int main() { throw std::runtime_error("Failed to create socket!"); } + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 500000; + + if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { + throw std::runtime_error("Failed to set timeout"); + } + sockaddr_in server_address; server_address.sin_family = AF_INET; server_address.sin_port = htons(9999); @@ -43,6 +95,8 @@ int main() { iohelper::write(buff_stream_out, PACKET::CONNECT); + // @todo Resend connect request until we have an ack (or reach timeout) + // @todo Same for requesting initial ids and entities if (sendto(sock, buff.data(), buff.size(), 0, to, to_size) < 0) { throw std::runtime_error("Failed to connect to server"); } @@ -61,40 +115,111 @@ int main() { throw std::runtime_error("Unexpected server response"); } - while (true) { - buff_stream_in.seekg(0, std::ios::beg); + bool connected = true; + uint8_t timeout = TIMEOUT; + std::thread receive([&thread_queue, sock, &from, &from_size, &window, &connected, &timeout] { + while (!glfwWindowShouldClose(window) && connected) { + std::array buff; - int bytes_received = recvfrom(sock, buff.data(), buff.size(), 0, (sockaddr*)&from, &from_size); + int bytes_received = recvfrom(sock, buff.data(), buff.size(), 0, (sockaddr*)&from, &from_size); - if (bytes_received < 0) { - throw std::runtime_error("Failed to receive data!"); + if (bytes_received < 0) { + timeout--; + if (timeout == 0) { + std::cout << "Connection to server lost!\n"; + connected = false; + } else if (timeout < TIMEOUT/3) { + std::cout << "Server connection interrupted\n"; + } + } else { + timeout = TIMEOUT; + thread_queue.push(std::move(buff)); + } + } + }); + + while (!glfwWindowShouldClose(window) && connected) { + glfwSwapBuffers(window); + glfwPollEvents(); + + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) { + buff_stream_out.seekp(0, std::ios::beg); + iohelper::write(buff_stream_out, PACKET::EVENT); + iohelper::write(buff_stream_out, "up"); + if (sendto(sock, buff.data(), buff.size(), 0, to, to_size) < 0) { + throw std::runtime_error("Failed to connect to server"); + } } - PACKET type = (PACKET)iohelper::read(buff_stream_in); - switch (type) { - case PACKET::IDS: - std::cout << "Receiving ids\n"; - ecs::serial::deserialize_ids(buff_stream_in); - break; + if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) { + buff_stream_out.seekp(0, std::ios::beg); + iohelper::write(buff_stream_out, PACKET::EVENT); + iohelper::write(buff_stream_out, "down"); + if (sendto(sock, buff.data(), buff.size(), 0, to, to_size) < 0) { + throw std::runtime_error("Failed to connect to server"); + } + } - case PACKET::ENTITIES: { - std::cout << "Receiving entities\n"; - size_t entity_count = iohelper::read_length(buff_stream_in); - for (size_t i = 0; i < entity_count; ++i) { - ecs::serial::deserialize(buff_stream_in, manager); + while (!thread_queue.empty()) { + std::array buff = thread_queue.front(); + iohelper::imemstream_fixed buff_stream_in(buff); + buff_stream_in.seekg(0, std::ios::beg); + + PACKET type = (PACKET)iohelper::read(buff_stream_in); + switch (type) { + case PACKET::IDS: + std::cout << "Receiving ids\n"; + ecs::serial::deserialize_ids(buff_stream_in); + break; + + case PACKET::ENTITIES: { + std::cout << "Receiving entities\n"; + size_t entity_count = iohelper::read_length(buff_stream_in); + for (size_t i = 0; i < entity_count; ++i) { + ecs::serial::deserialize(buff_stream_in, manager); + } + + manager.view().for_each([](ecs::Entity* entity, Position* position) { + std::cout << entity->uuid << ' ' << position->x << ' ' << position->y << '\n'; + }); + + break; } - manager.view().for_each([](ecs::Entity* entity, Position* position) { - std::cout << entity->uuid << ' ' << position->x << ' ' << position->y << '\n'; - }); + case PACKET::PING: { + buff_stream_out.seekp(0, std::ios::beg); + iohelper::write(buff_stream_out, PACKET::PING); + if (sendto(sock, buff.data(), buff.size(), 0, to, to_size) < 0) { + throw std::runtime_error("Failed to connect to server"); + } + break; + } - break; + default: + std::cout << "Unknown packet type: " << (uint32_t)type << '\n'; } - default: - std::cout << "Unknown packet type: " << (uint32_t)type << '\n'; + // dump_buffer(buff); + std::cout << "PACKET TYPE: " << type << '\n'; + thread_queue.pop(); } - - // dump_buffer(buff); } + + std::cout << "Disconnecting\n"; + + buff_stream_out.seekp(0, std::ios::beg); + iohelper::write(buff_stream_out, PACKET::DISCONNECT); + if (sendto(sock, buff.data(), buff.size(), 0, to, to_size) < 0) { + throw std::runtime_error("Failed to connect to server"); + } + + std::cout << "Waiting for thread to join\n"; + + shutdown(sock, SHUT_RDWR); + receive.join(); + + std::cout << "Shutting down\n"; + + glfwTerminate(); + return 0; } diff --git a/network-server/src/server.cpp b/network-server/src/server.cpp index 1f270c0..a6d66ae 100644 --- a/network-server/src/server.cpp +++ b/network-server/src/server.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include @@ -13,9 +15,34 @@ #include "packets.h" #include "debug.h" -ecs::Manager manager; -void on_connect(int sock, sockaddr_in from) { + +bool operator==(const sockaddr_in& lhs, const sockaddr_in& rhs) { + return lhs.sin_addr.s_addr == rhs.sin_addr.s_addr && lhs.sin_port == rhs.sin_port; +} + +namespace std { + template <> + struct hash { + size_t operator()(const sockaddr_in& from) const { + size_t addr_hash = hash()(from.sin_addr.s_addr); + size_t port_hash = hash()(from.sin_port); + + addr_hash ^= port_hash + 0x9e3779b9 + (addr_hash << 6) + (addr_hash >> 2); + return addr_hash; + } + }; +} + +struct Client { + uint8_t timeout = TIMEOUT; + ecs::Entity* player = nullptr; +}; + +ecs::Manager manager; +std::unordered_map clients = {}; + +std::string get_ip(sockaddr_in from) { uint32_t ip = from.sin_addr.s_addr; uint32_t bytes[4]; @@ -24,20 +51,30 @@ void on_connect(int sock, sockaddr_in from) { bytes[2] = (ip >> 16) & 0xff; bytes[3] = (ip >> 24) & 0xff; - std::cout << "Client connected: " << bytes[0] << '.' << bytes[1] << '.' << bytes[2] << '.' << bytes[3] << '\n'; + std::stringstream ss; + ss << bytes[0] << '.' << bytes[1] << '.' << bytes[2] << '.' << bytes[3]; + + return ss.str(); +} + +void send_entities(int sock, sockaddr_in from) { + std::array buff; + iohelper::omemstream_fixed buff_stream(buff); + iohelper::write(buff_stream, PACKET::ENTITIES); + iohelper::write_length(buff_stream, manager.view<>().size()); + for (auto [uuid, entity] : manager.view<>()) { + ecs::serial::serialize(buff_stream, entity); + } + + if (sendto(sock, buff.data(), buff.size(), 0, (sockaddr*)&from, sizeof(from)) < 0) { + throw std::runtime_error("Failed to send ids to client"); + } +} + +void send_ids(int sock, sockaddr_in from) { std::array buff; iohelper::omemstream_fixed buff_stream(buff); - iohelper::write(buff_stream, PACKET::ACK); - - if (sendto(sock, buff.data(), buff.size(), 0, (sockaddr*)&from, sizeof(from)) < 0) { - throw std::runtime_error("Failed to send ack to client"); - } - - // dump_buffer(buff); - - // Send ids and all entities - // @todo Make everything thread safe buff_stream.seekp(0, std::ios::beg); iohelper::write(buff_stream, PACKET::IDS); ecs::serial::serialize_ids(buff_stream); @@ -47,43 +84,55 @@ void on_connect(int sock, sockaddr_in from) { if (sendto(sock, buff.data(), buff.size(), 0, (sockaddr*)&from, sizeof(from)) < 0) { throw std::runtime_error("Failed to send ids to client"); } +} - // dump_buffer(buff); +void on_connect(int sock, sockaddr_in from) { + std::cout << "Client connected: " << std::dec << get_ip(from) << ':' << from.sin_port << '\n'; - std::cout << "Sending entities\n"; + ecs::Entity* player = manager.create_entity(); + player->add_component(0, 0); - buff_stream.seekp(0, std::ios::beg); - iohelper::write(buff_stream, PACKET::ENTITIES); - iohelper::write_length(buff_stream, manager.view<>().size()); - for (auto [uuid, entity] : manager.view<>()) { - ecs::serial::serialize(buff_stream, entity); - } + Client client = {TIMEOUT, player}; + clients.insert({from, client}); + + std::array buff; + iohelper::omemstream_fixed buff_stream(buff); + iohelper::write(buff_stream, PACKET::ACK); if (sendto(sock, buff.data(), buff.size(), 0, (sockaddr*)&from, sizeof(from)) < 0) { - throw std::runtime_error("Failed to send ids to client"); + throw std::runtime_error("Failed to send ack to client"); } - // THIS IS A TEST - std::cout << "Sending update\n"; + send_ids(sock, from); + send_entities(sock, from); +} - manager.view().for_each([](ecs::Entity* /* entity */, Position* position) { - if (int(position->y * 10) % 2 == 0) { - position->y *= 2; +void send_ping(int sock) { + for (auto i = clients.begin(), last = clients.end(); i != last; ) { + auto& from = i->first; + auto& timeout = i->second.timeout; + + timeout--; + + if (timeout < TIMEOUT/2) { + std::array buff; + iohelper::omemstream_fixed buff_stream(buff); + iohelper::write(buff_stream, PACKET::PING); + + if (sendto(sock, buff.data(), buff.size(), 0, (sockaddr*)&from, sizeof(from)) < 0) { + throw std::runtime_error("Failed to send ping to client"); + } } - }); - // @todo Figure out how to delta everything - buff_stream.seekp(0, std::ios::beg); - iohelper::write(buff_stream, PACKET::ENTITIES); - iohelper::write_length(buff_stream, manager.view<>().size()); - for (auto [uuid, entity] : manager.view<>()) { - ecs::serial::serialize(buff_stream, entity); - } + if (timeout == 0) { + std::cout << "Client timed-out: " << std::dec << get_ip(from) << ':' << from.sin_port << '\n'; - if (sendto(sock, buff.data(), buff.size(), 0, (sockaddr*)&from, sizeof(from)) < 0) { - throw std::runtime_error("Failed to send ids to client"); + manager.remove_entity(i->second.player); + i = clients.erase(i); + } else { + ++i; + } } - // dump_buffer(buff); } // This is the loop that listens for messages from clients @@ -102,28 +151,84 @@ void listener(int sock) { throw std::runtime_error("Failed to receive data!"); } + auto it = clients.find(from); + + if (it != clients.end()) { + it->second.timeout = TIMEOUT; + } else { + std::cout << "Unknown client\n"; + } + switch (iohelper::read(buff_stream)) { case PACKET::CONNECT: on_connect(sock, from); break; + + case PACKET::DISCONNECT: { + auto it = clients.find(from); + if (it == clients.end()) { + std::cout << "Unknown client tried to disconnect\n"; + break; + } + std::cout << "Client disconnected: " << std::dec << get_ip(from) << ':' << from.sin_port << '\n'; + + manager.remove_entity(it->second.player); + it = clients.erase(it); + break; + } + + case PACKET::IDS: + send_ids(sock, from); + break; + + case PACKET::ENTITIES: + send_entities(sock, from); + break; + + case PACKET::EVENT: { + std::string event = iohelper::read(buff_stream); + auto player = it->second.player; + if (!player) { + break; + } + + if (!event.compare("up")) { + std::cout << "Moving up\n"; + player->get_component()->x += 0.1f; + } else if (!event.compare("down")) { + std::cout << "Moving down\n"; + player->get_component()->x -= 0.1f; + } + for (auto [from, timeout] : clients) { + send_entities(sock, from); + } + break; + } + + // Maybe calculate the ping time here? + case PACKET::PING: { + break; + } + + default: + std::cout << "Unknown packet\n"; + break; } } } int main() { + /// Setup ECS generated::init(); - for (int i = 0; i < 10; ++i) { - // We can create entities - ecs::Entity* entity = manager.create_entity(); - // Then we can add components to them - entity->add_component(0.1f*i, 0.3f*i); - } - - manager.view().for_each([](ecs::Entity* entity, Position* position) { - std::cout << entity->uuid << ' ' << position->x << ' ' << position->y << '\n'; - }); + // for (int i = 0; i < 10; ++i) { + // // We can create entities + // ecs::Entity* entity = manager.create_entity(); + // // Then we can add components to them + // entity->add_component(0.1f*i, 0.3f*i); + // } + /// Setup socket int address_family = AF_INET; int type = SOCK_DGRAM; int protocol = 0; @@ -143,13 +248,23 @@ int main() { throw std::runtime_error("Failed to bind to port!"); } - std::thread lthread(std::bind(listener, sock)); + /// Start listener + std::thread listen(std::bind(listener, sock)); + + // @todo Add ping thread std::cout << "Server started!\n"; + /// Run server logic while (true) { - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + send_ping(sock); + + manager.view().for_each([](ecs::Entity* entity, Position* position) { + std::cout << entity->uuid << ' ' << position->x << ' ' << position->y << '\n'; + }); } - lthread.join(); + /// Waint for threads to join + listen.join(); } diff --git a/network-shared/include/packets.h b/network-shared/include/packets.h index a7175e4..7263661 100644 --- a/network-shared/include/packets.h +++ b/network-shared/include/packets.h @@ -2,9 +2,14 @@ #include +#define TIMEOUT 20 + enum PACKET : uint8_t { CONNECT, + DISCONNECT, + PING, ACK, IDS, - ENTITIES + ENTITIES, + EVENT };