先日 X (Twitter) でこんなポストを頂きました。
これは JP (HL) の動作を取り違えているので、残念ながらループすることはありません(おそらく暴走します)が、これをヒントとして、私が Ys'II デモで使っているタスク管理について、説明していきたいと思います。




  • 超高速ループ
さて、先に挑もうとしてた高速ループの基本的な考え方は、ループを連続ジャンプとして捉えて、動作中はループの先頭に、そして最後はループの最後に移動させようという試みでした。所謂テーブル参照ジャンプです。メモリにジャンプ先が格納されていて、そこを参照しながらジャンプする動作って、何か思いつきませんか?

そう、スタックです。call するとスタックに戻り番地が積まれ、ret でスタックからアドレスが取得されて PC(プログラムカウンタ)に設定されます。これをループに使ってみましょう。プログラムをドンッ!

COUNT equ 5 org $C000 Main: ld hl, .jptbl - COUNT * 2 ld (.exit + 1), sp di ld sp, hl .loop ; 処理 ret .exit ld sp, 0 ei ret REPT COUNT dw Main.loop
ENDM dw Main.exit
.jptbl
ループ回数は COUNT で定義しています。どうせ固定値を与えるのですから、アドレス計算はアセンブラに任せています。そのアドレスを sp に入れて…しまうと、普通の PC は割り込みが掛かった途端におかしなことになるので、先んじて割り込み禁止 di を掛けておきます。適当な処理を行った後に、いきなり ret とすると、ループ中は .loop ラベルに戻りますが、ループ終了で .exit に抜けてきます。
※ PC-8001 は通常は割り込みがないので di と ei は不要です。

ret だけでループが制御できていますし、なんならアライメントも適当で良いですし、256以上のループ回数でも速度低下はありません。一見、良いことづくめに見えますが、大きな問題点があります。それは、ループ中にスタックを使う処理が一切使えないことです。push, pop, call, ret は使えなくなります。さらに割込み禁止状態ですので、長時間の処理はできなくなります。その欠点を理解した上でなら、使い所はあるかもですねー

※充電池に関してはパナソニックは優秀ですよね。そろそろうちのも買い替えなきゃいけなさそう

  • タスク管理
さて、ret で任意のアドレスにジャンプできる…のであれば、シーケンシャル的な処理の制御には向いているんじゃないかと。つまりは、

call PRCSA call PRCSB call PRCSC call PRCSC jp GameEnd

これを

ld sp, .task ret .task dw PRCSA dw PRCSB dw PRCSC dw PRCSC
dw GameEnd

こう書いたら、メインループは短くて済むよねと。でも、このままだと、サブルーチンに任意のパラメータを受け渡す事ができません。例えば、PUTCHR という描画ルーチンがあったとして、HL = XY座標、DE = 描画データと設定が必要な場合はどうするかと。それも実は簡単です。なぜならタスクを管理しているのはスタックなのです。pop という便利な命令があるではありませんか。

Main: di ld sp, .task ret .exit ei jp GameEnd .task dw INIT dw PUTCHR, $1005, GRPPLAYER, dw PUTCHR, $2005, GRPENEMY1 dw PUTCHR, $3010, GRPENEMY2 dw .exit PUTCHR: pop hl pop de ; 処理 ret
これで、PUTCHR に対するパラメータを自在に調整できるようになります。さて、長期間の割り込み禁止はとてもマズイので、これを解消するようにします。各タスク処理中は本来のスタックアドレスに戻せば解決します。仮に本来のスタックがアドレスの最後尾にあるとしたら、各処理の先頭でスタックの初期化を行うようにするだけです。

※ 20231001-1345 更新
PUTCHR:の最後ですが、このリストでは sp を戻していない状態でしたので、jp では正常に動作しません。ret として、続いてタスクリストからジャンプ先を拾うのが正しい動作となります。大変失礼しました。

これで処理中はスタックが正常化されてますので、普通に push, pop, call を使用できるようになります。割り込みが掛かっても何ら問題ありません。

STACK equ 0 ld hl, .task ld (Main.prog), hl Main: di .prog eqe $ + 1 ld sp, .task ret .exit ei jp GameEnd .task dw INIT dw PUTCHR, $1005, GRPPLAYER, dw PUTCHR, $2005, GRPENEMY1 dw PUTCHR, $3010, GRPENEMY2 dw .exit PUTCHR: pop hl pop de ld (Main.prog), sp ld sp, STACK ei ; 処理 jp Main INIT: ld (Main.prog), sp ld sp, STACK ei ; 処理 jp Main
スタックの初期化はどこでも同じ書き方になりますから、マクロ化してしまうと見やすくなります。

TSTART MACRO ld (Main.prog), sp ld sp, STACK ei ENDM

このように設定しておけば、

PUTCHR: pop hl pop de TSTART ; 処理 jp Main INIT: TSTART ; 処理 jp Main

このように簡単に記述することが出来ます。このタスク管理技法最大の利点は、処理の流れを全てデータとして記述できることです。オートデモみたいな処理には最適です。皆さんの手によりさらなる改良が加えられることを期待しています。

※4本なら急速充電8本なら通常充電。まとめて8本は有り難いかもしれない。あと、モバイルバッテリーとしても使えるらしいので、停電時には便利かも。