80: Introduce an explicit Hub-managed JavaFX runtime service
This commit is contained in:
+18
-6
@@ -21,8 +21,8 @@
|
||||
<h1>Nenjim</h1>
|
||||
<p class="status"><strong>Implementation status:</strong> Journal format version 1, the immutable Java model,
|
||||
internal strict parser, read-only <code>JournalService</code> with a Hub-managed process implementation,
|
||||
passive explicitly refreshed <code>JournalServiceManager</code>, and the first central component Registry
|
||||
are implemented. Contexts,
|
||||
passive explicitly refreshed <code>JournalServiceManager</code>, Hub-managed
|
||||
<code>JavaFxRuntimeService</code>, and the first central component Registry are implemented. Contexts,
|
||||
resolution, classloading, discovery, runtime loading/removal, downloading, publishing, synchronization,
|
||||
watching, signatures, permissions, and license filtering remain future work.</p>
|
||||
|
||||
@@ -117,6 +117,14 @@ PREFERRED=nenjim:com_r35157_nenjim-hubd-api=1.0.3:'Built together'</code></pre>
|
||||
<code>.valuetypes</code>, and <code>.exception</code>, while parser, ingestion, chain state, filesystem
|
||||
loading, and the admin boundary live under <code>.impl.ref</code> without leaking through public
|
||||
signatures.</p>
|
||||
<p>All modern JavaFX applications share one JVM-wide toolkit. The Hub constructs one
|
||||
<code>JavaFxRuntimeServiceImpl</code>, registers it as <code>nenjim.javafx-runtime.service</code>, starts it
|
||||
through its <code>NenjimProcess</code> view, and injects its lifecycle-free service view into active Mission
|
||||
Control and Process Manager. All six modern JavaFX implementations require that service in their constructor.
|
||||
Only service startup calls <code>Platform.startup(...)</code>; task submission never initializes or retries
|
||||
JavaFX and is rejected until startup succeeds. Startup waits until the JavaFX Application Thread is ready and
|
||||
sets <code>Platform.setImplicitExit(false)</code>, keeping the shared runtime alive after the last window
|
||||
closes.</p>
|
||||
<pre><code>data/nenjim/journals/
|
||||
└── <artifact-coordinate>/
|
||||
├── <journal-version>.journal
|
||||
@@ -146,11 +154,15 @@ PREFERRED=nenjim:com_r35157_nenjim-hubd-api=1.0.3:'Built together'</code></pre>
|
||||
assembly for <code>data/nenjim/journals</code>, registers <code>nenjim.journal.service</code>, starts Journal
|
||||
second, and records it only after complete successful loading. Only then does the Hub construct the hardcoded
|
||||
business component graph in dependency order. A Journal failure therefore stops startup before Ticker,
|
||||
Evelyn, Burner, Mission Control, the online point, and the blocking wait.</p>
|
||||
Evelyn, Burner, the JavaFX runtime, Mission Control, Process Manager, the online point, and the blocking
|
||||
wait.</p>
|
||||
<p>The Ticker explicitly selects the Raydium EVE/USDT source ID while the hardcoded source remains registered but
|
||||
inactive. After Registry and Journal, the Hub activates only the existing explicit five-business-process
|
||||
sequence; it does not start every registered process. Successful process IDs are exposed as immutable ordered
|
||||
point-in-time snapshots, without implying continued health.</p>
|
||||
inactive. After Registry and Journal, the Hub activates the explicit Ticker, Production Evelyn, Test Evelyn,
|
||||
Production IOU Burner, JavaFX runtime service, Mission Control, and Process Manager sequence; it does not
|
||||
start every registered process. The shared JavaFX runtime is therefore active before either enabled JavaFX
|
||||
application. Successful process IDs, including <code>nenjim.javafx-runtime.service</code>, are exposed as
|
||||
immutable ordered point-in-time snapshots without implying continued health. A JavaFX startup failure is
|
||||
fail-fast and prevents Mission Control and Process Manager from starting.</p>
|
||||
<p><code>com.r35157.nenjim.hubd.Main</code> remains only the outer Java bootstrap. It constructs the reference Hub
|
||||
through the public <code>NenjimHub</code> interface and calls <code>start()</code> once. The Hub is neither a
|
||||
component nor a process and is not registered inside its own Registry.</p>
|
||||
|
||||
+17
-6
@@ -2,8 +2,8 @@
|
||||
|
||||
> **Dokumentstatus:** Journal-format version 1, den immutable Java-model, den interne tekstparser, det read-only
|
||||
> `JournalService` med en Hub-styret procesimplementation, den passive eksplicit refresh-baserede
|
||||
> `JournalServiceManager` og første version af det centrale component Registry er implementeret. Contexts,
|
||||
> resolution, discovery, runtime loading og removal er fortsat fremtidig funktionalitet.
|
||||
> `JournalServiceManager`, den Hub-styrede `JavaFxRuntimeService` og første version af det centrale component Registry
|
||||
> er implementeret. Contexts, resolution, discovery, runtime loading og removal er fortsat fremtidig funktionalitet.
|
||||
|
||||
**Indholdsfortegnelse:**
|
||||
<!-- TOC -->
|
||||
@@ -219,6 +219,14 @@ Journal-objekt og en separat passiv `JournalServiceManager`. Journal-manageren r
|
||||
til root og refresh gennem et package-private admin-view; almindelige `JournalService`-consumers kan ikke udlede
|
||||
administrationsadgangen.
|
||||
|
||||
Alle moderne JavaFX-applikationer deler ét JVM-wide JavaFX toolkit. Det repræsenteres af
|
||||
`JavaFxRuntimeService`, mens samme `JavaFxRuntimeServiceImpl`-objekt også implementerer `NenjimProcess` for Hub'ens
|
||||
lifecycle. Hub'en konstruerer og registrerer objektet som `nenjim.javafx-runtime.service`, starter det før de aktive
|
||||
JavaFX-applikationer og injicerer service-viewet i dem gennem deres constructors. Kun processens `start()` må kalde
|
||||
`Platform.startup(...)`; `runLater(...)` starter aldrig toolkit'et implicit og afviser arbejde, indtil startup er
|
||||
lykkedes. Startup venter på JavaFX Application Thread og sætter `Platform.setImplicitExit(false)`, så den delte
|
||||
runtime forbliver aktiv, når det sidste vindue lukkes.
|
||||
|
||||
Den package-private `NenjimRegistryServiceAdmin` er managerens interne mutationsadgang. Den må ikke gøres public,
|
||||
fordi almindelige consumers ellers kunne caste deres Registry-service til mutationsinterfacet og bryde det
|
||||
read-only service contract. Registration starter ikke et objekt, og Registry'et forbliver internt åbent for senere
|
||||
@@ -229,10 +237,13 @@ Applikationen ejer sine konkrete valg og sin activation. Tickerens initiale conf
|
||||
inaktiv. Hub'en starter Registry-processen først og registrerer den som `nenjim.registry.service`. Journal-servicen
|
||||
registreres dernæst som `nenjim.journal.service`, startes som den anden proces og skal være fuldt initialiseret, før
|
||||
business graph'en fortsætter. Derefter starter Hub'en kun den nuværende eksplicitte, dependency-safe liste: Ticker,
|
||||
Production Evelyn, Test Evelyn, Production IOU Burner og Mission Control. Alarmen og øvrige tidligere
|
||||
udkommenterede applikationer bliver ikke automatisk aktiveret. Efter hvert succesfuldt `start()` registrerer Hub'en
|
||||
process-ID'et i et immutable, ordered snapshot; et fejlet start bliver ikke registreret, og listen lover ikke fortsat
|
||||
health eller liveness. Startup er fail-fast og kan kun forsøges én gang uden generic rollback.
|
||||
Production Evelyn, Test Evelyn, Production IOU Burner, JavaFX-runtime-servicen, Mission Control og Process Manager.
|
||||
JavaFX-runtime-servicen startes altså før begge aktive JavaFX-applikationer; Composer, Test Tool, Soda Task Manager
|
||||
og Suwimo Client er fortsat inaktive, men bruger samme constructor-injection model. Alarmen bliver heller ikke
|
||||
automatisk aktiveret. Efter hvert succesfuldt `start()` registrerer Hub'en process-ID'et i et immutable, ordered
|
||||
snapshot, inklusive `nenjim.javafx-runtime.service`; et fejlet start bliver ikke registreret, og listen lover ikke
|
||||
fortsat health eller liveness. JavaFX-startfejl stopper opstarten før Mission Control og Process Manager. Startup er
|
||||
fail-fast og kan kun forsøges én gang uden generic rollback.
|
||||
|
||||
`com.r35157.nenjim.hubd.Main` er kun det yderste Java-bootstrap. `Main.main(...)` konstruerer `NenjimHubImpl` gennem
|
||||
det globale `NenjimHub`-interface og kalder `start()` præcis én gang. Hub'en ejer også det eksisterende online point
|
||||
|
||||
+5
-3
@@ -1,8 +1,8 @@
|
||||
# Tekniske termer
|
||||
|
||||
> **Dokumentstatus:** Journal-format version 1 og de tilhørende Java-typer, den interne parser, query-service,
|
||||
> Hub-styret Journal-proces, passive service-managers og første version af det centrale component Registry er
|
||||
> implementeret. Resolver-, Context-,
|
||||
> Hub-styret Journal-proces, JavaFX-runtime-service, passive service-managers og første version af det centrale
|
||||
> component Registry er implementeret. Resolver-, Context-,
|
||||
> classloader-, discovery-, runtime-loading-, retrieval- og publishing-termer beskriver kun fremtidig vision, når
|
||||
> det er angivet.
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
| Nenjim component | En færdigkonstrueret Nenjim-byggeklods, som er klar til brug. `NenjimComponent` er en ren marker uden ID eller lifecycle state. Service, applikation, algoritme og adapter beskriver roller, mens process beskriver en uafhængig lifecycle-klassifikation. |
|
||||
| Applikation | En user-facing eller orchestration-rolle. Rollen gør ikke i sig selv en component til en process; en aktiv applikationsimplementation kan separat implementere `NenjimProcess`. |
|
||||
| NenjimProcess | Det canonical `com.r35157.nenjim.process.NenjimProcess` component interface for noget, som Hub'en kan starte og tracke. Det eksponerer kun `start()` og lover ikke stop, restart, health, Context eller dependency lookup. |
|
||||
| NenjimHub | Den globale ejer af én kørende Nenjim runtime. Hub'en konstruerer og registrerer component graph'en, ejer dependency wiring og startup-policy, tracker succesfuldt startede process-ID'er, erklærer online point og ejer den afsluttende runtime wait. Den er hverken component eller process og registreres ikke i sit eget Registry. |
|
||||
| NenjimHub | Den globale ejer af én kørende Nenjim runtime. Hub'en konstruerer og registrerer component graph'en, ejer dependency wiring og startup-policy, tracker succesfuldt startede process-ID'er, starter den fælles JavaFX-runtime før dens applikationer, erklærer online point og ejer den afsluttende runtime wait. Den er hverken component eller process og registreres ikke i sit eget Registry. |
|
||||
| NenjimComponentId | Canonical lower-case Registry lookup identity med dot-separated segments og valgfrie interne bindestreger. ID'et er registration metadata i ét Registry, ikke object state, authorization eller artifact-version identity. |
|
||||
| NenjimRegistryService | Read-only component-katalog for almindelige consumers. Det giver immutable registration-order ID snapshots og typed lookup, men ingen registration, removal, loading, lifecycle, permission eller persistence administration. |
|
||||
| NenjimRegistryServiceAdmin | Package-private mutationsinterface i referenceimplementationen. Det er ikke en component, er ikke indexeret og må ikke gøres public, fordi almindelige consumers ellers kunne caste Registry-serviceobjektet til mutationsinterfacet. |
|
||||
@@ -41,6 +41,8 @@
|
||||
| JournalService | Registrerbart, filesystem-uafhængigt og read-only Service-API for current og historical Journal-queries. Det eksponerer ingen lifecycle-, path-, refresh-, parser- eller mutationsoperationer, og queries er gated indtil vellykket proces-startup. |
|
||||
| JournalServiceManager | Passivt, uregistreret administrationshandle for én Journal-service med kun normalized data root og explicit refresh. Hub'en beholder det; det har ingen start, stop, service-accessor eller query-operationer. |
|
||||
| Journal referenceimplementation | Ét package-private `JournalServiceImpl`-objekt er både `JournalService` og `NenjimProcess`; en separat `JournalServiceManagerImpl`, intern admin-view, parser, ingestion, chain state og filesystem-loader ligger under `com.r35157.nenjim.service.journal.impl.ref` uden at lække gennem public signatures. |
|
||||
| JavaFX runtime | Det ene JVM-wide JavaFX toolkit, som udfører UI-arbejde på JavaFX Application Thread. Hub'en ejer dets eneste startup-sti gennem `JavaFxRuntimeServiceImpl` og holder det aktivt efter sidste lukkede vindue med `Platform.setImplicitExit(false)`. |
|
||||
| JavaFxRuntimeService | Registrerbart service-view med kun non-null `runLater(Runnable)`. Samme referenceimplementation er en Hub-styret `NenjimProcess`; service-consumers får den gennem constructor injection, men får ingen lifecycle-, status-, configuration-, window-, event-, stop- eller restart-operationer. |
|
||||
| Context | Fremtidig beskrivelse af ønskede/resolverede artifactvalg og isoleret runtime; ikke implementeret af Journal-laget eller første Registry-version. |
|
||||
| Resolver | Fremtidig komponent for dependency graph og release-selection; Journal-laget repræsenterer reglerne, men vælger ikke en graf. |
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-02
|
||||
@@ -0,0 +1,102 @@
|
||||
## Context
|
||||
|
||||
`JavaFxRuntime.runLater(...)` currently combines lazy JavaFX toolkit startup
|
||||
with task submission. Mission Control and Process Manager use it in the active
|
||||
Hub graph; Composer, Test Tool, Soda Task Manager, and Suwimo Client use it in
|
||||
dormant modern paths. Consequently, any consumer can create global runtime
|
||||
state without a registered component or Hub lifecycle milestone.
|
||||
|
||||
The Registry already indexes one registered object through every implemented
|
||||
component interface, and the Hub already starts selected `NenjimProcess`
|
||||
objects in a hardcoded dependency-safe order and records IDs only after
|
||||
successful return. The new runtime uses those existing mechanisms.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- Represent the one JVM-wide JavaFX toolkit as an explicit service object.
|
||||
- Keep lifecycle control out of the public service API.
|
||||
- Permit exactly one startup attempt and publish availability only after the
|
||||
startup callback configures JavaFX successfully.
|
||||
- Reject every submission until successful startup and submit accepted work
|
||||
only through `Platform.runLater(...)`.
|
||||
- Inject one shared service into all six modern JavaFX consumers.
|
||||
- Start and record the runtime before both enabled JavaFX applications.
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- Stop, restart, recovery, supervision, health, status, configuration, events,
|
||||
window discovery/management, automatic dependency startup, Contexts,
|
||||
classloading, headless JavaFX, or replacement of the JavaFX UI APIs.
|
||||
- Refactoring unrelated legacy prototype code or adding a general test suite.
|
||||
|
||||
## Decisions
|
||||
|
||||
### One object has separate service and process views
|
||||
|
||||
`JavaFxRuntimeService` extends only `NenjimComponent` and exposes only
|
||||
`runLater(Runnable)`. The public `JavaFxRuntimeServiceImpl` implements that
|
||||
service plus `NenjimProcess`. A single Registry registration therefore indexes
|
||||
the identical object through both interfaces without exposing lifecycle on the
|
||||
service contract or requiring a manager/admin API.
|
||||
|
||||
### Startup is strict, synchronous at the milestone, and non-recovering
|
||||
|
||||
An atomic guard is consumed before any JavaFX call, so a second `start()`
|
||||
fails even after an interrupted or failed first attempt. `start()` calls
|
||||
`Platform.startup(...)` exactly once. Its callback sets implicit exit to false,
|
||||
captures callback failure, and releases a latch. The caller waits on that latch
|
||||
before opening a volatile availability gate or returning.
|
||||
|
||||
Synchronous toolkit failures, including an externally initialized toolkit,
|
||||
are wrapped with a service-specific startup diagnostic. Callback failure and
|
||||
interruption likewise leave availability closed. No path accepts an existing
|
||||
external toolkit, retries initialization, or rolls JavaFX back.
|
||||
|
||||
### Submission is gated and remains asynchronous
|
||||
|
||||
`runLater(...)` validates the task before checking availability, so null is
|
||||
always reported as a clear caller error. An unavailable service rejects work
|
||||
without invoking any JavaFX API. After startup it delegates directly to
|
||||
`Platform.runLater(...)`; the service never invokes application work itself,
|
||||
and failures inside queued UI work remain application failures.
|
||||
|
||||
### Hub ordering expresses the dependency
|
||||
|
||||
During application composition the Hub creates one runtime implementation and
|
||||
registers it as `nenjim.javafx-runtime.service`. The same service reference is
|
||||
injected into Mission Control and Process Manager. In the fixed startup list,
|
||||
the runtime follows the non-JavaFX business processes and precedes Mission
|
||||
Control and Process Manager. The common `startProcess` helper records its ID
|
||||
only after successful return. A runtime failure therefore propagates before
|
||||
either dependent JavaFX process starts.
|
||||
|
||||
### Dormant consumers have no compatibility path
|
||||
|
||||
Composer, Test Tool, Soda Task Manager, and Suwimo Client receive the service
|
||||
through their only constructors even though the active Hub does not currently
|
||||
construct them. All six consumers retain that dependency and replace static
|
||||
submission with the injected service. Removing the old utility then makes an
|
||||
implicit modern initialization path impossible at compile time.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [JavaFX is JVM-global and cannot be restarted] → The service deliberately
|
||||
accepts one attempt and treats prior external initialization as failure.
|
||||
- [Startup callback execution timing is platform-controlled] → A latch makes
|
||||
the successful service milestone explicit before `start()` returns.
|
||||
- [Interrupting startup can leave a JVM toolkit that later finishes] → The
|
||||
service remains unavailable and non-retryable; no unsupported rollback or
|
||||
recovery semantics are claimed.
|
||||
- [Queued UI work can fail later] → Task execution remains application-owned
|
||||
and is not confused with the already completed runtime startup milestone.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Introduce the service/process implementation and migrate all six consumers.
|
||||
2. Register, inject, start, and record the shared object in Hub order.
|
||||
3. Delete the static helper and audit modern sources for initialization paths.
|
||||
4. Update documentation and this focused active OpenSpec change.
|
||||
5. Strictly validate OpenSpec, build the repository, and use disposable probes
|
||||
for lifecycle guards without starting production `main()`.
|
||||
@@ -0,0 +1,43 @@
|
||||
## Why
|
||||
|
||||
Modern JavaFX applications currently submit work through a static helper that
|
||||
silently initializes the JVM-wide toolkit. That hides an active shared
|
||||
lifecycle outside the Registry, Hub startup order, constructor dependency
|
||||
graph, and started-process snapshot established by issues #77 through #79.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add a minimal `JavaFxRuntimeService` component API with only non-null
|
||||
asynchronous JavaFX Application Thread submission.
|
||||
- Add one reference object that is both the service and a single-attempt
|
||||
`NenjimProcess`; only its `start()` initializes JavaFX, waits for readiness,
|
||||
and disables implicit exit.
|
||||
- Construct and register that object as `nenjim.javafx-runtime.service`, start
|
||||
it before enabled JavaFX applications, and record it through the existing
|
||||
successful-start mechanism.
|
||||
- Constructor-inject the same service into Mission Control and Process Manager
|
||||
and migrate the four dormant modern JavaFX applications to the same model.
|
||||
- Remove the static `JavaFxRuntime` utility and document explicit runtime
|
||||
ownership and ordering.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Added Capabilities
|
||||
|
||||
- `javafx-runtime-service`: define the public service, process lifecycle,
|
||||
submission rules, consumer injection, and deliberately absent operations.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `nenjim-component-registry`: add Hub construction, registration, ordering,
|
||||
fail-fast behavior, and started-process tracking for the JavaFX runtime.
|
||||
- `nenjim-process-manager`: replace the implicit static runtime dependency with
|
||||
the injected service while preserving polling and presentation behavior.
|
||||
|
||||
## Impact
|
||||
|
||||
Affected code is limited to the JavaFX runtime boundary, six existing modern
|
||||
JavaFX consumers, Hub composition/startup, directly relevant documentation,
|
||||
and this active OpenSpec change. No configuration, dependency discovery,
|
||||
events, stop/restart lifecycle, window management, health monitoring,
|
||||
headless emulation, or broad legacy migration is introduced.
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: JavaFX runtime service separates use from lifecycle
|
||||
`com.r35157.service.javafx.JavaFxRuntimeService` SHALL be a public `NenjimComponent` interface exposing exactly `void runLater(@NotNull Runnable runnable)` and no initialization, shutdown, restart, status, configuration, window-management, event, or lifecycle operation. `com.r35157.service.javafx.impl.ref.JavaFxRuntimeServiceImpl` SHALL implement both that service and the canonical `NenjimProcess`, so one object supplies the ordinary service view and the Hub-owned process view without a separate manager or administration interface.
|
||||
|
||||
#### Scenario: Consumer receives only the service view
|
||||
- **WHEN** an ordinary JavaFX application receives a `JavaFxRuntimeService`
|
||||
- **THEN** it can submit non-null JavaFX work but cannot start, stop, restart, configure, inspect, or administer the runtime through that API
|
||||
|
||||
#### Scenario: One implementation supplies both views
|
||||
- **WHEN** one `JavaFxRuntimeServiceImpl` is registered once
|
||||
- **THEN** Registry lookup through `JavaFxRuntimeService` and `NenjimProcess` returns the identical object reference
|
||||
|
||||
### Requirement: JavaFX runtime startup is one-attempt and complete
|
||||
The implementation's `start()` SHALL be the only operation that calls `Platform.startup(...)`. It SHALL accept only the first attempt, configure `Platform.setImplicitExit(false)` on the JavaFX startup callback, wait for that callback to complete successfully, and open service availability only before returning successfully. Every repeated attempt SHALL fail with `IllegalStateException`, including after first-attempt failure or interruption. A synchronous or callback initialization failure SHALL propagate with a clear service-startup diagnostic, leave the service unavailable, and SHALL not be retried, recovered, restarted, rolled back, or silently accepted as an externally initialized toolkit.
|
||||
|
||||
#### Scenario: First startup succeeds
|
||||
- **WHEN** JavaFX startup invokes its callback and implicit exit is disabled successfully
|
||||
- **THEN** `start()` returns only after that callback completes and the service becomes available
|
||||
|
||||
#### Scenario: Startup callback has not completed
|
||||
- **WHEN** `Platform.startup(...)` has accepted initialization but its callback has not completed
|
||||
- **THEN** `start()` has not returned successfully and task submission remains unavailable
|
||||
|
||||
#### Scenario: Startup fails
|
||||
- **WHEN** toolkit initialization or callback configuration fails
|
||||
- **THEN** `start()` propagates a clear failure and the service remains unavailable without partial success
|
||||
|
||||
#### Scenario: Startup is repeated
|
||||
- **WHEN** `start()` is called after any earlier attempt began
|
||||
- **THEN** it fails with `IllegalStateException` without calling `Platform.startup(...)` again
|
||||
|
||||
#### Scenario: Last window closes
|
||||
- **WHEN** the last JavaFX window closes after successful service startup
|
||||
- **THEN** disabled implicit exit keeps the shared JavaFX runtime alive for later submitted work
|
||||
|
||||
### Requirement: JavaFX task submission is non-null gated and asynchronous
|
||||
`runLater(...)` SHALL reject null with `NullPointerException`. Before successful startup and after failed startup it SHALL reject every non-null task with `IllegalStateException` without initializing, starting, or retrying JavaFX and without executing the task. After successful startup it SHALL submit the exact task through `Platform.runLater(...)` and SHALL never execute it synchronously on the caller's thread. Acceptance SHALL make no guarantee that application UI work completes successfully, and exceptions raised by that work SHALL not change the completed runtime startup milestone.
|
||||
|
||||
#### Scenario: Null task is submitted
|
||||
- **WHEN** `runLater(null)` is called in any service state
|
||||
- **THEN** it fails with `NullPointerException` identifying the null task
|
||||
|
||||
#### Scenario: Task is submitted before successful startup
|
||||
- **WHEN** a caller submits a non-null task before `start()` succeeds or after its failure
|
||||
- **THEN** submission fails with `IllegalStateException` without any JavaFX initialization or synchronous task execution
|
||||
|
||||
#### Scenario: Task is accepted after startup
|
||||
- **WHEN** a non-null task is submitted after successful startup
|
||||
- **THEN** the service passes it to `Platform.runLater(...)` for asynchronous execution on the JavaFX Application Thread
|
||||
|
||||
#### Scenario: Application task fails
|
||||
- **WHEN** accepted UI work later throws on the JavaFX Application Thread
|
||||
- **THEN** that remains an application failure and does not convert the service's successful startup into a runtime startup failure
|
||||
|
||||
### Requirement: Modern JavaFX consumers require the shared service
|
||||
`EvelynMissionControlImpl`, `NenjimProcessManagerImpl`, `NenjimComposerImpl`, `NenjimTestToolImpl`, `SodaTaskManagerImpl`, and `SuwimoClientImpl` SHALL each require, reject null for, retain, and use a constructor-injected `JavaFxRuntimeService` for JavaFX Application Thread submission. No compatibility constructor, static fallback, direct `Platform.startup(...)`, lazy initialization helper, or competing initialization flag SHALL remain in the modern component graph. Direct ordinary JavaFX UI API use inside application code SHALL remain valid.
|
||||
|
||||
#### Scenario: Consumer dependency is null
|
||||
- **WHEN** any modern JavaFX implementation is constructed with a null runtime service
|
||||
- **THEN** construction fails with `NullPointerException`
|
||||
|
||||
#### Scenario: Consumer starts UI work
|
||||
- **WHEN** any modern JavaFX consumer requests window creation or JavaFX publication
|
||||
- **THEN** it submits through its retained service and neither initializes JavaFX nor falls back to static behavior
|
||||
|
||||
#### Scenario: Static initialization path is inspected
|
||||
- **WHEN** modern source is searched after migration
|
||||
- **THEN** `com.r35157.libs.javafx.JavaFxRuntime` is absent and only `JavaFxRuntimeServiceImpl.start()` calls `Platform.startup(...)`
|
||||
|
||||
### Requirement: Deferred JavaFX runtime features remain absent
|
||||
The JavaFX runtime service SHALL represent one JVM-wide toolkit and SHALL not implement multiple toolkits, headless emulation, toolkit replacement, shutdown, stop, restart, recovery, supervision, health monitoring, runtime status queries, window discovery or management, configuration, Context resolution, automatic dependency discovery/startup, lifecycle events, or subscriptions. This change SHALL not add `NenjimHub.conf` or configurable autorun.
|
||||
|
||||
#### Scenario: Runtime boundary is inspected
|
||||
- **WHEN** the delivered service, process implementation, Hub integration, and application constructors are examined
|
||||
- **THEN** only explicit one-time startup, asynchronous task submission, fixed Hub ordering, and dependency injection are present
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: NenjimHub owns composition and runtime startup
|
||||
`NenjimHub` SHALL be the global runtime owner and SHALL expose `start() throws Exception` plus an immutable ordered snapshot of successfully started process IDs. It SHALL be neither a component nor a process and SHALL not register itself. Its only accepted startup attempt SHALL construct the Registry subsystem, start the Registry process first, register it as `nenjim.registry.service`, construct and register the Journal subsystem as `nenjim.journal.service`, start Journal second, and continue to the complete hardcoded business component set from issue #76 only after Journal startup succeeds. During application composition the Hub SHALL construct exactly one `JavaFxRuntimeServiceImpl`, register it as `nenjim.javafx-runtime.service`, inject its service view into Mission Control and Process Manager, construct the Process Manager with itself, and register that manager as `nenjim.process-manager.default`. The Hub SHALL resolve only explicitly enabled IDs through `NenjimProcess`, start the JavaFX runtime service after enabled non-JavaFX business processes and before Mission Control and Process Manager, start Process Manager last, announce the existing online point, and preserve the existing blocking wait. Registration SHALL never imply startup, and the Registry and Journal managers SHALL remain distinct passive, unregistered handles retained privately by the Hub.
|
||||
|
||||
#### Scenario: Registry starts before graph construction
|
||||
- **WHEN** the Hub performs its first startup attempt
|
||||
- **THEN** it starts, registers, and records Registry first before constructing Journal or components in the business graph
|
||||
|
||||
#### Scenario: Journal starts before business composition
|
||||
- **WHEN** Registry startup and self-registration have succeeded
|
||||
- **THEN** the Hub registers, starts, and records Journal before constructing components in the business graph
|
||||
|
||||
#### Scenario: Registry is process-discoverable
|
||||
- **WHEN** the Registry registration has completed
|
||||
- **THEN** the same object is retrievable as both `NenjimRegistryService` and `NenjimProcess`
|
||||
|
||||
#### Scenario: Journal is process-discoverable
|
||||
- **WHEN** the Journal registration has completed
|
||||
- **THEN** the same object is retrievable as both `JournalService` and `NenjimProcess` under `nenjim.journal.service`, while its manager is not registered
|
||||
|
||||
#### Scenario: JavaFX runtime is process-discoverable
|
||||
- **WHEN** Hub-owned application composition has completed
|
||||
- **THEN** one object is retrievable as both `JavaFxRuntimeService` and `NenjimProcess` under `nenjim.javafx-runtime.service`
|
||||
|
||||
#### Scenario: JavaFX applications share the runtime
|
||||
- **WHEN** Hub-owned application composition has completed
|
||||
- **THEN** Mission Control and Process Manager retain the same `JavaFxRuntimeService` instance registered by the Hub
|
||||
|
||||
#### Scenario: Process Manager is process-discoverable
|
||||
- **WHEN** Hub-owned application composition has completed
|
||||
- **THEN** the object constructed with the current Hub and shared JavaFX runtime is retrievable as both `NenjimProcessManager` and `NenjimProcess` under `nenjim.process-manager.default`
|
||||
|
||||
#### Scenario: Required hardcoded bindings are registered
|
||||
- **WHEN** Hub composition completes
|
||||
- **THEN** Journal, the JavaFX runtime, the Process Manager, and all component IDs and implementation choices enumerated by issue #76 are present exactly as configured, including intentionally inactive components and excluding both service managers
|
||||
|
||||
#### Scenario: Explicit startup subset preserves order
|
||||
- **WHEN** Hub startup succeeds through the Process Manager phase
|
||||
- **THEN** successful process order is `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `evelyn.service.prod`, `evelyn.service.test`, `evelyn.iou-burner.prod`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
||||
|
||||
#### Scenario: Deliberately inactive processes remain inactive
|
||||
- **WHEN** Hub startup reaches its online point
|
||||
- **THEN** `jupiter-perps-alarm.default` and the dormant Composer, Test Tool, Soda Task Manager, and Suwimo Client paths have not been started
|
||||
|
||||
#### Scenario: Journal failure is fail-fast
|
||||
- **WHEN** Journal process startup throws
|
||||
- **THEN** Registry remains the only recorded process, no business component is constructed or started, the exception propagates, and the Hub neither announces online nor enters its final wait
|
||||
|
||||
#### Scenario: JavaFX runtime failure is fail-fast
|
||||
- **WHEN** JavaFX runtime service startup throws
|
||||
- **THEN** its ID is not recorded, Mission Control and Process Manager do not start, the exception propagates, and the Hub neither announces online nor enters its final wait
|
||||
|
||||
#### Scenario: Hub start is not retried
|
||||
- **WHEN** any first Hub startup attempt has begun and `start()` is requested again
|
||||
- **THEN** the later request fails with `IllegalStateException` whether the first attempt succeeded or failed
|
||||
|
||||
#### Scenario: Startup failure is fail-fast
|
||||
- **WHEN** any process startup throws
|
||||
- **THEN** the exception propagates, no later process starts, the Hub neither announces online nor enters its final wait, and no generic rollback or retry is attempted
|
||||
|
||||
### Requirement: Started-process snapshots are ordered immutable and truthful
|
||||
`NenjimHub.getStartedProcessIds()` SHALL be safe before, during, and after startup and SHALL return an immutable point-in-time snapshot in successful startup order. A process ID SHALL be appended only after that process's `start()` call returns successfully, including `nenjim.javafx-runtime.service` and `nenjim.process-manager.default`. Earlier successful entries SHALL remain visible after a later failure, while a failing process SHALL never be included. The snapshot SHALL make no health or continued-liveness guarantee.
|
||||
|
||||
#### Scenario: Snapshot before process startup
|
||||
- **WHEN** the snapshot is requested before any process has started successfully
|
||||
- **THEN** it is an empty immutable list
|
||||
|
||||
#### Scenario: Successful process is recorded
|
||||
- **WHEN** a process's `start()` call returns successfully
|
||||
- **THEN** its ID appears after all previously successful process IDs in every later snapshot
|
||||
|
||||
#### Scenario: JavaFX runtime uses the common recording rule
|
||||
- **WHEN** JavaFX runtime service `start()` returns successfully
|
||||
- **THEN** the Hub records `nenjim.javafx-runtime.service` through the same post-start rule used for every other process
|
||||
|
||||
#### Scenario: Process Manager uses the common recording rule
|
||||
- **WHEN** Process Manager `start()` returns successfully
|
||||
- **THEN** the Hub records `nenjim.process-manager.default` through the same post-start rule used for every other process
|
||||
|
||||
#### Scenario: Failed process is not recorded
|
||||
- **WHEN** a process's `start()` call throws
|
||||
- **THEN** its ID is absent while all earlier successful process IDs remain visible
|
||||
|
||||
#### Scenario: Snapshot is a point-in-time value
|
||||
- **WHEN** the Hub starts another process after a caller obtained a snapshot
|
||||
- **THEN** the earlier snapshot is unchanged and cannot be mutated by its caller
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Process Manager is a Hub-managed process
|
||||
`com.r35157.nenjim.npm.NenjimProcessManager` SHALL extend the canonical `NenjimProcess` and SHALL add no public refresh, polling, event, start, stop, or query operation. `NenjimProcessManagerImpl` SHALL require and retain its owning non-null `NenjimHub` as the sole source of displayed process status and the non-null shared `JavaFxRuntimeService` as the sole JavaFX Application Thread submission path.
|
||||
|
||||
#### Scenario: Process Manager is used through the common lifecycle
|
||||
- **WHEN** the registered Process Manager is resolved through `NenjimProcess`
|
||||
- **THEN** its inherited no-argument `start()` is the only public lifecycle operation used by the Hub
|
||||
|
||||
#### Scenario: Owning Hub is required
|
||||
- **WHEN** reference Process Manager construction receives a null Hub
|
||||
- **THEN** construction fails with `NullPointerException` before startup
|
||||
|
||||
#### Scenario: JavaFX runtime service is required
|
||||
- **WHEN** reference Process Manager construction receives a null JavaFX runtime service
|
||||
- **THEN** construction fails with `NullPointerException` before startup
|
||||
|
||||
#### Scenario: Public API remains minimal
|
||||
- **WHEN** a consumer examines `NenjimProcessManager`
|
||||
- **THEN** it exposes no Process Manager-specific refresh, polling, event, stop, status, or query method
|
||||
|
||||
### Requirement: Process Manager polls immediately on one non-blocking worker
|
||||
On its accepted startup attempt, the Process Manager SHALL request the existing window through its injected already-started `JavaFxRuntimeService` without initializing or starting JavaFX, then start exactly one dedicated background polling loop. The loop SHALL request `NenjimHub.getStartedProcessIds()` immediately and again after each normal three-second interval. Polling work SHALL not execute on the JavaFX Application Thread, and the worker SHALL be a daemon that does not prevent JVM termination.
|
||||
|
||||
#### Scenario: JavaFX prerequisite is unavailable
|
||||
- **WHEN** Process Manager startup submits its window before the injected JavaFX runtime service has started successfully
|
||||
- **THEN** service rejection propagates as Process Manager startup failure and no polling worker starts
|
||||
|
||||
#### Scenario: First snapshot is immediate
|
||||
- **WHEN** Process Manager startup schedules its JavaFX window and starts its polling worker
|
||||
- **THEN** the worker requests a Hub snapshot before waiting for the first three-second interval
|
||||
|
||||
#### Scenario: Polling continues at the required interval
|
||||
- **WHEN** one polling attempt completes without interruption
|
||||
- **THEN** the same worker waits three seconds and performs the next attempt
|
||||
|
||||
#### Scenario: Polling is not JavaFX work
|
||||
- **WHEN** the Hub snapshot is queried and mapped
|
||||
- **THEN** that work runs on the dedicated background polling thread rather than the JavaFX Application Thread
|
||||
|
||||
#### Scenario: Worker does not own JVM lifetime
|
||||
- **WHEN** only the Process Manager polling worker would otherwise keep the JVM alive
|
||||
- **THEN** its daemon status permits normal JVM termination
|
||||
|
||||
### Requirement: Successful snapshots replace the JavaFX list exactly
|
||||
Each successful poll SHALL map every returned `NenjimComponentId` to its exact `value()` string in Hub-provided order and SHALL publish that complete point-in-time list through the injected `JavaFxRuntimeService`. Every JavaFX control mutation SHALL execute on the JavaFX Application Thread, and publication SHALL replace the complete displayed list rather than infer additions or removals. The existing title, dimensions, and multiple-selection behavior MAY remain unchanged, and no hardcoded process rows SHALL remain.
|
||||
|
||||
#### Scenario: Ordered ID values are displayed
|
||||
- **WHEN** the Hub returns an ordered snapshot of component IDs
|
||||
- **THEN** the JavaFX list is replaced with the corresponding `value()` strings in the same order
|
||||
|
||||
#### Scenario: A later snapshot removes an ID
|
||||
- **WHEN** a successful later snapshot omits an ID present in the previously displayed snapshot
|
||||
- **THEN** complete replacement removes that string without the Process Manager maintaining authoritative lifecycle state
|
||||
|
||||
#### Scenario: Process Manager eventually displays itself
|
||||
- **WHEN** the Hub records `nenjim.process-manager.default` after its `start()` returns successfully
|
||||
- **THEN** a later successful poll displays that ID in the position supplied by the Hub
|
||||
|
||||
#### Scenario: JavaFX controls remain thread-confined
|
||||
- **WHEN** the window is created or a successful snapshot is published
|
||||
- **THEN** all JavaFX control creation and mutation is submitted through the retained runtime service for execution on the JavaFX Application Thread
|
||||
@@ -0,0 +1,24 @@
|
||||
## 1. JavaFX runtime service
|
||||
|
||||
- [x] 1.1 Add the minimal `JavaFxRuntimeService` component API with only non-null `runLater(Runnable)`.
|
||||
- [x] 1.2 Implement one public reference object as both service and `NenjimProcess` with strict single-attempt startup.
|
||||
- [x] 1.3 Wait for toolkit readiness, disable implicit exit, gate availability, and delegate accepted work asynchronously.
|
||||
|
||||
## 2. Consumer and Hub migration
|
||||
|
||||
- [x] 2.1 Constructor-inject and retain the runtime service in all six modern JavaFX consumers with no compatibility constructors.
|
||||
- [x] 2.2 Construct and register one runtime object as `nenjim.javafx-runtime.service`, preserving service/process identity.
|
||||
- [x] 2.3 Start and record the runtime through the common Hub path before Mission Control and Process Manager.
|
||||
- [x] 2.4 Remove the static JavaFX runtime helper and audit modern sources for competing initialization paths.
|
||||
|
||||
## 3. Specification and documentation
|
||||
|
||||
- [x] 3.1 Add the focused JavaFX runtime service capability and only the affected Registry and Process Manager deltas.
|
||||
- [x] 3.2 Document shared JVM ownership, constructor injection, explicit startup, ordering, started-process visibility, and implicit-exit behavior.
|
||||
|
||||
## 4. Verification
|
||||
|
||||
- [x] 4.1 Strictly validate this active change and all canonical specifications without synchronizing or archiving it.
|
||||
- [x] 4.2 Run the complete Gradle build without invoking production `main()`.
|
||||
- [x] 4.3 Use disposable probes and source audits to verify API shape, lifecycle rejection, Registry indexing, injection, ordering, and removal of hidden startup.
|
||||
- [x] 4.4 Inspect the final diff and worktree status without staging, committing, or pushing.
|
||||
@@ -0,0 +1,80 @@
|
||||
# javafx-runtime-service Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change 80-introduce-javafx-runtime-service. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: JavaFX runtime service separates use from lifecycle
|
||||
`com.r35157.service.javafx.JavaFxRuntimeService` SHALL be a public `NenjimComponent` interface exposing exactly `void runLater(@NotNull Runnable runnable)` and no initialization, shutdown, restart, status, configuration, window-management, event, or lifecycle operation. `com.r35157.service.javafx.impl.ref.JavaFxRuntimeServiceImpl` SHALL implement both that service and the canonical `NenjimProcess`, so one object supplies the ordinary service view and the Hub-owned process view without a separate manager or administration interface.
|
||||
|
||||
#### Scenario: Consumer receives only the service view
|
||||
- **WHEN** an ordinary JavaFX application receives a `JavaFxRuntimeService`
|
||||
- **THEN** it can submit non-null JavaFX work but cannot start, stop, restart, configure, inspect, or administer the runtime through that API
|
||||
|
||||
#### Scenario: One implementation supplies both views
|
||||
- **WHEN** one `JavaFxRuntimeServiceImpl` is registered once
|
||||
- **THEN** Registry lookup through `JavaFxRuntimeService` and `NenjimProcess` returns the identical object reference
|
||||
|
||||
### Requirement: JavaFX runtime startup is one-attempt and complete
|
||||
The implementation's `start()` SHALL be the only operation that calls `Platform.startup(...)`. It SHALL accept only the first attempt, configure `Platform.setImplicitExit(false)` on the JavaFX startup callback, wait for that callback to complete successfully, and open service availability only before returning successfully. Every repeated attempt SHALL fail with `IllegalStateException`, including after first-attempt failure or interruption. A synchronous or callback initialization failure SHALL propagate with a clear service-startup diagnostic, leave the service unavailable, and SHALL not be retried, recovered, restarted, rolled back, or silently accepted as an externally initialized toolkit.
|
||||
|
||||
#### Scenario: First startup succeeds
|
||||
- **WHEN** JavaFX startup invokes its callback and implicit exit is disabled successfully
|
||||
- **THEN** `start()` returns only after that callback completes and the service becomes available
|
||||
|
||||
#### Scenario: Startup callback has not completed
|
||||
- **WHEN** `Platform.startup(...)` has accepted initialization but its callback has not completed
|
||||
- **THEN** `start()` has not returned successfully and task submission remains unavailable
|
||||
|
||||
#### Scenario: Startup fails
|
||||
- **WHEN** toolkit initialization or callback configuration fails
|
||||
- **THEN** `start()` propagates a clear failure and the service remains unavailable without partial success
|
||||
|
||||
#### Scenario: Startup is repeated
|
||||
- **WHEN** `start()` is called after any earlier attempt began
|
||||
- **THEN** it fails with `IllegalStateException` without calling `Platform.startup(...)` again
|
||||
|
||||
#### Scenario: Last window closes
|
||||
- **WHEN** the last JavaFX window closes after successful service startup
|
||||
- **THEN** disabled implicit exit keeps the shared JavaFX runtime alive for later submitted work
|
||||
|
||||
### Requirement: JavaFX task submission is non-null gated and asynchronous
|
||||
`runLater(...)` SHALL reject null with `NullPointerException`. Before successful startup and after failed startup it SHALL reject every non-null task with `IllegalStateException` without initializing, starting, or retrying JavaFX and without executing the task. After successful startup it SHALL submit the exact task through `Platform.runLater(...)` and SHALL never execute it synchronously on the caller's thread. Acceptance SHALL make no guarantee that application UI work completes successfully, and exceptions raised by that work SHALL not change the completed runtime startup milestone.
|
||||
|
||||
#### Scenario: Null task is submitted
|
||||
- **WHEN** `runLater(null)` is called in any service state
|
||||
- **THEN** it fails with `NullPointerException` identifying the null task
|
||||
|
||||
#### Scenario: Task is submitted before successful startup
|
||||
- **WHEN** a caller submits a non-null task before `start()` succeeds or after its failure
|
||||
- **THEN** submission fails with `IllegalStateException` without any JavaFX initialization or synchronous task execution
|
||||
|
||||
#### Scenario: Task is accepted after startup
|
||||
- **WHEN** a non-null task is submitted after successful startup
|
||||
- **THEN** the service passes it to `Platform.runLater(...)` for asynchronous execution on the JavaFX Application Thread
|
||||
|
||||
#### Scenario: Application task fails
|
||||
- **WHEN** accepted UI work later throws on the JavaFX Application Thread
|
||||
- **THEN** that remains an application failure and does not convert the service's successful startup into a runtime startup failure
|
||||
|
||||
### Requirement: Modern JavaFX consumers require the shared service
|
||||
`EvelynMissionControlImpl`, `NenjimProcessManagerImpl`, `NenjimComposerImpl`, `NenjimTestToolImpl`, `SodaTaskManagerImpl`, and `SuwimoClientImpl` SHALL each require, reject null for, retain, and use a constructor-injected `JavaFxRuntimeService` for JavaFX Application Thread submission. No compatibility constructor, static fallback, direct `Platform.startup(...)`, lazy initialization helper, or competing initialization flag SHALL remain in the modern component graph. Direct ordinary JavaFX UI API use inside application code SHALL remain valid.
|
||||
|
||||
#### Scenario: Consumer dependency is null
|
||||
- **WHEN** any modern JavaFX implementation is constructed with a null runtime service
|
||||
- **THEN** construction fails with `NullPointerException`
|
||||
|
||||
#### Scenario: Consumer starts UI work
|
||||
- **WHEN** any modern JavaFX consumer requests window creation or JavaFX publication
|
||||
- **THEN** it submits through its retained service and neither initializes JavaFX nor falls back to static behavior
|
||||
|
||||
#### Scenario: Static initialization path is inspected
|
||||
- **WHEN** modern source is searched after migration
|
||||
- **THEN** `com.r35157.libs.javafx.JavaFxRuntime` is absent and only `JavaFxRuntimeServiceImpl.start()` calls `Platform.startup(...)`
|
||||
|
||||
### Requirement: Deferred JavaFX runtime features remain absent
|
||||
The JavaFX runtime service SHALL represent one JVM-wide toolkit and SHALL not implement multiple toolkits, headless emulation, toolkit replacement, shutdown, stop, restart, recovery, supervision, health monitoring, runtime status queries, window discovery or management, configuration, Context resolution, automatic dependency discovery/startup, lifecycle events, or subscriptions. This change SHALL not add `NenjimHub.conf` or configurable autorun.
|
||||
|
||||
#### Scenario: Runtime boundary is inspected
|
||||
- **WHEN** the delivered service, process implementation, Hub integration, and application constructors are examined
|
||||
- **THEN** only explicit one-time startup, asynchronous task submission, fixed Hub ordering, and dependency injection are present
|
||||
|
||||
@@ -186,7 +186,7 @@ The reference Registry construction boundary SHALL create one Registry object ex
|
||||
- **THEN** no component ID identifies the manager and no lookup returns it as a component or process
|
||||
|
||||
### Requirement: NenjimHub owns composition and runtime startup
|
||||
`NenjimHub` SHALL be the global runtime owner and SHALL expose `start() throws Exception` plus an immutable ordered snapshot of successfully started process IDs. It SHALL be neither a component nor a process and SHALL not register itself. Its only accepted startup attempt SHALL construct the Registry subsystem, start the Registry process first, register it as `nenjim.registry.service`, construct and register the Journal subsystem as `nenjim.journal.service`, start Journal second, and continue to the complete hardcoded business component set from issue #76 only after Journal startup succeeds. During application composition the Hub SHALL construct the Process Manager with itself and register it as `nenjim.process-manager.default`. The Hub SHALL resolve only the explicitly enabled IDs through `NenjimProcess`, start them in dependency-safe order with the Process Manager last, announce the existing online point, and preserve the existing blocking wait. Registration SHALL never imply startup, and the Registry and Journal managers SHALL remain distinct passive, unregistered handles retained privately by the Hub.
|
||||
`NenjimHub` SHALL be the global runtime owner and SHALL expose `start() throws Exception` plus an immutable ordered snapshot of successfully started process IDs. It SHALL be neither a component nor a process and SHALL not register itself. Its only accepted startup attempt SHALL construct the Registry subsystem, start the Registry process first, register it as `nenjim.registry.service`, construct and register the Journal subsystem as `nenjim.journal.service`, start Journal second, and continue to the complete hardcoded business component set from issue #76 only after Journal startup succeeds. During application composition the Hub SHALL construct exactly one `JavaFxRuntimeServiceImpl`, register it as `nenjim.javafx-runtime.service`, inject its service view into Mission Control and Process Manager, construct the Process Manager with itself, and register that manager as `nenjim.process-manager.default`. The Hub SHALL resolve only explicitly enabled IDs through `NenjimProcess`, start the JavaFX runtime service after enabled non-JavaFX business processes and before Mission Control and Process Manager, start Process Manager last, announce the existing online point, and preserve the existing blocking wait. Registration SHALL never imply startup, and the Registry and Journal managers SHALL remain distinct passive, unregistered handles retained privately by the Hub.
|
||||
|
||||
#### Scenario: Registry starts before graph construction
|
||||
- **WHEN** the Hub performs its first startup attempt
|
||||
@@ -204,17 +204,25 @@ The reference Registry construction boundary SHALL create one Registry object ex
|
||||
- **WHEN** the Journal registration has completed
|
||||
- **THEN** the same object is retrievable as both `JournalService` and `NenjimProcess` under `nenjim.journal.service`, while its manager is not registered
|
||||
|
||||
#### Scenario: JavaFX runtime is process-discoverable
|
||||
- **WHEN** Hub-owned application composition has completed
|
||||
- **THEN** one object is retrievable as both `JavaFxRuntimeService` and `NenjimProcess` under `nenjim.javafx-runtime.service`
|
||||
|
||||
#### Scenario: JavaFX applications share the runtime
|
||||
- **WHEN** Hub-owned application composition has completed
|
||||
- **THEN** Mission Control and Process Manager retain the same `JavaFxRuntimeService` instance registered by the Hub
|
||||
|
||||
#### Scenario: Process Manager is process-discoverable
|
||||
- **WHEN** Hub-owned application composition has completed
|
||||
- **THEN** the object constructed with the current Hub is retrievable as both `NenjimProcessManager` and `NenjimProcess` under `nenjim.process-manager.default`
|
||||
- **THEN** the object constructed with the current Hub and shared JavaFX runtime is retrievable as both `NenjimProcessManager` and `NenjimProcess` under `nenjim.process-manager.default`
|
||||
|
||||
#### Scenario: Required hardcoded bindings are registered
|
||||
- **WHEN** Hub composition completes
|
||||
- **THEN** Journal, the Process Manager, and all component IDs and implementation choices enumerated by issue #76 are present exactly as configured, including intentionally inactive components and excluding both service managers
|
||||
- **THEN** Journal, the JavaFX runtime, the Process Manager, and all component IDs and implementation choices enumerated by issue #76 are present exactly as configured, including intentionally inactive components and excluding both service managers
|
||||
|
||||
#### Scenario: Explicit startup subset preserves order
|
||||
- **WHEN** Hub startup succeeds through the Process Manager phase
|
||||
- **THEN** successful process order is `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `evelyn.service.prod`, `evelyn.service.test`, `evelyn.iou-burner.prod`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
||||
- **THEN** successful process order is `nenjim.registry.service`, `nenjim.journal.service`, `assetaz.ticker.default`, `evelyn.service.prod`, `evelyn.service.test`, `evelyn.iou-burner.prod`, `nenjim.javafx-runtime.service`, `evelyn.mission-control.default`, and `nenjim.process-manager.default`
|
||||
|
||||
#### Scenario: Deliberately inactive processes remain inactive
|
||||
- **WHEN** Hub startup reaches its online point
|
||||
@@ -224,6 +232,10 @@ The reference Registry construction boundary SHALL create one Registry object ex
|
||||
- **WHEN** Journal process startup throws
|
||||
- **THEN** Registry remains the only recorded process, no business component is constructed or started, the exception propagates, and the Hub neither announces online nor enters its final wait
|
||||
|
||||
#### Scenario: JavaFX runtime failure is fail-fast
|
||||
- **WHEN** JavaFX runtime service startup throws
|
||||
- **THEN** its ID is not recorded, Mission Control and Process Manager do not start, the exception propagates, and the Hub neither announces online nor enters its final wait
|
||||
|
||||
#### Scenario: Hub start is not retried
|
||||
- **WHEN** any first Hub startup attempt has begun and `start()` is requested again
|
||||
- **THEN** the later request fails with `IllegalStateException` whether the first attempt succeeded or failed
|
||||
@@ -233,7 +245,7 @@ The reference Registry construction boundary SHALL create one Registry object ex
|
||||
- **THEN** the exception propagates, no later process starts, the Hub neither announces online nor enters its final wait, and no generic rollback or retry is attempted
|
||||
|
||||
### Requirement: Started-process snapshots are ordered immutable and truthful
|
||||
`NenjimHub.getStartedProcessIds()` SHALL be safe before, during, and after startup and SHALL return an immutable point-in-time snapshot in successful startup order. A process ID SHALL be appended only after that process's `start()` call returns successfully, including `nenjim.process-manager.default`. Earlier successful entries SHALL remain visible after a later failure, while a failing process SHALL never be included. The snapshot SHALL make no health or continued-liveness guarantee.
|
||||
`NenjimHub.getStartedProcessIds()` SHALL be safe before, during, and after startup and SHALL return an immutable point-in-time snapshot in successful startup order. A process ID SHALL be appended only after that process's `start()` call returns successfully, including `nenjim.javafx-runtime.service` and `nenjim.process-manager.default`. Earlier successful entries SHALL remain visible after a later failure, while a failing process SHALL never be included. The snapshot SHALL make no health or continued-liveness guarantee.
|
||||
|
||||
#### Scenario: Snapshot before process startup
|
||||
- **WHEN** the snapshot is requested before any process has started successfully
|
||||
@@ -243,6 +255,10 @@ The reference Registry construction boundary SHALL create one Registry object ex
|
||||
- **WHEN** a process's `start()` call returns successfully
|
||||
- **THEN** its ID appears after all previously successful process IDs in every later snapshot
|
||||
|
||||
#### Scenario: JavaFX runtime uses the common recording rule
|
||||
- **WHEN** JavaFX runtime service `start()` returns successfully
|
||||
- **THEN** the Hub records `nenjim.javafx-runtime.service` through the same post-start rule used for every other process
|
||||
|
||||
#### Scenario: Process Manager uses the common recording rule
|
||||
- **WHEN** Process Manager `start()` returns successfully
|
||||
- **THEN** the Hub records `nenjim.process-manager.default` through the same post-start rule used for every other process
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
TBD - created by archiving change 79-integrate-process-manager-runtime-status. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Process Manager is a Hub-managed process
|
||||
`com.r35157.nenjim.npm.NenjimProcessManager` SHALL extend the canonical `NenjimProcess` and SHALL add no public refresh, polling, event, start, stop, or query operation. `NenjimProcessManagerImpl` SHALL require and retain its owning non-null `NenjimHub` as an explicit constructor dependency and SHALL use that Hub as the sole source of displayed process status.
|
||||
`com.r35157.nenjim.npm.NenjimProcessManager` SHALL extend the canonical `NenjimProcess` and SHALL add no public refresh, polling, event, start, stop, or query operation. `NenjimProcessManagerImpl` SHALL require and retain its owning non-null `NenjimHub` as the sole source of displayed process status and the non-null shared `JavaFxRuntimeService` as the sole JavaFX Application Thread submission path.
|
||||
|
||||
#### Scenario: Process Manager is used through the common lifecycle
|
||||
- **WHEN** the registered Process Manager is resolved through `NenjimProcess`
|
||||
@@ -14,12 +14,20 @@ TBD - created by archiving change 79-integrate-process-manager-runtime-status. U
|
||||
- **WHEN** reference Process Manager construction receives a null Hub
|
||||
- **THEN** construction fails with `NullPointerException` before startup
|
||||
|
||||
#### Scenario: JavaFX runtime service is required
|
||||
- **WHEN** reference Process Manager construction receives a null JavaFX runtime service
|
||||
- **THEN** construction fails with `NullPointerException` before startup
|
||||
|
||||
#### Scenario: Public API remains minimal
|
||||
- **WHEN** a consumer examines `NenjimProcessManager`
|
||||
- **THEN** it exposes no Process Manager-specific refresh, polling, event, stop, status, or query method
|
||||
|
||||
### Requirement: Process Manager polls immediately on one non-blocking worker
|
||||
On its accepted startup attempt, the Process Manager SHALL start or reuse the existing JavaFX runtime mechanism, request the existing Process Manager window, and start exactly one dedicated background polling loop. The loop SHALL request `NenjimHub.getStartedProcessIds()` immediately and again after each normal three-second interval. Polling work SHALL not execute on the JavaFX application thread, and the worker SHALL be a daemon that does not prevent JVM termination.
|
||||
On its accepted startup attempt, the Process Manager SHALL request the existing window through its injected already-started `JavaFxRuntimeService` without initializing or starting JavaFX, then start exactly one dedicated background polling loop. The loop SHALL request `NenjimHub.getStartedProcessIds()` immediately and again after each normal three-second interval. Polling work SHALL not execute on the JavaFX Application Thread, and the worker SHALL be a daemon that does not prevent JVM termination.
|
||||
|
||||
#### Scenario: JavaFX prerequisite is unavailable
|
||||
- **WHEN** Process Manager startup submits its window before the injected JavaFX runtime service has started successfully
|
||||
- **THEN** service rejection propagates as Process Manager startup failure and no polling worker starts
|
||||
|
||||
#### Scenario: First snapshot is immediate
|
||||
- **WHEN** Process Manager startup schedules its JavaFX window and starts its polling worker
|
||||
@@ -31,14 +39,14 @@ On its accepted startup attempt, the Process Manager SHALL start or reuse the ex
|
||||
|
||||
#### Scenario: Polling is not JavaFX work
|
||||
- **WHEN** the Hub snapshot is queried and mapped
|
||||
- **THEN** that work runs on the dedicated background polling thread rather than the JavaFX application thread
|
||||
- **THEN** that work runs on the dedicated background polling thread rather than the JavaFX Application Thread
|
||||
|
||||
#### Scenario: Worker does not own JVM lifetime
|
||||
- **WHEN** only the Process Manager polling worker would otherwise keep the JVM alive
|
||||
- **THEN** its daemon status permits normal JVM termination
|
||||
|
||||
### Requirement: Successful snapshots replace the JavaFX list exactly
|
||||
Each successful poll SHALL map every returned `NenjimComponentId` to its exact `value()` string in Hub-provided order and SHALL publish that complete point-in-time list through the existing JavaFX runtime integration. Every JavaFX control mutation SHALL execute on the JavaFX application thread, and publication SHALL replace the complete displayed list rather than infer additions or removals. The existing title, dimensions, and multiple-selection behavior MAY remain unchanged, and no hardcoded process rows SHALL remain.
|
||||
Each successful poll SHALL map every returned `NenjimComponentId` to its exact `value()` string in Hub-provided order and SHALL publish that complete point-in-time list through the injected `JavaFxRuntimeService`. Every JavaFX control mutation SHALL execute on the JavaFX Application Thread, and publication SHALL replace the complete displayed list rather than infer additions or removals. The existing title, dimensions, and multiple-selection behavior MAY remain unchanged, and no hardcoded process rows SHALL remain.
|
||||
|
||||
#### Scenario: Ordered ID values are displayed
|
||||
- **WHEN** the Hub returns an ordered snapshot of component IDs
|
||||
@@ -54,7 +62,7 @@ Each successful poll SHALL map every returned `NenjimComponentId` to its exact `
|
||||
|
||||
#### Scenario: JavaFX controls remain thread-confined
|
||||
- **WHEN** the window is created or a successful snapshot is published
|
||||
- **THEN** all JavaFX control creation and mutation executes through the existing JavaFX runtime on the JavaFX application thread
|
||||
- **THEN** all JavaFX control creation and mutation is submitted through the retained runtime service for execution on the JavaFX Application Thread
|
||||
|
||||
### Requirement: Polling failures preserve the last successful view
|
||||
A polling failure SHALL be logged clearly, SHALL enqueue no invented or partial replacement, SHALL not terminate the JavaFX application thread, and SHALL leave the last successfully displayed snapshot visible. The background loop SHALL retry after the next normal three-second interval. If background waiting is interrupted, the worker SHALL restore its interrupted status and terminate the polling loop cleanly. Synchronous failures from the Process Manager's `start()` SHALL remain ordinary fail-fast Hub process-start failures.
|
||||
|
||||
@@ -3,8 +3,8 @@ package com.r35157.evelyn.emc.impl.ref;
|
||||
import com.fanitas.evelyn.core.Evelyn;
|
||||
import com.fanitas.evelyn.core.EvelynStatusIndexPoint;
|
||||
import com.r35157.evelyn.emc.EvelynMissionControl;
|
||||
import com.r35157.libs.javafx.JavaFxRuntime;
|
||||
import com.r35157.nenjim.process.NenjimProcess;
|
||||
import com.r35157.service.javafx.JavaFxRuntimeService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
@@ -34,16 +34,20 @@ public final class EvelynMissionControlImpl implements
|
||||
|
||||
public EvelynMissionControlImpl(
|
||||
@NotNull Evelyn evelynServiceProd,
|
||||
@NotNull Evelyn evelynServiceTest
|
||||
@NotNull Evelyn evelynServiceTest,
|
||||
@NotNull JavaFxRuntimeService javaFxRuntimeService
|
||||
) {
|
||||
this.evelynProd = Objects.requireNonNull(evelynServiceProd, "evelynServiceProd");
|
||||
this.evelynTest = Objects.requireNonNull(evelynServiceTest, "evelynServiceTest");
|
||||
this.javaFxRuntimeService = Objects.requireNonNull(
|
||||
javaFxRuntimeService,
|
||||
"javaFxRuntimeService");
|
||||
refreshTimeline.setCycleCount(Timeline.INDEFINITE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
JavaFxRuntime.runLater(this::showWindow);
|
||||
javaFxRuntimeService.runLater(this::showWindow);
|
||||
}
|
||||
|
||||
private void showWindow() {
|
||||
@@ -311,6 +315,7 @@ public final class EvelynMissionControlImpl implements
|
||||
|
||||
private final Evelyn evelynProd;
|
||||
private final Evelyn evelynTest;
|
||||
private final JavaFxRuntimeService javaFxRuntimeService;
|
||||
private final Timeline refreshTimeline = new Timeline(new KeyFrame(
|
||||
Duration.seconds(CHART_REFRESH_SECONDS),
|
||||
event -> refreshCharts()
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.r35157.libs.javafx;
|
||||
|
||||
import javafx.application.Platform;
|
||||
|
||||
public final class JavaFxRuntime {
|
||||
|
||||
public static void runLater(Runnable runnable) {
|
||||
initialize();
|
||||
Platform.runLater(runnable);
|
||||
}
|
||||
|
||||
private static synchronized void initialize() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
Platform.startup(() -> Platform.setImplicitExit(false));
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private JavaFxRuntime() {
|
||||
}
|
||||
|
||||
private static boolean initialized;
|
||||
}
|
||||
@@ -1,14 +1,24 @@
|
||||
package com.r35157.nenjim.composer.impl.ref;
|
||||
|
||||
import com.r35157.libs.javafx.JavaFxRuntime;
|
||||
import com.r35157.nenjim.composer.NenjimComposer;
|
||||
import com.r35157.service.javafx.JavaFxRuntimeService;
|
||||
import javafx.stage.Stage;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class NenjimComposerImpl implements NenjimComposer {
|
||||
|
||||
public NenjimComposerImpl(
|
||||
@NotNull JavaFxRuntimeService javaFxRuntimeService) {
|
||||
this.javaFxRuntimeService = Objects.requireNonNull(
|
||||
javaFxRuntimeService,
|
||||
"javaFxRuntimeService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
JavaFxRuntime.runLater(this::showWindow);
|
||||
javaFxRuntimeService.runLater(this::showWindow);
|
||||
}
|
||||
|
||||
private void showWindow() {
|
||||
@@ -16,4 +26,6 @@ public final class NenjimComposerImpl implements NenjimComposer {
|
||||
window.setTitle("Nenjim Composer");
|
||||
window.show();
|
||||
}
|
||||
|
||||
private final JavaFxRuntimeService javaFxRuntimeService;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ import com.r35157.libs.solana.impl.ref.SolanaBlockChainImpl;
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import com.r35157.service.notification.BoundNotificationService;
|
||||
import com.r35157.service.notification.impl.discord.DiscordNotificationServiceImpl;
|
||||
import com.r35157.service.javafx.JavaFxRuntimeService;
|
||||
import com.r35157.service.javafx.impl.ref.JavaFxRuntimeServiceImpl;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -95,6 +97,7 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
startProcess(registryService, EVELYN_SERVICE_PROD_ID);
|
||||
startProcess(registryService, EVELYN_SERVICE_TEST_ID);
|
||||
startProcess(registryService, EVELYN_IOU_BURNER_PROD_ID);
|
||||
startProcess(registryService, JAVAFX_RUNTIME_SERVICE_ID);
|
||||
startProcess(registryService, EVELYN_MISSION_CONTROL_ID);
|
||||
startProcess(registryService, PROCESS_MANAGER_ID);
|
||||
|
||||
@@ -277,12 +280,22 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
|
||||
private void initializeApplications(
|
||||
NenjimRegistryServiceManager registryServiceManager) {
|
||||
missionControl = new EvelynMissionControlImpl(evelynServiceProd, evelynServiceTest);
|
||||
javaFxRuntimeService = new JavaFxRuntimeServiceImpl();
|
||||
registryServiceManager.registerComponent(
|
||||
JAVAFX_RUNTIME_SERVICE_ID,
|
||||
javaFxRuntimeService);
|
||||
|
||||
missionControl = new EvelynMissionControlImpl(
|
||||
evelynServiceProd,
|
||||
evelynServiceTest,
|
||||
javaFxRuntimeService);
|
||||
registryServiceManager.registerComponent(
|
||||
EVELYN_MISSION_CONTROL_ID,
|
||||
missionControl);
|
||||
|
||||
processManager = new NenjimProcessManagerImpl(this);
|
||||
processManager = new NenjimProcessManagerImpl(
|
||||
this,
|
||||
javaFxRuntimeService);
|
||||
registryServiceManager.registerComponent(
|
||||
PROCESS_MANAGER_ID,
|
||||
processManager);
|
||||
@@ -364,6 +377,8 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
new NenjimComponentId("evelyn.iou-burner.prod");
|
||||
private static final NenjimComponentId EVELYN_MISSION_CONTROL_ID =
|
||||
new NenjimComponentId("evelyn.mission-control.default");
|
||||
private static final NenjimComponentId JAVAFX_RUNTIME_SERVICE_ID =
|
||||
new NenjimComponentId("nenjim.javafx-runtime.service");
|
||||
private static final NenjimComponentId PROCESS_MANAGER_ID =
|
||||
new NenjimComponentId("nenjim.process-manager.default");
|
||||
private static final ΩRaydiumLiquidityPoolIdΩ EVE_USDT_RAYDIUM_POOL_ID =
|
||||
@@ -404,6 +419,7 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
private EvelynIOUBurnerService evelynIOUBurnerServiceProd;
|
||||
|
||||
// Application components
|
||||
private JavaFxRuntimeService javaFxRuntimeService;
|
||||
private EvelynMissionControl missionControl;
|
||||
private NenjimProcessManager processManager;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.r35157.nenjim.npm.impl.ref;
|
||||
|
||||
import com.r35157.libs.javafx.JavaFxRuntime;
|
||||
import com.r35157.nenjim.hubd.NenjimHub;
|
||||
import com.r35157.nenjim.npm.NenjimProcessManager;
|
||||
import com.r35157.service.javafx.JavaFxRuntimeService;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
@@ -18,8 +18,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public final class NenjimProcessManagerImpl implements NenjimProcessManager {
|
||||
|
||||
public NenjimProcessManagerImpl(@NotNull NenjimHub nenjimHub) {
|
||||
public NenjimProcessManagerImpl(
|
||||
@NotNull NenjimHub nenjimHub,
|
||||
@NotNull JavaFxRuntimeService javaFxRuntimeService) {
|
||||
this.nenjimHub = Objects.requireNonNull(nenjimHub, "nenjimHub");
|
||||
this.javaFxRuntimeService = Objects.requireNonNull(
|
||||
javaFxRuntimeService,
|
||||
"javaFxRuntimeService");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -29,7 +34,7 @@ public final class NenjimProcessManagerImpl implements NenjimProcessManager {
|
||||
"Nenjim Process Manager start has already been attempted");
|
||||
}
|
||||
|
||||
JavaFxRuntime.runLater(this::showWindow);
|
||||
javaFxRuntimeService.runLater(this::showWindow);
|
||||
|
||||
Thread pollingWorker = new Thread(
|
||||
this::pollStartedProcesses,
|
||||
@@ -69,7 +74,7 @@ public final class NenjimProcessManagerImpl implements NenjimProcessManager {
|
||||
.stream()
|
||||
.map(componentId -> componentId.value())
|
||||
.toList();
|
||||
JavaFxRuntime.runLater(
|
||||
javaFxRuntimeService.runLater(
|
||||
() -> replaceDisplayedSnapshot(processIds));
|
||||
} catch (RuntimeException failure) {
|
||||
log.error(
|
||||
@@ -94,6 +99,7 @@ public final class NenjimProcessManagerImpl implements NenjimProcessManager {
|
||||
NenjimProcessManagerImpl.class);
|
||||
private static final Duration POLL_INTERVAL = Duration.ofSeconds(3);
|
||||
private final NenjimHub nenjimHub;
|
||||
private final JavaFxRuntimeService javaFxRuntimeService;
|
||||
private final AtomicBoolean startAttempted = new AtomicBoolean();
|
||||
private ListView<String> processList;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
package com.r35157.nenjim.ntt.impl.ref;
|
||||
|
||||
import com.r35157.libs.javafx.JavaFxRuntime;
|
||||
import com.r35157.nenjim.ntt.NenjimTestTool;
|
||||
import com.r35157.service.javafx.JavaFxRuntimeService;
|
||||
import javafx.stage.Stage;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class NenjimTestToolImpl implements NenjimTestTool {
|
||||
|
||||
public NenjimTestToolImpl(
|
||||
@NotNull JavaFxRuntimeService javaFxRuntimeService) {
|
||||
this.javaFxRuntimeService = Objects.requireNonNull(
|
||||
javaFxRuntimeService,
|
||||
"javaFxRuntimeService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
JavaFxRuntime.runLater(this::showWindow);
|
||||
javaFxRuntimeService.runLater(this::showWindow);
|
||||
}
|
||||
|
||||
private void showWindow() {
|
||||
@@ -16,4 +26,6 @@ public final class NenjimTestToolImpl implements NenjimTestTool {
|
||||
window.setTitle("Nenjim Test Tool");
|
||||
window.show();
|
||||
}
|
||||
|
||||
private final JavaFxRuntimeService javaFxRuntimeService;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.r35157.service.javafx;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/** Access to the JVM-wide JavaFX Application Thread. */
|
||||
public interface JavaFxRuntimeService extends NenjimComponent {
|
||||
void runLater(@NotNull Runnable runnable);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.r35157.service.javafx.impl.ref;
|
||||
|
||||
import com.r35157.nenjim.process.NenjimProcess;
|
||||
import com.r35157.service.javafx.JavaFxRuntimeService;
|
||||
import javafx.application.Platform;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/** Hub-managed owner of the single JavaFX toolkit used by this JVM. */
|
||||
public final class JavaFxRuntimeServiceImpl implements
|
||||
JavaFxRuntimeService,
|
||||
NenjimProcess {
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (!startAttempted.compareAndSet(false, true)) {
|
||||
throw new IllegalStateException(
|
||||
"JavaFX runtime service start has already been attempted");
|
||||
}
|
||||
|
||||
CountDownLatch startupCompleted = new CountDownLatch(1);
|
||||
AtomicReference<Throwable> startupFailure = new AtomicReference<>();
|
||||
|
||||
try {
|
||||
Platform.startup(() -> {
|
||||
try {
|
||||
Platform.setImplicitExit(false);
|
||||
} catch (Throwable failure) {
|
||||
startupFailure.set(failure);
|
||||
} finally {
|
||||
startupCompleted.countDown();
|
||||
}
|
||||
});
|
||||
} catch (RuntimeException | Error failure) {
|
||||
throw failedStartup(failure);
|
||||
}
|
||||
|
||||
awaitStartupCompletion(startupCompleted);
|
||||
|
||||
Throwable failure = startupFailure.get();
|
||||
if (failure != null) {
|
||||
throw failedStartup(failure);
|
||||
}
|
||||
available = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runLater(@NotNull Runnable runnable) {
|
||||
Objects.requireNonNull(runnable, "runnable");
|
||||
if (!available) {
|
||||
throw new IllegalStateException(
|
||||
"JavaFX runtime service has not started successfully");
|
||||
}
|
||||
Platform.runLater(runnable);
|
||||
}
|
||||
|
||||
private static void awaitStartupCompletion(
|
||||
CountDownLatch startupCompleted) {
|
||||
try {
|
||||
startupCompleted.await();
|
||||
} catch (InterruptedException interrupted) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException(
|
||||
"Interrupted while waiting for JavaFX runtime startup",
|
||||
interrupted);
|
||||
}
|
||||
}
|
||||
|
||||
private static IllegalStateException failedStartup(Throwable failure) {
|
||||
return new IllegalStateException(
|
||||
"JavaFX runtime service failed to start",
|
||||
failure);
|
||||
}
|
||||
|
||||
private final AtomicBoolean startAttempted = new AtomicBoolean();
|
||||
private volatile boolean available;
|
||||
}
|
||||
@@ -1,14 +1,24 @@
|
||||
package com.r35157.stm.impl.ref;
|
||||
|
||||
import com.r35157.libs.javafx.JavaFxRuntime;
|
||||
import com.r35157.service.javafx.JavaFxRuntimeService;
|
||||
import com.r35157.stm.SodaTaskManager;
|
||||
import javafx.stage.Stage;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class SodaTaskManagerImpl implements SodaTaskManager {
|
||||
|
||||
public SodaTaskManagerImpl(
|
||||
@NotNull JavaFxRuntimeService javaFxRuntimeService) {
|
||||
this.javaFxRuntimeService = Objects.requireNonNull(
|
||||
javaFxRuntimeService,
|
||||
"javaFxRuntimeService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
JavaFxRuntime.runLater(this::showWindow);
|
||||
javaFxRuntimeService.runLater(this::showWindow);
|
||||
}
|
||||
|
||||
private void showWindow() {
|
||||
@@ -16,4 +26,6 @@ public final class SodaTaskManagerImpl implements SodaTaskManager {
|
||||
window.setTitle("Soda Task Manager");
|
||||
window.show();
|
||||
}
|
||||
|
||||
private final JavaFxRuntimeService javaFxRuntimeService;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
package com.r35157.suwimo.hub.client.impl.j2fx;
|
||||
|
||||
import com.r35157.libs.javafx.JavaFxRuntime;
|
||||
import com.r35157.service.javafx.JavaFxRuntimeService;
|
||||
import com.r35157.suwimo.hub.client.SuwimoClient;
|
||||
import javafx.stage.Stage;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class SuwimoClientImpl implements SuwimoClient {
|
||||
|
||||
public SuwimoClientImpl(
|
||||
@NotNull JavaFxRuntimeService javaFxRuntimeService) {
|
||||
this.javaFxRuntimeService = Objects.requireNonNull(
|
||||
javaFxRuntimeService,
|
||||
"javaFxRuntimeService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
JavaFxRuntime.runLater(this::showWindow);
|
||||
javaFxRuntimeService.runLater(this::showWindow);
|
||||
}
|
||||
|
||||
private void showWindow() {
|
||||
@@ -16,4 +26,6 @@ public final class SuwimoClientImpl implements SuwimoClient {
|
||||
window.setTitle("Suwimo Client");
|
||||
window.show();
|
||||
}
|
||||
|
||||
private final JavaFxRuntimeService javaFxRuntimeService;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user