セーブとロードのおはなし†ボブ「HA……HA……HA……」 というわけで、たまにはセーブとロードについてちゃんと考えてみよう、という話。 注意:以下、ゴースト起動時や他のゴーストからの交代時、ゴースト呼び出され時などに送られてくるOnBoot、OnFirstBoot、OnGhostChanged、OnGhostCalled(SSP独自イベント)、OnVanished(SSP独自イベント)などのイベントをまとめて起動系イベントと呼ぶことにします。 目次†エントリを分類してみよう†まず最初にエントリを値の使われ方によって分類して、どんなタイミングでセーブやロードをすればいいか考えましょう。
タイプA†OpenKEEPSにおけるトーク記述エントリの「sentence」などのようなエントリです。 タイプB†自発トークのタイミング用のカウンタなどの作業用エントリなどが該当します。 タイプC†ユーザー名とか好感度とか常に忘れちゃいけない値です。 タイプD†具体的にはゴーストの起動中にセクハラしたらフラグを立てて終了時に文句を言うとか、ゴースト起動時に起動時刻を覚えておいて終了時にそれを参照してトーク変えたいとか、そういうゴースト起動時に一旦初期化しておいて、その後、ゴースト起動時や起動中に値が設定されるようなエントリです。 ゴーストの起動時の処理とエントリの状態†次は、セーブやロードの話において押さえておく必要があるゴースト起動時の動作の話です。
descript.txtに「shiori.cache,0」と書けばゴーストキャッシュを無効にできますが、せっかくぽなさんが作った機能なのでコレも含めて何とかしてみましょう。詳しい話は後ほど。 おまけ:栞ロード時の動作†ちなみに栞ロード時の処理の順番は以下の通り。
特殊な起動・終了やそれに準ずる動作について†ここでクイズ。以下のうち、間違っているものはどれでしょう。
答え:全部間違い。 その1:ゴースト起動中の栞のロードやアンロード†栞のロード(とアンロード)といえばエントリが初期化されスクリプト記述ゾーンのKISがすべて実行されるというデカい処理なのに、ゴースト起動中にそんな困った処理が起きてしまうのです。
他3つはそうないとはいえ、ネットワーク更新時だなんて……('A`) その2:終了イベントを伴わないゴースト終了†これはまぁ、言われてみれば「あぁ、そういえば!」というレベルの話。
その3:ゴーストの起動中にゴースト起動イベントが来てしまう†実は、ゴーストの起動時イベントが複数回来る場合があります。 ここまでふまえて。 セーブやロードの方法を考えよう†ようやく本題。 タイプAとタイプBのエントリ†最初に書いたとおり、これらのタイプのエントリはセーブやロードをする必要はありません。以上。 タイプCのエントリのセーブとロード†タイプCのエントリはゴーストの最初の起動時に初期化して、その後、値の変化を保持し続けるエントリです。 まずはゴーストキャッシュのことなど考えず†ひとまずゴーストキャッシュのことは無視して栞アンロード時のエントリのセーブと栞ロード時のエントリのロードについて考えます。 System.Callback.OnGET,System.Callback.OnNOTIFY: $(entry ev.${System.Request.ID}) ev.OnBoot: \1\s[10]\0\s[0]起動しました。今まで${叩かれた回数}回叩かれました。\e ev.OnClose: \1\s[10]\0\s[0]さようなら。\- ev.OnGhostChanging: \1\s[10]\0\s[0]さようなら。\e ev.OnMouseDoubleClick: $(inc 叩かれた回数)\1\s[10]\0\s[0]これで${叩かれた回数}回目だ。叩くなバカ。\e System.Callback.OnUnload: $(セーブ) =kis function セーブ $( save "sav.txt" 叩かれた回数; ); function ロード $( load sav.txt; ); function 初期化処理 $( ロード; ); 初期化処理; =end ものすごく単純ですが、とりあえずの動作確認のためのモデルということで。 初期値をつけよう†上の内容でゴーストを起動すると、初回起動のときは「起動しました。今まで回叩かれました。」となってしまいます。 System.Callback.OnGET,System.Callback.OnNOTIFY: $(entry ev.${System.Request.ID}) ev.OnBoot: \1\s[10]\0\s[0]起動しました。今まで${叩かれた回数}回叩かれました。\e ev.OnClose: \1\s[10]\0\s[0]さようなら。\- ev.OnGhostChanging: \1\s[10]\0\s[0]さようなら。\e ev.OnMouseDoubleClick: $(inc 叩かれた回数)\1\s[10]\0\s[0]これで${叩かれた回数}回目だ。叩くなバカ。\e System.Callback.OnUnload: $(セーブ) =kis function セーブ $( save "sav.txt" 叩かれた回数; ); function ロード $( load sav.txt; ); function 初期化処理 $( ロード; if $[ $(size 叩かれた回数) == 0 ] $(.setstr 叩かれた回数 0); ); 初期化処理; =end 初期化処理関数の中のif文が新規に追加したところです。 ゴーストキャッシュ対策をしよう†今度はゴーストキャッシュの対策。 System.Callback.OnGET,System.Callback.OnNOTIFY: $(entry ev.${System.Request.ID}) ev.OnBoot: \1\s[10]\0\s[0]起動しました。今まで${叩かれた回数}回叩かれました。\e ev.OnClose: \1\s[10]\0\s[0]さようなら。\- ev.OnGhostChanging: \1\s[10]\0\s[0]さようなら。\e ev.OnMouseDoubleClick: $(inc 叩かれた回数)\1\s[10]\0\s[0]これで${叩かれた回数}回目だ。叩くなバカ。\e System.Callback.OnUnload: $(セーブ) =kis function セーブ $( save "sav.txt" 叩かれた回数; ); function ロード $( clear 叩かれた回数; load sav.txt; ); function 初期化処理 $( ロード; if $[ $(size 叩かれた回数) == 0 ] $(.setstr 叩かれた回数 0); ); 初期化処理; =end ev.OnCacheSuspend: $(セーブ) ev.OnCacheRestore: $(初期化処理) ロード関数の中のclearの処理と、OnCacheSuspendとOnCacheRestoreのイベントに対する処理のエントリ(ev.OnCacheSuspend, ev.OnCacheRestore)が新規に追加したところです。 タイプCのセーブとロードの話はここまで。 タイプDのエントリのセーブとロード†ネットワーク更新時などのゴースト起動中の栞リロード時にエントリの内容を復元する必要があるため、タイプDのエントリも場合によってはセーブとロードをする必要があります。 起動時イベントの中で初期化しよう†というわけで、まずは最も単純な場合である、起動時イベントのうちOnBootにだけ対応する場合について考えます。 System.Callback.OnGET,System.Callback.OnNOTIFY: $(entry ev.${System.Request.ID}) ev.OnBoot: $(初期化処理2)\1\s[10]\0\s[0]起動しました。今まで${叩かれた回数}回叩かれました。\e ev.OnClose: \1\s[10]\0\s[0]さようなら。\- ev.OnGhostChanging: \1\s[10]\0\s[0]さようなら。\e ev.OnMouseDoubleClick: $(inc 叩かれた回数; inc 叩かれた回数.今回;)\1\s[10]\0\s[0]これで${叩かれた回数}回目だ。今回起動時だけで${叩かれた回数.今回}回だ。叩くなバカ。\e System.Callback.OnUnload: $(セーブ) =kis function セーブ $( save "sav.txt" 叩かれた回数 叩かれた回数.今回; ); function ロード $( clear 叩かれた回数; load sav.txt; ); function 初期化処理 $( ロード; if $[ $(size 叩かれた回数) == 0 ] $(.setstr 叩かれた回数 0); ); function 初期化処理2 $( .setstr 叩かれた回数.今回 0; ); 初期化処理; =end ev.OnCacheSuspend: $(セーブ) ev.OnCacheRestore: $(初期化処理) これでゴースト起動時のみ値を初期化し、ゴースト起動中の栞のリロードでは値を初期化しないことができるはずです。 でも、これでもちろんめでたしめでたしではないわけで。 起動系イベント受信での初期化処理は1回だけ†次にOnGhostChangedなどのOnBoot以外の起動時イベントにも反応する場合について考えます。 System.Callback.OnGET,System.Callback.OnNOTIFY: $(entry ev.${System.Request.ID}) # OnBoot受信時の処理 ev.OnBoot: $(初期化処理2;entry evTalk.OnBoot;) # OnBootのイベント応答トーク evTalk.OnBoot: \1\s[10]\0\s[0]起動しました。今まで${叩かれた回数}回叩かれました。\e # OnGhostChanged受信時の処理 ev.OnGhostChanged: $( if $(size evTalk.OnGhostChanged) $( 初期化処理2; entry evTalk.OnGhostChanged; ) ) # OnBootChangedのイベント応答トーク evTalk.OnGhostChanged: \1\s[10]\0\s[0]切り替えられました。今まで${叩かれた回数}回叩かれました。\e # 以下、これまでと同じ ev.OnClose: \1\s[10]\0\s[0]さようなら。\- ev.OnGhostChanging: \1\s[10]\0\s[0]さようなら。\e ev.OnMouseDoubleClick: $(inc 叩かれた回数; inc 叩かれた回数.今回;)\1\s[10]\0\s[0]これで${叩かれた回数}回目だ。今回起動時だけで${叩かれた回数.今回}回だ。叩くなバカ。\e System.Callback.OnUnload: $(セーブ) =kis function セーブ $( save "sav.txt" 叩かれた回数 叩かれた回数.今回; ); function ロード $( clear 叩かれた回数; load sav.txt; ); function 初期化処理 $( ロード; if $[ $(size 叩かれた回数) == 0 ] $(.setstr 叩かれた回数 0); ); function 初期化処理2 $( .setstr 叩かれた回数.今回 0; ); 初期化処理; =end ev.OnCacheSuspend: $(セーブ) ev.OnCacheRestore: $(初期化処理) OnGhostChangedのイベントの受信時の処理の中でイベント応答トークのエントリであるevTalk.OnGhostChangedエントリの単語数を取得し、それが0でなければ(すなわち、一つでも単語があれば)、タイプDのエントリの初期化処理(初期化処理2)を実施し、evTalk.OnGhostChangedを返しています。このとき、本体から更に別の起動系イベントが送られてくることはない(と信頼する)のでタイプDのエントリの初期化処理が重複して実行されることはありません。 というわけで、ここで特別付録、SSP起動時に送られてくる起動時イベント一覧。 イベントの種類としては、OnBoot、OnFirstBoot、OnGhostChanged、OnGhostCalled、OnVanished、OnMateriaExist、OnEmbryoExist、OnNekodorifExistの8種類となります。 ところが。 OnOtherGhostVanishedとOnVanished†上に述べた各種起動時イベントですが、SSP限定において、起動時でもないのにOnVanishedが飛んできてしまうことがあるのです。そのキーとなるのが、OnOtherGhostVanished。 これが問題になるのはOnVanishedにイベントの応答のトークが存在していて、OnOtherGhostVanishedにはイベントの応答のトークが存在しているとき。 じゃあ、どうするか。一番、単純な手としては、OnOtherGhostVanishedのイベントの応答のトークが存在しない場合にOnVanishedのイベント応答のトークを返してあげてOnVanishedが送られて来ないようにすること。どっちにしろ、次にOnVanishedが送られて来るのですから、そんな対策で十分でしょう。 System.Callback.OnGET,System.Callback.OnNOTIFY: $(entry ev.${System.Request.ID}) # OnBoot受信時の処理 ev.OnBoot: $(初期化処理2;entry evTalk.OnBoot;) # OnBootのイベント応答トーク evTalk.OnBoot: \1\s[10]\0\s[0]起動しました。今まで${叩かれた回数}回叩かれました。\e # OnGhostChanged受信時の処理 ev.OnGhostChanged: $( if $(size evTalk.OnGhostChanged) $( 初期化処理2; entry evTalk.OnGhostChanged; ) ) # OnBootChangedのイベント応答トーク evTalk.OnGhostChanged: \1\s[10]\0\s[0]切り替えられました。今まで${叩かれた回数}回叩かれました。\e # OnVanished受信時の処理 ev.OnVanished: $( if $(size evTalk.OnVanished) $( 初期化処理2; entry evTalk.OnVanished; ) ) # OnVanishedのイベント応答トーク evTalk.OnVanished: \1\s[10]\0\s[0]ゴーストが削除されました。今まで${叩かれた回数}回叩かれました。\e # OnOtherGhostVanished受信時の処理 ev.OnOtherGhostVanished: $(entry evTalk.OnOtherGhostVanished ${evTalk.OnVanished}) # OnOtherGhostVanishedのイベント応答トーク evTalk.OnOtherGhostVanished: \1\s[10]\0\s[0]次は私の番か……。\e # 以下、これまでと同じ ev.OnClose: \1\s[10]\0\s[0]さようなら。\- ev.OnGhostChanging: \1\s[10]\0\s[0]さようなら。\e ev.OnMouseDoubleClick: $(inc 叩かれた回数; inc 叩かれた回数.今回;)\1\s[10]\0\s[0]これで${叩かれた回数}回目だ。今回起動時だけで${叩かれた回数.今回}回だ。叩くなバカ。\e System.Callback.OnUnload: $(セーブ) =kis function セーブ $( save "sav.txt" 叩かれた回数 叩かれた回数.今回; ); function ロード $( clear 叩かれた回数; load sav.txt; ); function 初期化処理 $( ロード; if $[ $(size 叩かれた回数) == 0 ] $(.setstr 叩かれた回数 0); ); function 初期化処理2 $( .setstr 叩かれた回数.今回 0; ); 初期化処理; =end ev.OnCacheSuspend: $(セーブ) ev.OnCacheRestore: $(初期化処理) 現在、事実上、起動時イベントが飛んでくるのはゴースト起動時とOnOtherGhostVanishedに何もトークを返さなかった場合のOnVanishedイベントのみです。 もし信用できない場合は……? パス。信用できない場合の更なる対策処理はとんでもなく複雑な処理になるので。 参考リンク†
コメント†
|