Sign in to follow this  
derefed

Taking different parameter types

Recommended Posts

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!

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?



Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
It turns out that the way I have things set up, it'll be much easier for my game to use ExecuteString() to call functions, i.e. like this:

ExecuteString("Say_To_User(1, \"Hello!\")");


Is there any difference between doing that and using Prepare(), SetArg(), and Execute()? I've run a few tests and everything seems to work the same... however, I'd like to be sure about it before I start developing a lot based on it.

Share this post


Link to post
Share on other sites
If you can represent all your arguments as strings, then the only difference is that ExecuteString is slower, in that it needs to compile the string and then execute it.

Share this post


Link to post
Share on other sites
Hmm... sounds like that'll be fine, because if I didn't use ExecuteString(), I'd have to write some function of my own that would end up doing the same, and it'd probably be slower.

I figure I'll have all my script functions in one file, then use ExecuteString() to call them whenever I need to. Thank God (or rather, WitchLord) for that function! It's making things a lot easier.

Share this post


Link to post
Share on other sites
derefed, I most likely have missed something, so please forgive me for asking, but why?!?

This way is slower, and IMHO, limits what you can do with the scripting engine.

As WitchLord indicated, you have to be able to represent all your parameters as strings. If this is what you are always going to be doing, you can easily write a wrapper for this, and not limit yourself to using a slow function call.

If, on the other hand, you are having to serialize your objects into strings so you can easily call your functions... O_o. My 2 cents is that you should really think hard about this.

Share this post


Link to post
Share on other sites
I want to do it this way because all of my function calls aren't being made from my C++ code, but rather from an XML file that stores all data for the game.

The game is text-based, and resembles a MUD. A room, for instance, is represented like this:


<room id="1">
<name>Grassy field</name>
<description>You find yourself in a vast field of tall grass.</description>

<exits>
<exit hotkey="n" description="North" roomid="2" />
</exits>

<events>
<onEnter call="Set_Var('room1_visited', 'true')" />
</events>
</room>





I would then have an AS file where the Set_Var function was defined. In this case, entering this room causes a variable "room1_visited" to be set to "true". Other uses would be to have an NPC start a conversation with you upon entering the room, or perhaps something as complex as having an item appear when you say a certain word to someone, and having several hostile NPCs spawn.

The reason I'm even using a scripting language at all is so I can program behaviors for rooms, NPCs, items, etc. without tampering with the source code for the game engine itself. I want people to be able to write their own worlds by writing an XML file to store all the data and an AS file to script all the behaviors/actions of entities in the game.

Now, the only way that I can see to do this without ExecuteString() is to parse the value of the "call" attribute to extract the variables/values, convert them into their proper C++ types, and send them into AS the way you propose... this seems like it would take a lot of work and would no doubt be slow. Additionally, I could also do something like:


<onEvent call="Set_Var">
<param type="string" value="room1_visited" />
<param type="bool" value="true" />
</onEvent>





However, this isn't quite as streamlined as the above code; I want it to be akin to using javascript with HTML.

If there's a better way to do this, I'm all ears.

Share this post


Link to post
Share on other sites
I think you'll be alright with using ExecuteString for what you want to do, it will definitely be the easiest way to implement it. Being a text based game I hardly think you'll be having problems with performance either.



Share this post


Link to post
Share on other sites
derefed, thank you. That does seem a reasonable approach. Again, I do worry that you can't pass objects around, but this doesn't seem to be an issue.

I've been doing a bunch of reading, and ran over this. The end implementation would be very similar to what SiCrane linked too, but it would not have as much redundant code. What i mean is it would not have this:

