utils#

Types#

type FnPtr = proc () {.nimcall.}#

Function pointer, used for interrupt handlers etc.


Lists#

type List[N: static[int], T] = object#

A list with a max capacity.

A List is an array-like container with a length field. Adding new items will increase the length, but it cannot grow beyond its maximum capacity.

Lists have predictable performance, as they can be allocated statically, unlike Nim’s built-in seq which is heap-allocated and can grow indefinitely.

Tip

When compiling with --boundChecks:on the game will panic when the list grows too big or an out-of-bounds access occurs. To debug this situation, see using a debugger.

Procs#

proc `[]`[N, T](list: var List[N,T], i: int): var T#

Get the element at the given index (mutable version).

proc `[]=`[N, T](list: var List[N,T], i: int, val: T)#

Set the element at the given index to a certain value.

template cap[N, T](list: List[N,T]): int#

The maximum capacity of the list.

proc add[N, T](list: var List[N,T], item: sink T)#

Add an item to the end of the list.

proc del[N, T](list: var List[N,T], i: int)#

Remove an element by index, putting the last element in its place.

proc delete[N, T](list: var List[N,T], i: int)#

Remove an element by index, shifting all other elements down.

proc insert[N, T](list: var List[N,T], item: sink T, i = 0.Natural)#

Insert an item into the list at the given index, shifting later elements along to make space.

proc clear[N, T](list: var List[N,T])#

Empty the list.

proc isFull[N, T](list: List[N,T]): bool#

Returns true if the list is at its maximum capacity.

proc contains[N, T](list: List[N,T], val: T): bool#

Check whether a value exists in the list.

This can also be invoked using the in or notin keywords.

Example:

var nums: List[10, int]

# ...

if 123 in nums:
  printf("123 is in the list.")

Unsafe procs#

These may be useful for optimisation, but they come with caveats which make them unsafe.

proc qcreate[N, T](list: var List[N,T]): ptr T#

Increase the length of the list and return a pointer to the new element.

The new element is uninitialised, so be sure to completely reset it.

proc qdel[N, T](list: var List[N,T], i: int)#

Remove an element by index, quicker version.

The last element won’t be deinitialised, so only do this if you know there’ll be no resource leakage.

proc qclear[N, T](list: var List[N,T])#

Empty the list, quicker version.

Elements won’t be deinitialised, so only do this if you know there’ll be no resource leakage.

Iterators#

iterator items[N, T](list: List[N,T]): lent T#

Loop over each element in the list.

iterator mitems[N, T](list: var List[N,T]): var T#
iterator pairs[N, T](list: List[N,T]): tuple[key: int, val: lent T]#

Loop over each element in the list along with its index.

iterator mpairs[N, T](list: var List[N,T]): tuple[key: int, val: var T]#

Memory peek & poke#

These are like volatileLoad and volatileStore from the Nim standard library, except they work even at the top level (and have nicer names).

proc peek[T](address: ptr T): T#

Read a value directly from some memory location.

proc poke[T](address: ptr T, value: T)#

Write a value directly to a memory location.


Memory copy / fill#

proc memset16(dst: pointer, hw: uint16, hwcount: SomeInteger)#

Fastfill for halfwords, analogous to memset()

Uses memset32() if hwcount > 5

Dst:

Destination address.

Hw:

Source halfword (not address).

Hwcount:

Number of halfwords to fill.

Note

dst must be halfword aligned.
r0 returns as dst + hwcount*2.
proc memcpy16(dst: pointer, src: pointer, hwcount: SomeInteger)#

Copy for halfwords.

Uses memcpy32() if hwcount > 6 and src and dst are aligned equally.

Dst:

Destination address.

Src:

Source address.

Hwcount:

Number of halfwords to fill.

Note

dst and src must be halfword aligned.
r0 and r1 return as dst + hwcount*2 and src + hwcount*2.
proc memset32(dst: pointer, wd: uint32, wcount: SomeInteger)#

Fast-fill by words, analogous to memset()

Like CpuFastSet(), only without the requirement of 32byte chunks and no awkward store-value-in-memory-first issue.

Dst:

Destination address.

Wd:

Fill word (not address).

Wcount:

Number of words to fill.

Note

dst must be word aligned.
r0 returns as dst + wcount*4.
proc memcpy32(dst: pointer, src: pointer, wcount: SomeInteger)#

Fast-copy by words.

Like CpuFastFill, only without the requirement of 32byte chunks

Dst:

Destination address.

Src:

Source address.

Wcount:

Number of words.

Note

src and dst must be word aligned.
r0 and r1 return as dst + wcount*4 and src + wcount*4.

Bit packing and duplication#

These take a hex-value and duplicate it to all fields, like 0x88 -> 0x88888888.

func dup8(x: uint8): uint16#

Duplicate a byte to form a halfword: 0x12 -> 0x1212.

func dup16(x: uint16): uint32#

Duplicate a halfword to form a word: 0x1234 -> 0x12341234.

func quad8(x: uint8): uint32#

Quadruple a byte to form a word: 0x12 -> 0x12121212.

func octup(x: uint8): uint32#

Octuple a nybble to form a word: 0x1 -> 0x11111111

func bytes2hword(b0, b1: uint8): uint16#

Pack 2 bytes into a word. Little-endian order.

func bytes2word(b0, b1, b2, b3: uint8): uint32#

Pack 4 bytes into a word. Little-endian order.

func hword2word(h0, h1: uint16): uint32#

Pack 2 halfwords into a word. Little-endian order.


Math helpers#

func isPowerOfTwo(n: SomeInteger): bool#

Return true if n is a power of two.

func logPowerOfTwo(n: uint): uint#

Given that n is a power of two, return the power.

proc octant(x, y: int): uint#

Get the octant that (x, y) is in.

This function divides the circle in 8 parts. The angle starts at the y=0 line and then moves in the direction of the x=0 line. On the screen, this would be like starting at the 3 o’clock position and moving clockwise.

proc octantRot(x0, y0: int): uint#

Get the rotated octant that (x, y) is in.

Like octant() but with a twist. The 0-octant starts 22.5° earlier so that 3 o’clock falls in the middle of octant 0, instead of at its start. This can be useful for 8 directional pointing.


Random numbers#

Uses a simple XorShift algorithm, which is adequate for most games.

Warning

Any range supplied to these procs should be less than 2^16 (65536).

For example, the following will all give inadequate results:

  • rand(max=999999)

  • rand(fp(-300)..fp(300))

  • rand(Natural)

If this is a problem, you could try rand() mod n instead (but that’s expensive and leads to an uneven distribution of numbers). In the fixed-point case, you could first get a random integer and then convert it to fixed, e.g. rand(-300..300).fp.

proc seed(seed: uint32)#

Seed the random number generator.

proc rand(): uint32#

Get a random 32-bit value.

proc rand[T: Fixed|SomeInteger](max: T): T#

Get a random integer in the range 0..max.

Note

max must be less than 2^16 or fp(256).

proc rand[T: Ordinal](a, b: T): T#

Get a random value between a and b inclusive.

Note

a - b must be less than 2^16, to avoid overflow.

proc rand[T: Ordinal](s: Slice[T]): T#

Get a random value from a slice.

Example:

let n = rand(0..100)
proc rand[T: Ordinal](t: typedesc[T]): T#

Get a random value of the given type.

proc pickRandom[T](arr: openArray[T]): T#

Get a random item from an array.

proc pickRandom[T](arr: ptr UncheckedArray[T], len: SomeInteger): T#

Get a random item from an unchecked array with a given length.


Compile-time helpers#

template readBin(path: static string): untyped#

Read a binary file at compile-time as an array of bytes.

If assigned to a top-level let variable, this data will be placed in ROM.

e.g.

let shipPal = readBin("ship.pal.bin")