Sorry for how obtuse this code is - it's a minimal repro of the problem that more or less mimics my original project, and I wanted to keep some aspects even if they don't make a whole lot of sense here.
I have a series of "Thing" types and a container that stores handles to them (a reference to a sequence + an index in it). I need to be able to retrieve the things from the container (easy), but also the container from the things (requires keeping track of the container type - in reality, there could be many other types).
When retrieving the container from the thing, there's a template that takes a "thing_type" index and outputs a new variable with the container. That's where I stop understanding what's going on. I CAN print out the container type, and it's correct. But I CAN'T access any of its member fields!
Here's the code:
type
Handle[T] = object
sequence: ref seq[T]
index:int
Thing = object of RootObj
owner_type:int
owner_index:int
ThingA = object of Thing
text:string
ThingB = object of Thing
value:float
ThingContainer = object # in my original code, this could also be of Thing
name:string
a:Handle[ThingA]
b:Handle[ThingB]
# We'll keep each object in their own list
var
container_list = new seq[ThingContainer]
thingA_list = new seq[ThingA]
thingB_list = new seq[ThingB]
# Populate the lists, create associations between things and containers
for n in 0..9:
thingA_list[].add(
ThingA(
text:"I'm at index " & $n,
owner_type: 0, #<--- type 0 means "ThingContainer" (arbitrary)
owner_index: n
)
)
thingB_list[].add(
ThingB(
value:n.float,
owner_type: 0, #<--- type 0 means "ThingContainer" (arbitrary)
owner_index: n
)
)
container_list[].add(
ThingContainer(
name: "Thing Container " & $n,
a:Handle[ThingA](sequence: thingA_list, index:n),
b:Handle[ThingB](sequence: thingB_list, index:n),
)
)
# "Dereference" a handle
proc `[]` [T:Thing](h:Handle[T]):T = h.sequence[h.index]
# This is probably where the problem is...
template retrieve_owner (item:Thing, owner:untyped, body:untyped) =
if item.owner_type == 0:
let owner = container_list[item.owner_index] # item is ThingContainer
body
# Comment out these lines in the macro, and the problem below won't happen
elif item.owner_type == 1:
let owner = thingA_list[item.owner_index] # item is ThingA
body
elif item.owner_type == 2:
let owner = thingA_list[item.owner_index] # item is ThingB
body
else:
echo "item has no owner!"
echo "\n-------------------------------------------------------------------------------"
echo "Test! Retrieve a random, correctly typed owner and print its type and name"
echo "Directly from its own list:"
var container = container_list[7]
echo typeof(container)
echo container.name
echo container.a[]
echo container.b[]
echo ""
echo "-------------------------------------------------------------------------------"
echo "Now retrieved from one of its members:"
thingA_list[7].retrieve_owner(owner):
echo typeof(owner) #<------ CORRECTLY prints out "ThingContainer" as the type
echo owner.name #<------ Incorrectly tells me that "name", "a" and "b" are undeclared.
echo owner.a[] #<------ Comment out to compile
echo owner.b[] #<------ Comment out to compile
echo ""
echo "-------------------------------------------------------------------------------\n"
You'll get compiler errors in the lines that try to print owner.name, owner.a[] and owner.b[]. Comment them out and it correctly prints the type of owner! Help!
you can test for a given type with of. I also think the objects must be refs in your case:
type
Thing = ref object of RootObj
ThingA = ref object of Thing
text:string
ThingB = ref object of Thing
value:float
var things = @[ThingA(text: "AA"), ThingB(value: 13.37)]
for thing in things:
if thing of ThingA:
echo ThingA(thing).text
if thing of ThingB:
echo ThingB(thing).value
I wrote down an approximation of what gets expanded by the template, and I think I get it now....
let item = thingA_list[7]
if item.owner_type == 0:
let owner = container_list[item.owner_index] # item is ThingContainer
# body
echo typeof(owner)
echo owner
echo owner.name
echo owner.a[]
echo owner.b[]
elif item.owner_type == 1:
let owner = thingA_list[item.owner_index] # item is ThingA
# body
echo typeof(owner)
echo owner
echo owner.name #<---- Doesn't work! ThingA doesn't have this member
echo owner.a[] #<---- Doesn't work! ThingA doesn't have this member
echo owner.b[] #<---- Doesn't work! ThingA doesn't have this member
elif item.owner_type == 2:
let owner = thingA_list[item.owner_index] # item is ThingB
# body
echo typeof(owner)
echo owner
echo owner.name #<---- Doesn't work! ThingB doesn't have this member
echo owner.a[] #<---- Doesn't work! ThingB doesn't have this member
echo owner.b[] #<---- Doesn't work! ThingB doesn't have this member
I can't use "when" instead of "if" because owner_type is not known at compile time... I think I need to change my approach. Maybe something the only "acts" when the type is the one that I want - I don't think I'll need to handle disparate types at the same time.
Thanks for you thoughtful reply!
Unfortunately, won't using ref objects also cause the objects to not be stored contiguously in memory anymore? If you look at my obtuse example (sorry!) you'll see that the Container doesn't actually carry the Thing, just a handle to it (it needs to be a handle, instead of a pointer, since the sequence where the thing is can be reallocated, i.e. when deleting items)
The previous design I had was more in line with what you're suggesting, and it worked great! But performance was also over 4x slower than what I'm getting with a sequence per Thing + Statically defined Entity types (which are a type of Thing). Most of the processing can happen without going through the entities, i.e. a system can simply linearly process the data in a sequence.
I have pretty much almost of it figured out and running, the only piece of the puzzle missing is that, since hierarchies can have objects of any arbitrary type (as long as they contain a "Node"), It's hard to automatically cast a handle to a children of the current entity. Like this:
root
|
+: child:Group Type
|
+child:Camera Type
+child:Sprite type
|
+child: Sprite type
+child: Effect type
There's nothing in the "Group" type that says it can have a "Camera" child - that relationship comes from a Node component that all entities that can be in the hierarchy carry. The Node object is not generic, so that all of its instances can live in a single sequence, so its "owner" type is an int that contains the type information (one unique id per Thing type, each associated with a single sequence ).
Bottom line, every single object is stored in sequences, and entities carry handles to the index of those sequences any time there's a relationship between two Things (Entities or Components).
This one works! The only difference is this line right before the last block:
when owner is ThingContainer:
The template still automates going through each type index and returning the right thing (in reality there will be dozens of types, so automating this is a big deal), I just have to handle it on the receiving end with that "when" statement to satisfy the static typing. Good enough for me!
type
Handle[T] = object
sequence: ref seq[T]
index:int
Thing = object of RootObj
owner_type:int
owner_index:int
ThingA = object of Thing
text:string
ThingB = object of Thing
value:float
ThingContainer = object of Thing
name:string
a:Handle[ThingA]
b:Handle[ThingB]
# We'll keep each object in their own list
var
container_list = new seq[ThingContainer]
thingA_list = new seq[ThingA]
thingB_list = new seq[ThingB]
# Populate the lists, create associations between things and containers
for n in 0..9:
thingA_list[].add(
ThingA(
text:"I'm at index " & $n,
owner_type: 0, #<--- type 0 means "ThingContainer" (arbitrary)
owner_index: n
)
)
thingB_list[].add(
ThingB(
value:n.float,
owner_type: 0, #<--- type 0 means "ThingContainer" (arbitrary)
owner_index: n
)
)
container_list[].add(
ThingContainer(
name: "Thing Container " & $n,
a:Handle[ThingA](sequence: thingA_list, index:n),
b:Handle[ThingB](sequence: thingB_list, index:n),
)
)
# "Dereference" a handle
proc `[]` [T:Thing](h:Handle[T]):T = h.sequence[h.index]
template retrieve_owner (item:Thing, owner:untyped, body:untyped) =
if item.owner_type == 0:
let owner = container_list[item.owner_index] # item is ThingContainer
body
elif item.owner_type == 1:
let owner = thingA_list[item.owner_index] # item is ThingA
body
elif item.owner_type == 2:
let owner = thingA_list[item.owner_index] # item is ThingB
body
else:
echo "item has no owner!"
echo "\n-------------------------------------------------------------------------------"
echo "Test! Retrieve a random, correctly typed owner and print its type and name"
echo "Directly from its own list:"
var container = container_list[7]
echo typeof(container)
echo container
echo container.name
echo container.a[]
echo container.b[]
echo ""
echo "-------------------------------------------------------------------------------"
echo "Now retrieved from one of its members:"
thingA_list[7].retrieve_owner(owner):
when owner is ThingContainer:
echo typeof(owner)
echo owner
echo owner.name
echo owner.a[]
echo owner.b[]
echo ""
echo "-------------------------------------------------------------------------------\n"