2014年6月20日金曜日

UnityでMono.Cecilを使う時の勘所

UnityでMono.Cecilの使う時の勘所

Mono.Cecilとは?

Mono.Cecilとは、.NETのCIL(共通中間言語)を操作するためのライブラリです。 .NETのDLLやexeを、生成したり改変したりすることが可能になります。

何に使うの?

主に

  • アスペクト指向言語の実装
  • コードの最適化
  • コードの難読化
  • リフレクションを使っている部分を置き換えて高速化
のために使われることが多いです。 ただし、かなりの黒魔術っぷりなのでむやみやたらには使わないようにしましょう。

Unityで使うときのはまりどころ

実機上(iPhoneやandroid)ではMono.Cecilは使えません

この辺は、.NET2.0subsetで動かすことになると思いますので、CILをいじるためのクラスをつかうMono.Cecilは使えません。 なので、基本はMono.CecilはEditorディレクトリ内にいれて、Editor上でのみで使用することになります。 iPhoneの場合は、AOTの為原理的に使用不可能です。 androidはJITなので、もしかしたらfullsetなら動くかもしれませんが未検証です。

.NET 2.0でビルドしましょう

.NET2.0用にビルドしないと動きません。

また、ビルドするときはMonoDevelopは絶対に使わないようにしましょう。MonoDevelopが糞なので、完全な.NET2.0のバイナリにビルドしてくれません。

手順としては、
  1. Mono.Cecilのソースコードをcloneしてくる
  2. Mono.Cecil.slnをXamarin studioやVisual studioで開く
  3. コンフィグレーションをnet_2_0_Release(またはdebug)にする
  4. プロジェクトオプション>ビルド>一般のTarget frameworkをMono / .NET 2.0に変更
  5. 一応クリーンをして、プロジェクトを全てビルド
  6. {Mono.Cecil}/bin/net_2_0_Releaseに出力されているdllをUnityプロジェクトのEditorディレクトリにコピー
これでUnityのEditor機能を作る際にMono.Cecilを使えるようになります。

デバッグ情報をきちんと出力しましょう

デフォルトだと、デバッグ情報を生成してくれません。そのため、DLLを改変した場合、例外のスタックとレースのファイルと行数が激しくずれてしまいます。 なので、ここを参考に、DLL出力時にデバッグシンボルを保存する設定をきちんとしてあげましょう。

2013年7月17日水曜日

chillを使ってクラス定義変更に強いシリアライズを実現

Java向けのシリアライズライブラリであるkryoと、そのscalaラッパーのchillを使って、クラスの変更に強いシリアライズ手法の実現方法のメモです。

javaのデフォルトのシリアライズだと、SerializableVersionをきちんと付けておかないと、クラスの定義を変更しフィールドを追加した時に過去のデータから復元できなくなります。
この挙動が悪いとは言いませんが、キャッシュの際にこの挙動をされるとクラス変更をするたびにキャッシュがクリアされてしまうことになったり、不意に過去のデータがよみなくなるなど、厳密な挙動では不便な場合が多々あります。なので、kryoを使って、クラスの定義変更に強いシリアライズが出来ないか試してみました。
まず、kryoをそのまま使うと、scalaのcase classがデシリアライズ出来ないので、ここは素直にtwitter様が作ってくれているchillを使いました。
また、kryoもデフォルトではクラスのフィールドを追加したり削除したりすると、過去のデータからはシリアライズできなくなるので、少々設定をいじる必要があります。以下がその方法です。


build.sbt
scalaVersion := "2.10.0"

libraryDependencies += "com.twitter" %% "chill" % "0.2.3"

ソース

import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer
import com.twitter.chill._

// kryoのインスタンスを取得
val kryo = KryoBijection.getKryo
// デフォルトのSerializerを、フィールド情報も保存してくれるCompatibleFieldSerializerに変更 
kryo.setDefaultSerializer( classOf[CompatibleFieldSerializer[Any]])

//新しいKryoInjectionインスタンスを作成
val Injection = KryoInjection.instance(kryo)

// シリアライズ
val bytes : Array[Byte] = Injection(Hoge(1,2,"hoge"))

// デシリアライズ
val des : Option[Any] = Injection.invert(bytes)

println(des)// Some(Hoge(1,2,"hoge"))


2013年4月17日水曜日

ローカルで削除されたファイルを一括でgit rmする

Mac環境で、ローカルのファイルを削除してgitのindexだけが残ってしまっている場合に、一括でgit rmする方法の紹介です。

コマンドは以下の通り。これをコピペして実行してください。
git status | grep deleted: | cut -c 15- | sed -e 's/ /\\ /g' | xargs git rm

やっていることは、

1. git statusでindexの変更取得
2. grep deleted: で削除されたファイルの行だけを抜き出す。
3. cut -c 15- でファイルのパスにあたる15文字以降だけを抜き出す
4. sed -e 's/ /\\ /g'で、ぱすに空白が含まれてしまっている場合にエスケープする
5. 抜き出されたパスをgit rmする

という手順になります。
行程3の部分が、gitのバージョンによって変化するかもしれないのでもしうまくいかない場合は
git status | grep deleted: | cut -c 15-
だけで実行してみて、正しくパスが取得できているかを確認してみてください。

2013年3月3日日曜日

Scala2.10のReflectionで、class constructorを取得する

まだ書きかけですが、とりあえずさらしておきます。
後日、もう少しバージョンアップします。

やりたい事は、ScalaのReflectionを使ってクラスのコンストラクタを取得して新しいインスタンスを作るです。
case classも、これでパラメータの取得が可能です。

build.sbt
scalaVersion := "2.10.0"

libraryDependencies += "org.scala-lang" % "scala-reflect" % "2.10.0"
App.scala


import scala.reflect.runtime.universe._
import scala.reflect._

case class Hoge(val name : String)


object App{

  def main(args : Array[String] ) {
    println(newFromParamsMap[Hoge](Map("name" -> "hoge")))
  }
  
  
  def newFromParamsMap[T](params : Map[String,Any])(implicit tt : TypeTag[T],ct : ClassTag[T]) : T = {
    
    val t = typeOf[T]
    
    // Primary constructorの取得
    val primaryConstructor = t.declarations.collectFirst({
      case m : MethodSymbol if m.isPrimaryConstructor => m
    })
    
    if(!primaryConstructor.isDefined){
      throw new Exception(s"Class ${t} doesn't have primary constructor.")
    }
    
    //コンストラクタのパラメーターを取得してくる
    //後日型のチェック機能も入れます。
    val constructorValues = primaryConstructor.get.paramss.map( _params => {
      _params.map( {
        case t : TermSymbol => {
          val p = params(t.name.encoded.trim)
          /*if( ! t.typeSignatur =:= typeOf(p.getClass){
            throw new Exception("Parameter :${t.name.encoded} doesn't match type!)
          }*/
          p
        }
      })
    })
    // 実行のためのMirror取得
    val mirror = tt.mirror
    
    val classMirror = mirror.reflectClass(t.typeSymbol.asClass)
    val constructorMirror = classMirror.reflectConstructor(primaryConstructor.get)
    
    constructorMirror(constructorValues.flatten :_*).asInstanceOf[T]
  }
  
  
}

2013年1月11日金曜日

lift-mapperのモデルのコードジェネレータ作ってみた

lift-mapperのmodelの雛形のコードジェネレータ作ってみました。 Lift-mapper model generator ソースは全部htmlにベタ書きしているので、htmlを保存するだけでローカルでも動きます。

2012年10月23日火曜日

sbt 0.12.Xでscala.tools.nsc.MissingRequirementErrorが出て、compile出来ない件

sbtのバージョンを0.11.3から、0.12.1に上げてみたところ、プロジェクトをコンパイルしようとすると
@sbt console
> compile

[info] Compiling 1 Scala source to PROJECT_PATH\target\scala-2.9.2\classes...
[trace] Stack trace suppressed: run 'last compile:compile' for the full output.
[error] (compile:compile) scala.tools.nsc.MissingRequirementError: object scala not found.
[error] Total time: 0 s, completed 2012/10/23 12:38:06
な感じのエラーが出るようになりました。いろいろ調べた結果、
libraryDependencies := ...
サンプルコード(というほどでもないが;; みたいな感じに、依存ライブラリを上書きしてしまっていることが原因でした。 解決方法は、
  1. :=は使わず、+=や<+=などを使う
  2. :=で上書きした後に、
    libraryDependencies <++= (autoScalaLibrary, sbtPlugin, scalaVersion) apply Classpaths.autoLibraryDependency
    
    で、必要なライブラリを追加する。
のどちらかになります。

2012年8月6日月曜日

MySQLでupdateしてinsertするとDeadLockが発生する

負荷テストしているときに、Deadlockの起きるはずのないところでDeadlockが起きたので、いろいろ調べた記録。
原因は、1つのTransaction内でupdateをかけてみて更新件数が0の場合、insertをするという操作をしているところでした。updateを行った際にそのKeyが存在しないと行ロックを広く取ってしまうことが原因みたいです。(あくまでらしいです。ソースコードなどまではおっていません。)
始めはO/R Mapper周りのバグかなと思ってたので、MySQL側の挙動であることを確定させるために検証コードも書いてみました。
ちなみに、Thread数が1ではこの問題は発生しません。

Sample code

App.scala
import java.sql.DriverManager
import scala.util.Random

object App{


  def main(args : Array[String]) {
  
    Class.forName("com.mysql.jdbc.Driver")
    val con = DriverManager.getConnection("jdbc:mysql://localhost/test", username, password);
    val st = con.createStatement()
    st.executeUpdate("""drop table DeadLockCheck""")
    st.executeUpdate("""create table if not exists DeadLockCheck(
    id INT PRIMARY KEY,
    c INT DEFAULT 0);""")
    st.close()
    con.close()
    val threads = (0 until 5).map(i =>{
      val t = new MyThread()
      t.start()
      t
    })
    threads.foreach(_.join)
    
  }
}

object MyThread{
  var random = new Random
}
class MyThread extends Thread{
  
  override def run() = {
    
    for(i <- 0 until 50){
      val con = DriverManager.getConnection("jdbc:mysql://localhost/test", username, password);
      con.setAutoCommit(false);
      val st = con.createStatement()
      val id = MyThread.random.nextInt.abs
      
      if(st.executeUpdate("""update DeadLockCheck set c = c + 1 where id = %s""".format(id)) <= 0){
        st.executeUpdate("""insert DeadLockCheck values(%s,1)""".format(id))
      }
      con.commit()
      
      st.close()
      con.close()
    }
  
  }

}


build.sbt
libraryDependencies += "mysql" % "mysql-connector-java" % "5.1.21"

結果

[info] Running App
[error] (Thread-82) com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
[error] (Thread-85) com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
[error] (Thread-84) com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
        at java.lang.reflect.Constructor.newInstance(Unknown Source)
        at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
        at com.mysql.jdbc.Util.getInstance(Util.java:386)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1065)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4074)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4006)
        at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2468)
        at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2629)
        at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2713)
        at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1794)
        at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1712)
        at MyThread$$anonfun$run$1.apply$mcVI$sp(App.scala:43)
        at scala.collection.immutable.Range.foreach$mVc$sp(Range.scala:75)
        at MyThread.run(App.scala:36)
com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
        at java.lang.reflect.Constructor.newInstance(Unknown Source)
        at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
        at com.mysql.jdbc.Util.getInstance(Util.java:386)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1065)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4074)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4006)
        at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2468)
        at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2629)
        at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2713)
        at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1794)
        at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1712)
        at MyThread$$anonfun$run$1.apply$mcVI$sp(App.scala:43)
        at scala.collection.immutable.Range.foreach$mVc$sp(Range.scala:75)
        at MyThread.run(App.scala:36)
com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
        at java.lang.reflect.Constructor.newInstance(Unknown Source)
        at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
        at com.mysql.jdbc.Util.getInstance(Util.java:386)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1065)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4074)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4006)
        at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2468)
        at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2629)
        at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2713)
        at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1794)
        at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1712)
        at MyThread$$anonfun$run$1.apply$mcVI$sp(App.scala:43)
        at scala.collection.immutable.Range.foreach$mVc$sp(Range.scala:75)
        at MyThread.run(App.scala:36)
[success] Total time: 1 s, completed 2012/08/06 19:01:10

参考

InnoDBのネクストキーロックによるデッドロックの例