Javaで太陽系天体シミュレーター&JavaをExe化してみました。(Java21以降対応)

Javaで太陽系天体シミュレーターを作成して、その後、Exe化してみました。
(下の方にダウンロードリンクがあります)

シミュレーターのほとんどは、Gemini3くんに手伝ってもらいましたが(笑)、
何時間もかけて調整を行って、なんとなく自分的にいい感じになりました。

実際の縮尺や時間からはかなり調整をかけています。
見ていて楽しめるように調整をしています。

画面イメージ

SolarSystemSim

見せどころ

初期のシミュレーターからいろいろとお願いをしてみました。
1)天体名が分かるようにスペースキーで表示するようにしました。
2)上下左右キーで拡大/縮小/時間調整ができます。
3)惑星および有名な準惑星をプロットしてみました。
4)主な彗星をプロットしてみました。名もない彗星もランダムに置いています。
5)エッジワースカイパーベルト、散乱円盤、オールトの雲もイメージ化しています。
6)シューメーカー・レヴィ第9彗星を蘇らせています。
詳しくは知らなかったですが、木星の重力圏にとらえられて、最後は分解・飲み込まれたみたいですね。
その重力圏にとらわれたイメージにしています。ぶつかりはしません。
7)見えるものではないですが、Java21から対応されているVirtual Threadを利用して、リソースを省力化しています。
なので、Java21以降対応になります。

Virtual Threadの採用について

わたしも今回初めて知ったVirtual Threadを採用しています。
数千の天体一つひとつが独立して計算を行い
(バーチャルスレッド。すべての天体が別のスレッドで動いています)
かつ メインの描画スレッドを邪魔しないスムーズな挙動を実現しています。
Javaで実現した『並列処理の宇宙』をぜひ体験してください。

JavaのExe化

前回のExe化はJavaFXとパッド対応まで含めて対応しましたが、
今回はピュアJavaで試してみました。

Exe化したバッチファイルは、
添付のzipファイル内の
makeExe.bat
を見てくださいね。

今回の記事まとめ

Javaで太陽系天体シミュレーター&JavaをExe化してみました。(Java21以降対応)
Javaをexe化してみる(2)- jpackageの活用 と 自動化バッチの紹介 -
Javaのバーチャルスレッドを利用してみた(3)感想とゲーム利用の有効性について考察

Youtubeに載せました♪
https://youtu.be/NPsgNbhEtds

シェアしていただけるとうれしいです。

TypeScript 非同期処理を一気通貫で同期処理として約束(Promise)させる

はじめに - Javaエンジニアの苦悩。JavaScriptの非同期処理にはまる -

JavaScriptの非同期処理、await を書いているのになぜかスルーされる……。そんな経験はありませんか?

わたしは数年前、Javaアプリケーション(アプレットだけど)をTypeScriptに移植する際、
この『止まらない非同期』に数週間も頭を抱えました。
結論から言うと、非同期処理を同期処理のように動かすには、
最下層から最上層まで**『一気通貫』で約束(Promise)を一気通貫で繋ぎ切る必要がありました。

今回の記事は、私が泥臭い試行錯誤の末にたどり着いた、非同期リレーの鉄則を共有します。

状況とJavaプログラマからの感覚

JavaからJavaScript(実際にはTypeScriptだけど)に書き換えをしていくとき、
通信処理部分が非同期処理になる。という話はインフラチームからは聞いていました。

現状、Javaでは同期処理となっているので、その通りにする必要がありました。
その解決策として、Promise(約束)を知りました。

その当時の各サイトの情報や、他チームのソースコードを見て真似て書いてみました。

まずは、サーバー通信処理への部分イメージ


function サーバー通信を待つ関数() {

	return new Promise((resolve, reject) => {

		// 1. 通信を投げる
		通信処理を投げる();

		// 2. 終わったかどうかを定期的にチェックする(ポーリング)
		const timer = setInterval(() => {
			if (通信成功した?) {
				clearInterval(timer);
				resolve("成功データ");
			} else if (タイムアウトした?) {
				clearInterval(timer);
				reject("エラー");
			}
		}, 100); // 100ミリ秒ごとに確認

	});
}

【Javaエンジニアの視点で】

