すべてのオブジェクトの固有データ部を完全に殺菌(ゼロクリア)する

条件コンパイルをフィックスするときの手続きにミスがあったため,ファイルをクローズしたときのシステムのクリーンアップに失敗していたが,ようやく保健所の調理場検査にパスできる状態になった.理想的には存続するすべてのオブジェクトの固有データ部を完全に殺菌(ゼロクリア)してしまうことが望ましいが,一部環境パラメータを保持しているオブジェクトがあり,そのようなものに関しては別途措置が必要になる.環境パラメータと一般データを形式的に識別することはできないので個別のクラスで対応することになるが,保全の必要な環境パラメータであることを明示するためにこれらを一度ローカル変数に退避しておいて,処理的には全領域をゼロクリアするというのがわかり易いのではないかと思う.保全されるべき環境パラメータには以下がある.

  1. long BASETABLE::tablesize テーブルの物理サイズ
  2. HWND COUPLING::hwnd ウィンドウハンドル
  3. long DATALIST::datasize リスト要素のデータサイズ
  4. QUICKDB KAKEIZU::carddata, marriage
  5. CSize TREEVIEW::WindowSize 親ウィンドウサイズ,物理単位 CPoint WindowPost ノーマル画面で親フレームの左上点物理座標 CPoint PreviewPost プレビュー画面で親フレームの左上点物理座標

NODULE::signatureという変数は使われていないので廃止する.

ZTシステム構成図7.BAD2.ZELを開いて,LIST::nextで停止する.(!find(listnod))が起きている.TribeRelocationでSIMPLEGRAPH:BuildTightHasseDiagramを実行しているところだ.障害ノードは#2408 NODELISTでSIMPLENODE #14742の次の要素を求めているが,#14742がリスト上に存在しないというエラーになっている.このリストはかなり複雑な構造になっているが,該当要素は存在していないようだ.重婚同類検定では枝グラフを3つ使っている.

SIMPLEGRAPH::TightenHasseDiagramは主語のグラフの他,引数でもう一つのグラフtajugraph3への参照が渡されている.問題の箇所にはこの2つのグラフの節点リストを取り違えるというミスがあった.重婚グラフ検定の論理は一度総点検する必要がある.もし,複数のグラフを併用する必要があるのであれば,2つのグラフを引数で渡して,関数自体はクラスの静的関数とした方がよいと思う.一応現状で正しい論理が実装されているという仮定で修正を入れてみよう.以下の関数を改造した.

  1. BuildTightHasseDiagram
  2. TightenHasseDiagram
  3. sortComponentList

「完全参照リスト管理をサポートする」というオプションをOPTIONSからSPECIFICATIONに格上げし,REFERENCELISTCONTROLとリネームした.⇒裏オプションでも動作することを確認しておこう.⇒参照リスト管理は現在,黒子としての存在に留まり表では活動していない.

SUPPORTSIMPLEGRAPH「SIMPLEGRAPH抽象グラフ検証系をサポートする」というオプションで抽象グラフ検証系を止めてみる.⇒実装した.グラフ検証系なしでも思ったより動作している.ZTシステム構成図7.ZELは難なく開けた.ただし,一部の検査論理は停止している.抽象グラフ検証系でサポートしている機能には以下がある.

  1. GENEBOX::CheckInverseCycle 危険対枝グラフの循環検定
  2. TOPOLOGY::TestInevitableMultiZero 重婚同類グラフの循環検定
  3. TRIBELIST::ShiftDirectAbsolute 絶対世代番号に基づきカードシフト
  4. TOPOLOGY::FindJikusenAncestor 血糖軸線図のサポート
  5. TOPOLOGY::MakeLinearEdgeList 系列枠の全順序を決定する

SUPPORTSIMPLEGRAPHオフのバージョンで渋沢一族8.ZELの全体図を#211 大川平兵衛の息子で開こうとして,TRIBEBOX:GetAlternativePrimeNodeで停止した.系列優先ノードが決定できない.この系列は#2501 先祖 #950 渋沢 長登(宗安)で,最初は#1038 尾高 平九郎を優先ノードとする親元関係で決定していたはずなのだが,#956 渋沢 やへの婚姻関係に切り替わっている.

最初のGetAlternativePrimeNodeで切り替わり,2回目で失敗しているという構図だ.何が変わったというのだろう?⇒いや,変化していないために停止している.これはこのカードがTRIBEBOX:DecidePrimaryNodeでリジェクトされていることを意味する.つまり,DecidePrimaryNodeとGetAlternativePrimeNodeが相反している.⇒DecidePrimaryNodeに逆婚姻関係を導入して解決した.

どうもDecidePrimaryNodeのできが悪過ぎる.今度は源氏:明石中宮で停止した.この関数は全面的に書き換えた方がよい.この関数では現在の優先ノードのチェックだけを行い,不可のときは自分で代替候補を探すのではなく,GetAlternativePrimeNodeに任せるようにした方がよい.優先ノードチェックの要点は,①GetRealnodeで優先実ノードを確保できるかどうか?②主系列と従系列の基準ノードに対する位置関係,③系列種別が合っているかどうか,だけでよい.

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というクラスはかなり融通の利かないクラスでサイズは固定でそのサイズも持っていない…

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

完全参照リスト管理と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+外部依存コード