kazumalab tech log

流行りとリラックマと嵐が大好きです。技術的ログ。

初めてのRubyGemsを作りました。

かずまです。
今回は色々困っていることがあってGemを作りました。

作ったもの

Haml-lintをAutoCorrectするものです。
今回はHash Attributeのあとにスペースを自動で入れてくれるものです。

既存のプロジェクトにHaml-Lintを入れたのですが、かなりHashのあとにスペースがなかったりしたので、これは手動でやっていたら果てしない時間がかかると思って作りました。

GlobalなRuby Gemsにはしないつもりです。
自分にとって必要だったので作成してローカルビルドして、ローカルインストールして使っています。

github.com

検証2日、実装1日ほどで作成したのでリファクタリング等はできていませんが、きちんと置き換えてくれると思います。

テストのカバレッジを上げていきたいですがmockを使ってテストを書きたかったので若干そこはまだです。

使ってみる

# bundle exec haml_lint_auto_corrector load file_name.html.haml

今回のlintの規約として

%div{ class: "sample" }
  %button{ class: "button" }= "Sample #{@user.name}"

ハッシュの間の値の前後にスペースを一つ入れるようにしないとLintで引っかかる場合に有効です。
上記コードであれば何もせずにそのまま終わります。

%div{class: "sample"}
  %button{class: "button"}= "Sample #{@user.name}"

このコードだとLintに引っかかるので自動で一つ前のコードのようにスペースを入れた形に置換してくれます。

Haml-lintにはAutoCorrectがない

Rubocopのように自動で置換してくれる機能がありません。
issuesにはたまにあがってくるそうなのですが、作者が好まないのか、Closeされるようです。

もちろん、AutoCorrectを使うのはあまりいい手だとは思えませんのでもしそれで作らないというのであればわかります。

ただ、これから作るプロジェクトであれば、作らなければOKなのですが、既存のプロジェクトだとかなりのコード量にもなっているので治すのは一苦労です。
ですので、使う用途としてはhamlファイルをいじるときに面倒なのでこのコマンドを実行して、自動修正して、あとのLintErrorは自分で修正する形がいいと思います。

実装

今回の実装

  • Pathからファイルを取得
  • バックアップを作成
  • nodeによる分割
  • 正規表現でlintに引っかかったらnodeの中身を置換
  • contextでマージ
  • バックアップが不要になったら削除

node一つがファイルの1行になっています。
contextが、置換したあとのデータファイルを持つイメージです。

難しい部分

面倒だったのが、Stringの中にインスタンスを入れて動的に変更する部分です。
上の例のコードであれば"#{@user.name}"です。

マッチしたら変更しない、にしようと思ったのですが、
1行レベルの正規表現マッチだったので、結構面倒な実装になるなーと思ったので、
計算量的には増えますが、一旦Hash全てにスペース入れたあとにマッチした部分だけもとに戻すということをしています。

