ほんじゃらねっと

ダイエット中プログラマのブログ

node.jsがやたら非同期化しようとするのをasync/awaitでどうにか同期化する

Node.jsは入出力関連の処理を非同期で行ないます。入出力イベントが非同期で実行されることで、遅い入出力処理の合間に他の処理を挟んで効率的にプログラムを実行することができます。この特長を活かすため、ライブラリに含まれる関数も非同期的に実行されるものがとても多いです。

簡単に処理を非同期化できるようになっているというのはうまく活用できれば大変ありがたいもので、node.jsの最大の特長だと言えます。

ところがただ「簡単に導入できて」「実行速度が速くて」「Javascriptで書ける」ツールとして手軽にnode.jsを使いたい、という場合にこの非同期至上主義なところが邪魔をすることがあります。

ちょっとした処理を行うコマンドラインスクリプトをサクッと作りたいだけなのに、思ったような順番で処理が実行されなかったり、コールバック地獄になったり。

同期的に動かそうとするだけでなぜこんなに分かりにくい書き方をしなければならないのか...。プログラミング初心者がnode.jsを使おうとした時に頭を悩ませる大変の要因はこの非同期処理の同期化によるものではないでしょうか。

せっかく簡単に導入できてJavascriptで書けて実行速度も速い最高のツールなのに、自分には合わない、とnode.jsを捨ててしまうのはもったいないです。

Node.jsのv7.6から導入された「async/await」を使うことで同期化がしやすくなり、慣れ親しんだものに近い形でプログラムが書けるようになりました。一度使ってみるとこれ無しではやってられなくなるくらいすっきりとしたコードが書けます。

今回はこの「async/awaitを用いた非同期処理の同期化」の方法について紹介します。

「async/await」を使用すると呼び出し側のコードがスッキリする

async/awaitはどういう機能かを乱暴に説明すると、「非同期な関数」に「async」というキーワードをつけておくことで非同期的にでも同期的にでも呼べるようにする機能です。「await」キーワード付でasync関数を呼ぶことで、あたかも同期的な処理を行う関数であるかのように扱えます。

コールバック型の非同期関数とasync/await型の非同期関数の定義と呼び出しのちがいを比較しながら見てみましょう。

非同期な関数はいつ実行が終了するか分からないので、引数として関数の処理完了後に実行する処理をまとめたコールバック関数を渡しておき、それを実行させます。非同期な関数が実行完了してから実行したい処理がある場合はコールバック関数の中に書くことになります。

望む実行順で非同期関数を実行したい場合はコールバック関数の中で非同期関数を呼び出し、そのコールバック関数の中でまた別の非同期関数を呼び出し、という風にコールバック関数が何重にも入れ子になった状態になります。これがいわゆる「コールバック地獄」というやつです。

仮にコールバック型の非同期関数「asyncReadValueCallbackFunc」が下記のように定義されているとしましょう。

function asyncReadValueCallbackFunc(params, callbackFunc) {
    // ...非同期な処理...
   result = ...;

   // 処理が終わってからcallback関数に処理結果を渡す。
    callbackFunc(result);
}

asyncReadValueCallbackFuncは下記のように呼んで使用します。

function main() {
    console.log('呼び出し前');

    asyncReadValueCallbackFunc('パラメータ', function (res) {
        console.log('asyncReadValueCallbackFunc実行後に実行したいコード');
        console.log(result);
    });

    console.log('呼び出し後');
}

この場合、「呼び出し後」というメッセージを出力する処理はasyncReadValueCallbackFuncの実行完了を待たずに実行されるので、「asyncReadValueCallbackFunc実行後に実行したいコード」の出力とどちらが先に実行されるかは実行してみないと分かりません。確実にasyncReadValueCallbackFuncの後に実行したい処理はコールバック関数内に記述します。

コールバック地獄になるとこんな感じのコードになります。

function main() {
    console.log('呼び出し前');

    asyncCallbackFunc1('パラメータ', function (result1) {
        console.log('asyncCallbackFunc1実行後に実行したいコード');

        asyncCallbackFunc2(result1, function (result2) {
            console.log('asyncCallbackFunc2実行後に実行したいコード');

            asyncCallbackFunc3(result2, function (result3) {
                console.log('asyncCallbackFunc3実行後に実行したいコード');
                console.log(result3);
            });
        });
    });

    console.log('呼び出し後');
}

最終的にresult3の計算結果を出すためにasyncCallbackFunc1〜3を順に実行しています。それぞれの関数は意図したとおりの順序で実行されますが、コードが非常に分かりづらいです。

async/awaitを使うと、非同期関数の処理結果をコールバック関数で受け取る代わりに戻り値として受け取ることができます。上記のasyncReadValueCallbackFuncと同じ処理をasync/awaitを用いて行う関数「asyncReadValueFunc」を実装してみましょう。

async function asyncReadValueFunc(params) {
    // ...非同期な処理...
    result = ...;

    // resultを実行結果として返す        
    return result;
}

コールバック関数を受け取るパラメータが不要となり、代わりに「async」というキーワードをfunction定義前に追加しています。

このasyncReadValueFuncは下記のように呼んで使用します。

async function main() {
    console.log('呼び出し前');

    const res = await asyncReadValueFunc('パラメータ');
    console.log('asyncReadValueFunc実行後に実行したいコード');
    console.log(result);

    console.log('呼び出し後');
}

呼び出す側はasyncReadValueFuncを呼び出す際に「await」というキーワードをつけていますが、それ以外は同期関数を呼び出すのと同じ形式で関数を呼んでいます。main関数を実行すると、「呼び出し前」「asyncReadValueFunc実行後に実行したいコード」「resultの値」「呼び出し後」の順で出力されます。

awaitをつけるだけで、コールバック関数を仕込んだりする必要がなく、普通の非同期でない関数と同じように結果を受け取ることができるわけです。

上述のコールバック地獄のサンプルをasync/await版に書き換えてみましょう。

async function main() {
    console.log('呼び出し前');

    const result1 = await asyncAwaitFunc1('パラメータ');
    console.log('asyncAwaitFunc1実行後に実行したいコード');

    const result2 = await asyncAwaitFunc2(result1);
    console.log('asyncAwaitFunc2実行後に実行したいコード');

    const result3 = await asyncAwaitFunc3('パラメータ');
    console.log('asyncAwaitFunc3実行後に実行したいコード');
    console.log(result3);

    console.log('呼び出し後');
}

実にスッキリしたコードになります。

asyncキーワードは関数の戻り値をPromiseオブジェクトに変換する

単純にasync/awaitを使うのは割と簡単です。上の例で見たとおり、asyncキーワード付で宣言された関数であれば、awaitキーワード付で呼び出すだけで同期的に使用することができます。

これだけでも便利に使えるのですが、更に使いこなすためには裏で何が行われているのかを知っておきましょう。

たとえば、非同期関数の前にasyncをつけることで、つける前と何が変わるのでしょう?

答えは、「asyncをつけるとreturnした値がPromiseオブジェクトに変換される」です。async関数とは、必ずPromiseオブジェクトを返す関数なのです。

PromiseはJavascriptに組み込まれた機能の1つで、async/awaitと同じく非同期処理を管理しやすくするためのものです。実際のところasyncとawaitはあくまでPromiseをより簡単に利用するために後から追加された機能であり、Promiseこそが主役なのです。

ではそのPromiseとは何かというと、これまた乱暴に説明すると「非同期処理を1つのオブジェクトにまとめて持ち回しやすくするもの」だと言えます。

ファイルの読み込みなり書き込みなりの時間のかかる入出力処理を非同期で行いつつ、完了した後で別の処理を行いたいと言う時に、その非同期処理ごと1つのPromiseオブジェクトにまとめてしまって、どこでも結果を受け取れるようにしよう、というものです。

Promiseを使うことで、非同期処理を実行する関数にコールバック関数を渡す代わりに、戻り値としてまだ中で非同期処理を実行しているPromiseオブジェクトを返す、ということができるようになります。

developer.mozilla.org

Promiseオブジェクトを返す関数の定義と、その関数を呼び出してPromiseオブジェクトを受け取る処理を見てみましょう。

// Promiseオブジェクトを返す非同期関数
function asyncPromiseFunc() {
    return new Promise(resolve => {
        // ...何かしらの時間がかかる処理...
        const result = ...;

        resolve(result);
    });
}

function main() {
    // 戻り値のpはPromiseオブジェクト
    const p = asyncPromiseFunc();

    // 非同期処理が完了したらthenに渡したコールバック関数の内容が実行される。
    p.then(function (result) {
        console.log(result);
    });
}

asyncPromiseFuncがPromiseオブジェクトを返す非同期関数です。非同期処理はreturnされるPromiseオブジェクト内で実行される。非同期処理の実行結果をどう処理するかはasyncPromiseFunc関数を呼び出した側でPromiseオブジェクトのthenメソッドにコールバック関数を渡すことで定義できます。

コールバック型関数とのちがいは、非同期関数側がコールバック関数について処理をする必要がなくなり、Promiseオブジェクトを返すだけで良くなった、というところです。また、「非同期処理はPromiseオブジェクトで扱う」という風に統一されることで、async/awaitのような形に発展させやすくなりました。

async関数の話に戻りましょう。

asyncキーワードは上記のPromiseオブジェクトを返す関数を簡単に定義できるようにするものです。

下記の2つの関数は同じようなPromiseオブジェクトを返します。

function asyncPromiseFunc1() {
    return new Promise(resolve => {
        // ...何かしらの時間がかかる処理...
        const result = ...;

        resolve(result);
    });
}

async function asyncPromiseFunc2() {
    // ...何かしらの時間がかかる処理...
    const result = ...;

    return result;
}

function main() {
    const p1 = asyncPromiseFunc1();
    const p2 = asyncPromiseFunc2();

    p1.then(function (result) {
        console.log(result);
    });

    p2.then(function (result) {
        console.log(result);
    });
}

async関数の方は変数を返しているだけのように見えますが、実際はPromiseオブジェクトを生成して返しています。なのでasyncPromiseFunc2を呼び出した側はPromiseオブジェクトを受け取り、thenで処理を定義します。

asyncがPromiseオブジェクトを返す非同期関数の定義を簡単にできるようにするためのものだ、ということが確認できました。

awaitはPromiseオブジェクトの結果を同期的に受け取れるようにする

async関数はPromiseオブジェクトを返す、ではawaitは何をしているのでしょうか?

非同期処理を同期化する、という意味ではどちらかというとasyncよりもawaitの方が重要な役割を担っています。

awaitは渡されたPromiseオブジェクトの実行完了を待って結果を返す機能を持ちます。

awaitを使うことで、コールバック関数を使用することなく直接非同期処理の結果を受け取ることができるわけです。これがあって初めて、コールバック関数無しで非同期処理を順次実行できるようになります。

asyncで書いたサンプルをawaitを使用する形に変更すると、下記のようになります。

function asyncPromiseFunc1() {
    return new Promise(resolve => {
        // ...何かしらの時間がかかる処理...
        const result = ...;

        resolve(result);
    });
}

async function asyncPromiseFunc2() {
    // ...何かしらの時間がかかる処理...
    const result = ...;

    return result;
}

async function main() {
    const result1 = await asyncPromiseFunc1();
    const result2 = await asyncPromiseFunc2();

    console.log(result1);
    console.log(result2);
}

Promiseオブジェクトであれば処理できるので、対象がasync関数なのか、Promiseオブジェクトを直接生成して返す関数なのかは関係ありません。何なら関数ではなく直接Promiseオブジェクトを渡してもちゃんと処理してくれます。

まとめついでに、色々なパターンの非同期処理とその結果を見てみましょう。

// Promiseオブジェクトを返す関数
function p1() {
    return new Promise(resolve => {
        resolve('test1');
    });
}

// Promiseオブジェクトを返すasync関数
async function p2() {
    return new Promise(resolve => {
        resolve('test2');
    });
}

// 値を返す関数
function p3() {
    return 'test3';
}

// 値を返すasync関数
async function p4() {
    return 'test4';
}

async function main() {
    const result11 = p1();
    console.log('result11', result11);

    const result12 = await p1();
    console.log('result12', result12);

    const result21 = p2();
    console.log('result21', result21);

    const result22 = await p2();
    console.log('result22', result22);

    const result31 = p3();
    console.log('result31', result31);

    const result32 = await p3();
    console.log('result32', result32);

    const result41 = p4();
    console.log('result41', result41);

    const result42 = await p4();
    console.log('result42', result42);

    const result99 = await new Promise(resolve => {
        resolve('result99');
    });
    console.log('result99', result99);
}

main();

実行結果

