Advertisement

Taking different parameter types

Started by August 03, 2007 09:13 PM
25 comments, last by WitchLord 17 years, 3 months ago
I'm slowly building my ScriptingEngine class for my game, which basically abstracts all the AS setup and function execution so that the game can easily call a function and get a return value whenever it wants. I've run into a bit of a quandary, however: how do I pass in different variable types for parameters to a script function? I took a look at the "console" sample included with AS, which seems to do this very thing. However, there are a few things that I don't understand by looking at the code. First off, what does the asFUNCTIONPR macro do, and why do I need it for overloading global functions? I can't find documentation for it in the docs. Second...

                int pos;
		if( (pos = input.find(" ")) != string::npos )
		{
			cmd = input.substr(0, pos);
			arg = input.substr(pos+1);
		}
		else
		{
			cmd = input;
			arg = "";
		}

		// Interpret the command
		if( cmd == "exec" )
			ExecString(engine, arg);
		else if( cmd == "help" )
			PrintHelp();
		else if( cmd == "quit" )
			break;
		else
			cout << "Unknown command." << endl;

[...]

void ExecString(asIScriptEngine *engine, string &arg)
{
	string script;

	script = "_grab(" + arg + ")";

	int r = engine->ExecuteString(0, script.c_str());
	if( r < 0 )
		cout << "Invalid script statement. " << endl;
	else if( r == asEXECUTION_EXCEPTION )
		cout << "A script exception was raised." << endl;
}



It looks to me as if this code only takes in one parameter, not multiple parameters. Is this true? If so, I can probably figure out how to make my program accept multiple params, but I'd just like to clarify so I don't end up doing extra work. Thanks!
Must admit I don't quite follow.

Are you asking how to make an CallFunction wrapper around the setarg,etc. AS functions? So one can then just call a AS function without worrying about manually setting all the arguments?

This CallFunction would then have to take an unknown amount of arguments, and you would have to sort out what type each was...is this what you mean?
Advertisement
Quote: Original post by _Sigma
Must admit I don't quite follow.

Are you asking how to make an CallFunction wrapper around the setarg,etc. AS functions? So one can then just call a AS function without worrying about manually setting all the arguments?

This CallFunction would then have to take an unknown amount of arguments, and you would have to sort out what type each was...is this what you mean?


Yep. I just wanna know how others have done it before I try what I'm thinking, which is to make some sort of Primitive class that has for properties "data" and "type" ... a Collection class of these Primitives will be sent into the CallFunction(), like...

PrimitiveCollection pc;pc.AddPrimitive(TYPE_FLOAT, 3.14159);pc.AddPrimitive(TYPE_INT, 47);pc.AddPrimitive(TYPE_STRING, "This will be printed upon calculation.");CallFunctionByDecl("float calc(float, int, string)", pc);


CallFunctionByDecl() can sort out easily which variables are of which type, and call the SetArg() functions accordingly. However, the "console" sample doesn't do this, but I can't figure out how exactly it works.

I am just curious as to whether this method of mine is fine or whether it's overkill and there's an easier way.

My other, simpler question is what does the asFUNCTIONPR macro do? There's no docs on it.
The asFUNCTIONPR macro is just a way to specify exactly which function you wish to take the pointer for. The macro is defined in angelscript.h as:

#define asFUNCTIONPR(f,p,r) asFunctionPtr((void (*)())((r (*)p)(f)))


f, is the name of the function
p, is the parameter list
r, is the return type

If you have two functions with the same name, but with different parameters you can use this macro to let the compiler know exactly which one of the two you wish to take the address for.

The console sample doesn't do what you're looking for. The arg to the ExecString() is actually a script that will be executed with engine->ExecuteString(). The sample doesn't specifically call a script function with or without arguments.

I'm not sure why you want to write a generic wrapper for calling script functions, but if you feel you need it then I suppose what you're doing could be one way of doing it. I would suspect however that your application doesn't actually call that many different script functions, that maybe you're just putting too much energy into this. It may just be easier to write the actual code that calls SetArg directly for the few locations you need it.

Regards,
Andreas

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Quote: Original post by WitchLord
I'm not sure why you want to write a generic wrapper for calling script functions

Because it would be cool to be able to call a function without having to worry about any of the underlying code (we've seen how easily I lie to AS and it punishes me!)

@OP, I've been mulling this around in my head for quite some time now. IMHO, what you are doing is really not much of an improvement over AS's default parameter passing.

In my mind, an idea situtation would be something like:

script
int myScriptFn(int i, MyObject o){//TODO}


int someInt = 5;MyObject obj("hello");int r = m_script.CallScriptFunction("myScriptFn",5,obj);


We then run into the problem of how to declare the variable argument list, and sort out wtf parameter type we have. So we need to do parsing on the fly, which can add considerable overhead to the call.

any ideas?

[Edited by - _Sigma on August 4, 2007 4:22:49 PM]
Here are some previous threads on this idea: One Two.

I've been working on a more advanced wrapper for AS that addresses some of the problems with the ones in those threads, but it requires a modified version of AS 2.8.1.
Advertisement
Actually... yeah, I might as well just call the SetArg()s. I originally wanted to make a function that I could call once and have the script function run, as _Sigma posted, but now that I think about it, I can just do it by Preparing, Arg Setting and Executing.

Perhaps in the future I'll implement my initially proposed method... right now though I want to focus on my game.
I'm going to try this tomorrow. I think using CRCs (so fast compares) and some preprocessing, you could build up the argument lists so one doesn't have to do substrings during execution time.

Variable argument lists...they get around this problem. I know these are bad, but in this case, is this a legit use?

I think there is a way to ensure the correct number of arguments as we will be comparing it against the function signature.

I can't remember, but AS supports overloaded script functions correct? If so, that might be the biggest problem.
Yes, AngelScript support function overloading with different parameter types.

Variable argument lists are not bad, as long as you know the risks and properly test your code. From what you seem to want to do however, I do not think variable argument lists are recommended. It's better to use templated functions, much like how SqPlus or LuaBind works. With templated functions you'll have full type checking and the code will be able to properly validate the parameters against the script function declaration. This is what the thread SiCrane linked to is all about.

SiCrane, you mention that you've modified AS to be able to do what you want. Is there anything you'd like me to add to the library?



AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Well, I already mentioned it in this thread, but in order to do what I want, I need to get at the function ID assigned in two places: when a function is registered and when the function is called.

The basic idea is that I want to register arbitrary function objects rather than just global functions or member functions, etc. So one way to do that is that instead of registering a different function with every function call, instead registering the same function each time that dispatches its behavior based on the function id.

In my implementation there's a std::map<int, GenericFunctor> where GenericFunctor is a typedef for a boost::function<void (asIScriptGeneric *)>. When the script engine calls a function it ends up calling this function:
      static void functor_mapper(asIScriptGeneric * gen) {        asCGeneric & concrete_generic = dynamic_cast<asCGeneric &>(*gen);        int id = concrete_generic.sysFunction->id;        (the_proxy->functor_map)[id](gen);      }

This extracts the id from the system function and uses it to dispatch to the correct GenericFunctor. However, in order to populate the functor map, I need the function id when registering the function. To do this, I've modified functions like RegisterGlobalFunction() to take an additional int & parameter at the end of the argument list which is used to return the id.

This is a zip of the current wrapper testbed if you want to see the actual code for what I've done. Some caveats: I'm pretty sure that it'll only compile on MSVC .NET 2005. It also takes a while to build.

I think the callable entity code can be used without the AS modifications, but I wouldn't place any money on it.

This topic is closed to new replies.

Advertisement