Last active
August 8, 2016 07:24
-
-
Save PhilipWitte/c14a4913e2fbc3704643 to your computer and use it in GitHub Desktop.
"Brim" - Nim version of maikklein's 'Breeze' ECS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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