result11 Promise { 'test1' }
result12 test1
result21 Promise { <pending> }
result22 test2
result31 test3
result32 test3
result41 Promise { 'test4' }
result42 test4
result99 result99

実行結果で「Promise { ... }」となっているのは、Promiseオブジェクトの状態になっているということです。awaitを介することでPromiseオブジェクトの処理結果が取得されていることが確認できます。

awaitを関数内で使用する場合は必ずその関数がasync指定されている必要があります。ルールとして覚えておきましょう。

async/awaitでは実装できない処理もある

async/awaitは基本的に組み合わせて使うことが多いですが、Promiseを直接使わないとうまく行かない場合もあります。というか自前で非同期処理を実装する場合はasyncではなくPromiseオブジェクトを生成することの方が多いのではないでしょうか。

例えば一定時間処理を停止するsleep関数を作って使いたい時は下記のような関数を作成します。

function sleep(waitMillSeconds: number) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, waitMillSeconds);
    });
}

awaitで呼び出せばシンプルにsleep処理を実行できます。

async function main() {
    console.log('開始');

    // 1秒待機
    await sleep(1000);

    console.log('終了');
}

main();

async/awaitの使い方だけでなく、Promiseオブジェクトについても理解しておく必要がある、ということです。

使いたい非同期関数がPromise非対応の場合はラッパー関数でPromise対応できる

最近のライブラリはだいたいPromise形式の返り値に対応しているので、そういったライブラリはthenを使う代わりにasync/awaitを使えばすっきり書けます。

しかしPromiseオブジェクトを返さない、コールバック関数を前提とした関数も多く存在します。こういった関数を便利に使いたい場合は、自分でPromiseオブジェクトを返すラッパー関数を作成します。

例えばファイルを行ごとにリストにして読み込みたい場合は、下記のような関数を定義するとasync/awaitで呼べるようになります。

import fs from 'fs';
import readline from 'readline';

function readFileLines(srcFilePath, encoding) {
    let rs = fs.createReadStream(srcFilePath, {encoding: encoding});
    let rl = readline.createInterface({input: rs});

    return new Promise((resolve, reject) => {
        const lineList = [];
        rl.on('line', (line) => {
            lineList.push(line);
        })
        .on('close', () => {
            resolve(lineList)
        });
    });
}

おわりに

async/awaitを使うと非同期関数を同期的に使うことができます。Promiseオブジェクトやらそれぞれのキーワードの役割やら色々理解しておかないといけない概念は多いですが、使いこなせば実装がスッキリしてnode.jsでの開発が一層楽しくなること間違いなしなので、ぜひ使ってみてください。

OpenCV.jsでインストール不要な画像ぬりえ化ツールをつくる

OpenCV、インストール難しすぎじゃね?

前回の記事で

blog.honjala.net

Node.jsでOpenCVを使うことに成功したわけですが、 OpenCVはとにかくインストールでうまくいかないことが多くて、 Macでどうにかうまく動いてもLinuxにインストールしようとすると またエラーでつまづいたりして、うんざりしました。

ImageMagickなんて適当にyum installするだけでインストールできるのに!

乗り換えたろか!と思いつつ、OpenCVはやっぱり使ってみたい。

ブラウザで動くらしいOpenCV.jsなら一度用意してしまえば 環境構築で二度と苦労しなくて済むのでは?

ということで今回はOpenCV.jsでの画像処理を試してみました。

docs.opencv.org

続きを読む

OpenCV + Node.js で画像からぬりえ用の線画をつくる

f:id:piro_suke:20190405013029j:plain

画像処理で遊びたいな、と思い立ったので、 以前PythonとOpenCVで作成した画像線画化スクリプトを Node.jsで作成しなおしてみました。

blog.honjala.net

OpenCVはPythonと使うのがやはり一番使いやすいのですが、 Javascript用APIも提供されているので、 Webページ上のJavascriptやNode.jsからでも利用することができます。

docs.opencv.org

しかし試してみるとNode.jsから生でOpenCV.jsを使うのはcanvasを利用することを 前提にしていたりしてなかなか手強く、うまくいかなかったので、 「opencv4nodejs」というライブラリを使うことにしました。

www.npmjs.com

こちらはOpenCVの関数をNode.jsからいい感じで使えるようにしてくれていて、 Pythonで書いた処理をNode.js用に書き換えるだけでサクッと動いてくれました。

作成した画像線画化スクリプトはこんな感じになりました:

