From f791bc26ce9ed4d7d1faf2cfda5477a62a4c8eb8 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 1 Apr 2022 12:03:50 -0400 Subject: [PATCH 01/56] Initial commit of assignment 3, replicated files from assignment 2 after copying assignment 3 from Scott's branch. Need to add the assets back. --- RoboCat/Chapter3.vcxproj | 18 +- RoboCat/Inc/Colour.h | 43 ++++ RoboCat/Inc/GameObject.h | 67 +++++ RoboCat/Inc/GraphicsLibrary.h | 80 ++++++ RoboCat/Inc/InputSystem.h | 75 ++++++ RoboCat/Inc/Networker.h | 73 ++++++ RoboCat/Inc/PlayerController.h | 46 ++++ RoboCat/Inc/Rock.h | 34 +++ RoboCat/Inc/TCPSocket.h | 1 + RoboCat/Inc/Wall.h | 43 ++++ RoboCat/Src/Colour.cpp | 41 +++ RoboCat/Src/GameObject.cpp | 28 +++ RoboCat/Src/GraphicsLibrary.cpp | 173 +++++++++++++ RoboCat/Src/InputSystem.cpp | 126 ++++++++++ RoboCat/Src/Main.cpp | 419 ++++++++++++++++++++++++++++++- RoboCat/Src/Networker.cpp | 387 ++++++++++++++++++++++++++++ RoboCat/Src/PlayerController.cpp | 138 ++++++++++ RoboCat/Src/Rock.cpp | 30 +++ RoboCat/Src/TCPSocket.cpp | 11 +- RoboCat/Src/Wall.cpp | 38 +++ 20 files changed, 1863 insertions(+), 8 deletions(-) create mode 100644 RoboCat/Inc/Colour.h create mode 100644 RoboCat/Inc/GameObject.h create mode 100644 RoboCat/Inc/GraphicsLibrary.h create mode 100644 RoboCat/Inc/InputSystem.h create mode 100644 RoboCat/Inc/Networker.h create mode 100644 RoboCat/Inc/PlayerController.h create mode 100644 RoboCat/Inc/Rock.h create mode 100644 RoboCat/Inc/Wall.h create mode 100644 RoboCat/Src/Colour.cpp create mode 100644 RoboCat/Src/GameObject.cpp create mode 100644 RoboCat/Src/GraphicsLibrary.cpp create mode 100644 RoboCat/Src/InputSystem.cpp create mode 100644 RoboCat/Src/Networker.cpp create mode 100644 RoboCat/Src/PlayerController.cpp create mode 100644 RoboCat/Src/Rock.cpp create mode 100644 RoboCat/Src/Wall.cpp diff --git a/RoboCat/Chapter3.vcxproj b/RoboCat/Chapter3.vcxproj index 8c859a81..ce090807 100644 --- a/RoboCat/Chapter3.vcxproj +++ b/RoboCat/Chapter3.vcxproj @@ -128,7 +128,7 @@ ProgramDatabase EnableFastChecks ..\SDL\include;Inc;..\ - Use + NotUsing RoboCatPCH.h @@ -370,14 +370,24 @@ + + + + + + + + + + @@ -390,15 +400,21 @@ + + + + + + diff --git a/RoboCat/Inc/Colour.h b/RoboCat/Inc/Colour.h new file mode 100644 index 00000000..5400c78d --- /dev/null +++ b/RoboCat/Inc/Colour.h @@ -0,0 +1,43 @@ +#pragma once + +/* +Allegro Wrapper Functions +Written by Adel Talhouk in FA21 +Colour.h + File information: + This file contains data used for colours. +*/ + +class Colour +{ + //-------------------------Private data------------------------- + + //Red channel + unsigned __int8 mR; + + //Green channel + unsigned __int8 mG; + + //Blue channel + unsigned __int8 mB; + + //Alpha channel + unsigned __int8 mA; + + //-------------------------Public data------------------------- +public: + + //Constructor(s) + Colour(); + Colour(unsigned __int8 r, unsigned __int8 g, unsigned __int8 b); + Colour(unsigned __int8 r, unsigned __int8 g, unsigned __int8 b, unsigned __int8 a); + + //Destructor + ~Colour(); + + //Accessor(s) + unsigned __int8 getR() { return mR; }; + unsigned __int8 getG() { return mG; }; + unsigned __int8 getB() { return mB; }; + unsigned __int8 getA() { return mA; }; +}; \ No newline at end of file diff --git a/RoboCat/Inc/GameObject.h b/RoboCat/Inc/GameObject.h new file mode 100644 index 00000000..d726281c --- /dev/null +++ b/RoboCat/Inc/GameObject.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include "GraphicsLibrary.h" + +using std::pair; + +/* + File information: + This file contains the definition for the GameObject class. +*/ + +enum GameObjectType +{ + INVALID = -1, + ROCK, + WALL, + PLAYER, + ENUM_SIZE +}; + +class GameObject +{ + //-------------------------Private data------------------------- + + + //-------------------------Protected data------------------------- + +protected: + + //Identifiers + const GameObjectType mGameObjectType = GameObjectType::INVALID; + const int mNetworkID; + + //Position + pair mPosition; + + //Graphics library + GraphicsLibrary* pGraphicsLibrary; + + //Sprite identifier + const std::string mSPRITE_IDENTIFIER; + + //Constructor(s) + GameObject(GameObjectType gameObjectType, const int networkID, GraphicsLibrary* graphicsLibrary); + GameObject(GameObjectType gameObjectType, const int networkID, GraphicsLibrary* graphicsLibrary, pair position, const std::string spriteIdentifier = ""); + + //-------------------------Public data------------------------- +public: + + //Destructor + virtual ~GameObject(); + + //Accessor(s) + const GameObjectType getGameObjectType() { return mGameObjectType; }; + const int getNetworkID() { return mNetworkID; }; + const pair getPosition() { return mPosition; }; + + //Mutator(s) + float setPosX(float posX) { mPosition.first = posX; }; + float setPosY(float posY) { mPosition.second = posY; }; + void setPos(pair newPos) { mPosition = newPos; }; + + //Functions + virtual void update() = 0; + virtual void draw() = 0; +}; \ No newline at end of file diff --git a/RoboCat/Inc/GraphicsLibrary.h b/RoboCat/Inc/GraphicsLibrary.h new file mode 100644 index 00000000..32ccafcc --- /dev/null +++ b/RoboCat/Inc/GraphicsLibrary.h @@ -0,0 +1,80 @@ +#pragma once + +/* +Allegro Wrapper Functions +Written by Adel Talhouk in FA21 +GraphicsLibrary.h + File information: + This file contains function abstractions from Allegro 5, wrapped up in my Graphics Library. This will + be used to render images and text to the screen. + Source I am consulting: Allegro 5.0.10 Manual - http://cdn.allegro.cc/file/library/allegro/5.0.10/allegro-5.0.10-manual.pdf +*/ + +#include +#include + +#include "Colour.h" + +//https://github.com/liballeg/allegro_wiki/wiki/Allegro-in-Visual-Studio#using-nuget-within-visual-studio +#include +#include +#include +#include +#include +#include + +enum TextAlignment +{ + ALIGN_LEFT = ALLEGRO_ALIGN_LEFT, + ALIGN_CENTER = ALLEGRO_ALIGN_CENTRE, + ALIGN_RIGHT = ALLEGRO_ALIGN_RIGHT +}; + +class GraphicsLibrary +{ + //-------------------------Private data------------------------- + + //Screen data + float mScreenSizeX; + float mScreenSizeY; + + //Allegro display + ALLEGRO_DISPLAY* mpDisplay; + + //Other images to draw + std::vector> mBitmapPointersVector; + + //UI text + ALLEGRO_FONT* mpFont; + ALLEGRO_COLOR mTextColour; + + friend class InputSystem; + + //-------------------------Public data------------------------- +public: + + //Constructor(s) + GraphicsLibrary(float screenSizeX, float screenSizeY); + + //Destructor + ~GraphicsLibrary(); + + //Accessor(s) + float getScreenSizeX() { return mScreenSizeX; }; + float getScreenSizeY() { return mScreenSizeY; }; + + //Mutator(s) + + //Functions + bool init(); + bool initText(std::string fontFilePath, int fontSize, Colour textColour); + void render(); + void loadImage(std::string imageFilePath, std::string imageIdentifier); + + //Drawing functions + void drawText(float posX, float posY, std::string text, TextAlignment alignment); + void drawRectangle(float topLeftX, float topLeftY, float bottomRightX, float bottomRightY, Colour colour, float thickness); + void drawImage(std::string imageIdentifier, float posX, float posY); + void drawScaledImage(std::string imageIdentifier, float posX, float posY, float scaleX, float scaleY); + void drawTintedImage(std::string imageIdentifier, float posX, float posY, Colour col); +}; \ No newline at end of file diff --git a/RoboCat/Inc/InputSystem.h b/RoboCat/Inc/InputSystem.h new file mode 100644 index 00000000..eb9ec329 --- /dev/null +++ b/RoboCat/Inc/InputSystem.h @@ -0,0 +1,75 @@ +#pragma once + +/* +Allegro Wrapper Functions +Written by Adel Talhouk in FA21 +InputSystem.h + File information: + This file contains the keycodes for input, which can be used in any way desired by other classes + and files. +*/ + +#include "GraphicsLibrary.h" + +//Include allegro libraries for input +#include + +enum KeyCode +{ + Esc = ALLEGRO_KEY_ESCAPE, + R = ALLEGRO_KEY_R, + Tab = ALLEGRO_KEY_TAB, + W = ALLEGRO_KEY_W, + A = ALLEGRO_KEY_A, + S = ALLEGRO_KEY_S, + D = ALLEGRO_KEY_D, + BACK = ALLEGRO_KEY_BACKSPACE +}; + +enum MouseButton +{ + LeftMouse = 0, + RightMouse = 1, + MiddleMouse = 2 +}; + +enum InputMode +{ + NONE = -1, + KeyPressed = ALLEGRO_EVENT_KEY_DOWN, + KeyReleased = ALLEGRO_EVENT_KEY_UP, + MouseDown = ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, + MouseUp = ALLEGRO_EVENT_MOUSE_BUTTON_UP +}; + +class InputSystem +{ + //-------------------------Private data------------------------- + + //Event queue + ALLEGRO_EVENT_QUEUE* mpMouseEventQueue; + ALLEGRO_EVENT_QUEUE* mpKeyboardEventQueue; + + //Event + ALLEGRO_EVENT mMouseEvent; + ALLEGRO_EVENT mKeyboardEvent; + + //-------------------------Public data------------------------- +public: + + //Constructor(s) + InputSystem(); + + //Destructor + ~InputSystem(); + + //Accessor(s) + float getMouseX(); + float getMouseY(); + std::pair getMousePosition(); + + //Functions + bool init(GraphicsLibrary* pGraphicsLib); + MouseButton getMouseInput(InputMode inputMode); + KeyCode getKeyboardInput(InputMode inputMode); +}; \ No newline at end of file diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h new file mode 100644 index 00000000..5aa8bc65 --- /dev/null +++ b/RoboCat/Inc/Networker.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include + +#include "GameObject.h" +#include "Rock.h" +#include "Wall.h" +#include "PlayerController.h" +#include "RoboCatPCH.h" + +using std::map; + +enum PacketType +{ + PACKET_CREATE, + PACKET_UPDATE, + PACKET_DELETE +}; + +//Networker is singleton (we only want one networker at a time) +class Networker +{ +public: + + static Networker* GetInstance() + { + if (mInstance) + { + delete mInstance; + mInstance = nullptr; + } + + mInstance = new Networker; + return mInstance; + }; + + void init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIdentifier, std::string playerSpriteIdentifier, float playerMoveSpeed, Colour wallColour); + void cleanup(); + + ~Networker(); + + //Starting and connect to server + bool initServer(std::string port); + bool connect(std::string serverIpAddress, std::string port); + + //Update game state + void receiveGameObjectState(); + void sendGameObjectState(int networkID, PacketType packetHeader); + + //Map + void addGameObject(GameObject* objectToAdd, int networkID); + GameObject* getGameObject(int networkID) { return mGameObjectsVec[networkID].second; }; + void updateGameObjects(); + void renderGameObjects(); + +private: + + Networker(); + + static Networker* mInstance; + + //Data + TCPSocketPtr* mpTCPSocket; + std::vector> mGameObjectsVec; + + //Data for GameObject replication + GraphicsLibrary* mpGraphicsLibrary; + float mPlayerMoveSpeed; + std::string mRockSpriteIdentifier; + std::string mPlayerSpriteIdentifier; + Colour mWallColour; +}; \ No newline at end of file diff --git a/RoboCat/Inc/PlayerController.h b/RoboCat/Inc/PlayerController.h new file mode 100644 index 00000000..439cf577 --- /dev/null +++ b/RoboCat/Inc/PlayerController.h @@ -0,0 +1,46 @@ +#pragma once + +/* + File information: + This file contains the definition for the PlayerController class, derived from GameObject +*/ + +#include "GameObject.h" +#include "InputSystem.h" + +class PlayerController : public GameObject +{ + //-------------------------Private data------------------------- + + //Reference to input system + InputSystem* pInputKeyDown; + InputSystem* pInputKeyUp; + + bool bShouldMoveUp = false; + bool bShouldMoveDown = false; + bool bShouldMoveLeft = false; + bool bShouldMoveRight = false; + + //Movement data + float mMoveSpeed; + + //-------------------------Public data------------------------- +public: + + //Constructor(s) + PlayerController(const int networkID, GraphicsLibrary* graphicsLibrary); + PlayerController(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, float moveSpeed, const std::string spriteIdentifier); + + //Destructor + ~PlayerController(); + + //Accessor(s) + float getMoveSpeed() { return mMoveSpeed; }; + + //Mutator(s) + void setMoveSpeed(float moveSpeed) { mMoveSpeed = moveSpeed; }; + + //Functions + void update(); + void draw(); +}; \ No newline at end of file diff --git a/RoboCat/Inc/Rock.h b/RoboCat/Inc/Rock.h new file mode 100644 index 00000000..a7e1e7dd --- /dev/null +++ b/RoboCat/Inc/Rock.h @@ -0,0 +1,34 @@ +#pragma once + +/* + File information: + This file contains the definition for the Rock class, derived from GameObject +*/ + +#include "GameObject.h" + +class Rock : public GameObject +{ + //-------------------------Private data------------------------- + + + //-------------------------Public data------------------------- +public: + + //Constructor(s) + Rock(const int networkID, GraphicsLibrary* graphicsLibrary); + Rock(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, const std::string spriteIdentifier); + + //Destructor + ~Rock(); + + //Accessor(s) + + + //Mutator(s) + + + //Functions + void update(); + void draw(); +}; \ No newline at end of file diff --git a/RoboCat/Inc/TCPSocket.h b/RoboCat/Inc/TCPSocket.h index 0779a82b..c84fb6ca 100644 --- a/RoboCat/Inc/TCPSocket.h +++ b/RoboCat/Inc/TCPSocket.h @@ -9,6 +9,7 @@ class TCPSocket int32_t Send( const void* inData, size_t inLen ); int32_t Receive( void* inBuffer, size_t inLen ); int SetNonBlockingMode(bool inShouldBeNonBlocking); + void CleanupSocket(); private: friend class SocketUtil; TCPSocket( SOCKET inSocket ) : mSocket( inSocket ) {} diff --git a/RoboCat/Inc/Wall.h b/RoboCat/Inc/Wall.h new file mode 100644 index 00000000..8d952487 --- /dev/null +++ b/RoboCat/Inc/Wall.h @@ -0,0 +1,43 @@ +#pragma once + +/* + File information: + This file contains the definition for the Wall class, derived from GameObject +*/ + +#include "GameObject.h" +#include "Colour.h" + +class Wall : public GameObject +{ + //-------------------------Private data------------------------- + float mSizeX; + float mSizeY; + float mThickness; + + Colour mColour; + + //-------------------------Public data------------------------- +public: + + //Constructor(s) + Wall(const int networkID, GraphicsLibrary* graphicsLibrary); + Wall(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, float sizeX, float sizeY, Colour colour, float thickness); + + //Destructor + ~Wall(); + + //Accessor(s) + float getWallSizeX() { return mSizeX; }; + float getWallSizeY() { return mSizeY; }; + float getWallThickness() { return mThickness; }; + + //Mutator(s) + void setWallSizeX(float sizeX) { mSizeX = sizeX; }; + void setWallSizeY(float sizeY) { mSizeY = sizeY; }; + void setWallThickness(float thiccc) { mThickness = thiccc; }; + + //Functions + void update(); + void draw(); +}; \ No newline at end of file diff --git a/RoboCat/Src/Colour.cpp b/RoboCat/Src/Colour.cpp new file mode 100644 index 00000000..f4877e0f --- /dev/null +++ b/RoboCat/Src/Colour.cpp @@ -0,0 +1,41 @@ +/* +Allegro Wrapper Functions +Written by Adel Talhouk in FA21 +Colour.cpp +*/ + +#include "RoboCatPCH.h" +#include "Colour.h" + +//Base constructor +Colour::Colour() +{ + mR = 255; + mG = 255; + mB = 255; + mA = 255; +} + +//Constructor - without alpha +Colour::Colour(unsigned __int8 r, unsigned __int8 g, unsigned __int8 b) +{ + mR = r; + mG = g; + mB = b; + mA = 255; +} + +//Constructor - with alpha +Colour::Colour(unsigned __int8 r, unsigned __int8 g, unsigned __int8 b, unsigned __int8 a) +{ + mR = r; + mG = g; + mB = b; + mA = a; +} + +//Destructor +Colour::~Colour() +{ + +} \ No newline at end of file diff --git a/RoboCat/Src/GameObject.cpp b/RoboCat/Src/GameObject.cpp new file mode 100644 index 00000000..e51952ad --- /dev/null +++ b/RoboCat/Src/GameObject.cpp @@ -0,0 +1,28 @@ +#include "GameObject.h" +#include "RoboCatPCH.h" + +GameObject::GameObject(GameObjectType gameObjectType, const int networkID, GraphicsLibrary* graphicsLibrary) + : mGameObjectType(gameObjectType), mNetworkID(networkID) +{ + //Graphics library + pGraphicsLibrary = graphicsLibrary; + + //Position + mPosition.first = 0.0; + mPosition.second = 0.0; +} + +GameObject::GameObject(GameObjectType gameObjectType, const int networkID, GraphicsLibrary* graphicsLibrary, pair position, const std::string spriteIdentifier) + : mGameObjectType(gameObjectType), mNetworkID(networkID), mSPRITE_IDENTIFIER(spriteIdentifier) +{ + //Graphics library + pGraphicsLibrary = graphicsLibrary; + + //Position + mPosition = position; +} +GameObject::~GameObject() +{ + //Graphics library gets cleaned up in main.cpp, just de-reference it here + pGraphicsLibrary = nullptr; +} \ No newline at end of file diff --git a/RoboCat/Src/GraphicsLibrary.cpp b/RoboCat/Src/GraphicsLibrary.cpp new file mode 100644 index 00000000..36d636de --- /dev/null +++ b/RoboCat/Src/GraphicsLibrary.cpp @@ -0,0 +1,173 @@ +/* +Allegro Wrapper Functions +Written by Adel Talhouk in FA21 +GraphicsLibrary.cpp +*/ + +#include "GraphicsLibrary.h" +#include "RoboCatPCH.h" + +#include +#include + +//Constructor +GraphicsLibrary::GraphicsLibrary(float screenSizeX, float screenSizeY) +{ + //Setup data - screen size + mScreenSizeX = screenSizeX; + mScreenSizeY = screenSizeY; + + //Allegro display + mpDisplay = nullptr; +} + +//Destructor +GraphicsLibrary::~GraphicsLibrary() +{ + //Delete bitmaps + std::vector>::iterator iterator; + for (iterator = mBitmapPointersVector.begin(); iterator != mBitmapPointersVector.end(); ++iterator) + { + al_destroy_bitmap(iterator->second); + } + mBitmapPointersVector.clear(); + + //Clean up font + al_destroy_font(mpFont); + mpFont = nullptr; + + //Clean up display + al_destroy_display(mpDisplay); + mpDisplay = nullptr; +} + +bool GraphicsLibrary::init() +{ + //Init allegro + if (!al_init()) + { + std::cout << "error initting Allegro\n"; + system("pause"); + return false; + } + + //Init image addon + if (!al_init_image_addon()) + { + std::cout << "error initting image add-on\n"; + system("pause"); + return false; + } + + //Setup display + mpDisplay = al_create_display(mScreenSizeX, mScreenSizeY); + + if (mpDisplay == nullptr) + { + return false; + } + + return true; +} + +bool GraphicsLibrary::initText(std::string fontFilePath, int fontSize, Colour textColour) +{ + //Init font add on + if (!al_init_font_addon()) + { + std::cout << "error initting font add-on\n"; + system("pause"); + return false; + } + + //Init ttf add on + if (!al_init_ttf_addon()) + { + std::cout << "error initting ttf add-on\n"; + system("pause"); + return false; + } + + //Init primitives + if (!al_init_primitives_addon()) + { + std::cout << "error initting primitives add-on\n"; + system("pause"); + return false; + } + + //Init font + mpFont = al_load_ttf_font(fontFilePath.c_str(), fontSize, 0); + mTextColour = al_map_rgba(textColour.getR(), textColour.getG(), textColour.getB(), textColour.getA()); + + return true; +} + +void GraphicsLibrary::render() +{ + //Flip display buffers + al_flip_display(); +} + +void GraphicsLibrary::loadImage(std::string imageFilePath, std::string imageIdentifier) +{ + //Add the name of the image and the loaded bitmap to the vector of pairs + mBitmapPointersVector.push_back(std::make_pair(imageIdentifier, al_load_bitmap(imageFilePath.c_str()))); +} + +void GraphicsLibrary::drawText(float posX, float posY, std::string text, TextAlignment alignment) +{ + al_draw_text(mpFont, mTextColour, posX, posY, alignment, text.c_str()); +} + +void GraphicsLibrary::drawRectangle(float topLeftX, float topLeftY, float bottomRightX, float bottomRightY, Colour colour, float thickness) +{ + //Set colour + ALLEGRO_COLOR col = al_map_rgba(colour.getR(), colour.getG(), colour.getB(), colour.getA()); + al_draw_rectangle(topLeftX, topLeftY, bottomRightX, bottomRightY, col, thickness); +} + +void GraphicsLibrary::drawImage(std::string imageIdentifier, float posX, float posY) +{ + //Find the image and draw if it exists + std::vector>::iterator iterator; + + for (iterator = mBitmapPointersVector.begin(); iterator != mBitmapPointersVector.end(); ++iterator) + { + if (iterator->first == imageIdentifier) + { + al_draw_bitmap(iterator->second, posX, posY, 0); + } + } +} + +void GraphicsLibrary::drawScaledImage(std::string imageIdentifier, float posX, float posY, float scaleX, float scaleY) +{ + //Find the image and draw if it exists + std::vector>::iterator iterator; + + for (iterator = mBitmapPointersVector.begin(); iterator != mBitmapPointersVector.end(); ++iterator) + { + if (iterator->first == imageIdentifier) + { + al_draw_scaled_bitmap(iterator->second, 0, 0, al_get_bitmap_width(iterator->second), al_get_bitmap_height(iterator->second), posX, posY, al_get_bitmap_width(iterator->second) * scaleX, al_get_bitmap_height(iterator->second) * scaleY, 0); + } + } +} + +void GraphicsLibrary::drawTintedImage(std::string imageIdentifier, float posX, float posY, Colour col) +{ + //Find the image and draw if it exists + std::vector>::iterator iterator; + + //Set colour + ALLEGRO_COLOR colour = al_map_rgba(col.getR(), col.getG(), col.getB(), col.getA()); + + for (iterator = mBitmapPointersVector.begin(); iterator != mBitmapPointersVector.end(); ++iterator) + { + if (iterator->first == imageIdentifier) + { + al_draw_tinted_bitmap(iterator->second, colour, posX, posY, 0); + } + } +} \ No newline at end of file diff --git a/RoboCat/Src/InputSystem.cpp b/RoboCat/Src/InputSystem.cpp new file mode 100644 index 00000000..80a65846 --- /dev/null +++ b/RoboCat/Src/InputSystem.cpp @@ -0,0 +1,126 @@ +/* +Allegro Wrapper Functions +Written by Adel Talhouk in FA21 +InputSystem.cpp +*/ + +#include "InputSystem.h" +#include "RoboCatPCH.h" + +#include + +//Constructor +InputSystem::InputSystem() +{ + //Create an event queue + mpMouseEventQueue = al_create_event_queue(); + mpKeyboardEventQueue = al_create_event_queue(); +} + +//Destructor +InputSystem::~InputSystem() +{ + //Cleanup event queues + al_destroy_event_queue(mpMouseEventQueue); + mpMouseEventQueue = nullptr; + al_destroy_event_queue(mpKeyboardEventQueue); + mpKeyboardEventQueue = nullptr; +} + +float InputSystem::getMouseX() +{ + //Update mouse state + ALLEGRO_MOUSE_STATE mouseState; + al_get_mouse_state(&mouseState); + + return mouseState.x; +} + +float InputSystem::getMouseY() +{ + //Update mouse state + ALLEGRO_MOUSE_STATE mouseState; + al_get_mouse_state(&mouseState); + + return mouseState.y; +} + +std::pair InputSystem::getMousePosition() +{ + //Update mouse state + ALLEGRO_MOUSE_STATE mouseState; + al_get_mouse_state(&mouseState); + + return std::make_pair(mouseState.x, mouseState.y); +} + +//Init +bool InputSystem::init(GraphicsLibrary* pGraphicsLib) +{ + //Init keyboard + if (!al_install_keyboard()) + { + std::cout << "error installing Allegro keyboard plugin\n"; + system("pause"); + return false; + } + + //Init mouse + if (!al_install_mouse()) + { + std::cout << "error installing Allegro mouse plugin\n"; + system("pause"); + return false; + } + + //Register screen event source + al_register_event_source(mpMouseEventQueue, al_get_display_event_source(pGraphicsLib->mpDisplay)); + al_register_event_source(mpKeyboardEventQueue, al_get_display_event_source(pGraphicsLib->mpDisplay)); + + //Register mouse event source + al_register_event_source(mpMouseEventQueue, al_get_mouse_event_source()); + + //Register keyboard event source + al_register_event_source(mpKeyboardEventQueue, al_get_keyboard_event_source()); + + return true; +} + +MouseButton InputSystem::getMouseInput(InputMode inputMode) +{ + //If there is an event + al_get_next_event(mpMouseEventQueue, &mMouseEvent); + + if (mMouseEvent.type == inputMode) + { + //Update mouse state + ALLEGRO_MOUSE_STATE mouseState; + al_get_mouse_state(&mouseState); + + //Check the button pressed + if (mouseState.buttons & 1) //Left mouse held + { + return MouseButton::LeftMouse; + } + else if (mouseState.buttons & 2) //Right mouse held + { + return MouseButton::RightMouse; + } + else if (mouseState.buttons & 4) //Middle mouse held + { + return MouseButton::MiddleMouse; + } + } +} + +KeyCode InputSystem::getKeyboardInput(InputMode inputMode) +{ + //If there is an event + al_get_next_event(mpKeyboardEventQueue, &mKeyboardEvent); + + if (mKeyboardEvent.type == inputMode) + { + return (KeyCode)mKeyboardEvent.keyboard.keycode; + } + +} \ No newline at end of file diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index 3b0ac9de..78d97f61 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -1,5 +1,323 @@ +//#include +#include + +#include "Networker.h" + +#include "InputSystem.h" +#include "GraphicsLibrary.h" +#include "GameObject.h" +#include "Rock.h" +#include "Wall.h" +#include "PlayerController.h" + +//-------------------------Graphics Data------------------------- +GraphicsLibrary* pGraphics; +float screenSizeX = 1000.0; +float screenSizeY = 600.0; + +//-------------------------Input Data------------------------- +InputSystem* pInput; +bool bCanSpawnGameObject = false; + +//-------------------------Assets------------------------- +const std::string ASSET_PATH = "Assets\\"; +const std::string BACKGROUND_IMAGE_FILE = "Background_Image.jpg"; +const std::string ROCK_IMAGE_FILE = "Rock_Image.png"; +const std::string WALL_IMAGE_FILE = "Square_Image.png"; +const std::string PLAYER_IMAGE_FILE = "Player_Image.png"; +const std::string ARIAL_FONT_FILE = "ARIBL0.ttf"; +const int FONT_SIZE = 32; + +//-------------------------Colours------------------------- +Colour white(255, 255, 255, 255); +Colour wallColour; + +//-------------------------Asset Identifiers------------------------- +const std::string BACKGROUND_IMAGE_SPRITE_IDENTIFIER = "background_image"; +const std::string ROCK_SPRITE_IDENTIFIER = "rock_image"; +const std::string WALL_SPRITE_IDENTIFIER = "wall_image"; +const std::string PLAYER_SPRITE_IDENTIFIER = "player_image"; + +//-------------------------Game Data------------------------- +bool bGameIsRunning = true; +std::map gameObjectMap; + +float FPS = 60; +ALLEGRO_TIMER* timer = nullptr; +ALLEGRO_EVENT_QUEUE* eventQueue = nullptr; + +float wallSizeX = 150; +float wallSizeY = 15; +float wallBorderThickness = 5.0; + +//-------------------------GameObject Data------------------------- +GameObjectType currentGameObjectType; +std::string currentGameObjectTypeString; + +//-------------------------Player Data------------------------- +PlayerController* pPlayerController; + +std::pair startingPlayerPos; +const std::pair STARTING_PLAYER_POSITION_SERVER = std::make_pair(300.0, 300.0); +const std::pair STARTING_PLAYER_POSITION_CLIENT = std::make_pair(900.0, 300.0); +float playerMoveSpeed = 0.5; + +//-------------------------Network Data------------------------- +Networker* Networker::mInstance = 0; +Networker* pNetworkManager; +int networkID = 0; + +bool init() +{ + bool bSuccessfulInit = false; + + //Setup the graphical window + pGraphics = new GraphicsLibrary(screenSizeX, screenSizeY); + bSuccessfulInit = pGraphics->init(); + + //Setup text + if (bSuccessfulInit) + bSuccessfulInit = pGraphics->initText(ASSET_PATH + ARIAL_FONT_FILE, FONT_SIZE, white); + + //Add images to the graphics library + pGraphics->loadImage(ASSET_PATH + BACKGROUND_IMAGE_FILE, BACKGROUND_IMAGE_SPRITE_IDENTIFIER); + pGraphics->loadImage(ASSET_PATH + ROCK_IMAGE_FILE, ROCK_SPRITE_IDENTIFIER); + pGraphics->loadImage(ASSET_PATH + WALL_IMAGE_FILE, WALL_SPRITE_IDENTIFIER); + pGraphics->loadImage(ASSET_PATH + PLAYER_IMAGE_FILE, PLAYER_SPRITE_IDENTIFIER); + + //Setup the input system + pInput = new InputSystem(); + if (bSuccessfulInit) + bSuccessfulInit = pInput->init(pGraphics); + + //Setup timer + timer = al_create_timer(1.0 / FPS); + eventQueue = al_create_event_queue(); + al_register_event_source(eventQueue, al_get_timer_event_source(timer)); + + //Init and return if it succeeded or not + return bSuccessfulInit; +} + +void start() +{ + //Default GameObject to spawn + currentGameObjectType = GameObjectType::ROCK; + currentGameObjectTypeString = "Rock"; + + wallColour = white; + + //If server + if (networkID == 0) + { + //Spawn player + pPlayerController = new PlayerController(networkID, pGraphics, startingPlayerPos, playerMoveSpeed, PLAYER_SPRITE_IDENTIFIER); + pNetworkManager->addGameObject(pPlayerController, networkID); + networkID++; + + //Send it out + pNetworkManager->sendGameObjectState(pPlayerController->getNetworkID(), PacketType::PACKET_CREATE); + + //Get client player + pNetworkManager->receiveGameObjectState(); + } + //If client + else if (networkID == 1) + { + //Get server player + pNetworkManager->receiveGameObjectState(); + + //Spawn player + pPlayerController = new PlayerController(networkID, pGraphics, startingPlayerPos, playerMoveSpeed, PLAYER_SPRITE_IDENTIFIER); + pNetworkManager->addGameObject(pPlayerController, networkID); + networkID++; + + //Send it out + pNetworkManager->sendGameObjectState(pPlayerController->getNetworkID(), PacketType::PACKET_CREATE); + } + + al_start_timer(timer); +} + +void update() +{ + //Get mouse down input + { + MouseButton mouseDownInput = pInput->getMouseInput(InputMode::MouseDown); + + //Handle input + switch (mouseDownInput) + { + case MouseButton::LeftMouse: + { + if (!bCanSpawnGameObject) + { + //Spawn current GameObject type + GameObject* gameObjectToSpawn; + + switch (currentGameObjectType) + { + case GameObjectType::ROCK: + { + pair mousePos = std::make_pair(pInput->getMouseX(), pInput->getMouseY()); + gameObjectToSpawn = dynamic_cast(new Rock(networkID, pGraphics, mousePos, ROCK_SPRITE_IDENTIFIER)); + pNetworkManager->addGameObject(gameObjectToSpawn, networkID); + pNetworkManager->sendGameObjectState(networkID, PacketType::PACKET_CREATE); + networkID++; + + break; + } + case GameObjectType::WALL: + { + pair mousePos = std::make_pair(pInput->getMouseX(), pInput->getMouseY()); + gameObjectToSpawn = dynamic_cast(new Wall(networkID, pGraphics, mousePos, wallSizeX, wallSizeY, wallColour, wallBorderThickness)); + pNetworkManager->addGameObject(gameObjectToSpawn, networkID); + pNetworkManager->sendGameObjectState(networkID, PacketType::PACKET_CREATE); + networkID++; + + break; + } + + default: + { + std::cout << "INVALID GAMEOBJECT TYPE! CANNOT CREATE!\n"; + + break; + } + } + + gameObjectToSpawn = nullptr; + bCanSpawnGameObject = true; + } + } + + default: + break; + } + } + + //Get mouse up input + { + MouseButton mouseUpInput = pInput->getMouseInput(InputMode::MouseUp); + { + //Prevent spawning multiple gameobjects with mouse down - allow player to spawn after mouse is released again + if (mouseUpInput == MouseButton::LeftMouse) + bCanSpawnGameObject = false; + } + } + + //Get keyboard input + { + KeyCode keyCode = pInput->getKeyboardInput(InputMode::KeyPressed); + + switch (keyCode) + { + case KeyCode::R: + { + break; + } + + case KeyCode::Tab: + { + //Cycle throught GameObject types + currentGameObjectType = static_cast((currentGameObjectType + 1) % GameObjectType::ENUM_SIZE); + + //DO NOT SPAWN A PLAYER + if (currentGameObjectType == GameObjectType::PLAYER) + currentGameObjectType = static_cast((currentGameObjectType + 1) % GameObjectType::ENUM_SIZE); + + switch (currentGameObjectType) + { + case GameObjectType::INVALID: + currentGameObjectTypeString = "INVALID"; + break; + + case GameObjectType::ROCK: + currentGameObjectTypeString = "Rock"; + break; + + case GameObjectType::WALL: + currentGameObjectTypeString = "Wall"; + break; + + default: + currentGameObjectTypeString = "INVALID"; + break; + } + + break; + } + + case KeyCode::Esc: + { + //Quit game + bGameIsRunning = false; + return; + break; + } + case KeyCode::BACK: + { + //Players have IDs 0 and 1, DO NOT TOUCH THEM + if (networkID > 1) + { + pNetworkManager->sendGameObjectState(networkID - 1, PacketType::PACKET_DELETE); + networkID--; + } + + break; + } + + default: + break; + } + } + + pNetworkManager->updateGameObjects(); +} + +void draw() +{ + //Background image + pGraphics->drawImage(BACKGROUND_IMAGE_SPRITE_IDENTIFIER, 0, 0); + + //Draw GameObjects + pNetworkManager->renderGameObjects(); + + //Text indicator of current GameObject Type + pGraphics->drawText(100, 50, "Current Object to Spawn: " + currentGameObjectTypeString, TextAlignment::ALIGN_LEFT); + + //Text indicators for instructions + { + pGraphics->drawText(pGraphics->getScreenSizeX() - 700, 50, "WASD - Move Player.", TextAlignment::ALIGN_LEFT); + pGraphics->drawText(pGraphics->getScreenSizeX() - 700, 100, "TAB - Change GameObject to Spawn.", TextAlignment::ALIGN_LEFT); + pGraphics->drawText(pGraphics->getScreenSizeX() - 700, 150, "Left Mouse - Spawn GameObject.", TextAlignment::ALIGN_LEFT); + pGraphics->drawText(pGraphics->getScreenSizeX() - 700, 200, "Backspace - Delete last GameObject.", TextAlignment::ALIGN_LEFT); + pGraphics->drawText(pGraphics->getScreenSizeX() - 700, 250, "ESC - Quit.", TextAlignment::ALIGN_LEFT); + } + + //Render it all + pGraphics->render(); +} + +void cleanup() +{ + //cleanup timer + al_destroy_timer(timer); + al_destroy_event_queue(eventQueue); + + //Cleanup network manager - cleans up GameObjects + pNetworkManager->cleanup(); + pNetworkManager = nullptr; + + //Cleanup input system + delete pInput; + pInput = nullptr; + + //Cleanup the graphics system + delete pGraphics; + pGraphics = nullptr; +} -#include "RoboCatPCH.h" #if _WIN32 @@ -17,9 +335,102 @@ int main(int argc, const char** argv) __argv = argv; #endif - SocketUtil::StaticInit(); + if (init()) + { + //Setup network manager + pNetworkManager = pNetworkManager->GetInstance(); + pNetworkManager->init(pGraphics, ROCK_SPRITE_IDENTIFIER, PLAYER_SPRITE_IDENTIFIER, playerMoveSpeed, wallColour); + + //Prompt for isServer or not + std::string input; + std::cout << "Are you the server? Type 'y' for yes, anything else for no.\n"; + std::cin >> input; + bool bIsServer = false; + bool bHasConnectd = false; + + if (input == "y") + { + bIsServer = true; + } + + //Setup server and client + if (bIsServer) + { + //-------------------------Server code------------------------- + + //Prompt for port number + std::string portNumber; + std::cout << "Enter port number: \n"; + std::cin >> portNumber; + + bHasConnectd = pNetworkManager->initServer(portNumber); + if (bHasConnectd) + std::cout << "main.cpp --> server initted.\n"; + + //Server PlayerController is networkID 0 + networkID = 0; + startingPlayerPos = STARTING_PLAYER_POSITION_SERVER; + } + else + { + //-------------------------Client code------------------------- + + //Prompt for server IP address + std::string serverIP; + std::cout << "Enter server IP address: \n"; + std::cin >> serverIP; + + //Prompt for port number + std::string portNumber; + std::cout << "Enter port number: \n"; + std::cin >> portNumber; - SocketUtil::CleanUp(); + bHasConnectd = pNetworkManager->connect(serverIP, portNumber); + if (bHasConnectd) + std::cout << "main.cpp --> client connected.\n"; + + //Client PlayerController is networkID 1 + networkID = 1; + startingPlayerPos = STARTING_PLAYER_POSITION_CLIENT; + } + + //If the peer has connected + if (bHasConnectd) + { + //Setup + start(); + + //Since 0 and 1 are used for both player controllers, start everything else at 2 + networkID = 2; + + //Loop the game + while (bGameIsRunning) + { + ALLEGRO_EVENT ev; + al_get_next_event(eventQueue, &ev); + + if (ev.type == ALLEGRO_EVENT_TIMER) + { + //Update call + update(); + + //Network updates - send player data + pNetworkManager->sendGameObjectState(pPlayerController->getNetworkID(), PacketType::PACKET_UPDATE); + + //Network update - receive packets + pNetworkManager->receiveGameObjectState(); + + //Draw call + draw(); + } + } + + //Cleanup when done + cleanup(); + } + } + + std::cin.get(); return 0; -} +} \ No newline at end of file diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp new file mode 100644 index 00000000..1aad896c --- /dev/null +++ b/RoboCat/Src/Networker.cpp @@ -0,0 +1,387 @@ +#include "Networker.h" +#include + +//Constructor +Networker::Networker() +{ + +} + +Networker::~Networker() +{ + cleanup(); +} + +void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIdentifier, std::string playerSpriteIdentifier, float playerMoveSpeed, Colour wallColour) +{ + mpTCPSocket = new TCPSocketPtr; + mGameObjectsVec = std::vector>(); + + //Data for GameObject replication + mpGraphicsLibrary = graphicsLibrary; + mRockSpriteIdentifier = rockSpriteIdentifier; + mPlayerSpriteIdentifier = playerSpriteIdentifier; + mPlayerMoveSpeed = playerMoveSpeed; + mWallColour = wallColour; +} + +void Networker::cleanup() +{ + //delete mInstance; + //mInstance = nullptr; + + //Cleanup map + std::vector>::iterator it; + for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + { + delete it->second; + it->second = nullptr; + } + mGameObjectsVec.clear(); + + (*mpTCPSocket)->CleanupSocket(); + delete mpTCPSocket; + mpTCPSocket = nullptr; + SocketUtil::CleanUp(); +} + +bool Networker::initServer(std::string port) +{ + SocketUtil::StaticInit(); + + //Create Socket + TCPSocketPtr sock = SocketUtil::CreateTCPSocket(SocketAddressFamily::INET); + if (sock == nullptr) + { + SocketUtil::ReportError("Creating Listenting Socket"); + ExitProcess(1); + } + std::cout << "Listening Socket Succesfully Created!\n"; + + //Create and Bind Address + SocketAddressPtr listenAddress = SocketAddressFactory::CreateIPv4FromString("0.0.0.0:" + port); + if (listenAddress == nullptr) + { + SocketUtil::ReportError("Creating Listening Address"); + ExitProcess(1); + } + + if (sock->Bind(*listenAddress) != NO_ERROR) + { + SocketUtil::ReportError("Binding listening socket"); + ExitProcess(1); + } + std::cout << "Listening Socket Succesfully Binded!\n"; + + if (sock->Listen() != NO_ERROR) + { + SocketUtil::ReportError("Listening on socket"); + ExitProcess(1); + } + std::cout << "Listening Socket Listening\n"; + + //Accept Connection + std::cout << "Waiting for connection...\n"; + + sock->SetNonBlockingMode(false); + SocketAddress incomingAddress; + TCPSocketPtr connSocket = sock->Accept(incomingAddress); + + while (connSocket == nullptr) + connSocket = sock->Accept(incomingAddress); + + + *mpTCPSocket = connSocket; + + std::cout << "Accepted connection from address: " << incomingAddress.ToString() << std::endl; + + if (mpTCPSocket != nullptr) + return true; + return false; +} + +bool Networker::connect(std::string serverIpAddress, std::string port) +{ + SocketUtil::StaticInit(); + + //Create Socket + TCPSocketPtr sock = SocketUtil::CreateTCPSocket(SocketAddressFamily::INET); + + if (sock == nullptr) + { + SocketUtil::ReportError("Creating Client Socket"); + ExitProcess(1); + return false; + } + + string address = "0.0.0.0:0"; + SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString(address.c_str()); + if (clientAddress == nullptr) + { + SocketUtil::ReportError("Creating Client Address"); + ExitProcess(1); + return false; + } + + if (sock->Bind(*clientAddress) != NO_ERROR) + { + SocketUtil::ReportError("Binding Client Socket"); + ExitProcess(1); + } + LOG("%s", "Client Socket Succesfully Binded!"); + + SocketAddressPtr srvAddress = SocketAddressFactory::CreateIPv4FromString(serverIpAddress + ":" + port); + if (srvAddress == nullptr) + { + SocketUtil::ReportError("Creating Server Address"); + ExitProcess(1); + } + + if (sock->Connect(*srvAddress) != NO_ERROR) + { + SocketUtil::ReportError("Connecting To Server"); + ExitProcess(1); + } + LOG("%s", "Succesfully Connect to the Server!"); + + *mpTCPSocket = sock; + + if (mpTCPSocket != nullptr) + return true; + return false; +} + + +void Networker::receiveGameObjectState() +{ + char buffer[1024]; + int32_t byteRecieve = (*mpTCPSocket)->Receive(buffer, 1024); + if (byteRecieve > 0) + { + InputMemoryBitStream IMBStream = InputMemoryBitStream(buffer, 1024); + + //Start reading + PacketType packetHeader; + IMBStream.Read(packetHeader); + int networkID; + IMBStream.Read(networkID); + GameObjectType receiveType; + IMBStream.Read(receiveType); + + //Logic depends on packer header type + switch (packetHeader) + { + case PacketType::PACKET_CREATE: + { + float posX; + float posY; + + IMBStream.Read(posX); + IMBStream.Read(posY); + + switch (receiveType) + { + case GameObjectType::ROCK: + { + Rock* newRock = new Rock(networkID, mpGraphicsLibrary, pair(posX, posY), mRockSpriteIdentifier); + mGameObjectsVec.push_back(pair(networkID, newRock)); + newRock = nullptr; + + break; + } + + case GameObjectType::PLAYER: + { + PlayerController* newPlayerController = new PlayerController(networkID, mpGraphicsLibrary, pair(posX, posY), mPlayerMoveSpeed, mPlayerSpriteIdentifier); + mGameObjectsVec.push_back(pair(networkID, newPlayerController)); + newPlayerController = nullptr; + + break; + + } + + case GameObjectType::WALL: + { + float sizeX; + float sizeY; + float thickness; + + IMBStream.Read(sizeX); + IMBStream.Read(sizeY); + IMBStream.Read(thickness); + + Wall* newWall = new Wall(networkID, mpGraphicsLibrary, pair(posX, posY), sizeX, sizeY, mWallColour, thickness); + mGameObjectsVec.push_back(pair(networkID, newWall)); + newWall = nullptr; + + break; + } + } + + break; + } + + case PacketType::PACKET_UPDATE: + + if (mGameObjectsVec[networkID].second != nullptr) + { + float x; + float y; + + switch (receiveType) + { + case GameObjectType::ROCK: + case GameObjectType::PLAYER: + + IMBStream.Read(x); + IMBStream.Read(y); + mGameObjectsVec[networkID].second->setPos(std::make_pair(x, y)); + break; + + case GameObjectType::WALL: + { + float sizeX; + float sizeY; + float thiccness; + + Wall* wall = (Wall*)mGameObjectsVec[networkID].second; + IMBStream.Read(x); + IMBStream.Read(y); + + wall->setPos(std::make_pair(x, y)); + + IMBStream.Read(sizeX); + IMBStream.Read(sizeY); + IMBStream.Read(thiccness); + + wall->setWallSizeX(sizeX); + wall->setWallSizeY(sizeY); + wall->setWallThickness(thiccness); + + break; + } + } + } + else + { + //Report error + std::cout << "ERROR: CANNOT UPDATE GAMEOBJECT ID " << networkID << " BECAUSE IT IS NOT IN THE NETWORK MANAGER MAP.\n"; + } + + break; + + case PacketType::PACKET_DELETE: + { + //Delete element in map + if (mGameObjectsVec.size() > 0) + { + std::vector>::iterator it; + for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + { + //DO NOT DELETE A PLAYER + if (it->first == networkID && it->second->getGameObjectType() != GameObjectType::PLAYER) + { + mGameObjectsVec.erase(it); + + break; + } + } + } + + break; + } + + default: + return; + } + } + else if (byteRecieve == -10053 || byteRecieve == -10054) + { + LOG("%s", "Disconnected From Server"); + exit(0); + } +} + +void Networker::sendGameObjectState(int ID, PacketType packetHeader) +{ + OutputMemoryBitStream OMBStream; + OMBStream.Write(packetHeader); + OMBStream.Write(mGameObjectsVec[ID].second->getNetworkID()); + OMBStream.Write(mGameObjectsVec[ID].second->getGameObjectType()); + + //Logic depends on packer header type + switch (packetHeader) + { + case PacketType::PACKET_CREATE: + case PacketType::PACKET_UPDATE: + + switch (mGameObjectsVec[ID].second->getGameObjectType()) + { + case GameObjectType::ROCK: + case GameObjectType::PLAYER: + OMBStream.Write(mGameObjectsVec[ID].second->getPosition().first); + OMBStream.Write(mGameObjectsVec[ID].second->getPosition().second); + break; + + case GameObjectType::WALL: + Wall* wall = (Wall*)mGameObjectsVec[ID].second; + OMBStream.Write(wall->getPosition().first); + OMBStream.Write(wall->getPosition().second); + OMBStream.Write(wall->getWallSizeX()); + OMBStream.Write(wall->getWallSizeY()); + OMBStream.Write(wall->getWallThickness()); + break; + } + + break; + + case PacketType::PACKET_DELETE: + { + //Delete it on the sender's end + if (mGameObjectsVec.size() > 0) + { + std::vector>::iterator it; + for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + { + //DO NOT DELETE A PLAYER + if (it->first == ID && it->second->getGameObjectType() != GameObjectType::PLAYER) + { + mGameObjectsVec.erase(it); + + break; + } + } + } + } + + break; + + default: + return; + } + + (*mpTCPSocket)->Send(OMBStream.GetBufferPtr(), OMBStream.GetBitLength()); +} + +void Networker::addGameObject(GameObject* objectToAdd, int networkID) +{ + mGameObjectsVec.push_back(pair(networkID, objectToAdd)); +} + +void Networker::updateGameObjects() +{ + std::vector>::iterator it; + for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + { + it->second->update(); + } +} + +void Networker::renderGameObjects() +{ + std::vector>::iterator it; + for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + { + it->second->draw(); + } +} \ No newline at end of file diff --git a/RoboCat/Src/PlayerController.cpp b/RoboCat/Src/PlayerController.cpp new file mode 100644 index 00000000..ab330883 --- /dev/null +++ b/RoboCat/Src/PlayerController.cpp @@ -0,0 +1,138 @@ +#include "PlayerController.h" +#include "RoboCatPCH.h" + +PlayerController::PlayerController(const int networkID, GraphicsLibrary* graphicsLibrary) + : GameObject(GameObjectType::PLAYER, networkID, graphicsLibrary) +{ + //Key down + pInputKeyDown = new InputSystem(); + pInputKeyDown->init(graphicsLibrary); + + //Key up + pInputKeyUp = new InputSystem(); + pInputKeyUp->init(graphicsLibrary); + + mMoveSpeed = 0.0; +} + +PlayerController::PlayerController(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, float moveSpeed, const std::string spriteIdentifier) + : GameObject(GameObjectType::PLAYER, networkID, graphicsLibrary, position, spriteIdentifier) +{ + //Key down + pInputKeyDown = new InputSystem(); + pInputKeyDown->init(graphicsLibrary); + + //Key up + pInputKeyUp = new InputSystem(); + pInputKeyUp->init(graphicsLibrary); + + mMoveSpeed = moveSpeed; +} + +PlayerController::~PlayerController() +{ + //Key down + delete pInputKeyDown; + pInputKeyDown = nullptr; + + //Key up + delete pInputKeyUp; + pInputKeyUp = nullptr; +} + +void PlayerController::update() +{ + //Get keyboard input - KeyPressed + { + KeyCode keyCode = pInputKeyDown->getKeyboardInput(InputMode::KeyPressed); + + switch (keyCode) + { + case KeyCode::W: + { + //Move up + bShouldMoveUp = true; + break; + } + + case KeyCode::A: + { + //Move left + bShouldMoveLeft = true; + break; + } + + case KeyCode::S: + { + //Move down + bShouldMoveDown = true; + break; + } + + case KeyCode::D: + { + //Move right + bShouldMoveRight = true; + break; + } + + default: + break; + } + } + + //Get keyboard input - KeyReleased + { + KeyCode keyCode = pInputKeyUp->getKeyboardInput(InputMode::KeyReleased); + + switch (keyCode) + { + case KeyCode::W: + { + //Stop moving up + bShouldMoveUp = false; + break; + } + + case KeyCode::A: + { + //Stop moving left + bShouldMoveLeft = false; + break; + } + + case KeyCode::S: + { + //Stop moving down + bShouldMoveDown = false; + break; + } + + case KeyCode::D: + { + //Stop moving right + bShouldMoveRight = false; + break; + } + + default: + break; + } + } + + //Apply move based on holding keys + if (bShouldMoveUp) + mPosition.second -= mMoveSpeed; + if (bShouldMoveDown) + mPosition.second += mMoveSpeed; + if (bShouldMoveLeft) + mPosition.first -= mMoveSpeed; + if (bShouldMoveRight) + mPosition.first += mMoveSpeed; +} + +void PlayerController::draw() +{ + //Draw sprite at mPosition + pGraphicsLibrary->drawScaledImage(mSPRITE_IDENTIFIER, mPosition.first, mPosition.second, 0.25, 0.25); +} \ No newline at end of file diff --git a/RoboCat/Src/Rock.cpp b/RoboCat/Src/Rock.cpp new file mode 100644 index 00000000..9b1c062e --- /dev/null +++ b/RoboCat/Src/Rock.cpp @@ -0,0 +1,30 @@ +#include "Rock.h" +#include "RoboCatPCH.h" + +Rock::Rock(const int networkID, GraphicsLibrary* graphicsLibrary) + : GameObject(GameObjectType::ROCK, networkID, graphicsLibrary) +{ + +} + +Rock::Rock(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, const std::string spriteIdentifier) + : GameObject(GameObjectType::ROCK, networkID, graphicsLibrary, position, spriteIdentifier) +{ + +} + +Rock::~Rock() +{ + +} + +void Rock::update() +{ + +} + +void Rock::draw() +{ + //Draw sprite at mPosition + pGraphicsLibrary->drawScaledImage(mSPRITE_IDENTIFIER, mPosition.first, mPosition.second, 0.25, 0.25); +} \ No newline at end of file diff --git a/RoboCat/Src/TCPSocket.cpp b/RoboCat/Src/TCPSocket.cpp index 494f776c..e4e9a20c 100644 --- a/RoboCat/Src/TCPSocket.cpp +++ b/RoboCat/Src/TCPSocket.cpp @@ -95,11 +95,16 @@ int TCPSocket::SetNonBlockingMode(bool inShouldBeNonBlocking) } } -TCPSocket::~TCPSocket() +void TCPSocket::CleanupSocket() { #if _WIN32 - closesocket( mSocket ); + closesocket(mSocket); #else - close( mSocket ); + close(mSocket); #endif } + +TCPSocket::~TCPSocket() +{ + CleanupSocket(); +} \ No newline at end of file diff --git a/RoboCat/Src/Wall.cpp b/RoboCat/Src/Wall.cpp new file mode 100644 index 00000000..03f446aa --- /dev/null +++ b/RoboCat/Src/Wall.cpp @@ -0,0 +1,38 @@ +#include "Wall.h" +#include "RoboCatPCH.h" + +Wall::Wall(const int networkID, GraphicsLibrary* graphicsLibrary) + : GameObject(GameObjectType::WALL, networkID, graphicsLibrary) +{ + mSizeX = 1.0; + mSizeY = 2.0; + + mColour = Colour(255, 255, 255, 255); + mThickness = 1.0; +} + +Wall::Wall(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, float sizeX, float sizeY, Colour colour, float thickness) + : GameObject(GameObjectType::WALL, networkID, graphicsLibrary, position) +{ + mSizeX = sizeX; + mSizeY = sizeY; + + mColour = colour; + mThickness = thickness; +} + +Wall::~Wall() +{ + +} + +void Wall::update() +{ + +} + +void Wall::draw() +{ + //Draw scaled sprite at mPosition + pGraphicsLibrary->drawRectangle(mPosition.first, mPosition.second, mPosition.first + mSizeX, mPosition.second + mSizeY, mColour, mThickness); +} \ No newline at end of file From 0572ed1082404878ae9f09274a48a0b89d6d3281 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 1 Apr 2022 12:08:18 -0400 Subject: [PATCH 02/56] Restored assets, restored display size to 1600x900 pixels. --- RoboCat/Assets/ARIBL0.ttf | Bin 0 -> 57448 bytes RoboCat/Assets/Background_Image.jpg | Bin 0 -> 87446 bytes RoboCat/Assets/Player_Image.png | Bin 0 -> 1991 bytes RoboCat/Assets/Rock_Image.png | Bin 0 -> 4672 bytes RoboCat/Assets/Square_Image.png | Bin 0 -> 5385 bytes RoboCat/Chapter3.vcxproj | 5 +++++ RoboCat/Src/Main.cpp | 4 ++-- 7 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 RoboCat/Assets/ARIBL0.ttf create mode 100644 RoboCat/Assets/Background_Image.jpg create mode 100644 RoboCat/Assets/Player_Image.png create mode 100644 RoboCat/Assets/Rock_Image.png create mode 100644 RoboCat/Assets/Square_Image.png diff --git a/RoboCat/Assets/ARIBL0.ttf b/RoboCat/Assets/ARIBL0.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a1e68a3663c75e6fdfb134bd5e063efdebcdaec9 GIT binary patch literal 57448 zcmeEvcYIVg_OEoy^gd&I+{Yf<7~BTixZ&UdV`BpWQw*j!xQ(&F4VTb^NkRw^0;H3& zDVw%QNDOI&)Xk>%z9qdUyCKPDlWdmYnfE=KF|c{R-~N8@_s@Hu&s$yVTwPu1=;-L^ z=tv`7GtL-u04U5ed*Onb_P)j&7+d-RW86D;L0pI%H*X{pw$DP1m&RERxjzDwUy+hr1BX2g9 z*MSIYof?PdHaw?wbPuk(^wx$cjF~}?vT1dozo&h2uY<8DJR1*n^|m%2@Vxj2(x1T-R>~Ws!yxx`NI?wfX)TF4ZqP~mX z6#YQ-hcT%!`(o~JC%BimuW|nvJ3scKICoq}+^^$)jz2g4&4ikSJqbT2Iua8TmnIG* z8Iw|yN|NR#btG*~x+3YWq-T;oO!_I=o;)tupS(D^FL_7u^~v`qznJ`K@^O#TGr?2o zS?(F|?DSmkIpTReMM{~R@?q+E-cs)}@7^>u?Lhk6^heU)Ne^Z;XFN6T^l|r$JC=EF z=F8)=#{X*kI};9M&ChDd8pzt4otoW|J&=74t}C+-Wu2D642%(UPLRq6>@eE_$x$ z!=j&xt;Lze6~&8+yNk~$-d{XY{7mtO#m7qGOQw|6m-LiuE4jMl?vm$9zAl|s`h4j} zrT-`^E8A0cbJ@dXua|v!+B;JwPwANQP(^vgyo!|-n=3Aia%C-=QsLO{U!c6 z{(%21|E2yr{IB@GoVs{w_tbNyUODyP)FV@0o)$f=oFocT^eroA^U;?jW`i`LSMs~$ z)0oV-dW6L?|0z(&-55|Zn47I)Ec7|WguW0E;b2$jQ%FDZ20KGvsIQ|J{3G;@xPKb@ zYApAE?@oOs++Y56dhXgy-Od^jUKqL| z^s{uEoJc88xb3)%a;gKa>0FY*dL z0r%JWZ1x~aXAQiM)v-I+@Az)6uwCp5?&A5pi@QPH-TVrk%WHTguVg=g9%vH3D`{PH z;@aqhGo#~&qT>dmV+W%9q7UnsKQXbZH##Pvt2ZXTw`)t6)YTiix+gk*bov4IX(d~$#XI<~Ee+h2Js<#PCo;rGGshTjFh1AaUFHu!Vk zx596M-wdC>VSQWjfo=K8w~XW`-vxgV-a5j6vpqj~@7Da}J@8j8cO~zKzr4|&d@20J z%k9bg;CC<9CGT1sle`0ddqYg}Hu!Vkw=T9PZ-L(ozo+5lhW8uf)@FNh3w(a#GJEoJ z_=ZJ}fyL3mQiH5|@mWQ($l65|#xwFfd5B|IG12^~3d5ox7%Ig;Vpa za3E^{p@XbW&Hl)8SH$2@(+#~s@TsX-=J=Lvk{xryFanYp0g8YfTyxg4Z ztO?^Y$7Q6ac~et7$w`R`@o};4nCK{%(_yz+EoPI^px0?Nsv=9AW%ChtWy8T(ZB~3r zO3IR)$aCC3Kaa@iw(n9#m?J*LYcDvFD*m5S9ZdMebK)TFLBiuE@s2J`%9YsY*scKv0t?C?!t!!&*LXPQPn`fl@+dM%_gyMrnL#4MeV93d4 z2MtC9jR+xa-$7nIg^PexT~&5aVmfn9_J{)_kX}W8)rfyr69V4pz=Su>lWz`(9=qVw zR}9(0i5QZJ5FgQ0j%Y=>p3V_}^9bAJIhg&}&I=CPSW8ousmmT6Q`OoJ_qV<&x+fyDB!n(a`#GW-W1371G z_#|H5S>^8Z(BsaX+dU)K&1*RIZ3?AXvSf)nC)=~L%8P=g=VVu{ng&k0^K!C@1$>M( zZB46)mQ~F}XVogt&RqdP>ji=~k+iCV8pgk?`M*lIb7xhXx2mnVjY>iRl_UOz;>H#( zC60Kiz`f~9B2ke9c%z6nP1Bcv-9+20`3;qLTjgz@4q+uJJQ33ri9tlw*h>$QT07!z z8u7GxM%erYFEW=92$Zm$ttBMzK*MvgXVsm|HKL~5yq=vuuo2$mJ^B?~D{>ZNQk?ISUjM$nw zcp2DFLQp-wp&~xTj(UV2>&6}#qzbYHDMod`1^m@UZfFj+upz}$Il}52mc)aR4HRC8 zaQIHL1lcV?Ge_7)JPJ@{Q4obA!ITsd&|Qc9tOZR!GCZ##{On;Z@rRf{FAMF_L~kA& zdlOYpZ-&R-oXFnfMdREfY~ZL7UB-z&t1a4D)loLWqyNS0K=}QLv$8>smq_LjlHz3w z8M4sX%SU3e5YEiniDr4lJ7UWkQ7aoBi!Wc|vDu*osQDInXU$vM5Uv@KlEyq_N>n@f(q5r*vEB>PUD8_;VJ8Bkw6+jl7#}L468vFTwL# zgeA}^BWy%i1^?24GY&kx4)++8-%4~vd^6Q~tWHFK1UHrcOMik!yc4vB>4PC~Tj&$` zAmYbp`(@s>cz3GbEf1D&*iE&5LY1rPH*tGV!v|?(>kB+MRfo4!;6^J>G93**n1lv?1bVz)z-j zK^fnJmSXbYSv7eoi+D+S_@dwdo?C!B20j5eT@k;V%A@p@7x+XOg1=!oxRkN=N*`{W8>N2i?KT9bB1auyp8TrZ(Hq{wQ3i{@#Jaa!MiNlgm$=QsJLEt#W!* z^^BU@nX_h}K4)&-y!i_j)-PJzuw?17<&9^oXliCHt!;t!l^va{R(Eyx^!Bak9~c}u zbM3nI8#Zp*eAbrXt!JNe?zZ!`pTA@0t_yZwxM%Oai!NsDl1que%Pzly?Z5J>tFK|d zV(i-MuD{{eH{NvfEw}#Ww%hMGa3{NKunas~rW?*Nr5Z7`$kLSsJA}`}p_;k#=>iIGr z;A{9<{37XT=~?MrkINJ1N%Ew6GCV#{SxRbZRB9})q|~g`ru2)xRYD=)^swu=#7&sL zC2=1w#cIn`K7-fs27U(b;p>3wDd748)+gMa1dm7HD*fMZ@lYuAG1V6{=1}NMz*j>r zhb|3)525qnG5ZXeFo%ZdejxaQ`^FD;eX!yERqtK>zWm;`?+w1U`n|ljyMch(w}xxD zjj-`(g5>B~%umyB&yDyZs(twVFQHf`yMbNJ&Sl%!A$AG-o}I@o#EkWFb}hRGYXUoY zJUfS7jM~d=H@lRb&lA{doU<#y5tS>DyQ|o**(2;Bb|Y(Ht!yuAV~?=_dzd}So?yRa zkF(#gA6Pqknmx&$VmGmsAbcNtjy=PkWgYDA>>q3gTg5urYSzuVSP$FJdf6J*$NJd- z8)Ad(OtzN&$kwq9Y(3k^He-fN_?g4~AIc3=g-#iQ5{ zJeup-^Xwh=F1wlC!rsGN-NLQx6ZR?ljD60&U|+Hk_BHzkD@y2s@kI&$hFB*?l~bCkY;+8pb;@jr$fIPp4joo(PTu77JJ^ zV3~mB0zzxx?Hzzt+zC>Q6=h)YQ^eRsj9tVeikKt;(^y96ZD5}uV7`Ebp+6#~I5YrQ z5;_7{8rlO`h89a><)Qh23$UJ(#u~8dlE#*V-UVEWveMXc0dEftA$%aT7ONFjRsd*c zDS&B6nGT%K1Gb28tAHJd$q*?skkZS>F)J{SV|Kh9hg`P;mV%OTpn=|&i*SpGZv`CB ztid+{?ZKx3(?S)1(}8n5t3k@~Y%VAt&*qD8y?`wuO{;)iB4v+&oA7o5C`kddGZ)JN zC2s@T@ivFiicSurHp)RM6f+$Z=AaY`&lj*kz~v&oMTA=g>=H3O0&Ws9TLE*y|094C z1k4w3I`HQT%5w$Hx!^OUBtGW~KIbyRpUYN?^j#u-kAOsfE+fo&!2B-Y1Of8}EDl|P z7~+2(_(|b15nmo^L3pVMF9+xIfSK}AyX3LkMalz!6TulQhl4W&(~xo^+L2%@U_N;D zE+FwLA8(HU7K?Bx+9+R?mCq)JEq|ZmsCN&kx8KBML=rhNoWzuyF|nlPnn27YHpr2q`Xcr*SUn=M?74(-1`b*Kil&c1% zl(O0AaiyS^!qh`c!C``=PfAfD;b|3+Xe$M6x1wFG0@_fQGEjIKAob8P(L>814-}?e zP$qgonP~AcAy;K=1}H2O(pAQ0p@qvp13_xvGFC70QY)7U8p<&K5VlqUsdtothIiRy zQM<{ac9TWzCW~HKj+VU#tKYUz9iTmUBVZ!hs2rRjNII$jOSTPVT}BG(eoTn;V~PSR=Rpn)*=0MB$*1wA$$mP-oY0uf#e z%BKr`GF|AC>7tHRLNcp_WLAk>Rgfjj6j+0RR7#adUlmEe74g*~SGCAh&1L~}HJc;g zT%@lSGB$(FK)EwSf0+Rc6sDG~5qWDw-WpbgcB}!N=<}eH-cpG*qQn}uOoYR^8u7Lk z#@z6)3joV9EY%B>YOss$%0{&tb_fPl{f=S)G* zOhL~~aEM}R1e}SyGm)3zd=XQRF>EGk;RU3gK2!AcnPQcHmZ;rqQQtYB;Rwp6(PxfG zKL^_22yoGeIakEg2@cnZTy?NK7@H?z=79sxB22ZO7l}Ur%IBlLUjfqiI3Fc5z$L&r zA3FSPz-6JsfHX$T$1f6IaFfz65a|~n&GQJ;+XZ;bAnR82`g)9!6t-h5s7F6q1ehe? zbYQ4QOA?#`PS%4bDS*^c_3U(!W)8SjkJ=EdgGE*kdI`=KDHn(|3q|^R5nd$1i$$6S zk!uNBvmP}i*d@y95pa{huoY<*iPl*pT4#}{#S&1CnuF$PfYe?~pbwu1oPpXc5xskf z=+R5iUtUBEwbT;PQcHwRUJ48?XbC&;EJeTj60jP)S_%vlt`Tq+C|N3GW-0Kzi*T2S z?-6hlVwNEnK`M8dkh*0e{W6sDE^0+|E=MU9fMhc)7wMOa^vgy1<)R&zi(JcrpVE+q zSuQX%3aT2B{wuUujezq7+yvMHP4YG%S#d4ULIlZ*YY|pli?HHagca8Uy@lmnjBE1& z$$o1QdVqMq5GF}$!H7QuNcLN+z}zY@w+hUy0&}at+$u1)3e2qnbF0AIDsZ+6oUH<9 ztH9YRaIO^N$Vy;%o~;yP^h#kHtQ5AvN?;g*^-lIfm%!5{@N@}0JtC$@#Po=mO^7*y zUPqF@Nl5-CA={gTGVLTet2gcX*`4I->ZXc2S^zptHBfGch;cljAwPdA(<1X=!1Zr>4$RJ=E3Zsqg7*?QIK)Os6jt z<%saDo`wC*ZGrCQ{?(q|cF)3&fM={=Pi1d^UvGc&AmQ!nYW4I7R(1{y2KobSp3WZ6 zAd=QE@YFRAdNMo9ds=g;zxi?i@^d{?(c$KIuIv~bKxZ8Y^q(1M`@dZLf6URazJVST zE>wVa{12Si0fl&4nZi1hinTB;*Ks{Ja3fag%(U8b>WUE7gJQ6L7t7Ma2j@UOy^a+8Y_Y|yq3@8v-oU&I-kSmVuf%XpU)Ry*GD~H#1~_|a0y?E70Bhh z5i5r)coT2tExeVtVNJ4~ujC!Plds~du~VcQ>x#X+4=a`Z*oiX8hxnO%Emj)W^9_6> z-^4d#1#=4@=3DvM{2YER-^S15+xhu?2j9te@e8o;zfekZ?+kMM*15O%EG z!|&zy@%#B<{s2G1ALI}5hxsG?QS6ZUEq|Q@!#`5@IUfD@wfRq{9XPYf1iKAKja_rkNGG3Q~nwD)O^9eOV|Caxif5-pEzvn;jANk+;Py8SJ82_0c=c7ExLlS<*kR(Z#6iJmdl2+15 zddVOeC6i>9ERt2SNp{I0IVG1AB}Gdyl3R+E;-q*fK}u8y`q1w*fpx82&E2{+L%oB6 zww5kK&ro*@4O%OEG;O_I81S@xf&SJ&&!E1!8=(OVB}S2WptEOXS3uv_+#l%a3bYRz zM5v$om#&${qt1cVN?+H|fZE#I-QBF|Sl`zX=+OX2XK$N2(A7N9p==2B_sYFJ0eNt3 zuR7S#9|$Pzy+i#km{swK~Rutio}Zg8Jh=MJ3CuD`&);) z4MRO`U`uOne?VQ?-+X4^e&_FUq2SoG>20(3j&SJVanUP0_*h6C)0@RYO}!I zI)*zC&KU^j44je^g@?(}bcWM(hSNm&BCvO!NYf**YkI>;d&5b4PeIpvB57|p=}9g+EUo?CBg_uO~t8?(7*F zkk848GHNN+99qay(Waa(V{~jFd#JtG`(Tj8k5+z-nBi3aEBDZuHKcM zt<7C1SJU3ri-s|;972l+x_gCmT24HLdtIQbtFsUCq3vl2_wA{IcbchThRzi?X>OI& zi*Dc3h7PC+bkof=*852U&0`N?Zipw*zePwyYT7!_?1V(FA7hPv$yi9&4oN1y>sp8U zp&44&3z~(pAS{Ar@gy)w?VVD4mpO+Oc06!H4rv5;ndI3Zew^u+vd z`u^~3K{#?Cd|MEX91u6%!U$oy!7+8PF#LW{+|+uaUL7I`WiwXp&{#;bG>mkExG76f zYh?p~W@#A9262P#>s=k_f!>p=20P@yV25T&INy44Qx|sh_V-|P0Mtws{L?gxn|5lL zN808HuLe3WXVP^Kbq#j*b*(poe*;58p!$s%V8+5`G0==XX_2ad$Xz%= zPRsX_evBt>If`ICX3IHa&c66E`ii z14)UtDpIjP_^zo6rwWLhzW2wS@oTUK>jRh- zBO(s|zxu5lRwd0g@jJ5TTSYyBnRcb_C20eq#m-_fI*|eg2&CJw51RS^q#l`bub8)n zO;-;&{K6a3hC;!q^J}O1d@7I$7lq7edcBs(vaD(}dY!D`XL&rxniDP@)(WT5=@g9) z>2z8hB6M0kpdu?8HJm}wAhSl+Q)UI11|jUbR5X-Zr_-H4O6g85gwkqM9n$F$t=EAV zjie~ZE)eTwWY_Cey{b|!twhBT4wVQ132;CUM`$BXBkMpba4NVoxB$hos>LOXD2fIm zWY_EUI#3~N)W{2D#HGp#E&mt{I`BwQwOWH7JOVDni=rdt^r!&a1)PYiAQ)AVK7ijrHBaPu>ovA3RESWis}a! z;gQj(V@QMyMnjlK%KyeAYDF+wtJf=#9Rq^+gE!(vJODq)MR;VSFtwHz+!8#Z%Q7GcH3h>bd z@gr8~0O>g_U9t#+U@~b;#3R&HqGC{%tRVtG0vyo85!#5;DZqz|cqD4Af_{1yjFG`y zbcpbU>?V`R04fw{l5jK<;?gK8@yKj85RbqHvq=GuL|cfW1qgRlK>)R-3fVD%=h);8 zZw9wi1tf%IW+F+juN0;K0Kj}&A?DdEOOcC*=R z1QjHEI*K%eiPeUAWU&~rDGa>PTZl*CyQ-c-zDndH3moLeq(XMg;5l}}!&?w3cq9^0 zO_5x+P!8y8R1UI7?CYwGj`b!Xi#cF;y~LPmR%Rw3^K($P&B_5IivI!lJH0UsR2% zjj}>t8x2;-9=)f`sKFRc+^H6)JR+x2X8|9rh_;zY@IZ?VQc8(z#3P$cXCoe=Vlov2 z9Ey$z015vQkBll>5*P7E)EZDc3u#ioTyzA%V&bvQW-~+LR3o&Zh_;A#5gys?W`;x{ z%WlO`1lfeCLX?G_hRBBi!J%(iwCGE)GCqQ9d08)qnQXm-?D1azwD4p9L|AvAQC(<={1xT?S;%Hjf29FXOq#6 z{z>mCGo+c~BaSLE7RDVpO?oTR*%9rqlHh?B2YNCkazI^J9S*&Nc!ZkDR1C^e^$v>} zgbX62k6NnwHEqHW7at7L1)sL zO%BK&y{F71%_kkz;^Y(PhqkgIodeNM8wnoF0jJp{5;>tRY)+@aNqP(|B~vjdOEoyH z77#)_0(v+?8*yfh4YUF$)R?F>AU%hrOGPuIBLD&s>C5`l+GDDgvKH{iaV`1C{kL*b2M6}CJ$_qSlp@}Jx3mmY!Tn3i`>J?g0reaW* z#^6G0SuA8j0X-a{jX1N`4qAazZ^KB5OHaDN9^nx&5Iho0BObY2E<30|bB8%;Lqg&a z>`3q^D#}JYf-Z=1f=4E56&ea7PV{uDqz$6uaOlv#T#!qL4Q+0VLL_*Ao-9)9s7Wo_ zD0;xSZ!x>jKj}SXhBQY|(v2!|$|G`Gj1KV8h3F^;bxzO{g`P}_qQE0ZRFpA_^c^au zP%*%vF-F;}AcS}X^l*eW;w)MRXmwbKM?x|H={ej3HDE531+mylJdTQra-dhjn6R2D z(uRb%jA0%{N81IDtk!53%oLLqWzbL#dX-H5kK*J5}VAN942`oxmT3=x7IZPS6riP^pgS zXk#?-$ZEDLR19!vjL~)*2q7Cwu;^4CS+!_MTy}#Uy%?9lW}#;rMq`^vW4A)YtbkxL zvPVZpJFI%E){0S;BJEDZ+fXsg`0<5;7&`+ap^syTN9ezp7Mvtsr$g9cClc&dr&A9d z6zz1{ai|hL29d-A6D6{u3}m+IVkm5|7_63P=rDRunbG4ZKH^YFI8XSQcw}-RT{NO& zoDm+ypo!^y45Y*v6Jv^@F#*!7P%$V=V~TOuK?v~(=-~)$#94Jt&Tgqt zk9)i)BO@2lzDznA0$O~YM3Km6OHa@A`P@FY+idnErt7E}lx24N(!C&rgdFJM2yMi9 zY?+`n(~};R9_3BM73EE$XKy@R=5$YbqSpiHN%bJR&*#eo6*iCCn@ExA<2_znZip0k zR8v!g{^m~e`D*;GgoI2G;&vfR#JSxFWKSxAEA*9@C-};J)62^X%S#I3YY>^>O7Nvo zBA+h-^*}{xDD27h__C+_vNP#DWiBr-r}&6NA>lmX=S1AnPV<$OO-FQ1`2;+=5)*5D z*&EzRZfWtW(!7?kBot0^f2AtdBL4@YPt&gU)%t>wNFPl*R#;>a%a z6z0%#VJ2O!5?@Jnp%2hk;KN5qYHDiALBHFVR+vqZC8zlcaizHv@u3GcJlsSl=>7He z!~JRb`7`SAkXC=fWo02aXGI&_^7_@Q^XpgFuV1}-`Rcai@WY7APs^|OQ=73H~IqU1^%%Jy_dG+em6d!RYB%CMwT!5RuyS}}B1EPml&%&d(pkTOujz~0| z)z-Fpc-TMe_ot=R&uNRIVo+9^f4FTq2+4;7dN@KGarIfNLF?-Jw$irJ<#TYAF0Z5K z|5^|u2BeW6MA9nhuFSrszJ`t_z^3*U_%zGjy?fU+l{ER9YC0MyvTs9E2d)}lsSiAYlbOuPVKtaz z#hAI(uZ)kepL8L7=0qC`;`={_&?ru>5i~L*poy6P&CCpF5zxvk2-~o0eH6QF2|BQ6 z^*DZ+Z~(fP6EI4^Xy!sVhDG74CvE{_Sxo2{i(_uUcoqwoARtcFBAmqH0h6%@{vX&i zm56W(cEbMyI~kJ!z03oc#!>*&St?)#^M?M8y^sVmu~+l&Y&=T`oPhnPKe8;A37E~s z1Lg=wCun}eS(U8N56s810VlE?z5wHTgRlmcJEoFdHu}}3o>{ugM$xZ{D&ZdO^ ziXDFht62r$3^o<8hD{57i{0h~XR=DbS=j6T4bB@7JRLiGzroJ%8Gv)Kv-cb9H>(Al z$7TY~$Bz1MunTP#&ZjJ7vjMT=8gLPt3%FRo2AuUcie1g~0GA3#dyl`u?$`xlhj2aM z88}t(6hY1^d{R0R{wY#~#|hV1Ha==+D^EMzE8u09?hI z09UhS?0m+KL%?qAq5YC}!2x1#A0YM}0Le**nwLwFNggYW%p7Vs?AkMI^Y82W?_vmwB(0-nvzMED%G7VumF zw_$JPCpc%g0jD9ivyFh~3%CROs6WQndNu>@Vp~EVu?qy;&4xoC;#A^Rz&!%)W#=Hg zkDUv65!(iMv4EGb?Fi$PV(0^QnShtG9SC2+b^`8ayF%~d^dP~j*lxh9*@b}D0KSK# zr+WaeWqSdyWBUNFXBPq9z%B;-HM<1xM!?MRhWv_%@#wkvmp2bN{f?u%L0Ka6f zhhAcTW^Vxgg}n**6?+TtDEmF&*8+aS{(w^k-?Bde{*}EA_?>`%WA7mRJ$n!E2f!C_ z>hyiUzq1dptNbVSA>cm*JjOmk_-FPB;Bf&**{28x*=K+u_Ic>Jh)&o2Tc<-`|68a3 zTc`h9r~kjC(;xW%PN%={Z=L>sY^Rfj{%_5az$YdYg!k+OXeB=H}8w)msl=KzFGSKw!m1F zWS7O2tT>#8IQ$%Ah_^+#^l|n?mpv(kjzG>K{h41 z^paWU(#xjg7>#7s#%N@GL`s#+id&UUdY9`Kr@@6^|1~i&x42dOrlgvsTTF9hZk3Z| zNtUslM^$X`x7hu0oZ;6HPh4NzaGV+!=XAxmo%o5$sW&<0WCOPv<{BicA=yx2kPT9t zMvaM6Wt*Ebx8E%>x5qu~mfd-cYx-jf&|KvwL#zTz=LL8!A1yy6xLwW7+H8Abd#;;q z^uyq(ZlaGjHa0qoz4jC*K2}HG?$s99Q{)t-4YaWxYPl>W`^I!gHddv(DL@%#)L? zUcZgo^463WfJH|O@{Ts<5wk?d?udbG79iYMTAC*UxM$g8N*$$kM=9}ZqK`FlHJ6XG zEA(|-i=>V6PRf?&eIVEx$V>qwwzRSV@lMCCry98Ra;K?@&eR zo>VC{wdBzFspEU4xI;eUuu(D^9fuUZ(j{f(5o?Y{Zs3TW*kT9I$}5P0kT$wSBn=`} zfPSf%l%=JIq`Omsm$eRp_VhBsBK`PXk%|LeyOz8dUXa{uPTZ@h7M^ZiSB zY-U`3er#rDY<_-RrmSl?yy<~A-gscs;Rd`BapCvs>;L#jxu*Sa>uYz7yw-X+u&E?H zy=2qI(zLXaLy@Yh({?HeiXTiO6SjlDVT>o6A@YJXb zIlELzo5)qBHR4-f*6d5;mEucv)@ZyeHKr*nWBkOZJootFJxSb}RFSkI>DnX(KeJ+= z$uQ^TdBbjZ;W>`UjtKvb+QFiN0!Jx&=uxl_{DasDCLXOgifAZEiijx%1J{rsX=9>e zv=BM$67zb}NW^j>RYiiUcpDv4gp;cfGjFOU$|VcI1CKM*cs?aIcf$UJ?6=Fe7OYxz z@q@v)gCE`cXkGc&-~IiI8Esov4<-kv&0SPeF=dW-Vewgk?(@%fHBCM8UGVpO zJmKjJ&XZn58tfQp_qwr(|~$HXCraJIQ0<1_Mt@;(b^=msnmSG!8U%qa965%}hN(DqeOcSDa zRb4q@K}kx-Ju_`-}1!CNXGXOjjDj{ z7|RAmhM4scKHqLpe!-jk8w9|O&-e|4HKCo)s2ow_XMA$_7*k_ z{wILU;{7&uKz2ymaM}YTC~}wtlpW575`(AnD}o)!#Y1066_65{<@nt=us)_>InzR; zvOd^nC=-ocK)SJK*FR8B|!@(hZOg{9vDgl!NvR&gZ)LPF|&ojwy zsc)Hnw97@7YRh39!B!IY%i8Gu=GeF$R@ei3U=gGkdL`!${H!O`N*2^IF$tuiBdozz zfRy|s2_qB&sTt!kq;wnfF0_P=w8TQ3g)hA1k#$v3@%|XoXUrG0(%jx)8oL7Z!G&K%5iR0Nx(5OxvDL^Nq5gmLf$O~)K@w~{9KBg_T=~JYF{3MLj8ZD?wQI7vy z7WF1izq_q<)|^&*u){t4W9n&lvSg@%vxqO<~!Le-0op=w$<4Z>us6y#$JrXkn~xd;a}{Qck-w9qlk zpjby|eNOGX^Nzpx%;%S8^2^)@B=rLrDX`VG0IU!i3Gyq`4 zq#mZgt`+uKik+v}VZYi_Vu9H<8Yg|mTMr)&UK3o0uZhl;`;Tu7J`wy{n!{fV7NGSi z(Rvz;U8$_aKUvYHrX)JC11%aL4Z8 z=yUsC-nTS~Uv2G~UEkZ+IJtQI#NbVsBR%+z#}oYVxeJ5Gb{2Q2^eqk3OMY(5E18qA!UmLH4r9`h%3g941?CtWqRoX_8&2AkJMU#yD9re3LYzv zJOy(JLkGc%Np^6L!QG z#YaSmOl>hU0iPqxCwYPul~qcNZ(PHn+b0zk7l~euYKTrQQ74Cicq;GZQ`&*oHf5I| z*!~{Aj}lulVd>hTz{PKG`trx&b-ZxJ-0G!g)Xr>-K67^M;Lz!_*T}AdibZ{ghn9YD zTk9f!dFey_J&$%w?0M_lt#|Wzvm2M!&R)?tTG705b5rxCbtHE)LZ2(4h}<>bBg76& zxi2Dl#GkBFq%RiIr-k(Gutrxz?}?V9CqS8pMbR?e5@Cbjf>3i6M}HxEPNB+R5|Plc zNn);{U-`)QXH(VP$6#TN=C5bM;H`@k*p$t(ZZ(q z(;^rR7JY(pz~aE!Bneyifg2k#Z9s0dgjZC`VItYePnbxj7ITWFM5RTnB`WgRNrMSw zuV#J9J8CUXR4?}zNUEaTB};lja~(4q^_esvKvL>#(5(-)gCY zp_A8$#Rn`oKs&_d6%@Gf@3k^g2-7E>+qqrOm3MyLqpTS{UAnsuUyjM- ze}Gp0G57oID z58e`d2;bqx9+^JnUDRkXOU8HI!rA;q_(M5{_ zWK5`NX4xJ*H+%6F^QKOl(P$2CazTf=|8(AcXY3113a;gs2Rr!-8O`h4&YD(SeA*d9 zzPGr#Y~zddzv{1RzB{-dG7&l!ic!B&-@rboIqZ%Hu=(q)yREJw+u_h#{s01IkKR?Z zsQgB&lx9nF6_r<)E2mAFIkB>E%9NQm?=8EoY@|#sE3@33p{FRyn<7n_A|LWk7+(1z;PKfQFOavvN#!5Y$P&7^V5n8plLDYrD37O0n>!pU6`_Y zVWCq0g(h=gQX5mu#VSFOBW$7I4Ewp86_+cwY*BCU)zCx1=WoIHl#(yt^4b^Y&+HC< zaP#NEy)XZsw|$S#IPJRf(t$wvY)55}f7@d}-Z!JW*by9GkTKccF=t`fwEW5{Wz+F5 z-A0FNeEG-MZJEx z{()aGZT_0kHEX9$?B>;Hx3uL~2H&M#ip6^gXlBIy`5v`emi zw)DF6z3H+o-JkwSy4(vOrKcw*>|=7`KBq(H-mEAon~md)y^e5xqO9%GnAODU& z`{b$x3sye!`B2k}wV$a+Uc2h-#wlqT4_wfH-=q|)OWs&p<*zw@+k)z;_=GOUPPxz2 zzoEw`v1$HHE5o{7$7?$pjwt%_TeP)Mi zvnE#4D_OUg-DYAdEx!pfj7&*PUK}tZ!9p+)j0?|fu9mKU`iJXkRzK2x>H5(X{K?)6 z*G<1-L_N~=T=4tL{t_I$D=|~)IsSgdH6vf*^P3#I>zLY!l8tPEzl13^MN;qP@cgFO7P1U8+1X zs*`>@S|MA0CgbYqFN1Fe-^WLrLB$o&&p&|*J)7yz|4*oRg(gXQqCtWl#k=e zC(1|Z!b&)$@Nm&rC{3d)rIOKSf~6?<$q)=3potD$gTS z9t<_KSd1{3!x|FH#qwI|p=X27zY+X7c>ldmJ$mW!zy0;v%;2o}t!634H>+%^54_r8BLBq=*eEmZ9B$_XSa~h{;Kl}RCT(YfZl2q$h6oVd>4vP-^7ep!|;k)DuV*2|N1gVG+|9{K`-2IrFS zMFgDE#@8P9gw?Vhzs+n_^e*}afeu!XQNiAB4LFF@T7$d=pC#DRq~SQNDCzmB+{CmF zYg8x9bkfeGo}sISYF0Ksfjev<9RfPr6FMQo^ly~ zQu^J`>(nDZ-xa(Z`S)SLL_&TOyU~wxhTNr#$I#+2Z$e=Iit(2Yu}VBc4G!jOk~O zWEjTT-3W0o-orC)yf$^-#{MqgUKX%fu1e%m(7*@+7andE&dIvRcn)y@huThb+$%} zAy})G^h7RHh$?Y>oMRYA?t3Kda72%gh7H>@kzNupWoc~z%?5Jp1orb=q)dK$uzvLI z;3ClTrkr)GT24RyHgtI-bjob#6f^$g>NI~Y&PQvdXLbBM^)huo{Q$X0U83#ea?Czi zvF|fDq2uGB<1L$6+}L{Fn2tx0;q^QiOE&5lX;S(rbGb%*5Hf%dOI~mw_`vr+1s{Bl zKY7!I7hQMDo*g$!?_kQhH@G(Vt6(U2|NXz>i$d=`{>YCnKl(V0Hu%q{Sd~8lyTyq$ zyf~iF~=5Fy!z4Ik}&2tss z7hD&-IQU?C^KajKn>Z!&(c~pLrAs$GtsbfQ-31#C7JFmj;~i&uf{XcMi{ny956B%Z{Y>AC*`%K^ z^HbG?c=#pqSk7^{1(Tbk>B*)3rKbe~H>E09>vn&=k$b?OJoLD|=y6(>=*REI=xeG&D@n>0ts34LK`T<_W7^xE zlBYHY^MjLv`7l>5`*}pE1CGl>pJ`mc;bs&3aYnh?DMjtmIq{iq_ZEBwwMTMnvBp}* z)GcYRqie!LDwek~&%#<`#w4h(umVx)_!ILEtT^|9)uXrVj+?-r*mma7HYl>o&i?#> zcVg0#8y8>DC9gWZb=~%D8&Ed3i)b*WW1Zj}|J?g9S-`YLqls0c>61emiK$`)RWX99 z8j16*OoOEZjf785aa2{~kX1?1aA^hOb8#l)8(0xc>67+Kk70!_GQnx=hcKfflR(qf z1;;PO2R>$0kshY8GeyP6XJ_J5y)TS@5RAf5_o0#uqXWOs;D3DSIH)yo>%Dr9#a?8j zL9P^mG+mZH&6s6UmdOk8F}XHHDU>Fw)1_Ie@|g6PN|QQu7-K^CDMKBRMzFRI={zwO z1~4dM%vCgdwK^Q#*FjO8tv9&94!uFfC}T9Zj1o5*FgD73FEfD;CP~HS5lnuG89ID1 z$AOQ6kqKghMJpQ(+=MaHhf_!rM(I8*c1wmao`@a}o?y*EEFq5#pWx4!m7$}J9XePU z>OTb#L2qbO&4LnKgE=9FR+$D?{DrL#e&03TW~sL;6=HB{Qv6g zO8}dyvi{$F?`3O}W>4BQZIY&Kx-WF6Ed;HG|OWPFDZ@xdJZQ9;v~>^DMn5|P79+91<@4!IM`pgC=b zXg6Xd(AC|B4fW&~)kgGl^c(fZk-w_FpR|p+9+ktf77o#(i+mlBGh86q%cq@Yz6d#l z6cD>nA+rN>&giPuy0sW=ffVN4Ce=t#Fr{a8J+9ZS+mby4BsVI5Q| z923$etz9qSsDPR>Ta1u)kK90X07CDiFMPwm$)zeSQI(1y0Ip+l9cr#dy!3l%6&CZTwyG5&mwBWMb5V1 zYH|9%|J`~&YB_Z=tem2o?|yIwXWmdTdd;sO} zYx!KocE(^g#z8)1WZxGG6~oH{=U=nVT1*?kj~|Mg6Ww-~-4!Ztvc*@%&k%)1#tIvK z6~61quE+8xid;4IwC6WD_)lOrY_uFs7QlSMaS23?ATr?mIK(}{C@X}ZGl1auGxzDi zOd*#BS8IR>V3RC%Up2Ji)GbecI-zaQ{?x2qd5;WIat4j+ot2Y6B$#_}MZ?gc8TsicgQ^3G$`>h~ zU?7-XSkrqlGyoA%e&?a|AA>JDMHn72R>e<<7p(+R!h-lLl1?u)KC9a<2)<^G)!l5u za3nVB1Nxbw!^WEIemg*IIEpLrg~k9M>XOFs#pNP(-VXZ^q70(k6^!+8EM?nkwqJch znfJ!h8^_hwD~)XH^!mD~*Yv##(1s#MBFf*Ci%%%uH|D2JeL!^Ixwv7&?F$xT4Gx6f z;>Jt{gh(Xa>2$dgnr&804E`jfG;7?hMyC@qmC9y{NsaKk^T|@@3mXHg3RE$Cs|2Y9 zY#m@kL<6I_Ag@e^l0gcHbT4ZR-1GT?NB{KD#EPu-gKj&9jbJNRR@f&GyKd={DZ_4J zJ98TjUE6cr?;hB5)39a!qGLyj`}Ey(Z{wN~G`<7c&dVQSd~NWI)A-60WD%AchlJJf zwVGYH5!oiVn$6avW}O>0w%yhQhwcn9p)oF{z3r{)8tXc~FlkB&NJ5efUM|$;sQRr5lF#pE9*>=nOLQAD9-(X9K?moSG1cuuGRFE?vA}?aBtE zSLJQyqnm*79T)K+WuQT`A;1FnLCy2g7V$MZtt4F|K5QOMv)LU;WQNALxWq=Bk26Kq zi1RCE$gvg~H@F180B_#jvDum?1RFK1u|fH%u_SGo$)=``+3ZyU3#CRdjQGuWny z%B6pw`PWXQ`%nA})8D0BYnndtn(paw?1^g&`=&Sw(|4`CjY$W7WF>bgKfT+$;QAqj zP+J5j^3ph*774&d(~EOyyPc2Lr-@Gv8Jzz_Q0(4SoRDHaLjw#y7QQSXv zH%nLJK7>*0KD=8w{;kN?TKsu^#*V43zWS!Kjgu~%ShBmHvf|odgQw3LI&_kpamiIM z?a4Xaj_-D+=9RnrSI^(wKhW*ist6(ib4k)IhR*pZc926Tdln9s z&mzeNYZ5cZV&wm~FXg z&zzPQl<5O&2ToLQ+_e5>^uXW0(>h5UFzu>2n-o$h@4{vX;+@H3m>KZ^{YO(QI@gu` zg`6>?ngRr!-o?E|@EGAF`)b+6kI?iTV6TOEeRiQHl4_Ps7G$*Lm0GsM`OJ0`)oilc zo$wYq=wO9#!6L(QBXLfQ96DPeP9fGI6m6Uw5?1LTYl-H>wpMQ2u;4&Vk*A~#?|Xj9 z#h+fr;k5#5bItAAURH;TK&gCg>xMiDni z*GE;14Zz##DDVc=i_kU;)HI#Zh19HZQWJE>CTl0!)&zuEosF(&8@d7o5kRI2B*l~V zOG+3qL3Da+wI1e_03;Zm@^-jQ0^GJ3w`3Hu|3F0CtCXI*Uzvg#Gl8Uhck590oBNcn zsdu15MCXfmVS?lmf5r=oB5illU`4S`&=PcOu;2UK9eZ zE#O|zlCxO7=`MX@(_3EVjaY4E1>Qe;MXxu_+GGh^ung0h(lgRknFxVH_I=LWZ=#+4 zrrOH&aQY_;kuIs@tD?QF@2y$hCYE|~J*y#2?O8NRHV0b`XJdUGVF z-*{z)oU#3y5#z_r81(uFt=EZ@287CI+}ipFv94cYX3-4|t$%LcKj>e~{z2c={bRY( z{`p3g&BUQ3c!hRR5!G3t149x7$SsR+;f&ZbrVf|8cv#Pe8 zNDR>gW@dr>wL%WNg(Qexf80!5TViX1W~9#NFX%YfiXeSL%W+wuNo0g=vBZs~Y-xS( zakI|;O*wJ(_8KmNrwtf711&oCz|>HIa_oZgz4Et6Pemi`xqZ?6yH`L2Ljo(a221ojLO>X~039n&jo4MgaJ&R{FCquGE` zN`tYy3#bj(Rd1gRwd~C$H;(UJ&z34PxLVe4#7t!tyVx^=DUhFFag7C`sSmXN{mw-V z>zB=k=D?75LXb|P-BzLYSwW_4WK^N(6r9;Qm`Bz)bq~34m@rF|75+V&l|~mRShoUf z6AoG8#K_?XH(z)j3EKjyu`RfaEiIovdi;&I56B*t+f$h?XFM@?RE6?sO7BT+$_~7L zF_7^fpDuMPB)?D$B$TS{Cgw=k`tcY zdve|U!AxsnV?u(yF|#m}G#Wt&G+GO+owAd3|0q?lm{bE*qvRr#EZ=sP3$fx79;+l_m{b!$w&;)66qZD`LEF}@)!Z(;tnH@YXKaP*j@WSV6#8l z&sHl9%6;eFRyJcKriu^H=f#i2Wvwe{M2tXS24X~v!gY}vr#Mf158R6mRLerB7Bmu7 zAJGEWVoiSJ^VvxwX#iDSE9eV>VO{7%v9ObGfz1xX9b1}It;-QCG!XC=S_q@;^G8py z7nS?iDkc5Ymr8%O5~}*Zzg0c29QVYQinxEzF@ z_L(Trn%fm!a~@Dl_?n|d$Insl4Q-Vi?dtF?a6M}^cQQmB-9VdY^i`c+B73)7!=Cu~ zb+|<)3>b97hhv`!jGZ-LoFDU01IOg16Md#6FrTmYDD*NNE5AQ_X@F%_ZP3^0#WX2T z|C(MRKx}^$5VJrOiTY;2f~z(NscF`_+F`MoBFDt&M8yHx0f~XV(K@LeL&ix%YM?9X zRU9IH*m_>9y!a`*q~yvO|7iVG`8z}XXpL7Hjw>wA2t`8gNJ?cMGljOKrn=Y`!SCGS z3JR`N&GJl>%ep)>HH8lScuQGIK~6DvVwm=VC|m~p`WrgjCNZ08x)CCyqfX_(ppY|Y*siuV&9NU21HJQL6o}J4M!32Iy<@^u_O<+} z^d|C>fot%ba1?j5338k$9AYf~5Hb@VLVoc>aYU($qq>xe& z0HheKTlv#l*AMPK|HlE__Dp(cO3|!$#)j{ZPAkVI_DH>b7puFfpZ|I<+Jnr3{}?9->dAam^`GX(xm$R?zVXA{FJ_GG z)G{RHrvj+v$8|0=)V_wEE6fZ{~efpNO3$>~kIM^RB(+D1e4m zz4}B)Z$%+9iCUBy+kS2p@ZOH?+FK^L-3CWGLbhY}_~oTLddraI`xi=6Z`HaxdJFXd z|9SP^F1=OjrXL9p4$~eK z41a{{xWg$Kg^(dG0Oz%Tur@Y@EdWhnm$IJS0{dx|^1;QcgFmHZU{#ooOO)Ss*HjsulNI44>lzw?9xktuF}0N35P92x1xIG4fjNE|K%K<)&$ zEyJJbOAiP}J)u_4B&0EJb|Qzi*1)(^Cbik11)h@xhe1yKWC-QPq$Gmp8*#nwOz|@? z$QtAG_6y6f@)N3M<#M83oFL zz|`JFR~Hpz`dZIwCMSqCw&Cbt(wOJMYefvR3SoC-iZy8PnQe(q?0IXdnA$7`;N1@z z3VIgS7m9_2#mzZ73&_o)VQXTdd6UzryEiEjgt!SIASUp5mjy!Mq!eG_@&cP!wmcm4 zi3K)ES{@Ahq~$3aQ^XWfUs^rzRgiwGlH)L5u-4$Dp9`c-)$t~&-Y{ssld4-Mf!>Ed z;|ncGSCF({4iJ%D;NT<4wLl3va5z+2%mbO)aSi$lxi7^%tU&or<8>8PWEPK@Q(Hc~ z_nN8qjlQ~J=iuBGJ660azTKRm(Kdbf%ww;u^(rfq2NiZ75HAh+c2u8|3bWJeNKxMJ zmJ!U2pTK-uw&>HpUF8ZCEPZACkR!{gZ|pa?UwU60wn8sRIz2|R3K1f!BO{%?(|YF& zknFY91#(CLh|^rb{DDBIInigg)b`D&))n-~WsQ@C*?2ai(d82QHhybimO+-8VxZBl zYYcQ7)@@3+josvK-TXZoa|hA8XwU1BN08W+ZpvKgx$PQ055e@Ca zxTwh{qI9M-ZZ%M}7xy;;6k_T_8XDG&H2FDYrK7f@wui6VGfX%A`k`Y=i*Nkvi6t*C z9iC}r3)3dn_U)ThsaRUZ%_`0xzOr`0h8KRjurdmj6 zGI7#b=zzF_c-r*6s~c+I%M;krJNg3PRnmrmR?deh*FS+%1#jCOeYZfsThW_ulh zZjblL3VJhk;_wxYvL8#h^@DZX>($&+{D zHR=FQK|37hp$%*S92k10J!F$)eG=0jM6BpRCdDJbo=5ADc1>}iR4v7Us-N8GN-Ne5 zZ&c3hRnGPO{pnkopx{_M`Qz7G=Z{}Y%G9@3ymk)ngtMAQPa;wvQsvD}^tp1IHO$xS zatki6KQW_F-?Sl%Wo7vbjl+;2qbVb!(0|YZ2X+C@%x?CXVqp_B-6+&`?t%U5EoU1tQQ+2MsK#!S!Sm%PM|pU=@^V%Z2BYLOkG?836cfk5_QOICfBSf8~s>)R|TE6dulQP_oz z(!KC-QIWf4qkb1mY}y0xtnqJ1f`RRTy?|o`{0{oUhl|_JfI@S?G46~Qf=gFdf`B5# z&I*X2Wjv0Ac#ZS{Cp4LrA%{9_IZRvhhVtMWr}6m)yY@6b-`K2dX=_uqu$gUbZ04mN z?56E&Z@8tv?N1d~UYI{D(_nr5^|50I^u20|_yHb0eHxFPW>Zg}RvtKw2ihMNw=A7G zVqucq=_{Ad|80V#yujWE{}03$smpkl|H^0%!f0j}vmWmq4X6p0%KxX^Bq47PaO8l=&Px+6w| zfY_*Il$@sCIN3NGcLW+D$oezIAmdgL8?K16*IOoAM9X2bIbt6M5c;si8E{6Nk`p0k zq8O2fA%HPlCzM+D+e`0=k=En%iL>Qlc^_FCSSzud zBd~`AX}8#ga|ARk#0IgemEWEO+;bLQUqyb5tOGo&L%s^LPTE7HsXaQp5IN+CCKHEL zg2YY7r^saG65Gdg%8#0spMLs@PU36ZBJwBNg}}-eM0$+|@T}A0mM@9fZSiIb$v5pa zi@5L!U?64x&^i#UA7?paa99zyj~inHn2kAz#h;9n3ucFfXHSB_S6OJ>#f$-?sC;~j zQqgoYfTT&Oa0aMU$cA78hQZTI;cI#DTJfsiGv{l}q0Fsi@3tOzUHRMV$~WxZUclz& zi&AT=^87yK`PPe~hV|KpHPUtzIP%k2BNp_dAuI^9&To0#JflgCR*bEv^4tn)N3NrLd z%+)Mxy-)1c`lh(_;)lwRYm^G!H z$UrF�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 Rc9ZpPMl#V}tY!30B7j5Zxis7yB9Fz= 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 IbxsLQ0Eoan=Kufz literal 0 HcmV?d00001 diff --git a/RoboCat/Assets/Rock_Image.png b/RoboCat/Assets/Rock_Image.png new file mode 100644 index 0000000000000000000000000000000000000000..f2322daef96e8b8dc209bb873c91f0f30c5f3752 GIT binary patch literal 4672 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 literal 0 HcmV?d00001 diff --git a/RoboCat/Chapter3.vcxproj b/RoboCat/Chapter3.vcxproj index ce090807..cabc7347 100644 --- a/RoboCat/Chapter3.vcxproj +++ b/RoboCat/Chapter3.vcxproj @@ -92,6 +92,11 @@ true true Bin\$(Configuration)\ + true + true + true + true + true true diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index 78d97f61..0591bc46 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -12,8 +12,8 @@ //-------------------------Graphics Data------------------------- GraphicsLibrary* pGraphics; -float screenSizeX = 1000.0; -float screenSizeY = 600.0; +float screenSizeX = 1600.0; +float screenSizeY = 900.0; //-------------------------Input Data------------------------- InputSystem* pInput; From 0c43d32aee5c16285291286561efcdfb758b983a Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 1 Apr 2022 12:22:18 -0400 Subject: [PATCH 03/56] Networker::receiveGameObjectState now returns a PacketType, and main checks if the returned value is PACKET_CREATE and increments the networkID if it does. This FIXED the issue where switching between senders causes a delay before spawning the most recent gameObject. --- RoboCat/Inc/Networker.h | 5 +++-- RoboCat/Src/Main.cpp | 7 ++++++- RoboCat/Src/Networker.cpp | 7 +++++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index 5aa8bc65..eea37eaa 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -15,7 +15,8 @@ enum PacketType { PACKET_CREATE, PACKET_UPDATE, - PACKET_DELETE + PACKET_DELETE, + PACKET_INVALID }; //Networker is singleton (we only want one networker at a time) @@ -45,7 +46,7 @@ class Networker bool connect(std::string serverIpAddress, std::string port); //Update game state - void receiveGameObjectState(); + PacketType receiveGameObjectState(); void sendGameObjectState(int networkID, PacketType packetHeader); //Map diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index 0591bc46..4c7e88aa 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -65,6 +65,7 @@ float playerMoveSpeed = 0.5; //-------------------------Network Data------------------------- Networker* Networker::mInstance = 0; Networker* pNetworkManager; +PacketType packetTypeReceived; int networkID = 0; bool init() @@ -418,7 +419,11 @@ int main(int argc, const char** argv) pNetworkManager->sendGameObjectState(pPlayerController->getNetworkID(), PacketType::PACKET_UPDATE); //Network update - receive packets - pNetworkManager->receiveGameObjectState(); + packetTypeReceived = pNetworkManager->receiveGameObjectState(); + + //If you receive a new GameObject, increment the network ID to keep spawning in sync! + if (packetTypeReceived == PacketType::PACKET_CREATE) + networkID++; //Draw call draw(); diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 1aad896c..a185bca8 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -152,7 +152,7 @@ bool Networker::connect(std::string serverIpAddress, std::string port) } -void Networker::receiveGameObjectState() +PacketType Networker::receiveGameObjectState() { char buffer[1024]; int32_t byteRecieve = (*mpTCPSocket)->Receive(buffer, 1024); @@ -292,14 +292,17 @@ void Networker::receiveGameObjectState() } default: - return; + return PacketType::PACKET_INVALID; } + + return packetHeader; } else if (byteRecieve == -10053 || byteRecieve == -10054) { LOG("%s", "Disconnected From Server"); exit(0); } + return PacketType::PACKET_INVALID; } void Networker::sendGameObjectState(int ID, PacketType packetHeader) From f75a08a8df1d197c2d5e6c91d00bf044d4f563f3 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Tue, 5 Apr 2022 12:47:49 -0400 Subject: [PATCH 04/56] Updated TO-DOs with questions and ideas. --- TO-DO.txt | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 TO-DO.txt diff --git a/TO-DO.txt b/TO-DO.txt new file mode 100644 index 00000000..64829e1e --- /dev/null +++ b/TO-DO.txt @@ -0,0 +1,34 @@ +Assignment Requirements: + +"Enhance" your previous game to simulate jitter, latency, and dropped packets, and add a reliability layer to deal with these potential pitfalls. Your game must: + +. Pass all checks on github +. Be able to open a window +. Have at least two players able to connect at once +--> These are the minimum requirements for the assignment to be gradable. + +- Packets are occasionally "dropped," by random chance or by some algorithm. Dropped packets are registered as having been sent by the sender, but do not actually call the socket's send() function. Random delay is introduced to some packet's send time, and packets can be delivered out of order. +- Game state is sent through a reliability layer. Important data is sent through guaranteed mechanisms, while data that does not require guaranteed delivery is sent through simpler mechanisms. Important data is resilient to jitter, out-of-order delivery, and high latency. + +--------------------------------------------------------------- +Questions: + +. TCP always resends dropped packets, and keeps a queue of things to resend so nothing arrives out of order? +. Where do the layers come into play in our previous assignment? Transport layer, Network layer, Link layer? + +. For the assignment, are we combining TCP with a custom UDP solution? +. Are we replacing or Send and Receive code in our NetworkManager? Do we port over all the serialization and deserialization code to the DeliveryNotificationManager, or do we keep it and call functions from DeliveryNotificationManager in our NetworkManager? + +--------------------------------------------------------------- +Notes and Ideas: + +. Use TransmissionData.h - make a derived class? +. Use Timimg.h and .cpp +. Use InFlightPacket.h and .cpp +. Use DeliveryNotificationManager.h and .cpp +. Use AckRange.h and .cpp + +--> Chapter 7 slide 14 gives a good explanation of how this can be done + +. Figure out how to read and write PacketSquenceNumber, AckRange, and hasAcks in our Networker (see DeliveryNotificationManager.cpp's reads from inInputStream) +. Override TransmissionData::HandleDeliveryFailure() and HandleDeliverySuccess() \ No newline at end of file From b2c6402e719be4a94ca35446c55643cde93b1dcb Mon Sep 17 00:00:00 2001 From: Alex Jaeger Date: Tue, 5 Apr 2022 14:13:54 -0400 Subject: [PATCH 05/56] Started writing logic for simulating Latency and Jitter. Generating a random value in between a positive and negative. Also created a queue for packet processesing. Wrote the logic for simulating in TO-DO.txt --- RoboCat/Inc/Networker.h | 13 ++++++++++--- RoboCat/Src/Main.cpp | 4 ++-- RoboCat/Src/Networker.cpp | 31 ++++++++++++++++++++++++++----- TO-DO.txt | 17 ++++++++++++++++- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index eea37eaa..aaa82444 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include "GameObject.h" #include "Rock.h" @@ -9,8 +12,6 @@ #include "PlayerController.h" #include "RoboCatPCH.h" -using std::map; - enum PacketType { PACKET_CREATE, @@ -36,7 +37,7 @@ class Networker return mInstance; }; - void init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIdentifier, std::string playerSpriteIdentifier, float playerMoveSpeed, Colour wallColour); + void init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIdentifier, std::string playerSpriteIdentifier, float playerMoveSpeed, Colour wallColour, float arrivalTime); void cleanup(); ~Networker(); @@ -55,6 +56,9 @@ class Networker void updateGameObjects(); void renderGameObjects(); + //Queue + void SortPacketQueue(); + private: Networker(); @@ -64,6 +68,9 @@ class Networker //Data TCPSocketPtr* mpTCPSocket; std::vector> mGameObjectsVec; + std::queue> mPacketQueue; //Might wanna change types depending on how we store the packets + int mArrivalTime; + bool mIsInit; //Data for GameObject replication GraphicsLibrary* mpGraphicsLibrary; diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index 4c7e88aa..b7bc3a82 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -42,7 +42,7 @@ const std::string PLAYER_SPRITE_IDENTIFIER = "player_image"; bool bGameIsRunning = true; std::map gameObjectMap; -float FPS = 60; +int FPS = 60; ALLEGRO_TIMER* timer = nullptr; ALLEGRO_EVENT_QUEUE* eventQueue = nullptr; @@ -340,7 +340,7 @@ int main(int argc, const char** argv) { //Setup network manager pNetworkManager = pNetworkManager->GetInstance(); - pNetworkManager->init(pGraphics, ROCK_SPRITE_IDENTIFIER, PLAYER_SPRITE_IDENTIFIER, playerMoveSpeed, wallColour); + pNetworkManager->init(pGraphics, ROCK_SPRITE_IDENTIFIER, PLAYER_SPRITE_IDENTIFIER, playerMoveSpeed, wallColour, 60); //Prompt for isServer or not std::string input; diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index a185bca8..ea3154e7 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -4,7 +4,6 @@ //Constructor Networker::Networker() { - } Networker::~Networker() @@ -12,10 +11,19 @@ Networker::~Networker() cleanup(); } -void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIdentifier, std::string playerSpriteIdentifier, float playerMoveSpeed, Colour wallColour) +void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIdentifier, std::string playerSpriteIdentifier, float playerMoveSpeed, Colour wallColour, float arrivalTime) { + if (mIsInit) + { + cleanup(); + } + + std::srand(time(NULL)); + mArrivalTime = arrivalTime; + mpTCPSocket = new TCPSocketPtr; mGameObjectsVec = std::vector>(); + mPacketQueue = std::queue>(); //Data for GameObject replication mpGraphicsLibrary = graphicsLibrary; @@ -23,13 +31,12 @@ void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIde mPlayerSpriteIdentifier = playerSpriteIdentifier; mPlayerMoveSpeed = playerMoveSpeed; mWallColour = wallColour; + + mIsInit = true; } void Networker::cleanup() { - //delete mInstance; - //mInstance = nullptr; - //Cleanup map std::vector>::iterator it; for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) @@ -42,7 +49,10 @@ void Networker::cleanup() (*mpTCPSocket)->CleanupSocket(); delete mpTCPSocket; mpTCPSocket = nullptr; + SocketUtil::CleanUp(); + + mIsInit = false; } bool Networker::initServer(std::string port) @@ -307,6 +317,8 @@ PacketType Networker::receiveGameObjectState() void Networker::sendGameObjectState(int ID, PacketType packetHeader) { + int time = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); // MIN + rand() % (MAX - MIN + 1), calculates a random number int between a neg and pos value + OutputMemoryBitStream OMBStream; OMBStream.Write(packetHeader); OMBStream.Write(mGameObjectsVec[ID].second->getNetworkID()); @@ -387,4 +399,13 @@ void Networker::renderGameObjects() { it->second->draw(); } +} + +//Not finished lol, will finish today +void Networker::SortPacketQueue() +{ + for (int i = 1; i <= mPacketQueue.size(); ++i) + { + + } } \ No newline at end of file diff --git a/TO-DO.txt b/TO-DO.txt index 64829e1e..d975e51e 100644 --- a/TO-DO.txt +++ b/TO-DO.txt @@ -31,4 +31,19 @@ Notes and Ideas: --> Chapter 7 slide 14 gives a good explanation of how this can be done . Figure out how to read and write PacketSquenceNumber, AckRange, and hasAcks in our Networker (see DeliveryNotificationManager.cpp's reads from inInputStream) -. Override TransmissionData::HandleDeliveryFailure() and HandleDeliverySuccess() \ No newline at end of file +. Override TransmissionData::HandleDeliveryFailure() and HandleDeliverySuccess() + +----------------------------------------------------------------- + +Simulating Latency/Jitter + +1. Along with header, send a float for arrival time + Note: Arrival time should be +/- a random amount, for simulating jitter + +2. Insert packet into a queue (couple ways to do this) + Note: one way the professor recommended is to enqueue the bytes recieve and split it that way (memory management yaaaaay) + +3. Sort Queue based on arrival time (this will simulate jitter) + +4. Each frame of the game, we process only the packets that arrvive based on the current arrival time + Note: if arrival time = x seconds, we only process the packets x seconds ago \ No newline at end of file From 441b575bb55bc6e546dced804c60599cbe60bad3 Mon Sep 17 00:00:00 2001 From: Twigz101 Date: Wed, 6 Apr 2022 00:01:35 -0400 Subject: [PATCH 06/56] Finished first verions of the queue sorting function, might need to slightly tweek depending on how we store packages --- RoboCat/Inc/Networker.h | 2 ++ RoboCat/Src/Networker.cpp | 50 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index aaa82444..7b4dd809 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -58,6 +58,8 @@ class Networker //Queue void SortPacketQueue(); + int FindMinIndex(int sortedIndex); + void InsertMinIndexToEnd(int minIndex); private: diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index ea3154e7..d692fe87 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -401,11 +401,57 @@ void Networker::renderGameObjects() } } -//Not finished lol, will finish today + void Networker::SortPacketQueue() { for (int i = 1; i <= mPacketQueue.size(); ++i) { - + int min = FindMinIndex(mPacketQueue.size() - i); + InsertMinIndexToEnd(min); + } +} + + +int Networker::FindMinIndex(int sortedIndex) +{ + int minIndex = -1; + int minVal = INT_MAX; + for (int i = 0; i < mPacketQueue.size(); i++) + { + std::pair currentPacketTime = mPacketQueue.front(); + mPacketQueue.pop(); + + if (currentPacketTime.first <= minVal && i <= sortedIndex) + { + minIndex = i; + minVal = currentPacketTime.first; + } + mPacketQueue.push(currentPacketTime); + } + + return minIndex; +} + +void Networker::InsertMinIndexToEnd(int minIndex) +{ + if (minIndex == -1) + { + return; + } + + std::pair minVal; + for (int i = 0; i < mPacketQueue.size(); i++) + { + pair currentPacket = mPacketQueue.front(); + mPacketQueue.pop(); + if (i != minIndex) + { + mPacketQueue.push(currentPacket); + } + else + { + minVal = currentPacket; + } } + mPacketQueue.push(minVal); } \ No newline at end of file From 46954e5eb973c31a280abbd325970331a790da23 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 8 Apr 2022 12:49:09 -0400 Subject: [PATCH 07/56] Updated text file with notes and tips, started implementing InFlightPackets into Networker queue. Changed it into a sorted queue so we don't have to do it manually and keep calling functions. --- RoboCat/Inc/Networker.h | 14 +++-- RoboCat/Src/Networker.cpp | 124 ++++++++++++++++++++------------------ TO-DO.txt | 10 ++- 3 files changed, 81 insertions(+), 67 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index 7b4dd809..1fbfe6da 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -10,6 +10,7 @@ #include "Rock.h" #include "Wall.h" #include "PlayerController.h" +#include "InFlightPacket.h" #include "RoboCatPCH.h" enum PacketType @@ -56,10 +57,10 @@ class Networker void updateGameObjects(); void renderGameObjects(); - //Queue - void SortPacketQueue(); - int FindMinIndex(int sortedIndex); - void InsertMinIndexToEnd(int minIndex); + ////Queue + //void sortPacketQueue(); + //int findMinIndex(int sortedIndex); + //void insertMinIndexToEnd(int minIndex); private: @@ -70,9 +71,10 @@ class Networker //Data TCPSocketPtr* mpTCPSocket; std::vector> mGameObjectsVec; - std::queue> mPacketQueue; //Might wanna change types depending on how we store the packets + //std::queue mInFlightPacketsQueue; + std::priority_queue> mInFlightPacketsQueue; int mArrivalTime; - bool mIsInit; + bool mbIsInitted; //Data for GameObject replication GraphicsLibrary* mpGraphicsLibrary; diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index d692fe87..521849a8 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -13,7 +13,7 @@ Networker::~Networker() void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIdentifier, std::string playerSpriteIdentifier, float playerMoveSpeed, Colour wallColour, float arrivalTime) { - if (mIsInit) + if (mbIsInitted) { cleanup(); } @@ -23,7 +23,8 @@ void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIde mpTCPSocket = new TCPSocketPtr; mGameObjectsVec = std::vector>(); - mPacketQueue = std::queue>(); + //mPacketQueue = std::queue>(); + mInFlightPacketsQueue = std::priority_queue>(); //Data for GameObject replication mpGraphicsLibrary = graphicsLibrary; @@ -32,7 +33,7 @@ void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIde mPlayerMoveSpeed = playerMoveSpeed; mWallColour = wallColour; - mIsInit = true; + mbIsInitted = true; } void Networker::cleanup() @@ -52,7 +53,7 @@ void Networker::cleanup() SocketUtil::CleanUp(); - mIsInit = false; + mbIsInitted = false; } bool Networker::initServer(std::string port) @@ -170,6 +171,9 @@ PacketType Networker::receiveGameObjectState() { InputMemoryBitStream IMBStream = InputMemoryBitStream(buffer, 1024); + //TO-DO: Read InFlightPacket squence number, SET SEQUENCE NUMBER AND SYNC BETWEEN HOSTS SOMEHOW + InFlightPacket* pInFlightPacket = new InFlightPacket(); + //Start reading PacketType packetHeader; IMBStream.Read(packetHeader); @@ -317,7 +321,8 @@ PacketType Networker::receiveGameObjectState() void Networker::sendGameObjectState(int ID, PacketType packetHeader) { - int time = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); // MIN + rand() % (MAX - MIN + 1), calculates a random number int between a neg and pos value + //TO-DO: Create InFlightPacket squence number, SET SEQUENCE NUMBER AND SYNC BETWEEN HOSTS SOMEHOW, set transmission data in InFlightpacket + InFlightPacket* pInFlightPacket = new InFlightPacket(); OutputMemoryBitStream OMBStream; OMBStream.Write(packetHeader); @@ -375,6 +380,9 @@ void Networker::sendGameObjectState(int ID, PacketType packetHeader) return; } + //TO-DO: Collect InFlightPackets in queue and send all of them when it gets too full (size() > maxSize) + //TO-DO: Add time to pInFlightPacket's timeDispatched + int time = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); // MIN + rand() % (MAX - MIN + 1), calculates a random number int between a neg and pos value (*mpTCPSocket)->Send(OMBStream.GetBufferPtr(), OMBStream.GetBitLength()); } @@ -402,56 +410,56 @@ void Networker::renderGameObjects() } -void Networker::SortPacketQueue() -{ - for (int i = 1; i <= mPacketQueue.size(); ++i) - { - int min = FindMinIndex(mPacketQueue.size() - i); - InsertMinIndexToEnd(min); - } -} - - -int Networker::FindMinIndex(int sortedIndex) -{ - int minIndex = -1; - int minVal = INT_MAX; - for (int i = 0; i < mPacketQueue.size(); i++) - { - std::pair currentPacketTime = mPacketQueue.front(); - mPacketQueue.pop(); - - if (currentPacketTime.first <= minVal && i <= sortedIndex) - { - minIndex = i; - minVal = currentPacketTime.first; - } - mPacketQueue.push(currentPacketTime); - } - - return minIndex; -} - -void Networker::InsertMinIndexToEnd(int minIndex) -{ - if (minIndex == -1) - { - return; - } - - std::pair minVal; - for (int i = 0; i < mPacketQueue.size(); i++) - { - pair currentPacket = mPacketQueue.front(); - mPacketQueue.pop(); - if (i != minIndex) - { - mPacketQueue.push(currentPacket); - } - else - { - minVal = currentPacket; - } - } - mPacketQueue.push(minVal); -} \ No newline at end of file +//void Networker::sortPacketQueue() +//{ +// for (int i = 1; i <= mPacketQueue.size(); ++i) +// { +// int min = findMinIndex(mPacketQueue.size() - i); +// insertMinIndexToEnd(min); +// } +//} +// +// +//int Networker::findMinIndex(int sortedIndex) +//{ +// int minIndex = -1; +// int minVal = INT_MAX; +// for (int i = 0; i < mPacketQueue.size(); i++) +// { +// std::pair currentPacketTime = mPacketQueue.front(); +// mPacketQueue.pop(); +// +// if (currentPacketTime.first <= minVal && i <= sortedIndex) +// { +// minIndex = i; +// minVal = currentPacketTime.first; +// } +// mPacketQueue.push(currentPacketTime); +// } +// +// return minIndex; +//} +// +//void Networker::insertMinIndexToEnd(int minIndex) +//{ +// if (minIndex == -1) +// { +// return; +// } +// +// std::pair minVal; +// for (int i = 0; i < mPacketQueue.size(); i++) +// { +// pair currentPacket = mPacketQueue.front(); +// mPacketQueue.pop(); +// if (i != minIndex) +// { +// mPacketQueue.push(currentPacket); +// } +// else +// { +// minVal = currentPacket; +// } +// } +// mPacketQueue.push(minVal); +//} \ No newline at end of file diff --git a/TO-DO.txt b/TO-DO.txt index d975e51e..78b71475 100644 --- a/TO-DO.txt +++ b/TO-DO.txt @@ -22,7 +22,7 @@ Questions: --------------------------------------------------------------- Notes and Ideas: -. Use TransmissionData.h - make a derived class? +. Use TransmissionData.h - make GameObject derived classes also derive from and override functions . Use Timimg.h and .cpp . Use InFlightPacket.h and .cpp . Use DeliveryNotificationManager.h and .cpp @@ -31,7 +31,11 @@ Notes and Ideas: --> Chapter 7 slide 14 gives a good explanation of how this can be done . Figure out how to read and write PacketSquenceNumber, AckRange, and hasAcks in our Networker (see DeliveryNotificationManager.cpp's reads from inInputStream) -. Override TransmissionData::HandleDeliveryFailure() and HandleDeliverySuccess() + +. Sync PacketSequenceNumbers on server and client!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +. Add a DeliveryNotificationManager* to Networker, handle cleanup as well +. Not sure where yet, but keep checking for timeouts on sent packets (if they did not receive acks, I think this is in DeliveryNotificationmanager), but use get transmission data in InFlightpacket to handle success or failure on ack and on timeout +--> Investigate functions in DeliveryNotificationManager and figure out how to implement them with out stuff ----------------------------------------------------------------- @@ -40,7 +44,7 @@ Simulating Latency/Jitter 1. Along with header, send a float for arrival time Note: Arrival time should be +/- a random amount, for simulating jitter -2. Insert packet into a queue (couple ways to do this) +2. Insert inflightpacket into a queue (couple ways to do this) Note: one way the professor recommended is to enqueue the bytes recieve and split it that way (memory management yaaaaay) 3. Sort Queue based on arrival time (this will simulate jitter) From 24342ea4b39fa32c65654ccd5e1f21e5e15e4cde Mon Sep 17 00:00:00 2001 From: Alex Jaeger Date: Fri, 8 Apr 2022 14:11:47 -0400 Subject: [PATCH 08/56] Worked on Jitter/Latency simulation. Created a struct that holds bytesRecieved, the buffer, and the time. We can use this to send packets out of order --- RoboCat/Inc/Networker.h | 8 ++ RoboCat/Src/Networker.cpp | 162 +++++++++++++++++++++++++++++++++++++- 2 files changed, 169 insertions(+), 1 deletion(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index 1fbfe6da..619c423d 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -21,6 +21,13 @@ enum PacketType PACKET_INVALID }; +struct GamePacket +{ + char* buffer; + int32_t byteRecieve; + float dispatchTime; +}; + //Networker is singleton (we only want one networker at a time) class Networker { @@ -50,6 +57,7 @@ class Networker //Update game state PacketType receiveGameObjectState(); void sendGameObjectState(int networkID, PacketType packetHeader); + PacketType processPacket(GamePacket gamePacket); //Map void addGameObject(GameObject* objectToAdd, int networkID); diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 521849a8..6ea4762c 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -162,11 +162,165 @@ bool Networker::connect(std::string serverIpAddress, std::string port) return false; } +//Goes through the Queue and processes the packets in order +PacketType Networker::processPacket(GamePacket gamePacket) +{ + if (gamePacket.byteRecieve > 0) + { + InputMemoryBitStream IMBStream = InputMemoryBitStream(gamePacket.buffer, 1024); + + //Start reading + PacketType packetHeader; + IMBStream.Read(packetHeader); + float dispatchTime; + IMBStream.Read(dispatchTime); + int networkID; + IMBStream.Read(networkID); + GameObjectType receiveType; + IMBStream.Read(receiveType); + + //Logic depends on packer header type + switch (packetHeader) + { + case PacketType::PACKET_CREATE: + { + float posX; + float posY; + + IMBStream.Read(posX); + IMBStream.Read(posY); + + switch (receiveType) + { + case GameObjectType::ROCK: + { + Rock* newRock = new Rock(networkID, mpGraphicsLibrary, pair(posX, posY), mRockSpriteIdentifier); + mGameObjectsVec.push_back(pair(networkID, newRock)); + newRock = nullptr; + + break; + } + + case GameObjectType::PLAYER: + { + PlayerController* newPlayerController = new PlayerController(networkID, mpGraphicsLibrary, pair(posX, posY), mPlayerMoveSpeed, mPlayerSpriteIdentifier); + mGameObjectsVec.push_back(pair(networkID, newPlayerController)); + newPlayerController = nullptr; + + break; + + } + + case GameObjectType::WALL: + { + float sizeX; + float sizeY; + float thickness; + + IMBStream.Read(sizeX); + IMBStream.Read(sizeY); + IMBStream.Read(thickness); + + Wall* newWall = new Wall(networkID, mpGraphicsLibrary, pair(posX, posY), sizeX, sizeY, mWallColour, thickness); + mGameObjectsVec.push_back(pair(networkID, newWall)); + newWall = nullptr; + + break; + } + } + + break; + } + + case PacketType::PACKET_UPDATE: + + if (mGameObjectsVec[networkID].second != nullptr) + { + float x; + float y; + + switch (receiveType) + { + case GameObjectType::ROCK: + case GameObjectType::PLAYER: + + IMBStream.Read(x); + IMBStream.Read(y); + mGameObjectsVec[networkID].second->setPos(std::make_pair(x, y)); + break; + + case GameObjectType::WALL: + { + float sizeX; + float sizeY; + float thiccness; + + Wall* wall = (Wall*)mGameObjectsVec[networkID].second; + IMBStream.Read(x); + IMBStream.Read(y); + + wall->setPos(std::make_pair(x, y)); + + IMBStream.Read(sizeX); + IMBStream.Read(sizeY); + IMBStream.Read(thiccness); + + wall->setWallSizeX(sizeX); + wall->setWallSizeY(sizeY); + wall->setWallThickness(thiccness); + + break; + } + } + } + else + { + //Report error + std::cout << "ERROR: CANNOT UPDATE GAMEOBJECT ID " << networkID << " BECAUSE IT IS NOT IN THE NETWORK MANAGER MAP.\n"; + } + + break; + + case PacketType::PACKET_DELETE: + { + //Delete element in map + if (mGameObjectsVec.size() > 0) + { + std::vector>::iterator it; + for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + { + //DO NOT DELETE A PLAYER + if (it->first == networkID && it->second->getGameObjectType() != GameObjectType::PLAYER) + { + mGameObjectsVec.erase(it); + + break; + } + } + } + + break; + } + + default: + return PacketType::PACKET_INVALID; + } + + return packetHeader; + } +} + PacketType Networker::receiveGameObjectState() { char buffer[1024]; int32_t byteRecieve = (*mpTCPSocket)->Receive(buffer, 1024); + + GamePacket packet; + packet.buffer = buffer; + packet.byteRecieve = byteRecieve; + packet.dispatchTime = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); + if (byteRecieve > 0) { InputMemoryBitStream IMBStream = InputMemoryBitStream(buffer, 1024); @@ -177,6 +331,8 @@ PacketType Networker::receiveGameObjectState() //Start reading PacketType packetHeader; IMBStream.Read(packetHeader); + float dispatchTime; + IMBStream.Read(dispatchTime); int networkID; IMBStream.Read(networkID); GameObjectType receiveType; @@ -326,6 +482,10 @@ void Networker::sendGameObjectState(int ID, PacketType packetHeader) OutputMemoryBitStream OMBStream; OMBStream.Write(packetHeader); + + float timeDispatched = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); + OMBStream.Write(timeDispatched); + OMBStream.Write(mGameObjectsVec[ID].second->getNetworkID()); OMBStream.Write(mGameObjectsVec[ID].second->getGameObjectType()); @@ -382,7 +542,7 @@ void Networker::sendGameObjectState(int ID, PacketType packetHeader) //TO-DO: Collect InFlightPackets in queue and send all of them when it gets too full (size() > maxSize) //TO-DO: Add time to pInFlightPacket's timeDispatched - int time = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); // MIN + rand() % (MAX - MIN + 1), calculates a random number int between a neg and pos value + //float timeDispatched = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); (*mpTCPSocket)->Send(OMBStream.GetBufferPtr(), OMBStream.GetBitLength()); } From a7731334b8406c124e588777bda3a29b68b9d3b9 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Mon, 11 Apr 2022 02:15:23 -0400 Subject: [PATCH 09/56] Changed code to add outgoing packets to queue in random order and send when the queue has 10 elements in it. Sorts based off of time which is randomized so that simulates jitter. Added code to drop some packets. Added pDeliveryNotificationManager and calling WriteState and ReadAndProcessState. Created function that calls DeliveryNotificationManager::ProcessTimedOutPackets and calling that in main.cpp. Saving. Need to change sockets to UDP and then I can test. Commented out Alex's code for now, but it helped me understand a lot (I just wanted to try a different approach. --- RoboCat/Inc/Networker.h | 24 +- RoboCat/Src/DeliveryNotificationManager.cpp | 2 +- RoboCat/Src/Main.cpp | 5 +- RoboCat/Src/Networker.cpp | 270 +++++++++++--------- TO-DO.txt | 7 +- 5 files changed, 169 insertions(+), 139 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index 619c423d..5762fb0e 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -10,7 +10,7 @@ #include "Rock.h" #include "Wall.h" #include "PlayerController.h" -#include "InFlightPacket.h" +#include "DeliveryNotificationManager.h" #include "RoboCatPCH.h" enum PacketType @@ -21,12 +21,12 @@ enum PacketType PACKET_INVALID }; -struct GamePacket -{ - char* buffer; - int32_t byteRecieve; - float dispatchTime; -}; +//struct GamePacket +//{ +// char* buffer; +// int32_t byteRecieve; +// float dispatchTime; +//}; //Networker is singleton (we only want one networker at a time) class Networker @@ -45,7 +45,7 @@ class Networker return mInstance; }; - void init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIdentifier, std::string playerSpriteIdentifier, float playerMoveSpeed, Colour wallColour, float arrivalTime); + void init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIdentifier, std::string playerSpriteIdentifier, float playerMoveSpeed, Colour wallColour/*, float arrivalTime*/); void cleanup(); ~Networker(); @@ -57,7 +57,8 @@ class Networker //Update game state PacketType receiveGameObjectState(); void sendGameObjectState(int networkID, PacketType packetHeader); - PacketType processPacket(GamePacket gamePacket); + void checkTimedOutPackets(); + //PacketType processPacket(GamePacket gamePacket); //Map void addGameObject(GameObject* objectToAdd, int networkID); @@ -80,8 +81,9 @@ class Networker TCPSocketPtr* mpTCPSocket; std::vector> mGameObjectsVec; //std::queue mInFlightPacketsQueue; - std::priority_queue> mInFlightPacketsQueue; - int mArrivalTime; + DeliveryNotificationManager* pDeliveryNotificationManager; + std::priority_queue, std::vector>, std::greater>> mOutputBitStreamQueue; + //int mArrivalTime; bool mbIsInitted; //Data for GameObject replication diff --git a/RoboCat/Src/DeliveryNotificationManager.cpp b/RoboCat/Src/DeliveryNotificationManager.cpp index ea85baa9..976b4bd0 100644 --- a/RoboCat/Src/DeliveryNotificationManager.cpp +++ b/RoboCat/Src/DeliveryNotificationManager.cpp @@ -127,7 +127,7 @@ void DeliveryNotificationManager::ProcessAcks( InputMemoryBitStream& inInputStre AckRange ackRange; ackRange.Read( inInputStream ); - //for each InfilghtPacket with a sequence number less than the start, handle delivery failure... + //for each InflightPacket with a sequence number less than the start, handle delivery failure... PacketSequenceNumber nextAckdSequenceNumber = ackRange.GetStart(); uint32_t onePastAckdSequenceNumber = nextAckdSequenceNumber + ackRange.GetCount(); while( nextAckdSequenceNumber < onePastAckdSequenceNumber && !mInFlightPackets.empty() ) diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index b7bc3a82..ea088fa0 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -340,7 +340,7 @@ int main(int argc, const char** argv) { //Setup network manager pNetworkManager = pNetworkManager->GetInstance(); - pNetworkManager->init(pGraphics, ROCK_SPRITE_IDENTIFIER, PLAYER_SPRITE_IDENTIFIER, playerMoveSpeed, wallColour, 60); + pNetworkManager->init(pGraphics, ROCK_SPRITE_IDENTIFIER, PLAYER_SPRITE_IDENTIFIER, playerMoveSpeed, wallColour/*, 60*/); //Prompt for isServer or not std::string input; @@ -421,6 +421,9 @@ int main(int argc, const char** argv) //Network update - receive packets packetTypeReceived = pNetworkManager->receiveGameObjectState(); + //Network check for and deal with timed out packets + pNetworkManager->checkTimedOutPackets(); + //If you receive a new GameObject, increment the network ID to keep spawning in sync! if (packetTypeReceived == PacketType::PACKET_CREATE) networkID++; diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 6ea4762c..8c2da8ae 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -11,7 +11,7 @@ Networker::~Networker() cleanup(); } -void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIdentifier, std::string playerSpriteIdentifier, float playerMoveSpeed, Colour wallColour, float arrivalTime) +void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIdentifier, std::string playerSpriteIdentifier, float playerMoveSpeed, Colour wallColour/*, float arrivalTime*/) { if (mbIsInitted) { @@ -19,12 +19,13 @@ void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIde } std::srand(time(NULL)); - mArrivalTime = arrivalTime; + //mArrivalTime = arrivalTime; mpTCPSocket = new TCPSocketPtr; mGameObjectsVec = std::vector>(); //mPacketQueue = std::queue>(); - mInFlightPacketsQueue = std::priority_queue>(); + pDeliveryNotificationManager = new DeliveryNotificationManager(true, true); + mOutputBitStreamQueue = std::priority_queue, std::vector>, std::greater>>(); //Data for GameObject replication mpGraphicsLibrary = graphicsLibrary; @@ -47,6 +48,10 @@ void Networker::cleanup() } mGameObjectsVec.clear(); + //Cleanup delivery notification manager + delete pDeliveryNotificationManager; + pDeliveryNotificationManager = nullptr; + (*mpTCPSocket)->CleanupSocket(); delete mpTCPSocket; mpTCPSocket = nullptr; @@ -162,6 +167,7 @@ bool Networker::connect(std::string serverIpAddress, std::string port) return false; } +/* //Goes through the Queue and processes the packets in order PacketType Networker::processPacket(GamePacket gamePacket) { @@ -309,163 +315,164 @@ PacketType Networker::processPacket(GamePacket gamePacket) return packetHeader; } } - +*/ PacketType Networker::receiveGameObjectState() { char buffer[1024]; int32_t byteRecieve = (*mpTCPSocket)->Receive(buffer, 1024); - GamePacket packet; - packet.buffer = buffer; - packet.byteRecieve = byteRecieve; - packet.dispatchTime = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); + //GamePacket packet; + //packet.buffer = buffer; + //packet.byteRecieve = byteRecieve; + //packet.dispatchTime = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); if (byteRecieve > 0) { InputMemoryBitStream IMBStream = InputMemoryBitStream(buffer, 1024); - //TO-DO: Read InFlightPacket squence number, SET SEQUENCE NUMBER AND SYNC BETWEEN HOSTS SOMEHOW - InFlightPacket* pInFlightPacket = new InFlightPacket(); - - //Start reading - PacketType packetHeader; - IMBStream.Read(packetHeader); - float dispatchTime; - IMBStream.Read(dispatchTime); - int networkID; - IMBStream.Read(networkID); - GameObjectType receiveType; - IMBStream.Read(receiveType); - - //Logic depends on packer header type - switch (packetHeader) + //Read sequence number + if (pDeliveryNotificationManager->ReadAndProcessState(IMBStream)) { - case PacketType::PACKET_CREATE: - { - float posX; - float posY; - - IMBStream.Read(posX); - IMBStream.Read(posY); - - switch (receiveType) + //Start reading + PacketType packetHeader; + IMBStream.Read(packetHeader); + //float dispatchTime; + //IMBStream.Read(dispatchTime); + int networkID; + IMBStream.Read(networkID); + GameObjectType receiveType; + IMBStream.Read(receiveType); + + //Logic depends on packer header type + switch (packetHeader) { - case GameObjectType::ROCK: + case PacketType::PACKET_CREATE: { - Rock* newRock = new Rock(networkID, mpGraphicsLibrary, pair(posX, posY), mRockSpriteIdentifier); - mGameObjectsVec.push_back(pair(networkID, newRock)); - newRock = nullptr; + float posX; + float posY; - break; - } - - case GameObjectType::PLAYER: - { - PlayerController* newPlayerController = new PlayerController(networkID, mpGraphicsLibrary, pair(posX, posY), mPlayerMoveSpeed, mPlayerSpriteIdentifier); - mGameObjectsVec.push_back(pair(networkID, newPlayerController)); - newPlayerController = nullptr; - - break; - - } - - case GameObjectType::WALL: - { - float sizeX; - float sizeY; - float thickness; - - IMBStream.Read(sizeX); - IMBStream.Read(sizeY); - IMBStream.Read(thickness); - - Wall* newWall = new Wall(networkID, mpGraphicsLibrary, pair(posX, posY), sizeX, sizeY, mWallColour, thickness); - mGameObjectsVec.push_back(pair(networkID, newWall)); - newWall = nullptr; - - break; - } - } - - break; - } - - case PacketType::PACKET_UPDATE: - - if (mGameObjectsVec[networkID].second != nullptr) - { - float x; - float y; + IMBStream.Read(posX); + IMBStream.Read(posY); switch (receiveType) { case GameObjectType::ROCK: + { + Rock* newRock = new Rock(networkID, mpGraphicsLibrary, pair(posX, posY), mRockSpriteIdentifier); + mGameObjectsVec.push_back(pair(networkID, newRock)); + newRock = nullptr; + + break; + } + case GameObjectType::PLAYER: + { + PlayerController* newPlayerController = new PlayerController(networkID, mpGraphicsLibrary, pair(posX, posY), mPlayerMoveSpeed, mPlayerSpriteIdentifier); + mGameObjectsVec.push_back(pair(networkID, newPlayerController)); + newPlayerController = nullptr; - IMBStream.Read(x); - IMBStream.Read(y); - mGameObjectsVec[networkID].second->setPos(std::make_pair(x, y)); break; + } + case GameObjectType::WALL: { float sizeX; float sizeY; - float thiccness; - - Wall* wall = (Wall*)mGameObjectsVec[networkID].second; - IMBStream.Read(x); - IMBStream.Read(y); - - wall->setPos(std::make_pair(x, y)); + float thickness; IMBStream.Read(sizeX); IMBStream.Read(sizeY); - IMBStream.Read(thiccness); + IMBStream.Read(thickness); - wall->setWallSizeX(sizeX); - wall->setWallSizeY(sizeY); - wall->setWallThickness(thiccness); + Wall* newWall = new Wall(networkID, mpGraphicsLibrary, pair(posX, posY), sizeX, sizeY, mWallColour, thickness); + mGameObjectsVec.push_back(pair(networkID, newWall)); + newWall = nullptr; break; } } - } - else - { - //Report error - std::cout << "ERROR: CANNOT UPDATE GAMEOBJECT ID " << networkID << " BECAUSE IT IS NOT IN THE NETWORK MANAGER MAP.\n"; + + break; } - break; + case PacketType::PACKET_UPDATE: - case PacketType::PACKET_DELETE: - { - //Delete element in map - if (mGameObjectsVec.size() > 0) - { - std::vector>::iterator it; - for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + if (mGameObjectsVec[networkID].second != nullptr) { - //DO NOT DELETE A PLAYER - if (it->first == networkID && it->second->getGameObjectType() != GameObjectType::PLAYER) + float x; + float y; + + switch (receiveType) { - mGameObjectsVec.erase(it); + case GameObjectType::ROCK: + case GameObjectType::PLAYER: + + IMBStream.Read(x); + IMBStream.Read(y); + mGameObjectsVec[networkID].second->setPos(std::make_pair(x, y)); + break; + + case GameObjectType::WALL: + { + float sizeX; + float sizeY; + float thiccness; + + Wall* wall = (Wall*)mGameObjectsVec[networkID].second; + IMBStream.Read(x); + IMBStream.Read(y); + + wall->setPos(std::make_pair(x, y)); + + IMBStream.Read(sizeX); + IMBStream.Read(sizeY); + IMBStream.Read(thiccness); + + wall->setWallSizeX(sizeX); + wall->setWallSizeY(sizeY); + wall->setWallThickness(thiccness); break; } + } + } + else + { + //Report error + std::cout << "ERROR: CANNOT UPDATE GAMEOBJECT ID " << networkID << " BECAUSE IT IS NOT IN THE NETWORK MANAGER MAP.\n"; } + + break; + + case PacketType::PACKET_DELETE: + { + //Delete element in map + if (mGameObjectsVec.size() > 0) + { + std::vector>::iterator it; + for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + { + //DO NOT DELETE A PLAYER + if (it->first == networkID && it->second->getGameObjectType() != GameObjectType::PLAYER) + { + mGameObjectsVec.erase(it); + + break; + } + } + } + + break; } - break; - } + default: + return PacketType::PACKET_INVALID; + } - default: - return PacketType::PACKET_INVALID; + return packetHeader; } - - return packetHeader; } else if (byteRecieve == -10053 || byteRecieve == -10054) { @@ -477,14 +484,16 @@ PacketType Networker::receiveGameObjectState() void Networker::sendGameObjectState(int ID, PacketType packetHeader) { - //TO-DO: Create InFlightPacket squence number, SET SEQUENCE NUMBER AND SYNC BETWEEN HOSTS SOMEHOW, set transmission data in InFlightpacket - InFlightPacket* pInFlightPacket = new InFlightPacket(); - OutputMemoryBitStream OMBStream; + + //Write state sent (packet sequence number and acks) + pDeliveryNotificationManager->WriteState(OMBStream); + + //Write packet header OMBStream.Write(packetHeader); - float timeDispatched = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); - OMBStream.Write(timeDispatched); + //float timeDispatched = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); + //OMBStream.Write(timeDispatched); OMBStream.Write(mGameObjectsVec[ID].second->getNetworkID()); OMBStream.Write(mGameObjectsVec[ID].second->getGameObjectType()); @@ -540,10 +549,29 @@ void Networker::sendGameObjectState(int ID, PacketType packetHeader) return; } - //TO-DO: Collect InFlightPackets in queue and send all of them when it gets too full (size() > maxSize) - //TO-DO: Add time to pInFlightPacket's timeDispatched - //float timeDispatched = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); - (*mpTCPSocket)->Send(OMBStream.GetBufferPtr(), OMBStream.GetBitLength()); + //Add packet data to queue in randomised order --> done through timeDispatched +- random and the priority_queue's sorting + float timeDispatched = Timing::sInstance.GetTimef() + (-100 + rand() & (100 - -100 + 1)); + mOutputBitStreamQueue.push(std::make_pair(timeDispatched, OMBStream)); + + //If the queue has more than 10 elements is it + if (mOutputBitStreamQueue.size() >= 10) + { + //Iterate though the queue + for (int i = 0; i < mOutputBitStreamQueue.size(); i++) + { + //Drop some packets + if (i % 3 != 0) + { + //Send packet + (*mpTCPSocket)->Send(OMBStream.GetBufferPtr(), OMBStream.GetBitLength()); + } + } + } +} + +void Networker::checkTimedOutPackets() +{ + pDeliveryNotificationManager->ProcessTimedOutPackets(); } void Networker::addGameObject(GameObject* objectToAdd, int networkID) diff --git a/TO-DO.txt b/TO-DO.txt index 78b71475..85b0326b 100644 --- a/TO-DO.txt +++ b/TO-DO.txt @@ -13,11 +13,8 @@ Assignment Requirements: --------------------------------------------------------------- Questions: -. TCP always resends dropped packets, and keeps a queue of things to resend so nothing arrives out of order? -. Where do the layers come into play in our previous assignment? Transport layer, Network layer, Link layer? - . For the assignment, are we combining TCP with a custom UDP solution? -. Are we replacing or Send and Receive code in our NetworkManager? Do we port over all the serialization and deserialization code to the DeliveryNotificationManager, or do we keep it and call functions from DeliveryNotificationManager in our NetworkManager? + --> UDP + our custom reliability code --------------------------------------------------------------- Notes and Ideas: @@ -34,7 +31,7 @@ Notes and Ideas: . Sync PacketSequenceNumbers on server and client!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! . Add a DeliveryNotificationManager* to Networker, handle cleanup as well -. Not sure where yet, but keep checking for timeouts on sent packets (if they did not receive acks, I think this is in DeliveryNotificationmanager), but use get transmission data in InFlightpacket to handle success or failure on ack and on timeout +. Not sure where yet, but keep checking for timeouts on sent packets (if they did not receive acks, I think this is in DeliveryNotificationManager), but use get transmission data in InFlightpacket to handle success or failure on ack and on timeout --> Investigate functions in DeliveryNotificationManager and figure out how to implement them with out stuff ----------------------------------------------------------------- From 27f4e5c826e9e2185d6f64c7c56549382c61ebb8 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Mon, 11 Apr 2022 03:01:04 -0400 Subject: [PATCH 10/56] Updated TO-DOs with remaining work. --- TO-DO.txt | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/TO-DO.txt b/TO-DO.txt index 85b0326b..1abcb771 100644 --- a/TO-DO.txt +++ b/TO-DO.txt @@ -19,20 +19,12 @@ Questions: --------------------------------------------------------------- Notes and Ideas: -. Use TransmissionData.h - make GameObject derived classes also derive from and override functions -. Use Timimg.h and .cpp -. Use InFlightPacket.h and .cpp -. Use DeliveryNotificationManager.h and .cpp -. Use AckRange.h and .cpp - --> Chapter 7 slide 14 gives a good explanation of how this can be done -. Figure out how to read and write PacketSquenceNumber, AckRange, and hasAcks in our Networker (see DeliveryNotificationManager.cpp's reads from inInputStream) - -. Sync PacketSequenceNumbers on server and client!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -. Add a DeliveryNotificationManager* to Networker, handle cleanup as well -. Not sure where yet, but keep checking for timeouts on sent packets (if they did not receive acks, I think this is in DeliveryNotificationManager), but use get transmission data in InFlightpacket to handle success or failure on ack and on timeout ---> Investigate functions in DeliveryNotificationManager and figure out how to implement them with out stuff +. Sync PacketSequenceNumbers on server and client! + --> Not needed I don't think +. Change packets to UDP +. Handle success or failure in TransmissionData (make GameObject derived classes also derive from and override functions) ----------------------------------------------------------------- From 79df4b4756deac20d8e6d24343d41e9faff8ddd8 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Mon, 11 Apr 2022 21:49:37 -0400 Subject: [PATCH 11/56] Commented out TCP stuff, implemented connectUDP, gonna work on send and receive udp. Saving progress, have to catch the shuttle. --- RoboCat/Inc/Networker.h | 18 +- RoboCat/Inc/UDPSocket.h | 1 + RoboCat/Src/Networker.cpp | 444 ++++++++++++++++++++++++++++++++------ RoboCat/Src/UDPSocket.cpp | 14 +- 4 files changed, 397 insertions(+), 80 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index 5762fb0e..b07fa4b2 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -51,12 +51,18 @@ class Networker ~Networker(); //Starting and connect to server - bool initServer(std::string port); - bool connect(std::string serverIpAddress, std::string port); + //bool initServer(std::string port); + //bool connect(std::string serverIpAddress, std::string port); + + bool connectUDP(std::string otherUserIpAddress, std::string port); //Update game state - PacketType receiveGameObjectState(); - void sendGameObjectState(int networkID, PacketType packetHeader); + //PacketType receiveGameObjectState(); + //void sendGameObjectState(int networkID, PacketType packetHeader); + + //Update game state - UDP + PacketType receiveGameObjectStateUDP(); + void sendGameObjectStateUDP(int networkID, PacketType packetHeader); void checkTimedOutPackets(); //PacketType processPacket(GamePacket gamePacket); @@ -78,7 +84,9 @@ class Networker static Networker* mInstance; //Data - TCPSocketPtr* mpTCPSocket; + //TCPSocketPtr* mpTCPSocket; + UDPSocketPtr* mpUDPSocket; + SocketAddressPtr* mpSocketAddressPtr; std::vector> mGameObjectsVec; //std::queue mInFlightPacketsQueue; DeliveryNotificationManager* pDeliveryNotificationManager; diff --git a/RoboCat/Inc/UDPSocket.h b/RoboCat/Inc/UDPSocket.h index 939df407..ae530d14 100644 --- a/RoboCat/Inc/UDPSocket.h +++ b/RoboCat/Inc/UDPSocket.h @@ -16,6 +16,7 @@ class UDPSocket */ int SetNonBlockingMode( bool inShouldBeNonBlocking ); + void CleanupSocket(); private: friend class SocketUtil; diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 8c2da8ae..7402066d 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -21,7 +21,8 @@ void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIde std::srand(time(NULL)); //mArrivalTime = arrivalTime; - mpTCPSocket = new TCPSocketPtr; + //mpTCPSocket = new TCPSocketPtr; + mpUDPSocket = new UDPSocketPtr; mGameObjectsVec = std::vector>(); //mPacketQueue = std::queue>(); pDeliveryNotificationManager = new DeliveryNotificationManager(true, true); @@ -52,76 +53,131 @@ void Networker::cleanup() delete pDeliveryNotificationManager; pDeliveryNotificationManager = nullptr; - (*mpTCPSocket)->CleanupSocket(); - delete mpTCPSocket; - mpTCPSocket = nullptr; + //(*mpTCPSocket)->CleanupSocket(); + //delete mpTCPSocket; + //mpTCPSocket = nullptr; + + (*mpUDPSocket)->CleanupSocket(); + delete mpUDPSocket; + mpUDPSocket = nullptr; SocketUtil::CleanUp(); mbIsInitted = false; } -bool Networker::initServer(std::string port) -{ - SocketUtil::StaticInit(); - - //Create Socket - TCPSocketPtr sock = SocketUtil::CreateTCPSocket(SocketAddressFamily::INET); - if (sock == nullptr) - { - SocketUtil::ReportError("Creating Listenting Socket"); - ExitProcess(1); - } - std::cout << "Listening Socket Succesfully Created!\n"; - - //Create and Bind Address - SocketAddressPtr listenAddress = SocketAddressFactory::CreateIPv4FromString("0.0.0.0:" + port); - if (listenAddress == nullptr) - { - SocketUtil::ReportError("Creating Listening Address"); - ExitProcess(1); - } - - if (sock->Bind(*listenAddress) != NO_ERROR) - { - SocketUtil::ReportError("Binding listening socket"); - ExitProcess(1); - } - std::cout << "Listening Socket Succesfully Binded!\n"; - - if (sock->Listen() != NO_ERROR) - { - SocketUtil::ReportError("Listening on socket"); - ExitProcess(1); - } - std::cout << "Listening Socket Listening\n"; - - //Accept Connection - std::cout << "Waiting for connection...\n"; - - sock->SetNonBlockingMode(false); - SocketAddress incomingAddress; - TCPSocketPtr connSocket = sock->Accept(incomingAddress); - - while (connSocket == nullptr) - connSocket = sock->Accept(incomingAddress); - - - *mpTCPSocket = connSocket; - - std::cout << "Accepted connection from address: " << incomingAddress.ToString() << std::endl; +//bool Networker::initServer(std::string port) +//{ +// SocketUtil::StaticInit(); +// +// //Create Socket +// TCPSocketPtr sock = SocketUtil::CreateTCPSocket(SocketAddressFamily::INET); +// if (sock == nullptr) +// { +// SocketUtil::ReportError("Creating Listenting Socket"); +// ExitProcess(1); +// } +// std::cout << "Listening Socket Succesfully Created!\n"; +// +// //Create and Bind Address +// SocketAddressPtr listenAddress = SocketAddressFactory::CreateIPv4FromString("0.0.0.0:" + port); +// if (listenAddress == nullptr) +// { +// SocketUtil::ReportError("Creating Listening Address"); +// ExitProcess(1); +// } +// +// if (sock->Bind(*listenAddress) != NO_ERROR) +// { +// SocketUtil::ReportError("Binding listening socket"); +// ExitProcess(1); +// } +// std::cout << "Listening Socket Succesfully Binded!\n"; +// +// if (sock->Listen() != NO_ERROR) +// { +// SocketUtil::ReportError("Listening on socket"); +// ExitProcess(1); +// } +// std::cout << "Listening Socket Listening\n"; +// +// //Accept Connection +// std::cout << "Waiting for connection...\n"; +// +// sock->SetNonBlockingMode(false); +// SocketAddress incomingAddress; +// TCPSocketPtr connSocket = sock->Accept(incomingAddress); +// +// while (connSocket == nullptr) +// connSocket = sock->Accept(incomingAddress); +// +// +// *mpTCPSocket = connSocket; +// +// std::cout << "Accepted connection from address: " << incomingAddress.ToString() << std::endl; +// +// if (mpTCPSocket != nullptr) +// return true; +// return false; +//} - if (mpTCPSocket != nullptr) - return true; - return false; -} +//bool Networker::connect(std::string serverIpAddress, std::string port) +//{ +// SocketUtil::StaticInit(); +// +// //Create Socket +// TCPSocketPtr sock = SocketUtil::CreateTCPSocket(SocketAddressFamily::INET); +// +// if (sock == nullptr) +// { +// SocketUtil::ReportError("Creating Client Socket"); +// ExitProcess(1); +// return false; +// } +// +// string address = "0.0.0.0:0"; +// SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString(address.c_str()); +// if (clientAddress == nullptr) +// { +// SocketUtil::ReportError("Creating Client Address"); +// ExitProcess(1); +// return false; +// } +// +// if (sock->Bind(*clientAddress) != NO_ERROR) +// { +// SocketUtil::ReportError("Binding Client Socket"); +// ExitProcess(1); +// } +// LOG("%s", "Client Socket Succesfully Binded!"); +// +// SocketAddressPtr srvAddress = SocketAddressFactory::CreateIPv4FromString(serverIpAddress + ":" + port); +// if (srvAddress == nullptr) +// { +// SocketUtil::ReportError("Creating Server Address"); +// ExitProcess(1); +// } +// +// if (sock->Connect(*srvAddress) != NO_ERROR) +// { +// SocketUtil::ReportError("Connecting To Server"); +// ExitProcess(1); +// } +// LOG("%s", "Succesfully Connect to the Server!"); +// +// *mpTCPSocket = sock; +// +// if (mpTCPSocket != nullptr) +// return true; +// return false; +//} -bool Networker::connect(std::string serverIpAddress, std::string port) +bool Networker::connectUDP(std::string otherUserIpAddress, std::string port) { SocketUtil::StaticInit(); //Create Socket - TCPSocketPtr sock = SocketUtil::CreateTCPSocket(SocketAddressFamily::INET); + UDPSocketPtr sock = SocketUtil::CreateUDPSocket(SocketAddressFamily::INET); if (sock == nullptr) { @@ -146,23 +202,17 @@ bool Networker::connect(std::string serverIpAddress, std::string port) } LOG("%s", "Client Socket Succesfully Binded!"); - SocketAddressPtr srvAddress = SocketAddressFactory::CreateIPv4FromString(serverIpAddress + ":" + port); - if (srvAddress == nullptr) + SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString(otherUserIpAddress + ":" + port); + if (sockAddress == nullptr) { SocketUtil::ReportError("Creating Server Address"); ExitProcess(1); } - if (sock->Connect(*srvAddress) != NO_ERROR) - { - SocketUtil::ReportError("Connecting To Server"); - ExitProcess(1); - } - LOG("%s", "Succesfully Connect to the Server!"); - - *mpTCPSocket = sock; + *mpUDPSocket = sock; + *mpSocketAddressPtr = sockAddress; - if (mpTCPSocket != nullptr) + if (mpUDPSocket != nullptr) return true; return false; } @@ -317,6 +367,7 @@ PacketType Networker::processPacket(GamePacket gamePacket) } */ +/* PacketType Networker::receiveGameObjectState() { char buffer[1024]; @@ -568,6 +619,259 @@ void Networker::sendGameObjectState(int ID, PacketType packetHeader) } } } +*/ + +PacketType Networker::receiveGameObjectStateUDP() +{ + char buffer[1024]; + int32_t byteRecieve = (*mpTCPSocket)->Receive(buffer, 1024); + + //GamePacket packet; + //packet.buffer = buffer; + //packet.byteRecieve = byteRecieve; + //packet.dispatchTime = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); + + if (byteRecieve > 0) + { + InputMemoryBitStream IMBStream = InputMemoryBitStream(buffer, 1024); + + //Read sequence number + if (pDeliveryNotificationManager->ReadAndProcessState(IMBStream)) + { + //Start reading + PacketType packetHeader; + IMBStream.Read(packetHeader); + //float dispatchTime; + //IMBStream.Read(dispatchTime); + int networkID; + IMBStream.Read(networkID); + GameObjectType receiveType; + IMBStream.Read(receiveType); + + //Logic depends on packer header type + switch (packetHeader) + { + case PacketType::PACKET_CREATE: + { + float posX; + float posY; + + IMBStream.Read(posX); + IMBStream.Read(posY); + + switch (receiveType) + { + case GameObjectType::ROCK: + { + Rock* newRock = new Rock(networkID, mpGraphicsLibrary, pair(posX, posY), mRockSpriteIdentifier); + mGameObjectsVec.push_back(pair(networkID, newRock)); + newRock = nullptr; + + break; + } + + case GameObjectType::PLAYER: + { + PlayerController* newPlayerController = new PlayerController(networkID, mpGraphicsLibrary, pair(posX, posY), mPlayerMoveSpeed, mPlayerSpriteIdentifier); + mGameObjectsVec.push_back(pair(networkID, newPlayerController)); + newPlayerController = nullptr; + + break; + + } + + case GameObjectType::WALL: + { + float sizeX; + float sizeY; + float thickness; + + IMBStream.Read(sizeX); + IMBStream.Read(sizeY); + IMBStream.Read(thickness); + + Wall* newWall = new Wall(networkID, mpGraphicsLibrary, pair(posX, posY), sizeX, sizeY, mWallColour, thickness); + mGameObjectsVec.push_back(pair(networkID, newWall)); + newWall = nullptr; + + break; + } + } + + break; + } + + case PacketType::PACKET_UPDATE: + + if (mGameObjectsVec[networkID].second != nullptr) + { + float x; + float y; + + switch (receiveType) + { + case GameObjectType::ROCK: + case GameObjectType::PLAYER: + + IMBStream.Read(x); + IMBStream.Read(y); + mGameObjectsVec[networkID].second->setPos(std::make_pair(x, y)); + break; + + case GameObjectType::WALL: + { + float sizeX; + float sizeY; + float thiccness; + + Wall* wall = (Wall*)mGameObjectsVec[networkID].second; + IMBStream.Read(x); + IMBStream.Read(y); + + wall->setPos(std::make_pair(x, y)); + + IMBStream.Read(sizeX); + IMBStream.Read(sizeY); + IMBStream.Read(thiccness); + + wall->setWallSizeX(sizeX); + wall->setWallSizeY(sizeY); + wall->setWallThickness(thiccness); + + break; + } + } + } + else + { + //Report error + std::cout << "ERROR: CANNOT UPDATE GAMEOBJECT ID " << networkID << " BECAUSE IT IS NOT IN THE NETWORK MANAGER MAP.\n"; + } + + break; + + case PacketType::PACKET_DELETE: + { + //Delete element in map + if (mGameObjectsVec.size() > 0) + { + std::vector>::iterator it; + for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + { + //DO NOT DELETE A PLAYER + if (it->first == networkID && it->second->getGameObjectType() != GameObjectType::PLAYER) + { + mGameObjectsVec.erase(it); + + break; + } + } + } + + break; + } + + default: + return PacketType::PACKET_INVALID; + } + + return packetHeader; + } + } + else if (byteRecieve == -10053 || byteRecieve == -10054) + { + LOG("%s", "Disconnected From Server"); + exit(0); + } + return PacketType::PACKET_INVALID; +} + +void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) +{ + OutputMemoryBitStream OMBStream; + + //Write state sent (packet sequence number and acks) + pDeliveryNotificationManager->WriteState(OMBStream); + + //Write packet header + OMBStream.Write(packetHeader); + + //float timeDispatched = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); + //OMBStream.Write(timeDispatched); + + OMBStream.Write(mGameObjectsVec[ID].second->getNetworkID()); + OMBStream.Write(mGameObjectsVec[ID].second->getGameObjectType()); + + //Logic depends on packer header type + switch (packetHeader) + { + case PacketType::PACKET_CREATE: + case PacketType::PACKET_UPDATE: + + switch (mGameObjectsVec[ID].second->getGameObjectType()) + { + case GameObjectType::ROCK: + case GameObjectType::PLAYER: + OMBStream.Write(mGameObjectsVec[ID].second->getPosition().first); + OMBStream.Write(mGameObjectsVec[ID].second->getPosition().second); + break; + + case GameObjectType::WALL: + Wall* wall = (Wall*)mGameObjectsVec[ID].second; + OMBStream.Write(wall->getPosition().first); + OMBStream.Write(wall->getPosition().second); + OMBStream.Write(wall->getWallSizeX()); + OMBStream.Write(wall->getWallSizeY()); + OMBStream.Write(wall->getWallThickness()); + break; + } + + break; + + case PacketType::PACKET_DELETE: + { + //Delete it on the sender's end + if (mGameObjectsVec.size() > 0) + { + std::vector>::iterator it; + for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) + { + //DO NOT DELETE A PLAYER + if (it->first == ID && it->second->getGameObjectType() != GameObjectType::PLAYER) + { + mGameObjectsVec.erase(it); + + break; + } + } + } + } + + break; + + default: + return; + } + + //Add packet data to queue in randomised order --> done through timeDispatched +- random and the priority_queue's sorting + float timeDispatched = Timing::sInstance.GetTimef() + (-100 + rand() & (100 - -100 + 1)); + mOutputBitStreamQueue.push(std::make_pair(timeDispatched, OMBStream)); + + //If the queue has more than 10 elements is it + if (mOutputBitStreamQueue.size() >= 10) + { + //Iterate though the queue + for (int i = 0; i < mOutputBitStreamQueue.size(); i++) + { + //Drop some packets + if (i % 3 != 0) + { + //Send packet + (*mpTCPSocket)->Send(OMBStream.GetBufferPtr(), OMBStream.GetBitLength()); + } + } + } +} void Networker::checkTimedOutPackets() { diff --git a/RoboCat/Src/UDPSocket.cpp b/RoboCat/Src/UDPSocket.cpp index adf859f7..9509aa3d 100644 --- a/RoboCat/Src/UDPSocket.cpp +++ b/RoboCat/Src/UDPSocket.cpp @@ -68,11 +68,7 @@ int UDPSocket::ReceiveFrom( void* inToReceive, int inMaxLength, SocketAddress& o UDPSocket::~UDPSocket() { -#if _WIN32 - closesocket( mSocket ); -#else - close( mSocket ); -#endif + CleanupSocket(); } @@ -98,3 +94,11 @@ int UDPSocket::SetNonBlockingMode( bool inShouldBeNonBlocking ) } } +void UDPSocket::CleanupSocket() +{ +#if _WIN32 + closesocket(mSocket); +#else + close(mSocket); +#endif +} \ No newline at end of file From 5c301df31f40b713b5184c982a839c2ade9b9988 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Mon, 11 Apr 2022 22:22:58 -0400 Subject: [PATCH 12/56] Changed code to call appropriate SendTo and ReceiveFrom functions for UDP. Saving before testing. --- RoboCat/Src/Networker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 7402066d..475503bd 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -624,7 +624,7 @@ void Networker::sendGameObjectState(int ID, PacketType packetHeader) PacketType Networker::receiveGameObjectStateUDP() { char buffer[1024]; - int32_t byteRecieve = (*mpTCPSocket)->Receive(buffer, 1024); + int32_t byteRecieve = (*mpUDPSocket)->ReceiveFrom(buffer, 1024, (**mpSocketAddressPtr)); //GamePacket packet; //packet.buffer = buffer; @@ -867,7 +867,7 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) if (i % 3 != 0) { //Send packet - (*mpTCPSocket)->Send(OMBStream.GetBufferPtr(), OMBStream.GetBitLength()); + (*mpUDPSocket)->SendTo(OMBStream.GetBufferPtr(), OMBStream.GetBitLength(), (**mpSocketAddressPtr)); } } } From 468da375d0940cf17a18219e80a61d6a6768ba33 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Mon, 11 Apr 2022 22:27:45 -0400 Subject: [PATCH 13/56] Added includes, but it is broken (Vector3 and Quaternion are undefined, includes added to DeliveryNotificationManager.h. Also still need to derive from TransmissionData for GameObjects and override functions (mentioned in txt file). --- RoboCat/Inc/DeliveryNotificationManager.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RoboCat/Inc/DeliveryNotificationManager.h b/RoboCat/Inc/DeliveryNotificationManager.h index b6a795a4..5813f7fa 100644 --- a/RoboCat/Inc/DeliveryNotificationManager.h +++ b/RoboCat/Inc/DeliveryNotificationManager.h @@ -1,3 +1,7 @@ +#include "InFlightPacket.h" +#include "MemoryBitStream.h" +#include "Timing.h" +#include "TransmissionData.h" class DeliveryNotificationManager { From 200a9611022fa66f496f40e0cbeb46946b3d4712 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Tue, 12 Apr 2022 17:14:12 -0400 Subject: [PATCH 14/56] Added includes of .h files in .cpp files, classes that derive from GameObject now also derive from TransmissionData. Need to figure out what the heck to do to handle success and failure. --- RoboCat/Inc/DeliveryNotificationManager.h | 2 +- RoboCat/Inc/PlayerController.h | 6 +++++- RoboCat/Inc/Rock.h | 7 ++++++- RoboCat/Inc/Wall.h | 6 +++++- RoboCat/Src/AckRange.cpp | 1 + RoboCat/Src/DeliveryNotificationManager.cpp | 3 ++- RoboCat/Src/InFlightPacket.cpp | 1 + RoboCat/Src/PlayerController.cpp | 9 +++++++++ RoboCat/Src/Rock.cpp | 9 +++++++++ RoboCat/Src/Timing.cpp | 2 +- RoboCat/Src/Wall.cpp | 9 +++++++++ TO-DO.txt | 1 - 12 files changed, 49 insertions(+), 7 deletions(-) diff --git a/RoboCat/Inc/DeliveryNotificationManager.h b/RoboCat/Inc/DeliveryNotificationManager.h index 5813f7fa..5e83d070 100644 --- a/RoboCat/Inc/DeliveryNotificationManager.h +++ b/RoboCat/Inc/DeliveryNotificationManager.h @@ -27,7 +27,7 @@ class DeliveryNotificationManager InFlightPacket* WriteSequenceNumber( OutputMemoryBitStream& inOutputStream ); - void WriteAckData( OutputMemoryBitStream& inOutputStream ); + void WriteAckData(OutputMemoryBitStream& inOutputStream) const; //returns wether to drop the packet- if sequence number is too low! bool ProcessSequenceNumber( InputMemoryBitStream& inInputStream ); diff --git a/RoboCat/Inc/PlayerController.h b/RoboCat/Inc/PlayerController.h index 439cf577..02baae88 100644 --- a/RoboCat/Inc/PlayerController.h +++ b/RoboCat/Inc/PlayerController.h @@ -8,7 +8,7 @@ #include "GameObject.h" #include "InputSystem.h" -class PlayerController : public GameObject +class PlayerController : public GameObject, public TransmissionData { //-------------------------Private data------------------------- @@ -31,6 +31,10 @@ class PlayerController : public GameObject PlayerController(const int networkID, GraphicsLibrary* graphicsLibrary); PlayerController(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, float moveSpeed, const std::string spriteIdentifier); + //Overloaded functions + void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager) const; + void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const; + //Destructor ~PlayerController(); diff --git a/RoboCat/Inc/Rock.h b/RoboCat/Inc/Rock.h index a7e1e7dd..fbe62015 100644 --- a/RoboCat/Inc/Rock.h +++ b/RoboCat/Inc/Rock.h @@ -6,8 +6,9 @@ */ #include "GameObject.h" +#include "TransmissionData.h" -class Rock : public GameObject +class Rock : public GameObject, public TransmissionData { //-------------------------Private data------------------------- @@ -19,6 +20,10 @@ class Rock : public GameObject Rock(const int networkID, GraphicsLibrary* graphicsLibrary); Rock(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, const std::string spriteIdentifier); + //Overloaded functions + void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager) const; + void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const; + //Destructor ~Rock(); diff --git a/RoboCat/Inc/Wall.h b/RoboCat/Inc/Wall.h index 8d952487..baa31773 100644 --- a/RoboCat/Inc/Wall.h +++ b/RoboCat/Inc/Wall.h @@ -8,7 +8,7 @@ #include "GameObject.h" #include "Colour.h" -class Wall : public GameObject +class Wall : public GameObject, public TransmissionData { //-------------------------Private data------------------------- float mSizeX; @@ -24,6 +24,10 @@ class Wall : public GameObject Wall(const int networkID, GraphicsLibrary* graphicsLibrary); Wall(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, float sizeX, float sizeY, Colour colour, float thickness); + //Overloaded functions + void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager) const; + void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const; + //Destructor ~Wall(); diff --git a/RoboCat/Src/AckRange.cpp b/RoboCat/Src/AckRange.cpp index 99bed803..3e05b1f3 100644 --- a/RoboCat/Src/AckRange.cpp +++ b/RoboCat/Src/AckRange.cpp @@ -1,4 +1,5 @@ #include "RoboCatPCH.h" +#include "AckRange.h" void AckRange::Write( OutputMemoryBitStream& inOutputStream ) const { diff --git a/RoboCat/Src/DeliveryNotificationManager.cpp b/RoboCat/Src/DeliveryNotificationManager.cpp index 976b4bd0..427a7308 100644 --- a/RoboCat/Src/DeliveryNotificationManager.cpp +++ b/RoboCat/Src/DeliveryNotificationManager.cpp @@ -1,4 +1,5 @@ #include "RoboCatPCH.h" +#include "DeliveryNotificationManager.h" namespace { @@ -48,7 +49,7 @@ InFlightPacket* DeliveryNotificationManager::WriteSequenceNumber( OutputMemoryBi } } -void DeliveryNotificationManager::WriteAckData( OutputMemoryBitStream& inOutputStream ) +void DeliveryNotificationManager::WriteAckData(OutputMemoryBitStream& inOutputStream) const { //we usually will only have one packet to ack //so we'll follow that with a 0 bit if that's the case diff --git a/RoboCat/Src/InFlightPacket.cpp b/RoboCat/Src/InFlightPacket.cpp index 251abe36..f331142a 100644 --- a/RoboCat/Src/InFlightPacket.cpp +++ b/RoboCat/Src/InFlightPacket.cpp @@ -1,4 +1,5 @@ #include "RoboCatPCH.h" +#include "InFlightPacket.h" InFlightPacket::InFlightPacket( PacketSequenceNumber inSequenceNumber ) : mSequenceNumber( inSequenceNumber ), diff --git a/RoboCat/Src/PlayerController.cpp b/RoboCat/Src/PlayerController.cpp index ab330883..f993204a 100644 --- a/RoboCat/Src/PlayerController.cpp +++ b/RoboCat/Src/PlayerController.cpp @@ -29,6 +29,15 @@ PlayerController::PlayerController(const int networkID, GraphicsLibrary* graphic mMoveSpeed = moveSpeed; } +void PlayerController::HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager) const +{ + inDeliveryNotificationManager-> +} +void PlayerController::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const +{ + inDeliveryNotificationManager-> +} + PlayerController::~PlayerController() { //Key down diff --git a/RoboCat/Src/Rock.cpp b/RoboCat/Src/Rock.cpp index 9b1c062e..723ae023 100644 --- a/RoboCat/Src/Rock.cpp +++ b/RoboCat/Src/Rock.cpp @@ -13,6 +13,15 @@ Rock::Rock(const int networkID, GraphicsLibrary* graphicsLibrary, pair +} +void Rock::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const +{ + inDeliveryNotificationManager-> +} + Rock::~Rock() { diff --git a/RoboCat/Src/Timing.cpp b/RoboCat/Src/Timing.cpp index 9a8be86c..114a8365 100644 --- a/RoboCat/Src/Timing.cpp +++ b/RoboCat/Src/Timing.cpp @@ -1,5 +1,5 @@ #include "RoboCatPCH.h" - +#include "Timing.h" #if !_WIN32 #include diff --git a/RoboCat/Src/Wall.cpp b/RoboCat/Src/Wall.cpp index 03f446aa..588ff667 100644 --- a/RoboCat/Src/Wall.cpp +++ b/RoboCat/Src/Wall.cpp @@ -21,6 +21,15 @@ Wall::Wall(const int networkID, GraphicsLibrary* graphicsLibrary, pair +} +void Wall::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const +{ + inDeliveryNotificationManager-> +} + Wall::~Wall() { diff --git a/TO-DO.txt b/TO-DO.txt index 1abcb771..4afa4dec 100644 --- a/TO-DO.txt +++ b/TO-DO.txt @@ -23,7 +23,6 @@ Notes and Ideas: . Sync PacketSequenceNumbers on server and client! --> Not needed I don't think -. Change packets to UDP . Handle success or failure in TransmissionData (make GameObject derived classes also derive from and override functions) ----------------------------------------------------------------- From 0b2d207769e56cdc0958b7f95a578a7b871ebc4d Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Tue, 12 Apr 2022 17:27:57 -0400 Subject: [PATCH 15/56] Fixed errors in DeliveryNotificationManager.cpp. --- RoboCat/Inc/DeliveryNotificationManager.h | 7 +------ RoboCat/Src/DeliveryNotificationManager.cpp | 3 +-- RoboCat/Src/PlayerController.cpp | 1 + RoboCat/Src/Rock.cpp | 1 + RoboCat/Src/Wall.cpp | 1 + 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/RoboCat/Inc/DeliveryNotificationManager.h b/RoboCat/Inc/DeliveryNotificationManager.h index 5e83d070..7501ab1c 100644 --- a/RoboCat/Inc/DeliveryNotificationManager.h +++ b/RoboCat/Inc/DeliveryNotificationManager.h @@ -1,8 +1,3 @@ -#include "InFlightPacket.h" -#include "MemoryBitStream.h" -#include "Timing.h" -#include "TransmissionData.h" - class DeliveryNotificationManager { public: @@ -27,7 +22,7 @@ class DeliveryNotificationManager InFlightPacket* WriteSequenceNumber( OutputMemoryBitStream& inOutputStream ); - void WriteAckData(OutputMemoryBitStream& inOutputStream) const; + void WriteAckData(OutputMemoryBitStream& inOutputStream); //returns wether to drop the packet- if sequence number is too low! bool ProcessSequenceNumber( InputMemoryBitStream& inInputStream ); diff --git a/RoboCat/Src/DeliveryNotificationManager.cpp b/RoboCat/Src/DeliveryNotificationManager.cpp index 427a7308..edc88d3b 100644 --- a/RoboCat/Src/DeliveryNotificationManager.cpp +++ b/RoboCat/Src/DeliveryNotificationManager.cpp @@ -1,5 +1,4 @@ #include "RoboCatPCH.h" -#include "DeliveryNotificationManager.h" namespace { @@ -49,7 +48,7 @@ InFlightPacket* DeliveryNotificationManager::WriteSequenceNumber( OutputMemoryBi } } -void DeliveryNotificationManager::WriteAckData(OutputMemoryBitStream& inOutputStream) const +void DeliveryNotificationManager::WriteAckData(OutputMemoryBitStream& inOutputStream) { //we usually will only have one packet to ack //so we'll follow that with a 0 bit if that's the case diff --git a/RoboCat/Src/PlayerController.cpp b/RoboCat/Src/PlayerController.cpp index f993204a..08dc72c7 100644 --- a/RoboCat/Src/PlayerController.cpp +++ b/RoboCat/Src/PlayerController.cpp @@ -33,6 +33,7 @@ void PlayerController::HandleDeliveryFailure(DeliveryNotificationManager* inDeli { inDeliveryNotificationManager-> } + void PlayerController::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const { inDeliveryNotificationManager-> diff --git a/RoboCat/Src/Rock.cpp b/RoboCat/Src/Rock.cpp index 723ae023..0111838e 100644 --- a/RoboCat/Src/Rock.cpp +++ b/RoboCat/Src/Rock.cpp @@ -17,6 +17,7 @@ void Rock::HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotifica { inDeliveryNotificationManager-> } + void Rock::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const { inDeliveryNotificationManager-> diff --git a/RoboCat/Src/Wall.cpp b/RoboCat/Src/Wall.cpp index 588ff667..7c0e5f39 100644 --- a/RoboCat/Src/Wall.cpp +++ b/RoboCat/Src/Wall.cpp @@ -25,6 +25,7 @@ void Wall::HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotifica { inDeliveryNotificationManager-> } + void Wall::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const { inDeliveryNotificationManager-> From 5d288e07e75762188504d0b02cafcd85dca8bfff Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Tue, 12 Apr 2022 17:30:40 -0400 Subject: [PATCH 16/56] Changed send and receive in main to use the UDP functions. Saving before changing connect method to use new UDP stuff too. --- RoboCat/Src/Main.cpp | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index ea088fa0..99e06cd7 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -117,16 +117,19 @@ void start() networkID++; //Send it out - pNetworkManager->sendGameObjectState(pPlayerController->getNetworkID(), PacketType::PACKET_CREATE); + //pNetworkManager->sendGameObjectState(pPlayerController->getNetworkID(), PacketType::PACKET_CREATE); + pNetworkManager->sendGameObjectStateUDP(pPlayerController->getNetworkID(), PacketType::PACKET_CREATE); //Get client player - pNetworkManager->receiveGameObjectState(); + //pNetworkManager->receiveGameObjectState(); + pNetworkManager->receiveGameObjectStateUDP(); } //If client else if (networkID == 1) { //Get server player - pNetworkManager->receiveGameObjectState(); + //pNetworkManager->receiveGameObjectState(); + pNetworkManager->receiveGameObjectStateUDP(); //Spawn player pPlayerController = new PlayerController(networkID, pGraphics, startingPlayerPos, playerMoveSpeed, PLAYER_SPRITE_IDENTIFIER); @@ -134,7 +137,8 @@ void start() networkID++; //Send it out - pNetworkManager->sendGameObjectState(pPlayerController->getNetworkID(), PacketType::PACKET_CREATE); + //pNetworkManager->sendGameObjectState(pPlayerController->getNetworkID(), PacketType::PACKET_CREATE); + pNetworkManager->sendGameObjectStateUDP(pPlayerController->getNetworkID(), PacketType::PACKET_CREATE); } al_start_timer(timer); @@ -163,7 +167,8 @@ void update() pair mousePos = std::make_pair(pInput->getMouseX(), pInput->getMouseY()); gameObjectToSpawn = dynamic_cast(new Rock(networkID, pGraphics, mousePos, ROCK_SPRITE_IDENTIFIER)); pNetworkManager->addGameObject(gameObjectToSpawn, networkID); - pNetworkManager->sendGameObjectState(networkID, PacketType::PACKET_CREATE); + //pNetworkManager->sendGameObjectState(networkID, PacketType::PACKET_CREATE); + pNetworkManager->sendGameObjectStateUDP(networkID, PacketType::PACKET_CREATE); networkID++; break; @@ -173,7 +178,8 @@ void update() pair mousePos = std::make_pair(pInput->getMouseX(), pInput->getMouseY()); gameObjectToSpawn = dynamic_cast(new Wall(networkID, pGraphics, mousePos, wallSizeX, wallSizeY, wallColour, wallBorderThickness)); pNetworkManager->addGameObject(gameObjectToSpawn, networkID); - pNetworkManager->sendGameObjectState(networkID, PacketType::PACKET_CREATE); + //pNetworkManager->sendGameObjectState(networkID, PacketType::PACKET_CREATE); + pNetworkManager->sendGameObjectStateUDP(networkID, PacketType::PACKET_CREATE); networkID++; break; @@ -261,7 +267,8 @@ void update() //Players have IDs 0 and 1, DO NOT TOUCH THEM if (networkID > 1) { - pNetworkManager->sendGameObjectState(networkID - 1, PacketType::PACKET_DELETE); + //pNetworkManager->sendGameObjectState(networkID - 1, PacketType::PACKET_DELETE); + pNetworkManager->sendGameObjectStateUDP(networkID - 1, PacketType::PACKET_DELETE); networkID--; } @@ -416,10 +423,12 @@ int main(int argc, const char** argv) update(); //Network updates - send player data - pNetworkManager->sendGameObjectState(pPlayerController->getNetworkID(), PacketType::PACKET_UPDATE); + //pNetworkManager->sendGameObjectState(pPlayerController->getNetworkID(), PacketType::PACKET_UPDATE); + pNetworkManager->sendGameObjectStateUDP(pPlayerController->getNetworkID(), PacketType::PACKET_UPDATE); //Network update - receive packets - packetTypeReceived = pNetworkManager->receiveGameObjectState(); + //packetTypeReceived = pNetworkManager->receiveGameObjectState(); + packetTypeReceived = pNetworkManager->receiveGameObjectStateUDP(); //Network check for and deal with timed out packets pNetworkManager->checkTimedOutPackets(); From 1b7ccd8144a5e6e63e2fdcc19b8029d94f7d25cc Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Tue, 12 Apr 2022 17:34:00 -0400 Subject: [PATCH 17/56] Updated code for connection. Will update instructions txt file. --- RoboCat/Src/Main.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index 99e06cd7..cb148096 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -366,12 +366,18 @@ int main(int argc, const char** argv) { //-------------------------Server code------------------------- + //Prompt for client IP address + std::string clientIP; + std::cout << "Enter client IP address: \n"; + std::cin >> clientIP; + //Prompt for port number std::string portNumber; std::cout << "Enter port number: \n"; std::cin >> portNumber; - bHasConnectd = pNetworkManager->initServer(portNumber); + //bHasConnectd = pNetworkManager->initServer(portNumber); + bHasConnectd = pNetworkManager->connectUDP(clientIP, portNumber); if (bHasConnectd) std::cout << "main.cpp --> server initted.\n"; @@ -393,7 +399,8 @@ int main(int argc, const char** argv) std::cout << "Enter port number: \n"; std::cin >> portNumber; - bHasConnectd = pNetworkManager->connect(serverIP, portNumber); + //bHasConnectd = pNetworkManager->connect(serverIP, portNumber); + bHasConnectd = pNetworkManager->connectUDP(serverIP, portNumber); if (bHasConnectd) std::cout << "main.cpp --> client connected.\n"; From 880e4b64f64da7833fb5d310b781696cdb6ab58b Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Wed, 13 Apr 2022 17:10:00 -0400 Subject: [PATCH 18/56] Only trying to resend packets on failure in Wall and Rock, success needs no code. Added comments indicating reasoning for not resending in PlayerController (only latest data matters since sending update packet each frame). Saving before trying to make it resend rock and wall packets. --- RoboCat/Src/PlayerController.cpp | 4 ++-- RoboCat/Src/Rock.cpp | 3 ++- RoboCat/Src/Wall.cpp | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/RoboCat/Src/PlayerController.cpp b/RoboCat/Src/PlayerController.cpp index 08dc72c7..f5462cf6 100644 --- a/RoboCat/Src/PlayerController.cpp +++ b/RoboCat/Src/PlayerController.cpp @@ -31,12 +31,12 @@ PlayerController::PlayerController(const int networkID, GraphicsLibrary* graphic void PlayerController::HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager) const { - inDeliveryNotificationManager-> + //Do nothing, the data will be stale if re-sent, since it is sending update packets every frame (only latest data matters) } void PlayerController::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const { - inDeliveryNotificationManager-> + //Cool, already taken care of } PlayerController::~PlayerController() diff --git a/RoboCat/Src/Rock.cpp b/RoboCat/Src/Rock.cpp index 0111838e..4ac5e8b0 100644 --- a/RoboCat/Src/Rock.cpp +++ b/RoboCat/Src/Rock.cpp @@ -15,12 +15,13 @@ Rock::Rock(const int networkID, GraphicsLibrary* graphicsLibrary, pair } void Rock::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const { - inDeliveryNotificationManager-> + //Cool, already taken care of } Rock::~Rock() diff --git a/RoboCat/Src/Wall.cpp b/RoboCat/Src/Wall.cpp index 7c0e5f39..1f88db7a 100644 --- a/RoboCat/Src/Wall.cpp +++ b/RoboCat/Src/Wall.cpp @@ -23,12 +23,13 @@ Wall::Wall(const int networkID, GraphicsLibrary* graphicsLibrary, pair } void Wall::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const { - inDeliveryNotificationManager-> + //Cool, already taken care of } Wall::~Wall() From c66945f3cbe7e0d3cf642c7d13c59498eca0e130 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Wed, 13 Apr 2022 17:23:41 -0400 Subject: [PATCH 19/56] Added functionality to resend packets (but packet type is hard-coded, needs to change to account for deletion too, because now it only sends create packets for rock and wall). Needs improvements to make it work with all packet types. Also creatd an error in DelNotMan.cpp. --- RoboCat/Inc/DeliveryNotificationManager.h | 7 ++++++- RoboCat/Src/DeliveryNotificationManager.cpp | 12 ++++++++++-- RoboCat/Src/Rock.cpp | 4 ++-- RoboCat/Src/Wall.cpp | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/RoboCat/Inc/DeliveryNotificationManager.h b/RoboCat/Inc/DeliveryNotificationManager.h index 7501ab1c..19e13834 100644 --- a/RoboCat/Inc/DeliveryNotificationManager.h +++ b/RoboCat/Inc/DeliveryNotificationManager.h @@ -1,14 +1,17 @@ +#include "Networker.h" + class DeliveryNotificationManager { public: - DeliveryNotificationManager( bool inShouldSendAcks, bool inShouldProcessAcks ); + DeliveryNotificationManager(bool inShouldSendAcks, bool inShouldProcessAcks, Networker* pNetworker); ~DeliveryNotificationManager(); inline InFlightPacket* WriteState( OutputMemoryBitStream& inOutputStream ); inline bool ReadAndProcessState( InputMemoryBitStream& inInputStream ); + void ResendPacket(const int ID, PacketType packetHeader); void ProcessTimedOutPackets(); uint32_t GetDroppedPacketCount() const { return mDroppedPacketCount; } @@ -46,6 +49,8 @@ class DeliveryNotificationManager uint32_t mDroppedPacketCount; uint32_t mDispatchedPacketCount; + //Networker + Networker* mpNetworker; }; diff --git a/RoboCat/Src/DeliveryNotificationManager.cpp b/RoboCat/Src/DeliveryNotificationManager.cpp index edc88d3b..1cba1e71 100644 --- a/RoboCat/Src/DeliveryNotificationManager.cpp +++ b/RoboCat/Src/DeliveryNotificationManager.cpp @@ -5,7 +5,7 @@ namespace const float kDelayBeforeAckTimeout = 0.5f; } -DeliveryNotificationManager::DeliveryNotificationManager( bool inShouldSendAcks, bool inShouldProcessAcks ) : +DeliveryNotificationManager::DeliveryNotificationManager(bool inShouldSendAcks, bool inShouldProcessAcks, Networker* pNetworker) : mNextOutgoingSequenceNumber( 0 ), mNextExpectedSequenceNumber( 0 ), //everybody starts at 0... @@ -13,7 +13,8 @@ mShouldSendAcks( inShouldSendAcks ), mShouldProcessAcks( inShouldProcessAcks ), mDeliveredPacketCount( 0 ), mDroppedPacketCount( 0 ), -mDispatchedPacketCount( 0 ) +mDispatchedPacketCount( 0 ), +mpNetworker(pNetworker) { } @@ -21,6 +22,8 @@ mDispatchedPacketCount( 0 ) //we're going away- log how well we did... DeliveryNotificationManager::~DeliveryNotificationManager() { + //Cleanup networker pointer (DO NOT DELETE, HAPPENS ELSEWHERE) + mpNetworker = nullptr; LOG( "DNM destructor. Delivery rate %d%%, Drop rate %d%%", ( 100 * mDeliveredPacketCount ) / mDispatchedPacketCount, ( 100 * mDroppedPacketCount ) / mDispatchedPacketCount ); @@ -160,6 +163,11 @@ void DeliveryNotificationManager::ProcessAcks( InputMemoryBitStream& inInputStre } } +void DeliveryNotificationManager::ResendPacket(const int ID, PacketType packetHeader) +{ + mpNetworker->sendGameObjectStateUDP(ID, packetHeader); +} + void DeliveryNotificationManager::ProcessTimedOutPackets() { float timeoutTime = Timing::sInstance.GetTimef() - kDelayBeforeAckTimeout; diff --git a/RoboCat/Src/Rock.cpp b/RoboCat/Src/Rock.cpp index 4ac5e8b0..3bc513ff 100644 --- a/RoboCat/Src/Rock.cpp +++ b/RoboCat/Src/Rock.cpp @@ -15,8 +15,8 @@ Rock::Rock(const int networkID, GraphicsLibrary* graphicsLibrary, pair + //Re-send create packet (only packet type we send for this GameObject) + inDeliveryNotificationManager->ResendPacket(mNetworkID, PacketType::PACKET_CREATE); } void Rock::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const diff --git a/RoboCat/Src/Wall.cpp b/RoboCat/Src/Wall.cpp index 1f88db7a..fc76c326 100644 --- a/RoboCat/Src/Wall.cpp +++ b/RoboCat/Src/Wall.cpp @@ -23,8 +23,8 @@ Wall::Wall(const int networkID, GraphicsLibrary* graphicsLibrary, pair + //Re-send create packet (only packet type we send for this GameObject) + inDeliveryNotificationManager->ResendPacket(mNetworkID, PacketType::PACKET_CREATE); } void Wall::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const From b172ca946e4102c3c8cd0f563455a36002822e4b Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Wed, 13 Apr 2022 17:47:00 -0400 Subject: [PATCH 20/56] Added reference to sequence number and finding the packet to resend, but need to figure out what the packet type was. Saving progress. --- RoboCat/Inc/DeliveryNotificationManager.h | 54 ++++++++++----------- RoboCat/Inc/InFlightPacket.h | 4 +- RoboCat/Inc/PlayerController.h | 4 +- RoboCat/Inc/Rock.h | 4 +- RoboCat/Inc/TransmissionData.h | 4 +- RoboCat/Inc/Wall.h | 4 +- RoboCat/Src/DeliveryNotificationManager.cpp | 32 ++++++++---- RoboCat/Src/InFlightPacket.cpp | 8 +-- RoboCat/Src/Networker.cpp | 2 +- RoboCat/Src/PlayerController.cpp | 4 +- RoboCat/Src/Rock.cpp | 8 +-- RoboCat/Src/Wall.cpp | 8 +-- 12 files changed, 73 insertions(+), 63 deletions(-) diff --git a/RoboCat/Inc/DeliveryNotificationManager.h b/RoboCat/Inc/DeliveryNotificationManager.h index 19e13834..36e158e0 100644 --- a/RoboCat/Inc/DeliveryNotificationManager.h +++ b/RoboCat/Inc/DeliveryNotificationManager.h @@ -4,50 +4,46 @@ class DeliveryNotificationManager { public: - DeliveryNotificationManager(bool inShouldSendAcks, bool inShouldProcessAcks, Networker* pNetworker); ~DeliveryNotificationManager(); - inline InFlightPacket* WriteState( OutputMemoryBitStream& inOutputStream ); - inline bool ReadAndProcessState( InputMemoryBitStream& inInputStream ); + inline InFlightPacket* WriteState(OutputMemoryBitStream& inOutputStream); + inline bool ReadAndProcessState(InputMemoryBitStream& inInputStream); - void ResendPacket(const int ID, PacketType packetHeader); - void ProcessTimedOutPackets(); + void ResendPacket(const int ID, const PacketSequenceNumber packetSequenceNum); + void ProcessTimedOutPackets(); - uint32_t GetDroppedPacketCount() const { return mDroppedPacketCount; } - uint32_t GetDeliveredPacketCount() const { return mDeliveredPacketCount; } - uint32_t GetDispatchedPacketCount() const { return mDispatchedPacketCount; } + uint32_t GetDroppedPacketCount() const { return mDroppedPacketCount; } + uint32_t GetDeliveredPacketCount() const { return mDeliveredPacketCount; } + uint32_t GetDispatchedPacketCount() const { return mDispatchedPacketCount; } - const deque< InFlightPacket >& GetInFlightPackets() const { return mInFlightPackets; } + const deque& GetInFlightPackets() const { return mInFlightPackets; } private: - - - - InFlightPacket* WriteSequenceNumber( OutputMemoryBitStream& inOutputStream ); - void WriteAckData(OutputMemoryBitStream& inOutputStream); + + InFlightPacket* WriteSequenceNumber(OutputMemoryBitStream& inOutputStream); + void WriteAckData(OutputMemoryBitStream& inOutputStream); //returns wether to drop the packet- if sequence number is too low! - bool ProcessSequenceNumber( InputMemoryBitStream& inInputStream ); - void ProcessAcks( InputMemoryBitStream& inInputStream ); - + bool ProcessSequenceNumber(InputMemoryBitStream& inInputStream); + void ProcessAcks(InputMemoryBitStream& inInputStream); - void AddPendingAck( PacketSequenceNumber inSequenceNumber ); - void HandlePacketDeliveryFailure( const InFlightPacket& inFlightPacket ); - void HandlePacketDeliverySuccess( const InFlightPacket& inFlightPacket ); + void AddPendingAck(PacketSequenceNumber inSequenceNumber); + void HandlePacketDeliveryFailure(const InFlightPacket& inFlightPacket, const PacketSequenceNumber packetSequenceNum); + void HandlePacketDeliverySuccess(const InFlightPacket& inFlightPacket, const PacketSequenceNumber packetSequenceNum); - PacketSequenceNumber mNextOutgoingSequenceNumber; - PacketSequenceNumber mNextExpectedSequenceNumber; + PacketSequenceNumber mNextOutgoingSequenceNumber; + PacketSequenceNumber mNextExpectedSequenceNumber; - deque< InFlightPacket > mInFlightPackets; - deque< AckRange > mPendingAcks; + deque mInFlightPackets; + deque mPendingAcks; - bool mShouldSendAcks; - bool mShouldProcessAcks; + bool mShouldSendAcks; + bool mShouldProcessAcks; - uint32_t mDeliveredPacketCount; - uint32_t mDroppedPacketCount; - uint32_t mDispatchedPacketCount; + uint32_t mDeliveredPacketCount; + uint32_t mDroppedPacketCount; + uint32_t mDispatchedPacketCount; //Networker Networker* mpNetworker; diff --git a/RoboCat/Inc/InFlightPacket.h b/RoboCat/Inc/InFlightPacket.h index 494e097e..b510fe50 100644 --- a/RoboCat/Inc/InFlightPacket.h +++ b/RoboCat/Inc/InFlightPacket.h @@ -22,8 +22,8 @@ class InFlightPacket return ( it != mTransmissionDataMap.end() ) ? it->second : nullptr; } - void HandleDeliveryFailure( DeliveryNotificationManager* inDeliveryNotificationManager ) const; - void HandleDeliverySuccess( DeliveryNotificationManager* inDeliveryNotificationManager ) const; + void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; + void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; private: PacketSequenceNumber mSequenceNumber; diff --git a/RoboCat/Inc/PlayerController.h b/RoboCat/Inc/PlayerController.h index 02baae88..87fe0881 100644 --- a/RoboCat/Inc/PlayerController.h +++ b/RoboCat/Inc/PlayerController.h @@ -32,8 +32,8 @@ class PlayerController : public GameObject, public TransmissionData PlayerController(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, float moveSpeed, const std::string spriteIdentifier); //Overloaded functions - void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager) const; - void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const; + void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; + void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; //Destructor ~PlayerController(); diff --git a/RoboCat/Inc/Rock.h b/RoboCat/Inc/Rock.h index fbe62015..8e7e2302 100644 --- a/RoboCat/Inc/Rock.h +++ b/RoboCat/Inc/Rock.h @@ -21,8 +21,8 @@ class Rock : public GameObject, public TransmissionData Rock(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, const std::string spriteIdentifier); //Overloaded functions - void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager) const; - void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const; + void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; + void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; //Destructor ~Rock(); diff --git a/RoboCat/Inc/TransmissionData.h b/RoboCat/Inc/TransmissionData.h index cc599ec0..30b44417 100644 --- a/RoboCat/Inc/TransmissionData.h +++ b/RoboCat/Inc/TransmissionData.h @@ -3,7 +3,7 @@ class DeliveryNotificationManager; class TransmissionData { public: - virtual void HandleDeliveryFailure( DeliveryNotificationManager* inDeliveryNotificationManager ) const = 0; - virtual void HandleDeliverySuccess( DeliveryNotificationManager* inDeliveryNotificationManager ) const = 0; + virtual void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const = 0; + virtual void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const = 0; }; typedef shared_ptr< TransmissionData > TransmissionDataPtr; \ No newline at end of file diff --git a/RoboCat/Inc/Wall.h b/RoboCat/Inc/Wall.h index baa31773..b11c9007 100644 --- a/RoboCat/Inc/Wall.h +++ b/RoboCat/Inc/Wall.h @@ -25,8 +25,8 @@ class Wall : public GameObject, public TransmissionData Wall(const int networkID, GraphicsLibrary* graphicsLibrary, pair position, float sizeX, float sizeY, Colour colour, float thickness); //Overloaded functions - void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager) const; - void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const; + void HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; + void HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const; //Destructor ~Wall(); diff --git a/RoboCat/Src/DeliveryNotificationManager.cpp b/RoboCat/Src/DeliveryNotificationManager.cpp index 1cba1e71..9cf98b86 100644 --- a/RoboCat/Src/DeliveryNotificationManager.cpp +++ b/RoboCat/Src/DeliveryNotificationManager.cpp @@ -140,14 +140,15 @@ void DeliveryNotificationManager::ProcessAcks( InputMemoryBitStream& inInputStre PacketSequenceNumber nextInFlightPacketSequenceNumber = nextInFlightPacket.GetSequenceNumber(); if( nextInFlightPacketSequenceNumber < nextAckdSequenceNumber ) { + HandlePacketDeliveryFailure(nextInFlightPacket, nextInFlightPacketSequenceNumber); + //copy this so we can remove it before handling the failure- we don't want to find it when checking for state auto copyOfInFlightPacket = nextInFlightPacket; mInFlightPackets.pop_front(); - HandlePacketDeliveryFailure( copyOfInFlightPacket ); } else if( nextInFlightPacketSequenceNumber == nextAckdSequenceNumber ) { - HandlePacketDeliverySuccess( nextInFlightPacket ); + HandlePacketDeliverySuccess(nextInFlightPacket, nextInFlightPacketSequenceNumber); //received! mInFlightPackets.pop_front(); //decrement count, advance nextAckdSequenceNumber @@ -163,9 +164,22 @@ void DeliveryNotificationManager::ProcessAcks( InputMemoryBitStream& inInputStre } } -void DeliveryNotificationManager::ResendPacket(const int ID, PacketType packetHeader) +void DeliveryNotificationManager::ResendPacket(const int ID, const PacketSequenceNumber packetSequenceNum) { - mpNetworker->sendGameObjectStateUDP(ID, packetHeader); + //Find packet header type + PacketType packetHeader; + for (auto& it = mInFlightPackets.begin(); it != mInFlightPackets.end(); ++it) + { + if (it->GetSequenceNumber() == packetSequenceNum) + { + packetHeader = it->GetTransmissionData(); + mpNetworker->sendGameObjectStateUDP(ID, packetHeader); + return; + } + } + + //Error cout if it fails + std::cout << "ERROR: COULD NOT RESEND PACKET (DeliveryNotificationManager::ResendPacket(...)\n"; } void DeliveryNotificationManager::ProcessTimedOutPackets() @@ -180,7 +194,7 @@ void DeliveryNotificationManager::ProcessTimedOutPackets() if( nextInFlightPacket.GetTimeDispatched() < timeoutTime ) { //it failed! let us know about that - HandlePacketDeliveryFailure( nextInFlightPacket ); + HandlePacketDeliveryFailure(nextInFlightPacket, nextInFlightPacket.GetSequenceNumber()); mInFlightPackets.pop_front(); } else @@ -202,16 +216,16 @@ void DeliveryNotificationManager::AddPendingAck( PacketSequenceNumber inSequence } -void DeliveryNotificationManager::HandlePacketDeliveryFailure( const InFlightPacket& inFlightPacket ) +void DeliveryNotificationManager::HandlePacketDeliveryFailure(const InFlightPacket& inFlightPacket, const PacketSequenceNumber packetSequenceNum) { ++mDroppedPacketCount; - inFlightPacket.HandleDeliveryFailure( this ); + inFlightPacket.HandleDeliveryFailure(this, packetSequenceNum); } -void DeliveryNotificationManager::HandlePacketDeliverySuccess( const InFlightPacket& inFlightPacket ) +void DeliveryNotificationManager::HandlePacketDeliverySuccess(const InFlightPacket& inFlightPacket, const PacketSequenceNumber packetSequenceNum) { ++mDeliveredPacketCount; - inFlightPacket.HandleDeliverySuccess( this ); + inFlightPacket.HandleDeliverySuccess(this, packetSequenceNum); } diff --git a/RoboCat/Src/InFlightPacket.cpp b/RoboCat/Src/InFlightPacket.cpp index f331142a..bc849419 100644 --- a/RoboCat/Src/InFlightPacket.cpp +++ b/RoboCat/Src/InFlightPacket.cpp @@ -9,18 +9,18 @@ mTimeDispatched( Timing::sInstance.GetTimef() ) } -void InFlightPacket::HandleDeliveryFailure( DeliveryNotificationManager* inDeliveryNotificationManager ) const +void InFlightPacket::HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const { for( const auto& pair : mTransmissionDataMap ) { - pair.second->HandleDeliveryFailure( inDeliveryNotificationManager ); + pair.second->HandleDeliveryFailure(inDeliveryNotificationManager, packetSequenceNum); } } -void InFlightPacket::HandleDeliverySuccess( DeliveryNotificationManager* inDeliveryNotificationManager ) const +void InFlightPacket::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const { for( const auto& pair : mTransmissionDataMap ) { - pair.second->HandleDeliverySuccess( inDeliveryNotificationManager ); + pair.second->HandleDeliverySuccess(inDeliveryNotificationManager, packetSequenceNum); } } \ No newline at end of file diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 475503bd..9ca7b910 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -25,7 +25,7 @@ void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIde mpUDPSocket = new UDPSocketPtr; mGameObjectsVec = std::vector>(); //mPacketQueue = std::queue>(); - pDeliveryNotificationManager = new DeliveryNotificationManager(true, true); + pDeliveryNotificationManager = new DeliveryNotificationManager(true, true, this); mOutputBitStreamQueue = std::priority_queue, std::vector>, std::greater>>(); //Data for GameObject replication diff --git a/RoboCat/Src/PlayerController.cpp b/RoboCat/Src/PlayerController.cpp index f5462cf6..52020c7f 100644 --- a/RoboCat/Src/PlayerController.cpp +++ b/RoboCat/Src/PlayerController.cpp @@ -29,12 +29,12 @@ PlayerController::PlayerController(const int networkID, GraphicsLibrary* graphic mMoveSpeed = moveSpeed; } -void PlayerController::HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager) const +void PlayerController::HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const { //Do nothing, the data will be stale if re-sent, since it is sending update packets every frame (only latest data matters) } -void PlayerController::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const +void PlayerController::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const { //Cool, already taken care of } diff --git a/RoboCat/Src/Rock.cpp b/RoboCat/Src/Rock.cpp index 3bc513ff..ac5e2595 100644 --- a/RoboCat/Src/Rock.cpp +++ b/RoboCat/Src/Rock.cpp @@ -13,13 +13,13 @@ Rock::Rock(const int networkID, GraphicsLibrary* graphicsLibrary, pairResendPacket(mNetworkID, PacketType::PACKET_CREATE); + //Re-send packet + inDeliveryNotificationManager->ResendPacket(mNetworkID, packetSequenceNum); } -void Rock::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const +void Rock::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const { //Cool, already taken care of } diff --git a/RoboCat/Src/Wall.cpp b/RoboCat/Src/Wall.cpp index fc76c326..9b36e3ee 100644 --- a/RoboCat/Src/Wall.cpp +++ b/RoboCat/Src/Wall.cpp @@ -21,13 +21,13 @@ Wall::Wall(const int networkID, GraphicsLibrary* graphicsLibrary, pairResendPacket(mNetworkID, PacketType::PACKET_CREATE); + //Re-send packet + inDeliveryNotificationManager->ResendPacket(mNetworkID, packetSequenceNum); } -void Wall::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager) const +void Wall::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const { //Cool, already taken care of } From 6407818fac8affdaf42e451ea00c7790937c7dea Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Wed, 13 Apr 2022 23:03:19 -0400 Subject: [PATCH 21/56] Decrementing network ID when receiving a delete packet. --- RoboCat/Src/Main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index cb148096..6418b430 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -444,6 +444,10 @@ int main(int argc, const char** argv) if (packetTypeReceived == PacketType::PACKET_CREATE) networkID++; + //If you receive a delete packet, decrement the network ID to keep spawning in sync! + if (packetTypeReceived == PacketType::PACKET_DELETE) + networkID--; + //Draw call draw(); } From 6f79e6db67c55ab38f3c6f974d3163c104f9dd12 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Thu, 14 Apr 2022 00:03:39 -0400 Subject: [PATCH 22/56] Work session WITH Alex over discord. Added code to get packet header type, we can now resend packets properly. Saving before testing. We also still have errors with this pointer in DeliveryNotificationManager.cpp. --- RoboCat/Inc/DeliveryNotificationManager.h | 8 ++- RoboCat/Src/DeliveryNotificationManager.cpp | 58 ++++++++++++++++----- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/RoboCat/Inc/DeliveryNotificationManager.h b/RoboCat/Inc/DeliveryNotificationManager.h index 36e158e0..acef0953 100644 --- a/RoboCat/Inc/DeliveryNotificationManager.h +++ b/RoboCat/Inc/DeliveryNotificationManager.h @@ -1,3 +1,5 @@ +#include + #include "Networker.h" class DeliveryNotificationManager @@ -17,7 +19,8 @@ class DeliveryNotificationManager uint32_t GetDeliveredPacketCount() const { return mDeliveredPacketCount; } uint32_t GetDispatchedPacketCount() const { return mDispatchedPacketCount; } - const deque& GetInFlightPackets() const { return mInFlightPackets; } + //const deque& GetInFlightPackets() const { return mInFlightPackets; } + const deque> GetInFlightPackets() const { return mInFlightPacketsPair; } private: @@ -35,7 +38,8 @@ class DeliveryNotificationManager PacketSequenceNumber mNextOutgoingSequenceNumber; PacketSequenceNumber mNextExpectedSequenceNumber; - deque mInFlightPackets; + //deque mInFlightPackets; + deque> mInFlightPacketsPair; deque mPendingAcks; bool mShouldSendAcks; diff --git a/RoboCat/Src/DeliveryNotificationManager.cpp b/RoboCat/Src/DeliveryNotificationManager.cpp index 9cf98b86..e2f90759 100644 --- a/RoboCat/Src/DeliveryNotificationManager.cpp +++ b/RoboCat/Src/DeliveryNotificationManager.cpp @@ -41,9 +41,11 @@ InFlightPacket* DeliveryNotificationManager::WriteSequenceNumber( OutputMemoryBi if( mShouldProcessAcks ) { - mInFlightPackets.emplace_back( sequenceNumber ); + //mInFlightPackets.emplace_back( sequenceNumber ); + mInFlightPacketsPair.emplace_back( std::make_pair(sequenceNumber, inOutputStream) ); - return &mInFlightPackets.back(); + //return &mInFlightPackets.back(); + return &mInFlightPacketsPair.back().first; } else { @@ -133,9 +135,11 @@ void DeliveryNotificationManager::ProcessAcks( InputMemoryBitStream& inInputStre //for each InflightPacket with a sequence number less than the start, handle delivery failure... PacketSequenceNumber nextAckdSequenceNumber = ackRange.GetStart(); uint32_t onePastAckdSequenceNumber = nextAckdSequenceNumber + ackRange.GetCount(); - while( nextAckdSequenceNumber < onePastAckdSequenceNumber && !mInFlightPackets.empty() ) + //while( nextAckdSequenceNumber < onePastAckdSequenceNumber && !mInFlightPackets.empty() ) + while( nextAckdSequenceNumber < onePastAckdSequenceNumber && !mInFlightPacketsPair.empty() ) { - const auto& nextInFlightPacket = mInFlightPackets.front(); + //const auto& nextInFlightPacket = mInFlightPackets.front(); + const auto& nextInFlightPacket = mInFlightPacketsPair.front().first; //if the packet has a lower sequence number, we didn't get an ack for it, so it probably wasn't delivered PacketSequenceNumber nextInFlightPacketSequenceNumber = nextInFlightPacket.GetSequenceNumber(); if( nextInFlightPacketSequenceNumber < nextAckdSequenceNumber ) @@ -144,13 +148,15 @@ void DeliveryNotificationManager::ProcessAcks( InputMemoryBitStream& inInputStre //copy this so we can remove it before handling the failure- we don't want to find it when checking for state auto copyOfInFlightPacket = nextInFlightPacket; - mInFlightPackets.pop_front(); + //mInFlightPackets.pop_front(); + mInFlightPacketsPair.pop_front(); } else if( nextInFlightPacketSequenceNumber == nextAckdSequenceNumber ) { HandlePacketDeliverySuccess(nextInFlightPacket, nextInFlightPacketSequenceNumber); //received! - mInFlightPackets.pop_front(); + //mInFlightPackets.pop_front(); + mInFlightPacketsPair.pop_front(); //decrement count, advance nextAckdSequenceNumber ++nextAckdSequenceNumber; } @@ -168,34 +174,60 @@ void DeliveryNotificationManager::ResendPacket(const int ID, const PacketSequenc { //Find packet header type PacketType packetHeader; - for (auto& it = mInFlightPackets.begin(); it != mInFlightPackets.end(); ++it) + //for (auto& it = mInFlightPackets.begin(); it != mInFlightPackets.end(); ++it) + for (auto& it = mInFlightPacketsPair.begin(); it != mInFlightPacketsPair.end(); ++it) { - if (it->GetSequenceNumber() == packetSequenceNum) + //If we found the inflight packet we want to re-send + if (it->first.GetSequenceNumber() == packetSequenceNum) { - packetHeader = it->GetTransmissionData(); + //Start reading packet until you find the PacketType + InputMemoryBitStream IMBStream = InputMemoryBitStream((char*)it->second.GetBufferPtr(), it->second.GetByteLength()); + + //Read junk data: 1) sequenceNumber, if shouldProcessAcks, 2) hasAcks, if hasAcks, 3) readAckRange + PacketSequenceNumber sequenceNumber; + IMBStream.Read(sequenceNumber); + + if (mShouldProcessAcks) + { + bool hasAcks; + IMBStream.Read(hasAcks); + + if (hasAcks) + { + AckRange ackRange; + ackRange.Read(IMBStream); + } + } + + //THEN we can read the rest of our packet, starting with PacketType + PacketType packetHeader; + IMBStream.Read(packetHeader); mpNetworker->sendGameObjectStateUDP(ID, packetHeader); return; } } //Error cout if it fails - std::cout << "ERROR: COULD NOT RESEND PACKET (DeliveryNotificationManager::ResendPacket(...)\n"; + std::cout << "ERROR: COULD NOT RESEND PACKET (DeliveryNotificationManager::ResendPacket()\n"; } void DeliveryNotificationManager::ProcessTimedOutPackets() { float timeoutTime = Timing::sInstance.GetTimef() - kDelayBeforeAckTimeout; - while( !mInFlightPackets.empty() ) + //while( !mInFlightPackets.empty() ) + while( !mInFlightPacketsPair.empty() ) { - const auto& nextInFlightPacket = mInFlightPackets.front(); + //const auto& nextInFlightPacket = mInFlightPackets.front(); + const auto& nextInFlightPacket = mInFlightPacketsPair.front().first; //was this packet dispatched before the current time minus the timeout duration? if( nextInFlightPacket.GetTimeDispatched() < timeoutTime ) { //it failed! let us know about that HandlePacketDeliveryFailure(nextInFlightPacket, nextInFlightPacket.GetSequenceNumber()); - mInFlightPackets.pop_front(); + //mInFlightPackets.pop_front(); + mInFlightPacketsPair.pop_front(); } else { From fb57337479adae16d2aaf27ffe2e394583150167 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Thu, 14 Apr 2022 00:26:51 -0400 Subject: [PATCH 23/56] The problem was most likely a circular include, so we commented out the include of Networker.h in DeliveryuNotificationManager.h. --- RoboCat/Inc/DeliveryNotificationManager.h | 3 ++- RoboCat/Src/DeliveryNotificationManager.cpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/RoboCat/Inc/DeliveryNotificationManager.h b/RoboCat/Inc/DeliveryNotificationManager.h index acef0953..b10c2286 100644 --- a/RoboCat/Inc/DeliveryNotificationManager.h +++ b/RoboCat/Inc/DeliveryNotificationManager.h @@ -1,6 +1,7 @@ #include -#include "Networker.h" +//#include "Networker.h" +class Networker; class DeliveryNotificationManager { diff --git a/RoboCat/Src/DeliveryNotificationManager.cpp b/RoboCat/Src/DeliveryNotificationManager.cpp index e2f90759..3707d529 100644 --- a/RoboCat/Src/DeliveryNotificationManager.cpp +++ b/RoboCat/Src/DeliveryNotificationManager.cpp @@ -1,4 +1,5 @@ #include "RoboCatPCH.h" +#include "Networker.h" namespace { From 33feebd7737a3f41bf6586fa86f5c47d23210a8e Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Thu, 14 Apr 2022 01:04:36 -0400 Subject: [PATCH 24/56] Fixed a few things to get rid of a few errors, but we still have hundreds of errors when building. Send help. --- RoboCat/Inc/TransmissionData.h | 2 ++ RoboCat/Src/InFlightPacket.cpp | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/RoboCat/Inc/TransmissionData.h b/RoboCat/Inc/TransmissionData.h index 30b44417..9ced30d7 100644 --- a/RoboCat/Inc/TransmissionData.h +++ b/RoboCat/Inc/TransmissionData.h @@ -1,3 +1,5 @@ +typedef uint16_t PacketSequenceNumber; + class DeliveryNotificationManager; class TransmissionData diff --git a/RoboCat/Src/InFlightPacket.cpp b/RoboCat/Src/InFlightPacket.cpp index bc849419..13d4fc67 100644 --- a/RoboCat/Src/InFlightPacket.cpp +++ b/RoboCat/Src/InFlightPacket.cpp @@ -11,16 +11,20 @@ mTimeDispatched( Timing::sInstance.GetTimef() ) void InFlightPacket::HandleDeliveryFailure(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const { - for( const auto& pair : mTransmissionDataMap ) + //for( const auto& pair : mTransmissionDataMap ) + for (auto pair = mTransmissionDataMap.begin(); pair != mTransmissionDataMap.end(); pair++) { - pair.second->HandleDeliveryFailure(inDeliveryNotificationManager, packetSequenceNum); + //pair.second->HandleDeliveryFailure(inDeliveryNotificationManager, packetSequenceNum); + pair->second->HandleDeliveryFailure(inDeliveryNotificationManager, packetSequenceNum); } } void InFlightPacket::HandleDeliverySuccess(DeliveryNotificationManager* inDeliveryNotificationManager, const PacketSequenceNumber packetSequenceNum) const { - for( const auto& pair : mTransmissionDataMap ) + //for( const auto& pair : mTransmissionDataMap ) + for (auto pair = mTransmissionDataMap.begin(); pair != mTransmissionDataMap.end(); pair++) { - pair.second->HandleDeliverySuccess(inDeliveryNotificationManager, packetSequenceNum); + //pair.second->HandleDeliverySuccess(inDeliveryNotificationManager, packetSequenceNum); + pair->second->HandleDeliverySuccess(inDeliveryNotificationManager, packetSequenceNum); } } \ No newline at end of file From 04c7a867f8e080ac6a696bf0a02f5d017df4d9b8 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Thu, 14 Apr 2022 15:24:46 -0400 Subject: [PATCH 25/56] NO ERRORS. --- RoboCat/Inc/AckRange.h | 2 ++ RoboCat/Inc/DeliveryNotificationManager.h | 24 ++++++++++++++++++--- RoboCat/Inc/InFlightPacket.h | 6 ++++++ RoboCat/Inc/MemoryBitStream.h | 2 ++ RoboCat/Inc/Networker.h | 13 ++++++++++- RoboCat/Inc/OutputWindow.h | 1 + RoboCat/Inc/PlayerController.h | 1 + RoboCat/Inc/RoboMath.h | 2 ++ RoboCat/Inc/SocketAddress.h | 2 ++ RoboCat/Inc/SocketAddressFactory.h | 2 +- RoboCat/Inc/SocketUtil.h | 2 ++ RoboCat/Inc/TCPSocket.h | 2 ++ RoboCat/Inc/Timing.h | 2 ++ RoboCat/Inc/TransmissionData.h | 5 +++++ RoboCat/Inc/UDPSocket.h | 2 +- RoboCat/Inc/Wall.h | 1 + RoboCat/Src/DeliveryNotificationManager.cpp | 1 + RoboCat/Src/Networker.cpp | 5 +++-- 18 files changed, 67 insertions(+), 8 deletions(-) diff --git a/RoboCat/Inc/AckRange.h b/RoboCat/Inc/AckRange.h index 004d14c9..d1432c4d 100644 --- a/RoboCat/Inc/AckRange.h +++ b/RoboCat/Inc/AckRange.h @@ -1,3 +1,5 @@ +#pragma once + //typedef PacketSequenceNumber uint16_t; class AckRange diff --git a/RoboCat/Inc/DeliveryNotificationManager.h b/RoboCat/Inc/DeliveryNotificationManager.h index b10c2286..ccc0dccb 100644 --- a/RoboCat/Inc/DeliveryNotificationManager.h +++ b/RoboCat/Inc/DeliveryNotificationManager.h @@ -1,6 +1,24 @@ +#pragma once + +#include "MemoryBitStream.h" +#include "InFlightPacket.h" +#include "AckRange.h" #include +#include + +using std::deque; + +////https://www.geeksforgeeks.org/priority-queue-of-pairs-in-c-with-ordering-by-first-and-second-element/ +//struct myComp { +// constexpr bool operator()( +// std::pair const& a, +// std::pair const& b) +// const noexcept +// { +// return a.first.GetSequenceNumber() > b.first.GetSequenceNumber(); +// } +//}; -//#include "Networker.h" class Networker; class DeliveryNotificationManager @@ -21,7 +39,7 @@ class DeliveryNotificationManager uint32_t GetDispatchedPacketCount() const { return mDispatchedPacketCount; } //const deque& GetInFlightPackets() const { return mInFlightPackets; } - const deque> GetInFlightPackets() const { return mInFlightPacketsPair; } + const deque> GetInFlightPackets() const { return mInFlightPacketsPair; } private: @@ -40,7 +58,7 @@ class DeliveryNotificationManager PacketSequenceNumber mNextExpectedSequenceNumber; //deque mInFlightPackets; - deque> mInFlightPacketsPair; + deque> mInFlightPacketsPair; deque mPendingAcks; bool mShouldSendAcks; diff --git a/RoboCat/Inc/InFlightPacket.h b/RoboCat/Inc/InFlightPacket.h index b510fe50..7d6a95d9 100644 --- a/RoboCat/Inc/InFlightPacket.h +++ b/RoboCat/Inc/InFlightPacket.h @@ -1,3 +1,9 @@ +#pragma once + +#include + +using std::unordered_map; + class DeliveryNotificationManager; //in case we decide to change the type of the sequence number to use fewer or more bits diff --git a/RoboCat/Inc/MemoryBitStream.h b/RoboCat/Inc/MemoryBitStream.h index c2138925..ba65a8ff 100644 --- a/RoboCat/Inc/MemoryBitStream.h +++ b/RoboCat/Inc/MemoryBitStream.h @@ -1,8 +1,10 @@ +#pragma once #include #include #include #include +#include "RoboMath.h" class GameObject; diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index b07fa4b2..8b601411 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -13,6 +13,17 @@ #include "DeliveryNotificationManager.h" #include "RoboCatPCH.h" +//https://www.geeksforgeeks.org/priority-queue-of-pairs-in-c-with-ordering-by-first-and-second-element/ +struct myComp { + constexpr bool operator()( + std::pair const& a, + std::pair const& b) + const noexcept + { + return a.first > b.first; + } +}; + enum PacketType { PACKET_CREATE, @@ -90,7 +101,7 @@ class Networker std::vector> mGameObjectsVec; //std::queue mInFlightPacketsQueue; DeliveryNotificationManager* pDeliveryNotificationManager; - std::priority_queue, std::vector>, std::greater>> mOutputBitStreamQueue; + std::priority_queue, std::vector>, myComp> mOutputBitStreamQueue; //int mArrivalTime; bool mbIsInitted; diff --git a/RoboCat/Inc/OutputWindow.h b/RoboCat/Inc/OutputWindow.h index a12ad9ef..73312118 100644 --- a/RoboCat/Inc/OutputWindow.h +++ b/RoboCat/Inc/OutputWindow.h @@ -1,3 +1,4 @@ +#pragma once // Encapsulates a simulated "output window," where // new messages are written from top to bottom diff --git a/RoboCat/Inc/PlayerController.h b/RoboCat/Inc/PlayerController.h index 87fe0881..1f4eda52 100644 --- a/RoboCat/Inc/PlayerController.h +++ b/RoboCat/Inc/PlayerController.h @@ -7,6 +7,7 @@ #include "GameObject.h" #include "InputSystem.h" +#include "TransmissionData.h" class PlayerController : public GameObject, public TransmissionData { diff --git a/RoboCat/Inc/RoboMath.h b/RoboCat/Inc/RoboMath.h index 718080a9..80603dad 100644 --- a/RoboCat/Inc/RoboMath.h +++ b/RoboCat/Inc/RoboMath.h @@ -1,3 +1,5 @@ +#pragma once + class Vector3 { public: diff --git a/RoboCat/Inc/SocketAddress.h b/RoboCat/Inc/SocketAddress.h index 05bad2b8..0c8d5671 100644 --- a/RoboCat/Inc/SocketAddress.h +++ b/RoboCat/Inc/SocketAddress.h @@ -1,3 +1,5 @@ +#pragma once + class SocketAddress { public: diff --git a/RoboCat/Inc/SocketAddressFactory.h b/RoboCat/Inc/SocketAddressFactory.h index fe1e0516..2f0bfe67 100644 --- a/RoboCat/Inc/SocketAddressFactory.h +++ b/RoboCat/Inc/SocketAddressFactory.h @@ -1,4 +1,4 @@ - +#pragma once class SocketAddressFactory { diff --git a/RoboCat/Inc/SocketUtil.h b/RoboCat/Inc/SocketUtil.h index 50a726c3..7238c192 100644 --- a/RoboCat/Inc/SocketUtil.h +++ b/RoboCat/Inc/SocketUtil.h @@ -1,3 +1,5 @@ +#pragma once + enum class SocketAddressFamily { INET = AF_INET, diff --git a/RoboCat/Inc/TCPSocket.h b/RoboCat/Inc/TCPSocket.h index c84fb6ca..42d8fb58 100644 --- a/RoboCat/Inc/TCPSocket.h +++ b/RoboCat/Inc/TCPSocket.h @@ -1,3 +1,5 @@ +#pragma once + class TCPSocket { public: diff --git a/RoboCat/Inc/Timing.h b/RoboCat/Inc/Timing.h index b5af28d8..23edd9fe 100644 --- a/RoboCat/Inc/Timing.h +++ b/RoboCat/Inc/Timing.h @@ -1,3 +1,5 @@ +#pragma once + class Timing { public: diff --git a/RoboCat/Inc/TransmissionData.h b/RoboCat/Inc/TransmissionData.h index 9ced30d7..8d695fdb 100644 --- a/RoboCat/Inc/TransmissionData.h +++ b/RoboCat/Inc/TransmissionData.h @@ -1,3 +1,8 @@ +#pragma once + +#include +using std::shared_ptr; + typedef uint16_t PacketSequenceNumber; class DeliveryNotificationManager; diff --git a/RoboCat/Inc/UDPSocket.h b/RoboCat/Inc/UDPSocket.h index ae530d14..6ed44431 100644 --- a/RoboCat/Inc/UDPSocket.h +++ b/RoboCat/Inc/UDPSocket.h @@ -1,4 +1,4 @@ - +#pragma once class UDPSocket { diff --git a/RoboCat/Inc/Wall.h b/RoboCat/Inc/Wall.h index b11c9007..b7499fa9 100644 --- a/RoboCat/Inc/Wall.h +++ b/RoboCat/Inc/Wall.h @@ -7,6 +7,7 @@ #include "GameObject.h" #include "Colour.h" +#include "TransmissionData.h" class Wall : public GameObject, public TransmissionData { diff --git a/RoboCat/Src/DeliveryNotificationManager.cpp b/RoboCat/Src/DeliveryNotificationManager.cpp index 3707d529..facbd384 100644 --- a/RoboCat/Src/DeliveryNotificationManager.cpp +++ b/RoboCat/Src/DeliveryNotificationManager.cpp @@ -17,6 +17,7 @@ mDroppedPacketCount( 0 ), mDispatchedPacketCount( 0 ), mpNetworker(pNetworker) { + mInFlightPacketsPair = deque>(); } diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 9ca7b910..94e0efb5 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -26,7 +26,7 @@ void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIde mGameObjectsVec = std::vector>(); //mPacketQueue = std::queue>(); pDeliveryNotificationManager = new DeliveryNotificationManager(true, true, this); - mOutputBitStreamQueue = std::priority_queue, std::vector>, std::greater>>(); + mOutputBitStreamQueue = std::priority_queue, std::vector>, myComp>(); //Data for GameObject replication mpGraphicsLibrary = graphicsLibrary; @@ -861,7 +861,8 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) if (mOutputBitStreamQueue.size() >= 10) { //Iterate though the queue - for (int i = 0; i < mOutputBitStreamQueue.size(); i++) + //for (size_t i = 0; i < mOutputBitStreamQueue.size(); i++) + for (size_t i = mOutputBitStreamQueue.size() - 1; i >=0 ; i--) { //Drop some packets if (i % 3 != 0) From 0fb87c4e1259893d5c918a4376cb8c0b0252a50a Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Thu, 14 Apr 2022 15:30:59 -0400 Subject: [PATCH 26/56] Builds. --- RoboCat/Src/Networker.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 94e0efb5..dc845a84 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -4,6 +4,7 @@ //Constructor Networker::Networker() { + mbIsInitted = false; } Networker::~Networker() From 7f673d7eed0f29acdb75b5bc91c8eab7e1e395ea Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Thu, 14 Apr 2022 17:01:13 -0400 Subject: [PATCH 27/56] Adding unsaved change, trying to get UDP sockets to connect properly. --- RoboCat/Src/Networker.cpp | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index dc845a84..08afeb5f 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -187,20 +187,20 @@ bool Networker::connectUDP(std::string otherUserIpAddress, std::string port) return false; } - string address = "0.0.0.0:0"; - SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString(address.c_str()); - if (clientAddress == nullptr) - { - SocketUtil::ReportError("Creating Client Address"); - ExitProcess(1); - return false; - } - - if (sock->Bind(*clientAddress) != NO_ERROR) - { - SocketUtil::ReportError("Binding Client Socket"); - ExitProcess(1); - } + //string address = "0.0.0.0:0"; + //SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString(address.c_str()); + //if (clientAddress == nullptr) + //{ + // SocketUtil::ReportError("Creating Client Address"); + // ExitProcess(1); + // return false; + //} + + //if (sock->Bind(*clientAddress) != NO_ERROR) + //{ + // SocketUtil::ReportError("Binding Client Socket"); + // ExitProcess(1); + //} LOG("%s", "Client Socket Succesfully Binded!"); SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString(otherUserIpAddress + ":" + port); @@ -210,10 +210,16 @@ bool Networker::connectUDP(std::string otherUserIpAddress, std::string port) ExitProcess(1); } + if (sock->Bind(*sockAddress) != NO_ERROR) + { + SocketUtil::ReportError("Binding Client Socket"); + ExitProcess(1); + } + *mpUDPSocket = sock; *mpSocketAddressPtr = sockAddress; - if (mpUDPSocket != nullptr) + if (*mpUDPSocket != nullptr) return true; return false; } From de1b0b5033977ba12b8f5ce59ebd6d183643c269 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 00:49:03 -0400 Subject: [PATCH 28/56] Connects with port number 8, but it starts the game before it actually connects to another peer (because of the bool). Need to somehow fix it or make sure it works. Throws an error when it tries to add to mOutputBitStreamQueue when callign the OutputMemoryBitStream destructor, will try to make it take pointers and delete data and clear it when sending. --- RoboCat/Src/Networker.cpp | 41 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 08afeb5f..10402fb6 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -23,7 +23,8 @@ void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIde //mArrivalTime = arrivalTime; //mpTCPSocket = new TCPSocketPtr; - mpUDPSocket = new UDPSocketPtr; + mpUDPSocket = new UDPSocketPtr(); + mpSocketAddressPtr = new SocketAddressPtr(); mGameObjectsVec = std::vector>(); //mPacketQueue = std::queue>(); pDeliveryNotificationManager = new DeliveryNotificationManager(true, true, this); @@ -62,6 +63,9 @@ void Networker::cleanup() delete mpUDPSocket; mpUDPSocket = nullptr; + delete mpSocketAddressPtr; + mpSocketAddressPtr = nullptr; + SocketUtil::CleanUp(); mbIsInitted = false; @@ -179,6 +183,7 @@ bool Networker::connectUDP(std::string otherUserIpAddress, std::string port) //Create Socket UDPSocketPtr sock = SocketUtil::CreateUDPSocket(SocketAddressFamily::INET); + sock->SetNonBlockingMode(false); if (sock == nullptr) { @@ -187,34 +192,28 @@ bool Networker::connectUDP(std::string otherUserIpAddress, std::string port) return false; } - //string address = "0.0.0.0:0"; - //SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString(address.c_str()); - //if (clientAddress == nullptr) - //{ - // SocketUtil::ReportError("Creating Client Address"); - // ExitProcess(1); - // return false; - //} - - //if (sock->Bind(*clientAddress) != NO_ERROR) - //{ - // SocketUtil::ReportError("Binding Client Socket"); - // ExitProcess(1); - //} - LOG("%s", "Client Socket Succesfully Binded!"); - - SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString(otherUserIpAddress + ":" + port); - if (sockAddress == nullptr) + string address = "0.0.0.0:0"; + SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString(address.c_str()); + if (clientAddress == nullptr) { - SocketUtil::ReportError("Creating Server Address"); + SocketUtil::ReportError("Creating Client Address"); ExitProcess(1); + return false; } - if (sock->Bind(*sockAddress) != NO_ERROR) + if (sock->Bind(*clientAddress) != NO_ERROR) { SocketUtil::ReportError("Binding Client Socket"); ExitProcess(1); } + LOG("%s", "Client Socket Succesfully Binded!"); + + SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((otherUserIpAddress + ":" + port).c_str()); + if (sockAddress == nullptr) + { + SocketUtil::ReportError("Creating Server Address"); + ExitProcess(1); + } *mpUDPSocket = sock; *mpSocketAddressPtr = sockAddress; From 9ae256f331f110f31d8d2a2cc5eb77e54fc639cb Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 00:57:03 -0400 Subject: [PATCH 29/56] Changed priorityu queue to hold pointers, deleting data after sending, changed queue to sort by least float in pair because I send the top first. --- RoboCat/Inc/Networker.h | 8 ++++---- RoboCat/Src/Networker.cpp | 35 +++++++++++++++++++---------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index 8b601411..53c1c1d7 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -16,11 +16,11 @@ //https://www.geeksforgeeks.org/priority-queue-of-pairs-in-c-with-ordering-by-first-and-second-element/ struct myComp { constexpr bool operator()( - std::pair const& a, - std::pair const& b) + std::pair const& a, + std::pair const& b) const noexcept { - return a.first > b.first; + return a.first < b.first; } }; @@ -101,7 +101,7 @@ class Networker std::vector> mGameObjectsVec; //std::queue mInFlightPacketsQueue; DeliveryNotificationManager* pDeliveryNotificationManager; - std::priority_queue, std::vector>, myComp> mOutputBitStreamQueue; + std::priority_queue, std::vector>, myComp> mOutputBitStreamQueue; //int mArrivalTime; bool mbIsInitted; diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 10402fb6..3dde7397 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -28,7 +28,7 @@ void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIde mGameObjectsVec = std::vector>(); //mPacketQueue = std::queue>(); pDeliveryNotificationManager = new DeliveryNotificationManager(true, true, this); - mOutputBitStreamQueue = std::priority_queue, std::vector>, myComp>(); + mOutputBitStreamQueue = std::priority_queue, std::vector>, myComp>(); //Data for GameObject replication mpGraphicsLibrary = graphicsLibrary; @@ -794,19 +794,19 @@ PacketType Networker::receiveGameObjectStateUDP() void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) { - OutputMemoryBitStream OMBStream; + OutputMemoryBitStream* OMBStream = new OutputMemoryBitStream(); //Write state sent (packet sequence number and acks) - pDeliveryNotificationManager->WriteState(OMBStream); + pDeliveryNotificationManager->WriteState(*OMBStream); //Write packet header - OMBStream.Write(packetHeader); + OMBStream->Write(packetHeader); //float timeDispatched = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); //OMBStream.Write(timeDispatched); - OMBStream.Write(mGameObjectsVec[ID].second->getNetworkID()); - OMBStream.Write(mGameObjectsVec[ID].second->getGameObjectType()); + OMBStream->Write(mGameObjectsVec[ID].second->getNetworkID()); + OMBStream->Write(mGameObjectsVec[ID].second->getGameObjectType()); //Logic depends on packer header type switch (packetHeader) @@ -818,17 +818,17 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) { case GameObjectType::ROCK: case GameObjectType::PLAYER: - OMBStream.Write(mGameObjectsVec[ID].second->getPosition().first); - OMBStream.Write(mGameObjectsVec[ID].second->getPosition().second); + OMBStream->Write(mGameObjectsVec[ID].second->getPosition().first); + OMBStream->Write(mGameObjectsVec[ID].second->getPosition().second); break; case GameObjectType::WALL: Wall* wall = (Wall*)mGameObjectsVec[ID].second; - OMBStream.Write(wall->getPosition().first); - OMBStream.Write(wall->getPosition().second); - OMBStream.Write(wall->getWallSizeX()); - OMBStream.Write(wall->getWallSizeY()); - OMBStream.Write(wall->getWallThickness()); + OMBStream->Write(wall->getPosition().first); + OMBStream->Write(wall->getPosition().second); + OMBStream->Write(wall->getWallSizeX()); + OMBStream->Write(wall->getWallSizeY()); + OMBStream->Write(wall->getWallThickness()); break; } @@ -862,19 +862,22 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) //Add packet data to queue in randomised order --> done through timeDispatched +- random and the priority_queue's sorting float timeDispatched = Timing::sInstance.GetTimef() + (-100 + rand() & (100 - -100 + 1)); mOutputBitStreamQueue.push(std::make_pair(timeDispatched, OMBStream)); + OMBStream = nullptr; //If the queue has more than 10 elements is it if (mOutputBitStreamQueue.size() >= 10) { //Iterate though the queue - //for (size_t i = 0; i < mOutputBitStreamQueue.size(); i++) - for (size_t i = mOutputBitStreamQueue.size() - 1; i >=0 ; i--) + //for (size_t i = mOutputBitStreamQueue.size() - 1; i >= 0 ; i--) + for (size_t i = 0; i < mOutputBitStreamQueue.size(); i++) { //Drop some packets if (i % 3 != 0) { //Send packet - (*mpUDPSocket)->SendTo(OMBStream.GetBufferPtr(), OMBStream.GetBitLength(), (**mpSocketAddressPtr)); + (*mpUDPSocket)->SendTo(mOutputBitStreamQueue.top().second->GetBufferPtr(), mOutputBitStreamQueue.top().second->GetBitLength(), (**mpSocketAddressPtr)); + delete mOutputBitStreamQueue.top().second; + mOutputBitStreamQueue.pop(); } } } From 031a980780f493eb23bc3bd8c5fd52b09001721c Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 00:57:41 -0400 Subject: [PATCH 30/56] Added comment to indicate new sorting in myComp struct. --- RoboCat/Inc/Networker.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index 53c1c1d7..9924fef1 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -20,7 +20,7 @@ struct myComp { std::pair const& b) const noexcept { - return a.first < b.first; + return a.first < b.first; //Sort by LEAST, since we send the queue.top and pop that first } }; From 79e55292f1798a00f7598f7f719d0c339be5a2ee Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 01:02:45 -0400 Subject: [PATCH 31/56] Instances can connect to each other but we need to make sure both have joined so we can sync up player controllers and the rest of the game. Will make Hello Packets. Saving. --- RoboCat/Src/Networker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 3dde7397..d3231530 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -183,7 +183,7 @@ bool Networker::connectUDP(std::string otherUserIpAddress, std::string port) //Create Socket UDPSocketPtr sock = SocketUtil::CreateUDPSocket(SocketAddressFamily::INET); - sock->SetNonBlockingMode(false); + sock->SetNonBlockingMode(true); if (sock == nullptr) { From 994aacd01b47f2d52c75b792d7abdc9233463166 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 01:14:03 -0400 Subject: [PATCH 32/56] Added PacketType::PACKET_HELLO and not starting game until one is received. Need to now actually figure out how to send one out and when. --- RoboCat/Inc/Networker.h | 1 + RoboCat/Src/Main.cpp | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index 9924fef1..25f4cafd 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -26,6 +26,7 @@ struct myComp { enum PacketType { + PACKET_HELLO, PACKET_CREATE, PACKET_UPDATE, PACKET_DELETE, diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index 6418b430..82d5ac63 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -65,7 +65,7 @@ float playerMoveSpeed = 0.5; //-------------------------Network Data------------------------- Networker* Networker::mInstance = 0; Networker* pNetworkManager; -PacketType packetTypeReceived; +PacketType packetTypeReceived = PacketType::PACKET_INVALID; int networkID = 0; bool init() @@ -354,7 +354,7 @@ int main(int argc, const char** argv) std::cout << "Are you the server? Type 'y' for yes, anything else for no.\n"; std::cin >> input; bool bIsServer = false; - bool bHasConnectd = false; + bool bSocketInitted = false; if (input == "y") { @@ -377,8 +377,8 @@ int main(int argc, const char** argv) std::cin >> portNumber; //bHasConnectd = pNetworkManager->initServer(portNumber); - bHasConnectd = pNetworkManager->connectUDP(clientIP, portNumber); - if (bHasConnectd) + bSocketInitted = pNetworkManager->connectUDP(clientIP, portNumber); + if (bSocketInitted) std::cout << "main.cpp --> server initted.\n"; //Server PlayerController is networkID 0 @@ -400,8 +400,8 @@ int main(int argc, const char** argv) std::cin >> portNumber; //bHasConnectd = pNetworkManager->connect(serverIP, portNumber); - bHasConnectd = pNetworkManager->connectUDP(serverIP, portNumber); - if (bHasConnectd) + bSocketInitted = pNetworkManager->connectUDP(serverIP, portNumber); + if (bSocketInitted) std::cout << "main.cpp --> client connected.\n"; //Client PlayerController is networkID 1 @@ -409,9 +409,15 @@ int main(int argc, const char** argv) startingPlayerPos = STARTING_PLAYER_POSITION_CLIENT; } - //If the peer has connected - if (bHasConnectd) + //If the socket in initted + if (bSocketInitted) { + //Check for peer connected + while (packetTypeReceived != PacketType::PACKET_HELLO) + { + packetTypeReceived = pNetworkManager->receiveGameObjectStateUDP(); + } + //Setup start(); From 390fcc154537a9da8ebe8653a53587d0bf50be7f Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 01:22:35 -0400 Subject: [PATCH 33/56] Wrote code to send and receive connection setup packets (if ID sent is < 0). Changed code to return early when receiving and send early after writing ID and packetType. --- RoboCat/Src/Main.cpp | 4 ++++ RoboCat/Src/Networker.cpp | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index 82d5ac63..dad5aedd 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -415,6 +415,10 @@ int main(int argc, const char** argv) //Check for peer connected while (packetTypeReceived != PacketType::PACKET_HELLO) { + //Send hello packets + pNetworkManager->sendGameObjectStateUDP(-5, PacketType::PACKET_HELLO); + + //Keep receiving packsts packetTypeReceived = pNetworkManager->receiveGameObjectStateUDP(); } diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index d3231530..65be5f9a 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -651,12 +651,22 @@ PacketType Networker::receiveGameObjectStateUDP() //IMBStream.Read(dispatchTime); int networkID; IMBStream.Read(networkID); + + //If ID received is < 0, we are doing connection stuff + if (networkID < 0) + { + return packetHeader; + } + GameObjectType receiveType; IMBStream.Read(receiveType); //Logic depends on packer header type switch (packetHeader) { + case PacketType::PACKET_HELLO: + break; + case PacketType::PACKET_CREATE: { float posX; @@ -805,6 +815,17 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) //float timeDispatched = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); //OMBStream.Write(timeDispatched); + //If ID received is < 0, we are doing connection stuff + if (ID < 0) + { + //Write ID + OMBStream->Write(ID); + + //Send packet and return from the function + (*mpUDPSocket)->SendTo(OMBStream, OMBStream->GetBitLength(), (**mpSocketAddressPtr)); + return; + } + OMBStream->Write(mGameObjectsVec[ID].second->getNetworkID()); OMBStream->Write(mGameObjectsVec[ID].second->getGameObjectType()); From c2b2748e6db3274feed575f46e2f5d646e15191a Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 01:40:09 -0400 Subject: [PATCH 34/56] It fails on the first line of OutputMemoryBitStream::WriteBits(), as it thinks the string has invalid characters after the static_cast(inData). --- RoboCat/Src/Networker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 65be5f9a..e9404732 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -794,7 +794,7 @@ PacketType Networker::receiveGameObjectStateUDP() return packetHeader; } } - else if (byteRecieve == -10053 || byteRecieve == -10054) + else if (byteRecieve == -10053/* || byteRecieve == -10054*/) { LOG("%s", "Disconnected From Server"); exit(0); @@ -804,7 +804,7 @@ PacketType Networker::receiveGameObjectStateUDP() void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) { - OutputMemoryBitStream* OMBStream = new OutputMemoryBitStream(); + OutputMemoryBitStream* OMBStream = new OutputMemoryBitStream; //Write state sent (packet sequence number and acks) pDeliveryNotificationManager->WriteState(*OMBStream); From e5800c51a5177b24066949a567c9c3bc085eeecc Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 01:51:32 -0400 Subject: [PATCH 35/56] Trying to make it send but not quite working. Data is no longer erroneous, but it will not send properly. --- RoboCat/Inc/DeliveryNotificationManager.h | 6 ++++-- RoboCat/Src/DeliveryNotificationManager.cpp | 6 +++--- RoboCat/Src/MemoryBitStream.cpp | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/RoboCat/Inc/DeliveryNotificationManager.h b/RoboCat/Inc/DeliveryNotificationManager.h index ccc0dccb..2622106f 100644 --- a/RoboCat/Inc/DeliveryNotificationManager.h +++ b/RoboCat/Inc/DeliveryNotificationManager.h @@ -8,6 +8,8 @@ using std::deque; +typedef uint16_t PacketSequenceNumber; + ////https://www.geeksforgeeks.org/priority-queue-of-pairs-in-c-with-ordering-by-first-and-second-element/ //struct myComp { // constexpr bool operator()( @@ -39,7 +41,7 @@ class DeliveryNotificationManager uint32_t GetDispatchedPacketCount() const { return mDispatchedPacketCount; } //const deque& GetInFlightPackets() const { return mInFlightPackets; } - const deque> GetInFlightPackets() const { return mInFlightPacketsPair; } + const deque> GetInFlightPackets() const { return mInFlightPacketsPair; } private: @@ -58,7 +60,7 @@ class DeliveryNotificationManager PacketSequenceNumber mNextExpectedSequenceNumber; //deque mInFlightPackets; - deque> mInFlightPacketsPair; + deque> mInFlightPacketsPair; deque mPendingAcks; bool mShouldSendAcks; diff --git a/RoboCat/Src/DeliveryNotificationManager.cpp b/RoboCat/Src/DeliveryNotificationManager.cpp index facbd384..a31dc055 100644 --- a/RoboCat/Src/DeliveryNotificationManager.cpp +++ b/RoboCat/Src/DeliveryNotificationManager.cpp @@ -17,7 +17,7 @@ mDroppedPacketCount( 0 ), mDispatchedPacketCount( 0 ), mpNetworker(pNetworker) { - mInFlightPacketsPair = deque>(); + mInFlightPacketsPair = deque>(); } @@ -44,7 +44,7 @@ InFlightPacket* DeliveryNotificationManager::WriteSequenceNumber( OutputMemoryBi if( mShouldProcessAcks ) { //mInFlightPackets.emplace_back( sequenceNumber ); - mInFlightPacketsPair.emplace_back( std::make_pair(sequenceNumber, inOutputStream) ); + mInFlightPacketsPair.emplace_back( std::make_pair(sequenceNumber, &inOutputStream) ); //return &mInFlightPackets.back(); return &mInFlightPacketsPair.back().first; @@ -183,7 +183,7 @@ void DeliveryNotificationManager::ResendPacket(const int ID, const PacketSequenc if (it->first.GetSequenceNumber() == packetSequenceNum) { //Start reading packet until you find the PacketType - InputMemoryBitStream IMBStream = InputMemoryBitStream((char*)it->second.GetBufferPtr(), it->second.GetByteLength()); + InputMemoryBitStream IMBStream = InputMemoryBitStream((char*)it->second->GetBufferPtr(), it->second->GetByteLength()); //Read junk data: 1) sequenceNumber, if shouldProcessAcks, 2) hasAcks, if hasAcks, 3) readAckRange PacketSequenceNumber sequenceNumber; diff --git a/RoboCat/Src/MemoryBitStream.cpp b/RoboCat/Src/MemoryBitStream.cpp index 39958ad7..649ccee2 100644 --- a/RoboCat/Src/MemoryBitStream.cpp +++ b/RoboCat/Src/MemoryBitStream.cpp @@ -81,6 +81,7 @@ void OutputMemoryBitStream::ReallocBuffer( uint32_t inNewBitLength ) { //just need to memset on first allocation mBuffer = static_cast( std::malloc( inNewBitLength >> 3 ) ); + memset( mBuffer, 0, inNewBitLength >> 3 ); } else From b7211adf99c70552b8069d8d781b8038e2b0291a Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 02:15:07 -0400 Subject: [PATCH 36/56] Only binding Server socket, need to change code in main to call right function. --- RoboCat/Inc/Networker.h | 1 + RoboCat/Src/Networker.cpp | 46 +++++++++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index 25f4cafd..734bf621 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -66,6 +66,7 @@ class Networker //bool initServer(std::string port); //bool connect(std::string serverIpAddress, std::string port); + bool initServerUDP(std::string serverIpAddress, std::string port); bool connectUDP(std::string otherUserIpAddress, std::string port); //Update game state diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index e9404732..e4a8bb68 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -177,7 +177,7 @@ void Networker::cleanup() // return false; //} -bool Networker::connectUDP(std::string otherUserIpAddress, std::string port) +bool Networker::initServerUDP(std::string serverIpAddress, std::string port) { SocketUtil::StaticInit(); @@ -192,21 +192,42 @@ bool Networker::connectUDP(std::string otherUserIpAddress, std::string port) return false; } - string address = "0.0.0.0:0"; - SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString(address.c_str()); - if (clientAddress == nullptr) + SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((serverIpAddress + ":" + port).c_str()); + if (sockAddress == nullptr) { - SocketUtil::ReportError("Creating Client Address"); + SocketUtil::ReportError("Creating Server Address"); + ExitProcess(1); + } + + if (sock->Bind(*sockAddress) != NO_ERROR) + { + SocketUtil::ReportError("Binding Server Socket"); ExitProcess(1); - return false; } + LOG("%s", "Server Socket Succesfully Binded!"); + + *mpUDPSocket = sock; + *mpSocketAddressPtr = sockAddress; + + if (*mpUDPSocket != nullptr) + return true; + return false; +} + +bool Networker::connectUDP(std::string otherUserIpAddress, std::string port) +{ + SocketUtil::StaticInit(); + + //Create Socket + UDPSocketPtr sock = SocketUtil::CreateUDPSocket(SocketAddressFamily::INET); + sock->SetNonBlockingMode(true); - if (sock->Bind(*clientAddress) != NO_ERROR) + if (sock == nullptr) { - SocketUtil::ReportError("Binding Client Socket"); + SocketUtil::ReportError("Creating Client Socket"); ExitProcess(1); + return false; } - LOG("%s", "Client Socket Succesfully Binded!"); SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((otherUserIpAddress + ":" + port).c_str()); if (sockAddress == nullptr) @@ -215,6 +236,13 @@ bool Networker::connectUDP(std::string otherUserIpAddress, std::string port) ExitProcess(1); } + //if (sock->Bind(*sockAddress) != NO_ERROR) + //{ + // SocketUtil::ReportError("Binding Client Socket"); + // ExitProcess(1); + //} + //LOG("%s", "Client Socket Succesfully Binded!"); + *mpUDPSocket = sock; *mpSocketAddressPtr = sockAddress; From f2ef410b7c4a163cebcd65ef31348f0d247d5dde Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 02:21:30 -0400 Subject: [PATCH 37/56] Server should now be listening from client address (0.0.0.0:0). --- RoboCat/Src/Main.cpp | 8 ++++---- RoboCat/Src/Networker.cpp | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index dad5aedd..37e44c35 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -367,9 +367,9 @@ int main(int argc, const char** argv) //-------------------------Server code------------------------- //Prompt for client IP address - std::string clientIP; - std::cout << "Enter client IP address: \n"; - std::cin >> clientIP; + std::string myIP; + std::cout << "Enter your IP address: \n"; + std::cin >> myIP; //Prompt for port number std::string portNumber; @@ -377,7 +377,7 @@ int main(int argc, const char** argv) std::cin >> portNumber; //bHasConnectd = pNetworkManager->initServer(portNumber); - bSocketInitted = pNetworkManager->connectUDP(clientIP, portNumber); + bSocketInitted = pNetworkManager->initServerUDP(myIP, portNumber); if (bSocketInitted) std::cout << "main.cpp --> server initted.\n"; diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index e4a8bb68..602aa7c1 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -206,8 +206,15 @@ bool Networker::initServerUDP(std::string serverIpAddress, std::string port) } LOG("%s", "Server Socket Succesfully Binded!"); + SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString("0.0.0.0:0"); + if (clientAddress == nullptr) + { + SocketUtil::ReportError("Creating Client Address"); + ExitProcess(1); + } + *mpUDPSocket = sock; - *mpSocketAddressPtr = sockAddress; + *mpSocketAddressPtr = clientAddress; if (*mpUDPSocket != nullptr) return true; @@ -236,13 +243,6 @@ bool Networker::connectUDP(std::string otherUserIpAddress, std::string port) ExitProcess(1); } - //if (sock->Bind(*sockAddress) != NO_ERROR) - //{ - // SocketUtil::ReportError("Binding Client Socket"); - // ExitProcess(1); - //} - //LOG("%s", "Client Socket Succesfully Binded!"); - *mpUDPSocket = sock; *mpSocketAddressPtr = sockAddress; From 0c4e564dce8e434f9a6f89404240d8f28ef654a5 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 02:36:25 -0400 Subject: [PATCH 38/56] Server can send to any IP. --- RoboCat/Inc/Networker.h | 2 +- RoboCat/Src/Main.cpp | 10 +++++----- RoboCat/Src/Networker.cpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index 734bf621..d1d1ac83 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -67,7 +67,7 @@ class Networker //bool connect(std::string serverIpAddress, std::string port); bool initServerUDP(std::string serverIpAddress, std::string port); - bool connectUDP(std::string otherUserIpAddress, std::string port); + bool connectUDP(std::string port); //Update game state //PacketType receiveGameObjectState(); diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index 37e44c35..78a894f5 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -389,10 +389,10 @@ int main(int argc, const char** argv) { //-------------------------Client code------------------------- - //Prompt for server IP address - std::string serverIP; - std::cout << "Enter server IP address: \n"; - std::cin >> serverIP; + ////Prompt for server IP address + //std::string serverIP; + //std::cout << "Enter server IP address: \n"; + //std::cin >> serverIP; //Prompt for port number std::string portNumber; @@ -400,7 +400,7 @@ int main(int argc, const char** argv) std::cin >> portNumber; //bHasConnectd = pNetworkManager->connect(serverIP, portNumber); - bSocketInitted = pNetworkManager->connectUDP(serverIP, portNumber); + bSocketInitted = pNetworkManager->connectUDP(/*serverIP, */portNumber); if (bSocketInitted) std::cout << "main.cpp --> client connected.\n"; diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 602aa7c1..29cd1fc4 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -221,7 +221,7 @@ bool Networker::initServerUDP(std::string serverIpAddress, std::string port) return false; } -bool Networker::connectUDP(std::string otherUserIpAddress, std::string port) +bool Networker::connectUDP(std::string port) { SocketUtil::StaticInit(); @@ -236,7 +236,7 @@ bool Networker::connectUDP(std::string otherUserIpAddress, std::string port) return false; } - SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((otherUserIpAddress + ":" + port).c_str()); + SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString(("0.0.0.0:" + port).c_str()); if (sockAddress == nullptr) { SocketUtil::ReportError("Creating Server Address"); From 3814f11bebe5fa2b78e75f78358c6e92aed99771 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 02:39:17 -0400 Subject: [PATCH 39/56] Server listening on its own socketAddress. --- RoboCat/Src/Networker.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 29cd1fc4..7668298b 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -206,15 +206,15 @@ bool Networker::initServerUDP(std::string serverIpAddress, std::string port) } LOG("%s", "Server Socket Succesfully Binded!"); - SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString("0.0.0.0:0"); - if (clientAddress == nullptr) - { - SocketUtil::ReportError("Creating Client Address"); - ExitProcess(1); - } + //SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString("0.0.0.0:0"); + //if (clientAddress == nullptr) + //{ + // SocketUtil::ReportError("Creating Client Address"); + // ExitProcess(1); + //} *mpUDPSocket = sock; - *mpSocketAddressPtr = clientAddress; + *mpSocketAddressPtr = sockAddress; if (*mpUDPSocket != nullptr) return true; From eb5bc6e7318c4dfdfb89a7ff2e0e901d6c75ee8b Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 02:43:23 -0400 Subject: [PATCH 40/56] Server can receive from anywhere. --- RoboCat/Src/Networker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 7668298b..568ee3b2 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -192,7 +192,7 @@ bool Networker::initServerUDP(std::string serverIpAddress, std::string port) return false; } - SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((serverIpAddress + ":" + port).c_str()); + SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((/*serverIpAddress + */"0.0.0.0:" + port).c_str()); if (sockAddress == nullptr) { SocketUtil::ReportError("Creating Server Address"); From dad7ffa5a1a7bf99c1b4eb8d52440e2034a36701 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 02:48:38 -0400 Subject: [PATCH 41/56] Trying to get them to connect but the address is invalid. May call it a night here. --- RoboCat/Inc/Networker.h | 2 +- RoboCat/Src/Main.cpp | 10 +++++----- RoboCat/Src/Networker.cpp | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index d1d1ac83..67fae09b 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -66,7 +66,7 @@ class Networker //bool initServer(std::string port); //bool connect(std::string serverIpAddress, std::string port); - bool initServerUDP(std::string serverIpAddress, std::string port); + bool initServerUDP(/*std::string serverIpAddress, */std::string port); bool connectUDP(std::string port); //Update game state diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index 78a894f5..ce372c82 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -366,10 +366,10 @@ int main(int argc, const char** argv) { //-------------------------Server code------------------------- - //Prompt for client IP address - std::string myIP; - std::cout << "Enter your IP address: \n"; - std::cin >> myIP; + ////Prompt for client IP address + //std::string myIP; + //std::cout << "Enter your IP address: \n"; + //std::cin >> myIP; //Prompt for port number std::string portNumber; @@ -377,7 +377,7 @@ int main(int argc, const char** argv) std::cin >> portNumber; //bHasConnectd = pNetworkManager->initServer(portNumber); - bSocketInitted = pNetworkManager->initServerUDP(myIP, portNumber); + bSocketInitted = pNetworkManager->initServerUDP(/*myIP, */portNumber); if (bSocketInitted) std::cout << "main.cpp --> server initted.\n"; diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 568ee3b2..979fa5ab 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -177,7 +177,7 @@ void Networker::cleanup() // return false; //} -bool Networker::initServerUDP(std::string serverIpAddress, std::string port) +bool Networker::initServerUDP(/*std::string serverIpAddress, */std::string port) { SocketUtil::StaticInit(); From a5e845a24024b022b431d61392c50c63e09ce479 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 12:08:05 -0400 Subject: [PATCH 42/56] They connect! Now errors because it checks for an ID that is too high for the gameObjectsVec in Networker. So much progress! --- RoboCat/Inc/Networker.h | 4 ++-- RoboCat/Src/Main.cpp | 20 ++++++++++---------- RoboCat/Src/Networker.cpp | 10 +++++----- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index 67fae09b..c2ee11ea 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -66,8 +66,8 @@ class Networker //bool initServer(std::string port); //bool connect(std::string serverIpAddress, std::string port); - bool initServerUDP(/*std::string serverIpAddress, */std::string port); - bool connectUDP(std::string port); + bool initServerUDP(std::string serverIpAddress, std::string port); + bool connectUDP(std::string serverIpAddress, std::string port); //Update game state //PacketType receiveGameObjectState(); diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index ce372c82..37e44c35 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -366,10 +366,10 @@ int main(int argc, const char** argv) { //-------------------------Server code------------------------- - ////Prompt for client IP address - //std::string myIP; - //std::cout << "Enter your IP address: \n"; - //std::cin >> myIP; + //Prompt for client IP address + std::string myIP; + std::cout << "Enter your IP address: \n"; + std::cin >> myIP; //Prompt for port number std::string portNumber; @@ -377,7 +377,7 @@ int main(int argc, const char** argv) std::cin >> portNumber; //bHasConnectd = pNetworkManager->initServer(portNumber); - bSocketInitted = pNetworkManager->initServerUDP(/*myIP, */portNumber); + bSocketInitted = pNetworkManager->initServerUDP(myIP, portNumber); if (bSocketInitted) std::cout << "main.cpp --> server initted.\n"; @@ -389,10 +389,10 @@ int main(int argc, const char** argv) { //-------------------------Client code------------------------- - ////Prompt for server IP address - //std::string serverIP; - //std::cout << "Enter server IP address: \n"; - //std::cin >> serverIP; + //Prompt for server IP address + std::string serverIP; + std::cout << "Enter server IP address: \n"; + std::cin >> serverIP; //Prompt for port number std::string portNumber; @@ -400,7 +400,7 @@ int main(int argc, const char** argv) std::cin >> portNumber; //bHasConnectd = pNetworkManager->connect(serverIP, portNumber); - bSocketInitted = pNetworkManager->connectUDP(/*serverIP, */portNumber); + bSocketInitted = pNetworkManager->connectUDP(serverIP, portNumber); if (bSocketInitted) std::cout << "main.cpp --> client connected.\n"; diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 979fa5ab..1e611c61 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -177,7 +177,7 @@ void Networker::cleanup() // return false; //} -bool Networker::initServerUDP(/*std::string serverIpAddress, */std::string port) +bool Networker::initServerUDP(std::string serverIpAddress, std::string port) { SocketUtil::StaticInit(); @@ -192,7 +192,7 @@ bool Networker::initServerUDP(/*std::string serverIpAddress, */std::string port) return false; } - SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((/*serverIpAddress + */"0.0.0.0:" + port).c_str()); + SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((serverIpAddress + ":" + port).c_str()); if (sockAddress == nullptr) { SocketUtil::ReportError("Creating Server Address"); @@ -221,7 +221,7 @@ bool Networker::initServerUDP(/*std::string serverIpAddress, */std::string port) return false; } -bool Networker::connectUDP(std::string port) +bool Networker::connectUDP(std::string serverIpAddress, std::string port) { SocketUtil::StaticInit(); @@ -236,7 +236,7 @@ bool Networker::connectUDP(std::string port) return false; } - SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString(("0.0.0.0:" + port).c_str()); + SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((serverIpAddress + ":" + port).c_str()); if (sockAddress == nullptr) { SocketUtil::ReportError("Creating Server Address"); @@ -850,7 +850,7 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) OMBStream->Write(ID); //Send packet and return from the function - (*mpUDPSocket)->SendTo(OMBStream, OMBStream->GetBitLength(), (**mpSocketAddressPtr)); + (*mpUDPSocket)->SendTo(OMBStream->GetBufferPtr(), OMBStream->GetBitLength(), (**mpSocketAddressPtr)); return; } From 016a40ba846ff70aa613aaf07e107aa84db68ad8 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 12:32:57 -0400 Subject: [PATCH 43/56] Cleaned up code. It's sending a PACKET_HELLO but receiving it itself. --- RoboCat/Inc/Networker.h | 4 -- RoboCat/Src/Main.cpp | 2 + RoboCat/Src/Networker.cpp | 118 -------------------------------------- 3 files changed, 2 insertions(+), 122 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index c2ee11ea..f8da98b6 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -63,9 +63,6 @@ class Networker ~Networker(); //Starting and connect to server - //bool initServer(std::string port); - //bool connect(std::string serverIpAddress, std::string port); - bool initServerUDP(std::string serverIpAddress, std::string port); bool connectUDP(std::string serverIpAddress, std::string port); @@ -97,7 +94,6 @@ class Networker static Networker* mInstance; //Data - //TCPSocketPtr* mpTCPSocket; UDPSocketPtr* mpUDPSocket; SocketAddressPtr* mpSocketAddressPtr; std::vector> mGameObjectsVec; diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index 37e44c35..9ba3f6d4 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -412,6 +412,8 @@ int main(int argc, const char** argv) //If the socket in initted if (bSocketInitted) { + packetTypeReceived = PacketType::PACKET_INVALID; + //Check for peer connected while (packetTypeReceived != PacketType::PACKET_HELLO) { diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 1e611c61..213cca53 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -22,7 +22,6 @@ void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIde std::srand(time(NULL)); //mArrivalTime = arrivalTime; - //mpTCPSocket = new TCPSocketPtr; mpUDPSocket = new UDPSocketPtr(); mpSocketAddressPtr = new SocketAddressPtr(); mGameObjectsVec = std::vector>(); @@ -55,10 +54,6 @@ void Networker::cleanup() delete pDeliveryNotificationManager; pDeliveryNotificationManager = nullptr; - //(*mpTCPSocket)->CleanupSocket(); - //delete mpTCPSocket; - //mpTCPSocket = nullptr; - (*mpUDPSocket)->CleanupSocket(); delete mpUDPSocket; mpUDPSocket = nullptr; @@ -71,112 +66,6 @@ void Networker::cleanup() mbIsInitted = false; } -//bool Networker::initServer(std::string port) -//{ -// SocketUtil::StaticInit(); -// -// //Create Socket -// TCPSocketPtr sock = SocketUtil::CreateTCPSocket(SocketAddressFamily::INET); -// if (sock == nullptr) -// { -// SocketUtil::ReportError("Creating Listenting Socket"); -// ExitProcess(1); -// } -// std::cout << "Listening Socket Succesfully Created!\n"; -// -// //Create and Bind Address -// SocketAddressPtr listenAddress = SocketAddressFactory::CreateIPv4FromString("0.0.0.0:" + port); -// if (listenAddress == nullptr) -// { -// SocketUtil::ReportError("Creating Listening Address"); -// ExitProcess(1); -// } -// -// if (sock->Bind(*listenAddress) != NO_ERROR) -// { -// SocketUtil::ReportError("Binding listening socket"); -// ExitProcess(1); -// } -// std::cout << "Listening Socket Succesfully Binded!\n"; -// -// if (sock->Listen() != NO_ERROR) -// { -// SocketUtil::ReportError("Listening on socket"); -// ExitProcess(1); -// } -// std::cout << "Listening Socket Listening\n"; -// -// //Accept Connection -// std::cout << "Waiting for connection...\n"; -// -// sock->SetNonBlockingMode(false); -// SocketAddress incomingAddress; -// TCPSocketPtr connSocket = sock->Accept(incomingAddress); -// -// while (connSocket == nullptr) -// connSocket = sock->Accept(incomingAddress); -// -// -// *mpTCPSocket = connSocket; -// -// std::cout << "Accepted connection from address: " << incomingAddress.ToString() << std::endl; -// -// if (mpTCPSocket != nullptr) -// return true; -// return false; -//} - -//bool Networker::connect(std::string serverIpAddress, std::string port) -//{ -// SocketUtil::StaticInit(); -// -// //Create Socket -// TCPSocketPtr sock = SocketUtil::CreateTCPSocket(SocketAddressFamily::INET); -// -// if (sock == nullptr) -// { -// SocketUtil::ReportError("Creating Client Socket"); -// ExitProcess(1); -// return false; -// } -// -// string address = "0.0.0.0:0"; -// SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString(address.c_str()); -// if (clientAddress == nullptr) -// { -// SocketUtil::ReportError("Creating Client Address"); -// ExitProcess(1); -// return false; -// } -// -// if (sock->Bind(*clientAddress) != NO_ERROR) -// { -// SocketUtil::ReportError("Binding Client Socket"); -// ExitProcess(1); -// } -// LOG("%s", "Client Socket Succesfully Binded!"); -// -// SocketAddressPtr srvAddress = SocketAddressFactory::CreateIPv4FromString(serverIpAddress + ":" + port); -// if (srvAddress == nullptr) -// { -// SocketUtil::ReportError("Creating Server Address"); -// ExitProcess(1); -// } -// -// if (sock->Connect(*srvAddress) != NO_ERROR) -// { -// SocketUtil::ReportError("Connecting To Server"); -// ExitProcess(1); -// } -// LOG("%s", "Succesfully Connect to the Server!"); -// -// *mpTCPSocket = sock; -// -// if (mpTCPSocket != nullptr) -// return true; -// return false; -//} - bool Networker::initServerUDP(std::string serverIpAddress, std::string port) { SocketUtil::StaticInit(); @@ -206,13 +95,6 @@ bool Networker::initServerUDP(std::string serverIpAddress, std::string port) } LOG("%s", "Server Socket Succesfully Binded!"); - //SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString("0.0.0.0:0"); - //if (clientAddress == nullptr) - //{ - // SocketUtil::ReportError("Creating Client Address"); - // ExitProcess(1); - //} - *mpUDPSocket = sock; *mpSocketAddressPtr = sockAddress; From 4548ac8ac2bc70e2eb7a8907aaef7079e4e806ab Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 15 Apr 2022 13:02:24 -0400 Subject: [PATCH 44/56] Fixing sockets binding and connecting, handing it off to Alex. --- RoboCat/Src/Networker.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 213cca53..2680bfbe 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -81,7 +81,7 @@ bool Networker::initServerUDP(std::string serverIpAddress, std::string port) return false; } - SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((serverIpAddress + ":" + port).c_str()); + SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((/*serverIpAddress + */"0.0.0.0:" + port).c_str()); if (sockAddress == nullptr) { SocketUtil::ReportError("Creating Server Address"); @@ -118,15 +118,23 @@ bool Networker::connectUDP(std::string serverIpAddress, std::string port) return false; } - SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((serverIpAddress + ":" + port).c_str()); + SocketAddressPtr servAddress = SocketAddressFactory::CreateIPv4FromString((serverIpAddress + ":" + port).c_str()); + SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((/*serverIpAddress + */"0.0.0.0:" + port).c_str()); if (sockAddress == nullptr) { SocketUtil::ReportError("Creating Server Address"); ExitProcess(1); } + if (sock->Bind(*sockAddress) != NO_ERROR) + { + SocketUtil::ReportError("Binding Client Socket"); + ExitProcess(1); + } + LOG("%s", "Server Socket Succesfully Binded!"); + *mpUDPSocket = sock; - *mpSocketAddressPtr = sockAddress; + *mpSocketAddressPtr = servAddress; if (*mpUDPSocket != nullptr) return true; From 31be9eddab55030bec7aaa6cf29f01d22034f4e5 Mon Sep 17 00:00:00 2001 From: Alex Jaeger Date: Fri, 15 Apr 2022 14:02:45 -0400 Subject: [PATCH 45/56] Fixed UDP init functions so that the server and client connect to each other. Exception being thrown when recieving an UPDATE_PACKET --- RoboCat/Inc/Networker.h | 11 +- RoboCat/Src/Main.cpp | 25 +-- RoboCat/Src/Networker.cpp | 418 +------------------------------------- 3 files changed, 16 insertions(+), 438 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index f8da98b6..f6a387c4 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -33,13 +33,6 @@ enum PacketType PACKET_INVALID }; -//struct GamePacket -//{ -// char* buffer; -// int32_t byteRecieve; -// float dispatchTime; -//}; - //Networker is singleton (we only want one networker at a time) class Networker { @@ -63,8 +56,8 @@ class Networker ~Networker(); //Starting and connect to server - bool initServerUDP(std::string serverIpAddress, std::string port); - bool connectUDP(std::string serverIpAddress, std::string port); + bool initServerUDP(std::string servPort, std::string clientPort); + bool connectUDP(std::string serverIpAddress, std::string servPort, std::string clientPort); //Update game state //PacketType receiveGameObjectState(); diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index 9ba3f6d4..d0187514 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -356,28 +356,22 @@ int main(int argc, const char** argv) bool bIsServer = false; bool bSocketInitted = false; - if (input == "y") + if (input == "y" || input == "Y") { bIsServer = true; } //Setup server and client + + //HARDCODING SOCKETS + std::string servSocket = "1234"; + std::string clientSocket = "2345"; if (bIsServer) { //-------------------------Server code------------------------- - //Prompt for client IP address - std::string myIP; - std::cout << "Enter your IP address: \n"; - std::cin >> myIP; - - //Prompt for port number - std::string portNumber; - std::cout << "Enter port number: \n"; - std::cin >> portNumber; - //bHasConnectd = pNetworkManager->initServer(portNumber); - bSocketInitted = pNetworkManager->initServerUDP(myIP, portNumber); + bSocketInitted = pNetworkManager->initServerUDP(servSocket, clientSocket); if (bSocketInitted) std::cout << "main.cpp --> server initted.\n"; @@ -394,13 +388,8 @@ int main(int argc, const char** argv) std::cout << "Enter server IP address: \n"; std::cin >> serverIP; - //Prompt for port number - std::string portNumber; - std::cout << "Enter port number: \n"; - std::cin >> portNumber; - //bHasConnectd = pNetworkManager->connect(serverIP, portNumber); - bSocketInitted = pNetworkManager->connectUDP(serverIP, portNumber); + bSocketInitted = pNetworkManager->connectUDP(serverIP, servSocket, clientSocket); if (bSocketInitted) std::cout << "main.cpp --> client connected.\n"; diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 2680bfbe..2cb9dc27 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -66,7 +66,7 @@ void Networker::cleanup() mbIsInitted = false; } -bool Networker::initServerUDP(std::string serverIpAddress, std::string port) +bool Networker::initServerUDP(std::string servPort, std::string clientPort) { SocketUtil::StaticInit(); @@ -81,7 +81,7 @@ bool Networker::initServerUDP(std::string serverIpAddress, std::string port) return false; } - SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((/*serverIpAddress + */"0.0.0.0:" + port).c_str()); + SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString(("0.0.0.0:" + servPort).c_str()); if (sockAddress == nullptr) { SocketUtil::ReportError("Creating Server Address"); @@ -95,15 +95,15 @@ bool Networker::initServerUDP(std::string serverIpAddress, std::string port) } LOG("%s", "Server Socket Succesfully Binded!"); - *mpUDPSocket = sock; *mpSocketAddressPtr = sockAddress; + *mpUDPSocket = sock; if (*mpUDPSocket != nullptr) return true; return false; } -bool Networker::connectUDP(std::string serverIpAddress, std::string port) +bool Networker::connectUDP(std::string serverIpAddress, std::string servPort, std::string clientPort) { SocketUtil::StaticInit(); @@ -118,8 +118,8 @@ bool Networker::connectUDP(std::string serverIpAddress, std::string port) return false; } - SocketAddressPtr servAddress = SocketAddressFactory::CreateIPv4FromString((serverIpAddress + ":" + port).c_str()); - SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString((/*serverIpAddress + */"0.0.0.0:" + port).c_str()); + SocketAddressPtr servAddress = SocketAddressFactory::CreateIPv4FromString((serverIpAddress + ":" + servPort).c_str()); + SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString(("0.0.0.0:" + clientPort).c_str()); if (sockAddress == nullptr) { SocketUtil::ReportError("Creating Server Address"); @@ -141,410 +141,6 @@ bool Networker::connectUDP(std::string serverIpAddress, std::string port) return false; } -/* -//Goes through the Queue and processes the packets in order -PacketType Networker::processPacket(GamePacket gamePacket) -{ - if (gamePacket.byteRecieve > 0) - { - InputMemoryBitStream IMBStream = InputMemoryBitStream(gamePacket.buffer, 1024); - - //Start reading - PacketType packetHeader; - IMBStream.Read(packetHeader); - float dispatchTime; - IMBStream.Read(dispatchTime); - int networkID; - IMBStream.Read(networkID); - GameObjectType receiveType; - IMBStream.Read(receiveType); - - //Logic depends on packer header type - switch (packetHeader) - { - case PacketType::PACKET_CREATE: - { - float posX; - float posY; - - IMBStream.Read(posX); - IMBStream.Read(posY); - - switch (receiveType) - { - case GameObjectType::ROCK: - { - Rock* newRock = new Rock(networkID, mpGraphicsLibrary, pair(posX, posY), mRockSpriteIdentifier); - mGameObjectsVec.push_back(pair(networkID, newRock)); - newRock = nullptr; - - break; - } - - case GameObjectType::PLAYER: - { - PlayerController* newPlayerController = new PlayerController(networkID, mpGraphicsLibrary, pair(posX, posY), mPlayerMoveSpeed, mPlayerSpriteIdentifier); - mGameObjectsVec.push_back(pair(networkID, newPlayerController)); - newPlayerController = nullptr; - - break; - - } - - case GameObjectType::WALL: - { - float sizeX; - float sizeY; - float thickness; - - IMBStream.Read(sizeX); - IMBStream.Read(sizeY); - IMBStream.Read(thickness); - - Wall* newWall = new Wall(networkID, mpGraphicsLibrary, pair(posX, posY), sizeX, sizeY, mWallColour, thickness); - mGameObjectsVec.push_back(pair(networkID, newWall)); - newWall = nullptr; - - break; - } - } - - break; - } - - case PacketType::PACKET_UPDATE: - - if (mGameObjectsVec[networkID].second != nullptr) - { - float x; - float y; - - switch (receiveType) - { - case GameObjectType::ROCK: - case GameObjectType::PLAYER: - - IMBStream.Read(x); - IMBStream.Read(y); - mGameObjectsVec[networkID].second->setPos(std::make_pair(x, y)); - break; - - case GameObjectType::WALL: - { - float sizeX; - float sizeY; - float thiccness; - - Wall* wall = (Wall*)mGameObjectsVec[networkID].second; - IMBStream.Read(x); - IMBStream.Read(y); - - wall->setPos(std::make_pair(x, y)); - - IMBStream.Read(sizeX); - IMBStream.Read(sizeY); - IMBStream.Read(thiccness); - - wall->setWallSizeX(sizeX); - wall->setWallSizeY(sizeY); - wall->setWallThickness(thiccness); - - break; - } - } - } - else - { - //Report error - std::cout << "ERROR: CANNOT UPDATE GAMEOBJECT ID " << networkID << " BECAUSE IT IS NOT IN THE NETWORK MANAGER MAP.\n"; - } - - break; - - case PacketType::PACKET_DELETE: - { - //Delete element in map - if (mGameObjectsVec.size() > 0) - { - std::vector>::iterator it; - for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) - { - //DO NOT DELETE A PLAYER - if (it->first == networkID && it->second->getGameObjectType() != GameObjectType::PLAYER) - { - mGameObjectsVec.erase(it); - - break; - } - } - } - - break; - } - - default: - return PacketType::PACKET_INVALID; - } - - return packetHeader; - } -} -*/ - -/* -PacketType Networker::receiveGameObjectState() -{ - char buffer[1024]; - int32_t byteRecieve = (*mpTCPSocket)->Receive(buffer, 1024); - - //GamePacket packet; - //packet.buffer = buffer; - //packet.byteRecieve = byteRecieve; - //packet.dispatchTime = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); - - if (byteRecieve > 0) - { - InputMemoryBitStream IMBStream = InputMemoryBitStream(buffer, 1024); - - //Read sequence number - if (pDeliveryNotificationManager->ReadAndProcessState(IMBStream)) - { - //Start reading - PacketType packetHeader; - IMBStream.Read(packetHeader); - //float dispatchTime; - //IMBStream.Read(dispatchTime); - int networkID; - IMBStream.Read(networkID); - GameObjectType receiveType; - IMBStream.Read(receiveType); - - //Logic depends on packer header type - switch (packetHeader) - { - case PacketType::PACKET_CREATE: - { - float posX; - float posY; - - IMBStream.Read(posX); - IMBStream.Read(posY); - - switch (receiveType) - { - case GameObjectType::ROCK: - { - Rock* newRock = new Rock(networkID, mpGraphicsLibrary, pair(posX, posY), mRockSpriteIdentifier); - mGameObjectsVec.push_back(pair(networkID, newRock)); - newRock = nullptr; - - break; - } - - case GameObjectType::PLAYER: - { - PlayerController* newPlayerController = new PlayerController(networkID, mpGraphicsLibrary, pair(posX, posY), mPlayerMoveSpeed, mPlayerSpriteIdentifier); - mGameObjectsVec.push_back(pair(networkID, newPlayerController)); - newPlayerController = nullptr; - - break; - - } - - case GameObjectType::WALL: - { - float sizeX; - float sizeY; - float thickness; - - IMBStream.Read(sizeX); - IMBStream.Read(sizeY); - IMBStream.Read(thickness); - - Wall* newWall = new Wall(networkID, mpGraphicsLibrary, pair(posX, posY), sizeX, sizeY, mWallColour, thickness); - mGameObjectsVec.push_back(pair(networkID, newWall)); - newWall = nullptr; - - break; - } - } - - break; - } - - case PacketType::PACKET_UPDATE: - - if (mGameObjectsVec[networkID].second != nullptr) - { - float x; - float y; - - switch (receiveType) - { - case GameObjectType::ROCK: - case GameObjectType::PLAYER: - - IMBStream.Read(x); - IMBStream.Read(y); - mGameObjectsVec[networkID].second->setPos(std::make_pair(x, y)); - break; - - case GameObjectType::WALL: - { - float sizeX; - float sizeY; - float thiccness; - - Wall* wall = (Wall*)mGameObjectsVec[networkID].second; - IMBStream.Read(x); - IMBStream.Read(y); - - wall->setPos(std::make_pair(x, y)); - - IMBStream.Read(sizeX); - IMBStream.Read(sizeY); - IMBStream.Read(thiccness); - - wall->setWallSizeX(sizeX); - wall->setWallSizeY(sizeY); - wall->setWallThickness(thiccness); - - break; - } - } - } - else - { - //Report error - std::cout << "ERROR: CANNOT UPDATE GAMEOBJECT ID " << networkID << " BECAUSE IT IS NOT IN THE NETWORK MANAGER MAP.\n"; - } - - break; - - case PacketType::PACKET_DELETE: - { - //Delete element in map - if (mGameObjectsVec.size() > 0) - { - std::vector>::iterator it; - for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) - { - //DO NOT DELETE A PLAYER - if (it->first == networkID && it->second->getGameObjectType() != GameObjectType::PLAYER) - { - mGameObjectsVec.erase(it); - - break; - } - } - } - - break; - } - - default: - return PacketType::PACKET_INVALID; - } - - return packetHeader; - } - } - else if (byteRecieve == -10053 || byteRecieve == -10054) - { - LOG("%s", "Disconnected From Server"); - exit(0); - } - return PacketType::PACKET_INVALID; -} - -void Networker::sendGameObjectState(int ID, PacketType packetHeader) -{ - OutputMemoryBitStream OMBStream; - - //Write state sent (packet sequence number and acks) - pDeliveryNotificationManager->WriteState(OMBStream); - - //Write packet header - OMBStream.Write(packetHeader); - - //float timeDispatched = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); - //OMBStream.Write(timeDispatched); - - OMBStream.Write(mGameObjectsVec[ID].second->getNetworkID()); - OMBStream.Write(mGameObjectsVec[ID].second->getGameObjectType()); - - //Logic depends on packer header type - switch (packetHeader) - { - case PacketType::PACKET_CREATE: - case PacketType::PACKET_UPDATE: - - switch (mGameObjectsVec[ID].second->getGameObjectType()) - { - case GameObjectType::ROCK: - case GameObjectType::PLAYER: - OMBStream.Write(mGameObjectsVec[ID].second->getPosition().first); - OMBStream.Write(mGameObjectsVec[ID].second->getPosition().second); - break; - - case GameObjectType::WALL: - Wall* wall = (Wall*)mGameObjectsVec[ID].second; - OMBStream.Write(wall->getPosition().first); - OMBStream.Write(wall->getPosition().second); - OMBStream.Write(wall->getWallSizeX()); - OMBStream.Write(wall->getWallSizeY()); - OMBStream.Write(wall->getWallThickness()); - break; - } - - break; - - case PacketType::PACKET_DELETE: - { - //Delete it on the sender's end - if (mGameObjectsVec.size() > 0) - { - std::vector>::iterator it; - for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) - { - //DO NOT DELETE A PLAYER - if (it->first == ID && it->second->getGameObjectType() != GameObjectType::PLAYER) - { - mGameObjectsVec.erase(it); - - break; - } - } - } - } - - break; - - default: - return; - } - - //Add packet data to queue in randomised order --> done through timeDispatched +- random and the priority_queue's sorting - float timeDispatched = Timing::sInstance.GetTimef() + (-100 + rand() & (100 - -100 + 1)); - mOutputBitStreamQueue.push(std::make_pair(timeDispatched, OMBStream)); - - //If the queue has more than 10 elements is it - if (mOutputBitStreamQueue.size() >= 10) - { - //Iterate though the queue - for (int i = 0; i < mOutputBitStreamQueue.size(); i++) - { - //Drop some packets - if (i % 3 != 0) - { - //Send packet - (*mpTCPSocket)->Send(OMBStream.GetBufferPtr(), OMBStream.GetBitLength()); - } - } - } -} -*/ - PacketType Networker::receiveGameObjectStateUDP() { char buffer[1024]; @@ -712,7 +308,7 @@ PacketType Networker::receiveGameObjectStateUDP() return packetHeader; } } - else if (byteRecieve == -10053/* || byteRecieve == -10054*/) + else if (byteRecieve == -10053 /*|| byteRecieve == -10054*/) { LOG("%s", "Disconnected From Server"); exit(0); From ec0e57c23ac0e6e5949d3ea4c0103b615533ffd5 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Sun, 17 Apr 2022 17:41:02 -0400 Subject: [PATCH 46/56] Players connect to each other, but do not create the instances of the other player, causing create packets to break because there are not enough things in the gameobjects vector in networker, causign an index out of range error. --- RoboCat/Inc/Networker.h | 8 +---- RoboCat/Src/Main.cpp | 16 +++++---- RoboCat/Src/Networker.cpp | 74 +++++---------------------------------- 3 files changed, 19 insertions(+), 79 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index f6a387c4..b2b1feca 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -56,7 +56,7 @@ class Networker ~Networker(); //Starting and connect to server - bool initServerUDP(std::string servPort, std::string clientPort); + bool initServerUDP(std::string clientIpAddress, std::string servPort, std::string clientPort); bool connectUDP(std::string serverIpAddress, std::string servPort, std::string clientPort); //Update game state @@ -67,7 +67,6 @@ class Networker PacketType receiveGameObjectStateUDP(); void sendGameObjectStateUDP(int networkID, PacketType packetHeader); void checkTimedOutPackets(); - //PacketType processPacket(GamePacket gamePacket); //Map void addGameObject(GameObject* objectToAdd, int networkID); @@ -75,11 +74,6 @@ class Networker void updateGameObjects(); void renderGameObjects(); - ////Queue - //void sortPacketQueue(); - //int findMinIndex(int sortedIndex); - //void insertMinIndexToEnd(int minIndex); - private: Networker(); diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index d0187514..63dd3700 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -363,15 +363,19 @@ int main(int argc, const char** argv) //Setup server and client - //HARDCODING SOCKETS - std::string servSocket = "1234"; - std::string clientSocket = "2345"; + //HARDCODING PORT NUMBERS + std::string servPort = "1234"; + std::string clientPort = "2345"; if (bIsServer) { //-------------------------Server code------------------------- - //bHasConnectd = pNetworkManager->initServer(portNumber); - bSocketInitted = pNetworkManager->initServerUDP(servSocket, clientSocket); + //Prompt for client IP address + std::string clientIP; + std::cout << "Enter client IP address: \n"; + std::cin >> clientIP; + + bSocketInitted = pNetworkManager->initServerUDP(clientIP, servPort, clientPort); if (bSocketInitted) std::cout << "main.cpp --> server initted.\n"; @@ -389,7 +393,7 @@ int main(int argc, const char** argv) std::cin >> serverIP; //bHasConnectd = pNetworkManager->connect(serverIP, portNumber); - bSocketInitted = pNetworkManager->connectUDP(serverIP, servSocket, clientSocket); + bSocketInitted = pNetworkManager->connectUDP(serverIP, servPort, clientPort); if (bSocketInitted) std::cout << "main.cpp --> client connected.\n"; diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 2cb9dc27..8a33943f 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -66,7 +66,7 @@ void Networker::cleanup() mbIsInitted = false; } -bool Networker::initServerUDP(std::string servPort, std::string clientPort) +bool Networker::initServerUDP(std::string clientIpAddress, std::string servPort, std::string clientPort) { SocketUtil::StaticInit(); @@ -81,6 +81,7 @@ bool Networker::initServerUDP(std::string servPort, std::string clientPort) return false; } + SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString((clientIpAddress + ":" + clientPort).c_str()); SocketAddressPtr sockAddress = SocketAddressFactory::CreateIPv4FromString(("0.0.0.0:" + servPort).c_str()); if (sockAddress == nullptr) { @@ -95,7 +96,7 @@ bool Networker::initServerUDP(std::string servPort, std::string clientPort) } LOG("%s", "Server Socket Succesfully Binded!"); - *mpSocketAddressPtr = sockAddress; + *mpSocketAddressPtr = clientAddress; *mpUDPSocket = sock; if (*mpUDPSocket != nullptr) @@ -146,11 +147,6 @@ PacketType Networker::receiveGameObjectStateUDP() char buffer[1024]; int32_t byteRecieve = (*mpUDPSocket)->ReceiveFrom(buffer, 1024, (**mpSocketAddressPtr)); - //GamePacket packet; - //packet.buffer = buffer; - //packet.byteRecieve = byteRecieve; - //packet.dispatchTime = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); - if (byteRecieve > 0) { InputMemoryBitStream IMBStream = InputMemoryBitStream(buffer, 1024); @@ -161,8 +157,6 @@ PacketType Networker::receiveGameObjectStateUDP() //Start reading PacketType packetHeader; IMBStream.Read(packetHeader); - //float dispatchTime; - //IMBStream.Read(dispatchTime); int networkID; IMBStream.Read(networkID); @@ -318,7 +312,7 @@ PacketType Networker::receiveGameObjectStateUDP() void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) { - OutputMemoryBitStream* OMBStream = new OutputMemoryBitStream; + OutputMemoryBitStream* OMBStream = new OutputMemoryBitStream(); //Write state sent (packet sequence number and acks) pDeliveryNotificationManager->WriteState(*OMBStream); @@ -337,6 +331,9 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) //Send packet and return from the function (*mpUDPSocket)->SendTo(OMBStream->GetBufferPtr(), OMBStream->GetBitLength(), (**mpSocketAddressPtr)); + + delete OMBStream; + OMBStream = nullptr; return; } @@ -444,59 +441,4 @@ void Networker::renderGameObjects() { it->second->draw(); } -} - - -//void Networker::sortPacketQueue() -//{ -// for (int i = 1; i <= mPacketQueue.size(); ++i) -// { -// int min = findMinIndex(mPacketQueue.size() - i); -// insertMinIndexToEnd(min); -// } -//} -// -// -//int Networker::findMinIndex(int sortedIndex) -//{ -// int minIndex = -1; -// int minVal = INT_MAX; -// for (int i = 0; i < mPacketQueue.size(); i++) -// { -// std::pair currentPacketTime = mPacketQueue.front(); -// mPacketQueue.pop(); -// -// if (currentPacketTime.first <= minVal && i <= sortedIndex) -// { -// minIndex = i; -// minVal = currentPacketTime.first; -// } -// mPacketQueue.push(currentPacketTime); -// } -// -// return minIndex; -//} -// -//void Networker::insertMinIndexToEnd(int minIndex) -//{ -// if (minIndex == -1) -// { -// return; -// } -// -// std::pair minVal; -// for (int i = 0; i < mPacketQueue.size(); i++) -// { -// pair currentPacket = mPacketQueue.front(); -// mPacketQueue.pop(); -// if (i != minIndex) -// { -// mPacketQueue.push(currentPacket); -// } -// else -// { -// minVal = currentPacket; -// } -// } -// mPacketQueue.push(minVal); -//} \ No newline at end of file +} \ No newline at end of file From 875428f3c665bdb446ef6cc76cc0d1736ed4e630 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Sun, 17 Apr 2022 17:52:41 -0400 Subject: [PATCH 47/56] Client not receiving server's PACKET_CREATE, which in turn will not let it send a PACKET_CREATE to the server. --- RoboCat/Src/Main.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index 63dd3700..9a2f819b 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -117,19 +117,22 @@ void start() networkID++; //Send it out - //pNetworkManager->sendGameObjectState(pPlayerController->getNetworkID(), PacketType::PACKET_CREATE); pNetworkManager->sendGameObjectStateUDP(pPlayerController->getNetworkID(), PacketType::PACKET_CREATE); //Get client player - //pNetworkManager->receiveGameObjectState(); - pNetworkManager->receiveGameObjectStateUDP(); + while (packetTypeReceived != PACKET_CREATE) + { + packetTypeReceived = pNetworkManager->receiveGameObjectStateUDP(); + } } //If client else if (networkID == 1) { //Get server player - //pNetworkManager->receiveGameObjectState(); - pNetworkManager->receiveGameObjectStateUDP(); + while (packetTypeReceived != PACKET_CREATE) + { + packetTypeReceived = pNetworkManager->receiveGameObjectStateUDP(); + } //Spawn player pPlayerController = new PlayerController(networkID, pGraphics, startingPlayerPos, playerMoveSpeed, PLAYER_SPRITE_IDENTIFIER); @@ -137,7 +140,6 @@ void start() networkID++; //Send it out - //pNetworkManager->sendGameObjectState(pPlayerController->getNetworkID(), PacketType::PACKET_CREATE); pNetworkManager->sendGameObjectStateUDP(pPlayerController->getNetworkID(), PacketType::PACKET_CREATE); } From 4153147444b1e6dfdd77092bcc19727f3cde386c Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Sun, 17 Apr 2022 23:53:46 -0400 Subject: [PATCH 48/56] IT WORKS BABYYYYYYYY. I made sure it sends immediately if the ID is < 2, to make sure both players get the data. There is a clear delay, but a little odd with object spawning. --- RoboCat/Src/Networker.cpp | 44 +++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 8a33943f..232a56bc 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -391,28 +391,40 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) return; } - //Add packet data to queue in randomised order --> done through timeDispatched +- random and the priority_queue's sorting - float timeDispatched = Timing::sInstance.GetTimef() + (-100 + rand() & (100 - -100 + 1)); - mOutputBitStreamQueue.push(std::make_pair(timeDispatched, OMBStream)); - OMBStream = nullptr; - - //If the queue has more than 10 elements is it - if (mOutputBitStreamQueue.size() >= 10) + //Make sure to send the first two gameobjects so both players can connect and be displayed on screen + if (ID > 1) { - //Iterate though the queue - //for (size_t i = mOutputBitStreamQueue.size() - 1; i >= 0 ; i--) - for (size_t i = 0; i < mOutputBitStreamQueue.size(); i++) + //Add packet data to queue in randomised order --> done through timeDispatched +- random and the priority_queue's sorting + float timeDispatched = Timing::sInstance.GetTimef() + (-100 + rand() & (100 - -100 + 1)); + mOutputBitStreamQueue.push(std::make_pair(timeDispatched, OMBStream)); + OMBStream = nullptr; + + //If the queue has more than 10 elements is it + if (mOutputBitStreamQueue.size() >= 10) { - //Drop some packets - if (i % 3 != 0) + //Iterate though the queue + //for (size_t i = mOutputBitStreamQueue.size() - 1; i >= 0 ; i--) + for (size_t i = 0; i < mOutputBitStreamQueue.size(); i++) { - //Send packet - (*mpUDPSocket)->SendTo(mOutputBitStreamQueue.top().second->GetBufferPtr(), mOutputBitStreamQueue.top().second->GetBitLength(), (**mpSocketAddressPtr)); - delete mOutputBitStreamQueue.top().second; - mOutputBitStreamQueue.pop(); + //Drop some packets + if (i % 3 != 0) + { + //Send packet + (*mpUDPSocket)->SendTo(mOutputBitStreamQueue.top().second->GetBufferPtr(), mOutputBitStreamQueue.top().second->GetBitLength(), (**mpSocketAddressPtr)); + delete mOutputBitStreamQueue.top().second; + mOutputBitStreamQueue.pop(); + } } } } + else + { + //Send packet + (*mpUDPSocket)->SendTo(OMBStream->GetBufferPtr(), OMBStream->GetBitLength(), (**mpSocketAddressPtr)); + + delete OMBStream; + OMBStream = nullptr; + } } void Networker::checkTimedOutPackets() From 24baec232343f71302abf57fe0f7bc836bcb586e Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Mon, 18 Apr 2022 00:06:47 -0400 Subject: [PATCH 49/56] Resized window. Sometime you can control both players from one instance, but it will eventually stop adn go back to normal. No idea why but fuck it. --- RoboCat/Src/Main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index 9a2f819b..ecf9fd14 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -12,8 +12,8 @@ //-------------------------Graphics Data------------------------- GraphicsLibrary* pGraphics; -float screenSizeX = 1600.0; -float screenSizeY = 900.0; +float screenSizeX = 1200.0; +float screenSizeY = 725.0; //-------------------------Input Data------------------------- InputSystem* pInput; From a0de7632d0831d7823f07a84d7048a1424010ef7 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Mon, 18 Apr 2022 00:23:25 -0400 Subject: [PATCH 50/56] txt --- TO-DO.txt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/TO-DO.txt b/TO-DO.txt index 4afa4dec..9f323cdb 100644 --- a/TO-DO.txt +++ b/TO-DO.txt @@ -10,21 +10,11 @@ Assignment Requirements: - Packets are occasionally "dropped," by random chance or by some algorithm. Dropped packets are registered as having been sent by the sender, but do not actually call the socket's send() function. Random delay is introduced to some packet's send time, and packets can be delivered out of order. - Game state is sent through a reliability layer. Important data is sent through guaranteed mechanisms, while data that does not require guaranteed delivery is sent through simpler mechanisms. Important data is resilient to jitter, out-of-order delivery, and high latency. ---------------------------------------------------------------- -Questions: - -. For the assignment, are we combining TCP with a custom UDP solution? - --> UDP + our custom reliability code - --------------------------------------------------------------- Notes and Ideas: --> Chapter 7 slide 14 gives a good explanation of how this can be done -. Sync PacketSequenceNumbers on server and client! - --> Not needed I don't think -. Handle success or failure in TransmissionData (make GameObject derived classes also derive from and override functions) - ----------------------------------------------------------------- Simulating Latency/Jitter From 4e42329fb5e8dd1dd1372f66a237ac097270b58c Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Tue, 19 Apr 2022 12:15:05 -0400 Subject: [PATCH 51/56] Making it SetTransmissionData for InFlightPackets. Not working yet, I need to convert from GameObject* to TransmissionDataPtr. GameObject base class now derives from TransmissionData, instead of each derived class. --- RoboCat/Inc/GameObject.h | 3 ++- RoboCat/Inc/PlayerController.h | 4 ++-- RoboCat/Inc/Rock.h | 4 ++-- RoboCat/Inc/Wall.h | 4 ++-- RoboCat/Src/Networker.cpp | 11 +++++++---- TO-DO.txt | 12 ++++++++++++ 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/RoboCat/Inc/GameObject.h b/RoboCat/Inc/GameObject.h index d726281c..79973f64 100644 --- a/RoboCat/Inc/GameObject.h +++ b/RoboCat/Inc/GameObject.h @@ -2,6 +2,7 @@ #include #include "GraphicsLibrary.h" +#include "TransmissionData.h" using std::pair; @@ -19,7 +20,7 @@ enum GameObjectType ENUM_SIZE }; -class GameObject +class GameObject : public TransmissionData { //-------------------------Private data------------------------- diff --git a/RoboCat/Inc/PlayerController.h b/RoboCat/Inc/PlayerController.h index 1f4eda52..722a18f4 100644 --- a/RoboCat/Inc/PlayerController.h +++ b/RoboCat/Inc/PlayerController.h @@ -7,9 +7,9 @@ #include "GameObject.h" #include "InputSystem.h" -#include "TransmissionData.h" +//#include "TransmissionData.h" -class PlayerController : public GameObject, public TransmissionData +class PlayerController : public GameObject/*, public TransmissionData*/ { //-------------------------Private data------------------------- diff --git a/RoboCat/Inc/Rock.h b/RoboCat/Inc/Rock.h index 8e7e2302..dbf79f8a 100644 --- a/RoboCat/Inc/Rock.h +++ b/RoboCat/Inc/Rock.h @@ -6,9 +6,9 @@ */ #include "GameObject.h" -#include "TransmissionData.h" +//#include "TransmissionData.h" -class Rock : public GameObject, public TransmissionData +class Rock : public GameObject/*, public TransmissionData*/ { //-------------------------Private data------------------------- diff --git a/RoboCat/Inc/Wall.h b/RoboCat/Inc/Wall.h index b7499fa9..5c7771c4 100644 --- a/RoboCat/Inc/Wall.h +++ b/RoboCat/Inc/Wall.h @@ -7,9 +7,9 @@ #include "GameObject.h" #include "Colour.h" -#include "TransmissionData.h" +//#include "TransmissionData.h" -class Wall : public GameObject, public TransmissionData +class Wall : public GameObject/*, public TransmissionData*/ { //-------------------------Private data------------------------- float mSizeX; diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 232a56bc..5b4068b1 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -315,14 +315,11 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) OutputMemoryBitStream* OMBStream = new OutputMemoryBitStream(); //Write state sent (packet sequence number and acks) - pDeliveryNotificationManager->WriteState(*OMBStream); + InFlightPacket* pInFlightPacket = pDeliveryNotificationManager->WriteState(*OMBStream); //Write packet header OMBStream->Write(packetHeader); - //float timeDispatched = mArrivalTime + (-100 + rand() & (100 - -100 + 1)); - //OMBStream.Write(timeDispatched); - //If ID received is < 0, we are doing connection stuff if (ID < 0) { @@ -352,6 +349,8 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) case GameObjectType::PLAYER: OMBStream->Write(mGameObjectsVec[ID].second->getPosition().first); OMBStream->Write(mGameObjectsVec[ID].second->getPosition().second); + pInFlightPacket->SetTransmissionData(0, (*mGameObjectsVec[ID].second)); + pInFlightPacket = nullptr; break; case GameObjectType::WALL: @@ -361,6 +360,8 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) OMBStream->Write(wall->getWallSizeX()); OMBStream->Write(wall->getWallSizeY()); OMBStream->Write(wall->getWallThickness()); + pInFlightPacket->SetTransmissionData(0, (*wall)); + pInFlightPacket = nullptr; break; } @@ -378,6 +379,8 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) if (it->first == ID && it->second->getGameObjectType() != GameObjectType::PLAYER) { mGameObjectsVec.erase(it); + pInFlightPacket->SetTransmissionData(0, (*it->second)); + pInFlightPacket = nullptr; break; } diff --git a/TO-DO.txt b/TO-DO.txt index 9f323cdb..f9c8eecf 100644 --- a/TO-DO.txt +++ b/TO-DO.txt @@ -15,6 +15,18 @@ Notes and Ideas: --> Chapter 7 slide 14 gives a good explanation of how this can be done +CLASS NOTES 19/04/2022: +For handling delviery failure and success, InFlightPacket implicit constructor call, need to give it transmission data. How? +--> override void HandleDelivery... in inherited classes +--> Pass an instance of the class to InFlightPacket +--> Tip: use std::shared_ptr as type so you don't have to worry about memory leaks +--> InFlightPacket::SetTransmissionData(any key (0 is best as it is the index and we only want 1 thing in container), instance of class); + +--> TransmissionDataPtr classInstance = std::make_shared(constructor args); // +--> InFlightPacket* packet = deliveryNotificationManager->WriteState(packetData); +--> packet->SetTransmissionData(index, classInstance); +--> //Write rest of packetData + ----------------------------------------------------------------- Simulating Latency/Jitter From daede02e380c21832a954ba0602fcbe279986df4 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Tue, 19 Apr 2022 12:21:55 -0400 Subject: [PATCH 52/56] Created copy constructor for gameObject, I don't think this will be useful anymore though, but I'm not sure. STILL NEED TO CONVERT GameObject* to TransmissionDataPtr. --- RoboCat/Inc/GameObject.h | 3 +++ RoboCat/Src/GameObject.cpp | 11 +++++++++++ RoboCat/Src/Networker.cpp | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/RoboCat/Inc/GameObject.h b/RoboCat/Inc/GameObject.h index 79973f64..ef412ed8 100644 --- a/RoboCat/Inc/GameObject.h +++ b/RoboCat/Inc/GameObject.h @@ -49,6 +49,9 @@ class GameObject : public TransmissionData //-------------------------Public data------------------------- public: + //Copy constructor + GameObject(const GameObject& copy); + //Destructor virtual ~GameObject(); diff --git a/RoboCat/Src/GameObject.cpp b/RoboCat/Src/GameObject.cpp index e51952ad..44e7a2ce 100644 --- a/RoboCat/Src/GameObject.cpp +++ b/RoboCat/Src/GameObject.cpp @@ -21,6 +21,17 @@ GameObject::GameObject(GameObjectType gameObjectType, const int networkID, Graph //Position mPosition = position; } + +GameObject::GameObject(const GameObject& copy) + : mGameObjectType(copy.mGameObjectType), mNetworkID(copy.mNetworkID), mSPRITE_IDENTIFIER(copy.mSPRITE_IDENTIFIER) +{ + //Graphics library + pGraphicsLibrary = copy.pGraphicsLibrary; + + //Position + mPosition = copy.mPosition; +} + GameObject::~GameObject() { //Graphics library gets cleaned up in main.cpp, just de-reference it here diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 5b4068b1..5dc30230 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -349,7 +349,7 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) case GameObjectType::PLAYER: OMBStream->Write(mGameObjectsVec[ID].second->getPosition().first); OMBStream->Write(mGameObjectsVec[ID].second->getPosition().second); - pInFlightPacket->SetTransmissionData(0, (*mGameObjectsVec[ID].second)); + pInFlightPacket->SetTransmissionData(0, std::make_shared(*mGameObjectsVec[ID].second)); pInFlightPacket = nullptr; break; From db288e5bd9886e3038a39867f297054127da56fa Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Tue, 19 Apr 2022 12:43:07 -0400 Subject: [PATCH 53/56] The player controllers sent are becoming invalid so they cannot update or draw. Set beakpoint in Networker::send() for PACKET_UPDATE when setting transmission data and follow though (line 352). Seems fine until breakpoint, couldn't investigate further because class is almost over and I am handing it off to Alex. --- RoboCat/Src/Networker.cpp | 12 ++++++------ TO-DO.txt | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 5dc30230..a057c882 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -349,8 +349,8 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) case GameObjectType::PLAYER: OMBStream->Write(mGameObjectsVec[ID].second->getPosition().first); OMBStream->Write(mGameObjectsVec[ID].second->getPosition().second); - pInFlightPacket->SetTransmissionData(0, std::make_shared(*mGameObjectsVec[ID].second)); - pInFlightPacket = nullptr; + pInFlightPacket->SetTransmissionData(0, std::shared_ptr(mGameObjectsVec[ID].second)); + //pInFlightPacket = nullptr; break; case GameObjectType::WALL: @@ -360,8 +360,8 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) OMBStream->Write(wall->getWallSizeX()); OMBStream->Write(wall->getWallSizeY()); OMBStream->Write(wall->getWallThickness()); - pInFlightPacket->SetTransmissionData(0, (*wall)); - pInFlightPacket = nullptr; + pInFlightPacket->SetTransmissionData(0, std::shared_ptr(wall)); + //pInFlightPacket = nullptr; break; } @@ -379,8 +379,8 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) if (it->first == ID && it->second->getGameObjectType() != GameObjectType::PLAYER) { mGameObjectsVec.erase(it); - pInFlightPacket->SetTransmissionData(0, (*it->second)); - pInFlightPacket = nullptr; + pInFlightPacket->SetTransmissionData(0, std::shared_ptr(it->second)); + //pInFlightPacket = nullptr; break; } diff --git a/TO-DO.txt b/TO-DO.txt index f9c8eecf..8c715421 100644 --- a/TO-DO.txt +++ b/TO-DO.txt @@ -22,7 +22,7 @@ For handling delviery failure and success, InFlightPacket implicit constructor c --> Tip: use std::shared_ptr as type so you don't have to worry about memory leaks --> InFlightPacket::SetTransmissionData(any key (0 is best as it is the index and we only want 1 thing in container), instance of class); ---> TransmissionDataPtr classInstance = std::make_shared(constructor args); // +--> TransmissionDataPtr classInstance = std::make_shared(constructor args); --> InFlightPacket* packet = deliveryNotificationManager->WriteState(packetData); --> packet->SetTransmissionData(index, classInstance); --> //Write rest of packetData From 6ec8835c1a80a477a75cefa2d6da69611355259d Mon Sep 17 00:00:00 2001 From: Alex Jaeger Date: Tue, 19 Apr 2022 14:01:06 -0400 Subject: [PATCH 54/56] Ran some tests to confirm player data is being sent and recieved properly. My guess is that it may not be getting set properly after the values are recieved or draw() needs to be altered to take into account new code. Pushing because I am leaving class --- RoboCat/Inc/Networker.h | 7 ------- RoboCat/Src/Main.cpp | 7 ++----- RoboCat/Src/Networker.cpp | 12 ++++++++++-- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index b2b1feca..8fc72654 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -59,10 +59,6 @@ class Networker bool initServerUDP(std::string clientIpAddress, std::string servPort, std::string clientPort); bool connectUDP(std::string serverIpAddress, std::string servPort, std::string clientPort); - //Update game state - //PacketType receiveGameObjectState(); - //void sendGameObjectState(int networkID, PacketType packetHeader); - //Update game state - UDP PacketType receiveGameObjectStateUDP(); void sendGameObjectStateUDP(int networkID, PacketType packetHeader); @@ -77,17 +73,14 @@ class Networker private: Networker(); - static Networker* mInstance; //Data UDPSocketPtr* mpUDPSocket; SocketAddressPtr* mpSocketAddressPtr; std::vector> mGameObjectsVec; - //std::queue mInFlightPacketsQueue; DeliveryNotificationManager* pDeliveryNotificationManager; std::priority_queue, std::vector>, myComp> mOutputBitStreamQueue; - //int mArrivalTime; bool mbIsInitted; //Data for GameObject replication diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index ecf9fd14..ade97f94 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -67,6 +67,8 @@ Networker* Networker::mInstance = 0; Networker* pNetworkManager; PacketType packetTypeReceived = PacketType::PACKET_INVALID; int networkID = 0; +std::string servPort = "1234"; +std::string clientPort = "2345"; bool init() { @@ -365,9 +367,6 @@ int main(int argc, const char** argv) //Setup server and client - //HARDCODING PORT NUMBERS - std::string servPort = "1234"; - std::string clientPort = "2345"; if (bIsServer) { //-------------------------Server code------------------------- @@ -437,11 +436,9 @@ int main(int argc, const char** argv) update(); //Network updates - send player data - //pNetworkManager->sendGameObjectState(pPlayerController->getNetworkID(), PacketType::PACKET_UPDATE); pNetworkManager->sendGameObjectStateUDP(pPlayerController->getNetworkID(), PacketType::PACKET_UPDATE); //Network update - receive packets - //packetTypeReceived = pNetworkManager->receiveGameObjectState(); packetTypeReceived = pNetworkManager->receiveGameObjectStateUDP(); //Network check for and deal with timed out packets diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index a057c882..86503899 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -20,12 +20,10 @@ void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIde } std::srand(time(NULL)); - //mArrivalTime = arrivalTime; mpUDPSocket = new UDPSocketPtr(); mpSocketAddressPtr = new SocketAddressPtr(); mGameObjectsVec = std::vector>(); - //mPacketQueue = std::queue>(); pDeliveryNotificationManager = new DeliveryNotificationManager(true, true, this); mOutputBitStreamQueue = std::priority_queue, std::vector>, myComp>(); @@ -239,6 +237,11 @@ PacketType Networker::receiveGameObjectStateUDP() IMBStream.Read(x); IMBStream.Read(y); + + //Checking to absolutly make sure values are getting recieved + std::cout << "Recieved Position X: " << x << std::endl; + std::cout << "Recieved Position Y: " << y << std::endl; + mGameObjectsVec[networkID].second->setPos(std::make_pair(x, y)); break; @@ -347,6 +350,11 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) { case GameObjectType::ROCK: case GameObjectType::PLAYER: + + //For testing to absolutly make sure values are getting written + std::cout << "Position X: " << mGameObjectsVec[ID].second->getPosition().first << std::endl; + std::cout << "Position Y: " << mGameObjectsVec[ID].second->getPosition().second << std::endl; + OMBStream->Write(mGameObjectsVec[ID].second->getPosition().first); OMBStream->Write(mGameObjectsVec[ID].second->getPosition().second); pInFlightPacket->SetTransmissionData(0, std::shared_ptr(mGameObjectsVec[ID].second)); From d2308f5d19a944388fd1c1a63ae0cb8d72833ab3 Mon Sep 17 00:00:00 2001 From: Adel Talhouk Date: Fri, 22 Apr 2022 12:50:29 -0400 Subject: [PATCH 55/56] Removed shared pointers, data does not get fucked up anymore. Need to figure out where to delete gameobjects after inflight packet is done dealing with data. --- RoboCat/Inc/InFlightPacket.h | 6 +++--- RoboCat/Src/Networker.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/RoboCat/Inc/InFlightPacket.h b/RoboCat/Inc/InFlightPacket.h index 7d6a95d9..9e620e3a 100644 --- a/RoboCat/Inc/InFlightPacket.h +++ b/RoboCat/Inc/InFlightPacket.h @@ -18,11 +18,11 @@ class InFlightPacket PacketSequenceNumber GetSequenceNumber() const { return mSequenceNumber; } float GetTimeDispatched() const { return mTimeDispatched; } - void SetTransmissionData( int inKey, TransmissionDataPtr inTransmissionData ) + void SetTransmissionData( int inKey, TransmissionData* inTransmissionData ) { mTransmissionDataMap[ inKey ] = inTransmissionData; } - const TransmissionDataPtr GetTransmissionData( int inKey ) const + const TransmissionData* GetTransmissionData( int inKey ) const { auto it = mTransmissionDataMap.find( inKey ); return ( it != mTransmissionDataMap.end() ) ? it->second : nullptr; @@ -35,5 +35,5 @@ class InFlightPacket PacketSequenceNumber mSequenceNumber; float mTimeDispatched; - unordered_map< int, TransmissionDataPtr > mTransmissionDataMap; + unordered_map< int, TransmissionData* > mTransmissionDataMap; }; \ No newline at end of file diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 86503899..34423cde 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -357,7 +357,7 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) OMBStream->Write(mGameObjectsVec[ID].second->getPosition().first); OMBStream->Write(mGameObjectsVec[ID].second->getPosition().second); - pInFlightPacket->SetTransmissionData(0, std::shared_ptr(mGameObjectsVec[ID].second)); + pInFlightPacket->SetTransmissionData(0, mGameObjectsVec[ID].second); //pInFlightPacket = nullptr; break; @@ -368,7 +368,7 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) OMBStream->Write(wall->getWallSizeX()); OMBStream->Write(wall->getWallSizeY()); OMBStream->Write(wall->getWallThickness()); - pInFlightPacket->SetTransmissionData(0, std::shared_ptr(wall)); + pInFlightPacket->SetTransmissionData(0, wall); //pInFlightPacket = nullptr; break; } @@ -387,7 +387,7 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) if (it->first == ID && it->second->getGameObjectType() != GameObjectType::PLAYER) { mGameObjectsVec.erase(it); - pInFlightPacket->SetTransmissionData(0, std::shared_ptr(it->second)); + pInFlightPacket->SetTransmissionData(0, it->second); //pInFlightPacket = nullptr; break; From 667b7f23f5c7071684da9e145627233d6b84f33f Mon Sep 17 00:00:00 2001 From: Alex Jaeger Date: Fri, 22 Apr 2022 14:06:52 -0400 Subject: [PATCH 56/56] =?UTF-8?q?Started=20reimplementing=20shared=5Fptrs,?= =?UTF-8?q?=20haven't=20tested=20yet=20since=20I=20had=20to=20run=20back?= =?UTF-8?q?=20to=20my=20place=20(need=20to=20get=20there=20before=20my=20r?= =?UTF-8?q?oomates=20leave,=20forgot=20ID=20=C2=AF\=5F(=E3=83=84)=5F/?= =?UTF-8?q?=C2=AF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RoboCat/Inc/Networker.h | 6 ++-- RoboCat/Src/Main.cpp | 25 ++++++++------ RoboCat/Src/Networker.cpp | 68 ++++++++++++++++++++++++++------------- 3 files changed, 65 insertions(+), 34 deletions(-) diff --git a/RoboCat/Inc/Networker.h b/RoboCat/Inc/Networker.h index 8fc72654..41caed9b 100644 --- a/RoboCat/Inc/Networker.h +++ b/RoboCat/Inc/Networker.h @@ -65,8 +65,8 @@ class Networker void checkTimedOutPackets(); //Map - void addGameObject(GameObject* objectToAdd, int networkID); - GameObject* getGameObject(int networkID) { return mGameObjectsVec[networkID].second; }; + void addGameObject(/*GameObject**/ shared_ptr objectToAdd, int networkID); + /*GameObject**/ shared_ptr getGameObject(int networkID) { return mGameObjectsVec[networkID].second; }; void updateGameObjects(); void renderGameObjects(); @@ -78,7 +78,7 @@ class Networker //Data UDPSocketPtr* mpUDPSocket; SocketAddressPtr* mpSocketAddressPtr; - std::vector> mGameObjectsVec; + std::vector>> mGameObjectsVec; DeliveryNotificationManager* pDeliveryNotificationManager; std::priority_queue, std::vector>, myComp> mOutputBitStreamQueue; bool mbIsInitted; diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index ade97f94..e40ab2ff 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -55,7 +55,8 @@ GameObjectType currentGameObjectType; std::string currentGameObjectTypeString; //-------------------------Player Data------------------------- -PlayerController* pPlayerController; +//PlayerController* pPlayerController; +shared_ptr pPlayerController; std::pair startingPlayerPos; const std::pair STARTING_PLAYER_POSITION_SERVER = std::make_pair(300.0, 300.0); @@ -114,7 +115,8 @@ void start() if (networkID == 0) { //Spawn player - pPlayerController = new PlayerController(networkID, pGraphics, startingPlayerPos, playerMoveSpeed, PLAYER_SPRITE_IDENTIFIER); + //pPlayerController = new PlayerController(networkID, pGraphics, startingPlayerPos, playerMoveSpeed, PLAYER_SPRITE_IDENTIFIER); + pPlayerController = std::make_shared(PlayerController(networkID, pGraphics, startingPlayerPos, playerMoveSpeed, PLAYER_SPRITE_IDENTIFIER)); pNetworkManager->addGameObject(pPlayerController, networkID); networkID++; @@ -137,7 +139,8 @@ void start() } //Spawn player - pPlayerController = new PlayerController(networkID, pGraphics, startingPlayerPos, playerMoveSpeed, PLAYER_SPRITE_IDENTIFIER); + //pPlayerController = new PlayerController(networkID, pGraphics, startingPlayerPos, playerMoveSpeed, PLAYER_SPRITE_IDENTIFIER); + pPlayerController = std::make_shared(PlayerController(networkID, pGraphics, startingPlayerPos, playerMoveSpeed, PLAYER_SPRITE_IDENTIFIER)); pNetworkManager->addGameObject(pPlayerController, networkID); networkID++; @@ -162,15 +165,17 @@ void update() if (!bCanSpawnGameObject) { //Spawn current GameObject type - GameObject* gameObjectToSpawn; + //GameObject* gameObjectToSpawn; switch (currentGameObjectType) { case GameObjectType::ROCK: { pair mousePos = std::make_pair(pInput->getMouseX(), pInput->getMouseY()); - gameObjectToSpawn = dynamic_cast(new Rock(networkID, pGraphics, mousePos, ROCK_SPRITE_IDENTIFIER)); - pNetworkManager->addGameObject(gameObjectToSpawn, networkID); + //gameObjectToSpawn = dynamic_cast(new Rock(networkID, pGraphics, mousePos, ROCK_SPRITE_IDENTIFIER)); + //gameObjectToSpawn = dynamic_cast>(Rock(networkID, pGraphics, mousePos, ROCK_SPRITE_IDENTIFIER)); + shared_ptr newRock = std::make_shared(Rock(networkID, pGraphics, mousePos, ROCK_SPRITE_IDENTIFIER)); + pNetworkManager->addGameObject(newRock, networkID); //pNetworkManager->sendGameObjectState(networkID, PacketType::PACKET_CREATE); pNetworkManager->sendGameObjectStateUDP(networkID, PacketType::PACKET_CREATE); networkID++; @@ -180,8 +185,9 @@ void update() case GameObjectType::WALL: { pair mousePos = std::make_pair(pInput->getMouseX(), pInput->getMouseY()); - gameObjectToSpawn = dynamic_cast(new Wall(networkID, pGraphics, mousePos, wallSizeX, wallSizeY, wallColour, wallBorderThickness)); - pNetworkManager->addGameObject(gameObjectToSpawn, networkID); + //gameObjectToSpawn = dynamic_cast(new Wall(networkID, pGraphics, mousePos, wallSizeX, wallSizeY, wallColour, wallBorderThickness)); + shared_ptr newWall = std::make_shared(Wall(networkID, pGraphics, mousePos, wallSizeX, wallSizeY, wallColour, wallBorderThickness)); + pNetworkManager->addGameObject(newWall, networkID); //pNetworkManager->sendGameObjectState(networkID, PacketType::PACKET_CREATE); pNetworkManager->sendGameObjectStateUDP(networkID, PacketType::PACKET_CREATE); networkID++; @@ -197,7 +203,8 @@ void update() } } - gameObjectToSpawn = nullptr; + //delete gameObjectToSpawn; + //gameObjectToSpawn = nullptr; bCanSpawnGameObject = true; } } diff --git a/RoboCat/Src/Networker.cpp b/RoboCat/Src/Networker.cpp index 34423cde..70058381 100644 --- a/RoboCat/Src/Networker.cpp +++ b/RoboCat/Src/Networker.cpp @@ -23,7 +23,7 @@ void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIde mpUDPSocket = new UDPSocketPtr(); mpSocketAddressPtr = new SocketAddressPtr(); - mGameObjectsVec = std::vector>(); + mGameObjectsVec = std::vector>>(); pDeliveryNotificationManager = new DeliveryNotificationManager(true, true, this); mOutputBitStreamQueue = std::priority_queue, std::vector>, myComp>(); @@ -40,10 +40,10 @@ void Networker::init(GraphicsLibrary* graphicsLibrary, std::string rockSpriteIde void Networker::cleanup() { //Cleanup map - std::vector>::iterator it; + std::vector >>::iterator it; for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) { - delete it->second; + delete it->second.get(); it->second = nullptr; } mGameObjectsVec.clear(); @@ -185,18 +185,24 @@ PacketType Networker::receiveGameObjectStateUDP() { case GameObjectType::ROCK: { - Rock* newRock = new Rock(networkID, mpGraphicsLibrary, pair(posX, posY), mRockSpriteIdentifier); - mGameObjectsVec.push_back(pair(networkID, newRock)); - newRock = nullptr; + //Rock* newRock = new Rock(networkID, mpGraphicsLibrary, pair(posX, posY), mRockSpriteIdentifier); + shared_ptr newRock = std::make_shared(Rock(networkID, mpGraphicsLibrary, pair(posX, posY), mRockSpriteIdentifier)); + mGameObjectsVec.push_back(pair>(networkID, newRock)); + + //delete newRock; + //newRock = nullptr; break; } case GameObjectType::PLAYER: { - PlayerController* newPlayerController = new PlayerController(networkID, mpGraphicsLibrary, pair(posX, posY), mPlayerMoveSpeed, mPlayerSpriteIdentifier); - mGameObjectsVec.push_back(pair(networkID, newPlayerController)); - newPlayerController = nullptr; + //PlayerController* newPlayerController = new PlayerController(networkID, mpGraphicsLibrary, pair(posX, posY), mPlayerMoveSpeed, mPlayerSpriteIdentifier); + shared_ptr newPlayerController = std::make_shared(PlayerController(networkID, mpGraphicsLibrary, pair(posX, posY), mPlayerMoveSpeed, mPlayerSpriteIdentifier)); + mGameObjectsVec.push_back(pair>(networkID, newPlayerController)); + + //delete newPlayerController; + //newPlayerController = nullptr; break; @@ -212,9 +218,12 @@ PacketType Networker::receiveGameObjectStateUDP() IMBStream.Read(sizeY); IMBStream.Read(thickness); - Wall* newWall = new Wall(networkID, mpGraphicsLibrary, pair(posX, posY), sizeX, sizeY, mWallColour, thickness); - mGameObjectsVec.push_back(pair(networkID, newWall)); - newWall = nullptr; + //Wall* newWall = new Wall(networkID, mpGraphicsLibrary, pair(posX, posY), sizeX, sizeY, mWallColour, thickness); + shared_ptr newWall = std::make_shared(Wall(networkID, mpGraphicsLibrary, pair(posX, posY), sizeX, sizeY, mWallColour, thickness)); + mGameObjectsVec.push_back(pair>(networkID, newWall)); + + //delete newWall; + //newWall = nullptr; break; } @@ -251,7 +260,8 @@ PacketType Networker::receiveGameObjectStateUDP() float sizeY; float thiccness; - Wall* wall = (Wall*)mGameObjectsVec[networkID].second; + //Wall* wall = (Wall*)mGameObjectsVec[networkID].second; + Wall* wall = (Wall*)mGameObjectsVec[networkID].second.get(); IMBStream.Read(x); IMBStream.Read(y); @@ -265,6 +275,9 @@ PacketType Networker::receiveGameObjectStateUDP() wall->setWallSizeY(sizeY); wall->setWallThickness(thiccness); + delete wall; + wall = nullptr; + break; } } @@ -282,7 +295,8 @@ PacketType Networker::receiveGameObjectStateUDP() //Delete element in map if (mGameObjectsVec.size() > 0) { - std::vector>::iterator it; + //std::vector>::iterator it; + std::vector>>::iterator it; for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) { //DO NOT DELETE A PLAYER @@ -357,12 +371,15 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) OMBStream->Write(mGameObjectsVec[ID].second->getPosition().first); OMBStream->Write(mGameObjectsVec[ID].second->getPosition().second); - pInFlightPacket->SetTransmissionData(0, mGameObjectsVec[ID].second); + //pInFlightPacket->SetTransmissionData(0, mGameObjectsVec[ID].second); + pInFlightPacket->SetTransmissionData(0, mGameObjectsVec[ID].second.get()); //pInFlightPacket = nullptr; break; case GameObjectType::WALL: - Wall* wall = (Wall*)mGameObjectsVec[ID].second; + + //Wall* wall = (Wall*)mGameObjectsVec[ID].second; + Wall* wall = (Wall*)mGameObjectsVec[ID].second.get(); OMBStream->Write(wall->getPosition().first); OMBStream->Write(wall->getPosition().second); OMBStream->Write(wall->getWallSizeX()); @@ -370,6 +387,10 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) OMBStream->Write(wall->getWallThickness()); pInFlightPacket->SetTransmissionData(0, wall); //pInFlightPacket = nullptr; + + delete wall; + wall = nullptr; + break; } @@ -380,14 +401,15 @@ void Networker::sendGameObjectStateUDP(int ID, PacketType packetHeader) //Delete it on the sender's end if (mGameObjectsVec.size() > 0) { - std::vector>::iterator it; + //std::vector>::iterator it; + std::vector>>::iterator it; for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) { //DO NOT DELETE A PLAYER if (it->first == ID && it->second->getGameObjectType() != GameObjectType::PLAYER) { mGameObjectsVec.erase(it); - pInFlightPacket->SetTransmissionData(0, it->second); + pInFlightPacket->SetTransmissionData(0, it->second.get()); //pInFlightPacket = nullptr; break; @@ -443,14 +465,15 @@ void Networker::checkTimedOutPackets() pDeliveryNotificationManager->ProcessTimedOutPackets(); } -void Networker::addGameObject(GameObject* objectToAdd, int networkID) +void Networker::addGameObject(/*GameObject**/ shared_ptr objectToAdd, int networkID) { - mGameObjectsVec.push_back(pair(networkID, objectToAdd)); + mGameObjectsVec.push_back(pair>(networkID, objectToAdd)); } void Networker::updateGameObjects() { - std::vector>::iterator it; + std::vector>>::iterator it; + //std::vector>::iterator it; for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) { it->second->update(); @@ -459,7 +482,8 @@ void Networker::updateGameObjects() void Networker::renderGameObjects() { - std::vector>::iterator it; + std::vector>>::iterator it; + //std::vector>::iterator it; for (it = mGameObjectsVec.begin(); it != mGameObjectsVec.end(); ++it) { it->second->draw();