hcr.rst 7.2 KB

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