Skip to content

Instantly share code, notes, and snippets.

@fowlmouth
Last active December 16, 2015 23:49
Show Gist options
  • Save fowlmouth/5516425 to your computer and use it in GitHub Desktop.
Save fowlmouth/5516425 to your computer and use it in GitHub Desktop.
nimrod component/entity system
## entitty has been moved to the fowltek package
Unicast macro result:
proc die*(entity: var TEntity) {..} =
echo "message ID is ", messageID("die")
echo "vtable is $# len" % $ len(entity.typeInfo.vtable)
if not entity.typeInfo.vtable[messageID("die")].isNil:
cast[proc (entity: var TEntity) {.noConv.}](entity.typeInfo.vtable[
messageID("die")])(entity)
Unicast macro result:
proc takeDamage*(entity: var TEntity; amount: int) {..} =
echo "message ID is ", messageID("takeDamage")
echo "vtable is $# len" % $ len(entity.typeInfo.vtable)
if not entity.typeInfo.vtable[messageID("takeDamage")].isNil:
cast[proc (entity: var TEntity; amount: int) {.noConv.}](entity.typeInfo.vtable[
messageID("takeDamage")])(entity, amount)
block:
let msg_id = MessageID("debugStr")
let comp = componentInfo(THealth)
if messageTypes[msg_id]:
if comp.multicast_messages.hasKey(msg_id):
echo "Overriding the implementation of multicast message `debugStr` for ",
comp.name
comp.multicast_messages[msg_id] = cast[pointer](proc (entity: var TEntity;
result: var seq[string]) =
result.add "$#: $#".format(ComponentInfo(THealth).name, entity[THealth]))
else:
if comp.unicast_messages.hasKey(msg_id):
echo "Overriding implementation of unicast message debugStr for ",
comp.name
comp.unicast_messages[msg_id] = (weight: 0, func: cast[pointer](proc (
entity: var TEntity; result: var seq[string]) =
result.add "$#: $#".format(ComponentInfo(THealth).name, entity[THealth])))
block:
let msg_id = MessageID("debugStr")
let comp = componentInfo(TPos)
if messageTypes[msg_id]:
if comp.multicast_messages.hasKey(msg_id):
echo "Overriding the implementation of multicast message `debugStr` for ",
comp.name
comp.multicast_messages[msg_id] = cast[pointer](proc (entity: var TEntity;
result: var seq[string]) =
result.add "$#: $#".format(ComponentInfo(TPos).name, entity[TPos]))
else:
if comp.unicast_messages.hasKey(msg_id):
echo "Overriding implementation of unicast message debugStr for ",
comp.name
comp.unicast_messages[msg_id] = (weight: 0, func: cast[pointer](proc (
entity: var TEntity; result: var seq[string]) =
result.add "$#: $#".format(ComponentInfo(TPos).name, entity[TPos])))
block:
let msg_id = MessageID("debugStr")
let comp = componentInfo(TVel)
if messageTypes[msg_id]:
if comp.multicast_messages.hasKey(msg_id):
echo "Overriding the implementation of multicast message `debugStr` for ",
comp.name
comp.multicast_messages[msg_id] = cast[pointer](proc (entity: var TEntity;
result: var seq[string]) =
result.add "$#: $#".format(ComponentInfo(TVel).name, entity[TVel]))
else:
if comp.unicast_messages.hasKey(msg_id):
echo "Overriding implementation of unicast message debugStr for ",
comp.name
comp.unicast_messages[msg_id] = (weight: 0, func: cast[pointer](proc (
entity: var TEntity; result: var seq[string]) =
result.add "$#: $#".format(ComponentInfo(TVel).name, entity[TVel])))
block:
let msg_id = MessageID("debugStr")
let comp = componentInfo(TSpriteInstance)
if messageTypes[msg_id]:
if comp.multicast_messages.hasKey(msg_id):
echo "Overriding the implementation of multicast message `debugStr` for ",
comp.name
comp.multicast_messages[msg_id] = cast[pointer](proc (entity: var TEntity;
result: var seq[string]) =
result.add "$#: $#".format(ComponentInfo(TSpriteInstance).name,
entity[TSpriteInstance]))
else:
if comp.unicast_messages.hasKey(msg_id):
echo "Overriding implementation of unicast message debugStr for ",
comp.name
comp.unicast_messages[msg_id] = (weight: 0, func: cast[pointer](proc (
entity: var TEntity; result: var seq[string]) =
result.add "$#: $#".format(ComponentInfo(TSpriteInstance).name,
entity[TSpriteInstance])))
block:
let msg_id = MessageID("debugStr")
let comp = componentInfo(TBoundingBox)
if messageTypes[msg_id]:
if comp.multicast_messages.hasKey(msg_id):
echo "Overriding the implementation of multicast message `debugStr` for ",
comp.name
comp.multicast_messages[msg_id] = cast[pointer](proc (entity: var TEntity;
result: var seq[string]) =
result.add "$#: $#".format(ComponentInfo(TBoundingBox).name,
entity[TBoundingBox]))
else:
if comp.unicast_messages.hasKey(msg_id):
echo "Overriding implementation of unicast message debugStr for ",
comp.name
comp.unicast_messages[msg_id] = (weight: 0, func: cast[pointer](proc (
entity: var TEntity; result: var seq[string]) =
result.add "$#: $#".format(ComponentInfo(TBoundingBox).name,
entity[TBoundingBox])))
block:
let msg_id = MessageID("takeDamage")
let comp = componentInfo(THealth)
if messageTypes[msg_id]:
if comp.multicast_messages.hasKey(msg_id):
echo "Overriding the implementation of multicast message `takeDamage` for ",
comp.name
comp.multicast_messages[msg_id] = cast[pointer](proc (entity: var TEntity;
amount: int) =
entity[THealth].hp -= amount
if entity[THealth].hp <= 0:
entity.die()
echo "Entity took damage, now at ", entity[THealth].hp)
else:
if comp.unicast_messages.hasKey(msg_id):
echo "Overriding implementation of unicast message takeDamage for ",
comp.name
comp.unicast_messages[msg_id] = (weight: 0, func: cast[pointer](proc (
entity: var TEntity; amount: int) =
entity[THealth].hp -= amount
if entity[THealth].hp <= 0:
entity.die()
echo "Entity took damage, now at ", entity[THealth].hp))
block:
let msg_id = MessageID("debugDraw")
let comp = componentInfo(TPos)
if messageTypes[msg_id]:
if comp.multicast_messages.hasKey(msg_id):
echo "Overriding the implementation of multicast message `debugDraw` for ",
comp.name
comp.multicast_messages[msg_id] = cast[pointer](proc (entity: var TEntity;
R: PRenderer) =
let s = $ entity[TPos]
let p = entity[TPos]
R.stringRGBA(p.x.int16, p.y.int16, s, 255, 0, 0, 255)
)
else:
if comp.unicast_messages.hasKey(msg_id):
echo "Overriding implementation of unicast message debugDraw for ",
comp.name
comp.unicast_messages[msg_id] = (weight: 0, func: cast[pointer](proc (
entity: var TEntity; R: PRenderer) =
let s = $ entity[TPos]
let p = entity[TPos]
R.stringRGBA(p.x.int16, p.y.int16, s, 255, 0, 0, 255)
))
@zah
Copy link

