hcr.rst 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. ===================================
  2. Hot code reloading
  3. ===================================
  4. The `hotCodeReloading`:idx: option enables special compilation mode where
  5. changes in the code can be applied automatically to a running program.
  6. The code reloading happens at the granularity of an individual module.
  7. When a module is reloaded, any newly added global variables will be
  8. initialized, but all other top-level code appearing in the module won't
  9. be re-executed and the state of all existing global variables will be
  10. preserved.
  11. Basic workflow
  12. ==============
  13. Currently hot code reloading does not work for the main module itself,
  14. so we have to use a helper module where the major logic we want to change
  15. during development resides.
  16. In this example we use SDL2 to create a window and we reload the logic
  17. code when ``F9`` is pressed. The important lines are marked with ``#***``.
  18. To install SDL2 you can use ``nimble install sdl2``.
  19. .. code-block:: nim
  20. # logic.nim
  21. import sdl2
  22. #*** import the hotcodereloading stdlib module ***
  23. import hotcodereloading
  24. var runGame*: bool = true
  25. var window: WindowPtr
  26. var renderer: RendererPtr
  27. var evt = sdl2.defaultEvent
  28. proc init*() =
  29. discard sdl2.init(INIT_EVERYTHING)
  30. window = createWindow("testing", SDL_WINDOWPOS_UNDEFINED.cint, SDL_WINDOWPOS_UNDEFINED.cint, 640, 480, 0'u32)
  31. assert(window != nil, $sdl2.getError())
  32. renderer = createRenderer(window, -1, RENDERER_SOFTWARE)
  33. assert(renderer != nil, $sdl2.getError())
  34. proc destroy*() =
  35. destroyRenderer(renderer)
  36. destroyWindow(window)
  37. var posX: cint = 1
  38. var posY: cint = 0
  39. var dX: cint = 1
  40. var dY: cint = 1
  41. proc update*() =
  42. while pollEvent(evt):
  43. if evt.kind == QuitEvent:
  44. runGame = false
  45. break
  46. if evt.kind == KeyDown:
  47. if evt.key.keysym.scancode == SDL_SCANCODE_ESCAPE: runGame = false
  48. elif evt.key.keysym.scancode == SDL_SCANCODE_F9:
  49. #*** reload this logic.nim module on the F9 keypress ***
  50. performCodeReload()
  51. # draw a bouncing rectangle:
  52. posX += dX
  53. posY += dY
  54. if posX >= 640: dX = -2
  55. if posX <= 0: dX = +2
  56. if posY >= 480: dY = -2
  57. if posY <= 0: dY = +2
  58. discard renderer.setDrawColor(0, 0, 255, 255)
  59. discard renderer.clear()
  60. discard renderer.setDrawColor(255, 128, 128, 0)
  61. var rect = Rect(x: posX - 25, y: posY - 25, w: 50.cint, h: 50.cint)
  62. discard renderer.fillRect(rect)
  63. delay(16)
  64. renderer.present()
  65. .. code-block:: nim
  66. # mymain.nim
  67. import logic
  68. proc main() =
  69. init()
  70. while runGame:
  71. update()
  72. destroy()
  73. main()
  74. Compile this example via::
  75. nim c --hotcodereloading:on mymain.nim
  76. Now start the program and KEEP it running!
  77. ::
  78. # Unix:
  79. mymain &
  80. # or Windows (click on the .exe)
  81. mymain.exe
  82. # edit
  83. For example, change the line::
  84. discard renderer.setDrawColor(255, 128, 128, 0)
  85. into::
  86. discard renderer.setDrawColor(255, 255, 128, 0)
  87. (This will change the color of the rectangle.)
  88. Then recompile the project, but do not restart or quit the mymain.exe program!
  89. ::
  90. nim c --hotcodereloading:on mymain.nim
  91. Now give the ``mymain`` SDL window the focus, press F9 and watch the
  92. updated version of the program.
  93. Reloading API
  94. =============
  95. One can use the special event handlers ``beforeCodeReload`` and
  96. ``afterCodeReload`` to reset the state of a particular variable or to force
  97. the execution of certain statements:
  98. .. code-block:: Nim
  99. var
  100. settings = initTable[string, string]()
  101. lastReload: Time
  102. for k, v in loadSettings():
  103. settings[k] = v
  104. initProgram()
  105. afterCodeReload:
  106. lastReload = now()
  107. resetProgramState()
  108. On each code reload, Nim will first execute all `beforeCodeReload`:idx:
  109. handlers registered in the previous version of the program and then all
  110. `afterCodeReload`:idx: handlers appearing in the newly loaded code. Please note
  111. that any handlers appearing in modules that weren't reloaded will also be
  112. executed. To prevent this behavior, one can guard the code with the
  113. `hasModuleChanged()`:idx: API:
  114. .. code-block:: Nim
  115. import mydb
  116. var myCache = initTable[Key, Value]()
  117. afterCodeReload:
  118. if hasModuleChanged(mydb):
  119. resetCache(myCache)
  120. The hot code reloading is based on dynamic library hot swapping in the native
  121. targets and direct manipulation of the global namespace in the JavaScript
  122. target. The Nim compiler does not specify the mechanism for detecting the
  123. conditions when the code must be reloaded. Instead, the program code is
  124. expected to call `performCodeReload()`:idx: every time it wishes to reload
  125. its code.
  126. It's expected that most projects will implement the reloading with a suitable
  127. build-system triggered IPC notification mechanism, but a polling solution is
  128. also possible through the provided `hasAnyModuleChanged()`:idx: API.
  129. In order to access ``beforeCodeReload``, ``afterCodeReload``, ``hasModuleChanged``
  130. or ``hasAnyModuleChanged`` one must import the `hotcodereloading`:idx: module.
  131. Native code targets
  132. ===================
  133. Native projects using the hot code reloading option will be implicitly
  134. compiled with the `-d:useNimRtl` option and they will depend on both
  135. the ``nimrtl`` library and the ``nimhcr`` library which implements the
  136. hot code reloading run-time. Both libraries can be found in the ``lib``
  137. folder of Nim and can be compiled into dynamic libraries to satisfy
  138. runtime demands of the example code above. An example of compiling
  139. ``nimhcr.nim`` and ``nimrtl.nim`` when the source dir of Nim is installed
  140. with choosenim follows.
  141. ::
  142. # Unix/MacOS
  143. # Make sure you are in the directory containing your .nim files
  144. $ cd your-source-directory
  145. # Compile two required files and set their output directory to current dir
  146. $ nim c --outdir:$PWD ~/.choosenim/toolchains/nim-#devel/lib/nimhcr.nim
  147. $ nim c --outdir:$PWD ~/.choosenim/toolchains/nim-#devel/lib/nimrtl.nim
  148. # verify that you have two files named libnimhcr and libnimrtl in your
  149. # source directory (.dll for Windows, .so for Unix, .dylib for MacOS)
  150. All modules of the project will be compiled to separate dynamic link
  151. libraries placed in the ``nimcache`` directory. Please note that during
  152. the execution of the program, the hot code reloading run-time will load
  153. only copies of these libraries in order to not interfere with any newly
  154. issued build commands.
  155. The main module of the program is considered non-reloadable. Please note
  156. that procs from reloadable modules should not appear in the call stack of
  157. program while ``performCodeReload`` is being called. Thus, the main module
  158. is a suitable place for implementing a program loop capable of calling
  159. ``performCodeReload``.
  160. Please note that reloading won't be possible when any of the type definitions
  161. in the program has been changed. When closure iterators are used (directly or
  162. through async code), the reloaded definitions will affect only newly created
  163. instances. Existing iterator instances will execute their original code to
  164. completion.
  165. JavaScript target
  166. =================
  167. Once your code is compiled for hot reloading, a convenient solution for implementing the actual reloading
  168. in the browser using a framework such as [LiveReload](http://livereload.com/)
  169. or [BrowserSync](https://browsersync.io/).