package nallar.patched.server;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.relauncher.Side;
import nallar.exception.ConcurrencyError;
import nallar.insecurity.InsecurityManager;
import nallar.tickthreading.Log;
import nallar.tickthreading.minecraft.DeadLockDetector;
import nallar.tickthreading.minecraft.ThreadManager;
import nallar.tickthreading.minecraft.TickThreading;
import nallar.tickthreading.minecraft.profiling.EntityTickProfiler;
import nallar.tickthreading.minecraft.profiling.Timings;
import nallar.tickthreading.patcher.Declare;
import nallar.tickthreading.util.EnvironmentInfo;
import nallar.unsafe.UnsafeUtil;
import net.minecraft.crash.CrashReport;
import net.minecraft.entity.Entity;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.Packet4UpdateTime;
import net.minecraft.profiler.Profiler;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.gui.IUpdatePlayerListBox;
import net.minecraft.util.ReportedException;
import net.minecraft.world.MinecraftException;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraftforge.common.Configuration;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import java.io.*;
import java.text.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.logging.*;
public abstract class PatchMinecraftServer extends MinecraftServer {
private ThreadManager threadManager;
private static float tickTime = 0;
private AtomicInteger currentWorld;
private Integer[] dimensionIdsToTick;
private Runnable tickRunnable;
@Declare
public static int currentTick_;
private static int TARGET_TPS;
private static int TARGET_TICK_TIME;
private static double currentTPS = 0;
private Map<Integer, Integer> exceptionCount;
private Map<String, long[]> worldTickLengths;
@Declare
public List<WorldServer> worlds_;
public static java.util.ArrayList playersToCheckWorld = new ArrayList();
@Declare
public final java.util.concurrent.atomic.AtomicInteger currentlySaving_ = null;
@Declare
public static java.util.Set<net.minecraftforge.common.Configuration> toSaveConfigurationSet_;
@Declare
public static final java.util.concurrent.ConcurrentLinkedQueue<Runnable> runQueue = new ConcurrentLinkedQueue<Runnable>();
public void construct() {
currentlySaving = new AtomicInteger();
exceptionCount = new HashMap<Integer, Integer>();
worldTickLengths = new ConcurrentHashMap<String, long[]>();
}
public static void staticConstruct() {
setTargetTPS(20);
}
public PatchMinecraftServer(File par1File) {
super(par1File);
}
@Declare
public static void addPlayerToCheckWorld(net.minecraft.entity.player.EntityPlayerMP entityPlayerMP) {
ArrayList<EntityPlayerMP> playersToCheckWorld_ = playersToCheckWorld;
synchronized (playersToCheckWorld_) {
playersToCheckWorld_.add(entityPlayerMP);
}
}
@Override
public void initiateShutdown() {
if (!serverRunning) {
return;
}
InsecurityManager.flushLogs();
this.serverRunning = false;
}
@Override
@Declare
public long[] getTickTimes(WorldServer w) {
return worldTickLengths.get(w.getName());
}
@Declare
public static void setTargetTPS(int targetTPS) {
assert targetTPS > 0 : "Target TPS must be greater than 0";
TARGET_TPS = targetTPS;
TARGET_TICK_TIME = 1000000000 / TARGET_TPS;
}
@Override
@Declare
public int getId(WorldServer world) {
if (worlds == null) {
for (int i = 0; i < worldServers.length; i++) {
if (worldServers[i] == world) {
return i;
}
}
} else {
for (int i = 0; i < worlds.size(); i++) {
if (worlds.get(i) == world) {
return i;
}
}
}
return Integer.MIN_VALUE;
}
@Override
public void run() {
//noinspection ThrowableInstanceNeverThrown
new ConcurrencyError("Just loading some exception classes.");
try {
InsecurityManager.init();
} catch (Throwable t) {
System.err.println("Failed to set up Security Manager. This is probably not a huge problem - but it could indicate classloading issues.");
}
toSaveConfigurationSet = new HashSet<Configuration>();
boolean isCrash = false;
try {
currentTick = (int) (System.currentTimeMillis() / 50);
if (this.startServer()) {
FMLLog.fine("calling handleServerStarted()");
FMLCommonHandler.instance().handleServerStarted();
FMLCommonHandler.instance().onWorldLoadTick(worlds == null ? worldServers : worlds.toArray(new WorldServer[worlds.size()]));
Set<Configuration> toSaveConfigurationSet = MinecraftServer.toSaveConfigurationSet;
MinecraftServer.toSaveConfigurationSet = null;
for (Configuration configuration : toSaveConfigurationSet) {
configuration.save();
}
this.serverIsRunning = true;
long lastTime = 0L;
double currentMaxTPS = 0;
while (this.serverRunning) {
long curTime = System.nanoTime();
long wait = (TARGET_TICK_TIME - (curTime - lastTime)) / 1000000;
if (wait > 0 && currentMaxTPS > TARGET_TPS) {
Thread.sleep(wait);
continue;
}
lastTime = curTime;
++currentTick;
++tickCounter;
try {
this.tick();
} catch (Exception e) {
Log.severe("Exception in main tick loop", e);
}
currentMaxTPS = TARGET_TICK_TIME * TARGET_TPS / tickTime;
currentTPS = currentMaxTPS > TARGET_TPS ? TARGET_TPS : currentMaxTPS;
}
try {
FMLCommonHandler.instance().handleServerStopping();
} catch (Throwable t) {
Log.severe("Exception occurred while stopping the server", t);
}
} else {
FMLLog.severe("startServer() failed.");
this.finalTick(null);
}
} catch (Throwable throwable) {
isCrash = true;
try {
if (serverRunning && serverIsRunning) {
DeadLockDetector.sendChatSafely("The server has crashed due to an unexpected exception during the main tick loop: " + throwable.getClass().getSimpleName());
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
}
} catch (Throwable ignored) {
}
FMLLog.log(Level.SEVERE, throwable, "Encountered an unexpected exception" + throwable.getClass().getSimpleName());
CrashReport crashReport;
if (throwable instanceof ReportedException) {
crashReport = this.addServerInfoToCrashReport(((ReportedException) throwable).getCrashReport());
} else {
crashReport = this.addServerInfoToCrashReport(new CrashReport("Exception in server tick loop", throwable));
}
File var3 = new File(new File(this.getDataDirectory(), "crash-reports"), "crash-" + (new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss")).format(new Date()) + "-server.txt");
if (crashReport.saveToFile(var3, getLogAgent())) {
getLogAgent().logSevere("This crash report has been saved to: " + var3.getAbsolutePath());
} else {
getLogAgent().logSevere("We were unable to save this crash report to disk.");
}
if (!TickThreading.instance.exitOnDeadlock) {
DeadLockDetector.tickAhead(999999);
try {
Thread.sleep(100L);
} catch (InterruptedException ignored) {
}
try {
saveEverything(true);
} catch (Throwable t) {
Log.severe("Failed to perform save after crash", t);
}
this.finalTick(crashReport);
}
} finally {
InsecurityManager.flushLogs();
try {
saveEverything(true);
} catch (Throwable t) {
Log.severe("Failed to perform shutdown save.", t);
}
Log.info("Stopping the server. serverRunning: " + serverRunning + ", serverIsRunning: " + serverIsRunning + ", is crash: " + isCrash);
this.getLogAgent().logInfo("Stopping server");
try {
this.stopServer();
this.serverStopped = true;
} catch (Throwable throwable) {
FMLLog.log(Level.SEVERE, throwable, "Exception while attempting to stop the server");
} finally {
try {
DeadLockDetector.checkForLeakedThreadManagers();
} finally {
InsecurityManager.flushLogs();
this.systemExitNow();
}
}
}
}
@Override
public String getServerModName() {
return "tickthreading,mcpc,spigot,craftbukkit,forge,fml";
}
@Override
public void tick() {
long startTime = System.nanoTime();
EntityItem.resetLast();
TickThreading.recentSpawnedItems = 0;
DeadLockDetector.tick(startTime);
Timings.tick();
EntityTickProfiler.ENTITY_TICK_PROFILER.tick();
if (this.startProfiling) {
this.startProfiling = false;
theProfiler.profilingEnabled = true;
theProfiler.clearProfiling();
}
theProfiler.startSection("root");
theProfiler.startSection("forgePreServerTick");
FMLCommonHandler.instance().rescheduleTicks(Side.SERVER);
FMLCommonHandler.instance().onPreServerTick();
theProfiler.endSection();
this.updateTimeLightAndEntities();
int ticks = this.tickCounter;
if (TickThreading.checkSaveInterval(ticks)) {
theProfiler.startSection("save");
this.serverConfigManager.saveAllPlayerData();
theProfiler.endSection();
}
theProfiler.startSection("tallying");
this.sentPacketCountArray[ticks % 100] = Packet.sentID - this.lastSentPacketID;
this.lastSentPacketID = Packet.sentID;
this.sentPacketSizeArray[ticks % 100] = Packet.sentSize - this.lastSentPacketSize;
this.lastSentPacketSize = Packet.sentSize;
this.receivedPacketCountArray[ticks % 100] = Packet.receivedID - this.lastReceivedID;
this.lastReceivedID = Packet.receivedID;
this.receivedPacketSizeArray[ticks % 100] = Packet.receivedSize - this.lastReceivedSize;
this.lastReceivedSize = Packet.receivedSize;
theProfiler.endStartSection("snooper");
if (!this.usageSnooper.isSnooperRunning() && ticks > 100) {
this.usageSnooper.startSnooper();
}
if (ticks % 6000 == 0) {
try {
this.usageSnooper.addMemoryStatsToSnooper();
} catch (NullPointerException ignored) {
}
}
if (ticks % 1200 == 0) {
EnvironmentInfo.checkOpenFileHandles();
}
theProfiler.endStartSection("forgePostServerTick");
FMLCommonHandler.instance().onPostServerTick();
ArrayList<EntityPlayerMP> playersToCheckWorld_ = playersToCheckWorld;
if (!playersToCheckWorld_.isEmpty()) {
synchronized (playersToCheckWorld_) {
for (EntityPlayerMP entityPlayerMP : playersToCheckWorld_) {
World world = entityPlayerMP.worldObj;
List<Entity> entityList = world.loadedEntityList;
synchronized (entityList) {
// No contains check, handled by TickManager, this list is an instance of LoadedEntityList.
entityList.add(entityPlayerMP);
}
if (!world.playerEntities.contains(entityPlayerMP)) {
world.playerEntities.add(entityPlayerMP);
}
}
playersToCheckWorld_.clear();
}
}
theProfiler.endSection();
theProfiler.endSection();
tickTime = tickTime * 0.98f + ((this.tickTimeArray[ticks % 100] = System.nanoTime() - startTime) * 0.02f);
}
@Override
public void updateTimeLightAndEntities() {
final Profiler profiler = theProfiler;
profiler.startSection("levels");
runQueuedTasks();
Integer[] dimensionIdsToTick = this.dimensionIdsToTick = DimensionManager.getIDs();
if (threadManager == null) {
threadManager = new ThreadManager(8, "World Tick");
currentWorld = new AtomicInteger(0);
tickRunnable = new TickRunnable(this);
}
currentWorld.set(0);
boolean concurrentTicking = tickCounter >= 100 && !profiler.profilingEnabled;
if (concurrentTicking) {
int count = threadManager.size();
if (count < dimensionIdsToTick.length) {
count = dimensionIdsToTick.length;
}
for (int i = 0; i < count; i++) {
threadManager.run(tickRunnable);
}
} else {
// Gregtech leaks the first world which is ticked. If we tick overworld first, no leak. Only applies to first tick on server start.
Integer[] dimensionIdsToTickOrdered = new Integer[dimensionIdsToTick.length];
int i = 0;
for (int d : dimensionIdsToTick) {
if (d == 0) {
dimensionIdsToTickOrdered[i++] = 0;
break;
}
}
for (int d : dimensionIdsToTick) {
if (d != 0) {
dimensionIdsToTickOrdered[i++] = d;
}
}
this.dimensionIdsToTick = dimensionIdsToTickOrdered;
if (tickCounter == 1) {
Log.checkWorlds();
}
doWorldTick();
}
profiler.endStartSection("players");
this.serverConfigManager.sendPlayerInfoToAllPlayers();
profiler.endStartSection("tickables");
for (IUpdatePlayerListBox tickable : (Iterable<IUpdatePlayerListBox>) this.tickables) {
tickable.update();
}
if (concurrentTicking) {
threadManager.waitForCompletion();
}
profiler.endStartSection("connection");
this.getNetworkThread().networkTick();
profiler.endStartSection("dim_unloading");
DimensionManager.unloadWorlds(worldTickTimes);
profiler.endSection();
}
private void runQueuedTasks() {
spigotTLETick();
for (Runnable runnable = runQueue.poll(); runnable != null; runnable = runQueue.poll()) {
runnable.run();
}
}
private void spigotTLETick() {
// Replaced in patcher
}
@Declare
public static float getTickTime() {
return tickTime;
}
@Declare
public static double getTPS() {
return currentTPS;
}
@Declare
public static double getTargetTickTime() {
return TARGET_TICK_TIME;
}
@Declare
public static double getTargetTPS() {
return TARGET_TPS;
}
@Override
@Declare
public void doWorldTick() {
final Profiler profiler = this.theProfiler;
int i;
while ((i = currentWorld.getAndIncrement()) < dimensionIdsToTick.length) {
int id = dimensionIdsToTick[i];
long var2 = System.nanoTime();
WorldServer world = DimensionManager.getWorld(id);
String name = world.getName();
if (!world.loadEventFired) {
Log.severe("Not sent WorldEvent.Load for loaded world " + name + ", sending it now.");
MinecraftForge.EVENT_BUS.post(new WorldEvent.Load(world));
}
if (world.getDimension() != id) {
Log.severe("World " + world.getName() + " exists in DimensionManager with an apparently incorrect dimension ID of " + id);
continue;
}
try {
profiler.startSection(world.getWorldInfo().getWorldName());
profiler.startSection("pools");
world.getWorldVec3Pool().clear();
profiler.endSection();
int tickCount = world.tickCount;
if (tickCount % 30 == 0) {
profiler.startSection("timeSync");
long totalTime = world.getTotalWorldTime();
boolean doDaylightCycle = world.getGameRules().getGameRuleBooleanValue("doDaylightCycle");
for (EntityPlayerMP entityPlayerMP : (Iterable<EntityPlayerMP>) world.playerEntities) {
entityPlayerMP.playerNetServerHandler.sendPacketToPlayer(new Packet4UpdateTime(totalTime, entityPlayerMP.getPlayerTime(), doDaylightCycle));
}
profiler.endSection();
}
profiler.startSection("forgeTick");
FMLCommonHandler.instance().onPreWorldTick(world);
profiler.endStartSection("worldTick");
world.tick();
profiler.endStartSection("entityTick");
world.updateEntities();
profiler.endStartSection("postForgeTick");
FMLCommonHandler.instance().onPostWorldTick(world);
profiler.endSection();
profiler.startSection("tracker");
world.getEntityTracker().updateTrackedEntities();
profiler.endSection();
if (tickCount % 14 == 0) {
exceptionCount.put(id, 0);
}
if (TickThreading.checkSaveInterval(tickCount)) {
theProfiler.startSection("save");
try {
currentlySaving.getAndIncrement();
long st = 0;
boolean enabled = Timings.enabled;
if (enabled) {
st = System.nanoTime();
}
if (world.saveTickCount++ % 20 == 0) {
world.saveAllChunks(false, null);
} else {
if (world.theChunkProviderServer.canSave()) {
world.theChunkProviderServer.saveChunks(false, null);
}
}
if (enabled) {
Timings.record("World/save", System.nanoTime() - st);
}
} catch (MinecraftException e) {
throw UnsafeUtil.throwIgnoreChecked(e);
} finally {
currentlySaving.getAndDecrement();
}
theProfiler.endSection();
}
profiler.endSection();
long[] tickTimes = worldTickLengths.get(name);
if (tickTimes == null) {
tickTimes = new long[100];
worldTickLengths.put(name, tickTimes);
worldTickTimes.put(id, tickTimes);
}
tickTimes[this.tickCounter % 100] = System.nanoTime() - var2;
} catch (Throwable t) {
Log.severe("Exception ticking world " + Log.name(world), t);
Integer c = exceptionCount.get(id);
if (c == null) {
c = 0;
}
c++;
if (c >= 10) {
if (serverRunning) {
DeadLockDetector.sendChatSafely("The world " + Log.name(world) + " has become unstable, and the server will now stop.");
Log.severe(Log.name(world) + " has become unstable, stopping.");
this.initiateShutdown();
}
}
exceptionCount.put(id, c);
}
}
}
@Override
@Declare
public void saveEverything() {
saveEverything(false);
}
@Override
@Declare
public void saveEverything(boolean force) {
if (force || (this.isServerRunning() && currentlySaving.get() == 0)) {
currentlySaving.getAndIncrement();
try {
Log.info("Saving all player data.");
this.serverConfigManager.saveAllPlayerData();
Log.info("Done saving player data, now saving " + DimensionManager.getWorlds().length + " worlds.");
if (worlds == null) {
for (WorldServer world : this.worldServers) {
world.canNotSave = false;
try {
world.saveAllChunks(true, null);
} catch (MinecraftException e) {
UnsafeUtil.throwIgnoreChecked(e);
}
world.theChunkProviderServer.saveChunks(true, null);
}
} else {
for (WorldServer world : worlds) {
world.canNotSave = false;
try {
world.saveAllChunks(true, null);
} catch (MinecraftException e) {
UnsafeUtil.throwIgnoreChecked(e);
}
world.theChunkProviderServer.saveChunks(true, null);
}
}
Log.info("World save completed, flushing world data to disk.");
if (worlds == null) {
for (WorldServer world : this.worldServers) {
world.flush();
}
} else {
for (WorldServer world : worlds) {
world.flush();
}
}
Log.info("Flushed world data to disk.");
} finally {
currentlySaving.getAndDecrement();
}
} else {
Log.severe("Server is already saving or crashed while saving - not attempting to save.");
}
}
public static class TickRunnable implements Runnable {
final MinecraftServer minecraftServer;
public TickRunnable(MinecraftServer minecraftServer) {
this.minecraftServer = minecraftServer;
}
@Override
public void run() {
minecraftServer.doWorldTick();
}
}
}