hcr.rst 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  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/sdl
  22. #*** import the hotcodereloading stdlib module ***
  23. import hotcodereloading
  24. var runGame*: bool = true
  25. var window: Window
  26. var renderer: Renderer
  27. proc init*() =
  28. discard sdl.init(INIT_EVERYTHING)
  29. window = createWindow("testing", WINDOWPOS_UNDEFINED.cint, WINDOWPOS_UNDEFINED.cint, 640, 480, 0'u32)
  30. assert(window != nil, $sdl.getError())
  31. renderer = createRenderer(window, -1, RENDERER_SOFTWARE)
  32. assert(renderer != nil, $sdl.getError())
  33. proc destroy*() =
  34. destroyRenderer(renderer)
  35. destroyWindow(window)
  36. var posX = 1
  37. var posY = 0
  38. var dX = 1
  39. var dY = 1
  40. proc update*() =
  41. for evt in events():
  42. if evt.kind == QUIT:
  43. runGame = false
  44. break
  45. if evt.kind == KEY_DOWN:
  46. if evt.key.keysym.scancode == SCANCODE_ESCAPE: runGame = false
  47. elif evt.key.keysym.scancode == SCANCODE_F9:
  48. #*** reload this logic.nim module on the F9 keypress ***
  49. performCodeReload()
  50. # draw a bouncing rectangle:
  51. posX += dX
  52. posY += dY
  53. if posX >= 640: dX = -2
  54. if posX <= 0: dX = +2
  55. if posY >= 480: dY = -2
  56. if posY <= 0: dY = +2
  57. discard renderer.setRenderDrawColor(0, 0, 255, 255)
  58. discard renderer.renderClear()
  59. discard renderer.setRenderDrawColor(255, 128, 128, 0)
  60. var rect: Rect(x: posX - 25, y: posY - 25, w: 50, h: 50)
  61. discard renderer.renderFillRect(rect.addr)
  62. delay(16)
  63. renderer.renderPresent()
  64. .. code-block:: nim
  65. # mymain.nim
  66. import logic
  67. proc main() =
  68. init()
  69. while runGame:
  70. update()
  71. destroy()
  72. main()
  73. Compile this example via::
  74. nim c --hotcodereloading:on mymain.nim
  75. Now start the program and KEEP it running!
  76. ::
  77. # Unix:
  78. mymain &
  79. # or Windows (click on the .exe)
  80. mymain.exe
  81. # edit
  82. For example, change the line::
  83. discard renderer.setRenderDrawColor(255, 128, 128, 0)
  84. into::
  85. discard renderer.setRenderDrawColor(255, 255, 128, 0)
  86. (This will change the color of the rectangle.)
  87. Then recompile the project, but do not restart or quit the mymain.exe program!
  88. ::
  89. nim c --hotcodereloading:on mymain.nim
  90. Now give the ``mymain`` SDL window the focus, press F9 and watch the
  91. updated version of the program.
  92. Reloading API
  93. =============
  94. One can use the special event handlers ``beforeCodeReload`` and
  95. ``afterCodeReload`` to reset the state of a particular variable or to force
  96. the execution of certain statements:
  97. .. code-block:: Nim
  98. var
  99. settings = initTable[string, string]()
  100. lastReload: Time
  101. for k, v in loadSettings():
  102. settings[k] = v
  103. initProgram()
  104. afterCodeReload:
  105. lastReload = now()
  106. resetProgramState()
  107. On each code reload, Nim will first execute all `beforeCodeReload`:idx:
  108. handlers registered in the previous version of the program and then all
  109. `afterCodeReload`:idx: handlers appearing in the newly loaded code. Please note
  110. that any handlers appearing in modules that weren't reloaded will also be
  111. executed. To prevent this behavior, one can guard the code with the
  112. `hasModuleChanged()`:idx: API:
  113. .. code-block:: Nim
  114. import mydb
  115. var myCache = initTable[Key, Value]()
  116. afterCodeReload:
  117. if hasModuleChanged(mydb):
  118. resetCache(myCache)
  119. The hot code reloading is based on dynamic library hot swapping in the native
  120. targets and direct manipulation of the global namespace in the JavaScript
  121. target. The Nim compiler does not specify the mechanism for detecting the
  122. conditions when the code must be reloaded. Instead, the program code is
  123. expected to call `performCodeReload()`:idx: every time it wishes to reload
  124. its code.
  125. It's expected that most projects will implement the reloading with a suitable
  126. build-system triggered IPC notification mechanism, but a polling solution is
  127. also possible through the provided `hasAnyModuleChanged()`:idx: API.
  128. In order to access ``beforeCodeReload``, ``afterCodeReload``, ``hasModuleChanged``
  129. or ``hasAnyModuleChanged`` one must import the `hotcodereloading`:idx: module.
  130. Native code targets
  131. ===================
  132. Native projects using the hot code reloading option will be implicitly
  133. compiled with the `-d:useNimRtl` option and they will depend on both
  134. the ``nimrtl`` library and the ``nimhcr`` library which implements the
  135. hot code reloading run-time.
  136. All modules of the project will be compiled to separate dynamic link
  137. libraries placed in the ``nimcache`` directory. Please note that during
  138. the execution of the program, the hot code reloading run-time will load
  139. only copies of these libraries in order to not interfere with any newly
  140. issued build commands.
  141. The main module of the program is considered non-reloadable. Please note
  142. that procs from reloadable modules should not appear in the call stack of
  143. program while ``performCodeReload`` is being called. Thus, the main module
  144. is a suitable place for implementing a program loop capable of calling
  145. ``performCodeReload``.
  146. Please note that reloading won't be possible when any of the type definitions
  147. in the program has been changed. When closure iterators are used (directly or
  148. through async code), the reloaded refinitions will affect only newly created
  149. instances. Existing iterator instancess will execute their original code to
  150. completion.
  151. JavaScript target
  152. =================
  153. Once your code is compiled for hot reloading, the ``nim-livereload`` NPM
  154. package provides a convenient solution for implementing the actual reloading
  155. in the browser using a framework such as [LiveReload](http://livereload.com/)
  156. or [BrowserSync](https://browsersync.io/).