Synthでejsを使う
Synthでテンプレートエンジンにejsを使用する時のメモ。Synthのバージョンは0.5.2です。
back/node_modules/synth/synth.jsに追記
back/package.jsonにejsの項目を追記してsynth install -bを実行します。その後、back/node_modules/synth/synth.jsにejsを使うように変更します。
/* the main synth init function */ exports = module.exports = function (options) { options = options || {}; var defaultResourceDir = path.join(process.cwd(), 'back/resources'); var viewDir = options.viewDir || path.join(process.cwd(), 'front'); //var viewEngine = options.viewEngine || 'jade'; var viewEngine = options.viewEngine || 'ejs';
front/index.ejsを作成
index.jadeがレンダリングした結果をもとに勝手にejs化しました。
<!DOCTYPE html> <html ng-app="my_app"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><%= appName %></title> <% for(var i = 0; i < cssFiles.length; i++){ %> <link rel="stylesheet" href=<%= cssFiles[i] %>> <% } %> <!-- Preloaded Data--> <script> var preloadedData = <%- data %>; </script> <% for(var i = 0; i < jsFiles.length; i++){ %> <script type="text/javascript" src="<%= jsFiles[i] %>"></script> <% } %> </head> <body> <h1><%= appName %></h1> <div ng-view> <% if(preloadHTML){ %> <script type='text/ng-template', id=<%= preloadHTMLPath %>> <% } %> </div> </body> </html>
node_modules/synth/lib/frontendRenderer.jsの改修
front/index.ejsとfront/html/hoge/getIndex.ejsを読みに行くようにコードを書きます。まずはejsをrequireしましょう。
var path = require('path'), Promise = require('bluebird'), fs = Promise.promisifyAll( require('fs') ), jade = require('jade'), ejs = require('ejs'), assets = require('./assets.js');
64行目付近に拡張子を置換してjadeファイルを読みに行っている場所があるので、ejsに置換するコードを追加し、さらにejs.renderFileを使ってejsファイルを読んであげるようにします。
var readHTML = function (path) { var production = process.env.NODE_ENV == 'production'; return fs.readFileAsync(path).error(function (err) { if (err.cause.code == 'ENOENT') { // Couldn't find the HTML version, how about jade? ejs.renderFile(path.replace(/\.html$/, '.ejs'), {}, function(err, result) { if(!err){ console.log(result); } else{ console.log(err); } }); //return jade.renderFile( path.replace(/\.html$/, '.jade'), { // pretty: !production //}); } else throw err; }) .catch(function (err) { if (err.code != 'ENOENT') throw err; return null; }); };
front/html/tweets/getIndex.ejs追加
frontendRenderer.jsを書き換えてもファイルを作らないと読んでもらえないのでファイルを作りましょう。これもindex.ejsと同様にjadeでレンダリングされたあとのHTMLを参考にejs化しました。
<ul class="tweet-timeline"> <li ng-repeat="tweet in tweets" class="tweet"> <div class="content">{{ tweet.content}}</div> <div class="date">{{ tweet.created_at | date:'medium' }}</div> </li> </ul>
以上です。あとはsynth sしてlocalhost:3000/tweetsにアクセスすればデフォルトのjade版と変わらない形でレンダリングされるはず。
UnityでS.S.デッドリー・ボンバーを作ってみる
人造人間13号*1が放つS.S.デッドリーボンバーをUnityで再現してみました。人造人間13号はドクター・ゲロが使っていたマシンが悟空抹殺のために自動で作りだした人造人間です。
構造は、まず真ん中のコアのような球体と、その外側に赤い帯が表示されている球体、さらに一番外側には透明な球体の3つのsphereオブジェクトを作成しました。赤い帯のような模様はテクスチャで表現しました。
透過テクスチャなのでサーフェスシェーダーでマルチパスレンダリングを行います。シェーダーのコードはこちらを参考にしました。
1番外側のsphereには6個のparticle systemを子オブジェクトに設定してあります。原作のようにサイズ差のあるパーティクルにするためにstartSizeをCurveで変更してあります。
6個のparticle systemのうち4個をスクリプト上で180フレームごとにランダムに出し分けています。
using UnityEngine; using System.Collections; public class SSDBOuterController : MonoBehaviour { public GameObject[] particle_lines; private System.Random r; private int[] particle_status = new int[6]; private int particle_interval = 0; // Use this for initialization void Start () { r = new System.Random(1000); for(int i = 0; i < particle_lines.Length; i++){ particle_lines[i].particleSystem.Pause(); } } // Update is called once per frame void Update () { if(particle_interval < 180){ gameObject.transform.Rotate (new Vector3((float)r.Next (2, 8), (float)r.Next (2, 8), (float)r.Next(2,8))); particle_interval++; } else{ // 一旦すべて表示状態にする for(int i = 0; i < particle_status.Length; i++){ particle_lines[i].particleSystem.renderer.enabled = true; particle_status[i] = 0; } int counter = 0; // ランダムで1/3を選んで消す while(counter < (particle_lines.Length / 3)){ int index = r.Next(0, particle_lines.Length - 1); if(particle_status[index] == 0){ particle_status[index] = 1; particle_lines[index].renderer.enabled = false; counter++; //Debug.Log (index); } } particle_interval = 0; } } }
赤い帯の回転と周りの光の筋の回転が同期していないのが原作において仕様であることと、particle systemを固定座標で回転させると見た目がワンパターンになってしまうのでこのような実装にしました。
さらに原作では光の筋の長さが一定ではないため、particle systemの設定を6本のうち2本だけ変えて2倍の長さにしてあります。
赤い帯のsphereと真ん中のコアの周りにただよう拡散光はHaloで表現しました。
ついでにskyboxを設定しました。skyboxのテクスチャはImport Packageで取り込まれるデフォルトのテクスチャです。
私はドラゴンボールに出てくるエネルギー波の中で、人造人間13号が放つS.S.デッドリーボンバーとピッコロの魔貫光殺砲の2つが単体でのビジュアルに最も富んでいるものだと思っています。
コードと素材一式はgithubに置いておきました。
この惑星(ほし)の半分を吹き飛ばすほどのパワーがあるのだぞ!!!
Synthを試してみる
先日東京Node学園14時限目に参加してきました。そこで@pchwさんから紹介があったSynthを試してみました。試してみた分かったことを書いておきます。
SynthはSPA作成を念頭において設計されたWebフレームワークです。
Angular.jsでサイトを作成した場合、HTMLの基本構造だけロードして中のデータは後からレンダリングされます。それだと検索エンジンフレンドリーではないというのと、APIロード完了までに空のdivやtableが表示されてしまってしまうという課題があったので、その課題に対するアプローチの1つということです。
Synthを使用しない(Angular.jsだけで実装した)場合と使用した場合のソースをさらしておきます。ちょくちょく出てくるRGBというプリフィックスやgridといったワードはプライベートプロジェクト由来のものなので特に他意はありません。
Synthを使用しない場合
テンプレート
<html ng-app> <head> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css"> <script src="angular.min.js"></script> <script src="05.js"></script> </head> <body ng-controller="RgbController"> <table class="table"> <tr> <th>Id</th> <th>Name</th> <th>Platform</th> <th>Comment</th> <th>IsCold</th> </tr> <tr ng-repeat="rgb in rgbjson"> <td>{{rgb.id}}</td> <td>{{rgb.name}}</td> <td>{{rgb.platform}}</td> <td>{{rgb.comment}}</td> <td>{{rgb.is_cold}}</td> </tr> </table> </body> </html>
コントローラー側です。
function RgbController($scope, $http, $timeout) { var uri = 'http://192.168.3.2/rgb/rgb.json'; var param = {num: 200}; $http.get(uri, param) .success(function(data, status, headers, config) { log('success', data, status, headers, config); }) .error(function(data, status, headers, config) { log('error', data, status, headers, config); }); // jsonを表示する function log(type, data, status, headers, config) { // delay $timeout(function(){ $scope.rgbjson = data; }, 5000); //$scope.rgbjson = data; } }
Synthを使用した場合
サーバ側。チュートリアルのコードをベースにしています。Promiseオブジェクトを返す必要があるのでmysql-promiseをnpm installしました。
back/resources/rgb/getRgb.js
exports.getIndex = function (req, res) { req.db.configure({ "host": "localhost", "user": "xxxx", "password": "xxxxxxxx", "database": "rgb" }); return req.db.query('SELECT * FROM grid_image').then(function (rows) { console.log(rows[0]); return { data:rows }; }); };
クライアント側。コントローラーから
front/controller/rgb.js
angular.module('my_app') .controller('rgbController', function ($scope, $http, data) { //console.log(data.data[0]); $scope.rgb_entries = data.data[0]; });
続いてビュー
front/html/rgb/getIndex.jade
ul.rgb-timeline li.grid(ng-repeat="entry in rgb_entries").content {{entry.id}} {{entry.name}} {{entry.comment}}
総括
私はVirtualBoxの仮想マシン上に環境を作りましたが、ファイルサイズがデフォルトの8GBだとチュートリアルで使用するmongodbにデータを作るgenerateTeweet.jsがディスク容量不足で失敗しました。VMで試す場合はディスクサイズを余裕を持った値にしておくと詰まずに済みます。ちなみにVMのファイルサイズの変更はこちらを参考に行いました。
preloadを試すときはAPIの返り値をPromiseオブジェクトにしないといけませんが、現時点(2014年8月24日)で公式サイトで提供しているNodeはv0.10なのでPromiseをサポートしていません。
ならばと思いgithubのmasterブランチにのっているものをビルドすると-preが付いているバージョンはnode-sassモジュールをインストールできないと怒られ、Synthサーバを起動できませんでした。Promiseが公式にサポートされる(であろう)Node v0.12のリリースを待ちましょう。
Synthサーバを立ち上げている時にサーバ側のコードを変更すると変更を検知して自動でサーバを再起動してくれるのはうれしいです。
普段Expressを使っていて、ライアントサイドとサーバサイドのコードがごっちゃになっているのはいかがなものかと思っていたのでfront/とback/でディレクトリが明確に別れている構成は好感が持てます。
Firefoxの新機能Loopを試す
WebRTC Meetup Tokyo #3に参加してきました。@chikoskiさんから紹介があったFirefoxの新機能のLoopを試してみました。
LoopはWebRTCを使用して実装されたビデオチャットサービスで、Firefoxのアルファ版のAuroraで試すことができます。
基本的な使い方はGoogle+ハングアウトと似ていて、生成されたURLを相手と共有してチャット開始といった感じです。
Firefox Auroraはメニューに電話アイコンがあるのでそのアイコンをクリックするとURLが生成されます。
生成されたURLを別のブラウザで開くとチャットの画面が表示されるのでボタンをクリックするとWebRTC特有のデバイスアクセスの許可を求めるポップアップが表示されるので許可するとビデオチャットが開始されます。
LoopはGoogle+ハングアウトとは違い、ブラウザの組み込み機能として実装されています。
FirefoxOSで動くアプリと連携できるようにそういう設計にしたというのと、Web-APIの拡充を啓蒙したいというMozillaさんのもくろみがあるのかもしれません。
とはいえ、デスクトップアプリとしても動作していて、ブラウザからウィンドウを切り離してSkypeのように使用することもできます(この辺はGoogle+ハングアウトも同様ですが、、)。
アルファ版ということもあってまだ機能はシンプルですがWebRTCを使ったサービスが増えていくのはいいと思います。あとはSafariやIEでもWebRTCがちゃんと動くようになればもっといいですね!
UnityでLeap Motionが認識した手を動かす
UnityでLeap Motionのビジュアライザーのように認識した手を動かしてみました。手は以下のオブジェクトで表現しました。
Leap MotionのSDKのバージョンはもちろんv2 Betaです。Unity側スクリプト言語はC#を使いました。ソース一式はgithubにあげてあります。
以下ハマった点
1つめ
正規化した座標をUnityのオブジェクトに適応させるときにlocalPositionに代入していましたが、それだと間接の座標だけ他と位置が離れてしまって、パーツの配置に違和感が生じていました。positionに代入しないといけませんでした。
unityWrist.transform.position = utils.getScaledUnityPosition(hand.WristPosition, interactionBox);
utils.getScaledUnityPositionは自作dllの関数です。
2つめ
手首のCubuオブジェクトの回転は直近フレームを保存して、RotationAngleでxyzの軸別に差分角度を取得してtransform.Rotateで動かす方法をとっています。
Unityでオブジェクトを回転させる時はtransform.rotationで絶対値を代入するとか、Quaternion.Slerpを使うとかいくつか方法があるようですが、Leap MotionSDKのAPIと相性がいい方法をチョイスしました。
Hand.RotationAngleで直近フレームとの差分の角度を取得するんですが、こいつがラジアン角で、Unityのtransform.Rotateで回転させるためにはオイラー角である必要があります。
なのでラジアンをオイラーに変換する必要があったんですが、そこに気づくのに半日かかりました。
float angleX = hand.RotationAngle(basisFrame, basis.xBasis); float angleY = hand.RotationAngle(basisFrame, basis.yBasis); float angleZ = hand.RotationAngle(basisFrame, basis.zBasis); // 回転する unityWrist.transform.Rotate(new Vector3( LMUtils.rot2Dir(angleX), LMUtils.rot2Dir(angleY), -1 * (LMUtils.rot2Dir(angleZ))));
LMUtils.rot2Dirは自作dllの関数です。z方向に-1をかけているのは認識した手の向きを変えるためです(positionも反転させています)。
動作中のスクリーンをキャプチャしました。Line RendererがPalmのSphereを貫通してますがそこは気にせず。(Leap Motionのビジュアライザーだと第3間接のSphereを横につなげて見た目を整えてますが、、)。
両手対応してます。ソースは1つにしたかったので、Unityのオブジェクトにタグをふっておいて、スクリプト側ではHandListでforeachしてタグとisLeftやisRightの結果を比較して描画し分けています。
Line Rendererで骨を表現するとlengthを気にしなくていいので楽です。あと親指は他の指より間接が一つ少ない(というかlengthが0の骨がある)ことを考慮する必要あります。
ベースはできたのでこれからUnityとLeap Motionを遊び尽くす。
httperfでnginxにおける非同期処理の限界を知る
nginxに負荷をかけて遊んでみました。webによくあがっているnginxのベンチマークは適当な静的ファイルを1つ用意してそこに対してアクセスを行うといったものです。
ただそれだとメインメモリのファイルキャッシュにのっているものを返すだけで、オンメモリで完結する処理のはずなので非同期処理が売りのnginxのベンチマークとしては微妙じゃないかなと思うわけです。
そこでメインメモリのキャッシュではまかなえない量のデータを用意してかつ複数ファイルに対してアクセスを行うようにして負荷をかけてみました。負荷をかけるツールはhttperfを使用しました。
ネットワークの帯域がボトルネックになっていないことを確認するために、事前にiperfで帯域を調査しておきます。自宅のLANと使用するシステムはすべて1000BASE-Tで接続されているので理論値では1Gbits/sの速度がでるはずです。
------------------------------------------------------------ Client connecting to 192.168.3.3, TCP port 5001 TCP window size: 23.5 KByte (default) ------------------------------------------------------------ [ 3] local 192.168.3.8 port 42975 connected with 192.168.3.3 port 5001 [ ID] Interval Transfer Bandwidth [ 3] 0.0-10.0 sec 1.09 GBytes 936 Mbits/sec
まあ実効値はこんなもんです。ネットワーク帯域はフルに使用可能なことが分かったのでOSの設定をしてopen(2)できるファイルディスクリプタの上限値を引き上げます。
まずはユーザプログラムが開くことができるファイルの最大数を引き上げるためlimit.confを変更します。
#ftp hard nproc 0 #ftp - chroot /ftp #@student - maxlogins 4 * soft nofile 1048560 * hard nofile 1048560
再起動してulimit -nで確認します。
shimada-k@backend:~$ ulimit -n 1048560
これでOS側の設定値は変更しました。今度はhttperf側で開くことができるファイル数を変更します。httperfがコードから参照しているFD_SETSIZEの値を変えてビルドしなおさないといけません。
これは/usr/include以下のヘッダファイルを書き換えることで対応します。OS側に設定した値と同一のものを設定しました。
/usr/include/linux/posix_types.h
#define __FD_SETSIZE 1048560
/usr/include/x86_64-linux-gnu/bits/typesizes.h
#define __FD_SETSIZE 1048560
これと同様の手順をnginxを動かすサーバ上でも行います。httperfに対してはポートの再利用の設定をソケットに対して行うコードを追加してビルドします。
core.cの941行目付近です。
int on = 1; setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on)); SYSCALL (BIND, result = bind (sd, (struct sockaddr *)&myaddr, sizeof (myaddr))); if (result == 0) break;
これでhttperfのソースをDLしてビルドします。nginxもビルドします。ここまでで限界突破されたhttperfとnginxができますので、open(2)できるファイルディスクリプタの上限値がボトルネックになる可能性はなくなりました。
さらにhttperf側でTCPポートの再利用の設定とTCPのTIME_WAIT対策としてWAIT時間の設定をします。LAN内なので短めに設定してます。これをしないとhttperf側で使用できるポート番号の数がボトルネックとなり、bind(2)がエラーになります。 /proc/sys/net/ipv4/以下のファイルに数値を書き込むことで対応します。
echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse echo 10 > /proc/sys/net/ipv4/tcp_fin_timeout
加えてnginxの設定でworker_connectionsを引き上げておきます。デフォルトでは1024ですが4096にあげておきます。
events { worker_connections 4096; }
実験環境を書いておきます。
クライアント側
サーバ側
なお今回nginxのworker_processesは1を設定してあります。nginxを動かすマシンは4Coreマシンですが1プロセスで処理を受け付けます。
アクセス先のデータとしては10KByte分のテキストが書き込まれているファイルを3355440個用意しました。合計で32GBになります。ファイルパスは後述するhttpertの引数に列挙してあります。nginx側のサーバには8GBのメインメモリをつんでありますが、用意した32GByte分のデータをすべてキャッシングすることは不可能です。
httperfを実行する際に引数でリクエスト先のパスが書かれたファイルを指定します。ただ毎回同じ内容のファイルを指定するとメインメモリのキャッシュにのってしまうので、ランダムに並べ替えてhttperfで読み込むようにします。毎回ランダムアクセスが発生させることで、ディスクアクセスを一定数発生させることを目的としています。
#!/bin/bash #----------------------------------------------- # パスが書かれたファイルをランダムに並べ替えて # httperfで負荷をかけるプログラム #----------------------------------------------- HTTPERF='/home/shimada-k/httperf-0.9.0/src/httperf' SERVER='192.168.3.3' if [ ! ${#} -eq 1 ]; then echo 'usage: httperf_text [num request rate]' exit 0; fi RATE=${1} NUM_CONNS=`expr ${1} \* 50` # echo ${RATE} # echo ${NUM_CONNS} # ランダムに並べ替える cat list_text | sort -R > list_text_random echo 'sort file is ready' # httperfが読み込めるようにnull区切りのファイルにする cat list_text_random | tr "\n" "\0" > list_text_uri echo 'httperf uri file is ready' echo "${HTTPERF} --hog --server=${SERVER} --port=80 --uri=/ --wlog=Y,/home/shimada-k/list_text_uri --rate=${RATE} --num-conns=${NUM_CONNS}" ${HTTPERF} --hog --server=${SERVER} --port=80 --uri=/ --wlog=Y,/home/shimada-k/list_text_uri --rate=${RATE} --num-conns=${NUM_CONNS}
上記シェルスクリプトは第1引数をrateに設定して、rateに50を掛けたものをnum-connsに指定しています。50を掛けている理由は、httperfのReply rateは5秒間隔でスコアを蓄積している*1ため、ある程度長い時間負荷をかけないとReply rateの数値を均一な精度で取得できないからです。今回は上記スクリプトに500から2500まで100刻みで引数を与えてhttperfのスコアを記録しました。
rate | second(s) | req/s | Repy rate(min) | Reply rate(avg) | Reply rate(max) | Net I/O(*106) |
---|---|---|---|---|---|---|
500 | 50.002 | 500 | 499.8 | 500 | 500 | 42.3 |
600 | 50.003 | 600 | 599.8 | 600 | 600 | 50.7 |
700 | 50.001 | 700 | 699.8 | 700 | 700.1 | 59.2 |
800 | 50.001 | 800 | 799.9 | 800 | 800.1 | 67.6 |
900 | 49.997 | 900.1 | 899.9 | 900.1 | 900.1 | 76.1 |
1000 | 49.997 | 1000.1 | 999.9 | 1000.1 | 1000.1 | 84.5 |
1100 | 50.001 | 1100 | 1099.9 | 1100 | 1100.1 | 93 |
1200 | 49.997 | 1200.1 | 1198.9 | 1199.9 | 1200.3 | 101.4 |
1300 | 49.995 | 1300.1 | 1300.1 | 1300.1 | 1300.3 | 109.9 |
1400 | 50.002 | 1400 | 1399.7 | 1399.9 | 1400.1 | 118.3 |
1500 | 49.997 | 1500.1 | 1499.9 | 1500.1 | 1500.1 | 126.8 |
1600 | 49.992 | 1600.3 | 1599.7 | 1600.2 | 1600.7 | 135.3 |
1700 | 49.996 | 1700.1 | 1625.9 | 1700.1 | 1774.3 | 143.7 |
1800 | 49.997 | 1800.1 | 1790.3 | 1800.1 | 1809.9 | 152.2 |
1900 | 50.36 | 1886.4 | 1899.1 | 1899.6 | 1899.7 | 159.5 |
2000 | 49.997 | 2000.1 | 1999.8 | 2000.1 | 2000.2 | 169.1 |
2100 | 49.994 | 2100.3 | 2099.9 | 2100.2 | 2100.8 | 177.5 |
2200 | 50.18 | 2192.1 | 1904.3 | 2197.7 | 2477.6 | 185.1 |
2300 | 50.012 | 2299.5 | 1965.9 | 2292.9 | 2568.4 | 193.8 |
2400 | 50.882 | 2358.4 | 1924.1 | 2392.9 | 2791.8 | 199.3 |
2500 | 50.582 | 2471.3 | 2061.5 | 2499.4 | 2917.1 | 208.9 |
上記表が取得した生データです。request/sとReply (min)が一致していればnginx側においてキャパ内の処理ができていると考えていいと思います。すべてのrateにおいてNet I/Oがiperfで取得した帯域以下なので通信がボトルネックにはなっていません。さらにnginxのerrorログにも特にエラーは記載されていないことを確認しているのでファイルopen数の上限もボトルネックにはなっていません。
このままだと見にくいので、Reply rate(min)をrequest数で割った数値をグラフにしてみました。
1700付近から投げたリクエスト数に対してReply rateが安定しなくなっていて、rateが2200以降は一気にグラフが下がっています。実験した環境においては2200並列程度がnginxにおける非同期処理の性能限界なのかなと思います。
目的の場所に的確に負荷をかけるためにはボトルネックを1つずつ取り除く必要があり、ハマりポイントが多かったので苦労しました。
テックヒルズ×iBeacon ハッカソンに参加してきました
iOS7からCoreLocationに追加された機能でメジャー番号とマイナー番号とUUIDを使って通信します。beacon製品は2013年11月から国内で販売されました。
作成したのはWebsocketとiBeaconを連携させたアプリで主にサバイバルゲームでの使用を提案させて頂きました(サバイバルゲームをやったことはありません)。
味方が所持しているbeaconの領域に敵が入ると検知して、「敵が近くにいる!」と教えてくれるアプリになります。beacon側の情報として相対距離が取得できるのでその情報によってより近くに敵がいる場合はUIViewのbackgroundColorを変化させるようにしました。
画面に表示されているのはハードコーディングされた仮想user_idです。SNSと連携することでもっと凝った演出ができると考えています。ソースコードはgithubに上げておきました。
一番ハマった点は敵がbeaconの領域に入った情報を検知する側のアプリでWebsocketを使用するためにAZSocketIOを使用してるんですが、サーバ側でsocket.ioのバージョンが1.0だとうまく通信できないことでした。