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

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が削除されたことで参照している実ノードが削除されたことには必ずしもならないというのは正しいと思われるが,現行ではゴミ箱に入るときはすでに死亡しているという前提なので矛盾が生じる.どう解決すればよいか?

アプリ終了時のMemoryBlockCountとMemoryBlockSizeの値が整合しない

アプリ終了時のMemoryBlockCountとMemoryBlockSizeの値が実際のカウントと整合しない.⇒ゴミ箱の廃棄をCouplingの削除の後に実行するようにした.ゴミ箱はCouplingの生成に先立って生成されているので,この方が筋が通っている.また,ゴミ箱とNringが存続していないと,メモリの使用状況が正確に把握できない.この変更を行ったことで,TRASHCAN:CheckWasteCountでも不整合が検出されるようになった.オブジェクトのカウントで32の差異が発生している.

この差異がCouplingの削除によって発生していることは明らかだ.つまり,この間に32個のオブジェクトが紛失していることになる.おそらく,これらはフロート化されたまま放置されているものと思われるが,そうではないかもしれない.フェーズをENDOFAPPLICATIONに切り替えるのをゴミ箱廃棄直前まで延期してこのエラーは解消した.

★ThrowCan 前 totalblock=0 totalsize=0 MemoryBlockCount=10037 MemoryBlockSize=8098118 TotalBlockCount=10037 TotalBlockSize=8098118

★ThrowCan 後 totalblock=0 totalsize=0 MemoryBlockCount=1 MemoryBlockSize=1180 TotalBlockCount=10037 TotalBlockSize=8098118

ThrowCan後に残っているMBCount=1, MBSize=1180とはゴミ箱のことだが,なぜこれが残ってしまうのかはよく分からない.⇒NODULE:operator deleteが呼び出されていない.強制的にNODULE::operator deleteを実行してやれば確かに消える.なぜCANだけがこのような動作になるのか?その理由が分からない.ゴミ箱TRASHCANは

