Hatena::ブログ(Diary)

Ko-Taのバ・ー・ルのようなもの

2017-09-05

spine c++ serialize

たぶん必要になるでしょうアニメーションのシリアライズ、保存、復元です。ただし、ゆるーいシリアライズです(後記)。

spineにはアニメーションを保存する機能が、ありそうでありません。作らないといけません。付けて欲しいなぁ…。

spineのアニメーション

アニメーションはトラックという概念で管理されています。エディタだと「preview」機能で確認できます。

[優先↑]

track2:[何もしない][瞬き][何もしない][瞬き]
track1:[狙う]
track0:[歩く][走る]
base  :[setup状態]

横方向が単純なモーションの切り替わりです。上の例では歩きから走るモーションに切り替わります。切り替わるタイミングは「delay」で指定することが出来、0(指定無し)だとループポイントで切り替わります。アニメーションを作成する際はループポイントで繋がるように作りましょうって感じみたいです。もちろん切り替わりが分からないようにミックス(ブレンド)されます。

で、縦方向がtrack(トラック)の概念です。難しいものではありません、単にキーフレームが上書きされるだけです。キーフレームが無い箇所(ボーンやスキニングポイント)はそのまま通過、歩いているアニメーションが使用されます。

こんな感じで、部分部分のアニメーションを上書きさせていって、ゲームで必要な動的なアニメーションをプログラムから制御しましょうというのがtrackです。

なお、trackが何も無い状態だと、エディタにおける setup 状態となります。


頑張ればlive2Dみたいなことも出来そうですが、顔を左右に動かすようなものをプログラムからブレンドさせてコントロールするのは現状無理があるので、そこが利用用途が異なる理由の1つでしょう。決まったアニメーションには強いのですが。(顔の3Dっぽい変形はスキニングメッシュのボーンベータ値でいけなくもないのですが、それに合わせて目などのパーツを連動させるのが現状無理です。ちょっとした3Dが導入されれば可能なのですが…3Dはロードマップに無さそう)

データ構造

アニメーションを保存するには spAnimationState の情報を使用します。

  • spAnimationState
struct spAnimationState {
	spAnimationStateData* const data;

	int tracksCount;
	spTrackEntry** tracks;

	spAnimationStateListener listener;

	float timeScale;

	spTrackEntryArray* mixingTo;
	void* rendererObject;
};

trackscount,tracks が上で説明したアニメーションの構造そのままです。ポインタのポインタ(**)がきたら9割は「あー配列かぁ」なので恐れないでください。trackscount分だけ構造体のポインタが並んでいるだけです。timescaleは使うかも知れませんね。保存して復元させると良いでしょう。

あとは復元には不要なので省略します。


次にtrackの情報 spTrackEntry について。

  • spTrackEntry
struct spTrackEntry {
	spAnimation* animation;
	spTrackEntry* next;
	spTrackEntry* mixingFrom;
	spAnimationStateListener listener;
	int trackIndex;
	int /*boolean*/ loop;
	float eventThreshold, attachmentThreshold, drawOrderThreshold;
	float animationStart, animationEnd, animationLast, nextAnimationLast;
	float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale;
	float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha;
	spIntArray* timelineData;
	spTrackEntryArray* timelineDipMix;
	float* timelinesRotation;
	int timelinesRotationCount;
	void* rendererObject;
	void* userData;
};

色々ありますが、9割ぐらいはアニメーションをセットした際に設定される値なので省略しちゃっても良いかなと思います。厳密にやり出すと前のアニメーションとのブレンドミックスなどもあるので相当大変です。今回はそこまでやりませんし、そこまで必要ないと思いますし。

必要そうなのは、現在のアニメーション名を知るための animation、次のアニメーションを知るための next、再生時間関連の tracktime,delay,timescale あたりでしょうか。alphaも弄るなら要りそうです(このalphaは色では無く、trackごとのアニメーションのブレンド率です)。


名前取得するアニメーションデータの構造体 spAnimation は簡単です。

  • spAnimation
typedef struct spAnimation {
	const char* const name;
	float duration;

	int timelinesCount;
	spTimeline** timelines;
} spAnimation;

これはデータなので、弄ってはいけません。動的な情報を管理する state ではありません。

name だけ拾えば良いですね。

duration はこのアニメーションの1ループの長さです。1.0で1secの単位で表されていますが、これは今回不要です。

シリアライズ、保存

主に spTrackEntry の情報を保存すれば、復元は出来そうです。trackごとにアニメーションが何個並んでいて、それぞれがどのような状態なのかを記憶していきます。簡単に言えば2次元ループ処理ですね。

  • _savestate
void _savestate(spAnimationState* animation,const std::string &filename) {
	std::stringstream* ss = new std::stringstream();
	int count = animation->tracksCount;

	*ss << "trackcount " << count << std::endl;
	for (int i = 0; i < count; i++) {
		if (animation->tracks[i] == nullptr) {
			*ss << "exists 0" << std::endl;
		}
		else {
			*ss << "exists 1" << std::endl;
			_savestate_tracks(ss, animation->tracks[i]);
		}
	}
	*ss << "[eof]";

	_writestringfile(ss, filename);  //stringstreamを保存する関数

	delete ss;
}

状態保存は構造体にして保存したほうが良いかなと思いましたが、テキストの方が見易いので stringstream 使っておきます。実際組む場合はもうちょっと賢いjsonなどに出力させた方がいいでしょう。

trackscount でループを回す際に注意点があります。アニメーションを track0,2 と指定した場合、track1 がnullになります。なので、nullチェックが必要です。ここでは「exists」がそれにあたります。存在する場合は、track の中のアニメーション情報 spTrackEntry の stringstream に追加していきます。

  • _savestate_tracks
void _savestate_tracks(std::stringstream* ss, spTrackEntry* track) {
	//entry animation count
	int count=1;
	{
		spTrackEntry* data = track;
		while (data->next != nullptr) {
			data = data->next;
			count++;
		}
		*ss << "animationcount " << count << std::endl;
	}
	//each animation satate (track entry)
	spTrackEntry* data = track;
	for (int i = 0; i < count; i++) {
		*ss << "name " << data->animation->name << std::endl;			// string
		*ss << "duration " << data->animation->duration << std::endl;	// animation time length
		*ss << "tracktime " << data->trackTime << std::endl;
		*ss << "timescale " << data->timeScale << std::endl;
		*ss << "loop " << data->loop << std::endl;						// int
		*ss << "delay " << data->delay << std::endl;
		//next
		data = data->next;
	}
}

横方向に当たるtrackないのアニメーションですが、数を知る場合は count が存在しないのでちょっと工夫が必要です。next で次の情報が取得出来る数珠つなぎで管理されているので、nextをくるくる遡ってカウントを数えます。nullがでたら終端です。

count が取得出来たら、あとは同じようにくるくる回して情報を保存していきます。

できあがったのがこちら。

  • savestate.txt
trackcount 3
  exists 1
  animationcount 2
    name walk
      duration 0.8667
      tracktime 0.333333
      timescale 1
      loop 1
      delay 0
    name run
      duration 0.8
      tracktime 0
      timescale 1
      loop 1
      delay 0.6667

  exists 0

  exists 1
  animationcount 1
    name aim
      duration 0
      tracktime 0.333333
      timescale 1
      loop 0
      delay 0

改行とスペースと空行は実際にはありません。分かりやすいように付けただけなので、実際のデータとは異なります。

デシリアライズ・復元

この情報から復元します。まっさらなモーション状態 spAnimationState_clearTracks にして、そこから再構築します。

コードもシリアライズとほぼ同じ構造なので、理解は簡単でしょう。

「>> name >> exists」のような構文は、値の読み込みです。便利ですが手抜きです:)

  • _loadstate
