はじめに
今回の記事は、前回の記事に対する「QuartzMiner_D」氏からのコメントをヒントに構成しています。百万の感謝を。
さて今回の目標は、タートル側に存在する「任意のプログラム」「任意の関数」を遠隔操作して呼び出して実行しよう、です。
任意のプログラムを呼び出すこと
前回の記事では、"os.run({},'/rom/programs/turtle/dance')" という文字列をタートル側に送信することで、タートル側の"dance"プログラムを呼び出しました。このプログラムによりタートルが踊りだします。
しかし、絶対パス表現(プログラムファイルの位置を完全表記)って、結構大変ですよね。できるならばもっと簡単な表記にしたいものです。
ファイルパスの解決をしてくれる関数として、shell.resolveProgram()
があるのですが、これを内部的に呼び出してくれるshell.run()
関数が使えると幸せになれそうです。
でも、前回のプログラムのままではshell.resolveProgram()
の実行に失敗してしまうため、shell.run()
は使えないのですよね……。
任意の関数を呼び出すこと
また現状では、タートル側のmsg_receiverプログラム内で定義したサブ関数を、遠隔操作で呼び出することができません。
たとえば以下は、前回のmsg_receiverプログラムの一部を抜粋し、少しだけ追記したものです。
このプログラム内で関数hevo()
を定義していますが、この関数は外部からのメッセージで呼び出すことができないのです。
-- 以下のプログラム実行中に、"hevo()"というメッセージを受信したとき rednet.open("right") function hevo() print('hevohevo!') end while true do local sender_id, message, distance = rednet.receive() -- "hevo()"というメッセージ受信 local func = loadstring(message) -- loadstring("hevo()")で関数コンパイル。 local status, err = pcall(func) -- 実行失敗。エラー。 end
これらの原因とその解決
一言でいうならば、「だいたいloadstring()のせい」
loadstring()によって新しい関数funcが作成されるのですが、作成された関数funcの環境(Lua用語、環境テーブルとも呼ぶ)がグローバル環境テーブル(Luaでは変数「_G」で表記します)に固定されていることが原因です。
グローバル環境テーブル下では、ファイルパスの検索がうまくいきません。そのためshell.resolveProgram()
が失敗します。
また、hevo()関数が保存されている現在の環境、新しく作成されたfunc関数が見ているグローバル環境、これらが異なっているので関数funcを実行しようとしても「hevo()ってなに?しらんよ」となってしまうのです。
ということはこれら問題を解決するためには、関数funcの環境をhevo()が保存されている現在の環境に変更してあげればよいわけです。
現在の環境を取り出す関数は、getfenv(1)
(引数"1"は省略可能)です。また、ある関数に新しい環境を設定する関数はsetfenv(関数,環境テーブル)
です。
よって、setfenv(func, getfenv())
という一文をプログラム中に書き込むだけで問題は解決します。
なお今回は「環境」に関する説明を大幅に簡略化しましたが、また後日、新しく記事を作って詳しく解説しようと思います。
今のところは、loadstring()によって作られる関数は現在の環境とは違うところを見ているのね、程度に理解しておけば問題ないかと(なげやり)。
受信側(タートル側)プログラム バージョン4
msg_receiver4
変更箇所は、2箇所だけ
重要な変更点はL14に追加した1行です。
L6-8で関数hevoを定義していますが、これはテストコードなので、自分で好きな関数を定義してかまいません。
送信側プログラム バージョン4
msg_sender4
変更箇所は、L20-22の3行だけ
"z"キーを押すと、shell.run('dance')
という文字列をタートルに送信します。タートルはこれを関数として実行、インストールされているプログラムの中からdance
という名前のプログラムを探し実行します。つまり、dance
プログラムを自分の中からいい感じで探してきて実行、踊れ!という命令です。
"x"キーを押すと、先ほどと同様に登録されているパスを全て検索して、boring
プログラムを探し出し、実行します。
"c"キーを押すと、タートル側プログラム内で定義されている関数hevo()を呼び出し、実行します。ちなみにhevo()は、画面に"hevohevo!"という文字を表示するだけの関数です。 この関数自体に特に意味はありませんが、今後、このプログラムを拡張するときに利用すると良いでしょう。たとえば、以下のような燃料補給関数を作っておくと便利かもしれません。
function hevo() turtle.select(16) turtle.refuel() turtle.select(1) end
おわりに
これで遠隔操作プログラムとしてはおおよそ問題のないものができたと思われます。
次回は、使いやすいように少しだけプログラムを微調整した完成版を紹介したいと思います。
もう少しだけ続くんじゃよ。
おたのしみに。