tr 的快取機制 [tr-G7G0]

如果原始檔案沒有變化,我們當然希望盡可能不要重新建構它,原因大致上都是因為建構是需要時間的,比如解譯一次 scribble 檔案然後生成 html 需要不少時間、typst 產生圖片需要不少時間等等。從很早期 tr 就有用很單純的方法判斷原始碼有沒有變化過:metadata 的變更時間

從 PR #53 開始 tr 就改從 content-addressed build store 機制判斷了。這套機制的想法是這樣:卡片 render 的結果會依賴一些輸入,用這些輸入計算出一個 sha1 signature,這就是 store 的 key

每次建構之前會用這個 key 判斷 store 中有沒有快取存在,如果有命中就把快取好的產物複製到輸出目錄,不然就表示需要真的建構一次(不管是因為快取被清理掉了還是因為原始碼更新了),並且把內容儲存進 store

signature 在 transclude 的拓樸序下排列(被 transcluded 的卡片會先被處理):

  1. 卡片自己的 source code:原始 .scrbl 檔案的 bytes,加上它每個 @include 進來的檔案的 bytes。所以即使 .scrbl 沒動,Agda html 之類的外部檔案一有變化時這張卡也會失效。不需要另外用 Makefile 之類的工具清理快取

  2. 卡片自己算出來的 metadata(已經帶有傳播過來的 backlinks / context / related)。

  3. 每個transclude卡片的完整 signature。所以改 transclude 卡片會讓每個嵌入它的 parent 卡片重新簽名;但反過來,改 parent 卡片不會影響 transclude 卡片

  4. 每個被引用的 neighborhood 的 title 跟 taxon(不管是來自 context/references/backlinks/related/authors),這是一張卡片的 neighborhoods 唯一會 render 的欄位。所以改一張卡片的標題,會重建那些在 Related 區塊 mention 它的卡片;但改卡片的內文則不會影響 neighborhoods

  5. 一個render-config-tag:設定檔裡的 head 欄位會影響render的內容。兩個render出來bytes會不同的output那他們的target就會有不同的signature,不會複製到對方的輸出

store 下來的內容放在 _tmp/cache/store/<sig>/,儲存:render 好的 embed.html 跟 out/(編譯好的 latex / typst svg)。index.html 不快取,因為這個計算很快

  1. deploy可以從同一份 _tmp 建立兩棵樹:先執行dev建構再執行release建構的話,只要內容跟config有影響內容的欄位相同,signature就相同,於是第二個target只需要從store複製,不用重複建構

  2. 把卡片revert回去還是會有快取命中,因為較早的signature對應的entry還在硬碟上(這在外部工具產出的一些東西不在signature計算中時確實偶爾會是問題,但不嚴重)

store 雖然保證了不同建構 target 時的正確性,但不知道某個特定 target 是由哪個 signature 產生,所以每個 output 目錄會留一個戳記 <output>/<addr>/.sig,記下它的 index.html 是哪個 signature 產生的。當戳記吻合、index.html 存在、且每個被快取的 output 檔都還在時,per-target 的工作(svg 加 index.html)就可以被跳過,只把 embed 刷新回 _tmp 供 transclude 它的父卡讀取。被刪掉或被竄改的 output 檔,即使戳記吻合,下一次 build 也會從 store 重新產生