It's pretty much a shot in the dark. Is something like this possible:
type ImportantStuff = object
littleMember[T]: seq[T]
func get[T](a: ImportantStuff, i: int): T =
a.littleMember[T][i]
? (I know that this syntax doesn't work, but maybe there is some similar possible)
I haven't yet used macros, but I assume they can't inject code into an existing object definition, right? (Except maybe if I pass a whole program into a macro)
I am not sure, I understand this. Where exactly there is a generic object member? I want to do something like this:
var c: ImportantStuff
discard c.get[int](12)
discard c.get[float](5)
As @xigoi said, it's the object itself that is generic, not its fields. This is the correct code:
type ImportantStuff[T] = object
littleMember: seq[T]
func get[T](a: ImportantStuff[T], i: int): T =
a.littleMember[i]
var c: ImportantStuff[int]
discard c.get(12)
discard c.get(5) # you can only choose `c` as either int OR float. Both of these calls will return int now.
The code you sent doesn't work though, as you can't have different T for the same object. Also you can't have both int and float in the same seq. You would have to make an object wrapping them in that case. To help you get the behavior you are looking for, we need to know more about your use-case, why do you want to have both int and float in the same seq?
import json
let c = %*[3.14,"pi",3]
proc get[T](o:JsonNode,i:int):T =
when T is float: o[i].getFloat()
elif T is int: o[i].getInt()
elif T is string: o[i].getStr()
echo c.get[:float](0)
echo c.get[:string](1)
echo c.get[:int](2)
?So the use case is a simple entity component system. What shirlyquirk posted comes quite close to what I want, and I think something like this is what I'll use. However, this requires me to manually "register" all component types in the ComponentManager, which I wanted to avoid.
Basically I hoped that
type ImportantStuff = object
littleMember[T]: seq[T]
func get[T](a: ImportantStuff, i: int): T =
a.littleMember[T][i]
var c: ImportantStuff
discard c.get[int](12)
discard c.get[float](5)
discard c.get[ComponenTypeA](5)
would expand to
type ImportantStuff = object
littleMember_int: seq[int]
littleMember_float: seq[float]
littleMember_ComponentTypeA: seq[ComponentTypeA]
func get[T](a: ImportantStuff, i: int): T =
when T is float: a.littleMember_float[i]
elif T is int: a.littleMember_int[i]
elif T is ComponentTypeA: a.littleMember_ComponentTypeA
var c: ImportantStuff
discard c.get[int](12)
discard c.get[float](5)
discard c.get[ComponentTypeA](1)
Sadly it doesn't work out of the box but it is possible using macros to generate the ImportantStuff type and get proc automatically.
type ImportantStuff = object
littleMember_int: seq[int]
littleMember_float: seq[float]
littleMember_ComponentTypeA: seq[ComponentTypeA]
func get[T](a: ImportantStuff, i: int): T =
when T is float: a.littleMember_float[i]
elif T is int: a.littleMember_int[i]
elif T is ComponentTypeA: a.littleMember_ComponentTypeA[i]
could in theory be created by:
ecsMacro(ImportantStuff, int, float, ComponentA)
I don't have the time right now, but unless someone else does it before me I could try and write an example of how the ecsMacro could be implemented later tonight.
Thanks! I tried this myself, but I got stuck, so this will definitely help understanding macros :D
Though, I think I will use this solution, as I will need the func typeId[T]() anyway. I am not sure if it is all sound, but it at least seems to work. And while it pretty inelegantly does all the stuff at runtime, it doesn't require registering component types:
func getUniqueId(): int =
{.cast(noSideEffect).}:
var idCounter {.global.} = 0
result = idCounter
idCounter += 1
func typeId[T](): int =
{.cast(noSideEffect).}:
let id {.global.} = getUniqueId()
return id
type ComponentVectors = seq[ref seq[int8]]
func get[T](componentVectors: var ComponentVectors): var seq[T] =
static: doAssert (ref seq[int8]).default == nil
let id = typeId[T]()
if componentVectors.len <= id:
componentVectors.setLen(id + 1)
if componentVectors[id] == nil:
componentVectors[id] = new seq[int8]
assert componentVectors[id] != nil
cast[ref seq[T]](componentVectors[id])[]
func get[T](componentVectors: ComponentVectors): seq[T] =
let id = typeId[T]()
if componentVectors.len > id and componentVectors[id] != nil:
return cast[ref seq[T]](componentVectors[id])[]
var c: ComponentVectors
c.get[:float]().add(0.5)
c.get[:int]().add(1234)
c.get[:float]().add(0.6)
echo c.get[:float]()
echo c.get[:int]()
You do you and if it works, nice! But I personally wouldn't use casting when safer alternatives are available though, but you do you :)
Also something I'm not understanding:
func getUniqueId(): int =
{.cast(noSideEffect).}:
var idCounter {.global.} = 0
result = idCounter
idCounter += 1
Why not just make it a proc instead of pretending it's a func?
Well, the point of keeping track of side-effects is that you keep track of them... It's the "leaf functions" (functions that doesn't depend on other functions) that should be funcs, and then the rest of the functions can be funcs or procs depending on what they actually are. For example the main function should be a proc but it can use funcs. Having everything be a func defeats the point of using them at all IMHO. But sure, if there is some silly case you can use it, but as I said, using func for absolutely everything defeats the point of using it in the first place if you only have to "cheat" the system all the time.
Nice solution you found though! Seems solid :D
"using func for absolutely everything defeats the point of using it in the first place"
My idea is that I normally never want side effects (so no procs). Everything that is necessary should be passed into the function, where I can immediately see what a specific function can affect.
But sometimes it is just not possible to do this, but at the same time it is clear that the side effects are harmless or very easy to reason about. Then I don't want to propagate proc through all calling functions, so I "cheat" the system, similar to how the unsafe block in Rust should be used.