コミケ告知

サークル活動の詳細は circle タグの記事へ。
2013年1月15日火曜日

viユーザー専用PS3コントローラーを作ってみた

# はてなダイアリーから移動した記事です。あまり真面目に整形していません。

PS3コントローラーの配線を外に引っ張り出して、テンキーを繋いでみました。
ゲームのコントローラーの入力(=スイッチ)を引っ張りだして挿げ替えただけですが。

f:id:moccos_info:20130115063320j:image:w300f:id:moccos_info:20130115063333j:image:w300


動画版も作ったんだけど、動画にして面白いものではなかった…(゚Д゚) 原理的にはとても単純だし、見た目がすごくなるわけでもないので、不向きでしたね。
D

動機

東方シリーズでSTGを覚えたため、キーボード操作でしかSTGを遊べない身体になっていました。PS2のSTG買っても、十字キーではとてもプレイする気にならなかったのです。勿体ないので、いつか作ろうとは思っていました。
コントローラーの入力スイッチ部分に、カーソルキーに相当する入力デバイスの出力を繋げれば出来るので、回路は簡単です。外形加工は多少めんどくさいですが…。

昨年末に、そろそろ作ろうかなーと思い立ったとき、ちょうどVimを使い始めの頃だったので、ついでにVimのカーソルキー配置にしようかという余計な考えが混入し、こうなりました。

素材

市販の適当なPS3コントローラーと、適当なUSBテンキーを組み合わせました。特定の製品に依存するようなことはないので、好きなもの(安いもの)を使えば良いはずです。今回使ったのは以下の製品。



コントローラーはSTG目的なので、有線を選択しました。この製品は安いし、レビューを見るとPS2規格ソフトウェアでも動作するようなので選びました。
テンキーはスイッチ部分しか使わないので、入力感に拘るつもりがなければ、安さ追求で良いでしょう。秋葉原だと200円くらいで売っているのも見かけました。運がよければ、100円ジャンクにもあるかも。

工作 - コントローラー側

十字キーの入力を外に引っ張り出すのみです。原理としては一切難しいところはありません。

f:id:moccos_info:20130115063317j:image:w200f:id:moccos_info:20130115063316j:image:w250
スイッチの金属らしき部分はソルダーレジストが乗っているのか、半田付けできませんでした。しかし、どのキーの近辺にも半田付けできるエリアが用意されていたので、ありがたくそれを使わせてもらっています。何のためにこうなっているのかは謎です。


f:id:moccos_info:20130115063318j:image:w250f:id:moccos_info:20130115063314j:image:w250
何かで使った余りの基板の欠片があったので、内部と外部を接続するようにネジ止めして、外側にピンヘッダを付けました。

工作 - テンキー側

分解

以前外付けHDDを分解したときに少し大変だったので身構えていましたが、突くと開くタイプの簡易ツメ+底面のネジで組み立てられていて、綺麗に簡単に分解できる製品でした。底面のゴム足シールの裏にネジが隠されているのに気づかず、筐体の端にでかい穴を開けてしまいましたが、そんなことは不要でした。
f:id:moccos_info:20130115063319j:image:w300

メンブレンスイッチ

メンブレンスイッチの構造については、以下の記事がわかりやすく、参考にしました。

導電インクで回路が印刷されたシートが2枚、絶縁用のシートが間に1枚。
f:id:moccos_info:20130115063330j:image:w300

絶縁用シートにはボタンが押される部分に穴が開いていて、キー入力によりそこが接触し、表・裏間が接続されます。こんな簡単な仕組みだったとは。
流用するためには、端に引き出されている部分のどの線がどう配線されているか、解析する必要があります。このシートに直接半田付けしようとすると溶けてしまうので、無難に元々の配線パターンに従って、端から引き出すことにしました。シートの位置固定の問題もあるので、余計なことはせずに、USBテンキーの元々の形態そのままになりました。使うのはキー4つだけなんですけどね。

f:id:moccos_info:20130115063332j:image:w250f:id:moccos_info:20130115063331j:image:w250

配線パターンは、この製品では以下のようになりました。fが表、rが裏です。製品ごとに違ったり、同じ製品でもロットにより改変がされる可能性もあるので、データそのものが誰かの役に立つものではありませんが。

f:id:moccos_info:20130115084333p:image

当然、それぞれの穴が表裏1つずつと繋がる構造になっています。これなら4/5/6/+を使うのが一番簡単そうですね。ただ今回、間違えて10pinの変換基板を用意してしまい、端を落とす必要があったため、1/2/3/Enterの横並びを使う事になりました。
実は変換基板の値段がテンキーと同じくらいかかっていて微妙感溢れるものの、量産しない場合に390円のコストはまあOKということにしましょう…
f:id:moccos_info:20130115063326j:image:w250

