2017年12月19日火曜日

今年読んだ本 2017

2017年に読んで良い、またはまぁまぁ良いと思った本。順不同。ここ最近はコンピューターグラフィックスに興味が強く湧いていたので3D関連の本ばかり。洋書多数。


WebGLをいじるのにシェーダーの解説付きのサンプルが見たかったので購入。ただ「おー、すげーいいじゃん」と思う機能のほとんどがOpenGL4.0からのサポートでWebGL2.0では使えないのが多数なのでなんだかなぁと言った感じ。現状のOpenGLで何ができるかをざっと見るには良いと思う。


3Dの当たり判定を自前の計算で行えるようになる。簡潔でいて分かりやすい。物理演算とか実際にどんな風にやっているか興味のある人にはとても良いと思う。


Three.jsの機能をざっと知りたかったので購入。よくまとまっていると思う。けど、ほとんどの内容はThree.jsのサイトみたら全部分かると思う。深い内容はとくになかった。


今年読んだ本で一番。dot product, cross productのような知識がなくても一から丁寧に説明してくれるので良く分かる。個人的にすごくよかったのは本書に出てくるほとんどすべての数式について実際にはそれがどういう現象を表しているのか詳説してくれているところ。理解がはかどった。


読書途中だけど良い。全体的にわかりやすい。込み入った説明になってくると難しくなってくるけどGeometryで問題になることがよくカバーされているので知識欲が刺激される。pseudocodeもあるのでこれをもとにプログラミングするときにも一助になりそう。ただpseudocodeはかなり文章よりだけどね。


ゲーム作るときの参考になるかなと思って購入。色々な撮影テクニックが簡潔に説明されていてわかりやすいし面白い。


遠近法を勉強するのに。ざっと読んだだけだけど良い。あとは実践あるのみなんだけどその時間がなかなかとれない。

2017年12月9日土曜日

VirtualBox上のUbuntuで起動時にブラックスクリーンから進まなくなった場合の対処

久方ぶりにVirtualBox上でUbuntuを起動したところセキュリティアップデートがこんもり溜まっていたり、UbuntuのLTSが16.04まであがっていたり、またVirtualBox自体も5.2.2にあがっていたりしたので数時間かけてまとめて更新していったところ無事に本記事タイトルの状況になったのでその際の対処法を記しておく。

状況としてはVirtualBoxでUbuntuを起動してもブラックスクリーンが表示され、そのまま何も起こらない、になる。

そこで「VirtualBox Ubuntu black screen」でGoogleするとフォーラムやらで質問している結果が出るわ出るわ。みなさん、同じ状況に陥っているらしくこれは解決は早いかなと思ったところ、ほとんどの提案は何の効果もない。ただ3d accelerationを無効にすると起動できるという提案だけは効果があったけれど、Ubuntuは3d accelerationがないと玉音放送をもってしても耐えがたいほどに遅く使い物にNothing。

というわけでトライアンドエラーを盲目的に繰り返すことしばし、なんの光明も見いだせないので、こりゃログでも見るしかないかなとVirtualBoxのログを見てみたところ何やら怪しい文言を発見。
※ログの表示はVirtualBox Managerの対象のゲストOSを右クリックして「Show Log」を選べば表示できる

「OpenGL Info: Host does not support OpenGL extension(s):」

・・・、これじゃねーの?という勘に従いGoogleしたところドンピシャの質問を発見。
[Solved] 3d acceleration not working since 5.1.28 ?

上記のフォーラムによると、「OpenGL Info: Host does not support OpenGL extension(s):」ではなくもっと下の方にある「VERR_CR_PKCS7_KEY_USAGE_MISMATCH」が問題らしくそれのせいで「supR3HardenedErrorV」が発生している模様。自分のログを確認すると確かに似た内容の文言が吐かれているので勧めに従いグラボのドライバを最新に更新し、さらに管理者権限で開いたコマンドプロンプトで「sfc /scannow」を実行し、念のため再起動してからVirtualBoxからUbuntuさんを起動したところ、先ほどまで死亡遊ばされていたUbuntuさんがついに3dブーストとともに正常起動。

今回の教訓はログはすぐにチェックしろ、です。

