#pragma once
#include <memory>
#include <functional>
#include "GameAction.h"
#include "ChannelInterface.h"
/*
The GameActionsChannel is designed to be symetrical on both the client and server to share this code between them.
It provides a bi-directional pipe of GameActions between the client and game server.
GameActions get placed in to a queue which is once per frame, serialized and turned in to packets and sent on a socket.
Also once per frame, the socket is drained and as many GameActions are de-serialized and dispatched as are available.
Additional logic is provided in the implementation to handle acknowledgement of actions and the resending of
unacknowledged actions in the event of a re-connection.
The GameActionsChannel operates on an abstract socket interface such that, depending on the platform, or if it is the
client or server, different implementation can be used for establishing the socket and for reading and writing to it.
This may also be useful for unit testing of the channel by allowing mocking in a test replacement for an actual socket.
*/
GA_BEGIN(ChannelState)
GA_MEMBER(int32_t, m_ver, 0)
GA_MEMBER(int32_t, m_seqNumber, 0)
GA_MEMBER(int32_t, m_gameTick, 0)
GA_MEMBER(int32_t, m_ackNumber, 0)
GA_END(ChannelState)
class GameActionsChannel
{
public:
explicit GameActionsChannel(std::shared_ptr<EventSink> a_eventSink);
void AssignChannelInterface(std::shared_ptr<ChannelInterface> a_channelInterface)
{
m_channelInterface = a_channelInterface;
}
std::shared_ptr<EventSink> GetEventSink()
{
return m_eventSink;
}
template <typename ActionType>
void QueueAction(ActionType& a_action)
{
std::shared_ptr<ActionType> action(std::make_shared<ActionType>(a_action));
m_queuedActions.push_back([action](std::vector<uint8_t>& a_outData) { ActionType::SerializeToJson(*action.get(), a_outData); });
}
// Returns true if still connected
bool Update(std::chrono::milliseconds a_timeElapsed, std::chrono::milliseconds a_timeout = std::chrono::milliseconds(0));
// TODO: this might only be needed for debug purposes. Will we need an explicit disconnect function?
void Disconnect()
{
if (m_channelInterface)
{
m_channelInterface->Close();
}
}
void ResyncConnection()
{
}
private:
void ReceiveAndDispatchActions(std::chrono::milliseconds a_timeout);
void SendQueuedActions(std::chrono::milliseconds a_timeout);
void Reset();
void Error(const char* errorMsg);
// Extra meta things - json actions to include clientConfig, ping, bandwidth probe etc
// std::string m_bandwidthProbeBuffer = "TheQuickBrownFoxJumpedOverTheLazyDog";
// TODO: perhaps could use double buffering for the send/recv buffers or something a bit better if deemed neccessary
// Send buffer
std::vector<uint8_t> m_queuedActionsBuffer;
std::vector<uint8_t> m_encodedSendBuffer;
int m_offset = 0;
// Receive buffer
std::vector<uint8_t> m_receiveBuffer;
std::vector<uint8_t> m_unpackedPacketBuffer;
size_t m_receiveBytesNeeded = 0;
bool m_receivedPacketHeader = false;
size_t m_receiveBufferOffset = 0;
ChannelState m_channelState;
std::map<std::string, void(*)(EventSink&, bne::JsonValueConstRef)> m_actionFactory;
std::shared_ptr<ChannelInterface> m_channelInterface;
std::shared_ptr<EventSink> m_eventSink;
std::vector < std::function<void(std::vector<uint8_t>& a_outData)> > m_queuedActions;
};
// Note: these are only exposed for the purposes of unit testing
// there shouldn't be a need to call these otherwise
namespace details
{
// For unit testing only
bool Pack(std::vector<uint8_t>& a_outputBuffer, const uint8_t* a_inputBytes, size_t a_intputLength);
// For unit testing only
bool Unpack(std::vector<uint8_t>& a_outputBuffer, uint8_t* a_inputBytes, size_t a_intputLength);
}