An important part of my networking code for Championship Ultimate is getting all the devices clocks synced up, so that they can talk intelligently about what time events occurred in the game. It turns out that this is not a trivial problem, and I spent a couple days working on it.
This question I asked on stackoverflow.com goes into more detail, and I got some helpful answers: http://stackoverflow.com/questions/3755208/measuring-time-difference-between-networked-devices
This wikipedia page was also helpful:
http://en.wikipedia.org/wiki/Precision_Time_Protocol
So, with all that info, I made a class to do the job that I'm fairly proud of. Perhaps it will be useful to someone else someday. Do whatever you want with the code, but it would be fun if you left a comment to say it was useful. The actual network communication stuff obviously needs to be changed to fit in your code.
TimeSync.h
//Call [timeSync startSync] on the client. The offset variable will be continually updated
//throughout the life of the program, and it will be sent back to the server also.
#define TS_DELAY_MAX 10000
@interface TimeSync : NSObject
{
NSTimeInterval t1, t2; //t1 is client time - server time, and t2 is server time - client time
NSTimeInterval offset; //this doesn't get calculated from t1 and t2 until it's stabilized
uint synccount;
}
-(NSTimeInterval)offset;
-(void)checkSyncStatus;
-(void)sendTimeSync;
-(void)sendDelayRequest;
-(void)sendDelayResponse:(NSTimeInterval)from;
-(void)processPacketType:(uint8_t)type TimeStamp:(NSTimeInterval)timeStamp;
-(void)sendSyncRequest;
-(void)publishOffset;
TimeSync.m
@implementation TimeSync
-(id)init
{
if (self = [super init])
{
t1 = TS_DELAY_MAX;
t2 = TS_DELAY_MAX;
offset = TS_DELAY_MAX;
synccount = 0;
}
return self;
}
-(void)sendSyncRequest
{
[[Basket shared].connection sendPacket:PT_SYNC_REQ Data:nil Mode:GKSendDataReliable];
}
-(void)sendTimeSync
{
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
[[Basket shared].connection sendPacket:PT_TIME_SYNC Data:[NSData dataWithBytes:&now length:sizeof(NSTimeInterval)] Mode:GKSendDataUnreliable];
}
-(void)sendDelayRequest
{
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
[[Basket shared].connection sendPacket:PT_DELAY_REQ Data:[NSData dataWithBytes:&now length:sizeof(NSTimeInterval)] Mode:GKSendDataUnreliable];
}
-(void)sendDelayResponse:(NSTimeInterval)from
{
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval diff = now - from;
[[Basket shared].connection sendPacket:PT_DELAY_RSP Data:[NSData dataWithBytes:&diff length:sizeof(NSTimeInterval)] Mode:GKSendDataUnreliable];
}
-(void)sendSyncNotify
{
[[Basket shared].connection sendPacket:PT_SYNC_NOTIFY Data:[NSData dataWithBytes:&offset length:sizeof(NSTimeInterval)] Mode:GKSendDataReliable];
}
-(void)startSync
{
//get a first quick measurement, then only measure every 10 seconds
//basically, a new offset will be published every minute
if (offset == TS_DELAY_MAX)
{
[self performSelector:@selector(startSync) withObject:nil afterDelay:0.5];
}
else
{
[self performSelector:@selector(startSync) withObject:nil afterDelay:10.0];
}
[self sendSyncRequest];
}
-(void)publishOffset
{
offset = (t2 - t1)/2.0;
NSLog(@"TimeSync found delay to be %f", offset);
NSLog(@"Ping was %f", (t2 + t1)/2.0);
//reset so we can measure a new drift
t1 = TS_DELAY_MAX;
t2 = TS_DELAY_MAX;
NSLog(@"Notifying server of offset...");
[self sendSyncNotify];
}
-(void)processPacketType:(uint8_t)type TimeStamp:(NSTimeInterval)timeStamp
{
NSTimeInterval timediff;
switch (type)
{
case PT_SYNC_REQ:
[self sendTimeSync];
break;
case PT_TIME_SYNC:
timediff = [NSDate timeIntervalSinceReferenceDate] - timeStamp;
if (timediff < t1)
t1 = timediff;
[self sendDelayRequest];
break;
case PT_DELAY_REQ:
[self sendDelayResponse:timeStamp];
break;
case PT_DELAY_RSP:
if (timeStamp < t2)
t2 = timeStamp;
synccount++;
if (synccount % 5 == 0) //publish the new offset every 5 measurements
{
[self publishOffset];
}
break;
case PT_SYNC_NOTIFY:
offset = -timeStamp;
NSLog(@"Received notification of offset:%f", offset);
default:
break;
}
}
-(NSTimeInterval)offset
{
return offset;
}