From cefd200c837e69a256c8e62d750640023541df7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20de=20Tastes?= Date: Fri, 9 Feb 2024 19:33:51 +0100 Subject: [PATCH] Iss22 suggestions (#36) * Add a countdown latch as a startup barrier for use by the RunOnFxThreadInterceptor to avoid race conditions between background threads interacting with javafx.application.Platform thread during startup. fixes #22 Signed-off-by: Scott M Stark * Updates per the comments fixes #22 Signed-off-by: Scott M Stark * Updates per the comments fixes #22 Signed-off-by: Scott M Stark * ClockEvents should be using synchronous events from RunOnFxThread Signed-off-by: Scott M Stark * Reorganize the code so that FxApplication remains a non-CDI bean Signed-off-by: Scott M Stark * wip * tests * wip * Add suggestions * Align RunOnFxThread test * 0.2.0 --------- Signed-off-by: Scott M Stark Co-authored-by: Scott M Stark --- .github/project.yml | 2 +- .../QuarkusFxExtensionProcessor.java | 9 +- .../fx/deployment/FxStartupTest.java | 41 ++++++++ .../fx/deployment/FxTestConstants.java | 7 ++ .../fx/deployment/QuarkusFxTest.java | 40 ++++---- .../fx/deployment/RunOnFxThreadTest.java | 99 +++++++++---------- docs/modules/ROOT/nav.adoc | 5 +- .../ROOT/pages/includes/attributes.adoc | 2 +- docs/modules/ROOT/pages/index.adoc | 8 +- .../ROOT/pages/startup-synchronization.adoc | 40 ++++++++ .../java/io/quarkiverse/fx/FxApplication.java | 24 ++--- .../io/quarkiverse/fx/FxStartupEvent.java | 7 ++ ...{StartupLatch.java => FxStartupLatch.java} | 12 ++- .../java/io/quarkiverse/fx/PrimaryStage.java | 3 +- .../fx/RunOnFxThreadInterceptor.java | 8 +- .../quarkiverse/fx/sample/AppController.java | 23 +++-- .../io/quarkiverse/fx/sample/ClockEvents.java | 13 ++- .../fx/sample/FxApplicationNotStarted.java | 23 +++++ .../quarkiverse/fx/sample/QuarkusFxApp.java | 13 +-- 19 files changed, 244 insertions(+), 135 deletions(-) create mode 100644 deployment/src/test/java/io/quarkiverse/fx/deployment/FxStartupTest.java create mode 100644 deployment/src/test/java/io/quarkiverse/fx/deployment/FxTestConstants.java create mode 100644 docs/modules/ROOT/pages/startup-synchronization.adoc create mode 100644 runtime/src/main/java/io/quarkiverse/fx/FxStartupEvent.java rename runtime/src/main/java/io/quarkiverse/fx/{StartupLatch.java => FxStartupLatch.java} (54%) create mode 100644 samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/FxApplicationNotStarted.java diff --git a/.github/project.yml b/.github/project.yml index 543ca4b..50e22db 100644 --- a/.github/project.yml +++ b/.github/project.yml @@ -1,3 +1,3 @@ release: - current-version: "0.1.0" + current-version: "0.2.0" next-version: "999-SNAPSHOT" \ No newline at end of file diff --git a/deployment/src/main/java/io/quarkiverse/fx/deployment/QuarkusFxExtensionProcessor.java b/deployment/src/main/java/io/quarkiverse/fx/deployment/QuarkusFxExtensionProcessor.java index 026db8d..e81378b 100644 --- a/deployment/src/main/java/io/quarkiverse/fx/deployment/QuarkusFxExtensionProcessor.java +++ b/deployment/src/main/java/io/quarkiverse/fx/deployment/QuarkusFxExtensionProcessor.java @@ -10,7 +10,12 @@ import org.jboss.jandex.VoidType; import org.jboss.logging.Logger; -import io.quarkiverse.fx.*; +import io.quarkiverse.fx.FXMLLoaderProducer; +import io.quarkiverse.fx.FxStartupLatch; +import io.quarkiverse.fx.PrimaryStage; +import io.quarkiverse.fx.QuarkusFxApplication; +import io.quarkiverse.fx.RunOnFxThread; +import io.quarkiverse.fx.RunOnFxThreadInterceptor; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -52,7 +57,7 @@ AdditionalBeanBuildItem primaryStage() { */ @BuildStep AdditionalBeanBuildItem startupLatch() { - return new AdditionalBeanBuildItem(StartupLatch.class); + return new AdditionalBeanBuildItem(FxStartupLatch.class); } @BuildStep diff --git a/deployment/src/test/java/io/quarkiverse/fx/deployment/FxStartupTest.java b/deployment/src/test/java/io/quarkiverse/fx/deployment/FxStartupTest.java new file mode 100644 index 0000000..0df3205 --- /dev/null +++ b/deployment/src/test/java/io/quarkiverse/fx/deployment/FxStartupTest.java @@ -0,0 +1,41 @@ +package io.quarkiverse.fx.deployment; + +import java.util.concurrent.CompletableFuture; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkiverse.fx.FxStartupLatch; +import io.quarkiverse.fx.QuarkusFxApplication; +import io.quarkus.runtime.Quarkus; +import io.quarkus.test.QuarkusUnitTest; + +public class FxStartupTest { + + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Inject + FxStartupLatch latch; + + @Test + @Timeout(value = 5) + void test() { + + Assertions.assertNotNull(this.latch); + CompletableFuture.runAsync(() -> Quarkus.run(QuarkusFxApplication.class)); + + try { + this.latch.await(); + } catch (InterruptedException e) { + Assertions.fail(e); + } + } +} diff --git a/deployment/src/test/java/io/quarkiverse/fx/deployment/FxTestConstants.java b/deployment/src/test/java/io/quarkiverse/fx/deployment/FxTestConstants.java new file mode 100644 index 0000000..d67835f --- /dev/null +++ b/deployment/src/test/java/io/quarkiverse/fx/deployment/FxTestConstants.java @@ -0,0 +1,7 @@ +package io.quarkiverse.fx.deployment; + +public final class FxTestConstants { + + // Allow some time between launch and FX readiness + static final int LAUNCH_TIMEOUT_MS = 3_000; +} diff --git a/deployment/src/test/java/io/quarkiverse/fx/deployment/QuarkusFxTest.java b/deployment/src/test/java/io/quarkiverse/fx/deployment/QuarkusFxTest.java index fceeda8..1c9d693 100644 --- a/deployment/src/test/java/io/quarkiverse/fx/deployment/QuarkusFxTest.java +++ b/deployment/src/test/java/io/quarkiverse/fx/deployment/QuarkusFxTest.java @@ -1,36 +1,37 @@ package io.quarkiverse.fx.deployment; -import io.quarkiverse.fx.PrimaryStage; -import io.quarkiverse.fx.QuarkusFxApplication; -import io.quarkus.runtime.Quarkus; -import io.quarkus.test.QuarkusUnitTest; +import static org.awaitility.Awaitility.await; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + import jakarta.enterprise.context.Dependent; import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.scene.layout.Pane; -import javafx.stage.Stage; + import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.awaitility.Awaitility.await; +import io.quarkiverse.fx.PrimaryStage; +import io.quarkiverse.fx.QuarkusFxApplication; +import io.quarkus.runtime.Quarkus; +import io.quarkus.test.QuarkusUnitTest; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; public class QuarkusFxTest { - private static final int LAUNCH_TIMEOUT_MS = 3_000; private static final int A_FANCY_TEST_VALUE = 42; private static final String FXML_CONTENT = """ @@ -63,7 +64,6 @@ void initialize() { } } - // Start unit test with your extension loaded @RegisterExtension static final QuarkusUnitTest unitTest = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); @@ -82,7 +82,7 @@ void testFXMLLaunchAndLoad() { CompletableFuture.runAsync(() -> Quarkus.run(QuarkusFxApplication.class)); await() - .atMost(LAUNCH_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .atMost(FxTestConstants.LAUNCH_TIMEOUT_MS, TimeUnit.MILLISECONDS) .until(primaryStageObserved::get); try { diff --git a/deployment/src/test/java/io/quarkiverse/fx/deployment/RunOnFxThreadTest.java b/deployment/src/test/java/io/quarkiverse/fx/deployment/RunOnFxThreadTest.java index b2c3103..c578ade 100644 --- a/deployment/src/test/java/io/quarkiverse/fx/deployment/RunOnFxThreadTest.java +++ b/deployment/src/test/java/io/quarkiverse/fx/deployment/RunOnFxThreadTest.java @@ -1,83 +1,72 @@ package io.quarkiverse.fx.deployment; +import static org.awaitility.Awaitility.await; + import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import jakarta.enterprise.context.Dependent; import jakarta.enterprise.event.Observes; -import org.awaitility.Awaitility; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkiverse.fx.FxApplication; import io.quarkiverse.fx.PrimaryStage; +import io.quarkiverse.fx.QuarkusFxApplication; import io.quarkiverse.fx.RunOnFxThread; +import io.quarkus.runtime.Quarkus; import io.quarkus.test.QuarkusUnitTest; -import javafx.application.Application; import javafx.application.Platform; import javafx.stage.Stage; -@Disabled // Disabled until #9 is fixed public class RunOnFxThreadTest { - private static final int LAUNCH_TIMEOUT_MS = 1_000; + private static final int ASYNC_RUN_TIMEOUT_MS = 500; @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar.addClasses(Observer.class)); + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + private static boolean primaryStageObserved = false; + private static Boolean regularThread; + private static Boolean fxThread; @Test - void annotatedMethodsExecuteOnFxThread() { - // launch - CompletableFuture.runAsync(() -> Application.launch(FxApplication.class)); - // wait for stage to be observed - Awaitility.await().atMost(LAUNCH_TIMEOUT_MS, TimeUnit.MILLISECONDS).until(Observer::stageObserved); - // stage should not be null - Assertions.assertNotNull(Observer.OBSERVED_STAGE.get()); - // - Assertions.assertTrue(Observer.STAGE_OBSERVER_METHOD_THREAD_IS_FX_THREAD); - Assertions.assertFalse(Observer.DO_ON_CURRENT_METHOD_THREAD_IS_FX_THREAD); - Assertions.assertTrue(Observer.DO_ON_FX_METHOD_THREAD_IS_FX_THREAD); + void test() { + // Non-blocking JavaFX launch + CompletableFuture.runAsync(() -> Quarkus.run(QuarkusFxApplication.class)); + + await() + .atMost(FxTestConstants.LAUNCH_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .until(() -> primaryStageObserved); + + // Synchronous regular thread run + this.runOnRegularThread(); + Assertions.assertTrue(regularThread); + + // Asynchronous FX thread run + this.runOnFxThread(); + Assertions.assertNull(fxThread); + + await().atMost(ASYNC_RUN_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .until(() -> fxThread != null); + + Assertions.assertTrue(fxThread); } - @Dependent - static class Observer { - public static final AtomicReference OBSERVED_STAGE = new AtomicReference<>(); - public static boolean STAGE_OBSERVER_METHOD_THREAD_IS_FX_THREAD; - public static boolean DO_ON_CURRENT_METHOD_THREAD_IS_FX_THREAD; - public static boolean DO_ON_FX_METHOD_THREAD_IS_FX_THREAD; - - public void observeStage(@Observes @PrimaryStage final Stage stage) throws ExecutionException, InterruptedException { - // Observe current thread - STAGE_OBSERVER_METHOD_THREAD_IS_FX_THREAD = Platform.isFxApplicationThread(); - // launch methods and observe threads - ExecutorService executor = Executors.newSingleThreadExecutor(); - executor.submit(this::doOnCurrentThread).get(); - executor.submit(this::doOnFxThread).get(); - executor.shutdownNow(); - // observe stage - Observer.OBSERVED_STAGE.set(stage); - } - - void doOnCurrentThread() { - DO_ON_CURRENT_METHOD_THREAD_IS_FX_THREAD = Platform.isFxApplicationThread(); - } - - @RunOnFxThread - void doOnFxThread() { - DO_ON_FX_METHOD_THREAD_IS_FX_THREAD = Platform.isFxApplicationThread(); - } - - public static boolean stageObserved() { - return OBSERVED_STAGE.get() != null; - } + void observePrimaryStage(@Observes @PrimaryStage final Stage stage) { + Assertions.assertNotNull(stage); + primaryStageObserved = true; } + void runOnRegularThread() { + regularThread = !Platform.isFxApplicationThread(); + } + + @RunOnFxThread + void runOnFxThread() { + fxThread = Platform.isFxApplicationThread(); + } } diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index a8291d3..4d559e8 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -1,2 +1,3 @@ -* xref:index.adoc[Quarkus FX] -* xref:run-on-fx-thread.adoc[] \ No newline at end of file +* xref:index.adoc[] +* xref:run-on-fx-thread.adoc[] +* xref:startup-synchronization.adoc[] \ No newline at end of file diff --git a/docs/modules/ROOT/pages/includes/attributes.adoc b/docs/modules/ROOT/pages/includes/attributes.adoc index 8edb85a..44f37d9 100644 --- a/docs/modules/ROOT/pages/includes/attributes.adoc +++ b/docs/modules/ROOT/pages/includes/attributes.adoc @@ -1 +1 @@ -:project-version: 0.1.0 \ No newline at end of file +:project-version: 0.2.0 \ No newline at end of file diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 173f349..ad3e321 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -35,7 +35,7 @@ dependencies { The extension allows using cdi features in JavaFX controller classes. + The FXMLLoader is made a CDI bean and can be injected in your application. -[source,java,subs=attributes+] +[source,java] ---- @Inject FXMLLoader fxmlLoader; @@ -48,7 +48,7 @@ public void load() { } ---- -[source,java,subs=attributes+] +[source,java] ---- public class MyFxmlController { @@ -68,7 +68,7 @@ The application will automatically be launched (thanks to a call to `javafx.appl If you need to customize the launch, you can provide a custom `@QuarkusMain`, such as : -[source,java,subs=attributes+] +[source,java] ---- package io.quarkiverse.fx.fxapp; @@ -89,7 +89,7 @@ public class QuarkusFxApplication implements QuarkusApplication { When the application is started, you can get obtain access to the primary `Stage` instance by observing the CDI event : -[source,java,subs=attributes+] +[source,java] ---- void onStart(@Observes @PrimaryStage final Stage stage) { // Do something with the stage diff --git a/docs/modules/ROOT/pages/startup-synchronization.adoc b/docs/modules/ROOT/pages/startup-synchronization.adoc new file mode 100644 index 0000000..96210b3 --- /dev/null +++ b/docs/modules/ROOT/pages/startup-synchronization.adoc @@ -0,0 +1,40 @@ += Startup Latch + +To synchronize the application with JavaFX readiness, one can use the `FxStartupLatch` bean. + +It provides an `await` method that is released when JavaFX app is ready (primary `Stage` instance is available). + +That is used in the inner `@RunOnFxThread` to ensure app is ready. + +[source, java] +---- +@Inject +FxStartupLatch startupLatch; + +// Blocking until FX env is ready +startupLatch.await(); + +// FX is ready +---- + +Alternatively, the `FxStartupEvent` can be observed. + +Example of a `SkipPredicated` that can be used in conjunction with a `@Scheduled` + +[source,java] +---- +@Singleton +public class FxApplicationNotStarted implements SkipPredicate { + + private volatile boolean started; + + void onFxStartup(@Observes final FxStartupEvent event) { + this.started = true; + } + + @Override + public boolean test(final ScheduledExecution execution) { + return !this.started; + } +} +---- \ No newline at end of file diff --git a/runtime/src/main/java/io/quarkiverse/fx/FxApplication.java b/runtime/src/main/java/io/quarkiverse/fx/FxApplication.java index ef66b1d..12a9f43 100644 --- a/runtime/src/main/java/io/quarkiverse/fx/FxApplication.java +++ b/runtime/src/main/java/io/quarkiverse/fx/FxApplication.java @@ -1,8 +1,5 @@ package io.quarkiverse.fx; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.spi.CreationalContext; -import jakarta.enterprise.inject.spi.Bean; import jakarta.enterprise.inject.spi.BeanManager; import jakarta.enterprise.inject.spi.CDI; import jakarta.enterprise.util.AnnotationLiteral; @@ -23,19 +20,16 @@ public class FxApplication extends Application { @Override public void start(final Stage primaryStage) { - // We need to obtain the StartupLatch singleton - BeanManager bm = CDI.current().getBeanManager(); - Bean bean = (Bean) bm.getBeans(StartupLatch.class).iterator().next(); - CreationalContext ctx = bm.createCreationalContext(bean); - StartupLatch started = (StartupLatch) bm.getReference(bean, StartupLatch.class, ctx); + BeanManager beanManager = CDI.current().getBeanManager(); - // Now broadcast the startup event - bm - .getEvent() - .select(new AnnotationLiteral() {}) - .fire(primaryStage); + // Broadcast the stage availability event + beanManager + .getEvent() + .select(new AnnotationLiteral() { + }) + .fire(primaryStage); - // Mark that the application has finished starting. - started.countDown(); + // Fire event that marks that the application has finished starting. + beanManager.getEvent().fire(new FxStartupEvent()); } } diff --git a/runtime/src/main/java/io/quarkiverse/fx/FxStartupEvent.java b/runtime/src/main/java/io/quarkiverse/fx/FxStartupEvent.java new file mode 100644 index 0000000..66bade4 --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/fx/FxStartupEvent.java @@ -0,0 +1,7 @@ +package io.quarkiverse.fx; + +/** + * Event used to indicate that the FX application has started + */ +public class FxStartupEvent { +} diff --git a/runtime/src/main/java/io/quarkiverse/fx/StartupLatch.java b/runtime/src/main/java/io/quarkiverse/fx/FxStartupLatch.java similarity index 54% rename from runtime/src/main/java/io/quarkiverse/fx/StartupLatch.java rename to runtime/src/main/java/io/quarkiverse/fx/FxStartupLatch.java index d4f7e5a..d148fdc 100644 --- a/runtime/src/main/java/io/quarkiverse/fx/StartupLatch.java +++ b/runtime/src/main/java/io/quarkiverse/fx/FxStartupLatch.java @@ -2,20 +2,22 @@ import java.util.concurrent.CountDownLatch; +import jakarta.enterprise.event.Observes; import jakarta.inject.Singleton; /** * A singleton for a startup latch used by {@linkplain FxApplication} and {@linkplain RunOnFxThreadInterceptor} */ @Singleton -public class StartupLatch { - private final CountDownLatch started = new CountDownLatch(1); +public class FxStartupLatch { + + private final CountDownLatch latch = new CountDownLatch(1); public void await() throws InterruptedException { - started.await(); + this.latch.await(); } - public void countDown() { - started.countDown(); + void onFxStartup(@Observes final FxStartupEvent event) { + this.latch.countDown(); } } diff --git a/runtime/src/main/java/io/quarkiverse/fx/PrimaryStage.java b/runtime/src/main/java/io/quarkiverse/fx/PrimaryStage.java index 4720162..6ac487e 100644 --- a/runtime/src/main/java/io/quarkiverse/fx/PrimaryStage.java +++ b/runtime/src/main/java/io/quarkiverse/fx/PrimaryStage.java @@ -12,12 +12,11 @@ /** * This is used as a qualifier as for receiving the notification main start entry point for all * JavaFX applications has been called. - * * It is also used to qualify the CountDownLatch type used by the {@linkplain FxApplication} uses * as a startup notification barrier. * * @see javafx.application.Application#start(Stage) - * @see FxApplication#produceStartedFlag() + * @see FxApplication#start(Stage) */ @Qualifier @Target({ ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD }) diff --git a/runtime/src/main/java/io/quarkiverse/fx/RunOnFxThreadInterceptor.java b/runtime/src/main/java/io/quarkiverse/fx/RunOnFxThreadInterceptor.java index 79f58f2..663c1d6 100644 --- a/runtime/src/main/java/io/quarkiverse/fx/RunOnFxThreadInterceptor.java +++ b/runtime/src/main/java/io/quarkiverse/fx/RunOnFxThreadInterceptor.java @@ -14,16 +14,18 @@ public class RunOnFxThreadInterceptor { private static final Logger LOGGER = Logger.getLogger(RunOnFxThreadInterceptor.class); + // The startup latch signalled by FxApplication @Inject - StartupLatch hasStarted; + FxStartupLatch startupLatch; @AroundInvoke public Object runOnFxThread(final InvocationContext ctx) throws Exception { LOGGER.tracef("intercepted %s on thread %s", ctx.getMethod(), Thread.currentThread()); - // Block any thread until the startup latch has been cleared + + // Block thread until the startup latch has been cleared // This will return immediately after the FxApplication#start has completed - hasStarted.await(); + this.startupLatch.await(); if (Platform.isFxApplicationThread()) { return ctx.proceed(); diff --git a/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/AppController.java b/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/AppController.java index 354318c..86dee08 100644 --- a/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/AppController.java +++ b/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/AppController.java @@ -1,19 +1,16 @@ package io.quarkiverse.fx.sample; -import jakarta.enterprise.context.Dependent; -import jakarta.enterprise.inject.Instance; -import jakarta.inject.Inject; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Singleton; -import javafx.application.Platform; +import io.quarkiverse.fx.RunOnFxThread; +import io.quarkus.logging.Log; import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; import javafx.scene.control.Label; -@Dependent +@Singleton public class AppController { - @Inject - Instance fxmlLoaderInstance; @FXML private Label timeLabel; @@ -22,7 +19,13 @@ private void initialize() { // } - public void onMessage(String timeString) { - Platform.runLater(() -> timeLabel.setText(timeString)); + @RunOnFxThread + public void onMessage(final String timeString) { + this.timeLabel.setText(timeString); + } + + void onMessage(@Observes final TimeEvent timeEvent) { + Log.infof("Time: %s;%s", timeEvent.unixTime(), timeEvent.timeString()); + this.onMessage(timeEvent.timeString()); } } diff --git a/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/ClockEvents.java b/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/ClockEvents.java index ca0cea3..6a6945d 100644 --- a/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/ClockEvents.java +++ b/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/ClockEvents.java @@ -6,18 +6,18 @@ import jakarta.enterprise.event.Event; import jakarta.inject.Inject; -import io.quarkiverse.fx.RunOnFxThread; import io.quarkus.logging.Log; import io.quarkus.scheduler.Scheduled; -import javafx.stage.Stage; /** * A sample CDI bean that receives Quarkus scheduler events that are routed back to the javafx interface */ @ApplicationScoped public class ClockEvents { + @Inject UnixTimeProvider timeProvider; + @Inject Event timeEvent; @@ -28,20 +28,19 @@ public class ClockEvents { * associated with @RunOnFxThread is able to synchronize with the * {@linkplain io.quarkiverse.fx.FxApplication#start(javafx.stage.Stage)} completion. */ - @Scheduled(every = "1s") - @RunOnFxThread + @Scheduled(every = "1s", skipExecutionIf = FxApplicationNotStarted.class) void timeIncrement() { Log.info("timeIncrement"); - sendTimeEvent(); + this.sendTimeEvent(); } private void sendTimeEvent() { try { - long unixTime = timeProvider.getTime(); + long unixTime = this.timeProvider.getTime(); String timeString = String.format("%016x", unixTime); TimeEvent event = new TimeEvent(unixTime, timeString); Log.infof("%d/%s", unixTime, timeString); - timeEvent.fire(event); + this.timeEvent.fire(event); } catch (IOException e) { Log.error("Failed to send TimeEvent", e); } diff --git a/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/FxApplicationNotStarted.java b/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/FxApplicationNotStarted.java new file mode 100644 index 0000000..1a33bb2 --- /dev/null +++ b/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/FxApplicationNotStarted.java @@ -0,0 +1,23 @@ +package io.quarkiverse.fx.sample; + +import jakarta.enterprise.event.Observes; +import jakarta.inject.Singleton; + +import io.quarkiverse.fx.FxStartupEvent; +import io.quarkus.scheduler.Scheduled.SkipPredicate; +import io.quarkus.scheduler.ScheduledExecution; + +@Singleton +public class FxApplicationNotStarted implements SkipPredicate { + + private volatile boolean started; + + void onFxStartup(@Observes final FxStartupEvent event) { + this.started = true; + } + + @Override + public boolean test(final ScheduledExecution execution) { + return !this.started; + } +} diff --git a/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/QuarkusFxApp.java b/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/QuarkusFxApp.java index 2a33553..4712e46 100644 --- a/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/QuarkusFxApp.java +++ b/samples/using-scheduler/src/main/java/io/quarkiverse/fx/sample/QuarkusFxApp.java @@ -19,9 +19,12 @@ public class QuarkusFxApp { @Inject FXMLLoader fxmlLoader; - private AppController appController; - public void start(@Observes @PrimaryStage final Stage stage) { + public void start(@Observes @PrimaryStage final Stage stage) throws InterruptedException { + + // Make it slow on purpose + Thread.sleep(3_000); + Log.info("Begin start"); stage.setOnCloseRequest(event -> { Platform.exit(); @@ -31,7 +34,6 @@ public void start(@Observes @PrimaryStage final Stage stage) { try { InputStream fxml = this.getClass().getResourceAsStream("/app.fxml"); Parent fxmlParent = this.fxmlLoader.load(fxml); - appController = this.fxmlLoader.getController(); Scene scene = new Scene(fxmlParent, 300, 200); stage.setScene(scene); @@ -42,9 +44,4 @@ public void start(@Observes @PrimaryStage final Stage stage) { } Log.info("End start"); } - - void onMessage(@Observes TimeEvent timeEvent) { - Log.infof("Time: %s;%s", timeEvent.unixTime(), timeEvent.timeString()); - appController.onMessage(timeEvent.timeString()); - } }