LinuxでWebGL動作せず

three.jsで作った3Dをlinuxで動作させたところ下記エラーが発生。

THREE.WebGLRenderer: Error creating WebGL context.

Windowsでは動作したのに…。

開発環境は以下。
Electron: v1.4.13
node.js: v4.4.4
three.js: 0.83.0
linux: Ubuntu14.04(64bit)
VM: Hyper-V

Electronは内部にChromeエンジンを搭載しているとのことで、まずはChromeを疑ってみる。
node.jsの影響が無いことを確認するために、前掲three.jsで多色グラデーションを試してみる。

まずはFirefoxで…。
これは問題なく動作する。

次にChrome最新版(57.0.2987.133-64bit)。
むむ…、動作せず。
デベロッパーツールで確認。

THREE.WebGLRenderer: Error creating WebGL context.

同じエラーである。
やはりChromeの問題であった。
ぐぐってみると、ChromeはWebGLのサポートを停止している模様。
[WebGL][Chrome]Chrome 10からWindows XPでのWebGLサポートを停止
でもWindowsでは動作したのに、何故linuxだけなのだろう…。
(因みにCentOS6でも動作せず。)

回避するにはchrome://flagsで「ソフトウェアレンダリングリストをオーバライド」を有効にする、といいうのが定石らしい。

これでChromeのWebGLは動作した。
あとはElectronである。

これがなかなかわからなかったのですが、ヒントはchrome://flags「ソフトウェアレンダリングリストをオーバライド」のハッシュ(#)にありました。

このフラグのハッシュ名は「ignore-gpu-blacklist」である。
なんかちょっと引く名前だ。
VMのgpuがブラックリストに載っているんでしょうか…?
ちょっと気持ち悪いんですが、これをElectronで指定できそうです。

const {app} = require('electron');
app.commandLine.appendSwitch('ignore-gpu-blacklist');

readyイベントの前に実行しなければならないので、スコープ外に記述します。

これでElectron(Ubuntu)でも無事Three.jsが動作しました。

Electronのipc通信で連想配列を送れるか?

Electronではレンダーとサーバーは別プロセスなので、情報交換はプロセス間通信となる。
そんな訳で情報交換には多少の制約はありそうだ。
以下の例はプロセス間(レンダーとサーバー)で配列をやり取りしている。

サーバー側

  • index.js

レンダー側

  • render.js
  • subrender.js

全ソースは以下。
index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Electron ipc test</title>
    <script type="text/javascript" src="render.js"></script>
  </head>
  <body>
    <div>ipc test !</div>
  </body>
</html>

index.js

'use strict';

var electron = require('electron');
var app = electron.app;
var BrowserWindow = electron.BrowserWindow;
var mainWindow = null;

app.on(
  'window-all-closed',
  function() {
    if (process.platform != 'darwin')
    app.quit();
  }
);
app.on(
  'ready',
  function() {
    mainWindow = new BrowserWindow({width: 500, height: 300});
    mainWindow.loadURL('file://' + __dirname + '/index.html');
    mainWindow.webContents.openDevTools();
    mainWindow.on(
      'closed',
      function() {
        mainWindow = null;
      }
    );
  }
);
electron.ipcMain.on(
  'hello-from-render',
  function(ev, arg) {
    console.log('receive data from render.');
    for(var key of Object.keys(arg))
      console.log('key=' + key + ' v=' + arg[key]);
    console.log('send data from server.');
    var ary = [];
    ary.push('A');
    ary.push('B');
    ary.push('C');
    ary['A'] = 'a';
    ary['B'] = 'b';
    ary['C'] = 'c';
    for(var key of Object.keys(ary))
      console.log('key=' + key + ' v=' + ary[key]);
    console.log('send ...');
    mainWindow.webContents.send('hello-from-server', ary);
  }
);

render.js

'use strict';

var electron = require('electron');

window.onload = function() {
  console.log('send data from render.');
  var ary = [];
  ary.push('A');
  ary.push('B');
  ary.push('C');
  ary['A'] = 'a';
  ary['B'] = 'b';
  ary['C'] = 'c';
  for(var key of Object.keys(ary))
    console.log('key=' + key + ' v=' + ary[key]);
  console.log('send ...');
  electron.ipcRenderer.send('hello-from-render', ary);
  console.log('send to sub ...');
  require('./subrender').emit('hello-from-render', ary);
}
electron.ipcRenderer.on(
  'hello-from-server',
  function(ev, arg) {
    console.log('receive data from server.');
    for(var key of Object.keys(arg))
      console.log('key=' + key + ' v=' + arg[key]);
  }
);

subrender.js

'use strict';

var EventEmitter = require('events').EventEmitter;
const event = new EventEmitter;
module.exports = event;

event.on(
  'hello-from-render',
  function(arg) {
    console.log('receive data from render by sub.');
    for(var key of Object.keys(arg))
      console.log('key=' + key + ' v=' + arg[key]);
  }
);

実行結果は以下の通り。

サーバー側のログ

receive data from render. <-- 連想配列の要素が欠落
key=0 v=A
key=1 v=B
key=2 v=C
send data from server.
key=0 v=A
key=1 v=B
key=2 v=C
key=A v=a
key=B v=b
key=C v=c
send ...

レンダー側のログ

send data from render.
key=0 v=A
key=1 v=B
key=2 v=C
key=A v=a
key=B v=b
key=C v=c
send ...
send to sub ...
receive data from render by sub. <-- EventEmitterでは連想配列もOK。
key=0 v=A
key=1 v=B
key=2 v=C
key=A v=a
key=B v=b
key=C v=c
receive data from server. <-- 連想配列の要素が欠落
key=0 v=A
key=1 v=B
key=2 v=C

添え字の配列は渡せるが、連想配列を渡すことはできなかった。
Jsonに変換できない、というのが理由でしょうか。
因みにNode.jsのイベントモジュールEventEmitterで送ればプロセス内なので連想配列も無事送ることができた。

three.jsで多色グラデーション

three.jsで多色グラデーションを作る。
多色グラデーション
9頂点8平面で構成されるメッシュにShaderMaterialで作成した多色グラデーションを貼り付けている。多色グラデーションは頂点シェーダーとフラグメントシェーダーの組み合わせで実現する。

頂点シェーダー

attribute vec3 verpos;
attribute vec3 verrgb;
varying vec3 varingrgb;
void main() {
  gl_Position = projectionMatrix * modelViewMatrix * vec4(verpos, 1.0);
  varingrgb = verrgb;
}

フラグメントシェーダー

varying mediump vec3 varingrgb;
void main() {
  gl_FragColor = vec4(varingrgb, 1.0);
}

verposには頂点の座標、verrgbには頂点の色が渡り、varingrgbでフラグメントシェーダーに引き渡される。頂点シェーダーの処理はVertexに対するが、フラグメントシェーダーはピクセルに対するため、GLSLが色を案分することでグラデーションが実現する。

3D操作はOrbitControls.jsを使っている。
OrbitControls.js

以下は全ソース(html)です。
直下にthree.min.jsとOrbitControls.jsがあれば動作します。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>shade test</title>
  <script type="text/javascript" src="three.min.js"></script>
  <script type="text/javascript" src="OrbitControls.js"></script>
  <script type="text/javascript">
    var renderer;
    var camera;
    var controls;
    var scene;
    var container;
    var light;
    var light2;
    var mesh;
    var geometry;
    function shadetest() {
      init_threejs();
      init_scene();
      init_camera();
      init_light();
      init_object();
      reset_position();
      loop();
    }
    function init_threejs() {
      renderer = new THREE.WebGLRenderer({antialias: true});
      renderer.setClearColor(0xffffff, 1);
      renderer.setSize(1, 1);
      container = document.getElementById('canvas3d');
      container.appendChild(renderer.domElement);
    }
    function init_scene() {
      scene = new THREE.Scene();
    }
    function init_camera() {
      camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100000);
      camera.up.x = 0;
      camera.up.y = 0;
      camera.up.z = 1;
      camera.position.set(0, 0, 5);
      controls = new THREE.OrbitControls(camera, renderer.domElement);
    }
    function init_light() {
      light = new THREE.DirectionalLight(0xcccccc, 1.6);
      light.position = new THREE.Vector3(-100, 500, 800);
      scene.add(light);
      light2 = new THREE.AmbientLight(0x333333);
      scene.add(light2);
    }
    function init_object() {
      var vertexPositions = [
        [-1.0, -1.0, 1.0],
        [0.0, -1.0, 1.0],
        [1.0, -1.0, 1.0],
        [-1.0, 0.0, 1.0],
        [0.0, 0.0, 1.0],
        [1.0, 0.0, 1.0],
        [-1.0, 1.0, 1.0],
        [0.0, 1.0, 1.0],
        [1.0, 1.0, 1.0]
      ];
      var vertexColors = [
        [1.0, 0.0, 0.0],
        [0.0, 1.0, 0.0],
        [0.0, 0.0, 1.0],
        [1.0, 1.0, 0.0],
        [0.0, 1.0, 1.0],
        [1.0, 0.0, 1.0],
        [0.5, 0.5, 0.0],
        [0.0, 0.5, 0.5],
        [0.5, 0.0, 0.5]
      ];
      var indices = new Uint16Array([
        0, 1, 4,
        1, 2, 4,
        2, 5, 4,
        5, 8, 4,
        8, 7, 4,
        7, 6, 4,
        6, 3, 4,
        3, 0, 4
      ]);
      var vertices = new Float32Array(vertexPositions.length * 3);
      var colors = new Float32Array(vertexPositions.length * 3);
      for (var i = 0; i < vertexPositions.length; i++) {
        vertices[i * 3 + 0] = vertexPositions[i][0];
        vertices[i * 3 + 1] = vertexPositions[i][1];
        vertices[i * 3 + 2] = vertexPositions[i][2];
        colors[i * 3 + 0] = vertexColors[i][0];
        colors[i * 3 + 1] = vertexColors[i][1];
        colors[i * 3 + 2] = vertexColors[i][2];
      }
      var shader = {
        fragmentShader: [
          "varying mediump vec3 varingrgb;",
          "void main() {",
            "gl_FragColor = vec4(varingrgb, 1.0);",
          "}"
        ].join("\n"),
        vertexShader: [
        
          "attribute vec3 verpos;",
          "attribute vec3 verrgb;",
          "varying vec3 varingrgb;",
          "void main() {",
            "gl_Position = projectionMatrix * modelViewMatrix * vec4(verpos, 1.0);",
            "varingrgb = verrgb;",
          "}"
        ].join("\n")
      }
      var material = new THREE.ShaderMaterial(
        {
          vertexShader: shader.vertexShader,
          fragmentShader: shader.fragmentShader
        }
      );
      var geometry = new THREE.BufferGeometry();
      geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
      geometry.setIndex(new THREE.BufferAttribute(indices,  1));
      geometry.addAttribute('verpos', new THREE.BufferAttribute(vertices, 3));
      geometry.addAttribute('verrgb', new THREE.BufferAttribute(colors, 3));
      var mesh = new THREE.Mesh(geometry, material);
      mesh.position.set(0, 0, 0);
      scene.add(mesh);
    }
    function reset_position() {
      var sz = {w:container.offsetWidth, h:container.offsetHeight};
      renderer.setSize(sz.w, sz.h);
      camera.aspect = sz.w / sz.h;
      camera.updateProjectionMatrix();
    }
    function loop() {
      requestAnimationFrame(loop);
      controls.update();
      renderer.clear();
      renderer.render(scene, camera);
    }
  </script>
</head>
<body onload="shadetest();" onresize="reset_position();">
  <div id="canvas3d" style="width:300px;height:300px;">
  </div>
</body>
</html>

IIS経由でDB認証エラー

ASP.netはVisual Studioでデバッグができるので、.netプログラマには相性が良い。

先刻、SQL serverにアクセスするWebアプリを作成したが、Visual Studioのデバッガーでは認証されるのに、IIS経由では認証エラーになってしまう。
エラーの詳細は以下。

「このログインで要求されたデータベース”○○○○○”を開けません。ログインに失敗しました。ユーザー’IIS APPPOOL\DefaultAppPool’はログインできませんでした。」
iis_login_error

どうやらDBにアクセスするワーカープロセスの権限がデバッガーとIISで異なることが原因のようだ。
「DefaultAppPool」というのは、IISのアプリケーションプールの名前だ。
名称からしてWindows認証のようだ。

それではMicrosoft SQL Server Management Studioでログインユーザーを作成してみよう。
ログイン名「IIS APPPOOL\DefaultAppPool」でWindows認証とする。

sqlserver_manstd_gen

ユーザーマッピングで目的のデータベースにdb_ownerのロールを与える。

sqlserver_manstd_umap

これで認証エラーは解消できるはずだ。
尚、ワーカープロセスのユーザー名はシステム環境により異なるようだ。
拙宅の場合「NT AUTHORITY\IUSR」というケースもあった。

InputMan for ASP.NETのバージョン移行

ASP.NET改修のお仕事でInputManを使う必要があり、同一バージョンの製品を購入し、さっそくVisualStudioで動作確認と思いきや、何故か例外が発生してしまう。

'WebDev.WebServer40.EXE' (マネージ (v4.0.30319)): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\GrapeCity.Web.Input.v70\v4.0_7.0.2016.219__c3bd7c1dccef5128\GrapeCity.Web.Input.v70.dll' が読み込まれました
Application Error: System.Web.HttpParseException: 基本クラスに 'EditID' という名前のフィールドが含まれていますが、その型 (GrapeCity.Web.Input.IMEdit.GcTextBox) はコントロール (GrapeCity.Web.Input.IMEdit.GcTextBox) の型と互換性がありません。
場所 System.Web.Compilation.AssemblyBuilder.AddBuildProvider(BuildProvider buildProvider)
場所 System.Web.Compilation.BuildProvidersCompiler.ProcessBuildProviders()

どうやらDLLのバージョンとプログラムが認識しているバージョンが違うらしい。
インストールも正常にいってるし、プロジェクトにも最新のファイルが取り込まれているのに何故だ。

少々嵌っていると、なんとaspの宣言でバージョンが指定されているのを発見。

<%@ Register assembly="GrapeCity.Web.Input.v70, Version=7.0.2014.0122, ... %>

しかもビルドバージョン以降が違う。
購入version⇒7.0.2016.0219

これが原因なのか・・・。
該当箇所は多数だ。
相当な修正量である。

途方に暮れながらGrapeCityのサイトを眺めていると、バージョン移行ツールが付属しているとの事。

確かにあった。
inputman_tool

実行してみる。
inputman_ikou

7.0.2014.0122から7.0.2016.0219への変換と認識されている。
これを変換が必要なファイルに対して行う。
当方はcsprojに対して行ったが、他にconfig関係のファイルも指定できるようだ。

これで全て上手くいった。

FormBorderStyleの外観

.NetのFormはFormBorderStyleによりResizeの動作や枠の外観を変更できる。
確かにVisualStudioのデザイン画面上ではFormBorderStyleの変更で外観も追随する。
ところが実行してみるとWindows7上では枠の外観がデザイン画面のそれと一致しない。
少しおかしい…、バグなのか?
確かめてみよう。

以下のコード(DPI仮想化非対応)を用意した。

private void MainForm_Load(object sender, EventArgs e)
{
  labelMessage.Text = "FormBorderStyle = " +
                      this.FormBorderStyle.ToString();
}
private void buttonCopy_Click(object sender, EventArgs e)
{
  //Rectangle r = new Rectangle(Left, Top, Width, Height);
  Rectangle r = Bounds;
  Bitmap bmp = new Bitmap(r.Width, r.Height,
                          PixelFormat.Format32bppArgb);
  Graphics g = Graphics.FromImage(bmp);
  Point sOrg = new Point(r.Left, r.Top);
  Point dOrg = new Point(0, 0);
  g.CopyFromScreen(sOrg, dOrg, 
                   new Size(r.Width, r.Height),
                   CopyPixelOperation.SourceCopy);
  Clipboard.SetImage(bmp);
  bmp.Dispose();
  MessageBox.Show("画面キャプチャをClipboardに転送しました。", 
                  "確認",
                  MessageBoxButtons.OK, MessageBoxIcon.Information);
}

ボタンを押すと自身のFormをキャプチャする仕組みだ。
キャプチャの範囲はFormのBoundsを使う。
BoundsとはFormのLeft、Top、Width、Heightを保持するプロパティだ。
これできっちりキャプチャが取れるはずだ。
以下は全てのFormBorderStyleでキャプチャした結果である。
画面上にFormBorderStyleを表示している。