template <typename OP> inline void ExecuteSetArg(asIScriptContext* ctx, int arg, OP op){ /* shouldn't reach here */ }
template <> inline void ExecuteSetArg(asIScriptContext* ctx, int arg, DummyOperand op){ /* do nothing */ }
template <> inline void ExecuteSetArg(asIScriptContext* ctx, int arg, asQWORD op){ ctx->SetArgQWord(arg, op); }
template <> inline void ExecuteSetArg(asIScriptContext* ctx, int arg, asDWORD op){ ctx->SetArgDWord(arg, op); }
template <> inline void ExecuteSetArg(asIScriptContext* ctx, int arg, double op){ ctx->SetArgDouble(arg, op); }
template <> inline void ExecuteSetArg(asIScriptContext* ctx, int arg, float op){ ctx->SetArgFloat(arg, op); }
template <> inline void ExecuteSetArg(asIScriptContext* ctx, int arg, void* op){ ctx->SetArgObject(arg, op); }

And one could easily add more parameters w/o a problem.

You then get the type safety, but the flexibility of an unknown amount of template parameters.

Share this post


Link to post
Share on other sites
Let's say, for the sake of argument, that performance *was* an issue, but I had the same setup in terms of using an XML file for data and calling functions from there. (I plan to do a graphical game in the future that would no doubt store data / scripts in the same manner.) What approach would you recommend? Would this:


<onEnter call="Some_Function">
<param type="int" value="47" />
<param type="string" value="hello world!" />
</onEnter>



sort of thing be the way to go? From that info, my XML parser would get the info on what type the variables should be and could call the right SetArg() functions to be sent into the interpreter.

Share this post


Link to post
Share on other sites
Without seeing everything working together, your way seems reasonable. A few questions/comments though:

- You need a way to return a value. Yet that can easily be addressed.
- You cannot pass objects to your function

It's a nice simple way of doing things, but I just feel limited by it...am not 100% sure why though...

Share this post


Link to post
Share on other sites
Hmmm... maybe a better way to go about all this would be to do the following:

- Have all data stored in the XML file, but ONLY data; no AS function calls. All game entities are given a unique ID
- Have an AS file that holds all my scripting stuff -- classes, functions, etc.
- In the AS file, there is an "OnEnter" function for rooms that takes an ID for a parameter. It will hold a long switch() block, in which I can define whatever behaviors I wish for certain rooms when a user enters (perhaps it would also take parameters such as the user's ID, but let's keep things simple for the explanation)
- Whenever a player enters a room, the C++ code calls the AS function "OnEnter", sending in the room ID in question

I could have a number of event functions defined for numerous game entities. This design makes it such that calls don't have to be constructed in the XML, which means I don't have to use ExecuteString(). This also seems to be organized better.

Any thoughts?

Share this post


Link to post
Share on other sites
Long switch blocks are usually a warning sign. You probably don't want to define an object's behavior outside where you define other traits about an object.

Share this post


Link to post
Share on other sites
I believe that if I were to implement an engine like what you're describing I would choose something similar to your last approach.

The data files (XML) can define the event handlers that should be called by the application when events are triggered. All event handlers have a predefined function signature, e.g. 'void function(event& e)', where the event type is a class that holds the necessary information, for example the id of the object fo which the event was triggered.

The event handlers can be defined in a separate script file, named by the XML file, or may even be defined inside the XML. If defined inside the XML the application should extract all the script pieces and compile them as one script as it is loading the XML file.

Example:


-- XML file
<room id="1">
<name>Grassy field</name>
<description>You find yourself in a vast field of tall grass.</description>

<exits>
<exit hotkey="n" description="North" roomid="2" />
</exits>

<events>
<event name="onEnter" handler="EnterRoom1" />
<event name="onExit" hanlder="ExitRoom1" />
</events>
</room>

-- Script code
void EnterRoom1(event &e)
{
RoomObject @obj = @GetRoomObject(e.id);
if( obj.hasVisited )
{
// The player has already visited this room

...
}
else
{
// This is the first time the player visits the room
obj.hasVisited = true;

...
}
}

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
Long switch blocks are usually a warning sign. You probably don't want to define an object's behavior outside where you define other traits about an object.


But I'm not trying to define behaviors of an entire class, but rather of different objects of a class. Basically, I would need Room1 to have a different onEnter() implementation than Room2, because they do different things when one enters; they are both Room objects, however.

Share this post


Link to post
Share on other sites
Exactly my point. If Room1 and Room2 have different behaviors, why define them in the same place? That's what you would be doing with a switch statement.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this