エクスプローラでOpen Live Writerの全文検索ができない

ログの作成・投稿に使っているOpen Live Writerをストアアプリからデスクトップ版に切り替えた.ストアアプリではOpen Live Writerをインストールしたフォルダを見つけることができない.マイクロソフトではストアアプリの方を売り込みたいらしく,Open Live Writerのウェブサイトは閉鎖されてしまっているが,ようやくGitHubにある Open Live Writer の現物を見つけた. EXEのあるフォルダが分からないとファイルの拡張子とアプリケーションをリンクすることができない.また,データファイルのデフォルトアプリが決まっていないと,エクスプローラでコンテンツ検索ができないことになっている.

いままでOpen Live Writerを重用してきたのは,過去記事の全文検索がローカルで可能だったからなので,もしそれができないとなればOpen Live Writerを使うメリットは半減してしまう.デスクトップ版のOLWをインストールして,*.wpostをOLWとリンクし,ファイルのアイコンを叩けばOpen Live Writer が起動するところまではできたのだが,全文検索がどうしてもできない.かなり悩んでいる.Meryなどのエディタには全文検索機能(grep)が付いているが,これは対象ファイルがプレーンテキストでないと動作しないので,まったく使えない.

現行の系列再配置処理はかなり場当たりに作られているので相当冗長な部分があるかもしれない.ファインチューニングすれば倍速くらいまでは向上できそうな感触があるが,しばらくは現状で進むしかないだろう.「純血統図をサポートし上流が養親系だけの場合の下流検定の動作を保証する」というところがいまいち分明でないので,もう少し詰めてみる.これが片付くとnodule.hで預かっているすべてのオプションが片付いたことになり,あとはBobject.hが抱えているDEBUGとVERIFYタグの項目を点検するだけになる.かなり整理されてきた.

オプションPUREBLOODSUPPORTではTOPOLOGY::getkinshipDegreeで以下のようなことを行っている.

  1. 基準ノードの親数をカウントする 
  2. もし,親数がゼロでなければ,先祖ノードを列挙し,先祖ノードを起点に親等計算を行う ただし先祖ノード不在で親数1の場合は基準ノードを起点に親等計算を行う
  3. 親数ゼロの場合は基準ノードを起点に親等計算を行う

PUREBLOODSUPPORTがONのときとOFFのときの違いは,OFFでは(1)を実行していないこと,(2)で先祖ノード不在の場合を見ていないこと,(3)の親数ゼロのケースが存在しないことが挙げられる.しかし,このようなことは純血統図でなくても起こり得ることであり,もしそれが処理できていなければ従来論理は誤っているということになる.オプションをOFFにして動作を確認してみよう.⇒特に問題はないように見える.先祖ノードからの下流検定ループで先祖ノードが空になった場合には必ず基準ノードからの下流検定を実施するようになっているから,漏れが発生するおそれはないように思われる.

この修正が導入された事情を知りたいので過去ログを探してみた.純血統図が導入されたのは2014年の3月頃と思われるが,結構てこずっていたようだ.PUREBLOODSUPPORTに直接該当する記事は見つからなかったが,3月20日の記事で,

「親等計算はつねに全域的に計算し,どこで計算しても同じ値になるべきであると考えられるから,親等計算で養親系を登らないようにするというのは多少問題があるようにも感じる.上記のような検定漏れが生じることを防止するのならむしろTABLESORT::getkinshipDegreeで対処した方がよいのではないかとも思われる.⇒その方向で再修正した.」

とあるのが近いのではないかと思う.「親等計算はつねに全域的に計算し,どこで計算しても同じ値になるべきである」とするのなら,一番確実な計算方法は親族範囲によってカードの有効・無効を決定する前に全体図上で親等計算を実施するというのが解ではないかと思う.親等計算は基準ノードさえ決まればあとは自動的に計算可能だから,系統並び替えを実施する前に実施することができる.この方式なら「親等不一致」は原理的に起こり得ない.しかし,純血統図の場合などはむしろ養親系を除外したときの数字が欲しいのではないだろうか?

親等計算をどこで実施するか?については再考の余地があると思う.PUREBLOODSUPPORTに関しては棄却とするか,ないしDEFECTEDではないだろうか?⇒一旦棄却でフィックスしてしまおう.

そればかりではなく,TOPOLOGY::getkinshipDegreeで先祖ノードが空のとき実行している基準ノードからの親等計算も不要と思われる.実際,モジュール構成図 TEST2の全体図テストではこの論理を完全に止めても問題なく動作している.現行ではsearchForefatherで先祖ノードが空の場合には基準ノードを返しているためと思われる.⇒例外が発生した場合には停止するように仕掛けてしばらく仮修正で運用する.

▲親等計算論理の解析中,ゼルコバの木モジュール構成図 TEST2の全体図テストで TEST2.ZELをノード=21 UndoChainのとき,PAIRBOX::repairCommonEndPointの出口で (CheckSamePoint())により停止した.反例サンプルは作られていない.反例を取れるようにしたが,再現しない.⇒リリース版だからではないか?確かにIDE上では停止する.反例サンプルはBUG20-11-16 16-50-27.ZEL.これは後で見ることにしよう.

DEBUG_NEVERでも反例を出力した方がよい.⇒対処した.⇒ただし,起動するたびに反例サンプルが生成されるというのは流石に過剰だ.パネルを出して,「保存」でファイルを生成というのがよいかも…

おかしい.66点サンプルの全体図テストが6面で終わってしまう.⇒一覧表上で現在選択されているカードから最後までをテストするようになっているようだ.⇒「仕様」ということにしておこう.

Bobject.hに置いてあるDEBGU: とVERIFY: タグのオプションの整理に掛かろう.すでにTRIBEBOXではシフト禁止リストというのを廃止しているのに,NAMEBOXにはSHIFTPROHIBITEDという属性が残っている.これは使われているのだろうか?シフト禁止は主に長い尻尾がオーバーシュートした場合の回数をカウントして上限を超えた場合にはシフト禁止にするなどの動作になっているが,長い尻尾長オーバーなどの事象はほぼ根絶されたものと思われる.ただし,カードシフト操作自体はSHIFTPROHIBITEDのステージ【5.1】絶対世代番号に基づいてカードシフトでShiftDirectAbsoluteとして実行されている.おそらく発現する可能性はゼロと思われるが,とりあえず現行のままとする.

まず最初にデフォルトでONになっている項目をチェックしておきたい.考え方としてはDEBUGはすべてデフォルトOFF,VERIFYはすべてデフォルトONというのがノーマルな状態と思うので,まず,DEBUGにあるデフォルトONから見ることにしよう.たとえば,NOSEGMENTSILENT(セグメント値ゼロでダンプしない)というのがある.この反対は「セグメント値ゼロでダンプ」だが,これならDEBUG: になる.「セグメント値ゼロで停止」ならVERIFYになるだろう.これはやはり正論理に改めておいた方がよいのではないか?

これに関連するブール値として,NODISPNOSEGMENT // セグメント値を持たないノードは描画しないというのがある.しかし,この値はFALSEに固定で実際には作動していない.過去の遺物と言ってもよい代物だが…セグメント値ゼロで描画まで来るということはまずないと考えられるから,この辺りはコードから一掃してもよいのではないか?余分なコードがなくなるとプログラムもかなりメンテし易くなる.NODISPNOSEGMENTとNODISPNOSEGMENT はともに廃止とする.

属性値は大文字で表記することが多いので,属性を返す関数も大文字表記にするとわかり易いのでないか?たとえば,IsElderWifeならISELDERWIFEとする.ただし,このような関数は副作用を持ってはならない.⇒関数型プログラミングに向かう方向だ.

INVESTIGATEVSPLITS(検出した垂直スプリットを画面に表示する)は廃止でよいはずだ.垂直セグメント検定はすでに廃止されているのだから…いや,垂直検定とは別に「全域垂直スプリット検査」というのがある.おそらくこれが作動することはないと思われるが…水平スプリット検査にはINVESTIGATEHSPLITSというのがある.これらはVERIFYでよいのではないか?「残留参照カウントの警告パネルを表示する」も監視という意味ではVERIFYだろう.

PRINTPARAMETER(人名枠にノード属性を表示する)というのはデフォルトでONということもないが,デバッグ時はつねに作動しておくものなのかもしれない.VERIFYに置くデフォルトでONの項目も通常はデバッグ時限定と考えられるので,_DEBUGで囲んでおけばよい.

DRAWTRIBEGENEBOX(世代枠矩形領域を描画する)は現状でフィックスでよいのではないか?系図画面設定パネルで切り替えできるようになっているが,これがOFFでは表示できないし,系列枠にはそのようなものはない.いや,これはオプションかも知れない.ONではGENEBOX::Drawを呼び出すようになっているが,OFFではXORで矩形領域を塗りつぶすようになっている.

世代枠反転色というのを試して,何も出なかったので廃止と決めたのだが,確かに作動はしている.ただし,なにかバグがあるらしく,米粒くらいにしか見えない.⇒これもなにかの役に立つ可能性はあるので温存しておこう.どこに置けばよいか?設定パネルでは「世代枠反転色」とあるので,もともとはこれが仕様だったのだろう.「背景色」としないで「反転色」としたのは,ユーザがバグと間違えないようにというつもりだったのだろう.⇒世代枠が表示されないのは矩形領域に値が入っていないためかもしれない.⇒とりあえず,OPTIONSに移しておこう.系列枠を描画すると上書きされてしまうのは欠陥だ.もし,ONでリリースするのなら,反転色を背景色に改めなくてはならない.

「WriteNonStopSampleListを一時停止@20161026」がデフォルトでONというのも芳しくない.デバッグ用のログ出力などはほとんどフラグを使って操作しているので,その方式に改めた方がよい.⇒WriteNonStopTestLogというフラグを新設した.DEBUG: には現在デフォルトOFFの項目が38個ある.このうちのDEBUG:で始まる11個はすべて廃止してcheck文の中に押し込むことにする.⇒片付いた.

「CheckWasteCountを呼ぶ」というのがある.ゴミ箱の中身をダンプするというものだと思うが,むしろ,呼び出し側ではなく,OFFのときはCheckWasteCountから無動作で戻るという動作の方がすっきりする.「CheckWasteCountを実行する」に改めておこう.

「端点共有ノード対をカラー表示」というのがある.ちょうど今その反例サンプルが手元にあるので試してみよう.⇒BUG20-11-16 16-50-27.ZELを開いてみたが,特に色の付いている線分は見当たらない.GREENないしBLUEで塗ることになっているのだが…解消してしまっているのだろうか?端点共有というのはノード対の属性なので,これがリセットされているということは解消しているということではないか?

「ノード対区間ゼロで端点共有不可」というのと「端点一致でsamepoitゼロ」というのが起きている.ただし,前者はエラーにはなっていない.障害は[ノード対]:#634:#308 MDB(1)→#191 MDB(0)で起きている.MDBは親を3組持っている.topologyとlinktable, namesortだ.

image

左から入ってくるnamesort→MDB(イエロー枠)と直上から下がるlinktable→MDBはわかり易いが,確かに右から入ってくるtopology→MDBを識別するのは難しい.右隣りのPDBも同じように親を3組持っているが,こちらははっきりと識別できる.「端点一致でsamepoitゼロ」というのが何を意味するのかよくわからないが,多少不都合であることだけは確かだ.それにしても,「端点共有属性」をどこで落としているのかが問題だ.⇒これは,後で見ることにする.

「相対座標系から絶対座標系への変換結果をチェックする」というのがある.これは絶対座標系に変換する前にあらかじめTREEVIEWとの原点距離から測定した絶対距離を保存しておいて変換後に比較するというものだ.異同があれば,停止するようになっている.使うチャンスがあるかどうかは別として置いておくでよいだろう.

「選択人名枠の背景色描画でXORを使わない」というのがある.どんなことになるのかやってみよう.⇒これはかなりひどいことになる.一度クリックすると元に戻らない.

image

XORで描画する以外の方法があるのかないのか分からないが,これは現状でフィックスでよいのではないだろうか?あるいはSPECIFICATIONに移動することもできるが…単に矩形枠を塗り潰しているだけなので捨ててもよいと思う.「画面中央不動点をカラー表示する」というのもある.最近この必要性を感じていたところだ.すでに試みていたとは…その割には成功していないような感じもするが…

「HasMultiOccurrence:隠蔽カードを検出する」というのは,前にも出てきていたような気がするが…⇒いや,これはどこからも参照されていない.すでに廃止されていると思う.

DISREGARDLIMITSIZEOVER(描画矩形領域オーバーフローを無視する)これは仕様的にまずいと思う.オーバーフローを無視するにしても対処するにしても,オーバーフローが発生していることを検知しなければ始まらない.現状では「描画矩形領域オーバーフローを無視する」ONにすると検査自体を実行しないことになってしまう.

これは表現を変えて,「描画矩形領域サイズオーバーを監視する」CHECKDRAWSIZEOVERとしてデフォルトONでSPECIFICATIONに移すことにする.⇒これでDUMP: タグの中はすべてデフォルトOFFになった.23件になった.あとはVERIFYだけだ.こちらは,デフォルトOFFから見てゆくことにしよう.

PRINTABNORMALGENEGAPはcheck文で代替できる.

TOTALCONFLICTION,ALLOWMARGGAP,UPSTREAMSYMMETRY,SYMMETRICPRIMEMOVE,DONTAPPLYLOCALBEND,OYASANSYOPATH,CHECKCROSSINGJOINT,CHECKSYMMETRICPOINT,LOCALBENDCONSTRAINT,SELECTOLDEST,CHECKTRIBECOMPLETE,SINGLESPECIALLINE,INITILIZEPAPERSIZE,_DEBUG_VACUUM,ISALIGNTIGHT,NONRIGIDSYMMETRY,LINEAGE_PARTIALTREE,MOVELOWERBLOCK

は参照されていないので廃止.これらを除くとDEBUG: から降りてきたのを除いて,VERIFYに残ったのはデフォルトONが7件,OFFが3件になった.計10件になった.いや,廃止になるものがまだ残っていた.AVAILABLECHANNEL,MOVENEUTRAL,COMPLETETREE.

「写真がゴミ箱の中にあれば停止」というのがある.どういうことだろう?カードの中にあるbitmapinfo,cBitmap,notepageは任意サイズのオブジェクトだが,これらがゴミ箱には入らないというのはどういう理由なのか?確かに写真イメージなどはアプリ終了時にまとめて始末していたような気もする…UNDOでバックアップするときに複製を取るのはコストが掛かり過ぎるという理由だろうか?20180215という日付があるので,ログを調べてみよう.タイトルは「写真イメージ貼り込み・削除のUNDOをサポートする」となっているので,やはりその辺りが理由になっているようだ.

freeblockで取得したメモリはすべてアプリ終了時まで保持されている.これは他のオブジェクトでも同じだが,delete xxx を実行すると,オブジェクトxxxはゴミ箱に入る.写真や記録ページはカードに付帯しているものだが,明示的に delete されていないので,そのままfreeblockにキープされた状態になっている.「画像を貼り付けてもUndoで消えてしまう」という問題が発生したために,もしかしてゴミ箱の中ではないかと考えてこのトラップを仕掛けたのだろう.実際には入っていなかったので外してもよいのではないかと思う.

オブジェクトがゴミ箱の中に入っているか否かをチェックする関数IsInTrashCanはあるが,オブジェクトのメモリ上のアドレスからfreeblockの位置を取得する手続きを実行している例はこれ以外にない.そういう関数を一つ作っておいてもよいと思うが,小さ過ぎて見つけるのは結構難しいかもしれない.⇒そこまでやる必要もないだろう._getblock_という関数は別のところでも使われている.⇒廃止でよいと思う.INITIALIZEPRINTERも廃止でよい.このプラグマでは何もしていない.InitializePrinter自体は複数の別の位置で実行されている.

