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は事後に実行するのではなく,事前に実行されるべきものであると言える.

ゴミ屋敷のクリーニングに取り掛かる

全189点のZTシステム構成図7.ZELを直系親族図で開いて閉じると,アプリ終了時に実行されるCallSetCouplingPtrの入口で,ゴミ箱に5302個,Nringに3318個,合計8620個のオブジェクトが入っている.Nringには現在アクティブなオブジェクト,ゴミ箱には廃棄されたオブジェクトが入る.Nringの内訳を見ると,最多はNODEREFLISTで1296,次が基本クラスのnoduleで1256,CARDLINK 189,SIMPLEEDGE 153,SIMPLENODE 119,REFLINK 119,MARGLINK 88,COMPLIST 41,longtable 11,SIMPLEGRAPH 9,NODELIST 9,EDGELIST 9,nlist 2のように続いている.CARDLINK 189とMARGLINK 88はファイルに含まれる人名と結婚の総数を示している.

NODEREFLIST(参照リスト)は「永続性オブジェクト」と定義され,「終末期」でも存続することになっているので1296というのは「参照を持つオブジェクト」の総数を示している.SIMPLEGRAPH(検定用枝グラフ)が9個残っていて,NODELIST 9,EDGELIST 9,COMPLIST 41,SIMPLEEDGE 153,SIMPLENODE 119はすべてその関係だ.SIMPLENODEとREFLINKの個数が一致しているのはなぜだろう?nlistはテンプレートクラスで裸のnlistという使い方はないはずなのだが,2個存在している… longtable 11というのは長整数配列ですべてカードのシステム通番を格納してそれぞれの用途別に使っているはずだ.無名のnoduleが1256個もあるというのは驚異だ.何に使っているのか?見当も付かない.ほぼゴミ屋敷と言っても過言ではない.

特に問題なのはNODEREFLISTの始末だ.新しいファイルがオープンされ,ほとんどのノードがゴミ箱に移動した後でも,削除禁止状態になっているNODEREFLISTだけはNringに留まることになる.これはゼロエミッションの観点からするとかなり問題がある.ゴミ箱の中のオブジェクトはリサイクル可能だが,Nringにあるものはすでに不用の状態でも再利用できない.⇒NODEREFLISTを一括解放するReleaseRefListという関数を用意した.この関数はCloseFamilyBaseの出口で実行するのがもっとも安全であると考えられる.⇒これでCallSetCouplingPtrの入口におけるNODEREFLISTの個数は1296→483まで減少した.

しかし,この位置でCARDLINKとMARGLINKが存続しているというのはかなり疑問だ.COUPLING::CloseFamilyBaseの出口では

DEBUG_NEVER (topology && (topology->PDB->getmaxrecn() || topology->MDB->getmaxrecn()))

でテーブルが空であることを確認している…CARDLINKやMARGLINKはこれらのテーブル(MDB, PDB)以外には居場所はないはずなのだが… ⇒いや,テーブルにはリンクがそっくり残っている.どういうことだろう?CloseFamilyBaseは引数のmodeがTRUEでなければEraseFamilyTreeを実行しないようになっている.それはそれでよいとして,中身が詰まっているのにmaxrecnがゼロというのはかなりまずい.どこでリセットしているのだろう?⇒そもそもCloseFamilyBaseはつねに引数TRUEで呼び出されている.どこかに欠陥がある.

CloseFamilyBaseはmode ONで呼び出されているが,この中から呼び出されるEraseFamilyTreeにはmodeが渡されていない.つまり,FALSEで呼び出している.EraseFamilyTreeがTRUEで呼び出されるのは終了処理のCallSetCouplingPtrで単独で呼び出されるケースの場合だけだ.EraseFamilyTreeではmodeがFALSEのときには,LINKTABLE:ClearTableを実行し,TRUEのときにはFAMILYTREE::CleanSlotを実行している.CleanSlotはNODULEクラスの共通関数で,デフォルトでは接続を含めたすべてのスロットを空にする動作になっている.

問題はLINKTABLE::ClearTableだ.この関数ではmaxrecnなどのパラメータのリセットしか行っていない.LINKTABLEのClearTableはBASETABLEのClearTableを上書きするもので,中からBASETABLE:ClearTableを呼び出している.この関数ではcleartableでパラメータをリセットし,CleanSlotをデフォルトモードで実行して全スロットをクリアしている.にも関わらず,リンクが残っているというのはなぜか?

CleanSlotは共通定義マクロで定義されている共通関数なのでトレースできない.マクロのコピーから別関数を仕立てて試してみよう.⇒なぜテーブルがクリアされないのか分かった.BASETABLE自身のスロットはlookupだけで,テーブル本体は基本クラスのARRAYが持っている.従って,ARRAYをCleanSlotしなければ意味がない.いや,実際BASETABLEのデストラクタでは以下の行でそれをやっている.

ARRAY<bnum>:CleanSlot(); 

lookupはlongtableなのでパーツとして温存しておいても差し支えはない.ARRAYの派生クラスにはこの他,REFLINKとTRASHCANがある.⇒対処した.これでNringに残ったオブジェクトは当初の3318→557まで減少した.検定グラフがこの時点でも残っているというのはおかしいので,どこかでパージしておこう.

現行ではSIMPLEGRAPHのインスタンスは9個あって,すべてTOPOLOGYの所有になっている.EraseFamilyTreeではmodeがONのときはTOPOLOGYを削除しているが,OFFのときには何もしていない.⇒すべてのグラフをパージする論理を追加したが,問題がある.アプリ起動時にはグラフを生成・初期化しているが,OpenFileBaseでは特に何もしていないため,系統並び替えに入ってからグラフ不在で停止してしまう.OpenFileBaseの中で多分TOPOLOGYを初期化しているところがあるはずなので,そこでグラフの存否を確認することにしてみよう.

OpenFamilyBaseでは特に何もしていない.その上位関数であるOpenFamilyTreeではInitializeFamilyTreeを実行している.TOPOLOGYはFAMILYTREEが管理しているので,この中で実行されるのが妥当だろう.ここで一つ疑問なのは,InitializeFamilyTreeを実行した後で,CloseFamilyBaseが実行されるという点だ.この点は前から疑問があったので実行順序を変えるなどのことを試しているが,変更すると逆に動作しなくなったりするため放置状態になっている.

やはりダメだ.系統並び替えに入って,TITLEBOX::SetDispParmでエラーが起きている.TITLEBOXは設定されている.(pdc->GetMapMode() == MM_TEXT)というエラーだ.このデバイスコンテキストはCOUPLINGがウィンドウのハンドルから取り出したものだ.座標計算をしているのだから,論理座標系でなくてはならないはずだが,いままでその辺りを気にしたことはない… 後ろの方でsetMappingをやっていたので,前方に移動した.⇒完璧に動作するようになった.

▲GetDCCは40箇所の出現がある おそらく必要な箇所にはsetMappingが入っているものと思われるが,今回のように「たまたま動いている」場所もあるのではないか?

Nringに残っているオブジェクトの個数はわずか43個まで減少した.グラフを外しただけでここまで劇的に減少するという理由はよくわからないが,ともかくこれで,システムはほぼ「コア」を残すだけの状態になったと言ってよい.あと,気になる点として以下が残るだけになった.

  1. 生のnoduleが7個ある.これは何か?
  2. 生のLISTが1個ある.これは何か?
  3. 生のnlistが2個ある.これは何か?
  4. NODEREFLISTが4個ある.これをゼロとすることは可能か?

(1)はおそらく,NODULEのstaticオブジェクトではないかと思う.確認する方法はあるだろうか?⇒すべてLISTELEMENTというラベルが付いている.「固有データ部を用いる直列リスト要素オブジェクト」でDATALISTで要素を追加するDATALIST::putで設定している.LIST:insertでも同様のことをやっている.NLIST::put,insertも同様だ.LISTELEMENTはリスト生成時に指定したデータサイズを持つ無名のnoduleでここにデータを書き込んでさまざまな用途に用いる.LISTELEMENTが他の用途に流用されることはないと考えられるから,本来ならリストを削除したときに同時にゴミ箱に入っていなくてはならないのだが,そうなっていないのはなぜだろう?リスト操作にまだ何かしら不備が残っているのだろうか?snumは14, 32, 34, 38, 40, 61, 62というかなり若い番号を持っている.

