CARDLINK::selectflagオフという障害がぶり返している

BUG20-12-25 17-30-25.ZELの全体図を#5 pagesetupで開いて,TRIBEBOX::GetMajorTribeChainでASSERT_NEVER(upper == major) 「主系列チェーンがループしている」が起きる.このサンプルはZTシステム構成図7.ZELでカード削除を複数回実行→UNDO→REDOを繰り替えして発生したものだ.

障害が発生しているのはTRIBEBOX #7837,先祖#670 familytree,優先#2083 noduleだ.TribeRelocationのステージ【7.9】仮ノード消去と両手に花を適用して多重カードを削減の段で,TOPOLOGY:CheckAbsorbMarriage→TOPOLOGY::CheckAbsorbMarriageを実行しているところだ.この処理は「人名ノードpersonの右枝リストの内容をすべて本ノードの右枝リストに移転する」というもので,多重カード削減の一手段として実行されている.

この関数では系列優先実ノードの切り替えが常套的に起こっているようだが,好ましくない.多重カードの削減手段は複数あるので,あえて系列優先実ノードを対象とする必要はないと考えられる.⇒NAMEBOX:AbsorbMarriageの冒頭で対象ノードがIsPrimaryRealのときはゼロ復帰するようにした.⇒結果,多重カードも発生していないようなのでこれでよいのではないかと思う.

上記サンプルを開いてアプリ終了で,MARGBOX::MargPointOffsetのエラー(IsTooYoungWife())で(!upper || !person)が起きる.⇒フェーズがTOPOLOGICALSORT以下ではゼロ復帰するようにした.この後,NAMEBOX::RestoreExtractBoxで系列枠不在で停止した.この関数はINITIALSTATEではゼロ復帰するようになっているが,CHAOTICSTATE(INITIALIZED)でも同様とする.

ファイルを開き直したとき,CARDLINK::selectflagがオフになっているという障害がぶり返している.これは2020/12/09に確認された問題で,原因はファイルをクローズしたときselectcardlistがクリアされていないため,対策として仮想関数Cleanを導入して完全に解決しいていたはずだったのだが… しかし,現行版の実装は予定していたものより,かなり後退しているように見える.オプションをフィックスする手順で何か失敗していたのではないかと思われるが,ともかく修復を試みることにしよう.まず,現状を見てみることにしよう.

  1. NODULE::Clean 仮想関数で仮想関数cleanを呼び出している
  2. NODULE::clean フラグを2つクリアしているだけ
  3. COUPLING::CloseFamilyBase Clean関数の実行
  4. COUPLINGクラスの共通ヘッダ:COMMONHEADERSHORT
  5. KAKEIZUクラスの共通ヘッダ:COMMONHEADERSHORT
  6. NODULE::doClean 純粋仮想関数
  7. KAKEIZU::doClean cleanの呼び出し
  8. nodule::doClean 固有データ部を0クリア
  9. doCleanの呼び出し元:TRASHCAN::ReuseWaste

Clean仮想関数は「接続されたすべての下流オブジェクトをクリーンアップする」という目的で20201210に導入され,20201216にFIXしている.しかし,現状はまったくそのようなものにはなっていない.原因としては,「Disposeの設置を義務化@20201211」というオプションと交叉してしまったことが考えられる.このオプションは廃止されているが,そのあおりで必要なロジックが消滅してしまったのではないだろうか?⇒復元して動作するようになったが,おそらく以前のロジックは元々動作していなかったのではないかと思う.doCleanは固有データ部をまるごとゼロクリアする関数だが,その中で使っているdataptrとdatalenという2つの関数が仮想関数になっていなかった.

