replace client ticks with a timer

Prevents DH loading issues when MC ticks are paused
This commit is contained in:
James Seibel
2025-12-13 11:19:33 -06:00
parent ed5aeb8951
commit 5be5c5a5bc
5 changed files with 30 additions and 168 deletions
@@ -318,35 +318,6 @@ public class ClientApi
//============//
// clint tick //
//============//
@Deprecated
public void clientTickEvent()
{
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
profiler.push("DH-ClientTick");
try
{
IDhClientWorld clientWorld = SharedApi.tryGetDhClientWorld();
if (clientWorld != null)
{
clientWorld.clientTick();
}
}
catch (Exception e)
{
// handle errors here to prevent blowing up a mixin or API up stream
LOGGER.error("Unexpected error in ClientApi.clientTickEvent(), error: "+e.getMessage(), e);
}
profiler.pop();
}
//============//
// networking //
//============//
@@ -1,102 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
public class EventLoop implements AutoCloseable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private final boolean PAUSE_ON_ERROR = ModInfo.IS_DEV_BUILD;
private final ExecutorService executorService;
private final Runnable runnable;
/** the future related to the given runnable */
private CompletableFuture<Void> runnableFuture;
private boolean isRunning = true;
public EventLoop(ExecutorService executorService, Runnable runnable)
{
this.executorService = executorService;
this.runnable = runnable;
}
public void tick()
{
if (runnableFuture != null && runnableFuture.isDone())
{
try
{
runnableFuture.join();
}
catch (CompletionException ce)
{
LOGGER.error("Uncaught exception in event loop", ce.getCause());
if (PAUSE_ON_ERROR)
{
isRunning = false;
}
}
catch (Exception e)
{
LOGGER.error("Exception in event loop", e);
if (PAUSE_ON_ERROR)
{
isRunning = false;
}
}
finally
{
runnableFuture = null;
}
}
if (runnableFuture == null && isRunning)
{
runnableFuture = CompletableFuture.runAsync(runnable, executorService);
}
}
public void close()
{
if (runnableFuture != null)
{
runnableFuture.cancel(true);
}
runnableFuture = null;
executorService.shutdown();
}
public boolean isRunning() { return runnableFuture != null && !runnableFuture.isDone(); }
}
@@ -21,27 +21,21 @@ package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.level.DhClientServerLevel;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.EventLoop;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLevel> implements IDhClientWorld
{
private final Set<DhClientServerLevel> dhLevels = Collections.synchronizedSet(new HashSet<>());
public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client Server World Ticker", 2);
public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); //TODO: Rate-limit the loop
private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer");
@@ -53,6 +47,15 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
{
super(EWorldEnvironment.CLIENT_SERVER);
LOGGER.info("Started DhWorld of type " + this.environment);
this.clientTickTimer.scheduleAtFixedRate(new TimerTask()
{
@Override
public void run()
{
DhClientServerWorld.this.dhLevels.forEach(DhClientServerLevel::clientTick);
}
}, 0, IDhClientWorld.TICK_RATE_IN_MS);
}
@@ -136,19 +139,6 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
}
}
private void _clientTick()
{
//LOGGER.info("Client world tick with {} levels", levels.size());
this.dhLevels.forEach(DhClientServerLevel::clientTick);
}
@Override
public void clientTick()
{
//LOGGER.info("Client world tick");
this.eventLoop.tick();
}
//================//
@@ -194,8 +184,8 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
this.dhLevelByLevelWrapper.clear();
this.eventLoop.close();
LOGGER.info("Closed DhWorld of type " + this.environment);
this.clientTickTimer.cancel();
LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
}
}
@@ -24,17 +24,17 @@ import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.EventLoop;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
{
@@ -42,8 +42,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
public final ClientOnlySaveStructure saveStructure;
public final ClientNetworkState networkState = new ClientNetworkState();
public final ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client World Ticker");
public final EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick);
private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer");
@@ -59,6 +58,15 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
this.levels = new ConcurrentHashMap<>();
LOGGER.info("Started DhWorld of type " + this.environment);
this.clientTickTimer.scheduleAtFixedRate(new TimerTask()
{
@Override
public void run()
{
DhClientWorld.this.levels.values().forEach(DhClientLevel::clientTick);
}
}, 0, IDhClientWorld.TICK_RATE_IN_MS);
}
@@ -127,11 +135,6 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
}
}
private void _clientTick() { this.levels.values().forEach(DhClientLevel::clientTick); }
@Override
public void clientTick() { this.eventLoop.tick(); }
@Override
public void addDebugMenuStringsToList(List<String> messageList)
{
@@ -143,7 +146,6 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
public void close()
{
this.networkState.close();
this.dhTickerThread.shutdownNow();
ArrayList<CompletableFuture<Void>> closeFutures = new ArrayList<>();
for (DhClientLevel dhClientLevel : this.levels.values())
@@ -175,7 +177,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
}
this.levels.clear();
this.eventLoop.close();
this.clientTickTimer.cancel();
LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
}
@@ -24,7 +24,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
public interface IDhClientWorld extends IDhWorld
{
void clientTick();
/** how long in between client ticks in milliseconds */
long TICK_RATE_IN_MS = 100L;
default IDhClientLevel getOrLoadClientLevel(ILevelWrapper levelWrapper) { return (IDhClientLevel) this.getOrLoadLevel(levelWrapper); }
default IDhClientLevel getClientLevel(ILevelWrapper levelWrapper) { return (IDhClientLevel) this.getLevel(levelWrapper); }