void _loadstate(spAnimationState* animation, const std::string &filename) {
	std::stringstream* ss = _readstringfile(filename); // stringstreamにファイル読み込みます
	std::string name;
	int count;
	*ss >> name >> count;
	printf("%s:%d\r\n", name.c_str(),count);

	for (int i = 0; i < count; i++){
		int exists;
		*ss >> name >> exists;
		if (exists == 0) {
			// null
		}
		else {
			_loadstate_tracks(ss, animation, i);
		}
	}
	delete ss;
}

void _loadstate_tracks(std::stringstream* ss, spAnimationState* animation, int no) {
	// animation count
	std::string name;
	int count;
	*ss >> name >> count;

	// each animation
	for (int i = 0; i < count; i++) {
		// animation name
		std::string animationname;
		*ss >> name >> animationname;
		// duration (not use)
		float duration;
		*ss >> name >> duration;
		// tracktime
		float tracktime;
		*ss >> name >> tracktime;
		// timescale
		float timescale;
		*ss >> name >> timescale;
		// loop
		int loop;
		*ss >> name >> loop;
		// delay
		float delay;
		*ss >> name >> delay;

		spTrackEntry* entry;
		if (i == 0) {
			entry = spAnimationState_setAnimationByName(animation, no, animationname.c_str(), loop);
		}
		else {
			entry = spAnimationState_addAnimationByName(animation, no, animationname.c_str(), loop, delay);
		}
		entry->trackTime = tracktime;
		entry->timeScale = timescale;
		//printf("animation:%s time:%f delay:%f loop:%d\r\n", animationname.c_str(), tracktime, delay, loop);
	}
}

モーションを構築する際に一応 setAnimation と addAnimation を使い分けてあります。全部 add で良いような気もしますが、set のほうがtrackにあるモーションをすべて破棄して設定するとあるので、安全かな?と思います。

実は stringstream の特性でアニメーション名にスペースがあると動きません。実装の際は string convertspaceword(const string) みたいな関数で文字を置き換えて対処などしてください。

あと AnimationByName はサンプルなので使ってますが、無い名前を渡すとnull例外アクセスで死ぬので、ちゃんと自分で名前チェック入れるか自前でfix命令作って代用してくださいね。

実験

前回作ったポリゴン表示ログに細工して、出力ファイルが同じかどうか比較します。

//before
triangle:vector(824.137,932.741,728.086,745.995,547.565,838.845):argb=(1,1,1,1)
triangle:vector(547.565,838.845,643.616,1025.59,824.137,932.741):argb=(1,1,1,1)
triangle:vector(568.606,539.411,586.991,577.27,592.505,573.412):argb=(1,1,1,1)
//after
triangle:vector(824.138,932.741,728.086,745.995,547.565,838.845):argb=(1,1,1,1)
triangle:vector(547.565,838.845,643.616,1025.59,824.138,932.741):argb=(1,1,1,1)
triangle:vector(558.639,534.024,592.008,535.425,590.503,537.489):argb=(1,1,1,1)

あれ?ちょっと違う…。

いろいろ試した結果、spSkeleton_updateWorldTransform(ワールド座標などをスケルトンに反映)を抜いてみると

//before
triangle:vector(824.137,932.741,728.086,745.995,547.565,838.845):argb=(1,1,1,1)
triangle:vector(547.565,838.845,643.616,1025.59,824.137,932.741):argb=(1,1,1,1)
triangle:vector(568.606,539.411,586.991,577.27,592.505,573.412):argb=(1,1,1,1)
//after
triangle:vector(824.137,932.741,728.086,745.995,547.565,838.845):argb=(1,1,1,1)
triangle:vector(547.565,838.845,643.616,1025.59,824.137,932.741):argb=(1,1,1,1)
triangle:vector(568.606,539.411,586.991,577.27,592.505,573.412):argb=(1,1,1,1)

やったーおなじだー。

spSkeleton_updateWorldTransform のドキュメントを見ると「IKやら物理計算も反映させます」とあるので、それかなと思うのですが、サンプル「spineboy」には設定されてないような……。


これ以上は実際に表示させて詰めるしか無さそうです。

なお、復元直後に_savestateして保存したアニメーション情報を見てみると一緒だったので、アニメーション情報に関してはちゃんと復元されているようです。

忘れてました、emptyアニメーション

アニメーションをその状態で停止させる命令に EmptyAnimation というのがあります。これは特殊なアニメーションになるので、対応させるには上記にちょっと工夫が必要になります。


まず empty をセットすると trackentry の情報がどうなるのかを調べましょう。

spAnimationState_addEmptyAnimation(animationState, 0, 0.0f, 0.0f);  //mixDuration=0.0 delay=0.0

...
  name walk
...
  name <empty>
    duration 0
    tracktime 0
    timescale 1
    loop 0
    delay 0.7667
  name run
    duration 0.8
    tracktime 0
...

名前が <empty> になるのが特徴です。そのほかのパラメータは特に変わりはありません。

mixDurationは前のモーションとミックスブレンドするものだと思われます。停止させるのにミックス?よくわかりませんが、試しに1秒を指定してみると。

spAnimationState_addEmptyAnimation(animationState, 0, 1.0f, 0.0f);  //mixDuration=0.0 delay=0.0

...
  name walk
...
  name run
    duration 0.8
    tracktime 0
...

消えました。…なんで?

コードを追うと spAnimationState_update あたりで消滅しています。

最後に追加しないとだめよ!という事で最適化されたのかもしれませんが、コードを追ってもよく分からなかったので……見なかったことに。

emptyアニメーションの判断

このemptyアニメーションなんですが、trackentryから判別する手段がこれといって用意されているわけではありません。なので、自前で判別する必要があるのですが、大きく2パターンあります。


  • 名前から判別

entry->animation->name の名前が <empty> となるのが特徴です。

たぶんこの仕様は変わらないと思います。たぶん。(なおconstで名前が定義されてたりはしません)

くれぐれも自分が作ったモーションに <empty> なんて名前を付けてはいけません。ってマニュアルには書いてなかったので、世界中で数人ぐらいはこれで苦しんだ人も居たかも知れません。


  • animetionポインタのアドレス

もっとプログラム的に判別するなら AnimationState.c ファイルの戦闘を見るとemptyアニメーションがちゃっかり static されてます。

static spAnimation* SP_EMPTY_ANIMATION = 0;

いわゆるひとつのしんぐるとんで、entry->animation が SP_EMPTY_ANIMATION と同値であればemptyだと判断できます。

問題はこの SP_EMPTY_ANIMATION がexternされていないことなんです……。


というわけで、名前なりで判断して、loadstateのところを以下のように切り替えてあげましょう。