おかしい.nodl_floatを通っていない.⇒setNringでつかまえた.参照リストの要素オブジェクトだ.どうもこれは存続しているコアオブジェクトの保持する参照リストの要素であったように思われる.しかし,ReleaseRefListでは参照リストのデータカウントがゼロでない場合には削除しないようにしているのだが… 仮にリストが何かの理由で削除されたとすれば,同時に削除されているはずだし… 可能性として,フェーズが BEGININGSになっていれば,~NODULEで全スロットクリアの対象になる可能性がある… ⇒単純な理由だ.まだ存続しているNODEREFLISTが4つある.これらリストのリスト要素だ.

(2)生のLISTというのは確かにどこかで使っていたような気はするが,何に使っていたのかは覚えていない… 親ノードはTRIBELISTだ.splitlist(系列間シンメトリ婚スプリット検定用リストへの参照)というのがある.@20201102廃止というコメントが付いているが,見たところ依然として存続しているようだ.⇒TREIBLIST::splitlistであることは間違いない.このような特殊オブジェクトを生成したときには「名前」を設定しておいた方がよい.⇒nam1を「SPLITLIST」とした.

(3)も同じような事情だろう.これらには,SYMMETRYLIST,TOOYOUNGLISTの名前を付けておいた.(4)のNODEREFLISTの親はTREEVIEW, CARDTABLE, MARGTABLE, GENELISTだ.これらを削減するには,参照を廃止して関数化すればよいのだが,参照元がどこかを突き止めなくてはならない.⇒BreakNringを書き直して残ったNODEREFLISTをダンプしてみよう.最初の3つは2箇所から,最後の1つは1箇所から参照されている.

  1. PAGESETUP→TREEVIEW
  2. TRIBELIST→TREEVIEW
  3. TOPOLOGY→CARDTABLE
  4. NAMESORT→CARDTABLE
  5. TOPOLOGY→MARGTABLE
  6. NAMESORT→MARGTABLE
  7. TRIBELIST→GENELIST

バックアップを取っておこう.PAGESETUP→TREEVIEWの修正は難なく終わったが,次のTRIBELIST→TREEVIEWでエラーが発生した.

ZTシステム構成図7.ZELを基準ノード=202 NODULEで開いて,系統並び替え中GoDownStream→setstarttribe→Sansyoで(previous && previous->getpnode() == this && previous->getpnum() == edan) で停止 これは「接続用スロットには参照設定できない」という意味だ ⇒クリーンビルドしたらまたコンパイルエラーが出てきた.

同上サンプルで起動して系統並び替え中のClearTable→…~NANEBOX→…GetGeneBoxで停止 (gnum < 0)が発生している ⇒再実行したらエラーなしでファイルを開いた.

NODEREFLISTは1個減って3になった.最後のTRIBELIST→GENELISTを先に見ることにしよう.

▲同上サンプル→TribeRelocation→…GENELIST::InsertGeneBoxで停止した.原因は世代枠不在(!gbox).クリーンビルドして何箇所もコンパイルエラーが出た.⇒その後,再起動して解消した.

PDB,MDBの修正完了後実行して,OpenFamilyBase→ InitializeFamilyTreeでgetmaxrecnを実行しようとしてアクセス違反例外が発生した ⇒上記と同様コンパイルエラーがなくなるまでクリーンビルドを繰り替えして解消した.参照スロットPDBを廃止してPDB()のように関数化したところは285箇所,MDB→MDB()は108箇所.

▲修正がすべて完了した後に起動して上記と同じ→…~NANEBOX→…GetGeneBoxで停止するエラーが発生 ⇒バグと認定される.起動後に系統並び替えすると発生する.今回は#11 extraslot2dedeで起きたが,多分別のカードでも起きると思う.ClearTable中に起きている.ClearTable実行中に世代を問うということが果たして正当と言えるのかどうか?今日は寝て,明日また考えることにしよう.

nodule::operator deleteで参照残留

ZTシステム構成図7.ZELを起動してSIMPLEGRAPH:DecompConnectedComponentの冒頭でCOMPLISTリストのキャンセルを実行しているところでnodule::operator deleteが(nptr->refcnt && PHASE != CLEARTABLE)というエラーを出している このリストはCOMPLIST#23242で要素数40個,cancelの冒頭ではパラメータは整合している cancel動作そのものはDATALIST::cancellにゆだねているが,事後bottomlistとcurnodを参照解除している

削除されているのはREFLINK#23399で残留カウントは1,PHASEはSAMEGENEMARRIAGE(重婚同類グラフを生成).このオブジェクトはcancel対象リストの末尾ノードだ.参照リストを見ると空になっているので,参照カウントが残っていること自体誤りと思われる.これはどういうことだろう?⇒間違っているのは参照リスト管理の方だ.#23242COMPLIST[3]からの参照が残っている.先に参照リスト管理の方を見ておこう.REFLINK#23399が空になっている.その直前には,#23242COMPLIST[3]からの参照を処理していたのだが…

~NODULEでREFERISTスロットをクリーンアップしていた.参照/接続関係はノードの生死に関わりなく有効でなくてはならないから,参照リストもパーマネントオブジェクトでなくてはならない.⇒そもそも,定数定義でREFERISTとEXTRA_ANCHORの順位が逆転していた.よくこれで動いていたものだ…※ これで参照リスト管理の問題は解決した.

※これは不思議でも偶然でもない.このシステムでは(必ずしもすべてはないがほとんどの)スロットが仮想化されているためだ.少なくともアクセス関数によってしか読み書きされないスロットは原理的に仮想化されたスロットと言っても間違いではない.

curnodからの参照が残る問題は,LIST::dataCountDownの論理ミスだった.論理ミスというより,見落としというべきかもしれない.Sansyo(LISTsCURNOD, lower)でcurnodを次ノードに移動しているが,終端の場合は空になるので,toplistに付け替えていた.しかし,このタイミングではtoplistにはまだ現在の値が残っているため,Sansyo(LISTsCURNOD, toplist)が残ってしまう.間違いのない方法として,Sansyo(LISTsCURNOD, upper)で一つ前のノードを指すようにした.これで問題は解決し,エラーなしでアプリを起動して終了できるようになった.整理する前にバックアップを取っておこう.

アプリ終了時のnodule::resetNringでNODEREFLISTが大量に残留している.1296個もある.どうしたのだろう?そもそもこれらはどこで使われているのだろう?⇒分かった.完全参照リスト管理だ.参照リストが置かれているスロットは圏外(永続オブジェクト)として削除しないようにしたためだ.従来論理では~NODULEで参照リスト管理用スロットまで削除していたのを残すように変更している.フェーズがBEGINNINGS以下ではすべて削除するように修正した.現行ではCOUPLING::EraseFamilyTreeでBEGINNINGS状態に戻る.

リスト検査関数CheckAllListの対象リストをLISTからDATALISTの範囲に拡張して,エラーが出た.COUPLING::CloseFamilyBaseの入口で3つのNODEREFLISTでエラーが検出される.⇒checkdatacountは仮想関数でNODEREFLISTも固有関数を持っているが,checkdatacountの戻り値がerrcountを返すように仕様変更されているのに対応していなかった.⇒エラーを返すように修正した.

アプリ終了時CallSetCouplingPtrでCouplingを削除した後,ゴミ箱の中にはオブジェクトが8036個,Nリングに584個,合計8620個のオブジェクトが計数されている.Nリングに入っている584個のうち一つを除いてすべてNODEREFLISTだ.残る一つはゴミ箱そのもの.ゴミ箱の中には別に713個のNODEREFLISTが入っている.ゴミ箱を投棄するとNリングの中身まで空になってしまうのはなぜか?⇒NODEREFLIST(参照リスト)はゴミ箱に入っているオブジェクトに付帯しているため,ゴミを焼却処分したとき一緒に処理される.

EraseTreeViewはクローズファイルで実行されているが,ここでPHASEをBEGINNINGSではなくINITIALSTATEに留めるようにすると,1296個生成された参照リストはすべてNリングに移ってゴミ箱には一つも残らない状態になる.NODEREFLIST(参照リスト)はBEGINNINGSより上のフェーズでは(永続オブジェクトとして保護されているため)自動的に削除されないようになっているからだ.

人間(じんかん)到るところ静山あり

静山とは墳墓の地の意.これこそまさにCDD(制御された解体)が希求しているところだ.いつ,どこで削除されてもきちんと始末が付けられることがCDDの目標である.前に「構造物を解体するのは簡単だ」と述べた.構造物はグラフ理論的には木であり,終端の葉から削除することによって秩序正しく解体できる.実はゼルコバの木で採用しているデータ構造にはこのような骨格としての木が埋め込まれている.つまり,ゼルコバの木システムはCOUPLINGオブジェクトを頂点とする木を構成しており,すべてのオブジェクトはこの木の一部である.プログラム開発の進化という観点から見ると,我々のプログラムは少なくとも「脊椎動物」の段階に達したと言えるだろう.逆にこのような骨格を持たないシステムは「軟体動物」の段階に留まっていると言えるかもしれない.

