Last active
March 13, 2017 02:27
-
-
Save define-private-public/96c9e23fc66da9ab7b1c125d9e1fc2d5 to your computer and use it in GitHub Desktop.
For an article on hot loading w/ Nim
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import osproc | |
# Where your Nim compiler is located | |
const nimEXE = "/home/ben/bin/Nim/bin/nim" | |
# Compile and wait for it to be done | |
let | |
compile = startProcess(nimEXE, "", ["c", "--app:lib", "game"]) | |
compileStatus = waitForExit(compile) | |
close(compile) | |
if compileStatus == 0: | |
# Compile was successful, do your DLL business here... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# game logic file | |
# | |
# compile with `nim c --app:lib game` | |
import strfmt | |
{.pragma: rtl, exportc, dynlib, cdecl.} | |
# frameNum -- which number frame this is | |
# dt -- time since last update (in seconds) | |
# total -- total elapsed time (in seconds) | |
proc update*(frameNum: int; dt, total: float) {.rtl.} = | |
echo "{0}: update() [#{1}] dt={2}".fmt(total.format("5.2f"), frameNum.format("03d"), dt) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Minimal amount of code needed to load the DLL for game.nim (on Linux) | |
# | |
# compile with `nim c min` | |
import dynlib | |
# Make a function prototype | |
type | |
updateProc = proc (frameNum: int; dt, total: float) {.nimcall.} | |
var | |
dll: LibHandle # Library that's loaded | |
update: updateProc # Function to call, and reload | |
# Load the library up | |
dll = loadLib("./libgame.so") # Change this for your OS | |
if dll != nil: | |
# Get the address where the `update()` proc is stored | |
let updateAddr = dll.symAddr("update") | |
if updateAddr != nil: | |
update = cast[updateProc](updateAddr) | |
# Run it | |
if update != nil: | |
update(5, 0.4, 2.4) | |
else: | |
echo "Wasn't able to load update() from DLL." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This runs the game | |
# | |
# compile with `nim c --threads:on runner` | |
import os, osproc, dynlib, locks, times | |
import stopwatch | |
# Where your nim compiler is located | |
const nimEXE = "/home/ben/bin/Nim/bin/nim" | |
# Platform independant way of building a DLL path | |
proc buildDLLPath(name: string): string = | |
when defined(windows): | |
return name & ".dll" | |
elif defined(macosx): | |
return "./lib" & name & ".dylib" | |
else: | |
# Assume it's linux or UNIX | |
return "./lib" & name & ".so" | |
# Proc prototype | |
type | |
updateProc = proc (frameNum: int; dt, total: float) {.nimcall.} | |
# Global variables | |
var | |
dll: LibHandle # Library that's loaded | |
update: updateProc # Function to call, and reload | |
dllReady = false # DLL has been loaded or not | |
running = true # Running the "game," | |
# Locks for threading & flags | |
dllLock: Lock | |
dllReadyLock: Lock | |
runningLock: Lock | |
# Setup the loading lock | |
initLock(dllLock) | |
initLock(dllReadyLock) | |
initLock(runningLock) | |
# Checks to see if a module/file has been changed, then will recompile it and | |
# load the DLL. It keeps on doing this until `running` has been set to `false` | |
# This proc should be run in its own thread. | |
proc loadDLL(name: string) {.thread.} = | |
# Make some paths | |
let | |
dllPath = buildDLLPath(name) | |
nimSrcPath = name & ".nim" | |
var | |
lastWriteTime = 0.Time | |
isRunning = true | |
while isRunning: | |
# Check for change on .nim file | |
var writeTime = 0.Time | |
try: | |
writeTime = getFileInfo(nimSrcPath).lastWriteTime | |
except: | |
discard | |
if lastWriteTime < writeTime: | |
echo "Write detected on " & nimSrcPath | |
lastWriteTime = writeTime | |
# if so, try compile it | |
let | |
compile = startProcess(nimEXE, "", ["c", "--app:lib", name]) | |
compileStatus = waitForExit(compile) # TODO maybe should have a timeout | |
close(compile) | |
# if compilaiton was good, load the DLL | |
if compileStatus == 0: | |
# Get the lock | |
acquire(dllLock) | |
# unload the library if it has already been loaded | |
if dll != nil: | |
unloadLib(dll) | |
dll = nil | |
# (Re)load the library | |
echo "Attempting to load " & dllPath | |
dll = loadLib(dllPath) | |
if dll != nil: | |
let updateAddr = dll.symAddr("update") | |
if updateAddr != nil: | |
update = cast[updateProc](updateAddr) | |
echo "Successfully loaded DLL & functions " & dllPath | |
acquire(dllReadyLock) | |
dllReady = true | |
release(dllReadyLock) | |
else: | |
echo "Error, Was able to load DLL, but not functions " & dllPath | |
else: | |
echo "Error, wasn't able to load DLL " & dllPath | |
# Release the lock | |
release(dllLock) | |
else: | |
# Bad compile, print a message | |
echo nimSrcPath & " failed to compile; not reloading" | |
# sleep for 1/5 of a second, then check for changes again | |
sleep(200) | |
# Check for quit | |
acquire(runningLock) | |
isRunning = running | |
release(runningLock) | |
# Block until the DLL is loaded | |
proc waitForDLLReady() = | |
acquire(dllReadyLock) | |
var ready = dllReady | |
release(dllReadyLock) | |
while not ready: | |
# Test again every 1/5 second | |
sleep(200) | |
acquire(dllReadyLock) | |
ready = dllReady | |
release(dllReadyLock) | |
# Main game prodecedure and loop | |
proc main() = | |
# Loading a DLL needs to be in it's own thread | |
var dllLoadingThread: Thread[string] | |
createThread(dllLoadingThread, loadDLL, "game") | |
# Setup some of the game stuff | |
var | |
sw = stopwatch() | |
lastFrameTime = 0.0 | |
frameCount = 0 | |
t = 0.0 | |
# Hold here until our DLLs are ready | |
echo "Waiting for the DLL to be loaded..." | |
waitForDLLReady() | |
# Start the loop | |
echo "Running for 60 seconds..." | |
sw.start() | |
while t < 60.1: | |
let delta = t - lastFrameTime | |
if delta >= 0.5: | |
# run a frame | |
update(frameCount, delta, t) | |
# Set next | |
lastFrameTime = t | |
frameCount += 1 | |
t = sw.secs | |
echo "Shutting down." | |
# Cleanup our threads | |
acquire(runningLock) | |
running = false | |
release(runningLock) | |
joinThread(dllLoadingThread) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment