iteratornim-lang

How to call iterator for Chipmunk 7 bindings (Nim)


I'm using the chipmunk7 library bindings, and trying to call the iterator eachShape. The bindings source is here

SpaceShapeIteratorFunc* = proc (shape: Shape; data: pointer) {.cdecl.}
  ## Space/body iterator callback function type.

[..]

proc eachShape*(space: Space; `func`: SpaceShapeIteratorFunc; data: pointer) {.cdecl, importc: "cpSpaceEachShape".}
  ## Call `func` for each shape in the space.

And calling like this

import std/math
import chipmunk7
import playdate/api


var gravity = v(0, 100)
var timeStep = 1.0/50.0
var time = 0.0

var space = newSpace()
space.gravity = gravity

[..]

proc drawChipmunkHello*() =
  # iterate over all shapes in the space
  for shape in eachShape(space):
    if shape.shapeType == ShapeType.circle:
      let circle = shape as CircleShape
      drawCircle(circle.body.position, circle.radius, circle.body.angle, kColorBlack)
    elif shape.shapeType == ShapeType.segment:
      let segment = shape as SegmentShape
      drawSegment(segment, kColorBlack)
    elif shape.shapeType == ShapeType.poly:
      let poly = shape as PolyShape
      for i in 0 ..< poly.count:
        let a = poly.getVert(i)
        let b = poly.getVert((i + 1) % poly.count)
        playdate.graphics.drawLine(a.x.toInt, a.y.toInt, b.x.toInt, b.y.toInt, 1, kColorBlack);

I get the coompiler error

hello.nim(63, 25) Error: type mismatch: got <Space>
but expected one of:
proc eachShape(space: Space; func: SpaceShapeIteratorFunc; data: pointer)
  first type mismatch at position: 2
  missing parameter: func
1 other mismatching symbols have been suppressed; compile with --showAllMismatches:on to see them

Line 63 being the first line of drawChipmunkHello()

A c-sample of how to call the iterator is here

What is the correct syntax to call the iterator?


Solution

  • What Chipmunk calls an iterator and what Nim calls an iterator are two different things. The way you're trying to call eachShape is indeed correct for a Nim iterator with one parameter. But the three-parameter construct that Chipmunk calls an iterator is not callable this way. Let's look at the error message, it say that eachShape where given <Space>, i.e. a single argument of type Space but it expected:

    proc eachShape(space: Space; func: SpaceShapeIteratorFunc; data: pointer)
      first type mismatch at position: 2
      missing parameter: func
    

    which is three arguments Space, SpaceShapeIteratorFunc, and a pointer. It also tells us that the type mismatch happened at position 2 and that it failed because it was simply missing the func argument, which makes sense, because we only provided one Space argument. Now, let's have a look at SpaceShapeIteratorFunc:

    SpaceShapeIteratorFunc* = proc (shape: Shape; data: pointer) {.cdecl.}
      ## Space/body iterator callback function type.
    

    As we can see this is simply an alias for a procedure which takes a Shape and a pointer. It is also defined as being {.cdecl.} which just means that it follows the standard C calling convention. What this means is that we have to convert the body of your loop into a procedure, and then manually pass that procedure to the iterator. The pointer that is accepted by SpaceShapeIteratorFunc and required by eachShape is where we can store any context that we want to have available in the iterator.

    Let's do a simple rewrite:

    import std/math
    import chipmunk7
    import playdate/api
    
    
    var gravity = v(0, 100)
    var timeStep = 1.0/50.0
    var time = 0.0
    
    var space = newSpace()
    space.gravity = gravity
    
    [..]
    
    proc shapeIter(shape: Shape, data: pointer) {.cdecl.} =
        if shape.shapeType == ShapeType.circle:
          let circle = shape as CircleShape
          drawCircle(circle.body.position, circle.radius, circle.body.angle, kColorBlack)
        elif shape.shapeType == ShapeType.segment:
          let segment = shape as SegmentShape
          drawSegment(segment, kColorBlack)
        elif shape.shapeType == ShapeType.poly:
          let poly = shape as PolyShape
          for i in 0 ..< poly.count:
            let a = poly.getVert(i)
            let b = poly.getVert((i + 1) % poly.count)
            playdate.graphics.drawLine(a.x.toInt, a.y.toInt, b.x.toInt, b.y.toInt, 1, kColorBlack);
    
    proc drawChipmunkHello*() =
      # iterate over all shapes in the space
      eachShape(space, shapeIter, nil)
    

    As you can see we've now split the body of the iterator body into a new procedure shapeIter which matches the signature of SpaceShapeIteratorFunc and we pass this along to eachShape together with a nil pointer because we don't need to pass any context. The reason we don't have to pass any context is because all the variables required live in the global scope of our module. If we indeed needed some context to be passed along into the body of the iterator we would need to pass this via the data: pointer field. This is quite common in C, but can be a bit cumbersome to do in Nim. This is a bit more of an advanced topic, but you could use a Nim closure for this and get the procedure and environment pointers with rawProc and rawEnv respectively. Note that rawProc still has a {.nimcall.} calling convention, so a caller helper proc would be required. All of this complexity could be put into a custom for loop macro at which point you would be able to call the iterator like you did in your first example. But this is getting into some quite complex territory.