我々のシステムが元々木構造を取っているのなら,解体はもっと簡単にできてもよいのではないか?確かにその通りだが,その基礎の上にさまざまな系統の経路(血流系,呼吸系,リンパ系,神経系,ホルモン系,消化系,排泄系,運動系,その他もろもろ)が多重ネットワークを構成しているためことはそう簡単にはゆかない.現行でもあるオブジェクトを削除するとそれに接続している下位オブジェクトはすべて自動的に削除されるようになっているから,システム全体を破棄するためには,頂点のCOUPLINGを削除するだけでことは済むようになっているし,実際,現行でも最終的にはCOUPLINGの削除で止めを刺すようにはなっている.従って,CDDが完成するということは,現在COUPLING削除の前段で行っているすべての前処理が不要になることであると考えられる.

アプリ終了→…EraseTreeViewでGENEBOXを削除して被参照カウントの残留が出た GENELIST#34→GENEBOX#15223が残っている

このエラーにはSWO(SearchWrongObject)で仕掛けている検査ルーチンの動作が関わっている.snum=34というオブジェクトを追いかけているのだが,そこで使っているLIST::top()という関数が副作用を持っているためだ.topはリストの先頭要素を返すルーチンだが,curnod(カレントリスト要素)を更新するような動作になっている.昨日はcurnod自体を廃止しようとしていたのだが,一部で使われているため(直ちには)廃止することができなかった.top()をtoplistに変えることはできると思うのでやってみよう.⇒うまくいった.

アプリ終了の出口近くで,残留したLIST派生クラスのインスタンス64個のうち1個でデータ数不一致が発生している #34GENELISTでデータ数13のところゼロになっている ⇒ toplistはすでに空になっているので,datacount 不整合と考えられる.この原因はGENELISTではまだCDDをサポートしていないためと考えられる.⇒対処した.

現在実装されているLISTの派生クラスにはNLISTとnlistがある.いずれもテンプレートクラスでその下には以下の派生クラスがある.

  1. GENELIST GENEBOX ◯
  2. PAIRLIST PAIRBOX ◯
  3. NODELIST SIMPLENODE 
  4. EDGELIST SIMPLEEDGE ◯
  5. TRIBELIST TRIBEBOX ◯
  6. COMPLIST REFLINK

最後のCOMPLISTはnlistの派生クラスで,それ以外はすべてNLISTだ.これらのうちの4つはすでに対応済みになっているので残り2つも片付けてしまおう.修正はただ単に各リスト要素クラスのデストラクタからdataCountDownを呼び出すというだけだから,あっと言う間に終わってしまう.~クラス名のデストラクタとその中から呼び出されるDisposeの切り分けはややあいまいだが,基本的にDisposeでは参照解決を行い,dataCountDownとCleaSlotだけはデストラクタで行うということにしておこう.デストラクタの入口では重複実行を回避するために if (finished()) return; としているが,これは完全に削除されたか,ないし~NODULEの処理が完了したことを意味しているので,重複実行される可能性はゼロではない.個別のデストラクタではこれと別に入口で disposing++;  を実行しているので,これも見ることにする.

いや,これだけでは手抜きかもしれない.安全のため,そのリストに自ノードが接続していることを確認してからdataCountDownすることにしよう.COMPLISTは少し厄介だ.このリストは何をリスト要素としているのだろう?なるほど,なぜCOMPLISTがnlistでNLISTではないのか?ということが分かった.リスト構造は接続によって構成されるが,接続するスロットがゼロスロット固定なので,複数のリストに同時に連結することはできない.NLISTはオブジェクトを直接接続して構成されるリストだが,nlistはリスト要素クラスオブジェクトという特殊オブジェクトを要素とし,そこから実際のオブジェクトを参照するようになっているはずだ.COMPLISTは以下のように定義されている.

class COMPLIST : public nlist<SIMPLENODE>

いや,ちょっと違うかもしれない.nlistの定義を見ると,

nlist 基本オブジェクトリスト(リスト要素はnoduleの派生クラスオブジェクト)

となっている.NLISTとnlistの違いはなんだろう?NLISTの説明もまったく同じだ.

NLIST 実装オブジェクトリストクラス(リスト要素はnoduleの派生クラスオブジェクト)

ただし,テンプレートが少し違う.

template <class NODECLASS>    —  NLIST
template <class LISTNODE, int CID>  — nlist

いや,それだけではない.nlistではSLOTZEROではなくSLOTONEを使っている.しかし,オブジェクトは参照でなく接続として生成されている.ただし,そのオブジェクトは<NODECLASS>で指定したオブジェクトではなく,REFLINKという特殊オブジェクトだ.REFLINKはサイズ1の配列クラスとして定義されている.上のSLOTONEというのはREFLINKからオブジェクトを参照するためのスロットだ.nlistの定義というより,説明文は間違っているか,少なくともあまり正確ではない.

nlist 基本オブジェクトリスト(リスト要素REFLINKからnoduleの派生クラスオブジェクトを参照)

としておこう.従って,COMPLISTではREFLINKからdataCountDownが送られることになる.⇒これで6つでそろった.これですべてだろうか?LISTから直接生成されているインスタンスはなかったろうか?ないという訳ではない.というか,存在する.たとえば,参照リストなどはそのタイプだ.系列枠のスプリット検定用リストなどというものもある.シンメトリ婚スプリット検定用リストというのもこのタイプだ.DATALISTは2つのタイプのリストを生成することができる.一つは前出したREFLINKをリスト要素とするもの,もう一つのタイプは一般オブジェクトクラスのnoduleを要素とするものだ.REFLINKではすでに整備されているが,noduleにも同様措置を取る必要がある.

noduleクラスにはリスト検索ルーチンとしてgetlistというのがある.これはノード型という属性をチェックして所属リストか否かを判定しているが,ここではこれらの属性を無視してより一般的なgetUpperClassNode(‘h’)で検索することにする.これで少なくともリスト要素に関しては万全整ったのではないかと思われる.

参照カウントの不一致が出てしまった.多分,~noduleで親リストを探すのに一般的手法を使ったためではないかと思われる.終末処理ではnoduleのデストラクタは最後に呼び出されるから,それ以前にすでに処理が済んでいるものを重ねて処理することになってしまっているのではないか?これを回避するためにはやはり,旧来ロジックのような属性チェックが必要ということだろう.⇒どうもまだ通っていない.

▲ZTシステム構成図7.ZELを起動してSIMPLEGRAPH:DecompConnectedComponentの冒頭でCOMPLISTリストのキャンセルを実行しているところでnodule::operator deleteが(nptr->refcnt && PHASE != CLEARTABLE)というエラーを出している このリストはCOMPLIST#143473で要素数40個,cancelの冒頭ではパラメータは整合している cancel動作そのものはDATALIST::cancellにゆだねているが,事後Bottomlist(NULL)を実行して参照解除している

多分これは以前はDATALIST::cancellの前に実行していたのではないかと思う.bottomlistやtoplistの健全性はつねに検査されているので,事前にセットするのはやはり好ましくない.この参照削除に責任があるのはリスト要素であるREFLINKそのものと考えられるが,REFLILNK:Disposeではなにもやっていない.dataCountDownをDisposeに移動し,合わせてLIST::ClearSansyoを実行するようにして解決した.ただし,dataCountDownではリストのパラメータの調整を行っているのだから,本来ならClearSansyoを実行しなくても動作するはずである.そうなっていないのは,LIST::dataCountDownに問題があるからだ.

実際の削除を実行しているのはDATALISTだが,LIST:dataCountDownではLISTクラスのメンバ変数についての操作しか実行していない.これでは明らかに不十分だ.現行ではdataCountDownという関数はLISTクラスに唯一存在し,それだけですべてをカバーするようになっている.もし,それを仕様とするのであれば,LIST:dataCountDownですべてのことをやらなくてはならない.しかし,それでは基本クラス以下が何をやっているかすべて知らなくてはならないので,現実的ではない.むしろ,LIST::dataCountDownの中から下位クラスに通知をパスして,いまの場合で言えばDATALIST:dataCountDownを実行するようにさせるべきだろう.そうしておかないと,今後さらに拡張したときに問題が生じるおそれがある.

