package skadistats.clarity.analyzer.replay; import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.binding.IntegerBinding; import javafx.beans.property.BooleanProperty; import javafx.beans.property.Property; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.scene.control.Slider; import org.fxmisc.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import skadistats.clarity.analyzer.Main; import skadistats.clarity.decoder.Util; import skadistats.clarity.processor.entities.UsesEntities; import skadistats.clarity.source.LiveSource; import java.io.File; import java.io.IOException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @UsesEntities public class ReplayController { private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(r -> { Thread t = new Thread(r); t.setDaemon(true); return t; }); protected final Logger log = LoggerFactory.getLogger(getClass()); private final Slider slider; private ScheduledFuture<?> timer; private Property<PropertySupportRunner> runner = new SimpleObjectProperty<>(); private IntegerBinding tick = Bindings.selectInteger( EasyBind.select(runner) .selectObject(PropertySupportRunner::tickProperty) .orElse(0) ); private IntegerBinding lastTick = Bindings.selectInteger( EasyBind.select(runner) .selectObject(PropertySupportRunner::lastTickProperty) .orElse(0) ); private BooleanProperty playing = new SimpleBooleanProperty(false); public ReplayController(Slider slider) { this.slider = slider; slider.maxProperty().bind(lastTick); playing.addListener(this::playingStateChanged); slider.valueProperty().addListener(this::sliderValueChanged); tick.addListener(this::tickChanged); Main.primaryStage.setOnCloseRequest(event -> haltIfRunning()); } private void playingStateChanged(ObservableValue<? extends Boolean> v, Boolean o, Boolean n) { if (n) { if (timer == null) { timer = executor.scheduleAtFixedRate( this::timerTick, 0L, (long)(getRunner().getEngineType().getMillisPerTick() * 1000000.0f), TimeUnit.NANOSECONDS ); } } else { if (timer != null) { timer.cancel(true); timer = null; } } } private void sliderValueChanged(ObservableValue<? extends Number> v, Number o, Number n) { double val = n.doubleValue(); // Hack: if the value is not exactly an integer, the slider has been clicked if (val != Math.floor(val)) { getRunner().setDemandedTick(n.intValue()); } } private void tickChanged(ObservableValue<? extends Number> v, Number o, Number n) { if (!slider.isValueChanging()) { slider.setValue(n.doubleValue()); } } private void timerTick() { Platform.runLater(() -> { if (slider.isValueChanging()) { return; } PropertySupportRunner r = getRunner(); if (r.getTick() < r.getLastTick() && !r.isResetting()) { r.setDemandedTick(r.getTick() + 1); } }); } public ObservableEntityList load(File f) throws IOException { haltIfRunning(); PropertySupportRunner r = new PropertySupportRunner(new LiveSource(f.getAbsoluteFile().toString(), 30, TimeUnit.SECONDS)); ObservableEntityList observableEntities = new ObservableEntityList(r.getEngineType()); runner.setValue(r); r.runWith(this, observableEntities); return observableEntities; } public void haltIfRunning() { setPlaying(false); if (getRunner() != null) { getRunner().halt(); try { getRunner().getSource().close(); } catch (IOException e) { Util.uncheckedThrow(e); } } } public PropertySupportRunner getRunner() { return runner.getValue(); } public Property<PropertySupportRunner> runnerProperty() { return runner; } public void setRunner(PropertySupportRunner runner) { this.runner.setValue(runner); } public IntegerBinding lastTickProperty() { return lastTick; } public IntegerBinding tickProperty() { return tick; } public boolean getPlaying() { return playing.get(); } public BooleanProperty playingProperty() { return playing; } public void setPlaying(boolean playing) { this.playing.set(playing); } }