I've been building my game engine in Nim for quite a while and it's coming along well. Last night it occurred to me that I could replace the component system using Nim concepts instead.
I don't have a specific question, I'm just curious what you guys think of this as I've almost never used concepts before. Is this a terrible idea?
The behaviour is defined in distinct modules:
movement.nim
type
Positionable = concept p
p.position is Vec3
p.direction is Vec3
Movable = concept m, var mv
m is Positionable
m.velocity is Vec3
IsGrounded = concept g
g.heightOffTheFloor is float32
template implementsPositionable* {.dirty.} =
position: Vec3
direction: Vec3
template implementsMovable* {.dirty.} =
position: Vec3
direction: Vec3
velocity: Vec3
template implementsGrounded* {.dirty.} =
heightOffTheFloor: float32
proc applyInputs[T: Movable](m: var T, vel: Vec3) =
m.velocity = vel
proc applyMovement[M: Movable](m: var M, dt: float) =
m.position += m.velocity * dt
proc applyGrounding*[T: Positionable and IsGrounded](entity: var T, bvh: Node) =
entity.position = bvh.getHeight(entity.position) + [0'f32, entity.heightOffTheFloor, 0].Vec3
And then I have an entity macro that allows me to create mixins of sorts:
import std/macros
import ../navigation/movement
import ../animation/state_machine
{.experimental: "dynamicBindSym".}
macro entity*(name: untyped, body: untyped): untyped =
let fields = newNimNode(nnkRecList)
for item in body:
case item.kind
of nnkIdent, nnkSym: # Behaviour name (implementsMovable)
let sym = bindSym(item.strVal)
let impl = sym.getImpl()
let templateBody = impl[^1]
for field in templateBody:
if field.kind == nnkCall:
fields.add newIdentDefs(postfix(field[0], "*"), field[1][0])
of nnkCall: # field: Type
fields.add newIdentDefs(postfix(item[0], "*"), item[1][0])
else:
error "Unexpected node in entity body: " & $item.kind, item
let objTy = newNimNode(nnkObjectTy)
objTy.add newEmptyNode()
objTy.add newEmptyNode()
objTy.add fields
let typeDef = newNimNode(nnkTypeDef)
typeDef.add postfix(name, "*")
typeDef.add newEmptyNode()
typeDef.add objTy
let typeSection = newNimNode(nnkTypeSection)
typeSection.add typeDef
result = typeSection
# echo result.repr
And the implementation of the concepts can be defined just like a type with the templates from the relevant behaviour modules:
entity Character:
implementsMovable
implementsGrounded
someOtherField: int