From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Kieran Wallbanks Date: Fri, 2 Apr 2021 17:28:58 +0100 Subject: [PATCH] Add Tick TemporalUnit diff --git a/src/main/java/io/papermc/paper/util/Tick.java b/src/main/java/io/papermc/paper/util/Tick.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null +++ b/src/main/java/io/papermc/paper/util/Tick.java @@ -0,0 +0,0 @@ +package io.papermc.paper.util; + +import net.kyori.adventure.util.Ticks; +import org.jetbrains.annotations.NotNull; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalUnit; +import java.util.Objects; + +/** + * A TemporalUnit that represents the target length of one server tick. This is defined + * as 50 milliseconds. Note that this class is not for measuring the length that a tick + * took, rather it is used for simple conversion between times and ticks. + * @see #tick() + */ +public final class Tick implements TemporalUnit { + private static final Tick INSTANCE = new Tick(Ticks.SINGLE_TICK_DURATION_MS); + + private final long milliseconds; + + /** + * Gets the instance of the tick temporal unit. + * @return the tick instance + */ + public static @NotNull Tick tick() { + return INSTANCE; + } + + /** + * Creates a new tick. + * @param length the length of the tick in milliseconds + * @see #tick() + */ + private Tick(long length) { + this.milliseconds = length; + } + + /** + * Creates a duration from an amount of ticks. This is shorthand for + * {@link Duration#of(long, TemporalUnit)} called with the amount of ticks and + * {@link #tick()}. + * @param ticks the amount of ticks + * @return the duration + */ + public static @NotNull Duration of(long ticks) { + return Duration.of(ticks, INSTANCE); + } + + /** + * Gets the number of whole ticks that occur in the provided duration. Note that this + * method returns an {@code int} as this is the unit that Minecraft stores ticks in. + * @param duration the duration + * @return the number of whole ticks in this duration + * @throws ArithmeticException if the duration is zero or an overflow occurs + */ + public int fromDuration(@NotNull Duration duration) { + Objects.requireNonNull(duration, "duration cannot be null"); + return Math.toIntExact(Math.floorDiv(duration.toMillis(), this.milliseconds)); + } + + @Override + public @NotNull Duration getDuration() { + return Duration.ofMillis(this.milliseconds); + } + + // Note: This is a workaround in order to allow calculations with this duration. + // See: Duration#add + @Override + public boolean isDurationEstimated() { + return false; + } + + @Override + public boolean isDateBased() { + return false; + } + + @Override + public boolean isTimeBased() { + return true; + } + + @SuppressWarnings("unchecked") // following ChronoUnit#addTo + @Override + public @NotNull R addTo(@NotNull R temporal, long amount) { + return (R) temporal.plus(getDuration().multipliedBy(amount)); + } + + @Override + public long between(@NotNull Temporal start, @NotNull Temporal end) { + return start.until(end, ChronoUnit.MILLIS) / this.milliseconds; + } +} diff --git a/src/test/java/io/papermc/paper/util/TickTest.java b/src/test/java/io/papermc/paper/util/TickTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null +++ b/src/test/java/io/papermc/paper/util/TickTest.java @@ -0,0 +0,0 @@ +package io.papermc.paper.util; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TickTest { + + @Test + public void testTickLength() { + assertEquals(50, Duration.of(1, Tick.tick()).toMillis()); + assertEquals(100, Duration.of(2, Tick.tick()).toMillis()); + } + + @Test + public void testTickFromDuration() { + assertEquals(0, Tick.tick().fromDuration(Duration.ofMillis(0))); + assertEquals(0, Tick.tick().fromDuration(Duration.ofMillis(10))); + assertEquals(1, Tick.tick().fromDuration(Duration.ofMillis(60))); + assertEquals(2, Tick.tick().fromDuration(Duration.ofMillis(100))); + } + + @Test + public void testAddTickToInstant() { + Instant now = Instant.now(); + assertEquals(now, now.plus(0, Tick.tick())); + assertEquals(now.plus(50, ChronoUnit.MILLIS), now.plus(1, Tick.tick())); + assertEquals(now.plus(100, ChronoUnit.MILLIS), now.plus(2, Tick.tick())); + assertEquals(now.plus(150, ChronoUnit.MILLIS), now.plus(3, Tick.tick())); + } + + @Test + public void testTicksBetweenInstants() { + Instant now = Instant.now(); + assertEquals(0, now.until(now.plus(20, ChronoUnit.MILLIS), Tick.tick())); + assertEquals(1, now.until(now.plus(50, ChronoUnit.MILLIS), Tick.tick())); + assertEquals(1, now.until(now.plus(60, ChronoUnit.MILLIS), Tick.tick())); + assertEquals(2, now.until(now.plus(100, ChronoUnit.MILLIS), Tick.tick())); + } +}