メインループはすでに不要になっている?

メインループは当初(と言ってもかなり後になってからのことだが)タイムアウトでブレークするようになっていた.描画要素のレイアウト計算が無限ループに陥ってないし発散して停止しなくなる場合に備えての措置だ.その後はデバッグモードではループ回数オーバーでブレークするというオプションが導入されたが,リリース版では以前としてタイムオーバーを監視している.しかし,実際の動作ではつねにループ回数2回で脱出しているように思われる.これはすでにループ初回のラウンドで処理が完結していることを意味すると考えてよい.

言い換えれば,ここでは単純に処理を直列に実行するだけでよい状態になっていると言えるのではないか?ループを一回空回しするための時間コストがどの程度のものか分からないが,余分な処理はできる限り排除した方がよい.急ぐ修正ではないので,以下のことをチェックできるようにしてしばらく運用してみることにする.①ループを空回しするのに要する時間を計測する,②つねにループを2周でブレークすることを確認する.反例があれば停止する.

さて,昨日の続きに戻ることにしよう.PENDINGタグに残っているのはオン/オフを含めてあと5件だけになった.うち4件はすべて「描画矩形領域オーバーフロー」に関わるものなので,実質2件ということになるが,最初のSTRONGSHIFTREGULATIONは「すでに使われていない属性値」に関係してすでにパージを予定されている.まず,「すでに使われていない属性値@20201113」と「すでに使われていない変数@20201113」をフィックスしてしまおう.前者が10箇所,後者が11箇所だ.⇒完了した.

▲上記修正後,アプリ終了時のメモリリークが増えている.これまでは64バイトというのしかなかったが,20バイト,648バイトというのが現れた.もし,これがアプリがnewで生成したものなら,サイズ648バイトで検出できるはずだ.⇒20, 64, 648バイトに相当するメモリ取得は発生していない.メモリの取得関数は一つしかないので間違いないと思う.これには,MFCのオブジェクトは関知していないが…newを使っているところはDLLだけで149箇所ある.メモリリークはDLLだけに関係するものではないが… ⇒ソリューション全体では156箇所だ.

もし状態変化がいまの修正によるものなら,DLLに関係するものということになるが,修正をフィックスしてしまったので戻すことができない.⇒今朝の始業時バックアップを走らせてみた.同じ事象が起きる.つまり,気付かなかったが,もっと前から起きていた事象だ.Detected memory leaks! は2段階で起きている.後段のダンプは前から確認されていたが,前段のダンプは比較的最近のものだ.newで取り出しているMFC(など)NODULEクラス外のオブジェクトには以下がある.

  1. MyRichEdit 128
  2. tm 36
  3. CRichEditCtrl 128
  4. CBitmap 8
  5. CMetaFileDC 16
  6. CBrush 8

しかし,どれもサイズ的には一致しない.この他にnewで取得している以下のような配列がある.

  1. long*[segmentcnt]
  2. long[max- min + 1]

いずれも関数の出口で解放されている上,これを使っている関数MAXIMALGRAPH::SegmentBlockInclusionはすでに使われていない.

MAXIMALGRAPH::SegmentBlockInclusionは使われていないので「使われていない関数」で廃棄する.これを含めてMAXIMALGRAPHで使われなくなった関数が14個ある.CheckMergeSegment1~6は使用可能だが,SWO(SearchWrongObject)があれば代替可能と思われるので廃止する.⇒FIX完了した.

描画矩形領域オーバー時のオプションには以下がある.

  1. 描画矩形領域オーバーフローを無視する
  2. 描画矩形領域オーバーフローのとき自動的にズームアウトする
  3. 描画矩形領域オーバーフローのとき食み出した領域をクリッピングする
  4. 描画矩形領域オーバーフローのときメッセージパネルを表示する

現行では特に何もしていないのだから,(1)に該当するようにも思われるが相違点があるのだろうか?DISREGARDLIMITSIZEOVERがオフのときにはTREEVIEW::Refreshでダンプが出るようになっている.また,CtrlZoomInとCtrlZoomOutでは (TreeView->OVERSIZE) であればエラー復帰になるが,オンのときはつねに通常復帰している.VB側では戻り値によって何か動作が変わるのだろうか?いや,特に何もしていないようだ.従って,このオプションの効果はデバッグ時のダンプだけのように思われる.最後の(4)はすでに廃止されている.

(2)ではTREEVIEW::GetScrollValueからLimitSizeOverという関数が呼ばれてズーム倍率を調整しているようだ.(3)も廃止になっているので,効果があるのは(1)と(2)だけということになる.(1)はDEBUGのタグに移動し,(2)はOPTIONSということでよいのではないか?(3),(4)は当然廃止だ.「自動的にズームアウトする」という動作の効果は実際に試してみなくては分からないが,それは後日ということにしてとりあえず,デフォルトオフでOPTIONSに入れておく.これでPENDINGタグは空になった.

いくつかの項目をOPTIONSからSPECIFICATIONに移した.たとえば,「描画オブジェクトのリサイクルシステムを用いる」や「極大グラフ検定を実施する」などで,これらは裏面も実行可能ではあるが,実際にはオフに設定する予定はないという意味では「仕様」と見るのが自然であるからだ.というか,OPTIONSでデフォルトONであるような項目はすべてSPECIFICATIONとみなすべきだろう.

つまり,OPTIONSに残るのはデフォルトOFFの項目だけということになる.この中には「完全被参照リスト管理を実行する」や「SIMPLEGRAPHのattributeを廃止」がある.「結婚枠を折り畳む機能をサポートする」のように実装されていないオプションも便宜的にOPTIONSに収納しておこう.デフォルトOFFで現在OPTIONSに入っているものとしては,これらの他に以下がある.

  1. 印刷出力では連結線分・罫線を太線で描画する
  2. 単位家系図を廃止して拡張Z木図をサポートする
  3. 描画矩形領域オーバーフローのとき自動的にズームアウトする

(1)と(2)に関しては出荷時に再検討するというのでよいと思うが,(2)の「拡張Z木図」というのがよくわからない.Z木図というのはもともと多系統図だったはずだ.「拡張Z木図の場合は上流系の兄弟ノードを含む」という記述がある.また,2016-05-24 確定として「拡張Z木図を廃止して姻戚関係図をサポートする」とある.現行ではどうなっているのだろう?現行では親族範囲は7種で

  1. 標準家系図
  2. Z木家系図
  3. 直系血族図
  4. 傍系血族図
  5. 直系親族図
  6. 傍系親族図
  7. 法定親族図

となっている.拡張Z木図も姻戚関係図も出てこない.2018-03-26には「姻戚関係図を直系親族図に仕様変更」とあるので,現在の直系親族図がそれに該当するもののようだ.

ビルドしようとすると以下のパネルが出てくるようになった

image

書き加えた部分を削除しても変化しない.mmm… 分かった.✗(バツ)という文字を使ったためだ.X(乗算記号)なら通る.

姻戚関係図というのは多分,直系血族+配偶者の直系血族だったと思う.親族にはこれに血族の配偶者が加わるので,直系親族図というのは

直系親族図=直系血族+直系血族の配偶者+配偶者の直系血族+配偶者の直系血族の配偶者

となっているのではないかと思う.※

※親族図メニュー→親族範囲→直系親族図のヒントを見ると「本人の直系血族(先祖系と子孫系)とその配偶者,および配偶者の直系血族=直系親族」とあるのでこちらの方が正しい.つまり,直系親族図には「配偶者の直系血族の配偶者」は含まれない.

Z木家系図=直系血族+直系血族の配偶者

だと思うが,拡張Z木図はこれに本人の兄弟姉妹とその配偶者を追加したものではないだろうか?標準家系図は,

標準家系図=本人の(もっとも遠い)先祖の直系血族+その配偶者

なので,

拡張Z木図=本人の(すべての)先祖の直系血族+その配偶者

となっていたのかもしれない.直系親族図はわたしがイメージする「親族図」にもっとも近いので,現行の仕様通りでよいのではないかと思う.EXPANDGENEALOGYで「単位家系図を廃止して拡張Z木図をサポートする」ことができるのか?見てみよう.⇒このオプションはすでに廃止されている.まぁ,上の記録から見て当然かもしれない…これでOPTIONSのデフォルトOFFの項目は5件となったが,この他にまだデフォルトでONという項目が3つある.

  1. メタファイルに出力する@20180312
  2. 同性婚をアブノーマルな婚姻とする
  3. 系列枠内水平スプリット検定をシンメトリ婚を持つ系列に限定する

OPTIONSにはまだ実現されていない機能を含めてもよいということになっているので,「メタファイルに出力する」をOPTIONSに含めることは妥当だが,デフォルトでONになっているという点が気になるところだ.何をやっているのだろう?なるほど,「ファイルへ出力をサポートしない@20180314」というオプションに依存しているため,現在は実行できるようにはなっていない.⇒ようやくストーリィが読めてきた.それは,こういう意味だ.つまり,

印刷ダイアローグで「ファイルへ出力」を選択した場合には「メタファイル」に出力される

ということだろう.「ファイルへ出力」を選択した場合には,通常のアプリでは*.pngファイルに出力するようになっていると思う.描画イメージはCImageで保持しているので,PNG出力することは難しくない.実際,「ファイルへ出力をサポートしない」→OFF→「メタファイルに出力する」→OFFのときは,*.bmpファイルに出力するようなコードになっている.多分BMPよりPNGの方がファイルサイズは小さくなるはずだと思うので,通常通り,PNG出力でもよいのではないだろうか?

メタファイルにこだわったのは,ビットマップではなくベクトル形式で画像データを出力したかったためだが,メタファイルを編集できる(無料)ソフトが見つからなかったためペンディングになっていたのではないだろうか?「ファイルへ出力」できないというのも不都合なので,「ファイルへ出力をサポートしない」をSPECIFICATIONから一旦OPTIONSに格下げした後,改めてファイル出力形式を再検討すべきではないだろうか?⇒「ファイルへ出力」をサポートし,とりあえず,BMPに出力できるようにしておこう.この場合は「メタファイルに出力する」はOFFになるから,OPTIONSに留まる資格が維持される.

「同性婚をアブノーマルな婚姻とする」はONになっているが,その内容はかなり疑わしい.ゼルコバの木の初期バージョンというか,かなり最近までは異性婚以外はすべてエラーとして処理され,誤入力した場合には自動的に性別が整合するように補正するような仕組みまで組み込まれていたのだが,さすがに時代の潮流には逆らえずかなりルーズな扱いにはなってきている.従って,「同性婚をアブノーマルな婚姻とする」ONというのはかなり疑わしい.何をやっているのか見てみよう.

結婚リンクにはabnormalsexという属性があり,「配偶者性別に矛盾がある」場合つまり,同性婚の場合にはONになるという仕掛けだ.このabnormalsexという値が真の場合には「結婚点」の形状が変化するようになっている.具体的には,とここまで書いたところでサンプルを作ろうとしてエラーになった.⇒下記▲参照

結婚点は形状が違うというより,塗りつぶしの色が異なるだけのようだ.つまり,白と黒の中間色のグレーで表示されている.これはかなりわかり易いような気がする.

image

結婚点の白・黒は必ずしも女性・男性を意味するものではない.結婚連結線はあるカードとその配偶者を連結する水平線で,配偶者が複数ある場合には本人(重婚者)側が黒,配偶者側に白が与えられる.この色別はその連結線の所有者が誰かを示すためのもので,そのカードが本人ポジション(親の子ども枠)にあるか,配偶者ポジション(配偶者の親の子ども枠)にあるかには関わりがない.

「同性婚をアブノーマルな婚姻とする」という表現はややどぎつい印象を与えるかもしれないが,結婚点をグレー表示すること自体は穏便な表現になっていると思われるので,このオプションはSPECIFICATIONとしてよいと思われる.ただし,今回のセッション中に再現手順はまだ確立していないが,親子関係を設定しただけで女性が父親になってしまう例が複数発生している.これは明らかに予定されていない動作であり,誤動作と見るしかないので,バグとして追いかける必要がある.

▲下図のような家系図を入力中,GIRLの父母MAN+OTHER MANをOTHER MAN+MANに書き換えて登録で停止した.

image

このエラーが同性婚に関わりがあるのかないのかは不明.BUG20-11-14 22-23-24.ZELで再現できる.エラーパネルをOKで閉じるとGIRLのカードには父:OTHER MAN, 母:WOMANが残っているが,親子連結線は描画されていない.母の欄にMANと記入したのにWOMANとなっているのは,入力氏名の探索にあいまい検索を使っているため,MAN→WOMANを第一候補として登録したものと思われる.

image

GIRLで並び替えると下図のようになる.

image

できの悪い図面だ.MANが多重になってしまっている.こんな簡単な作図ができないようでは,まだまだ先が遠いという感じがしてしまう…この障害は再現できるので,先に残ったOPTIONSのデフォルトONの項目を片付けてしまおう.「系列枠内水平スプリット検定をシンメトリ婚を持つ系列に限定する」というやつだ.どうなっているのか見てみよう.TRIBEBOX::CheckInnerSplitの入口で(!SymmetryCount && !TooYoungCount)の場合は単にゼロ復帰しているというだけだ.つまり,その系列内にフロート婚と呼ばれる特殊婚が存在しない場合は処理をパスするというフローになっている.これで特に問題は生じていないように思われるので,現状でFIXでよいのではないだろうか?

これでPENDINGタグはデフォルトでON/OFFに関わらず完全に空になった.つまり,「PENDINGゼロという目標」を達成することができた.その代わり,OPTIONSには「未完成のオプション」が含まれることになったが,これらはONにしない限り作用することはないので放置されたとしても安全だ.しかし,INCOMPLETEにポジションを移した項目がかなりある.これらはINCOMPLETEというより,説明にやや疑義があるというもので,動作確認を含めた精査が必要だ.全部で8項目あり,うち「直属系列落ちを無視する@20181001」を除く7つの項目はすべてデフォルトONになっている.

  1. SELECTFOREFATHER IsFurtherのオプション
  2. 配偶者の性別不問とした  動作に疑問がある
  3. WriteNonStopSampleListを一時停止 WriteNonStopSampleList
  4. 衝突検定で計算誤差を許容する CheckIncidentalCollision
  5. ここでは直属系列落ちを許容する SolveTribeCollision
  6. 直属系列落ちを無視する SolveBondedTribeCollision
  7. MakeYokogakiBoxをパス TITLEBOX::SetDispParm
  8. NAMEBOX:Drawへ移動 意味不明なところがある

