Game Development Community

isDefined()

by Orion Elenzil · 05/26/2009 (7:54 pm) · 15 comments

Tim McClarren, Michael Plotz, and Erez Morag all had hands in this.

This is a console function which lets you test whether a symbol such as "%foo", "$foo", or "foo" is defined without printing errors in the log if you have $Con::WarnUndefinedVariables enabled. the first would be a local variable, the second a global variable, and the last a SimObject. It also lets you optionally pass in a value to assign to the variable if it doesn't exist.

Examples:

function myFunction(%foo)
{
   if (!isDefined("%foo"))   // be sure to include the quotes
   {
      error("hey, you didn't provide param foo");
      return;
   }

   // do stuff
}


function myOtherFunction(%foo)
{
   isDefined("%foo", "my default value for foo");  // be sure to use quotes with "%foo" 

   // do stuff
}


and here's the source.
note that getConsoleTrace() is not part of the stock engine,
so you can either implement it from this resource or just skip it.

consoleFunctions.cc
ConsoleFunction(isDefined, bool, 2, 3, "isDefined(variable name [, value if not defined])")
{
   if(dStrlen(argv[1]) == 0)
   {
      Con::errorf("isDefined() - did you forget to put quotes around the variable name?");
      return false;
   }

   StringTableEntry name = StringTable->insert(argv[1]);

   // local or global?
   if (name[0] == '%')
   {
      if (gEvalState.stack.size())
      {
         Dictionary::Entry* e = gEvalState.stack.last()->lookup(name);

         if (e)
         {
            return true;
         }
         else if (argc > 2)
         {
            gEvalState.stack.last()->setVariable(name, argv[2]);
         }
      }
      else
      {
         Con::errorf("%s() - no local variable frame.", __FUNCTION__);
      }
   }
   else if (name[0] == '$')
   {
      Dictionary::Entry* e = gEvalState.globalVars.lookup(name);

      if (e)
      {
         return true;
      }
      else if (argc > 2)
      {
         gEvalState.globalVars.setVariable(name, argv[2]);
      }
   }
   else  // it's perhaps an object
   {
      if (dStrcmp(argv[1], "0") && dStrcmp(argv[1], "") && (Sim::findObject(argv[1]) != NULL))
      {
         return true;
      }
      else if (argc > 2)
      {
         Con::errorf("%s() - can't assign a value to a variable of the form \"%s\"", __FUNCTION__, argv[1]);
      }
   }

   return false;
}

#1
05/26/2009 (11:39 pm)
Hey Orion, always cool snipets!

Have a question with this one, however (sorry for my ignorance, btw) but what would be the difference with

if (%foo !$= "")
#2
05/26/2009 (11:49 pm)
heh. excellent question!!

i now see that unless you've enabled $Con::warnUndefinedVariables, this is pretty meaningless. i'd forgotten that wasn't on by default. thanks for the catch.
#3
05/27/2009 (12:18 am)
Ha! Im feeling so proud of myself right now... catched Orion on his own net of code snipets! hehe.

Glad to help, keep up the constant flow of good advise and code, Mr. Elenzil.
#4
05/27/2009 (2:26 am)
And what the heck is that tilting little square on top of your name label??
#5
05/27/2009 (3:30 am)
Morse code?
#6
05/27/2009 (3:45 am)
Ah, maybe! but its too irregular as to follow... and Orion has some script changin its profile pic, so probably no time to decipher it...
#7
05/27/2009 (7:08 pm)
The dot is hypnotizing. You must seek the answer from a man named:

x wilson x
#8
05/27/2009 (7:54 pm)
i think my reply to that can only be: elenzil.com/images/reply.gif
#9
05/27/2009 (8:14 pm)
Bastard!
#10
05/27/2009 (8:17 pm)
Thank you, Orion!
#11
05/27/2009 (8:20 pm)
;)
#12
06/03/2009 (10:14 pm)
You left a couple of 's out of this line:

Con::errorf("%s() - can't assign a value to a variable of the form "%s"", __FUNCTION__, argv[1]);

It should be:

Con::errorf("%s() - can't assign a value to a variable of the form \"%s\"", __FUNCTION__, argv[1]);

I'm merging this into Torque 3D Beta 3 while I wait on a full recompile to finish =)
#13
06/03/2009 (10:16 pm)
Hrm...it appears that the forum system will eat the escape characters if you aren't careful...

I had to put a double \ to get the single one to show up in my fixed line.
#14
06/03/2009 (10:45 pm)
sweet, glad this is being merged in and sorry for the typo.
i think i did some untested editing on that line as internally i print something a bit different, a script stacktrace, but i haven't posted that into a resource yet so always end up doing some last-minute editing.

hm, i just put a single \ in the code and it seems to display.
#15
06/12/2009 (1:40 am)
Here is my updated version:

ConsoleFunction(isDefined, bool, 2, 3, "isDefined(variable name [, value if not defined])")
{
   if(dStrlen(argv[1]) == 0)
   {
      Con::errorf("isDefined() - did you forget to put quotes around the variable name?");
      return false;
   }

   StringTableEntry name = StringTable->insert(argv[1]);

   // Deal with <var>.<value>
   if (dStrchr(name, '.'))
   {
      static char scratchBuffer[4096];

      S32 len = dStrlen(name);
      AssertFatal(len < sizeof(scratchBuffer)-1, "isDefined() - name too long");
      dMemcpy(scratchBuffer, name, len+1);

      char * token = dStrtok(scratchBuffer, ".");

      if (!token || token[0] == '')
         return false;

      StringTableEntry objName = StringTable->insert(token);

      // Give Sim::findObject() first crack at it
      SimObject * obj = Sim::findObject(objName);

      // If we didn't find it with Sim::findObject() and it
      // is a local variable then attempt to find it in scope
      if (!obj && objName[0] == '%')
      {
         if (gEvalState.stack.size())
         {
            Dictionary::Entry* ent = gEvalState.stack.last()->lookup(objName);

            if (ent)
               obj = Sim::findObject(ent->getIntValue());
         }
      }

      if (!obj)
         return false;

      token = dStrtok(0, ".");
      if (!token)
         return false;

      StringTableEntry valName = StringTable->insert(token);

      // Store these so we can restore them later
      bool saveModStatic = obj->canModStaticFields();
      bool saveModDyn = obj->canModDynamicFields();

      // Set this so that we can search both static and dynamic fields
      obj->setModStaticFields(true);
      obj->setModDynamicFields(true);

      const char* value = obj->getDataField(valName, 0);

      // Restore our mod flags
      obj->setModStaticFields(saveModStatic);
      obj->setModDynamicFields(saveModDyn);

      if (dStrlen(value) > 0)
         return true;
      else if (argc > 2)
         obj->setDataField(valName, 0, argv[2]);
   }
   else if (name[0] == '%')
   {
      // Look up a local variable
      if (gEvalState.stack.size())
      {
         Dictionary::Entry* ent = gEvalState.stack.last()->lookup(name);

         if (ent)
            return true;
         else if (argc > 2)
            gEvalState.stack.last()->setVariable(name, argv[2]);
      }
      else
         Con::errorf("%s() - no local variable frame.", __FUNCTION__);
   }
   else if (name[0] == '$')
   {
      // Look up a global value
      Dictionary::Entry* ent = gEvalState.globalVars.lookup(name);

      if (ent)
         return true;
      else if (argc > 2)
         gEvalState.globalVars.setVariable(name, argv[2]);
   }
   else
   {
      // Is it an object?
      if (dStrcmp(argv[1], "0") && dStrcmp(argv[1], "") && (Sim::findObject(argv[1]) != NULL))
         return true;
      else if (argc > 2)
         Con::errorf("%s() - can't assign a value to a variable of the form "%s"", __FUNCTION__, argv[1]);
   }

   return false;
}


I attempted to use it in place of !$= "" on an object field and it kinda fell on its face so I added support for that.

Note that I did have to expose canModStaticFields() and canModDynamicFields() on SimObject for it to be perfectly safe (I'm not 100% sure why SimObject::getDataField() requires those flags to be true for getting a value but I'm not going to muck with that for now). You could probably safely ignore all of that part for now since most things have those flags set.

I am also probably going to move my cute little "local object variable name" loop up directly into SimObject::findObject() but I wanted to have it in this piece of code since it is critical.