Previous Blog Next Blog
Prev/Next Blog
by date

Plan for Joel Baxter

Plan for Joel Baxter
Name:Joel Baxter
Date Posted:Nov 26, 2002
Rating:Not Rated
Public:YES
Comments:YES
RSS Feed:GarageGames Blog feedor Subscribe with .
Profile Page:View profile page for Joel Baxter

Blog post
The Daily: Namespaces arcana.
The Daily for Nov 25 2002:

For a slight change of pace, here's a micro-focussed update.

Pop quiz, scripting experts! What do you think happens if you put this in a scriptfile fps/test.cs and then exec it:

function TestObject::Generate(%name)
{
%obj = new ScriptObject(%name) {
class = TestObject;
superClass = GeneratedObject;
};
return %obj;
}

function GeneratedObject::Quorkle()
{
echo("Quorkle!");
}

$newThing = TestObject::Generate(foo);
$newThing.Quorkle();

This is rather contrived, but at least it's brief. I picked ScriptObject for this example because it is crystal-clear what effect it should have on the namespaces arrangement.

This is kind of a trick question, as the resultant behavior is nonintuitive enough that I think you could call it a bug. Here's what you get:

fps/test.cs (0): Unknown command Quorkle.
Object foo(1336) TestObject -> GeneratedObject -> ScriptObject -> SimObject

Ho ho! It says it can't find a namespace to run Quorkle in, and then it helpfully tells you that it looked in the GeneratedObject namespace... ?? Lies!

Here's the deal. The invocation of TestObject::Generate causes a namespace lookup. Since this is the first time this happens for the TestObject namespace, TGE constructs the set of known functions invokable in the TestObject namespace (which consists only of Generate).

Then we actually call the function, and the creation of the ScriptObject links the GeneratedObject namespace as a parent to the TestObject namespace.

Finally, we invoke Quorkle on the generated object. The generated object is associated with the TestObject namespace, but Quorkle is not listed among the functions invokable in TestObject, so we get the error. Doh.

Well, as I said, this is a contrived example. In practice, you will almost never invoke a function in a namespace until after that namespace's link to its parent has already been established. So the first namespace lookup will register the functions in the parent namespace (and the parent of the parent, etc.) as invokable functions, and all will be well. But this behavior did perplex me today for a while (with a GuiControl object rather than a ScriptObject, in a more complex situation).

Even if you do run into the bug it should usually be easy to work around; in the silly example above, we could rename TestObject::Generate to TestObjectGenerate, or MakeTestObject, or George, or whatever -- so that it's just a global name rather than a name in the TestObject namespace -- and the badness would go away. That's assuming though that you know enough about what is causing the problem to make that change. :-)

--

Those of you who have spent a lot of time mucking in the scripts may at this point be starting to wonder about the Editor object. The Editor namespace is actually referenced a couple of times before the Editor object is created (and it's the creation of the Editor object that links the Editor namespace to its parent) so is it possible to, for example, invoke GuiControl functions on Editor, like Editor.getExtent()?

Actually, yeah, that will work as expected. The reason: sets of invokable functions for the namespaces are kept in a cache, which gets cleared at certain times during engine startup, when packages are activated/deactivated, and when functions are declared. When you create the Editor, the scripted Editor::onAdd execs files that contain function definitions, which therefore clears the cache, just before the engine Editor::onAdd links the Editor namespace to its parent. So the next namespace lookup for Editor happens "fresh" and will be aware of its parent namespaces chain. Lucky happenstance.

As that situation hints, you can kill this little namespace bug by force-clearing that cache. The appropriate time to do this, I think, would be when TGE realizes that two namespaces are getting linked together; in specific terms, this means changing the end of Namespace::classLinkTo from

walk->mParent = parent;

to

if (walk->mParent != parent) {
walk->mParent = parent;
trashCache();
}

Now if you apply that fix, you might well wonder what performance impact it would have. Caches after all do tend to work better if you don't keep dumping them out. I think that in this case it's a non-issue for plausible TGE scripting, as namespace linkage is something that happens at the beginning of the game and not during the game. I wouldn't say for certain that no one would ever be in a situation where they would want to constantly create and link namespaces (because you can't just keep linking a limited, non-growing set of namespaces), but it seems unlikely.

But... just to be safe, perhaps a better fix would instead involve having classLinkTo check to see if functions for this namespace or its children are currently in the cache, and if so, adding the functions from the parent namespace chain to the sets of functions for the namespace and its children, rather than nuking the cache. Seems like a bit of work... if nobody else ever hits this bug, it could be best to just stop picking at it. :-)

Recent Blog Posts
List:06/16/04 - Plan for Joel Baxter
11/26/02 - Plan for Joel Baxter
11/23/02 - Plan for Joel Baxter
11/21/02 - Plan for Joel Baxter
11/20/02 - Plan for Joel Baxter
11/19/02 - Plan for Joel Baxter
11/15/02 - Plan for Joel Baxter
11/14/02 - Plan for Joel Baxter

Submit ResourceSubmit your own resources!

Joel Baxter   (Nov 26, 2002 at 07:37 GMT)
I clarified a few things above ... if by some strange chance someone has already read this, and there were parts that seemed off, maybe go take another look. :-)

You must be a member and be logged in to either append comments or rate this resource.