アプリ終了時の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

「ABSTRUCTTREEを試してみる@20201208」は無謀な試みだった

「ABSTRUCTTREEを試してみる@20201208」という修正は収束の見込みが立たないので,一旦中止して昨日のバックアップに戻った.2回目のバックアップは「ABSTRUCTTREE」以前の修正はすべて入っているので手戻りは生じない.ここで一度ここまでの修正をすべて現状でフィックスしておくことにする.以下のコンパイルオプションがある.

  1. FAMILYTREE:Initializeを廃止する@20201208 3箇所
  2. COUPLING()でコア系図木を生成@20201208 13箇所
  3. フェーズの遷移を整理する@20201208 9箇所
  4. InitCoupling>InitTreeViewを廃止@20201208 1箇所
  5. CloseFamilyBaseの引数を廃止@20201208 5箇所
  6. EraseFamilyTreeの引数を廃止@20201208 2箇所

修正完了した.今日は始業時バックアップを取っていないので,バックアップしておこう.気になる点が2つある.①baselist(基本世代枠リスト)をTREEVIEWの下に移動する,②TITLELINKをTREEVIEW以外の位置に移動するの2点だ.それほど大きい修正にはならないと思うのだが… 現行ではbaselistはTOPOLOGYが管理している.

「baselistをTREEVIEWに移動@20201209」の修正を行っているところだが,急にマイクロソフトの複数のヘッダファイルでエラーが出始めた.原因は全くわからない.エラーが出ているのはcorecrt_io.h, mmsystem.h, mmiscapi.hなどで大量のコンパイルエラーが出る.修正を戻しても状況はまったく変わらない.HD,RAM,CPUなどの資源が逼迫しているようにも見えない.おそらく,状況はバックアップに戻っても変わらないのではないかと思う.

一度リブートしてみることにする.⇒バックアップは問題なくビルドできた.仕掛り版をビルドしてみよう.⇒やはりエラーが出る.TableSort.cppのコンパイルでmmiscapi.hがエラーを出している.「構文エラー: ‘;’ が ‘*’ の前にありません。」や「型指定子がありません – int と仮定しました。」,「’pchBuffer’: 不明なオーバーライド指定子です」などだ.もう一度作り直してみよう.

TablelSort.cppには以下の5つの外部ヘッダファイルがインクルードされていたが,現在はまったく参照されていないのですべて廃止とする.shlobj.h,tchar.h,string.h,timeapi.h,corecrt_io.h.「baselistをTREEVIEWに移動@20201209」の修正は完了した.COUPLING()でFAMILYTREEとTREEVIEWの生成順を元に戻しても動作することを確認した.TITLELINKは現在TREEVIEWの下にあるが,これは描画要素ではないので,FAMILYTREEに移動する.⇒完了した.

ただし,FAMILYTREEへの移管というのは失策だったかもしれない.FAMILYTREEは実際のところ,ほとんどタイトルに関与していない.むしろ,COUPLINGの方が関わりはある.これから再修正というのもなんであるし,現行ではタイトル枠の表示自体が不調なので,それに掛かるときに再考することにしよう.

フェーズの遷移についてもう少し調べてみよう.INITIALSTATE≡コア骨格木であるということが明らかになった.ではINITIALIZINGとINITiALIZEDでは何がどう違うのか?この2つのフェーズはどちらもCOUPLING::InitLinkTableの中で遷移している.この関数はCOUPLING::ReadFamilyBaseの中でデータベースを読み込んだ直後に実行される.INITIALIZINGに遷移する地点ではノード数はコアの32から73まで増えているが,まだ参照は1件も発生していない.これに続くループで結婚リンクとカードリンクが初期化されて描画要素が生成され,それに従って参照が発生する.つまり,INITIALSTATE→ INITIALIZINGは参照ゼロの区間と言える.

INITIALIZINGにはInitLinkTableの中で遷移しているが,むしろReadFamilyBaseの中でreadFamilyBaseが完了して,InitLinkTableの実行に移るタイミングで切り替えた方がわかり易いと思う.同様に,INITIALIZEDもInitLinkTableの中で遷移するのではなく,ReadFamilyBaseの出口で設定するという方がわかり易いのではないか?INITIALIZEDというのはTOPOLOGICALSORT待ちという状態であり,INITIALIZED→TOPOLOGICALSORTの間は空白であってよいと思われる.⇒いや,フェーズの遷移はもっと上のレベルで実施した方がよい.フェーズの遷移はすべてCOUPLING::OpenFamilyTree上で見えるようにしてみよう.このためにはOpenFamilyBaseを少なくとも2つの部分に分解しなくてはならない.というか,そのためにはReadFamilyBaseを2つに分解しなくてはならない.

大体目処が付いた.OpenFamilyBaseを分割するというのはさすがにやり過ぎだろう.OpenFamilyTreeではCloseFamilyBaseも呼び出しているので,OpenFamilyBaseとCloseFamilyBaseを同レベルで扱うのがよい.CloseFamilyBaseの出口でINITIALSTATEになり,OpenFamilyBaseの出口でINITIALIZEDになるというのが一番バランスがよいと思う.コードを見易くするために,ここで一度オプションをフィックスしておこう.⇒まずい.PARTIALNAME::Entry2ListNumでエラーが出るようになってしまった.⇒修正「フェーズ遷移はOpenFamilyTreeで行う@20201209」を戻したら収まった.

COUPLING::ReadFamilyBaseで使っているローカル変数を分離して外に出したパートに渡さなくてはならない.⇒KAKEIZUにfullpathというchar配列を設けてファイル名をコピーするようにした.KAKEIZUデータベースは古い3つ組ファイル形式とMFCのアーカイバを使ったZELファイルを扱っているため,かなりややこしい作りになっている.この辺りも少し整理しておきたい.一度バックアップを取ってから始めよう.

ReadFamilyBaseを廃止して,KAKEIZU:readFamilyBaseを直接呼び出すように作り変えてみよう.⇒ReadFamilyBaseの廃止はできたが,Ancestory.zelを開いてエラーが出てしまった.そればかりでなく,バランスも崩れている.エラーはTREEVIEW::DeselectNodeで (!card->selectflag)というエラーだ.今朝のバックアップでもTREEVIEW:DeselectNodeのエラーは出ないもののバランスは崩れている.後戻りしない修正を繰り返しているので,不良の出ない版まで戻るのは難しい… TREEVIEW::DeselectNodeのエラーも出ている.ファイルを開き直すと発生する.⇒ここはもはや目を瞑って水に飛び込むしかない.

▲ファイルを開き直すと(!card->selectflag)というエラーが起きる.これはselectcardlistとカードのselectflagが同期していないことを示すものだ.⇒ファイルを開いたときにselectcardlistをクリアしていないのではないだろうか?⇒clearという関数は持っている.ファイルを閉じたときにclearしていないことは確かだ.これはフェーズの遷移にも関わる問題なので,やはり一度修正をフィックスしてしまうことにする.フィックス対象のOPTIONSは4つある.

  1. baselistをTREEVIEWに移動@20201209 13箇所
  2. TITLELINKをFAMILYTREEに移動@20201209 26箇所
  3. フェーズ遷移はOpenFamilyBaseで行う@20201209 1箇所
  4. COUPLING:ReadFamilyBase廃止@20201209 3箇所

コア系図木のみが存在する状態をINITIALSTATEフェーズとする

初期フェーズおよび,系図木のトポロジーに何らかの影響を与えるような操作が行われたときのフェーズの遷移にはかなりあいまいなところがある.それぞれのフェーズに入るための要件,およびそこから次のフェーズに遷移するための条件,つまりフェーズの境界を決定する,言い換えれば各フェーズの定義を確定する必要がある.

アプリ起動時のフェーズはデフォルトでGROUNDZEROだ.これは「更地」であるということを意味する.次のINITIALSTATEは「初期状態/ファイルクローズ時の状態」と定義されている.これまでの考察に鑑みると,INITIALSTATEではコア系図木(ZTシステムのコアブロック)が存在することが要件になると考えられる.コア系図木は以下の一行で自動的に構築されるものでなくてはならない.

Coupling = new ((nodule*)&Coupling, SEIZEGROUND) COUPLING;

COUPLING Couplingは系図木(ZT系図システム)の頂点を成すオブジェクトであり,その直下のオブジェクトはCOUPLINGのコンストラクタ内で生成される.また,これら直下オブジェクトの直下オブジェクトはそれぞれのコンストラクタの中で生成されるから,コア系図木は上記の一行が実行された時点で確立されなくてはならない.また,ファイルがクローズされた時点では再びこの状態に戻っていなくてはならないと考えられる.そうなっているかどうかを(実際にはそうなっていないと思われる…)確認してみよう.現状ではCallSetCouplingPtrの入口の系図木は以下のオブジェクトから構成されている.

番号    個数    CID    クラス名
#  1      1    &    COUPLING ◯
#  2      1    F    FAMILYTREE ◯
#  3      1    J    LINKTABLE ☓
#  4      1    K    KAKEIZU ◯
#  5     13    L    longtable ▲ 12個不足
#  6      1    N    NAMESORT ☓
#  7      1    P    PARTIALNAME ☓
#  8      1    T    TOPOLOGY ☓
#  9      1    U    UNDOSYSTEM ☓
# 10      1    W    TITLELINK ◯
# 11      1    c    CARDTABLE ☓
# 12      1    f    GENELIST ☓
# 13      1    h    LIST ☓
# 14      2    l    nlist ☓
# 15      1    q    PAGESETUP ◯
# 16      1    u    TRASHCAN ◯
# 17      1    v    TREEVIEW ◯
# 18      1    y    MARGTABLE ☓
# 19      1    z    TRIBELIST ☓

クラス名で19,オブジェクトの個数32というのが,コア系図木を構成する基本的なコンポーネントだ.一方現状でCouplingを生成した時点ではクラス数で11,オブジェクト数で16しかない.この中には以下のような,①nodule x 4,②TITLEBOX x 1,③NODEREFLIST x 3など上記に含まれないものも入っているので,欠けているオブジェクト数は16個よりも大きい.欠けているものはほとんどFAMILYTREEの下位オブジェクトなので,まずこれらを追加してみよう.

現行ではこれらはFAMILYTREE::InitializeFamilyTreeで生成しているので,FAMILYTREE()からそれを呼び出すようにしておこう.⇒TOPOLOGYが生成され,TOPOLOGY::initializeでエラーが出た.TOPOLOGY::baselistの初期化のためにTREEVIEWが必要になっている.世代枠リストというのは描画に関係するものなので,描画リストの先頭が必要になってくる.この意味では基本世代枠リストはTREEVIEWの下にある方が自然であるような気がする.

実際系列世代枠リストは系列枠自身が管理している.この見直しは後からやることにして,ここではTREEVIEW空で停止しないようにしておこう.実際,ABSTRUCTTREEのときにはTREEVIEWなしでも動作するようになっている.TOPOLOGY::SetPhaseでも同様エラーが出るが,無視しておこう.これでCOUPLINGの生成時のオブジェクト数は46,クラス数で23となり,コア系図木より大分大きな木になってしまった.

余分なものとしては,nodule x 2,SIMPLEGRAPH x 9,TITLEBOX x 1,NODEREFLIST x 2がある.SIMPLEGRAPH をコアに含めてしまってもよいが,ない方がすっきりするので,生成しないようにしてみよう.⇒これらはTOPOLOGY::initializeで生成されている.TITLEBOXはTITLELINK()で生成されている.これも止めておこう.

どこかで「参照」が生成されている.コア系図木では参照を使わないということになっているので,どこで発生しているのか調べてみよう.TITLELINK→TREEVIEWという参照がTREEVIEW()で設定されている.TITLELINKはコア系図木に含まれているので,参照スロットを廃止して関数でアクセスするようにしておこう.

TITLELINKは現在TREEVIEWが管理するようになっているが,あまりよくないと思う.タイトルというのは系図データの一部であり,必ずしも描画と直結している訳ではない.⇒暫定的に現状で進めることにする.⇒これでCOUPLING生成時とCallSetCouplingPtr入口で完全に一致するコア系図木を持てるようになった.

次に確認すべきことはファイルをクローズした状態がこのコア系図木になっているという点だ.アプリ終了時にはファイルをクローズしているはずだから,おそらく間違いはないとは思われるが… その前にどこかでグラフを生成しておかなくてはならない.⇒TOPLOGLY:GenerateGraphsという関数を作って,COUPLING::OpenFamilyBaseから呼び出すようにした.

系統並び替えの冒頭,BuildGeneListでdomainが空というエラーになる.世代枠が生成されたときにTREEEVIEWがまだ生成されていないためだ.⇒暫定的にCOUPLING()でFAMILYTREEとTREEVIEWの生成順を入れ替えた.⇒うまくいった.

COUPLING::CloseFamilyBaseを実行後のコア系図木がデフォルトと完全一致していることを確認した.これでGROUNDZEROとINITIALSTATEの状態は確認できた.逆に言うと,フェーズをINITIALSTATEに切り替えできるのは,①COUPLINGの生成後と②ファイルクローズの2つしかないということになる.これ以外の場所で設定しているとすればそれは誤りだ.確認してみよう.現在INITIALSTATEを設定している場所は9箇所(関数名では6)ある.

  1. FAMILYTREE::callSendCard カードデータの更新 → INITIALIZED
  2. COUPLING::TopologicalSort 系統並び替えで例外発生 → アプリ終了
  3. COUPLING::EraseFamilyTree EraseFamilyTreeの入口 → 出口でINITIALSTATE
  4. FAMILYTREE::InitializeFamilyTree 下位コンポーネント生成後 → 不用
  5. FAMILYTREE::getNewCard 新規カード生成で失敗 → INITIALIZED
  6. COUPLING::OpenFamilyTree ファイルオープンで例外 → アプリ終了

カードデータの更新や新規カード生成では系統並び替え直前のINITIALIZEDでよいのではないかと思う.例外が発生したときはとりあえず,アプリ終了するものとしておこう.InitializeFamilyTreeはCOUPLINGのコンストラクタから呼び出されるだけだから,何もしなくてよい.EraseFamilyTreeの動作は,CloseFamilyBaseの主要部分を占めると思われるのでその出口でINITIALSTATEとするのでよいのではないか?EraseFamilyTreeが「解体」の主要パートを実行しているが,CloseFamilyBaseではまだフェーズはDRAWSTAGEのままなのであちこちで検査に引っかかってしまう.⇒暫定的にEraseFamilyTreeの入口でCHAOTICSTATE=INITIALIZEDまで落とすようにした.

これで大体筋が通ったのではないかと思う.ENDOFAPPLICATIONではゴミ箱を廃棄してCouplingをdeleteする.コア系図木は new…COUPLINGで生成されているので,delete Couplingで始末するというので首尾一貫している.INITIALSTATE→ INITIALIZING→ INITIALIZEDの辺りをもう少し詳しくみてみよう.

アプリを起動すると,アプリ側ではファイルをオープンする前に「デフォルト画面設定を取得する」ということをやっている.このとき,COUPLING::InitCouplingが実行され,その中からTREEVIEW:InitTreeViewが呼び出されている.しかし,ここで設定された参照はファイルオープン前に実行されるファイルクローズによってクリアされてしまうので,ほとんど意味のないことをやっているように思われる.

上記修正は通ったが,アプリ終了時にエラーが出るようになってしまった.新設したオプションを止めても変わらない.どこか壊してしまったのではないかという気がするが,今日は一度もバックアップを取っていない… ともかく一度バックアップを取ってから追いかけることにする.

ZTシステム構成図7.ZELの全体図を#1 couplingで開いて終了して,nodule::Disposeで空でないスロットがあるというエラーになった.障害ノードは#10 CARDTABLEでlongtable *lookupが残っている.CARDTABLEは独自のスロットを持っていないが,基本クラスのBASETABLEのデストラクタには以下のようなコメントが付いている.

2017-07-27 スロットを全クリアする×→スロット配列のみクリアする

全クリアするように修正して解消した.20201127にClearTableに関係してARRAY<bnum>::CleanSlot();を追加する修正を行っているが,このときに何か勘違いして余分なことをやってしまったのではないかという気がする.不審なのはなぜ,この誤りがいままで発現しなかったのか?という点だ.現行ではすべてのlongtableはコア系図木の一部として最終段階まで維持されるが,従来論理ではどこかで削除していた可能性もなくはない… ともかくこれで一件落着したことにしておこう.

COUPLING::CloseFamilyBaseとCOUPLING::EraseFamilyTreeは引数を持っているが,つねにTRUEで動作しているので,廃止してしまおう.EraseFamilyTreeの場合,modeがONでは以下が実行される.

  1. delete familytree->undosys
  2. delete topology
  3. familytree->CleanSlot()

このモードはアプリ終了時に一度だけ実行されるようになっているが,現行ではそれに代えてdelete COUPLINGでコア系図木を根こそぎ切り倒すようになっているので,不用と考えてよい.⇒対処した.

ABSTRUCTTREEというグローバル変数がある.これは「ファイルの追加読み込み」を実行するとき,描画要素を持たない系図木を構築するためのオプションだが,これをONにして起動したらどういうことになるのか,ちょっと見てみたい.⇒これは流石に簡単には収束しそうもない.一旦中止した方がよさそうだ.

終点がgoodsonのノード対は最大区間を除き端点共有不可

「終点がgoodsonのノード対は最大区間を除き端点共有不可」という規則がある.goodsonというのは子ども枠の中で親の結婚点から下りる垂線の直下にある人名枠,つまり結婚枠の吊り位置に当たるノードだ.「終点がgoodsonのノード対」では連結線の終端がこの垂線上にあるため,端点共有すると共有連結線が垂線を横断する形になって,誤読の可能性が高くなる.上の規則はそれを防止するための対策だが,「最大区間を除き」という条件があるために判定が結構厄介なものになっている.端点共有束全体を「最大区間」の連結線を持つ1個のノード対とみなすことでこの図式はかなりわかり易くなる.

実際,一部ではそのように扱っているところもあるはずだが,それを徹底すれば共有端点ノード対と一般のノード対が一つのチャンネルを共用するということも可能になるはずだ.これは多少なりともチャンネル数の削減に効果があると考えられるので実装したいと思うが,ここでは一旦保留して先に進むことにする※.現在確認されている障害は「一時的にSTOPを抑制する@20201028」で止めてあったものだが.このオプションをフィックスする時点で再考することにする.

※勘違いしている.現状でもそうなっている.

以下のコンパイルオプションを現状でフィックスする.

  1. DEBUG:一時的にCheckSamePointをパス@20201206 1箇所
  2. 増設スロットの繋ぎ変えはnodl_floatに@20201205 1箇所
  3. ノード対削除後に端点共有束を調整しない 2箇所
  4. 長さゼロのノード対は端点共有対象としない 2箇所

さて,いよいよPAIRLIST::Removeを廃止するときが来た.それをやるとどんないいことあるの,だって?もちろんあるよ.これをやると少なくともリストクラスに関しては削除に関する操作が完全に統合されたことになる.つまり,いつでもどこでもdeleteするだけでオブジェクトを削除できるようになる.必要なのはdeleteElementとdataCountDownという2つの関数を整備するだけだ.つまり,オブジェクト削除というクリティカルな操作の完全な一般化が実現したということになる.⇒PAIRLIST::Removeは9箇所から参照されている.⇒修正完了した.

端点共有では増設スロット(EXTRASLOT)を使って接続チェーンを構成している.EXTRASLOTはMARGBOXでも「SymmetricActionでブロック移動実行時に使われるリストを一時的に接続する」ために使われているはずだ.どういう使われ方をしているのか見ておくことにしよう.MARGBOX::SymmetricActionでは対象結婚枠のEXTRASLOTに以下のようなリストを接続している.

nlist<Bobject> *list = new (this, MARGBOXsEXTRA) nlist<Bobject>;

ただし,このオブジェクトはごく短命で,この行に続くわずか2, 3ステップで削除されてしまう.

int count1 = MoveParentGroup(treeview, tribe, list);
originate(CPoint(delta, 0));
int count2 = RestoreParentGroup(list);
delete list;

MoveParentGroupはBobjectの関数で「該ノードを親参照している外部系列ノードの親参照を付け替える」ということをやっているらしい.RestoreParentGroupはその逆操作だ.通常あるオブジェクトを移動すると,そのオブジェクトを「親参照」している描画要素はすべて移動することになるが,ここでは外部系列内描画要素に影響を与えない方法で単体移動※しようとしているものと解される.nlistのリスト要素はREFLINKでそこからオブジェクトを間接参照するようになっているので,このリストの構築は既存システム要素の構成には影響しない.※⇒系列内の下流ノードは結婚枠とともに移動する.

MARGBOXはPAIRBOXの端点共有のシナリオにはまったく登場しないので,EXTRASLOTの取り合いのようなことは起こらないと考えてよいだろう.また,このnlist<Bobject>のEXTRASLOTやスロットゼロには何も接続されていないから,このリストの削除も単純な単体オブジェクトの削除として完結する.nodule.hの「増設スロットの使用例」という説明では「SymmetricActionでブロック移動実行時に使われるリストを一時的に接続する」と解説している.

2020/12/03のログには懸案事項として9件をリストアップしている.うち,(1)OnListremove,removingを廃止,(3)dataCountDownの一般化,blackflagの廃止,(4)nodl_floatを終了処理に含める,(9)ReferenceList(参照チェックリスト)の廃止の4項目は大体終わっているので,(2)CallSetCouplingPtrを始末する,終末期のフェーズというのを見てみることにしよう.いや,その前に「PAIRLIST:Removeを廃止する@20201207」をフィックスしてしまおう.このオプションは13箇所に出てくる.⇒対処した.ここで一度バックアップを取っておく.

実際のところ,「CallSetCouplingPtrの始末」はほとんど付いている.アプリ終了でCallSetCouplingPtrの入口に入った時点でアクティブなオブジェクトは32個しか残っていない.メモリ上の不定サイズオブジェクトを管理するfreeblockにもオブジェクトは残っていない.ゴミ箱には10,008個の廃棄オブジェクトが入っているが,ThrowCanを呼べば空っぽにできる.この状態はファイルをクローズして新規ファイルをオープンする前の状態に等しい.⇒実際そうなっていることを確認してみよう.いや,その前にまだカウントが少し合っていないところがある.

NODEREFLISTは1366個生成されているが,ゴミ箱には1363個しか入っていない.Nringの中には見当たらないので,3個紛失してしまっている.また,ゴミ箱に入っているfreeblockが42個しかないというのも解せない.freeblockはトータルで10,040個も使われているのでいくらリサイクルされているとは言っても少な過ぎるような気がする.

いや,ちょっと勘違いしている.10,040というのは生成されたすべてのオブジェクトの総数だ.このサンプルには画像などは全く含まれていないので,freeblockオブジェクト数が32というのは妥当な数だ.参照リスト生成数とゴミ箱の中の個数が一致しないのはリサイクルされているからではないか?⇒確かにそのようだ.3個リサイクルされている.しかし,これらはいつゴミ箱に入ったのだろう?リサイクルされているNODEREFLISTのSNUMは#15, #11, #9といずれも若い番号だ.

アプリを起動した後,最初にCloseFamilyBaseの中で以下の3つのNODEREFLISTが削除されている.①TITLELINK ,②TREEVIEW,③TITLEBOX .いずれのオブジェクトも参照されていない.しかし,参照リストが生成されているということは,そこに来るまでの間にはどこかから参照されていたのだろう.アプリではファイルをオープンする前に必ずクローズを実行しているため,一度も使われていない状態でもCloseFamilyBaseが掛かってくる場合がある.

このタイミングで③TITLEBOXが存在するというのはかなり疑問だ.①TITLELINK と②TREEVIEWはコアシステムに含まれるので,これらが存在すること自体は正当だが,参照が存在するということは何かしらアクティブな動作が発生したことを意味するのでやや疑問だ.フェーズの境界がかなりあいまいになっているので,少し整理してみよう.

  1. GROUNDZERO 0: 更地 アプリ開始時→ゴミ箱/コア系図木の構築
  2. INITIALSTATE 1:    初期状態/ファイルクローズ状態  ファイルのオープン→データベースへの接続
  3. INITIALIZING 2:    初期化処理中/系図木の変化→系図データのロード
  4. INITIALIZED 3:    初期化完了
  5. TOPOLOGICALSORT 4:    系統並び替えの開始
  6. CLEARTABLE 5:    個人/結婚リンクテーブル初期化
  7. DRAWSTAGE 28:    系図木描画準備完了
  8. ENDOFAPPLICATION -1: アプリケーションの終了
  9. CHAOTICSTATE = INITIALIZED 初期化中/UNDO処理中フェーズではエラーを無視

ノード対削除後に端点共有束を調整しない

ZTシステム構成図7.ZELの直系血族図を#201 noduleでソートしてPAIRLIST::Removeの(bundletop->CheckSamePoint())エラーで停止した.202 NODULEなどでも起きている.⇒開いただけでは発生しない.系統並び替えを実行すると起きる.起動して終了では起きない.障害は系統並び替え冒頭の基本世代枠リストのcancelで起きている.

Removeで削除されているのは#20597で#20166と端点共有になっている.#20597が削除された後,#20166は別のチャンネルにある#21074と端点共有になる.このようなことはあるのではないだろうか?端点共用には,①右終点共有,②左終点共有,③左始点共有,④右始点共有,⑤端点共有しないの別がある.

PAIRBOX#20166は#19882 linktable(1)→ #776 linktable(0),#21074:#20773はundosys(1)→ #758 undosys(0)だ.ちなみに#20597は#20297 namesort(1)→ #785 namesort(0)で,#20597と#20166は左始点共有だ.

image

上の図ではnamesortとlinktableが端点共有になるのは分かるが,linktableとundosysが端点共有になる理由は分からない.垂直線分の描画が不調なのでこの問題は保留とし,ダンプだけ出して停止しないようにしておこう.⇒いや,むしろここでは端点共有の調整も検査も行わないという方が正しいのではないか?メインの通常フローで削除された場合なら,Removeの中で調整しなくても事後に対処されているはずであるし,終末期であればもちろん調整しても意味がない.⇒「ノード対削除後に端点共有束を調整しない」オプションを設置した.

同上サンプルの全体図:#81 complistで系統並び替え中NAMEBOX:RetrieveGhostでCheckSamePointエラーが発生した.いや,基準ノードは#82 SIMPLEGRAPHかもしれない.⇒確かにそのようだ.今度は上記とは逆のエラーだ.つまり,設定は端点共有で実際には端点共有なしという状態だ.これは上の修正からの当然の帰結だ.

この障害はTRIBELIST::MakePairListClean→ CheckPairList→ CheckShiftedPairBox→ PairBoxGeneChange→ RetrieveGhostで起きている.TOPOLOGY::CheckShiftedPairBoxでは「チャンネル切り替えにより巻き戻し」ということを実施しているので,外側の処理に任せてもよいのではないかという気がするのだが…

TOPOLOGY::CheckPairListでは前段でCheckShiftedPairBoxを実行した後,後段でCheckPairEndPointを実行して端点共有の調整を行っている.端点共有は大域的な処理なので,部分的に調整しても意味がないような気がする.実際,PAIRLIST::Removeで行っている調整では端点共有束の先頭ノードの属性変更しか実施されていない.これではほとんど意味がないように思われる.むしろ,ノード対の削除によって副次的に端点共有の不整合が発生することを認めた方がよいのではないか?⇒RetrieveGhostでも「ノード対削除後に端点共有束を調整しない」を適用するとしてみよう.

同上サンプルの法定親族図:#171 MARGBOXを開いて,repairCommonEndPointで停止した.出口のCheckSamePointに引っかかった.これはかなりまずいのではないか?この関数は共有端点束の調整を目的とするものだ.⇒基準ノードはその次の#172 ownerだ.

入口のCheckSamePointでは「ノード対区間ゼロで端点共有不可」と診断されている.おそらく,repairCommonEndPointはこのような状態を修復する手段を持っていないのだろう.このようなノード対は危険対(始点終点が同一座標のノード対の組 端点共有不可)と呼ばれている.このようなノード対の端点を共有すれば誤読が発生することは避けられないことからそのように呼ばれているのだろう.⇒いや,「危険対」というのはもう少し違うのではないか?

危険対枝グラフの循環検定というのがあるが,これは「始点終点が同一座標で逆転」というものだが,ここで言う始点終点が同一座標というのは一つのノード対が同一座標の始点・終点を持つという意味ではなく,{始点,終点}→{始点,終点}→…{始点,終点}のような連鎖があったときの一番最初に出てくる始点と最後の終点が同じで連鎖が閉路を形成しているようなものを言っているはずだ.障害の起きているノード対は#18512:#8855 MDB(1)→#974 MDB(0).

水平連結線長さがゼロであるようなノード対にはまったく誤読の危険性はあり得ない.単純に上から下へ垂線が通っているだけなのだから,他の連結線がどこから入ろうが,どこへ出てゆこうが問題は起きない.従って,「ノード対区間ゼロで端点共有不可」であったとしても,そのことから派生するリスクは存在しない.つまり,端点共有束の中にあっても外にあってもまったく問題は生じない.間違いがあるとすれば,これを共有端点束に入れるという操作そのものだろう.

image

上図で問題となっている「ノード対」はMDB(イエロー)の直上にあるlinktableから入ってくる垂線以外の何ものでもない.従って,①CheckSamePointはこのようなノード対をエラーとすべきではない,②このようなノード対は端点共有としない,としなくてはならない.CheckSamePointは端点共有の「間違い」を見つける関数なので,それとは別に端点共有候補であると判定ないし検査関数があるはずだ.⇒CRITICALPAIR属性を与えてしまうのが一番簡単なのではないか?

CheckSamePointの判定は誤ってはいない.CheckSamePointは端点共有の「間違い」を見つける関数なのだから,始点終点同一座標で端点共有設定されているものを「間違い」と指摘しているだけだ.ただし,このノード対はどこにでも置けるのだから,あえて「間違い」としなくてもよいのではないか?実際そのように修正してエラーは解消した.このノード対がどこで端点共有に設定されているのかを見てみよう.⇒最初にノード対が生成された時点で端点共有になっている.この判定はAvailableChannelで行っている.⇒「実ノードと仮ノードの水平座標が同一の場合は任意のチャンネルに置いてよい」とした.

BRect.Width()が負値を返す場合があり得るので,MAXKEISANGOSAの関わる計算すべてで絶対値を取るようにした.

なぜだろう?図式がまったく変わってしまった.

image

この結婚枠はtopologyの子ども枠なので本来なら中吊りしなくてはならないところだが,当初は先頭のPDBで吊っていたのが,末尾のkeisengraphに移ってしまっている.かなりおかしい.どこで何が変わったのだろう?ノード対のチャンネルの話だけでここまで変わるというのはかなり問題がある.topologyは結婚枠を2つ持っている.#146と#153だ.前者はgoodson空で,後者のgoodsonは#1163keisengraph(0)だ.この図面ではなぜかtopologyだけではなく,GCでない子ども枠はすべて末子で吊られている.

当初#146(最初の子ども枠)は#965 PDB(0)がgoodsonになっていたのだが,「拡張子ども枠で下流結婚枠のgoodsonが返された」として拡張子ども枠#153の#1073 SymmetryList(0)に切り替わり,その後同じ理由で#1163 keisengraph(0) に切り替わっている.これらの動きはすべてSETRELATIVEフェーズで起きていて,その後は調整されていない.

拡張子ども枠は拡張枠の中央にもっとも近いノードをgoodsonとしている.この意味ではSymmetryList(0)が正解ということになるのだが… おかしい.何も修正していないつもりだが,動作がまた変わってしまった.最初の状態(PDBがgoodson)に戻っている.⇒MARGBOX:makeGoodSonにひどいミスがあった.計算式の中で符号が反対になっている項があった.⇒ここで一度バックアップを取っておこう.

▲同上サンプルを#136 PrimaryでソートしてPAIRBOX::CalcPairBoxで停止した.NOCOMMONPAIR属性を持つノード対が端点共有でダブっている.NOCOMMONPAIRノード対はNOCOMMONENDPT属性持つ実ノードを持つノード対で,「終点がgoodsonのノード対は最大区間を除き端点共有不可」とされる.

これですよ,これ,これが見たかったんだ!

すでにノーリターンポイントを超えてしまっているのでこのまま驀進するしかない.どこに向かって?崖っぷちに出るまでかな?

PAIRLIST::putbottomではdatacountを更新していない 

LIST::putbottomにはdatacountの更新が入っている.それだけではなく,この関数では繋いでいるスロットも間違っているようだ.この関数は引数を2つ持っているが,2番目の引数commonは共有端点ノード対を示しているはずだ.だとすれば増設スロットに接続しなくてはならないのに,スロットゼロに接続している.⇒この関数の後半部,commonが空でない場合はまったく使われていない.⇒関数の仕様を変更して,後半部を完全に抹消した方がよい.⇒対処した

▲端点共有→繋ぎ替えの動作を確認する

端点共有ノード対は接続チェーンを構成しているので,リストと呼ぶべきだが,慣用的に端点共有束という言い方をしているので,これに統一することにする.「束」と呼んでいるのは連結線がダブって1本に束ねられているように見えることから名付けたものと思われる.離散数学でいう束※とは関係ない.※⇒任意の2つの要素が唯一の上限と唯一の下限を持つような半順序集合を束と呼ぶ

端点共有ノード対の操作は,そこで完結している限りオブジェクトの削除や解体とは関わりがないが,増設スロットをどう操作しているか?という点に興味がある.PAIRBOX::setsamepointで「端点共有なし」以外の値を設定しているのは,PAIRBOX::SetSamePointとPAIRBOX:repairCommonEndPoint,PAIRLIST::puttail,NAMEBOX:makePairBoxしかない.SetSamePointはMoveSamePointとmakePairBoxから呼び出されている.

puttailはMoveCommonChannelToから呼び出されて,端点共有束のチャンネル移動を実施している.MoveCommonChannelToから呼び出されるcutoutはdeleteElementでノード対をノード対リストから切断しているが,後続する端点共有束もノード対と一緒に移動するため,dataCount()でカウントダウンしている.つまり,nodl_floatでは端点共有束をリストとしては扱っていないことになる.実際にそうなっているのかどうかを確認してみよう.⇒いや,MoveCommonChannelToは現行では使われていない.これは紛らわしいので抹消しておこう.⇒PAIRLIST::cutout,puttailも廃止される.

MoveSamePointではRemoveとReConnectを使って繋ぎ替えを実施している.RemoveではdeleteElementでフロート化した後,後続端点共有ノード対を前方に繋ぎ替え,後続リストノードは後続端点共有ノード対に繋ぎ替えている.ノード対リストからの切断はすべてdeleteElementで実行されるので実質nodl_floatが実行されている.nodule::nodl_floatの説明には,

増設スロットに接続されている子ノードXは対象ノードが接続されていたスロットに接続され,スロットゼロの子ノードZはノードXのスロットゼロチェーン末尾に移動する.子ノードXが空の場合はノードZを
対象ノードが接続されていたスロットに接続する.

とあるので,deleteElementの後に実行しているReConnectは不用である.ReConnectの説明では「Reconnectではそのノードの元の接続先については何も操作していない.通常はnodl_floatで切断してからReconnectを実行する.」とあり,むしろnodl_floatに任せた方が安全なのではないか?一度バックアップを取ってから書き換えを試みる.