if is_string_interprolation
  code_line.gsub!(/\#{\s.*\s\}/) do |word|
    word.gsub!(FRONT_REG_SPACE) { |w| "{#{w[2]}" }
    word.gsub!(BUTTOM_REG_SPACE) { |w| "#{w[0]}}" }
  end
end

色々ツッコミどころはありそうなコードですが、
そういった場合...気になったらpullrequestいただければ嬉しいです。
後にリファクタリングする予定ですが。。。

ということで今後もサービスを効率よく進めるために色々作りつつコードの綺麗さを上げて行きたいと思います。頑張ります。

Unity SteamVR Pluginのソースを読む Part2

かずまです。

今日も少しSteamVRのAPIを読んでいこうと思います。
前回は後半寝ぼけながら書いて...いました。

f:id:kazumalab:20170704070157j:plain

SteamControllerが有効になったところの処理を追っかけて終わりました。
今日はその続きから。

読むのもちろんこちら!


前提

まず、Controllerを動かすための分類分けをしておきます。

APIを提供している部分

  • SteamVR_Controller.cs

Controllerの実装を行っている部分

  • SteamVR_TrackedObject.cs
  • SteamVR_TrackedController.cs
  • open_api.cs
  • SteamVR_Utils.cs

基本開発で使うものはSteamVR_Controllerだけをスクリプトで呼び出して、ボタンが押された!などと言ったCallbackを受け取ることになります。
前回の読み会はわかりにくかったので、今回はサンプルを出して、そこから辿っていこうと思います。

// 常にControllerの位置を取るサンプルコード
void Start () {
	if (trackedController == null) {
		trackedController = this.gameObject.GetComponent<SteamVR_TrackedController>();
	}
}

void Update () {
	device = SteamVR_Controller.Input ((int)trackedController.controllerIndex);

	Debug.Log(device.transform.pos);
}

上記コードのDebugの部分、Controllerの位置を常に表示しているスクリプトについて辿って行きます。

device.transform

まずはtransformを構成している部分のお話です。
通常のMonobehaviorのtransformとは型が違うようです。

f:id:kazumalab:20170704054557p:plain

SteamVR_Controller.csの49行目に来ました。
ここではまずSteamVR_Utils.RigidTransformがどんなものなのかを見て、その後ゲッターのインスタンス生成時の引数poseが何かについて見ていきます。

SteamVR_Utils.RigidTransform

f:id:kazumalab:20170704055607p:plain

RigidTransformメソッドが何をしているのか?

ここでは4 × 4のMatrixを受け取ってVector3に変換しています。
Matrix4x4.identityは恒等行列を返しています。

恒等行列はこれです。

 {
Matrix4x4.identity =
\left(
 \begin{array}{ccc}
   1 & 0 & 0 & 0 \\\
   0 & 1 & 0 & 0 \\\
   0 & 0 & 1 & 0 \\\
   0 & 0 & 0 & 1
 \end{array}
\right)

}

UnityC#のリファレンスにも載っています。
Unity - スクリプトリファレンス: Matrix4x4.identity

// 
public RigidTransform(HmdMatrix34_t pose) {
	var m = Matrix4x4.identity;

	m[0, 0] =  pose.m0;
	m[0, 1] =  pose.m1;
	m[0, 2] = -pose.m2;
	m[0, 3] =  pose.m3;

	m[1, 0] =  pose.m4;
	m[1, 1] =  pose.m5;
	m[1, 2] = -pose.m6;
	m[1, 3] =  pose.m7;

	m[2, 0] = -pose.m8;
	m[2, 1] = -pose.m9;
	m[2, 2] =  pose.m10;
	m[2, 3] = -pose.m11;

	this.pos = m.GetPosition();
	this.rot = m.GetRotation();
}

ControllerとかHMDの座標はMatrixで返って来てるのかな?という所感です。
次にここを常に読んでいる場所があるはずです。そこを見ていきます。
座標ゴニョゴニョはわかりやすいものを参考文献に乗せています。

値がマイナスになっている箇所は位置の場合はZ軸に対して反転させている、という解釈ですが間違っていればご指摘ください。
参考にした資料にも載っていますが、GetPositionメソッドに書かれている通りです。

// SteamVR_Utils.cs 117行目
public static Vector3 GetPosition(this Matrix4x4 matrix)
	{
		var x = matrix.m03;
		var y = matrix.m13;
		var z = matrix.m23;

		return new Vector3(x, y, z);
	}

三列目を使っていることがわかります。
matrixにはGetColumn(int)というメソッドがあるのでそれで取ってきても同じことができそうですね。

ん?あれ?おかしい。
Rubyのメタプロっぽくなってるのはなんで?

  var m = Matrix4x4.identity;

この行ではMatrixの型が入るはず、、、なのになんでSteamVR_Utils.GetPositionが呼び出されてるんだろうか。
詳細を見てみるとExtension method from SteamVR_Utilsと書いてあります。

とりあえず、"Extension method from" でググってみたところ、C#には拡張メソッドがあるみたいです。
それを使えばRubyのメタプロっぽい書き方ができますね。ただし、新たにメソッド名を定義しては使えないみたいですね。

ufcpp.net

なので、ここでGetPositionの引数にthisが入っているのはそういうことだったんですね。
このクラス内だけで呼び出せる拡張メソッドということですね。
気になるのでこの話題は今度プログラム書いてみようと思います。

pose(TrackedDevicePose_t)

次にSteamVR_Utils.RigidTransformのインスタンスを生成する時に使用しているposeについて見ていきます。

f:id:kazumalab:20170704055500p:plain

poseはTrackedDevicePose_tという型を持っていて、それはstruct型になっています。
Trackingするもの(HMDとかControllerとかTracker)の情報を構造的に持たせるために作っています。
次のopen_api.csにかかれているTrackedDevicePose_tです。

// open_api.cs 3944行目
[StructLayout(LayoutKind.Sequential)] public struct TrackedDevicePose_t
{
	public HmdMatrix34_t mDeviceToAbsoluteTracking;
	public HmdVector3_t vVelocity;
	public HmdVector3_t vAngularVelocity;
	public ETrackingResult eTrackingResult;
	[MarshalAs(UnmanagedType.I1)]
	public bool bPoseIsValid;
	[MarshalAs(UnmanagedType.I1)]
	public bool bDeviceIsConnected;
}

StructLayout

最適なレイアウト(メモリレイアウトの話)をするために利用します。
そのためにまずはメモリのアラインメントを知る必要があるらしい。

前提としてメモリの読み書きは4、8の倍数になっている方が読み書きが早いので
プログラミング言語ではアドレスがきれいな倍数になるようにフィールドを並び替えます。

C#ではStructLayoutを使うことでそのフィールドをカスタムできるらしいです。
そもそも個人でゲーム作ってる時にアラインメントを気にしたことはなかった、と言うより初めて知りました。(恥ずかしい)

LayoutKind.Sequential

これは宣言した順番に並べていく手法。
未使用であってもきちんとそのメモリを確保します。

他にも

  • Auto: コンパイラー裁量で並び替えを認める
  • Explicit: 複合型の作者が明示的に位置を指定する

もしかすると今後使うところがあるかもしれないのでメモします。

MarshalAs(UnmanagedType.I1)

マネージ型とアンマネージ型

マネージ型はGC(ガーベージコレクション)が自動的にいらなくなったオブジェクトを破壊してくれたりする。
ココらへんは自動でメモリ管理を行ってくれないようになっているの管理しないと行けないのだろうと予測している。
MarshalAsを使うことで、マネージ型からアンマネージ型への変換が可能になるそうです。
ココらへんは個人的に若干難しく感じます。

I1

1 バイト符号付き整数。 このメンバーを使用すると、Boolean 値を 1 バイトの C スタイル bool (true = 1、false = 0) に変換することができるみたいです。ってかC#ではそもそも1と0のboolはできなかったのか....普段は使わないけど。

d.hatena.ne.jp

あとbool型をそのまま使うと8bitあるそうです。
bbs.wankuma.com
若干信憑性に欠けるソースかもしれませんが...。

今日はここまで!
これでtransformに何が入っていて、どのように値が生成されて...までが読めたはずです、はず。

最後に

今日はControllerが動いているところのソースを分解してみました。
このような感じでControllerが動いているんだなーという所感です。
若干これでいいのか?という感じで終わりますが、読みにくくはないかな〜という感じです。
正直openvr_apiについては5000行近くあるのに少ししか読めてないので結構つらそうだなーという...。

気になる点があればご指摘くださいませ〜。

Unity SteamVR Pluginのソースを読む Part1 導入

かずまです。

SteamVRPluginのソースを読みます。
そのメモです。
主にC#を理解できればいいかなってところです。

準備アセット

これです。
HTC Viveを持っていればこれをUnityに入れて動かすって感じです。

ここから気になったところをどんどん羅列して書いていく

SteamVR_Events.csでのイベント管理

// 189行目
static System.Collections.Generic.Dictionary<EVREventType, Event<VREvent_t>> systemEvents = new System.Collections.Generic.Dictionary<EVREventType, Event<VREvent_t>>();
	public static Event<VREvent_t> System(EVREventType eventType)
	{
		Event<VREvent_t> e;
		if (!systemEvents.TryGetValue(eventType, out e))
		{
			e = new Event<VREvent_t>();
			systemEvents.Add(eventType, e);
		}
		return e;
	}

Calibrationが有効になっているときとか、なった瞬間とかいろんなシステム側のイベントクラスになっているみたい。
Dictionaryを取り出す時にTryGetValueを使ったほうがKeyNotFoundExceptionよりも計算量的にも効率いいらしい。なるほど。

Dictionary.TryGetValue メソッド (System.Collections.Generic)

EventクラスがUnityEventを継承している

永続的にシーンにコールバックを追加できるらしい。
わざわざ毎回Callbackをセットしなくてもいいのか。便利。

確かにSteamControllerが有効になった時にいつでもCallbackが呼び出せるようにしておかないとだめか。
呼び出しはInvoke()で呼べる。

Unity - マニュアル: UnityEvent
Unity - スクリプトリファレンス: UnityEvent

SteamVR_TrackedObject.csでのトラッキングするオブジェクト

このスクリプトではMonoBehaviourを継承している。
そのためOnEnableやOnDisable()が使える。
この2つのメソッドはオブジェクトが有効になった時に呼び出されるCallcackになっている。

Unity - スクリプトリファレンス: MonoBehaviour.OnEnable()

// 74行目
newPosesAction.enabled = true;

newPosesActionは先程見たSteamEventsになる。

void Awake()
	{
		newPosesAction = SteamVR_Events.NewPosesAction(OnNewPoses);
	}

OnNewPosesというメソッドをActionに追加してオブジェクトを渡している。
ここの56行目あたりで、位置が取れそう。

  var pose = new SteamVR_Utils.RigidTransform(poses[i].mDeviceToAbsoluteTracking);
  if (origin != null)
    {
	transform.position = origin.transform.TransformPoint(pose.pos);
	transform.rotation = origin.rotation * pose.rot;
  } else {
	transform.localPosition = pose.pos;
	transform.localRotation = pose.rot;
}


ここのposeで値が取れそうな気がします。
次はSteam_Utils_SteamVR_Utils.RigidTransformを見ていこうと思いますが、
今日はここまで。
とりあえず、SteamControllerやTrackingするオブジェクトが
Steamに認識されたときに行われる部分までいきました。

以上です。

VM上のNginxからProxypassでMac上で起動しているRailsを動かすお話

かずまです。

お久しぶりです。
社会人になってはや3ヶ月、元気にやっております。
Rubyのエンジニアとしてどうにか勉強中です。


さて、今日はネットワークネタになります。
Ansibleを使ってVagrant上にNginxを起動し、ローカル環境のMacで動いているRailsを動かしてみようというお話です。
その時に詰まったのでメモしておきます。

Vagrant

www.vagrantup.com

簡単にMac上にVMを建てられます。
AnsibleやChef、Itamaeを使うと環境構築が一発にできます。

今回はCentos7を使って行きます。
Vagrant Fileにprivate ipをお忘れなく。

www.vagrantup.com
ちなみに、外からも見えるようにしたい場合はpublicを使うと良さそうです。

Nginx

静的なページを表示したりするWebサーバー。
RailsとかNodeJSはアプリケーションサーバーです。

今回の構造

f:id:kazumalab:20170628222948p:plain

あるPCからNginxのIPアドレスでアクセスして、それをNginxが受けて、Rails側にリクエストを送るという構造。
このボックス一つは一つのサーバーになっています。

ただし、今回は面倒なので、リクエストの送る先と受ける先を同じにするということです。

Nginxの設定

今回はインストールなどの導入は省きます。
Ansibleを使う場合は一度手動でサーバー構築してからやるほうが良さそうです。

とりあえず今回はdefault.confをいじることにします。
アクセスしたものをプロキシーパスでローカル環境のMacを指し示しているわけですね。

# on VM Terminal
# vim /etc/nginx/conf.d/default.conf
server {
    listen       80;
    server_name  localhost;

    access_log  /var/log/nginx/log/host.access.log  main;
    error_log   /var/log/nginx/log/host.error.log;

    location / {
        # rails ip address in other server
        proxy_pass http://192.168.0.2:3000;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

一応、ミニマムな感じで書きました。
実際に運用する場合はヘッダーに色々記述する必要があると思いますので。

後はログは出力する設定にしたので、もしディレクトリがなければ生成してください。
権限はrootでも大丈夫なはずです。書き込み権限だけは与えてください。

proxy_passに自分のローカル環境のip + Railsを立ち上げたポートを記述しておきます。

Railsを立ち上げる

# on Mac Terminal
$ rails s -b 0.0.0.0

これで同じネットワーク内であればIPでアクセスが可能です。
一応、Macのip+ポート番号でアクセスできるか確認して、VM側でもCurlとかして確認すると良さそうです。
Headerのみ返す場合は

# on VM Terminal
$ curl --head http://192.168.0.2:3000

多分こんな感じだったはず。

アクセス時にPermissionがないといわれる

これでNginxを再起動してIPでアクセスしたところ、一応反応はあるけど502が返ってくるようになりました。
先程設定したログを見てみるとパーミッションでコケているらしいです。

# on VM Terminal
failed (13: Permission denied) while 
connecting to upstream, client: 192.168.0.2, server: localhost, request: "GET / HTTP/1.1", upstream:

ぐぐってみたところ似たような問題が!

stackoverflow.com

SELinuxがだめっていっているそうですね。
まぁそもそも違うサーバーにプロキシーで飛ばすのおかしいよというお話だと思いますが、とりあえず、動かすことが目的なので、
SELinuxをDisableにしてみます。
もちろんSELinuxにProxypassを有効にしてあげればいいのですが、その前にまず原因がSELinuxなのかを判別したかったのでとりあえずオフにしました。

# on VM Terminal
$ setenforce 0

これでNginxを介して、ローカルのRailsを動かすことに成功しました。

参考URLに書いてある感じで、止めるのではなくSELinuxの設定をすると良さそう。
以上です。

LINE SIMを契約してみました件。

かずまです。

今月全く更新していなかったようです。
Uniteに行ってきたよ!ぐらいしか報告できることはないです...笑

さて、今回はタイトルの通りLINE SIMを購入してみたというお話なので技術系ではありません。

LINE SIMとは?

今流行の格安SIMの一つで、株式会社LINEが手がけるサービスの一部になります。
ちなみにLINEの回し者ではありません。

ドコモのユーザーであれば今持っているスマホにSIMを差し込み簡単な設定をするだけで使えます。
同じくSIMフリー端末を持っているユーザーでも可能です。

特徴

値段が安い!

結論から言うとdocomo + LINE SIMで4500円になりました。(以前は9000円なので約半額)

docomoの回線を使っている

docomoの通信回線を借りているので、docomoが入る場所であればサクサクつながります。
回線が混雑している場合は除くそうです。

契約したもの

僕は音声通話はdocomoのカケホーダイを使っているので、データのみSIMプランを選択、
さらにInstagramFacebookTwitter、LINEはよく使うのでコミュニケーションプランの5GBにしました。

f:id:kazumalab:20170521212200p:plain
https://mobile.line.me/plan/communication-free/より


月額は1640円です。
初月はこれに400円のSIMカード発行料金がかかるそうです。

利点

これはかなり利点だと思います。
僕の場合TwitterInstagramはよく観るし、通信の割合としてもブラウザを見ている通信量よりも多いので
すごくうれしいです。なのでTwitterを見るだけであればずっと合計0バイトです。
ちなみに5GBを超えてしまった場合はWeb閲覧等は遅くなりますが、SNS系は速度は早いままです。便利!

  • 契約はWebからも店舗からもできる(即日渡しもあり)

僕はwebでやったのですが、うまく契約できず(保険証+補助書類でうまく契約できなかった)、結局店舗にいきました。
店頭で受付しているのは

mobile.line.me

ここでできるそうです。また、申込みをしてすぐに受け取れるのは

mobile.line.me
ここの店舗のみだそうです。
ちなみに僕はヨドバシカメラ秋葉原店に行ってきました。

即日渡しは20時までに申し込み完了しなければいけない

みたいです。
店舗で即日渡しを希望される方は19:00までに行くといいと思います。

  • 契約情報などがLINE上で見られる

LINE Mobileの公式アカウントとお友達になると契約情報を確認したりできます。

f:id:kazumalab:20170521213746p:plain:w300
これはかなり便利です。リアルタイムで見られます。

f:id:kazumalab:20170521213820p:plain:w300
ここからサイトに飛んで詳細情報も確認できます。

  • LINEポイントがたまる

毎月使用料金が請求されると思います。
その金額の100円につき1ポイントはいるそうです。

何ヶ月かするとスタンプなど買えますね。

注意点

Webブラウザから見る公式のTwitterInstagramのカウントされない(言ってた気がする)

Messengerや非公式のTwitterクライアントはカウントされる

支払いはクレジットカードのみ

僕みたいにdocomo+LINE SIMだとスマホが2台もつかデュアルシムの端末が必要