You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
7.6 KiB
176 lines
7.6 KiB
(set-cakelisp-option use-c-linkage true)
|
|
;; Because we could be building things without using C linkage, we need to separate all this code
|
|
;; out to prevent oscillating rebuilds
|
|
(add-build-config-label "HotLoadingModifiedCode")
|
|
|
|
(import "DynamicLoader.cake"
|
|
"ComptimeHelpers.cake" "CHelpers.cake")
|
|
(c-import "<unordered_map>" "<vector>" "<string>")
|
|
|
|
(comptime-cond
|
|
('Unix
|
|
(c-import "<sys/stat.h>" "<unistd.h>" &with-decls "<stdbool.h>")))
|
|
|
|
;;
|
|
;; Function management
|
|
;;
|
|
|
|
(def-type-alias FunctionReferenceArray (template (in std vector) (addr (addr void))))
|
|
(def-type-alias FunctionReferenceMap (template (in std unordered_map) (in std string) FunctionReferenceArray))
|
|
(def-type-alias FunctionReferenceMapIterator (in FunctionReferenceMap iterator))
|
|
(def-type-alias FunctionReferenceMapPair (template (in std pair) (const (in std string)) FunctionReferenceArray))
|
|
|
|
(var registered-functions FunctionReferenceMap)
|
|
|
|
(var current-lib DynamicLibHandle null)
|
|
(comptime-cond
|
|
('Unix
|
|
(var hot-reload-lib-path (addr (const char)) "libGeneratedCakelisp.so")
|
|
;; Not used on Unix due to more advanced versioning required to avoid caching issues
|
|
(var hot-reload-active-lib-path (addr (const char)) "libGeneratedCakelisp_Active.so"))
|
|
('Windows
|
|
(var hot-reload-lib-path (addr (const char)) "libGeneratedCakelisp.dll")
|
|
;; Need to copy it so the programmer can modify the other file while we're running this one
|
|
(var hot-reload-active-lib-path (addr (const char)) "libGeneratedCakelisp_Active.dll")))
|
|
|
|
;; This is incremented each time a reload occurs, to avoid caching problems on Linux
|
|
(var hot-reload-lib-version-number int 0)
|
|
|
|
(defun register-function-pointer (function-pointer (addr (addr void))
|
|
function-name (addr (const char)))
|
|
(var findIt FunctionReferenceMapIterator
|
|
(call-on find registered-functions function-name))
|
|
(if (= findIt (call-on end registered-functions))
|
|
(scope
|
|
(var new-function-pointer-array FunctionReferenceArray)
|
|
(call-on push_back new-function-pointer-array function-pointer)
|
|
(set (at function-name registered-functions)
|
|
;; This could also be written as (std::move new-function-pointer-array)
|
|
;; I'm not sure how I want it to work
|
|
(call (in std move) new-function-pointer-array)))
|
|
(call-on push_back (path findIt > second) function-pointer)))
|
|
|
|
(defun-local copy-binary-file-to (srcFilename (addr (const char))
|
|
destFilename (addr (const char)) &return bool)
|
|
;; Note: man 3 fopen says "b" is unnecessary on Linux, but I'll keep it anyways
|
|
(var srcFile (addr FILE) (fopen srcFilename "rb"))
|
|
(var destFile (addr FILE) (fopen destFilename "wb"))
|
|
(when (or (not srcFile) (not destFile))
|
|
(perror "fopen: ")
|
|
(fprintf stderr "error: failed to copy %s to %s\n" srcFilename destFilename)
|
|
(return false))
|
|
|
|
(var buffer (array 4096 char))
|
|
(var totalCopied size_t 0)
|
|
(var numRead size_t (fread buffer (sizeof (at 0 buffer)) (array-size buffer) srcFile))
|
|
(while numRead
|
|
(fwrite buffer (sizeof (at 0 buffer)) numRead destFile)
|
|
(set totalCopied (+ totalCopied numRead))
|
|
(set numRead (fread buffer (sizeof (at 0 buffer)) (array-size buffer) srcFile)))
|
|
|
|
(fclose srcFile)
|
|
(fclose destFile)
|
|
|
|
(return true))
|
|
|
|
(defun do-hot-reload (&return bool)
|
|
(when current-lib
|
|
(dynamic-library-close current-lib))
|
|
|
|
;; Load the library. This requires extra effort for two reasons:
|
|
;; 1) We can't update the library if it's currently loaded, so we must run on a copy
|
|
;; 2) Linux has caching mechanisms which force us to create uniquely-named copies
|
|
(comptime-cond
|
|
('Unix
|
|
;; We need to use a new temporary file each time we do a reload, otherwise mmap/cache issues segfault
|
|
;; See https://bugzilla.redhat.com/show_bug.cgi?id=1327623
|
|
(scope ;; Clean up the old version, so they don't stack up and eat all the disk space
|
|
(var prev-library-name (array 256 char) (array 0))
|
|
(snprintf prev-library-name (sizeof prev-library-name) "libHotReloadingTemp_%d.so"
|
|
hot-reload-lib-version-number)
|
|
(when (!= -1 (access prev-library-name F_OK))
|
|
(remove prev-library-name)))
|
|
|
|
;; Must be a unique filename, else the caching will bite us
|
|
(incr hot-reload-lib-version-number)
|
|
(var temp-library-name (array 256 char) (array 0))
|
|
(snprintf temp-library-name (sizeof temp-library-name) "libHotReloadingTemp_%d.so"
|
|
hot-reload-lib-version-number)
|
|
(unless (copy-binary-file-to hot-reload-lib-path temp-library-name)
|
|
(return false))
|
|
;; False = don't use global scope, otherwise symbols may bind to the old version!
|
|
(set current-lib (dynamic-library-load temp-library-name false)))
|
|
|
|
(true ;; Other platforms don't need the fancy versioning
|
|
(unless (copy-binary-file-to hot-reload-lib-path hot-reload-active-lib-path)
|
|
(return false))
|
|
;; False = don't use global scope, otherwise symbols may bind to the old version!
|
|
(set current-lib (dynamic-library-load hot-reload-active-lib-path false))))
|
|
|
|
(unless current-lib
|
|
(return false))
|
|
|
|
;; Intialize variables
|
|
(var global-initializer (addr void)
|
|
(dynamic-library-get-symbol current-lib "hotReloadInitializeState"))
|
|
(if global-initializer
|
|
(scope
|
|
(def-function-signature global-initializer-signature ())
|
|
(call (type-cast global-initializer global-initializer-signature)))
|
|
(fprintf stderr "warning: global initializer 'hotReloadInitializeState' not found!"))
|
|
|
|
(for-in function-referent-it (ref FunctionReferenceMapPair) registered-functions
|
|
(var loaded-symbol (addr void)
|
|
(dynamic-library-get-symbol current-lib
|
|
(call-on c_str (path function-referent-it . first))))
|
|
(unless loaded-symbol
|
|
(return false))
|
|
;; TODO: What will happen once modules are unloaded? We can't store pointers to their static memory
|
|
(for-in function-pointer (addr (addr void)) (path function-referent-it . second)
|
|
(set (deref function-pointer) loaded-symbol)))
|
|
(return true))
|
|
|
|
(defun hot-reload-clean-up ()
|
|
(dynamic-library-close-all))
|
|
|
|
;;
|
|
;; Data/state management
|
|
;;
|
|
|
|
(def-type-alias StateVariableMap (template (in std unordered_map) (in std string) (addr void)))
|
|
(def-type-alias StateVariableMapIterator (in StateVariableMap iterator))
|
|
|
|
(var registered-state-variables StateVariableMap)
|
|
(var verbose-variables bool false)
|
|
|
|
(defun hot-reload-find-variable (name (addr (const char)) variable-address-out (addr (addr void)) &return bool)
|
|
(var find-it StateVariableMapIterator (call-on find registered-state-variables name))
|
|
(unless (!= find-it (call-on end registered-state-variables))
|
|
(set variable-address-out nullptr)
|
|
(when verbose-variables
|
|
(fprintf stderr "Did not find variable %s\n" name))
|
|
(return false))
|
|
(when verbose-variables
|
|
(fprintf stderr "Found variable %s at %p.\n" name (path find-it > second)))
|
|
(set (deref variable-address-out) (path find-it > second))
|
|
(return true))
|
|
|
|
;; TODO: Free variables. They'll need generated destructors for C++ types (see compile-time vars)
|
|
(defun hot-reload-register-variable (name (addr (const char)) variable-address (addr void))
|
|
(set (at name registered-state-variables) variable-address))
|
|
|
|
;;
|
|
;; Building
|
|
;;
|
|
|
|
(comptime-cond
|
|
;; Did this weird thing because comptime-cond doesn't have (not)
|
|
('No-Hot-Reload-Options) ;; Make sure to not touch environment (they only want headers)
|
|
(true
|
|
(comptime-cond
|
|
('Unix
|
|
;; Search the current directory for hot-reloading library to load
|
|
(add-library-runtime-search-directory ".")
|
|
;; Make sure the thing which gets loaded can access our API
|
|
(add-linker-options "--export-dynamic")
|
|
(add-build-options "-fPIC")))))
|
|
|