001/* 002 * Trident - A Multithreaded Server Alternative 003 * Copyright 2014 The TridentSDK Team 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package net.tridentsdk.server.concurrent; 019 020import net.tridentsdk.config.Config; 021import net.tridentsdk.registry.Registered; 022import net.tridentsdk.server.util.ConcurrentCircularArray; 023import net.tridentsdk.server.world.TridentWorld; 024import net.tridentsdk.server.world.TridentWorldLoader; 025import net.tridentsdk.util.TridentLogger; 026import net.tridentsdk.world.World; 027 028import javax.annotation.concurrent.ThreadSafe; 029import java.nio.file.Paths; 030import java.util.Iterator; 031import java.util.concurrent.TimeUnit; 032import java.util.concurrent.atomic.AtomicInteger; 033 034/** 035 * Handles the running of the server, the "ticks" that occur 20 times a second 036 * 037 * @author The TridentSDK Team 038 */ 039@ThreadSafe 040public class MainThread extends Thread { 041 private static final boolean FINISH_TASKS_LEFT = new Config(Paths.get("server.json")) 042 .getConfigSection("performance") 043 .getBoolean("finish-tasks-left"); 044 private static final int RECENT_TICKS_KEPT = 40; 045 private static final String NAME = "Trident - Tick Thread"; 046 047 private final AtomicInteger ticksElapsed = new AtomicInteger(); 048 private final AtomicInteger notLostTicksElapsed = new AtomicInteger(); 049 private final AtomicInteger ticksToWait = new AtomicInteger(); 050 private final int ticksPerSecond; 051 private final int tickLength; 052 053 private final ConcurrentCircularArray<Integer> recentTickLength = new ConcurrentCircularArray<>(RECENT_TICKS_KEPT); 054 055 /** 056 * system.currenttimemillis() when the server's first tick happened, used to keep on schedule, subject to change 057 * when the server is running slow 058 */ 059 private long zeroBase; 060 private volatile boolean pausedTicking; 061 private volatile boolean redstoneTick; 062 063 /** 064 * Creates the MainThread runner from the amount of heartbeats the server should take per second the server runs 065 * 066 * @param ticksPerSecond the amount of heartbeats per second 067 */ 068 public MainThread(int ticksPerSecond) { 069 super(NAME); 070 this.zeroBase = System.currentTimeMillis(); 071 this.ticksPerSecond = ticksPerSecond; 072 this.tickLength = 1000 / ticksPerSecond; 073 } 074 075 public void doRun() throws InterruptedException { 076 long startTime = System.currentTimeMillis(); 077 078 this.ticksElapsed.getAndIncrement(); 079 080 // if we've paused, wait and then skip the rest 081 if (this.pausedTicking) { 082 this.calcAndWait(0); 083 return; 084 } 085 086 if (this.ticksToWait.get() > 0) { 087 this.ticksToWait.getAndDecrement(); 088 this.calcAndWait(0); 089 return; 090 } 091 092 this.notLostTicksElapsed.getAndIncrement(); 093 094 // Entities are ticked by the world 095 for (World world : TridentWorldLoader.WORLDS.values()) { 096 TickSync.increment("WORLD: " + world.name()); 097 ((TridentWorld) world).tick(); 098 } 099 100 // TODO: check the worlds to make sure they're not suffering 101 102 ((TridentTaskScheduler) Registered.tasks()).tick(); 103 104 TickSync.awaitSync(); 105 106 long time; 107 while ((time = System.currentTimeMillis() - startTime) < tickLength) { 108 Runnable next = TickSync.waitForTask(TimeUnit.NANOSECONDS.convert(time, TimeUnit.NANOSECONDS)); 109 if (next != null) { 110 Registered.plugins().executor().execute(next); 111 } 112 } 113 114 if (!FINISH_TASKS_LEFT) { 115 int left = TickSync.left(); 116 if (left > 0) { 117 TridentLogger.get().warn("Skipped " + left + " plugin task(s) this tick"); 118 } 119 } else { 120 while (TickSync.left() > 0) { 121 Runnable runnable = TickSync.next(); 122 if (runnable != null) runnable.run(); 123 } 124 } 125 126 TickSync.reset(); 127 recentTickLength.add((int) (System.currentTimeMillis() - startTime)); 128 } 129 130 @Override 131 public void run() { 132 super.run(); 133 134 while (!this.isInterrupted()) { 135 try { 136 doRun(); 137 } catch (InterruptedException e) { 138 break; 139 } 140 } 141 } 142 143 /** 144 * Interesting new feature (relative to other implementations) that would allow it to pause ticking 145 */ 146 public boolean pauseTicking() { 147 // TODO: configurable 148 this.pausedTicking = true; 149 return true; 150 } 151 152 /** 153 * Calculates how long it should wait, and waits for that amount of time 154 * 155 * @param tit the Time in Tick (tit) 156 */ 157 private void calcAndWait(int tit) { 158 this.correctTiming(); 159 160 int ttw = this.tickLength - tit; 161 162 if (ttw <= 0) { 163 return; 164 } 165 166 try { 167 Thread.sleep((long) ttw); 168 } catch (InterruptedException ex) { 169 this.interrupt(); 170 } 171 } 172 173 // TODO HOT CONCURRENT METHOD NOT INLINEABLE: TOO LARGE 174 // 54 bytes 175 private void correctTiming() { 176 long expectedTime = (long) ((this.ticksElapsed.get() - 1) * this.tickLength); 177 long actualTime = System.currentTimeMillis() - this.zeroBase; 178 if (actualTime != expectedTime) { 179 // if there is a difference of less than two milliseconds, just update zerobase to compensate and maintain 180 // accuracy 181 if (actualTime - expectedTime <= 2L) { 182 this.zeroBase += actualTime - expectedTime; 183 } 184 185 // handle more advanced divergences 186 } 187 } 188 189 /** 190 * Instead of needing to be resumed, it will instead just skip this many ticks and resume 191 * 192 * @param ticks the ticks to pause for 193 * @return whether the server allows the pausing of ticking 194 * @see MainThread#pauseTicking() 195 */ 196 public boolean pauseTicking(int ticks) { 197 this.ticksToWait.addAndGet(ticks); 198 return true; 199 } 200 201 /** 202 * Complement to MainThread#pauseTicking(), resumes the ticking of the server 203 */ 204 public void resumeTicking() { 205 this.pausedTicking = false; 206 } 207 208 @Override 209 public void interrupt() { 210 super.interrupt(); 211 } 212 213 @Override 214 public UncaughtExceptionHandler getUncaughtExceptionHandler() { 215 return super.getUncaughtExceptionHandler(); 216 } 217 218 /** 219 * Used to get the length of the average tick for the past 40 ticks 220 * 221 * <p>Does not count ticks that have been skipped via MainThread#pauseTicking()</p> 222 * 223 * @return the average length of a tick for the past 40 ticks 224 */ 225 public double getAverageTickLength() { 226 Iterator<Integer> iter = recentTickLength.iterator(); 227 228 double total = 0d; 229 while (iter.hasNext()) { 230 total += iter.next(); 231 } 232 233 return total / RECENT_TICKS_KEPT; 234 } 235 236 /** 237 * Gets the elapsed ticks 238 * 239 * @return the ticks elapsed 240 */ 241 public int getTicksElapsed() { 242 return this.ticksElapsed.get(); 243 } 244 245 /** 246 * Gets the ticks that were not lost in the time elapsed 247 * 248 * @return the ticks not lost 249 */ 250 public int getNotLostTicksElapsed() { 251 return this.notLostTicksElapsed.get(); 252 } 253}