From 2d606e38eae75cab22e6ebf02abe96be36ecd52a Mon Sep 17 00:00:00 2001 From: Araq Date: Tue, 18 Jun 2019 12:17:06 +0200 Subject: [PATCH] HCR: better documentation (cherry picked from commit 38f05bb9a32ea89a2724448fc6c843ec7c0485bd) --- doc/hcr.rst | 216 +++++++++++++++++++++++++++++++++++++++++++++ doc/nimc.rst | 88 ------------------ tools/kochdocs.nim | 1 + 3 files changed, 217 insertions(+), 88 deletions(-) create mode 100644 doc/hcr.rst diff --git a/doc/hcr.rst b/doc/hcr.rst new file mode 100644 index 0000000000..e04fce6188 --- /dev/null +++ b/doc/hcr.rst @@ -0,0 +1,216 @@ +=================================== + Hot code reloading +=================================== + +The `hotCodeReloading`:idx: option enables special compilation mode where +changes in the code can be applied automatically to a running program. +The code reloading happens at the granularity of an individual module. +When a module is reloaded, any newly added global variables will be +initialized, but all other top-level code appearing in the module won't +be re-executed and the state of all existing global variables will be +preserved. + + +Basic workflow +============== + +Currently hot code reloading does not work for the main module itself, +so we have to use a helper module where the major logic we want to change +during development resides. + +In this example we use SDL2 to create a window and we reload the logic +code when ``F9`` is pressed. The important lines are marked with ``#***``. +To install SDL2 you can use ``nimble install sdl2``. + + +.. code-block:: nim + + # logic.nim + import sdl2/sdl + + #*** import the hotcodereloading stdlib module *** + import hotcodereloading + + var runGame*: bool = true + var window: Window + var renderer: Renderer + + proc init*() = + discard sdl.init(INIT_EVERYTHING) + window = createWindow("testing", WINDOWPOS_UNDEFINED.cint, WINDOWPOS_UNDEFINED.cint, 640, 480, 0'u32) + assert(window != nil, $sdl.getError()) + renderer = createRenderer(window, -1, RENDERER_SOFTWARE) + assert(renderer != nil, $sdl.getError()) + + proc destroy*() = + destroyRenderer(renderer) + destroyWindow(window) + + var posX = 1 + var posY = 0 + var dX = 1 + var dY = 1 + + proc update*() = + for evt in events(): + if evt.kind == QUIT: + runGame = false + break + if evt.kind == KEY_DOWN: + if evt.key.keysym.scancode == SCANCODE_ESCAPE: runGame = false + elif evt.key.keysym.scancode == SCANCODE_F9: + #*** reload this logic.nim module on the F9 keypress *** + performCodeReload() + + # draw a bouncing rectangle: + posX += dX + posY += dY + + if posX >= 640: dX = -2 + if posX <= 0: dX = +2 + if posY >= 480: dY = -2 + if posY <= 0: dY = +2 + + discard renderer.setRenderDrawColor(0, 0, 255, 255) + discard renderer.renderClear() + discard renderer.setRenderDrawColor(255, 128, 128, 0) + + var rect: Rect(x: posX - 25, y: posY - 25, w: 50, h: 50) + discard renderer.renderFillRect(rect.addr) + delay(16) + renderer.renderPresent() + + +.. code-block:: nim + + # mymain.nim + import logic + + proc main() = + init() + while runGame: + update() + destroy() + + main() + + +Compile this example via:: + + nim c --hotcodereloading:on mymain.nim + +Now start the program and KEEP it running! + +:: + + # Unix: + mymain & + # or Windows (click on the .exe) + mymain.exe + # edit + +For example, change the line:: + + discard renderer.setRenderDrawColor(255, 128, 128, 0) + +into:: + + discard renderer.setRenderDrawColor(255, 255, 128, 0) + +(This will change the color of the rectangle.) + +Then recompile the project, but do not restart or quit the mymain.exe program! +:: + + nim c --hotcodereloading:on mymain.nim + +Now give the ``mymain`` SDL window the focus, press F9 and watch the +updated version of the program. + + + +Reloading API +============= + +One can use the special event handlers ``beforeCodeReload`` and +``afterCodeReload`` to reset the state of a particular variable or to force +the execution of certain statements: + +.. code-block:: Nim + var + settings = initTable[string, string]() + lastReload: Time + + for k, v in loadSettings(): + settings[k] = v + + initProgram() + + afterCodeReload: + lastReload = now() + resetProgramState() + +On each code reload, Nim will first execute all `beforeCodeReload`:idx: +handlers registered in the previous version of the program and then all +`afterCodeReload`:idx: handlers appearing in the newly loaded code. Please note +that any handlers appearing in modules that weren't reloaded will also be +executed. To prevent this behavior, one can guard the code with the +`hasModuleChanged()`:idx: API: + +.. code-block:: Nim + import mydb + + var myCache = initTable[Key, Value]() + + afterCodeReload: + if hasModuleChanged(mydb): + resetCache(myCache) + +The hot code reloading is based on dynamic library hot swapping in the native +targets and direct manipulation of the global namespace in the JavaScript +target. The Nim compiler does not specify the mechanism for detecting the +conditions when the code must be reloaded. Instead, the program code is +expected to call `performCodeReload()`:idx: every time it wishes to reload +its code. + +It's expected that most projects will implement the reloading with a suitable +build-system triggered IPC notification mechanism, but a polling solution is +also possible through the provided `hasAnyModuleChanged()`:idx: API. + +In order to access ``beforeCodeReload``, ``afterCodeReload``, ``hasModuleChanged`` +or ``hasAnyModuleChanged`` one must import the `hotcodereloading`:idx: module. + + +Native code targets +=================== + +Native projects using the hot code reloading option will be implicitly +compiled with the `-d:useNimRtl` option and they will depend on both +the ``nimrtl`` library and the ``nimhcr`` library which implements the +hot code reloading run-time. + +All modules of the project will be compiled to separate dynamic link +libraries placed in the ``nimcache`` directory. Please note that during +the execution of the program, the hot code reloading run-time will load +only copies of these libraries in order to not interfere with any newly +issued build commands. + +The main module of the program is considered non-reloadable. Please note +that procs from reloadable modules should not appear in the call stack of +program while ``performCodeReload`` is being called. Thus, the main module +is a suitable place for implementing a program loop capable of calling +``performCodeReload``. + +Please note that reloading won't be possible when any of the type definitions +in the program has been changed. When closure iterators are used (directly or +through async code), the reloaded refinitions will affect only newly created +instances. Existing iterator instancess will execute their original code to +completion. + +JavaScript target +================= + +Once your code is compiled for hot reloading, the ``nim-livereload`` NPM +package provides a convenient solution for implementing the actual reloading +in the browser using a framework such as [LiveReload](http://livereload.com/) +or [BrowserSync](https://browsersync.io/). diff --git a/doc/nimc.rst b/doc/nimc.rst index 4f5808cd24..c2801a3f47 100644 --- a/doc/nimc.rst +++ b/doc/nimc.rst @@ -414,94 +414,6 @@ the generated C contains code to ensure that proper stack traces with line number information are given if the program crashes or an uncaught exception is raised. -Hot code reloading ------------------- - -The `hotCodeReloading`:idx: option enables special compilation mode where -changes in the code can be applied automatically to a running program. -The code reloading happens at the granularity of an individual module. -When a module is reloaded, any newly added global variables will be -initialized, but all other top-level code appearing in the module won't -be re-executed and the state of all existing global variables will be -preserved. One can use the special event handlers ``beforeCodeReload`` and -``afterCodeReload`` to reset the state of a particular variable or to force -the execution of certain statements: - -.. code-block:: Nim - var - settings = initTable[string, string]() - lastReload: Time - - for k, v in loadSettings(): - settings[k] = v - - initProgram() - - afterCodeReload: - lastReload = now() - resetProgramState() - -On each code reload, Nim will first execute all `beforeCodeReload`:idx: -handlers registered in the previous version of the program and then all -`afterCodeReload`:idx handlers appearing in the newly loaded code. Please note -that any handlers appearing in modules that weren't reloaded will also be -executed. To prevent this behavior, one can guard the code with the -`hasModuleChanged()`:idx: API: - -.. code-block:: Nim - import mydb - - var myCache = initTable[Key, Value]() - - afterCodeReload: - if hasModuleChanged(mydb): - resetCache(myCache) - -The hot code reloading is based on dynamic library hot swapping in the native -targets and direct manipulation of the global namespace in the JavaScript -target. The Nim compiler does not specify the mechanism for detecting the -conditions when the code must be reloaded. Instead, the program code is -expected to call `performCodeReload()`:idx every time it wishes to reload -its code. - -It's expected that most projects will implement the reloading with a suitable -build-system triggered IPC notification mechanism, but a polling solution is -also possible through the provided `hasAnyModuleChanged()`:idx API. - -In order to access ``beforeCodeReload``, ``afterCodeReload``, ``hasModuleChanged`` -or ``hasAnyModuleChanged`` one must import the `hotcodereloading`:idx module. - -**Usage in Native projects:** - -Native projects using the hot code reloading option will be implicitly -compiled with the `-d:useNimRtl` option and they will depend on both -the ``nimrtl`` library and the ``nimhcr`` library which implements the -hot code reloading run-time. - -All modules of the project will be compiled to separate dynamic link -libraries placed in the ``nimcache`` directory. Please note that during -the execution of the program, the hot code reloading run-time will load -only copies of these libraries in order to not interfere with any newly -issued build commands. - -The main module of the program is considered non-reloadable. Please note -that procs from reloadable modules should not appear in the call stack of -program while ``performCodeReload`` is being called. Thus, the main module -is a suitable place for implementing a program loop capable of calling -``performCodeReload``. - -Please note that reloading won't be possible when any of the type definitions -in the program has been changed. When closure iterators are used (directly or -through async code), the reloaded refinitions will affect only newly created -instances. Existing iterator instancess will execute their original code to -completion. - -**Usage in JavaScript projects:** - -Once your code is compiled for hot reloading, the ``nim-livereload`` NPM -package provides a convenient solution for implementing the actual reloading -in the browser using a framework such as [LiveReload](http://livereload.com/) -or [BrowserSync](https://browsersync.io/). DynlibOverride diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim index cb9664a40e..9f2ceb2d00 100644 --- a/tools/kochdocs.nim +++ b/tools/kochdocs.nim @@ -101,6 +101,7 @@ doc/tut1.rst doc/tut2.rst doc/tut3.rst doc/nimc.rst +doc/hcr.rst doc/overview.rst doc/filters.rst doc/tools.rst