In my last post I setup a simple example skeleton project and made it compile like I wanted to. This time around, I am going to go through my process of figuring things out. I’m new to both Nim and SDL2, so I have a bunch of stuff to learn!
After skimming the skeleton code from last time, and generally getting a grasp of what the program is doing, I start picking out pieces of the code that I want to know more about. The first such piece is this one:
var
evt: TEvent
runGame = true
fpsman: TFPSmanager
fpsman.init
I have a bunch of questions, but lets start with “What is ‘TFPSManager’? Does it come from SDL2? is it part of the wrapper?” (alright, lets start with 3 questions…)
As it turns out, it’s defined in sdl2/gfx.nim
, which is a wrapper for the SDL2_gfx library, which seems to be not part of the SDL2 library proper, but instead is a sort of boiler-plate project.
The sdl2/gfx.nim
library is actually imported into the skeleton by this:
import sdl2, sdl2/gfx
I’m still wondering how sdl2/gfx
imports TFPSmanager into the skeleton’s scope, but I’ll save figuring that out for later. Regardless, the sdl2/gfx
part pulls in the exported symbols in sdl2/gfx
.
And here’s the actual type definition:
type
TFPSmanager* {.pure, final.} = object
framecount*: cint
rateticks*: cfloat
baseticks*: cint
lastticks*: cint
rate*: cint
{.pure, final.}
is a pragma statement saying:
. This type cannot be inherited from (final) . This type should have not have any runtime type checking for binary compatibility with other compiled languages (pure)
Basically, this type needs to match up with a type found in the SDL2_gfx library called FPSmanager
, which these pragmas ensure.
My next question, seeing this type definition, is “Where are ‘cint’ and ‘cfloat’ defined?”
Turns out, these are built-into the compiler system
module, which is included and available by default in all modules. Took me some doing, for some reason, but I ended up finding the documentation here. The documentation basically just explicitely says that cint
== C’s ‘int’ == int32
in Nim.
Okay, so back to the skeleton – there’s a line: fpsman.init
. Where is this method or property defined? Actually, it’s a “procedure” definition, and it’s located in the sdl2/gfx.nim file as well. But this teaches me a bunch of things! First, here are the definitions:
proc init*(manager: var TFPSmanager) {.importc: "SDL_initFramerate".}
proc setFramerate*(manager: var TFPSmanager; rate: cint): SDL_Return {.
importc: "SDL_setFramerate", discardable.}
proc getFramerate*(manager: var TFPSmanager): cint {.importc: "SDL_getFramerate".}
proc getFramecount*(manager: var TFPSmanager): cint {.importc: "SDL_getFramecount".}
proc delay*(manager: var TFPSmanager): cint {.importc: "SDL_framerateDelay", discardable.}
They are forward declarations of methods, with which the FFI imports declarations for from C libs.
And then, the procedures here are defined to take advantage of syntactic sugar called method call syntax in Nim. Essentially, the first value of a procedure can be prefixed to a call of the procedure. Example:
# assuming this is defined
proc xlen(s: string) =
result = len(s)
# then this:
assert(xlen("value")==5)
# is the same as:
assert("value".xlen==5)
Pretty nifty!
Back to the init
definition: since init
takes 1 parameter, a TFPSmanager
, then the statement fpsman.init
makes sense, as it’s syntactic sugar for init(fpsman)
.
Next up, what is the asterisk between init
and (
mean? Easy! It tells Nim that this procedure is to be exported by the module.
What about the ‘importc’ pragma statement? Well, like you’d imagine, it’s for telling the compiler that this forward declaration represents a definition found in a C library, and the FFI should do it’s magic and import that definition.
And What about that ‘SDL_Return’ value – where’s that coming from? It’s in sdl2.nim:
SDL_Return* {.size: sizeof(cint).} = enum SdlError = -1, SdlSuccess = 0 ##\
## Return value for many SDL functions. Any function that returns like this \
## should also be discardable
A type definition for an enum with 2 values, that is marked as being the size
of a cint
so as to be compatible with code referenced using the FFI.
Back to the procedures defined for use with TFPSmanager
, the last thing I had a question about tonight is the discardable
pragma value. The answer is pretty straight-forward: it is telling the compiler that the return value can be discarded implicitely – IE there’s no need to use the discard
keyword when calling this procedure.
And that’s it for tonight!
I learned a bunch about how Nim works, and a little about SDL2.
Until next time!