グレインの備忘録

プログラミング関係とかをつらつらと。

構造体のDictionaryでは直接値を変更できない

コンテナの返値が値型か参照型かに気をつけましょうねという話。

シチュエーション

例えば、以下のようなコードを書いてみたとする。

public class Program
{
    struct MyStruct
    {
        public int val1;
        public int val2;
    }
    
    public static void Main()
    {
        var dic = new Dictionary<int, MyStruct>();
        
        dic[0].val1 = 1;
        dic[0].val2 = 2;
    }
}

自前の構造体をDictionaryに格納し、その値を変更するコードである。

しかし、これはコンパイルエラーになる。

error CS1612: 変数ではないため、'Dictionary.this[int]' の戻り値を変更できません

Dictionary内の要素に値を代入することができていないようだ。

dic[0]の返値は参照型ではなく値型

実は、これはMyStructが値型であることが原因である。

つまり、dic[0]というのは、dic内の0番目の要素のコピーであって、それに対していかなる操作を行ってもdic内の要素には一切影響しない。

上記のように[]で参照したものの値を直接書き換えたいならば、MyStructをclassにするしかない。

public class Program
{
    class MyStruct
    {
        public int val1;
        public int val2;
    }
    
    public static void Main()
    {
        var dic = new Dictionary<int, MyStruct>();
        
        dic.Add(0, new MyStruct());

        dic[0].val1 = 1;
        dic[0].val2 = 2;
    }
}

これならdic[0]はdic内の0番目の要素への参照となり、正しく動く。


C++ならコンテナは必ず参照型を返してくれるので、原因が分かるのに時間がかかった。

コンテナのイテレータを保持するのは避けよう

STLとかのコンテナが吐き出すイテレータをポインタ代わりに持っとくのはやめましょうねというお話。

コンテナへの非const操作はイテレータを破壊する(可能性がある)

「可能性がある」だから余計厄介だったりする。

テストケースではうまく動いても実環境では動かなかったり。

詳しい話はこちらのページに投げさせていただく。

qiita.com

スクメロが起動しなくなった話

スクールガールストライカーズトゥインクルメロディーズ、通称「スクメロ」と呼ばれるゲームがある。

9月頭のメンテ直後に、全く起動することができなくなってしまった。

具体的には、タイトル画面でスタートするとデータダウンロードが入り、直後に落ちてしまうのである。

仕方がないので原因を調査してみる。

原因を調べる

apkの中身を見てみると、SmartBeatが搭載されていることが分かった。

f:id:grainrigi:20170910194742p:plain

SmartBeatというのはアプリのクラッシュを自動で報告するツールである。

SmartBeat スマホアプリのクラッシュ解析ツール

実はこのツール、ローカルにもクラッシュログを残すので、それを解析することで原因をつかめる可能性がある。

クラッシュログを見る

なお、クラッシュログを見るにはrootが必須となる。

格納場所は「/data/data/com.square_enix.android_googleplay.TwinkleMelodiesj/app_SmartBeat/dump_tmp/」である。

ダンプには二種類あって、バイナリとjson形式がある。

jsonの方を覗いてみると、ログが逐一記録されていた。

さて、例外が発生した付近のログは以下のようになっていた。

V/AudioManager(29685): isBluetoothA2dpOn...
I/Unity   (29685): UnauthorizedAccessException: Access to the path
"/storage/sdcard1/Android/data/com.square_enix.android_googleplay.TwinkleMelodiesj/files/nettemp/2o8rsp89.n51"
is denied.
I/Unity   (29685):   at System.IO.File.SetAttributes (System.String path, FileAttributes fileAttributes) [0x00000] in :0 
I/Unity   (29685):   at RQ.Network.RQ_NetworkManager.OnInitialize () [0x00000] in :0 
I/Unity   (29685):   at RQ.Common.SingletonBehaviour`1[T].RegisterInstance () [0x00000] in :0 
I/Unity   (29685):  
I/Unity   (29685): (Filename: currently not available on il2cpp Line: -1)
I/Unity   (29685): 
D/dalvikvm(29685): GC_CONCURRENT freed 6127K, 58% free 5286K/12380K, paused 3ms+9ms, total 48ms
D/dalvikvm(29685): WAIT_FOR_CONCURRENT_GC blocked 35ms
V/AudioManager(29685): isBluetoothA2dpOn...
D/dalvikvm(29685): GC_FOR_ALLOC freed 873K, 56% free 5454K/12380K, paused 21ms, total 21ms
I/dalvikvm-heap(29685): Grow heap (frag case) to 8.624MB for 369706-byte allocation
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
V/AudioManager(29685): isBluetoothA2dpOn...
E/dalvikvm(29685): JNI ERROR (app bug): accessed stale local reference 0x1d200001 (index 0 in a table of size 0)
E/dalvikvm(29685): VM aborting
V/AudioManager(29685): isBluetoothA2dpOn...

発生しているのは「UnautorizedAccessException」、つまり非許可のファイル操作に関する例外である。

操作対象のファイルは「/%AppData%/files/nettemp/2o8rsp89.n51」である。

フォルダの名前からして一時ファイルであろう。

ファイルを消して再起動

試しにnettempフォルダ以下のファイルを消して再起動してみる。

すると、途中で落ちることなく正常に動作した

どうやらnettempフォルダ以下のファイル操作に瑕疵があるようだ。

解決策

スクメロが勝手に落ちるときには、「(ストレージ直下)/Android/data/com.square_enix.android_googleplay.TwinkleMelodiesj/files/nettemp」フォルダ以下のファイルを消してみるとよい、と思われる。

neosnippetに自分のスニペットを追加する

前回の記事ではneosnippetを導入したが、自作のスニペットを使いたくなったので方法をメモ。

neosnippetの設定

まずは、スニペットファイルの保存先を指定する。

プラグイン起動時の設定に一行追加する。

let g:neosnippet#snippets_directory = ~/.vim/dein/repos/github.com/Shougo/neosnippet-snippets/snippets/'

したら再起動する。

スニペットの編集

まずは、スニペットを追加したいタイプのファイルを開く。

そして、「:NeoSnippetEdit」とコマンドを入力する。

すると、スニペットの編集画面が開く。

スニペットの構文は以下の通り。

snippet [識別子]
alias [略記法]
<インデント>スニペット内容
<インデント>スニペット内容
…

スニペット内容は基本は展開したい内容をそのまま書く。

また、Ctrl+Kで順にカーソルが移動できるような場所は「${数字:デフォルト値}」として書けばよい。

終わったら「:w」で保存する。


以上でスニペットの追加は完了である。

OOP初心者がブロック崩しを作ってみる その2

※この記事は「OOP初心者がブロック崩しを作ってみる その1」の続きです

衝突判定を考える

さて、衝突判定のやり方を考えよう。

衝突はボールとReflectorの間で起こる。とりあえず以下のようにクラス分けしてみる。

f:id:grainrigi:20170902161501p:plain

ReflectorCollectionにReflectorを全て格納しておいて、Ball側から参照、衝突判定を行う。

move関数はBallに速度に従っての移動を指示する関数で、移動するたびに衝突判定を行う。

BallとReflectorのgetRect関数はそれぞれの専有する範囲を返す。(衝突判定に使用)

衝突判定を外に出す

これでも悪くはないが、Ballが「移動する物体」と「衝突する物体」の2つの役割を持っているので、分割したい。

ということで、衝突判定は外に出し、Ballは速度に従って移動することに専念することにする。

f:id:grainrigi:20170902163639p:plain

CollisionManagerが衝突判定を担当する。

流れとしては、

  1. Ballのmove関数が呼ばれる
  2. Ballが速度に従って移動
  3. BallがexecuteCollisionを呼ぶ
  4. CollisionManagerが全Reflectorと衝突判定
  5. CollisionManagerがgiveImpulseを呼び、ボールに力積を与える

という感じ。

Observerパターンを適用する

結合度の面から言えば、BallとCollisionManagerがお互いの関数を直接呼び出しているのであまり良くない。

そもそも、CollisionManagerはボールの移動をトリガーとして衝突判定を行うのである。

したがって、CollisionManagerがボールの移動を監視すれば良い。

つまり、Observerパターンを適用する。

f:id:grainrigi:20170902171012p:plain

move関数で移動した後、observerに移動を通知する。

これで多少はすっきりした。

Reflectorが衝突を知るには

ブロックのように衝突の有無を情報として必要とするReflectorが存在するので、それを通知する必要がある。

それもCollisionManagerが担当するのが妥当であろう。

以上のことを加味してまとめたクラス図はこうなった。

f:id:grainrigi:20170902171952p:plain

衝突判定に関しては概ねこのような感じでいいだろう。

次回予告

次回「OOP初心者がブロック崩しを作ってみる その3」は、この大まかな設計を基に実際にコードを書いてみることにする。

バッファの内容を変数に読み込む方法

日本語で調べても一切情報が出てこなかったので焦った。

方法

:%y [レジスタ名]
:let content = @[レジスタ名]

一旦レジスタにヤンクしてから、その内容を変数に入れる。

@[レジスタ名]でレジスタの内容を参照できるようだ。

UMLをテキストで書ける「PlantUML」を試す

UML図を書くフリーのツールにはいろいろあるが、テキストコマンドで書けるものの一つに「PlantUML」がある。

テキストベースで書けると何がいいかというと、

などの点が挙げられる。

使い方等

PlantUML自体はJavaで書かれたテキストプロセッサであるが、Web上でこれを試せるサービスがある。

PlantUML Web Server

リファレンスはここにある。

PlantUML

クラス図・フローチャートのほかにも色々とかけるらしい。

使用例

テキスト

@startuml
start
:ボールを移動;

repeat
  :Reflectorを一つ取り出す;
  if (ボールと衝突したか?) then (yes)
    :ボールの移動方向を変える;
  else (no)
  endif
repeat while (Reflectorはあまだあるか?)

stop
@enduml

出力

f:id:grainrigi:20170830235007p:plain

これは積極的に使っていきたい。