-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathApianClock.cs
184 lines (153 loc) · 7.16 KB
/
ApianClock.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
using System;
using System.Collections.Generic;
using UniLog;
namespace Apian
{
public interface IApianClock
{
// ReSharper disable UnusedMemberInSuper.Global,UnusedMember.Global
long CurrentTime { get;} // This is the ApianTime
void Set(long desiredTimeMs, float rate=1.0f );
bool IsIdle { get;} // hasn't been set yet
long SystemTime { get;} // system clock
long SysClockOffset {get; } // The current effective offset from system time
void OnP2pPeerSync(string remotePeerId, long clockOffsetMs, long netLagMs); // sys + offset = apian
void SendApianClockOffset(); // Another part of Apian might want us to send this ( when member joins, for instance)
void OnApianClockOffset(string remotePeerId, long apianOffset);
void Update(); // loop
// ReSharper enable UnusedMemberInSuper.Global,UnusedMember.Global
}
// ReSharper disable once UnusedType.Global
public class DefaultApianClock : IApianClock
{
//
// Clock rate can be adjusted
//
// reported time is:
// time = (sysMs - sysMsBase) * timeMult + timeOffset
// where:
// sysMs - current system time
// sysMsBase - system time when the rate or offset was last set
// time - clock rate. 1.0 is real time
// timeOffset - the time you wanted it to be last time you set the rate or offset
//
// set rate and offset at the same time.
// TODO: should get notified of peerleft?
public readonly UniLogger Logger;
// Internal vars
private readonly ApianBase _apian;
private long _sysMsBase; // the system time last time the rate was set
private long _apianTimeBase; // this is the ApianTime when the clock wa last set
private float _currentRate;
private const int OffsetAnnouncementPeriodMs = 10000; //
private long NewNextOffsetAnnounceTime {get => SystemTime + OffsetAnnouncementPeriodMs + new Random().Next(OffsetAnnouncementPeriodMs/4); }
private long _nextOffsetAnnounceTime;
public DefaultApianClock(ApianBase apian)
{
_apian = apian;
_sysOffsetsByPeer = new Dictionary<string, long>();
_apianOffsetsByPeer = new Dictionary<string, long>();
Logger = UniLogger.GetLogger("ApianClock");
}
// Keeping track of peers.
private readonly Dictionary<string, long> _sysOffsetsByPeer;
private readonly Dictionary<string, long> _apianOffsetsByPeer;
// IApianClock public stuff
public bool IsIdle { get => (_currentRate == 0 && _apianTimeBase == 0);} // hasn't been set yet
public long SystemTime { get => DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;} // system clock
public long CurrentTime { get => (long)((SystemTime - _sysMsBase) * _currentRate) + _apianTimeBase;} // This is the ApianTime
public long SysClockOffset {get => CurrentTime - SystemTime; } // The current effective offset.
public void Update()
{
if (IsIdle)
return;
// Not the most sophisticated way. But easy to understand and implment in many languages.
long nowMs = SystemTime;
if (nowMs > _nextOffsetAnnounceTime)
SendApianClockOffset();
}
// Set the time
public void Set(long desiredTimeMs, float rate=1.0f )
{
Logger.Verbose("Set()");
_apianTimeBase = desiredTimeMs;
_sysMsBase = SystemTime;
_currentRate = rate;
}
public void OnP2pPeerSync(string remotePeerId, long clockOffsetMs, long netLagMs) // sys + offset = apian
{
// This is a P2pNet sync ( lag and sys clock offset determination )
Logger.Verbose($"OnPeerSync() from {remotePeerId}.");
// save this
_sysOffsetsByPeer[remotePeerId] = clockOffsetMs;
}
public void OnApianClockOffset(string p2pId, long remoteApianOffset)
{
// peer is reporting it's local apian offset (SysClockOffset to the peer, peerAppOffset to us)
// By using P2pNet's estimate for that peer's system offset vs our system clock
// ( ourSysTime + peerOffset = peerSysTime)
// we can infer what the difference is betwen our ApianClock and theirs.
// and by "infer" I mean "kinda guess sorta"
// remoteAppClk = sysMs + peerOffSet + peerAppOffset
//
Logger.Verbose($"OnApianClockOffset() from peer {p2pId}");
if (p2pId == _apian.ApianGroup.LocalPeerId)
{
Logger.Verbose("OnApianClockOffset(). Oops. It's me. Bailing");
return;
}
_apianOffsetsByPeer[p2pId] = remoteApianOffset;
if (IsIdle) // this is the first we've gotten. Set set ours to match theirs.
{
if (_sysOffsetsByPeer.ContainsKey(p2pId)) // we need to have a current P2pNet sys clock offset
{
// CurrentTime = sysMs + peerOffset + peerAppOffset;
Set( SystemTime + _sysOffsetsByPeer[p2pId] + remoteApianOffset );
Logger.Verbose($"OnApianClockOffset() - Set clock to match {p2pId}");
}
} else {
UpdateForOtherPeers();
}
}
// Internals
public void SendApianClockOffset()
{
if (_apian.ApianGroup != null)
{
Logger.Verbose($"SendApianClockOffset() - Current Time: {CurrentTime}");
_apian.SendApianMessage(_apian.ApianGroup.GroupId, new ApianClockOffsetMsg( _apian.ApianGroup.GroupId, _apian.ApianGroup.LocalPeerId, SysClockOffset));
}
_nextOffsetAnnounceTime = NewNextOffsetAnnounceTime;
}
private void UpdateForOtherPeers()
{
long localErrSum = 0;
int peerCount = 0;
foreach (string pid in _sysOffsetsByPeer.Keys)
{
try {
if (pid != _apian.ApianGroup.LocalPeerId)
{
long peerSysOff = _sysOffsetsByPeer[pid]; // ( ourTime + offset = peerTime)
long peerAppOff = _apianOffsetsByPeer[pid];
// localErr = remoteAppClk - localAppClk = (sysMs + peerOffset + peerAppOffset) - CurrentTime
localErrSum += SystemTime + peerSysOff + peerAppOff - CurrentTime;
peerCount++;
}
} catch(KeyNotFoundException) {}
}
if (peerCount > 0)
{
long localErrMs = localErrSum / peerCount;
// Try to correct half of the error in kOffsetAnnounceBaseMs
float newRate = 1.0f + (.5f * localErrMs / OffsetAnnouncementPeriodMs);
Set(CurrentTime, newRate);
Logger.Verbose($"Update: local error: {localErrMs}, New Rate: {newRate}");
}
else
{
Logger.Verbose("Update: No other peers.");
}
}
}
}