Newer
Older
Import / projects / Gameloft / core / Network / SessionWatchDog.h
#pragma once


#include "Network/GameSession.h"
#include "Network/NetworkGameActions.h"


class SessionWatchDog : public GameClass
{
public:
  SessionWatchDog(std::shared_ptr<GameSession> a_session, std::string a_name)
    : GameClass(a_session)
    , m_name(a_name)
  {
    SessionRegisterListener(HandlePing);
    SessionRegisterListener(HandlePong);
    SessionRegisterListener(HandleConnected);
    SessionRegisterListener(HandleDisconnected);
    SessionRegisterListener(HandleInDanger);
    SessionRegisterListener(HandleOutOfDanger);
    SessionRegisterListener(HandleBandwidthProbeRequest);
    SessionRegisterListener(HandleBandwidthProbeResponse);
    m_probe.m_probeStr = "BandwidthProbeString";
  }

  ~SessionWatchDog()
  {
  }

  void SetTimeOuts(std::chrono::milliseconds a_inDangerTimeOut, std::chrono::milliseconds a_abandonedConnectionThreshold, std::chrono::milliseconds a_timeBetweenPings)
  {
    m_timeoutTillInDanger = a_inDangerTimeOut;  // Amount of time until considered in-danger
    m_abandonedConnectionThreshold = a_abandonedConnectionThreshold;
    m_timeBetweenPings = a_timeBetweenPings;  // Frequency to send pings
  }

  void HandlePing(const PingAction& a_ping)
  {
    PongAction pong;
    pong.SetRespondingToAction(a_ping);
    SessionQueueAction(pong);
  }

  void HandlePong(const PongAction& a_pong)
  {
    bool okay = a_pong.IsResponseToAction(m_currentPing);
    if (!okay)
    {
      VerboseDebugging("Got an INVALID Pong");
    }
    m_havePong |= okay;
    if (okay && m_isInDanger)
    {
      SessionQueueEvent(ConnectionOutOfDanger());
    }
  }

  void HandleBandwidthProbeRequest(const BandwidthProbeRequest& a_probe)
  {
    VerboseDebugging("Got a BandwidthProbe Request");
    BandwidthProbeResponse resp;
    resp.m_probeStr = a_probe.m_probeStr;
    resp.SetRespondingToAction(a_probe);
    SessionQueueAction(resp);
  }

  void HandleBandwidthProbeResponse(const BandwidthProbeResponse& a_probe)
  {
    bool okay = a_probe.IsResponseToAction(m_probe) && (a_probe.m_probeStr.size() == m_probe.m_probeStr.size());
    VerboseDebugging("Got a %s BandwidthProbe Response", okay ? "valid" : "INVALID");
  }

  void HandleConnected(const ConnectionEstablished& a_connect)
  {
    m_isConnected = true;
    m_isInDanger = false;
    m_elapsedTimeSinceLastSentPing = std::chrono::milliseconds(0);
    VerboseDebugging("Got a Connection Established");
  }

  void HandleDisconnected(const ConnectionLost& a_lost)
  {
    m_isConnected = false;
    VerboseDebugging("Got a Connection Lost");
  }

  void HandleInDanger(const ConnectionInDanger& a_danger)
  {
    m_isInDanger = true;
    VerboseDebugging("Got a Connection In-Danger");
  }

  void HandleOutOfDanger(const ConnectionOutOfDanger& a_danger)
  {
    m_isInDanger = false;
    VerboseDebugging("Got a Connection Out-Of-Danger");
  }

  bool UpdateWatchDog(std::chrono::milliseconds a_timeElapsed)
  {
    if (!m_isConnected)
    {
      return false;
    }

    m_elapsedTimeSinceLastSentPing += a_timeElapsed;

    if (!m_isInDanger && m_elapsedTimeSinceLastSentPing > m_timeoutTillInDanger)
    {
      m_missedPongs++;
      SessionQueueEvent(ConnectionInDanger());
    }

    if (m_isInDanger && m_elapsedTimeSinceLastSentPing > m_abandonedConnectionThreshold)
    {
      SessionQueueEvent(ConnectionLost());
      return false;
    }

    if (m_havePong && m_elapsedTimeSinceLastSentPing > m_timeBetweenPings)
    {
      PingAction newPing;
      m_currentPing = newPing;
      SessionQueueAction(m_currentPing);
      m_elapsedTimeSinceLastSentPing = std::chrono::milliseconds(0);
      m_havePong = false;

      /*
      // TODO: This is just test code to try out the re-connection path by forcably closing it periodically (should remove this when done testing the re-connection code)
      static int testRetry = 0;
      if (m_name == "Server") // Test when it is the server doing the disconnecting (try both cases, client or server initiated disconnections)
      {
        testRetry++;
        if (testRetry == 3) // every 10th ping, force disconnect to test the re-connecting code
        {
          // Only needed for debugging/testing
          GetGameContext()->Disconnect();
          testRetry = 0;
        }
      }
      */
    }

    return true;
  }

private:
  std::chrono::milliseconds m_timeoutTillInDanger = std::chrono::seconds(2);  // Amount of time until considered in-danger
  std::chrono::milliseconds m_abandonedConnectionThreshold = std::chrono::seconds(5);  // Amount of time until consider the connection abandoned
  std::chrono::milliseconds m_timeBetweenPings = std::chrono::seconds(1);      // Frequency to send pings
  std::string m_name;
  PingAction m_currentPing;
  int m_missedPongs = 0;
  bool m_havePong = true;
  std::chrono::milliseconds m_elapsedTimeSinceLastSentPing = std::chrono::seconds(0);
  bool m_isInDanger = false;
  bool m_isConnected = false;
  BandwidthProbeRequest m_probe;

protected:
  template <typename ...Args>
  void VerboseDebugging(const char* fmt, Args ...args)
  {
#if 0 // Enable to debug the client<->server handshake
    printf("%s: ", m_name.c_str());
    printf(fmt, args...);
    printf("\n");
#endif
  }
};