diff --git a/include/Engine/Network/Client.h b/include/Engine/Network/Client.h index af7c9072..95888b1f 100644 --- a/include/Engine/Network/Client.h +++ b/include/Engine/Network/Client.h @@ -1,14 +1,84 @@ #ifndef Client_h__ #define Client_h__ -#include +#include +#include -class Client +#include +#include + +#include "Network/Network.h" +#include "Network/MessageType.h" +#include "Network/PlayerDefinition.h" +#include "Network/SnapshotDefinitions.h" +#include "Core/World.h" +#include "Core/EventBroker.h" +#include "Core/ConfigFile.h" +#include "Input/EInputCommand.h" + +class Client : public Network { - Client(); - ~Client(); +public: + Client(ConfigFile* config); + ~Client(); + void Start(World* world, EventBroker* eventBroker) override; + void Update() override; + void Close(); +private: + // Assio UDP logic + boost::asio::ip::udp::endpoint m_ReceiverEndpoint; + boost::asio::io_service m_IOService; + boost::asio::ip::udp::socket m_Socket; + // Sending message to server logic + int bytesRead = -1; + char readBuf[1024] = { 0 }; + int snapshotInterval = 33; + std::clock_t previousSnapshotMessage = std::clock(); + // Packet loss logic + unsigned int m_PacketID = 0; + unsigned int m_PreviousPacketID = 0; + unsigned int m_SendPacketID = 0; + + // Game logic + World* m_World; + std::string m_PlayerName; + int m_PlayerID = -1; + + // Network logic + PlayerDefinition m_PlayerDefinitions[MAXCONNECTIONS]; + SnapshotDefinitions m_NextSnapshot; + bool m_ThreadIsRunning = true; + double m_DurationOfPingTime; + std::clock_t m_StartPingTime; + // Use to check if we should send disconnect message + // if game is turned of by closing window. + bool m_WasStarted = false; + + // Private member functions + void readFromServer(); + void sendSnapshotToServer(); + int receive(char* data, size_t length); + void send(Packet& packet); + void connect(); + void disconnect(); + void ping(); + void moveMessageHead(char*& data, size_t& length, size_t stepSize); + void parseMessageType(Packet& packet); + void parseEventMessage(Packet& packet); + void parseConnect(Packet& packet); + void parsePing(); + void parseServerPing(); + void parseSnapshot(Packet& packet); + void identifyPacketLoss(); + bool isConnected(); + EntityID createPlayer(); + + // Events + EventBroker* m_EventBroker; + EventRelay m_EInputCommand; + bool OnInputCommand(const Events::InputCommand &e); }; #endif diff --git a/include/Engine/Network/MessageType.h b/include/Engine/Network/MessageType.h new file mode 100644 index 00000000..8d09c7ee --- /dev/null +++ b/include/Engine/Network/MessageType.h @@ -0,0 +1,17 @@ +#ifndef MessageType_h__ +#define MessageType_h__ + +// Message types used by both server and client. +// Used to determine what type of message was sent. +enum class MessageType +{ + Connect, + Disconnect, + ClientPing, + ServerPing, + Message, + Snapshot, + Event, +}; + +#endif diff --git a/include/Engine/Network/Network.h b/include/Engine/Network/Network.h new file mode 100644 index 00000000..0f7baefe --- /dev/null +++ b/include/Engine/Network/Network.h @@ -0,0 +1,19 @@ +#ifndef Network_h__ +#define Network_h__ + +#include "Core/World.h" +#include "Core/EventBroker.h" +#include "Network/Packet.h" + +#define MAXCONNECTIONS 8 +#define INPUTSIZE 128 + +class Network +{ +public: + virtual ~Network() { }; + virtual void Start(World* m_world, EventBroker *eventBroker) = 0; + virtual void Update() = 0; +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Network/Packet.h b/include/Engine/Network/Packet.h new file mode 100644 index 00000000..daf39962 --- /dev/null +++ b/include/Engine/Network/Packet.h @@ -0,0 +1,61 @@ +#ifndef Packet_h__ +#define Packet_h__ + +#include +#include "Network/MessageType.h" +#include "Core/Util/Logging.h" + +// Defines the +class Packet +{ +public: + // arg1: Type of message (Connect, Disconnect...) + // arg2: PacketID for identifying packet loss. + Packet(MessageType type, unsigned int& packetID); + // Used to create packet from already existing data buffer. + Packet(char* data, const int sizeOfPacket); + + ~Packet(); + // Add primitive types like int, float, char... + template + void WritePrimitive(T val) + { + // Check if we are trying to add more than the package can fit. + if (m_MaxPacketSize < m_Offset + sizeof(T)) { + LOG_WARNING("Packet AddPrimitive(): You are trying to add more than we have allocated for!"); + } + memcpy(m_Data + m_Offset, &val, sizeof(T)); + m_Offset += sizeof(T); + } + // Pops the first element as if it was a primitive. + template + T ReadPrimitive() + { + if (m_Offset < m_ReturnDataOffset + sizeof(T)) { + LOG_WARNING("Packet PopFrontPrimitive(): You are trying to remove more than what exists in this packet!"); + return -1; + } + T returnValue; + memcpy(&returnValue, m_Data + m_ReturnDataOffset, sizeof(T)); + m_ReturnDataOffset += sizeof(T); + return returnValue; + } + // Add a string to the message + void WriteString(std::string str); + // Add data to the message + void WriteData(char* data, int sizeOfData); + // Pops the first element as if it was a string. + std::string ReadString(); + char* ReadData(int SizeOfData); + + int Size() { return m_Offset; }; + char* Data() { return m_Data; }; + +private: + char* m_Data; + unsigned int m_ReturnDataOffset = 0; + int m_Offset = 0; + unsigned int m_MaxPacketSize = 128; +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Network/PlayerDefinition.h b/include/Engine/Network/PlayerDefinition.h new file mode 100644 index 00000000..dbacda95 --- /dev/null +++ b/include/Engine/Network/PlayerDefinition.h @@ -0,0 +1,11 @@ +#ifndef PlayerDefinition_h__ +#define PlayerDefinition_h__ +#include + +struct PlayerDefinition { + int EntityID = -1; + std::string Name = ""; + boost::asio::ip::udp::endpoint Endpoint; +}; + +#endif diff --git a/include/Engine/Network/Server.h b/include/Engine/Network/Server.h index bb198b7e..5c1ac1fb 100644 --- a/include/Engine/Network/Server.h +++ b/include/Engine/Network/Server.h @@ -1,12 +1,85 @@ #ifndef Server_h__ #define Server_h__ -#include +#include +#include -class Server +#include +#include + +#include "Network/MessageType.h" +#include "Network/PlayerDefinition.h" +#include "Core/World.h" +#include "Core/EventBroker.h" +#include "Network/Network.h" + +class Server : public Network { - Server(); - ~Server(); +public: + Server(); + ~Server(); + void Start(World* m_world, EventBroker *eventBroker) override; + void Update() override; + void Close(); + +private: + // UDP logic + boost::asio::ip::udp::endpoint m_ReceiverEndpoint; + boost::asio::io_service m_IOService; + boost::asio::ip::udp::socket m_Socket; + PlayerDefinition m_PlayerDefinitions[MAXCONNECTIONS]; + + // Sending messages to client logic + char readBuffer[1024] = { 0 }; + int bytesRead = 0; + // time for previouse message + std::clock_t previousePingMessage = std::clock(); + std::clock_t previousSnapshotMessage = std::clock(); + std::clock_t timOutTimer = std::clock(); + // How often we send messages (milliseconds) + int intervalMs = 1000; + int snapshotInterval = 50; + int checkTimeOutInterval = 100; + + //Timers + std::clock_t m_StartPingTime; + std::clock_t m_StopTimes[8]; + + // Game logic + World* m_World; + EventBroker* m_EventBroker; + // vec.size() = ammount of players to create, stores playerID's + std::vector m_PlayersToCreate; + + // Packet loss logic + unsigned int m_PacketID; + unsigned int m_PreviousPacketID; + unsigned int m_SendPacketID; + + // Close logic + bool m_ThreadIsRunning = true; + + // Private member functions + int receive(char* data, size_t length); + void readFromClients(); + void send(Packet& packet, int playerID); + void send(Packet& packet); + void moveMessageHead(char*& data, size_t& length, size_t stepSize); + void broadcast(std::string message); + void broadcast(Packet& packet); + void sendSnapshot(); + void sendPing(); + void checkForTimeOuts(); + void disconnect(int i); + void parseMessageType(Packet& packet); + void parseEvent(Packet& packet); + void parseConnect(Packet& packet); + void parseDisconnect(); + void parseClientPing(); + void parseServerPing(); + void parseSnapshot(Packet& packet); + void identifyPacketLoss(); + EntityID createPlayer(); }; #endif diff --git a/include/Engine/Network/SnapshotDefinitions.h b/include/Engine/Network/SnapshotDefinitions.h new file mode 100644 index 00000000..b643d18c --- /dev/null +++ b/include/Engine/Network/SnapshotDefinitions.h @@ -0,0 +1,12 @@ +#ifndef SnapshotDefinitions_h__ +#define SnapshotDefinitions_h__ + +struct SnapshotDefinitions +{ + // "+Forward" is 8 characters * sizeof(char) = 8 + std::string InputForward; + // "+Right" is 6 characters * sizeof(char) = 6 + std::string InputRight; +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Rendering/IRenderer.h b/include/Engine/Rendering/IRenderer.h index 1acb2454..5dce14c7 100644 --- a/include/Engine/Rendering/IRenderer.h +++ b/include/Engine/Rendering/IRenderer.h @@ -35,7 +35,7 @@ public: virtual void Draw(RenderQueueCollection& rq) = 0; protected: - Rectangle m_Resolution = Rectangle(1280, 720); + Rectangle m_Resolution = Rectangle::Rectangle(1280, 720); bool m_Fullscreen = false; bool m_VSYNC = false; int m_GLVersion[2]; diff --git a/include/Game/Game.h b/include/Game/Game.h index 7933c8a1..33dc88a6 100644 --- a/include/Game/Game.h +++ b/include/Game/Game.h @@ -19,6 +19,13 @@ #include "PlayerSystem.h" #include "Editor/EditorSystem.h" +// Network +#include +#include "Network/Network.h" +#include "Network/Server.h" +#include "Network/Client.h" + + class Game { public: @@ -39,12 +46,21 @@ private: World* m_World; SystemPipeline* m_SystemPipeline; RenderQueueFactory* m_RenderQueueFactory; + // Network variables + boost::thread m_NetworkThread; + + // Network methods + void networkFunction(); + Network* m_ClientOrServer; + bool m_IsClientOrServer = false; EventRelay m_EInputCommand; bool debugOnInputCommand(const Events::InputCommand& e); void debugInitialize(); void debugTick(double dt); + EventRelay m_EKeyDown; + }; #endif diff --git a/include/Game/PlayerSystem.h b/include/Game/PlayerSystem.h index 1b152548..577fbbb0 100644 --- a/include/Game/PlayerSystem.h +++ b/include/Game/PlayerSystem.h @@ -6,43 +6,22 @@ #include "Common.h" #include "Core/System.h" -#include "Core/EventBroker.h" -#include "Core/EKeyDown.h" -#include "Core/EKeyUp.h" #include "Collision/ETrigger.h" -struct KeyInput -{ - bool Forward = false; - bool Left = false; - bool Back = false; - bool Right = false; -}; - class PlayerSystem : public PureSystem { public: PlayerSystem(EventBroker* eventBroker) : PureSystem(eventBroker, "Player") { - EVENT_SUBSCRIBE_MEMBER(m_EKeyDown, &PlayerSystem::OnKeyDown); - EVENT_SUBSCRIBE_MEMBER(m_EKeyUp, &PlayerSystem::OnKeyUp); EVENT_SUBSCRIBE_MEMBER(m_ETouch, &PlayerSystem::OnTouch); EVENT_SUBSCRIBE_MEMBER(m_EEnter, &PlayerSystem::OnEnter); EVENT_SUBSCRIBE_MEMBER(m_ELeave, &PlayerSystem::OnLeave); } virtual void UpdateComponent(World* world, ComponentWrapper& player, double dt) override; - private: float m_Speed = 5; - glm::vec3 m_Direction; - KeyInput input; - - EventRelay m_EKeyDown; - bool OnKeyDown(const Events::KeyDown &event); - EventRelay m_EKeyUp; - bool OnKeyUp(const Events::KeyUp &event); EventRelay m_EEnter; bool OnEnter(const Events::TriggerEnter &event); EventRelay m_ETouch; diff --git a/resources/DefaultConfig.ini b/resources/DefaultConfig.ini index 208f413c..3ab402ad 100644 --- a/resources/DefaultConfig.ini +++ b/resources/DefaultConfig.ini @@ -3,9 +3,17 @@ LogLevel=1 LoadMap= EditorEnabled=false + [Video] Fullscreen=false VSYNC=false Width=1280 Height=720 -FOV=45 \ No newline at end of file +FOV=45 + +[Networking] +StartNetwork=false +IsServer=false +Name=Bob +Address=127.0.0.1 +Port=13 \ No newline at end of file diff --git a/resources/DefaultInput.ini b/resources/DefaultInput.ini index b0b9e3d0..95ebbc22 100644 --- a/resources/DefaultInput.ini +++ b/resources/DefaultInput.ini @@ -18,4 +18,7 @@ F1=ToggleEditor 1=EditorToolMove 2=EditorToolRotate 3=EditorToolScale -X=EditorToggleTransformSpace \ No newline at end of file +X=EditorToggleTransformSpace +C=ConnectToServer +N=SwitchToServer +M=SwitchToClient \ No newline at end of file diff --git a/resources/Schema/Components/Player.xml b/resources/Schema/Components/Player.xml index 91a3bb4e..190f2ed0 100644 --- a/resources/Schema/Components/Player.xml +++ b/resources/Schema/Components/Player.xml @@ -1,3 +1,7 @@ + false + false + false + false \ No newline at end of file diff --git a/resources/Schema/Components/Player.xsd b/resources/Schema/Components/Player.xsd index 78c5866b..76a6a8fb 100644 --- a/resources/Schema/Components/Player.xsd +++ b/resources/Schema/Components/Player.xsd @@ -7,6 +7,10 @@ + + + + diff --git a/resources/Schema/Entities/Test.xml b/resources/Schema/Entities/Test.xml index 0233ff95..528c7e95 100644 --- a/resources/Schema/Entities/Test.xml +++ b/resources/Schema/Entities/Test.xml @@ -34,7 +34,7 @@ - + diff --git a/src/Engine/Collision/Collision.cpp b/src/Engine/Collision/Collision.cpp index d076d1c2..c4af6258 100644 --- a/src/Engine/Collision/Collision.cpp +++ b/src/Engine/Collision/Collision.cpp @@ -252,6 +252,9 @@ bool GetEntityBox(World* world, ComponentWrapper& AABBComponent, AABB& outBox) glm::vec3 mini = outBox.MinCorner(); glm::vec3 maxi = outBox.MaxCorner(); + if (modelRes == nullptr) { + return false; + } glm::mat4 modelMatrix = modelRes->m_Matrix * glm::translate(glm::mat4(), (glm::vec3)cTrans["Position"]) * glm::scale((glm::vec3)cTrans["Scale"]); diff --git a/src/Engine/Network/Client.cpp b/src/Engine/Network/Client.cpp index bc513799..5c7e9c8f 100644 --- a/src/Engine/Network/Client.cpp +++ b/src/Engine/Network/Client.cpp @@ -1,11 +1,334 @@ -#include "Network\Client.h" +#include "Network/Client.h" -Client::Client() +using namespace boost::asio::ip; + + +Client::Client(ConfigFile* config) : m_Socket(m_IOService) { - + // Default is local host + std::string address = config->Get("Networking.Address", "127.0.0.1"); + int port = config->Get("Networking.Port", 13); + m_ReceiverEndpoint = udp::endpoint(boost::asio::ip::address::from_string(address), port); + // Set up network stream + m_PlayerName = config->Get("Networking.Name", "Raptorcopter"); + m_NextSnapshot.InputForward = ""; + m_NextSnapshot.InputRight = ""; } Client::~Client() { } + +void Client::Start(World* world, EventBroker* eventBroker) +{ + m_WasStarted = true; + m_EventBroker = eventBroker; + m_World = world; + + // Subscribe to events + m_EInputCommand = decltype(m_EInputCommand)(std::bind(&Client::OnInputCommand, this, std::placeholders::_1)); + m_EventBroker->Subscribe(m_EInputCommand); + + + //while (m_PlayerName.size() > 7) { + // LOG_INFO("Please enter your name (No longer than 7 characters):"); + // std::cin >> m_PlayerName; + //} + m_Socket.connect(m_ReceiverEndpoint); + LOG_INFO("I am client. BIP BOP"); +} + +void Client::Update() +{ + readFromServer(); +} + +void Client::Close() +{ + if (m_WasStarted) { + disconnect(); + m_ThreadIsRunning = false; + m_EventBroker->Unsubscribe(m_EInputCommand); + } +} + +void Client::readFromServer() +{ + if (m_Socket.available()) { + bytesRead = receive(readBuf, INPUTSIZE); + if (bytesRead > 0) { + Packet packet(readBuf, bytesRead); + parseMessageType(packet); + } + } + std::clock_t currentTime = std::clock(); + if (snapshotInterval < (1000 * (currentTime - previousSnapshotMessage) / (double)CLOCKS_PER_SEC)) { + if (isConnected()) { + sendSnapshotToServer(); + } + previousSnapshotMessage = currentTime; + } +} + +void Client::sendSnapshotToServer() +{ + // Reset previouse key state in snapshot. + m_NextSnapshot.InputForward = ""; + m_NextSnapshot.InputRight = ""; + + auto player = m_World->GetComponent(m_PlayerDefinitions[m_PlayerID].EntityID, "Player"); + + + // See if any movement keys are down + // We dont care if it's overwritten by later + // if statement. Watcha gonna do, right! + if (player["Forward"]) { + m_NextSnapshot.InputForward = "+Forward"; + } + if (player["Left"]) { + m_NextSnapshot.InputRight = "-Right"; + } + if (player["Back"]) { + m_NextSnapshot.InputForward = "-Forward"; + } + if (player["Right"]) { + m_NextSnapshot.InputRight = "+Right"; + } + + if (m_NextSnapshot.InputForward != "") { + Packet packet(MessageType::Event, m_SendPacketID); + packet.WriteString(m_NextSnapshot.InputForward); + send(packet); + } else { + Packet packet(MessageType::Event, m_SendPacketID); + packet.WriteString("0Forward"); + send(packet); + } + + if (m_NextSnapshot.InputRight != "") { + Packet packet(MessageType::Event, m_SendPacketID); + packet.WriteString(m_NextSnapshot.InputRight); + send(packet); + } else { + Packet packet(MessageType::Event, m_SendPacketID); + packet.WriteString("0Right"); + send(packet); + } +} + +void Client::parseMessageType(Packet& packet) +{ + int messageType = packet.ReadPrimitive(); + if (messageType == -1) + return; + // Read packet ID + m_PreviousPacketID = m_PacketID; // Set previous packet id + m_PacketID = packet.ReadPrimitive(); //Read new packet id + //IdentifyPacketLoss(); + + switch (static_cast(messageType)) { + case MessageType::Connect: + parseConnect(packet); + break; + case MessageType::ClientPing: + parsePing(); + break; + case MessageType::ServerPing: + parseServerPing(); + break; + case MessageType::Message: + break; + case MessageType::Snapshot: + parseSnapshot(packet); + break; + case MessageType::Disconnect: + break; + case MessageType::Event: + parseEventMessage(packet); + break; + default: + break; + } +} + +void Client::parseConnect(Packet& packet) +{ + m_PlayerID = packet.ReadPrimitive(); + LOG_INFO("%i: I am player: %i", m_PacketID, m_PlayerID); +} + +void Client::parsePing() +{ + m_DurationOfPingTime = 1000 * (std::clock() - m_StartPingTime) / static_cast(CLOCKS_PER_SEC); + LOG_INFO("%i: response time with ctime(ms): %f", m_PacketID, m_DurationOfPingTime); +} + +void Client::parseServerPing() +{ + Packet packet(MessageType::ServerPing, m_SendPacketID); + packet.WriteString("Ping recieved"); + send(packet); +} + +void Client::parseEventMessage(Packet& packet) +{ + int Id = -1; + std::string command = packet.ReadString(); + if (command.find("+Player") != std::string::npos) { + Id = packet.ReadPrimitive(); + // Sett Player name + m_PlayerDefinitions[Id].Name = command.erase(0, 7); + } else { + LOG_INFO("%i: Event message: %s", m_PacketID, command.c_str()); + } +} + +void Client::parseSnapshot(Packet& packet) +{ + std::string tempName; + for (size_t i = 0; i < MAXCONNECTIONS; i++) { + // We're checking for empty name for now. This might not be the best way, + // but it is to avoid sending redundant data. + tempName = packet.ReadString(); + + + // Apply the position data read to the player entity + // New player connected on the server side + if (m_PlayerDefinitions[i].Name == "" && tempName != "") { + m_PlayerDefinitions[i].Name = tempName; + m_PlayerDefinitions[i].EntityID = createPlayer(); + } else if (m_PlayerDefinitions[i].Name != "" && tempName == "") { + // Someone disconnected + // TODO: Insert code here + break; + } else if (m_PlayerDefinitions[i].Name == "" && tempName == "") { + // Not a connected player + break; + } + if (m_PlayerDefinitions[i].EntityID != -1) { + + // Move player to server position + int dataSize = m_World->GetComponent(m_PlayerDefinitions[i].EntityID, "Transform").Info.Meta.Stride; + memcpy(m_World->GetComponent(m_PlayerDefinitions[i].EntityID, "Transform").Data, packet.ReadData(dataSize), dataSize); + } + } +} + +int Client::receive(char* data, size_t length) +{ + boost::system::error_code error; + + int bytesReceived = m_Socket.receive_from(boost + ::asio::buffer((void*)data, length), + m_ReceiverEndpoint, + 0, error); + + if (error) { + //LOG_ERROR("receive: %s", error.message().c_str()); + } + + return bytesReceived; +} + +void Client::send(Packet& packet) +{ + m_Socket.send_to(boost::asio::buffer( + packet.Data(), + packet.Size()), + m_ReceiverEndpoint, 0); +} + +void Client::connect() +{ + Packet packet(MessageType::Connect, m_SendPacketID); + packet.WriteString(m_PlayerName); + m_StartPingTime = std::clock(); + send(packet); +} + +void Client::disconnect() +{ + Packet packet(MessageType::Connect, m_SendPacketID); + packet.WriteString("+Disconnect"); + send(packet); +} + +void Client::ping() +{ + Packet packet(MessageType::Connect, m_SendPacketID); + packet.WriteString("Ping"); + m_StartPingTime = std::clock(); + send(packet); +} + +void Client::moveMessageHead(char*& data, size_t& length, size_t stepSize) +{ + data += stepSize; + length -= stepSize; +} + +bool Client::OnInputCommand(const Events::InputCommand & e) +{ + if (isConnected()) { + ComponentWrapper& player = m_World->GetComponent(m_PlayerDefinitions[m_PlayerID].EntityID, "Player"); + if (e.Command == "Forward") { + if (e.Value > 0) { + (bool&)player["Forward"] = true; + (bool&)player["Back"] = false; + } else if (e.Value < 0) { + (bool&)player["Back"] = true; + (bool&)player["Forward"] = false; + } else { + (bool&)player["Forward"] = false; + (bool&)player["Back"] = false; + } + } + if (e.Command == "Right") { + if (e.Value > 0) { + (bool&)player["Right"] = true; + (bool&)player["Left"] = false; + } else if (e.Value < 0) { + (bool&)player["Left"] = true; + (bool&)player["Right"] = false; + } else { + (bool&)player["Left"] = false; + (bool&)player["Right"] = false; + } + } + } + if (e.Command == "ConnectToServer") { // Connect for now + connect(); + } + return false; +} + + +void Client::identifyPacketLoss() +{ + // if no packets lost, difference should be equal to 1 + int difference = m_PacketID - m_PreviousPacketID; + if (difference != 1) { + LOG_INFO("%i Packet(s) were lost...", difference); + } +} + +bool Client::isConnected() +{ + if (m_PlayerID != -1) { + if (m_PlayerDefinitions[m_PlayerID].EntityID != -1) { + return true; + } + } + return false; +} + +EntityID Client::createPlayer() +{ + EntityID entityID = m_World->CreateEntity(); + ComponentWrapper transform = m_World->AttachComponent(entityID, "Transform"); + ComponentWrapper model = m_World->AttachComponent(entityID, "Model"); + model["Resource"] = "Models/Core/UnitSphere.obj"; + ComponentWrapper player = m_World->AttachComponent(entityID, "Player"); + return entityID; +} diff --git a/src/Engine/Network/Packet.cpp b/src/Engine/Network/Packet.cpp new file mode 100644 index 00000000..52b15065 --- /dev/null +++ b/src/Engine/Network/Packet.cpp @@ -0,0 +1,72 @@ +#include "Network/Packet.h" + +Packet::Packet(MessageType type, unsigned int& packetID) +{ + m_Data = new char[m_MaxPacketSize]; + // Create message header + // Add message type + int messageType = static_cast(type); + Packet::WritePrimitive(messageType); + packetID = packetID % 1000; // Packet id modulos + Packet::WritePrimitive(packetID); + packetID++; +} + +// Create message +Packet::Packet(char* data, const int sizeOfPacket) +{ + // Resize message + m_MaxPacketSize = sizeOfPacket; + // Copy data newly allocated memory + m_Data = new char[sizeOfPacket]; + memcpy(m_Data, data, sizeOfPacket); + m_Offset = sizeOfPacket; +} + +Packet::~Packet() +{ + delete[] m_Data; +} + +void Packet::WriteString(std::string str) +{ + // Message, add one extra byte for null terminator + int sizeOfString = str.size() + 1; + if (m_Offset + sizeOfString > m_MaxPacketSize) { + LOG_WARNING("Package::WriteString(): Data size in packet exceeded maximum package size.\n"); + } + memcpy(m_Data + m_Offset, str.data(), sizeOfString * sizeof(char)); + m_Offset += sizeOfString * sizeof(char); +} + +void Packet::WriteData(char * data, int sizeOfData) +{ + if (m_Offset + sizeOfData > m_MaxPacketSize) { + LOG_WARNING("Packet::WriteData(): Data size in packet exceeded maximum packet size.\n"); + } + memcpy(m_Data + m_Offset, data, sizeOfData); + m_Offset += sizeOfData; +} + +std::string Packet::ReadString() +{ + std::string returnValue(m_Data + m_ReturnDataOffset); + if (m_Offset < m_ReturnDataOffset + returnValue.size()) { + LOG_WARNING("packet ReadString(): Oh no! You are trying to remove things outside my memory kingdom"); + return "PopFrontString Failed"; + } + // +1 for null terminator. + m_ReturnDataOffset += returnValue.size() + 1; + return returnValue; +} + +char * Packet::ReadData(int SizeOfData) +{ + if (m_Offset < m_ReturnDataOffset + SizeOfData) { + LOG_WARNING("packet ReadData(): Oh no! You are trying to remove things outside my memory kingdom"); + return nullptr; + } + unsigned int oldReturnDataOffset = m_ReturnDataOffset; + m_ReturnDataOffset += SizeOfData; + return (m_Data + oldReturnDataOffset); +} \ No newline at end of file diff --git a/src/Engine/Network/Server.cpp b/src/Engine/Network/Server.cpp index f65dc834..261583cc 100644 --- a/src/Engine/Network/Server.cpp +++ b/src/Engine/Network/Server.cpp @@ -1,11 +1,355 @@ -#include "Network\Server.h" +#include "Network/Server.h" -Server::Server() -{ - -} +Server::Server() : m_Socket(m_IOService, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 13)) +{ } Server::~Server() -{ +{ } + +void Server::Start(World* world, EventBroker* eventBroker) +{ + m_World = world; + m_EventBroker = eventBroker; + for (size_t i = 0; i < MAXCONNECTIONS; i++) { + m_StopTimes[i] = std::clock(); + } + LOG_INFO("I am Server. BIP BOP\n"); +} + +void Server::Update() +{ + readFromClients(); +} + +void Server::Close() +{ + m_ThreadIsRunning = false; +} + +void Server::readFromClients() +{ + // m_ThreadIsRunning might be unnecessary but the + // program crashed if it executed m_Socket.available() + // when closing the program. + + if (m_Socket.available()) { + try { + bytesRead = receive(readBuffer, INPUTSIZE); + Packet packet(readBuffer, bytesRead); + parseMessageType(packet); + } catch (const std::exception& err) { + //LOG_ERROR("%i: Read from client crashed %s", m_PacketID, err.what()); + } + + } + std::clock_t currentTime = std::clock(); + // Send snapshot + if (snapshotInterval < (1000 * (currentTime - previousSnapshotMessage) / (double)CLOCKS_PER_SEC)) { + sendSnapshot(); + previousSnapshotMessage = currentTime; + } + + // Send pings each + if (intervalMs < (1000 * (currentTime - previousePingMessage) / (double)CLOCKS_PER_SEC)) { + sendPing(); + previousePingMessage = currentTime; + } + + // Time out logic + if (checkTimeOutInterval < (1000 * (currentTime - timOutTimer) / (double)CLOCKS_PER_SEC)) { + checkForTimeOuts(); + timOutTimer = currentTime; + } +} + +void Server::parseMessageType(Packet& packet) +{ + int messageType = packet.ReadPrimitive(); // Read what type off message was sent from server + + // Read packet ID + m_PreviousPacketID = m_PacketID; // Set previous packet id + m_PacketID = packet.ReadPrimitive(); //Read new packet id + //IdentifyPacketLoss(); + switch (static_cast(messageType)) { + case MessageType::Connect: + parseConnect(packet); + break; + case MessageType::ClientPing: + //parseClientPing(); + break; + case MessageType::ServerPing: + parseServerPing(); + break; + case MessageType::Message: + break; + case MessageType::Snapshot: + parseSnapshot(packet); + break; + case MessageType::Disconnect: + parseDisconnect(); + break; + case MessageType::Event: + parseEvent(packet); + break; + default: + break; + } +} + +int Server::receive(char * data, size_t length) +{ + length = m_Socket.receive_from( + boost::asio::buffer((void*)data + , length) + , m_ReceiverEndpoint, 0); + return length; +} + +void Server::send(Packet& packet, int playerID) +{ + m_Socket.send_to( + boost::asio::buffer(packet.Data(), packet.Size()), + m_PlayerDefinitions[playerID].Endpoint, + 0); +} + +void Server::send(Packet & packet) +{ + m_Socket.send_to( + boost::asio::buffer( + packet.Data(), + packet.Size()), + m_ReceiverEndpoint, + 0); +} + +void Server::moveMessageHead(char *& data, size_t & length, size_t stepSize) +{ + data += stepSize; + length -= stepSize; +} + +void Server::broadcast(std::string message) +{ + Packet packet(MessageType::Event, m_SendPacketID); + packet.WriteString(message); + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() != boost::asio::ip::address()) { + send(packet, i); + } + } +} + +void Server::broadcast(Packet& packet) +{ + for (int i = 0; i < MAXCONNECTIONS; ++i) { + if (m_PlayerDefinitions[i].Endpoint.address() != boost::asio::ip::address()) { + send(packet, i); + } + } +} + +void Server::sendSnapshot() +{ + Packet packet(MessageType::Snapshot, m_SendPacketID); + for (size_t i = 0; i < MAXCONNECTIONS; i++) { + + // Send an empty name if there is no player connected on this position. + packet.WriteString(m_PlayerDefinitions[i].Name); + + if (m_PlayerDefinitions[i].EntityID == -1) { + continue; + } + // Pack transfrom component into data packet + auto transform = m_World->GetComponent(m_PlayerDefinitions[i].EntityID, "Transform"); + packet.WriteData(transform.Data, transform.Info.Meta.Stride); + } + broadcast(packet); +} + +void Server::sendPing() +{ + // Prints connected players ping + for (size_t i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() != boost::asio::ip::address()) { + int ping = 1000 * (m_StopTimes[i] - m_StartPingTime) / static_cast(CLOCKS_PER_SEC); + LOG_INFO("%i: Player %i's ping: %i", m_PacketID, i, ping); + } + } + + // Create ping message + Packet packet(MessageType::ServerPing, m_SendPacketID); + packet.WriteString("Ping from server"); + // Time message + m_StartPingTime = std::clock(); + // Send message + broadcast(packet); +} + +void Server::checkForTimeOuts() +{ + int timeOutTimeMs = 5000; + int startPing = 1000 * m_StartPingTime + / static_cast(CLOCKS_PER_SEC); + + for (size_t i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() != boost::asio::ip::address()) { + int stopPing = 1000 * m_StopTimes[i] + / static_cast(CLOCKS_PER_SEC); + if (startPing > stopPing + timeOutTimeMs) { + LOG_INFO("Player %i timed out!", i); + disconnect(i); + } + } + } +} + +void Server::disconnect(int i) +{ + broadcast("A player disconnected"); + LOG_INFO("Player %i disconnected/timed out", i); + + // Remove enteties and stuff + m_PlayerDefinitions[i].Endpoint = boost::asio::ip::udp::endpoint(); + m_PlayerDefinitions[i].EntityID = -1; + m_PlayerDefinitions[i].Name = ""; +} + +void Server::parseEvent(Packet& packet) +{ + size_t i; + for (i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() == m_ReceiverEndpoint.address()) { + break; + } + } + // If no player matches the address return. + if (i >= 8) + return; + + unsigned int entityId = m_PlayerDefinitions[i].EntityID; + std::string eventString = packet.ReadString(); + if ("+Forward" == eventString) { + m_World->GetComponent(entityId, "Player")["Forward"] = true; + m_World->GetComponent(entityId, "Player")["Back"] = false; + } else if ("-Forward" == eventString) { + m_World->GetComponent(entityId, "Player")["Forward"] = false; + m_World->GetComponent(entityId, "Player")["Back"] = true; + } else if ("0Forward" == eventString) { + m_World->GetComponent(entityId, "Player")["Forward"] = false; + m_World->GetComponent(entityId, "Player")["Back"] = false; + } + if ("+Right" == eventString) { + m_World->GetComponent(entityId, "Player")["Left"] = false; + m_World->GetComponent(entityId, "Player")["Right"] = true; + } else if ("-Right" == eventString) { + m_World->GetComponent(entityId, "Player")["Right"] = false; + m_World->GetComponent(entityId, "Player")["Left"] = true; + } else if ("0Right" == eventString) { + m_World->GetComponent(entityId, "Player")["Right"] = false; + m_World->GetComponent(entityId, "Player")["Left"] = false; + } +} + +void Server::parseConnect(Packet& packet) +{ + LOG_INFO("Parsing connections"); + // Check if player is already connected + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() == m_ReceiverEndpoint.address()) { + return; + } + } + + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() == boost::asio::ip::address()) { + // Create new player + m_PlayerDefinitions[i].EntityID = createPlayer(); + m_PlayerDefinitions[i].Endpoint = m_ReceiverEndpoint; + m_PlayerDefinitions[i].Name = packet.ReadString(); + + m_StopTimes[i] = std::clock(); + + LOG_INFO("Player \"%s\" connected on IP: %s", m_PlayerDefinitions[i].Name, m_PlayerDefinitions[i].Endpoint.address().to_string()); + + Packet packet(MessageType::Connect, m_SendPacketID); + packet.WritePrimitive(i); // Player ID + + send(packet, i); + + // Send notification that a player has connected + std::string str = m_PacketID + "Player " + m_PlayerDefinitions[i].Name + " connected on: " + + m_PlayerDefinitions[i].Endpoint.address().to_string(); + broadcast(str); + break; + } + } +} + +void Server::parseDisconnect() +{ + LOG_INFO("%i: Parsing disconnect", m_PacketID); + + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() == m_ReceiverEndpoint.address()) { + disconnect(i); + break; + } + } +} + +void Server::parseClientPing() +{ + LOG_INFO("%i: Parsing ping", m_PacketID); + // Return ping + Packet packet(MessageType::ClientPing, m_SendPacketID); + packet.WriteString("Ping received"); + send(packet); // This dosen't work for multiple users +} + +void Server::parseServerPing() +{ + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() == m_ReceiverEndpoint.address()) { + m_StopTimes[i] = std::clock(); + break; + } + } +} + +// NOT USED +void Server::parseSnapshot(Packet& packet) +{ + // Does no logic. Returns snapshot if client request one + // The snapshot is not a real snapshot tho... + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() != boost::asio::ip::address()) { + m_Socket.send_to( + boost::asio::buffer("I'm sending a snapshot to you guys!"), + m_PlayerDefinitions[i].Endpoint, + 0); + } + } +} + +void Server::identifyPacketLoss() +{ + // if no packets lost, difference should be equal to 1 + int difference = m_PacketID - m_PreviousPacketID; + if (difference != 1) { + LOG_INFO("%i Packet(s) were lost...", difference); + } +} + +EntityID Server::createPlayer() +{ + EntityID entityID = m_World->CreateEntity(); + ComponentWrapper transform = m_World->AttachComponent(entityID, "Transform"); + transform["Position"] = glm::vec3(-1.5f, 0.f, 0.f); + ComponentWrapper model = m_World->AttachComponent(entityID, "Model"); + model["Resource"] = "Models/Core/UnitSphere.obj"; + model["Color"] = glm::vec4(rand()%255 / 255.f, rand()%255 / 255.f, rand() %255 / 255.f, 1.f); + ComponentWrapper player = m_World->AttachComponent(entityID, "Player"); + return entityID; } diff --git a/src/Game/Game.cpp b/src/Game/Game.cpp index bf990e23..f1582d5d 100644 --- a/src/Game/Game.cpp +++ b/src/Game/Game.cpp @@ -22,7 +22,7 @@ Game::Game(int argc, char* argv[]) m_Renderer = new Renderer(m_EventBroker); m_Renderer->SetFullscreen(m_Config->Get("Video.Fullscreen", false)); m_Renderer->SetVSYNC(m_Config->Get("Video.VSYNC", false)); - m_Renderer->SetResolution(Rectangle( + m_Renderer->SetResolution(Rectangle::Rectangle( 0, 0, m_Config->Get("Video.Width", 1280), @@ -58,6 +58,11 @@ Game::Game(int argc, char* argv[]) m_SystemPipeline->AddSystem(); m_SystemPipeline->AddSystem(); + // Invoke network + if (m_Config->Get("Networking.StartNetwork", false)) { + //boost::thread workerThread(&Game::networkFunction, this); + networkFunction(); + } m_LastTime = glfwGetTime(); debugInitialize(); @@ -65,6 +70,10 @@ Game::Game(int argc, char* argv[]) Game::~Game() { + // Call before to ensure that thread closes correctly. + //if (m_IsClientOrServer) + // m_ClientOrServer.Close(); + delete m_FrameStack; delete m_EventBroker; } @@ -87,10 +96,16 @@ void Game::Tick() m_InputProxy->Process(); m_EventBroker->Swap(); + // Update network + if (m_IsClientOrServer) { + m_ClientOrServer->Update(); + } + // Iterate through systems and update world! m_SystemPipeline->Update(m_World, dt); debugTick(dt); m_Renderer->Update(dt); + m_EventBroker->Process(); m_RenderQueueFactory->Update(m_World); GLERROR("Game::Tick m_RenderQueueFactory->Update"); @@ -112,6 +127,16 @@ bool Game::debugOnInputCommand(const Events::InputCommand& e) ResourceManager::Load(mapToLoad)->PopulateWorld(m_World); } } + if (e.Command == "SwitchToServer" && e.Value > 0) { + m_ClientOrServer = new Server(); + LOG_INFO("Switching to server"); + m_ClientOrServer->Start(m_World, m_EventBroker); + } + if (e.Command == "SwitchToClient" && e.Value > 0) { + m_ClientOrServer = new Client(m_Config); + m_ClientOrServer->Start(m_World, m_EventBroker); + LOG_INFO("Switching to client"); + } return false; } @@ -125,3 +150,23 @@ void Game::debugTick(double dt) { m_EventBroker->Process(); } + +void Game::networkFunction() +{ + bool isServer = m_Config->Get("Networking.IsServer", false); + if (!isServer) { + m_IsClientOrServer = true; + m_ClientOrServer = new Client(m_Config); + } + if (isServer) { + m_IsClientOrServer = true; + m_ClientOrServer = new Server(); + } + m_ClientOrServer->Start(m_World, m_EventBroker); + // I don't think we are reaching this part of the code right now. + // ~Game() is not called if the game is exited by closing console windows + // When server or client is done set it to false. + //m_IsClientOrServer = false; + // Destroy it + //delete m_ClientOrServer; +} \ No newline at end of file diff --git a/src/Game/PlayerSystem.cpp b/src/Game/PlayerSystem.cpp index 9f7a14cf..17b9a7f5 100644 --- a/src/Game/PlayerSystem.cpp +++ b/src/Game/PlayerSystem.cpp @@ -2,58 +2,25 @@ void PlayerSystem::UpdateComponent(World * world, ComponentWrapper & player, double dt) { - if (input.Forward) { - m_Direction.z = -1; - } else if (input.Back) { - m_Direction.z = 1; - } else { - m_Direction.z = 0; + player["Velocity"] = glm::vec3(0.f, 0.f, 0.f); + if ((bool&)player["Forward"] == true) { + ((glm::vec3&)player["Velocity"]).z = m_Speed * float(dt) * -1; + } - if (input.Left) { - m_Direction.x = -1; - } else if (input.Right) { - m_Direction.x = 1; - } else { - m_Direction.x = 0; + if ((bool&)player["Left"] == true) { + ((glm::vec3&)player["Velocity"]).x = m_Speed * float(dt) * -1; + } + if ((bool&)player["Back"] == true) { + ((glm::vec3&)player["Velocity"]).z = m_Speed * float(dt); + } + if ((bool&)player["Right"] == true) { + ((glm::vec3&)player["Velocity"]).x = m_Speed * float(dt); } - ComponentWrapper& transform = world->GetComponent(player.EntityID, "Transform"); - (glm::vec3&)player["Velocity"] = m_Speed * float(dt) * m_Direction; - (glm::vec3&)transform["Position"] += (glm::vec3)player["Velocity"]; -} - -bool PlayerSystem::OnKeyDown(const Events::KeyDown & event) -{ - if (event.KeyCode == GLFW_KEY_W) { - input.Forward = true; + if ((glm::vec3)player["Velocity"] != glm::vec3(0.f)) { + ComponentWrapper& transform = world->GetComponent(player.EntityID, "Transform"); + (glm::vec3&)transform["Position"] += (glm::vec3)player["Velocity"]; } - if (event.KeyCode == GLFW_KEY_A) { - input.Left = true; - } - if (event.KeyCode == GLFW_KEY_S) { - input.Back = true; - } - if (event.KeyCode == GLFW_KEY_D) { - input.Right = true; - } - return true; -} - -bool PlayerSystem::OnKeyUp(const Events::KeyUp & event) -{ - if (event.KeyCode == GLFW_KEY_W) { - input.Forward = false; - } - if (event.KeyCode == GLFW_KEY_A) { - input.Left = false; - } - if (event.KeyCode == GLFW_KEY_S) { - input.Back = false; - } - if (event.KeyCode == GLFW_KEY_D) { - input.Right = false; - } - return false; } bool PlayerSystem::OnTouch(const Events::TriggerTouch &event)