次の写真は、もともと入っていたUSB接続の基板とコネクタ。ここに半田付けして引っ張りだしても良かったかなあ。
f:id:moccos_info:20130115063325j:image:w300

外部への引き出し

コントローラー側と同様に、筐体に穴をあけて基板を通し、内側をネジ止め・外側にピンヘッダという構成です。
f:id:moccos_info:20130115063322j:image:w250f:id:moccos_info:20130115063324j:image:w250
ネジ穴を適当に開けたため、ちょっと曲がっているのがださいですが、誰も見ませんこんなところ。

使用感

viのカーソルキーが全く身についていない状態なので、まだ上下移動がうまくできません。さらに、ナナメ移動のときに1個空きに同時押しするのが直感的に難しいです。
そして、ゲームをする際には当然4本指を固定で置きますが、本来HJKLはJがホームポジションなので、指使いがちょっと違います。JとKの位置関係は覚えられるかもしれないけれど、指使いを脊髄に叩き込むとまでは行かないかもしれません。


また、実は左手で通常コントローラーの右手部を操作していることにも、ちょっと無理があります。こっちも左手用のテンキーを用意するのがベストなのかもしれません。もうやらんけどw

2013年1月4日金曜日

Windows上でVisualStudio 2010と2012用にboostをビルド(x86/x64)

# はてなダイアリーから移動した記事です。あまり真面目に整形していません。

意外とめんどくさくて、次忘れそうなのでメモ。

ビルド環境

cl.exeでビルド。環境変数の設定がたくさんあって大変なので、無理せず、スタートメニューから辿れるビルド環境のコマンドプロンプトを使います。

  • Windows SDK 7.1 Command Prompt
    • Microsoft Windows SDK v7.1の下にある

Visual Studio 2010のVisual Studioコマンドプロンプトだと、x86(32bit)しか作れないっぽいので、x64(64bit)のライブラリをビルドするのには必要。そもそも64bitバイナリをビルドする人はインストールしているはずですが。
Visual Studio 2012ではx86用とx64用のプロンプトが選べるので、2012だけならそちらで良いかも。

x86/x64の選択

setenv でさくっと設定できます。

Usage: "Setenv [/Debug | /Release][/x86 | /x64 | /ia64][/vista | /xp | /2003 | /2008 | /win7][-h | /?]"
/Debug - Create a Debug configuration build environment /Release - Create a Release configuration build environment /x86 - Create 32-bit x86 applications /x64 - Create 64-bit x64 applications /ia64 - Create 64-bit ia64 applications /vista - Windows Vista applications /xp - Create Windows XP SP2 applications /2003 - Create Windows Server 2003 applications /2008 - Create Windows Server 2008 or Vista SP1 applications /win7 - Create Windows 7 applications


Debug版とRelease版両方一度にビルドしてくれるので、そこを設定する必要はなさそうです。
> setenv /x64
または
> setenv /x86


ビルド時の引数による設定

まず、ビルド用の準備(共通)。解凍したboostソースコードのトップディレクトリで、バッチファイルを一撃。

h:\>cd boost_1_52_0
h:\boost_1_52_0>bootstrap.bat


あとは必要に応じてbjamへの引数を変えます。
参考:c++ - 64-bit version of Boost for 64-bit windows - Stack Overflow

  • ターゲットバージョン
    • --toolset=msvc-10.0 など
      • 9.0が2008、10.0が2010、11.0が2012
  • 出力先
    • --stagedir=x64 など
    • 指定したディレクトリの下にさらに lib が掘られて、そこに出力される

例:

bjam --toolset=msvc-11.0 --build-type=complete --stagedir=x86 stage
bjam --toolset=msvc-10.0 address-model=64 --build-type=complete --stagedir=x64 stage


64bit環境にしても、address-modelを設定しないと32bitになります。

32bitのビルドが行われる場合の、ビルド開始直後の表示例。
Performing configuration checks
- 32-bit : yes - x86 : yes


64bitのときは、32-bit の項目が表示されませんでした。(noと表示されるわけではない)
Performing configuration checks
- x86 : yes


出力ファイル

出力ファイル名は boost_random-vc100-mt-1_52.lib のように、コンパイラのバージョン入りになるので、バージョンごとに最終的な置き場所を変える必要はなさそうです。ファイル名のboostバージョンの前の部分に、gdと入っているのがデバッグ版です。boost_random-vc100-mt-gd-1_52.lib など。
ただし、どうやらこの長い名前をプログラマが意識する必要はなさそうです。

リンク

ヘッダをincludeしただけでビルドすると動いてしまい、なんだこりゃ?とヘッダを追いかけてみると、どうやら環境を判断して必要なlibファイルの名前を生成し、勝手にスタティックリンクしてくれているようです。あちこちに転がっているconfig.hppや、config/auto_link.hppなどが暗躍しています。
どうやらダイナミックリンクにしたい場合は…

  • boost関係全てDLLにするなら、BOOST_ALL_DYN_LINKを定義
  • 特定のライブラリのみDLLにするなら、BOOST_PROGRAM_OPTIONS_DYN_LINKのような個別定義を使用

