Closed lego-eden closed 1 month ago
I have now come up with a (temporary?) fix for this issue. I have rewritten the run
method in BaseReactor
. It now looks like this:
def run[Alg <: Basic, Frame, Canvas](frame: Frame)(implicit
a: AnimationRenderer[Canvas],
e: Renderer[Alg, Frame, Canvas],
m: MouseClick[Canvas] & MouseMove[Canvas],
runtime: IORuntime
): Unit = {
import BaseReactor.*
def mouseEventProducer(mouseEventQueue: Queue[IO, MouseEvent], canvas: Canvas): IO[Unit] = {
val mouseMove = canvas.mouseMove.map(pt => MouseMove(pt))
val mouseClick = canvas.mouseClick.map(pt => MouseClick(pt))
mouseMove.merge(mouseClick)
.foreach(mouseEventQueue.offer)
.compile
.drain
}
def tickProducer(tickQueue: Queue[IO, A], mouseEventQueue: Queue[IO, MouseEvent]): IO[Unit] = {
Stream
.fixedRate[IO](this.tickRate)
.evalScan[IO, A](this.initial)((prev, _) =>
def drainMouseQueue(a: A): IO[A] =
for
mouseEvent <- mouseEventQueue.tryTake
state <- mouseEvent match
case Some(MouseMove(pt)) => drainMouseQueue(this.onMouseMove(pt, a))
case Some(MouseClick(pt)) => drainMouseQueue(this.onMouseClick(pt, a))
case None => IO.pure(a)
yield state
for
mouseState <- drainMouseQueue(prev)
state = this.onTick(mouseState)
yield state
)
.takeWhile(a => !this.stop(a))
.foreach(tickQueue.offer)
.compile
.drain
}
def consumer(tickQueue: Queue[IO, A], canvas: Canvas): IO[Unit] = {
Stream.unit.repeat
.evalScan[IO, A](this.initial)((prev, _) =>
for
maybeTaken <- tickQueue.tryTake
state = maybeTaken match
case Some(a) => a
case None => prev
yield state
)
.takeWhile(a => !this.stop(a))
.map(a => Image.compile[Alg](this.render(a)))
.animateWithCanvasToIO(canvas)
}
(
for
canvas <- frame.canvas[Alg, Canvas]()
tickQueue <- Queue.circularBuffer[IO, A](1)
// mouseEventQueue <- Queue.circularBuffer[IO, MouseEvent](1)
mouseEventQueue <- Queue.unbounded[IO, MouseEvent]
_ <-
(mouseEventProducer(mouseEventQueue, canvas), tickProducer(tickQueue, mouseEventQueue), consumer(tickQueue, canvas))
.parMapN((_, _, _) => ())
yield ()
).unsafeRunAsync(_ => ())
}
}
The main difference between this method and the existing one, is that this one has split the ticks into their own fiber, thus decoupling the tickRate completely from the canvas, bypassing the issue. I have also decided to only evaluate the mouse-events when a tick occurs. They keep piling up in a queue, which is then emptied the next tick. The mouse-events are evaluated as they are taken from the queue.
https://github.com/user-attachments/assets/bb90a67c-88fa-4b54-9f82-bb8734f905fb
Interesting. I suspect the root cause is somewhere in the depths of the Java2D framework it's redrawing the screen when the mouse moves, and this is triggering whatever ends up invoking tick
. I haven't looked at that code in quite a while, so I don't recall how it works.
You've taken what seems a reasonable approach to me. Writing Java2D code is, at this point, as much archaeology as it is programming. Documentation tends to be very old. At the moment I don't have a strong opinion about doing that archaeology, vs taking the approach you have, vs looking more closely at an alternative like Skija.
Closed by #172
When experimenting with
Reactor
I found out that at sufficiently high update-rate (I used.withUpdateRate(5.millis)
), the animation on the canvas speeds up while moving the mouse.I have tried copying the code for the
BaseReactor
and theReactor
, removing the references to mouse-movement and mouse-clicking, but the bug is still present. I suspect this is an issue to do with theCanvas
in java2d.Happy for any help or tips on how I can solve this.
https://github.com/user-attachments/assets/136b58ce-f766-4c6a-9b45-96240c0ff79e