その前にEXTRA_ANCHORをEXTRASLOTにリネームしておこう.この増設スロットはスロットゼロに近いもので,アンカーとして用いられることはおそらくないものと考えられる.⇒18箇所あった.

ZTシステム構成図7.ZELを#223 anod[]で開いてGENEBOX:getFloorで停止した.直接基本世代枠リストのインデックス(1発進)がゼロになっている.⇒最初#223で開いて,#202 NODULEでソートすると再現できる.ということは,系統並び替えを実行するときの初期化が不十分ということになる.

障害は系統並び替えの冒頭の基本世代枠リストのcancel中に起きている.フェーズはTOPOLOGICALSORTだ.ゼロが返るというのは,そのノードがリストに含まれていないことを意味する.この世代枠は削除処理中(disposing)でリスト上には存続しているが,おそらくリスト先頭要素でtop()から検索したのでは見つからないのだろう.リスト上に存在しない場合には0ではなくFARFUTUREを返すようにしておこう.

全体図テストに掛けてみたが,このサンプルではRemoveの対象が端点共有ノード対でactionが偽というパターンは出てこない.ただし,actionが真というのはあり,このときはリストに2点登録されているのに,deleteElementでリストが空になってしまう.ただし,datacountは1になっている.この動作はおかしいので追いかけてみよう.

#1 couplingを開いて終了で再現できる.このケースではリストにはPAIRBOX #33492, #33479, #32917 の3点が登録されていて,#33492と#33479は端点共有束を構成しているが,#33492の削除で#33479もリストから消えてしまう.

PAIRLIST::Removeではかなり技巧的なことをやっている.増設スロットが終了処理中に抹消されてしまうことから防禦するために,冒頭でこのスロットを空にしてからdeleteないしnodl_floatを実行している.このオブジェクトは事後にReConnectされるので最終的にはノーマルな状態に戻っているようだが… ⇒対応修正して25行のコードがわずか1行で済むようになった.これですよ,これ,これが見たかったんだ!

#82 SIMPLEGRAPHでソートしてLIST::nextでリスト末尾不整合のエラーが起きた.PAIRLIST::Removeに仕掛けたcheckdatacountがエラーを出している.MakePairListClean→ … → RetrieveGhost→ Removeというフローだ.RetrieveGhostはノード対を1個抹消するだけの関数だが,これ1本で475行もあるという十分大きな関数だ.

PAIRLISTは分岐のあるリストなので何をもってリスト末尾とするか?で議論があるような気がする.PAIRLIST::nextは端点共有を含むすべての要素を巡回できるので,この順序で末尾というのが順当な定義と思われるが,直列リスト末尾という考え方もあり得ないことはない.それにしても,リスト要素がまだ10個もあるのにリスト末尾が空というのはどこかで間違えているのだろう.

PAIRLISTはdataCountDownを持っていない.これはかなりおかしいのではないか?PAIRLIST::dataCountDownを作らなくても,nodule *preceding(nodule *)という関数を作れば対応できる可能性はある.多分この関数は仮想関数にする必要があるのではないかと思われるが…DATALISTはnodule*preceding(nodule*)を持っているので試してみよう.⇒preceding仮想関数は実装できたが,問題がある.

確かにすべてのノードに全順序を与えて,その末尾をボトムとするというのは合理的だが,現行論理の中にはおそらくそれに対応していないものがあるのではないかつまり,端点共有ではないノード対をリストに追加しようとしたときの接続先がボトムになっている.どうすればよいか?端点共有というのは例外であり,通常のノード対は直列リスト上にあるので,ボトムと言えばそちらの方が正解のような気もするが…

PAIRLISTではリスト上の次ノードを取り出すためにnextとnextboxという2つの関数を使い分けている.nextはリスト上のすべての要素を巡回する関数,nextboxは基本クラスのnextを呼び出すだけの直列リスト専用関数だ.nextboxをnextとしていれば,多分なにもしなくても動作していた可能性はあるが,余計なことをしてしまったものだ.この名前を入れ替えるのはかなり難しい.通常はリネームするとコンパイルエラーが出るのでどこを修正すればよいのかが分かるが,nextは基本クラスの関数なのでそれがなくてもエラーにはならない…

現行の動作は直列リスト型で動作している.⇒問題はそこではない.端点共有が存在する場合には,直列リスト上にないノードが直列リスト上に現れるという問題だ.dataCountDownはLISTのメンバー関数なのでそこまでは手が回らない.そのノードが末尾ノードのときにはprecedingで上位ノードを取り出しているが,この関数を曲げて後方にある端点共有ノードを引っ張り出すというのはあまりに強引だ.ここは素直にPAIRLIST::dataCountDownを導入すべきだろう.

「PAIRLISTにdataCountDownを導入@20201122」というコメントがあるが,どこにもその形跡はない.20201203にフィックスしたことになっているが,内容的に同じなので割愛したのかもしれない.⇒できた.LISTがbottomlistを書き換えてしまうので事前にチェックし,直列リスト上にあることを確認するためにIsOntheListを使った.