Convert Image Files To Line Drawings With OpenCV A ...

「SOURCE_DIRECTORY_PATH」に画像が入ったフォルダを指定して、 「DEST_DIRECTORY_PATH」に出力先フォルダを指定すると、 JPEG画像を一括で線画に変換して出力します。

おわり

これでいろいろOpenCVの機能を使って遊べそうです。

詳解 OpenCV 3 ―コンピュータビジョンライブラリを使った画像処理・認識

詳解 OpenCV 3 ―コンピュータビジョンライブラリを使った画像処理・認識

  • 作者: Gary Bradski,Adrian Kaehler,松田晃一,小沼千絵,永田雅人,花形理
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2018/05/26
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

Node.jsで作成した自動化プログラムをGUI化してより多くの人に役立ててもらおう

プログラマの仕事は、人が行う作業を代わりに行なってくれるプログラムを書くことであり、それはつまるところ「作業を自動化」することだ。

長くプログラマをやってると忘れちゃうけど、

コマンドプロンプトを開いてコマンドを実行するのは、

プログラマ以外にとってはとてもハードルが高い。

作業を自動化するためのツールをGUI化するメリットについて考えてみた。

後半でNode.jsでのGUI化の方法についても紹介する。

GUI化するとメインの処理とは別で画面の作成や入力処理を実装する必要があり、

単純に開発時の作業が増えるのだけど、CLIと比較してそれだけのメリットがあるのか?

まず思いつくことで、一番重要なのは

GUI化することで作成したツールを

プログラマ以外の人が抵抗なく使えるようになるので、

対象ユーザーがグンと増える、ということ。

自動化ツールを必要としているのはむしろプログラマ以外の人たちであると考えると、

CLIスクリプトをソースと一緒に提供して頑張って勉強してもらうよりも、

GUIアプリを提供してそれを使ってもらう方が当然使ってもらいやすい。

もう1つ、CLIツールよりもGUIの方が良いな、と思うのは

「入力パラメータの保存と選択がしやすくなる」、ということ。

例えばリモートのLinux環境やDBを操作するような自動化ツールの場合、

色々な接続先に対して使うので、

接続先アドレスやユーザー名を保存しておき、一覧から選べると使いやすい。

GUI化するとメニュー等でメイン機能以外のサブ機能を持たせやすくなるので、

こういった入力の簡略化がしやすい。

あと出力についても、テキストだけでなく

グラフ等テキスト以外の形式で見せることができるので、

データの内容に適した分かりやすい出力ができる。

Node.jsで作成したスクリプトのGUI化方法

今まで書いてきた記事を見返すと、

どの自動化ツールもGUI化すればより多くの人にメリットを提供できそうなので、

今後は自動化ツールを提供する時は

できる限りGUI化したものを提供するように心がけたい。

GUI化の形としては

  • Webアプリ
  • デスクトップアプリ
  • スマホアプリ

があるけど、自動化ツールを作るならまずはデスクトップアプリだろう。

Javascript/Node.jsでデスクトップアプリを作る方法としては、

「Electron」で作るのがおそらく実績として一番多いんじゃないかと思うのだけど、

ちょうど数日前にGoogleが「carlo」という、

Node.jsアプリをより手軽にGUI化できるライブラリをリリースしたようなので、

今後はこちらも選択肢になりそう。

github.com

まだ試してないので、なにをどこまでできるのか近いうちに検証してみたい。

UIフレームワークを選択する

どういう形でGUI化を行うのであれ、

GUI化する場合はレイアウトとかコンポーネントのデザインを考慮する必要がある。

Node.jsベースのアプリをGUI化する場合、

WebアプリだろうがElectronだろうがcarloだろうが、

UIはHTML、CSS、Javascriptで作成するので、

ハイブリッドに使えるUIフレームワークを1つ習得しておくと使い回しが効いて良い。

大抵はAngular/React/Vueのいずれかのフレームワークと、

それに対応したUIフレームワークを組み合わせて使う感じだと思う。

私は今のところ「Vue.js + VuetifyJS」がお気に入り。

https://vuetifyjs.com/ja/getting-started/quick-startvuetifyjs.com

VuetifyJSはアプリ風レイアウトができること、

画面サイズによってレスポンシブにレイアウトを変更できること、

コンポーネントが十分に揃っていることが気に入ったポイント。