SORTLOOSEBOX(ルーズな結婚枠配列をソートする)は廃止してもよいだろう.現在はアクティブではないし,MAXIMALGRAPH:SortLooseBoxも使われていない.この関数はルーズな兄弟枠を世代順に並び替えるもので,コンパクトなレイアウトを作り出すための効果的な手続きを案出しようとする試みだが,あまり成功したとは言えない.この関数自体廃棄でよいと思う.INTERSECTRECT(BRect:InterSectRect関数で接触を交差に含める)とあるが,現状では「接触を交差に含めない」動作になっている.この関数は広範に使用されているので,現状でフィックス以外の選択はない.

HasMultiOccurrence:隠蔽カードを検出しない@20190131は検出しないとしながら,実際はhiddenで停止するようになっている.有効で可視のカードが隠蔽されているというのはやはりおかしいので停止でよいのではないだろうか?⇒DEBUG_NEVERで置き換えることにしよう.CHECKRINGCOUNTはデフォルトOFFなのでDEBUGに移動する.SYMMETRICMOVELOOP(HeapSymmetryBoxでCheckMargPointループを実行する)はすでに2016-11-14に 廃止が決定している.

終わった!VERIFYにはデフォルトONが5個残った.まだ少し残っている.DEBUG: の方はすでに参照されていない項目のチェックをやっていない.PRINTEMPTYPAGE,PRINTPHASENAME,はすでに参照されていないので廃止.これですべてだ.DEBUG: はすべてデフォルトOFFで22件,VERIFY: がすべてONで5件となった.OPTIONS: はすべてデフォルトOFFで9件,SPECIFICATION: がすべてONで15件だ.いや,OPTIONSにDEBUG: のタグでデフォルトONというのが混じっている.

DRAWTRIBEGENEBOXだ.裏面の反転表示は動作不良になっている.デフォルトONというのは規定ではSPECIFICAITONかVERIFYになるところなのだが… 動作的に考えるとSPECIFICATIONに置くしかないような気がする.裏面を補修して元の仕様に戻すことも考えられるが,なぜ現在の仕様になっているのかも考えなくてはならない.あえて分類すれば,INCOMPLETEになってしまうのだが…(一応現状で動いているし)それほど重要な項目でもない…⇒SPECIFICATIONに入れておいた.ONでなければ動かないという条件があるのでここに置くしかない.

「直属系列落ち」というオプション

ゼルコバの木では本人(基準ノード)の(もっとも遠い)先祖の直系血族とその配偶者の集合を標準家系図と呼んでいる.系図全体をこのような標準家系図(系列)の合成とみなすというのがゼルコバの木の基本的な立ち位置だ.ある先祖ノードの標準家系図は系列であり,先祖ノードの数だけ系列が存在する.この意味で系列の位置関係を決定することがゼルコバの木の課題であると言える.これを系列再配置問題と呼ぶ.

各系列において標準家系図の基準ノードに相当する人名を系列優先ノードとする.人名は複数の先祖を持つ場合があり,また他系列のノードと婚姻関係を持つことがあるので,同じ人名が複数の系列の中に登場する場合がある.もし二つの系列の間にこのような重複する人名が存在しないとすれば,その二つの系列には何のつながりもない.

通常は全体図に含まれるほとんどの系列は何らかの繋がりを少なくとも他の系列に一つは持っている.始系列(基準ノードの標準家系図)を除く(孤立していない)すべての系列の優先ノードはこのような重複人名カードの中から選択される.このような重複するカード(多重カード)を図面上から消去して多重ゼロの状態にすることが系図作成の目標であり,系図の品質を評価する最大の基準である.

系列優先ノードは複数の系列と関係を持っている可能性はあるが,それらのうち始系列にもっとも近いものを選択してその系列の主系列とし,その系列を(主系列の)従系列とする.系列を節点とし,系列の主従関係を枝とするグラフが木になることは容易に示すことができる.(ただしここではグラフは連結であることを仮定する)

系列再配置の基本は系列を主系列の近傍に配置すること,主系列に出現する系列優先実ノードと従系列に出現する仮ノードの世代を一致させることの2点だが,それだけでは多重を解消することはできない.場合によっては優先ノードを切り替える必要が生じることもある.

多重カードを解消するための配置を決定するという問題はとても難しく,当初はいわゆる日本的職人芸(Japanese craftmanship)で解決しようとしていたのだが,どうしても埒が明かないということが分かったので,方向転換し,もう少し「数学的」に考えることにした.

その結果,①オブジェクトの移動には垂直と水平の2方向があり,これらは独立に操作できること(なぜなら「直交」しているから),②多重に関係するのは垂直方向の操作だけであることが分かった.さらに,比較的最近になってから③重婚同類グラフが循環することが多重の原因であり,重婚同類循環が存在する場合にはどんなことをしても多重を解消できないということが明らかになった.

重婚同類グラフ検定はグラフの静的な検査なのでメインループに入る前の予備検定でストレートに計算を完了することができる.従って,系列再配置は水平方向の調整を行うだけのものになった.水平方向の移動では2つのことが起きる.一つは系列間の衝突であり,もう一つは乖離だ.

このような問題の最適解を得る方法は知られていないので,基本的には漸近法を適用して解を求めるしかない.これを行っているのが,ApproximationとSolveBondedTribeCollisionという2つの関数だ.前者は系列間の乖離の解消を担当し,後者は衝突の回避を図るものだが,両者は互いに競合するので最終的に静定するまでループする.

さて,今日は「直属系列落ち」とは何か?というところから始めなくてはならない.一方では「ここでは直属系列落ちを許容する」がONとなっているのに,他方では「直属系列落ちを無視する」がOFFという矛盾した状態になっている.そもそも「直属系列」とは何か?これは昨日調べたように,①HASNOMINORであるか,ないし②BOTTOMEMPTYでかつ,HASNOMINOR以外の下位系列を持たないような系列と定義される.ここで,HASNOMINORとは「始系列を除き下位系列を持たない系列」,BOTTOMEMPTYとは「最下段世代枠が空で最下段世代に消去された系列優先仮ノードが存在する系列」である.

つまり,主系列にごく近い直参旗本のような系列と考えてよい.従って,これらの系列は主系列の近傍に配置されるべきというのが「直属系列」の趣旨であるように思われる.一方BONDEDTRIBE(束縛系列)という概念がある.これは「すべての下位系列と交叉しかつ上位系列と交叉している系列」とされるので,おそらく主系列(および自系列の下流系)との位置関係が確定した系列と考えてよいだろう.ただし,IsBondedTribeでは(BONDEDTRIBE | MAGARIHOSEI | SPOUSESENZO)を返しているので,もう少し広い概念だ.束縛系列の反対概念が自由系列で IsFreeTribeBox は !IsBondedTribeに等しい.

何が問題なのか?それが問題だ.現状では「直属系列落ちを無視する@20181001」はOFFなので,!IsBondedTribeのとき,IsDirectSubTribeを検査している.無視と言いながら検査しているところもおかしいが,!IsBondedTribeのときIsDirectSubTribeとは何を意味しているのかを調べてみよう.この条件を書き下ろすと次のようになる.

!(BONDEDTRIBE | MAGARIHOSEI | SPOUSESENZO) && (MAGARIHOSEI | BOTTOMEMPTY | SPOUSESENZO | HASNOMINOR)

これは結局,

(!BONDEDTRIBE & !MAGARIHOSEI & !SPOUSESENZO) & (MAGARIHOSEI | BOTTOMEMPTY | SPOUSESENZO | HASNOMINOR)

だから,MAGARIHOSEI とSPOUSESENZO の項は消えて,

!BONDEDTRIBE & (BOTTOMEMPTY | HASNOMINOR)

のようになると考えられる.(BOTTOMEMPTY | HASNOMINOR)は直属系列の定義にやや近いので,これは(広義の)直属系列でかつ(狭義の)束縛系列でないものと読める.しかし,にも関わらずここでは停止していないので,一体何がこのパスを通過しているのか分からなくなってしまう.⇒(事実上)何も通っていない.SolveBondedTribeCollisionでは冒頭でCheckAllBondedTribeを実行した後,エラーなしでゼロ復帰している.つまり,SolveBondedTribeCollisionはCheckAllBondedTribeを実行するだけのものになっている.

実際,CheckAllBondedTribeの中では自由系列がなくなるまでループを回している.ただし,自由系列が存在した場合には戻り値を返すので最初から自由系列は存在していなかったということになる…つまり,CheckAllBondedTribeが正しく動作することが保証されているのなら,SolveBondedTribeCollisionの後段は不要になるはずだ.さらに言えば,もしかすると,衝突検定を実施しているSolveTribeCollisionさえ不要になっている可能性がある.⇒いや,むしろ逆にSolveTribeCollisionの中からApproximationが呼び出されている.SolveTribeCollisionは自身を再帰実行しているので,この関数一つあればこと足りるのではないか?

どうもCheckAllBondedTribeは何もしていないように思われる.もし,それが本当ならおそらくSolveBondedTribeCollisionも不要になるはずだ.どうもその気配が濃厚になってきた.この処理を完全に止めたバージョンを作ってみよう.⇒とりあえず,問題なく動作している.20180717に「SolveBondedTribeCollisionを実行関数化する」という修正を行っている.この頃のログは残っているだろうか?「テント村」は2018年12月に開設されているのでそれよりも前の話だ.My Weblog Postsのバックアップでも2016-02-08から2018-12-14の間は空白になっている.確かにこれは正確にネットが止まっていた期間だ.

通常ブログが使えないときでも,自己宛てにメールを送ってログ代わりにしていたはずなのだが,メールサーバにアクセスできなければ,それもできない… それ以前からWindows Live Writerを使っているのだから,オフラインで下書きに保存くらいのことはできたのではないか?確かにその通りだ.下書きに残っている.2018-07-17のタイトルは「AccidentalCollisionを三検査関数で書き換える」だ.

「CheckHorizontalOrderを三検査関数で書き換えるというのはやはり無理があるとしても,AccidentalCollisionを三検査関数で書き換えることは不可能ではないと思われる.試してみることにしよう.」

三検査関数というのは,CheckAllBondedTribe,CheckAllCollision,DetectSplitだ.これらは検査用関数だが,それを実行関数に仕立てるということをやっている.「すべての束縛系列がブロック内で衝突していなければ,あとは自由系列ブロックの水平移動だけ」という立場から衝突検定をCheckBondedTribeCollisionに限定するという方針で,ある程度時間を短縮するという効果が出ている.「SolveBondedTribeCollisionを実行関数化する」というのはその一環として実施されたものだろう.

この頃は先祖並び自動オフというところに焦点があったようで,現在テストしているサンプルは先祖並び自動オンだから,これで動いたから安心とは言えない…現在のバージョンにはCheckBondedTribeCollisionという関数は残っていない.多分この関数をリネームしたものがSolveBondedTribeCollisionなのだろう.それをいま削除しようとしているのだが…おそらく,このときの修正は検査関数をそのままの位置で実行関数に格上げするようなやり方だったのではないだろうか?つまり,オリジナルの処理で漏れてしまうところを検査関数をある意味で強化することによって解決したということなのではないか?

「Approximationを廃止する@20180712」というコメントは残っているが,実際には存続している.SolveBondedTribeCollisionを停止すると今度はAccidentalCollision自身がほとんど何もしていないことになってしまう.確かに,この関数を止めても問題なく動いている.ことのついでにこの関数も廃止してみよう.⇒少し時間が掛かるが,渋沢の全体図テストをやってみることにしよう.⇒出口検査のCheckAllCollisionで停止した.さすがにこれは無理かもしれない.スプリットも発生している.AccidentalCollisionを復活させてSolveBondedTribeCollisionだけ停止というモードでテストしてみたが,やはり同じところで停止する.

基準ノード=214 穂積重樹だ.⇒SolveBondedTribeCollisionを復活させ,CheckAllBondedTribeだけを実行して復帰するように改造して同じエラーになった.つまり,SolveBondedTribeCollisionの後段は必要ということになる.反例は#214 穂積重樹だ.いや,その前にAccidentalCollisionが無動作になっていた.

