@@ -8,14 +8,40 @@ import java.util.concurrent.TimeUnit
88/* *
99 * A resettable timer that reuses a single scheduled executor thread.
1010 *
11+ * **Thread Reuse Pattern (Memory Efficiency)**
12+ *
13+ * This class addresses a critical memory leak by reusing a single thread instead of creating new threads
14+ * for each timer reset. The original NanoTimer created a new thread per call, causing:
15+ * - At 500 TPS: 500 new threads/second
16+ * - Over 21 hours: ~37 million thread objects allocated
17+ * - Result: OutOfMemoryError as GC couldn't keep up with thread allocation pressure
18+ *
19+ * ResettableTimer uses a [ScheduledExecutorService] with a single daemon thread that persists for the
20+ * lifetime of the timer. Multiple calls to [schedule] reschedule the same thread, not create new ones.
21+ *
22+ * **Timing Guarantees**
23+ *
1124 * The timer guarantees that the job runs no earlier than the configured minimum delay and
1225 * no later than the maximum delay after scheduling. A call to [notifyReady] requests execution
1326 * as soon as the minimum delay has elapsed. Paused time is excluded from timing calculations.
27+ *
28+ * **Usage Example**
29+ *
30+ * ```kotlin
31+ * // Create once per game (not per turn!)
32+ * val timer = ResettableTimer { doWork() }
33+ *
34+ * // Reset/reschedule on each turn - reuses existing thread
35+ * timer.schedule(minDelayNanos = 1000, maxDelayNanos = 5000)
36+ * ```
1437 */
1538class ResettableTimer (
1639 /* * Job to execute when the timer triggers. */
1740 private val job : Runnable
1841) {
42+ // Single-thread executor: REUSES the same thread for all scheduled tasks.
43+ // This is the key to avoiding the memory leak. The thread name includes "Timer" for debugging visibility.
44+ // Daemon thread flag ensures the JVM can shut down even if the timer is still active.
1945 private val executor: ScheduledExecutorService = Executors .newSingleThreadScheduledExecutor { runnable ->
2046 Thread (runnable, " TurnTimeoutTimer" ).apply { isDaemon = true }
2147 }
@@ -130,7 +156,7 @@ class ResettableTimer(
130156 if (! executor.awaitTermination(1 , TimeUnit .SECONDS )) {
131157 executor.shutdownNow()
132158 }
133- } catch (e : InterruptedException ) {
159+ } catch (_ : InterruptedException ) {
134160 executor.shutdownNow()
135161 Thread .currentThread().interrupt()
136162 }
0 commit comments