« VRMクラウド向けにトロコンもどきを作ってみた。 | トップページ | VRMポータル工事中 »

種別コードを先入先出するキュー

VRM5では、各編成に0~7の「種別コード」を付けることができ、Set/GetTrainCode命令で随時設定/取得することができる。何に使うかはさておいて、VRMスクリプトで,これを先入れ先出しするキュー(待ち行列)を作りたい。どのようにしてもよいのだが,VRMスクリプトでは配列変数は使えないので、0~7が2進数では000~111の3bitに収まることを利用して、ふつうの32bit整数をうまく使い、種別コードが10個まで入るようなキューを実装してみよう。

やりたいことを図解すると次のようになる。

3bitqueue

図の下半分では2進数で考えている。3bitずつのハコに書いてある数字を繋げて読んでしまうと,6bit,9bit,...の整数値になる。このつなげたものを1つの変数に収めることにしよう,という発想だ。VRMスクリプトの整数は32bit整数(符号付き)ということになっているので,10個まで1つの変数=キューに入れることになる。

また,2進数で考えることにすると,VRMスクリプトでビット演算が使える便利さが味わえる。

まずは,キューに次々と3bitずつの数を入れていくことを考えよう。

2進数にして1000倍(10進で言えば8倍)すると下に000を付け加えることができる。そこに新しくキューに入る数を足し算すればよい。下図では新しく101をキューに加えている。

3bitqueuein

次に,キューの一番古い3bitを取り出す操作を考えよう。AND演算でうまく読みたいところを取り出しに行く方針にしてみる。では一体,今読むべき桁はどこであろうか。

実はもう一つ厄介なことがあって,種別コードには0が存在するので,キュー変数=0のとき,それが空のキューであるか,種別コード=0が1つ以上入ったキューなのか,見た目上区別がつかない。

この2つのややこしさを,少々トリッキーかもしれないが,読み取り部変数をもう一つ仕立てておくことによって解決する。赤字で下段に書いたのが,読み取り部変数である。

3bitqueue2_2

当初の空のキューはキュー変数と読み取り部変数がともに0であるが,最後にキューに1つだけ種別ID=0が入っている状況では読み取り部変数が111(=7)になっている。キューの出入りに合わせて、読み取り部変数の111のところが連動して左右にずれるようにする。

キュー変数と読み取り部変数をAND演算すると,読み取り部変数が111の部分のみ取り出すことができる。そのままでは,010 000のように4桁以上になっているかもしれないが,3桁以下になるまで1000(つまり8)で割れば種別コードそのものになる。

では,これをVRMスクリプトに書いてみよう。

BeginFuncより前で2つのグローバル変数を宣言する。

//
// キューっぽいもの
// 000-111を右から入れて左から出す(FIFO)
//

Var VarQueue //キュー変数
Var VarReader //読み取り部変数

以下は,必要に応じて適当なメソッドにしよう。

//
// レジスタにIN
// 変数inidを追加
//
mul VarQueue 8
or VarQueue inid

// VarReaderを同時にシフトする。もともと0だったときは111(2)=7にする
if> VarReader 0
	mul VarReader 8
else
	set VarReader 7
endif
//
//
// キューからOUT
// キューが空の場合はエラーコード-1を出す
//
Var outid
if VarReader
	mov outid VarQueue
	and outid VarReader
	
	// 7以下になるまで8で割る
	:div8
	if>= outid 8
		div outid 8
		jump div8
	endif
	
	div VarReader 8
else
	set outid -1
endif

キューから出す部分では,ラベルジャンプを使用してループを組んでいる。

実は,キューからOUTのメソッドのほうだが,VarQueueから読みたいところ3bitを読み出した後も特に読んだ分を削除したりするような処理をしていない。つまりキューに入れるに従ってどんどんVarQueueが8倍+αされて大きくなっていき,すぐに31bitを越えて桁あふれしてしまう(桁あふれしたときにはじめて削除される,ということでもある。)のだが,実行してみたところとくに読み出されてくる値に問題がなく意図通りに動くので,そのままにしている。もちろん,11個以上キューに蓄えようとするとエラーになるので注意。

すぐに試してみたい人向けに,1キーと2キーでキューに0~7の乱数をIN/OUTするように仕上げたものを載せておこう。レイアウトスクリプトにでも貼り付ければ動く。(一応,自己責任で!)

//
// キューっぽいもの
// 000-111を右から入れて左から出す(FIFO)
//

Var VarQueue //キュー変数
Var VarReader //読み取り部変数

Var EidKey1
Var EidKey2
SetEventKey this RegisterIN EidKey1 1
SetEventKey this RegisterOUT EidKey2 2


BeginFunc RegisterIN
//
// キューにIN
// 変数inidを追加
//
Var inid
irnd inid
and inid 7

mul VarQueue 8
or VarQueue inid

// VarReaderを同時に3bit左シフトする。もともと0だったときは111(2)=7にする
if> VarReader 0
	mul VarReader 8
else
	set VarReader 7
endif
DrawMessage "QUEUE IN"
DrawVar inid
DrawVar VarQueue
DrawVar VarReader
EndFunc


BeginFunc RegisterOUT
//
//
// キューからOUT
// キューが空の場合はエラーコード-1を出す
//
Var outid
if VarReader
	mov outid VarQueue
	and outid VarReader
	
	// 7以下になるまで8で割る
	:div8
	if>= outid 8
		div outid 8
		jump div8
	endif

	// 同時にVarReaderを3bit右シフト
	div VarReader 8
else
	set outid -1
endif
DrawMessage "QUEUE OUT"
DrawVar outid
DrawVar VarQueue
DrawVar VarReader
EndFunc

素人レベルでCとかPythonとかを書くこともあるのだが、こういうリッチな言語でビット演算を使ったことはないし、入門書の類でもビット演算に関わる項目は見かけた覚えがない。やっぱり、こういうやり方は時代遅れなんだろうか?

|

« VRMクラウド向けにトロコンもどきを作ってみた。 | トップページ | VRMポータル工事中 »

VRM:研究」カテゴリの記事

コメント

確認したら、「種別ID」ではなく「種別コード」が正式な名称だったので記事を一部修正した。

あと、VRM Terminalの「ビット演算講義」のNOT演算の部分がVRMスクリプトの挙動と違うことが書いてあるのでそのうち直すつもり。


このキュー自体は、自律自動運転で、退避/追い越しをするかしないかの判断に使えるなあと思っていたりはするけど、そこまでのやる気がないw

投稿: AKAGI | 2017年10月26日 (木) 12時34分

コメントを書く



(ウェブ上には掲載しません)




トラックバック


この記事へのトラックバック一覧です: 種別コードを先入先出するキュー:

« VRMクラウド向けにトロコンもどきを作ってみた。 | トップページ | VRMポータル工事中 »