Fix projector watcher caching.
On write didn't do what I hoped it'd do in combination with computed get.
This commit is contained in:
@@ -2,9 +2,6 @@
|
||||
|
||||
package li.cil.oc2.common.network;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.RemovalNotification;
|
||||
import li.cil.oc2.api.API;
|
||||
import li.cil.oc2.common.Config;
|
||||
import li.cil.oc2.common.blockentity.ProjectorBlockEntity;
|
||||
@@ -19,17 +16,16 @@ import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* Mostly round-robin load balancer for allowing projectors to send data to clients.
|
||||
* <p>
|
||||
@@ -63,11 +59,7 @@ public final class ProjectorLoadBalancer {
|
||||
* but then only one keeps watching it. In that case, we want to still remove the other player
|
||||
* from the projector info, but keep the info for the still watching player.
|
||||
*/
|
||||
private static final Cache<ProjectorBlockEntity, ProjectorInfo> PROJECTOR_INFO = CacheBuilder.newBuilder()
|
||||
.weakKeys()
|
||||
.expireAfterWrite(Duration.ofMillis(CACHE_EXPIRES_AFTER))
|
||||
.removalListener(ProjectorLoadBalancer::handleProjectorInfoRemoved)
|
||||
.build();
|
||||
private static final Map<ProjectorBlockEntity, ProjectorInfo> PROJECTOR_INFO = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Global byte budget for sending stuff to clients. This is filled up every tick and consumed
|
||||
@@ -84,16 +76,15 @@ public final class ProjectorLoadBalancer {
|
||||
*/
|
||||
@Nullable private static ProjectorInfo lastSender;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Updates timestamp of a player currently watching a projector.
|
||||
*/
|
||||
public static void updateWatcher(final ProjectorBlockEntity projector, final ServerPlayer player) {
|
||||
try {
|
||||
PROJECTOR_INFO.get(projector, () -> addProjectorInfo(projector))
|
||||
.players.put(player, System.currentTimeMillis());
|
||||
} catch (final ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
PROJECTOR_INFO
|
||||
.computeIfAbsent(projector, ProjectorLoadBalancer::addProjectorInfo)
|
||||
.handleWatchedBy(player);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +93,7 @@ public final class ProjectorLoadBalancer {
|
||||
* Ignored if there are no players watching the projector.
|
||||
*/
|
||||
public static void offerFrame(final ProjectorBlockEntity projector, final Supplier<ByteBuffer> messageSupplier) {
|
||||
final ProjectorInfo info = PROJECTOR_INFO.getIfPresent(projector);
|
||||
final ProjectorInfo info = PROJECTOR_INFO.get(projector);
|
||||
if (info != null) {
|
||||
info.nextFrameSupplier = messageSupplier;
|
||||
}
|
||||
@@ -114,8 +105,7 @@ public final class ProjectorLoadBalancer {
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void handleServerTick(final TickEvent.ServerTickEvent event) {
|
||||
PROJECTOR_INFO.cleanUp();
|
||||
removeExpiredPlayers();
|
||||
updateCache();
|
||||
|
||||
if (BUDGET.updateAndGet(ProjectorLoadBalancer::replenishBudget) > 0) {
|
||||
sendNextReadyPacket();
|
||||
@@ -127,9 +117,11 @@ public final class ProjectorLoadBalancer {
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void handleServerStopped(final ServerStoppedEvent event) {
|
||||
PROJECTOR_INFO.invalidateAll();
|
||||
PROJECTOR_INFO.clear();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private static int getMaxBudget() {
|
||||
// We allow over-budgeting projectors to some degree, to allow short bursts of larger frame changes.
|
||||
// Otherwise, this would be divided by twenty, since we attempt to send every tick.
|
||||
@@ -140,6 +132,19 @@ public final class ProjectorLoadBalancer {
|
||||
return Math.min(getMaxBudget(), budget + Math.max(1, Config.projectorAverageMaxBytesPerSecond / 20));
|
||||
}
|
||||
|
||||
private static void updateCache() {
|
||||
final Iterator<ProjectorInfo> iterator = PROJECTOR_INFO.values().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final ProjectorInfo info = iterator.next();
|
||||
info.removeExpiredPlayers();
|
||||
if (info.isNoLongerWatched()) {
|
||||
iterator.remove();
|
||||
|
||||
removeProjectorInfo(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ProjectorInfo addProjectorInfo(final ProjectorBlockEntity projector) {
|
||||
projector.setRequiresKeyframe(); // When first watcher starts, immediately request keyframe.
|
||||
final ProjectorInfo info = new ProjectorInfo(projector.getBlockPos());
|
||||
@@ -153,32 +158,19 @@ public final class ProjectorLoadBalancer {
|
||||
return info;
|
||||
}
|
||||
|
||||
private static void handleProjectorInfoRemoved(final RemovalNotification<ProjectorBlockEntity, ProjectorInfo> notification) {
|
||||
final ProjectorInfo info = requireNonNull(notification.getValue());
|
||||
|
||||
private static void removeProjectorInfo(final ProjectorInfo info) {
|
||||
if (lastSender == info) {
|
||||
if (lastSender.next == lastSender) {
|
||||
lastSender = null; // Last element in list, clear list.
|
||||
// Last element in list, clear list.
|
||||
lastSender = null;
|
||||
} else {
|
||||
lastSender = info.next; // Shift current entry to next.
|
||||
// Shift current entry to next.
|
||||
lastSender = info.next;
|
||||
}
|
||||
}
|
||||
|
||||
info.remove();
|
||||
}
|
||||
|
||||
private static void removeExpiredPlayers() {
|
||||
if (lastSender == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ProjectorInfo start = lastSender;
|
||||
do {
|
||||
lastSender.removeExpiredPlayers();
|
||||
lastSender = lastSender.next;
|
||||
} while (lastSender != start);
|
||||
}
|
||||
|
||||
private static void sendNextReadyPacket() {
|
||||
if (lastSender == null) {
|
||||
return;
|
||||
@@ -193,6 +185,8 @@ public final class ProjectorLoadBalancer {
|
||||
} while (lastSender != start);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Tracks info for a single projector. This class is an entry in a circular double linked list,
|
||||
* i.e. the last entry will always point back to the first entry, which makes looping over it
|
||||
@@ -225,6 +219,7 @@ public final class ProjectorLoadBalancer {
|
||||
* The current penalty, in the form of rounds in the round-robin to skip.
|
||||
*/
|
||||
private int skipCount;
|
||||
|
||||
@Nullable private Supplier<ByteBuffer> nextFrameSupplier;
|
||||
@Nullable private Future<?> runningEncode;
|
||||
|
||||
@@ -252,10 +247,18 @@ public final class ProjectorLoadBalancer {
|
||||
next = null;
|
||||
}
|
||||
|
||||
public void handleWatchedBy(final ServerPlayer player) {
|
||||
players.put(player, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public void removeExpiredPlayers() {
|
||||
players.entrySet().removeIf(entry -> System.currentTimeMillis() - entry.getValue() > CACHE_EXPIRES_AFTER);
|
||||
}
|
||||
|
||||
public boolean isNoLongerWatched() {
|
||||
return players.isEmpty();
|
||||
}
|
||||
|
||||
public boolean sendIfReady() {
|
||||
if (skipCount > 0) {
|
||||
skipCount--;
|
||||
|
||||
Reference in New Issue
Block a user