実行順序に関して言えば,デストラクタの実行順序とは逆に下位クラス(基本クラス)のdataCountDownを先に実行した方がよいのではないかという気がする.⇒いや,素直にデストラクタの実行順序を踏んだ方が安全なのではないか?⇒それはそれでよいことにしよう.原則的にはすべてのクラスが固有の遺言執行書を書く必要がある.遺言執行書がこように階層化されるとすれば,この関数は仮想化すべきなのではないだろうか?さもないとどこかで中抜きされて下位クラスに渡されてしまう可能性がある.修正してみよう.

いや,そんな簡単な話ではないのではないか?noduleのデストラクタで遺言書を送信したときのことを考えた方がよい.noduleの生のインスタンスがリスト要素になる場合があるから,noduleには遺言書を発行する資格がある.しかし,たとえばSIMPLENODEの中にはnoduleが入っているからSIMPLENODEとnoduleが同時に遺言書を発行したら,処理がかち合ったり,ダブったりする可能性はないか?対象を直列リストに限定したとしても,リストクラスはそれ自体が階層になっているから,各クラスの処理にダブりがない限り,各クラス別に遺言書を出すことも合理化できるとしよう.リストクラスがNあるとすれば,N個の異なる遺言書のテンプレートがあることになる.これはいいだろう.

問題はこれを発行する側にある.つまり,オブジェクト自身クラス階層の上にあり,デストラクタはこの階層を下降して逐次処理を行うという仕掛けになっている.つまり,あるオブジェクトは玉ねぎのようにそれ自体階層化している.しかし,上位ですでに遺言書を発行していることはblackflag をチェックすれば確認できるはずだ.従って,noduleで起きた問題は検査を厳格にしなくても,フラグをチェックするだけで回避できた可能性がある.⇒まず,この点について確認してみよう.ロジックを元に戻してエラーが発生することは確認できた.⇒いや,このエラーももう少し詳しく見る必要がある.

エラーは参照リスト管理の中で起きている.この中ではリスト要素の削除が実行されるので,内部的に参照リンク操作が含まれている.リスト要素はnoduleなのでnoduleからCDCが発行され,~nodule→ find→ top→ SansyoでReferenceControlの不一致が発生している.⇒何とか強引に動かした.ポイントは「参照リスト管理」の中では参照を操作してはならないという点だ.いや,実際そういう作りにはなっているのだが,remove関数の中でデバッグ用に実行している検査関数が副作用を与える点が問題だ.top()やcurnodを存続させるとしても,検査用に別途副作用のないルーチンを確保しなくてはならない.

どうも先にそれをやらないとあちこちで躓いてしまうのではないか?引数を持たないnext()などはcurnodを基準としているので,それがないと都合の悪いことが起きる.最近は引数なしのnextを使った記憶はほとんどない…いや,それほどは使われていない.全体で5箇所だけだ.これは真っ先に廃止してよいだろう.Next(void)というのもある.curnodに関してはベタ参照にしてしまうという手もないわけではない.あまりよい考えではないかもしれないが… なぜだろう?nlistは引数付きのNextを持っていない.⇒Followingという名前を使っていた.

ついでにtopとTopも廃止したいところだが,その前にバックアップ.top()はさすがに多い.303箇所もある.curnodは50箇所の出現する.ただし,そのうちの10箇所は「curnodを廃止する」だ.⇒curnodの廃止はひとまず置いて,先にいま停止しているところをクリアしてしまおう.上記で解析した通り,この問題を解決するためにはDATALIST:dataCountDownを作るしかない.というかそれが最善策だと思う.方針を立ててみよう.

  1. 現在の「制御された解体」の範囲を直列リスト操作に限定する
  2. 各リストクラスは個別に事後処理関数dataCountDownを持つ
  3. ただし,削除そのものを実行していないクラスでは持たなくてもよい
  4. 削除対象となるリスト要素のデストラクタでは自ノードが接続するリストを検索し,存在すればdataCountDownを送信する
  5. 親リストとのコンタクトが切れている場合には送信しない(できない)
  6. また,自ノードのblackflagがオンの場合も送信しない(送付済み)
  7. dataCountDown関数では入口でblackflagをインクリメントする
  8. dataCountDown関数では出口で下位のdataCountDownを呼び出す
  9. 削除後の後処理としてdataCountDownを実行する
  10. ただし,対象リスト要素のblackflagがオンの場合には実行しない
  11. 処理完了後にblackflagをリセットしない

現時点ではLIST::dataCountDownしかないので,追加してインプリメントされるのはDATALIST::dataCountDownだけだ.この関数では自クラスが管理するtoplistの更新を行う.これは現在 delete を実行しているブロックのロジックをそっくりdataCountDown関数の中に移植するだけのはずだ.dataCountDown関数は内部操作の場合と外部で削除された場合の共通論理となる.

一つのクラスでdeleteを実行している箇所が複数ある場合にはどうなるか,それぞれに異なる遺言書を付けるのか?⇒そのようなことはあり得ない.もし,現行でそういう論理が存在するとすれば,deleteを実行しているブロックを関数化して一箇所にまとめるべきだ.そうできないという理由は考えられない.もちろん遺言執行書は一クラスで高々一つ持つだけだ.DATALISTにはdeleteを実行している箇所が3箇所あるので,deleteElementとしてまとめよう.LISTは1箇所だけなのでその必要はないが,同じ名前の関数を作って分離することにする.

制御された解体(controlled demolition)のポイントが大体つかめた

制御された解体(controlled demolition)のポイントが大体つかめた.病院のベッドであるいは自宅で近親者に囲まれて最後の時を迎えるのがノーマルな死であるとすれば,事故死や自殺,孤独死などはアノーマルな死と言える.このような意味でオブジェクトのアノーマルな削除に対処するというのが制御された解体の目的である.

削除されたオブジェクトはメモリから消去(ないしゴミ箱に移動)される前に必ずそのデストラクタが呼び出されるので,知人・関係者に死亡通知を送りつけることができる.その時点で知人・関係者とのコンタクトが取れなくなっている場合には何もできないが,知人・関係者がすでに他界しているのであれば(できないことは)問題にはならない.

関係者側では死亡通知を受理した時点で事後処理を実施する.この方式なら関係者が関わりを持たない場所でアノーマリィに削除された場合にも対処することができるので,システムの一貫性・データの整合性を保つことがこれまでよりはるかに容易なものになる.PAIRLIST→ PAIRBOXとEDGELIST→ SIMPLEEDGEの関係に限定してテスト的に実装しているが,まだエラーが残っている.

アプリ終了でEraseTreeView→~TRIBEBOX→… SIMPLEGRAPH:TakeRemainSansyoを実行中にLIST::nextでエラーが検出される

TakeRemainSansyoのループで最初にNODELISTを処理した時点でデータ不整合が発生している.対象グラフはTOPOLOGYのgraph3(系列木グラフ生成用枝グラフ)だ.識別し易いようにgraph3をリネームしてTribeTreeGraphとしておこう.このグラフの節点は系列枠だ.⇒確かにNODELIST::Removeを実行しただけでEDGELISTに影響が及んでいる.不思議だ…節点が削除されるとそれを参照している枝にも影響が出るということのようだが… EDGELISTはNODELIST要素を参照しているので,その参照がクリアされることは確かだが… NODELIST::Removeが実行されると,EDGELISTの要素は23個から16個に減少する.つまり,7個削減されている.事後のdatacountとtoplistの値は正しいが,bottomlistの値がtoplistと同じになっている.

グラフの枝の両端点のいずれかが削除されるとその枝も削除されるようになっているが,どこでやっているのかはよくわからない.いずれにしても,この操作で不整合が発生していることは間違いない.節点の削除を実施するグラフの関数は2つある.releaseとReleaseだ.前者は節点が参照するオブジェクトが削除されたとき,後者は節点そのものが削除されたときの操作だが,いずれも枝の両端点のうちいずれかが削除されたときには,枝を枝リストから削除するようになっている.しかし,枝の削除を実行するSIMPLEGRAPH::removeでSIMPLEEDGE::removeの前後をチェックしているが,不整合は検知されていない.

