@@ -0,0 +1,2626 @@
+package skadistats.clarity.processor.runner;%0A%0Aimport skadistats.clarity.processor.reader.OnMessage;%0Aimport skadistats.clarity.source.Source;%0Aimport skadistats.clarity.wire.common.proto.NetMessages;%0Aimport skadistats.clarity.wire.csgo.proto.CsGoNetMessages;%0A%0Aimport java.io.IOException;%0Aimport java.time.Duration;%0Aimport java.time.Instant;%0Aimport java.util.concurrent.atomic.AtomicReference;%0A%0Aimport static java.time.Instant.now;%0Aimport static java.util.concurrent.TimeUnit.NANOSECONDS;%0Aimport static java.util.concurrent.TimeUnit.SECONDS;%0A%0A%0Apublic class RealtimeRunner extends SimpleRunner %7B%0A%0A    private static final long SECOND_TO_NANOSECOND = NANOSECONDS.convert(1, SECONDS);%0A%0A    private final Instant startTime;%0A    private AtomicReference%3CDuration%3E delay = new AtomicReference%3C%3E();%0A    private Duration tickInterval;%0A%0A    public RealtimeRunner(Source s) throws IOException %7B%0A        this(s, Duration.ZERO);%0A    %7D%0A%0A    public RealtimeRunner(Source s, Duration delay) throws IOException %7B%0A        super(s);%0A        setDelay(delay);%0A        startTime = now();%0A    %7D%0A%0A    private boolean canDelay() %7B%0A        return loopController.syncTickSeen && tickInterval != null;%0A    %7D%0A%0A    private void delayUntil(int upcomingTick) %7B%0A        try %7B%0A            while(true) %7B%0A                Instant shouldBeAt = startTime.plus(delay.get()).plus(tickInterval.multipliedBy(upcomingTick));%0A                long milliDelay = Duration.between(now(), shouldBeAt).toMillis();%0A                if (milliDelay %3C= 0L) %7B%0A                    return;%0A                %7D%0A                Thread.sleep(Math.min(milliDelay, 250L));%0A            %7D%0A        %7D catch (InterruptedException e) %7B%0A            Thread.currentThread().interrupt();%0A        %7D%0A    %7D%0A%0A    @Override%0A    protected void startNewTick(int upcomingTick) %7B%0A        if (canDelay()) %7B%0A            delayUntil(upcomingTick);%0A        %7D%0A        super.startNewTick(upcomingTick);%0A    %7D%0A%0A    private void setTickInterval(float tickIntervalFloat) %7B%0A        tickInterval = Duration.ofNanos((long) (SECOND_TO_NANOSECOND * tickIntervalFloat));%0A    %7D%0A%0A    @OnMessage(CsGoNetMessages.CSVCMsg_ServerInfo.class)%0A    protected void onCsgoServerInfo(CsGoNetMessages.CSVCMsg_ServerInfo serverInfo) %7B%0A        setTickInterval(serverInfo.getTickInterval());%0A    %7D%0A%0A%0A    @OnMessage(NetMessages.CSVCMsg_ServerInfo.class)%0A    protected void onDotaServerInfo(NetMessages.CSVCMsg_ServerInfo serverInfo) %7B%0A        setTickInterval(serverInfo.getTickInterval());%0A    %7D%0A%0A    public Duration getDelay() %7B%0A        return delay.get();%0A    %7D%0A%0A    public void setDelay(Duration delay) %7B%0A        this.delay.set(delay);%0A    %7D%0A%0A%7D%0A

add RealtimeRunner