1 //
2 //  SuperTuxKart - a fun racing game with go-kart
3 //  Copyright (C) 2018 SuperTuxKart-Team
4 //
5 //  This program is free software; you can redistribute it and/or
6 //  modify it under the terms of the GNU General Public License
7 //  as published by the Free Software Foundation; either version 3
8 //  of the License, or (at your option) any later version.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 #ifndef HEADER_NETWORK_TIMER_SYNCHRONIZER_HPP
20 #define HEADER_NETWORK_TIMER_SYNCHRONIZER_HPP
21 
22 #include "network/stk_host.hpp"
23 #include "utils/log.hpp"
24 #include "utils/time.hpp"
25 #include "utils/types.hpp"
26 
27 #include <atomic>
28 #include <cstdlib>
29 #include <deque>
30 #include <numeric>
31 #include <tuple>
32 
33 class NetworkTimerSynchronizer
34 {
35 private:
36     std::deque<std::tuple<uint32_t, uint64_t, uint64_t> > m_times;
37 
38     std::atomic_bool m_synchronised, m_force_set_timer;
39 
40 public:
NetworkTimerSynchronizer()41     NetworkTimerSynchronizer()
42     {
43         m_synchronised.store(false);
44         m_force_set_timer.store(false);
45     }
46     // ------------------------------------------------------------------------
isSynchronised() const47     bool isSynchronised() const               { return m_synchronised.load(); }
48     // ------------------------------------------------------------------------
enableForceSetTimer()49     void enableForceSetTimer()
50     {
51         if (m_synchronised.load() == true)
52             return;
53         m_force_set_timer.store(true);
54     }
55     // ------------------------------------------------------------------------
resynchroniseTimer()56     void resynchroniseTimer()                  { m_synchronised.store(false); }
57     // ------------------------------------------------------------------------
addAndSetTime(uint32_t ping,uint64_t server_time)58     void addAndSetTime(uint32_t ping, uint64_t server_time)
59     {
60         if (m_synchronised.load() == true)
61             return;
62 
63         if (m_force_set_timer.load() == true)
64         {
65             m_force_set_timer.store(false);
66             m_synchronised.store(true);
67             STKHost::get()->setNetworkTimer(server_time + (uint64_t)(ping / 2));
68             return;
69         }
70 
71         const uint64_t cur_time = StkTime::getMonoTimeMs();
72         // Discard too close time compared to last ping
73         // (due to resend when packet loss)
74         // 10 packets per second as seen in STKHost
75         const uint64_t frequency = (uint64_t)((1.0f / 10.0f) * 1000.0f) / 2;
76         if (!m_times.empty() &&
77             cur_time - std::get<2>(m_times.back()) < frequency)
78             return;
79 
80         // Take max 20 averaged samples from m_times, the next addAndGetTime
81         // is used to determine that server_time if it's correct, if not
82         // clear half in m_times until it's correct
83         if (m_times.size() >= 20)
84         {
85             uint64_t sum = std::accumulate(m_times.begin(), m_times.end(),
86                 (uint64_t)0, [cur_time](const uint64_t previous,
87                 const std::tuple<uint32_t, uint64_t, uint64_t>& b)->uint64_t
88                 {
89                     return previous + (uint64_t)(std::get<0>(b) / 2) +
90                         std::get<1>(b) + cur_time - std::get<2>(b);
91                 });
92             const int64_t averaged_time = sum / 20;
93             const int64_t server_time_now = server_time + (uint64_t)(ping / 2);
94             int difference = (int)std::abs(averaged_time - server_time_now);
95             if (std::abs(averaged_time - server_time_now) <
96                 UserConfigParams::m_timer_sync_difference_tolerance)
97             {
98                 STKHost::get()->setNetworkTimer(averaged_time);
99                 m_times.clear();
100                 m_force_set_timer.store(false);
101                 m_synchronised.store(true);
102                 Log::info("NetworkTimerSynchronizer", "Network "
103                     "timer synchronized, difference: %dms", difference);
104                 return;
105             }
106             m_times.erase(m_times.begin(), m_times.begin() + 10);
107         }
108         m_times.emplace_back(ping, server_time, cur_time);
109     }
110 };
111 
112 #endif // HEADER_NETWORK_TIMER_SYNCHRONIZER_HPP
113