Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(netork)!: rewrite ping logic #5106

Merged
merged 13 commits into from
Jul 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2023 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0
package org.terasology.engine.network;

import org.terasology.engine.entitySystem.entity.EntityRef;
import org.terasology.gestalt.entitysystem.component.Component;

import java.util.HashMap;
import java.util.Map;

/**
* PingStockComponent stock the ping information of one user.
* <p>
* Might be used to stock ping information and display it in future.
*/
public final class PingComponent implements Component<PingComponent> {

@Replicate
private Map<EntityRef, Long> pings = new HashMap<>();

public void setValues(Map<EntityRef, Long> values) {
pings.clear();
pings.putAll(values);
}

public Map<EntityRef, Long> getValues() {
return new HashMap<>(pings);
}

@Override
public void copyFrom(PingComponent other) {
this.setValues(other.getValues());
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2021 The Terasology Foundation
// Copyright 2023 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0
package org.terasology.engine.network;

Expand All @@ -8,7 +8,6 @@
import org.terasology.engine.entitySystem.systems.RegisterMode;
import org.terasology.engine.entitySystem.systems.RegisterSystem;
import org.terasology.engine.entitySystem.systems.UpdateSubscriberSystem;
import org.terasology.engine.logic.players.LocalPlayer;
import org.terasology.engine.network.events.DisconnectedEvent;
import org.terasology.engine.network.events.PingFromClientEvent;
import org.terasology.engine.network.events.PingFromServerEvent;
Expand All @@ -25,18 +24,21 @@
/**
* This system implement the server ping to clients on need base.
* It runs on the server, pings to all clients who subscribe this function.
*
* @see PingFromServerEvent
* @see PingFromClientEvent
* @see SubscribePingEvent
* @see UnSubscribePingEvent
*/
@RegisterSystem(RegisterMode.AUTHORITY)
public class ServerPingSystem extends BaseComponentSystem implements UpdateSubscriberSystem {

/** The interval in which pings are sent, in milliseconds. */
private static final long PING_PERIOD = 200;

@In
private EntityManager entityManager;

@In
private LocalPlayer localPlayer;

private Map<EntityRef, Instant> startMap = new HashMap<>();

private Map<EntityRef, Instant> endMap = new HashMap<>();
Expand All @@ -52,54 +54,77 @@ public void initialise() {

@Override
public void update(float delta) {
long time = Duration.between(lastPingTime, Instant.now()).toMillis();
Instant now = Instant.now();
long time = Duration.between(lastPingTime, now).toMillis();
if (time > PING_PERIOD) {

// Server ping to all clients only if there are clients who subscribe
if (entityManager.getCountOfEntitiesWith(PingSubscriberComponent.class) != 0) {
Iterable<EntityRef> clients = entityManager.getEntitiesWith(ClientComponent.class);
for (EntityRef client : clients) {
if (client.equals(localPlayer.getClientEntity())) {
continue;
}

// send ping only if client replied the last ping
Instant lastPingFromClient = endMap.get(client);
Instant lastPingToClient = startMap.get(client);
// Only happens when server doesn't receive ping back yet
if (lastPingFromClient != null && lastPingToClient != null && lastPingFromClient.isBefore(lastPingToClient)) {
continue;
}

Instant start = Instant.now();
startMap.put(client, start);
client.send(new PingFromServerEvent());
}
// only collect ping information if anybody is interested
if (entityManager.getCountOfEntitiesWith(PingComponent.class) > 0) {
startPings();
updateSubscribers();
} else {
clear();
}
lastPingTime = now;
}
}

//update ping data for all clients
for (EntityRef client : entityManager.getEntitiesWith(PingSubscriberComponent.class)) {
PingStockComponent pingStockComponent;
if (!client.hasComponent(PingStockComponent.class)) {
pingStockComponent = new PingStockComponent();
} else {
pingStockComponent = client.getComponent(PingStockComponent.class);
}
if (localPlayer != null && localPlayer.getClientEntity() != null) {
pingMap.put(localPlayer.getClientEntity(), new Long(5));
}
pingStockComponent.setValues(pingMap);
client.addOrSaveComponent(pingStockComponent);
}
/**
* Clear internal maps, for instance, when there are no more subscribers.
*/
private void clear() {
startMap.clear();
endMap.clear();
pingMap.clear();
}

lastPingTime = Instant.now();
/**
* Send a ping signal ({@link PingFromServerEvent}) from the server to all
* clients.
*
* Any entity with a {@link PingComponent} is considered as subscriber.
*
* Clients are supposed to answer with {@link PingFromClientEvent} to confirm
* the ping.
*/
private void startPings() {
for (EntityRef client : entityManager.getEntitiesWith(ClientComponent.class)) {
sendPingToClient(client);
}
}

/**
* Send a ping signal to the client.
*/
private void sendPingToClient(EntityRef client) {
Instant lastPingFromClient = endMap.get(client);
Instant lastPingToClient = startMap.get(client);
// Send ping only if the client has replied to the last ping. This happens when
// there is still a ping in-flight, that is, the server hasn't received an answer
// from this client yet.
if (lastPingFromClient != null && lastPingToClient != null
&& lastPingFromClient.isBefore(lastPingToClient)) {
return;
}

startMap.put(client, Instant.now());
client.send(new PingFromServerEvent());
}

/**
* Update the ping stock ({@link PingComponent}) on all subscribers
*/
private void updateSubscribers() {
for (EntityRef client : entityManager.getEntitiesWith(PingComponent.class)) {
client.updateComponent(PingComponent.class, pingComponent -> {
pingComponent.setValues(pingMap);
return pingComponent;
});
}
}

@ReceiveEvent(components = ClientComponent.class)
public void onPingFromClient(PingFromClientEvent event, EntityRef entity) {
Instant end = Instant.now();
endMap.put(entity, end);
endMap.put(entity, Instant.now());
updatePing(entity);
}

Expand All @@ -119,19 +144,11 @@ public void onDisconnected(DisconnectedEvent event, EntityRef entity) {

@ReceiveEvent(components = ClientComponent.class)
public void onSubscribePing(SubscribePingEvent event, EntityRef entity) {
entity.addOrSaveComponent(new PingSubscriberComponent());
entity.addOrSaveComponent(new PingComponent());
}

@ReceiveEvent(components = ClientComponent.class)
public void onUnSubscribePing(UnSubscribePingEvent event, EntityRef entity) {
entity.removeComponent(PingSubscriberComponent.class);
entity.removeComponent(PingStockComponent.class);

//if there is no pingSubscriber, then clean the map
if (entityManager.getCountOfEntitiesWith(PingSubscriberComponent.class) == 0) {
startMap.clear();
endMap.clear();
pingMap.clear();
}
entity.removeComponent(PingComponent.class);
}
}
Loading