Sinatraで肥大化したファイルを分割する
SinatraとVueの黒魔術化
次なる実験場はこのジェネレータ。
ベースにSinatraを使っているので、そこに乗っける形でVueによるSPA化を施していく。
ぶっちゃけ、下記ページに必要なことは8割方書いてある。
以下、備忘録
index.htmlの返し方
上記ページでは「フロントエンドサーバがnpm、バックエンドサーバがsinatra」となっていたりして
そいつらの橋渡しをするようにしているようだが、
「別にSinatraあるんだから、そこで全部やっちゃえばよくね?」ということで今回はSinatraのみで対処した。
要するに、"/"にアクセスされたらSinatraで拾って"/index.html"を内部的に返せばよいわけである。
これは、Sinatraのreadmeにある「別ルーティングの誘発」を利用することで実現できる。
プログラム内でページ遷移
this.$router.pushを使う。
ちなみに、動的ルーティングのパラメータはthis.$route.params、こいつはthis.$router。
めちゃめちゃ紛らわしい💢
yarn
最近ではnpmの代わりにyarnを使うのが流儀らしい。
早いらしいが体感的にそこまで違いを感じなかった。
実際に数値で比べてみないとわかんないものだししょうがないね。
Vueいじってみた
今更ながら「Vue.jsを使いたい」という欲求を満たすため、自分のサイトをゴリゴリ改造しました。
従来mithril.js(in livescript)だったものを、Vue.js+Vue-router(in javascript)に変更してます。
以下、備忘録。
開発の流れ/詰まった点
Vue-router
最初はメインのページをindex.htmlにゴリゴリ書いていったんだが、
うちのサイトはメインページの他に本用のページがある。
この2種類のページを使い分けるために、メインページが一段落ついてからVue-routerを用いてSPA化に着手した。
Vue-routerはrouter-viewタグ以下にページ個別の内容を記述する。
その”ページ個別の内容”は、通常コンポーネントとして提供する。
つまり、現状からSPA化するためには、下記のようなフローを辿ることになる。
単一ファイルコンポーネント
コンポーネント — Vue.js
Vueのコンポーネントは気の遠くなるくらい多機能だけど、ここではごく基本的な機能のみを利用する。
問題は、コンポーネント化するためにはtemplateにJSの文字列リテラルとして持たせないといけない点。
できれば、リテラルでなく純粋なhtmlとして記述したい。
その欲求を解決してくれるのが単一ファイルコンポーネントである。
これは、例えばhome.vueというファイルを作って、そこのtemplateタグ内にコンポーネント化したいhtmlを記述するというものである。
単一ファイルコンポーネントであれば、ほぼ純粋なhtmlとしてページを記述できる。
単一ファイルコンポーネント — Vue.js
vue.jsのcomponentをwebpackで.vueにして単一ファイルコンポーネントにする - Qiita
ここでの注意点は2つ。
templateタグ直下の要素は必ず1個のみにするという点*1と、
単一ファイルコンポーネントを使うためには、.vueファイルのコンパイルが必要であるという点だ。
Webpack
vueファイルはそのままでは使えない。コンパイルしてJS(VDOM)化しないといけない。
モジュールバンドラが必要になる。
私はモジュールバンドラといえばbrowserifyしか知らなかったのだが、
Webで調べたところ、WebpackというものがVueとよく併用されているようなので、そちらを利用してみることにした。
webpack
webpack 入門 (v3系 対応) - Qiita
vueファイルのコンパイルには、通常の設定に加えて、vue-loaderを使うよう設定する必要がある。
vue-loaderはnpmでインストールする。
Vue.jsを使った大規模開発に必要なもの - Qiita
Introduction · vue-loader
また、vue-template-complierもあわせてインストールする必要がある。
ページが切り替わらない(1)
本用のページは一つ前のページと一つ後ろのページにそれぞれ遷移できる。
最初、ページを切り替えようとすると白いページに遷移してしまうことがあった。
理由は単純で、aタグを使っていたから。
Vue-routerでの遷移はrouter-linkタグを使う必要がある。
遷移先もhrefでなくtoで指定する必要があるため注意。
以下蛇足
ちなみにaタグで単純に遷移できない理由は、SPAではルートが"/"でなく"/#/"や"/?/"などになり、ルートの変換が必要になるためである。
aタグで"/"を直接変更してしまったら、シングルページでなくなってしまうのでSPAでなくなってしまう。よくよく考えてみると自明の理であった。
ページが切り替わらない(2)
上記問題が解決したあとも、ページを切り替えようとするとURLは変更されるのにページの内容は変更されないことがあった。
これについては、下記ページが詳しい。
[vue-router] パラメーター違いのページリンクをクリックしても更新されない時は ? - atuweb : つながりを作るWebプログラマ
以下蛇足
公式のページにも「$routeをwatchしろ」と書いてはいるのだが、
watchで何をすればいいかはいまいちピンときてなかった。
上記ページのようにwatchメソッド内でto.params.idを引数としてdataの各変数を更新してしまうのが直感的でわかりやすい。
その場合、更新用メソッドが必要になる。初期読み込み時に更新用メソッドを読み込むためには、createdプロパティを利用する。
gulpとwebpack
今までgulpを使っていたので、引き続きメインのタスクランナーにはgulpを利用しwebpackはモジュールバンドラに徹してもらうことにする。
gulpとwebpackの連携については、下記ページが詳しい。
以下蛇足
webpackを導入した際、webpackの実行でエラーが発生していた。
"=>"*3を認識していなかったようなので、決断的npmバージョンアップを実行。
(io.js 2.0.2→v8.9.4にバージョンアップ。かなり浦島太郎状態である……)
ところがバージョンアップ後、gulpの途中でよく止まるようになってしまった。
issueのページを見てもピンとこなかったので、古いnodeモジュールをすべて削除。
gulpfileを頼りに、必要最低限のモジュールを入れ直す羽目になった……。
デプロイ
とりあえず諸々整理して、npm startを実行すれば静的サーバが立ち上がるように設定し終わったので、ここでherokuにデプロイ。
しかしデプロイ後、アプリケーションエラーが発生するようになってしまった。
原因は、npm startで利用している一部のモジュールをdevDependenciesとしてインストールしていたため。
静的ファイルはすべてnpm startで生成していたため、heroku上でも同じモジュールが使えないといけないのだが、どうやらherokuではdepencenciesはインストールしてもdevDependenciesはインストールしてくれないようだ。
お行儀がいいとはいえないが、すべてdepencenciesに突っ込むことで解決した。
Vueの感想
- VDOMをhtmlとして書けるのが嬉しい。それでいてコンパイルするから最終的にはVDOMとして動作しているのが面白い
- 最初の動機は「vue.js+vue-loader.jsだけ使えばnpm非依存で作れるのでは……?」だったが、終わってみればnpmからは逃れられそうになかった
- vueファイル内ではpugやstylusも使えるようなので、そっちも使ってみたい
- 「『htmlとして書けるのが嬉しい』のにpug使うの?」って言われるかもしれないけど、JSのリテラルでない点が重要なのでhtml or pugはそこまで重要ではない。むしろvueファイルごとに使い分けられるので、そういう楽しみもある(と思う)
- テンプレートは奥が深そう(親子関係とか)なので、もう少し勉強が必要そう
- アニメーションさせたい
- 既存のruby製サイト(Sinatraなど)に組み込めれば強そうなので、研究したい
以上。
*1:ただしv-if/v-elseを用いて”結果的に要素1個のみになる状況”にしても、許容される
*2:私はvue.jsをダウンロードして利用していたので、vue-cliはインストールしていない
*3:アロー関数( https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/arrow_functions)
遅延処理に使えそうなgem
処理速度を向上させようとして失敗した話
気付けばもう2018年です。
去年は結局ブログのエントリを1本しか出しませんでした。
ブログのエントリは、twitterよりはまとまった話がしやすいですし大概追いやすいので、少し去年の四方山話をまとめておこうと思います。
閑話休題。
去年の12月、実装していたプログラムの納期が迫っていた頃の失敗談です。
そのプログラムは、複数のラジコンを制御するようなプログラムで、
MQTT*1を受信した際の処理とリアルタイム処理の二本柱で制御していました。
このリアルタイム処理が曲者で、
「0.2秒毎にDBのテーブルXの全レコードを最新の状態に更新し処理Aを行う」スレッドAと
「0.5秒毎にDBのテーブルXの全レコードを最新の状態に更新し処理Bを行う」スレッドBが併存していました*2。
この「全レコードを最新の状態に更新」は処理A、処理Bとも”直前”*3に行う必要があるため、まとめることができなかったわけです。
レコード件数は高々10件程度ですが、
プログラムの性質上、当該更新処理にかかる数十ミリ秒は馬鹿にできない時間でした。
かと言って、スレッドでの処理間隔を不用意に開けることはできません。
なぜなら処理間隔が開きすぎるとラジコンの制御が間に合わず、ラジコン同士が衝突したりコースアウトしたりするリスクが増大するためです。
なんとかできないか……。
そこで着目したのは「MQTT受信処理」の方でした。
下記のように処理を修正すれば全レコードを更新するタイミングが減ると考え、実際にプログラムを修正して様子を見ました。
- 特定のMQTTを受信した際にスレッドA,Bで行っている処理を行うべきタイミングかチェックする
- 処理を行うべきタイミングでなければ、行うべきタイミングになるまで待機し、再度チェックを掛ける*4
結論から言うと、これは大失敗でした。
修正後のプログラムを走らせると、10分くらい経過した時点でDBエラーが発生するようになりました。
ログを検証すると「全レコードを更新している途中のスレッドが殺された」ために起きていた現象のようでした。
プログラムを修正した際に一部のスレッドは「そのスレッドが請負う処理を行う必要がなくなった場合*5、外部から当該スレッドを殺す」ようにしていました。
「全レコードを更新している途中でスレッドを殺したらそりゃダメよなぁ」ということで、「レコード更新中はフラグを立てて、外部からスレッドを殺せないようにする」よう修正を掛けましたが、これは何故か失敗。
「面倒だし、スレッドを殺すこと自体を止めよう」と再度修正を掛けましたが、今度はスレッドが無尽蔵に作成され、処理がパンクする事態に。
ここに至ってこの修正部分にこれ以上時間を費やすことは不毛であると判断し、結局、以前通りのリアルタイム処理に巻き戻しました。
この件の教訓としては、概ね以下のような感じです。
- スレッドを外部から殺すような処理を設ける場合、DBなどの外部リソースを変更している最中にスレッドを殺さないようにする
- そもそも一度立てたスレッドはできるだけ外部から殺さない。スレッド内の適切なタイミングで終了チェック処理などを設ける
- (上記に関連して)スレッドなどの非同期処理は基本Fire&Forget(撃ちっぱなし)の精神を厳守する
- 常駐スレッドが無尽蔵に立つような設計は厳に慎む(例えあるスレッドの生存期間が短くても、そのスレッドが後続のスレッドを延々と立て続けるなら、それは常駐と変わらない)
- エンバグなどで困った時のために、修正部分をいつでも切り捨てられるようにする(修正用ブランチを切ってそこで処理するなど)
- 処理速度が致命的に遅いわけでないなら、わざわざ処理速度を上げるための修正をしない。プログラムの修正には常にエンバグのリスクが伴う
3/4 #MegaCiv1703 レポ
3/4にねいじまさん主催の隔月MegaCiv会に参加してきました!
MegaCivって何?って人はこれを見よう!!
今回の参加者は9人で、西側マップでのプレイ。毎度ながら12時間の長丁場となりました。
見知り合いがパチもんさんだけという(私にとっては)新鮮味あふれる面子でのプレイです。
クジで順序を決め好きな国家を選んでいった結果、私はヘラス担当になりました。
混沌極まる欧州情勢の真っ直中にあるヘラス……これは刺激的なプレイが期待できそうです。
以下おぼえがき。
石器時代
1~5ターンは都市のない荒涼とした石器時代。
今までの経験で”5ターン目に32人口から2都市建設、20人口残し”*1が最適戦略と
いう認識があったので、その通りに進める。
この段階で一番重要なのは国境線の策定。
今までのプレイでは(ルールブックにある)”歴史的領土に則りお互い公平に”というスタンスで進めていたのだが、今回は歴史的領土の確認を怠っており、とりあえず目一杯拡大してから交渉しようという方針で進めていった。
その結果5ターン目時点のヘラスの領土が
- 南側はATHENAIまで
- 北側はWESTERN SARAMATIA/EASTERN SARAMATIAまで
- 東側はCREMEAまで
- 西側はPANNONIAまで
とお前拡大しすぎなんじゃというくらい歴史的領土を侵犯した版図になってしまった*2。
流石に周辺諸国から「それは広すぎでは……?」と警告を受けたのでその後徐々に縮小していくことになるのだが、今振り返ってみると、警告で済まされず全面戦争が起きてもおかしくない侵犯ぶりだと思う。
こうなってしまったのは隣接するローマ/ケルトがどちらも初プレイだったことが原因と考えられる。特にローマは4ターン目で1都市建設したのが裏目だったか版図を拡大できず、終盤まで苦しい戦いを強いられていた。
5ターン目の2都市はODESSUS/SARONAEに建設した。片方ローマ用の都市用地なのは気にしない。また注目すべきは4ターン目でイベリアが早々にカルタゴ領に攻め入っていたことか。初プレイ*3だとこういう事故も起きる……。
初期青銅器時代
6~8ターンは都市を乱立させつつ交易を賑わせる初期青銅器時代。
前述の通り大帝国を確立したヘラスであるので2都市→4都市→7都市→9都市と瞬く間に栄華を極めてしまった。
もちろん9都市を維持できるとは思っていなかったが、今回は8都市を3ターンほど維持するという快挙を達成できた。ひとえに他国の歴史的領土(主にローマ/ケルト領)を占領しつつ、戦争をのらりくらりと回避していたおかげなのだが。
さて交易の方はどうだったかというと今ひとつ振るわず。
7ターン目に金属加工、8ターン目に君主論&機織とL2にタッチできないまま時代を駆け抜けた。
中期青銅器時代
9~11ターンは災害と戦いつつ技術を買い漁る中期青銅器時代。
謀反が起きたり、黒海に(ヒッタイト経由で)暴風雨が吹き荒れたり、逆に暴風雨を自引きしてヒッタイトに打撃を与えたり、伝染病をもらったりと、そんな感じ。印象としてはカルタゴが災害引きまくっていたようだ。
特に暴風雨ぶち当てに関しては、ヒッタイトがL5までの自災害を必死で調整した中でコレが命中した結果、ヒッタイトのASTを1つ遅らせる事態になってしまった。これが尾を引いたか、ヒッタイトが優勝争いから一歩退く結果になってしまった*4。
また、この頃になると他国がL3技術にタッチし始めてくる。
ヒッタイトが政治を先んじて取り、次いでケルトが世界的偉業を取る。
ん? 世界的偉業?!
政治は許せても*5世界的偉業*6は許せん。ということでケルトを懲らしめることに。
ちょうどいいことに、以前ケルトに謀反で取られた都市が1つある上に、ケルトがヘラスの国境付近に12トークンを集めている*7。そこで謀反都市に攻め入りつつ、都市計画地にヘラスから1トークン*8を送り込んで都市計画を破綻させることにした。
そしてケルトから交易品カードを1枚いただくのだが、選んだ結果が象牙*9。
これはケルトも怒り心頭だったので、象牙は交易で丁重にお返ししておいた。その後ケルトは(戦争を恐れてか?)徐々に西方に退いていった。空いた土地には人口に余裕のあるヒッタイトが侵食していった。
技術に関しては、識字→建築→法と進めた。この時点で赤メイン路線はほぼ確定。
「カードをケチっても農業は取れるが、ヘラスに農業は不要だろ」という判断で多少無理して建築を取ったのだが、これが後々活きることになった。
後期青銅器時代以降
12~16ターンは優勝争いが本格化し、ついに収束する後期青銅器時代~後期鉄器時代。
12~13ターン目は洪水を自引きしたり、ヒッタイトが引いた洪水でこちらのトークンが多数流されたりしつつ、民主主義、高度軍事、都市様式、記録文書を取得した。ここまでくると赤のL1技術はほぼタダで取得できる。
14ターン目
さて14ターン目、この時点での主観的評価は下記の通り。
- アッシリアは動きは地味ながらL3技術を買い始めておりかつASTもほぼ遅れていない
- ヒッタイトはASTは遅れているが、L3技術にタッチしているので先頭がまごつくと危険
- ヘラスはL3技術にはタッチできてないが都市数は多いしASTは先頭
- ケルトはL3技術にタッチもしているしASTも先頭
- ミノアはL3技術がいち早く3枚に達しており、かつASTもほぼ遅れていない
- カルタゴ/イベリアは技術はあんまり進んでないようにみえるが、都市数はすごい
- ローマは内乱などの受益で育ちつつあるが、脅威ではない
- エジプトはあまり見れてない
ということで主観的にはケルト≒ミノア≧ヘラス≒アッシリア≧他という感じである。
ここで戦況が動く。ミノアがL3技術で明らかに先んじているのが判明した結果、ヒッタイトの主導にヘラス/カルタゴが応じる形で対ミノア同盟が結託され、ミノアに連合軍が雪崩込んだ。とは言え戦況はミノア有利。一応ヘラスが金属加工&高度軍事持ちだったが、他2国は非赤系、対するミノアは軍事&海上戦&高度軍事持ちという盤石の状態だったためだ。結局、ミノアの余剰トークンを消費しきったかどうか、というレベルで収束し、メインのお仕置きは災害頼りとなった。
ちなみに14ターン目のヘラス技術購入は前ターンで札を使い切っていたため、神権政治と神秘主義のみ。青50が赤5クレジット増える彫刻でなく神秘主義なのは迷信を恐れたため。
15ターン目
続く15ターン目。このターン後期鉄器INの恐れがあるヘラスとケルトに矛先が向く。ヘラスはミノア/ヒッタイト連合から攻撃を受けることが明白な状況であった。さらに厳しいことに先手番だったので、高度軍事を活かしつつ、都市の建設が可能な配置を心掛ける。
そして紛争。ヒッタイトの都市攻めは退けたが、ミノアの方は圧倒的物量を前になすすべもなく、1都市陥落。
ミノアに交易品カードを1枚取られることになるのだが、4枚中3枚ハーブ1枚ワックスというデンジャラス構成*10で、取られたのはワックス。九死に一生を得た。
そして交易品獲得。この時点で5都市しかないのでほぼ鎖国状態になるかなと予想していた状態で、まさかの迷信。神秘主義のおかげで2都市衰退で済んだが、海賊都市でさらに1都市取られてしまい、2都市フィニッシュ。当然ながらASTは進めず。ケルトも同様に2都市に落ち込み、勝負は次ターンに持ち越された。
16ターン目
ASTトラックで先頭を走っていたヘラス&ケルトに、ミノア、アッシリア他諸国が追いつく。都市数を削られた2国に変わり、ミノアとアッシリアに矛先が向く。ヘラスは対ミノア同盟には参加せず、6都市建設ムーブで最後の交易*11(&災害)に望みを賭ける。ここで運良く4枚目のハーブを獲得でき、なんとか3枚目のL3技術獲得の目処を立てる。
そして災害。ここで後期鉄器IN確実かと思われたミノアに、エーゲ海の暴風雨が襲いかかる。ヒッタイトの神業じみた神風だ。そしてヘラスも巻き添えで1都市衰退。さらに特殊能力によりアッシリア、ケルトがそれぞれ1都市を併合される。
この結果、ヘラスのみが後期鉄器INすることとなり、ゲームは終了した。
最終的にはヘラスは122点。ありがたいことに1位を獲得できた。
ちなみに2位は終盤8~9都市を維持しつつ技術を買い漁っていたイベリアであった。イベリアが退行でAST遅れてなければ危うかったかも。
感想
正直1位になれるとは思ってなかった。針の糸を通すかのような偶然の連続だった。
ざっと挙げるだけでも
- 14ターン目に神秘主義でなく彫刻を取っていたら
- 15ターン目で「建設の効果は1ターン1回まで」というルールに気付けなかったら
- 15ターン目にミノアにハーブを取られていたら
- 16ターン目に4枚目のハーブを獲得できなかったら
- 16ターン目にヒッタイトが暴風雨を引いていなかったら
- 16ターン目に発生していた飢饉の二次被災を受けていたら
- 2位のイベリアが退行を食らっていなかったら
今回の勝利には繋がらなかったに違いない。
今回はローマのつまづきに乗じる形で前半勢力を拡大しつつ、後半は反らせるだけ矛先を反らしながら逃げ切った、それに尽きると思う。
個人的には今回のMVPはヒッタイトかなと思う。最初から最後まで盤面に影響を与え続けていたから。
ヘラスに関しての感想
今回は歴史的領土を大いに侵犯したプレイだったため、あまり参考になるかはわからないが……。
- 都市用地は6つ。荒地都市を建てやすい立地が多く、7都市維持は比較的容易と思われる。ただしすべて沿岸都市なので攻められると弱い
- 周辺国はヒッタイト/ミノア/ケルト/ローマ。互いが全力で国境を推し進めれば、基本的には歴史的領土に落ち着くはずである。注意しないといけないのがミノアで、ここは絶対に圧力を掛けてはいけない。ミノアは領土に関して人一倍敏感である
- 一方でミノアが伸びた場合はメインで処す役目も帯びている
- 農業は不要。機織はミノアに攻め込むなら有効。天文航法は基本的には不要(遠洋に接している土地が少なすぎる)
以上、おぼえがき終わり!!
参加された皆様、本当にありがとうございました!!