creativescala / doodle

Compositional vector graphics in Scala / Scala.JS
https://creativescala.org/doodle/
Apache License 2.0
327 stars 75 forks source link

No java2d animation shown on Linux #78

Closed dacr closed 5 years ago

dacr commented 5 years ago

Hi,

I did a small try with your latest commit (Release 0.9.4-SNAPSHOT), built locally using sbt publishLocal and then tried using an ammonite script based on your example named PulsingCircle. When I tried to start it I got a very small window frame, not a 600x600 one, with nothing inside it, when I resize it, I can only see a small white rectangle, and everything else is black.

image

import $ivy.`org.creativescala::doodle:0.9.4-SNAPSHOT`
import cats.instances.all._
import doodle.core._
import doodle.syntax._
import doodle.java2d.effect._
import doodle.java2d._
import doodle.java2d.algebra._
import doodle.interact.syntax._
import monix.reactive.Observable

val frame = Frame.size(600, 600)

def circle(diameter: Int): Picture[Unit] =
  Picture{ implicit algebra =>
    algebra.circle(diameter.toDouble).strokeColor(Color.crimson).strokeWidth(3.0)
  }

val animation: Observable[Picture[Unit]] =
  Observable
    .repeat(1)
    .scan((3, 10)){ (state, _) =>
      val (inc, diameter) = state
      if(diameter >= 500) (-3, diameter - 3)
      else if(diameter <= 10) (1, diameter + 3)
      else (inc, diameter + inc)
    }
    .map{ case (_, d) => circle(d) }

val r = for {
    canvas <- frame.canvas
    frames  = animation
  } yield frames.animateFrames(canvas)

r.unsafeRunAsync(println _)

scala.io.StdIn.readLine("Enter to exit...") // required when run as a script

You really did a great job with the doodle API, this is exactly the kind of vector graphics I was looking for :)

noelwelsh commented 5 years ago

🤔If the window doesn't resize that suggests the whole computation isn't running. Does a more normal example (e.g. Picture{ implicit alg => alg.circle(100) }.draw(), or any of the examples in the image subproject) work?

Unfortunately I don't have a Linux box at hand to test.

dacr commented 5 years ago

OK with 0.9.3 :

import $ivy.`org.creativescala::doodle:0.9.3`
import doodle.syntax._
import doodle.java2d._
Picture{ implicit alg => alg.circle(100) }.draw()
scala.io.StdIn.readLine("Enter to exit...") // required when run as a script

OK also with 0.9.4-SNAPSHOT :

import $ivy.`org.creativescala::doodle:0.9.4-SNAPSHOT`
import doodle.syntax._
import doodle.java2d._
Picture{ implicit alg => alg.circle(100) }.draw()
scala.io.StdIn.readLine("Enter to exit...") // required when run as a script

both displayed exactly the same picture : image

noelwelsh commented 5 years ago

I'll make some changes in the near future that should reduce the possibility of error when using animate. If that doesn't fix it we'll have to dig deeper.

noelwelsh commented 5 years ago

I've pushed a new 0.9.4-SNAPSHOT version. Can you try this, say the PulsingCircle example?

dacr commented 5 years ago

Unfortunately I still don't see the animation and I got the same small white rectangle. From thread point of view, AWT-EventQueue-0 is in runnable state on this stack trace :

