売春処女プアプアが家庭的アイウエオを行う

完全参照リスト管理とUNDOシステムが共存する体勢を確立することは,参照リスト管理を恒常的なシステムの一部として仕様化するための第一歩だ.「UNDOで参照リスト管理と参照カウントの不一致が発生する」という問題が起きているが,いよいよ大詰めの段階に差し掛かった.これを解決できればZTは「超クリーンなシステム」に変容するための準備が整ったことになる.「超クリーンなシステム」とは「グラスクリーンな世界」であり,ZTが追求する誰にも踏まれていない純白な処女雪に覆われた「スノーホワイトの世界」である.

プログラミングの世界には「オリジナルバージョンに戻る」ことを意味するvirginize(処女化/童貞化)という用語が存在する(わたしも知らなかった).virginizeとは具体的にはバックアップに戻ること,ないし条件コンパイルマクロをオフにして修正前の状態に戻すことに相当するが,基本的には「修正が取り返しが付かない程度に間違った方向に進んでいるとき,それまでの経緯をすべて捨てて振り出しに戻ること」と解釈するのが妥当である.実際,プログラミングでは「これを修正してすべてのバグを取り除くより一から作り直した方が早い」という局面があることは広く認知されている(最初にそれを言い出したのはIBMだ).

いま出ている障害の一因としてTRASHCAN::ReuseWasteで廃棄オブジェクトをリサイクルするときの初期化が不十分というより,まったく実行されていないという問題が出てきた.これを解決するために,新規メモリブロックを使い始めるときと同様に固有データ部を完全にゼロクリアし,すべてのスロットを空にすることにした.これは言ってみればrevirginize(処女膜再生)の施術に相当する.プログラマというのは言ってみれば白雪姫に仕える七人のこびとであったのかもしれない…

ZTシステム構成図7.ZELの全体図を#1 couplingで開き,この基準カードを削除すると,UNDONODE #115359の持っているNODEREFLIST #115361がMARGLINK #311に乗り移るという事象が起きている.これは,カード削除コマンド実行後のUNDOBASE::CommandEndで起きている.何が起きているのか?実に興味深いところだ.

障害はUNDOBASE::SetUndoList→ UNDOBASE::SetUndoList→ ReferenceControlで起きている.UNDONODE #115245の枝2にUNDONODE #115359への参照を設定しようとしているところだ.#115359は参照リストを持っていないので,新たに生成された参照リストがNODEREFLIST #11536だ.このオブジェクトは再生品だ.⇒しかし,NODEREFLISTがリサイクルに回っているということ自体おかしい.NODEREFLISTはENDOFAPPLICATIONフェーズ以外では削除されないことになっているからだ.この時点ではMARGLINK #311はPROLONG状態でNring上に存在する.参照リストは持っていない.

いや,ちょっと間違えていた.SWO(SearchWrongObject)の条件設定でANDとすべきところがORになっていた.⇒障害はやはり,UNDOBASE::UndoCopyで起きている.ここではShadowから実ノードにオブジェクトイメージをコピーしているので,確かにそのようなことは起こり得る.実ノードが参照リストを持たないとしても,イメージにはそれが残っている可能性はある.というか,実際そのような動作になっている.しかし,上記のようにNODEREFLIST #11536はリサイクルされているので,ダブリが生じたということだろう.

問題はNODEREFLISTがゴミ箱に入っているという点だ.#11536はリサイクル時に付番されているので,それ以前の通番は上書きされてしまっている.⇒#2726だったようだ.⇒nodule::~noduleの末尾に「countゼロの参照リストを削除する@20201214」というのが入っている.countゼロの参照リストは保持していても意味がないので始末するという趣旨と思われるが,PROLONGされたノードの場合は除外されなくてはならない.⇒対処した.これですべてのエラーは解消した.つまり,完全参照リスト管理とUNDOシステムの共存は実現された.

ただし,UNDO/REDOを実行したとき,~noduleで参照リストカウントが残っているという現象がある.~noduleの段階ではすべての参照はクリアされていることになっていたはずだ… ⇒現状ではCLEARTABLEフェーズでは参照管理を放棄した状態になっている.CLEARTABLEを含むすべてのフェーズで完全な参照管理を実現するのはまだ先の話だ.

UNDOでは参照カウント不一致のエラーはまったく表示されないのに,REDOではかなりのエラーが出ている.UNDO/REDOには直接関係しない描画要素(NAMEBOX, MARBOXなど)なので放置でも実害はないが… カウント差はほとんど1なので押さえることも可能ではないか?

REDOではMARGLINK:#311とCARDLINK:#689が削除されている.これらからの参照が処理されていないのではないか?⇒どうもそういうことのようだ.⇒この問題を解決するには,やはり参照管理を徹底する以外ない.まず,~noduleで参照リストカウントゼロの場合に(UNDO保全オブジェクト以外では)参照リストを削除するとしていたのを廃止し,参照リストは原則として「死んでも付いて回る」ものとした.

ただし,ゴミ箱に入っている参照リストの親オブジェクトがリサイクルされる場合には削除される.~NODEREFLISTでは参照リストカウントが残っている場合には停止するようにした.この結果,CARDLINK #689[6]→ NAMEBOX #690の参照が残っていることが判明した.

しかし,CARDLINK #689[6]にはNAMEBOX:#243582が入っている.NAMEBOX #690はすでにゴミ箱に入っているが,CARDLINK #689は生きている.というか,UNDOで復活したものと思われる.これは何が悪いのか?CARDLINK #689が削除されたとき,NAMEBOX #690が参照解除されていなかったものと思われる.いや,かなりおかしい.CARDLINK[6]というのは参照ではなく,接続のはずだ.

image

!解けた!一日一個の禁令を破ってここぞとばかり,全量投下の勝負を賭けてみた.有り金を賭ける賭博師の心境だ.チョコレートパワーの威力だね.いや,最初の一個で解けたよ!エラーは全部きれいに消えた!

UNDONODE::UndoCopyでは参照カウントと参照リストカウントの差分を調整するため,復元されたオブジェクトの全参照スロットを対象にReferenceControlで参照リスト登録を実施している.問題はこのとき,「接続」と「参照」を切り分けなくてはならないという点だ.

通常はリンクされているオブジェクトを見れば判定できるのだが,いまの場合,NAMEBOX #690はすでに死亡しているため,接続モードを確認することができない.このような場合にはそのノードの存否をチェックするようにして解決した.このスロットはこの後,UNDOBASE:RestoreShadowでアクティブな現物ノードによって置き換えられている.漏れを防ぐために~noduleでNODEREFLISTの参照リストカウントがゼロになっていることを確認するようにしておこう.

上記と同様の操作(#1削除→UNDO→REDO)でNODEREFLISTのデストラクタで参照リストカウントの残留により停止した.親オブジェクトはすでに死亡しているNAMEBOX #322244でCARDLINK #689からの参照が残っている.#689[6]は空になっている.

CARDLINK[6]は接続スロットだから,参照リストに入っていることがそもそもの間違いだ.⇒どうもかなり難しい話になってきた.NAMEBOX #322244はリサイクルされた再生品という以外は特に問題のないオブジェクトだ.DELETEDはEXISTINGになっているので,弾くこともできない.いや,確かにおかしいところはある.このノードの親はNAMEBOXになっている.確かに,NAMEBOXはリスト構造で保持されているので,親がNAMEBOXというのは当たり前だが,CARDLINK[6]にリンクするためには接続しなくてはならないはずだ.

UndoCopyではそうなっていないのは仕方ないとしても,事後の調整というのがあるはずだ.本当は,その事後調整を実施したあとで,ReferenceControlの調整を行うべきなのではないだろうか?多分それしかないような気はするが,問題はUNDONODE::UndoCopyを呼び出しているUndoRestoreはUNDOSYSTEMの関数だという点だ.つまり,アプリ依存コードなので,UNDOBASEでできる範囲がより狭まってしまうということになる.⇒UNDONODE::SetNodeRefListという関数を作ってUndoRestoreから呼び出すようにしてみよう.

いや,もっといい場所がある.CountupReferenceという関数がある.ここでは参照カウントのインクリメントを実施している.それと同時にReferenceControlをやればよい.というか,ここではSansyoの取り直しを実施するというのが一番適切なのではないか?⇒完璧だ!傷跡も残らないくらいの完璧なサージェリィ,これ以上付け加えるものも引き去るものもないという感じになってきた.バックアップを取っておこう.

以下のブロックを選択して最初のカードを削除してエラーが発生した

image

@4 treeviewを削除で再現できる.PAIRBOX::CalcPairBoxでエラーが起きている.「NOCOMMONPAIRノード対は端点共有束で唯一でなくてはならない@20180915」というエラーだ.NOCOMMONPAIRは「終点がgoodsonのノード対は最大区間を除き端点共有不可@2018-09-04」とされている.このトラブルは既出だが,もう少し整理する必要がある.エラーを無視して描画は可能.出口検査はパスしているので,ここでは停止しないようにしておこう.

▲NOCOMMONPAIRノード対の論理を整理する.端点共有ノード対の場合は,NOCOMMONPAIRをつねにリスト先頭に配置するというのがわかり易いのではないか?

複数回のカード削除の後,UNDOで「フェーズのイレギュラーな遷移」が起きた.TOPOLOGICALSORTからINITIALIZEDに遷移しようとしている.⇒COUPLING::TopologicalSortの冒頭でSetPhase(TOPOLOGICALSORT)の後にTREEVIEW::SetDispParmを実行している.これはAUTOCHANNELのときの既定チャンネル数を設定するためだが,SetDispParmの中でフェーズの切り替えが発生している.これを避けるために,この操作はRESTOREDCIRCULATIONの前に移動する.

同様の動作テスト中,Bobject::setparentでエラーを表示しようとして例外が発生した.bprintfの引数に修正ミスがあった.

仮修正をフィックスしておこう.3件ある.

  1. UndoCopyでSansyo動作を実行@20201222 → 廃止
  2. オブジェクト削除で参照カウントゼロを確認@20201223 2箇所
  3. Bobject:initializeでCleanSlotする@20201223 → 廃止

仮修正17箇所,#ifdef ,#ifndefも一掃した.

UNDOで参照リスト管理と参照カウントの不一致が発生する

UNDOで参照リスト管理と参照カウントの不一致が発生する.かなり難しい.これが解けないと参照リスト管理を導入する意味も半減してしまう,正念場だ.参照リストのバックアップが必要なことは明らかだ.しかし,参照リストを単独でバックアップしても意味がない.リスト全体が保全されなくてはならない.だとすれば,むしろ参照リスト自体を「凍結」してしまうのが早いのではないか?

しかし,そのようなことが可能だろうか?⇒UNDONODE::UndoCopyでは,最初にそのオブジェクトの参照をすべて参照解除しているが,Shadowイメージをコピーした後で,記載された参照リンクを参照リストに登録するという動作を追加してみた.

これにより,参照リストと参照カウントの不一致は当初の34件から6件まで減少した.これら不一致のうち,4件はUndoProcess中に発生し,残り2件はその後に実施されるCOUPLING::TopologicalSortで発生している.後の2件はNAMEBOXとMARGBOXが関わるもので,これらはUNDO動作とは直接関わりがないのではないかと思われる.従って,UNDOに関わる不一致は最初の4件だけと言ってよいと思う.

障害が起きているのは,MARGLINK:#311,CARDLINK #689,UNDONODE #115324,UNDONODE #115359だが,UNDONODEの2件はかなり疑問がある.UNDONODE #115324の参照リストは72, 参照カウント2となっているが,UNDONODEが外部から72も参照されるということは考え難い.というか,UNDONODEは参照フリーだったのではなかったか?⇒UNDONODE自体はチェーンを構成するためのスロットを2つ持っているが,実ノードやShadowへのリンクはべた参照だ.もちろん,外部から72も参照されるということはあり得ない.

TRASHCAN::ReuseWasteはオブジェクトを再利用するとき,まったくクレンジングということをやっていないようだ.2020/12/10に実装したはずのClean関数の仕掛けが消えてしまっている.どこかで修正のフィックスに失敗しているのではないだろうか?再利用可能なオブジェクトがない場合にはfreeblock::getmemでメモリブロックを確保した後,ゼロクリアしているので問題ないが… doCleanは使えるので,まずこれで固有データ部をクリアし,CleanSlotでスロットを全クリアしておこう.

CleanSlotはそのオブジェクトのクラスの分しかクリアしていない.CleanSlotは主にデストラクタで使われるのでこの方式で問題ないが,スロットゼロや拡張スロットまで含めた完全なCleanSlotが必要だ.⇒~NODULEではそれと同等のことをやっている.⇒void NODULE:CleanSlot(int topslot)というのを作った.いや,この名前ではvoid CleanSlot(bool)とかち合ってしまう.⇒CleanAllSlotとしておこう.⇒完璧にrevirginizeできた!ただし,NODULE::doCleanは純粋仮想関数としているので,nodule::doCleanを明示的に呼ぶ必要がある.

この修正でUNDONODEの参照リストが変化するものと思っていたが,予定外のNAMEBOXとMARGBOXが消えて,UNDONODEの2件が残ってしまった.これはかなりおかしいので,何かしらのバグと思われる.UNDONODEが72も参照を持っているということはおよそ考えられない.何かの間違いだと思う.⇒UNDONODEと別のオブジェクトが同じ参照リストを持っている.⇒ここまでは想定通りだが,偽の参照リストを持っているのは,MARGLINK:#311とCARDLINK #689の方だった.なんでこんなことが起きるのだろう?考え難い.

どこかですり替えが起きているのだと思うが… MARGLINK:#311とCARDLINK #689はどちらも<再生>されている.つまり,一度削除されたオブジェクトを復活させたものだ.従って,<再生>の手順に誤りがあることになる.これをやっているのは,UNDOBASE:RestoreShadowだ.⇒いや,呼ばれているのはUNDOSYSTEM:RestoreShadowだ.RestoreShadowに入るときにはMARGLINKは参照リストを持っていない.まだUNDONODEの参照リストカウントも2しかない.どうもUNDOでデータを保全した時点で誤りが発生しているようだ.カード削除コマンド出口のUNDOSYSTEM::CommandEndだ.

Nringの残留オブジェクトが9件発生

BUG20-12-20 00-37-44.ZELの全体図を#229 式部卿宮の北の方で開いてアプリ終了して,Nringの残留オブジェクトが9件発生している.いずれもfreeblockだ.⇒freeblock::ReleaseFreeBlockがどこでも実行されていない.EraseFamilyTreeで実行していたはずなのだが… 条件コンパイルをフィックスする時点で消してしまったのだろうか?2020-12-15のバックアップを見ると,#ifdef 廃止@20201213 となっている.しかし,これは間違っている.

freeblock自体はnoduleオブジェクトの一種だから,ゴミ箱に入っていればゴミ箱の廃棄で削除されるが,アクティブなオブジェクトはCOULPINGとは別建てで管理されている.freeblockはリスト管理されていて,リスト先頭はグローバル変数のtopblockにある.この管理方式はあまりよくないと思う.ゴミ箱がCANというオブジェクトで管理されているように,freeblock管理の代表オブジェクトを定めて,CANと同格の位置に設置するようにした方がよい.しかし,その前にまず,Nringの残留の問題を片付けておこう.

freeblockは固有データ部にフリーメモリブロックを持つオブジェクトで,削除された場合は通常のオブジェクトと同様にゴミ箱に入るのでリサイクルは可能だが… Nringにどんなオブジェクトが残っているのか調べてみよう.以下のfreeblockが残留している.#879から連番で#887までの9個で,すべて個人記録ページだ.このアドレスはCARDLINK:cardbase.notepageに格納されている.CARDBASEに格納されている以下のリンクはすべてfreeblockで取得されたものと思われる.

CBitmap *cBitmap;            // MFCビットマップ
BITMAPINFO *bitmapinfo;        // カード画像DIビットマップ
char *notepage;                 // ノートページデータへのポインタ

これらのリンクはベタ参照であり,noduleオブジェクトとして管理されている訳ではないので,オブジェクトが削除されても自動的には削除されないから,どこかで明示的に削除する必要がある.画像イメージや記録ページデータなどはファイルに属するものだから,ファイルクローズで廃棄でよいのではないだろうか?

いや,少し違うのではないか?CARDBASEに載っているものは,CARDBASEのデストラクタで始末するべきだ.この意味では,@20201213 の決定の方が正しいような気がする.⇒現行では~CARDBASEでは何もしていない… ⇒ここでcBitmap,bitmapinfo,notepageを削除するようにしてみよう.

どうもこのCARDBASEのnotepage管理はあまり整っていないように思われる.notesizeがCARDBASEの下のCARDATAに入っている.しかも,ここにはもう一つ別のCBitmap*cbitmapというリンクまである.どうなっているのだろう?⇒CARDATAは外部とのインタフェース用,CARDBASEはデータ管理用という差があるのかもしれないが… メモリ管理ということは念頭になかったように思われる.

freeblock::delmem_((void**)&notepage, carddata.notesize);   

上記で一応メモリからの解放はできているようだが,「カウント不整合」がまた起きている.上の文が正しいかどうかも検証を要する.notepageというのはCARDLINK上のメンバー変数であり,メモリブロックを管理するfreeblockのアドレスが正しく引けているかどうかはチェックする必要がある.⇒関数の使い方を間違っている.bool freeblock:_delmem_(void **mptr)を使う必要がある.この関数を使ってカウント不整合も消えた.この関数ではサイズを指定する必要がないので,CARDBASEでサイズを押さえていないことも説明できる.

Nringの残留も解消した.CBitmap *cBitmapも同じ方法で削除しようとしたが,エラーになった.freeblock::ReleaseFreeBlockではカード写真イメージを解放するのに,DeleteBitmapImageという関数を使っている.CBitmapでは「ビットマップを破棄」するという操作が必要だ.

cBitmap->DeleteObject(); // ビットマップを破棄する
delete cBitmap;

しかし,delete cBitmapで削除できるということは,このオブジェクトはfreeblockを使っていないのだろうか?⇒確かに,new CBitmap; で生成されているようだ.CARDBASEが保持するオブジェクトに関してはこれでクリアできた.RTFを使っているところはカードの記録ページの他にもあったはずだ.タイトル履歴とか… ⇒調べてみよう.freeblockのMMTYPEには以下がある.

  1. MM_NODULE,        未使用
  2. MM_NOTEPAGE,    カード記録ページ CARDLINK::cardbase.notepage
  3. MM_CARDIMAGE,    カード写真イメージ CARDLINK::cardbase.bitmapinfo
  4. MM_CHUNKFILE,    記録ページ読み込み用バッファ FAMILYTREE::chunk.FILEBUFF
  5. MM_BUNSFILE,    データ読み込み用バッファ BUNSFILE::bunsbuff
  6. MM_QUICKDB,     カードテーブル項目名 QUICKDB::itemtable[maxitem].itemname
  7. MM_TEMPLATE,  廃止
  8. MM_TITLEINFO    タイトル履歴 TITLELINK::TitleInfo.Rtfbuf

タイトル履歴はTITLELINK::Disposeで削除しているので問題ない.CHUNKFILE chunkを持っているのはFAMILYTREEではなくCOUPLINGだ.chunk.FILEBUFFの解放は~CHUNKで実施すべきだろう.⇒いや,やっている.~BUNSFILEでもbunsbuffの解放は実施されている.~QUICKDBにも同様に措置されている.一応これですべて押さえたということになるだろう.MM_TEMPLATEは完全にソースからパージしてよいと思う.⇒いや,REVISIONの古いデータを読み込む場合に必要になるので残しておくしかない.

バックアップも取ったので,最近の修正をフィックスしておこう.以下の3件がある.

  1. Nodule:numberを廃止する@20201218 24箇所
  2. COUPLING:TemplateBuffを廃止する@20201219 13箇所
  3. 優先実ノードは本人・配偶者を問わない@20201221 2箇所

▲ZTシステム構成図7.ZELの全体図を#5 pagesetupで開いて,基準ノードを削除→UNDOで参照リストと参照カウントの不一致が発生する.⇒MARGLINK:#570のスロット20からの参照が参照リストに登録されていない.つまり,Sansyoを呼び出さない参照の書き換えが実行されているように思われる.

UNDONODE:UndoCopyでは冒頭ですべての参照をリセットしているが,その後の動作は単純な上書きコピーになっている.コピーではなくSansyoの実動作で書き込まなくてはならない.⇒一応実装してみたが… これはかなり難しそうだ.

昨日の仮修正を破棄してバックアップに戻る

昨日の仮修正は一旦破棄してバックアップに戻ろう.何が問題なのか?一番の問題は先帝系列で明石中宮が展開されていないという点だろう.明石中宮は※3系列で外部との接点を持つ唯一のノードなので,このノード以外に系列優先ノードとなり得るカードは存在しない.このノードがリジェクトされているのは,対向する有効な実ノードが存在しないためだ.対向する有効な実ノードとは,すなわち先帝系列で展開されるべき明石中宮の人名枠に他ならない.なぜそういう流れになっているのか,追跡してみよう.

明石中宮の義母である紫の上の父式部卿宮は結婚を3つ持っている.単身婚,式部卿宮の北の方,紫の上の母だ.この式部卿宮+紫の上の母の結婚が展開されていない.この結婚枠の所属系列が未定であるためだ.これはMARGBOX::IsPrimeboxOrNoに「基準ノードの配偶者が外部に有配偶者婚を持っている場合には,その結婚は基準ノードの配偶者の配偶者側で展開される@20190128」というルールがあるためだ.このサンプルの基準ノードは#229 式部卿宮の北の方で,式部卿宮はその配偶者に当たるため,弾かれている.この規則を暫定的に止めて描画できた!

image

確かにかなり難しい図面であるかもしれない… しかし,このような反例があるために,このルールを破棄ないし緩和するというのはよい方向であるとは思えない.IsPrimeboxOrNoという関数はある結婚を夫と妻のどちらのポジションで展開するか?という疑問に応えるための関数で,基本にはどちらを選択しても描画は可能となっているという前提がある.この前提が崩れたのは,「基準ノードの配偶者が外部に有配偶者婚を持っている場合には,その結婚は基準ノードの配偶者の配偶者側で展開される」というルールが厳し過ぎるためというよりは,むしろ「もっと優先度の高いルールが存在する」ということだろう.

つまり,その結婚がある位置で展開されないと描画不能となるようなものが存在するということではないのか?しかし,それをルール化するのは容易くはない.系列枠リストの最初の7系列を表示してみた.

  1. #915 先祖=#701 式部卿宮の北の方(0) 優先=#701 式部卿宮の北の方(0) 始系列 type=始系列
  2. #917 先祖=#509 先帝(0) 優先=#1093 式部卿宮(1)→#701 式部卿宮の北の方(0) →主系列#915:式部卿宮の北の方 type=BTW左接続関係
  3. #919 先祖=#557 ※6(0) 優先=#1101 先帝の后宮(1)→#511 先帝の后宮(0) →主系列#917:先帝 type=婚姻関係
  4. #921 先祖=#405 ※3(0) 優先=#1102 明石中宮(1)→#339 明石中宮(0) →主系列#917:先帝 type=親元関係
  5. #923 先祖=#603 中務宮(0) 優先=#1104 明石の尼君(1)→#521 明石の尼君(0) →主系列#921:※3 type=婚姻関係
  6. #925 先祖=#637 右大臣(明石)(0) 優先=#1110 今上(1)→#339 明石中宮(0) →主系列#917:先帝 type=BTW左接続関係
  7. #927 先祖=#589 按察の大納言(若紫)(0) 優先=#1112 紫の上の母(1)→#535 紫の上の母(0) →主系列#917:先帝 type=婚姻関係

1番目の式部卿宮の北の方系列は始系列だから,無条件で描画される.2番目の先帝系列の優先仮ノードは式部卿宮であり,実ノードは基準ノードの配偶者だから,この系列自体には何の問題もない.もちろん,式部卿宮+紫の上の母の結婚を他系列に譲ったとしても,問題なく描画できる.問題は※3系列の優先ノード候補が明石中宮しかないという点にある.ここで躓くのは,式部卿宮+紫の上の母の結婚の移転先が,※3系列より系列順位的に後方にあるという点にある.このようなことは予想されていなかったので,この場で対処することは不可能だ.

解決策としては,上記で実行したように何らかの条件を提示してこの結婚が紫の上の母側ではなく,式部卿宮側で展開する必要があるということを示す必要がある.これを決定するためには少なくとも次のことが言えなくてはならない.

  1. 系列優先ノード候補を一つしか持たない系列Kが存在する
  2. この優先ノードの実ノードを(子どもとして)含む結婚はこの系列Kに先行して展開されなくてはならない
  3. この結婚を系列Kより後方の系列に移籍してはならない

判定1.もあまり簡単ではないが,2., 3. も分かり易いものではない.通常始系列に含まれる結婚は最初に展開され,基準ノードの配偶者の系列はそれに準ずると考えられるから,通常の手順であれば,この結婚が「後方」に移動することは避けられない.仮にこの手順に入るまでに,すでに系列枠リストは生成され,系列順位が決定しているとしよう.また,系列優先ノードも選定済みであるとする.実際,現行論理ではそうなっていると言ってよいと思われる.とすれば,上の条件文は以下のように書き換えることが可能かもしれない.

「IsPrimeboxOrNoの検査対象の結婚に系列優先ノード(候補)が(子どもとして)含まれる場合は本人位置で展開する」

例外としては,その配偶者の系列が「系列優先ノードの系列」より先行する系列である場合は移動を認めるとしてもよい.以上をまとめると,

「IsPrimeboxOrNoの検査対象の結婚に系列優先ノード候補が子どもとして含まれる場合は本人位置で展開する ただし,配偶者系列が系列優先ノード候補系列より上位にあるときはその限りではない」

となる.しかし,これは絵に描いた餅で終わる可能性もある.まだ,この時点では結婚枠はおろか,人名枠でさえどの系列に属するかは確定していないと考えられるからだ.系列枠はすでに生成済みであるとすれば,系列枠のPrimaryには系列優先ノードが入っているはずだから,系列枠リストを走査すれば確認することは可能だが,ややコスト高になる.CARDLINK自体にその情報(優先ノード候補であるという情報)が入っていれば探索コストはかなり削減できるのだが…

CARDLINKには先祖ノードへのリンクを格納するancestryというスロットがあるが,これは必ずしも所属系列とは一致しない.CARDLINKはNAMEBOXのリストを持っているので,このリストをチェックすることはできる.しかし,たとえば,式部卿宮+紫の上の母の結婚を検査するタイミングでは明石中宮のNAMEBOXはまだ一つも展開されていないのではないだろうか?実装してみよう.

いや,問題はもう一段難しい.IsPrimeboxOrNoの対象結婚枠は式部卿宮+紫の上の母だが,明石中宮はこの結婚枠には入っていない.

式部卿宮+紫の上の母→紫の上→明石中宮

という関係だ.そこまでここで追求するのは無理なのではないか?というより,もし,それをやるとすれば系列優先ノードから先祖ノードまでの経路上に存在するすべての結婚が検査対象となってしまう.実際,そこまでやらないと解決にはならないのではないかと思う.かなり厄介な話に発展してしまった.つまり,あるノードを系列優先ノードとして予約するためにはそのノードから先祖ノードまでのすべての結婚枠を予約する必要があるということになる.⇒この方向はかなり険しいが,もしそれしか道がないのだとしたらそうするしかない.

もう一つ別のより簡便な方式も考えられる.現行の論理で一度GoDownStreamを通した後,MakeUpTreeで再挑戦の機会を与えるという方式だ.これなら,確定できる系列はすべて連結完了しているはずだから,系列順位とは無関係に接続点を探すことができる.

現行方式でまさにそれをやっているのではないだろうか?TRIBEBOX:GetAlternativePrimeNodeという関数はそれを行うために用意されたのではないのか?であるとすれば,この関数をより洗練させればよいというだけの話になる.それは実現可能な話ではないかと思う.大分回り道してしまったが,ようやくスタート地点まで戻ってきた.

一応GetAlternativePrimeNodeで(不十分ながら)接続先を見つけることはできるようになったが,TRIBEBOX::DecidePrimaryNodeが通らない.この関数は自分自身を再帰的に呼び出しているので無限ループに陥ってしまう.結局まだ問題は解決していないということのようだ.GetRealnodeでは「空でない可視の人名枠オブジェクト」を求めているため,按察の大納言系列の#1112 明石中宮(1)が弾かれてしまっている.しかし,所属系列枠が決まっているのに不可視というのはどういうことだろう?⇒GoDownStreamで所属系列を設定するタイミングで可視化してみたが,状況は変わらない.

どこかで落とされているようだ.CARDLINK::NameBoxを実行すると初期化される.しかし,この動作はかなりおかしい.対象となっているのは明石中宮ではなく,北山の尼君だ.

CARDLINK::NameBox→ NAMEBOX::makeProxy→ initializeで生成された北山の尼君の仮ノードを隠蔽リストにつなぎ込んでいる.この措置は「挿入」として実施されるため,挿入位置のノードが操作されて対象ノードの後ろに移動し,このとき不可視設定されている.隠蔽リストは原則として不可視となっているため,このような動作になっているのだろう.GoDownStreamでは人名枠などの描画要素は生成されてはいるが,まだ可視のYリストには登録されず,MAKEUPTREEで初めて繋ぎ込まれるという動作になっている.このため,MAKEUPTREEで実施しているGetAlternativePrimeNodeは再チャレンジになっていない.

見てきたように,従来手順では優先ノードが決定できない系列が発生することは避けられない.これを予防するための措置を導入することは不可能ではないが,高度に難解なものになることは避けられず,時間・空間効率に及ぼす影響も無視できない.この意味では系列木の生成を段階化して,後段で最終解決するというのは現実的であると思われる.中間段階に当たるGODOWNSTREAMでは描画要素はすべて隠蔽リスト上にあり,かつ隠蔽リスト上にあるノードはつねに不可視とするというルールになっており,これを直ちに変更するというのも現実的ではない.

TRIBEBOX::GetRealnodeはMAKEUPTREEに入ってから適用される関数だが,MAKEUPTREEが完了するまでは描画要素がすべてYリストに繋がって可視化した状態にならないため,この関数で認める系列優先実ノードの条件を緩和するしかない.実際問題として,人名リンクが有効であるような人名枠は基本的に合法と考えられるから,ここでは可視/不可視を問わないことにする.これで描画まで進むことができた.ただし,一つだけ問題がある.

TRIBEBOX::GetAlternativePrimeNodeの中段,(T2 == major)のとき(marglink)で停止する.TRIBEBOX::GetAlternativePrimeNodeで系列枠#927 先祖:#588 按察の大納言 (若紫)の優先ノードを探すメインループを抜けたところで誤動作している.婚姻関係では見つけられず,逆婚姻関係を探して見つけているが,相手方が空であるために間違った分岐に進んでいる.相手方を見つける論理は以下のように単純なものだが,単身婚の場合があるということが見落とされていた.

if (wife == marg2->OTTO) partner = marg2->TUMA;
else partner = marg2->OTTO;

逆婚姻関係の場合には従系列で配偶者となっているのだから,主系列では本人となる可能性が高いと考えられるが,実際にどちらに割り当てられるかは必ずしも分明ではない.ある結婚の夫と妻のどちらを本人としどちらを配偶者とするかということは,その結婚枠をどの系列ないし,どの「家」に配置するかを決定することに等しい.その最終決定を行っているのがMAKEUPTREEフェーズなので,このプロセスが完了するまでは確定的なことは言えない.しかし,系列優先ノードの決定はそれらが確定する以前になされなくてはならないという矛盾がある.

系列優先仮ノード→実ノードという関係はある意味でかなりルーズな関係であり,必要なことは,①2つの系列関係が接点を持つこと,②同世代の2つの人名ノードの配置によって系列枠の垂直位置関係を決定すること,の2つが満たされれば十分であると考えられる.従って,その関係を決定するためにはそのノードが本人ノードであるか配偶者であるかまでは問う必要がないと(基本的には)考えられる.この立場からすれば,婚姻関係の実ノードはつねに(従系列における)本人ノード,逆婚姻関係の場合は(従系列における)配偶者ノードとしてよいのではないか?このルールなら単身婚の場合も問題なく処理できる(はずだ).

image

一応これでできたようだ.最初の図と今回の図では微妙に細部が異なる.最初の図では紫の上の母が夫の式部卿宮との接触距離まで接近しているのに対し,後の図ではかなり開いたものになっている.これは「基準ノードの配偶者が外部に有配偶者婚を持っている場合には,その結婚は基準ノードの配偶者の配偶者側で展開される」というルールを文字通り実現したもので,基準ノードの式部卿宮の北の方の眼からすれば,この方がずっと好ましいことは間違いないだろう.明石中宮も2番目の図ではかなり遠いところに配置されているが,式部卿宮の北の方の距離感からすれば,こんな感じなのではないだろうか?

このサンプルではTRIBEBOX::GetAlternativePrimeNodeの出動が必要となるケースは1件しか起きていない.それも,※3系列の明石中宮ではなく,按察の大納言(若紫)系列の優先ノードが紫の上の母から式部卿宮に切り替わるという動きだ.これはかなり予想外の結末だが,この障害の真因が,MARGBOX::IsPrimeboxOrNoで式部卿宮+紫の上の母の結婚を紫の上の母系列で展開するように決定したことにあることからすれば,当然の成り行きとも言える.この修正は,結婚枠展開の自由度を残したという点では「※3系列の明石中宮」にこだわるというアプローチより優れていると言ってよいと思う.

※3系列はどうなったかと言えば,明石中宮を優先ノードとして,按察の大納言系列の従系列になっている.しかし,この方式が「完璧」であると言えるかと言えば,多少の不安は残る.系列の従属関係が当初の線形順序を崩しているため,場合によっては,本来連結でなければならない系統図が複数のブロックに分離してしまう可能性はゼロではない.系列優先ノードの選択というのは,かなり難易度の高い部分だったような気もするが,ようやくある水準に達したのではないかという気もする.

TRIBEBOX::decidePrimaryNodeで系列優先ノード決定不能が起きる

▲BUG20-12-20 00-37-44.ZELを開いて,TRIBEBOX:decidePrimaryNodeで系列優先ノード決定不能が起きる.障害が起きているのは[系列枠]: #921 先祖=#405 ※3で優先ノード候補は#338 @4明石中宮だが,TRIBEBOX::GetRealnodeで有効な実ノードが選択できないため,リジェクトされている.

かなり難しい問題だ.ある意味で現行方式が破綻していると言っても過言ではない.ZTは当初実親子関係しか扱っていなかったが,養親子関係が導入された後でも基本的な構成規則である「すべての人名はいずれかの系列に所属するという」という原則が踏襲されてきた.養親子関係をサポートするためにはこの原則を緩和するしかないのだが,原則それ自体の見直しが行われることがなかったため,不良が潜伏していつか露見するという宿命を負っていたのではないだろうか?

現行ではたとえば,CARDLINK::ancetryには所属系列の先祖ノードへの参照が格納されるようになっている.これは,つまり,ある系列に所属する人名は他の系列に所属することはできないことを暗に示している.実際には,養親子関係を含む系図を扱っている以上,運用的にそれをカバーしてきたものと思われるが,原理的に整合していないのでどこかで破綻することは避けられない.どこをどう改訂ないし改善すればよいのか?TOPOLOGY::topologicalSortをフェーズで区分すると,

  1. TOPOLOGICALSORT 系統並び替え開始
  2. CLEARTABL テーブル初期化
  3. EXTRACTPARTIAL 部分図ノードの抽出
  4. SETBASECARD 基準ノード設定
  5. KINSHIPDEGREE 予備検定(親等計算)
  6. DECOMPOSITION 本検定(系列分解)
  7. PHYLETICTREE 系列枠の生成と世代計算
  8. GODOWNSTREAM 系図木生成と展開:グループ゚を設定
  9. MAKEUPTREE 系図木構築:Yリストへの繋ぎ込み
  10. SETSIBLINGS 拡張子ども枠の設定
  11. SETRELATIVE 系図木相対領域計算中
  12. BUILDGENELIST 基本/系列世代枠リストの生成
  13. SOLVECOLLISION 系列内同世代結婚枠の衝突解消処理
  14. BUILDCENTERLINE 血統軸線図の構築
  15. TRIBERELOCATION 系列枠再配置
  16. SAMEGENEMARRIAGE 重婚同類グラフを生成
  17. AFTERMARGSAMEGENE 重婚同類グラフの後処理
  18. SYMMETRICGEOMETRY シンメトリ婚を展開する
  19. MAINEXPERIMENT 系列間衝突回避処理
  20. HORIZONTALORDER 先祖並び自動オフ時の系列水平配置
  21. FOLDINGCHANNELS チャンネル数自動のときのチャンネル整理
  22. HEAPTRIBEBOX 系列枠包括領域集積
  23. MAKEABSOLUTE 絶対座標系変換
  24. SETTITLEBOX 系図外枠矩形領域を最終決定
  25. DRAWSTAGE 系図木描画準備完了

のようになる.ただし,最後のDRAWSTAGEはCOUPLING:TopologicalSortで設定される.いま問題になっているところは(6)DECOMPOSITION から(9)MAKEUPTREEまでの処理と考えられるので,そこだけを抽出すると,

  1. DECOMPOSITION 本検定(系列分解)
  2. PHYLETICTREE 系列枠の生成と世代計算
  3. GODOWNSTREAM 系図木生成と展開:グループ゚を設定
  4. MAKEUPTREE 系図木構築:Yリストへの繋ぎ込み

のようになる.(1)DECOMPOSITIONではすべてのノード(人名リンク)に先祖ノードを割り当てて,排他的な系列に分解する.(2)では系列枠オブジェクトを生成して,系列枠リストを構成する.(3)GODOWNSTREAMでは各系列の先祖ノードからの下流検定を実施して,人名枠オブジェクト(仮ノード)を生成し,系列の最小・最大世代番号を決定する,(4)MAKEUPTREEでは系図木(描画リスト)を生成し,すべての描画要素の初期化を行う.エラーが出ているのは(4)のMAKEUPTREEだ.

フェーズの整理を行っていて,エラーが出た.上のリストで18. SYMMETRICGEOMETRY シンメトリ婚を展開するを実行している場所は実際には,21. FOLDINGCHANNELS チャンネル数自動のときのチャンネル整理の直前に移動しているので,SYMMETRICGEOMETRYの値を変更したところ,フローはまったく変化していないも関わらず,エラーが出るようになった.COUPLING::TopologicalSortの出口検査で(SymmetryList && SymmetryList->len() != SYM) で停止している.

SYMMETRICGEOMETRYの値が変化したことより,TOPOLOGY:BuildSymmetryListの直前でPHASEにSYMMETRICGEOMETRYをセットしていることが障害の原因となっている.BuildSymmetryListの中でフェーズによって処理が分岐するようになっているのではないか?⇒いや,BuildSymmetryListの後でCheckMultiCardsを実行してもエラーにはならない.BuildSymmetryListでフェーズをセットしなければエラーは回避できるが,なぜこのようなエラーが出るのかについては追求する必要がある.⇒原因は大体つかめた.

3つパターンがある.①オリジナルパターン→エラーなし,②BuildSymmetryList値を変更のみ→エラーなし③SYMMETRICGEOMETRY値を変更,BuildSymmetryList前でフェーズ設定→エラー発生.エラーはTOPOLOGY::SymmetryListのカウントと系列枠にセットされたカウントの合計が一致しないというものだが,実際にはTOPOLOGY::SymmetryListのカウントゼロで系列枠のシンメトリ婚カウント1という状況だ.

①のオリジナルコードの場合には,BuildSymmetryListの実行時のフェーズがSYMMETRICGEOMETRYと異なるため,MARGBOX:getSymmetricLeftBoxで厳格な条件が要求されるためシンメトリ婚不成立となっている.また,②の場合は,BuildSymmetryList実行時のフェーズ番号がSYMMETRICGEOMETRYよりも小さくて無動作で抜けているのでやはりシンメトリ婚不成立となり,不一致は生じない.③の場合はBuildSymmetryListの実行時フェーズがSYMMETRICGEOMETRYと一致するため条件が緩和されて,シンメトリ婚が成立,おそらく後に破棄されたためカウント不一致になるのだろう.

構成規則が時と場合によって厳しくなったり緩和されたりするという仕様はかなり問題があるので統一した方がよいと考えるが,カウント不一致というのはまた別の問題なので,まず先にこちらをクリアしておこう.⇒対処した.⇒今度は系列枠のSymmetryCountの方が少なくなってしまった.Ancesry.zelを開いて不一致が発生する.SymmetryListというのはMARGBOXを要素とするリストだ.シンメトリ婚に設定されると,その結婚枠はSYMMETRICMARGという属性を持つようになる.

SYMMETRICMARGがセットされるのはBuildSymmetryListの中だけで,このときは系列枠のSymmetryCountも同時にインクリメントされている.また,この値がリセットされるのはMARGBOX:AbandonSymmetricの中だけで,今回の修正はこの関数に関わるものだ.SymmetryCountのデクリメントはresetghostbits(SYMMETRICMARG)で実行される.resetghostbitsを呼ばずに直接リストからパージして,SymmetryCountをデクリメントしている箇所もあったが,すべてresetghostbitsで操作するように修正した.

ただし,resetghostbitsの中の論理にもミスがあった.SymmetryList->Removeの戻り値を見てカウントダウンしていた.LIST::Removeは現在リスト長を返すような仕様になっている.Ancestry.zelでシンメトリが崩れているという現象は大分前から出ているが,落ち着いてから調べることにする.最初の問題に戻ろう.

▲MARGBOX:getSymmetricLeftBoxでSYMMETRICGEOMETRYのときに限ってシンメトリ婚成立の条件を緩和している

▲Ancestry.zelでシンメトリが崩れている

DECOMPOSITIONではまだ描画オブジェクトは生成されていないので,人名リンクベースで人名をグループ分けしたものを系列と称しているに過ぎない.PHYLETICTREEでは先祖ノードを頂点とする系列枠オブジェクトが具体的に生成される.次のGODOWNSTREAMでは先祖ノードの下流系を展開してメンバーとその配偶者の人名枠を生成し,その所属を決定する.最後のMAKEUPTREEでは系列優先ノードを(最終)決定し,系列の相互位置関係を確定する.

見た限りではどうも三番目のGODOWNSTREAMに問題があるように思われる.ここでは,所属系列の移動ということがかなり頻繁に起こっているため,どこかに盲点が発生しそうな感触がある.

NAMEBOX #339 明石中宮(0)はPHYLETICTREEフェーズで[系列枠]: #917 先祖=#509 先帝(0)に振り分けられているが,GODOWNSTREAMに入って,※3系列に移動している.先帝系列は系列枠リスト上では※3系列より先にあるのに,明石中宮の人名リンクが先帝系列で展開されないという理由が分からない.このサンプルには系列が89個含まれているが,最初の7つまで表示すると,

  1. #915 先祖=#701 式部卿宮の北の方(0) 優先=式部卿宮の北の方 始系列 type=始系列
  2. #917 先祖=#509 先帝(0) 優先=式部卿宮  type=婚姻関係
  3. #919 先祖=#557 ※6(0) 優先=先帝の后宮  type=婚姻関係
  4. #921 先祖=#405 ※3(0) 優先=明石中宮  type=親元関係
  5. #923 先祖=#603 中務宮(0) 優先=明石の尼君  type=婚姻関係
  6. #925 先祖=#637 右大臣(明石)(0) 優先=髭黒  type=婚姻関係
  7. #927 先祖=#589 按察の大納言(若紫)(0) 優先=紫の上の母  type=婚姻関係

のようになっている.※3系列は順位からいくと4番目だ.

系列優先仮ノードが逆婚姻関係でパートナーが先祖ノードのケース

TRASHCANとUNDOSYSTEMというZTシステムの重要な機能のモジュール化に成功した.TRASHCANは完全なネィティブnoduleクラスとして確立され,UNDOSYSTEMもその基幹部分をUNDOBASEというネイティブクラスとして切り出すことができた.これでシステムの不透明な部分が相当程度グラスボックス化したと言える.時期は不明だが,前に一度試みて不成功に終わったUNDO機能と関わりがあるNODULE::numberという変数も廃止することができた.アプリ終了時には,ゴミ箱の内容を廃棄してから delete CAN を実行しているが,これもストレートに delete CAN だけで済むことを確認した.しかし,delete CAN でゴミ箱の中身が消えるのはなぜだろう?CleanSlotではすべてのスロットをdeleteしているが,スロットゼロは除外されているはずなのだが…

下図のような大量のカードを一括削除して,TRIBEBOX::SetPrimeLinkで停止した.

image

(prmtype && !marglink && PHASE <= TRIBERELOCATION && CENTERLINE != CENTERLINE_SEXIAL)という理由だ.始系列ではないのに優先ノードが結婚リンクを持っていない.⇒BUG20-12-19 17-32-21.zelで反例サンプルを保全した.この障害は2020/12/02のリリース版でも発現するので,最近の修正とは無関係と思われる.

TRIBEBOX::GetAlternativePrimeNodeの論理に穴が空いていた.先祖ノード#238 FAMILYTREEの対象系列#534は優先仮ノード#166 extraslot2の相手方を見つけられなかったため,代替として#226 COUPLINGを選択した.相手方#154 couplingは系列#528 先祖=#426 couplingの本人(先祖)ノードだが,既存コードには相手方が先祖の場合が抜けていた.優先仮ノードの結婚リンクは,すでにパートナーを見つけた時点で決定可能なので,既存コードは不要と思われる.

源氏物語6のテーマを切り替えようとして,「フェーズのイレギュラーな遷移」エラーが発生した.DRAWSTAGEからTOPOLOGICALSORTに移行しようとしている.テーマを切り替えただけではトポロジーには変化はないが,図形のサイズ・位置には変化が現れるのでレイアウトの再計算を実施する必要がある.系統並び替えを実施しないで再描画するUpdateDiagramという関数はあるが,これで間に合うだろうか?spannodcount > 0 ということは系統並び替えが必要ということを意味するのだが… TOPOLOGY::UpdateDiagramでは「写真を表示しているときは調整不能,系統並び替えが必要」としている.⇒ここでは,FAMILYTREE::updatespannodで強制的にフェーズをINITIALIZEDに落とすということにしておこう.

ZTシステム構成図7.ZELを開いた後,BUG20-12-19 17-32-21.ZELを開こうとしてエラーになった.COUPLING::initializeの出口で(TemplateBuff)が起きている.⇒再現しない.⇒再現した.源氏6の後,ZTシステム構成図7.ZELを開いて起きた.ただし,再現するには何か条件が必要なようだ.TemplateBuffは記録ページテンプレートを保持するためのバッファで,COUPLING::SaveTemplateで使われている.しかし,この関数は呼び出されている形跡がない.⇒いや,VBのSaveFileからの呼び出しがある.

SaveTemplateだけでなく,LoadTemplateというのもある.「記録ページテンプレート」というのは過去の遺物だ.以前は記録ページのレコードをカテゴライズするためのテンプレートファイルというのを持っていたが,いまは完全に仕様から落とされている.TemplateBuffは廃止でよいと思われるが,エラーが発生する状況をもう少し詳しく確認しておきたい.ひょっとしたら,バッファオーバーランのようなことが起きている可能性もある.SaveTemplateはファイル保存時に実行されているので,ファイルを保存してみれば分かるだろう.

確かに実行されているが,サイズはゼロだ.ただし,COUPLING:SaveTemplate側ではサイズ+4の領域を確保しているので,サイズゼロでもメモリブロックの取得は実行される.記録ページテンプレートはCOUPLING::SerializeHeaderで読み込まれている.この部分の論理は残しておかないと古いファイルが読み込み不能になる可能性がある.また,現状では書き込みの部分も作動しているはずだから,この論理を止めるためには「REVISIONのアップデート」が必要だ.⇒片付いた.REVISIONを更新したので,ここで一度リリース版を起こしておこう.Version 2.2.0.019 Release 2020-12-20とした.

▲源氏6.1の318点から48点を一括削除してTRIBEBOX::SetPrimeLinkでエラーが発生した.BUG20-12-20 00-37-44.ZELで再現できる.問題の系列は先祖#404 ※3の系列枠#921で,本来の優先ノードは#338 明石中宮だったのだが,このノードが系列所属ノードの中に入ってこない.どこかでancestryリンクを書き換えてしまっているのだろうか?元々は先帝系列に属していたようだ.ancestry値はTOPOLOGY:FilteringKinship→ GetkinshipDegree→ …→ CARDLINK:getKinshipDegreeで設定されているが,TribeDecompositionの入口で一度リセットされ,→…CARDLINK::KinshipDegreeで再設定される.

どうも現行方式の原理的欠陥が露呈してしまっているような気がする.現行方式では一度設定された先祖リンクは変更できないようになっているが,場合によっては不可避の系列優先ノードというのが存在する.つまり,外部に接点を持つカードがそれしかない場合には,そのノードは複数の系列にダブル登録するしかないのではないか?おそらく「逆婚姻関係」というのはそのような矛盾を解消するための苦肉の策だったのだろう.しかし,「逆婚姻関係」はある限られた範囲でしか成立しない.そもそもMakeUpTreeの段階になって,GetAlternativePrimeNodeを実行しなくてはならないというところがこの弱点の現れだ.

系列の連結関係を「婚姻関係」に限定すれば,現行方式でもカバーできているのかもしれない.婚姻関係なら一方を系列に属する本人,他方を配偶者として連結することができる.しかし,「親元関係」ではそういう訳にはゆかない.どちらも「本人ノード」として立てなければならないからだ.逆に言えば,そのような欠陥があるにも関わらず,部外の明石中宮が優先ノードとして選択されているということは抜け道はあるということだろう.なぜこの選択がリジェクトされることになったのかその理由を見てみよう.

TRIBEBOX::GetRealnodeで弾かれているためだ.明石中宮は仮ノードを2つ持っている.#339 明石中宮(0)が※3系列,#1112 明石中宮(1)は按察の大納言(若紫)系列だが,(1)はIsSolidNameBoxで弾かれている.これは,按察の大納言(若紫)系列が※3系列より後方にあるためと思われる.明石中宮は先帝系列に属しているのに,それに属する人名枠が存在しないのはなぜだろう?多分,これが一番の問題点なのではないかと思う.⇒.#339 明石中宮(0)は最初に先帝系列に編入されている.TRIBELIST::GoDownStreamで※3に切り替わっている.

昨日朝イチで片付けた障害がぶり返している

アプリを起動して,カードを1枚削除→終了でGetCardBaseのエラー(PHASE < DRAWSTAGE)が起きるという,昨日の朝イチで片付けた障害がぶり返している.ただし,今回はUNDOシステムありでテストしているので,昨日の朝とは条件が異なる.系統並び替えが実行されていないとすれば,昨日のUNDOの改修が影響しているのだろう.⇒いや,UNDOSYSTEM::CommandEndからCOUPLING:TopologicalSortを実行している.少なくともTopologicalSortの出口ではDRAWSTAGEになっている.⇒確かにどこかでフェーズをINITIALIZEDまで落としている.

心当たりはある.おそらく,FAMILYTREE::SetUndoBaseだ.これはUNDOCOMMANDに基準ノードなどの情報を設定する関数で,これまではあちこちに分散していたのを関数化したものだ.この中でSetPhase(CHAOTICSTATE)を実行している.この関数は,①UNDOSYSTEM:CommandStart,②UNDOSYSTEM:MakeNewCommand,③UNDOSYSTEM:CommandEndから呼び出されている.最後の③は系統並び替えの完了後だから,ここではSetPhaseは実行するべきではない.多分これはCommandStartFlagで切り分けることができるだろう.

カード削除2件→UNDOでCARDLINK::DownStreamのエラー(Invalidated || !nambox || !nambox->YUpper() || !nambox->getY() || !tribe)が起きた.namboxが空.かなりまずい.起動→基準カード削除→UNDOでは別のエラーになる.TRIBELIST:MakeTribeBoxで(marglink && !marglink->GetAncestor())というエラーが起きている.最初のエラーの再現手順を確定しておこう.

ZTシステム構成図7.ZEL:全体図を#1 couplingで開いた後,①#1 couplingを削除,②#61 NLIST < LISTNODE, CID>, #87 GENEBOX, #230 GENEBOXを選択して一括削除,③UNDO という手順で再現できる.手順が簡単なので,2番目の障害から追いかけてみよう.UNDOSYSTEM::UndoRedoCommandでUndoProcessを実行後の系統並び替えを実行しているところだ.⇒いや,事例としては最初の方が易しい.namboxが空ということはUNDOで復元に失敗したというだけだが,2番目の事例では状況を解析するのがかなり厄介だ.再現手順でも,最初のカード削除は省略できる.

障害が起きているのはCARDLINK:#369 @61NLIST< LISTNODE, CID>だ.確かにnamboxが空になっている.UNDOでは通常系統並び替えが実施されるため,描画要素は保全の対象になっていない.しかし,人名リンクには必ずデフォルトで1つは人名枠が付属することになっている.これをどこかで補充しなくてはならない.UndoRedoCommandとUndoProcessはほとんど外部依存コードなので従来論理がそのまま実行されているはずなのだが…

現行ではOpenFamilyBaseの中でKAKEIZU::readFamilyBaseを実行後にInitLinkTable→CARDLINK::initializeでデフォルトのNAMEBOXを生成している.描画要素はすべて頂点のTREEVIEWに描画リストによって連結されている.INITIALIZEDの段階では描画リストは一応使える状態になっていることが予定されている.NAMEBOXはCARDLINKに接続しているので,CARDLINKが削除されたとき同時に削除されているから,復元しただけではnamboxは元の状態には戻らない.

昨日のログでは「RestoreShadow オーバーライドは不要」としているが,間違っている.この関数は元々UNDONODEが実行していたものだが,UNDOSYSTEMに移管した後,外部依存コードなしと認定されてUNDOBASEが受け持つことになったものだ.明らかにオリジナルのコードには外部依存コードが含まれている.⇒UNDOSYSTEM:RestoreShadowを復元して動作するようになった.⇒2番目の障害も解消し,完全に動作するようになった.

バックアップも取ったので,ここまでの修正をフィックスしておこう.

  1. UNDOからTITLEINFOを切り離す@20201217 8箇所
  2. UNDOを基本・拡張クラスに分解@20201217 1箇所
  3. RestoreShadowをUNDOSYSTEMに移管@20201217 6箇所
  4. TRASHCANのアプリ依存度を確認する@20201216 2箇所
  5. GetCardBaseの動作にフェーズは関わりがない@20201217

DEFINETRASHCANとDEFINEUNDOSYSTEMの2つのオプションの組み合わせ,つまりゴミ箱を使う/使わないxUNDOを使う/使わないの4つのパターンすべての動作を確認したが,問題なさそうだ.

UNDO機能なしで動作することを検証する

UNDO機能なしの動作を検証するために,UndoRedo.h内のすべてのクラス定義を止めてビルドしたバージョンをテストしているところだが,実行時にFAMILYTREE:GetCardBaseで(PHASE < DRAWSTAGE)エラーが発生する.これは系統並び替えが実施されていないことを意味する.UNDOではサポートするすべてのコマンドの入口と出口でUNDOSYSTEM:CommandStartとCommandEndという関数を呼び出しているが,系図木に変化があった場合にはCommandEndで必ず系統並び替えを実行している.フェーズがDRAWSTAGEになっているということは,系統並び替えが実施されたことと同義だ.

GetCardBaseは,アプリの人名カード画面に表示するためのデータを取り出す関数で,この処理はフェーズに関わりなく実行可能なはずであるから,まず,ここでは停止しないようにしておこう.⇒今度は,TREEVIEW::GetScrollValueで同じエラーが発生する.GetScrollValueはアプリ側のCenteringCardSubから呼び出されている.ここでは図面のセンタリングを実行しようとしているのだから,その前に図面がレンダリングされている必要がある.⇒やや変則的だが,GetScrollValueから直接系統並び替えを実行できるようにしておこう.⇒これで問題は解決した.UI的にはUNDOシステムが搭載されていないので,カードを削除しても系図画面のツールバーのUNDOボタンは有効にならない.

これでUNDOとゴミ箱なしでもシステムが問題なく動作することを確認できた.TRASHCANはアプリケーションコードにまったく依存しないネイティブなnoduleクラスとして確立されたが,UNDOにそこまでのことを要求するのは無理がある.実際,UNDOを機能させるためにはアプリケーションコードのあちこちにUNDOSYSTEMのコードを埋め込む必要がある.(コードと言っても少数の特定関数を呼び出すだけだが…)問題はUNDOSYSTEM自体が(どの程度)アプリケーションに依存しているかという点だ.まず,この点を確認してみよう.

UNDOCOMMAND(a.k.a. UNDOCHAIN)にはもろにCARDLINKへの参照が4個設置されている.これについては後で考えるとして,暫定的にvoid*としておこう.⇒「暫定:UNDOSYSTEMの外部依存度@20201217」というマクロでコンパイルエラーを止めてビルドは通るようになったが,もちろんこのコードでは実行できない.「暫定:UNDOSYSTEMの外部依存度@20201217」は45箇所も入っている.満身創痍というところだ.今回はこれを一掃するところまでは目論んでいないのだが,何が可能か?考えてみよう.高度に抽象化されたUNDOシステムを想定することは可能だが,今日明日の課題ではない.

アプリケーションとUNDOシステムを切り離すための速攻的な措置を考えるとすると,アプリとUNDOの間にヘルパーないしサポートクラスのようなものを入れるということがまず考えられる.UNDOSYSTEMクラスを基本クラスと拡張クラスに分解し,基本クラスを完全にアプリから切り離すということも考えられる.これを実装するのはそれほど難しくないのでやってみることにしよう.ただし,その前に基本クラスや基本関数自体がアプリ依存になっているという問題がある.たとえば,UNDONODEクラスには最初からCARDLINKへの参照が入っているし,UndoProcessというUNDO処理関数まで余分な情報を必要としている.

int UndoProcess(bool redo, long &basenode, long &primary, short &lastcommand, TITLEINFO &titleinfo);

UNDONODEに入っているCARDLINK*はそれぞれ,①主選択カード,②現基準カード,③全体図基準カード,④部分図基準カードへの参照だ.系統並び替えなどを実施すると基準カードが変化するが,系統並び替えなどの操作はUNDOの保全対象となっていない.(その代わり系統並び替え履歴,主選択カード履歴で巻き戻しすることはできる)これらの情報は本来UNDOの守備範囲外なのだが,UNDO/REDOで再描画したときの画面を元の状態に近いものとして表示するためにはどうしてもこれらの情報を時系列で保全しておく必要がある.

それにしても,UNDONODEのような構成要素にその情報を持たせるというのは適切とは言えないが,便宜上このような仕様になってしまった.それどころか,UndoProcessの最後の引数であるTITLEINFOはアプリに現在の部分図タイトル情報を渡すという目的のためだけに設置されているようで,もしそれが本当ならかなりの手抜きというか,横着としか言いようがない.まず,この点を確認してみよう.

確かにそうなっている.TITLEINFOは下り方向でしか使われていない.宅急便の戻り脚に発送を依頼するというのはありとしても,これではまるきり,郵便屋さんに宅急便の荷物を預けるようなものだ… UndoRedoCommandを実行しているのはGCのmZelkova:mUndoRedoで,取り出したTITLEINFOはLastTitleというところに格納されている.TITLEINFOの取り出し関数ないしコマンドが見当たらない.TITLEINFORMATIONを取り出す関数はあるが,TITLEINFOとは内容が異なる.すべてのTITLEINFOを取り出す関数と現部分図エントリを取り出す関数を組み合わせれば目的は達成できそうだが,ここでは放置して動作を観察してみることにする.

▲mZelkova::mUndoRedoでTITLEINFOを更新する手段を導入する

UNDOCOMMANDに入っているCARDLINK情報は現状のままとして,UNDOSYSTEMを2階層に分解するというのをやってみよう.外部からはUNDOSYSTEMとして認識されているので,この名前を拡張クラス名として維持,基本クラスはUNDOBASEとしてみる.⇒実装した.

UNDONODE::RestoreShadowでLINKTABLEを参照している.これを避けるためにこの関数をUNDOSYSTEMに移管してみる.UNDONODE:UndoRestoreもUNDOSYSTEMに移管する.⇒一応動作しているようなのでバックアップを取っておこう.UNDOシステムにはUNDONODE,UNDOCOMMAND,UNDOBASE,UNDOSYSTEMの4つのコンポーネントがあるが,外部依存コードはUNDOSYSTEMが単独で扱うようになった.UNDOCOMMANDが持っていた4つのCARDLINK*はすべてlong整数に変えてカードの参照番号を格納するようにした.

UNDOSYSTEMのコードを少し整理してみよう.現在UNDOBASEは17個関数を持っているが,そのうちの6個はUNDOBASEで完結してUNDOSYSTEMでオーバーライドされないものだ.それ以外のものも,UNDOBASEの関数でカバーできるところはできるだけUNDOBASEの関数を呼び出すようにして,UNDOSYSTEMには外部依存部分だけが記述されるという体裁にしたい.

  1. BackupPointData 一部にはネイティブコードもあるが,ほとんどまるごと外部依存コード
  2. CommandStart UNDOBASEのコード+FAMILYTREE:SetUndoBase BackupPointDataは仮想関数とした
  3. UndoProcess ほとんど外部依存コード
  4. RestoreShadow オーバーライドは不要
  5. GetUndoStat UndoCurptr不在のときは,FAMILYTREE:SetUndoStat
  6. UndoRedoCommand ほとんど外部依存コード
  7. UNDOSYSTEM::CommandEnd UNDOBASE+外部依存コード

賽の河原で成仏しきれない亡者9名を発見

ゴミ箱の廃棄中メモリブロックカウントの不整合が発生する.メモリブロックカウントはgetmemでヒープからメモリブロックを取得したときにインクリメントされ,delmemで解放されたときにデクリメントされる.getmemはGlobalAllocPtrを呼び出してグローバルメモリを取得し,delmemはGlobalFreePtrでそれを返却する.これら2つの関数以外にGlobalAllocPtrとGlobalFreePtrを使っている場所は存在しない.つまり,入口と出口は完全に押さえられている.にも関わらず,何度数え直しても記録されたカウントと現物個数が一致しない.

nodule::operator deleteでは通常はdelmemを実行する代わりに,そのオブジェクトをゴミ箱に送付する.ゴミ箱が存在しない場合にはどうなるのか?ZTシステムは必ずしもゴミ箱を必要としない.ゴミ箱を設置しないというオプションでも動作するというのが設計上の要件だ.確かに,ゴミ箱廃棄中というのはかなりクリティカルな状況であることは間違いないが,それにしてもこの不一致はどこから生じているのか?

成仏しきれない仏がいるとすれば,それは賽の河原であるに違いない.そこを探してみるしかない.しかし,どうやって?いや,それは至って簡単だ.ThrowTrashCanの中でdeleteを実行している行の前後を監視して,メモリブロックカウントの推移を見ればよい.もし,deleteを通過しているにも関わらず,カウントが変化していないとすれば,そのオブジェクトが探している失踪者であることは間違いない.これで成仏しきれない9名の亡者を特定することができた.あとはトレースするだけだ.

これらの失踪者はアプリ終了時,FAMILYTREE::EraseFamilyTree→ LINKTABLE::ClearTableによって削除されているが,Shadowを持っているためゴミ箱には直行せず,Nringに一時係留された後resetUndoChainでUNDOチェーンがリセットされるタイミングでゴミ箱に移動している.UNDOチェーンのリセットではチェーンに連結されたすべてのUNDONODEが削除されるが,このUNDONODEのデストラクタ→UNDONODE::Disposeで実施している操作に問題がある.

UNDONODE::Disposeはそのノード(UNDONODE)が参照している実ノードのDELETED値をDEADに設定してからゴミ箱に送付しているが,その実ノードがShadowを持っているため,LIFE値はそのままとしている.この操作は,「Shadowを持つオブジェクトは削除してはならない」というUNDO規則による.ゴミ箱が廃棄されたときにこのオブジェクトは閻魔大王(NODULE::operator delete)の前に進み,「LIFEはセットかリセットか?」という大王の尋問に「セット」と答えるため,大王は「帰れ」と宣告し亡者は放免されて賽の河原に戻るという筋書きだ.

この筋書きが正しいとすれば,一つの簡単な解決法として「NODULE:operator deleteでは無条件にdelmemする」という方法がある.多分この解法はそれ自体としては誤っていないと思われるので,実装してみることにしよう.⇒問題なさそうだ.エラーはこれで解消されたが,UNDONODE::Disposeの扱いには問題があるので対策を講じる必要がある.⇒対処した.「Nringとゴミ箱の重複登録を禁止@20201213」という指針に従って,実ノードがShadowを持つ場合にはゴミ箱に入れないようにした.ゴミ箱を廃棄するときには,すべてのUNDONODEが削除されるので,その実ノードのShadowチェーンの最後のUNDONODEが削除されたタイミングでゴミ箱に入ることになる.

修正が大分累積してしまったので,まとめて一掃しておこう.12月10日以降の修正としては以下がある.

  1. Clean仮想関数の導入@20201210 7箇所
  2. METRIXでTREEVIEWを参照しない@20201210 1箇所
  3. 参照リンクを移動してはならない@20201210 3箇所
  4. CHAOTICSTATEでは描画要素を無視@20201211 1箇所
  5. METRIXからのNAMEBOX参照を廃止@20201211 16箇所
  6. metrixからのTREEVIEW参照を廃止@20201211 16箇所
  7. Nringとゴミ箱の重複登録を禁止@20201213 19箇所
  8. BUNSFILE:closeでバッファを解放@20201213 1箇所
  9. ENDOFAPPLICATIONではなくGROUNDZEROで終わる@20201211 2箇所
  10. countゼロの参照リストを削除する@20201214 1箇所
  11. BUNSBUFを廃止する@20201214 8箇所
  12. delmem,_delmem,delmem_を使う@20201215 2箇所
  13. NODULE:operator:deleteでは無条件にdelmemする@20201216 1箇所

この他以下のオプションがOFFになっている.

  1. COMMONHEADERSHORTの改訂@20201210
  2. Disposeの設置を義務化@20201211
  3. ゴミ箱なしの動作を確認@20201214
  4. NODULE:operator:deleteを廃止する@20201214
  5. DoublePtrで親リンク空の場合NULLSPOTを返さない@20201215

このうち,(3)のみ保留とし,それ以外はすべて廃止する.この他仮修正が11箇所ある.⇒すべてクリアした.

ZTシステム構成図7.ZELの全体図を#1 couplingで開いて,このカードを削除→UNDOでUNDOSYSTEM::UndoProcessのエラーが発生する.UNDONODEの参照する実ノードの親リンク空というエラーだ.⇒これはあり得る状態だ.この実ノードは削除され,フロート状態でNring上にある.つまり,これは「Nringとゴミ箱の重複登録を禁止」によって起きるようになった事象で,状態としてはノーマルだ.

同上操作で,FAMILYTREE::GetCardBase中(k >= hubo->margbase.kids)で停止した.huboのkids配列にカードが存在しないというエラーだ.このカードは#5 pagesetupで削除されたcouplingに代わる基準ノードで,5人兄弟の2番目.カード削除前は,coupling+COUPLINGの子どもで4人兄弟の先頭だった.母親のCOUPLINGが単身になり,単身婚の子ども1人を加えて5人兄弟になっている.

UNDOで戻ったときに親の結婚ページで兄弟4人というのは正しいが,リンクがすべて空ではどうしようもない.⇒この結婚リンクは夫が削除されたために妻の単身婚と併合され,削除されているはずなのにUNDOで<再生>されていない.⇒UNDOシステムでIsInTrashCanという関数が4箇所で使われている.UNDOはゴミ箱の存否と関わりなく動作しなくてはならないのだから,IsInTrashCanを使うことは許されない.⇒代わりに(Deleted() != EXISTING)で判定するようにした.⇒この修正だけで完全に元通り動作するようになった.

「ゴミ箱なしの動作を確認@20201214」オプションを復活させて動作を確認してみよう.今回は少し厳格に,TRASHCANのクラス定義それ自体を止めてみる.⇒確立できた.「DEFINETRASHCAN」というマクロで切り分けた.このオプションをSPECIFICATIONとすれば,「ゴミ箱なしの動作を確認@20201214」は廃止してもよいだろう.元々あった「USERECYCLESYSTEM」も廃止でよいと思う.このオプションでは「描画オブジェクトのリサイクルシステム」ということが意識されているようだが,現在のリサイクルシステムは基本的にZTのすべてのオブジェクトクラスをカバーするものとなっている.

TRASHCANのコードがどの程度アプリケーションに依存しているか,あるいはどの程度までアプリケーションから独立しているかを見てみよう.これを調べるにはnodule.hだけをインクルードしてみればよい.⇒TRASHCAN::ReuseWasteではCOMPLISTとCARDLINKの情報が必要となっている.しかし,これは多分不要だと思う.これらのクラスのコンストラクタの中でやればよいだけの話だから… 実際それは実行されているから,このコードは不要だ.⇒TRASHCAN::CleanSansyoにもNAMEBOXを参照しているコードが入っていたが,まったく不用なのでカットした.これでTRASHCANは完全にnoduleネイティブなクラスとして確立された.UNDOシステムを切り離すのも難しくないと思われるのでやってみよう.⇒実装した.

▲リサイクルシステム,UNDOシステム,参照リスト管理を止めたシステムを起動→カードを3枚一括削除してFAMILYTREE::GetCardBaseで(PHASE < DRAWSTAGE)エラーが出た.フェーズはINITIALIZEDになっている.UNDOの中で実行していた系統並び替えが実行されていないためだろう.カード削除処理中にMARGBOX::getGenerationが実行され,(coordinate() == ABSOLUTE)で停止するという事象も起きる.

UndoShadowCount, UndoShadowSizeという2つの変数を追加して解決

MemoryBlockCountとMemoryBlockSizeが実際と合わないという不具合がぶり返してしまった.カードを3点削除→UNDOSYSTEM:CommandEndで系統並び替えを実行する直前だ.totalcount 10165に対し,MemoryBlockCountは10218になっている.つまり,デクリメントされていない.カードを1点削除するだけで再現できる.カウントで24の差異が出る.CANとNringの合計とオブジェクト数=生成オブジェクト数ーリサイクル個数は一致している.カードを削除した場合,UNDOオブジェクトが生成されるので総数では増加しなくてはならない.従って,間違っているのはtotalcountの方である可能性がある.⇒UNDONODEは24個生成されてNringに入っている.nodecountが更新されていないのではないか?⇒いや,nodecountは最新だ.

ゴミ箱のカウントが+91, Nringが-24でMemoryBlockCountは+91.つまり,Nringからゴミ箱に移動した分がそっくり消えている.MemoryBlockCountはヒープから実メモリを取得したときにしかインクリメントされていないので,この差異がどこから発生しているのかよく分からない.⇒FAMILYTREE::DeleteCardDataの実行で91件の新規メモリ取得が発生している.同じ件数だけゴミ箱が増えているのだが,問題はNringの純減-24がどこに行ってしまったか?という点だ.

少なくともメモリからパージされていないのだから,ゴミ箱以外には往き場所はないのだが… Nringからパージされているノードは208件ある.これらはすべてゴミ箱に移動しているものと推定されるが,ゴミ箱はそこまで増加していない.ますます訳が分からなくなってきた… どうもこの統計はかなり怪しい.MemoryBlockCountをデクリメントしている関数が2つある.block::delmemと_delmemだ._delmemからdelmemを呼び出すようにして,1箇所で管理するようにしておこう.これでメモリ取得・解放の窓口はgetmem, delmemに1本化できた.

getmem/delmemはシステムで使用する全メモリを管理しているので,MemoryBlockCountとMemoryBlockSizeは全メモリに対応している.サイズ不定のフリーメモリは通常freeblockオブジェクトとして生成されるが,中には完全なフリーメモリとして扱われるものもある.UNDOのShadowイメージはその典型だ.というか,おそらくそれ以外では使われていないのではないかと思う.バッファなどはどうか?その辺りもそうなっているかもしれない… ⇒少なくともQUICKDBで使われているバッファはfreeblockで取得したものだ.

getmemで直接フリーメモリを取得している例として,COUPLING:SaveFamilyTreeで使っている人名/結婚リンクバックアップ領域というのがあるが,その関数内で解放されている.それ以外ではUNDOのShadowしかない.getmemではどこから呼び出されているか判別できないので,呼び出し元のUNDOSYSTEM::SetUndoListで管理するしかないだろう.暫定的にUndoShadowCount, UndoShadowSizeという2つの変数を作ってみた.⇒フリーメモリの取得はこれでよいとして,削除にどう対応するか?⇒UNDONODE::Disposeでfreeblock::delmemしている.ここで対処すればよい.⇒準備は整った.数字を見てみよう.

完全に一致した!これで問題は解決した.グローバルメモリの収支はこれで完全に押さえることができたと思う.UNDOの動作を確認してみよう.⇒ReferenceControlで参照カウントと参照リストの不一致が発生した.昨日はゴミ箱なしでこの不良が出ていたが,ゴミ箱ありでも同じ結果だ.UNDONODE::UndoRestoreで「オブジェクトの参照を一旦すべてクリアする」ということをやっている.ここでは対象ノードのすべてのスロットを検査して参照解除を実施しているが,スロット7,  8, 39と進んだところでエラーになっている.これはかなりおかしい.

スロット7, 8で問題なく処理できたのに39で突然不一致になるというのは考え難い※.障害ノードは#977 baselist 基本世代枠@33のCARDLINKでスロット39というのは,結婚ページ0に当たる.このカードは親ページを2, 結婚ページ1を持っている.ここで不一致が生じるというのはノーマルな動作なのではないかと思う.⇒いずれにしても参照リストと参照カウントの不一致は避けられない,UNDOに対応した処理が入っていないのだから… ⇒この問題は保留としておこう.

※これはちょっと勘違いしている.参照リストというのは被参照ノードが持っているリストで,参照元のスロットを連続検査しているが,対象の参照リストはそれぞれ独立のものだ.

▲参照リスト管理をUNDOと両立させる

ゴミ箱ありでカードを3枚削除→アプリ終了してfreeblock::delmemでエラーが発生した.引数のダブルポインタが空を指している.フロート状態のノードを削除しようとしているためだ.DoublePtrは親リンクが空の場合はNULLSPOTを返している.これはNULLが格納された場所を指している.freeblock::delmemでこのエラーが出るということは,この関数ではフロート状態のオブジェクトをdeleteできないということを意味する.これは仕様的にはやや問題があるが,実際問題として,ゴミ箱に入っているノードが親なしという状態自体がイレギュラーだ.

なぜこんなことが起こるのかについては,別途調べる必要がある.暫定的にNULLSPOTにこのノードのアドレスを格納して返すようにした.このアドレスはメモリをパージした後直ちにNULLに戻されるので,ここだけの話であれば,これでも悪くはないのだが…

CAN::ThrowCanを実行中,QNが負というエラーが発生した.printnamaeでバッファオーバーランが発生している.⇒修正した.

上記でfreeblock::_delmemの仕様を変更して,delmemを呼び出すように修正しているが,やはりこれでは通らない.これらの2つは用途が微妙に異なるので,それぞれ独立に併存させるしかない.

  1. freeblock::_delmemとdelmemはともにメモリ上の要素ブロックを解放するための関数で,事後にMemoryBlockCountとMemoryBlockSizeを更新する.また,これらの関数では要素のアドレスが格納されたポインタ(スロットないしベタ参照)を(可能な場合には)空にする.
  2. freeblock::delmemは汎用的なメモリ削除関数でfreeblockを含むnoduleクラスオブジェクトの他,完全なフリーメモリブロックも扱うことができるが,メモリブロックを参照するリンクに空を書き込む必要があるため,引数ではvoid**を取っている.
  3. この仕様では,フリーメモリを関数内部で(ローカル変数を使って)getmemして廃棄することはできないということになる.この制限はかなりきついかもしれない.厳格過ぎるかもしれないが,安全ではある.フリーメモリを解放するためには,それが「どこに置いてあるか?」を知らなくてはならない.
  4. freeblock::_delmemはnoduleクラスオブジェクトをメモリからパージするために用いる.noduleは通常メモリからパージされる時点ではフロート状態で所在不明(接続先不明)となっているため,delmemにダブルポインタの形式で渡すことができない.

もし,これらを統一して単一の関数でMemoryBlockCountとMemoryBlockSizeを管理したいというのであれば,delmemと_delmemの両側から呼び出されるフラットな関数を用意するしかない.実装は簡単なのでやってみよう.getmemに対応するフラットな関数を改めてdelmemとし,現行のdelmemはdelmem_とリネームする.delmemは外部から使えないようにprivateとした.

▲MemoryBlockCountの不整合がぶり返してしまった.カードを1点削除→アプリ終了して,totalcount 1 MemoryBlockCount 10, TotalSize 1180 MemoryBlockSize 27576となった.要素数で9個,サイズで26396のオブジェクトが紛失している.ThrowCan 前 diff=0 10302 = waste 10301 + Nodecount 1 =10302となっているので,NringにはCANしか残っていない.残りはすべてゴミ箱の中とUNDOのShadowだけだ.NringTotal 1180+CanTotal 7023434+UndoShadowSize 7024614は,MemoryBlockSize 7024614と一致している.

ThrowCan 後ではUndoShadowSize 0となっているので,UNDOのShadowは完全にパージされている.GlobalFreePtrを実行しているのはfreeblock::delmemしかないし,この関数の中でしかMemoryBlockCountとMemoryBlockSizeは更新されていないのだから,間違いようがないとしか思えないのだが…

単にファイルを開いて閉じるではこのエラーは発生しないので,カード削除とそれに伴うUNDO処理にからんだ問題であることは確かだが… ⇒いや,少なくともUNDOとは関わりがないのではないか?UNDOはCOUPLING::EraseFamilyTreeの中ですでにパージされている.上で,

NringTotal:1180+CanTotal:7023434+UndoShadowSize=7024614

としているが,間違いだ.プリント文の誤記で,実際は

NringTotal:1180+CanTotal:7023434+UndoShadowSize=0 = 7024614

でUndoShadowSize=0となっている.つまり,UNDOのShadowはすでにパージ完了している.何度数え直してもゴミ箱の中は個数で10301,サイズで7023434だ.結局,ThrowCanのどこかで漏れが発生していると考えるしかない.⇒成仏しきれない亡者を9名発見した.CARDLINKが7つ,MARGLINKが2つ.deleteでメモリからパージされるためにはNODULEのoperator deleteが起動されなくてはならないが,何かの理由でそうならなかったのだろう.⇒NODULE::operator deleteには来ているが,LIFEが空になっていないためパージを免れている.

大体の様子はつかめた.これらのノードはアプリ終了時,EraseFamilyTreeでLINKTABLE::ClearTableによって削除されている.ただし,このときはShadow付きであるためゴミ箱には直行せず,Nringの中で保留状態になっている.この後のresetUndoChainでゴミ箱に移動しているようだ.CallSetCouplingPtrがアプリから呼び出されるときには,すでにNringにはコア骨格木しか残っていない.

resetUndoChainには明示的に関係オブジェクトをゴミ箱に移すようなコードは存在しない.単にコマンドチェーンのUNDOCHAINを1個づつdeleteしているだけだ.とすれば,UNDONODE:Disposeしかない.⇒確かにそのようだ.ここではUNDONODEが参照している実ノードをDumpWasteでゴミ箱に投入している.しかも,その前にsetNring(address, NRING_DELETE)を実行しているのだから,息の根が止まってもよさそうなものだが… ⇒setNringではShadowが残っているため,Shadow->DELETED = DEADとしている.もし,Shadowが付いていなければLIFEに空がセットされているのだが…

UNDOチェーンの長さは有限なので満杯になると後ろからチェーンを切断するような動作になっているはずだ.従って,UNDONODEが削除されたことで参照している実ノードが削除されたことには必ずしもならないというのは正しいと思われるが,現行ではゴミ箱に入るときはすでに死亡しているという前提なので矛盾が生じる.どう解決すればよいか?