FormBorderStyle=None
fbs-none

FormBorderStyle=SizableToolWindow
fbs-sizabletoolwindow

FormBorderStyle=FixedToolWindow
fbs-fixedtoolwindow

FormBorderStyle=Sizable
fbs-sizable

FormBorderStyle=FixedDialog
fbs-fixeddialog

FormBorderStyle=Fixed3D
fbs-fixed3d

FormBorderStyle=FixedSingle
fbs-fixedsingle

None、SizableToolWindow、Sizable以外は微妙に端が切れてキャプチャされている。
FormのBoundsと異なる形状で表示されていることがわかるだろう。

やはりこれはバグなのか?
.Net APIが返却するFormのBoundsと外観の違いは、VisualStudioのデザイン画面との食い違いを見ても明らかである。
とは言え、この現象はWindows10に於いても改善されていない。
何らかの意味のある「仕様」なのか?

わからないのである…。

起動時にMainFormを非表示にする

.netのMain form、即ちApplication.Runに渡すアプリケーションのメインウィンドウとなるFormオブジェクトだが、これを起動時に非表示にする真っ当な方法が無い。
WinAPI時代はPreCreateWindow等の気の利いたタイミングでWS_VISIBLEをオフする手があったが、どうも.netにはこのような手段が無い…、ようだ。
ググってみても、Application.Runに渡さずShowDialogしろとか、ActivatedハンドラでHideしろとか、なんだかすっきりしない。

そこで非表示は諦めて、透明度(Opacity)で代替してみた。
以下は初期表示にOpacityを0.0に設定(非表示状態)し、徐々に透明度を下げる(1.0に近づける)ことで1.5秒後にFormの表示が完了するようにしている。

private void Form_Load(object sender, EventArgs e)
{
  Timer timer = new Timer();
  timer.Tick += new EventHandler(OpeningTimerProc);
  timer.Interval = 10;
  timer.Start();
  timer.Tag = System.DateTime.Now.Ticks;
  this.Opacity = 0.0;
}
private void OpeningTimerProc(object sender, EventArgs e)
{
  if (sender is Timer)
  {
    long DISPLAY_TIME = 15000000;
    Timer timer = (Timer)sender;
    long w = System.DateTime.Now.Ticks - (long)timer.Tag;
    if (w >= DISPLAY_TIME)
    {
      this.Opacity = 1.0;
      timer.Stop();
    }
    else
    {
      this.Opacity = 1.0 * w / DISPLAY_TIME;
    }
  }
}

上記はアプリケーションのメインウィンドウとなるFormクラスで、Form_LoadはLoadハンドラだ。
開始時間をTagに格納し、タイマーで透明度を下げる時間の判定に使っている。
Tagはなんだか便利だ。
もちろんいきなりOpacityを1.0にしても良いが、1.5秒もアプリの起動操作に反応が無い、というのも宜しくないので、このようにしてみた。

ToolTipのバルーンをコントロール以外の図形に表示する

.NetのバルーンツールはToolTipだが、コントロールではないオブジェクト、例えば自作の図形に適用するには、少々細工が必要だ。
バルーンを表示するタイミングはMouseMoveイベントで自作図形の範囲にマウスが侵入する時だろう。

private void MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) {
  System.Drawing.Point pt = new System.Drawing.Point(e.X, e.Y);
  Rectangle rect = myShape.GetExtent();
  if(rect.Contains(pt)) {
    toolTip.Show("こんにちは、図形!", pt, 3000); // 3秒間バルーン表示
  }
}

myShapeは自作図形、GetExtentは図形の存在領域を物理単位で返す関数、toolTipは.netのToolTipコントロールだ。
これを実行してみる。
自作図形の上にマウスを置くと、バルーン「こんにちは、図形!」が表示されるが、チラつきが激しい。
美しくない。
MouseMove発生毎にtoolTip.Showが呼ばれるのがチラつきの原因だろう。
ToolTipコントロールがバルーン表示中に呼ばれるShowを無視してくれることを期待したが、甘かった。
ここは表示制御が必要のようだ。