zah commented May 11, 2013

Multicast messages are usually void, but there are two common patterns for returning values from them.

One easy method is that one of the parameters serve as return value accumulator (e.g. a var sequence that the component implementations will append items to).

When the algorithm for combining the return values must be more complicated, or you want to interrupt the processing of the multicast message before reaching all components, my system allowed you to specify a "combiner" algorithm. It was very similar in mechanism to the combiners in various signals/slots libraries. See boost.signal for example:
http://www.boost.org/doc/libs/1_53_0/doc/html/signals/tutorial.html
(Search for "Signal return values")

@zah
Copy link

zah commented May 15, 2013

Nice progress, fowl. I have to study a bit more carefully what are you doing inside the macros, but I can see certain things already. You don't need to have this kind of branching inside the message dispatchers:
if not entity.typeInfo.vtable[messageID("die")].isNil:

Instead, it's smarted if you populate the vtable with a "default" implementation that raises an error or silently ignores the message. My system also allowed the user to supply his own default implementation for a given message. The default implementation should take an entity as a parameter (instead of component). To achieve this you need to use a trampoline function. Here is how it works:

  1. In the vtable, you store a function that uses offset 0.
  2. When it's called, it reads the go_this pointer that is inevitably stored at that location and then it just forwards all the parameters to the user supplied default implementation (stored in the EnityManager by MessageID).

@zah
Copy link

zah commented May 15, 2013

I've been busy this week at work, but I'll look at the problems you've reported this weekend (or sooner).

I've noticed you are not a big user of getAst or quote from the macros module. Why so? They are both nicer to look at IMO and much faster to execute by the compiler (because most of the AST crunching happens only once during the semantic analysis instead of every time in "evals").

@zah
Copy link

zah commented May 15, 2013

The same argument for branching in unicast messages applies for multicast as well.

There is no reason why the dispatched code can't be just

for entry in entity.typeInfo.multicastTable[MulticastMessageID("msgname")]:
   cast[proc_type](entity.procptr) (offset_ptr(entity.entityData, entry.offset), args... )

If there is only one implementation, it still will be faster to have a for loop over 1 element array/seq than to have a branching in the dispatcher code to detect that.

I'm a bit inconsistent in my explanations about the vtable and the multicastTable. The reason for this is that in my system there were no such distinction (I was just using C++ unions for the record types). It's easier to explain the system with two tables and it doesn't hurt the performance at all, but please note that if there are two tables, there must be also two MessageID functions: MessageID vs MulticastMessageID

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment