/*******************************************************************************
* Copyright 2015 Klaus Pfeiffer <klaus@allpiper.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.jfastnet.messages;
import com.jfastnet.ISimpleProcessable;
import com.jfastnet.messages.features.MessageFeatures;
import com.jfastnet.messages.features.TimestampFeature;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
/** This message is sent from the server to the client and then back to the
* server. It is used to measure the round trip time.
* @author Klaus Pfeiffer - klaus@allpiper.com */
@Slf4j
public class TimerSyncMessage extends Message implements IInstantServerProcessable, IDontFrame {
/* -- */
public static volatile boolean started = false;
public static ISimpleProcessable initiateSynchronousStart;
private static final Object checkStartedLock = new Object();
/* -- */
/** Client ID returning the sync request. */
private int clientId;
/** System timestamp when last sync action was requested. Only set by server. */
@Setter @Getter
private static long lastTimestamp;
@Getter
private static Map<Integer, Long> roundTrip = new HashMap<>();
/** System playerIndex plus timestampDifferenceToHost equals host time. */
@Getter
private static Map<Integer, Long> timestampDifferenceToHost = new HashMap<>();
@Getter
private MessageFeatures features = new MessageFeatures();
public TimerSyncMessage(int clientId) {
this.clientId = clientId;
getFeatures().add(new TimestampFeature());
}
/*
* IMPORTANT: the server processes this method!
*/
@Override
public void process(Object context) {
/*
* The host sends out this action, which gets immediately returned (without processing)
* by the clients and received by the server,
* who then calculates the roundtrips for all clients.
*/
long retrievedTs = getTimestamp();
long sysTs = System.currentTimeMillis();
long roundTripTime = sysTs - lastTimestamp;
log.trace("RTT for player {}: {} ms", clientId, roundTripTime);
roundTrip.put(clientId, roundTripTime);
long offsetToHost = -(retrievedTs - sysTs - (roundTripTime / 2));
timestampDifferenceToHost.put(clientId, offsetToHost);
// send information back to client, so he can compute actions accordingly
ClientTimerSyncMessage clientTimerSyncMessage = new ClientTimerSyncMessage(offsetToHost, roundTripTime);
clientTimerSyncMessage.setReceiverId(clientId);
getConfig().internalSender.send(clientTimerSyncMessage);
synchronized (checkStartedLock) {
if (!started && getConfig().requiredClients.size() > 0) {
Map<Integer, Boolean> requiredClients = getConfig().requiredClients;
boolean allReady = true;
for (Boolean ready : requiredClients.values()) {
if (!ready) {
allReady = false;
break;
}
}
if (allReady) {
started = true;
initiateSynchronousStart.process();
}
}
}
}
@Override
public ReliableMode getReliableMode() {
return ReliableMode.UNRELIABLE;
}
}