"AWT-EventQueue-0" #23 prio=6 os_prio=0 tid=0x00007f0def505000 nid=0x3670 in Object.wait() [0x00007f0d37bfa000]
   java.lang.Thread.State: RUNNABLE
    at ammonite.$file.vectorgraphics$minusdoodle$minusanimate$minusexample$$$Lambda$2252/1526894424.apply(Unknown Source)
    at doodle.java2d.package$Picture$$anon$1.apply(package.scala:61)
    at doodle.java2d.package$Picture$$anon$1.apply(package.scala:59)
    at doodle.java2d.effect.Java2DPanel.register$1(Java2DPanel.scala:60)
    at doodle.java2d.effect.Java2DPanel.$anonfun$render$1(Java2DPanel.scala:71)
    at doodle.java2d.effect.Java2DPanel.$anonfun$render$1$adapted(Java2DPanel.scala:71)
    at doodle.java2d.effect.Java2DPanel$$Lambda$2256/1625679293.apply(Unknown Source)
    at cats.effect.IO$.$anonfun$async$1(IO.scala:1062)
    at cats.effect.IO$.$anonfun$async$1$adapted(IO.scala:1060)
    at cats.effect.IO$$$Lambda$2209/1241213245.apply(Unknown Source)
    at cats.effect.internals.IORunLoop$RestartCallback.start(Redefined)
    at cats.effect.internals.IORunLoop$.cats$effect$internals$IORunLoop$$loop(IORunLoop.scala:119)
    at cats.effect.internals.IORunLoop$.startCancelable(IORunLoop.scala:41)
    at cats.effect.IO.unsafeRunCancelable(IO.scala:289)
    at cats.effect.Concurrent$.$anonfun$liftIO$2(Concurrent.scala:330)
    at cats.effect.Concurrent$$$Lambda$2258/1658052214.apply(Unknown Source)
    at monix.eval.internal.TaskCreate$.$anonfun$cancelableEffect$1(TaskCreate.scala:72)
    at monix.eval.internal.TaskCreate$$$Lambda$2259/1163330973.apply(Unknown Source)
    at monix.eval.internal.TaskCreate$Cancelable0Start.apply(TaskCreate.scala:53)
    at monix.eval.internal.TaskCreate$Cancelable0Start.apply(TaskCreate.scala:41)
    at monix.eval.internal.TaskRestartCallback.start(Redefined)
    at monix.eval.internal.TaskRunLoop$.executeAsyncTask(TaskRunLoop.scala:583)
    at monix.eval.internal.TaskRunLoop$.goAsync4Future(TaskRunLoop.scala:634)
    at monix.eval.internal.TaskRunLoop$.startFuture(TaskRunLoop.scala:530)
    at monix.eval.Task.runToFutureOpt(Task.scala:583)
    at monix.eval.Task.runToFuture(Task.scala:541)
    at monix.reactive.internal.operators.MapTaskObservable$MapAsyncSubscriber.onNext(MapTaskObservable.scala:159)
    at monix.reactive.internal.operators.MapOperator$$anon$1.onNext(MapOperator.scala:43)
    at monix.reactive.internal.builders.Zip2Observable.rawOnNext$1(Zip2Observable.scala:62)
    at monix.reactive.internal.builders.Zip2Observable.monix$reactive$internal$builders$Zip2Observable$$signalOnNext$1(Zip2Observable.scala:81)
    at monix.reactive.internal.builders.Zip2Observable$$anon$2.onNext(Zip2Observable.scala:167)
    - locked <0x00000000ed080908> (a java.lang.Object)
    at monix.reactive.subjects.PublishSubject.sendOnNextToAll(PublishSubject.scala:122)
    at monix.reactive.subjects.PublishSubject.onNext(PublishSubject.scala:97)
    at doodle.java2d.effect.Canvas$$anon$1.actionPerformed(Canvas.scala:46)
    at javax.swing.Timer.fireActionPerformed(Timer.java:313)
    at javax.swing.Timer$DoPostEvent.run(Redefined)
    at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758)
    at java.awt.EventQueue.access$500(EventQueue.java:97)
    at java.awt.EventQueue$3.run(EventQueue.java:709)
    at java.awt.EventQueue$3.run(EventQueue.java:703)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:728)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Redefined)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Redefined)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Redefined)
    at java.awt.EventDispatchThread.pumpEvents(Redefined)
    at java.awt.EventDispatchThread.pumpEvents(Redefined)
    at java.awt.EventDispatchThread.run(Redefined)

I'll take a deeper look using intellij debugger to see if I can provide you more information about what's going on...

dacr commented 5 years ago

OK I've just created a small dedicated project based on your animation example (in order to use the IDE debugger), and this time it works fine :) looks like the issue is related to ammonite or some undesired interaction with it.

dacr commented 5 years ago

OK once again, I found a way to have doodle animations work fine with ammonite (script or REPL mode) : Just put all the code within an object like this :

import $ivy.`org.creativescala::doodle:0.9.4-SNAPSHOT`

import cats.instances.all._
import doodle.core._
import doodle.syntax._
import doodle.java2d.effect._
import doodle.java2d._
//import doodle.java2d.algebra._
import doodle.interact.syntax._
import monix.reactive.Observable

object TryIt {
  val frame = Frame.size(600, 600)

  def circle(diameter: Int): Picture[Unit] =
    Picture { implicit algebra =>
      algebra
        .circle(diameter.toDouble)
        .strokeColor(Color.crimson)
        .on(algebra
          .circle((diameter - 9).toDouble)
          .strokeColor(Color.crimson.spin(30.degrees)))
        .on(algebra
          .circle((diameter - 18).toDouble)
          .strokeColor(Color.crimson.spin(60.degrees)))
        .strokeWidth(3.0)
        .noFill
    }

  val animation: Observable[Picture[Unit]] =
    Observable
      .repeat(1)
      .scan((3, 18)) { (state, _) =>
        val (inc, diameter) = state
        if (diameter >= 500) (-3, diameter - 3)
        else if (diameter <= 18) (3, diameter + 3)
        else (inc, diameter + inc)
      }
      .map { case (_, d) => circle(d) }

  def go = animation.animateFrames(frame)
}

TryIt.go

scala.io.StdIn.readLine("Enter to exit...") // required when run as a script

So I close the issue, as this is something I've already encountered with some other scala libraries (such as akka-http) while trying to use them with amonite.

noelwelsh commented 5 years ago

Thanks for debugging this! Good to know what the issue is, in case someone else has the same problem.