スマホ用WebアプリとElectronアプリを作ってみたけど、

どちらもアプリの形式に関わらずサクッと書けた。

他にもBulmaとかOnsenUIとか、色々フレームワークがあるので、

試してしっくりくるものを選ぶのが良いと思う。

bulma.io

onsen.io

おわり

GUI化に慣れていないうちは、

考えることが多くて「めんどくさ...」となることが多いけど、

何個か作ってるうちに慣れるはずなので頑張ろう。

基礎から学ぶ Vue.js

基礎から学ぶ Vue.js

  • 作者:mio
  • 発売日: 2018/05/29
  • メディア: 単行本(ソフトカバー)

マインクラフトBE (Win10版)をNode.jsで自動操作する

子どもたちがマインクラフト(iOS版)にはまっており、一緒に遊んだりしている。

クリエイティブモードで一緒にわーわー言いながら街や建物を作るのはなかなか楽しい。

楽しいのだけど、

ちまちまブロックを置いていくのはそろそろ飽きてきたので、

ビャーっとブロックを配置したり、

建物を自動生成したり、

自動化してもっと面白いことができないものかと調べてみた。

調べたところ、

自動化できる仕組みがいくつか用意されていた。

functionコマンド

最初に見つけたのが、「function」コマンド。

これはファイルに書いたコマンドのリストを実行するもので、

まさに求めていたものだったのだけど、

残念ながらJava版のみでWindows版は非対応だった。

napoan.com

iOS版の Pocket Edition (PE) と一緒に遊ぶには、

Windows版でないといけないらしい。

MakeCode for Minecraft

次に見つけたのが「MakeCode for Minecraft」。

minecraft.makecode.com

Microsoft製で、

Scratchのように処理を組み合わせてコマンドを作ったり、

Javascriptでコマンドを作ったりできる。

子供にプログラミングに触れてもらうこともできて最高なツール。

MakeCodeを起動して、マイクラ側からWebSocketでconnectして、

MakeCode側でイベントや発言に対して実行する処理を定義したら、

そのイベントをマイクラ側で実行するだけで処理を実行できる。

Windows10版でも使えるし、大抵の環境はこれで自動化できるはず...

なのだけど

うちのPCが非力すぎるのか、Mac上の仮想Windows環境だからなのか、

やたら負荷がかかって、コマンドを実行するとすぐ落ちる。

残念。これは使いたかった。PCを買い替えたら再挑戦したい。

自前WebSocketサーバーを立てる

それで諦めかけていたのだけど、

MakeCodeとの接続に使用しているconnectコマンドは

WebSocketサーバに接続するためのコマンドのようなので、

MakeCodeの代わりに自前でWebSocketサーバーを立ててマイクラとやりとりしたら

コマンドも流せるんじゃない?ということで試してみた。

WebSocketは基本的にサーバ側とクライアント側でイベント名を決めておき、

そのイベント名を使ってデータのやりとりをしているはずなので、

まずはMakeCodeにWebSocketクライアントとして接続してみたり、

WebSocketサーバにマイクラから接続してみたり、

同じようにマイクラとWebSocket通信してる事例がないかググってみたりした。

どうやらMakeCodeはJSONデータをマイクラとやりとりしており、

あらかじめ受け取りたいイベントを登録(subscribe)するリクエストを

マインクラフトに投げることでマイクラがそのイベントが発生した時に

メッセージイベントを投げてくれるらしい。

また、マイクラに対してコマンドリクエスト(commandRequst)を投げることで

マイクラ側でコマンドを実行してくれるらしい。

で、マイクラ側からはJSONデータでコマンドの成否や結果情報がレスポンスとして返される。

下記のページが参考になった。

https://www.reddit.com/r/MCPE/comments/5ta719/mcpewin10_global_chat_using_websockets/

EventSubscribe.js · GitHub

MCPE/Win10 WebSocket JSON Messages · GitHub

これで、

  1. イベント発生時のサーバへの送信設定(subscribe)
  2. コマンドの実行(commandRequest)
  3. レスポンスの取得

がJSON形式でやり取りできることが分かった。

受け取れるイベントの種類は上記の参考ページに記載されている

「MCPE & W10 Event Names」で確認できる。

コマンドは、マイクラで実行できるコマンドをそのまま書けばいいみたい。

