Game Development Community

dev|Pro Game Development Curriculum

Inline anonymous function declarations in TorqueScript

by Konrad Kiss · 03/20/2009 (1:52 pm) · 23 comments


After many hours of learning Bison and other stuff I always subconsciously hoped to never have to understand, + with a very helpful hint from Ben Garney, I managed to get anonymous functions (that I'm so fond of from JavaScript and mostly jQuery) right into TorqueScript! Yey.

For those of you, who are not yet familiar with anonymous functions, or how awesome they are, here's some quick info. Consider the following code:
function testFunction() {
   %retval = myFuncCall(%variable, "callbackFunction");
}

function callbackFunction(%someparam) {
   echo(%someparam);
}

What this resource does is make the following possible as an alternative to the above code:
function testFunction() {
   %retval = myFuncCall(%variable, function(%someparam) {
      echo(%someparam);
   });
}

This resource makes the anonymous function receive an internal function name, and returns that name to the caller function (here myFuncCall). So in effect, all it does is insert the generated function name into myFuncCall and append the function definition to the file that's being compiled, like this:
function testFunction() {
   %retval = myFuncCall(%variable, "autoFN1");
}

function autoFN1(%someparam) {
   echo(%someparam);
}

If you use it as a callback, you will still need to take care of calling the callback function of course, but this makes it possible to beautify the onMissionDownloadPhase1..2..3 uhm.. feature.

It also lets you have a better overview of your code. This is very handy when dealing with threaded callbacks. HttpObject, cURL, sql resultset callbacks are good uses of this, but it doesn't end there.

***

Ok, now how to get this into your source.. Note that I've done this in TGEA 1.7.1, but you probably should have no problem implementing this into any other flavor of Torque from TGE 1.5.2.

First of all, back up all your files, with special attention to the following ones:
Quote:
console/ast.h
console/CMDgram.y
console/CMDscan.l
console/codeBlock.cpp
console/console.cpp
platform/platform.cpp

console/ast.h - after
extern StmtNode *statementList;

add
extern StmtNode *inlineFnList;

console/CMDgram.y - after
%union {
   char              c;
   int               i;

add
int		     id;

still in console/CMDgram.y - change
Quote:
| VAR '[' aidx_expr ']'
{ $$ = (ExprNode*)VarNode::alloc($1, $3); }
;

slot_acc

to
Quote:
| VAR '[' aidx_expr ']'
{ $$ = (ExprNode*)VarNode::alloc($1, $3); }
| rwDEFINE '(' var_list_decl ')' '{' statement_list '}'
{
U32 autoId = yyvsp[-6].id;

String fnname = String("autoFN_" + String::ToString(autoId));
StringTableEntry afnName = StringTable->insert(fnname.c_str());
StmtNode *fndef = FunctionDeclStmtNode::alloc(afnName, NULL, $3, $6);

if(!gInlineFnList) {
gInlineFnList = fndef;
} else {
gInlineFnList->append(fndef);
}

$$ = StrConstNode::alloc((UTF8*)fnname.utf8(), false);
}
;

slot_acc

console/codeBlock.cpp - on both finds, after
statementList   = NULL;

add
inlineFnList    = NULL;

still in console/codeBlock.cpp - on both finds, after
// Now do some parsing.
	smCurrentParser->setScanBuffer(script, fileName);
	smCurrentParser->restart(NULL);
	smCurrentParser->parse();

add
// here we probably already have an inlineFnList as well
	// with all the inline function definitions
	// if there's something in it, append it to the statement list
	if (statementList) {
		if (inlineFnList) {
			statementList->append(inlineFnList);
		}
	}

console/console.cpp - after
StmtNode *statementList;

add
StmtNode *inlineFnList;

console/CMDscan.l - change
SPACE    [ tvf]
HEXDIGIT [a-fA-F0-9]

%%
         ;
{SPACE}+ { }

to
SPACE    [ tvf]
HEXDIGIT [a-fA-F0-9]

%%
         CMDlval.id = 0;
{SPACE}+ { }

still in console/CMDscan.l - change
"function"  { CMDlval.i = lineIndex; return(rwDEFINE); }

to
"function"  { CMDlval.i = lineIndex; 
              autoFuncID++; 
              CMDlval.id = autoFuncID; 
              return(rwDEFINE); }

again in console/CMDscan.l near the top after #define YY_NEVER_INTERACTIVE 1 add:
extern U32 autoFuncID;

and finally in platform/platform.cpp, after the includes:
U32 autoFuncID = 0;

Now, run your console/generateCompiler.bat file to generate the CMD*.cpp and CMD*.h files. This is very important. If you make a syntax error, the files will not be generated, and you won't be able to compile your engine - in this case, your backups will be nice to have.

Then, recompile your engine, and cross your fingers. ;)

Thanks for reading through, enjoy anonymous embedded functions in TorqueScript!

--Konrad

@konradkiss
KonradKiss @ GitHub
konradkiss.com
Page«First 1 2 Next»
#21
09/14/2010 (8:09 pm)
@Manoel: That is the most useful thing to me right now! I have a huge number of inline functions by now - declaring callbacks on the object itself is pretty nice - but never been able to debug those.

I think this should make it into the engine - now that debugging works.

@Orion: Good luck with your game! I was wondering what you've been up to! :) Perhaps blog about it sometime.

Edit: I just updated a part of the resource that generates the function name - it didn't quite work as intended. :)
#22
09/26/2010 (12:23 am)
It looks like these functions don't work once they are compiled. Manoel, have you tried compiling your scripts with anonymous functions in them into dso-s?

I'll need to fix this tomorrow, just wanted to ask you before I began looking for the cuplrit.
#23
12/29/2014 (2:42 pm)
Pull request based on this resource. DSO compiling now works, and some things have been moved around (for example, now there are no changes to the lexer). Should make it into T3D 3.7 :).
Page«First 1 2 Next»