デフォルトOFFの「直属系列落ちを無視する」から見てみよう.この分岐はTRIBELIST::SolveBondedTribeCollisionに置かれ,OFFの場合にはDEBUG_NEVER (tribe->IsDirectSubTribe())を実行するようになっている.つまり,IsDirectSubTribeで停止するようになっている.ということはIsDirectSubTribe=直属系列落ちを意味すると考えて間違いないだろう.つまり,現状は「直属系列落ちなら停止する」という動作になっている.SolveBondedTribeCollisionという関数の目的は「すべての束縛系列の衝突を検査し修復する」ということだ.束縛系列に対して,自由系列というのがあり,それぞれがブロックを構成している…

束縛系列は(BONDEDTRIBE|MAGARIHOSEI|SPOUSESENZO)のいずれかの属性を持った系列と定義される.

  • BONDEDTRIBE すべての下位系列と交叉しかつ上位系列と交叉している系列
  • MAGARIHOSEI 先祖曲がり補正を受けた系列(スプリット検定をパスする)
  • SPOUSESENZO BTW左手本人が系列先祖でかつ系列優先ノードの特殊BTWに関わる系列と左手本人およびその結婚枠

かなり難解だが,自由系列というのはおそらく主系列とそのブロックに直接接触していない系列のことだろう.始系列を除くすべての下位系列は主系列に従属するという関係にあるから,この関係を枝とする木を構成する.しかし,系列は大雑把に言って右から左に水平配置されるので,いわば切り倒した木を薪にして並べたような状態になっていると考えられる.これはそれ自体一種のトポロジーソーティングのようなものになっていると考えられるが,それをどうやって解決しているのだろう?IsDirectSubTribeという関数が返しているのは,

(MAGARIHOSEI|BOTTOMEMPTY|SPOUSESENZO|HASNOMINOR)

という値だ.新たに2つの属性が出てきた.

  • BOTTOMEMPTY 最下段世代枠が空で最下段世代に消去された系列優先仮ノードが存在する系列
  • HASNOMINOR 始系列を除き下位系列を持たない系列

直属系列に関しては次のような記述がある.

以下のような系列をその主系列の直属系列とする
①下位系列を持たない系列(付帯系列)
②最下段世代枠が空で最下段世代に消去された系列優先仮ノードが存在しかつ,付帯系列以外の下位系列を持たないような系列

上の定義に従えば①はHASNOMINOR,②はBOTTOMEMPTYでかつ,HASNOMINOR以外の下位系列を持たないような系列となる.IsDirectSubTribeにはBOTTOMEMPTYやHASNOMINORが含まれているところから見ると,直属系列落ちというのは,直属系列でなくなるというより,「直属系列の状態になる」のように汲み取れる.束縛系列にはMAGARIHOSEIとSPOUSESENZOが含まれているから,「束縛系列の状態から直属系列の状態に変化する」のようなニュアンスが感じられるがあまり正確ではない.

この操作に関係する属性は5つあるが,そのうちHASNOMINOR(付帯系列)だけは動的に変化する可能性はないと考えられる.(系列参照関係は動的に変化し得るので絶対にないとは言い切れない…)おそらく,この図法では直属系列と呼ばれるものをできる限り主系列の近傍に配置するというストラテジーを考えているのだろう.もう少し読まないと何をやっているのか理解できないが…

PENDINGゼロという目標は容易に達成可能