void _loadstate_tracks(std::stringstream* ss, spAnimationState* animation, int no) {
...省略...
		spTrackEntry* entry;
		if (i == 0) {
			if (animationname == "<empty>")
				entry = spAnimationState_setEmptyAnimation(animation, no, 0.0f);
			else
				entry = spAnimationState_setAnimationByName(animation, no, animationname.c_str(), loop);
		}
		else {
			if (animationname == "<empty>")
				entry = spAnimationState_addEmptyAnimation(animation, no, 0.0f, delay);
			else
				entry = spAnimationState_addAnimationByName(animation, no, animationname.c_str(), loop, delay);
		}
		entry->trackTime = tracktime;
		entry->timeScale = timescale;
...省略...

なお、delayの値がどうにも-999.7みたいな値を示すこともあるので、設定しないほうが良いのかも知れません。わからん。

2017-09-01

Netjs

Netjsもduocodeと同じくC#からTypeScript(JS)へ変換するプロジェクトです。ちょっと試してみましょう。

インストール

  • Netjs

https://github.com/praeclarum/Netjs

これはduocodeほど環境を必要とされません。githubからcloneして、コンパイルしてexeとして使用します。

出力はTypeScriptなので、まだならその環境もインストールしておきましょう。

変換

c#のクラスモジュール、特にPCL(ポータブル・クラス・ライブラリ)が望ましいみたいです。

C#でDLLで吐かせた後、そのDLLからTypeScriptに変換します。

公式の通り

netjs Library.dll

でtsが出力されます。

これも mscorlib 依存ですが、そのファイルは一緒に出力されません。どこにあるのかというと、cloneしたもののトップディレクトに「mscorlib.ts」として存在しするので、適当にコピーして環境にもってきましょう。

コンパイルはバージョンの指定「-t ES5」が必要です。無いと怒られます。

tsc -t ES5 mscorlib.ts Library.ts --out out.js 

で、1つのファイルにまとめられて完了です。

duocodeと比べると「mscorlib.ts」のファイル容量がかなり少ないです。これはtsによるところもあるでしょうが、バージョンが古いみたいです。

例えば下記のようなクラスを変換してみたところ

	public class text
	{
		/**
		 * A to Z change low case.
		**/
		static public string lowercase(string s)
		{
			int len = s.Length;
			//StringBuilder sb = new StringBuilder(len);    // error unsupport capacity
			StringBuilder sb = new StringBuilder();

			for (int i = 0; i < len; i++)
			{
				char c = s[i];
				if ((c >= 'A') && (c <= 'Z')){
					c = (char)((int)c + 32);
				}
				sb.Append(c);
			}
			return sb.ToString();
		}
	}
....

StringBuilder Class は用意されていますが、StringBuilder.capacityが未実装で、変換後にエラーが出ました。

github も2年前に更新となっているので、長らくプロジェクトは停滞しているみたいです。


まとめ

duocodeに比べるとTypeScriptで出力されるのがとても良い感じなのですが、プロジェクトとしてはバージャンアップが望めないみたいなので他を当たるのが良さそうです。

2017-08-30

duocode - C# to JS

今までキワモノだぁ!と思っていたdocodeをちょっと試してみたので、忘れないように書き残しておきます。

主にマルチプラットフォームに耐えうるか調べてみます。

  • duocode

http://duoco.de/

doccode それはC#をJavaScriptに変換してくれる素敵なツール。VSコンパイラの機能で行っているようです。

30日間無料があるのでそれで使用感をつかみたいと思います。

インストール

インストールは公式にそって大まかに進めてください。

ただし、以下の2点が大まかな説明からは抜けているので、個別にインストールしないとコンパイルできません。。

  • iis express のインストール

https://www.microsoft.com/ja-JP/download/details.aspx?id=48264

webアプリのプロジェクト作成に必要になります。もちろんiisが入っていれば不要でしょう。

  • node watcher

nodeアプリのデバッグに必要なようです。

実行時に「無いのでこのコマンドでインストールしてね」と出るので省略します。

自分の環境では壮大にインストールエラーになりましたが、その後デバッグ可能になりました。コワイです。きっと機能不完全だと思います。

今回は使わないのでパスします。

プロジェクト

大きく分けて3種類のプロジェクトが選択可能になります。

  1. webアプリ(html)
  2. クラスモジュール
  3. nodeJSアプリ

感触をつかむには最初はwebアプリがいいでしょう。

webアプリデバッグの注意点

さっそく売りの1つのVS上でのデバッグを試してみたくなりますよね。

サンプルにも「ここにブレークポイントを置いてみよう!」なんてコメントもあります。わくわくしますね。

設置して実行すると、あ…豪快に通過しますね…ダメじゃないですか。


理由はリモートターゲット(webなのでブラウザ)によって対応してないみたいです。

そんな…Edgeちゃんここでもダメなの?IEだとちゃんと止まるようですね。しかしめっちゃ重い……。

Edgeの場合はブラウザ上でデバッグすれば問題ありませんでした。吐き出されたJSにSourceMapコードがついてるので、対応ブラウザでちゃんと元のC#ファイルの箇所を示してくれるようです。

うん。

クラスモジュールの作成

慣れてきた(?)ところで、最小構成とマルチプラットフォームの程度が知りたいのでクラスモジュールを試します。

この機能はC#の一連のクラスモジュールをそのままJSのモジュールに変換します。デフォルトでは自動的に一個のファイルの形に結合して落とし込んでくれます。

新規プロジェクトを選ぶと、空のクラスが作成されます。適当に書いて弄ってコンパイル。

script\ フォルダに返還後のJSファイルなどが出力されます。

ClassLibrary1.dll     // C#クラスモジュール
ClassLibrary1.js      // 出力JS
ClassLibrary1.js.map  // JS SourcaMap debug
ClassLibrary1.pdb     // VS SourceMap debug
mscorlib.d.ts         // type script 定義ファイル
mscorlib.js           // .net 基礎ライブラリのJS版
mscorlib.min.js       // mscorlib.js最適化版。改行と空行を無くしたもの

mscorlib と一緒に出力・使用されるのが duocode の特徴です。

動作させるには mscorlib.js/mscorlib.min.js と ClassLibrary1.js の2つを使用します。

webや組み込みであれば

<script src="mscorlib.js" />
<script src="ClassLibrary1.js" />

という感じで2つのソースを連結すれば動作します。

またnodeJSなら

require("mscorlib.js");

を ClassLibrary1.js の先頭に追加すれば動作します。


なお、変換されたJS(ClassLibrary1.js)はこんな感じになっております。

(function ClassLibrary1() {
"use strict";
var $asm = {
    fullName: "ClassLibrary1",
    anonymousTypes: [],
    types: [],
    getAttrs: function() { return [new System.Reflection.AssemblyTitleAttribute.ctor("ClassLibrary1"), new System.Reflection.AssemblyDescriptionAttribute.ctor(""), new System.Reflection.AssemblyConfigurationAttribute.ctor(""), new System.Reflection.AssemblyCompanyAttribute.ctor(""), new System.Reflection.AssemblyProductAttribute.ctor("ClassLibrary1"), new System.Reflection.AssemblyCopyrightAttribute.ctor("Copyright \xA9  2017"), new System.Reflection.AssemblyTrademarkAttribute.ctor(""), new System.Reflection.AssemblyCultureAttribute.ctor(""), new System.Reflection.AssemblyVersionAttribute.ctor("1.0.0.0"), new System.Reflection.AssemblyFileVersionAttribute.ctor("1.0.0.0"), new DuoCode.Runtime.CompilerAttribute.ctor("3.0.1654.0")]; }
};
var $g = (typeof(global) !== "undefined" ? global : (typeof(window) !== "undefined" ? window : self));
var ClassLibrary1 = $g.ClassLibrary1 = $g.ClassLibrary1 || {};
var $d = DuoCode.Runtime;
$d.$assemblies["ClassLibrary1"] = $asm;
ClassLibrary1.Class1 = $d.declare("ClassLibrary1.Class1", 0, $asm);
$d.define(ClassLibrary1.Class1, null, function($t, $p) {
    $t.ctor = function Class1() {
        $t.$baseType.ctor.call(this);
        console.log("constructor :)");
    };
});
return $asm;
})();
//# sourceMappingURL=ClassLibrary1.js.map

TypeScriptの出力と決定的に違うのはすべてが mscorlib に依存したコードで出力されることです。細かい部分も.netをエミュレートしているような印象が強いです。よく見る.ctorなんかconstrucotrですね。

このソースを保守管理するの現実的なレベルではありません。ソースマップがあるとはいえ実行時エラーはかなり厳しい宇宙言語的な内容を返すでしょう。

デバッグや開発は基本的にC#で完結する環境を作って行い、多言語との受け渡しとしてDLLみたいな(もう中身は弄らないぞ!)感じで扱うなら問題無さそうです。とはいえ、DLLに比べれば追跡もエラー箇所特定も初期状態で恵まれた環境なのは確かです。

mscorlib.jsを許せるかどうか

このファイルの存在が duocode を使用した際の最大の課題点です。

.netの基礎部分を記したもので、duocodeで変換を行う限りこれに依存します。クラスの根幹部分もこれに依存しているため事実上分離は不可能です。サイズが mscorlib.js(864K) / mscorlib.min.js(471K)と結構あります。今後も増えるでしょう。容量と初回起動時のコストで評価が分かれると思います。個人的には規模の割に十分軽いと思いますが。

また、外部から使用する際は.netエミュレータの方言があるので、一層自前インターフェイスクラスで包んであげたりする必要があるでしょう。ただし、TypeScriptを使用するのであればこの辺の問題はパス出来そうです(後記)。

吐き出されたコードによるオブジェクト(クラス)の展開は、ちゃんとグローバルに構造を展開してくれているので、プログラムや他のJSから容易に引っ張ってこれる点はとても気が利いています。グローバル汚染と言われるかも知れませんが…。


所感

C#移植とおもって使うと肩すかしを食らいますが、言語変換としてみればとてもよく出来てると思います。デバッグまで付いてきますし。

単なるwebページに使うにはちょっと大がかりかなーというのは確かですが、C#におけるPCL(ポータブル・クラス・ライブラリ)の一種として使用するなら十分に運用の可能性があると思われます。

C#上で環境を構築、テストを行い、その一部モジュール(ただし環境依存性が無いこと)を色んなところで使いたいなら協力だと思います。

サンプルのような1からwebアプリを作るのは他の色んなJSライブラリを入れることを考えると……ちょっと現実的じゃない気がします。すべてC#で組む勢いならいいですが、結局既存のJSライブラリとの連携がどうにもならないので…。

nodeJSアプリについても同様の不安が残ります。データベースや各種HTTPのシェイクハンドなども言語依存性が高いですし…。


あくまでC#による一部資産を生かすための手段の1つ以上のことは望まないほうが幸せかな?と言った印象です。

おっと忘れてましたが、売りのVS上でのデバッグについては、思った以上に素直に動きませんでしたので残念な印象です。まだ産まれたばかりですし今後に期待しましょう。

おまけ、ChakraCoreで動かす場合

基本的には1つのファイルに結合すれば問題ないのですが、一点だけエラーが発生します。

var $g = (typeof(global) !== "undefined" ? global : (typeof(window) !== "undefined" ? window : self));

グローバルオブジェクトの取得で失敗します。検索する3つの変数がどれもデフォルトでは存在しません。まぁそうですよね。

global、windowsをプログラム側で用意してやれば動作します。selfでも大丈夫だと思いますが、他のオブジェクトのプロパティでも使われているのでどうかなーといったところです(参照優先順位的にだいじょうぶなのは確認済みです)

JsGetGlobalObject(globalref);
JsGetPropertyIdFromName("global",idref)
JsSetProperty(globalre,fidref,globalref); // (global).global = global

そろそろglobal参照を定義しませんか?JSさん…

おまけ、TypeScript出力

VSプロジェクト設定から選ぶことができま…あれ?declare?

/// <reference path="./mscorlib.d.ts" />

declare module ClassLibrary1 {
    // ClassLibrary1.Class1
    export interface Class1 extends System.Object {
    }
    export interface Class1TypeFunc extends TypeFunction {
        (): Class1TypeFunc;
        prototype: Class1;
        new (): Class1;
        ctor: { new (): Class1; };
    }
    var Class1: Class1TypeFunc;
}

TypeScriptの定義ファイル(d.ts)が出力されるだけでした。今まで通りJSも出力しとくから最後に勝手に繋げて使ってね!と言うことみたいです。

クラスに関する取り扱いはTypeScriptを通した方が断然楽だと思うので、変換したものにTypeScriptでインターフェイスを書いて一層噛ましてあげるとプログラムとの連携がスムーズになりそうです。全部プログラムで済まそうとするときっと大変です。

mscorlibの対応状況

Microsoftのバックアップもあるので大丈夫かと思うのですが、今のところStringBuilderなどの主要クラス、プロパティもちゃんと実装されておりました。

ただ、ローケルとかあのあたりは避けるべきでしょう。DateFormatあたりは調べておいた方が良さそうです。文字コード変換なども諦めて自前実装しましょう。と、このあたりはマルチプラットフォーム前提だとしょうがないですね。どの言語でも似たようなものです。