diff --git a/.gitignore b/.gitignore index bea91a67..37c9fe57 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ *.pdb Debug/ +packages/ diff --git a/Assignment3README.txt b/Assignment3README.txt new file mode 100644 index 00000000..f6583087 --- /dev/null +++ b/Assignment3README.txt @@ -0,0 +1,13 @@ +run the server FIRST via command line: SocketDemo.exe server +run the client via command line: SocketDemo.exe + +press the escape key to end the Allegro Display + +server is a green circle +client is a red square + +both can shoot with space bar but the projectiles do nothing and never die. + +movement on both apps is just Up Down Left Right + +the rain is simulated on the server and sent to the client. diff --git a/RoboCat/Chapter3.vcxproj b/RoboCat/Chapter3.vcxproj index 10d69fef..6fd098a7 100644 --- a/RoboCat/Chapter3.vcxproj +++ b/RoboCat/Chapter3.vcxproj @@ -92,6 +92,10 @@ true true Bin\$(Configuration)\ + true + true + true + true true @@ -127,12 +131,12 @@ WIN32;_DEBUG;DEBUG;PROFILE;_WINDOWS;_WIN32_WINNT=0x0600;%(PreprocessorDefinitions) ProgramDatabase EnableFastChecks - ..\SDL\include;Inc;..\ - Create + ..\SDL\include;Inc;..\;$(SolutionDir)packages\Allegro.5.2.7.1\build\native\include\allegro5 + NotUsing RoboCatPCH.h - d3d11.lib;dxguid.lib;winmm.lib;comctl32.lib;%(AdditionalDependencies);Ws2_32.lib + allegro_color.lib;allegro_primitives.lib;allegro.lib;d3d11.lib;dxguid.lib;winmm.lib;comctl32.lib;%(AdditionalDependencies);Ws2_32.lib true true true @@ -141,6 +145,7 @@ AsInvoker %(DelayLoadDLLs) Console + $(SolutionDir)packages/Allegro.5.2.7.1\build\native\v143\win32\lib true @@ -370,7 +375,17 @@ + + + + + + + + + + @@ -378,8 +393,14 @@ + + + + + + @@ -392,8 +413,22 @@ Create RoboCatPCH.h + + + + - + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/RoboCat/Inc/AckRange.h b/RoboCat/Inc/AckRange.h new file mode 100644 index 00000000..004d14c9 --- /dev/null +++ b/RoboCat/Inc/AckRange.h @@ -0,0 +1,35 @@ +//typedef PacketSequenceNumber uint16_t; + +class AckRange +{ +public: + AckRange() : mStart( 0 ), mCount( 0 ) {} + + AckRange( PacketSequenceNumber inStart ) : mStart( inStart ), mCount( 1 ) {} + + //if this is the next in sequence, just extend the range + inline bool ExtendIfShould( PacketSequenceNumber inSequenceNumber ); + + PacketSequenceNumber GetStart() const { return mStart; } + uint32_t GetCount() const { return mCount; } + + void Write( OutputMemoryBitStream& inOutputStream ) const; + void Read( InputMemoryBitStream& inInputStream ); + +private: + PacketSequenceNumber mStart; + uint32_t mCount; +}; + +inline bool AckRange::ExtendIfShould( PacketSequenceNumber inSequenceNumber ) +{ + if( inSequenceNumber == mStart + mCount ) + { + ++mCount; + return true; + } + else + { + return false; + } +} \ No newline at end of file diff --git a/RoboCat/Inc/ByteSwap.h b/RoboCat/Inc/ByteSwap.h new file mode 100644 index 00000000..5407540c --- /dev/null +++ b/RoboCat/Inc/ByteSwap.h @@ -0,0 +1,117 @@ + +#ifndef RoboCat_ByteSwap_h +#define RoboCat_ByteSwap_h + +inline uint16_t ByteSwap2( uint16_t inData ) +{ + return ( inData >> 8 ) | ( inData << 8 ); +} + +inline uint32_t ByteSwap4( uint32_t inData ) +{ + return ( ( inData >> 24 ) & 0x000000ff ) | + ( ( inData >> 8 ) & 0x0000ff00 ) | + ( ( inData << 8 ) & 0x00ff0000 ) | + ( ( inData << 24 ) & 0xff000000 ); +} + +inline uint64_t ByteSwap8( uint64_t inData ) +{ + return ( ( inData >> 56 ) & 0x00000000000000ff ) | + ( ( inData >> 40 ) & 0x000000000000ff00 ) | + ( ( inData >> 24 ) & 0x0000000000ff0000 ) | + ( ( inData >> 8 ) & 0x00000000ff000000 ) | + ( ( inData << 8 ) & 0x000000ff00000000 ) | + ( ( inData << 24 ) & 0x0000ff0000000000 ) | + ( ( inData << 40 ) & 0x00ff000000000000 ) | + ( ( inData << 56 ) & 0xff00000000000000 ); +} + + +template < typename tFrom, typename tTo > +class TypeAliaser +{ +public: + TypeAliaser( tFrom inFromValue ) : + mAsFromType( inFromValue ) {} + tTo& Get() { return mAsToType; } + + union + { + tFrom mAsFromType; + tTo mAsToType; + }; +}; + + +template class ByteSwapper; + +//specialize for 1... +template +class ByteSwapper< T, 1 > +{ +public: + T Swap( T inData ) const + { + return inData; + } +}; + + +//specialize for 2... +template +class ByteSwapper< T, 2 > +{ +public: + T Swap( T inData ) const + { + uint16_t result = + ByteSwap2( TypeAliaser< T, uint16_t >( inData ).Get() ); + return TypeAliaser< uint16_t, T >( result ).Get(); + } +}; + +//specialize for 4... +template +class ByteSwapper< T, 4 > +{ +public: + T Swap( T inData ) const + { + uint32_t result = + ByteSwap4( TypeAliaser< T, uint32_t >( inData ).Get() ); + return TypeAliaser< uint32_t, T >( result ).Get(); + } +}; + + +//specialize for 8... +template +class ByteSwapper< T, 8 > +{ +public: + T Swap( T inData ) const + { + uint64_t result = + ByteSwap8( TypeAliaser< T, uint64_t >( inData ).Get() ); + return TypeAliaser< uint64_t, T >( result ).Get(); + } +}; + +template < typename T > +T ByteSwap( T inData ) +{ + return ByteSwapper< T, sizeof( T ) >().Swap( inData ); +} + +inline void TestByteSwap() +{ + int32_t test = 0x12345678; + float floatTest = 1.f; + + printf( "swapped 0x%x is 0x%x\n", test, ByteSwap( test ) ); + printf( "swapped %f is %f\n", floatTest, ByteSwap( floatTest ) ); + printf( "swapped 0x%x is 0x%x\n", TypeAliaser< float, uint32_t >( floatTest ).Get(), TypeAliaser< float, uint32_t >( ByteSwap( floatTest ) ).Get() ); +} + +#endif diff --git a/RoboCat/Inc/CircleClass.h b/RoboCat/Inc/CircleClass.h new file mode 100644 index 00000000..e4674b16 --- /dev/null +++ b/RoboCat/Inc/CircleClass.h @@ -0,0 +1,76 @@ +#pragma once + +#include "allegro.h" +#include "allegro_primitives.h" +#include +#include + +class CircleClass +{ +public: + CircleClass(string name, int windowWidth, int windowHeight, int xStart, int yStart, float newRadius); + CircleClass(); + void Read(InputMemoryBitStream& iStream); + void Write(OutputMemoryBitStream& oStream) const; + void Draw(); + void UpdatePos(int xChange, int yChange); + + string mName; + int mWindowWidth; + int mWindowHeight; + std::vector position; + float radius; +}; + +void CircleClass::Read(InputMemoryBitStream& iStream) +{ + iStream.Read(mName); + iStream.Read(position[0]); + iStream.Read(position[1]); + iStream.Read(radius); +} + +void CircleClass::Write(OutputMemoryBitStream& oStream) const +{ + oStream.Write(mName); + oStream.Write(position[0]); + oStream.Write(position[1]); + oStream.Write(radius); +} + +void CircleClass::Draw() +{ + al_draw_filled_circle(position[0], position[1], radius, al_map_rgb(0, 255, 0)); +} + +CircleClass::CircleClass(string name, int windowWidth, int windowHeight, int xStart, int yStart, float newRadius) +{ + mName = name; + mWindowWidth = windowWidth; + mWindowHeight = windowHeight; + position.push_back(xStart); + position.push_back(yStart); + radius = newRadius; +} + +CircleClass::CircleClass() +{ + mName = ""; + mWindowWidth = 100; + mWindowHeight = 100; + int xTemp = 0; + int yTemp = 0; + position.push_back(xTemp); + position.push_back(yTemp); + radius = 10; +} + +void CircleClass::UpdatePos(int xChange, int yChange) +{ + position[0] += xChange; + position[1] += yChange; + if (position[0] > mWindowWidth) position[0] = 0; + if (position[1] > mWindowHeight) position[1] = 0; + if (position[0] < 0) position[0] = mWindowWidth; + if (position[1] < 0) position[1] = mWindowHeight; +} \ No newline at end of file diff --git a/RoboCat/Inc/DeliveryNotificationManager.h b/RoboCat/Inc/DeliveryNotificationManager.h new file mode 100644 index 00000000..b6a795a4 --- /dev/null +++ b/RoboCat/Inc/DeliveryNotificationManager.h @@ -0,0 +1,72 @@ + +class DeliveryNotificationManager +{ +public: + + + DeliveryNotificationManager( bool inShouldSendAcks, bool inShouldProcessAcks ); + ~DeliveryNotificationManager(); + + inline InFlightPacket* WriteState( OutputMemoryBitStream& inOutputStream ); + inline bool ReadAndProcessState( InputMemoryBitStream& inInputStream ); + + void ProcessTimedOutPackets(); + + 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; } + +private: + + + + 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 ); + + + void AddPendingAck( PacketSequenceNumber inSequenceNumber ); + void HandlePacketDeliveryFailure( const InFlightPacket& inFlightPacket ); + void HandlePacketDeliverySuccess( const InFlightPacket& inFlightPacket ); + + PacketSequenceNumber mNextOutgoingSequenceNumber; + PacketSequenceNumber mNextExpectedSequenceNumber; + + deque< InFlightPacket > mInFlightPackets; + deque< AckRange > mPendingAcks; + + bool mShouldSendAcks; + bool mShouldProcessAcks; + + uint32_t mDeliveredPacketCount; + uint32_t mDroppedPacketCount; + uint32_t mDispatchedPacketCount; + +}; + + + +inline InFlightPacket* DeliveryNotificationManager::WriteState( OutputMemoryBitStream& inOutputStream ) +{ + InFlightPacket* toRet = WriteSequenceNumber( inOutputStream ); + if( mShouldSendAcks ) + { + WriteAckData( inOutputStream ); + } + return toRet; +} + +inline bool DeliveryNotificationManager::ReadAndProcessState( InputMemoryBitStream& inInputStream ) +{ + bool toRet = ProcessSequenceNumber( inInputStream ); + if( mShouldProcessAcks ) + { + ProcessAcks( inInputStream ); + } + return toRet; +} \ No newline at end of file diff --git a/RoboCat/Inc/InFlightPacket.h b/RoboCat/Inc/InFlightPacket.h new file mode 100644 index 00000000..494e097e --- /dev/null +++ b/RoboCat/Inc/InFlightPacket.h @@ -0,0 +1,33 @@ +class DeliveryNotificationManager; + +//in case we decide to change the type of the sequence number to use fewer or more bits +typedef uint16_t PacketSequenceNumber; + +class InFlightPacket +{ +public: + + InFlightPacket( PacketSequenceNumber inSequenceNumber ); + + PacketSequenceNumber GetSequenceNumber() const { return mSequenceNumber; } + float GetTimeDispatched() const { return mTimeDispatched; } + + void SetTransmissionData( int inKey, TransmissionDataPtr inTransmissionData ) + { + mTransmissionDataMap[ inKey ] = inTransmissionData; + } + const TransmissionDataPtr GetTransmissionData( int inKey ) const + { + auto it = mTransmissionDataMap.find( inKey ); + return ( it != mTransmissionDataMap.end() ) ? it->second : nullptr; + } + + void HandleDeliveryFailure( DeliveryNotificationManager* inDeliveryNotificationManager ) const; + void HandleDeliverySuccess( DeliveryNotificationManager* inDeliveryNotificationManager ) const; + +private: + PacketSequenceNumber mSequenceNumber; + float mTimeDispatched; + + unordered_map< int, TransmissionDataPtr > mTransmissionDataMap; +}; \ No newline at end of file diff --git a/RoboCat/Inc/LinkingContext.h b/RoboCat/Inc/LinkingContext.h new file mode 100644 index 00000000..893e73aa --- /dev/null +++ b/RoboCat/Inc/LinkingContext.h @@ -0,0 +1,67 @@ + +#ifndef RoboCat_LinkingContext_h +#define RoboCat_LinkingContext_h + +class GameObject; + +class LinkingContext +{ +public: + + LinkingContext() : + mNextNetworkId( 1 ) + {} + + uint32_t GetNetworkId( GameObject* inGameObject, bool inShouldCreateIfNotFound ) + { + auto it = mGameObjectToNetworkIdMap.find( inGameObject ); + if( it != mGameObjectToNetworkIdMap.end() ) + { + return it->second; + } + else if( inShouldCreateIfNotFound ) + { + uint32_t newNetworkId = mNextNetworkId++; + AddGameObject( inGameObject, newNetworkId ); + return newNetworkId; + } + else + { + return 0; + } + } + + GameObject* GetGameObject( uint32_t inNetworkId ) const + { + auto it = mNetworkIdToGameObjectMap.find( inNetworkId ); + if( it != mNetworkIdToGameObjectMap.end() ) + { + return it->second; + } + else + { + return nullptr; + } + } + + void AddGameObject( GameObject* inGameObject, uint32_t inNetworkId ) + { + mNetworkIdToGameObjectMap[ inNetworkId ] = inGameObject; + mGameObjectToNetworkIdMap[ inGameObject ] = inNetworkId; + } + + void RemoveGameObject( GameObject *inGameObject ) + { + uint32_t networkId = mGameObjectToNetworkIdMap[ inGameObject ]; + mGameObjectToNetworkIdMap.erase( inGameObject ); + mNetworkIdToGameObjectMap.erase( networkId ); + } + +private: + std::unordered_map< uint32_t, GameObject* > mNetworkIdToGameObjectMap; + std::unordered_map< const GameObject*, uint32_t > mGameObjectToNetworkIdMap; + + uint32_t mNextNetworkId; +}; + +#endif diff --git a/RoboCat/Inc/MemoryBitStream.h b/RoboCat/Inc/MemoryBitStream.h new file mode 100644 index 00000000..0bfd1874 --- /dev/null +++ b/RoboCat/Inc/MemoryBitStream.h @@ -0,0 +1,161 @@ + +#include +#include +#include +#include + +class GameObject; + +inline uint32_t ConvertToFixed( float inNumber, float inMin, float inPrecision ) +{ + return static_cast< int > ( ( inNumber - inMin ) / inPrecision ); +} + +inline float ConvertFromFixed( uint32_t inNumber, float inMin, float inPrecision ) +{ + return static_cast< float >( inNumber ) * inPrecision + inMin; +} + + +class OutputMemoryBitStream +{ +public: + + OutputMemoryBitStream() : + mBitHead(0), + mBuffer(nullptr) + { + ReallocBuffer( 1500 * 8 ); + } + + ~OutputMemoryBitStream() { std::free(mBuffer); } + + void WriteBits( uint8_t inData, uint32_t inBitCount ); + void WriteBits( const void* inData, uint32_t inBitCount ); + + const char* GetBufferPtr() const { return mBuffer; } + uint32_t GetBitLength() const { return mBitHead; } + uint32_t GetByteLength() const { return ( mBitHead + 7 ) >> 3; } + + void WriteBytes( const void* inData, uint32_t inByteCount ) { WriteBits( inData, inByteCount << 3 ); } + + /* + void Write( uint32_t inData, uint32_t inBitCount = 32 ) { WriteBits( &inData, inBitCount ); } + void Write( int inData, uint32_t inBitCount = 32 ) { WriteBits( &inData, inBitCount ); } + void Write( float inData ) { WriteBits( &inData, 32 ); } + + void Write( uint16_t inData, uint32_t inBitCount = 16 ) { WriteBits( &inData, inBitCount ); } + void Write( int16_t inData, uint32_t inBitCount = 16 ) { WriteBits( &inData, inBitCount ); } + + void Write( uint8_t inData, uint32_t inBitCount = 8 ) { WriteBits( &inData, inBitCount ); } + */ + + template< typename T > + void Write( T inData, uint32_t inBitCount = sizeof( T ) * 8 ) + { + static_assert( std::is_arithmetic< T >::value || + std::is_enum< T >::value, + "Generic Write only supports primitive data types" ); + WriteBits( &inData, inBitCount ); + } + + void Write( bool inData ) { WriteBits( &inData, 1 ); } + + void Write( const Vector3& inVector ); + void Write( const Quaternion& inQuat ); + + void Write( const std::string& inString ) + { + uint32_t elementCount = static_cast< uint32_t >( inString.size() ); + Write( elementCount ); + for( const auto& element : inString ) + { + Write( element ); + } + } + +private: + void ReallocBuffer( uint32_t inNewBitCapacity ); + + char* mBuffer; + uint32_t mBitHead; + uint32_t mBitCapacity; +}; + +class InputMemoryBitStream +{ +public: + + InputMemoryBitStream( char* inBuffer, uint32_t inBitCount ) : + mBuffer( inBuffer ), + mBitCapacity( inBitCount ), + mBitHead( 0 ), + mIsBufferOwner( false ) {} + + InputMemoryBitStream( const InputMemoryBitStream& inOther ) : + mBitCapacity( inOther.mBitCapacity ), + mBitHead( inOther.mBitHead ), + mIsBufferOwner( true ) + { + //allocate buffer of right size + int byteCount = ( mBitCapacity + 7 ) / 8; + mBuffer = static_cast< char* >( malloc( byteCount ) ); + //copy + memcpy( mBuffer, inOther.mBuffer, byteCount ); + } + + ~InputMemoryBitStream() { if (mIsBufferOwner) { /*std::cout << "Freeing " << mBuffer << std::endl;*/ free(mBuffer); }; } + + const char* GetBufferPtr() const { return mBuffer; } + uint32_t GetRemainingBitCount() const { return mBitCapacity - mBitHead; } + + void ReadBits( uint8_t& outData, uint32_t inBitCount ); + void ReadBits( void* outData, uint32_t inBitCount ); + + void ReadBytes( void* outData, uint32_t inByteCount ) { ReadBits( outData, inByteCount << 3 ); } + + template< typename T > + void Read( T& inData, uint32_t inBitCount = sizeof( T ) * 8 ) + { + static_assert( std::is_arithmetic< T >::value || + std::is_enum< T >::value, + "Generic Read only supports primitive data types" ); + ReadBits( &inData, inBitCount ); + } + + void Read( uint32_t& outData, uint32_t inBitCount = 32 ) { ReadBits( &outData, inBitCount ); } + void Read( int& outData, uint32_t inBitCount = 32 ) { ReadBits( &outData, inBitCount ); } + void Read( float& outData ) { ReadBits( &outData, 32 ); } + + void Read( uint16_t& outData, uint32_t inBitCount = 16 ) { ReadBits( &outData, inBitCount ); } + void Read( int16_t& outData, uint32_t inBitCount = 16 ) { ReadBits( &outData, inBitCount ); } + + void Read( uint8_t& outData, uint32_t inBitCount = 8 ) { ReadBits( &outData, inBitCount ); } + void Read( bool& outData ) { ReadBits( &outData, 1 ); } + + void Read( Quaternion& outQuat ); + + void ResetToCapacity( uint32_t inByteCapacity ) { mBitCapacity = inByteCapacity << 3; mBitHead = 0; } + + + void Read( std::string& inString ) + { + uint32_t elementCount; + Read( elementCount ); + inString.resize( elementCount ); + for( auto& element : inString ) + { + Read( element ); + } + } + + void Read( Vector3& inVector ); + +private: + char* mBuffer; + uint32_t mBitHead; + uint32_t mBitCapacity; + bool mIsBufferOwner; + +}; + diff --git a/RoboCat/Inc/RainParticle.h b/RoboCat/Inc/RainParticle.h new file mode 100644 index 00000000..c018305f --- /dev/null +++ b/RoboCat/Inc/RainParticle.h @@ -0,0 +1,97 @@ +#pragma once + +#include "allegro.h" +#include "allegro_primitives.h" +#include "vector" + +#include + +class RainParticle +{ +public: + RainParticle(int windowWidth, int windowHeight, float xStart, float yStart, float newRadius, float newR, float newG, float newB, float newA); + RainParticle(); + void Read(InputMemoryBitStream& iStream); + void Write(OutputMemoryBitStream& oStream) const; + void Draw(); + void UpdatePos(float xChange, float yChange); + + int mWindowWidth; + int mWindowHeight; + std::vector color; + std::vector position; + float radius; +}; + +RainParticle::RainParticle(int windowWidth, int windowHeight, float xStart, float yStart, float newRadius, float newR, float newG, float newB, float newA) +{ + mWindowWidth = windowWidth; + mWindowHeight = windowHeight; + color.push_back(newR); + color.push_back(newG); + color.push_back(newB); + color.push_back(newA); + + position.push_back(xStart); + position.push_back(yStart); + + radius = newRadius; +} + +RainParticle::RainParticle() +{ + float tempR, tempG, tempB, tempA, tempX, tempY; + + mWindowWidth = 100; + mWindowHeight = 100; + color.push_back(tempR); + color.push_back(tempG); + color.push_back(tempB); + color.push_back(tempA); + + position.push_back(tempX); + position.push_back(tempY); + + radius = float(); +} + +void RainParticle::Read(InputMemoryBitStream& iStream) +{ + iStream.Read(color[0]); + iStream.Read(color[1]); + iStream.Read(color[2]); + iStream.Read(color[3]); + + iStream.Read(position[0]); + iStream.Read(position[1]); + + iStream.Read(radius); +} + +void RainParticle::Write(OutputMemoryBitStream& oStream) const +{ + oStream.Write(color[0]); + oStream.Write(color[1]); + oStream.Write(color[2]); + oStream.Write(color[3]); + + oStream.Write(position[0]); + oStream.Write(position[1]); + + oStream.Write(radius); +} + +void RainParticle::Draw() +{ + al_draw_filled_circle(position[0], position[1], radius, al_map_rgba(color[0], color[1], color[2], color[3])); +} + +void RainParticle::UpdatePos(float xChange, float yChange) +{ + position[0] += xChange; + position[1] += yChange; + if (position[0] > mWindowWidth) position[0] = 0; + if (position[1] > mWindowHeight) position[1] = 0; + if (position[0] < 0) position[0] = mWindowWidth; + if (position[1] < 0) position[1] = mWindowHeight; +} \ No newline at end of file diff --git a/RoboCat/Inc/RectangleObject.h b/RoboCat/Inc/RectangleObject.h new file mode 100644 index 00000000..428f13eb --- /dev/null +++ b/RoboCat/Inc/RectangleObject.h @@ -0,0 +1,82 @@ +#pragma once + +#include "allegro.h" +#include "allegro_primitives.h" +#include + +class RectangleObject +{ +public: + string mName; + int mWindowWidth; + int mWindowHeight; + int width; + int height; + int xPos; + int yPos; + void Read(InputMemoryBitStream& stream); + void Write(OutputMemoryBitStream& stream) const; + RectangleObject(string name, int windowWidth, int windowHeight, int newWidth, int newHeight, int startPosX, int startPosY); + RectangleObject(); + void UpdatePos(int xChange, int yChange); + void Draw(); + //bool operator==(const RectangleObject& other); + +}; +//bool RectangleObject::operator==(const RectangleObject& other) +//{ +// return mName == other.mName; +//} +void RectangleObject::Read(InputMemoryBitStream& stream) +{ + stream.Read(mName); + stream.Read(width); + stream.Read(height); + stream.Read(xPos); + stream.Read(yPos); +} + +void RectangleObject::Write(OutputMemoryBitStream& stream) const +{ + stream.Write(mName); + stream.Write(width); + stream.Write(height); + stream.Write(xPos); + stream.Write(yPos); +} + +RectangleObject::RectangleObject(string name, int windowWidth, int windowHeight, int newWidth, int newHeight, int startPosX, int startPosY) +{ + mName = name; + mWindowWidth = windowWidth; + mWindowHeight = windowHeight; + width = newWidth; + height = newHeight; + xPos = startPosX; + yPos = startPosY; +} +RectangleObject::RectangleObject() +{ + mName = ""; + mWindowWidth = 100; + mWindowHeight = 100; + width = 10; + height = 10; + xPos = rand() % 100 + 1; + yPos = rand() % 100 + 1; +} + +void RectangleObject::UpdatePos(int xChange, int yChange) +{ + xPos += xChange; + yPos += yChange; + if (xPos > mWindowWidth) xPos = 0; + if (yPos > mWindowHeight) yPos = 0; + if (xPos < 0) xPos = mWindowWidth; + if (yPos < 0) yPos = mWindowHeight; +} + +void RectangleObject::Draw() +{ + al_draw_filled_rectangle(xPos, yPos, xPos + width, yPos + height, al_map_rgb(255, 0, 0)); +} \ No newline at end of file diff --git a/RoboCat/Inc/RoboCatShared.h b/RoboCat/Inc/RoboCatShared.h index 83a3871a..d7eac1bc 100644 --- a/RoboCat/Inc/RoboCatShared.h +++ b/RoboCat/Inc/RoboCatShared.h @@ -49,6 +49,17 @@ using std::unordered_set; class RoboCat; class GameObject; +#include "RoboMath.h" + +#include "TransmissionData.h" +#include "InFlightPacket.h" + +#include "LinkingContext.h" +#include "ByteSwap.h" +#include "MemoryBitStream.h" +#include "AckRange.h" + +#include "Timing.h" #include "StringUtils.h" #include "SocketAddress.h" #include "SocketAddressFactory.h" @@ -56,3 +67,4 @@ class GameObject; #include "TCPSocket.h" #include "SocketUtil.h" #include "OutputWindow.h" +#include "DeliveryNotificationManager.h" diff --git a/RoboCat/Inc/RoboMath.h b/RoboCat/Inc/RoboMath.h new file mode 100644 index 00000000..718080a9 --- /dev/null +++ b/RoboCat/Inc/RoboMath.h @@ -0,0 +1,199 @@ +class Vector3 +{ +public: + + float mX, mY, mZ; + + Vector3( float x, float y, float z ) : + mX( x ), + mY( y ), + mZ( z ) + {} + + Vector3() : + mX( 0.0f ), + mY( 0.0f ), + mZ( 0.0f ) + {} + + void Set( float x, float y, float z ) + { + mX = x; + mY = y; + mZ = z; + } + + friend Vector3 operator+( const Vector3& inLeft, const Vector3& inRight ) + { + return Vector3( inLeft.mX + inRight.mX, inLeft.mY + inRight.mY, inLeft.mZ + inRight.mZ ); + } + + friend Vector3 operator-( const Vector3& inLeft, const Vector3& inRight ) + { + return Vector3( inLeft.mX - inRight.mX, inLeft.mY - inRight.mY, inLeft.mZ - inRight.mZ ); + } + + // Component-wise multiplication + friend Vector3 operator*( const Vector3& inLeft, const Vector3& inRight ) + { + return Vector3( inLeft.mX * inRight.mX, inLeft.mY * inRight.mY, inLeft.mZ * inRight.mZ ); + } + + // Scalar multiply + friend Vector3 operator*( float inScalar, const Vector3& inVec ) + { + return Vector3( inVec.mX * inScalar, inVec.mY * inScalar, inVec.mZ * inScalar ); + } + + friend Vector3 operator*( const Vector3& inVec, float inScalar ) + { + return Vector3( inVec.mX * inScalar, inVec.mY * inScalar, inVec.mZ * inScalar ); + } + + Vector3& operator*=( float inScalar ) + { + mX *= inScalar; + mY *= inScalar; + mZ *= inScalar; + return *this; + } + + Vector3& operator+=( const Vector3& inRight ) + { + mX += inRight.mX; + mY += inRight.mY; + mZ += inRight.mZ; + return *this; + } + + Vector3& operator-=( const Vector3& inRight ) + { + mX -= inRight.mX; + mY -= inRight.mY; + mZ -= inRight.mZ; + return *this; + } + + float Length() + { + return sqrtf( mX * mX + mY * mY + mZ * mZ ); + } + + float LengthSq() + { + return mX * mX + mY * mY + mZ * mZ; + } + + float Length2D() + { + return sqrtf( mX * mX + mY * mY ); + } + + float LengthSq2D() + { + return mX * mX + mY * mY; + } + + void Normalize() + { + float length = Length(); + mX /= length; + mY /= length; + mZ /= length; + } + + void Normalize2D() + { + float length = Length2D(); + mX /= length; + mY /= length; + } + + friend float Dot( const Vector3& inLeft, const Vector3& inRight ) + { + return ( inLeft.mX * inRight.mX + inLeft.mY * inRight.mY + inLeft.mZ * inRight.mZ ); + } + + friend float Dot2D( const Vector3& inLeft, const Vector3& inRight ) + { + return ( inLeft.mX * inRight.mX + inLeft.mY * inRight.mY ); + } + + friend Vector3 Cross( const Vector3& inLeft, const Vector3& inRight ) + { + Vector3 temp; + temp.mX = inLeft.mY * inRight.mZ - inLeft.mZ * inRight.mY; + temp.mY = inLeft.mZ * inRight.mX - inLeft.mX * inRight.mZ; + temp.mZ = inLeft.mX * inRight.mY - inLeft.mY * inRight.mX; + return temp; + } + + friend Vector3 Lerp( const Vector3& inA, const Vector3& inB, float t ) + { + return Vector3( inA + t * ( inB - inA ) ); + } + + static const Vector3 Zero; + static const Vector3 UnitX; + static const Vector3 UnitY; + static const Vector3 UnitZ; +}; + + +class Quaternion +{ +public: + + float mX, mY, mZ, mW; + +}; + + +template< int tValue, int tBits > +struct GetRequiredBitsHelper +{ + enum { Value = GetRequiredBitsHelper< ( tValue >> 1 ), tBits + 1 >::Value }; +}; + +template< int tBits > +struct GetRequiredBitsHelper< 0, tBits > +{ + enum { Value = tBits }; +}; + +template< int tValue > +struct GetRequiredBits +{ + enum { Value = GetRequiredBitsHelper< tValue, 0 >::Value }; +}; + +namespace RoboMath +{ + const float PI = 3.1415926535f; + float GetRandomFloat(); + + Vector3 GetRandomVector( const Vector3& inMin, const Vector3& inMax ); + + inline bool Is2DVectorEqual( const Vector3& inA, const Vector3& inB ) + { + return ( inA.mX == inB.mX && inA.mY == inB.mY ); + } + + inline float ToDegrees( float inRadians ) + { + return inRadians * 180.0f / PI; + } +} + +namespace Colors +{ + static const Vector3 Black( 0.0f, 0.0f, 0.0f ); + static const Vector3 White( 1.0f, 1.0f, 1.0f ); + static const Vector3 Red( 1.0f, 0.0f, 0.0f ); + static const Vector3 Green( 0.0f, 1.0f, 0.0f ); + static const Vector3 Blue( 0.0f, 0.0f, 1.0f ); + static const Vector3 LightYellow( 1.0f, 1.0f, 0.88f ); + static const Vector3 LightBlue( 0.68f, 0.85f, 0.9f ); + static const Vector3 LightPink( 1.0f, 0.71f, 0.76f ); + static const Vector3 LightGreen( 0.56f, 0.93f, 0.56f ); +} diff --git a/RoboCat/Inc/Timing.h b/RoboCat/Inc/Timing.h new file mode 100644 index 00000000..b5af28d8 --- /dev/null +++ b/RoboCat/Inc/Timing.h @@ -0,0 +1,31 @@ +class Timing +{ +public: + + Timing(); + + void Update(); + + float GetDeltaTime() const { return mDeltaTime; } + + double GetTime() const; + + float GetTimef() const + { + return static_cast< float >( GetTime() ); + } + + float GetFrameStartTime() const { return mFrameStartTimef; } + + + static Timing sInstance; + +private: + float mDeltaTime; + uint64_t mDeltaTick; + + double mLastFrameStartTime; + float mFrameStartTimef; + double mPerfCountDuration; + +}; \ No newline at end of file diff --git a/RoboCat/Inc/TransmissionData.h b/RoboCat/Inc/TransmissionData.h new file mode 100644 index 00000000..cc599ec0 --- /dev/null +++ b/RoboCat/Inc/TransmissionData.h @@ -0,0 +1,9 @@ +class DeliveryNotificationManager; + +class TransmissionData +{ +public: + virtual void HandleDeliveryFailure( DeliveryNotificationManager* inDeliveryNotificationManager ) const = 0; + virtual void HandleDeliverySuccess( DeliveryNotificationManager* inDeliveryNotificationManager ) const = 0; +}; +typedef shared_ptr< TransmissionData > TransmissionDataPtr; \ No newline at end of file diff --git a/RoboCat/RectangleObject.h b/RoboCat/RectangleObject.h new file mode 100644 index 00000000..e69de29b diff --git a/RoboCat/Src/AckRange.cpp b/RoboCat/Src/AckRange.cpp new file mode 100644 index 00000000..99bed803 --- /dev/null +++ b/RoboCat/Src/AckRange.cpp @@ -0,0 +1,33 @@ +#include "RoboCatPCH.h" + +void AckRange::Write( OutputMemoryBitStream& inOutputStream ) const +{ + inOutputStream.Write( mStart ); + bool hasCount = mCount > 1; + inOutputStream.Write( hasCount ); + if( hasCount ) + { + //most you can ack is 255... + uint32_t countMinusOne = mCount - 1; + uint8_t countToAck = countMinusOne > 255 ? 255 : static_cast< uint8_t >( countMinusOne ); + inOutputStream.Write( countToAck ); + } +} + +void AckRange::Read( InputMemoryBitStream& inInputStream ) +{ + inInputStream.Read( mStart ); + bool hasCount; + inInputStream.Read( hasCount ); + if( hasCount ) + { + uint8_t countMinusOne; + inInputStream.Read( countMinusOne ); + mCount = countMinusOne + 1; + } + else + { + //default! + mCount = 1; + } +} \ No newline at end of file diff --git a/RoboCat/Src/DeliveryNotificationManager.cpp b/RoboCat/Src/DeliveryNotificationManager.cpp new file mode 100644 index 00000000..ea85baa9 --- /dev/null +++ b/RoboCat/Src/DeliveryNotificationManager.cpp @@ -0,0 +1,209 @@ +#include "RoboCatPCH.h" + +namespace +{ + const float kDelayBeforeAckTimeout = 0.5f; +} + +DeliveryNotificationManager::DeliveryNotificationManager( bool inShouldSendAcks, bool inShouldProcessAcks ) : +mNextOutgoingSequenceNumber( 0 ), +mNextExpectedSequenceNumber( 0 ), +//everybody starts at 0... +mShouldSendAcks( inShouldSendAcks ), +mShouldProcessAcks( inShouldProcessAcks ), +mDeliveredPacketCount( 0 ), +mDroppedPacketCount( 0 ), +mDispatchedPacketCount( 0 ) +{ +} + + +//we're going away- log how well we did... +DeliveryNotificationManager::~DeliveryNotificationManager() +{ + LOG( "DNM destructor. Delivery rate %d%%, Drop rate %d%%", + ( 100 * mDeliveredPacketCount ) / mDispatchedPacketCount, + ( 100 * mDroppedPacketCount ) / mDispatchedPacketCount ); +} + + + +InFlightPacket* DeliveryNotificationManager::WriteSequenceNumber( OutputMemoryBitStream& inOutputStream ) +{ + //write the sequence number, but also create an inflight packet for this... + PacketSequenceNumber sequenceNumber = mNextOutgoingSequenceNumber++; + inOutputStream.Write( sequenceNumber ); + + ++mDispatchedPacketCount; + + if( mShouldProcessAcks ) + { + mInFlightPackets.emplace_back( sequenceNumber ); + + return &mInFlightPackets.back(); + } + else + { + return nullptr; + } +} + +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 + //however, if we have more than 1, we'll make that 1 bit a 1 and then write 8 bits of how many packets + //we could do some statistical analysis to determine if this is the best strategy but we'll use it for now + + //do we have any pending acks? + //if so, write a 1 bit and write the first range + //otherwise, write 0 bit + bool hasAcks = ( mPendingAcks.size() > 0 ); + + inOutputStream.Write( hasAcks ); + if( hasAcks ) + { + //note, we could write all the acks + mPendingAcks.front().Write( inOutputStream ); + mPendingAcks.pop_front(); + } +} + + + +//returns wether to drop the packet- if sequence number is too low! +bool DeliveryNotificationManager::ProcessSequenceNumber( InputMemoryBitStream& inInputStream ) +{ + PacketSequenceNumber sequenceNumber; + + inInputStream.Read( sequenceNumber ); + if( sequenceNumber == mNextExpectedSequenceNumber ) + { + mNextExpectedSequenceNumber = sequenceNumber + 1; + //is this what we expect? great, let's add an ack to our pending list + if( mShouldSendAcks ) + { + AddPendingAck( sequenceNumber ); + } + //and let's continue processing this packet... + return true; + } + //is the sequence number less than our current expected sequence? silently drop it. + //if this is due to wrapping around, we might fail to ack some packets that we should ack, but they'll get resent, so it's not a big deal + //note that we don't have to re-ack it because our system doesn't reuse sequence numbers + else if( sequenceNumber < mNextExpectedSequenceNumber ) + { + return false; + } + else if( sequenceNumber > mNextExpectedSequenceNumber ) + { + //we missed a lot of packets! + //so our next expected packet comes after this one... + mNextExpectedSequenceNumber = sequenceNumber + 1; + //we should nack the missing packets..this will happen automatically inside AddPendingAck because + //we're adding an unconsequitive ack + //and then we can ack this and process it + if( mShouldSendAcks ) + { + AddPendingAck( sequenceNumber ); + } + return true; + } + + //drop packet if we couldn't even read sequence number! + return false; +} + + +//in each packet we can ack a range +//anything in flight before the range will be considered nackd by the other side immediately +void DeliveryNotificationManager::ProcessAcks( InputMemoryBitStream& inInputStream ) +{ + + bool hasAcks; + inInputStream.Read( hasAcks ); + if( hasAcks ) + { + AckRange ackRange; + ackRange.Read( inInputStream ); + + //for each InfilghtPacket 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() ) + { + const auto& nextInFlightPacket = mInFlightPackets.front(); + //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 ) + { + //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 ); + //received! + mInFlightPackets.pop_front(); + //decrement count, advance nextAckdSequenceNumber + ++nextAckdSequenceNumber; + } + else if( nextInFlightPacketSequenceNumber > nextAckdSequenceNumber ) + { + //we've already ackd some packets in here. + //keep this packet in flight, but keep going through the ack... + ++nextAckdSequenceNumber; + } + } + } +} + +void DeliveryNotificationManager::ProcessTimedOutPackets() +{ + float timeoutTime = Timing::sInstance.GetTimef() - kDelayBeforeAckTimeout; + + while( !mInFlightPackets.empty() ) + { + const auto& nextInFlightPacket = mInFlightPackets.front(); + + //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 ); + mInFlightPackets.pop_front(); + } + else + { + //it wasn't, and packets are all in order by time here, so we know we don't have to check farther + break; + } + } +} + +void DeliveryNotificationManager::AddPendingAck( PacketSequenceNumber inSequenceNumber ) +{ + //if you don't have a range yet, or you can't correctly extend the final range with the sequence number, + //start a new range + if( mPendingAcks.size() == 0 || !mPendingAcks.back().ExtendIfShould( inSequenceNumber ) ) + { + mPendingAcks.emplace_back( inSequenceNumber ); + } +} + + +void DeliveryNotificationManager::HandlePacketDeliveryFailure( const InFlightPacket& inFlightPacket ) +{ + ++mDroppedPacketCount; + inFlightPacket.HandleDeliveryFailure( this ); + +} + + +void DeliveryNotificationManager::HandlePacketDeliverySuccess( const InFlightPacket& inFlightPacket ) +{ + ++mDeliveredPacketCount; + inFlightPacket.HandleDeliverySuccess( this ); +} diff --git a/RoboCat/Src/InFlightPacket.cpp b/RoboCat/Src/InFlightPacket.cpp new file mode 100644 index 00000000..251abe36 --- /dev/null +++ b/RoboCat/Src/InFlightPacket.cpp @@ -0,0 +1,25 @@ +#include "RoboCatPCH.h" + +InFlightPacket::InFlightPacket( PacketSequenceNumber inSequenceNumber ) : +mSequenceNumber( inSequenceNumber ), +mTimeDispatched( Timing::sInstance.GetTimef() ) +{ + //null out other transmision data params... +} + + +void InFlightPacket::HandleDeliveryFailure( DeliveryNotificationManager* inDeliveryNotificationManager ) const +{ + for( const auto& pair : mTransmissionDataMap ) + { + pair.second->HandleDeliveryFailure( inDeliveryNotificationManager ); + } +} + +void InFlightPacket::HandleDeliverySuccess( DeliveryNotificationManager* inDeliveryNotificationManager ) const +{ + for( const auto& pair : mTransmissionDataMap ) + { + pair.second->HandleDeliverySuccess( inDeliveryNotificationManager ); + } +} \ No newline at end of file diff --git a/RoboCat/Src/Main.cpp b/RoboCat/Src/Main.cpp index f8c6c7d0..6b7185f4 100644 --- a/RoboCat/Src/Main.cpp +++ b/RoboCat/Src/Main.cpp @@ -1,14 +1,664 @@ #include "RoboCatPCH.h" +#include "allegro5.h" +#include "allegro_primitives.h" +#include "allegro_color.h" +#include "RectangleObject.h" +#include "RainParticle.h" +#include "CircleClass.h" +#include "math.h" #include #include #include #include +#include +#include + +using namespace std; +int windowWidth = 800; +int windowHeight = 600; +int FRAMERATE = 16; #if _WIN32 +class Packet +{ +public: + Packet(); + Packet(double stamp, char* buffer, int byteCount); + ~Packet(); + double timeStamp; + char* mBufferPtr; + int mByteCount; +private: + +}; +Packet::Packet(double stamp, char* buffer, int byteCount) { + timeStamp = stamp; + mBufferPtr = buffer; + mByteCount = byteCount; +}; + +Packet::Packet() +{ + timeStamp = 0; + mBufferPtr = new char[4096]; + mByteCount = 4096; +} + +Packet::~Packet() +{ +} + + +void ThrowSocketError(std::string errorMsg) +{ + SocketUtil::ReportError(errorMsg.c_str()); + ExitProcess(1); // kill +} + +ALLEGRO_TIMER* GiveMeATimer(float incr) +{ + ALLEGRO_TIMER* timer = al_create_timer(incr); + if (timer == nullptr) + { + ERROR; + } + al_start_timer(timer); + return timer; +} + + +TCPSocketPtr StartServer() +{ + TCPSocketPtr listeningSocket = SocketUtil::CreateTCPSocket(SocketAddressFamily::INET); + if (listeningSocket == nullptr) + ThrowSocketError("Could not create socket: NullPtr"); + + SocketAddressPtr addressPtr = SocketAddressFactory::CreateIPv4FromString("0.0.0.0:8080"); // a hard-coded port to bind to + if (addressPtr == nullptr) + ThrowSocketError("Could not create server address"); + + bool bindError = listeningSocket->Bind(*addressPtr) != NO_ERROR; // bind the socket and check if there is an error + if (bindError) + ThrowSocketError("Could not bind to address"); + + bool listenError = listeningSocket->Listen() != NO_ERROR; + if (listenError) + ThrowSocketError("Listen Error"); + + LOG("%s", "Waiting to accept connections..."); + + SocketAddress incomingAddress; + TCPSocketPtr incomingSocket = listeningSocket->Accept(incomingAddress); + while (incomingSocket == nullptr) // if it didnt work the first time, try again + { + incomingSocket = listeningSocket->Accept(incomingAddress); + } + LOG("Request accepted from: %s", incomingAddress.ToString().c_str()); + + return incomingSocket; +} + +TCPSocketPtr StartClientConnection() +{ + TCPSocketPtr clientSocket = SocketUtil::CreateTCPSocket(SocketAddressFamily::INET); + if (clientSocket == nullptr) + ThrowSocketError("Could not create client socket"); + + std::string address = StringUtils::Sprintf("127.0.0.1:8081"); + SocketAddressPtr clientAddress = SocketAddressFactory::CreateIPv4FromString(address.c_str()); + if (clientAddress == nullptr) + ThrowSocketError("Creating client address"); + + if (clientSocket->Bind(*clientAddress) != NO_ERROR) + ThrowSocketError("Binding Client Socket"); + + SocketAddressPtr serverAddress = SocketAddressFactory::CreateIPv4FromString("127.0.0.1:8080"); + if (serverAddress == nullptr) + ThrowSocketError("Creating server address"); + + if (clientSocket->Connect(*serverAddress) != NO_ERROR) + ThrowSocketError("Connecting to server"); + + return clientSocket; +} + +void BradsTotallyOriginalServer() +{ + TCPSocketPtr incomingSocket = StartServer(); + incomingSocket->SetNonBlockingMode(true); + + //Timer + ALLEGRO_TIMER* timer = GiveMeATimer(0.001); + double currentTime = al_get_timer_count(timer); + float lastTime = 0; + float deltaTime = 0; + + // Game Loop Variables + bool exit = false; + const int numObjects = 1; + + //Greate GameObjects + CircleClass greenCircle[numObjects]; + for (int j = 0; j < numObjects; j++) + { + greenCircle[j] = CircleClass("serverPlayer", windowWidth, windowHeight, 100, 100, 20); + } + + const int numDroplets = 10; + RainParticle rain[numDroplets]; + for (int k = 0; k < numDroplets; k++) + { + rain[k] = RainParticle(windowWidth, windowHeight, rand() % windowWidth + 1, rand() % windowHeight + 1, rand() % 5 + 5, 0, 0, rand() % 254 + 1, rand() % 254 + 1); + } + + RectangleObject inObjects[numObjects]; + list < RectangleObject* > clientProjectiles; + list < CircleClass* > serverProjectiles; + + + ALLEGRO_DISPLAY* display; + display = al_create_display(windowWidth, windowHeight); + + ALLEGRO_KEYBOARD_STATE keyState; + ALLEGRO_EVENT_QUEUE* eventQueue = al_create_event_queue(); + al_register_event_source(eventQueue, al_get_keyboard_event_source()); + + int xAxis = 0; + int yAxis = 0; + + bool xMove = true; + bool yMove = true; + + int projShot = 0; + + list packets = {}; + DeliveryNotificationManager deliveryManager(true, true); + list deadRects = {}; + list deadCircles = {}; + + while (!exit) //GameLoop + { + //Timer + currentTime = al_get_timer_count(timer); + deltaTime = currentTime - lastTime; + lastTime = currentTime; + + //send Data + OutputMemoryBitStream oStream = OutputMemoryBitStream(); + + deliveryManager.WriteState(oStream); + for (int i = 0; i < numObjects; i++) + { + greenCircle[i].Write(oStream); + } + + for (int j = 0; j < numDroplets; j++) + { + rain[j].Write(oStream); + } + + int projectileCount = serverProjectiles.size(); + oStream.Write(projectileCount); + for each (CircleClass * projectile in serverProjectiles) + { + projectile->Write(oStream); + } + + incomingSocket->Send(oStream.GetBufferPtr(), oStream.GetByteLength()); + + //recieve Data + char* buffer = new char[4096]; + int32_t bytesReceived = int32_t(); // get username + + bytesReceived = incomingSocket->Receive(buffer, 4096); + if (bytesReceived != -10035) + { + double stampTime = al_get_timer_count(timer); + int randomSin = rand() % 2; + if (randomSin == 0) + { + stampTime += rand() % 200 + 1; // jitter + } + else + { + stampTime -= rand() % 200 + 1; // jitter + } + stampTime += 1000; //latency + Packet* pack = new Packet(stampTime, buffer, 4096); + if (!((rand() % 100 + 0) < 5)) //5% packet loss + packets.push_back(pack); + if (packets.size() > 1) + packets.sort([](const Packet* a, const Packet* b) { return a->timeStamp < b->timeStamp; }); + + } + if (!packets.empty()) + { + bool processOrNot = false; + + processOrNot = packets.front()->timeStamp < al_get_timer_count(timer); + + while (processOrNot) // maybe while + { + InputMemoryBitStream packStream(packets.front()->mBufferPtr, 4096); + + deliveryManager.ReadAndProcessState(packStream); + deliveryManager.ProcessTimedOutPackets(); + + for (int i = 0; i < numObjects; i++) + { + inObjects[i].Read(packStream); + } + int clientProjectilesCount; + packStream.Read(clientProjectilesCount); + clientProjectiles.clear(); + for (int i = 0; i < clientProjectilesCount; i++) + { + RectangleObject* temp = new RectangleObject(); + temp->Read(packStream); + clientProjectiles.push_back(temp); + } + if(!packets.empty()) + packets.pop_front(); + + //process timed out acks + if (!packets.empty()) + processOrNot = packets.front()->timeStamp < al_get_timer_count(timer); + else + processOrNot = false; + + } + } + + + ALLEGRO_EVENT events; + + al_get_next_event(eventQueue, &events); + if (events.type == ALLEGRO_EVENT_KEY_UP) + { + switch (events.keyboard.keycode) + { + case ALLEGRO_KEY_ESCAPE: + exit = true; + case ALLEGRO_KEY_UP: + yMove = false; + yAxis = 0; + case ALLEGRO_KEY_DOWN: + yMove = false; + yAxis = 0; + case ALLEGRO_KEY_RIGHT: + xAxis = 0; + xMove = false; + case ALLEGRO_KEY_LEFT: + xAxis = 0; + xMove = false; + } + } + if (events.type == ALLEGRO_EVENT_KEY_DOWN) + { + al_get_keyboard_state(&keyState); + if (al_key_down(&keyState, ALLEGRO_KEY_UP)) + { + yMove = true; + yAxis = -1; + } + if (al_key_down(&keyState, ALLEGRO_KEY_DOWN)) + { + yMove = true; + yAxis = 1; + } + if (al_key_down(&keyState, ALLEGRO_KEY_RIGHT)) + { + xAxis = 1; + xMove = true; + } + if (al_key_down(&keyState, ALLEGRO_KEY_LEFT)) + { + xAxis = -1; + xMove = true; + } + if (al_key_down(&keyState, ALLEGRO_KEY_SPACE)) + { + ++projShot; + CircleClass* proj = new CircleClass(to_string(projShot), windowWidth, windowHeight, greenCircle[0].position[0], greenCircle[0].position[1], 10); + serverProjectiles.push_back(proj); + } + } + + for (int i = 0; i < numObjects; i++) + { + if (xMove || yMove) + { + greenCircle[i].UpdatePos(xAxis, yAxis); + } + inObjects[i].Draw(); + greenCircle[i].Draw(); + } + + /*if (!deadRects.empty()) + { + for (RectangleObject* deadRect : deadRects) + { + if(!clientProjectiles.empty()) + for (RectangleObject* liveRect : clientProjectiles) + { + if (deadRect == liveRect) + { + cout << "remove" << endl; + RectangleObject* temp = deadRect; + clientProjectiles.remove(temp); + deadRects.remove(temp); + + if (!clientProjectiles.empty()) + clientProjectiles.front(); + + if (!deadRects.empty()) + deadRects.front(); + + cout << "remove worked at least 1 time" << endl; + delete[] temp; + break; + } + } + } + }*/ + + for each (RectangleObject * projectile in clientProjectiles) + { + projectile->Draw(); + } + /*while (deadCircles.size() > 0) + { + for each (CircleClass * deadCircle in deadCircles) + { + for each (CircleClass * liveRect in serverProjectiles) + { + if (deadCircle == liveRect) + { + serverProjectiles.remove(deadCircle); + deadCircles.remove(deadCircle); + } + } + } + }*/ + for (CircleClass * projectile: serverProjectiles) + { + projectile->UpdatePos(0, -1); + projectile->Draw(); + /*for (RectangleObject* rects: clientProjectiles) + { + int xDist = rects->xPos - projectile->position[0]; + int yDist = rects->yPos - projectile->position[1]; + float dist = sqrt((xDist*xDist) + (yDist * yDist)); + if (dist < 1000) + { + cout << "kill eachother" << endl; + deadRects.push_back(rects); + deadCircles.push_back(projectile); + cout << "kill eachother worked" << endl; + break; + } + }*/ + } + for (int j = 0; j < numDroplets; j++) + { + rain[j].UpdatePos(1, 3); + rain[j].Draw(); + } + al_flip_display(); + al_clear_to_color(al_map_rgb(0,0,0)); + + //wait for the frame to end + int currentPeriod = al_get_timer_count(timer) - currentTime; + while (currentPeriod < FRAMERATE) + { + currentPeriod = al_get_timer_count(timer) - currentTime; + } + if (currentTime > 600000) + al_set_timer_count(timer, 0); + } + al_destroy_display(display); +} + +void BradsLessOriginalClient() +{ + TCPSocketPtr clientSocket = StartClientConnection(); + clientSocket->SetNonBlockingMode(true); + + //timer + ALLEGRO_TIMER* timer = GiveMeATimer(0.001); + double cTime = al_get_timer_count(timer); + float lastTime = 0; + float deltaTime = 0; + + bool exit = false; + const int numObjects = 1; + + //create Game Objects + RectangleObject outRectangles[numObjects]; + + for (int j = 0; j < numObjects; j++) + { + outRectangles[j] = RectangleObject("clientPlayer", windowWidth, windowHeight, 20, 20, rand() % windowWidth + 1, rand() % windowHeight + 1); + } + + // Read In Objects + CircleClass inObjects[numObjects]; + + list < RectangleObject* > clientProjectiles = {}; + list < CircleClass* > serverProjectiles = {}; + + const int numDroplets = 10; + RainParticle rain[numDroplets]; + + // Main Game Loop + ALLEGRO_DISPLAY* display; + display = al_create_display(windowWidth, windowHeight); + + ALLEGRO_KEYBOARD_STATE keyState; + ALLEGRO_EVENT_QUEUE* eventQueue = al_create_event_queue(); + al_register_event_source(eventQueue, al_get_keyboard_event_source()); + + int xAxis = 0; + int yAxis = 0; + + bool xMove = true; + bool yMove = true; + + int projShot = 0; + + list packets = {}; + DeliveryNotificationManager deliveryManager(true, true); + while (!exit) //GameLoop + { + //Timer + cTime = al_get_timer_count(timer); + deltaTime = cTime - lastTime; + lastTime = cTime; + + //send Data + OutputMemoryBitStream oStream = OutputMemoryBitStream(); + + deliveryManager.WriteState(oStream); + + for (int i = 0; i < numObjects; i++) + { + outRectangles[i].Write(oStream); + } + + int projectileCount = clientProjectiles.size(); + oStream.Write(projectileCount); + for each (RectangleObject* projectile in clientProjectiles) + { + projectile->Write(oStream); + } + clientSocket->Send(oStream.GetBufferPtr(), oStream.GetByteLength()); + + // Recieve data + char* buffer = new char[4096]; + int32_t bytesReceived = int32_t(); + bytesReceived = clientSocket->Receive(buffer, 4096); + + if (bytesReceived != -10035) + { + double stampTime = al_get_timer_count(timer); + int randomSin = rand() % 2; + if (randomSin == 0) + { + stampTime += rand() % 200 + 1; // jitter + } + else + { + stampTime -= rand() % 200 + 1; // jitter + } + stampTime += 1000; //latency + Packet* pack = new Packet(stampTime, buffer, 4096); + if(!(rand()%100+0 < 5)) //5% packet loss + packets.push_back(pack); + if (packets.size() > 1) + packets.sort([](const Packet* a, const Packet* b) { return a->timeStamp < b->timeStamp; }); + } + + if (!packets.empty()) + { + bool processOrNot = false; + + processOrNot = packets.front()->timeStamp < al_get_timer_count(timer); + + while(processOrNot) // maybe while + { + + InputMemoryBitStream packStream(packets.front()->mBufferPtr, 4096); + + deliveryManager.ReadAndProcessState(packStream); + deliveryManager.ProcessTimedOutPackets(); + + + for (int i = 0; i < numObjects; i++) + { + inObjects[i].Read(packStream); + } + for (int j = 0; j < numDroplets; j++) + { + rain[j].Read(packStream); + } + int serverProjectilesCount; + packStream.Read(serverProjectilesCount); + serverProjectiles.clear(); + for (int i = 0; i < serverProjectilesCount; i++) + { + CircleClass* temp = new CircleClass(); + temp->Read(packStream); + serverProjectiles.emplace_back(temp); + } + packets.pop_front(); + //readack + if(!packets.empty()) + processOrNot = packets.front()->timeStamp < al_get_timer_count(timer); + else + processOrNot = false; + + + } + + } + + ALLEGRO_EVENT events; + + al_get_next_event(eventQueue, &events); + + if (events.type == ALLEGRO_EVENT_KEY_UP) + { + switch (events.keyboard.keycode) + { + case ALLEGRO_KEY_ESCAPE: + exit = true; + case ALLEGRO_KEY_UP: + yMove = false; + yAxis = 0; + case ALLEGRO_KEY_DOWN: + yMove = false; + yAxis = 0; + case ALLEGRO_KEY_RIGHT: + xAxis = 0; + xMove = false; + case ALLEGRO_KEY_LEFT: + xAxis = 0; + xMove = false; + } + } + if (events.type == ALLEGRO_EVENT_KEY_DOWN) + { + al_get_keyboard_state(&keyState); + if (al_key_down(&keyState, ALLEGRO_KEY_UP)) + { + yMove = true; + yAxis = -1; + } + if (al_key_down(&keyState, ALLEGRO_KEY_DOWN)) + { + yMove = true; + yAxis = 1; + } + if (al_key_down(&keyState, ALLEGRO_KEY_RIGHT)) + { + xAxis = 1; + xMove = true; + } + if (al_key_down(&keyState, ALLEGRO_KEY_LEFT)) + { + xAxis = -1; + xMove = true; + } + if (al_key_down(&keyState, ALLEGRO_KEY_SPACE)) + { + ++projShot; + RectangleObject* proj = new RectangleObject(to_string(projShot), windowWidth, windowHeight, 10, 10, outRectangles[0].xPos, outRectangles[0].yPos); + clientProjectiles.push_back(proj); + } + } + + for (int i = 0; i < numObjects; i++) + { + if (xMove || yMove) + { + outRectangles[i].UpdatePos(xAxis, yAxis); + } + outRectangles[i].Draw(); + inObjects[i].Draw(); + } + for each (CircleClass * projectile in serverProjectiles) + { + projectile->Draw(); + } + for each (RectangleObject* projectile in clientProjectiles) + { + projectile->UpdatePos(0, 1); + projectile->Draw(); + } + + for (int j = 0; j < numDroplets; j++) + { + rain[j].Draw(); + } + al_flip_display(); + al_clear_to_color(al_map_rgb(0, 0, 0)); + + //wait for the frame to end + int currentPeriod = al_get_timer_count(timer) - cTime; + while (currentPeriod < FRAMERATE) + { + currentPeriod = al_get_timer_count(timer) - cTime; + } + if (cTime > 600000) + al_set_timer_count(timer, 0); + } + al_destroy_display(display); +} + int main(int argc, const char** argv) { UNREFERENCED_PARAMETER(argc); @@ -22,32 +672,26 @@ int main(int argc, const char** argv) __argv = argv; #endif - SocketUtil::StaticInit(); + SocketUtil::StaticInit(); + if (!al_init()) return -1; + + al_init_primitives_addon(); - OutputWindow win; + al_install_keyboard(); - std::thread t([&win]() - { - int msgNo = 1; - while (true) - { - std::this_thread::sleep_for(std::chrono::milliseconds(250)); - std::string msgIn("~~~auto message~~~"); - std::stringstream ss(msgIn); - ss << msgNo; - win.Write(ss.str()); - msgNo++; - } - }); + srand(time(NULL)); - while (true) + bool isServer = StringUtils::GetCommandLineArg(1) == "server"; // check if the command on the executable is 'server' + if (isServer) { - std::string input; - std::getline(std::cin, input); - win.WriteFromStdin(input); + BradsTotallyOriginalServer(); + } + else + { + BradsLessOriginalClient(); } - SocketUtil::CleanUp(); - + SocketUtil::CleanUp(); // socket cleanup return 0; } + diff --git a/RoboCat/Src/MemoryBitStream.cpp b/RoboCat/Src/MemoryBitStream.cpp new file mode 100644 index 00000000..39958ad7 --- /dev/null +++ b/RoboCat/Src/MemoryBitStream.cpp @@ -0,0 +1,171 @@ +#include "RoboCatPCH.h" + +void OutputMemoryBitStream::WriteBits( uint8_t inData, + uint32_t inBitCount ) +{ + uint32_t nextBitHead = mBitHead + static_cast< uint32_t >( inBitCount ); + + if( nextBitHead > mBitCapacity ) + { + ReallocBuffer( std::max( mBitCapacity * 2, nextBitHead ) ); + } + + //calculate the byteOffset into our buffer + //by dividing the head by 8 + //and the bitOffset by taking the last 3 bits + uint32_t byteOffset = mBitHead >> 3; + uint32_t bitOffset = mBitHead & 0x7; + + uint8_t currentMask = ~( 0xff << bitOffset ); + mBuffer[ byteOffset ] = ( mBuffer[ byteOffset ] & currentMask ) | ( inData << bitOffset ); + + //calculate how many bits were not yet used in + //our target byte in the buffer + uint32_t bitsFreeThisByte = 8 - bitOffset; + + //if we needed more than that, carry to the next byte + if( bitsFreeThisByte < inBitCount ) + { + //we need another byte + mBuffer[ byteOffset + 1 ] = inData >> bitsFreeThisByte; + } + + mBitHead = nextBitHead; +} + +void OutputMemoryBitStream::WriteBits( const void* inData, uint32_t inBitCount ) +{ + const char* srcByte = static_cast< const char* >( inData ); + //write all the bytes + while( inBitCount > 8 ) + { + WriteBits( *srcByte, 8 ); + ++srcByte; + inBitCount -= 8; + } + //write anything left + if( inBitCount > 0 ) + { + WriteBits( *srcByte, inBitCount ); + } +} + +void OutputMemoryBitStream::Write( const Vector3& inVector ) +{ + Write( inVector.mX ); + Write( inVector.mY ); + Write( inVector.mZ ); +} + +void InputMemoryBitStream::Read( Vector3& outVector ) +{ + Read( outVector.mX ); + Read( outVector.mY ); + Read( outVector.mZ ); +} + +void OutputMemoryBitStream::Write( const Quaternion& inQuat ) +{ + float precision = ( 2.f / 65535.f ); + Write( ConvertToFixed( inQuat.mX, -1.f, precision ), 16 ); + Write( ConvertToFixed( inQuat.mY, -1.f, precision ), 16 ); + Write( ConvertToFixed( inQuat.mZ, -1.f, precision ), 16 ); + Write( inQuat.mW < 0 ); +} + + + +void OutputMemoryBitStream::ReallocBuffer( uint32_t inNewBitLength ) +{ + if( mBuffer == nullptr ) + { + //just need to memset on first allocation + mBuffer = static_cast( std::malloc( inNewBitLength >> 3 ) ); + memset( mBuffer, 0, inNewBitLength >> 3 ); + } + else + { + //need to memset, then copy the buffer + char* tempBuffer = static_cast( std::malloc( inNewBitLength >> 3 ) ); + memset( tempBuffer, 0, inNewBitLength >> 3 ); + memcpy( tempBuffer, mBuffer, mBitCapacity >> 3 ); + std::free( mBuffer ); + mBuffer = tempBuffer; + } + + //handle realloc failure + //... + mBitCapacity = inNewBitLength; +} + + +void test1() +{ + OutputMemoryBitStream mbs; + + mbs.WriteBits( 11, 5 ); + mbs.WriteBits( 52, 6 ); +} + +void InputMemoryBitStream::ReadBits( uint8_t& outData, uint32_t inBitCount ) +{ + uint32_t byteOffset = mBitHead >> 3; + uint32_t bitOffset = mBitHead & 0x7; + + outData = static_cast< uint8_t >( mBuffer[ byteOffset ] ) >> bitOffset; + + uint32_t bitsFreeThisByte = 8 - bitOffset; + if( bitsFreeThisByte < inBitCount ) + { + //we need another byte + outData |= static_cast< uint8_t >( mBuffer[ byteOffset + 1 ] ) << bitsFreeThisByte; + } + + //don't forget a mask so that we only read the bit we wanted... + outData &= ( ~( 0x00ff << inBitCount ) ); + + mBitHead += inBitCount; +} + +void InputMemoryBitStream::ReadBits( void* outData, uint32_t inBitCount ) +{ + uint8_t* destByte = reinterpret_cast< uint8_t* >( outData ); + //write all the bytes + while( inBitCount > 8 ) + { + ReadBits( *destByte, 8 ); + ++destByte; + inBitCount -= 8; + } + //write anything left + if( inBitCount > 0 ) + { + ReadBits( *destByte, inBitCount ); + } +} + +void InputMemoryBitStream::Read( Quaternion& outQuat ) +{ + float precision = ( 2.f / 65535.f ); + + uint32_t f = 0; + + Read( f, 16 ); + outQuat.mX = ConvertFromFixed( f, -1.f, precision ); + Read( f, 16 ); + outQuat.mY = ConvertFromFixed( f, -1.f, precision ); + Read( f, 16 ); + outQuat.mZ = ConvertFromFixed( f, -1.f, precision ); + + outQuat.mW = sqrtf( 1.f - + (outQuat.mX * outQuat.mX + + outQuat.mY * outQuat.mY + + outQuat.mZ * outQuat.mZ)); + bool isNegative; + Read( isNegative ); + + if( isNegative ) + { + outQuat.mW *= -1; + } +} diff --git a/RoboCat/Src/TCPSocket.cpp b/RoboCat/Src/TCPSocket.cpp index 494f776c..0cfe2b20 100644 --- a/RoboCat/Src/TCPSocket.cpp +++ b/RoboCat/Src/TCPSocket.cpp @@ -55,8 +55,12 @@ int32_t TCPSocket::Receive( void* inData, size_t inLen ) int bytesReceivedCount = recv( mSocket, static_cast< char* >( inData ), inLen, 0 ); if( bytesReceivedCount < 0 ) { - SocketUtil::ReportError( "TCPSocket::Receive" ); - return -SocketUtil::GetLastError(); + int error = -SocketUtil::GetLastError(); + if (error != -10035) + { + SocketUtil::ReportError("TCPSocket::Receive"); + } + return error; } return bytesReceivedCount; } diff --git a/RoboCat/Src/Timing.cpp b/RoboCat/Src/Timing.cpp new file mode 100644 index 00000000..9a8be86c --- /dev/null +++ b/RoboCat/Src/Timing.cpp @@ -0,0 +1,62 @@ +#include "RoboCatPCH.h" + + +#if !_WIN32 + #include + using namespace std::chrono; +#endif + +Timing Timing::sInstance; + +namespace +{ +#if _WIN32 + LARGE_INTEGER sStartTime = { 0 }; +#else + high_resolution_clock::time_point sStartTime; +#endif +} + +Timing::Timing() +{ +#if _WIN32 + LARGE_INTEGER perfFreq; + QueryPerformanceFrequency( &perfFreq ); + mPerfCountDuration = 1.0 / perfFreq.QuadPart; + + QueryPerformanceCounter( &sStartTime ); + + mLastFrameStartTime = GetTime(); +#else + sStartTime = high_resolution_clock::now(); +#endif +} + +void Timing::Update() +{ + + double currentTime = GetTime(); + + mDeltaTime = ( float ) ( currentTime - mLastFrameStartTime ); + + mLastFrameStartTime = currentTime; + mFrameStartTimef = static_cast< float > ( mLastFrameStartTime ); + +} + +double Timing::GetTime() const +{ +#if _WIN32 + LARGE_INTEGER curTime, timeSinceStart; + QueryPerformanceCounter( &curTime ); + + timeSinceStart.QuadPart = curTime.QuadPart - sStartTime.QuadPart; + + return timeSinceStart.QuadPart * mPerfCountDuration; +#else + auto now = high_resolution_clock::now(); + auto ms = duration_cast< milliseconds >( now - sStartTime ).count(); + //a little uncool to then convert into a double just to go back, but oh well. + return static_cast< double >( ms ) / 1000; +#endif +} \ No newline at end of file diff --git a/RoboCat/packages.config b/RoboCat/packages.config new file mode 100644 index 00000000..9ff6486d --- /dev/null +++ b/RoboCat/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file