実際には、手探り状態で必死でした。
「Promiseというものを使えばいいらしい」
「setIntervalで監視すれば待てるはずだ」……。
慣れないTypeScriptと格闘しながら、ようやく書き上げたこの最下層の関数。

「これだけロジックを組んだんだ。
これでもう、Javaの同期処理と同じように、
この関数を呼んだところでプログラムは一旦止まり、
データが返ってくるのを待ってくれるはずだ」

そう、一番下(最下層)さえ「待てる構造」に作り替えれば、
上層階のプログラムたちは何も知らなくても恩恵に預かれると、
本気で信じていたのです。

上層部は「約束」を無視して走り去っていく。。。

最下層プログラムを作りきり、
上位メソッドは現行Javaのロジックを移植して、いざ、処理を呼び出してみました。

フロントエンドは変わっているので、
A(GUI)→B→C(最下層の共通処理)でいうと、Bの部分は移植でいけました。
Aはもちろん新規作成です。

Javaエンジニアの感覚なら、通信が終わるまで止まってくれるはずです。

Bの通信処理


export class B {

	public 通信処理B():返信データ {
・・・
		// Cが終わるまでここで「待つ」
		let 返信データ = C.サーバー通信を待つ関数();

		return 返信データ;

	}

}

Aのイベント処理(GUI)

イメージとしてはBと変わりはありません。


export class A {

	public イベント処理A() {
・・・
		// Bが終わるまでここで「待つ」
		let 返信データ = B.通信処理B();

		// ここでようやく本物のデータを使って貼り付けができる
		this.データ貼り付け処理(data);

	}

}

これが止まらないんです。
データ貼り付け処理が先に動いてしまうんです。
ここからなぜなぜ地獄にハマりました・・・汗。

解決:最上層から最下層まで「一気通貫」で繋ぎ切る

結論から言うと、JavaScript/TypeScriptの世界で同期処理を実現するには、
**「バトン(Promise)を受け取る全員が、前の走者を待つ(await)」**
というルールを徹底するしかありません。

一箇所でも await を忘れたり、async を抜かしたりすれば、
その瞬間に「一気通貫」は崩壊し、プログラムは走り去ってしまいます。

Bの修正:リレーの中継地点

まずは中間の「B」です。 ここが一番の盲点でした。
自分も「約束(Promise)」を返すように宣言し、下層の結果をしっかり待ちます。


export class B {

	// 1. asyncを付けて「非同期(約束を返す)メソッド」であることを宣言
	// 2. 戻り値を Promise<返信データ> に包む
	public async 通信処理B():Promise<返信データ> {
・・・
		// 3. await を付けて、Cが終わるまでここで「止まって待つ」
		let 返信データ = await C.サーバー通信を待つ関数();

		return 返信データ;

	}

}

Aの修正:一気通貫のゴール地点

そして最上層の「A」です。
ここも同様に「待ち」の姿勢を入れなければなりません。


export class A {

	public async イベント処理A() {
・・・
		// 4. ここでも await。Bが結果を持ってくるまで次の行へ行かせない!
		let 返信データ = await B.通信処理B();

		// 5. ここでようやく、本物のデータ手に入れることができ、データ貼り付けができる
		this.データ貼り付け処理(data);

	}

}

まとめ:数週間かけて分かった「一気通貫」の正体

Javaエンジニアの私にとって、
最大の誤算は**「一番下が待てば、上も勝手に待つだろう」**という思い込みでした。

JavaScriptの世界はシビアで厳しいものでした。
「待ってほしいなら、全員が『待つ(await)』と宣言し、
バトン(Promise)を繋ぎ続けなければならない」。

この「一気通貫」の鎖さえ完成すれば、
非同期処理はまるで魔法のように、
Javaの同期処理と同じような顔をして動いてくれるようになります。

もし、あなたの await が無視されているなら、
どこかでその鎖が切れていないか、
数珠つなぎを一から辿ってみてください。

注意! たとえAとCが完璧でも、
中間のBが await を一回忘れるだけで、
Aの手元にはまた「空っぽの箱(Pending状態のPromise)」が届くことになります。

参考情報

Promise を使う(Promise チェーンの解説)
https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Using_promises

「async/await」の公式解説
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function

#TypeScript #async-await

シェアしていただけるとうれしいです。