矢の悪魔(嘘)
そ、そんなバカな、こんなはずワ…
前々回、前回の記事で自動お着換えMODを修正してキャッキャウフフムフフしていたものの、しばらく遊んでいると急に動作が不安定になってきました。
前々回:
前回:
修正元MODのリンクはこちら。
skyrimspecialedition.2game.info
具体的な事象
・くちゃくちゃに着替えてしまう(バニラの自動最強装備の挙動でもない)
・ホットキーで再着替えさせようとしても着替えてくれない
・同じくホットキーのNPCの各種着替え設定ダイアログがなかなか出てこない
・突然全裸になることがある
ウーム困った。というわけで今回も修正タイムです。
Pupyrusログを見てみる
困ったときのPupyrusログ。Pupyrus萌え
さっそくあからさまなログを発見しました。
---------------------------------
stack:
[UIListMenu (2B000E05)].UIListMenu.ResetMenu() - "UIListMenu.psc" Line 115
[dz_auto_change_quest (FE0B9800)].dz_outfits_mcm_menu.dz_equip_menu() - "dz_outfits_mcm_menu.psc" Line 1951
[dz_auto_change_quest (FE0B9800)].dz_outfits_mcm_menu.dz_set_NPC_outfit_hotkey() - "dz_outfits_mcm_menu.psc" Line 1600
[dz_auto_change_quest (FE0B9800)].dz_outfits_mcm_menu.OnKeyUp() - "dz_outfits_mcm_menu.psc" Line 1429
[01/08/2023 - 02:25:40PM] Error: Array index 333 is out of range (0-127)
stack:
---------------------------------
「Array index 333 is out of range (0-127)」とまぁじつに分かりやすい配列インデックスのアクセスエラーログです。
UIListMenu.ResetMenuのソースを覗いてみると_entryBufferという変数があって、0から_entryBuffer - 1までUIListMenuで保持している各配列変数を初期化する処理が走っているのですが、
その各配列変数が128までしかサイズ確保していないわけですね。で、ログから読み取れるのはIndexが333ってことで明らかにオーバーしているわけです。
コードを見る限り、_entryBufferは初期値が0で、AddEntryItem関数で各種配列にデータが追加されるときに+1されるか、SetPropertyInt関数で直接書き換えられない限りにおいては増えたり変わったりはしません。
SetpropertyInt関数はコールしておらず、AddEntryItemにループで渡している配列も128。どうやったらこんな数字が…?
お着換え処理を多重に走らせてしまっていたのが原因
色々追いかけていったらやらかしていたのはどうやら自分のようでした。
当たり前といえば当たり前なんですが、OnAttachedToCellやOnCellDetachなど複数イベント処理でスクリプトに保持している装備変更スペルをキャストしているので、同じプロパティスペルを同時に複数キャストしているような状況ができててしまっていたんです。OnAttachedToCellやOnCellDetachはセットで呼ばれることが多いです。で、例えばこの二つがほぼ同時に起動すると、ダブルキャストになってしまい同じ処理が2重に実行されてしまいます。
AddEntryItemにデータを追加するループ処理などが2重に実行されてしまうと、単純に考えれば256回ループが回ることになります。結果_entryBufferが128以上の数値にまでインクリメントされてしまい、エラーが発生していたようでした。
フォロワーのインベントリと隠しコンテナの装備を交換している処理も同じように重複することでこんがらがってしまったのでしょう。さらに実行している処理同士がアイテムの取り合いをしてしまうのでおかしくなるのは必然ですね。
各ホットキーの処理が動かなかったのは_entryBufferが128以上の値になってしまうと実装上の都合で必ずエラーになってしまい、かつ初期化もされないのでエラーが発生して一向に機能しなってしまっていたためでした。
ゲームをしばらく遊んで問題が発生したのはタイミングの問題で起きる確率的な問題であったからだと思います。0.5の修正の時にこの問題が発生しないのは上記のインベントリの仕組みがないのとUIExtensionを使っていないからでしょう。
対策:排他チェック処理を追加する
------
bool isInProsessflg ;追加した変数
if isInProsessflg
return
EndIf
isInProsessflg = false
;既存の処理
isInProsessflg = true
------
こんな感じのコードをdz_outfits_location_effect_scriptの各イベント処理に追加しました。「bool isInProsessflg」はこのスクリプト全体で使う変数として宣言し、その下の処理を各イベント処理に追記しました。
着せ替え処理中は何もせずに処理を終了し、そうでない場合はフラグを立て、処理完了後に戻すことで重複処理をふさいでいます。
似たようなものにGotoState文というのもあるみたいですが(このスクリプトにも使われている)、イベント単位での制御処理みたいなので、異なるイベントにまたいでチェックする処理を実現するためこのような方法をとりました。
結果としてうまくいきました。着替え処理も1回か2回流れたとしても1回目の着替え処理が完了した後で流れるのでこんがらがることはなくなりエラーも発生しなくなりました。ただFinalyのような例外処理がないのでそこが心配ですね。フラグの値は必ずfalseに戻しておきたいのですがあるんでしょうかねpupyrusにも例外処理って。
普段からフレームワークに頼っているエセプログラマーのサガ故の凡ミスでしたね…。同時実行性をちゃんと考えよう!
さて、今度こそこれで安定するだろうきっと。いろいろ勉強になったのでこれを機に自分で何かMOD作ってみるのもいい気がしてきました。
おまけ:UIExtensionの修正
直す必要はないんですけど個人的に気になったので。
UIListMenuのResetMenuの修正
今回エラーがでていた関数です。その中に
int i = 0
While i < _entryBuffer
各配列変数初期化処理
EndWhile
という感じの処理がありますがそれを
int i = 0
int tilI = 128
if tilI < _entryBuffer
tilI = _entryBuffer
Endif
While i < tilI
各配列変数初期化処理
EndWhile
に変えます。128を超える値になるのは基本的に想定外の作りのようですので_entryBufferがおかしくなっても大丈夫なように最大ループ回数を128までにする、という感じです。
追記:そもそもスカイリムは128以上の要素をもつ配列を持つことはできないようです。
フリッカーもやってます★