手順としては、WebSocketサーバを立てておき、

マインクラフト側で「/connect」コマンドで対象サーバに接続する。

接続後、受け取りたいイベントをsubscribeしておき、

イベント処理としてイベント発生時に実行したいコマンドを定義しておく。

試しに、Node.jsで

「build」と発言したら自分のいる位置にブロックを配置する

WebSocketサーバを書いてみた。

const WebSocket = require('ws');
const app = require('express')();
const server = require('http').Server(app);
const uuid = require('uuid/v4');

// setblockコマンドリクエスト用JSON文字列を生成する関数
function setBlockCommand(x, y, z, blockType) {
    return JSON.stringify({
        "body": {
            "origin": {
                "type": "player"
            },
            "commandLine": util.format("setblock %s %s %s %s", x, y, z, blockType),
            "version": 1
        },
        "header": {
            "requestId": uuid(),
            "messagePurpose": "commandRequest",
            "version": 1,
            "messageType": "commandRequest"
        }
    });
}

// ユーザー発言時のイベント登録用JSON文字列を生成する関数
function subscribePlayerChatEventCommand() {
    return JSON.stringify({
        "body": {
            "eventName": "PlayerMessage"
            //"eventName": "PlayerChat"
        },
        "header": {
            "requestId": uuid(), // UUID
            "messagePurpose": "subscribe",
            "version": 1,
            "messageType": "commandRequest"
        }
    });
}

const wss = new WebSocket.Server({server});

// マイクラ側からの接続時に呼び出される関数
wss.on('connection', socket => {
    console.log('user connected');

    // ユーザー発言時のイベントをsubscribe
    socket.send(subscribePlayerChatEventCommand());

    // 各種イベント発生時に呼ばれる関数
    socket.on('message', packet => {
        console.log('Message Event');
        console.log(packet);
        const res = JSON.parse(packet);

        // ユーザーが「build」と発言した場合だけ実行
        if (res.header.messagePurpose === 'event' && res.body.properties.Sender !== '外部') {
            if (res.body.eventName === 'PlayerMessage' && res.body.properties.Message.startsWith('build')) {
                console.log('start build');

                // 石ブロックを配置するリクエストを送信
                socket.send(setBlockCommand('~0', '~0', '~0', 'stonebrick'));
            }
        }
    });

});

server.listen(3000, () => {
    console.log('listening on *:3000');
});

マイクラ側で

/connect <WebSocketサーバが動いているPCのIPアドレス>:3000

と実行するとサーバ側で「user connected」と表示されるはず。

その後、マイクラ側で「build」と発言したらその位置にブロックが配置されるはず。

ここまでできたら、

様々なイベントをsubscribeして移動した後ろにブロックを配置していったり、

ブロック生成処理をループ処理して建物を作る、みたいなこともできる。

おわり

これまでWebSocketを使う機会がなかったので、良い勉強になった。

次は元々の目的だった、建物自動生成に挑戦したい。

Minecraft (PC/Mac 版)

Minecraft (PC/Mac 版)

Minecraft (マインクラフト) - Switch

Minecraft (マインクラフト) - Switch

Node.jsでSQLServerにWindows認証接続する

苦労したのでメモ。

Node.jsでのデータベース操作にはいつもknexを使用していて、

Knex.js - A SQL Query Builder for Javascript

knexはSQLServerにも対応しているのだけど、

そこで使用されているmssqlパッケージがWindows認証には対応していないのか、

うまく接続できなかった。

www.npmjs.com

このmssqlパッケージで使用されるドライバには

デフォルトで使用される「Tedious」と

Node.jsの0.12以上で使用可能な「msnodesqlv8」の

2種類があるようで、「Tedious」だとうまく接続できないっぽい。

knexでどうドライバを切り替えるのかが分からなかったのだけど、

単体で「msnodesqlv8」を使用するとうまく接続できた。

www.npmjs.com

続きを読む

作成した Node.js スクリプトをサーバーレスな AWS Lambda で手軽に定期実行する

以前作成した映画情報取得用スクリプトのように

blog.honjala.net

定期的に実行したいスクリプトを作成した場合、

そのスクリプトをどこで動かすか、というのが悩みどころになる。

いくつか方法はあるのだけど、

ローカルのPCでタスクスケジューラなりcronなりで動かす方法は

無料だし手軽にできるけど、実行したい時間にPCを起動しておく必要がある。

さくらインターネット等のVPSを借りてcronで動かす方法は

費用はそんなに高くないけど、ファイアウォールの設定等

安心して使える環境を作るまでのハードルが結構高い。

一昔前はそこで頑張ってVPSを立てるか、諦めるかしかなかったのだけど、

今はそんな時のためのサーバーレス(=サーバ管理のいらない)なサービスが使える。

今回は AWS の AWS Lambda を使って、

作成したスクリプトを定期実行する環境を作ってみたい。

続きを読む

Node.jsでテレビの映画放映情報をWebスクレイピングしてSlackに通知する

最近素晴らしいことに家族内でSlackを使ってやりとりするようになった。

せっかくなので何かボット的なものを作ろう、ということで

昔Clojureで作った、

「映画情報をスクレイピングして通知する」スクリプトのNode.js版を作って、

定期的にSlackに通知するようにしてみたい。

blog.honjala.net

続きを読む

Windowsでタスクスケジューラを使用せずにnode.jsスクリプトを定期実行する

会社の自分のPC(Windows)で何かを定期的に実行したいときは

だいたいPythonなりnode.jsなりでスクリプトを書いて

タスクスケジューラに登録しておく。

のだけど、

タスクスケジューラは未ログイン時に実行しようとすると

ログインアカウントを登録しておく必要があったり、

パスワードを変更した時に更新を忘れて実行に失敗しまくってたり、

そのログイン失敗のせいで社内ネットワークから閉め出されちゃったり、

色々と困ることがある。

Linuxのcrondと同じような仕組みができないか、と探してみたら

「pm2」と「node-cron」の組み合わせで実現できそうだったので、試してみた。

続きを読む

Vue.js + SVG でブロック崩しゲーム(自動版)を作る

数当てゲーム、マインスイーパーに続き、Vue.jsでこども向けゲームを作る。

もう少し動きのあるゲームが作ってみたいので、

今回はボールが跳ね回るブロック崩しゲームに挑戦する。

これまでに作ったゲームは初期処理で何か答えを生成しておいて、

あとはユーザーのイベントに応じて処理を実行するだけだったのだけど、

ブロック崩しの場合はゲームがスタートしたら終始ボールが

動いている必要があるので、この仕組みから考えてみよう。

続きを読む

node.jsでWebスクレイピングして取得データを保存する

node.jsでデータ収集のためのWebスクレイピングを行う。

Webスクレイピングの流れというのはだいたい決まっていて、

  1. WebページにアクセスしてHTMLを取得する
  2. 取得したHTMLの中から必要なデータを抽出する
  3. 抽出したデータを保存する

の3段階となる。

続きを読む

node.jsでリモートのLinux環境やデータベースの操作を自動化する

ぼくが仕事でLinuxサーバ環境に接続して行う操作というのはだいたい決まっていて、

  • コマンドでサーバの状態を確認
  • ログをファイル出力してダウンロード
  • サービスの設定変更と再起動
  • SSHトンネル経由でDBアクセス

のうちどれかを行うことが多い。

基本は手作業だったり、シェルスクリプトを作って

実行したりする形で対応できるものなのだけど、

結構めんどくさいので、

今回はnode.jsでどこまで自動化できるかを試してみたい。

続きを読む

Vue.js + SVG でヌメロン風数当てゲームをつくる

Vue.jsでこども用ゲーム作成シリーズ第3弾。

前回作ったマインスイーパーは、ゲームルール自体の中毒性のおかげか、

たまにプレイしてくれてるようで作った甲斐があった。

blog.honjala.net

飽きないうちに次のゲームを作成しておこう、ということで

今回は昔テレビで観たヌメロンというゲームを参考に、

数当てゲームを作ってみた。

Numer0n - Wikipedia

続きを読む

Vue.js + SVG でマインスイーパーもどきをつくる

前回作った数当てゲームがこどもに好評だったので、

blog.honjala.net

次はもう少し複雑なマインスイーパー作成に挑んでみた。

数当てゲームはシンプルすぎて2秒で飽きられたけど、

マインスイーパーならしばらく遊んでくれるにちがいない。

今回は前回使った Vue + Vuex + TypeScript に加えて、

今後より高度なゲームを作るにあたって役に立つであろう、

SVGを使って画面を作成してみた。

続きを読む