Traversing object tree

edited May 2013 in Help request
So, I took an example of the resources project, but I want to traverse objects in the [scene] during initialization and assign UserData based on object's in-game type.

I came up with function to inspect every object in the scene. Is it the appropriate method?
void traverseScene(orxOBJECT *child) {
    orxOBJECT *sibling;
    while ((child = orxObject_GetOwnedChild(child))) {
        orxLOG("%s: child name", orxObject_GetName(child));
        sibling = child;
        while ((sibling = orxObject_GetOwnedSibling(sibling))) {
            orxLOG("%s: sibling name", orxObject_GetName(sibling));
            traverseScene(sibling);
        }
    }
}

It seems to work and list all of the objects I expect to see.

Comments

  • edited September 2013
    This looks fine for me, but please note a couple of things:

    - This will only traverse the objects that are children of the Scene node, you might have objects that are disconnected from that hierarchy.
    However orx does bookkeeping on all the orxSTRUCTURE derivative that are created, if you want to iterate through all the orxOBJECTS, you can do so like this:
    for (orxOBJECT *pstObject = orxOBJECT(orxStructure_GetFirst(orxSTRUCTURE_ID_OBJECT));
         pstObject != orxNULL;
         pstObject = orxOBJECT(orxStructure_GetNext(pstObject)))
    

    - Objects that have been created as part of the Scene hierarchy can decide to exclude themselves from that hierarchy at any point (usually done for permanent objects, UI objects, etc...)

    - You might want to look at Scroll, which is a thin C++ layer on top of orx.

    Scroll brings a few interesting features such as an in-game level editor + load/save capacity, firect binding of C++ classes to config section, etc.

    Faistoiplaisir started a forum thread a while ago on this topic.
    There are also other threads related to Scroll.
    Acksys wrote a couple of first tutorials about Scroll.
    You can look at a couple of game jam entries I wrote in the past, all using orx+Scroll, the code might be slightly outdated but the logic should still be valid:
    - Mushroom Stew
    - Breaktris

    I always use Scroll myself for my own projects. :)
  • edited May 2013
    So, if one was to traverse the scene by going through all the objects what is the best way to do a comparison to what we're looking for?

    Just keep doing strcmp() on names until there is a match, or is there a better way?
  • edited May 2013
    I think iarwain will say that you should check scroll and you should. I am in the process of doing so.

    In my pure C experiment I used a different strategy.

    In Init function I traverse object structure and build user data structure based on the name of the Config section. I push config section based on the object name. I have custom keys that start with G (stands for game). So, I have GType for type of the object and other properties. Based on the custom options I construct user structure. Once user structure is built it describes game object properties, so code at runtime does not have to make string comparisons.
    typedef struct gData_t {
        const orxSTRING team;
        const orxSTRING type;
        gMovable *movable;
        gWeapon *weapon;
    } gData;
    
        orxConfig_PushSection(orxObject_GetName(obj));
        if (orxConfig_HasValue("GType")) {
            data = malloc(sizeof(gData)); // create user data
            ...
            data->type = orxConfig_GetString("GType");
            if (orxString_Compare("tank", data->type) == 0) {
                data = malloc(sizeof(gData));
                data->movable = malloc(sizeof(gMovable));
                data->movable->targetIsSet = orxFALSE;
                ...
    ....
        if (data) {
            orxObject_SetUserData(obj, data);
        }
    

    From code above you can see that I use orxString_Compare to compare strings and then I set user data in the end.

    So, in my case "tank" name is a game type. I don't have unique tanks. Just tank type and team it belongs to. So, I don't really care for the object name except for the way of getting to configuration section.

    So, during object update I can do the following:
    void UpdateObject(orxOBJECT *obj)
    {
        gData *data = orxObject_GetUserData(obj);
        if (data && data->movable) {
            gMovable *movable = data->movable;
            if (movable->targetIsSet) {
    

    I kind of really enjoyed this kind of model.

    Now I need to see how it can be done with C++ Scroll model.
  • edited May 2013
    Is it still with the goal of setting a UserData?

    Objects all have a unique GUID, but in this case that won't help you much. All you know of your object being the name, you'd have to do a string compare.

    A better approach would be to attach your data when your object is created, by listening to the orxOBJECT_EVENT_CREATE event.

    The best approach, finally, would be to use Scroll which does all that (and more) behind the scene. ;)
  • edited May 2013
    Ah, kulak answered faster than me! :)

    Well, kulak, you will have a very similar feature with Scroll, except it will be done behind the scene.

    In Scroll you bind C++ classes to config section. That binding is bi-directional.
    If you create an instance of a C++ class that was has such a binding, the matching orxOBJECT will be created as well and its UserData will contain a pointer to your C++ class.
    Reciprocally, if you create an orxOBJECT that was bound to a C++ class, the C++ object will get created as well, and the same UserData link will be made.
    Upon destruction, everything will get cleaned nicely, as it should.

    Lastly, Scroll supports inheritance in its bindings.

    Let's say you have a C++ class named MyObject, which inherits from ScrollObject, and you bind it to the config section Thing. If you have:
    [Thing]
    ...
    
    [[email protected]]
    ...
    
    [[email protected]]
    ...
    

    Table and Chair are automatically bound to the C++ class MyObject as well. You can, of course, override that binding if need be. Bindings are defined when Scroll gets initialized and shouldn't be modified later on.

    Last thing: using Scroll means you can't access UserData for storing your own structure anymore, but as you now have a base class (ScrollObject) and as many variants as you want (all the classes you define that derive from it).

    Now let's say that you don't want to hardcode all the bindings in your code, you can have a similar approach to your GType: iterate through all the config sections, when a section has the GType key, gets its value, depending on its content you can issue a binding of this section to whichever C++ class you want.

    Lemme know if you have any questions. :)
  • edited May 2013
    I'm building up the scene hierarchy by setting parents which have not been set in the ini, so I need to locate local root objects. Ok, strcmp it is, this is an init-only operation so hopefully it won't be too bad.

    About Scroll, I have certainly looked at it, but I'm approaching matters one thing at a time. First pure orx, then depending on what works and what doesn't...
  • edited May 2013
    Sounds like a reasonable approach.

    Why don't you have the whole object scene hierarchy coming from config though?
  • edited May 2013
    I have a variable sized map, which is dynamically generated
  • edited May 2013
    I see, how do you reconstruct it then? By using pre-defined anchors?
  • edited May 2013
    The size is set during map construction and stays constant. For every tile, I create from config, set properties (such as position) based on the generation algorithm and assign to parent object (which is located by name)
  • edited May 2013
    Is that parent object unique? Or at least unique "per name", as in only one instance of name Parent1, one of name ParentX, etc...

    Do you store the name somewhere in the tile config or is it hardcoded?

    I'm asking all those questions as I feel there's probably a way to bypass the string compare by keeping a collection of GUIDs instead.
  • edited May 2013
    The parent object is unique (eg TileRootObject) and is defined in the ini. I suppose I could look for it during object created event, but then I'll still end up doing one strcmp per object. If I find myself needing to do this more often than once, I'll stash an orxOBJECT* somewhere. Or would it better to keep the id rather than the pointer?
  • edited May 2013
    btw, what is the difference between owner and parent?
  • edited May 2013
    If you have a unique parent, here's what you can do:

    In config:
    [TileRootObject]
    TrackList = [TileRootObjectTrack]
    
    [TileRootObjectTrack]
    0 = > Object.GetID ^ # Config.SetValue RunTime Root <
    

    In code:
    // Before creating your tiles
    orxConfig_PushSection("RunTime");
    orxOBJECT *pstRootTile = orxOBJECT(orxStructure_Get(orxConfig_GetU64("Root")));
    orxConfig_PopSection();
    
    // When handling orxOBJECT_EVENT_CREATE
    orxOBJECT *pstObject = orxOBJECT(_pstEvent->hSender);
    if(<IsTile>(pstObject))
    {
      orxObject_SetParent(pstObject, pstRootTile);
    
      // Possibly ownership too:
      orxObject_SetOwner(pstObject, pstRootTile);
    }
    

    Parent: always refer to the scene graph, ie. physical relationship in the world defined by the tree of orxFRAMES.
    Owner: same meaning as allocation ownership: if an owner gets deleted, all his children will get deleted as well.

    Often an object will have the same other object as both parent and owner, but it's not mandatory as the concepts are completely unrelated.

    Let's take an example: let's say you can throw sticky explosive, but if you die, they should disappear as well. When one of those stickies hits an enemy, the enemy will become its parent, whereas the player still remains its owner.
  • edited May 2013
    Quick addendum: storing a pointer is faster, but if the pointed structure gets deleted, then you end up with a dangling pointer, or worse another structure that got allocated at the same memory location, sneaky!

    Storing a GUID makes it less efficient for retrieval, but if the matching structure doesn't exist anymore or isn't the one that you originally referenced, orxStructure_Get(GUID) will return orxNULL.

    You can also increase the reference counter of the structure when storing its pointer to make sure it doesn't get deleted, however you'll have then to make sure not to forget to decrease it when you're done (otherwise that object will never get deleted). You might also disrupt the workflow when the rightful owner tries to delete that structure as you'll then become the implicit owner.
  • edited May 2013
    iarwain, thank you for the explanation, it is excellent as always. I really liked the bit about the sticky grenades :laugh: I got the scenegraph working the "dumb" way last night, but let's see if I can upgrade it using this method.

    I've looked through the ini docs, but I don't seem to be able to find a description of the syntax used here:
    [TileRootObjectTrack]
    0 = > Object.GetID ^ # Config.SetValue RunTime Root <
    

    The current description for a timeline track is:
    [Float] = "Your timeline event text here" | "Your command"
    

    Some questions:
    - >< must be begin-command, end-command, # is the separator, but what is ^ ?
    - Is there a list of available commands (such as Object.GetID, Config.SetValue) and the parameters they take?
  • edited May 2013
    Yes, commands are a separate beast that got added toward the end of last year. I actually wrote a (hopefully) decent introductory post on the orx-dev group.

    Your questions should find answer there. :)

    However, a quick recap (it's also in the original timeline & command forum post):
    - > means push the result of the command on the internal stack
    - < means retrieve the last pushed value from the stack and use it as a command argument
    - ^ is a special keyword that only works for commands executed from a timeline: it'll get dynamically replaced by the GUID of the object on which the timeline/command is executed.

    Commands can be used from timelines as well as from the interactive console (default toggle key: ` [backquote]).
    The console is becoming quite powerful, allowing to easily change some aspects of the game, creating/modifying objects on the fly, finding texture in memory and saving them on disk, etc...)

    All the available commands (and aliases) can be listed from within the console using... a command! ;) (Command.ListCommands / Command.ListAliases).

    Ah, and of course commands can be added at runtime by the users for their own needs. :)

    As always, I recommend people who want to follow new features and/or discuss them before implementation to subscribe to that group.
  • edited May 2013
    Here's my version of the tree traversal.
    This one is depth-first, has indentations and prints the root node.
    void PrintTree( orxOBJECT* _root, orxU32 _uiDepth/*=0*/ )
    {
        orxLOG("%*s", _uiDepth+strlen(orxObject_GetName(_root)),
                      orxObject_GetName(_root));
        if ( _root = orxObject_GetOwnedChild(_root) ) {
            _uiDepth += 2;
            PrintTree(_root, _uiDepth);
            while ( _root = orxObject_GetOwnedSibling(_root) ) {
                PrintTree(_root, _uiDepth);
            }
         }
    }
    
  • edited May 2013
    Looks good to me.
    Since 2 days ago, if you wish to strictly traverse the scene graph, you can use orxObject_GetChild()/orxObject_GetSibling() instead of the Owned version, for the differences mentioned in the sticky grenade example. ;)
  • edited May 2013
    I am not getting something.

    Why

    _uiDepth += 2;

    and not

    _uiDepth += 1;


    What does depth + length do?
    _uiDepth+strlen(orxObject_GetName(_root))
  • edited May 2013
    depth controls printing indentation, not actual recursion depth (although it is related. by a factor of 2)

    _uiDepth+strlen(orxObject_GetName(_root)) sets the printf padding
  • edited May 2013
    I'm having some trouble converting from orxObject_GetOwned* to orxObject_Get*. The former returns orxOBJECT*, while the latter returns orxSTRUCTURE*. Simply enclosing orxSTRUCTURE* with an orxOBJECT(), does not help. Am I doing the structure->object conversion wrong or is there a bigger issue at play?
  • edited May 2013
    The hierarchy accessors (GetParent, GetChild, GetSibling) return orxSTRUCTUREs as they can be something else than orxOBJECTs.

    As you've already seen, in the hierarchy we can reference orxCAMERAs as well but also orxSPAWNERs.

    You can check which kind of structure it is with orxStructure_GetID(), and the orxOBJECT() caster will filter anything that is not an orxOBJECT.

    If you are sure of your hierarchy (set via ChildList and calls to SetParent(), not SetOwner()) and it still doesn't work as it should, it might be a bug. :)
Sign In or Register to comment.