From f3eee7b24a9f61ff02febe69dad37c23675ee557 Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Fri, 31 Jul 2015 19:58:12 -0700 Subject: [PATCH] update render cache to use keymaking function from pollen/cache (closes #74) --- cache.rkt | 8 +++--- render.rkt | 71 +++++++++++++++++++++++------------------------------- 2 files changed, 34 insertions(+), 45 deletions(-) diff --git a/cache.rkt b/cache.rkt index 5f4790f..ac85370 100644 --- a/cache.rkt +++ b/cache.rkt @@ -1,10 +1,10 @@ #lang racket/base -(require racket/path racket/function racket/file file/cache sugar/coerce "project.rkt" "world.rkt" racket/rerequire "debug.rkt") +(require racket/path racket/file file/cache sugar/coerce "project.rkt" "world.rkt" racket/rerequire "debug.rkt") ;; The cache is a hash with paths as keys. ;; The cache values are also hashes, with key/value pairs for that path. -(provide reset-cache cached-require path->key path->hash) +(provide reset-cache cached-require paths->key path->hash) (provide (all-from-out racket/rerequire)) (define (get-cache-dir) @@ -15,7 +15,7 @@ (cache-remove #f (get-cache-dir))) -(define (path->key source-path [template-path #f]) +(define (paths->key source-path [template-path #f]) ;; key is list of file + mod-time pairs (define path-strings (map (compose1 ->string ->complete-path) (append (list source-path) @@ -41,7 +41,7 @@ [(world:current-compile-cache-active) (define pickup-file (build-path (get-cache-dir) "pickup.rktd")) (cache-file pickup-file #:exists-ok? #t - (path->key path) + (paths->key path) (get-cache-dir) (λ _ (write-to-file (path->hash path) pickup-file #:exists 'replace)) #:max-cache-size (world:current-compile-cache-max-size)) diff --git a/render.rkt b/render.rkt index cb1e5ef..3270a1b 100644 --- a/render.rkt +++ b/render.rkt @@ -1,21 +1,19 @@ #lang racket/base -(require racket/file racket/rerequire racket/path racket/match) +(require racket/file racket/path racket/match) (require sugar/coerce sugar/test sugar/define sugar/container sugar/file sugar/len) (require "file.rkt" "cache.rkt" "world.rkt" "debug.rkt" "pagetree.rkt" "project.rkt" "template.rkt") +;; used to track renders according to modification dates of component files +(define mod-date-hash (make-hash)) ;; when you want to generate everything fresh, ;; but without having to #:force everything. ;; render functions will always go when no mod-date is found. -(define (reset-modification-dates) - (set! modification-date-hash (make-hash))) +(define (reset-mod-date-hash) + (set! mod-date-hash (make-hash))) -;; mod-dates is a hash that takes lists of paths as keys, -;; and lists of modification times as values. -(define modification-date-hash #f) -(reset-modification-dates) (module-test-internal - (check-pred hash? modification-date-hash)) + (check-pred hash? mod-date-hash)) ;; using internal contracts to provide some extra safety (negligible performance hit) @@ -28,17 +26,13 @@ (and (list? x) (andmap valid-path-arg? x))) -(define/contract (make-mod-dates-key paths) - (valid-path-args? . -> . valid-path-args?) - paths) ; for now, this does nothing; maybe later, it will do more (module-test-internal (require racket/runtime-path) (define-runtime-path sample-dir "test/data/samples") (define samples (parameterize ([current-directory sample-dir]) (map path->complete-path (directory-list ".")))) - (define-values (sample-01 sample-02 sample-03) (apply values samples)) - (check-equal? (make-mod-dates-key samples) samples)) + (define-values (sample-01 sample-02 sample-03) (apply values samples))) (define/contract (path->mod-date-value path) @@ -50,25 +44,17 @@ (check-equal? (path->mod-date-value sample-01) (file-or-directory-modify-seconds sample-01))) -(define/contract (store-render-in-modification-dates . rest-paths) - (() #:rest valid-path-args? . ->* . void?) - (define key (make-mod-dates-key rest-paths)) - (hash-set! modification-date-hash key (map path->mod-date-value key))) +;; each key for mod-date-hash is a list of file / mod-date pairs (using pollen/cache keymaking function) +;; when a file is rendered, a new key is stored in the hash (with trivial value #t) +;; after that, the hash-key-comparision routine intrinsic to hash lookup +;; can be used to test whether a render is obsolete. +;; create a new key with current files. If the key is in the hash, the render has happened. +;; if not, a new render is needed. +(define (update-mod-date-hash source-path template-path) + (hash-set! mod-date-hash (paths->key source-path template-path) #t)) -(module-test-internal - (check-equal? (store-render-in-modification-dates sample-01 sample-02 sample-03) (void)) - (check-true (hash-has-key? modification-date-hash (list sample-01 sample-02 sample-03)))) - - -(define/contract (modification-date-expired? . rest-paths) - (() #:rest valid-path-args? . ->* . boolean?) - (define key (make-mod-dates-key rest-paths)) - (or (not (key . in? . modification-date-hash)) ; no stored mod date - (not (equal? (map path->mod-date-value key) (get modification-date-hash key))))) ; data has changed - -(module-test-internal - (check-true (modification-date-expired? sample-01)) ; because key hasn't been stored - (check-false (apply modification-date-expired? samples))) ; because files weren't changed +(define (mod-date-missing-or-changed? source-path template-path) + (not (hash-has-key? mod-date-hash (paths->key source-path template-path)))) (define (list-of-pathish? x) (and (list? x) (andmap pathish? x))) @@ -79,7 +65,7 @@ ;; Because certain files will pass through multiple times (e.g., templates) ;; And with render, they would be rendered repeatedly. ;; Using reset-modification-dates is sort of like session control. - (reset-modification-dates) + (reset-mod-date-hash) (for-each (λ(x) ((if (pagetree-source? x) render-pagetree render-from-source-or-output-path) x)) xs)) @@ -122,16 +108,17 @@ (begin (message "render: directory require files have changed. Resetting cache & file-modification table") (reset-cache) ; because stored data is obsolete - (reset-modification-dates))) ; because rendered files are obsolete + (reset-mod-date-hash))) ; because rendered files are obsolete requires-changed?) (define/contract (render-needed? source-path template-path output-path) - (complete-path? (or/c #f complete-path?) complete-path? . -> . boolean?) - (or (not (file-exists? output-path)) - (modification-date-expired? source-path template-path) - (and (not (null-source? source-path)) (file-needed-rerequire? source-path)) - (and (world:check-directory-requires-in-render?) (directory-requires-changed? source-path)))) + (complete-path? (or/c #f complete-path?) complete-path? . -> . (or/c #f symbol?)) + (or (and (not (file-exists? output-path)) 'file-missing) + (and (mod-date-missing-or-changed? source-path template-path) 'mod-key-missing-or-changed) + (and (not (null-source? source-path)) (file-needed-rerequire? source-path) 'file-needed-rerequire) + (and (world:check-directory-requires-in-render?) (directory-requires-changed? source-path) 'dir-requires-changed))) + (define/contract+provide (render-to-file-if-needed source-path [template-path #f] [maybe-output-path #f] #:force [force #f]) @@ -160,8 +147,11 @@ [else (error (format "render: no rendering function found for ~a" source-path))])) (message (format "render: ~a" (file-name-from-path source-path))) - (store-render-in-modification-dates source-path template-path) ; todo?: this may need to go after render - (apply render-proc (cons source-path (if template-path (list template-path) null)))) + (define render-result (apply render-proc (cons source-path (if template-path (list template-path) null)))) + ;; wait till last possible moment to store mod dates, because render-proc may also trigger its own subrenders + ;; e.g., of a template. + (update-mod-date-hash source-path template-path) + render-result) (define/contract (render-null-source source-path) @@ -275,7 +265,6 @@ racket/format racket/function racket/port - racket/rerequire racket/list racket/match racket/string