/*
* Copyright (c) 2009-2010 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.network.sync;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.jme.math.FastMath;
import com.jme3.network.connection.Client;
import com.jme3.network.connection.Server;
import com.jme3.network.events.ConnectionAdapter;
import com.jme3.network.service.Service;
public class ServerSyncService extends ConnectionAdapter implements Service {
private static final ByteBuffer BUFFER = ByteBuffer.wrap(new byte[10000]);
private float updateRate = 0.1f;
private float packetDropRate = 0;
private long latency = 0;
private HashMap<Long, SyncMessage> latencyQueue;
private final Server server;
private final SyncSerializer serializer = new SyncSerializer();
// private final ArrayList<Client> connectedClients = new ArrayList<Client>();
private final ArrayList<SyncEntity> npcs = new ArrayList<SyncEntity>();
private final HashMap<SyncEntity, Integer> npcToId
= new HashMap<SyncEntity, Integer>();
private static int nextId = 0;
private int heartbeat = 0;
private float time = 0;
public ServerSyncService(Server server){
this.server = server;
server.addConnectionListener(this);
}
public void setNetworkSimulationParams(float packetDropRate, long latency){
if (latencyQueue == null)
latencyQueue = new HashMap<Long, SyncMessage>();
this.packetDropRate = packetDropRate;
this.latency = latency;
}
private EntitySyncInfo generateInitInfo(SyncEntity entity, boolean newId){
EntitySyncInfo info = new EntitySyncInfo();
info.className = entity.getClass().getName();
info.id = newId ? nextId ++ : npcToId.get(entity);
info.type = EntitySyncInfo.TYPE_NEW;
BUFFER.clear();
serializer.write(entity, BUFFER, true);
BUFFER.flip();
info.data = new byte[BUFFER.limit()];
BUFFER.get(info.data);
return info;
}
private EntitySyncInfo generateSyncInfo(SyncEntity entity){
EntitySyncInfo info = new EntitySyncInfo();
info.className = null;
info.id = npcToId.get(entity);
info.type = EntitySyncInfo.TYPE_SYNC;
BUFFER.clear();
serializer.write(entity, BUFFER, false);
BUFFER.flip();
info.data = new byte[BUFFER.limit()];
BUFFER.get(info.data);
return info;
}
private EntitySyncInfo generateDeleteInfo(SyncEntity entity){
EntitySyncInfo info = new EntitySyncInfo();
info.className = null;
info.id = npcToId.get(entity);
info.type = EntitySyncInfo.TYPE_DELETE;
return info;
}
public void addNpc(SyncEntity entity){
EntitySyncInfo info = generateInitInfo(entity, true);
SyncMessage syncMsg = new SyncMessage();
syncMsg.setReliable(true);
syncMsg.heartbeat = heartbeat;
syncMsg.infos = new EntitySyncInfo[]{ info };
try {
server.broadcast(syncMsg);
} catch (IOException ex) {
ex.printStackTrace();
}
synchronized (npcs){
npcs.add(entity);
npcToId.put(entity, info.id);
}
}
public void removeNpc(SyncEntity entity){
EntitySyncInfo info = generateDeleteInfo(entity);
SyncMessage syncMsg = new SyncMessage();
syncMsg.setReliable(true);
syncMsg.heartbeat = heartbeat;
syncMsg.infos = new EntitySyncInfo[]{ info };
try {
server.broadcast(syncMsg);
} catch (IOException ex) {
ex.printStackTrace();
}
synchronized (npcs){
npcs.remove(entity);
npcToId.remove(entity);
}
}
@Override
public void clientConnected(Client id){
System.out.println("Server: Client connected: " + id);
SyncMessage msg = new SyncMessage();
msg.setReliable(true); // sending INIT information, has to be reliable.
msg.heartbeat = heartbeat;
EntitySyncInfo[] infos = new EntitySyncInfo[npcs.size()];
msg.infos = infos;
synchronized (npcs){
for (int i = 0; i < npcs.size(); i++){
SyncEntity entity = npcs.get(i);
EntitySyncInfo info = generateInitInfo(entity, false);
infos[i] = info;
}
try {
id.send(msg);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
@Override
public void clientDisconnected(Client id){
System.out.println("Server: Client disconnected: " + id);
}
private void sendDelayedMessages(){
ArrayList<Long> removeList = new ArrayList<Long>();
for (Map.Entry<Long, SyncMessage> entry : latencyQueue.entrySet()){
if (entry.getKey() > System.currentTimeMillis())
continue;
removeList.add(entry.getKey());
if (packetDropRate > FastMath.nextRandomFloat())
continue;
for (Client id : server.getConnectors()){
try {
id.send(entry.getValue());
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
for (Long removeEntry : removeList)
latencyQueue.remove(removeEntry);
}
public void update(float tpf){
if (latencyQueue != null)
sendDelayedMessages();
if (npcs.size() == 0)
return;
time += tpf;
if (time < updateRate){
return;
}else{
time = 0;
}
SyncMessage msg = new SyncMessage();
msg.setReliable(false); // Purely SYNC message, reliability not needed
msg.heartbeat = heartbeat;
synchronized (npcs){
EntitySyncInfo[] infos = new EntitySyncInfo[npcs.size()];
msg.infos = infos;
for (int i = 0; i < npcs.size(); i++){
SyncEntity entity = npcs.get(i);
EntitySyncInfo info = generateSyncInfo(entity);
entity.onLocalUpdate();
infos[i] = info;
}
}
if (latencyQueue != null){
long latencyTime = (long) (latency + (FastMath.nextRandomFloat()-0.5f) * latency);
long timeToSend = System.currentTimeMillis() + latencyTime;
latencyQueue.put(timeToSend, msg);
}else{
for (Client id : server.getConnectors()){
try {
id.send(msg); // unreliable
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
heartbeat++;
if (heartbeat < 0){
// overflow detected
heartbeat = 0;
}
}
}
|