ようやく乗ってきたようだ.200箇所も分岐がある中でPENDINGをゼロにするなどという目標はとてつもないものに聞こえるかもしれないが,PENDINGから,①SPECIFICATION,②OPTIONS,③DEFECTED に移すという道が開けたのでPENDINGが空になるというのももはや時間の問題だ.ここで,①SPECIFICATIONというタグは「仕様」であり,このようなものを作るという「決定」だが,特に「それなしではシステムが動作しないようなオプション」が含まれる.言い換えれば裏面(#defineをコメントアウト)では動作しないことを認める.

一方,②OPTIONSは「任意選択可能なオプション」であり,基本的にはオンとオフの両側で動作するものでなくてはならない.ただし,「裏面の動作は必ずしも保証されない」としてよい.③DEFECTEDは本来ならOPTIONSの候補であるところだが,明らかに不具合が見られるようなもので,「当面の間使用を禁止する」類のものだ.

昨日の修正では誤って「仮修正」を廃止してしまったための手戻りが発生した.一応STOP文で停止してくれたのでことが発覚し,比較的短時間でシューティングできたが,やはり,「現状」というのは(そこまでの動作確認の積み重ねがあるので)重く見なくてはならない.『その場の思い付き命取り』という格言をもう一度噛みしめるべきだ.

PairBoxGeneChangeの中でノード対が削除される場合がある PairBoxGeneChangeの呼び出し箇所を総点検する必要がある

PairBoxGeneChangeは8箇所から呼び出しがあり,その中で戻り値を取っているのは4箇所だけだ.PAIRBIX::retrieveShiftedPairBoxでは自ノード対を対象にPairBoxGeneChangeを実行している.⇒この関数の動作は安全だ.出口で「このノード対は死んだ~」という表示が出るくらいだから…その他3箇所で対処した.

さて,昨日の続きに戻ろう.DEBUGタグとVERIFYタグをすべてオフにして,PENDINGの動作を検証しているところだ.定義文のコメントから「仕様」と考えられるものはとりあえずすべてOPTIONSに移動することにする.この中には「純正血統図をサポートする」,「同性婚をアブノーマルな婚姻とする」などがあるが,ここでは動作確認は行わない.

たとえば,「純正血統図をサポートする」ならオフの場合はどういう動作になるのかを調べなくてはならないが,「純正血統図をサポートする」をオフにしても,系図画面設定パネルから「純血統図」が消える訳ではないし,そのときの動作がどのようなものになるのかも分からない.しかし,そこまでやると手が回らないので,ここでは「OPTIONSの動作確認は出荷時検査で行う」とだけマニフェストしておく.

これでPENDINGタグのデフォルトでオンに残るのは7件になった.これらにはコメントからよく趣旨が汲み取れないもの,ないし疑義のあるものなどが含まれるが,一度すべてINCOMPLETEに差し戻して別途動作確認を行うことにする.これらはおそらくOPTIONSに昇格するのではなく,フィックス→確定になるはずだ.PENDINGカテゴリでデフォルトでオフのものに関してはすべて現状でフィックスでよいのではないかと思われるが,一通り調べてみる必要がある.この中には

  1. HeapUpperの循環検査を行なう→廃止
  2. 計算時間制限の代わりに計算ループ回数に上限を設定する
  3. MakeSectionだけでノード対区間計算を完結させる
  4. カード世代シフトの規制を強める
  5. 描画矩形領域オーバーフロー(3件)
  6. 直属系列落ちを許容する/しない

などがある.HeapUpperの循環検査はYリストの誤接続などで生じる循環を検出するもので,すでにそのようなバグは完全に消滅したとみられるので廃止でよいと思う.計算時間制限というのはメインループが止まらない場合に時間で処理を打ち切るという安全装置だがすでに廃止され,現行のループ回数制限を越えるような事象も起きていないと判断できるのでフィックスでよい.これにはTESTタグで「当面使用禁止」としているオプションも絡んでいるが,整理可能な論理のクリアランスが必要だ.ノード対区間計算もなんどか仕様変更を繰り返しているが,現状でフィックスでよいのではないだろうか?カード世代シフトの規制を強めるというのはよく分からないが,「カードシフト」という機構自体が廃止されているという話もあるので,調査する必要がある.

「描画矩形領域オーバーフロー」という事象もすでに根絶されているようなので周辺論理を含めて一掃でよいのかもしれない.「直属系列落ち」が何を示しているのかよくわからないが,「ここでは直属系列落ちを許容する」というのと,「直属系列落ちを無視する」というのがあり,混乱している.というか,「許容する」と「無視する」というのが同義であるとすれば,では「直属系列落ちを無視しない」ところはあるのか?ということになる.そのような場所がないというのなら,「直属系列」という概念自体消滅する.そういう場所があるとしたら,むしろ積極的に「この場所では直属系列落ちを…のようにする」ということが明示されなくてはならない.

開いていたサンプル(ZTシステム構成図7.ZEL)を閉じようとしてFAMILYTREE::GetPersonalBaseで停止 topologyが空になっている

VB側からの呼び出しによるものでVBではすでにFormClosingを実行しているところだ.PHASEはゼロでINITIALSTATEに戻っている.FAMILYTREE自体はまだ活きているが,スロットはすべて空になっている.再現できるだろうか?系図画面設定パネルを操作したあとしばらく放置してからアプリ終了という手順だったと思うが…VBからのコマンドから発生する例外はGCでキャッチするようになっているので異常終了のようなことにはならないとは思われるが…

開いて閉じる,あるいは開いて設定パネルを出して終了では起きないが,部分図への切り替え,カラー設定など操作してから終了すると再現する.VBとDLLは並列実行されていて,特に待ち合わせということをしていないので,VB側の終了処理が遅れればこのようなことはあり得るのではないかと思われる.どうすればよいか?通常それぞれのコマンドは単体で完結しているので,特に待ち合わせの必要はないが,終了時は全体の処理時間が相当なものになるためまだ存続していると思って問い合わせするなどのことはあり得るのではないか?

GetPersonalBaseではASSERT_NEVERで停止しているが,これを内部で判断して動作を変えることはできる.しかし,このようなことは他の関数でも起き得ると考えなくてはならないから,かなり広範な修正が必要になってくる.以下のような外部インタフェース関数では:

_EXPORT long __stdcall GetPersonal(long num, PERSONAL* person);

入口で_ASSERT_NEVER(!FamilyTree)を実行してFAMILYTREEが活きていることを確認しているが,ここに専用マクロを設置してPHASEを確認するようにすればよいのではないか?たとえば,_ASSERT_ACTIVEとし,PHASEがINITIALSTATEではゼロ復帰するというのでよいのではないだろうか?⇒INITIALSTATEではなく,BEGINNINGSまで戻っていた.⇒こういう状態で呼び出しが掛かってくるのはこの件一回だけだが,念のためすべての外部関数にこのマクロを仕込んでおくことにする.(!FamilyTree)の検査もこのマクロの中で実施して,マクロを一本化しておこう.⇒52箇所あった.ただし,うち2箇所はvoid関数(シグネーチャが異なる)なので直接コードを書き込んだ.

いや,今度は3つ呼び出しが掛かってきた.CallGetUndoStat,GetRecordCount,CallSetKeizuParm.タイミングによっていろいろ変わるようだ.すべての外部関数に仕込んでおいたのが正解だった.

CHECKHEAPCIRCULATION(HeapUpperの循環検査を行なう)はすでにどこからも参照されていないので単純に廃止.⇒間違えた.大ミスをするところだった…検索条件が「このドキュメント」になっていた.このオプションは2017年1月に廃止され,2018年3月に停止が確認されているので廃止でよいと思われるが,Yリストの上流方向での循環検査ルーチンというのが見当たらないので,どこかにコードとして残しておいた方がよい.この検査ではBobject::heaptestという専用変数を使っているが,他の検査ルーチンではcheckmarkというのを使っているので,これを使うように改めればよい.まず,Bobject::HeapUpperの中ある検査コードを切り出してみよう.

いや,この部分だけを切り出してもあまり意味がないかもしれない…HeapUpper自体動的にYリストを上昇しながら実行される計算過程なので,その関数内では動作が完結しない.Yリスト(描画リスト)というのはBobjectに備わった機構で3つのスロットと1つの整変数を使って構成される有向グラフだ.ルートを除くすべての描画オブジェクトは1入力枝と2出力枝を持ち全体として大きな2分木を構成する.たとえば,抽象グラフ検証系を使ってこのような有向グラフが有向木となることを検査することは難しくないが,それなりのコストが掛かる.

単発的にあるノードが上流経路で循環していないことを確認するには単純に木を上昇して自ノードに遭遇しないことを確認するだけでよい.コードにしても2, 3行で終わってしまう.有向グラフが木であるか否かを検査するツールを整備することは意味がある.親参照パスも同種の有向木だ.ただちに必要というものでもないので,これはまた後日ということにしておこう.ということで,ためらわず廃止でよいのではないか?

SORTINGTIMEOUTLIMITの説明には「計算時間制限の代わりに計算ループ回数に上限を設定する」とあるが,実際のコードではSORTINGTIMEOUTLIMITが未定義のとき(つまり現状では)TIMEOUTLIMITが立っていないときにのみ「制限時間」をレジストリに登録するという動作になっている.この値はVBで読み出されて表示される.分かり難い説明だが,どうも書き込まれているのは実際の計算に要した実時間で,「制限時間」という場所を借りているだけのようだ.

TIMEOUTLIMITというブール値はレジストリの「計算時間制限」に格納されている.計算時間制限と制限時間は下図のようにVBの隠しパーツで設定できるようになっている.

image

時間制限するがオンで計算時間3秒になっているが,実際の動作はどうなっているのだろう?⇒TIMEOUTLOOPがゼロでないときは時間制限は作動しない.ようやく少し読めてきた.

このシステムではループが停止しないときに備えて元々タイムアウトでブレークするような作りだったのだが,デバッグ上の都合で回数でブレークというのを導入したのだろう.なぜ回数か?というと,時間制限では実行時にムラが出て現象を再現出来ない可能性があるためだ.いくらマイクロセコンドのクロックで制御された機械でも実時間にはどうしてもさまざまな理由でムラが生じる.バックグラウンドタスクの影響を受けることもあり,室温にさえ左右されるかもしれない.事象が発生したときのループカウントを使えば現象を確実に再現できる.

現行論理ではデバッグ時にはTIMEOUTLOOPカウントを10に設定し,リリース版ではゼロに設定している.TIMEOUTLOOP値がゼロの場合には時間制限が掛かるはずだがそのような動作にも見えないのはなぜか?リリース版では時間計測していないから,時間はつねにゼロだ.

現行ではおそらくすべてのサンプルでメインループを2回しか回っていない.2回回るのは,システムが静定するのを待っているからだ.つまり,データが更新されている間はループするようになっているため,最小限でも2回回らなくてはならない.もし,2回目がデータが更新されないことを確認するだけのものであるのなら,ループではなく単純な線形処理に書き換えてしまってもよいはずだ.すでにそういう状態になっているのではないか?この点に関してはもう少し調べなくてはならない.

ともかく,SORTINGTIMEOUTLIMITというオプションは説明にあるほどのことをやっている訳ではないので,現状で単純にフィックスでよい.その他,loopcountとroundがまったく同じ値を持っていてどちらかは不用であるなど,気になる点は多々あるが,また後でということにしよう.SORTINGTIMEOUTLIMITの説明文は「計算時間制限なしの場合,計算実時間をレジストリ登録する」のように書き換えた.

MAKESECTIONFULL(MakeSectionだけでノード対区間計算を完結させる)はすでにフィックスしている.どこからも参照されていない.STRONGSHIFTREGULATIONも説明文が悪い.このオプションではTRIBEBOX::ResetCriticalの中で禁止ノード対リストを空にした後,系列内のすべての人名ノードのSHIFTRECURRENT属性をリセットしている.これが「カード世代シフトの規制を強める」ことだろうか?

いや,おかしい.そもそも禁止ノード対リストはとっくの昔に廃止したのではなかったろうか?何かまたとんでもない失敗をやらかしてしまったような予感がする…prohibitlistとshiftlistを廃止したのは11月9日だ.この日付には何か不吉感が漂っている.「系列枠のシフト管理用ノード対リストを廃止@20201109」という名義で始末しているはずなのだが…ソースファイルのどこにも痕跡がない.

確かに2020年11月10日には9日の二回目バックアップまで戻っているので,その後に実施された修正は抜けている.9日の記事は2つのノード対リストのパージ以外では一括変換のことしか触れられていないので,この部分だけ修正を入れれば多分同期が取れると思う.まず,これをやっておこう.キーワードには「系列枠のシフト管理用ノード対リストを廃止@20201113」とする.以下の関数・変数もすべて不用となる.

NAMEBOX::shiftnum,NAMEBOX::targetnum,TRIBEBOX::RemoveFromShiftList,UpdateShiftList,ShiftMultiCards,DumpShiftList,DumpProhibitList,PAIRBOX::shiftnode,gettarget,shiftcard,shifttarget

これからフィックスするところだが,かなり修正が入ったので一旦バックアップを取っておこう.mmm… また例外が発生してしまった.

image

障害発生日時:2020年11月13日20時56分51秒 bugflag=0 C:\Users\babalabo\Desktop\添付サンプル\源氏物語全系譜6.ZEL
Failed at  with error 1008: 存在しないトークンを参照しようとしました。 障害発生日時:2020年11月13日20時56分51秒 bugflag=1 C:\Users\babalabo\Desktop\添付サンプル\源氏物語全系譜6.ZEL
0x781DFFD0 (ZelkovaDLL3.dll) で例外がスローされました (ZelkovaTree2021.exe 内): 0xC0000005: 場所 0x000000A8 の読み取り中にアクセス違反が発生しました

エラーは画面右上の終了ボックスをチェックしたタイミングで発生する.⇒リリース版をビルドしていた.デバッグ版ではnodule::Disposeで停止する.nodule::Disposeに入ってくるときには,すべてのスロットが空になっていなくてはならないのだが,残留リンクがあるようだ.PAIRBOXの2つのスロットを潰しているのにインデックス名が残っていた.⇒対処した.問題なく閉じられるようになった.

さて,バックアップも取ったのでいよいよ「系列枠のシフト管理用ノード対リストを廃止@20201113」をフィックスすることにしよう.もちろん,最初から最終的な記述に進んでもよいのだが,やはり手順を踏んで最初に#defineを定義し,次に変更箇所を#ifndef で書き換えてゆくというステップの方が安全だ.どっちみち最終的には(修正タイムスタンプを除いて)すべて削除してしまうのだが…

修正箇所は35箇所あるが,すべて「系列枠のシフト管理用ノード対リストを廃止@20201113」のキーワードが付いているので検索で容易に位置を特定できる.あとはグレーの「アクティブでないブロック」を一つづつ潰してゆくだけだ.⇒終わった.

STRONGSHIFTREGULATIONをフィックスしようとしているところだった.STRONGSHIFTREGULATIONが作動しているのはTRIBEBOX:ResetCriticalでここでは「カードシフトの循環防止用パラメータを初期化する」を実行している.この関数はTRIBELIST:TribeRelocationから呼び出されているが,「垂直セグメント検定用パラメータを初期化する」という説明が付いている.垂直セグメント検定というものはすでに廃止されているので,この関数自体不用となっている可能性がある.

確かに,SHIFTRECURRENTという属性自体がすでに使われていない.「系列枠のシフト管理用ノード対リストを廃止@20201113」の一部として,SHIFTRECURRENTを廃止してみよう.もう一度このキーワードを#defineして始めよう.⇒修正した.これ以外の属性でもすでに使われていないものが発生している可能性があるので,点検しておこう.

ENDPOINTFAILUREはすでに使われていない.「すでに使われていない属性」が11種もあった.この修正はかなり大きなものになりそうなので,一度バックアップを取ってから初めた方がよい.「すでに使われていない変数」というのもある…

目標:PENDINGゼロでVERIFICATION,TEMPORARYの完全ニュートラルを達成

コンパイルオプションを6つのカテゴリに区分し,それらにタグを付けて管理するようにした.

  1. VERSION comdebug.h COMDEBUG:
  2. INCOMPLETE nodule.h INCOMPLETE:
  3. PENDING nodule.h PENDING: 
  4. TEMPORARY bobject.h DEBUG: , LOCAL:
  5. VERIFICATION bobject.h VERIFY:
  6. TEST coupling.h TEST:

comdebug.hに入っているCOMDEBUG:はバージョンやライセンス管理などに用いるものでデバッグの対象とはならない.nodule.hにはINCOMPLETEとPENDINGが入っているが,INCOMPLETEは現在ゼロ個,PENDINGが36個ある.PENDINGの中には将来的な拡張に備えるものもあるので必ずしもすべてという訳ではないが,一応PENDINGの個数をゼロにするというのがこの作業の目標と言える.TEMPORARYはデバッグ時のダンプなど一時的に使われるコード,VERIFICATIONは検証のために常設されているプローブだが,いずれも無害コード(フローを変化させず,データの書き換えを行わないもの)でなくてはならない.TEMPORARYとVERIFICATIONがこのような意味でのニュートラルなコードになっていることを確認することもこの作業の一部に含まれる.

coupling.hに入っているTESTは包括テストなどを実施するためのオプションだが,すでにかなり古くなっているように思われる.実際,現行では包括テストはすべてヘルプメニューから実施できるようになっており,コードをいじる必要はない.システムに組み込まれた作り付けの装置のようなものになっている.TESTコードの大半は廃棄の対象になるかもしれない.オプションをオンないしオフにしてエラーが発生するとすれば,基本的には動作する側でフィックスすることになる.

不用となったコードを参考資料として温存するか否かはまた別の判断になるだろう.INCOMPLETEということはデバッグ未完了ということを意味するから,これを一応対象外とすれば,このセッションの目的は「PENDINGゼロでTEMPORARYとVERIFICATIONがデータフローニュートラルであることを確認する」ことにあると言える.

counpling.h はシステム構成図の頂点をなすCOUPLINGクラスを定義するものなので,すべてのコードから参照可能になっていると思っていたが,一部にアクセスできないものがあった.これまでヘッダファイルのヘッダファイルとして使われていたkakeizu.h はcoupling.h でもインクルードしているので,include “kakeizu.h” となっていたところをすべて include “coupling.h” に書き換えた.nodule.h や comdebug.h は元々グローバルにアクセスできるようになっている.coupling.h はkakeizu.h に入っているKAKEIZUを参照しているので, kakeizu.h から coupling.h をインクルードすることはできない.

PENDINGはデフォルト(既定でオンのオプションをオンとして),DEBUGはすべてオフ,VERIFYはデフォルトを試して動作した.ただし,Bobject::vsegmentが廃止されているため,NAMEBOX::DrawとMARGBOX::Drawで修正が発生した.DEBUGはすべてオフのまま,VERIFYのデフォルトオンをすべてオフに切り替えて試したところ,広域スプリットと描画不良が発生した.ただし,TRIBELIST:TribeBaseList→baselist,GENEBOX::getGene→getGenerationの書き換えた.⇒以下のオプションをオンにすることによって描画できた!

#define ABSOLUTEYLIST            // VERIFY: ★絶対座標変換を描画リストに沿って実施する

これはかなり驚異だ!止めてあるオプションの中には,「極大グラフ検定を実施する」,「三極検定を①結婚点一致→②衝突検定→③セグメント検定の順で実行する」,「★BRect::InterSectRect関数で接触を交差に含める」など,システムの基本仕様のようなものまで含まれている.

ともかくABSOLUTEYLISTを実施しないと描画できないことが分かったので,この定義はオプションから仕様(SPECIFICATION)に格上げするしかない.いや,そう結論するのはあまりにも単純だ.ABSOLUTEYLISTがオフのときには何もしていない,つまり相対座標系のまま放置されているのだから,描画できるはずがない.仮に描画関数がすべて相対座標に対応していればその可能性もゼロではないが,現行ではほとんどの関数はそうなっていないはずだ.

将来的にすべての描画関数が相対座標系に対応するということも考えられないことではないが,ここでは一旦このオプションを仕様として確定するしかないのではないか?⇒#if defined(ABSOLUTEYLIST) を #if 1 に変えて確定したが,元の論理は残した.「Yリストに沿った変換」ではなく「座標系変換パスに沿った変換」というのも考える余地があるからだ.むしろそれを試して両者が完全一致することを確認した方がよい.ただし,それはまた後日ということにしておこう.

ここでは一旦DEBUGとVERIFYのデフォルトでオンの項目をすべて戻した上で,PENDINGの分を試してみることにしよう.⇒2つのオプションをオンに戻すことでエラーを回避することができた.一つは「GetOSDisplayStringの使用停止@20190109」でもう一つは「CHUNKFILEクラスを実装する」だ.前者がないと「’GetVersionExA’: が非推奨」の警告が出てオブジェクトファイルが作れない.また,後者がないと,カード写真イメージのロードに失敗する.従って,これらも必須オプションということになるが,ここで新たに2つのサブカテゴリないしタグを追加することを提案したい.

①SPECIFICATIONと②OPTIONSだ.前者はそれがないとシステムが動作しないもの,後者はオン・オフを切り替えても動作するが,オフのときの動作は保証しないというものだ.両者の線引きには多少微妙なところがあるが,いまの場合,上記のABSOLUTEYLISTや,「GetOSDisplayStringの使用停止@20190109」はSPECIFICATIONで「CHUNKFILEクラスを実装する」はOPTIONSでよいのではないかと思う.一旦PENDINGからSPESIFICATIONないしOPTIONSに格上げされたオプションは,基本的には確定したものと考えてよい.

このサブカテゴリに該当するものはかなりあると思われるので,整理してみよう.というか,まだデフォルトでオフのオプションをオンに切り替えるテストをやっていないので,そちらを先に片付けてしまおう.デフォルトでオンのオプションはすべてオンに戻しておくことにする.多少の修正が入っているので一度バックアップを取っておこう.いや,それどころではない.例外が発生している.

上記の環境で起動→終了で,以下のようなメモリアクセス違反の例外が発生した.

mblock->**mmptr** が 0x93203436

再現しない.障害はSaveFamilyBaseで起きているのでバックアップタイマが作動するまで待つ必要があるのかもしれない.例外はfreeblock:delmptrで発生している.バックアップファイルは作られたが正常に終了できた.バックアップが更新されるまで待ってみよう.以下の行が数回出ていたのでかなり時間が経っていたかもしれない.

書き込み:zoom_normal=0.290677 preview=0.305460

終了ボタンが効かなかったような気がするので,放置していただけで起きていた可能性もある.これまでにこのような例外の発生は見たことがないので何か微妙なタイミングがからむ超低頻度の事象がたまたま起きたという可能性もある.このプリント文はSerializeHeaderで出しているものだ.⇒再現した.上の「書き込み」を4回出したあと,例外が発生している.ただし,今度は少し状況が違う.

**this** が 0x92D0907B

というパターンだ.この障害は直ちにはシューティングできない可能性もあるので(いじっているうち消えてしまう可能性もある)現状をサンプルも入れて別にバックアップしておこう.何か止めているオプションで影響しそうなものはあるだろうか?「ファイル保存時バッファを予約する」などというのは可能性がありそうだ.バックアップタイマーのインターバルを縮めて再現できるかどうか見てみよう.現行では1分タイマーになっているので,10秒に短縮してみる.

どうも効きが悪い.この値を短くしても効果はない.これはタイマーが作動する「インターバル」でタイムアップするまでの時間ではない.この値はユーザの設定値でレジストリに保持されている.しかもこの値は1分単位だ.とりあえず,1分に変えてみよう.少しは速くなったようだ.RESCUEMINUTEという変数に格納されているので,これを直接変えることもできる.あるいは残り時間の計算をいじった方が早いかもしれない.⇒確かに4回目で起きている.比較対象のタイムスタンプが分単位なので分より速くはできない.今度は3回目で起きた.

OPENQUICKDB(ファイル保存時バッファを予約する)をオンにしてみよう.⇒確かにこれだ.すでに9回「書き込み」が発生しているが,例外は起きていない.つまり,このオプションも必須ということになる.バッファを使わないという方法もあるいは可能なのかも知れないが少なくとも現状ではそうなっていない.これでSPECIFICATIONに入るものが3つになった.「CHUNKFILEクラスを実装する」を含めそれ以外のPENDINGのかなりのものはOPTIONSに移すことができる.

もう一つサブカテゴリ(タグ)としてDEFECTIVEというのを作っておこう.これは「欠陥がある」という意味ではINCOMPLETEと同じだが,INCOMPLETEは完全なものに仕上げる必要があるのに対し,DEFECTIVEは「当面使用禁止」というもので放置しておくことができるものだ.TESTに入っているオプションはほとんどそこに入ると思う.INCOMPLETEはnodule.hが一元管理しているが,DEFECTIVEはそのオプションが配置された特定ヘッダファイルの中で個別管理することにする.⇒DEFECTIVEはタグではなく,#if defined(DEFECTIVE)のブロックで隔離するだけでよい.多分これで相当整理されると思う.RESCUEINTERVALは1分では粗すぎるので,20秒にしておこう.

PENDINGの中からOPTIONSに移せるものを拾い出してみよう.テストを公平なものにするために,DEBUGとVERIFYはすべて止めておくことにする.その上でオンで動くものはOPTIONS候補だ.IMPLEMENTCHUNKFILE(CHUNKFILEクラスを実装する)もSPECIFICATIONとしておこう.カード写真イメージが読めないというのは致命的ではないが,欠陥であることは明らかだ.PENDINGのうちデフォルトでオンの項目すべて(17件)をオンにしてテストしてみよう.サンプルはZTシステム構成図7.ZELとする.

上記設定で起動→部分図→全体図でエラーが発生した.PAIRBOX::getLocationで(Deleted())が起きている.「一時的にSTOPを抑制する@20201026」で止めてあったところだ.シューティングを試みることにしよう.PAIRBOX::getLocationで失敗している.⇒getLocationを呼び出す前にすでに「ノード対世代不一致」が検出されている.このノード対はすでに削除されてゴミ箱の中に入っている.StillAliveという検査をすり抜けている.なぜだろう?StillAliveでは#2434が返っている.USERECYCLESYSTEM (描画オブジェクトのリサイクルシステムを用いる)はオンになっている.「DEBUGとVERIFYはすべて止めておく」という設定にはなっていない.

確かにここではまだ死んでいない.PairBoxGeneChangeを実行した時点で削除されている.PairBoxGeneChangeでは「世代差ゼロ以下でノード対を破棄」が実行される.この関数はnewboxを返しているので戻り値を見れば成功したか否かは判定できる.それをやっていないのが致命的だ.⇒対処した.

USERECYCLESYSTEM(描画オブジェクトのリサイクルシステムを用いる)をオフにしているのに,削除されたオブジェクトがゴミ箱に入っている.USERECYCLESYSTEMというのはリサイクルを実施する/しないではなく,リサイクルシステムを使っているときにはCARDLINKとMARGLINKを初期化する必要があるというだけの話だ.これをオフにしても動作しているのは,特に必要な処理ではないということを意味するように思われる.ゴミ箱はシステム木のルートであるCOUPLINGを生成する以前に生成されている.⇒ゴミ箱を生成しないようにしてみたが問題なく動作している.オブジェクトはフロート状態になっているが,アクセスは可能だ.つまり,ゴミ箱はなくても動く.deleteしても直ちには削除されず,Nringが管理しているのだろう.

リサイクルシステムはCANが空のときは実質無動作になるので,USERECYCLESYSTEMのときはCANを生成しないようにすれば,名実ともに「リサイクルシステムを用いる」の言葉が活きてくる.リサイクルシステムはなければ動作しないというものではないから,SPECIFICATIONというよりはOPTIONSとすべきだろう.

上記サンプルでDEBUG, VERIFYはすべてオフ,PENDINGのデフォルトオンはすべてオン,OPTIONSもすべてオンの環境で,源氏物語全系譜6.ZELを開いて停止した.(primarynode->getmarglink() != getOyalink())が起きている.障害はTRIBEBOX::decidePrimaryNodeで起きている.インストールされているアプリでは以下のような別の障害が起きる.この版は2.2.0.015 R 2020-11-05だ.

image

この後も操作の度にエラーが出る.源氏でこんなにエラーが出るなんて信じられない.どこかで間違ってしまったのではないだろうか?再開発スタート版を見てみよう.その前に開発機でバックアップを取っておこう.⇒確かにスタート版ですでに起きている.この版を採用するとき十分なチェックをしたつもりだったのだが…ZELKOVA_2020-10-12★では,「非連結グラフ」というのは出るが,ここまでひどくはない.ZELKOVA_2020-10-24では問題なく開ける.再開発スタートは10月14日だから,それより後の版だ…無傷で開けるのは10月24日が最後でその後は重婚同類検定で以下のようなエラーが出るようになる.

image

リリース版は滅多に作らないので日付と内容が一致しているとは限らない…デフォルトでオンのオプションをすべてオンに戻してみたが,動作は変わらない.ともかく,少し追いかけてみよう.どうもかなりおかしい.TRIBEBOX::Oyalinkには値を設定しているところがない.だからつねに空だ.10月24日版で見ると,ASSERT_NEVERの行は「仮修正@20190129」としてコメントアウトしてある.どうもそれを不用意に外してしまったようだ.TRIBEBOX::Oyalinkには値が設定されていないのだから,このパラメータはすでに廃止になっているのと同じだ.その方針で修正してみよう.

勘違いしていた.Oyalinkは活きている.これがなくては動かない.Oyalinkというのは,「従系列に属する優先仮ノードの親の結婚リンクへの参照」だ.いまの場合は軒端の荻が優先ノード,主系列は光源氏のいる一院系列,従系列は配偶者の蔵人の少将が先祖ノードになっている.軒端の荻の親の伊予介は先祖ノードで別の系列を立てている.軒端の荻の出現箇所は以上の3箇所だが,蔵人の少将の系列では配偶者ポジションなので親はいない.つまり,Oyalinkは空だ.従って,優先ノードのgetmarglinkと系列のgetOyalinkが一致することはあり得ない.しかし,それ以外の論理には抜けはないと思われるので,ASSERTION行だけの誤りということになる.以下のような記録がある.

@20190129 → 廃止@20201107 decidePrimaryNode,SetMinorTribe

仮修正@20190129を廃止したという意味だろう.decidePrimaryNodeというのはいまの問題箇所,SetMinorTribeにも同様の(おそらく)誤りがあるが,この2箇所にはSTOP文が入っているため,間違っていても停止するだろうという見込み(安全を見込んで)で廃止で確定したのだろう.@20190129の修正は3箇所あって,もう一つのNAMEBOX::IfBTWPossibleの修正は活きている.

もう一度DEBUGとVERIFYのオンをオフに戻して次に進もう.SEPARATEVHZONEはすでにどこからも参照されていないので廃止.HORIZONTALQUICKARANGE,BESTEFFORTCHANNELTROUBLEも同様.ファイルへ出力をサポートしない@20180314はいまのところは「仕様」と考えられるのでSPECIFICATIONに移しておこう.PUREBLOODLINE 純正血統図をサポートするも完全に仕様だが,オフでどういう動作になるのか見ておく必要がある.

多重カードのカウントがおかしい.ZTシステム構成図7.ZELで多重が10件以上出ているのに,表示では多重ゼロ,不可避ゼロとなっている.

image

いや,何か勘違いしていたようだ.上の図では多重14で合っている.ただし,不可避が1で多重が14も出るというのはよく分からない.重婚同類循環は3となっている.

▲書き込み:zoom_normal=0.322251 preview=0.034721の表示にタイムスタンプを追加する⇒タイマーのインターバルを確認したい

こうなったらまた出戻る以外の選択肢はない

とんでもないことになってしまった.大きな過ちを冒していた.こうなったらまた出戻る以外の選択肢はない.リスタートポジションは確保したのでもう一度やり直すことにしよう.大部分の修正はヘッダファイルの書き換えなので,修正がそのまま再利用できるから必要な作業は最小限のものに留まる見込みだ.おそらく今日中にもクリアできるだろう.

この作業は二つのポイントから見て重要だ.条件コンパイルは基本的にプログラム開発の分岐点に相当するから,分岐点がp個あれば,観念的には2^pの異なるバージョンが存在することになる.仮に条件コンパイル文が100個存在するとすれば,

2^100=1,267,650,600,228,229,401,496,703,205,376

もの異種バージョンが存在し得ることになる.修正をフィックスするという作業の目的はこの分岐点を最終的にはゼロ個まで削減し,2^0=1,つまり,単一バージョンまで統合することにある.この状態になって始めてプログラムは「正式版」と呼ばれることになるだろう.

条件コンパイルのもう一つの解釈は,それがプログラム開発の履歴を表現するものとなっているというポイントだ.つまり,「分岐点」というのは一つの新しい「進化」の方向性であり,プログラムの成長を促す「新芽」であると考えられる.修正をフィックスした時点でその分岐に付けられたコメントは「修正履歴」として記録されるべきだ.この「履歴」はプログラムの開発途上で「経験」したさまざまな事象の蓄積であるとも考えられる.この「経験された事象」は具体的には「障害」ないし「障壁」であると考えられるから,それに対処するために実装された「対策」を体系化したものが「免疫系」と呼ばれるシステムであり,「自己の恒真性を維持するための仕組み」であるとも言える.

さて,ぼちぼち始めることにしよう.現状では#defineで定義された値を持たないキーワード(コンパイルオプション)は226箇所ある.昨日確認された186件に比べると大分多いが,これはヘッダファイルが最新でC++ソースファイルが古いバージョンに戻っているため重複が発生していることによる.まず,この重複を解消するところから始めよう.昨日Meryを再インストールして「並び替え」マクロを使うことができるようになったので,この作業は簡単だ.検索結果をMeryで並び替えして重複行をチェックするだけでよい.⇒対処した.

#define\s+\S*\s*(/|$) (C)

で検索すると,まだ200個残っている.14件のダブリが残っているが,これはリネームしているものがあるためと思われる.DLLだけバックアップを取って進めることにしよう.現状では#ifdef が372個,#ifndef が59個,#if defined が429個,#if !defined が44個ある.#if の方はすでに原則としてすでに始末されていると考えられるので,#ifdef, #ifndef の431個を個別にチェックしてゆくことにする.#ifdef, #ifndef を #if の形に書き換えるのは正規表現を使って一括処理できるはずだから,まず,以下のような修正を実施することにする.

  1. #ifdef, #ifndef の条件式(キーワード)が#define によって定義済みのときは何もしない
  2. キーワードが未定義のときは,現状のままフィックスする つまり,グレー表示のアクティブでないブロックを削除し,平文に戻す アクティブでないブロックが存在しないときも同じ
  3. キーワードが未定義でアクティブなブロックが存在しないときは,x行削除のコメントを残してアクティブでないブロックを抹消する
  4. ただし,内容に疑義があるとき,ないしそのオプションの存続が必要と認められるときには,新たなキーワードを定義し,負論理を正論理に改めた上で条件分岐文を温存する
  5. 特定ヘッダファイル(nodule.h, comdebug.h, coupling.h, Bobject.h)以外のファイルにある#define文はLOCAL: に属するものを除いてすべて抹消する
  6. 「衝突検定で計算誤差を許容する」に関わる論理はすべて温存して後日再検証することにする 未定義のものに関しては新たにキーワードを定義し,「衝突検定で計算誤差を許容する」という表記とMAXKEISANGOSAの適用が「同期」するように真偽値を定める 
  7. ただし,「計算誤差を見る」の類は現状(AboutEqualを使う論理)でフィックスする 「計算誤差を無視する@20170509」も確定する 「MAXKEISANGOSAを適用する@20171229」は現状でフィックスした
  8. 「この関数は最高速で実行されるべきだ」の類は「速度最優先オフ」に統合する
  9. キーワードが定義済か否かの検査を「定義をここに表示」メニューで判定してはならない 定義はあってもコメントアウトされている場合があるので,つねに「実検索」の結果を見て判断する必要がある
  10. 何かの理由で温存したい論理がある場合には,if 0/1 のような表記を認める
  11. 「配偶者の性別不問とした@20180201」は未定義だが,新設してPENDINGとする 「メタファイルに出力する@20180312」も未定義だが,PENDINGとした

コンパイルエラーが発生した ⇒最優先オフをオンにしたためだ.checkブロックの内容が古くなっていた.⇒対処した.

非アクティブブロックがグレー表示されなくなってしまった.これでは修正を誤る可能性が高い.⇒ビルドし直したら正常に戻った.⇒ダメだ.また出てきた.今度はビルドして戻らない.一旦VSを落として再起動でようやく正常に戻った.

古いコードでDEBUGPRINTというマクロが見当たらない.⇒空のマクロを作っておく.DEBUGDUMPというキーワードはマクロとオプションの両方に使われている.整変数に使っているところもある.整変数はiDEBUGDUMPとしてみよう.マクロはmDEBUGDUMPとする.

NAMEBOX:: PaintNameFrameは2つの関数から呼び出されている.デバッグ時にはNAMEBOX::Drawから,リリース版ではNAMEBOX::DrawNameからという変則的な呼び出しになっている.おかしいので,つねにNAMEBOX::Drawで実行するように書き換えた.

#ifdef と#ifndef の点検は完了した.あとは整理するだけとなった.その前に#ifdef を#if defined の形式に変換してしまおう.いや,その前に#defineされて使われていない定義というのがあるはずなので,それを先に片付けてしまおう.⇒#defineの個数は200個になった.増減があるので,多分これで正しいのだと思う.バックアップを取っておこう.#ifdef から #if defined への一括変換は昨日やっている.

検索:#ifdef (.+)\b
置換:#if defined($1)

ただし,この方式ではコメントが入っている行ではコンパイルエラーが発生する.コメント付きの場合にも適用できるようにしたい…

検索:#ifdef\s+(\S*)(/|\b)*?   (S)
置換:#if defined($1)       (R)

これでなんとか行けそうだ.#ifdef は140個ある.140個置換した.⇒うまくいった!ビルドも通った.#ifndef も同じ要領でやっておこう.#ifndef は61個ある.⇒問題なく変換できた.さて,この200個の#define文が1個になるまで削減するというのだが,可能だろうか?とりあえず,やり易いところからやってゆくことにしよう.

まず,ローカルに定義されているLOCAL: は整変数に変えてしまってもよいだろう.実際この方LO法は現在すでに使われている.#define DISPALWAYS を int DISPALWAYS に変えるというだけだ.⇒これで定義文は7個減って193になった.残りはすべて特定ヘッダファイルだけということになる.

これはダンプルーチンなどをシステムの装置に組み込むということを意味する.計測装置をシステムに組み込むというのは,たとえば車にスピードメーターを付けるようなものであり,もしロジックが混み合うようなことがあれば,その部分だけ切り出してルーチン化してやればよい.Bobject.hにはDEBUG:という区分でデバッグ用の定義がどっさり入っているが,装置化できるものは装置化してしまうのが一番よいと思う.少なくとも1つのファイル内で完結している定義に関してはそれをやるべきだろう.いままでやってきたことと真逆のことをやることになるが,それも仕方ない.⇒この修正は一旦撤回することにする.

▲comdebug.hの古いプリント文などの定義は廃棄してもよいのではないか?

▲_DEBUG_,XDEBUGなどの動作をチェックする必要がある 古いバージョンは廃棄した方がよい

すべての定義文をデフォルト(=現状)でオンとデフォルトでオフに区分けして管理する その上ですべての定義文をオンにして動作を確認する また,すべての定義文をオフにして動作を確認する ただし,comdebug.hとcoupling.hの管理する分は除く(ということは対象はnodule.hとBobject.h内の定義文に限定される

まず,すべてのスイッチオンを試してみる.コンパイルエラーが出ている.MERGETESTCARDIMAGEでbool NOCARDIMAGEを初期化しているが,参照されていない.⇒#if 0 で仮止めした.COLCOLORSEGMENTやCOLVERTICALSEGMENTで使っているHというパラメータが未定義.⇒対処した.COLVERTICALSEGMENTで使っているvsegmentという変数が未定義.⇒垂直セグメント検定という機構は完全に廃止されている.SIZEOVERAUTOZOOMOUTでLimitSizeOverの引数が足りない.⇒補充した.⇒とりあえず動いた.

▲TESTタグのPRESETPARAMETERを実行するとNAMEBOX:EraseGhostNode→setCriticalでエラーが発生する.チャンネル不足が発生しているものと思われる.チャンネル数自動ならエラーは発生しない.このエラーを仮修正でパスすると,MakePairListClean中にnoduleのデストラクタで非参照カウントの残留が発生する.サンプルはゼルコバの木モジュール構成図 TEST.ZEL.エラーを無視して描画は可能.PAIRLIST→PAIRBOXの参照が残っている.TESTPERFECTでも同じエラーになる.⇒いや,これはプリセット値が入っていたためと思われる.しかし,自動テストの動作にはなっていない…

▲PENDINGをすべてオン,COMDEBUGは無指定,それ以外のすべてのオプションをオフにしてエラーが発生する.スプリットが発生しているが,それ以上に画面が壊れている.

過去の修正をFIXするという取り組み

過去の修正をFIXするという取り組みは進んではいるが,進捗は捗々しいものではない.あまり手が掛かるので一括変換で#ifdef を #if defined に変換したのが却って裏目に出ている.キーワード(コンパイルオプション)の定義・未定義を判別するのが著しく困難になってしまった.こうなったら振り出しに戻ってすべての#if definedを1個づつ点検してゆくしかないのだが,その前に正規表現の構文を研究して,#define文で(値を設定しない)定義だけのキーワードを選り分けることができるようにしておきたい.後ろにコメントが付いていない文は検索できるが,コメント付き定義文だけを抽出することができない.

昔はSEDやVIを使ってバッチファイルで多数ファイルの一括変換などを自在にやっていたのだが,一度あるところで社内LANの大元のHD(システム全体をカバーするタワー型のストレージ,当時のことでわずか70MB,ユーザの端末にはHDは搭載されていなかった)のユーザファイルを全部消してしまうという大失策をやったことがあり,それからは足が遠のいてしまった.(このときはHPのサポートチームが飛んできて鮮やかに復活せてくれたのだが…大いに懲りた)

できた!これでコメントありの定義文をすべて抽出できる

#define\s+\S*\s*/ (A)

157個あった.#define文は修正FIXの問題があるので集中管理した方がよいと思う.ただし,そのファイル内に限定したローカルなデバッグ用オプションは従来通り認めることにする.ローカルなオプションは(デバッグ時にしか使われないのだから)#if defined(_DEBUG)のブロックに置かなくてはならない.#define文は原則として以下の4つのファイルのいずれかに置くことにする.

  1. nodule.h 修正履歴,関数の論理・仕様に関わる定義(INCOMPLETE,PENDING)
  2. comdebug.h バージョン管理に関わる定義,ライセンスに関わる定義,デバッグ用ツールに関わる定義 (VERSION)
  3. Bobjecct.h デバッグ用オプション定義(TEMPORARY),検証に関わる定義(VERIFICATION)
  4. coupling.h テスト環境に関わる定義(ENVIRONMENT)

まず,この観点から定義文を整理してみよう.たとえば次の例文を見てみよう.

#define SOLVETRIBESPLIT // 系列間スプリット検定の動作を従来仕様に戻す

もし,SOLVETRIBESPLITがどこからも参照されていなければ,この定義は無条件で「廃止」される.いまの場合,このキーワードは別の場所で別の意味「系列間スプリットの発生により移動が実施された」として使われていた.つまり,本来の用途してはフィックスしていることになるが,そうでない場合にはnodule.hのINCOMPLETEかないしPENDINGカテゴリに移されなければならない.もし,この修正が正当であることが検証されれば,定義を廃止して修正履歴に移動する.ただし,単純に「廃止」されただけの場合は必ずしも「履歴」として保存されなくてもよい.いまの場合は「単純な廃止」に相当する.

検証用にこの論理を(場合によっては加工して)残す必要がある場合には,nodule.hのVERIFICATIONに移動する.また,事後の再発に備えてデバッグ用に論理を残す場合にはBobject.hのTEMPORARYに預けることになる.昨日コメントなし定義の抽出用に使っていた #define (?!.*( |\t)).*$ で検索するとヒット件数が7しかない.ほとんどの定義の後ろにコメントを付けたこともあるが,付けていないものもある.

#define\s+\S*\s*$ (B)

これで30件ヒットした.多分これは動作していると思う.(A)式と(B)式を合体して1式にできるだろうか?

#define\s+\S*\s*/
#define\s+\S*\s*$

これは特に難しくなさそうだ.末尾が/か$で終わればよいのだから…つまり,

#define\s+\S*\s*(/|$) (C)

でよいはずだ.これで186件検出した.もう一度個別にテストしてみよう.前者が158件,後者が30件で単純合計は188件となる.ということは重複が2件存在しなくてはならないのだが…エディタで並び替えができると簡単に重複行を調べることができるのだが…常用しているエディタのMeryではマクロでその機能が提供されているはずだが,見つからない.もう一度インストールし直してみよう.⇒今度はProgram FilesのMeryの中にMacrosというフォルダができた.早速使ってみよう.

確かにダブっているエントリが2つある.

#define    _POLLFILE_H_
#define    __HOSTCMD_

テキストに「/」が含まれる行を探す(A)式を実行しても上の2つが検索結果に入ってしまう.このタイプの記述はすべてのヘッダファイルが持っているので,この2つだけ特異な動作になるというのはおかしい.(A)式を実行して「/」を含まない行が検出されるということはあり得ないのでVSの「検索」のバグと考えるしかない.(C)式を使えばもちろんこのようなことは起こらない.⇒すべてのオプション定義にコメントを付けて以下のコードで識別できるようにした.

  1. INCOMPLETE: nodule.h 0
  2. PENDING: nodule.h 28
  3. COMDEBUG: comdebug.h 25
  4. TEST: coupling.h 17
  5. VERIFY: Bobject.h 47
  6. DEBUG: Bobject.h 35
  7. LOCAL: *.cpp 7
  8. 合計 6種 *.h 5本, *.cpp 6本 159

オプション定義のある*.cppファイルは,SimpleGraph.cpp, Potential.cpp, PairBox.cpp, MargBox.cpp,Jikusenzu.cpp,DoublyBlessed.cppがある.たとえば,「LOCAL:」を含む定義を検索するときには以下のような式を使った.

(#define\s+\S*\s*(/|$))(.* LOCAL:)

INCOMPLETEはいまのところゼロだが,検証結果によってはPENDINGから移行してくることもあり得る. Bobject.hに含まれるVERIFYとDEBUGは完全にニュートラルでなくてはならず,実行フローやロジックに影響を与えないことが保証されなくてはならないが,現状ではその確証はない.PENDINGに含まれる項目のほとんどは直ちにフィックスしてもほぼ問題ないと考えられるが,ここでは一旦置いて,先に「無定義オプション」つまり,「負論理で定義された仮修正」の始末を急ぐことにしよう.これを行う方法としては,とりあえず,#if defined/!defined文を虱潰しにチェックするしかないような気がする.ともかくそれをやってみよう.

VERIFICATIONとDEBUGの相違点は前者は一般に常時走っているのに対し,DEBUGはバグをトレースする目的で一時的に出動するというところだ.この意味ではASSERTIONなどはVERIFICATIONのカテゴリに属するとも言える.というか,VERIFICATIONとして扱われているロジックは将来的にはASSERTIONに昇格するものと見るべきだろう.VERIFICATIONにはデバッグ支援という目的で設置されているので,(DEBUG_NEVERなどと同様)必ずしもリリース版で動作することを意図していない.つまり,ASSERTIONとVERIFICATIONの違いはリリース版に常設されるものであるか否かという点にある.VERIFICATIONでは事象が発生した時点で停止するだけでよいが,ASSERTIONにはアプリを続行するための例外処理が整備されなくてはならない.

▲comdebug.hで定義するASSERTIONマクロに例外処理を整備する

「MAXKEISANGOSAを適用する@20171229」は未定義だが,論理が逆(負論理)になっている.この論理は「再開発スタート版」でもそのようになっている.つまり,本来は#ifndefでなくてはならないところが,#ifdefになっていたのではないかと思われる.「MAXKEISANGOSAを適用」に関係する論理は無数にあるが,オプションになっているところでは.「MAXKEISANGOSAを適用しない」という流れになっている.しかし,必ずしも普遍的にそうなっている訳ではないので,どうも統一的な方針を欠いたまま場当たりに修正しているのではないか?という懸念がある.ここでは,「MAXKEISANGOSAを適用する@20171229」に#define文を与えた上で,現行論理のまま据え置くことにしておくが,全般的な見直しが必要と考える.

いや,違う.もっと重大な失敗を冒している.やはり,機械的に#ifdef から#if definedに置換した失敗は大間違いだった.定義されている場合はそれでよいが,未定義の場合は例外なく負論理で書いているので真偽を逆にしなくてはならない.手動で修正していたときにはそのような論理の書き換えを行っていたのだが…機械的に置換したところより,前まで戻るしかない.とんでもないことになってしまった.

一括変換をやったのは昨日のことだ.昨日は2回バックアップを取っている他にDLLフォルダだけのバックアップを一度やっている.

  1. 始業時バックアップ 10:07
  2. 一回目バックアップ 13:53
  3. DLLをバックアップ  15:32
  4. 二回目バックアップ 19:06 

おそらく一回目バックアップは一括変換を実施する前に実行されていると思う.(でなかったらアホだ)⇒一回目バックアップではまだ,prohibitlistとshiftlistの始末も付けていない.DLLをバックアップ でも同じだ.二回目バックアップでも手を付けていない…#ifdef は379点まで減少している.多分「#ifdef _DEBUG, _DEBUG_, XDEBUGの3種をすべて#if defined()に一括変換」という辺りだろう.

prohibitlistとshiftlistのパージはログの冒頭近くで書いているが,実際には修正のFIXと並行して,その片手間にやっていたのだろう.いずれにしても二回目バックアップは安全なところまでしか一括変換をやっていないので,ここからスタートすれば十分安全と思われる.すべてをここまで巻き戻すのではなく,*.hに関わる修正は現在のものが使えるのではないかと思う.重要な書き換えはすべてヘッダファイルに入っているので,これを再利用できれば相当な利得がある.まず,そういう形で設(しつら)えてみよう.かなり手戻りだが,失敗するよりはましだ.

系列枠のシフト管理用ノード対リストを廃止

#ifdef プラグマを潰す作業を続けているところだが,まだ840個も残っている.今日中に終わるだろうか?動作には影響のない益の少ない作業のようにも思われるが,これが完了すればソースコードがかなり読み易くなることは確実なのでがんばってみることにしよう.幸いVSは検索結果を表示するウィンドウを3つ持っているので,残存する#ifdefの検索結果を「検索結果テーブル」に表示しながら,別のウィンドウでキーワード検索するなどのことが並行して実行できるのは大変都合がよい.

ノード対は通常基本世代枠リスト→世代枠→ノード対リストに接続されている.系列枠は世代シフト管理用ノード対リストというのを2種類持っている.一般にリストはリスト要素オブジェクトをスロットゼロを使ってチェーン状にリンクしている.逆に言えば,リスト要素は一つのリストにしか接続できない.系列枠が持っているノード対リストはどうやってノード対にアクセスしているのだろう?

系列枠のprohibitlistとshiftlistは現在使われていないように思われる.⇒終了時にcancelするところはあるが,リストを構成している論理は見当たらない.すでに廃れているのではないだろうか?⇒TRIBEBOX::CleanSansyoでは対象オブジェクトがこの2種のノード対リストである場合は停止するような論理になっている.つまり,明らかにこの2つはとっくの昔に廃止されている.⇒パージは簡単に終わったが,実行時エラーが出た.⇒クラス構成の始末がついていなかった.TRIBEsPROHIBITとTRIBEsSHIFTが残っていた.

この修正に伴ってNAMEBOX::shiftnum,NAMEBOX::targetnumも不用になるのではないか?TRIBEBOX::RemoveFromShiftListも不用になる.UpdateShiftList,ShiftMultiCards,RemoveFromShiftList,DumpShiftList,DumpProhibitListも同様.もしかすると不用ロジックはもっと広範囲に拡がる可能性もある.そもそも,カードシフトという概念自体がすでに崩れているのではないだろうか?もしかするとこれは事実かも知れないが,影響が大き過ぎるので後日ということにして先に進む.OBSOLETEで仮止めしてある部分が廃棄されることは確実なので#ifdefを#if definedに書き換える必要はないと思われるが,OBOSOLETEではあまりに一般的過ぎるので「系列枠のシフト管理用ノード対リストを廃止@20201109」に変えておこう.

▲InitializePrinterはすでに使われていないのではないか

#ifdef _DEBUG, _DEBUG_, XDEBUGの3種をすべて#if defined()に一括変換して#ifdef は362個まで減少した.あとは個別にこつこつと修正してゆくしかない.⇒VSの検索・置換は正規表現が使えるので試してみよう.以下で一括変換できた.

検索:#ifdef (.+)\b
置換:#if defined($1)

キーワードの中に全角@が入っていても処理できる.ただし,この置換は行末に閉じ括弧が入るので行中にコメントが入っているとコンパイルエラーになる.コメントが入っているのは1ファイル1個くらいなのですぐにビルドできるようになった.あとは,存続するものと廃止するものを切り分けるだけだ.#define文は1210個もある.定義だけで値を持たないリテラルをピックアップできればよいのだが…以下の検索式で定義だけのリテラルを検索できる.

検索:#define (?!.*( |\t)).*$

ただし,後ろにコメントが付いているものはこの式では弾かれてしまう.mmm…少しおかしい.49個しかヒットしない.尻尾に何か付いているものがあるのだろうか?⇒コメントが付いているものと思われる.これは別途扱うことにする.

ともかくまず,この49個を始末してしまおう.いや,これらは基本的には問題ない.というか検証する必要はあるにしても原則としては存続というのがルールだ.始末しなくてはならないのはむしろ「無定義」のキーワードだ.それをどうやって見つけることができるだろう?#ifdef から#if definedに書き換える手順の中でそれを一つづつやってきたのだが,一括変換してしまったので境界線が消えてしまった…あとは,#if definedを一つづつチェックするしかないのではないだろうか

残っている分岐はすべて現状でフィックス

過去の修正をすべてフィックスして完全にさらの状態から再出発するための工程を進めているところだ.肝要な部分に関してはすでにほぼ完了しているが,#ifdef, #ifndefによる条件コンパイルの分岐はまだ1000箇所以上ある.できるところまでは整理しておきたいが,基本的に現在残っている分岐はすべて「現状でフィックス」という方針なので作業的には難しいものではない.分岐には#defineでキーワードを定義したものと,それを欠いているものがある.#defineがないものはその場でフィックスしてよいが,#defineがあるものについては検証が必要だ.

分岐を残す場合にはその目的・用途によって以下の4種に区分することにする.①TEMPORARY,②INCOMPLETE,③PENDING,④VERIFICATION.TEMPORARYはデバッグ時のダンプなど必要に応じて一時的に用いるもの,INCOMPLETEは修正が確定していないもの,PENDINGは再検討を要するもの,VERIFICATIONは動作確認のために(常時/一時的に)設置するものとする.

キーワードに関わる分岐が複数ファイルに関係する場合にはヘッダファイルを使った広域に適用される定義が必要だが,1ファイル内に留まる場合にはソースファイルの冒頭に直接記述する略式を認める.キーワードには原則日本語でその趣旨を明示するようにする.フィックスしたキーワードは原則としてコメントとしてファイルに残すこと,修正箇所は少なくとも@日付でマーキングして修正履歴をトレースための手がかりを残しておくこととする.つまり,@日付はタイムスタンプであると同時に識別子でもある.キーワードにはコメントで解説を付することが望ましい.日付だけのキーワードは望ましくない.また,日付が変わっても一連の修正には同一キーワードを適用する方がよい.

分岐を残す場合にはすべて#if definedないし#if !definedの形式に改める.ただし,一時的な仮修正では#ifdef 形式で負論理の条件式を用いることを認める.(逆に言えば,#ifdef形式の修正はすべて「仮修正」と認定される)暫定的な修正では#define文を切った上で,PENDINGとすべきである.PAIRLISTでdatacountの残留が発生するという事象があるので,先に見ておくことにしよう.

PAIRLIST::~PAIRLISTでdatacountの残留が発生している.アプリを起動→終了で再現できる.⇒これはノーマルな動作である.delete PAIRLIST→~PAIRLIST→dispose→cancelで始めてdatacountはゼロになる.check文の中で検査すればよいのだが,呼び出し頻度の高い関数は余分なものをできるだけ持たないようにしているので,速度最優先オフのときはcheckブロックが使えるようにしておこう.

同上サンプルで終了時,世代に_INT_MAX(FARFUTURE)が入っているPAIRLISTがある.~PAIRLISTのscount=76と77,SNUMは307と308だ.PAIRLISTのダンプで表示される世代番号は親の世代枠から取っている.世代枠が空であるためだ.⇒系列枠は世代シフト管理用ノード対リストというのを2種類持っている.シフトノード対リストと禁止ノード対リストだ.これらは管理用のノード対リストであり,直接系列枠に接続して,世代枠には繋がっていない.⇒系列枠に接続しているPAIRLISTの場合はそれを明示的にダンプするようにした.PAIRBOX::dumpのASSERTIONには誤りがあった.

また失敗した.

1>d:\zelkova\zelkovadll\src\mergecard.cpp(878): fatal error C1020: 予期しない #endif です。

多分これは孤立した#endifを削除するだけで通るだろう.⇒うまくいったようだ.RECYCLENEWNUMBER(リサイクル時には新しい通番を付与する)は現状でフィックスすることにする.すべての系列枠を親参照パスから切断する@20180503も確定しておこう.この他,確定した主なオプションはあとでリストにして示すことにする.これらの修正履歴は原則nodule.hに記載することにする.

#ifndefは完全に片付いた.以下は除外した.①マイクロソフトのコード,②ヘッダファイル冒頭のヘッダ名の宣言,③_DEBUGなどデバッグモードを示すキーワード.#ifndefにはこれらによって73件が残っている.#ifdefの現在数は846だ.ともかく,片付くまでやるしかない.一度バックアップを取っておこう.

コンパイルオプションの真偽を負論理から正論理に変更する

過去のすべての修正をフィックスし,まっさらなところから「再開発スタート版」を開始するという方針を立てて作業を続けている.fixという英語は「修理する」という意味に使われるが,ここでは「修正が完了し,バージョンの分岐が存在しなくなった状態」と定義する.つまり,#if, #else, #endif がソースコード上から消えることを意味する.nodule.h, Bobject.h で定義されていた条件コンパイル式に関してはすべてFIX完了し,「仮修正」,「暫定」の文字列はシステム内から完全に一掃された状態になったが.,分岐を持つ修正がまだかなりの個数残っている.これらはTEMPORARY, INCOMPLETE,および保留※で識別できるようにしてある.※はPENDINGで識別することにした.

TEMPORARYは一時的な修正で元のソースに戻すという前提で挿入されているもの,INCOMPLETEはまだ修正が確定していないとみなされるものという位置付けだ.これらが,その定義通りの状態になっているかどうかを試すには,#defineで定義された識別子の真・偽を切り替えてみればよい.TEMPORARYは一時的なコードなのだから,オンでもオフでも正常に動作しなくてはならない.従って,TEMPORARYプラグマがこの条件を満たしていれば,リリース版では復旧させるという条件下で存続が認められる.INCOMPLETEの場合も,同様に識別子をオン・オフして動作を確認することで正当性をチェックできる.もし,オンで不具合が出ればオフの論理が正しく,オフで停止すればオンの論理で確定できる.いずれでも不具合が発現しないとすれば(実際はほとんどそのようになっていると思う),その修正の意味を読んでメリットのある方を選択することになる.オンとオフの動作が同じなら,場合によってはTEMPORARYに移管するということも考えられる.

一つ問題なのは現状ではこれらのプラグマのほとんどが負論理になっているという点だ.これは#define文を省略して,識別子の意味と反対の論理で記述されているということを意味する.つまり,たとえば「この文は誤り@20201107」が未定義のまま使われている #ifdef この文は誤り@20201107 #else #endif のブロックではアクティブなブロックが誤りで,アクティブでないブロックが正しいという逆の意味になる.

#ifdef この文は誤り@20201107 (#defineしていないので偽)
  アクティブでないブロック(この文は正しい)
#else
  アクテイブなブロック(この文は誤り)
#endif

オン・オフの切り替えテストを行うためにはかなり不都合なので,とりあえず,すべてのプラグマで#define文を宣言して正論理に切り替えておく必要がある.まず,この修正をやっておこう.修正が必要なところは60箇所くらいある.ことのついでにこのような場所では#ifdef ではなく #if defined() を使うようにすることにしよう.こうしておけばより詳細な条件式を論理演算子を使って記述することもできるし,通常の#ifdefとも区別することができるだろう.

現在システム上には#ifdef文が893個存在する.#if文は69個あるが,うちVSが生成する*.rcの4個を除く65個はすべてVBの#If文(大文字のI)だ.いや,まだある.#ifndef が178箇所で使われている.#ifdefと合わせると1071個の条件コンパイル式があるということになる.これを総点検するというのは現実的ではない.ここでは#defineを置いていない条件式は「一掃」の対象になると宣言するに留めておこう.

▲「保留」されている論理を点検しフィックスする必要がある

ともかく,TEMPORARYとINCOMPLETEに含まれる項目を正論理に変えるところから始めよう.

▲終了時PAIRLISTのデストラクタでdetacount 非ゼロが起こり,ダンプが表示される.サンプルはゼルコバの木モジュール構成図 TEST.ZEL,基準ノードは#33 baselist,軸線図法.datacount=2.

▲#ifdef _DEBUG_で実行を抑制しているブロック/関数が135箇所ある.この中にはすでに廃れてしまっているロジックなども含まれると思われるが,一度点検する必要がある.

#ifdef OBSOLETE(廃れた)はNOTHINGと同様,無条件に削除してよい.いや,待てよ.何かおかしなことが起きている.OBSOLETEの中に含まれているGetOSDisplayStringという関数はそれ以外のどこにも含まれていないのに未定義にならない.⇒いや,出てきた.未解決の外部シンボルとなっている.つまり,この関数はOBSOLETEにはできない.あるいは完全に廃止するかのどちらかだ.このコードは確か外部から拾ってきたものでOSのバージョンなどを取得するためのルーチンだ.残しておいてもよいのではないか?⇒if !defined(GetOSDisplayStringを停止@20190109)で使えるようにしておいた.

この関数を廃止した理由はこのコードがWindows 7 あたりまでしかサポートしていないためだ.修正すれば,Windows 10でも使えるようになるかもしれないが,’GetVersionExA’: が非推奨 というエラー(警告)が出てビルドできないので差し替えが必要だ.警告をエラーとして扱うというオプションを緩和すれば使えないことはないが…このようなこともあるのでいきなりOBSOLETEを削除もリスクがある.⇒OBSOLETE 3件を削除した.NOTHINGは6件ある.⇒始末した.

OCXで使われているint TEMPORARYの意味・用途が不明.「プレビュー→ノーマル一時切り替え中」となっているが,値はゼロのまま変化していない.⇒廃止でよいと思う.TEMPORARYはすべて整理した.INCOMPLETEに移ろう.INCOMPLETE@20190128はdecidePrimaryNodeを隠蔽しているが#elseは空で,改訂版のdecidePrimaryNodeは無条件で動いている.これはすでに確定しているものと判断する.INCOMPLETE@20190129には複数の用途がある.

  1. decidePrimaryNodeで(primarynode->getmarglink() != getOyalink())のとき停止しない
  2. SetMinorTribeで「基準ノードを参照する系列の優先ノード切り替えは禁止」を無視する

多分どちらも反例があると思われるので検証が必要だ.INCOMPLETE@20190202はもっといろいろな場合を含んでいる.

  1. EstablishMajorTribeChainで「系列順位が逆転している場合」を無視
  2. MakeUpTreeで先祖ノードが単身先祖配偶者のとき,EstablishMajorTribeChain(先行系列との接続関係を確立する)を実行しない
  3. TRIBEBOX::MakeUpTreeのループでSetPrimeNode(系列優先仮ノードと優先実ノードを設定)を実行する
  4. MakeUpTreeで「系列優先ノードが隠蔽されている」とき停止しない
  5. SetupPrimaryNodeで始系列の場合も「系列優先実ノードが家内婚配偶者で隠蔽されている場合」の探索を実施する
  6. SetupPrimaryNodeで始系列でない場合には優先実ノードが隠蔽されている場合を認める

これはおそらく完全に確定した修正のように思われる.INCOMPLETE@20190204を見てみよう.

  1. TRIBELIST::MakeUpTreeでEstablishMajorTribeChain(先行系列との接続関係を確立する)を実行する代わりにSetupPrimaryNode(系列優先仮ノードと優先実ノードを設定)している
  2. setMajortribeでsetrealnode(系列優先実ノード参照をリセット)しない
  3. GetMajorTribeChainでIsSolidNameBox検査とsetrealnodeを実行している
  4. TRIBEBOX::GetRealnodeで系列優先実ノードが見つからないとき,優先仮ノードのNameBoxから取り出している処理を廃止

これも確定しているように思われるが,項目4が正当であるためには,前段で系列優先仮ノードから必ず優先実ノードを決定できることが示されなくてはならない.INCOMPLETE@20190130は2箇所だけだ.

  1. ReduceMultiCardで人名枠をIsVisibleCardではなくIsValidNameBoxで判定している
  2. ReduceMultiCardで仮ノード消去の対象カードから配偶者を除外している

項目2は配偶者が消去されるのはBTWの場合だけということを主張している.これは正しいのではないか?最後のINCOMPLETE@20190131は6箇所だ.

  1. TRIBELIST::MakeUpTreeで系列優先仮ノードが隠蔽されているとき,優先仮ノードを探索してTRIBELIST::MakeUpTree(系列優先仮ノードと優先実ノードを設定)するブロックを廃止している.ただし,#elseブロックは空なのでこれは確定と考えるしかない.SetupPrimaryNodeには「隠蔽された系列優先ノードの補正」という処理が入っているので,おそらくここでは不用になったものと思われる.
  2. CARDLINK::getProxyで系列優先仮ノードを無条件で可視化している.いや,違う.これは一般の人名ノードの場合だ.結婚リンクを与えて,その結婚リンクに繋がる仮ノードを返すという関数だ.ここで可視化する必要があるのかどうか?という点に関しては疑問がある.
  3. TRIBEBOX::SetPrimeNodeでSetupPrimaryBoxの代わりにsetPrimenodeを呼び出している.SetupPrimaryBoxという関数はすでに廃止されているので,これは確定だ.
  4. TRIBEBOX::GetRealnodeで系列優先実ノードが有効なカードでなかった場合の処理が挿入されている.#elseにはこれを代替するようなコードは含まれていないので,これで確定とするしかない.
  5. Bobject::getvisibleで(hidden() && visible && PHASE >= INITIALIZED)のとき停止するのを抑制している.hiddenかつvisibleという状態はあり得ないとしているのか?無視してよいと見ているのか,意図が不明なので暫定的に検査を復活させておく.

項目2で隠蔽ノードまで無差別に可視化しているところを見ると,この修正ではカードが隠蔽状態にあることと可視であることは無関係と考えているように思われる.実際,隠蔽状態にあるノードが表示されないというのは事実だがそれは可視状態であるか否かということとは無関係だ.従って,項目2も確定と見てよいのではないだろうか?そうすれば,INCOMPLETE@20190131のプラグマは完全に消える.まあ,一応残して様子を見ることにしよう.まず,TEMPORARYのカテゴリに属するオプションを全部止めて動作を確認しておこう.これらは止めた状態がノーマルな状態のはずだから,当然動かなくてはならない.

GetOSDisplayStringで「’GetVersionExA’: が非推奨として宣言されました。」が出る.「GetOSDisplayStringの使用停止@20190109」がTEMPORARYに入っている.⇒これはPENDING というカテゴリを作って移しておこう.

上の1件を除く4件のTEMPORARYオプションを止めて走らせたところ,Bobject::getvisibleで停止した.これは上記INCOMPLETE@20190131の項目5で「意図が不明なので暫定的に検査を復活させておく」とした論理に該当する.これは現行論理で確定でよいと思われるが,気になるのでどんなノードが引っかかっているのかを見ておこう.printnamaeでダンプしようとしたが,この関数からgetvisibleを呼び出しているのでスタックオーバーフローが発生してしまう.printshortなら大丈夫だろう…⇒#279 kakeizu(0)だ.PHASEは12でMAKEUPTREE.系図木を構築しているところだ.Yリストへの繋ぎ込みはこのフェーズで実行されるので,このフェーズでは無視としてみよう.⇒これで一応描画できた.

完全木テストを通してみよう.全体図が通れば親族図は大体通るので全体図テストでもよかったのかもしれないが,サンプルが小さいのでそんなには掛からないだろう…この版は多分完全被参照リスト管理が動いているのでかなり遅い…それでも1分以内に終わった.添付サンプルも問題ないようだ.次にINCOMPLETEのカテゴリのオプションをすべて止めて動作を見ることにしよう.いや,この辺りで一度バックアップを取っておいた方がよいかもしれない…次はINCOMPLETEオプションをすべて止めて動作を見ることにする.

TRIBEBOX::GetAlternativePrimeNodeで停止した.サンプルは渋沢一族8.ZEL.基準ノードは#211 大川平兵衛の息子.このエラーはMakeUpTree→EstablishMajorTribeChainの中で起きているものだ.forループの中で (T2 == this) のとき,(oyalink)で停止する.これにおそらく一番近いのはINCOMPLETE@20190202なのでこのオプションを復活させて走らせてみよう.⇒+INCOMPLETE@20190131で動作

TRIBEBOX::GetRealnodeにかなりひどいバグがあった.これはオプションとはまったく無関係の独立のバグだ.realodeが空のときにrealnode->getrelationを呼び出そうとしている.考えられないミスだ.ここには「実ノードが単親婚保持ノードの配偶者の場合:配偶者の配偶者を実ノードとする」という説明が付いている.こんな初歩的なバグが残っているとは…⇒このバグが発現しなかったということはこのフローを踏まなかったということなので廃棄しておこう.⇒解決

INCOMPLETE@20190131を追加してこの図面は出力できた.渋沢の全体図テストをやってみよう.いや,それどころではない.下のパネルを出した状態でハングしてしまった.

image

再現しなくなった…⇒表示が出るまでに時間が掛かっているのではないか?通常はOKボタンで直ちにパネルを閉じるが,何かのタイミングで閉じるのが遅くなったための現象ではないか?渋沢で描画までに8.5秒掛かっている.PAIRLISTのダンプを出しているためかもしれない…

INCOMPLETE@20190131単独では同じエラーが発現する.INCOMPLETE@20190131と@20190202がセットとなる必要があるので,この2つは確定ということにしておこう.@20190131の修正は例の隠蔽ノード可視に関わるものなので,この論理が動作に不可欠であることが分かる.@20190202は未決が7個もあるので一発で確定でよいのかどうか不安もあるが,一連の修正として一括確定とする.

INCOMPLETE@20190129の修正は2箇所で,一つはASSERT_NEVER(primarynode->getmarglink() != getOyalink())の停止,もう一つは「優先仮ノードが変化しない場合,系列に多重カードが存在する場合は切り替えを許可する」という緩和策を廃止して一律に基準ノード参照系列をリセットするものだが,いずれもオリジナルを復活させておくことにする.(オリジナルの場合,条件が成立するときは停止するようになっている)

INCOMPLETE@20190130は確定でよいと思われる.また,この確定により,IsVisibleCardは使用されないことが確定する.INCOMPLETE@20190204の修正の主眼はEstablishMajorTribeChainの廃止ではないかと思う.その代わりに導入されたのが,SetupPrimaryNodeだろう.EstablishMajorTribeChainの呼び出しを行わないというのはすでに@20190202で導入されていてそれを徹底したのがINCOMPLETE@20190204ということになるのではないか?どの道,その方向に進んでいるのだから徹底した方がよい.

これでINCOMPLETEはすべてフィックスされた.これらのオプションは2019年1月28日から2月4日までの比較的短い期間に集中している.つまり,一貫した一つのテーマの下に進められたものと思われる.

作業開始時点で893個あった#ifdefは873個になった.わずか20個しか減っていないが,後の残りは原則すべて現状でFIXでよいのではないかと思う.日本語で定義されている分だけでも確定してしまいたいのだが,どのくらい時間が掛かるだろうか?⇒意外にそれほどの時間は掛からないものかもしれないが…プラグマが一つのファイルに限定される場合にはヘッダファイルを使わずにCPPだけで始末ができるだろう.そのやり方ならあまり時間は掛からないと思う.

すべての修正をフィックスし「再開発スタート版」のインタクトな開始点を確保する

サブマシンにログインしようとしたらパスワードが間違っています,になった.何度打ち直しても通らない.電源を落として再起動したが,変わらない.仕方がないのでキーボードを外してタブレットでスクリーンキーボードを使って入力したら通った.キーボードをつなぎエディタを起動してテキストを入力してみると,”ai”と入力したつもりが,”a5”となっている.NUMキーがオンになっていた.いつもと違ってキーボードの右上にあるインジケータ3つのうち2つが点灯していることには気付いていたが,文字というより刻印のアイコンが小さくて読めない.ともかくログインできたらネットでマニュアルを探して調べてみようと思っていたが,ログインできないのだからどうすることもできなかった.

NUMキーをオンにするためにはFnキーを押しながらInsキーを押さなくてはならない.そんな込み入ったことを間違ってもやるはずはないと思うのだが,そうなっていたのだからいつの間にかそんなことをしていたのだろう.昨夜は意識はかなりはっきりしていたのでまだまだ作業できそうな気分だったが,思っているよりずっと意識が希薄になっていたのかもしれない.昨日は仕掛りの修正をいくつかFIXしておこうと思ったのだが,何度も惨めな失敗を繰り返している.

クリティカルな修正を行う場合には,まず共通ヘッダファイルで「SIMPLEEDGE_generationを廃止@20201020」のようなリテラルを定義し,それから#ifdef #else #endifで修正前のコードを保存したまま修正する.Visual Studioは偽になっているプラグマのブロックをグレー表示してくれるので,FIXするときにはグレーのブロックを削除すればよいというだけの話だが,#if #endifがネストしているような場合もあり,そういうときにはすべてグレーで塗りつぶされてしまうので見間違えるリスクはかなり高くなる.実際問題として100箇所修正を入れるより,修正点が100箇所ある修正Aをフィックスする方が難しい.

今日はもちろん全天快晴でクリアできるとは思うが,やってみなければ分からない.対策としては失敗に備えて細かくバックアップを取るしかないが,パッケージ全体をバックアップするのではなくDLLのソースとヘッダに限定すればかなり簡単になるだろう.

昨日の目標は「いくつかの修正をFIXする」ことだったが,今日は「すべての修正をFIXする」ところまで拡張しようと思う.このセッションを「再開発スタート版」というテーマで開始した以上,まず,それに相応しい開始ポジションを確保しなくてはならない.#defineで定義された条件コンパイルによる修正の他に,「仮修正」ないし「暫定修正」のコメント付き修正が無数にある.これらを一掃して完全にクリアな状態までソースコードを戻すというのが今日の目標だ.作業に掛かる前の版を別に保存しておくことにしよう.

とは言え,STOP文の実行を一時的に抑制するために入れた暫定修正などもある.⇒これらは一度通常の状態に戻してから改めて逐次止めるなどの措置を取り直すことにする.このような暫定修正が2本ある.暫定修正@20201026と暫定修正@20201028だ.⇒一応現行のサンプルではどこにも停止しない.あとはグレー表示のブロックを一掃するだけだ.ただし,日付だけはコメントに残して置いた方がよい.日付があれば後日ログを逆引きすることも可能だ.いや,これでも結構失敗する.大きいブロックでは修正するたびにコンパイルして失敗したらUNDOで戻るというのしかない.VSはプラグマブロックをーで閉じると「アクティブな/でないプリプロセッサブロック」を表示してくれる.これに従うのが一番確かだ.小さい+ーのチェックボックスを使うので,マウスが滑らかに動かないと作業にならない.⇒大体要領がつかめた.

ようやく調子に乗ってきたと思ったら,

fatal error C1004: 予期せぬ EOF が検出されました。

が出てしまった.UNDOでどこまで戻ればよいか?⇒一つ戻っただけで収まった!つねにプラグマ(ディレクティブ)を3つ削除しなくてはならないのだが,最後に除去する#elseを取り忘れていたのだろう?これでソースファイル1本が終わった.SimpleGraph.cppには元のコードが見えなくなるくらい修正が入っていた.ビルドしてみよう.ビルドも通る.実行も可能だ.⇒if (0)などのコードがある.暫定修正で処分が決まらない場合は,「保留」のリテラルを使ってコードを残しておく.「NOTHING」は無条件削除.Jikusenzu.cppが終わった.いや,終わっていない.Bobject.hにもかなりのコンパイルオプションがある.これらも一旦すべてFIXしてしまうしかないだろう.もし,後日問題が発生したら,「ZELKOVA 2020-11-06 最終版」まで戻って調べ直すしかない.⇒ともかく先にnodule.hのオプションに集中しよう.

ここでバックアップを取っておこう.ZelkovaDLLフォルダの中にはINCLUDEとSRCがあるが,フォルダごとバックアップすることにする.370MB,コピーは数秒で終わる.⇒どうも大失敗をやらかしたような気がする.「SIMPLEGRAPHのattributeを廃止@20201105」を止めたままで作業を開始してしまった.全部一からやり直さなくてはならない.⇒この修正は一旦放棄することにしよう.DrawingObjectを実装するときにもう一度見直せばよい.それほど大きな修正ではない.残っている部分は「保留」にしておけばよい.

「有効結婚リンク判定の重複処理削減@20201031」をFIXして実行時エラーが出てしまった.修正に失敗している可能性が高いが…UNDOでそこまで戻せるかどうか?やってみよう.関係するファイルは,KinshipDegree.cpp,PartialMap.cpp,TableSort.cppの3本だけだ.ファイルを比較してしまった方が早いだろう.⇒余分なことをしていた.FilteringKinshipを実行後でなければ意味がない検査を前方に移動していた.その場の思い付きが命取り…なんでそんなことをやったかって?見た目にその方がかっこいいと思ったから,そばにあった大文字のASSERT_NEVER3つを寄せて並べてみただけ…

「SIMPLEGRAPHのattributeを廃止@20201105」はやっぱり復活させておかないと不都合だ.すでにこの修正を当てにしているところがある…⇒いや,それも少しおかしいのでは…止めた状態でこれまでビルドできていたのだから,どこかが変わってしまったと見るしかない.未定義エラーが出ているところで修正を誤ったのではないか?⇒確かにそのようだ.バックアップから復元した.⇒フルバックアップしておこう.⇒また修正を誤っているようだ.未定義が出てきた.DataList.cpp.DATALIST::cancelでBottomlistを呼び出している.⇒修正した.

nodule.hの分が完了した.17本の条件コンパイル付き修正をFIXした.他にテスト用暫定修正が3件.次はBobject.hで定義されているものを見てみよう.ここではまとめてバックアップしておこう.⇒Bobjectの分が完了した.14件をFIXした.うち4件は廃止.その他に保留としたものが2件.「ファイルへ出力をサポートしない@20180314」とCOLLAPTIBLEMARRIAGE(結婚枠を折り畳む機能をサポートする)だが,よくわからないので保留としておいた.DLLだけバックアップ.

仮修正が41件,暫定修正が35件,そのほかに暫定を含む文字列が26件ある.暫定と仮修正では仮修正の方が一時的なものという度合いが強いので,まずこれからチェックしてみよう.いや,こちらは後回しにした方がよい.仮修正の中にはテスト用に設置してそのまま放置というのもあり得る.暫定修正は一応論理的には意味のある修正と考えられるから,それが安定動作しているという意味で外せるかもしれない.

暫定修正の場合は「暫定」を取って「修正」とし,日付だけ残すことにしよう.削除されている場合は「x行削除」とする.暫定修正で一時的に設置しているものはTEMPORARY@yyyymmdd のように表記することにしよう.⇒暫定の文字列はソリューション全体から一掃された.一時的な動作に関してはすべてTEMPORARYの語を含むようにした.暫定公開,暫定リリースなどは非公式版公開,非公式リリース,応急対処版などとした.現在「TEMPORARY」の出現は28箇所ある.

▲LoadWndPostの修正にあいまいな点があるので確認を要する.

仮修正@20190202が8箇所もある.これは調べる必要がある.修正が閉じていない可能性があるものはINCOMPLETEを付することにする.失敗した.手順を誤って仮修正@20190202をすべて仮修正に変えてしまった.⇒バックアップから復元した.仮修正@20190131も12箇所ある.これもINCOMPLETEに付け替えておく.⇒仮修正はほとんど同じ日付で複数存在する.@20190204が4個,@20190128が3個,@20190130が3個,@20190129が2個.あとはFIXできそうだ.INCOMPLETEは32箇所になった.

  • TEMPORARY 28
  • INCOMPLETE 32

TEMPORARYはあくまで一時的なものなのでそれほど問題性はないが,INCOMPLETE はFIXする必要がある.

「完全被参照リスト管理」の実装を完了した

「完全被参照リスト管理」という仕組みを実装しているところだ.これが完成すれば,込み入った参照関係の網目が完全にアンダーコントロールに入ったことになる.この処理の追加コストが時間的・空間的にどのくらいのものになるのかが興味あるところだが,まだバグが取り切れていないのでもうしばらくかかりそうだ.ファイルを開くまでは順調に動作しているが,系統並び替えを実行するとPAIRLIST::cancelで被参照カウントの残留が発生する.何が残っているかは新設した完全参照リストをダンプするだけで確定できた.DATALISTクラスで参照を発生させないための改修を行っているのでその修正がまだ収束していない.

再現手順は構成図 TEST.zelをUNDOCHAINで開いてgraph3でソートだけだが,#43490 PAIRBOXで被参照カウントの残留が起きる.暫定的にBottomlist(NULL)の行をリスト要素の削除の前に実行するようにして解消した.この修正が正当なものであることはあとから見ることにしよう.引き続き同種のエラーが今度はTRIBELISTのcancelで発生する.ただし,このエラーの原因は明瞭だ.TRIBELIST::cancelの中からDATALIST::cancelを呼び出しているためだ.これをNLIST::cancelないしLIST::cancelとすれば問題は解決する.⇒解消した.PAIRLIST:cancelのループでリスト要素を削除する前にBottomlist(NULL)で要素への参照を解除することは妥当でありかつ必要である.

このサンプルでざっと包括テストを通してみよう.

上記サンプルを#29 PDBでソートして「逆転循環サイクルの枝を破棄」が発生し,SIMPLEEDGE::dumpでlinkにPAIRBOXが入っているために停止した.GENEBOX::CheckInverseCycleでは検査に枝グラフgraph1を使っている.このグラフの節点はPAIRBOXだ.グラフを作成するときにそのグラフのノードの型を設定できるようにしておくとよいのではないか?graph1は逆転循環検査にしか使っていないので名前も明示的なものにした方がよい.⇒InvertCycleGraphと呼ぶことにした.

SIMPLEGRAPHにはattributeという変数を持っているが,モノ/ヒモノの判別以外の用途には使われていないので,この領域に属性ではなくノードクラスのCIDを格納するとよいのではないか?そのグラフが何を扱っているかを特定することは有用だし,CIDが分かればモノ/ヒモノを判別することもできる.モノはCIDを持っているが,ヒモノの場合はたとえば-1などの値を入れればよい.もし,ヒモノもデータタイプごとに識別したいのであれば,負値の定数を与えればよい.⇒改造してみよう.デフォルトはnoduleタイプとし,原則つねにグラフのnew関数ではコンストラクタに引数を与えて呼び出すことにする.⇒この修正は一段落してから実施することにして,ここでは停止しないようにしておこう.

完全木テストを実施してみる.全体図テストと親族図テストを統合したものだ.⇒どうもさすがに描画が遅くなっているような気がする.これではリリース版には採用できないかもしれない.つまり,デバッグ用途にしか使えないということになる.まぁ,それはそれでそれなりの有用性はあると思われるが…現在のサンプルで計測したパフォーマンスは

TIME=0.193 validcardnum=66 validmargnum=24 nodecount=2967 lastsnum=64006  INTRASH=204 REUSE=60835 WASTE=61039 NEWOBJ=64006

のような数値だ.カード66,結婚24で系統並び替えに要する時間は0.2秒,アクティl部なオブジェクトは2967個,ゴミ箱に204個あり,生成されたオブジェクト総数(再利用を含む)は64006だ.これを完全参照リスト管理導入前と比較してみよう.一旦ここでバックアップを取っておく.⇒旧版に戻して実行してみた.

TIME=0.084 validcardnum=66 validmargnum=24 nodecount=580 lastsnum=805  INTRASH=72 REUSE=153 WASTE=225 NEWOBJ=805

この差は隠しようがない.時間効率で2.3倍,空間効率で5倍,ステップ数で80倍の差がある.これだけ大きな差があるとさすがに公開版に適用するのは無理があるのではないだろうか?完全参照リスト管理の有用性は後処理にあるが,最大の後処理はアプリ終了時の一回しか実施されないし,そのときにはユーザはそこでどれほど時間が掛かってもほとんど気にしないだろう.(この後処理は完全参照リストを使ってもっと効率化することはできるが,実効的にはほとんど有意性を認められない)

ロジックが分岐するのはメンテナンス上好ましくない.ロジック的には前者の方が整理されているし,後者は前者の特定ケースとして処理可能なので,前者を採用することでフィックスし,ReferenceControlだけをNOPにすることで対応するということにしよう.残念だが,仕方ない.ReferenceControlを止めると数字は以下のように変わる.

TIME=0.084 validcardnum=66 validmargnum=24 nodecount=580 lastsnum=805  INTRASH=72 REUSE=153 WASTE=225 NEWOBJ=805

導入前と完全に一致している.まぁ,これでよいのではないだろうか?これを現実として受け入れるしかないだろう.ところで,この改修(完全被参照リスト管理の導入)に取り組むことになったきっかけは何だったのだろう?きっかけは「抽象グラフ検証系の複線化」という修正の中で非参照カウントの残留が発生したという辺りのようだ.シューティングのために従来方式の参照リストを使おうとしたら不具合が起きたりしたためだろう.ことのついでに「NODULE,Bobject,DATALISTなどを「抽象クラス」として再定義する」というのをやってみよう.

クラスを抽象クラス化するには仮想関数の少なくとも1個を純仮想関数にする必要がある.まず,DATALISTクラスを見てみよう.ここには仮想関数は一つしかない.この関数は実装されているが,実際には何もしないでゼロを返しているだけなのでこれを純粋仮想関数とするのは容易だ.「DATALISTクラスの抽象クラス化@20201105」で修正してみよう.⇒実装したが,DATALISTのインスタンスを生成しているところがある.さっきまで掛かっていたReferenceControlだ.DATALISTの抽象化を止めるか,新たに派生クラスを作るしかない.

この参照リストを作るためにNODEREFという構造体を新設しているが,その代わりに参照リストクラスというクラスを新設するというのがもっとも正しい.REFLISTというリテラルはすでに使われているので,NODEREFLISTとしてみよう.クラスを追加登録するためには,まず,nodule::getClassNameにCIDとクラス名文字列を登録しなくてはならない.以下のように登録した.

case ‘x’: s = “NODEREFLIST”; break;    // < ‘h’ LIST

同様の内容をnodule.hのコメント欄にも記録する.あとはCOMMONHEADERPARTマクロがすべてやってくれる.LISTにも仮想関数の実装が必要だ.これで使えるようになった.いや,まだある.CleanSansyo,Dispose,dumpの3つの関数だけは実装しなくてはならない.これらはCOMMONHEADERPARTの中で定義されている.NODULEファミリィの共通ヘッダ定義にはCOMMONHEADERSHORTとCOMMONHEADERPARTがあるが,どう違うのだろう?

COMMONHEADERPARTはCOMMONHEADERSHORTに上記3つの関数を追加したものだ.これらを必要としないクラスはCOMMONHEADERSHORTを選択することができる.COMMONHEADERSHORTを使っているクラスには,BASETABLE,NLIST,nlist,ARRAY,PSCLASSがある.クラス定義内で実装する必要がある場合にそうしているのではないか?テンプレートクラスの場合はその方が対処し易い.NODEREFLISTも手抜きしてCOMMONHEADERSHORTを使うことにした.

実行できるようになった.修正を戻した版も実行可能であることを確認しておこう.⇒問題なさそうだ.こんどはBobjectを仮想化してみよう.Bobjectには仮想関数はたくさんあるが,実装を持たない派生クラスも結構ありそうな気がする.Bobject::Drawという関数はどうか?おそらくこの関数はほとんどすべての派生クラスで実装しているはずだ.⇒中には持っていないものもある.たとえばTREEVIEW,というかこれだけだ.逆にRefreshという関数を持っているのはTREEVIEWだけだ.Refreshを廃止してDrawに統一すればよいのではないか?

Refreshを廃止してもコンパイルエラーは出ない.おそらくRefreshを使っているのはTREEVIEWだけだが,TREEVIEW::Refreshはシグネーチャが違うのでエラーにならない.この関数は引数を3つ持っているが,最後のmapmodeをMAP_DISPLAYで定数化したバージョンをTREEVIEW::Drawとしておこう.⇒これでビルドできた.

しかし,動作上の問題が出てきた.TREEVIEW::Refreshはデバイスコンテキストが空で呼び出されることを認めていないが,DrawがRedrawからcdc空で呼び出される場合がある.TREEVIEW::Refreshはあくまで実描画を目的としているので,TREEVIEW::DrawにはBobject::Drawの内容を移しておくことにする.⇒動作するようになった.少なくとも現行ではTREEVIEW::Drawは実描画には使われていない.修正を戻して動作確認する.⇒OKだ.

noduleは実装クラスだが,NODULEは仮想化できるはずだ.適当な仮想関数があるだろうか?仮想関数は山のように持っているのだが…void setpid(void)はどうか?この関数はCOMMONHEADERSHORTで定義されているのでほとんどすべてのクラスで実装されている.NODULEもこの関数の実装を持っているが,これはコンストラクタの中で一度実行されるだけなのでコンストラクタに内容を転記しておけばよいはずだ.

実装した.動作している.setpidは共通ヘッダ部定義で定義されている他はnoduleにあるだけだ.つまり,共通ヘッダ定義を持たないクラスはNODULEとnoduleだけと考えられる.NODULEは別としてnoduleが共通ヘッダ定義を使わないというのはなぜだろう?noduleは共通ヘッダ定義の関数をほとんど自前で持っているように見えるので,なぜ共通ヘッダ部定義を使わないのか理由が分からない.noduleクラスにヘッダ定義を持ち込むとどういうことになるのか調べてみよう.

共通ヘッダ定義と重複する関数を除くと,未定義変数が3つ現れる.pclassid,vsfunctable,VSLOTFUNCだ.また,nodule::_SLOTが非標準の構文とされる.⇒これらの定義がnoduleのクラス定義より後方に置かれていたためだ.DrawTreeView.cppまでコンパイルするとまたnodule.hでエラーが出始めた.

nodule::operator new’: すべてのコントロールのパス、関数を回帰するとランタイム スタック オーバーフローが発生します。

このエラーはDrawTreeView.cppで始めて発生し,それ以外のファイルでは何も起きない.MakeTribeBox.cppでも出た.RestoreTable.cpp,nodule.cppでも出る.⇒最終的にエラーが出るのはDrawTree.cpp,MakeTribeBox.cpp,RecycleSystem.cpp,ZelkovaDLL.cppだけになった.ただし,これらのファイル名がプリントされているということはコンパイル完了とみなされるので,おそらくこれらのファイルのあとにコンパイルしたファイルでエラーが出ているものと思われる.

このエラーの原因は明瞭だが,なぜこのエラーが出るファイルと出ないファイルに分かれるのかは調べておいた方がよい.C++ファイル80本のうちオブジェクトファイルができていないもの26個あった.共通点はよく分からないがBobject.cppを含め,Bobjectクラスのファイルが多いような気がする.⇒※動作から推定すると,*.hの検査と*.cppのコンパイルは別スレッドで同時進行しているように思われる.

void *nodule::operator newとnodule::operator deleteを外した共通ヘッダ部を作ってCOMMONHEADERBASICとし,COMMONHEADERSHORTはこれにこれらの関数を追加したものという構成に変えて,noduleクラスではCOMMONHEADERBASICを展開するようにした.nodulleのoperator newとdeleteをNODULEに移管できない主な理由は,オブジェクトのリサイクルをnoduleが担当しているためだ.リサイクル操作はnewとdeleteの中で実行されるので,このような仕掛けが必要となっている.

これまではnoduleは他のNODULEクラスとはまったく異なる扱いになっていたが,この整理によってoperator newとdeleteを除けば,他のクラスと全く同じ同じ構成を持つNODULEの標準的クラスとして位置付けられるようになった.修正を戻しても動作することを確認した.

SIMPLEGRAPH::attributeを廃止し,グラフのノードから参照するオブジェクトのクラスのCIDを格納するように仕様変更する.CIDが正のときは通常のnoduleクラス,負値の場合は-CIDサイズのデータが格納されるものとする.⇒実装した.

本日4回目のバックアップを取っておこう.これまでの修正をいくつかフィックスしておくことにする.まず,「SIMPLEEDGE_generationを廃止@20201020」と「NODULE:nodegeneを廃止する@20201021」は確定でよいだろう.⇒ダメだ.修正に失敗してしまった.もう一度やり直そう.大きいブロックを消すときは一旦隠蔽してから削除した方が安全だ.前者が18ヶ所,後者が47ヶ所ある.失敗すると痛いのでまた取っておこう.⇒また失敗だ.未解決の外部参照が2つも出てしまった.CARDLINK::getnodegene(void)とCARDLINK:setnodegene(int)だ.今日はもう寝たほうがいいのかもしれない…