「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を使った.

総訪問者数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箇所ある.最初に仮修正と暫定修正をフィックスしてしまおう.