Game Development Community

Nested object creation

by Daniel Buckmaster · in General Discussion · 08/17/2010 (11:53 pm) · 11 replies

This seems to be a pretty weird question - but can anyone detail how SimGroups know to add objects to themselves that are created inside their script constructors? For example:
new SimGroup(Group1) {
   new SimGroup(Group2) {};
};
Group2 is added to Group1 automatically.

Is there some sort of hook for this so I can apply this behaviour to other object types? I've successfully gotten my new object class (SceneObject-derived, not SimGroup) to write out its children when saved (so, I have a mission file with the nested object structure as above), but I'm at a loss as to how to restore the proper parent-child relationships when the mission is loaded.

About the author

Studying mechatronic engineering and computer science at the University of Sydney. Game development is probably my most time-consuming hobby!


#1
08/18/2010 (12:25 am)

This is handled within the TorqueScript VM (compiledEval), through the OP_ADD_OBJECT instructions generated for the object definitions.
#2
08/18/2010 (1:30 am)
Aha, cheers. So I've found the bit where the adding happens, but it seems like a bad idea to hijack these inner workings of the engine for a new object class.

Any chance that inheriting from SimGroup would give me the behaviour I wanted? Only trouble is, my object class needs to inherit from SceneObject. Last time I tried inheriting from both of these, I got errors which made no sense to me.

(To explain a bit more: I'm working on hierarchical AI navigation graphs. So each waypoint is an object in the world, but also can contain other waypoints as children.)

Failing this approach, I guess it's always an option to script my own file format for saving navigation graphs. I just prefer having one file for a mission.
(On the other hand, we already have a .mis file, a .ter file, and a lightmap file... adding a navigation file isn't that ridiculous a proposition...)
#3
08/18/2010 (1:42 am)

Inheriting from both SceneObject and SimGroup won't work since that splits the inheritance hierarchy and inherits from SimObject twice (the inheritance chains there aren't declared "virtual").

You could solve the problem through adding abstract interfaces and switching the TorqueScript VM to it and away from using the concrete SimSet/SimGroup classes in the relevant places, but that would be a bit of work and your class would have to reimplement a couple of things.
#4
08/18/2010 (2:08 am)
Quote:(the inheritance chains there aren't declared "virtual")
Okay, I figured this was some issue with Torque, since from what I've read of MI, diamond structures should be possible. If not desirable :P.

Quote:You could solve the problem through adding abstract interfaces
Attractive, but I'm fairly sure I'm not qualified to make such a sweeping change to Torque's internals. Though it wouldn't be *all* that sweeping. Hmm. It does seem like overkill, but it's an all-round more elegant solution to the problem than saving out a new file and loading it on mission load.

What the heck. Let's try it!
#5
08/18/2010 (2:15 am)
Quote:Okay, I figured this was some issue with Torque, since from what I've read of MI, diamond structures should be possible. If not desirable :P.

It's not really an issue with Torque, though. Actually, this is a problem with C++. C++ multiple inheritance is borked. It works where you have one baseline of concrete classes and then a set of interface-like classes (or classes with some mixin functionality). However, beyond that, it really breaks.

The concept of "virtual" base classes is totally borked. It creates all kinds of problems and the impact on the generated code is pretty drastic.

Quote:hough it wouldn't be *all* that sweeping.

Yep, it'd be some pretty localized changes.

Quote:What the heck. Let's try it!

Awesome.
#6
08/18/2010 (2:28 am)
Thanks for the info! Good to know for the future. Amusingly though, my Waypoint class now has multiple inheritance up the wazoo. I wrote my pathfinding to be completely generic, so Waypoint is already inheriting from an abstract AStarNode class - and as well as SceneObject and now my new object container interface. Ah well.
#7
08/18/2010 (2:34 am)
Quote:Waypoint is already inheriting from an abstract AStarNode class - and as well as SceneObject and now my new object container interface.

That's just fine. You have your baseline through SceneObject and then a set of abstract interfaces (they don't need to be purely abstract they just must be separate from the baseline, that's all). That works very well.
#8
08/18/2010 (2:47 am)
I see - I didn't think there was anything particularly wrong with it, but... well, you never know. The idea of having a 'baseline' and interfaces makes sense.

Well, the changes turned out to be hilariously simple, at least up to this point. In simBase.h:
// Abstract base class for SimSets and SimGroups
class SimContainer
{
public:
	virtual void addObject(SimObject* obj)=0;
	virtual void removeObject(SimObject* obj)=0;

   virtual void pushObject(SimObject*)=0;
   virtual void popObject()=0;

	virtual SimObject *findObject(const char *name)=0;

   virtual SimObject* front()=0;
   virtual SimObject* first()=0;
   virtual SimObject* last()=0;
   virtual bool       empty()=0;
   virtual S32        size()=0;
};

It's not a complete interface, but it works for my purposes. Then I made SimSet inherit from SimContainer and removed the const from size().

And in compiledEval.cc, I changed the references to SimGroup and SimSet in the OP_ADD_OBJECT block to SimContainer. Interestingly, there's now no need to have the separate references for Sets and Groups, since they're both found under the broader Container class.

Now to add SimContainer to Waypoint and see if it actually *does* work as intended.
#9
08/18/2010 (2:53 am)
Quote:And in compiledEval.cc, I changed the references to SimGroup and SimSet in the OP_ADD_OBJECT block to SimContainer. Interestingly, there's now no need to have the separate references for Sets and Groups, since they're both found under the broader Container class.

Be careful there. There is an important distinction that compiledEval makes. If the outer object is only a SimSet, the object is added to RootGroup anyway. If it's a SimGroup, it's only added to the outer object.

SimGroups are how TorqueScript does lifetime management so it's important to preserve this distinction.
#10
08/18/2010 (7:59 am)
Aha, thanks for that. I didn't catch that subtlety. At the moment, this is what I've got in compiledEval:
// What group will we be added to, if any?
            U32 groupAddId = intStack[UINT];
            SimContainer *grp = NULL;
            SimContainer *set = NULL;

            if(!placeAtRoot || !currentNewObject->getGroup())
            {
               if(placeAtRoot)
               {
                  // Deal with the instantGroup if we're being put at the root.
                  const char *addGroupName = Con::getVariable("instantGroup");
                  if(!Sim::findObject(addGroupName, grp))
                     Sim::findObject(RootGroupId, grp);
               }
               else
               {
                  // Otherwise just add to the requested group or set.
                  Sim::findObject(groupAddId, set);
               }

               // If we didn't get a group, then make sure we have a pointer to
               // the rootgroup.
               if(!grp)
                  Sim::findObject(RootGroupId, grp);

               // add to the parent group
               grp->addObject(currentNewObject);

               // add to any set we might be in
               if(set)
                  set->addObject(currentNewObject);
            }
I believe this will preserve cases when objects are added to a group and a set - if 'set' actually happens to point to a SimGroup, then when the object is added to 'set', it will be removed from 'grp' automatically.




EDIT: Success! My initial implementation seems to be working cheerfully. The waypoint graph saves as a nested hierarchy of objects (which obviously required implementing Waypoint::write to be recursive on its children), and loads the same way! Now I just need to deal with neighbour links :P.

EDIT: After a little fiddling with packUpdate, neighbour networks are now loading up properly! Now, onwards to actually getting a path from the network.

EDIT: The saga continues :P. Turns out the client-side objects are building a neighbour network just fine, but not so on the server. If I manually go into the editor and move a node around, their neighbour update routine is called and I can get a path between them. But I can't figure out adding neighbours upon creation. I make a call to the neighbour update function when a waypoint's parent is changed, but it seems like at the point this is happening (during mission load), gServerContainer isn't in a mood to take radius searches.
#11
10/10/2010 (9:00 pm)
So the pathfinding is working more-or-less (except for not at all in a release build :P), but I've been thinking about applying this nested object-creation paradigm to more general situations, like ShapeBase's mounting. I've always thought it'd be great to be able to mount items in the world editor without having to go into the console and hook them up by hand. It'd also be nice if those relationships could be saved (not even so much for the world editor... but think of quick-saves as well!).

Proposal: exposing the MountInfo struct to script control (i.e., setting mMount.node by hand instead of going through mount methods), then implementing 'mount object' as adding an object to another object which is a SimContainer. The parameters set in the MountInfo struct then define how the object behaves relative to its parent. They can also be saved out with the other script-exposed fields.

I've been thinking about redoing the object mounting paradigm for a while now... I don't necessarily want to genericise it to the level of attaching SceneObjects to each other with arbitrary transforms, but at least defining an arbitrary 'mount from' node and 'mount to' node would be nice.

EDIT: Further benefits include being able to abstract guiTreeViewCtrl to use SimContainer (or as I've renamed it, SimSetInterface... Container class already exists so better avoid confusion :P) so that in the editor, you can drag-and-drop objects in the inspector tree to mount them to each other. And then select the objects and play with their mMount.node names to get them mounted to the right spot, etc. Sounds like a win for level-building. Sort of.