Skip to content

Instantly share code, notes, and snippets.

@PhilipWitte
Last active August 8, 2016 07:24
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PhilipWitte/c14a4913e2fbc3704643 to your computer and use it in GitHub Desktop.
Save PhilipWitte/c14a4913e2fbc3704643 to your computer and use it in GitHub Desktop.
"Brim" - Nim version of maikklein's 'Breeze' ECS
import brim
# ---
type
Position = object
x, y: float
Velocity = object
x, y: float
# ---
proc printName(name:string) =
echo "Name: ", name
proc printPosVel(pos:Position, vel:Velocity) =
echo "Position: ", pos, ", Velocity: ", vel
# ---
proc makeMonster(x,y:float, name:string): auto =
return (Position(x:x, y:y), Velocity(), name)
# ---
proc main =
type
Entities = componentGroup(Position, Velocity, string)
Movables = componentGroup(Position, Velocity)
var
world: (Entities, Movables)
# setup
world.add(makeMonster(10, 5, "Monster3"))
world.add(makeMonster(11, 5, "Monster2"))
world.add(Position(x:42, y:24), Velocity(x:5, y:5), "Monster1")
world.add(Position(), Velocity(x:10, y:5)) # Movable Object
# update
world.call(printName)
world.call(printPosVel)
# cleanup
world.clear()
# ---
main()
# prints...
# Name: Monster3
# Name: Monster2
# Name: Monster1
# Position: (x: 10.0, y: 5.0), Velocity: (x: 0.0, y: 0.0)
# Position: (x: 11.0, y: 5.0), Velocity: (x: 0.0, y: 0.0)
# Position: (x: 42.0, y: 24.0), Velocity: (x: 5.0, y: 5.0)
# Position: (x: 0.0, y: 0.0), Velocity: (x: 10.0, y: 5.0)
import macros
# --- --- --- #
type
SomeWorld* = concept world
# a World is any tuple..
world is tuple
# ..who's fields are ComponentGroup..
for field in world.fields:
field is ComponentGroup
SomeGroup* = concept group
# a Group is any tuple..
group is tuple
# ..who's fields are ptr types..
for field in group.fields:
field is ptr; # XXX: `;` needed for bug workaround
ComponentGroup*[T:SomeGroup] = object
size: int
count: int
comps: T
# ---
macro componentGroup*(comps:varargs[typed]): untyped =
## Makes a ComponentGroup type who's "comps" are a tuple of `ptr` type of each parameter.
## Eg, `componentGroup(int, float)` produces `ComponentGroup[(ptr int, ptr float)]`
assert comps.len > 0
let par = newNimNode(nnkPar)
for c in comps:
assert getType(c).typeKind == ntyTypeDesc
par.add newNimNode(nnkPtrTy).add(c)
return newNimNode(nnkBracketExpr).add(bindSym("ComponentGroup"), par)
# ---
proc grow*(group:var ComponentGroup, amount:int) =
## Grows each component array in `group` by and arbitrary amount.
let oldSize = group.size
let oldCount = group.count
let newCount = oldCount + amount
group.count = newCount
if newCount >= oldSize:
let newSize = newCount * 2 # grow by 2x
group.size = newSize
if oldSize == 0:
for comp in group.comps.fields:
comp = type(comp[]).create(newSize)
else:
for comp in group.comps.fields:
comp = comp.resize(newSize)
proc clear*(group:var ComponentGroup) =
## Deletes all component arrays in `group` and resets the count.
group.size = 0
group.count = 0
for comp in group.comps.fields:
comp = comp.resize(0)
proc clear*(world:var SomeWorld) =
## Clears all component groups in `world`.
for group in world.fields:
group.clear()
# ---
template component*(group:ComponentGroup, compIndex:static[int], index:int): auto =
## Gets the comonent instance at `index` of `group`'s component array `compIndex`.
let comp = group.comps[compIndex]
let offset = index * sizeof(type comp[])
let address = cast[int](comp)
cast[type comp](address + offset)[]
proc size*(group:ComponentGroup): int {.inline.} = group.size
proc count*(group:ComponentGroup): int {.inline.} = group.count
# --- --- --- #
template quoteExpr(body): NimNode = quote(body)[0]
# ---
proc groupCompsNode(compGroup:NimNode): NimNode =
# TODO: ensure is `ComponentGroup` node
const recsAstIndex = 2
const compsAstIndex = 2
let groupType = getType(compGroup)
assert groupType.kind == nnkObjectTy
let groupRecs = groupType[recsAstIndex]
assert groupRecs.len == 3
return getType(groupRecs[compsAstIndex])
proc compNode(groupComps:NimNode): NimNode =
# TODO: ensure is `ptr[type]` node
assert groupComps.kind == nnkBracketExpr
assert groupComps.len == 2
return groupComps[1]
iterator groupCompsNodes(worldType:NimNode): (int, NimNode) =
for i in 1 .. <worldType.len:
yield (i - 1, worldType[i].groupCompsNode)
iterator compNodes(groupComps:NimNode): (int, NimNode) =
for i in 1 .. <groupComps.len:
yield (i - 1, groupComps[i].compNode)
iterator entityNodes(entityType:NimNode): (int, NimNode) =
for i in 1 .. <entityType.len:
yield (i - 1, entityType[i])
iterator paramNodes(eventType:NimNode): NimNode =
for i in 2 .. <eventType.len:
yield eventType[i]
# ---
macro add*(world:SomeWorld, entity:tuple): untyped =
## Copies `entity` to the appropriate group in `world`.
let worldType = getType(world)
let entityType = getType(entity)
assert worldType.len > 1 # ensure has 1+ components
assert entityType.len > 1 # ensure has 1+ components
result = newStmtList()
for gi, groupComps in worldType.groupCompsNodes:
# only consider groups which have the same amount of components
if entityType.len != groupComps.len: continue
# generate a potential component assignment for each world group
let assignments = newStmtList()
let groupCount = genSym(nskLet, "count")
# compare group component types against each entity component type
var isCompat = true
for ci, comp in groupComps.compNodes:
var hasComp = false
for ei, entityComp in entityType.entityNodes:
if sameType(comp, entityComp):
assignments.add quoteExpr do:
`world`[`gi`].component(`ci`, `groupCount`) = `entity`[`ei`]
hasComp = true
break
if not hasComp:
isCompat = false
break
if isCompat:
result.add quote do:
let `groupCount` = `world`[`gi`].count
`world`[`gi`].grow(1)
`assignments`
break # only add to first group found with matching component types
macro add*(world:SomeWorld, comps:varargs[typed]): untyped =
## Makes a tuple out of `comps` and calls `world.add()` with it.
let tup = newNimNode(nnkPar)
for comp in comps:
tup.add(comp)
return quoteExpr do:
`world`.add(`tup`)
# ---
macro call*(world:SomeWorld, event:proc): untyped =
## Calls `event` for each instance of `world`'s groups that have matching components.
let worldType = getType(world)
let eventType = getType(event)
assert worldType.len > 1 # ensure has 1+ components
assert eventType.len > 2 # ensure has 1+ params
result = newStmtList()
for gi, groupComps in worldType.groupCompsNodes:
# generate a potential event call for each world group
let eventCall = newCall(event)
let eventIndex = genSym(nskForVar, "index")
# compare event param types against each group component type
var isCompat = true
for param in eventType.paramNodes:
var hasParam = false
for ci, comp in groupComps.compNodes:
if sameType(param, comp):
eventCall.add quoteExpr do:
`world`[`gi`].component(`ci`, `eventIndex`)
hasParam = true
break
if not hasParam:
isCompat = false
break
if isCompat:
result.add quoteExpr do:
for `eventIndex` in 0 .. <`world`[`gi`].count:
`eventCall`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment