fix reloading behavior (closes #64)

pull/84/head
Matthew Butterick 9 years ago
parent c62bdbbe72
commit 61d7b222bd

@ -1,11 +1,11 @@
#lang racket/base #lang racket/base
(require racket/path racket/file file/cache sugar/coerce "project.rkt" "world.rkt" racket/rerequire "debug.rkt") (require racket/file file/cache sugar/coerce "project.rkt" "world.rkt" racket/rerequire)
;; The cache is a hash with paths as keys. ;; The cache is a hash with paths as keys.
;; The cache values are also hashes, with key/value pairs for that path. ;; The cache values are also hashes, with key/value pairs for that path.
(provide reset-cache cached-require paths->key path->hash) (provide reset-cache cached-require paths->key)
(provide (all-from-out racket/rerequire))
(define (get-cache-dir) (define (get-cache-dir)
(build-path (world:current-project-root) (world:current-cache-dir-name))) (build-path (world:current-project-root) (world:current-cache-dir-name)))
@ -18,19 +18,29 @@
(define (paths->key source-path [template-path #f]) (define (paths->key source-path [template-path #f])
;; key is list of file + mod-time pairs ;; key is list of file + mod-time pairs
(define path-strings (map (compose1 ->string ->complete-path) (define path-strings (map (compose1 ->string ->complete-path)
(append (list source-path) (append (list source-path)
(if template-path (list template-path) null) (if template-path (list template-path) null)
(or (get-directory-require-files source-path) null)))) (or (get-directory-require-files source-path) null))))
(map cons path-strings (map file-or-directory-modify-seconds path-strings))) (map cons path-strings (map file-or-directory-modify-seconds path-strings)))
(define (update-directory-requires source-path)
(define directory-require-files (get-directory-require-files source-path))
(and directory-require-files (map dynamic-rerequire directory-require-files))
(void))
(define (path->hash path) (define (path->hash path)
(dynamic-rerequire path) (dynamic-rerequire path)
(hash (world:current-main-export) (dynamic-require path (world:current-main-export)) ;; new namespace forces dynamic-require to re-instantiate 'path'
(world:current-meta-export) (dynamic-require path (world:current-meta-export)))) ;; otherwise it gets cached in current namespace.
(parameterize ([current-namespace (make-base-namespace)])
(hash (world:current-main-export) (dynamic-require path (world:current-main-export))
(world:current-meta-export) (dynamic-require path (world:current-meta-export)))))
(define (cached-require path-string subkey) (define (cached-require path-string subkey)
(define path (with-handlers ([exn:fail? (λ _ (error 'cached-require (format "~a is not a valid path" path-string)))]) (define path (with-handlers ([exn:fail? (λ _ (error 'cached-require (format "~a is not a valid path" path-string)))])
(->complete-path path-string))) (->complete-path path-string)))
@ -40,11 +50,13 @@
(cond (cond
[(world:current-compile-cache-active) [(world:current-compile-cache-active)
(define pickup-file (build-path (get-cache-dir) "pickup.rktd")) (define pickup-file (build-path (get-cache-dir) "pickup.rktd"))
(cache-file pickup-file #:exists-ok? #t (let ([key (paths->key path)])
(paths->key path) (cache-file pickup-file
(get-cache-dir) #:exists-ok? #t
(λ _ (write-to-file (path->hash path) pickup-file #:exists 'replace)) key
#:max-cache-size (world:current-compile-cache-max-size)) (get-cache-dir)
(λ _ (write-to-file (path->hash path) pickup-file #:exists 'replace))
#:max-cache-size (world:current-compile-cache-max-size)))
(hash-ref (file->value pickup-file) subkey)] (hash-ref (file->value pickup-file) subkey)]
[else ; cache inactive [else (parameterize ([current-namespace (make-base-namespace)])
(dynamic-require path subkey)])) (dynamic-require path subkey))]))

@ -6,5 +6,5 @@
(define update-implies '("txexpr" "sugar")) (define update-implies '("txexpr" "sugar"))
(define scribblings '(("scribblings/pollen.scrbl" (multi-page)))) (define scribblings '(("scribblings/pollen.scrbl" (multi-page))))
(define raco-commands '(("pollen" (submod pollen/command raco) "issue Pollen command" #f))) (define raco-commands '(("pollen" (submod pollen/command raco) "issue Pollen command" #f)))
(define compile-omit-paths '("tests" "tools")) (define compile-omit-paths '("test" "tools"))
(define test-omit-paths '("tests/data" "tools")) (define test-omit-paths '("test/data" "tools"))

@ -1,5 +1,5 @@
#lang racket/base #lang racket/base
(require racket/file racket/path racket/match) (require racket/file racket/rerequire racket/path racket/match)
(require sugar/coerce sugar/test sugar/define sugar/container sugar/file sugar/len) (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") (require "file.rkt" "cache.rkt" "world.rkt" "debug.rkt" "pagetree.rkt" "project.rkt" "template.rkt")

@ -11,40 +11,22 @@
The slowest part of a @racket[render] is parsing and decoding the source file. Often, previewing a single source file necessarily means decoding others (for instance templates, or other source files that are linked into the main source file). But usually, only one source file is changing at a time. Therefore, Pollen stores copies of the exports of source files — namely, whatever is stored in @code[(format "~a" world:main-export)] and @code[(format "~a" world:meta-export)] — in the cache so they can be reused. The slowest part of a @racket[render] is parsing and decoding the source file. Often, previewing a single source file necessarily means decoding others (for instance templates, or other source files that are linked into the main source file). But usually, only one source file is changing at a time. Therefore, Pollen stores copies of the exports of source files — namely, whatever is stored in @code[(format "~a" world:main-export)] and @code[(format "~a" world:meta-export)] — in the cache so they can be reused.
@defparam[current-cache hash hash?]{A parameter that refers to the current cache. It is initialized with @racket[make-cache].
The cache is a hash table that uses the complete path of a source file as its keys. The value associated with each of these keys is a subcache — another hash table with keys @racket['doc], @racket['metas] (for storing the exports of the source file) and @racket['mod-time] (for storing the modification time, provided by @racket[file-or-directory-modify-seconds]).}
@defproc[ @defproc[
(cached-require (cached-require
[source-path pathish?] [source-path pathish?]
[key (or/c 'doc 'metas 'mod-time)]) [key (or/c 'doc 'metas)])
(or/c txexpr? hash? integer?)] (or/c txexpr? hash? integer?)]
Similar to @racket[(dynamic-require _source-path _key)], except that it first tries to retrieve the requested value out of @racket[current-cache]. If it's not there, or out of date, @racket[dynamic-require] is used to update the value. Similar to @racket[(dynamic-require _source-path _key)], except that it first tries to retrieve the requested value out of the cache. If it's not there, or out of date, @racket[dynamic-require] is used to update the value.
The only keys supported are @racket['doc], @racket['metas], and @racket['mod-time]. The only keys supported are @racket['doc] and @racket['metas].
If you want the speed benefit of the cache, you should @bold{always} use @racket[cached-require] to get data from Pollen source files. That doesn't mean you can't still use functions like @racket[require], @racket[local-require], and @racket[dynamic-require]. They'll just be slower. If you want the speed benefit of the cache, you should @bold{always} use @racket[cached-require] to get data from Pollen source files. That doesn't mean you can't still use functions like @racket[require], @racket[local-require], and @racket[dynamic-require]. They'll just be slower.
@defproc[
(make-cache)
hash?]
Initializes @racket[current-cache].
@defproc[ @defproc[
(reset-cache) (reset-cache)
void?] void?]
Clears @racket[current-cache]. When only the nuclear option will do. Clears the cache. When only the nuclear option will do.
@defproc[
(cache-ref
[source-path pathish?])
hash?]
Returns the subcache associated with the key @racket[_source-path], which will itself be a hash table. See @racket[current-cache].

@ -0,0 +1,3 @@
#lang racket/base
(provide id)
(define (id) "second")

@ -0,0 +1,2 @@
#lang pollen
(id)

@ -0,0 +1 @@
◊(format "~a" doc)

@ -0,0 +1,42 @@
#lang at-exp racket/base
(require rackunit racket/runtime-path pollen/render racket/file racket/system)
;; define-runtime-path only allowed at top level
(define-runtime-path rerequire-dir "data/rerequire")
(define-runtime-path directory-require.rkt "data/rerequire/directory-require.rkt")
(define-runtime-path pre.txt.pp "data/rerequire/pre.txt.pp")
(define-runtime-path pre.txt "data/rerequire/pre.txt")
(define-runtime-path template.txt "data/rerequire/template.txt")
(define-runtime-path markup.txt.pm "data/rerequire/markup.txt.pm")
(define-runtime-path markup.txt "data/rerequire/markup.txt")
(copy-file markup.txt.pm pre.txt.pp #t)
;; test makes sure that file render changes after directory-require changes
(parameterize ([current-output-port (open-output-string)])
(display-to-file @string-append{#lang racket/base
(provide id)
(define (id) "first")} directory-require.rkt #:exists 'replace)
(render-to-file-if-needed markup.txt.pm)
(check-equal? (file->string markup.txt) "rootfirst")
(render-to-file-if-needed pre.txt.pp)
(check-equal? (file->string pre.txt) "first")
(sleep 1)
(display-to-file @string-append{#lang racket/base
(provide id)
(define (id) "second")} directory-require.rkt #:exists 'replace)
(render-to-file-if-needed markup.txt.pm)
(check-equal? (file->string markup.txt) "rootsecond")
(render-to-file-if-needed pre.txt.pp)
(check-equal? (file->string pre.txt) "second"))
(delete-file pre.txt.pp)
(delete-file pre.txt)
(delete-file markup.txt)
(delete-directory/files (build-path (current-directory) "pollen-cache"))
Loading…
Cancel
Save