バルーン表示直後に表示中フラグを設け、Timerイベントのハンドラでこれをクリアする。
このロジックでバルーン表示中のShow呼び出しを抑制してみよう。

bool balloonDisplay = false;
private void MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) {
  System.Drawing.Point pt = new System.Drawing.Point(e.X, e.Y);
  Rectangle rect = myShape.GetExtent();
  if(!balloonDisplay && rect.Contains(pt)) {
    toolTip.Show("こんにちは、図形!", pt, 3000); // 3秒間バルーン表示
    balloonDisplay = true;
    Timer timer = new Timer();
    timer.Tick += new EventHandler(ClearEvent);
    timer.Interval = 5000; // 5秒後にバルーン表示抑制解除
    timer.Start();
  }
}
private void ClearEvent(object sender, EventArgs e) {
  ((Timer)sender).Stop();
  balloonDisplay = false;
}

バルーン表示時間と抑制解除の時間は適宜変更するとよい。

DataGridViewの印刷クラス

DataGridViewには印刷機能が無い。
自作が必要だ。
その前に、まずは先人の力を拝借できるか検索。
以下がベストマッチ。
clsDataGridViewPrinter
有難うございます。
使わせて頂きます。
でもvb.net版ですから、C#版を作成しました。
DataGridViewPrinter
使い方は上記のサイトをご覧下さい。

Haskellで乱数を使う

モナドを習得するためWinGHCiでHaskellを写経中だが、アクションのサンプルで使う乱数用のモジュールのインポートが失敗する。

Could not find module 'System.Random'

どうやらSystem.Randomモジュールが実装されていないようだ。
ググってみると、ghc7.2.1からSystem.Randomはバンドルされなくなっている、らしい。

というわけでWinGHCiでSystem.Randomパッケージをインストールしてみる。

まずはパッケージをここから入手しよう。
当方はrandom-1.1.tar.gzを取得した。
展開すると以下の通り。

haskell-system-random
SetupプログラムがHaskellのソースで梱包されている。
コンパイルしてみよう。

random-1.1>ghc Setup.hs
[1 of 1] Compiling Main             ( Setup.hs, Setup.o )
Linking Setup.exe ...

実行してみる。

random-1.1>Setup.exe
no command given (try --help)

怒られた。
「ヘルプを見ろ」と言っているので、見てみる。

random-1.1>Setup --help

This Setup program uses the Haskell Cabal Infrastructure.
See http://www.haskell.org/cabal/ for more information.

Usage: Setup [GLOBAL FLAGS] [COMMAND [FLAGS]]

Commands:
  configure     Prepare to build the package.
  build         Compile all/specific components.
  repl          Open an interpreter session for the given component.
  install       Copy the files into the install locations. Run register.
  copy          Copy the files into the install locations.
  haddock       Generate Haddock HTML documentation.
  clean         Clean up after a build.
  sdist         Generate a source distribution file (.tar.gz).
  hscolour      Generate HsColour colourised code, in HTML format.
  register      Register this package with the compiler.
  unregister    Unregister this package with the compiler.
  test          Run all/specific tests in the test suite.
  bench         Run all/specific benchmarks.
  help          Help about commands.

For more information about a command use
  Setup COMMAND --help

Typical steps for installing Cabal packages:
  Setup configure
  Setup build
  Setup install

Global flags:
 -h --help            Show this help text
 -V --version         Print version information
    --numeric-version Print just the version number

Cabal packagesをインストールするにはconfigure、build、installの順で実行して見ろと言っている。
やってみる。

random-1.1>Setup configure
Configuring random-1.1...

random-1.1>Setup build
Building random-1.1...
Preprocessing library random-1.1...
[1 of 1] Compiling System.Random    ( System\Random.hs, dist\build\System\Random.o )

System\Random.hs:43:1: Warning: Tab character
・
・(Warning多数)
・
System\Random.hs:528:1: Warning: Tab character
In-place registering random-1.1...

random-1.1>Setup install
Installing library in C:\Program
Files\Haskell\x86_64-windows-ghc-7.10.3\random-1.1-7Qv4dDT5WBF9U3tMWkytIb
Registering random-1.1...

完了。

winghci-system-random
System.Randomをインポートできるようになった。