TakeRemainSansyoの入口でトリガーを掛けても,SIMPLEGRAPH:remove(SIMPLEEDGE*)実行では停止しない.⇒TakeRemainSansyoは再帰的に実行されている!再帰から戻ったときに値が戻ってしまうのではないか?というか,いまのblackflagのプロトコルは再帰実行されることを予定していない.また難問が出てきた… 現行方式では delete → Dispose → dataCountDown → 事後処理のフローは直列かつ即時・排他的に実行されることを予定している.blackflagを関数のローカル変数に置くことができれば再帰実行に対処することは難しくないが,delete の実行と dataCountDown は独立の別関数でまったく非同期に実行されるため,クラスのメンバ変数とするしかない.つまり,現行方式ではこの問題には原理的に対処できない.

ではどうすればよいか?こうなれば豚のトレーサビリティシステムのように個体に識別子を付けて履歴管理するしかない.いや,個体識別子はもちろん付いている.豚の全頭履歴管理を行うというのは確かにすごいと思うが,プログラム的には比較的単純なデータベースがあれば済む.多重な関係ネットワークを持つオブジェクトの終末管理にはあまり向いていない.いずれにしても個体にマークを付けるしかないことは明らかだ.マーク名はblakflagとして,それをどこに置けばよいか?拡張性を考えれば,noduleクラスメンバとするのが適切だろう.noduleは基底クラスNODULEから直接派生する唯一のクラスであり,オブジェクト全数管理のためのNリングシステムとリサイクルシステムおよびUNDOシステムを備えた応用システム構築用の基本クラスだ.オプションではあるが,完全参照リスト管理も持っている.これに終末期管理のための制御された解体機能が備われば,ほぼ万全と言えるのではないか?

死亡通知同期用フラグとしてnodule::blackflagを設置すれば,dataCountDownによる死亡通知は不要になるのだろうか?いや,ならない.養豚場から盗まれた豚がどこかで密殺された場合,これがなければ終末処理ができない.今日はまだ修正は1件も入っていないので,このまま続けよう.⇒フラグではなく,deleteの実行者のリンクを直接書き込んでおくというのはどうだろう?こうすれば,いちいちデスノートの宛て先を探しまくる必要はなくなる.そのアドレス宛てにデスノートを送るだけでよい.デスノートは定型文なのでそれぞれのクラスで用意する.DeathNoteという仮想関数を一つnoduleに作っておいてもよいのではないか?nodule::nodule *executerとしてみよう.この変数はオブジェクトのライフタイムで一度しか使われないのだから,operator new で初期化しておけばよいだろう.

いや,少し違うのではないか?dataCountDownは密殺された豚の死亡通知を管理者に届ける仕組みだ.dataCountDownを管理者に送れるということは管理者とのコンタクトが切れていないという証明であり,管理者がexecuterに署名したところで何の意味もない.密殺者は署名できないし,適切な終末処理を持っていないのだから,署名しても意味がない.やはり,blackflagを使うしかないと思う.

blackflagとexecuterを併用することは可能だが,そこまでする必要もないだろう.まだ障害が残っている.

今度はTRIBELISTのCleanSansyo中にbottomlistに関するエラーが出た LIST::nextで検出されている.TRIBELIST自体の不調だ ⇒ CleanSansyoの入口ですでに不正規になっている.これを防止するには,TRIBELIST自体にCDD(Controlled Demolition)を導入するしかない.⇒ダメだ.効果がない.⇒dataCountDown自身でエラーを作っている.⇒対処した.

▲COUPLING::CloseFamilyBaseが過剰に呼び出されている ファイルをオープンしていないときはNOPリターンでよい ⇒ 未了

▲COUPLING::EraseFamilyTreeにモードを設け,アプリ終了時には「EraseFamilyTreeで削除しない@20201120」で保留したブロックを強制削除する ⇒ 保留

▲アプリ終了の出口近くで,残留したLIST派生クラスのインスタンス64個のうち1個でデータ数不一致が発生している #34GENELISTでデータ数13のところゼロになっている

GENELIST#34を追跡するようなSWO(SearchWrongObject)を仕掛けて,完全参照リスト管理から「参照元ノードリスト不記載」エラーが出た ⇒ SWOがクリティカルゾーンに入って再帰が起きているためだ.escapeで回避するようにして止まった.

▲リストアクセス関数のLIST::topなどを実行するとcurnodへの書き込みが発生する topやnextなどのルーチンは副作用がないことを予定しているので,このような動作は望ましくない 実際問題としてcurnodはほとんどまったく使われていないと考えられるので廃止してよいのではないか? ⇒ 一度バックアップを取ってから廃止手続きを進めることにしよう.LIST::insertはcurnodを参照する動作になっている.かなり疑問の多い論理なので後で精査することにする.

probeではアイドリング時のカウントアップを避けるため,DRAWSTAGEフェーズではScoutとscountを更新しないようにした ⇒ これはかなり重要なポイントだ.これでようやく,トラッキングの完全再現性が保証されるようになった.

▲アプリ終了→…EraseTreeViewでGENEBOXを削除して被参照カウントの残留が出た GENELIST#34→GENEBOX#15223が残っている

死者からのコールバック

まだ,制御された解体(controlled demolition)の実験段階だが,反応は悪くない.アプリ終了時にTREEVIEW::EraseTreeViewで描画オブジェクトの無差別削除が実行され,PAIRBOXのデストラクタが呼び出されると,PAIRBOXでは自ノードの所属するPAIRLISTを割り出して(もし,存在すれば)PAIRLIST::dataCountDownを呼び出す.これは自分自身の死亡通知を管理者に送るのと同義で,管理者はそれに従って(埋葬などの)事後処理を実施することができる.

これまでもCleanSansyoなど同等趣旨の関数は実装されてきているが,今回はっきりと,これが手続き(処理)ではなくコミュニケーションの一種であるということに気付いた.デストラクタからの呼び出しは謂わば死者からのコールバックに相当する.自分ですべてをやろうとするより,それを知っている人に依頼した方が早いし,確実だ.自分の葬儀を生前にデザインすることは可能だが,実施することはできない.つまり,必要なのはプロシージャからプロトコルへの転換だ.

ZTシステム構成図7.ZELを基準ノード=202 NODULEで開いて終了するとき,PAIRLIST::dataCountDownでリスト上にオブジェクトが見つからないというエラーが発生する

dataCountDownの呼び出しはPAIRBOXのデストラクタ→Disposeから実行される.ノード対リストを管理しているPAIRLISTを割り出すためにはPAIRBOXがPAIRLISTに接続していなくてはならない.ということは,このノードはリスト中に含まれているのにfind関数で見つけられなかったということになる.⇒PAIRLIST::findは直列リストの部分しか見ていない.PAIRLISTはそれから分岐した「端点共有ノード対リスト」を持っているので,そちらも探さなくてはならない.もし,findという関数の仕様が元々「直列リスト要素を探す」ことであるのなら,別の探索関数が必要になる.⇒findは以下から呼び出されている.

  1. CheckShiftedPairBox(bool force) (TOPOLOGY)
  2. RetrieveGhost(void) (NAMEBOX)
  3. dataCountDown(PAIRBOX * pbox) (PAIRLIST)
  4. putbottom(PAIRBOX * pbox, PAIRBOX * common) (PAIRLIST)

いずれもfindの動作を拡張しても問題ないように思われるので修正しみよう.⇒いや,現行でもPBOX::findは端点共有リストまで探索するようになっている.PAIRLIST::nextでそれをやっている.⇒いや,それどころではなくなった.とんでもないことが起きている.nodule:grandparentの実行カウントがテストのたびに変動している.あり得ない!これは決定性チューリングマシンだ.仮にマルチスレッドで並列実行されるようなことがあったとしても,止める場所は同じなのだから実行回数が変動するはずがない.

nodule::grandparentの引数オブジェクトのsnumが225749のときダンプするように仕掛けて変動を見たところ,画面が出力されるまでは一定だが,終了ボックスをチェックして最初にダンプされる時点でカウント40くらいの変動がある.信じ難い現象だ.何が影響しているのだろう?画面が出てから最初のダンプまでの間にgrandparentは3万回近く実行されている.ccid=’&’ COUPLINGでダンプしてみよう.どうもこの関数は画面を表示してアイドリングしている間にも呼び出しが掛かってくるようだ.⇒カーソルが系図画面に入っただけで呼び出しが掛かってくる.

getUpperClassNodeというgrandparentを呼び出しているだけの関数があるので使ってみよう.getUpperClassNodeは画面を開くまでに37058回呼び出されているが,アイドリング状態では変動しない.OCXではOnMouseMoveを取っているので,ここからDLLの関数が呼び出されているのだろう.どんな関数が呼び出されているのか知りたいが,また後でということにして,PAIRLIST::findに戻ろう.

PAIRPLIST::DumpPairListでもこのノードはダンプされない.PAIRLIST:nextboxが間違っている.というか,bottomに間違った値が入っている.nextboxは現在要素がbottomのときは次要素としてNULLを返している.bottomは誤っている可能性が高いので生値を返した方がよい.いや,この関数ではなくLIST::nextだ.⇒修正しておこう.⇒LIST::nextの中でbottomの整合性チェックを行うようにしたところ,この不正がかなり早期に発生していることが検出された.

MakePairListClean…→ RetrieveGhost…→ PAIRBOX::Dispose→ PAIRLIST::CleanSansyoで起きているようだ.PAIRBOXはLIST::_removeで削除されている.⇒いや,これは過渡的なものではないか?⇒いや,違うかもしれない.bottomlistの操作をdeleteの後に実行するようにしたが,同じだ.LIST::_removeの作動中フラグOnLISTremoveを立てて,この間はチェックしないようにした.

この後,bottomlist, topolistの不整合が続いたので,LIST::_removeで行っているパラメータ更新をすべてLIST::_removeに移動した.ただし,これは例外的な措置でリスト要素がPAIRBOXである場合に限定される.これでPAIRBOXの削除に関わる不整合は完全に一掃された.

TREEVIEW::EraseTreeView→~TRIBEBOX→…SIMPLEGRAPH:TakeRemainSansyo→…LIST::nextでbottomlist不整合が発生した

これは全く上記と同じ事象と考えられるので,対策も同じだ.EDGELISTはNLISTの派生クラスなのでLISTの孫クラスに当たる.SIMPLEEDGEのデストラクタでdataCountDownを発行し,それをLISTで受けるようにすればよいはずだ.NLISTは追加パラメータを持たないので,すべてLISTで処理できる.いまのところdataCountDownはPAIRLISTのメンバ関数になっているが,これをLISTに移管してしまった方がよい.まず,それをやっておこう.⇒対処した.

SIMPLEEDGE::Disposeでは始点・終点オブジェクトへの参照解除しかやっていない.getgraphという関数はあるが,所属リストの取り出し関数もない.ここでは簡略にDisposeの中で直接LISTを探して,そこにdataCountDownを送ることにしよう.ただし,getUpperClassNodeではそのオブジェクトに埋め込まれたCIDを探しにゆくので,LISTを直接探しても見つからない可能性がある.というより,見つからないだろう.pclassid(基本クラスIDテーブル)を使って親クラスを探すことができたはずだ.NODULE::IsSuperClassという関数がある.これを使ってgetUpperClassNodeを拡張すればよい.⇒誤動作する可能性はないだろうか?可能性はゼロではないが,まずないと考えてよいのでは?

求めるクラスIDをCIDとしたとき,CIDをそのものずばり持っているオブジェクトがあればそれを優先しなくてはならない.その手前にスーパークラスにCIDをもつ別のクラスオブジェクトがあった場合には問題になるかもしれない…安全策としては,まず現行方式で探索し,発見できなかった場合には拡張探索するというのが順当なのではないだろうか?とりあえず,そのように実装してみよう.拡張探索には現行探索も含まれているので多少冗長にはなるが,仕方ない.⇒実装した.

アプリを起動して,TRIBELIST::TribeRelocationのステージ【7.2】完全木検定:すべての系列を完全系列として正準化するでMakePairList…→ TOPOLOGY::CheckPairList… →GENEBOX:CheckInverseCycle…→ LIST::cancel…→ ~SIMPLEEDGE→ Dispose→ LIST:dataCountDownでdetacountが負になった.CheckInverseCycleでグラフを初期化するためにEDGELISTをキャンセルしているところだ.LIST→NLIST→EDGELISTの間のどこかでdatacountをデクリメントしているのだろう.

いや,違う.LIST::cancelではDATALIST::cancelが実行される.削除は自前でやっているが,cancelは下請けに出している.LISTからDATALISTに通知を送るか?ないし,DATALISTを含む大改造に着手するか?⇒これはかなり難しい問題だ.

  1. リスト要素のデストラクタでは自分の死因を割り出すことはできない
  2. dataCountDownはリスト要素の属するLISTの派生クラスのインスタンスに送られる これはXLISTとする
  3. XLIST→LISTではリスト要素のクラスによってパラメータの更新を実施
  4. 処理がLISTの内部で完結している場合はこれでよいが,基本クラスのDATALISTに処理を依頼している場合には,DATALISTに死亡通知を送らなくてはならない
  5. DATALISTではdataCountDownを受け取ったときにはパラメータ更新を実施しない

いや,もう少し簡単な方法がある.DATALISTに何かフラグを設ければよい.たとえば,blackflagとしておこう.LISTではdataCountDownを受け取ったときにはBFを立てればよい.DATALISTではBFが立っているときにはパラメータの更新を実施しないでフラグを落とすだけとする.LISTでたとえば_removeなどの処理を行った場合には_removeで落とせばよい.これでよいのではないだろうか?

リスト要素をdeleteする前にフラグをリセットしておけば,まず間違いはないと思われる.外部で削除された場合にはどうなるか?LISTではDCDを受け取ったタイミングでBF+するが,これをリセットするものはいない.従って,deleteを実行する前のリセットは必須である.

現行のdataCountDownにはまだ不備がある.この関数ではdatacount,bottomlist, curnodを更新しているが,toplilstは更新されていない.本来なら,datacountとtoplistの更新はDATALISTで実施しなくてはならないところだ.ただし,toplistはリスト先頭ノードの接続スロットなので,特に何もしなくても更新された状態にはなっている.つまり,現状でも問題はない.⇒修正は入れ終わったが,エラーが出ている.

▲アプリ終了でEraseTreeView→~TRIBEBOX→… SIMPLEGRAPH:TakeRemainSansyoを実行中にLIST::nextでエラーが検出される.

「終末期をどう生きるか」ないし「老化とはそもそもなんであるのか」

「ところで,世界の有限性が明らかになるなかで生きるとは,つまり限界超過生存(オーバーシュート)の状態で人類が生きるとは,ある意味で,人類全体が巨大な終末医療のホスピスに入るということではないだろうか?」と故加藤典洋が書いたのは今から7年前の2013年だ.これに全面的に同意するものではないが,2020年現在の世界をかなり正確に予見するものであったことは間違いない.限界超過生存(オーバーシュート)つまり,「生き過ぎてしまった」というのは個人的にも当てはまるところだが,「終末期をどう生きるか」ないし「老化とはそもそもなんであるのか」ということは問われなくてはならない.

ある存在が個体であるということは有限であるということであり,有限であるということは空間的にも時間的にも有限であることを意味するとすれば,個体である人間がいつか死に直面することは不可避である.生きていることには悲しみもあるが楽しみもある.いつか人はその楽しみを失うことになるのではあるが,そのプロセスを可能な限り苦痛の少ないものにすることが「老化」の本質であり,目標なのではないか?つまり,解体プロセスをできるだけ穏やかに進行させることが創造者の本意だったのではないか?そこには相当の創意・工夫も感じられるが,創造者の力を持ってしてもそれをパーフェクトなものにすることはできなかった.つまり,組み立てることは簡単でもそれを秩序正しく分解することはとても難しい.いま,我々が直面しているのはその問題である.

たとえば,ここに家と家を結ぶ入り組んだ街路を持つ一つの城塞都市があったとする.あるときこの街の市長が街の建て替えを思い付き,全住民に移転を命じた.移転して空家になった家屋は取り壊され,その家に接続する路はすべて封鎖される.ただし,この都市の路はすべて一方通行であるため,路の入口側で「この先行き止まり」の看板を立てなくてはならない※.しかし,一部の路は生活に必須な経路となっているため,封鎖ではなく迂回のための繋ぎ替えが必要になる場合もある.ある家から発する一方通行路の出口にある家を知ることはできるが,その家に達する一方通行路の入口の家を知ることはできない.移転が完了するまで残った住民すべてがこれまで通りの生活を維持できることがこの大規模工事の条件である.この工事を安全に施工するための指針を示せ.

※一方通行路の両端(入口と出口)を除き,この路に接する家はないものとする.また路同士は交叉しない(交差点は存在しない).

