Commits
AgentLV authored and md_5 committed 75a8885d1fb
25 25 | /** |
26 26 | * The fundamental concepts for this implementation: |
27 27 | * <li>Main thread owns {@link #head} and {@link #currentTick}, but it may be read from any thread</li> |
28 28 | * <li>Main thread exclusively controls {@link #temp} and {@link #pending}. |
29 29 | * They are never to be accessed outside of the main thread; alternatives exist to prevent locking.</li> |
30 30 | * <li>{@link #head} to {@link #tail} act as a linked list/queue, with 1 consumer and infinite producers. |
31 31 | * Adding to the tail is atomic and very efficient; utility method is {@link #handle(CraftTask, long)} or {@link #addTask(CraftTask)}. </li> |
32 32 | * <li>Changing the period on a task is delicate. |
33 33 | * Any future task needs to notify waiting threads. |
34 34 | * Async tasks must be synchronized to make sure that any thread that's finishing will remove itself from {@link #runners}. |
35 - | * Another utility method is provided for this, {@link #cancelTask(CraftTask)}</li> |
35 + | * Another utility method is provided for this, {@link #cancelTask(int)}</li> |
36 36 | * <li>{@link #runners} provides a moderately up-to-date view of active tasks. |
37 37 | * If the linked head to tail set is read, all remaining tasks that were active at the time execution started will be located in runners.</li> |
38 38 | * <li>Async tasks are responsible for removing themselves from runners</li> |
39 39 | * <li>Sync tasks are only to be removed from runners on the main thread when coupled with a removal from pending and temp.</li> |
40 40 | * <li>Most of the design in this scheduler relies on queuing special tasks to perform any data changes on the main thread. |
41 41 | * When executed from inside a synchronous method, the scheduler will be updated before next execution by virtue of the frequent {@link #parsePending()} calls.</li> |
42 42 | */ |
43 43 | public class CraftScheduler implements BukkitScheduler { |
44 44 | |
45 45 | /** |
53 53 | /** |
54 54 | * Tail of a linked-list. AtomicReference only matters when adding to queue |
55 55 | */ |
56 56 | private final AtomicReference<CraftTask> tail = new AtomicReference<CraftTask>(head); |
57 57 | /** |
58 58 | * Main thread logic only |
59 59 | */ |
60 60 | private final PriorityQueue<CraftTask> pending = new PriorityQueue<CraftTask>(10, |
61 61 | new Comparator<CraftTask>() { |
62 62 | public int compare(final CraftTask o1, final CraftTask o2) { |
63 - | return (int) (o1.getNextRun() - o2.getNextRun()); |
63 + | int value = (int) (o1.getNextRun() - o2.getNextRun()); |
64 + | |
65 + | // If the tasks should run on the same tick they should be run FIFO |
66 + | return value != 0 ? value : o1.getTaskId() - o2.getTaskId(); |
64 67 | } |
65 68 | }); |
66 69 | /** |
67 70 | * Main thread logic only |
68 71 | */ |
69 72 | private final List<CraftTask> temp = new ArrayList<CraftTask>(); |
70 73 | /** |
71 74 | * These are tasks that are currently active. It's provided for 'viewing' the current state. |
72 75 | */ |
73 76 | private final ConcurrentHashMap<Integer, CraftTask> runners = new ConcurrentHashMap<Integer, CraftTask>(); |