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 */
017package net.tridentsdk.server.concurrent;
018
019import net.tridentsdk.server.TridentServer;
020import net.tridentsdk.util.TridentLogger;
021
022import javax.annotation.concurrent.GuardedBy;
023import java.util.LinkedList;
024import java.util.Queue;
025import java.util.concurrent.ConcurrentLinkedQueue;
026import java.util.concurrent.CountDownLatch;
027import java.util.concurrent.TimeUnit;
028import java.util.concurrent.atomic.LongAdder;
029import java.util.concurrent.locks.LockSupport;
030
031/**
032 * Synchronizes plugin calls with the ticking thread
033 *
034 * <p>This class is necessary because plugins that are not following the Trident threading model can mutate and break
035 * the state consistency as described by the model. This is not good because plugins should not be expected to follow
036 * the threading model.</p>
037 *
038 * <p>A demonstration of the state consistency being broken by conflicting thread-models:
039 * <pre>{@code
040 *      WeatherConditions cnd = world.weather();
041 *      if (!cnd.isRaining()) {    // If it is not raining
042 *          cnd.setRaining(true);  // start raining
043 *      }
044 *
045 *      // Should return true!
046 *      boolean stillRaining = cnd.isRaining();
047 * }</pre>
048 * The {@code stillRaining} variable can return false:
049 * <pre>
050 *      World Tick Thread ---> raining == false -----------------> raining = !true -> raining == false
051 *      Plugin Thread -----> raining == false -> raining = true -----------------------> stillRaining == false
052 * </pre>
053 * Not only do we not provide a set raining method, this class will be used to hold off plugin notification of server
054 * events until the entire tick has been processed.
055 * </p>
056 *
057 * <p>Unless you want to severely mess up the server, do not call any methods in this class unless:
058 * <ul>
059 *     <li>You know what you are doing (unlikely)</li>
060 *     <li>Developing Trident (in which case you need to check with Pierre to ensure you're doing it right)</li>
061 * </ul></p>
062 *
063 * @author The TridentSDK Team
064 */
065public final class TickSync {
066    private TickSync() {
067    }
068
069    public static volatile boolean DEBUG = false;
070
071    private static final Queue<String> expect = new ConcurrentLinkedQueue<>();
072    private static final Queue<String> completed = new ConcurrentLinkedQueue<>();
073    private static final LongAdder expected = new LongAdder();
074    private static final LongAdder complete = new LongAdder();
075
076    private static volatile CountDownLatch latch = new CountDownLatch(1);
077
078    @GuardedBy("this")
079    private static final Queue<Runnable> pluginTasks = new LinkedList<>();
080
081    /**
082     * Increments the expected updates counter
083     */
084    public static void increment(String s) {
085        expected.increment();
086        if (DEBUG) {
087            expect.add(s + " T: " + Thread.currentThread().getName());
088        }
089    }
090
091    /**
092     * Records that an update has occurred
093     * <p>
094     * <p>Signals the main thread to sync method to continue if the expected and update counters match</p>
095     */
096    public static void complete(String s) {
097        complete.increment();
098        if (DEBUG) {
099            completed.add(s + " T: " + Thread.currentThread().getName());
100        }
101
102        if (canProceed()) {
103            latch.countDown();
104        }
105    }
106
107    /**
108     * Tests to see if the expected and update counters match
109     *
110     * @return {@code true} to indicate that the ticking can proceed
111     */
112    public static boolean canProceed() {
113        return expected.sum() == complete.sum();
114    }
115
116    /**
117     * Blocks the thread until this method is called again by a {@link #complete(String)} method
118     */
119    // TODO HOT CONCURRENT METHOD NOT INLINEABLE: TOO LARGE
120    // 215 bytes
121    public static void awaitSync() {
122        boolean b = canProceed();
123        if (b) return;
124
125        try {
126            if (!latch.await(200, TimeUnit.MILLISECONDS)) {
127                TridentLogger.get().warn("Lost tick sync: complete-" + complete.sum() + " needed-" + expected.sum() + " proceed-" + b);
128                if (DEBUG) {
129                    TridentLogger.get().warn("");
130                    TridentLogger.get().warn("===== PRINTING COMPLETED TASKS =====");
131                    completed.forEach(s -> TridentLogger.get().warn(s));
132                    TridentLogger.get().warn("===== END COMPLETED TASKS =====");
133                    TridentLogger.get().warn("");
134                    TridentLogger.get().warn("===== PRINTING NEEDED TASKS =====");
135                    expect.forEach(s -> TridentLogger.get().warn(s));
136                    TridentLogger.get().warn("===== END NEEDED TASKS =====");
137                    TridentLogger.get().warn("AVG TICK TIME: " + TridentServer.instance().mainThread().getAverageTickLength() + " ms");
138                } else {
139                    TridentLogger.get().warn("Enable debug to see extra information");
140                }
141            }
142        } catch (InterruptedException e) {
143            e.printStackTrace();
144        }
145    }
146
147    /**
148     * Resets the counters and the blocking mechanisms for the next tick iteration
149     */
150    // TODO HOT CONCURRENT METHOD NOT INLINEABLE: TOO LARGE
151    // 40 bytes
152    public static void reset() {
153        expected.reset();
154        complete.reset();
155        expect.clear();
156        completed.clear();
157        latch = new CountDownLatch(1);
158    }
159
160    /**
161     * Synchronizes the task for later execution once the tick completes
162     *
163     * @param pluginTask the task
164     */
165    // TODO HOT CONCURRENT METHOD NOT INLINEABLE: MAIN THREAD METHOD TOO LARGE
166    // ?? bytes
167    public static void sync(Runnable pluginTask) {
168        synchronized (TickSync.class) {
169            pluginTasks.add(pluginTask);
170        }
171
172        if (canProceed()) {
173            LockSupport.unpark(TridentServer.instance().mainThread());
174        }
175    }
176
177    /**
178     * Waits for a task to become available, or blocks until waitNanos has elapsed, in which case {@code null}
179     * will be returned
180     *
181     * @param waitNanos the nanos to wait for a task
182     * @return a task, or {@code null} if there were none
183     * @throws InterruptedException if the current thread was interrupted in waiting for a task
184     */
185    // TODO HOT CONCURRENT METHOD NOT INLINEABLE: TOO LARGE
186    // 48 bytes
187    public static Runnable waitForTask(long waitNanos) throws InterruptedException {
188        synchronized (TickSync.class) {
189            Runnable task;
190            if ((task = pluginTasks.poll()) == null) {
191                LockSupport.parkNanos(waitNanos);
192                task = pluginTasks.poll();
193            }
194
195            return task;
196        }
197    }
198
199    /**
200     * Obtains the next task in the queue
201     *
202     * @return the next task, or {@code null} if there were none
203     */
204    public static Runnable next() {
205        synchronized (TickSync.class) {
206            return pluginTasks.poll();
207        }
208    }
209
210    /**
211     * Obtains the tasks left in the queue
212     *
213     * @return the amount of tasks left
214     */
215    public static int left() {
216        synchronized (TickSync.class) {
217            return pluginTasks.size();
218        }
219    }
220}