TorqueScript/Namespaces

From TDN

Namespace-es Give Me Headache-es!

Why the title? Well, to be honest when I started to write this guide I thought, "Oh, yeah... I get that namespace thing... no problemo." Then, reality hit me in the face. I started making test cases for a project one day and found I had crossed my wires somewhere. After a few days and some help from Josh Williams (employee #9, not to be mistaken with the guy who gets eaten by the grue in episode 47...) I got my head on straight again.

For every object in Torque there is a namespace. Additionally, namespaces are chained. This means, that when the engine starts to search for something in the namespace, it begins at the entry point associated with the current object and seeks upward through all the parent's namespaces till it either finds what it is looking for or fails out. "Yes, yes", you say, "we've covered this, but how do we use this feature?" To answer that question, we'll look at some examples, starting with the simple stuff.

Object Namespace Hierarchies
When we wish to create a new function for the namespace of an object, we do something like this:

            function GameBase::doIt(%this) 
            {
                echo ("Calling StaticShape::doIt() ==> on object" SPC %this);
            };

The function 'doIt' is being declared in the 'GameBase' namespace. This means, that we can call this function on any object created from the GameBase class or its children. For example:

            %myTarget = new StaticShape(CoolTarget) 
            {
                position = "0 0 0";
                dataBlock = "SimpleTarget1";
            };
            
            %myTarget.doIt();

Assuming the tag in %myTarget is, say... 100, the above call would produce the output in the console:

            Calling StaticShape::doIt() ==> on object 100

If you are observant, you'll notice a couple of things:

  1. When we called doIt(), we did so without passing an argument explicitly, but when the console message printed, it did in fact get an argument with the value 100.
  2.                        The one argument doIt() does take is named %this.
    
Regarding #1, because we used the handle to call the function (%myTarget.doIt()), the tag (ID) of this object gets passed implicitly to the function. That said, all the following calls will produce the same result:

            %myTarget.doIt();

            StaticShape::doIt(%myTarget);
            
            CoolTarget.doIt();
            
            "CoolTarget".doIt();
            
            100.doIt()
            
            "100".doIt()
            
            StaticShape::doIt(100);

As you can see, there are various ways to call the same function, all of which are useful in different scenarios. Isn't TorqueScript great? Please note, in the cases where we use the name of the object, the name will be passed as the ID. Torque automatically does lookups for names, thus in most cases, names can be used interchangeably with IDs, as long as the names are unique.

So far so good, we've discussed the most basic use of namespaces. Now let's talk about datablock namespaces.

Simple Datablock Namespaces
As previously mentioned, datablocks are nothing more than objects themselves. They exist in the console alongside regular objects and they too have their own namespaces. For example if we wish to create a new function for ItemData namespace, we can do something like this:

            function ItemData::GetFields(%ItemDbID) 
            {    
                echo ("Calling ItemData::GetFields () ==> on object" SPC %ItemDbID);    
                echo (" catetory     =>" SPC %ItemDbID.category);    
                echo (" shapeFile    =>" SPC %ItemDbID.shapeFile);    
                echo (" mass         =>" SPC %ItemDbID.mass);    
                echo (" elasticity   =>" SPC %ItemDbID.elasticity);    
                echo (" friction     =>" SPC %ItemDbID.friction);    
                echo (" pickUpName   =>" SPC %ItemDbID.pickUpName);    
                echo (" maxInventory =>" SPC %ItemDbID.maxInventory);    
            }

The function 'GetFields' is being declared in the 'ItemData' namespace. Considering that CrossbowAmmo is an instance of ItemData:

            // from crossbow.cs (edited)
            datablock ItemData(CrossbowAmmo)
            {
                category = "Ammo";
                shapeFile = "~/data/shapes/crossbow/ammo.dts";
                mass = 1;
                elasticity = 0.2;
                friction = 0.6;
                pickUpName = "crossbow bolts";
                maxInventory = 20;
            };

We could call our new function on it thus:

            ==>CrossbowAmmo.GetFields();

             Calling ItemData::GetFields () ==> on object CrossbowAmmo
             catetory     => Ammo
             shapeFile    => egt/data/shapes/crossbow/ammo.dts
             mass         => 1
             elasticity   => 0.199413
             friction     => 0.599218
             pickUpName   => crossbow bolts
             maxInventory => 20
             

Now, this may seem completely trivial, but it is important to understand, that a majority of the interesting methods that are called by the engine as a response to user action, like onCollision(), onAdd(), create(), etc., are not called on instances of objects. They are called on the datablocks of instances of objects that use datablocks. This is crucial because we can do some very special things with datablocks and their namespaces.

Inserting Datablock Namespaces (ClassName) Datablocks provide a 'hook' with which to manipulate the namespace calling sequence. The 'hook' is the className field. You'll see this used in crossbow.cs for the CrossbowAmmo datablock. It works thus:

            datablock ItemData(CrossbowAmmo)
            {
                ...
                className = "Ammo";
                ...
            };
            

What this is doing is 'adding' a new namespace between CrossbowAmmo and ItemData, so that the namespace calling sequence will look like this: CrossbowAmmo -> Ammo -> ItemData -> et cetera. If we defined two functions thus:


            function Ammo::onPickup(%AmmoDB, %AmmoOBJ, %Picker, %Amount) 
            {
               echo ("Calling Ammo::onPickup () ==> on ammo DB" SPC %AmmoDB);    
                %AmmoDB.DoIt();
            }
            
            function Ammo::DoIt(%AmmoDB) 
            {
               echo ("Calling Ammo::DoIt () ==> on ammo DB" SPC %AmmoDB);    
            }
            

We could then collide with an ammo item and expect to see the following message:


            Calling Ammo::onPickup () ==> on ammo DB 66
            Calling Ammo::DoIt () ==> on ammo DB 66
      

This powerful feature allows us to insert a special namespace which we can use for several different datablocks. In otherwords, if we were to define two more ItemDatablocks thus:

            datablock ItemData(FlamingCrossbowAmmo)
            {
                ...
                className = "Ammo";
                ...
            };
            
            datablock ItemData(ExplodingCrossbowAmmo)
            {
                ...
                className = "Ammo";
                ...
            };
            

Later in our code object derived from the three different datablocks CrossbowAmmo, FlamingCrossbowAmmo, and ExplodingCrossbowAmmo, can all use the same onPickup() and DoIt() functions as declared in the Ammo:: namespace. This cuts way down on the amount of code we need to write.

Namespace Inheritance? You might wonder at some time, whether namespace hierarchies can be inherited. The answer is both No. If we do this,


            datablock ItemData(CrossbowAmmo)
            {
                ...
                // no classNameField
                ...
            };
            
            
            datablock ItemData(FlamingCrossbowAmmo : CrossbowAmmo)
            {
                ...
            };
            

The namespace calling sequence for CrossbowAmmo will be: CrossbowAmmo -> ItemData -> et cetera, and for FlamingCrowssbowAmmo it will be: FlamingCrossbowAmmo -> ItemData -> et cetera. If we want FlamingCrossbowAmmo to use the CrossbowAmmo namespace, we have to do this:



            datablock ItemData(CrossbowAmmo)
            {
                ...
                // no classNameField
                ...
            };
            
            
            datablock ItemData(FlamingCrossbowAmmo)
            {
                ...
                className = "CrossbowAmmo";
                ...
            };
            

Note: If you do define a className field in a datablock, subsequent children datablocks will copy that value to their own className field unless it is over-written in the childs definition:


            datablock ItemData(CrossbowAmmo)
            {
                ...
                className = "Ammo";
                ...
            };
            
            
            datablock ItemData(FlamingCrossbowAmmo:CrossbowAmmo)
            {
                ...
            };
            

Now, the namespace calling sequence for CrossbowAmmo will be: CrossbowAmmo -> Ammo -> ItemData -> et cetera, and for FlamingCrowssbowAmmo it will be: FlamingCrossbowAmmo -> Ammo -> ItemData -> et cetera.