339 lines
8.4 KiB
C++
339 lines
8.4 KiB
C++
#include <vector>
|
|
#include <string>
|
|
#include <iostream>
|
|
|
|
#include <Sockets/UdpServer.hpp>
|
|
#include <Core/Serialization.hpp>
|
|
#include <Core/Loggers.hpp>
|
|
#include <J3ML/J3ML.hpp>
|
|
#include <Core/Reflection.hpp>
|
|
#include <Core/Generator.hpp>
|
|
|
|
/* Networking Design Notes
|
|
*
|
|
* All packets are prefixed with `CaveGame`'s ascii representation. This functions as a header.
|
|
* After that, an 8-bit number indicates the message type of the packet.
|
|
*
|
|
*/
|
|
|
|
using namespace CaveGame;
|
|
using namespace CaveGame::Core;
|
|
using namespace std::chrono_literals;
|
|
using namespace J3ML::SizedIntegralTypes;
|
|
|
|
template <typename T>
|
|
void test(T value, T expected)
|
|
{
|
|
if (value != expected)
|
|
Logs::Error(std::format("rtt<{}> fail: expected {}, value {}", Reflection::type_name<T>(), expected, value));
|
|
else
|
|
Logs::Server(std::format("rtt<{}> success: expected {}, value {}", Reflection::type_name<T>(), expected, value));
|
|
}
|
|
|
|
template <typename T>
|
|
void test_diff(T value, T expected, T epsilon)
|
|
{
|
|
T diff = std::abs(std::abs(value) - std::abs(expected));
|
|
|
|
if (diff > epsilon)
|
|
Logs::Error(std::format("rtt<{}> fail: expected {}, value {}, diff {}", Reflection::type_name<T>(), expected, value, diff));
|
|
else
|
|
Logs::Server(std::format("rtt<{}> success: expected {}, value {}, diff {}", Reflection::type_name<T>(), expected, value, diff));
|
|
}
|
|
|
|
bool server_running = true;
|
|
bool client_running = true;
|
|
|
|
void RunServer() {
|
|
|
|
std::cout << "Starting server thread." << std::endl;
|
|
|
|
UDPServer<32776> udpServer;
|
|
|
|
udpServer.onRawMessageReceived = [&](const char* message, int length, std::string ipv4, uint16_t port) {
|
|
Logs::Server(std::format("recv from {}:{} => {} ({} bytes)", ipv4, port, std::string(message), length));
|
|
//std::cout << "server: " << ipv4 << ":" << port << " => " << message << "(" << length << ")" << std::endl;
|
|
|
|
|
|
if (length == 32776)
|
|
{
|
|
std::cout << "peter" << std::endl;
|
|
}
|
|
|
|
if (message[0] == 21) {
|
|
Logs::Server(std::format("Received encoded-data buffer, decoding..."));
|
|
|
|
Buffer b;
|
|
b.size = length;
|
|
b.data = new uint8_t[length];
|
|
b.index = 1;
|
|
|
|
memcpy(b.data, message, length);
|
|
|
|
test<std::string>(read_string(b), "encoded-data");
|
|
test<u8>(read_u8(b), 7);
|
|
test<u8>(read_u8(b), 12);
|
|
test<u16>(read_u16(b), 32000);
|
|
test<u32>(read_u32(b), 666420);
|
|
test<u64>(read_u64(b), 420690000);
|
|
test<s8>(read_s8(b), -64);
|
|
test<float>(read_f32(b), 3.14159f);
|
|
test_diff<float>(read_f16(b), 3.14159f, 1e-2f);
|
|
test<std::string>(read_string(b), "Tetsuo!.!.!..!!..!!");
|
|
}
|
|
|
|
if (strcmp(message, "ping") == 0)
|
|
{
|
|
udpServer.SendTo("pong", ipv4, port);
|
|
//return;
|
|
}
|
|
|
|
if (strcmp(message, "poll") == 0)
|
|
{
|
|
udpServer.SendTo("CaveGame Server v1.0.0 Protocol v1.", ipv4, port);
|
|
//return;
|
|
}
|
|
|
|
|
|
//udpServer.SendTo(message, length, ipv4, port);
|
|
};
|
|
|
|
// Bind the server to a port.
|
|
udpServer.Bind(42069, [](int errorCode, std::string errorMessage) {
|
|
std::cout << errorCode << ":" << errorMessage << std::endl;
|
|
});
|
|
|
|
std::cout << "Server running on port " << udpServer.RemotePort() << std::endl;
|
|
|
|
// You should do an input loop, so the program won't terminate immediately.
|
|
|
|
//std::string input;
|
|
//getline(std::cin, input);
|
|
//while (input != "exit") {
|
|
// getline(std::cin, input);
|
|
//}
|
|
|
|
while (server_running)
|
|
{
|
|
std::this_thread::sleep_for(10ms);
|
|
}
|
|
|
|
std::cout << "Server Closing" << std::endl;
|
|
|
|
udpServer.Close();
|
|
}
|
|
|
|
|
|
/*
|
|
Packet - up to 1024 bytes of data.
|
|
PacketType - Unreliable, Reliable, ReliableOrdered
|
|
Packet data is appended to NetworkDataBuffer, .
|
|
Messages are read out of NetworkDataBuffer.
|
|
Each message has an ACK id which upon receiving, you reply to your peer with a list of ACKs,
|
|
unacknowledged messages are re-sent in proceeding packets, up to N retries.
|
|
|
|
*/
|
|
|
|
void RunClient() {
|
|
|
|
CaveGame::Core::Generator* generator = new Generator(0);
|
|
CaveGame::Core::Chunk* chk = new Chunk({0, 5});
|
|
|
|
|
|
generator->FirstPass(chk);
|
|
|
|
|
|
std::cout << "Starting client thread." << std::endl;
|
|
|
|
const std::string IP = "localhost";
|
|
const uint16_t PORT = 42069;
|
|
|
|
UDPSocket<32776> udpSocket(true);
|
|
|
|
udpSocket.Connect(IP, PORT);
|
|
|
|
udpSocket.onRawMessageReceived = [&](const char* message, int length, std::string ipv4, uint16_t port) {
|
|
Logs::Client(std::format("recv from"));
|
|
//std::cout << "received: " << ipv4 << ":" << port << " => " << message << "(" << length << ")" << std::endl;
|
|
};
|
|
|
|
udpSocket.Send("ping");
|
|
std::this_thread::sleep_for(10ms);
|
|
udpSocket.Send("poll");
|
|
std::this_thread::sleep_for(10ms);
|
|
|
|
for (int i = 0; i < 1000; i++)
|
|
{
|
|
udpSocket.Send(std::format("Sequence of messages in rapid succession {}/1000.", i+1));
|
|
if (i % 20 == 0)
|
|
std::this_thread::sleep_for(20ms);
|
|
}
|
|
|
|
Buffer test;
|
|
test.index = 0;
|
|
test.size = 512;
|
|
test.data = new uint8_t[384];
|
|
|
|
write_u8(test, 21);
|
|
write_string(test, "encoded-data");
|
|
write_u8(test, 7);
|
|
write_u8(test, 12);
|
|
write_u16(test, 32000);
|
|
write_u32(test, 666420);
|
|
write_u64(test, 420690000);
|
|
write_s8(test, -64);
|
|
write_f32(test, 3.14159f);
|
|
write_f16(test, 3.14159f);
|
|
write_string(test, "Tetsuo!.!.!..!!..!!");
|
|
|
|
udpSocket.Send((const char*)test.data, test.size);
|
|
|
|
std::this_thread::sleep_for(10ms);
|
|
|
|
test.index = 0;
|
|
test.size = 32776;
|
|
test.data = new uint8_t[32776];
|
|
|
|
chk->write(test);
|
|
|
|
uint8_t messages[33][1024];
|
|
|
|
for (int i = 0; i < 33; i++)
|
|
{
|
|
memcpy(&messages[i], (test.data+test.index)+(i*1024), 1024);
|
|
udpSocket.Send((const char*)messages[i], 1024);
|
|
}
|
|
|
|
//udpSocket.Send((const char*)test.data, test.size);
|
|
|
|
|
|
while (client_running) {
|
|
std::this_thread::sleep_for(10ms);
|
|
}
|
|
|
|
std::cout << "Client closing." << std::endl;
|
|
}
|
|
|
|
namespace Reliability {
|
|
struct Header {
|
|
uint16_t sequence; // A number that increases with each packet sent.
|
|
uint16_t ack; // The most recent packet sequence number received.
|
|
uint32_t ack_bits; // A bitfield encoding the set of acked packets.
|
|
};
|
|
|
|
const int BufferSize = 1024;
|
|
uint32_t sequence_buffer[BufferSize];
|
|
|
|
struct PacketData {
|
|
bool acked;
|
|
double send_time;
|
|
};
|
|
|
|
PacketData packet_data[BufferSize];
|
|
|
|
PacketData * GetPacketData(uint16_t sequence)
|
|
{
|
|
const int index = sequence % BufferSize;
|
|
if (sequence_buffer[index] == sequence)
|
|
return &packet_data[index];
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
PacketData & InsertPacketData(uint16_t sequence)
|
|
{
|
|
const int index = sequence % BufferSize;
|
|
sequence_buffer[index] = sequence;
|
|
return packet_data[index];
|
|
}
|
|
|
|
struct Message {};
|
|
|
|
struct TestMessage : public Message
|
|
{
|
|
|
|
};
|
|
}
|
|
|
|
using namespace CaveGame::Core;
|
|
|
|
struct Header
|
|
{
|
|
std::string watermark = "RCG25";
|
|
|
|
void Write(Buffer& buffer)
|
|
{
|
|
write_string(buffer, watermark);
|
|
}
|
|
|
|
void Read(Buffer& buffer)
|
|
{
|
|
watermark = read_string(buffer);
|
|
}
|
|
};
|
|
|
|
class Packet
|
|
{
|
|
public:
|
|
Header header;
|
|
virtual void Write(Buffer& buffer)
|
|
{
|
|
header.Write(buffer);
|
|
}
|
|
|
|
virtual void Read(Buffer& buffer)
|
|
{
|
|
header.Read(buffer);
|
|
}
|
|
};
|
|
|
|
class ChatPacket : public Packet
|
|
{
|
|
public:
|
|
std::string sender;
|
|
std::string message;
|
|
|
|
void Read(CaveGame::Core::Buffer &buffer) override
|
|
{
|
|
Packet::Read(buffer);
|
|
write_string(buffer, sender);
|
|
write_string(buffer, message);
|
|
}
|
|
|
|
void Write(CaveGame::Core::Buffer &buffer) override
|
|
{
|
|
Packet::Write(buffer);
|
|
sender = read_string(buffer);
|
|
message = read_string(buffer);
|
|
}
|
|
};
|
|
|
|
// TODO: Boneyard structure - Rare surface "dungeon"
|
|
|
|
|
|
int main(int argc, char** argv) {
|
|
|
|
Logs::Server.IncludeLocation(false);
|
|
Logs::Client.IncludeLocation(false);
|
|
|
|
std::cout << "Running Program" << std::endl;
|
|
|
|
std::thread server(RunServer);
|
|
std::thread client(RunClient);
|
|
|
|
server.detach();
|
|
std::this_thread::sleep_for(100ms);
|
|
client.detach();
|
|
|
|
std::string input;
|
|
getline(std::cin, input);
|
|
while (input != "exit") {
|
|
getline(std::cin, input);
|
|
}
|
|
|
|
server_running = false;
|
|
client_running = false;
|
|
|
|
|
|
return 0;
|
|
} |