2013年1月3日木曜日

2012年に覚えた(はずの)事一覧

# はてなダイアリーから移動した記事です。あまり真面目に整形していません。

プログラミング関係で何か覚えるたびにTwitterにポストしていたものの振り返り。
Twitter記法並べたらクソ重いなあ。




言語はJavascript(主にjQuery)、Erlang、Java、Scala、C++、まれにC#やPython。環境はVimとgitが多めでした。
覚えた、って書いてもその後二度と使わないようなものは、見事に忘れてます。tmuxはCygwinで動かなかったから使わなくなったし、Vimで画面分割するのに慣れたからGNU screenでの画面分割も使っていないなあ。


しばらくErlangは使わないだろうから、完全に忘れちゃうかもしれません。一昨年少し触ったHaskellは、もはや入門からやり直さないといけない程度に忘れました。うーん勿体ない。Java・Scala・C++はそれなりに進歩、C#・Python・R言語は実力キープ~やや前進という程度でした。
2012年に新しく覚えた言語が無かった気がするけれども、中途半端に手を出しても使わなければ忘れてしまうので、HaskellかC#あたりを強化する方向にしようかなー…と思います。全く新規の物の方が面白いけれども、これ以上器用貧乏で手を広げるより、何か小さくても自慢できるようなものが出来る程度に、何かに専念した方がいいかなーと。

2013年1月1日火曜日

ソースファイルに書いたメンバ関数ってインライン展開されるんだっけ?

# はてなダイアリーから移動した記事です。あまり真面目に整形していません。

ヘッダファイルでクラス宣言中に書けばインライン化されやすいのは知ってるけれども、ソースファイルに書くとどうなるんだっけ?と、突然気になって調べてみました。もちろん、そのクラス内の別のメンバ関数から呼び出す場合の話です。

// Hoge.h
#include <stdio.h>

class Hoge {
 public:
  void moge();
  void pub(int &x);
  void pub_i1(int &x) {
    ++x;
    printf("%d ", x);
  }
  void pub_i2(int &x);

 private:
  void priv(int &x);
  void priv_i1(int &x) {
    ++x;
    printf("%d ", x);
  }
  void priv_i2(int &x);
};

// Hoge.cpp
#include "Hoge.h"

void Hoge::moge() {
  int n = 0;
  pub(n);
  pub_i1(n);
  pub_i2(n);
  priv(n);
  priv_i1(n);
  priv_i2(n);
  printf("%d\n", n);
}
void Hoge::pub(int &x) {
    ++x;
    printf("%d ", x);
}
inline void Hoge::pub_i2(int &x) {
    ++x;
    printf("%d ", x);
}
void Hoge::priv(int &x) {
    ++x;
    printf("%d ", x);
}
inline void Hoge::priv_i2(int &x) {
    ++x;
    printf("%d ", x);
}

Hoge::moge()で何やってるのか確認します。

gcc

  • version 4.5.3 (GCC)

-Sオプションで出力。

デフォルトの最適化オプション
    movl    $0, -12(%ebp)
    leal    -12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    __ZN4Hoge3pubERi
    leal    -12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    __ZN4Hoge6pub_i1ERi
    leal    -12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    __ZN4Hoge6pub_i2ERi
    leal    -12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    __ZN4Hoge4privERi
    leal    -12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    __ZN4Hoge7priv_i1ERi
    leal    -12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    __ZN4Hoge7priv_i2ERi
    movl    -12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    $LC1, (%esp)
    call    _printf
    leave

全部call。

-O2
    movl    8(%ebp), %ebx
    leal    -12(%ebp), %esi
    movl    %esi, 4(%esp)
    movl    $0, -12(%ebp)
    movl    %ebx, (%esp)
    call    __ZN4Hoge3pubERi
    movl    -12(%ebp), %eax
    movl    $LC0, (%esp)
    addl    $1, %eax
    movl    %eax, -12(%ebp)
    movl    %eax, 4(%esp)
    call    _printf
    movl    -12(%ebp), %eax
    movl    $LC0, (%esp)
    addl    $1, %eax
    movl    %eax, -12(%ebp)
    movl    %eax, 4(%esp)
    call    __printf
    movl    %esi, 4(%esp)
    movl    %ebx, (%esp)
    call    __ZN4Hoge4privERi
    movl    -12(%ebp), %eax
    movl    $LC0, (%esp)
    addl    $1, %eax
    movl    %eax, -12(%ebp)
    movl    %eax, 4(%esp)
    call    __printf
    movl    -12(%ebp), %eax
    movl    $LC0, (%esp)
    addl    $1, %eax
    movl    %eax, -12(%ebp)
    movl    %eax, 4(%esp)
    call    __printf
    movl    -12(%ebp), %eax
    movl    $LC1, (%esp)
    movl    %eax, 4(%esp)
    call    _printf
    addl    $32, %esp
    popl    %ebx

ヘッダファイル内のクラス定義で記述された関数と、ソースファイル中でinline指定された関数が展開されました。
publicでinlineな関数も展開されています。これで外部から呼ぶと…たとえばmain関数からpub_i2()を呼ぶと、リンクエラーになります。inlineキーワードはあくまでもヒントです!というスタンスかと思いきや、インライン化を優先するんですね。意外でした。

#include "Hoge.h"

int main(int argc, char **argv) {
  Hoge hoge;
  int n = 0;
  hoge.pub(n);
  hoge.pub_i1(n);
  hoge.pub_i2(n);

  return 0;
}
% gcc Main.cpp Hoge.o
/cygdrive/z/Temp/ccgFs4xP.o:Main.cpp:(.text+0x4e): undefined reference to `Hoge::pub_i2(int&)'
collect2: ld returned 1 exit status

cl.exe (Visual Studio)

  • Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86

/FAオプションで出力。

デフォルトの最適化オプション
    push    ebp
    mov ebp, esp
    sub esp, 8
    mov DWORD PTR _this$[ebp], ecx
; Line 4
    mov DWORD PTR _n$[ebp], 0
; Line 5
    lea eax, DWORD PTR _n$[ebp]
    push    eax
    mov ecx, DWORD PTR _this$[ebp]
    call    ?pub@Hoge@@QAEXAAH@Z            ; Hoge::pub
; Line 6
    lea ecx, DWORD PTR _n$[ebp]
    push    ecx
    mov ecx, DWORD PTR _this$[ebp]
    call    ?pub_i1@Hoge@@QAEXAAH@Z         ; Hoge::pub_i1
; Line 7
    lea edx, DWORD PTR _n$[ebp]
    push    edx
    mov ecx, DWORD PTR _this$[ebp]
    call    ?pub_i2@Hoge@@QAEXAAH@Z         ; Hoge::pub_i2
; Line 8
    lea eax, DWORD PTR _n$[ebp]
    push    eax
    mov ecx, DWORD PTR _this$[ebp]
    call    ?priv@Hoge@@AAEXAAH@Z           ; Hoge::priv
; Line 9
    lea ecx, DWORD PTR _n$[ebp]
    push    ecx
    mov ecx, DWORD PTR _this$[ebp]
    call    ?priv_i1@Hoge@@AAEXAAH@Z        ; Hoge::priv_i1
; Line 10
    lea edx, DWORD PTR _n$[ebp]
    push    edx
    mov ecx, DWORD PTR _this$[ebp]
    call    ?priv_i2@Hoge@@AAEXAAH@Z        ; Hoge::priv_i2
; Line 11
    mov eax, DWORD PTR _n$[ebp]
    push    eax
    push    OFFSET $SG3877
    call    _printf
    add esp, 8
; Line 12
    mov esp, ebp
    pop ebp
    ret 0

全部call。

-O2
; Line 5
    push    1
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 6
    push    2
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 7
    push    3
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 8
    push    4
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 9
    push    5
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 10
    push    6
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 11
    push    6
    push    OFFSET ??_C@_03PMGGPEJJ@?$CFd?6?$AA@
    call    _printf
    add esp, 56                 ; 00000038H
; Line 12
    ret 0

全部展開。
さすがに関数の中身がcallの手続きより語数少ないのは少なすぎたか、と関数内のprintfを増量してみましたが、その程度では結果は変わりませんでした。

-Ox
; Line 5
    push    1
    push    OFFSET $SG3882
    call    _printf
; Line 6
    push    2
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 7
    push    3
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 8
    push    4
    push    OFFSET $SG3891
    call    _printf
; Line 9
    push    5
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 10
    push    6
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 11
    push    6
    push    OFFSET $SG3877
    call    _printf
    add esp, 56                 ; 00000038H
; Line 12
    ret 0

全部展開。
なお、-O2も-Oxも、cl Main.cpp Hoge.obj としてみたところ、何事もなくリンク・実行とも成功しました。objdump -rするとゴチャゴチャついてます。インライン化するけど実態も定義しておく、と。速度を最適化してるんだから、サイズでかくなることに文句いっちゃいかんだろ、ということですかね。

総括

gccは想像通りの動作をする。
cl.exeは予想以上にばりばり展開する。

gccはとても小さなプラットフォームまで対象なので、仕様通りに動いてくれないと困る…という感じでしょうか。
とにかく、private関数の定義にinlineと書けばinline化されそうな事はわかりました。