にゃみかんてっくろぐ

猫か百合を見守る壁になりたい

P2P 地震情報 Windows 版を「半分くらいクロスプラットフォームで」リニューアルしました

地震情報アプリ界隈 Advent Calendar 2021 14 日目の記事です。

P2P 地震情報 Windows 版を 10 年ぶりに更新しました。デザインは一新しましたが、目立った新機能はなく、未実装の箇所も残っています。

しかし、実は半分くらいがクロスプラットフォームになり、そのコードは Linux 上で動いていたりします。今回は、そのアーキテクチャをご紹介したいと思います。

P2P 地震情報とは

P2P 地震情報は、気象庁地震情報・津波予報と、ユーザ同士の「揺れた!」という情報を P2P ネットワークで共有する無償のサービスです。

f:id:no_clock:20211213235521p:plain
P2P地震情報 Windows 版 動作の様子

Windows 版のほか、 iOS 版Android 版Twitter @p2pquake 、さらに開発者向けに JSON / WebSocket API も提供しています。

Windows 版の問題点

Windows 版 (Beta3) は Visual Basic 6.0 で作っていました。運用を続けるうちに徐々に問題が生じてきました。

  • 古すぎる。開発環境は 2008 年にサポート終了。実行は Windows 10 でも可能。
  • マルチスレッドではない。 P2P 通信にはそもそも不向きである。
  • API 提供にあたり、地震感知情報の収集のためだけに Windows 環境を常時運用する必要がある。

そこでリニューアルすることにしたわけです。

Windows 版のアーキテクチャ

Windows 版は、 Microsoft のソフトウェアフレームワーク .NET 5 で開発しています。もともと .NET Core と呼ばれていたもので、クロスプラットフォーム対応が特徴です。

開発・保守がしやすいよう 6 つのコンポーネントに分割していて、画面(ユーザインタフェース)を除く 5 つのコンポーネントクロスプラットフォームに対応しています。

f:id:no_clock:20211213235712p:plain
Windows 版のコンポーネント構成

Web 版と地図は同じ

クロスプラットフォームであることの一例を見てみます。以下の画像は、 Linux サーバで動いている Web 版の地震情報地図と、新 Windows 版の地震情報地図を並べたものです。ほとんど同じです。 Linux でも Windows でも同じ地図生成のソースコードで動作しているためです。

f:id:no_clock:20211214000731p:plain
左: Web 版の地震情報地図、 右: 新 Windows 版の地震情報地図

アーキテクチャ詳解

ここからは少しだけアーキテクチャを詳しく見ていきます。

Client: サーバ通信、 P2P 通信、それらのコントローラ

コア部分にあたる Client は、 P2P 地震情報のピアとして動作するために、サーバ・ピア通信を担います。

デザインパターンの Mediator パターンと State パターンを組み合わせたようなクラス設計にしています。接続済の状態から切断するときは、ピアとの接続をすべて切断し、サーバに参加終了の通信をする… といった具合です。

f:id:no_clock:20211214000001p:plain
ざっくりしたクラス構成

f:id:no_clock:20211213235843p:plain
ざっくりした状態遷移図

地震情報などのデータを受信すると、下位クラスでイベントが発生し、上位へ運ばれ、最終的に最上位である MediatorContext クラスのイベントが発生します。画面にあたる WpfClient は、このイベントを処理して表示や通知をしています。

ソケットプログラミングは不安定で面倒なものです。接続済みかチェックしてデータを送信しても、送信する瞬間には切断されていることも珍しくありません。悲しいことに、ソースコードには try-catch が溢れています。

f:id:no_clock:20211214000036p:plain
悲しい try-catch (GitHub: epsp-peer-cs/Client/Common/Net/CRLFSocket.cs)

Map: ImageSharp によるクロスプラットフォーム描画

Map は地図生成を担います。といっても、地図生成には画像処理以外の要素も絡んでいます。

なお、クロスプラットフォームの描画ライブラリ SixLabors.ImageSharp を用いています。

WpfClient: WPF (Windows Presentation Foundation) による GUI

ユーザインタフェースを提供するのは WpfClient です。 ModernWpfUI を用いてモダンな見た目になっています。

モダンなのは見た目だけで、 WPF 自体はフレームワークとして「枯れた」部類に入っている気がします。 WinUI 3.NET MAUI がはるかにモダンです。ただ、ここで下手に躓いて開発が滞るのだけは避けたいという思惑がありました。

「 UI だけなら作り直しは簡単だから、まずは完成を」という気持ちで作りきったので、設計と呼べるものはあまりありません。 Program.cs は 400 行を超えて恥ずかしい感じになっています。ただ、クラスの依存方向が反転することが極力ないよう実装しています。

地震感知情報の地図生成については、 1 件ずつ生成していると受信ペースに追いつかないため、適宜生成を省略するように工夫しています。

選ばなかったアーキテクチャ

開発にあたっては、次のアーキテクチャも検討しましたが、最終的には選びませんでした。

  • Electron: モダンなクロスプラットフォームとして最有力候補でしたが、既に安定稼働していた P2P 通信部分をきっちり移植できる自信がありませんでした。
  • Electron.NET + Blazor (WebAssembly): P2P 通信部分がそのまま使えるものの、 ASP.NET Core が未知だったため躊躇しました。
  • Client を gRPC サーバ化 + Flutter: Flutter デスクトップ対応があったので検討したものの、さすがに上記 2 案と比べても奇抜すぎて止めました。

クロスプラットフォーム対応の使いどころ

そんなわけで、半分ほどクロスプラットフォーム対応になっています。実は、 GUI 以外は既に Linux 環境で実際に運用しています。

  • Map: Windows 版以外の地図画像を生成するために、 Linux サーバ上で実行
  • Asn1PKCS: P2P 地震情報 サーバ (Linux 版) で利用
  • Client, PKCSPeerCrypto: API 等で提供するための地震感知情報の収集に、 Linux サーバ上で実行

コンポーネント分割せずに失敗した過去

余談ですが、 Windows 版リニューアルは過去 2~3 度ほど試みていて、いずれも失敗していました。

野心的に新機能を盛り込みすぎたり、「一気に」作り直すやり方で進めていたりして、今振り返ると「そりゃ失敗しますよ」という感じがします。

今回は機能的にはかなり控えめにして、小さく分割して、「使えるところからどんどん動かしていく」という方法で進めました。少しずつでも「ちゃんと動いている」という安心感は、開発を前進させる力になりました。

YouTube コーディング配信

またまた余談ですが、開発の模様は一部 YouTube で配信していました。他人の開発風景ってなかなかまじまじと見る機会がないもので、ならば自分から、という感じでした。

1 ヶ月くらいで止めちゃいましたが、録画は続けていて 50 時間以上になっています。数分くらいに短くまとめて動画にしたいなと思っています(いつ完成するかはわからない)。

今後の課題

アーキテクチャや実装自体はかなり良くなりました。一方、機能としてはまだまだで、メモリ消費もかなり激しいです。

私も一利用者として使いつつ、完成度を高めていきたいと思います。

ソースコード

MIT ライセンスです。なんだかんだでそこそこのボリュームになりました(一部未コミットのコードを含みます)。

$ cloc . --include-ext=xaml,cs
github.com/AlDanial/cloc v 1.82  T=68.71 s (5.7 files/s, 609.7 lines/s)
----------------------------------------------------------------
Language      files          blank        comment           code
----------------------------------------------------------------
C#              377           4986           4124          31773
XAML             18             41             16            952
----------------------------------------------------------------
SUM:            395           5027           4140          32725
----------------------------------------------------------------