終点が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を使った.

総訪問者数30000人まであと一人

あと一人で総訪問者数が3万人に達する.この幸運をつかむラッキーな人はどこの誰だろう?気付いたらスナップショットを送ってください.撮れた!わたしじゃないよ.現在オンライン中の人数が2になっている…

image

これはつまり,スナップショットが撮れたからその人が当人とは限らないということだね.プログラムのデバッグてのはこんなものではないかと思う… しかし,こういう瞬間に立ち会えるということは,ラッキーな人は一人じゃないってことかもしれないね.いままでこんなことは一度も経験したことがない… 少し運が向いてきたのかな?

BUG20-12-02 01-34-26.ZELを#195 ghostnodeで起動して,PAIRLIST::CheckNormalizedSectionで(pbox->getpairlist() != this)という理由で停止した

障害の発生している地点は昨日とは異なるが,同じ状態で停止している.⇒修正が徹底していなかった.deleteElementではdeleteの場合とnodl_floatの場合を処理しているが,nodl_floatではデストラクタの出番はないため,親リストの内部処理でdataCountDownを実施しなくてはならない.⇒pairbox.cppに未修正の論理が残っていた.これで「blackflagを廃止する@20201203」の修正は一応通ったことになる.

これまでdataCountDownを直接呼び出していたところはすべてdeleteElementの呼び出しで置き換えた.また,unsettledのセット・リセットはdeleteElementで行っているので,呼び出し側では不要となる.blackflagはまったく使われていないので,「blackflagを廃止する@20201203」を現状でフィックスしてよいだろう.PAIRLISTではあと5箇所nodl_floatが使われている.うち一つはPAIRLISTそのものが対象だが,他の4つはdeleteElementに置き換える可能であることを確認してみよう.⇒問題なさそうだ.

PAIRLISTにはまだdataCount()を直接操作しているところがいくつか残っている.これらをすべて共通処理として隠蔽したい.⇒その前に「参照チェックリストの廃止」を片付けてしまおう.この参照リストはデバッグ用にシステムに1個だけ設置されているが,すでに完全参照リスト管理を持っているので不要になっている.このリストはNODULEの特殊スロットにリンクされているが,間違い易いので廃止しておくのが賢明だ.バックアップを取ってから始めることにしよう.⇒実装した.問題なく動作するようになった.

この修正は後戻りしない修正とし,バックアップを取った上で「nodl_floatをdeleteElementで置換@20201204」と「XPAND_ANCHORを廃止する@20201204」をフィックスすることにする.このバックアップは「ZELKOVA 2020-12-04 改訂」として保全した.⇒修正完了した.dataCount()を操作しているところが12箇所ある.リストアップしてみよう.

  1. PAIRBOX::MoveSamePoint

PAIRBOX::MoveSamePointの論理に誤りがある この関数ではノード対(共有端点リスト)を別の共有端点リストに移動しているが,移動元リストのdatacountをインクリメントしている.明らかにこれは誤りだ.⇒修正した.

LIST::remove, _removeを廃止する DATALIST::remove, _removeも廃止できる

この修正の影響範囲は予想外に大きい.GENELIST,NODELIST,EDGELIST,TRIBELISTなど軒並みLIST::removeを使っていた.修正後に走らせてエラーは出ていないが,これらの修正に関わるオブジェクトのデストラクタではdataCountDownを呼び出すようにしなくてはならない.nlistとNLISTのすべての派生クラスで対応する修正が必要だ.⇒いや,すでにほとんど入っている.⇒入っていないものもある.nlist<MARGBOX>とnlist<Bobject>だ.前者はTOPOLOGYのSymmetryListやTooYoungListとして使われている.後者はMARGBOX::SymmetricActionの中で一時的に生成/使用されている.現用サンプルではこの辺りの仕掛けはまったく作動していないようだ.

ZTシステム構成図7.ZELを開こうとしてPAIRLIST::ResetPairIndexでデータカウント不一致が発生した.再起動して新たに開こうとしたら,今度はcheckdatacount→nextboxで停止した.ノード対とノード対リストでチャンネルの不一致が起きている.

このエラーは少なくとも「LIST:_removeを廃止する@20201204」の修正とは無関係だ.つまり,それ以前から発生していたものと思われる.すでに「後戻りしない修正点」を超えてしまっているので,戻ることもできない.⇒この障害はPAIRLISTのデストラクタの中で起きている.つまり,PAIRLIST自体がすでに削除中の状態になっている.しかも,このループに入る前にはcancelが実行されているので,リストは完全に空になっていなくてはならない.

確かに,datacountはゼロだし,listtopは空になっている.それでは何をやっているのか?⇒後続リストの繋ぎ替えということをやっているようだ.確かに後続リストというのは存在している.要素は1個だけだ.ResetPairIndexに入る直前にPindexを書き換えているので,不一致になるのは当たり前だ.⇒こういうのはあまりよくないと思う.Pindexの値を引数で与えるようにしておこう.

ResetPairIndexを実行する前の状態ですでにデータ数不一致は起きている.これはTribeRelocationの出口近くのFoldingChannelsで起きているので,おそらくこの関数の中で失敗しているのだろう.⇒TRIBELIST:FoldingChannelsに入るところですでに不良が起きている.⇒ReduceMultiCardで起きているようだ.MoveChannelToで失敗している.⇒PAIRLIST::takeoutが間違っているようだ.⇒deleteElementを実行してdatacountが変化していない.⇒DATALIST::findが仮想関数になっていなかった.しかも,PAIRLISTは自前のfind関数を持っていない!いや,持ってはいるが,シグネーチャが一致しない.⇒仮想関数をオーバーライドして固有の関数を呼び出すようにした.

TRASHCAN::ReuseWasteで((int)Lastnum() != NEWOBJECTCNT)により停止した.lastnum=247111,NEWOBJECTCNT=247112でNEWOBJECTCNTの方が1多い.ということは通番を持たないオブジェクトがどこかにいるということになるのだが… NEWOBJECTCNTはTRASHCAN::ReuseWasteでカウントされる.lastnumをカウントアップしているのはsetNringで,以下のコマンドで新規番号が生成される.NRING_INSERT,NRING_REUSE,NRING_EMBED.

やはり,curnodは廃止する以外ないのではないか?operator new がネストして実行されている.SWOの中でcheckdatacountを実行しているが,この中でnextboxを実行してcurnodが更新され,参照の付け替えが起こるので参照リスト管理で新しい参照リストが生成される.リスト末尾も参照だが,検査中には変化しない… operator newの間はcheckdatacountしないというのが一番確実なのではないか?unsettled をnoduleクラス全体に適用できるようにするというのがよいと思う.

いや,少し勘違いしている.unsettledというのは環境ではなく,個物の状態でそれも個体ではなく,親ノードの状態だ.operator new という状態を伝達するには,何か環境を示す変数が必要になる.一番簡単なのはグローバル変数を作ってやればそれまでだが…いずれにしても,nextのような動作で参照が発生するというのはよくないと思う.curnodは即刻廃止すべきなのではないか?⇒やってみることにしよう.一度バックアップを取っておいた方がよかろう.

「LIST:curnodを廃止する@20201124」という仕掛りのオプションがある.⇒片付いた.以下の関数を廃止した.

  1. nodule *insert(nodule *listnod, void *dp = NULL)
  2. nodule *next(void)
  3. void *listnext(void)
  4. void *listbefore(void)

