3232import org .bukkit .event .Event ;
3333import org .eclipse .jdt .annotation .Nullable ;
3434
35- import java .util .ArrayList ;
36- import java .util .Collections ;
3735import java .util .Iterator ;
38- import java .util .List ;
3936import java .util .Map ;
4037import java .util .Map .Entry ;
38+ import java .util .PriorityQueue ;
4139import java .util .concurrent .ConcurrentHashMap ;
4240
4341public class EvtAtTime extends SkriptEvent implements Comparable <EvtAtTime > {
@@ -54,20 +52,27 @@ public class EvtAtTime extends SkriptEvent implements Comparable<EvtAtTime> {
5452 private static final Map <World , EvtAtInfo > TRIGGERS = new ConcurrentHashMap <>();
5553
5654 private static final class EvtAtInfo {
57- private int lastTick ; // as Bukkit's scheduler is inconsistent this saves the exact tick when the events were last checked
58- private int currentIndex ;
59- private final List <EvtAtTime > instances = new ArrayList <>();
55+ /**
56+ * Stores the last world time that this object's instances were checked.
57+ */
58+ private int lastCheckedTime ;
59+
60+ /**
61+ * A list of all {@link EvtAtTime}s in the world this info object is responsible for.
62+ * Sorted by the time they're listening for in increasing order.
63+ */
64+ private final PriorityQueue <EvtAtTime > instances = new PriorityQueue <>(EvtAtTime ::compareTo );
6065 }
6166
62- private int tick ;
67+ private int time ;
6368
6469 @ SuppressWarnings ("NotNullFieldNotInitialized" )
6570 private World [] worlds ;
6671
6772 @ Override
6873 @ SuppressWarnings ("unchecked" )
6974 public boolean init (Literal <?>[] args , int matchedPattern , ParseResult parseResult ) {
70- tick = ((Literal <Time >) args [0 ]).getSingle ().getTicks ();
75+ time = ((Literal <Time >) args [0 ]).getSingle ().getTicks ();
7176 worlds = args [1 ] == null ? Bukkit .getWorlds ().toArray (new World [0 ]) : ((Literal <World >) args [1 ]).getAll ();
7277 return true ;
7378 }
@@ -78,10 +83,9 @@ public boolean postLoad() {
7883 EvtAtInfo info = TRIGGERS .get (world );
7984 if (info == null ) {
8085 TRIGGERS .put (world , info = new EvtAtInfo ());
81- info .lastTick = (int ) world .getTime () - 1 ;
86+ info .lastCheckedTime = (int ) world .getTime () - 1 ;
8287 }
8388 info .instances .add (this );
84- Collections .sort (info .instances );
8589 }
8690 registerListener ();
8791 return true ;
@@ -93,13 +97,11 @@ public void unload() {
9397 while (iterator .hasNext ()) {
9498 EvtAtInfo info = iterator .next ();
9599 info .instances .remove (this );
96- if (info .currentIndex >= info .instances .size ())
97- info .currentIndex --;
98100 if (info .instances .isEmpty ())
99101 iterator .remove ();
100102 }
101103
102- if (taskID = = -1 && TRIGGERS .isEmpty ()) { // Unregister Bukkit listener if possible
104+ if (taskID ! = -1 && TRIGGERS .isEmpty ()) { // Unregister Bukkit listener if possible
103105 Bukkit .getScheduler ().cancelTask (taskID );
104106 taskID = -1 ;
105107 }
@@ -120,59 +122,64 @@ public boolean isEventPrioritySupported() {
120122 private static void registerListener () {
121123 if (taskID != -1 )
122124 return ;
125+ // For each world:
126+ // check each instance in order until triggerTime > (worldTime + period)
123127 taskID = Bukkit .getScheduler ().scheduleSyncRepeatingTask (Skript .getInstance (), () -> {
124128 for (Entry <World , EvtAtInfo > entry : TRIGGERS .entrySet ()) {
125129 EvtAtInfo info = entry .getValue ();
126- int tick = (int ) entry .getKey ().getTime ();
130+ int worldTime = (int ) entry .getKey ().getTime ();
127131
128132 // Stupid Bukkit scheduler
129- if (info .lastTick == tick )
133+ // TODO: is this really necessary?
134+ if (info .lastCheckedTime == worldTime )
130135 continue ;
131136
132137 // Check if time changed, e.g. by a command or plugin
133- if (info .lastTick + CHECK_PERIOD * 2 < tick || info .lastTick > tick && info .lastTick - 24000 + CHECK_PERIOD * 2 < tick )
134- info .lastTick = Math2 .mod (tick - CHECK_PERIOD , 24000 );
138+ // if the info was last checked more than 2 cycles ago
139+ // then reset the last checked time to the period just before now.
140+ if (info .lastCheckedTime + CHECK_PERIOD * 2 < worldTime || (info .lastCheckedTime > worldTime && info .lastCheckedTime - 24000 + CHECK_PERIOD * 2 < worldTime ))
141+ info .lastCheckedTime = Math2 .mod (worldTime - CHECK_PERIOD , 24000 );
135142
136- boolean midnight = info .lastTick > tick ; // actually 6:00
143+ // if we rolled over from 23999 to 0, subtract 24000 from last checked
144+ boolean midnight = info .lastCheckedTime > worldTime ; // actually 6:00
137145 if (midnight )
138- info .lastTick -= 24000 ;
146+ info .lastCheckedTime -= 24000 ;
139147
140- int startIndex = info . currentIndex ;
141- while ( true ) {
142- EvtAtTime next = info . instances . get ( info . currentIndex );
143- int nextTick = midnight && next . tick > 12000 ? next . tick - 24000 : next . tick ;
148+ // loop instances from earliest to latest
149+ for ( EvtAtTime event : info . instances ) {
150+ // if we just rolled over, the last checked time will be x - 24000, so we need to do the same to the event time
151+ int eventTime = midnight && event . time > 12000 ? event . time - 24000 : event . time ;
144152
145- if (!(info .lastTick < nextTick && nextTick <= tick ))
153+ // if the event time is in the future, we don't need to check any more events.
154+ if (eventTime > worldTime )
146155 break ;
147156
148- // Execute our event
149- ScheduledEvent event = new ScheduledEvent (entry .getKey ());
150- SkriptEventHandler .logEventStart (event );
151- SkriptEventHandler .logTriggerEnd (next .trigger );
152- next .trigger .execute (event );
153- SkriptEventHandler .logTriggerEnd (next .trigger );
157+ // if we should have already caught this time previously, check the next one
158+ if (eventTime <= info .lastCheckedTime )
159+ continue ;
160+
161+ // anything that makes it here must satisfy lastCheckedTime < eventTime <= worldTime
162+ // and therefore should trigger this event.
163+ ScheduledEvent scheduledEvent = new ScheduledEvent (entry .getKey ());
164+ SkriptEventHandler .logEventStart (scheduledEvent );
165+ SkriptEventHandler .logTriggerEnd (event .trigger );
166+ event .trigger .execute (scheduledEvent );
167+ SkriptEventHandler .logTriggerEnd (event .trigger );
154168 SkriptEventHandler .logEventEnd ();
155-
156- info .currentIndex ++;
157- if (info .currentIndex == info .instances .size ())
158- info .currentIndex = 0 ;
159- if (info .currentIndex == startIndex ) // All events executed at once
160- break ;
161169 }
162-
163- info .lastTick = tick ;
170+ info .lastCheckedTime = worldTime ;
164171 }
165172 }, 0 , CHECK_PERIOD );
166173 }
167174
168175 @ Override
169176 public String toString (@ Nullable Event event , boolean debug ) {
170- return "at " + Time .toString (tick ) + " in worlds " + Classes .toString (worlds , true );
177+ return "at " + Time .toString (time ) + " in worlds " + Classes .toString (worlds , true );
171178 }
172179
173180 @ Override
174181 public int compareTo (@ Nullable EvtAtTime event ) {
175- return event == null ? tick : tick - event .tick ;
182+ return event == null ? time : time - event .time ;
176183 }
177184
178185}
0 commit comments