2016年11月9日水曜日

async、await、CancellationTokenについて

職場でasync、await、CancellationTokenの実装方法についてまとめたので同じ内容をまとめておく。
※サンプルコードはWeb API 2を想定している。またDBアクセス部分はDapperを使用している。

この記事は下記リンクの要約なのでasync, awaitについての詳細は下記のリンクを参照してもらいたい。日本語翻訳されていないので英語。
Asynchronous Programming with async and await (C#)

使うべき場所
下記のような状況ではWebサーバのパフォーマンス向上が見込めるので積極的に使ったほうが良い。
  • DBでの処理(問い合わせ、実行など)
  • DB接続のオープン処理
  • ファイルIOなど
async, awaitの非同期処理で知っておくべきことは「awaitされている処理も呼び出し元のThreadと同一のThreadで処理される」ということにある。なぜならasync, awaitキーワードは呼び出されたメソッドを呼び出し元のThread上で必要に応じて適時処理するだけであり、新規にThreadを作成するわけではないからだ。
Threads(詳細説明)

つまりDBやファイルIOなどの別プロセスの結果待ちが発生する処理などでは非常に有用ではあるけれど、非同期処理になることを期待して単純に処理を二分しても実際には同一のThread上で二つの処理が適時行われるだけなので意味がないということ。ところでWebサーバのCPUリソースを多量に必要とするような処理の場合はasync, awaitとTask.Runを組み合わせると良い。Task.RunはThreadpoolへ処理を投げるのでパフォーマンス向上が見込める。

async, awaitのWeb APIサンプル
// TestController.cs
public async Task<TestDto> GetTest(int id, CancellationToken cancellationToken){ // A
    var dto = new TestDto();
    var data = TestDataFacade.GetDataAsync(id, cancellationToken);               // B
    dto.Salary = CalculateSalary();                                              // C
    dto.Data = await data;                                                       // D
    return dto;
}
  • A、リクエストのキャンセルを考慮してCancellationTokenを最終パラメータに指定する
    時間のかかる処理などはリクエストがキャンセルされた場合に処理を中止できるのが望ましいのでCancellationTokenを実装すると良い
  • B、非同期処理の開始
    AsyncのSuffixは非同期処理のネーミングコンベンションなので関数名につけるようしたほうが良い
  • C、Bが非同期処理されている間にCalculateSalaryが実行される
  • D、Bの非同期処理の結果待ち
// TestDataFacade.cs
public async Task<IEnumerable<TestItem>> GetDataAsync(int id, CancellationToken cancellationToken){
    using (var con = _connectionProvider.GetEditableConnection()){
        await con.OpenAsync(cancellationToken);                  // A
        return await con.QueryAsync<TestItem>(                   // B
            new CommandDefinition(                               // C
                "select * from Tests where id=@id", new { id }, cancellationToken: cancellationToken));
    }
}
  • A、DB接続のオープンを非同期で行う
  • B、Dapperで非同期問い合わせを行う
  • C、cancellationTokenを渡すためにCommandDefinitionが必要
async, awaitの動作解説図
下記リンク先にある解説図を見ると一連の動きが良く分かるので是非参照してもらいたい。
What Happens in an Async Method(詳細説明)

まとめ1
ここまででasync, awaitキーワードの使い方が動作原理を含めて理解できたと思う。使い方を誤らなければ簡易な記述で容易にパフォーマンス向上が見込めるので使える状況では積極的に使っていくべきだとは思うけれど、ともすると処理フローは複雑化しやすく、関連する箇所全体で非同期処理を念頭に置いた実装にしなければならないので使いどころは慎重に見極めないといけない。


非同期リクエストのクライアントからのキャンセル方法
こここらは非同期リクエストをクライアントからキャンセルする方法を解説する。
var currentXHR = $.ajax({
    url: "/api/Test/GetTest/1",
    type: "GET"
});

currentXHR.done(function (data) {
    // 取得したデータで何かする
}).fail(function (jqXHR, textStatus, errorThrown) {
    if (errorThrown === "abort") {
        alert('処理を中断しました。');
    } else {
        alert('処理中にエラーが発生しました。エラー内容:' + errorThrown);
    }
}).always(function () {
    currentXHR = null;
});

// MEMO : 非同期処理中にページ遷移した場合はリクエストをキャンセルする
$(window).unload(function() {
    if (currentXHR) {
        currentXHR.abort();
    }
});

// MEMO : 非同期処理中にキャンセルボタンを押下した場合はリクエストをキャンセルする
$('button.cancel').click(function() {
    if (currentXHR) {
        currentXHR.abort();
    }
});
ページ遷移時に非同期リクエストが処理されている場合は明示的にリクエストをabortしないとキャンセルされない。また重たい処理などを実行する場合はキャンセル処理を実装しておくと不要な処理の実行を防ぐことによってパフォーマンス向上が見込めるので(エンドユーザがレスポンスを待たずにページ遷移してしまったとき用などに)積極的にキャンセル処理は実装したほうが良いだろう。

Web API 2のバグ対応
Web API 2のバグで非同期処理中にAbortするとOperationCanceledExceptionが発生するのでそれを抑制する必要がある。下記コードをGlobal.asax.csに追加してほしい。
class CancelledTaskBugWorkaroundMessageHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
        // キャンセルされた場合はエラー内容を空っぽにして送り返す
        if (cancellationToken.IsCancellationRequested)
        {
            return new HttpResponseMessage(HttpStatusCode.InternalServerError);
        }
        return response;
    }
}