class TRASHCAN : public ARRAY<MAXTRASHCAN>
class ARRAY : public nodule
class nodule : public NODULE {
static TRASHCAN *CAN; // ゴミ箱(廃棄オブジェクト分別収集)

のように定義され,メモリ上では

TRASHCAN *nodule::CAN = NULL; // ゴミ箱(廃棄オブジェクト…

のように配置されている.通常のオブジェクトは一度ゴミ箱に入ってから最終処分される.ゴミ箱自身はゴミ箱に入っていないということが影響しているのだろうか?ゴミ箱はNringに入っているか?⇒入っている.ゴミ箱廃棄直前ではNringにはこれしか入っていない.TRASHCAN:ThrowCanとdelete CANは別々に実行されている.

delete CAN

を実行したとき,NODULE::operator deleteが実行されないという点を除けば,すべての動作が整序している.CANに特異な点があるとすれば,noduleクラスの静的メンバーになっているという点だが,静的とは言え,ただのポインタなので中身のオブジェクトの生成・削除とは関わりがない…何かコンパイラが誤動作しているか,誤認しているのではないかという気がするのだが,おそらく,CANをNODULEの静的メンバーにしてしまえばこういうことは起きないのではないかという気もする.試してみよう.一度バックアップを取ってから…

その前にこのシステムがゴミ箱がなくても動作することを確認しておいた方がよいのではないか?USERECYCLESYSTEMというオプション(SPECIFICATION)はあるが… ⇒ゴミ箱がなくても動作には支障はないが,MemoryBlockCount=118005 MemoryBlockSize=25116470が丸残りになってしまう.現物はdeleteでメモリからは解放されているはずなので,残っているのはただの数字と思われるが…

この数字はnewで生成されたすべてのオブジェクトを含んでいる.つまり,カウントがまったくデクリメントされていない.ということはおそらく,NODULE:operator deleteが一度も実行されていないことを意味すると思われるのだが… どう考えればよいのだろう?

どうもこれは思ったより難しい問題であったような気がする.「ゴミ箱を生成しない」システムでアプリ終了しようとしたら,ReleaseReflistでエラーが発生した.NODULE::operator []が動作しないというエラーだ.対象ノードはすでに削除されているので,NODULEの仮想関数テーブルが潰れている.参照リスト管理ではこれまでオブジェクトの生死に関わりなく参照管理を行うという方針で進めてきているが,それもこれもゴミ箱のようなシステムがあったお陰と言ってもよい.つまり,nodule::operator deleteで寸止めしているため,仮想関数テーブルが辛うじて使える状態になっていたのだろう.

オブジェクトの生死を問わないというのは参照リスト管理に限った話ではない.UNDOもそれがなければ成立しない.削除されたオブジェクトを復活させようという話なのだから… 従って,ZTではNODULE:operator deleteが作動しないというのがノーマルであり,作動しているとすればそれは例外であると考えなくてはならない.NODULE:operator deleteが作動する条件というのはあまりはっきりしないが,おそらく,「一度deleteされ,もう一度newによって再生されたオブジェクト」なのではないか?⇒これは確認してみなければ分からない.

いずれにしても,nodule::operator deleteの中からNODULE::operator deleteを実行することの可否は,少なくともこのシステムでは死活問題だ.死者の延命・復活という手段がなければZTシステムは成立しない.ただし,参照管理リスト上にすでに死亡したオブジェクトが入っているというのは少しおかしいような気がする.なぜこんな状態になっているのかということをまず,先に調べた方がよいと思う.そのノードはどこで死んだのか?そのノードを参照するリンクが残っているのはなぜか?

障害が起きているノードは#74のMARGBOXだ.このノードはEraseTreeViewの中で削除されている.CleanSlotは実行されているが,参照管理に使っている特殊スロットは対象外で,参照リストには12個も参照が残っている.しかし,ReleaseReflistでMARGBOXの参照リストを削除する段ではすでにリスト要素はゼロになっている.つまり,問題は死亡したノードに参照リストが残っているという点だけだ.しかし,これは仕様であり,譲ることはできない.

従って,結論的には「nodule::operator deleteの中からNODULE:operator deleteを実行するという解決策はない」とするしかない.NODULE::operator deleteでやっていることは以下の一行だけだ.

if (!nptr->Shadow && !nptr->LIFE && (INDELETEPHASE || !nodule::CAN)) freeblock::_delmem(nptr);

これをnodule::operator deleteで実行すればよいというだけの話なのではないか?⇒いや,結構難しい.ThrowCanでゴミ箱のオブジェクトをdeleteしようとしてもできない.フローでは_delmemをパスしているので,メモリ上には残っているし,仮想関数テーブルも活きているようだが delete wasteが実行できない.おそらく,nodule::operator deleteを再実行することが禁止されているのだろう.つまり,nodule:operator deleteとNODULE::operator deleteの2段構成はどうしても不可欠ということのようだ.

「死亡したノードに参照リストが残っているという点」という点をもう少し考えてみよう.本来死亡したノードへの参照はデストラクタの中ですべてクリアされるというのが本筋であり,参照カウントがゼロになったノードでは参照リストを削除することができるとすれば,通常なら~noduleで参照リストを解放できるはずだ.⇒うまくいった!~noduleで参照カウントが残っているオブジェクトは存在しない.従ってすべての参照リストはその時点で削除される.⇒もう一度,「ゴミ箱なしの動作を確認@20201214」を試してみよう.

今回はnodule::operator deleteの中から直接NODULE::operator deleteを呼び出すのではなく,処理の対象を限定して,Shadowを持たないノードのうち,ゴミ箱に入らないものでかつLIFEが空となっているものだけを_delmemするようにして動作するようになった.ただし,一つだけ問題がある.ThrowCanを実行後にはMemoryBlockCount=0 MemoryBlockSize=0のようにきれいにクリアされているが,resetNringの手前でMemoryBlockCount=-1 MemoryBlockSize=-156になる.⇒理由は明らかだ.その前にrootnodeを削除しているためだ.

rootnodeにはNODULEのメンバーのnodule nullpointが入っている.このオブジェクトはnodule クラスの内部オブジェクトだが,静的メンバーでnewで生成されたものではないので,_delmemする必要はない.どこでそれを判断すればよいか?MemoryBlockCountは_delmemで更新しているので,ここでゼロ復帰でよいのではないか?このオブジェクトがnullpointであるということは,snum=0であることで判定できる.また,ntype=NOTAUTODELETEであることからも識別できる.⇒OKだ.かなりクリーンなイメージに近づいてきた.

UNDOシステムはゴミ箱がなくても動作しなくてはならない.確認しておこう.⇒ReferenceControlで参照カウントと参照リストの不一致が発生した.ゴミ箱を復旧して,MemoryBlockCountとMemoryBlockSizeが実際と合わないという事象が復活してしまった.ファイルを開いて終了では発生しないので,これまで見過ごされていた可能性はある.

アプリから完全独立な抽象UNDO機能系

UNDOシステムの理想はアプリケーションから完全に独立な抽象機能系だ.UNDOはアプリが何をやっているのかを知らなくても対象オブジェクトの形式的な解析だけで保全する範囲・手順を決定できるものとする.そのようなものが存在するとすれば,アプリケーションではUNDOをまったく意識せずに自由にアプリ固有のコードを書くことができるし,システムメンテナンスのコストも大幅に削減されるだろう.そんなことが可能だろうか?もちろん,原理的には可能だ.コマンド実行ごとに系全体を「バージョン」としてバックアップするだけで簡単・確実に実現できる.ただし,この方式はコスト的に見て現実的ではない.

たとえば,テキストエディタのようなものであれば,テキスト編集は①挿入と②削除の2つのプリミティブな操作に還元できると考えられるから,①の場合は,位置と挿入文字列,②の場合は位置と削除文字数を記録するだけで基本的なUNDO機能をサポートできるかもしれない.これは言ってみればミニマムなUNDO系の実現と言える.しかし,ZTの場合なら,たとえば「カード登録」という一つのコマンドで,複数の関係(親子,結婚,兄弟など)の変化が同時多発的に発生し,それらが相互にどう影響し合うのかもにわかには判別できない.

しかし,完全な参照管理が実現された超クリーンで透明なシステムであればひょっとしてそのようなことができるのではないかと期待することはできる.ZTのUNDO機能はエラーが発生したときにUNDOで戻ってそれを再現できる程度には強力だが,まだそこまでの抽象度には達していない.ともあれ,まずいま出ている目先の問題から片付けることにしよう.どうもこの不良はシステム的なもの(設計不良)というより,むしろバグ(論理不良)ではないかという気がしてきた.少なくとも見た限りではNring上に同一snum(システム通番)を持つ複数のオブジェクトが存在しているように見える.まず,この点を確認してみよう.

誤読していた.Nring上には重複は存在しない.重複していたのはUNDONODEからの参照先だ.ただし,UNDONODEは複製ノードと一対一に対応していたはずだから,やはり重複があるというのは誤りなのではないか?⇒複製ノードが同一snumを持つ場合は当然あり得る.もちろん,実体が同じなら重複していることになるが… 問題なさそうだ.

「Nringとゴミ箱にダブル登録」という問題を考えなくてはならないが,その前にカード削除で新たにCARDLINKとMARGLINKが生成されるという動作を調べてみよう.⇒どうもまだ勘違いしているようだ.CARDLINKとMARGLINKがNringに残る問題と「Nringとゴミ箱にダブル登録」問題は完全に一つの問題だ.わかり易くするためにはダブル登録を止めるしかない.ゴミ箱に入っているオブジェクトはリサイクル可能というのが本来の趣旨なのだから,Shadow付きオブジェクトはリサイクル不可としてNringに置くべきなのではないか?⇒そのように決定.

nodule::DELETEDの値を{-1, 0, 1}ではなく,明示的に{PROLONG, EXISTING, DEAD}で示すようにしておこう.⇒nodule::operator deleteでShadow付きオブジェクトはゴミ箱に移動しないように修正して,CheckWasteCountでカウント不一致になった.⇒これまではNringのカウントとしてアクティブノードだけを計上していたが,延命ノードの重複が解消したので,全ノードをカウントする必要がある.⇒これで中間の集計はすべて一致するようになったが,アプリ終了の出口関数で不一致になった.ゴミ箱の中身が25点少なくなっている.

この差分はReleaseRefListの実行によって起きている.いや,違う.EraseFamilyTreeの中だ.UNDOSYSTEM::resetUndoChainだ.⇒UNDOSYSTEM::CheckUndoChainでエラーが出た.親ノード不在(!undo->address->getpnode())というエラーだが,これは「Nringとゴミ箱の重複登録を禁止@20201213」の結果で問題ない.ゴミ箱に入っているということは親を持っているということになるが,Nringに入っているPROLONGノードは親を持たないフロート状態になっている.

▲初回のEraseFamilyTreeのとき,すでにMemoryBlockCount=32 MemoryBlockSize= 143564がリザーブされている.常駐させるか,ないし,取得を遅延させるか.

▲UNDONODE::DisposeでShadowオブジェクトを直接delmem_でメモリからパージしている.実際にはこの前段でfreeblockはすべて解放されているので,実際の効果はないのだが,あまりよいマナーではない.ここはfreeblockのアドレスを取得して delete するべきだ.

UNDONODE::DisposeでPROLONGオブジェクトの始末を付けるようにした.⇒エラーはすべて解消した.手順としては,フリーブロックの解放はUNDOSYSTEMのリセットより後にするべきだと思う.順序を入れ替えてみよう.⇒UNDONODE::Disposeでshadowを保持するfreeblockをdeleteしようとしてエラーになった._getblock_でfreeblockの逆引きができない.shadowはfreeblock::getmemで取得したアドレスなので,_getblock_でfreeblockのアドレスを取れるはずでは…

いや,違う.勘違いしていた.shadowは完全なフリーメモリだ.つまり,getmemで取得し,delmem_でリリースするという類のものだ.delmem_は下位関数としてdelmemを呼び出しているが,バランスが悪い.getmemの対向としてはdelmemの方がよい.名前を取り替えておこう.⇒現行ではアプリ終了時にゴミ箱内の廃棄物を処分している.freeblockもリサイクル可能なのだから,中間で廃棄する必要はないのではないか?さらに言えば,最終的にはfreeblockもゴミ箱に落ちるのだから,アプリ終了時にはゴミ箱の処分だけやればよいのではないか?

上記の通り,freeblock::ReleaseFreeBlockを止めてTRASHCANに任せたところ,freeblockが2つ残留した.#34と#36だ.基本的にfreeblockは作成者が責任をもって始末すべきものだ.⇒#34はBUNSFILE::openで取得しているファイル読み込みのためのバッファだ.この関数が呼ばれるたびに,前回取得したメモリを解放しているので,関数の出口で解放してよいのではないか?closeという関数はある.BUNSFILE::closeには,「オブジェクト存続中はバッファbunsbuffを維持する」という説明が付いている.~BUNSFILEで解放している.

QUICKDBはKAKEIZUの埋め込みオブジェクトなのでデストラクタは呼び出されない.KAKEIZUはQUICKDBを2つ持っているが,これらをnewで生成してやれば,明示的にdeleteすることもできるのだが… ファイルをクローズしてもバッファを使うというのは考え難いので,Closeで廃棄しておこう.closeはCloseFamilyBaseのタイミングで掛かってくるのでまったく問題ない.一度ここでバックアップを取っておこう.

freeblockでMemoryBlockCountとMemoryBlockSizeが更新されていない.totalblock, totalsizeは更新されている.⇒いや,totalsizeはゼロのまま変化していない.⇒コードを追加した.totalblockはfreeblockのカウント, totalsizeはfreeblockのヘッダ部を除いたフリーメモリの正味サイズの合計だ.Couplingを削除してMemoryBlockCount=33 MemoryBlockSize=143756まで落ちる.メモリリークの可能性はあるが,CrtDumpMemoryLeaksのダンプはもっとずっと小さな数字だ.

★DRAWSTAGE totalblock=42 totalsize=169966 MemoryBlockCount=10033 MemoryBlockSize=6975606 TotalBlockCount=10033 TotalBlockSize=6975606

★INITIALSTATE totalblock=0 totalsize=1291806 MemoryBlockCount=10037 MemoryBlockSize=8098118 TotalBlockCount=10037 TotalBlockSize=8098118

★delete Coupling totalblock=0 totalsize=1291806 MemoryBlockCount=32 MemoryBlockSize=143564 TotalBlockCount=10037 TotalBlockSize=8098118

アプリ終了時でtotalblock 0 totalsize 1291806 MemoryBlockCount 32 MemoryBlockSize 143564 のメモリが紛失していることになる.freeblockではCloseFamilyBase→EraseFamilyTreeの入口で不一致が発生している.KAKEIZU::closeFamilyBaseの中で不一致になる.qfile.closeで起きるようだ.バッファのサイズが変化しているのではないだろうか?⇒freeblock::delmptrでtotalsizeを更新していなかった.freeblockの数字は合うようになったが,MemoryBlockCountは33も残っている.サイズで143756の残がある.

わかり易くするためにfreeblock::delmptrとfreeblock::delmem_を廃止して,_delmem_とdelmemに統合した._delmem_に回ってくる分に関してはMemoryBlockCount,MemoryBlockSizeの更新が入っていなかったので,その論理を追加したところ,MemoryBlockCount=-9 MemoryBlockSize=143588という不整合が出た.MemoryBlockSizeの減量をオブジェクトsizeから取得するようにして,MemoryBlockCount -9 MemoryBlockSize -33770という結果になった.

これはおそらくdeleteで一度ゴミ箱に入ったものが再度deleteされているためと考えられる.freeblock::_delmemやfreeblock::delmemで削除される場合は実際にメモリからパージされているので問題ない.NODULE::operator deleteではfreeblock::_delmem(nptr)でメモリ解放しているので,_delmem_で引くというのはやはり間違っていると思う.つまり,消し忘れが33件残っているということではないだろうか?

清掃工場で生ゴミの処理がストップしている

カードを削除後,アプリ終了してゴミ箱の中身を処分する段階で,「生ゴミだ!」というエラーが出ている.「生ゴミ」というのは,UNDOでバックアップしたオブジェクト・イメージが未処理の状態で残っていることを意味する.生ゴミはCOUPLING::EraseFamilyTreeでReleaseShadowによって処理されているはずだが,どうもそれが機能していないようだ.昨日のログでは「始業時バックアップではこのようなエラーは出ていない」としているが,これは事実誤認で,その前日2020/12/10の始業時バックアップでもすでにこの現象は出ている.

2020/12/09には「ここまでの修正をすべて現状でフィックスしておく」として仕掛りになっているすべてのOPTIONSを一掃してしまっているので,これ以前のバージョンに戻ることは難しい※.UNDOの論理は結構複雑で動作の解析も難しいが,「後戻りしない」と決めたのだから,このまま進むしかない.※⇒【2020/12/08の始業時バックアップでも同じ現象が出る.】⇒nodule::ReleaseShadowで

if (nod->Shadow && !nod->Shadowed()) {

の論理を逆転して,

if (nod->Shadow && nod->Shadowed()) {

のように変えたら,エラーは解消した.どうもShadowed()という関数の意味を逆に解釈しているのではないかという気がする.ただし,この修正を行うことでTRASHCAN::ThrowCanはエラーなしに終わるが,その後の delete Coupling でUNDOSYSTEMを解体するところでsetNringのエラーが発生する.どちらにしてもこのエラーはUNDOSYSTEMを解析しないと対処できないので,少し調べてみよう.

image

この図面は縦長なので,タブレットを90度回転させて撮ってやろうと思ったのだが,タブレットにはゼルコバの木を一度もインストールしたことがない.最新リリース版は一度VAIOにインストールしているので,簡単に終わるはずだったのにエラーになってしまった.そう言えばVAIOのときにも何かあったなと思い出してログを引っ張りだ出そうとしたが,あいにく現状ではOpen Live Writerの全文検索ができない.

しかし,それほど昔の話ではないのでサイトで検索することにした.ChromeのウェブストアにGoogle検索を使ったプラグインがあったので使ってみた.サイト内検索ができるというのはかなり有り難い.結局,VS2017に対応する再配布パッケージが必要ということがわかったので,Visual C++ 2017 Redistributable (x86)をマイクロソフトサイトからダウンロードしてインストール,これでZTもインストールできた.

UNDOでは何かコマンドを実行しようとするときには,事前に更新の予測されるオブジェクトのイメージのバックアップを取って保全する.ただし,事前と事後という区分けはややあいまいで,あるコマンドの事後は次のコマンドの事前でもあるから,コマンド実行後にも変化があればそのイメージが保存される.事前に予測して保全するというのはややムダのある動作※だから本来ならコマンド処理の中でデータが更新される直前にその部分だけをバックアップすればよいのだが,そうなるとコマンド処理とUNDO処理の境界が消えてとんでもなくややこしいものになってしまう虞があるため,現行のような仕様になっている.

※多分現行では事後に保全データをチェックして変化なしの場合にはそのデータを破棄していたはずだ.

UNDOSYSUTEMは一つのコマンドに対して一つのUNDOCHAINノードが対応したコマンドのチェーンを持っている.一つのUNDOCHAINはそのコマンドで保全された個別オブジェクトの複製イメージを保持するUNDONODEのチェーンを持っている.ここで問題なのはこのオブジェクトのイメージはどういうデータ構造になっているのか?という点だ.複製ノードをすべてNODULEオブジェクトとして生成することは可能だが,コストが掛かり過ぎる.それではどうしているのか?

オブジェクトの複製はfreeblockで必要サイズのメモリを確保し,そこにオブジェクトのイメージを転記している.freeblockはEraseFamilyTreeの中でまとめてパージされている.また,複製イメージへのリンクはベタ参照なので参照管理とは無関係に削除できる.UNDONODEはチェーンを構成するためのzenpoUndoとkohoUndoという2つのリンクスロットの他,実ノードの物理アドレスを指すaddressと複製ノードへのべた参照を格納するshadowを持っている.

オブジェクトは生成から消滅までの間に物理アドレスが変化することはないので,UNDONODEから実ノードへのaddress参照は状態に関わらずつねに有効だ.UNDONODEと複製ノードは1対1に対応しているが,一つのオブジェクトが複数の複製ノードを持つ場合があり得る.つまり,実ノードと複製ノードの関係は1対多であるため,NODULEのShadowという特殊スロットでチェーンを構成し,これを管理している.

オブジェクトは複製ノードを持っている間,つまり,Shadowが空でない間は削除されないということになっている.従って,削除されてもゴミ箱には入らず,フロート状態でNringに繋がっていると考えられる.(実際にそうなっているかどうかは分からない.前にゴミ箱とNringに二重登録されているという話があった)さて,これで大体のイメージは掴めたので,実際にどういう動作になっているのかを確かめてみよう.

まず,削除などの操作を一切行わず,ファイルを開いて閉じるだけの動作を確認しておこう.⇒アプリ終了時,CallSetCouplingPtrの入口ではアクティブオブジェクト 32(19クラス)というコア骨格木と呼んでいる状態になっている.ゴミ箱には10003個(20クラス)の廃棄オブジェクトが入っている.カードを3枚削除して終了した場合を見てみよう.⇒Nringには,281個(25クラス)のオブジェクトが入っている.オブジェクト数で249,クラス数で6増加している.内訳で見ると,

  • nodule 114
  • UNDOCHAIN 2
  • UNDONODE 53
  • MARGLINK 10
  • NODEREFLIST 55
  • CARDLINK 15

となっている.これらはすべてUNDOに関わりのあるノードと思われる.UNDOチェーンをパージするコマンドがあったはずなので探してみよう.UNDOSYSTEM::resetUndoChainという関数がある.これを実行したらどうなるか?⇒完全にコア骨格木を復元できた.ただし,その分ゴミ箱の中身は増えて10410個(22クラス)に変化している.何もしないときのオブジェクト総数は10032,3カード削除したときには407個増加して10410.仕様的にはこれで動作は完全と言えるところだが,なぜ生ゴミが出るのか,ないし,なぜReleaseShadowは機能していないのかというナゾはまだ解き明かされていないので調べておこう.

EraseFamilyTreeでfreeblockをパージする前の状態を見ると,Nringの中身はコア骨格木の状態,freeblockはゼロ,ゴミ箱の中身は空だ.しかし,これでは辻褄が合わない.1万個近いオブジェクトがどこかに消えてしまっている.そんなバカな… ⇒アプリ開始から終了までの間にEraseFamilyTreeは複数回呼び出されている.初回がまったくの空であるのは当然だ.アプリ終了時,freeblock解放の直前では,Nringは1396個(21クラス),ゴミ箱8639個(19クラス)だ.Nring1396個のうち,1361はNODEREFLISTで,3個がfreeblock,残り32個がスケルトンだ.この3個のfreeblockがUNDOで生成された複製ノードと思われる.

いや,違う.上の数字は「何もしていない」ときの数字だ.このfreeblock3個が何の用途で作られているのかは分からないが,UNDOの複製ノードでないことは確かだ.実際,この3個を除けばトータル10032となり何もしないときのオブジェクト総数と一致する.freeblockはReleaseFreeBlockでメモリから完全にパージされてしまうので,ゴミ箱にも残らない※.カードを3つ削除した結果を見てみよう.Nringには1648個(26クラス)のオブジェクトが残っている.ゴミ箱には8819個(19クラス)入っている.※⇒【間違っている.ENDOFAPPLICATIONフェーズまではメモリからはパージされず,ゴミ箱に残っている.】

Nring上のfreeblockの個数は3で変化していない.UNDOCHAIN x 2, UNDONODE x 53がUNDOチェーンのために追加されている.目に付くのはCARDLINK x 15,MARGLINK x 10 が入っているという点だ.ゴミ箱にはCARDLINK x 189,MARGLINK x 88 が入っているが,これらは現物の人名リンクと結婚リンクと考えられるので,Nringに入っているのはすべてUNDOのために新たに生成されたもののように思われる.複製はfreeblockのメモリ上にコピーされたものと思っていたが,どうも現物のコピーを使っているようだ.

実行されたコマンドは「カード削除」の1件だけだが,UNDOCHAINは2個生成されている.これは「カード削除」の前と後が保全されているためで,UNDOCHAINは存在するとすればつねに2個以上になる.CARDLINK 15, MARGLINK 10 が複製ノードだとしても,UNDONODE 53 をカバーすることはできない.プリミティブなオブジェクトとして無名のnoduleが114個もあるので,これらの中にそれらが含まれている可能性はあるが,どうやってそれを分離することができるだろう?

ゴミ箱にはfreeblockが39個入っている.これらがすべてUNDOの複製ノードであるとすると,CARDLINK 15, MARGLINK 10と合わせて64個となり,今度はUNDONODE 53 を超過してしまう.どうやったらこの帳尻を合わせることができるのだろう?ゴミ箱とNringのダブリは25でこの数字はDELETEDがNRING_INVALIDATEであるノードの個数と同じだ.この数はNring上のCARDLINKとMARGLINKの総数に等しい.つまり,これらはすべてNRING_INVALIDATEとされるようなノードということになる.整理してみよう.

  1. Nring上のノード総数 1648 
  2. ゴミ箱上のノード総数 8819
  3. Nringとゴミ箱の二重登録 25 ー非アクティブノード
  4. Nring上のCARDLINK 15 + MARGLINK 10 = 25 =(3)
  5. Nringとゴミ箱の合計(ダブリを除く)10442
  6. アプリ終了時のノード総数 10442

これで一応帳尻は合った.問題はUNDONODEの53という数字の内訳だ.⇒内訳もわかった.CARDLINK x 30, MARGLINK x 18, PARTIALNAME x 3, longtable x 2だ.

保健所の調理場検査をパスするための条件

NODULEにCleanとdoCleanという2つの仮想関数を導入した.これらは,ファイルをクローズしてシステムがコアスケルトンの状態に戻ったとき,アクティブな全オブジェクトを対象にクリーンアップを実施するための関数だ.これによってシステムは完全に初期化され,INITIALSTATEの状態に戻る.言ってみればINITIALSTATEというのは,すべての食器と調理具を洗い上げて食器棚に格納した状態と考えられる.それに続くINITIALIZINGフェーズはデータベースに接続し,系図データを外部からロードしているので,早朝長靴を履いて市場に出かけ野菜や肉・魚などを買い調える段階だろう.とすれば,INITIAIZEDは食材を洗ったり,カットしたりなどの下ごしらえに相当する.ここでシェフの登場を待って,TOPOLOGICALSORTからがいよいよクッキングの本番であり,シェフの腕の見せ所だ.厨房もこのくらいクリーンになっていれば,保健所の検査も通るかな?

ZTシステム構成図7.ZELを開いて,3つのカードを選択し,一括削除するとTRIBEBOX::setTribeRealnodeで(CheckTribe(funcname) && !OnmergeCardLink)エラーになる.削除されたのは#61 NLIST < LISTNODE, CID>,#187 GENEBOX,#230 GENEBOXだ.カード名がクラス名になっているのでかなり紛らわしいが,NAMEBOXの#2211 GENEBOX(0)が削除され,このノードが優先ノードとなっている系列枠#8440で先祖ノードと優先ノードがともに不在となっている.⇒#187 GENEBOXは一人系列先祖なので削除されると,その系列は消滅する.CHAOTICSTATEでは描画要素(系列枠・人名枠・結婚枠など)に関わる一切の操作・検査は無効とすべきだ.この修正はロジック本文を過剰に煩瑣なものにするので,むしろ関数の冒頭でゼロ復帰するようにした方がよい.その方が修正も極小で済む.

▲UNDOでは参照の付け替えを頻繁に実施しているため,参照リスト管理と矛盾が発生する.この件は保留とする.

C++のベタクラスからnoduleオブジェクトを参照している.たとえばMETRIX→ NAMBOX,metrix→ TREEVIEWなどがある.これらは廃止すべきだ.⇒METRIX→ NAMBOXを廃止しただけで広範な影響が出ている.理由はよく分からないが代替ロジックで置き換えてみよう.TREEVIEWは「基準ノードの代表人名枠」を持っていると思っていたが,持っていなかった.グローバル変数のBASEBOXというのもある.これもまずいと思う.TREEVIEWにNAMEBOX baseboxを設置し,SetBaseBoxでその値を設定するようにしただけで解決した.ただし,非参照カウントの残留が出ている.TREEVIEWからの参照だ.⇒TREEVIEW::CleanSansyoに項目を追加した.

metrix→ TREEVIEWも同様に廃止した.⇒ビルドは通ったが,TREEVIEW::ViewRectでエラーが出るようになった.⇒クリーンビルドして動作するようになった.グローバル変数のBASEBOXも廃止した.

仮想関数Disposeの実装を義務付ける必要がある.Disposeの実装を持たないクラスのデストラクタからDisposeが実行されると,nodule:Dispoeが実行されることになり,~noduleから実行されるnodule:Disposeとダブってしまう.実装を義務化するためには共通ヘッダ部で関数定義しておくのが早いのではないか?⇒却って裏目に出てしまった.未解決の外部シンボルが山ほど出てくる.⇒ARRAY,NLIST,nlist,BASETABLEの派生クラスを個別に点検するしかない.

▲ゴミ箱については何も修正していないつもりだが,カードを削除して終了すると,「生ゴミだ!」というエラーが出るようになった.DELETEDに-1という値が入っている.ノーマルなゴミの場合は1だ.始業時バックアップではこのようなエラーは出ていない.

食器棚に汚れた食器が並んでいるというのは

「フェーズ」はプロセスフローを適当に区切ってそれぞれの区間に名前を(勝手に)付けたものだ.1996年からゼルコバの木の開発に取り組んでいるが,「フェーズ」という概念を使い始めたのは2017年頃からと思われる.グローバル変数PHASEには「ビルド時の誤動作を防止するため@20170712」という説明が付いているしコメントには「処理モード,デバッグ用」とあり,便宜上のものと考えられていたフシがある.しかし,秩序ある解体が可能となるような完全にクリーンなシステムを構築するためには,この概念が不可欠であることが次第にわかってきた.

これまでに少なくともINITIALSTATEという区間はコア骨格木(接続関係のみで連結されたモジュール構造の核となる部分)だけが存在する状態であり,その次のINITIALIZINGという区間ではノードは追加されているが,「参照」は発生していない状態として再定義された.INITIALSTATEはアプリ起動時の初期状態で,ファイルがクローズされたときにはこの状態に戻る.INITIALIZINGはデータベースに接続して系図データがロードされた状態,INITIALIZEDはすべてのデータが展開されて系統並び替え(TOPOLOGICALSORT)を待つだけの状態だ.

「初期状態」が完全にクリーンなものになっているということはデバッグの成否と密接な関りがある.デバッグでもっとも肝要なポイントはその事象が再現できるということであり,再現可能なバグは原理的には必ず解決することができる.しかし,バグを再現する条件がすべて明らかになっていたとしても,「初期化」が不十分であればその検定の成功は覚束ない.初期状態が不定では「決定性」という条件が満たされないからだ.すべてのオブジェクトは生成時にそのオブジェクトクラスのコンストラクタによって完全にクリーンアップされる(べきである).ゴミ箱に入っている廃棄オブジェクトのリサイクルでも形式的にはnewで生成されるので,必ずコンストラクタが実行される.

ここまではよいのだが,ファイルがクローズされてコア骨格木の初期状態に戻るときにはこのような汎用的な仕組みが存在しないため個別にクリーンアップを実行しているものの,現状ではきわめて不十分なものであることが判明した.たとえば,longtableという長整数配列オブジェクトはすべて使いっ放しの状態になっていた.ファイルを再オープンするときには通常それぞれのモジュールの初期化を行っているので特に問題は発生していなかったが,今回発現した(!card->selectflag)の問題はこの欠陥が露呈したものだ.初期化が完全ならもちろんこのような問題は発生しないが,仮に食後に下げた食器を流し台に積み上げておいて調理の直前に洗って水切り台に移しそのまますぐに使い回すというのでよいとしても,食器棚に汚れた食器が並んでいるというのは頂けない.

現行ではこのようなクリーンアップに用いる汎用関数として,以下の3種,①clean, ②clear,③Clearが使われている.①のcleanは基底クラスNODULEの仮想関数で,主にコンストラクタから呼び出されることを想定している.②clearはinitializeなどの初期化の場面で使用されるが,初期化の範囲がモードによって異なる場合には,③Clearを全パートのクリアとし,②clearをその下位関数として使うなどのように使い分ける.

INITIALSTATEに戻るときなどに必要なクリーンアップを行う関数として,以下のような仕様の仮想関数Cleanを用意することにする.Cleanでは(1)自オブジェクトの固有データ部をゼロクリアする,(2)自オブジェクトに接続しているすべてのオブジェクトにつき,関数Cleanを再帰的に実行する.(3)この関数は共通ヘッダ部で汎用的に定義される.関数Cleanを実行すると,そのオブジェクトの下流のすべてのオブジェクトの固有データ部がゼロクリアされる.ZTシステムではすべてのオブジェクトはスロット部+固有データ部(だけ)からできているので,これより徹底したクリーンアップはあり得ない.実装してみよう.

現行ではNODULEクラスの共通ヘッダマクロには以下の3種がある.

  1. COMMONHEADERBASIC classname, classid, getclassize, getcid, VSLOT, _SLOt, SLOT, _slotsize, _vslotsize, dataptr, datalen, CleanSlot, setpid
  2. COMMONHEADERSHORT ①+operator new/delete
  3. COMMONHEADERPART ②+CleanSansyo, Dispose, dump

Clean()は現行のCleanSlotに近いので,COMMONHEADERBASICで定義してみよう.⇒実装した.CallSetCouplingPtrでCouplingをdeleteする直前でCoupling->Clean()を試してみたところ,すべてのlongtableのcountがきれいにクリアされた.一応マクロをコピーしてステップ実行して動作を確認しておこう.⇒動作は問題ないようだが,大きな問題がある.固有データ部を完全クリアすると,COUPLINGの保持するウィンドウハンドルのような環境パラメータまで失われてしまう.

このような維持しなくてはならないパラメータがまだ他にもあるのか?それともCOUPLINGだけの問題なのかを切り分けるためCOUPLINGを除外してその下流全体をCleanしたところ,データベースをオープンするところでBCMDTABLEのエラーが発生した.cmdtableが存在しないというエラーだ.BCMDTABLEはC++のベタクラスで,QUICKDBのメンバー,QUICKDBはKAKEIZUのメンバーだ.これらはKAKEIZUの固有データ部にあるので,全クリアすれば当然消えてしまう.

一般のオブジェクトはほとんど全クリアして問題ないと思われるので,全クリアしないオプションを選択できるようにしてみよう.まず,

virtual void NODULE::doClean(void){ memset(dataptr(), 0, datalen()); }

という関数を作り,COMMONHEADERPARTでもそれを別途実装する.デフォルトのdoCleanを使いたくない場合,COMMONHEADERSHORTを適用すれば,固有のdoClean関数をカスタマイズできる.doCleanの実装を強要するためには,NODULE::doCleanは純粋仮想関数としておくのがよいのかもしれない.⇒いや,それもあまり意味がないのではないか?noduleクラスに実装すればエラーは発生しなくなる.⇒どちらでも大勢に影響はないので,nodulle::doCleanを実装するようにした.

系統並び替え中,TRIBELIST::GoDownStream→ CARDLINK:DownStream…でNAMEBOX::makeProxyがgettreeviewで空というエラーを出した.gettreeviewはBobjectの関数で,以下の行でゼロ復帰している.if (metrix) return metrix->treeview;

これはかなりまずいと思う.metrixはクラスMETRIXのインスタンスでMETRIXはクラスmetrixから派生している.このmetrixの中にTREEVIEWへのべた参照が入っている.これはまずい.metrixからオブジェクトへの参照を排除しなくてはならないが,暫定的にこの行を止めておこう.この辺りは相当古い化石のようなコードだ.

nodule::Disposeで(!LIFE || DELETED == 1)で停止した.LIFEやDELETEDはNODULEのヘッダ部にあるので,今回の修正で影響するとは考えられないのだが… 障害ノードはREFLINK #4334でSIMPLENODEを参照している.SIMPLEGRAPH:DecompConnectedComponentの冒頭で連結成分リストをcancelしているところだ.~ARRAY()→CleanSlotで起きているようだ.⇒nodule:Disposeがダブって実行されている.⇒コンパイルエラーを避けるために入れた仮修正が残っていた.

ARRAYの派生クラスで「テーブルをクリアしたことにはならない@20201127」という理由でCleanSlotをARRAY<>::CleanSlotに修正しているが,これは誤りだ.この誤りはBASETABLEではすでに補修されているが,REFLINKとTRASHCANに残っている.⇒対処した.

COUPLINGもCOMMONHEADERSHORTを使うようにした.doCleanからはinitializeを呼び出すようにした.これで完全にクリーンなコア骨格木を樹立することができた.⇒INITIALSTATEからINITIALIZINGに遷移するまでの間に複数回INITIALIZEDが掛かってくる.COUPLING:EraseFamilyTreeの入口でCHAOTICSTATEをセットしている.⇒現フェーズがINITIALIZEDより大きい場合にのみ更新するようにした.

カード削除,新規カードその他系図木のトポロジーが変化するような操作を行ったときにはフェーズが一旦INITIALIZINGになったあと,INITIALIZEDをパスしてTOPOLOGICALSORTにジャンプする.むしろ,INITIALIZEDとすべきだろう.⇒このようなケースでは一律

if (PHASE > INITIALIZED) topology->SetPhase(CHAOTICSTATE);

としておこう※.※⇒(INITIALIZED=CHAOTICSTATE)

複数カードを選択して1枚ずつ削除するとき,2枚目でTREEVIEW:setSelectedNameのエラーが出た.「選択領域表示状態での主選択カード切り替えは不可」とされる.カード削除→TREEVIEW:CleanSansyoでsetSelectCardが実行されている.⇒動作的には問題ないように思われる.実際のところこれ以外どうしようもない.暫定的に引数のNAMEBOXが空のときは停止しないようにしておこう.

複数選択してまとめてカード削除したら,nodule::ReferenceControlで「参照元ノードリスト不記載」になった.FAMILYTREE:DeleteAllCardで一括削除しているが,実際には1点づつの削除を反復しているだけなのだが… カード一括削除でつねにエラーになる訳ではなく,何か特殊な条件があるようだ.⇒多重カードの関係かもしれない… #61と#62を一括削除して再現する.このケースを追いかけてみよう.

2枚目のカード#62CARDLINKのデストラクタでCleanSlotを実行しているところだ.エラーが起きているのはCARDLINK::HUBOS[0]というスロットだ.#61と#62は親子関係なので,#61の結婚リンクが入っている※.#61が削除されたからと言って,この結婚リンクが自動的に消滅するというものでもない.配偶者や子どもがいれば本人が削除されても結婚リンクが存続する場合はあり得る.※⇒これは事実誤認

参照リスト管理は死んだ後まで付いてゆく仕組みなので間違いようがないと思われるのだが… CARDLINK#1238→MARGLINK1#563という参照なので,#563の参照リストを追いかけてみることにしよう.⇒不記載となっているのは#1238[7]→#563だが,スロット7からの参照というのは明らかに登録されていない.このカードは親を2組持っていて,一方の結婚リンクが抹消されたあと,結婚ページの移動を行っている.これはかなりまずい.おそらく,このような操作を行っているところは他にもかなりあると思われる.⇒このケースについては解決した.

▲まとめて選択→一括削除してTRIBEBOX::setTribeRealnodeでエラーになった.上記と類似したことが起きているのではないか?選択範囲は以下のような感じだ.

image