「地図があればいいんじゃないの?」と言われるかもしれないが,この街そのものを1個の地図と考えれば,徒歩で実地調査するのと地図を読むのでは計算量理論(計算複雑性理論)的には何の違いもない.生活に必須な路にはさまざまな種類がありそれぞれに担当の係がいる.万一「この先行き止まり」の看板が出ていない行き止まり路が発見された場合にはその工事を担当した係のクビが飛ぶことになっているが,手抜きや情報漏れ,資源不足などいろいろな事情から完璧を期待するのは難しい.(構造物を解体するのはこれに比較するとはるかに簡単だ.一番外側/内側から1個づつコンポーネントを取り外してゆけばよい)我々がやろうとしているのはこのようなネットワークの秩序だった解体,ある種の制御された解体(controlled demolition)に他ならない.

MakeAbsoluteを実行後にTOPOLOGY::CheckAtypicalMarriageが実行されている この関数は結婚連結線の所属を決めるために常用世代番号を使って基本世代枠リストにアクセスしている

CheckAtypicalMarriageの計算では絶対座標系を使っているのでMakeAbsoluteより前に移動することはできない.物理世代番号を使うように書き改める必要がある.⇒実際,nbox->getGeneration() – MinGeneration + 1で物理世代番号に変換している.⇒対処した.

かなり厄介な話になってきた.⇒いや,以外に容易に片付いた.少なくとも描画までは進める.ただし,出口検査は水平スプリット検査を除いてすべて絶対座標変換の前に移動した.また,水平スプリット検査はなぜか相対座標系では誤動作してしまう.元々絶対座標系で動作するように作られているのかもしれない…

絶対座標変換フェーズでGEN2DEVを実行して系列枠不在が多発している 系列枠はgroupから取り出しているので世代操作とは無関係のはずだが…これは,MAKEABSOLUTEフェーズ以外では発生していない.おそらく,これまでも発生していたはずだが,世代番号の取り出しを実施していなかったため発覚しなかったものと思われる.不要ノードと考えられるので,どこかのタイミングでパージした方がよい.⇒結婚枠と結婚リンクは一体とみなされている.また人名リンクと人名枠(0)もつねに不離不即とされるため,パージすることはできない.

絶対座標変換から外してもよいのではないか?実際,計算しようがない.ただし,HIDE_EXPOSEという状態もある.HIDE_EXPOSEというのは初期状態ですべての描画要素が隠蔽リスト上にあるという状態を示すものと思われる.現行ではHIDE_EXPOSEはゼロという値を割り当てられているので,識別が難しい.⇒隠蔽リスト上のノードは絶対座標系変換から外すというのでよいと思う.

以下の関数はCLEARTABLEフェーズ以下(アモルファス状態)では無動作で抜けるようにした.これらは比較的規模の大きな関数で理論的には不可能ではないとしても,いま直ちに対処するのは難しい.

  1. NAMEBOX::RetrieveGhost
  2. MARGBOX::cancelBetweenTwo

PAIRLIST::cancelでデータカウント残が出てしまった.PAIRBOXのデストラクタで所属するPAIRLISTを割り出してdatacountを削減することは不可能ではないが,PAIRLIST自身で実施するデクリメントとかち合ってしまう可能性がある.⇒方法はいくつか考えられる.①削除中のノードにマークを付けておく,②削除中のノードの控えを取っておく.いずれにしても,PAIRBOX::DisposeからPAIRLIST::CleanSansyoでコールバックされるので,削除中であれば本体でデクリメント,そうでなければCleanSansyoでデクリメントする.⇒いや,単純につねにCleanSansyoでデクリメントでよいのではないだろうか?

PAIRLISTはremoveとRemoveという2つの削除手段を持っているのになぜあえてdeleteで直接削除しているのだろう?removeはRemoveを引数TRUEで呼び出しているだけだが,Removeはやや複雑だ.ノード対リストは端点共有ノード対接続チェーンというのを持っている.また,この関数ではdeleteする場合とnodl_floatでフロート状態にしたまま切断する場合がある.後者は別のノード対リストに繋ぎ替えするための過渡的な状態だ.cancelで直列リストのノードだけを(端点共有ノード対チェーンに接続するノード対を無視して)deleteしているのは,それによって枝分かれのリストも同時に削除されるためだろう.一度バックアップを取ってから修正に入ることにしよう.

PAIRLISTはCleanSansyoをLISTに引き渡し,LISTはそれをDATALISTにパスしているが,DATALISTでは何もしていない.PAIRLISTはリスト上の他のノードにもCleanSansyoを要請しているが,リストというのは本来接続関係なのでほとんど空動作になっているものと思われる.datacountはDATALISTで定義されているものなので,ここで直接管理すべきものではないだろうか?カウントダウンする専用のコールバック関数を作るというのが一番確実であるような気がするのだが…

ただし,それをやるとすると,DATALISTのすべての派生クラスでdatacount操作の修正が必要になる.PAIRLISTはDATALISTのremoveなどの関数を使っていないので※,実験的にまず,ここで試してみるのがよいのではないか?フローティングする場合は別としてdeleteの場合は必ず削除されたオブジェクトからdataCountDownの通知を待つようにすればよいのではないか?⇒実装した.まだ上がっていないが,結構おもしろくなってきた.※⇒使っている.

少し先を急ぎ過ぎているのではないか?

どうも少し先を急ぎ過ぎているのではないかという気がする.危ない橋を渡っているのではないか?昨日は一度もバックアップを取らなかった.それだけ夢中になっていたのかもしれないが,壊滅的な状況になっている.昨日の修正を一度捨てて前日のバックアップまで戻るか?このまま泥沼の中を匍匐前進するか?状況はかなり悪いが,修正を少し戻して状況を観察してみることにしよう.もし,どうしてもダメなら退却するしかない.その前にいまの不良が前日版(ZELKOVA 2020-11-20)ですでに起きていたものかどうかを確認しておこう.

現在の環境設定は「_DEBUGマクロ未定義」と「FORMALVERSION」いずれもOFFのDebugモードだ.「完全被参照リスト管理を実行する」はONになっている.⇒いや,参照リスト管理そのもので障害が起きているので,これは止めておこう.⇒やはり元凶は昨日のnodl_floatの修正だ.これを戻せば取り敢えず動作する.明らかに何か読み損なっていたに違いない.ここでは現行論理は正しく現行仕様を反映していると考えるしかない.つまり,間違えていたのはこのわたしだ.

一番腑に落ちないのは,なぜこの修正が参照リスト管理にまで影響を及ぼすのか?という点だ.nodl_floatでは参照リスト管理に関わるスロットREFERISTはまったく操作していない…いずれにしても修正が間違っていたことに変わりはないのでここでは撤退以外の選択はない.一応この版はそれなりに動作しているので安定版として保全しておいた方がよいのではないか?もう少し整理を進めると整った状態になるとは思われるが,その前にまたドツボにハマる可能性もある.一旦休止して,仮修正をフィックスしておこう.いや,これは安定版と呼ぶにはほど遠い.画面はすでに表示されているが,停止している.非参照カウントの残留などが無数に出てくる.⇒この障害は前日版でも起きている.

▲ZTシステム構成図7.ZELを基準ノード=202 NODULEで開いて終了のとき,被参照カウントの残留と残留参照元を検出が多発する 

NAMEBOX #1828への参照として,#1819NAMEBOX[22]と#15158GENEBOX[18]が残っている.NAMEBOX[22]はNAMEsSAMEGENE:samegene 同一世代人名枠チェーン次ノード人名枠への参照,GENEBOX[18]はGENEBOXsBOTTOM:bottom1  同一世代人名枠チェーン末尾人名枠への参照だ.おそらくこのノードはsamegeneチェーンの末尾ノードなのだろう.この世代枠が特定できて,かつこのチェーンが壊れていなければチェーンをたぐることで始末することができる.この世代枠はどこが管理しているのだろう?

系列枠だろうか?⇒このノードのsamegeneはすでに空になっている.この障害はTREEVIEW::EraseTreeViewで起きている.ここでは描画オブジェクトの草刈りをやっているので細かいオペラ―ションはまったく無視されているはずだ.しかし,個別ノードがデストラクタで正しく対応していれば,それ(秩序だった解体)も不可能ではないはずなのだが… 少なくともNAMEBOX::Disposeではそれらしきことをやっていないので,まずそれを組み込んでみることにする.

samegeneは処理されず,デストラクタ出口のCleanSlotでクリアされている.NAMEBOX::CleanSansyoにはsamegeneをクリアしているところはあるが,チェーン管理はしていない.GENEBOX::CleanSansyoでは始末を付けている.同一世代人名枠チェーンは基本世代枠リストが管理している.baselistはTOPOLOGYの所有でTRIBELISTが管理している.TRIBELIST::CleanSansyoは実行されてはいるが,NAMEBOXに関しては何もやっていない.いや,やっている.実際この中でGENEBOX:CleanSansyoが実行されている.

このノード#1828が関係する世代枠は#15158だが,GENEBOX:CleanSansyoでは人名枠の世代から割り出した#15182が検査対象となり,ヒットしないため素通りになっている.⇒いや,この処理はDisposeの中でも実行されている.getSameGeneBoxで世代枠を取り出している.どこかで世代枠の付け替えが発生しているように思われる.問題ノードのデストラクタではすでにsamegeneは空になっている.どこかで強制的に参照解除してしまったのだろう.これは参照クリアをやっているところでチェーンを維持していないために起こっているものと思われる.少なくともGENEBOXは要素の追加と削除手続きを持たなくてはならない.GENELISTにはAddSameChainという関数がある.RemoveSameChainを追加しておこう.

どうも世代関係で間違えているようだ.世代枠リストをダンプしようとすると,GENEBOX::getLocationでエラーが発生する.いや,これは検査のロジック的間違いだ.というか,TRIBEBOX::getPotentialが間違っているのではないか?始系列の場合はつねに0を返しているが,どう考えてもこれはおかしい.また,KeitoMinとPotentialはつねに同じ絶対値を持つことになっているのでどちらかに統一してもよいのではないか?以下のように定義されているのだが…

Potential 基準ノードの物理世代番号,potential 優先ノードの常用世代番号=優先ノードと基準ノードの世代差

Potentialは(系統内の)すべての系列で共通なのだから,同じ値が返されなくてはおかしい.TRIBEBOX::getPotentialで「場合分けしない@20180912」というコメントが付いているのは,このことではないだろうか?もしかすると仮修正をフィックスする段階で誤って逆の論理を残してしまった可能性もある.しかし,ここをいじると,動かなくなる.⇒暫定的にGENEBOX::getLocationの検査ブロックだけを外して走らせるようにした.これで見ると,GENEBOX#15158の常用世代番号は-6ということになる.NAMEBOX#1828の世代は-4とされるので,合っていない.どちらが悪いのか?#1828はCARDTABLE(0) だ.

どうも訳が分からなくなってきた.基本世代枠リストには13世代分の世代枠が登録されているが,実際の図面では9世代しかない.基準ノードの位置は物理世代番号では4に当たる.この意味でCARDTABLE(0)の世代が-4というのは間違っていない.物理世代番号で0~3までの4世代のGENEBOXは空,最下層の12世代も同世代人名枠ゼロになっている.描画上の問題は起きていないので,このような状態でも描画は可能であるようだが,どこで調整しているのだろう?人名枠のgetGenerationは正しい値を返しているようなので,世代枠の世代取得関数が間違っているかないし古いのではないかと思われる.

image

基本世代枠にはノード対リストがリンクされているはずなので,内容をチェックしてみよう.ノード対リストの世代番号もダンプされる世代枠の番号とまったく同じだ.というか,多分ノード対リストは所属する世代枠から世代番号を取得しているのではないかと思う.中身が空の世代枠が存在することはやむを得ないとしても,同世代人名枠の人数と画面に表示された人名枠の個数が合っていない.

同世代人名枠数M≧表示されているカード数Nであるとすると,まず,Mがゼロの世代が5つ,ゼロでない世代が8に対し,実際の画面では9世代にカードが表示されている.カード数を上から拾うと,

N=4, 4, 15, 21, 24, 11, 6, 6 となる.一方ダンプでは,ゼロの世代を除いてM=2, 3, 9, 20, 15, 9, 5, 4 のようになってまったく一致しない,というか,表示されているカードより同世代人名数の方が少ないというのが理解できない.画面上に表示されているカードは重複を除いて80だ.Mの合計は67,Nの合計は91.多重が11あるのでそれを引くと80となり,表示されているカード数と一致する.ただし,コンソールには多重14という数字が出ている.この数字にも疑問がある.画面上のカードに世代番号を表示してみよう.

基準ノードの世代番号は-2,物理世代番号は4と表示されている.CARDTABLE(0)では世代番号-6,物理世代番号0でこれらの値は(それなりに)正しい.つまり,getLocationは画面上の物理世代番号を返し,getGenerationは基本世代枠リストと一致する(常用)世代番号を返している.どこで不一致が生じているのか?基本的には動作しているので,一部の世代関数のバージョンが古いことが考えられる…

原因は分かった.系列枠がすでにパージされてしまっているからだ.TREEVIEW::EraseTreeViewは描画要素を根こそぎ刈り倒して更地にしてしまうので,系列枠が存続していることを期待できない.系列枠が存在しないと基準ノードと系列優先ノードの世代差(ポテンシャル)が分からなくなってしまうためだ.物理世代番号を使うことはできないのだろうか?getLocationは最初にgetGenerationで常用世代番号を取得したあと,GEN2DEVで物理世代番号に変換しているだけだ.

すでに「解体モード」に入っているため秩序正しく分解してゆくのが難しいことは分かる.しかし,ここまで来た以上そこまでやってしまいたい…常用世代番号ないし物理世代番号をどこかに格納しておけば,それを取り出すだけになる.Bobject::COORDINATEには2という値が入っている.ABSOLUTEが1でRELATIVEが2だ.TREEVIEW:EraseTreeViewの冒頭,草刈りの前に値を切り替えている.

これを遅延させて,更地になってから切り替えるようにすることはできるだろう.絶対座標系への転換のタイミングですべての世代計算を「絶対世代番号」に切り替えればよいのではないだろうか?それしか方法はないと思う.Bobjectクラスは描画要素の一般クラスなので世代関係のパラメータは一切持っていない.世代が関係するのはNAMEBOX, MARGBOX, GENEBOX, PAIRBOXなどだが,すべてBobjectの派生クラスなのでBobjectにgenerationというメンバーを追加してやるのが早そうだ.それしかないのではないだろうか?BobjectにGetGenerationという仮想関数を追加し,変換時にはこれを使って各クラスオブジェクトの値を取り出したあと,絶対座標系に切り替えればよい.

絶対座標系に切り替わったあとは,個別の世代関数はGetGenerationを呼び出して格納した値を取り出すという段取りになる.つまり,GetGenerationという関数は座標系モードに従って,値の取り出し方を切り替えるという関数だ.一度バックアップを取ってから始めることにしよう.修正はそれほど大掛かりなものにはならないと思う.絶対世代番号という用語はすでに使われている.なんと呼べばよいか?暫定的に確定世代番号としてみよう.GetGenerationの設置を義務付けるためにBobjectでは純粋仮想関数としておくことにする.⇒常用世代番号を使うつもりだったが,一つ問題が出てきた.

PAIRBOXは物理世代番号しか使っていない.物理世代番号に切り替えるのは難しくないが,ほとんどの場面では常用世代番号を使っている.これをすべて物理世代番号に切り替えるというのもかなり大変だ.物理世代番号 = 常用世代番号+系統ポテンシャル という関係なのでもう一つ系統ポテンシャルという値が必要になる.系統ポテンシャルは系統ごとに異なるので一つだけ保持するという訳にはゆかない.⇒物理世代番号というのがもっとも合理的かつおそらくそれしかないと思われるが,書き換えが大変だ.ともかくそれをやってみよう.

仮想関数GetGenerationをBobjectのすべての派生クラスに配置するまでは簡単にできた.物理世代番号の取得はBobject::setabsoluteに次の1行を追加するだけだ.

_generation = GetGeneration(); // 物理世代番号を取得して保存

座標系の切り替えは以下のポイントで実施している.

  1. TREEVIEW::InitTreeView → RELATIVE
  2. COUPLING::EraseFamilyTree → RELATIVE
  3. TREEVIEW::EraseTreeView → RELATIVE
  4. COUPLING::InitLinkTable → RELATIVE
  5. COUPLING::TopologicalSort
  6. Bobject::MakeAbsolute → RELATIVE → ABSOLUTE

MARGBOX::getGenerationで停止した. (PHASE > GODOWNSTREAM)では「系列枠不在」という理由で止まった.つまり,この結婚枠は系列枠に所属していない.なぜこのようなものがあるのだろう?なんとこの結婚枠はCOUPLINGに直接接続している!(結婚枠は通常結婚リンクに接続している)隠蔽リストに乗っているようにも見えるが,hideflagはゼロ.YリストではTREEVIEWに直接接続している.Yが4という値を持っているので,隠蔽リスト上にあることは間違いない.ゴミと見て間違いないが,なぜこんなものがこんなところにあるのか?は追求されなくてはならない.

さて,ここまではできたが,その後が…