SolveBondedTribeCollisionでCheckAllBondedTribeだけを実行するというバージョンを走らせて,基準ノード=300 三島美禰で停止した.CheckAllBondedTribeで初めて変異が発生した.基準ノード=302 堀切良平などでも同様の動作になるが,最終的には解決しているようだ.CheckAllBondedTribeで衝突はすべて解決しているのでSolveBondedTribeCollision後段の処理はまったく不要であるようにも思えるが,現状のままとしてバックアップに戻ることにしよう.

現在のインストール版V2.2.0.015は源氏を開いてエラーを出すので差し替えておこう.⇒V2.2.0.017_R20201115をリリースした.

「直属系列落ちを無視する@20181001」と「ここでは直属系列落ちを許容する@20181007」をとりあえずフィックスしておこう.SolveBondedTribeCollisionの去就についてはパフォーマンスの問題として別途取り組むこととする.「SELECTFOREFATHER」は先祖ノードのどちらが遠いかを判定する関数の仕様に関するもので,世代を比較するものと親等を比較するものがある.現行は世代比較になっているが,これを仕様としてSPECIFICATIONに移動.「WriteNonStopSampleListを一時停止@20161026」はファイルオープンテスト中にログをファイルに書き込むための関数.デバッグ用途なのでDEBUBタグに移動.

「衝突検定で計算誤差を許容する@20180930」→現状でフィックス.このプラグマではMAXKEISANGOSA という定数を使っている.現在,MAXKEISANGOSAは75箇所から参照されているが,一方で「厳密値を適用する」ないし「厳密値を返す」などとしているところが18箇所ある.厳密値を返すというところはおそらく,上位関数で判定しているためだろう.ただし,許容範囲に等号を含むか否かではぶれがある.

「NAMEBOX:Drawへ移動@20190131」→現状でフィックス.「MakeYokogakiBoxをパス@20180328」→現状でフィックス.MakeYokogakiBoxは横書きタイトルを描画する際の矩形領域を計算する関数で,タイトル枠には,①タイトル行,②作成者名,③作成日付,④コメントの4つのテキストボックスがあるが,コメント枠だけは別計算しているためMakeYokogakiBoxで計算しないという趣旨だ.

(配偶者の性別不問とした@20180201)→このオプションには問題がある.このプラグマは以下のようなコードを無効化している.

// 新規作成した父母の個人情報で性別情報が設定されていない場合があるので,ここで補充しておく.
if (Father) Father->sex = true;
if (Mother) Mother->sex = false;

前に延べた「親子関係を設定」しただけで女性が父になってしまうという現象はおそらく,これが原因になっていたものと思われる.この部分の論理はもう少し詳しく調べなくてはならない.ここでは,暫定的にOPTIONのタグに移動してデフォルトOFFとしておく.これでINCOMPLETEタグは一掃できたが,PENDINGに出戻ってきた項目が一つある.PUREBLOODSUPPORT(純血統図をサポートし上流が養親系だけの場合の下流検定の動作を保証する)というものだ.多分これは動作上必要な措置と考えられるので現状でFIXということになると思われるが,純血統図に関してはもう少し考えてみたい.

純血統図(親族図)では養親系を表示しないようにするということが考えられるからだ.現行では直系血族図を表示したとき,養親系も同時に表示されることになるが,純血統図とすると養親系を除外した血族図になるというイメージなのだが…⇒いや,現状でもそうなっているのではないか?実際,システム構成図はほとんど養親子関係しか存在しないため,純血統図を選ぶと基準カードだけになってしまう.つまり,動作している.何か勘違いしているか,あるいは別の問題があるのか?

いや,そもそもこのオプションは本当に必要なのか?システム構成図で見た限りではOFFでも特に問題なく動作しているように見える.

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

メインループは当初(と言ってもかなり後になってからのことだが)タイムアウトでブレークするようになっていた.描画要素のレイアウト計算が無限ループに陥ってないし発散して停止しなくなる場合に備えての措置だ.その後はデバッグモードではループ回数オーバーでブレークするというオプションが導入されたが,リリース版では以前としてタイムオーバーを監視している.しかし,実際の動作ではつねにループ回数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だけで始末ができるだろう.そのやり方ならあまり時間は掛からないと思う.