protected void Application_Start(object sender, EventArgs e) {
    GlobalConfiguration.Configure(config => {
        // ...省略...

        // abort処理で発生する例外出力を抑制するための処理
        config.MessageHandlers.Add(new CancelledTaskBugWorkaroundMessageHandler());
    });
}

まとめ2
Single Page Appなどで多量のAjaxリクエストを行っている最中にエンドユーザがページを離れてしまうとサーバサイドで行っている処理はすべて無意味になってしまうので、そのようなときのためにもキャンセル処理を組み込んでおくと良いだろう。

2016年9月15日木曜日

ユニットテストでlog4netのログを出力する方法

いまさらな内容だけど備忘録としてユニットテストでlog4netのログを出力する方法を記しておく。

static constructorで下記設定を行っておけばユニットテストの結果Windowの下にログが出力される。

[Subject("Test")]
public class TestSpec
{
        static TestSpec()
        {
                // MEMO : ユニットテストでlog4netを使用するための設定
                var consoleAppender = new log4net.Appender.ConsoleAppender { Layout = new SimpleLayout() };
                BasicConfigurator.Configure(consoleAppender);
        }

        // 以下省略
}

2016年2月18日木曜日

チヌかかり釣りMEGAの開発でやらなければよかった4つのこと

チヌかかり釣りMEGA(以下MEGA)を公開、運営しはじめてから来月で丸5年になる。また、公開時からホストサーバとして使用してきていたExpressWebがサービスを今年で終了するということで色々とこのプロジェクトの開発を振り返った際に「あぁこうすりゃよかったなぁ」と後悔したことをまとめておきたい。


1、Microsoftの技術は使うべきではなかった
誤解してもらいたくないのでひとまず断わっておくと私は.NET1.0からの.NET開発者である。仕事ではASP.NET, ASP.NET MVC, Windows Forms, Silverlight, WPF, SQL Server等々とどっぷりとMSの開発環境で経験を積んできている。その流れでMEGAの開発の話がビジネスパートナーとの間であがったときに自然とASP.NET MVCとSQL Serverを選択してしまった。しかし、この選択は今でもなんやかやと足をひっぱっている。主に費用面で。

そう、MSの製品を使うと高いのだ。

まずサーバは必然的にWindows Serverとなる。今なら.net coreとかあるのでLinuxでも動作させられるのだろうけど5年前にはそんなものは影も形もなかった。またSQL Serverは他のDBに比べて余計にお金がかかるし、仮に自前のサーバを建てたとしてもライセンス料もろもろが発生する。

私はMSの開発環境は大変好きなのだが、それが自分のビジネス(超小規模)に向いているとは言えない。MS製品は潤沢な資金を持っているお客さんを捕まえたときに使用するべきものだと思う。


2、一つの技術で作るべきだった
実はMEGAは色々な技術で構成されている。
  • メインのサイト
    ASP.NET MVC(C#)、SQL Server
  • 渡船店(余談だが渡船は「とせん」と読む)独自のホームページが更新されたかをチェックするチェッカー(Cron)
    Django nonrel、Google App Engine(Python)
  • 渡船店のホームページから釣果を抜き取ってくるCron on Ubuntu
    Node.js(Javascript)、Backbone.js、Mongodb
  • 独自広告設定用のサイト on Ubuntu
    Node.js(Javascript)、Backbone.js、Mongodb
  • ちぬあたりMEGA(ゲーム)
    CreateJS(Html版)、Android(Android版)
と、まとまりの無い状況になっている。なぜこうなったのか!?

なぜこうなったのか!というと、もうね、やっぱり初手でMSの技術を選択したってのが大きいと自分的には感じているのだけど、それ以上にその時々で流行りの技術に興味のおもむくままに手を出してきたってのが一番悪かったと思う。一応その技術を選択した背景にはそれなりの技術的な理由があるのだけれども、チェッカーを作ったころの2011~2012年あたりはNoSQLが流行りだしてたころで、「おっ、GAEなら無料でサイトもホストできんじゃん」という安易な考えを助長する状況だったのも悪く働いた。この記事を書くにあたって当時書いてたコードをざっと見てみたけど、もうね、ほんときれいさっぱりPythonとか忘れてるから。数年前にコード書くのに勉強して、数ヶ月だけ使って、それ以来使ってない言語とかSyntaxレベルで忘れるからね。仮にチェッカーを手直しする必要が出てももう無理だよ。むりぽ。

さらにチェッカーの開発環境が何だったかさえ定かではない。PC買い替えに伴い、日常的に使わない開発環境を構築しなかったために大和の主砲のロストテクノロジーばりにどうやって開発してたのかさえ分からない。どうすんのこれ状態だ。


3、流行りの技術に飛びつかない
2の続きなんだけど、カッティングエッジな技術とかが話題になっていると気になるし調べちゃうとどうしても使いたくなってくる。で、それで何かしら作っちゃう。でもね、これはほんと危険。まず開発環境が違うので時がたつと確実に環境構築方法を忘れる。環境が残っていればよいけどPCの買い替えなんかがあったら大変だ。でも必要に迫られて数年前に作ったものを修正しようとするとどうやって開発してたかを覚えていない。IDEは何だったのか、どうやってコンパイルしてたのか、Webサーバはどうやって起動させてたのか、などなど思い出すのに苦労する。試行錯誤しながら環境構築するのは面倒なのでモチベーションがあがらず時間もかかる。

サンドボックス的にお試しで最新技術をちょろっと触る分には良いと思うけど、何らかのプロジェクトの一部分にプログラミング言語やらプラットフォームが異なるものを混ぜ込ませるのは本当に避けた方が良い。

たとえば全文検索機能が必要になったときにSQL Serverをすでに使っているのならばFull Text Searchを検討するべきであって、Lucene.Netなどに特段の理由もなく浮気するのは後々面倒な事態に直面することが予想されるので避けるべきだ。


4、安定した技術を使うべき
Node.jsはライブラリも豊富で開発もやりやすかったのだけれども、保守の観点から考えると最悪である。私がNode.jsで開発しはじめたときのバージョンは0.6.9だった。その後、必要な機能の実装も終わり運用を開始し、一年後ぐらいに機能追加した際にNode.jsのバージョンを0.10.1だかに更新しようとしたところ失敗してしまった。なぜかというと、使用しているライブラリの大半がそのバージョンに対応していなかったからだ。

Node.jsのバージョンアップは使用しているライブラリが増えるほどに難しくなる。バージョンアップをすんなり成功させるのはほとんど無理じゃないかな。DLLヘルならぬnpmヘルだ。最新のNode.jsの環境では何らかの解決方法があるのかもしれないけど。

で、今現在のNode.jsの最新バージョンを調べたらv5.6とかになっている。私の環境とメジャーバージョンで5も違う!2~3年でバージョンが5もあがってんのかよ。なんじゃそりゃ。バージョンアップしようにも作り直しに近い形で手を入れないといけないので、とてもじゃないけどそんなことはできない。

このように若い技術は変遷が激しい。互換性を保ったまま移り変わっていくのは難しいので大抵新しいバージョンになるとインターフェースが変わっていたりして動かなくなる。しかもその変わり方が非常に激しい。その技術をメインで扱っているのならそれでもやっていけると思うけれど、つまみ食い的に手を出して、作った後はしばらく放置するような状況の場合は本当に負担になる。


まとめ
MEGAはビジネス的には全然成功していないのでそこらへんを念頭に置いて下記のまとめを読んでもらいたい。
  • お金のかかる技術は使うべきではない(例:MSの製品全般)。ただ、お金があるならこんなことを気にする必要はなし
  • Webフレームワーク、Database、Cron、他のなんらかの便利スクリプトなど、できるだけ同一のプログラミング言語、技術、プラットフォームでまとめる。同じIDEが使えると環境構築の手間も省けて楽
  • 保守を第一に考えて、安定した、こなれた技術を使う。移り変わりの激しい技術はメインで使うのでもきついのに、それがサブだったりしたら本当に大変
ある程度の経験のある開発者ならば言語、技術の違いなどは大した問題ではないのだけれど、プロジェクトの保守、運用を考えた場合に、とくに小さなチームや独りで開発している場合などは、それらの違いが大きな負担になる。これは無用の負荷なのでできるだけ避けるべきである。ヒソカが言うところの「メモリのムダ使い」というやつだ。そんな無用なところに気を使うのではなく、製品の品質向上、機能追加にこそリソースを割くべきである。

と、後悔はつきないけど、具体的にどういうテクノロジーをMEGAのために今後取捨選択したら良いかは今のところ何も思いつかない。

2015年9月2日水曜日

d3.jsでチャートを作る パイチャート

前回に引き続き今回もd3.jsでのチャートをまとめておく。今回はパイチャートについて。

前々回はこちら
d3.jsでチャートを作る ラインチャートとバーチャートを2つのy軸上に描画する
前回はこちら
d3.jsでチャートを作る ツールチップ



SFiddleはこちら

弧上に描画するためのarc()とパイ上に配置するためのpie()を用意する。それと配色用のcolor。
 var arc = d3.svg.arc()
              .innerRadius(50)
              .outerRadius(radius);
  var pie = d3.layout.pie()
              .value(function (d) { return d.sales; })
              .sort(null) // ソートはしない
              .startAngle(-Math.PI / 3) // -60度から
              .endAngle(Math.PI / 3);   // 60度まで
  var color = d3.scale.category10();

データをpie()にかませて配置用のデータへと変換する。
var container = svg.selectAll('g')
                      .data(pie(data))
                      .enter()
                      .append('g');

弧を描画する。
// 色をつけて弧を描画する
  container.append('path')
              .style("fill", function(d, i) { return color(i); })
              .attr('d', arc);

arc.centroid()を使用すると真ん中の座標が簡単に取得できる。
// 真ん中に文字を描画する
  container.append('text')
              .attr('class', 'label value')
              .attr('transform', function(d, i) {
                  return 'translate(' + arc.centroid(d) + ')';
              })
              .attr('text-anchor', 'middle')
              .text(function (d, i) { return d.value; });

弧の外側に文字を描画するには三角関数を使用して座標を算出する。またtext-anchorを角度によって変更している。
// 外側に文字を描画する
  container.append('text')
              .attr('class', 'label name')
              .attr('transform', function(d, i) {
                  // 弧の外側を取得。パイチャートでは90度(Math.PI/2)の位置が0度計算になっているので注意。それなのでxは-する。yはSVGだと向きが逆になるので+する
                  var labelR = radius + 20
                      , x = labelR * Math.cos((d.endAngle - d.startAngle) / 2  + d.startAngle - Math.PI/2)
                      , y = -labelR * Math.sin((d.endAngle - d.startAngle) / 2 + d.startAngle + Math.PI/2);
                  return 'translate(' + x + ','+ y + ')';
              })
              .attr("text-anchor", function(d) {
                  var angle = (d.endAngle + d.startAngle) / 2;
                  return 0 < angle && angle < Math.PI ? "start" : "end";
              })
              .style("fill", function(d, i) { return color(i); })
              .text(function(d, i) { return data[i].busho; });

d3.jsでチャートを作る ツールチップ

前回に引き続き今回もd3.jsでのチャートをまとめておく。今回はおもにツールチップ的なものについて。ツールチップ的な部分以外は前回のものとあまり大差ないため省略するので詳細は前回を参照してもらいたい。

前回はこちら
d3.jsでチャートを作る ラインチャートとバーチャートを2つのy軸上に描画する
次回はこちら
d3.jsでチャートを作る パイチャート



JSFiddleはこちら

X軸。今回はtimeを使用している。またrange()の指定は少しずらすために30からとなっている。
// x軸は日付。y軸にくっつけたくなかったので30からはじめている
  var x = d3.time.scale().nice()
              .domain(d3.extent(data, function (d) { return d.date; }))
              .range([30, width]);
  var yearMonthFormat = d3.time.format("%Y/%m");
  var xAxis = d3.svg.axis().scale(x)
                  .orient('bottom')
                  .tickFormat(yearMonthFormat);
  svg.append('g')
      .attr('class', 'x axis')
      .attr('transform', 'translate(0, ' + height + ')')
      .call(xAxis);

ツールチップ。
// ツールチップ
  var focus = svg.append('g')
          .attr('class', 'focus');
  focus.append('rect')
           .attr({ x: -10, y: -25, width: 105, height: 50 });
  // circle
  focus.append('circle')
          .attr('class', 'profit')
          .attr({ r: 5, cy: -15 });
  focus.append('circle')
          .attr('class', 'sales')
          .attr({ r: 5 });
  focus.append('circle')
          .attr('class', 'expense')
          .attr({ r: 5, cy: 15 });
  // text
  focus.append('text')
          .attr('class', 'profit')
          .style('text-anchor', 'end')
          .attr({ x: 90, y: -15, dy: '.35em' });
  focus.append('text')
          .attr('class', 'sales')
          .style('text-anchor', 'end')
          .attr({ x: 90, dy: '.35em' });
  focus.append('text')
          .attr('class', 'expense')
          .style('text-anchor', 'end')
          .attr({ x: 90, y: 15, dy: '.35em' });

オーバーレイとツールチップの表示場所の指定。チャート上のどこにマウスがあっても表示したいのでオーバーレイはチャート全体を覆っている。mousemove()の冒頭でマウスの現在座標からその位置よりも左側に位置するデータをbisector()で取得している。それだけだとマウスが二つのデータ間にある場合、必ず左側のものが選ばれてしまうのでマウスとデータ間の距離を比較してツールチップを表示するべきデータを取得している。このx.invert()の使用方法はtime()でしか使用できないので注意。ordinal(), linear()の場合(linear()は違うのかも。調査していないので不明)はデータの幅とrange()、マウスの座標から当該のデータを探し出す必要がある(詳細は下記参考を参照してもらいたい)。
// オーバーレイ
  svg.append('rect')
      .attr('class', 'overlay')
      .attr({ width: width, height: height })
      .on('mouseover', function () { focus.style('display', 'block'); })
      .on('mouseout', function () { focus.style('display', 'none'); })
      .on('mousemove', mousemove);

  var bisectDate = d3.bisector(function (d) { return d.date; }).left
      , formatValue = d3.format(",.2f")
      , formatCurrency = function (d) { return formatValue(d) + '億円'; };
  function mousemove() {
      var x0 = x.invert(d3.mouse(this)[0])
          , i = bisectDate(data, x0, 1);
      if (i < data.length) {
          var d0 = data[i - 1],
              d1 = data[i],
              d = x0 - d0.date > d1.date - x0 ? d1 : d0; // 一番近いデータを取得
          focus.attr('transform', 'translate(' + (x(d.date)) + ',' + y(d.sales) + ')');
          focus.select('text.profit').text(formatCurrency(d.profit));
          focus.select('text.sales').text(formatCurrency(d.sales));
          focus.select('text.expense').text(formatCurrency(d.sales - d.profit));
      }
  }

参考
Inversion with ordinal scale