見通しをよくするために,以下の3つのオプションは即フィックスすることにする.

  • LIST:_removeを廃止する@20201204 22箇所
  • ResetPairIndexに引数を渡す@20201204 5箇所
  • LIST:curnodを廃止する@20201124 30箇所

終わった!バックアップを取っておこう.

LIST::appendは廃止する.curnod廃止によりLIST::putと同義になったため.⇒まったく使われていなかった.

PAIRLIST::removeも廃止できる.

舵を切らずに直進するという選択

どうも進捗が捗々しくない.少しピッチを上げることにしよう.

▲BUG20-12-02 01-34-26.ZELを起動→終了してPAIRLIST:checkdatacountでデータ数不一致が検出される datacount=3に対し,実カウント2

仮修正や条件コンパイル文が錯綜してフローを解析するのがとても難しい.この際思い切ってすべて現状でフィックスしてからデバッグに入ることにする.舵を切らずに直進するという選択が正しかったかどうかはいずれ判明することだ.以下のOPTIONSがある.

  1. 確定世代番号を導入する@20201121 41箇所
  2. EraseFamilyTreeに終了モードを導入@20201124 3箇所
  3. 同一世代人名枠チェーンをリングに転換@20201201 9箇所
  4. PAIRLISTにdataCountDownを導入@20201122 22箇所
  5. blackflagの論理逆転@20201128 16箇所
  6. toplistを関数化(参照化)@20201201 2箇所
  7. UNDO保全ノードをNringから除外@20201202 3箇所
  8. コアシステムの参照を関数化@20201127 28箇所

Dドライブが満杯になってしまったので,バックアップをEドライブに移動.84,211個のファイルを移動して空き容量66GBになった.これでまたしばらくは遊べる.上記OPTIONSはすべてフィックスした.

上記エラーはPAIRLIST::Remove→LIST::dataCountDown→DATALIST:dataCountDown→ DATALIST::findで起きている.この直後にdatacountがデクリメントされるのだから不一致となるのは当然のようにも思われる.findは接続ベースの関数だから削除操作中にも使われるが,checkdatacountをこのようなクリティカルゾーンで動作するようにできるだろうか?⇒内部処理であれば,removingがONであることで判定できる.外部処理の場合はそれもできないが,dataCountDownの中なら可能かもしれない.ただし,dataCountDownが実行された後でもremovingの状態は継続している.blackflagがONになったときはdataCountDownが完了したとみなすことはできるかもしれない.

PAIRLISTは単純な直列リストではなく,そこから分岐した枝リストをもっているためPAIRLIST::Removeはかなり難しい関数になっている.しかし,木構造を持っているのはPAIRLISTだけではないので,これがこなせないようではZTに明日はない.暫定的にPAIRLISTにunsettledというフラグを設けて,この区間ではcheckdatacountは無効であるということにしてみよう.⇒エラーは解消した.というより回避できるようになった.PAIRIST::cancelの格段でcheckdatacountを実行しているのでデータ整合性の問題は発生していないことが確認できる.この辺りはもう少し整理しておきたいところだが,先に進むことにしよう.⇒ここで一度バックアップを取っておこう.

仮修正がまだ34個残っている.⇒クリアした.#ifdefと#ifndefも書き換えておこう.#ifdefが44個,#ifndefが31個あった.INCOMPLETE:に2つオプションが入っている.「タイトル枠無しで動作する@20201119」と「EraseFamilyTreeで削除しない@20201120」だ.前者はTITLEBOXが空という状態を認めるという仕様変更だが,まだ動作確認していないので追ってということにしておく.後者はまったく参照されていないので捨ててよい.もう一つ,「nodule_floatは間違っている@20201120」というのがある.これはもう少し置いておこう.懸案事項が大分積み上がっているのでメモっておこう.

  1. OnListremove,removingを廃止
  2. CallSetCouplingPtrを始末する,終末期のフェーズ
  3. dataCountDownの一般化,blackflagの廃止
  4. nodl_floatを終了処理に含める
  5. unsettledの一般化,On~フラグの代替?
  6. 検査区間(フェーズ)の点検,INITIALSTATEなど
  7. エラートラップを点検,throw errを廃止
  8. グローバル変数の廃止/局所化
  9. ReferenceList(参照チェックリスト)の廃止

OnLISTremoveは,DATALIST::cancelとLIST::cancelで設定され,リスト要素削除中のインジケータとして用いられてきたが,賞味期限が過ぎたので廃止してよい.unsettledを使ってほぼカバーできる.unsettledの意義付けはいまのところややあいまいだが,基本的には「接続関係が流動的」と解される.現行ではこれに類似したフラグとしてOn~というのが100個ほどある.関数~を実行中という意味のフラグだが,用途によってはunsettledで代替できるような気もする.

removingもリスト要素削除中フラグで,LIST::deleteElement,LIST:_remove,DATALIST::deleteElement,NLIST::put,PAIRLIST:Removeでセットされている.unsettledの区間はremovingよりやや広く,これだけでremovingをカバーできる.unsettledは現在PAIRLISTのメンバーとして定義されているが,有用性が認められればDATALISTファミリからnoduleクラス全体まで拡張することが考えられる.

nodule::nodl_floatはオブジェクトをシステムから切断してフロート状態にするための汎用関数で,そのオブジェクトの下位コンポーネントは接続したまま取り出される.ただし,そのオブジェクトがリスト上にある場合はそのオブジェクトの後続ノードはリストに残される.Disconnectもこれに類似した関数だが,Disconnectの場合には後続リストとともにそっくり取り出される.繋ぎ戻すための関数としてはConnectとReConnectがある.

PAIRLISTではnodl_floatでフロート化したオブジェクトを別のリストに移動する場合があり,このときはリストから切断するという操作が実施されるため,実質的に削除と同等の操作が必要になる.他のクラスではスロットの移動など接続関係の操作のみで完結するため,「事前処理」のようなものは必要としていない場合が多い.先にblackflagの廃止を試してみることにしよう.⇒実装した.

▲BUG20-12-02 01-34-26.ZELを#195 ghostnodeで起動して,PAIRLIST::CheckVerticallyInverseで(pbox->getpairlist() != this)により停止した 

自分の始末は自分で付ける,すべて自己責任

オブジェクトのノーマルな終了手続きとアノーマルな終了手続きを切り分けるためにblackflagというフラグを導入しているが,最終的には廃止して,状況に関わりなくいつでもどこでも単にdeleteを実行するだけで整合的な終了措置を実現できるのではないかと思う.なぜなら,blackflagは親オブジェクト側でdataCountDownを実行した,つまりオブジェクトのデストラクタではdataCountDownを実行しなくてよいということを伝達するだけのものになっているからだ.

フラグを廃止しオブジェクトのデストラクタでつねにdataCountDownを実行するようにしても動作には変わりはない.ノーマルな終了処理とアノーマルな終了処理が実質的に同一の操作になる.これでオブジェクトの親は親であることに責任を持たなくてもよいということになった.逆に言えば,自分自身の始末を付けるのは自分であり,すべて自己責任ということにもなる…オブジェクトの世界は意外にシンプルでわかり易い.(いや,人の世界では元々そうだ.親に先立つのはノーマルではないとみなされている…)

このようなこと(秩序立った終末期処理)を可能としているのは,やはりZTシステムで採用している「(双方向)接続」という関係の存在ではないだろうか?「(単方向)参照」しか存在しない世界でこのようなことができるだろうか?不可能ではないかもしれないが,おそらく「接続」と似た機構がどこかで必要になってくるような気がする…