これでほぼ問題なく動作するようになったが,完璧であるとは言えない.doCleanは自クラスの固有データ部を完全にゼロクリアすることができるが,noduleクラスから派生したクラスからさらに派生したクラスの場合には,それぞれの固有データ部は縞模様で接続し,必ずしも連続なものにはなっていない.つまり,それぞれのクラスごとにdoCleanを実行する必要がある.ZTの骨格木を構成するクラスの中ではこのようなクラスには以下がある.

  1. CARDTABLE→ BASETABLE→ ARRAY
  2. GENELIST→ NLIST→ LIST→ DATALIST
  3. LIST→ DATALIST
  4. nlist→ LIST→ DATALIST
  5. TREEVIEW→ Bobject
  6. MARGTABLE→ BASETABLE→ ARRAY

doClean自体は仮想関数なので,nodule::doCleanを呼び出せばよいだけなのだが… ここまでやらなくても実行上はほとんど問題ないと思われるが,一応実装しておこう.上記にはテンプレートクラスなども含まれているが,このようなクラスでもdataptrとdatalenは正しく動作しているだろうか?⇒問題なさそうだ.

ZTシステム構成図7.ZELを開いて,「画面に合わせてズーム」を実行してTREEVIEW::GetAutoZoomRateで停止した.(!WindowSize.cx || !WindowSize.cy)が起きている.ただし,再現しない.⇒先に別ファイル(ZTシステム構成図7.BAD.ZEL)を開いた状態から,開く→ズームで再現するようだ.これは上記の修正に関係あるかもしれない.WindowSizeは「親ウィンドウサイズ,物理単位」なので,アプリ起動時に取得したものだろう.TREEVIEW::doCleanではTREEVIEW:Clearを呼んでおくことにしよう.

ZTシステム構成図7.ZELを開いて,次にZTシステム構成図7.BAD.ZELを開こうとして,Bobject::getdrawsizeでエラーになった.(coordinate() == ABSOLUTE)で停止している.PHASEはINITIALSTATEだ.TREEVIEW::Clearでgetdrawsizeしている.getdrawsizeはBobjectの関数で,相対座標系であることを前提としているので,TREEVIEW::Clearでリセットするのが筋だろう.⇒ダメだ.まだ同じエラー(!WindowSize.cx || !WindowSize.cy)が起きる.⇒doCleanが思ったような動作になっていない.この関数を仮想関数化するというのが間違いなのかもしれない.いや,むしろ,dataptrとdatalenを仮想関数化したのが間違いなのではないか?

固有データ部というのは,そのクラスに固有の位置とサイズを持っているのだから,仮想関数化するというのはまったく意味がない.逆に言えば,個別クラスごとに実装する必要がある.共通ヘッダ部でdataclearという関数を用意することはできるだろう.doCleanは共通ヘッダ部には関数宣言だけを置いて,個別クラスに実装を義務付けるというのがわかり易いのではないか?たとえば,こんな感じだ.

void dataclear(void){ memset(dataptr(), 0, datalen()); }

この定義は現行ではnodule::doCleanの定義とまったく同一だ.doCleanの実装例を示すと以下のようになる.

void MYCLASS::doClean(void){
  dataclear();
  BASECLASS::doClean();
}

既存の仮想関数にcleanというのがあるので,dataclearではなく,datacleanとしておこう.ほとんどの場合は,cleanはdatacleanを呼び出すだけで済むのではないかと思う.方式的にはこれで正しいとは思われるが,数あるnoduleの派生クラスでdoCleanが未定義になってしまう.仮想関数としてのdataPtr, dataLen, dataCleanがあってもよいのではないか?その上で,

nodule::doClean(void){ dataClean(); }

とすればよい.このようにすれば,noduleから直接派生したクラスでは何もしなくて済む.cleanという関数はdoCleanを呼び出すのが正しいのではないだろうか?もちろん,コンストラクタから呼び出される場合は段階的に処理されるので,そのクラスの固有処理をやればよいのだが… いや,これだけではまだ不十分だ.TREEIEW→Bobject→noduleの場合, Bobjectにも固有のdoCleanを実装しないと,dataCleanが実行されてしまう.BASETABLEも固有データ部にtablesizeという不変パラメータを持っている.また,longtableというクラスはかなり融通の利かないクラスでサイズは固定でそのサイズも持っていない…

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA