diff --git a/src/main/scala/ocelot/desktop/OcelotDesktop.scala b/src/main/scala/ocelot/desktop/OcelotDesktop.scala index 03acdfb..ca41c06 100644 --- a/src/main/scala/ocelot/desktop/OcelotDesktop.scala +++ b/src/main/scala/ocelot/desktop/OcelotDesktop.scala @@ -24,6 +24,7 @@ import java.util.concurrent.locks.{Lock, ReentrantLock} import javax.swing.JFileChooser import scala.collection.mutable import scala.collection.mutable.ArrayBuffer +import scala.concurrent.duration.Duration import scala.io.Source import scala.jdk.CollectionConverters._ import scala.util.{Failure, Success, Try} @@ -36,9 +37,9 @@ object OcelotDesktop extends Logging { val ticker = new Ticker private val TickerIntervalHistorySize = 5 - val tickerIntervalHistory = new mutable.Queue[Long](TickerIntervalHistorySize) + val tickerIntervalHistory = new mutable.Queue[Duration](TickerIntervalHistorySize) - def pushToTickerIntervalHistory(interval: Long): Unit = { + def pushToTickerIntervalHistory(interval: Duration): Unit = { if (tickerIntervalHistory.size >= TickerIntervalHistorySize) tickerIntervalHistory.dequeue() tickerIntervalHistory.enqueue(interval) } diff --git a/src/main/scala/ocelot/desktop/node/nodes/RelayNode.scala b/src/main/scala/ocelot/desktop/node/nodes/RelayNode.scala index fe34358..39e36e7 100644 --- a/src/main/scala/ocelot/desktop/node/nodes/RelayNode.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/RelayNode.scala @@ -19,4 +19,8 @@ class RelayNode(val relay: Relay) extends Node(relay) { override def getNodeByPort(port: NodePort): network.Node = relay.sidedNode(port.direction.get) override protected val exposeAddress = false + + override def shouldReceiveEventsFor(address: String): Boolean = { + ports.exists(port => getNodeByPort(port).address == address) + } } diff --git a/src/main/scala/ocelot/desktop/ui/UiHandler.scala b/src/main/scala/ocelot/desktop/ui/UiHandler.scala index f4960c9..619654b 100644 --- a/src/main/scala/ocelot/desktop/ui/UiHandler.scala +++ b/src/main/scala/ocelot/desktop/ui/UiHandler.scala @@ -24,6 +24,7 @@ import java.nio.channels.Channels import java.nio.file.{Files, Paths} import javax.imageio.ImageIO import scala.collection.mutable +import scala.concurrent.duration.DurationInt object UiHandler extends Logging { var root: RootWidget = _ @@ -34,7 +35,7 @@ object UiHandler extends Logging { private val fpsCalculator = new FPSCalculator private val ticker = new Ticker - ticker.tickInterval = 1000f / 60f + ticker.tickInterval = 1.second / 60 def getHierarchy: Array[Widget] = hierarchy.toArray diff --git a/src/main/scala/ocelot/desktop/ui/widget/ChangeSimulationSpeedDialog.scala b/src/main/scala/ocelot/desktop/ui/widget/ChangeSimulationSpeedDialog.scala index 89a3eb2..dbbe63a 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/ChangeSimulationSpeedDialog.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/ChangeSimulationSpeedDialog.scala @@ -4,11 +4,14 @@ import ocelot.desktop.OcelotDesktop import ocelot.desktop.audio.{SoundSource, SoundSources} import ocelot.desktop.geometry.Padding2D import ocelot.desktop.ui.layout.LinearLayout +import ocelot.desktop.ui.widget.ChangeSimulationSpeedDialog.validateIntervalUs import ocelot.desktop.ui.widget.modal.ModalDialog import ocelot.desktop.util.Orientation +import scala.concurrent.duration.{Duration, DurationLong} + class ChangeSimulationSpeedDialog() extends ModalDialog { - private var tickInterval: Option[Long] = Some(OcelotDesktop.ticker.tickInterval) + private var tickInterval: Option[Duration] = Some(OcelotDesktop.ticker.tickInterval) private def confirm(): Unit = { if (tickInterval.isDefined) { @@ -27,53 +30,45 @@ class ChangeSimulationSpeedDialog() extends ModalDialog { private var inputTPS: TextInput = _ private var inputMSPT: TextInput = _ - private def formatMSPT(v: Long) = (v.toFloat / 1000).toString - private def formatTPS(v: Long) = (1000000 / v.toFloat).toString + private def formatMSPT(interval: Duration): String = (interval.toMicros / 1000f).toString + private def formatTPS(interval: Duration): String = (1_000_000f / interval.toMicros).toString inputMSPT = new TextInput(formatMSPT(OcelotDesktop.ticker.tickInterval)) { focus() + private def parseInput(text: String): Option[Duration] = try { + validateIntervalUs((text.toFloat * 1000).toLong) + } catch { + case _: NumberFormatException => None + } + override def onInput(text: String): Unit = { - tickInterval = try { - val v = (text.toFloat * 1000).toLong - inputTPS.setInput(formatTPS(v)) - Some(v) - } catch { - case _: NumberFormatException => None + tickInterval = parseInput(text).map { interval => + inputTPS.setInput(formatTPS(interval)) + interval } } - override def validator(text: String): Boolean = { - try { - text.toFloat - true - } catch { - case _: NumberFormatException => false - } - } + override def validator(text: String): Boolean = parseInput(text).isDefined override def onConfirm(): Unit = confirm() } inputTPS = new TextInput(formatTPS(OcelotDesktop.ticker.tickInterval)) { + private def parseInput(text: String): Option[Duration] = try { + validateIntervalUs((1_000_000 / text.toFloat).toLong) + } catch { + case _: NumberFormatException => None + } + override def onInput(text: String): Unit = { - tickInterval = try { - val v = ((1 / text.toFloat) * 1000000).toLong - inputMSPT.setInput(formatMSPT(v)) - Some(v) - } catch { - case _: NumberFormatException => None + tickInterval = parseInput(text).map { interval => + inputMSPT.setInput(formatMSPT(interval)) + interval } } - override def validator(text: String): Boolean = { - try { - text.toFloat - true - } catch { - case _: NumberFormatException => false - } - } + override def validator(text: String): Boolean = parseInput(text).isDefined override def onConfirm(): Unit = confirm() } @@ -96,6 +91,7 @@ class ChangeSimulationSpeedDialog() extends ModalDialog { override def onClick(): Unit = close() }, Padding2D(right = 8)) + // TODO: disable the button if tickInterval.isEmpty children :+= new Button { override def text: String = "Apply" override def onClick(): Unit = confirm() @@ -103,3 +99,16 @@ class ChangeSimulationSpeedDialog() extends ModalDialog { } }, Padding2D.equal(16)) } + +object ChangeSimulationSpeedDialog { + private val MaxUpdateInterval = 1.minute + private val MinUpdateInterval = 1.micro + + private def validateIntervalUs(us: Long): Option[Duration] = try { + val interval = us.micros + + Option.when(interval >= MinUpdateInterval && interval <= MaxUpdateInterval)(interval) + } catch { + case _: IllegalArgumentException => None + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/statusbar/StatusBar.scala b/src/main/scala/ocelot/desktop/ui/widget/statusbar/StatusBar.scala index 6a399a2..8f68bd2 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/statusbar/StatusBar.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/statusbar/StatusBar.scala @@ -1,6 +1,5 @@ package ocelot.desktop.ui.widget.statusbar -import ocelot.desktop.audio.Audio import ocelot.desktop.color.Color import ocelot.desktop.geometry.{Padding2D, Size2D} import ocelot.desktop.graphics.Graphics @@ -13,6 +12,8 @@ import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} import ocelot.desktop.{ColorScheme, OcelotDesktop} import org.lwjgl.input.Keyboard +import scala.concurrent.duration.DurationInt + class StatusBar extends Widget { override protected val layout: LinearLayout = new LinearLayout(this, contentAlignment = Alignment.Center) { contentAlignment = Alignment.Center @@ -59,12 +60,12 @@ class StatusBar extends Widget { new ChangeSimulationSpeedDialog().show() })) menu.addEntry(new ContextMenuEntry("Reset simulation speed", () => { - OcelotDesktop.ticker.tickInterval = 50000 + OcelotDesktop.ticker.tickInterval = 50.millis })) if (OcelotDesktop.tickerIntervalHistory.nonEmpty) menu.addSeparator() for (elem <- OcelotDesktop.tickerIntervalHistory.reverseIterator) { - menu.addEntry(new ContextMenuEntry((1000000 / elem.toFloat).toString, () => { + menu.addEntry(new ContextMenuEntry((1_000_000f / elem.toMicros).toString, () => { OcelotDesktop.ticker.tickInterval = elem })) } diff --git a/src/main/scala/ocelot/desktop/util/Ticker.scala b/src/main/scala/ocelot/desktop/util/Ticker.scala index 2e4d8a2..23177e3 100644 --- a/src/main/scala/ocelot/desktop/util/Ticker.scala +++ b/src/main/scala/ocelot/desktop/util/Ticker.scala @@ -1,24 +1,26 @@ package ocelot.desktop.util import java.util.concurrent.TimeUnit +import scala.concurrent.duration.{Duration, DurationInt} class Ticker extends Logging { var lastTick: Long = System.nanoTime() var tick: Long = 0 - private[this] var _tickInterval: Long = _ + private var _tickIntervalNs: Long = _ - def tickInterval: Long = _tickInterval / 1000 + def tickInterval: Duration = Duration.fromNanos(_tickIntervalNs) - def tickInterval_=(us: Float): Unit = { - logger.info(f"Setting tick interval to $us us (${1000000f / us} s^-1)") - _tickInterval = (us * 1000).toLong + def tickInterval_=(interval: Duration): Unit = { + val us = interval.toMicros + logger.info(f"Setting tick interval to $us μs (${1_000_000f / us} s^-1)") + _tickIntervalNs = interval.toNanos } - tickInterval = 50000 + tickInterval = 1.second / 20 def waitNext(): Unit = { - val deadline = lastTick + _tickInterval + val deadline = lastTick + _tickIntervalNs var time = System.nanoTime() while (time < deadline) { val rem = deadline - time