BUG20-12-02 01-34-26.ZELを開いて,孤立した#251 noduleを削除してUNDOSYSTEM::CommnadEndで(unode->create < 0 && !unode->address->Deleted() && !unode->address->checkcid(‘P’))というエラーが出た

CommnadEndが呼び出されるときにはすでにdeleteは実行されているのだから,Deletedになっていないというのはかなりおかしい.⇒ゴミ箱に入っているのは確かだが…Nringから移動したという形跡がない.⇒VAIOにインストールされているバージョンではこのような現象は起きていない.VAIOにインストールされているのはV2.2.0.016 R2020-11-02だ.⇒とんでもないミスを冒していた.~noduleをdisoposingがONという理由でパスしていた.完全な間違いだ.

同上の操作を行って,UNDOSYSTEMの出口のTOPOLOGY:topologicalSorting→ChecWasterCountでカウントが合っていない waste + Nodecount() – (NEWOBJECTCNT – REUSECOUNT)はつねにゼロでなくてはならないところ,差分1が生じている

9994 = waste=23 + Nodecount=9971
9993 = NEWOBJECTCNT=129753 – REUSECOUNT=119760

NEWOBJECTCNTとREUSECOUNTの数字はおそらく間違いないので,waste+Nodecountの方に誤りがあるのではないか?ダブって数えている可能性がある.削除されたオブジェクトがゴミ箱に入っていることは確かなので,Nringのカウントが間違っているか,ないしリング上に残っているのだろう.⇒ゴミ箱への移動とNringからの切断が同時に行われていればまず,このようなことは起きないはずなのだが…

ゴミ箱への投棄TRASHCAN::DumpWasteはnodule::operator deleteで実行される.Nringからの削除はsetNringのコマンドNRING_DELETEないし,NRING_DISCONNECTで実施される.NRING_DELETEではオブジェクトのDELETEDをONに設定するがまだコンタクトは切れていない.NRING_DISCONNECTでリングからの切断が実行される.しかし,これが実行されるのはIfDuplicatedという特殊なケースだけだ.⇒いや,違う.NRING_DELETEのケース文ではブレイクしないで下に続くNRING_DISCONNECTを実行している.

setNring(NRING_DELETE)はnodule::operator deleteでDumpWasteの直前に実施されている.条件はShadowedではないこととDELETEDがゼロ以下であることだ.削除されたカードはUNDOで保全されてShadowedとなっているため,Nringから切断されていない.コメントでは「UNDOで保全されたShadow付きノードもゴミ箱に入れる@20170825」としている.このコメントは「UNDOで保全されたShadow付きノードはNringとゴミ箱にダブル登録される」と解釈されるが,これはChecWasterCountの指針と合致しない.

2017年のログが残っているだろうか?「2017-08-25 UNDOで保全されたオブジェクトをゴミ箱の管理下に置く」という記事があった.これは決定事項であり,「仕様」と考えるしかなさそうだ.「UNDOで保全されたオブジェクト」とは一般には削除されたオブジェクトであり,それがゴミ箱に移動するというのは妥当であるとしても,Nringとの重複というのはかなり問題だ.ゴミ箱の中のオブジェクトはリサイクルの対象となるが,Shadowedオブジェクトは除外されているようなので,その点で誤動作することはないとしても,あまり感心した状態ではない.

いまここではUNDOシステムの細部まで踏み込みたくはないし,UNDO自体はとりあえず問題なく動作しているように思われるのでUNDOシステムをいじることはしたくないが,カウントは一致するようにしておかなくてはならない.NringにはNRING_INVALIDATEという操作があるが,「DELETE=NRING_INVALIDATEであることとShadowを持つことは必ずしも一致しない」とあるので,単純にこれだけを除外すればよいということにはならない.ゴミ箱に入っているという属性はないので,チェックするとすればIsInTrashCanを実行しなくてはならない.

ここでは暫定的にNringのnodecountをカウントするときに,NRING_INVALIDATEでかつShadowedであるものを除外するとしてみよう.⇒NRING_INVALIDATEはコマンドでDELETEには(通常の削除されたオブジェクトと同様)負値が入る.NRING_INVALIDATE→ Nring上でDELETEが負という状態と一致する.実質的にはこれはShadowedと一致しているはずだ.NRING_INVALIDATEはカード合併時にも適用されているが,その後すぐに削除されるので状態としては維持されていない.いや,まだおかしいことがある.

NODEREFLISTが11個もダブル登録されている.⇒これはおそらく,IsInTrashCanの誤動作だ.いや,誤動作ではない.削除されたカードに付帯してゴミ箱に移動しているためだ.ゴミ箱はCIDごとのリストになっているので,付帯オブジェクトはゴミとしてはカウントされないから,重複にはならない.⇒nodule::IsOntheListという関数を新設して直列リスト上のノードだけを拾うようにした.⇒nodecountではなく,activeringを使えば数字は一致する.今日のところは取り敢えず,これで逃げておくことにしよう.

上記サンプルを起動→終了してCloseFamilyBaseのとき,~TRIBEBOXでdatacountが負になった 障害ノードは系列枠#8478.DATALIST::findにcheckdatacountを仕掛けたところ,TRIBELISTではひどいデータカウントの不一致が発生していることが分かった.他のリストでは起きていないので,何かTRIBELISTに限った事情があるように思われる.DATALISTでdatacountをprivateにしてみよう.datacountを取り出すsizeという関数があるが,リストなのでlenとリネームした.datacountを直接操作している関数がいくつかある.

  1. NLIST::put
  2. nlist::put
  3. PAIRBOX::MoveSamePoint
  4. PAIRBOX::ChannelChange
  5. PAIRLIST::puttail
  6. PAIRLIST::takeout
  7. PAIRLIST::cutout
  8. NAMEBOX::makePairBox
  9. NLIST::insert

datacount→len()に書き換えた箇所は58箇所.TRIBELISTはNLISTの派生クラスなのでこれらが関係しているとすれば,上の(1)putかないし(9)のinsert,あるいはその両方と考えられるが,これらの関数でカウントダウンが起きる可能性はない… 暫定的にdataCountというdatacountの参照※を返す関数を作っておこう.⇒LISTがDATALISTのfriendクラスになっていた.⇒datacountが負になる原因が分かった.dataCountDownが重複して呼び出されていた.最初は~TRIBEBOX,ついで~noduleからも掛かってくる.これはblackflagがONになっていないためだ.dataCountDownの中でONにすることで解消した.

※ここで言う『参照』とZTシステムで常用している「参照」では意味が異なる.ZTで「参照」というときにはつねにオブジェクトを指すポインタ(メモリ上のオブジェクトのアドレス)を意味している.「リンク」という語もこの意味で用いられる.これに対しC++言語で言う『参照』はオブジェクトの実体を示すものとして定義され,『参照』を別の変数に代入すると,そこには元のオブジェクトの複製が格納される.また,『参照』を返す関数をオブジェクトの実体そのものとして操作できる.

▲同上サンプルを起動→終了してPAIRLIST::checkdatacountでデータ数不一致が発生する.⇒多分これは擬似的なものだと思う.PAIRLIST::Removeの中で起きているので,リストには削除対象ノードが含まれているためではないか?いや,数字はその逆だ.datacountつまり,論理値が3でリスト上の実カウントが2になっている.これはおかしい.PAIRLISTの処理は多少込み入っているので,先にロジックを整理してしまった方がよいのではないか?

昨日やり残したところから始めるとしよう

昨日やり残したところから始めるとしよう.「toplistを関数化し,もし,リスト先頭要素が「脳死状態」と判定された場合には,次ノードを返す」という修正だ.⇒DATALIST::toplistをlisttopとリネームし,toplistをすべてtoplist()に一括変換した.⇒一つ問題が出てきた.LIST::swapではtoplist を直接書き換えている.これは親子接続関係の書き換えも含むかなりクリティカルな操作だが,listtopはDATALISTのprivateとして外部からのアクセスを禁止するようにしたい.⇒この関数自体をDATALISTに移管するのが適当ではないか?コンパイルは通ったが,リンクでPAIRLIST *toplist()が未解決の外部参照になった.関数の内部でローカル変数としてtoplistを使っていたところがある.

ZTシステム構成図7.ZELを#201 noduleで起動→終了してnodule::operator deleteで参照カウント残が出た.障害ノードはTRIBEBOX#7865,参照は2つ残っている.TRIBELIST#32の枝4と5だ.これはbrokenとstarttribeに相当する.これをクリアするのは,TRIBELIST::dataCountDownの役割だろう.⇒いや,そのロジックはすでに実装済みだ.DATALIST::findはリスト要素検索のとき,toplist()から検索している.DATALIST の操作ではtoplist()は使えないのではないだろうか?⇒いや,多分これだけだ.findで削除中ノードが検出できることと,toplistの関数化は裏表の関係がある.どちらも欠かせない.

これで少なくともリスト操作に関しては欠けるところのない状態になった.大きな修正ではないがバックアップしておこう.⇒動作はかなり安定してきているが,この際なので懸案を片付けておきたい.秩序だった解体を実現するために不可欠な要件はリストやチェーンを崩さずに解体を進めるという点にある.リストに関してはほぼそれを満たすところまで来ていると思われるが,チェーンに関してはほとんど未着手と言ってよい.ポイントはチェーンを管理する親ノードを子ノード側から突き止めることができるようになっていなくてはならないという点だ.

たとえば,同一世代人名枠チェーンの場合,NAMEBOXでは自ノードの世代から所属世代枠を決めなくてはならないが,世代というパラメータには有効期間というのがあるので,つねに確実に操作することを保証できない.これを避けるためのもっとも簡便な方法はチェーンをリング化することであると思われる.チェーンを操作する汎用関数のセットはすでに存在しているが,現行論理はそれなりに閉じているので,これをもう一度書き換えるのも得策ではない.要は末尾をつねに親オブジェクトにリンクすることになるのだから,それほど難しい修正ではない.

どこから着手すればよいか?Sansyo(GENEBOXsSAMEGENE)を操作している関数は2つある.GENELIST:AddSameChainとGENEBOX:ReleaseSameChainだ.また,GENEBOX::FindSameChainという検索関数もある.GENEBOXでは samegeneとbottom1を管理している.NAMEBOX *samegeneがこのチェーンを構成するためのスロットだ.もし,既存論理がbottom1を正しく管理できているのなら,修正箇所を見つけるのは簡単だ.Sansyo(GENEBOXsBOTTOM…を実行しているところを見つけて,bottom1からGENEBOXへの参照を設定するだけでよい.このような場所が8箇所ある.この修正は「同一世代人名枠チェーンをリングに転換@20201201」としておこう.修正箇所は以下の通り.

  1. GENEBOX::CleanSansyo
  2. GENELIST::AddSameChain
  3. GENEBOX::ReleaseSameChain

無論これだけでは足りない.少なくとも以下の修正が必要だ.

  1. 末尾ノードがリストから切断されたとき→sanemgeneをリセットする
  2. リングを走査するときの脱出口を設置する

上記サンプルを起動→終了してGENEBOX::CleanSansyoで末尾ノードが後続ノードを持っているというエラーになった.⇒これはエラーではない.親のGENEBOXだ.~NAMEBOXで同一世代人名枠チェーンの親世代枠を探索するのに,リングを使うように修正した.

同上サンプルを#24 UNDOCHAINでソートしようとして((primary->IsPrimGhost() || primary->IsLongTail() || primary->IsBundledString()) && (action && PHASE > CHAOTICSTATE && !primary->getghostbits(YOUNGERWIFE)))となったため停止した.PAIRLIST::Removeでノード対を削除しているところだ.系統並び替えのステージ【9】でCheckZeroPositionを使って「ZTYW婚の適正配置を実施」しているところだ.

この条件式の意味は,「ノード対の仮ノードは消去された仮ノードか長い尻尾ないし束ねられたLDR垂線のダミー仮ノードのいずれかで,かつ,検査中ではなくフェーズはINITIALIZEDより大で TYW参照を含む長い尻尾の中間に設置されるノード対の仮ノードでない場合」だが,IF文の条件が(PHASE > CLEARTABLE)となっているので,かいつまんで言えば,「TYW参照を含む長い尻尾の中間に設置されるノード対の仮ノード」は認められない,ないし,そのようなものを検出したときには停止するという意味になる.

YOUNGERWIFE(TYW参照を含む長い尻尾の中間に設置されるノード対の仮ノード)という属性が問題だ.この属性はNAMEBOX:CheckHorizontalDraftでセットされている.この関数は「長過ぎる尻尾のダミーノードの水平位置に差異が生じた場合,その部分をカバーするノード対を新規に追加する」ためのものだ.字句通り解釈すると,長い尻尾が曲がりくねることを認める(サポートする)と言っているように聞こえる.エラーを無視して描画できるので,チェックしてみよう.

image

画面上には存在しないので,どこかでリセットされたものと思われる.いや,間違えていた.条件式は「YOUNGERWIFEではないもの」だった.ということは,「かつ」の後ろはほとんどの場合成立するので,最初の「消去された仮ノードか長い尻尾ないしLDR束の仮ノード」であれば停止することになる.どうも意味不明なので「PAIRLIST:Removeの検査で停止しない@20201201」で一時停止しておくことにする.

大体問題なく動作するようになったようだ.チェーンはまだこの他にもいろいろ使われているが,これまであまりトラブルを起こしてこなかったように思われるのでしばらくこれで運用してみることにする.ここで一旦これまでの修正をフィックスしておくというのがよいのではないだろうか?その前に一つリリース版を起こしておくことにしよう.⇒Version 2.2.0.018 Release 2020-12-01とした.

上記サンプルを#202 NODULEでソートして終了しようとして,nodule::operator deleteで参照カウント=2というのが出た.⇒再現する.PAIRBOX #27851がEraseTreeViewの中で削除されるところだ.PAIRLIST#12519からの参照が2つ残っている.枝2と3はbottom2とcurnodだ.⇒PAIRLIST::findの冒頭でtop()が空を返している.LIST:topはtoplist()を返している.⇒PAIRLIST::findからDATALIST::findを呼び出すしかないだろう.toplistの関数化ということはtoplistの参照化を意味している.つまり,関数には参照ベースの関数と接続ベースの関数の違いがあることが認識されなくてはならない.

DATALISTは直列リストだが,PAIRLISTは端点共有リストを含む複合リストになっている.物理(接続)ベース)のfindと論理(参照)ベースのfindを切り分けるしかないのではないか?物理ベースのfindを使う場所は限定されているはずだ.前者を_find,後者をこれまで通りfindとしてみよう.あるいはremovingでクリティカル区間を区切った方がシンプルになるかもしれない…しかし,アノーマルな削除ではremovingを立てることはできない.⇒findにデフォルトFALSEの引数を設けて,TRUEのときだけ物理ベースで動作するというのはどうか?それがよいかもしれない.やってみよう.いや,それよりもtop関数に引数を渡す方が簡便だ.

DATALISTはtopという関数を持っていない.toplistとtopを使い分ければよいのではないか?topを論理ベース,toplist を物理ベースという使い分けが考えられる.現状では両者が混在しているが,まず,top()に一本化した後,必要な箇所ではtoplistを使うようにすればよい.これもややクリティカルな修正なので一度バックアップを取っておこう.

PAIRLIST::findを元の論理に戻しただけで解決した.先頭要素はtop()で取り出している.ただし,終了時参照カウントが残る事象が再発している.⇒DATALIST::toplistで物理先頭リスト要素を返すようにして,PAIRLIST::findではtoplistでリスト先頭を取り出すようにした.DATALIST::findは物理ベースで動作しているので,これで一応すべて筋が通ったことになる.何か問題が発生したらそのとき対応することにしよう.後は,現状でフィックスするだけではないだろうか?⇒まだリリース版を起こしていない… ⇒リリース版のビルドでエラーが出ている.

cl : コマンド ライン warning D9025: ‘/Zi’ より ‘/ZI’ が優先されます。
cl : コマンド ライン error D8016: コマンド ライン オプション ‘/GL’ と ‘/ZI’ は同時に指定できません

/ZI はエディットコンティニュをサポートする形式で PDB ファイルを生成する./GLはプログラム全体の最適化を有効にする.⇒どうも/ZIをリリース版の方で設定していたようだ.道理で,なんだか効きが悪いと思っていたのだが…

▲debugビルドで以下の警告が出る.「warning LNK4075: /EDITANDCONTINUE は /SAFESEH の指定によって無視されます。」以下のURLにvcxprojファイルを書き換えて対処する方法が書かれていたので試してみる.

https://qiita.com/gocha/items/d690f1240813aef8b303

かなりまずいことになった.ZelkovaDLL3プロジェクトが(利用不可)になり,プロジェクトファイルを元に戻しても復元できないようになってしまった.⇒バックアップをベースに修正ソースとヘッダだけ差し替えた.⇒これをビルドして以下のようなエラーになった.

1>   ライブラリ D:\ZELKOVA\\Release\ZelkovaDLL3.lib とオブジェクト D:\ZELKOVA\\Release\ZelkovaDLL3.exp を作成中
1>   コード生成しています。
1>d:\zelkova\zelkovadll\src\bugreportdialog.cpp : fatal error C1001: コンパイラで内部エラーが発生しました。(コンパイラ ファイル ‘d:\agent\_work\18\s\src\vctools\compiler\utc\src\p2\main.c’、行 187)
1> この問題を回避するには、上記の場所付近のプログラムを単純化するか変更してください。詳細については、Visual C++ ヘルプ メニューのサポート情報コマンドを選択してください。またはサポート情報 ヘルプ ファイルを参照してください。
1>  link!InvokeCompilerPass()+0x2f79a
1>  link!InvokeCompilerPass()+0x2e9f8
1>  link!CloseTypeServerPDB()+0xd5266
1>
1>d:\zelkova\zelkovadll\src\bugreportdialog.cpp : fatal error C1001: コンパイラで内部エラーが発生しました。(コンパイラ ファイル ‘d:\agent\_work\18\s\src\vctools\compiler\utc\src\p2\main.c’、行 187)
1> この問題を回避するには、上記の場所付近のプログラムを単純化するか変更してください。詳細については、Visual C++ ヘルプ メニューのサポート情報コマンドを選択してください。またはサポート情報 ヘルプ ファイルを参照してください。
1>  link!InvokeCompilerPass()+0x2f79a
1>  link!InvokeCompilerPass()+0x2e9f8
1>  link!CloseTypeServerPDB()+0xd5266
1>
1>LINK : fatal error LNK1000: Internal error during IMAGE::BuildImage
1>
1>  Version 14.16.27043.0
1>
1>  ExceptionCode            = C0000005
1>  ExceptionFlags           = 00000000
1>  ExceptionAddress         = 02ED3E6D (02A40000) “C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\HostX86\x86\c2.dll”
1>  NumberParameters         = 00000002
1>  ExceptionInformation[ 0] = 00000000
1>  ExceptionInformation[ 1] = 00000024
1>
1>CONTEXT:
1>  Eax    = 00000000  Esp    = 00E9E810
1>  Ebx    = 0000000C  Ebp    = 00E9E820
1>  Ecx    = 0000A246  Esi    = 25541F0A
1>  Edx    = 00000000  Edi    = 2263102C
1>  Eip    = 02ED3E6D  EFlags = 00010212
1>  SegCs  = 00000023  SegDs  = 0000002B
1>  SegSs  = 0000002B  SegEs  = 0000002B
1>  SegFs  = 00000053  SegGs  = 0000002B
1>  Dr0    = 00000000  Dr3    = 00000000
1>  Dr1    = 00000000  Dr6    = 00000000
1>  Dr2    = 00000000  Dr7    = 00000000
1>プロジェクト “ZelkovaDLL.vcxproj” のビルドが終了しました — 失敗。

クリーンビルドして解消した.

上記「warning LNK4075: /EDITANDCONTINUE は /SAFESEH の指定によって無視されます。」は以下で解決した.「ソリューションのプロパティから、「リンカー」→「詳細設定」→「安全な例外ハンドラーを含むイメージ」を「いいえ(/SAFESEH:NO)」に変更します。」http://replication.hatenablog.com/entry/2016/04/13/233924

▲リリース版をインストールして上記サンプルの全体図テストを実施して,#195で停止した.#196, #197でも起きる.BUG20-12-02 01-34-26.ZELで再現できる.⇒メインループを3回回っている.メインループを直列処理に変えられるのではないかと思っていたのだが,反例が出てしまった.この件はしばらく保留とする.

今回の改修は2020/11/18頃から始まっている.仕掛りのOPTIONSが9件,仮修正が37件,暫定修正が2件,#ifdefが107箇所,#ifndefが102箇所ある.最初に仮修正と暫定修正をフィックスしてしまおう.

ZTシステムの優位性がむしろ不利益となる

生存期間が過ぎたオブジェクトは通常そのオブジェクトを生成した親オブジェクトによって削除され消滅する.このような手続きをノーマルな終了処理と呼んでおこう.しかし,場合によってはそのオブジェクトの状態に関わりなくいきなり削除される場合もあり得る.このような措置をアノーマルな終了手続きとする.制御された解体とはこのようなアノーマルな終了を含めてすべてのオブジェクトがシステム整合的に秩序立って解体されるようなプロセスである.このために必要な要件はほぼ明らかになった.つまり,①すべての終了処理は事前処理として実行されなくてはならない,②アノーマルな終了の場合にも①の手続きが実行されることが保証されなくてはならないという二点である.

しかし,ここにはややパラドクサルな問題が存在する.①の手続きはオブジェクトQの息がまだ残っている間にQの存在を仮定せずに整合する世界の状態を準備する.つまり,Qがいてもいなくてもよい状態にするというのが①の手続きだ.それならば,①の手続きを開始する以前にQの存在を抹消してしまえばよいのだが,そういう訳にもゆかないという事情がある.つまり,Qの関係者は親オブジェクトPだけではない…親オブジェクトとの接続を切断してしまうと,Qの世界における位置は不定となり,どこからもQを探すことはできなくなる.従ってP⇒Qの関係の切断は終了手続きの最終段階で実行されなくてはならない.ただし,P⇒Qという関係の存在は事後の世界像と矛盾する.つまり,終了手続きが進行している期間中はまだ事前処理は完了していない.

たとえば,リストPからQを削除する手続きを考えてみよう.リストPは①リスト先頭要素,②リスト末尾要素,③現リスト要素という3つのパラメータを管理しているとする.②と③は参照関係だが,①は先頭要素への接続リンクだ.このとき,削除対象のQが先頭要素であった場合には①は現状のまま(手続きの最終段階まで)存続することになり,「事前に事後の世界を実現」したことにはならない.

このような事象の起こる原因は「接続と参照の混用」にあると考えられる.①が参照であれば,Qが存命している間にも別のオブジェクトを参照するように付け替えることは可能であり,そうすればこの問題も解消する.つまり,リストにもう一つの参照用スロットを追加することで問題は解決する.「接続と参照の混用」を回避するもう一つの方法としては,パラメータを関数化するということも考えられる.

たとえば,toplistという変数の代わりにtoplist()という関数を用いれば,モードによって返す値を切り替えるなどのこともできる.あるいは,関数内部に静的変数を置いて,事前に値を設定しておくなどのことも可能である.しかし,どちらの方法もあまり望ましいものとは言えない.モードや内部変数の使用には誤用のリスクもあり,プログラムのメンテナンスを難しくする要因となりがちだ.

「接続と参照」を完全に分離することは可能だろうか?通常(世間一般)のプログラムには「接続」という観念は存在せず,リンクと言えばすべて「参照」のことを意味すると考えられるから,「接続と参照」を完全に分離することが必要であるとすれば,ZTシステムの優位性がむしろ逆に不利益として作用していることを認めることになる.(こんなはずではなかったのだが…)ここでは暫定的にtoplistを関数化し,もし,リスト先頭要素が「脳死状態」と判定された場合には,次ノードを返すようにしておくことにしよう.

▲ZTシステム構成図7.ZELを直系親族図で起動したあと,#201 noduleでソートしてTOPOLOGY::CountMultiCardで停止した.基準ノードの多重出現が起きている.

image

確かにそのようだ.この不良は少なくとも現在インストールされているリリース版で起きている.この不良が最近作り込まれたものか,それともかなり前から起きていたのかどうかについては不明だが,とりあえず保留して先に進むことにする.まず,上記した指針に従って,toplistの関数化から始めることにしよう.

上記操作後,アプリ終了してNAMEBOX::CancelBetweenTwoで停止した.(rightbox->IsRightHandBox())という理由だ.カードが関係する両手に花関係をキャンセルしようとしているが,cancelBetweenTwoを実行したにも関わらず状態が変化していないというエラーだ.この人名枠の結婚チェーンの先頭結婚枠の属性RIGHTHANDBOXが落ちていないという意味だ.結婚チェーンの先頭枠が変化している.処理対象となっているカードは両手に花の左手本人カードだ.BTW解除の対象となっている結婚枠は右手本人の結婚チェーン上にある.BTW解除によってこの結婚チェーンが変化することがあり得るのかどうか?という点が問題だ.BTW解除した直後の状態をSUWで見られるだろうか?ちょっと無理なようだ.すでにCloseFamilyBaseでINITIALSTATEまで落ちている.ファイルを開いたときの状態を見てみよう.

image

SIMPLEGRAPH(グレーのカード)は結婚を10持っている.一つはnoduleを子どもとする単身婚でそれ以外はすべてこのボックス内にある.このボックスではSIMPLEGRAPHは配偶者ポジションにあるため,通常の多婚歴のように1本の結婚線で連結することができないため,BTWを使った「姉妹重婚」の形態になっている.SIMPLEGRAPHの結婚はgraph1との間に子どもがいる他はすべて子なしのため不可視になっている.graph1との結婚が#468, graph2との結婚が#216,graph3との結婚が#202…のようになっていて,最初の#468だけが普通の結婚でSIMPLEGRAPH(1)は配偶者としてgraph1の左に表示されている.それ以外の結婚枠はすべてBTW右手枠として,左手本人の下にある.

不良の原因は単純だ.NAMEBOX::CancelBetweenTwoの下位関数cancelBetweenTwoが(PHASE <= CLEARTABLE)では無動作で復帰するようになっているからだ.2020年11月22日のログに「以下の関数はCLEARTABLEフェーズ以下では無動作で抜けるようにした.これらは比較的規模の大きな関数で理論的には不可能ではないとしても,いま直ちに対処するのは難しい.」として,①NAMEBOX:RetrieveGhostと②MARGBOX::cancelBetweenTwoを挙げている.⇒CancelBetweenTwoも同様に扱うようにした.

▲HOMEキーが効かない 逆に分身カードをクリックすると本人カードにジャンプしてしまう 

上の図面を開いている状態で系図画面設定で人名枠高をインクリメントしてGetGeneBoxで(gnum < 0)により停止した

NAMEBOX:Dispose→getSameGeneBoxからの呼び出しだ.getSameGeneBoxを呼び出しているのはNAMEBOX::Disposeだけだ.エラーが発生しているのは系統並び替えの入口でClearTableを実施しているところだ.この段階ではsamegeneはまったく使われていないはずだが,NAMEBOXから見るとどこから参照されているか,ないし参照されていないのか?を判断することができないので,getSameGeneBoxに問い合わせしようとしているのだが,当然失敗する.基本世代枠の操作では物理世代番号(確定世代番号)が使われる.

系列枠の垂直位置関係が決定するまではこの値を確定することはできない.ClearTableではもちろん系列枠は存在していないので,不定としか言えない.⇒GEN2DEVで系列枠が存在しないときはFARFUTUREを返すようにした.⇒これで取り敢えずエラーは解消したが,抜本的に解決するためには,チェーン操作を世代番号に依存するのではなく,トポロジーによって決定できるようにする必要がある.これにはチェーンをリング化する以外方法はない.

▲姉妹重婚のときは左手本人から出る1本の連結線で表示した方がよい

ZTシステムを構成するための機構部品

オブジェクトQが所属するオブジェクト,つまり,オブジェクトが接続しているか,ないしリスト/チェーン接続しているようなオブジェクトをQの親オブジェクトPと呼ぶことにしよう.オブジェクトQは通常親オブジェクトPによって生成され,Pによって削除される.

チェーンとは同じ番号のスロットに格納されたリンクを使って直列に連結されたオブジェクトの集合を言う※.チェーンを構成するために用いるリンクには「接続」と「参照」がある.接続リンクは双方向リンクで親オブジェクトと子オブジェクトの親子関係を確立するために用いる.参照リンクはさまざまな用途に用いられる単方向リンクで参照先オブジェクトはそのオブジェクトを参照するリンクの個数(参照カウント)は知っているが,どこから参照されているかを知ることはできない.

※グラフ理論で全順序集合を意味する「チェーン」とは意義が異なる

このようなチェーンの中で特に「接続」によって繋がったチェーンを「リスト」と呼ぶ※.スロットゼロはリストを構成するためにのみ用いる専用のスロットである.リストは通常「リストクラス」に属する親オブジェクトによって管理される.チェーンの中で終端が始端にリンクして閉路を構成するようなものを特に「リング」と呼ぶことがある.

※もちろんこれは,プログラミング言語LISPの「リスト」とは関係ない

リストおよびチェーンは通常スロットを1個だけ使って構成されるが,2つ以上のスロットを用いて分岐のあるチェーンつまり,木(二分木/多分木)を構成することもある.リストはゼロスロットを専有するため,あるオブジェクトが複数のリストに所属することはできないが,拡張スロットを用いて2つまでのリストを限定的に用いることはできる.ゼルコバの木システムを構成するための機構部品はこれだけである.

オブジェクトが持っているスロットの個数を価数としてみよう.すべてのオブジェクトは親オブジェクトへのリンクを格納する「親スロット」と「スロットゼロ」を持っているので,少なくとも二価であると言える.ゼルコバの木システムのオブジェクトはすべてNODULEと呼ばれる基底クラスから派生したもので,NODULEは(拡張スロットなど特殊スロットを除外すれば)二価のオブジェクトである.NODULEは純粋仮想クラスで実装を持たないが,それから派生したnoduleはそのままあらゆる用途に使用可能な汎用オブジェクトである.

スロットの実体はオブジェクトへのリンクを格納しインデックスによってアクセスする配列だが,NODULEシステムの最大の特長はオブジェクトにアクセスするときの手段が,名前を持ったクラスメンバーへのアクセスという通常の方法と別に,スロット配列を使った無名オブジェクトへの一般的アクセスという方法があるという点にある.つまり,N個のオブジェクトを所有するオブジェクトをN+2価の素子(モナド)として扱うことができる.スロット配列を使ったアクセスはNODULEないしnoduleの共通操作として完全に一般化されているので,アプリケーションのコードでは固有の動作部分を記述するだけでよい.

ZTシステム構成図7.ZELを#1 couplingで起動→終了してoperator deleteで参照カウントの残留が発生する 障害が起きているのはSIMPLENODE NODELISTの枝2からの参照が残っている

昨日の修正,blackflagの仕様変更がまだ収束していないためと思われる.この障害はアプリ終了→ CloseFamilyBase→ …SIMPLEGRAPH:Dispose→ …DATALIST::cancel→ LIST::deleteElement→ …nodule::operator deleteで起きている.LIST::deleteElementはまだ修正されておらず,dataCountDownが事後に実行されている.dataCountDownをdeleteの前に実行するという修正を「blackflagの論理逆転@20201128」というオプションで入れておこう.⇒ダメだ.なぜだろう?⇒いや,違う.これは別の障害だ.

同上サンプルでoperator deleteの参照カウント残がNODEREFLIST #32811(3)→PAIRBOX #32810 で起きている.枝3はcurnod参照だ.やはり事前処理が欠けている.PAIRLIST自体はdataCountDownを持っていないが,deleteの直前でこれを呼び出さないと始末されない.⇒datacountが負になってしまった.⇒datacountはDATALISTが管理している.ここで操作すべきではない.

~PAIRBOXでgetcouplingが空を返している.昨日の障害がぶり返している.昨日の修正では「cancelでdeleteの代わりにRemoveを使う」となっているが,その修正が見当たらない.バージョンを間違えているのではないだろうか?いや,確かに昨日は一度バックアップに戻っている.昨日の修正は全部落ちていると考えるしかない.⇒対処した.

LIST::_removeで(toplist && !bottomlist())が起きている.⇒dataCountDownを事前処理として実行しているため,deleteが完了するまでは不整合が発生する可能性がある.この区間をremovingとして排除するようにした.

TRIBELIST::SortTribeListで(max != datacount)が起きている.max=21に対し,datacountは16.checkdatacountでチェックすると確かにdatacountの方が間違っている.SortTribeListの入口ですでに不正状態になっている.⇒LIST::_removeでdelete前にblackflagを上げていなかった.⇒blackflagを操作している箇所を総点検する.

TRIBEBOXの削除でoperator deleteの参照カウント残が出る TRIBELIST #32からの参照が2つ残っている枝4と枝5からTRIBEBOX #8183を参照している.broken(破壊された系列枠への参照)とstarttribe(基準ノードの属する始系列への参照)だ.現行ではTRIBEBOXのデストラクタからTRIBELIST::CleanSansyoの呼び出しを止めている.これを実行するとリストパラメータの不整合が起きてしまうためだ.上記参照をクリアするのは,むしろTRIBELIST:dataCountDownでなくてはならない.

~TITLEBOX→TITLEBOX::CleanSlot→clearslot(, true)で参照カウント不一致が発生した.参照が残っているのにカウントはゼロになっている.障害ノードはTITLELINK#7.どうもこれはNODEREFLIST#9の方が誤っているように思われる.⇒DATALIST::deleteElementに「blackflagの論理逆転@20201128」に対応していない論理があった.

LIST::_removeで(toplist && !bottomlist())が起きた.障害が起きているのはPAIRLIST#13545で2つのノード対#32810と#34311がぶら下がっているというだけのものだ.削除対象は先頭の#32810で,処理終了時にはtoplistとbottomlistがともに#34311とならなくてはならないところだ.PAIRLIST::cancelでは冒頭でbottomlistを空にしている.これはまずいのではないか?従来論理では出口でそうしていたようだが…⇒これを修正しただけで動作するようになった.

同上サンプルを開いて,系統並び替えを実行したところ,COUPLING::TopologicalSort→TOPOLOGY::ResetExperimentでbottomlist不正で停止した.NODELIST::CleanSansyoでリスト要素を削除している.削除されているのはSIMPLENODE#31523でSIMPLENODEのデストラクタ中の動作だ.NODELIST#4278がその親リスト.⇒LIST::checkdatacountでremovingを見るようにして解消.

コアでは参照→関数化して参照ゼロを実現

アプリ終了時最後に実行されるCallSetCouplingPtrの入口でアクティブなオブジェクトとして存続しているノードはわずか32個になった.これこそ我が骨の骨,ゼルコバの木のコアシステムそのものだ.これらはすべて「接続」によってリンクされるシステムの骨格であり,ここには「参照」リンクは1つも含まれていない.ZTシステム構成図でこの32個のノードからなる部分図を描いてそれがどう見えるかを試してみよう.

image

かなりわかり易い図になっているが,上図にはまだ欠けている要素がいくつかある.longtable 13個のうち表示されているのは6個だけで,その他にもTITLELINKやTRASHCANなどが未登録だ.かなり難しい問題がぶり返してきているので,データの補充は後回しにして,先に目先の問題を片付けることにしよう.

ZTシステム構成図7.ZELを部分図「コアシステムコンポーネント」基準ノード=1 couplingで開いた後,直系親族図 基準ノード=1 couplingに切り替えようとして,LIST::deleteElement→dataCountDownで停止 DATALIST::findでノードが見つからないというエラー

対象ノードはPAIRBOX#48980でMakePairListClean→ CheckPairList→ …PAIRLIST::Remove→ LIST:_removeで削除されている.対象ノードはノーマルだが,すでにフロート状態になっている.障害はPAIRBOX::MoveSamePointでノード対を共有端点束に移動しようとしているところで起きている.⇒dataCountDownのところでfindするのはそもそも無理なのではないか?外部で削除された場合はデストラクタがoperator deleteより先に呼び出されるから,その時点はまだリストとのコンタクトは続いているが,内部で削除された場合には「事後処理」になるから,当然リストとのコンタクトは切断されていると考えなくてはならない.dataCountDownでは内部/外部を弁別することはできないのだから,find自体が無理と考えるしかない.⇒対処した.これで動作するようになった.

同上サンプルを#201 noduleでソートしようとして,DATALIST:cancel中,PAIRBOX:Dispose→getfamilytreeでgetcouplingが空を返している PAIRLISTはPAIRBOXを直接リスト要素としているはずだが,PAIRBOXの親がLISTELEMENTでその親が空になっている

~PAIRBOXがネストして実行されている.PAIRLIST::cancelでは連続削除ではなく,1点づつ削除しているはずだが…削除対象PAIRBOXのスロットゼロのPAIRBOXは~noduleで前方に繋ぎ替えているはずなのだが… 正確には~nodule→nodl_floatでそれをやっているはずだ.⇒いや,まだ親に繋がったまま~NODULEに入ってくるケースはある.ただし,~NODULEの冒頭でnodl_floatが実行されているから,そこで始末は付いているはずだ.⇒どういうケースで接続したままになっているのかは別途調べる必要がある.

障害が起きているのはEXTRA_ANCHORスロットだ.PAIRBOXはここを端点共有リスト接続に使っている.このスロットはスロットゼロと同様に繋ぎ替え操作が必要だ.現在のdeleteElementにはその操作が入っていない.⇒いや,PAIRLISTのdeleteElementではそれをやっているのでは?⇒PAIRLISTは自前のdeleteElementを持っていない!現行でdeleteElementを持っているのは,DATALISTとLISTだけだ.

PAIRLIST::Removeがノード削除の実行ルーチンだが,これをdeleteElementとdataCountDownに分解するのはかなり難しい.deleteの実行で処理を前後に区分することは可能だが,事前に得た情報が事後に必要になってくる.これをどう伝達することができるか?外部で削除された場合にはそもそもこの事前情報というのは存在しない.必要な措置は,端点共有をどう始末するか?という点だが,これを一般処理の中で実行できるようにする必要がある.EXTRA_ANCHORを使っているのは現時点ではPAIRBOXとMARGBOXだけだが,どちらも接続チェーンとして使っている.EXTRA_ANCHORの接続チェーンの繋ぎ替えをやっている論理をどこかで見た記憶があるのだが…

繋ぎ替えの一般化については後で見ることにして,現在の問題に関する「応急措置」を考えてみることにしよう.cancelで削除されるのだから,これはノーマルな内部処理のケースに該当する.内部処理なのだから,blackflagはOFFで正常に削除されていれば,事後処理で繋ぎ替えが実施されることになるはずだ.cancelでdeleteの代わりにRemoveを使うようにしてこの障害は一応回避できた.ただし,(toplist && !bottomlist())が発生している.bottomlistを空にしているのはPAIRBOX::Disposeから呼び出されているPAIRLIST::CleanSansyoだ.bottomlistに対象ノード対が入っている.

どうも何か大きな勘違いをしていたような気がする.現在実装されているDCD(DataCountDown)の仕組みを整理してみよう.

  1. DCDは接続によって連結された直列リストとその拡張を対象とする
  2. リストクラスは仮想関数deleteElementとdataCountDownを持つ
  3. deleteElementにはそのリストの要素を削除する一般的手順を記述する
  4. deleteElementはそのクラスで実行されるすべてのdeleteをカバーする
  5. detaCountDownには要素を削除した後に実行されるべき後処理を記述
  6. 削除されたオブジェクトのデストラクタでは自ノードの所属するリストを割り出して,detaCountDownを送付する
  7. detaCountDownが実行されたことはblackflagによって識別できる

この仕様にはいくつかの誤認がある.まず,要因が内部であるか外部であるかを問わず,削除されたノードのデストラクタはつねに実行されるから,内部/外部の別なくdataCountDownは(親リストが存続している限り)必ず実行される.従って,blackflagはつねにオンになっているから,フラグとしての意味をなさない.また,dataCountDownが事後に実行されるという観念も誤っている.デストラクタはoperator deleteの実行に先立って実行されるのだから,むしろ事前処理と呼ぶ方が適切だろう.通常の手順では

delete classQ → ~classQ →…→ ~nodule → 切断 → ~NODULE → ゴミ箱/解放  

のようなフローで処理が進行する.オブジェクトの接続を切断してシステム木から切り離す操作が最終ステージ直前の~noduleの段階まで遅延されるのは,親ノードとのコンタクトを失ったノードをコントロールするすべが存在しないためだ.最後までアンダーコントールに置くためには親ノードとの接続が最後まで確保されることが必要である.従って,終末期の接続関係を見て状況を判断するというのは正しくない.

むしろ,blackflagをこれまでと反対の意味(逆論理)で使うことが考えられる.つまり,クラスの内部手続きはそれがノーマルな通常処理であることを認識できるのだから,そこでフラグをONにすることによってオブジェクト側に状態を伝達することができる.オブジェクトのデストラクタではblackflagがONなら通常通り終了し,OFFの場合に限ってdataCountDownを実行する.dataCoutDownは切断の前に実行されるから,まずほとんどのアノーマルケースはこれでカバーできる.

この方式ではこれまでの論理をほとんど修正する必要がないというのが最大のメリットだ.あるクラスの中で実行しているオブジェクト削除を関数化して一箇所に集中するのは意味があるが,deleteElementの実装は必ずしも必須ではない.dataCoutDownで行う処理はほとんどの場合,クラスの内部手続きと共通していると考えられるので,クラス手続き内でこの関数を再利用できればさらによい.今日の修正は大きくないので一度バックアップに戻って出直すことにしよう.

修正は一応完了した.PAIRLISTで(toplist && !bottomlist)というエラーが起きていたが,PAIRBOX::Disposeで実施していたPAIRLIST:CleanSansyoを廃止して動作するようになった.

NAMEBOX::RetrieveGhost→PAIRLIST::remove→nodule::operator deleteで参照カウントが残っている おそらくPAIRLIST:CleanSansyoの廃止とトレードオフになっている ⇒どうもまだ勘違いしているところがあるようだ.operator deleteで参照カウントが残るのは当然で,現在の仕様が,「事後に参照解決する」というものである限り付いて回るだろう.それをCleanSansyoで無理やり解決してきたのが今までの手法で,これは方針を基本的なところで誤っている.

「事後にできることはすべて事前にできる」と考えなくてはならない.人であれば,たとえば,「葬儀」や「相続」などのように死後でなければできないことはある.しかし,オブジェクトにはそんなものは存在しない.もし,オブジェクトが存在しなくなったときの世界がそれまでと少しでも変わるところがあるとすれば,deleteを実行する前にそれを実現してしまえばよい.そうすることの弊害は何もないと考えられる.

最小限必要なことは,「ぎりぎりまで親との接続を確保すること」の一点に尽きる.従って,親との接続関係を切断するときに行わなくてはならないことを除けば,ほとんどの事後処理は事前に実行可能であると考えられる.逆に言うと,deleteの前後で処理を2つの関数に完全に分割するようなことは実際的に不可能である.事後処理のためには事前情報が必要になるため,完全分離することはできない.

そうなると,オブジェクトの削除では事前処理→切断→処分という単純なフローになり,この事前処理の部分はそっくりdataCountDownで実行可能と考えられるから,ノーマルな削除手続きとアノーマルな削除手続きは(親が存続している限り)完全に統合可能であると言える.切断するときの措置に関しては別途考えなくてはならないが,それを除けばこのスキームでまったく問題ないはずだ.別の言い方をすれば,dataCountDownは事後に実行するのではなく,事前に実行されるべきものであると言える.