🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Catching call by value/return by value with class types

Started by
15 comments, last by WitchLord 16 years, 11 months ago
Quote: With these methods it is much easier for the application to use template functions for setting and getting values, e.g:


Witchlord, does this function completely replace the SetArgXXX functions, so that I could rewrite this mess

bool sil::c_FunctionCall::set_argument(int a_id, sil::Any value, bool is_reference){	if (!ctx) return false;	if (state != SIT_READY) return false;	int r_code = -1;	if (is_reference) 	{		this->reference_parameters.push_back(value); //Ensure the lifetime of reference parameters.		r_code = ctx->SetArgAddress( a_id, value.void_pointer() );	}	else if (value.is_of_type<char>())		r_code = ctx->SetArgDWord(a_id,value.reference<char>());	else if (value.is_of_type<short>())		r_code = ctx->SetArgDWord(a_id,value.reference<short>());	else if (value.is_of_type<int>())		r_code = ctx->SetArgDWord(a_id,value.reference<int>());	else if (value.is_of_type<unsigned char>())		r_code = ctx->SetArgDWord(a_id,value.reference<unsigned char>());	else if (value.is_of_type<unsigned short>())		r_code = ctx->SetArgDWord(a_id,value.reference<unsigned short>());	else if (value.is_of_type<unsigned int>())		r_code = ctx->SetArgDWord(a_id,value.reference<unsigned int>());	else if (value.is_of_type<bool>())		r_code = ctx->SetArgDWord(a_id,value.reference<bool>());	else if (value.is_of_type<float>())		r_code = ctx->SetArgFloat(a_id,value.reference<float>());	else if (value.is_of_type<double>())		r_code = ctx->SetArgDouble(a_id,value.reference<double>());	else		r_code = ctx->SetArgObject(a_id, value.void_pointer());	if (r_code < 0) { state = SIT_ERROR; return false; }	return true;}		


as

if (is_reference) 	{		this->reference_parameters.push_back(value); //Ensure the lifetime of reference parameters.		*(void**)GetArgPointer(a_id) = value.void_pointer(); //I know how awful this is.	}	else		value.assign_to_void(GetArgPointer(a_id));
Advertisement
Quote: Original post by WitchLord
This would be something like MatchFunctions in the compiler. I'll see what can be done, though with the addition of number 1 it should be possible to implement this logic outside the library.

Well, I tried implementing the logic and ended up not doing a good job (read: catastrophic failure), thus it seemed like a good idea to go to the Man himself for this. :) I also tried bundling the type arguments in a form I could pass to MatchFunctions(), but I couldn't work my head around the compiler innards necessary to get that to work.

Quote:
3. I'll expose the function ids for registered functions. At first I'll have the register methods return the id, but at a later time it would also be necessary to allow the enumeration of registered functions.

It'd probably be less of a change to the API to add a int * parameter to the end with a default null argument. At least then you can keep the success/failure return.

Quote: Original post by Deyja
Looks like my wrapper is totally outdated now. SiCrane, I implemented most of what you want, but I wasn't using the generic calling conventions. I used an any class and bound all the types to AS using macros, so I could automatically create tables of all the type information. Then, given a list of anys, my wrapper would be able to ask them for their types, then enumerate the functions in a module and find the best match, even performing conversions if neccessary (and possible).

I'm not wrapping my head around this very well, could I see code for what you're doing?

Quote: Our goals might conflict somewhat, though.

Well that doesn't stop us from taking what parts work from each others project anyways. :)

Quote: Also, I wouldn't mind a bit of elaboration on
Quote: I've noticed that a common error with using AngelScript is registering a native function that accepts a class type by value.
How is this anymore of an error in AngelScript than in C++? Passing a large class around by value might be a design flaw, but it's not an error. And passing small classes by value shouldn't be forbidden.]

Example 1, Example 2.

As mentioned, the strict macros can accept classes by value if you add in some template specializations. But since it seems like legitimate passing objects by value is done more often than I thought, the template calls could probably be modified to check the engine to see if the types had been registered with AddRef/Release behavior instead.
Quote: As mentioned, the strict macros can accept classes by value if you add in some template specializations.
Okay, my gripe was that you seemed to want to disable this. If the idea is only to make it harder for new users to shoot themselves in the foot, then it's great. I agree with what most people are saying about having to know about calling conventions and that jazz being hard on new programmers, but I also want to be sure nothing changes to make it impossible for those of us who do know to take advantage of the knowledge.

Quote: I'm not wrapping my head around this very well, could I see code for what you're doing?


I can paste code. The general algorithm is there (It's really pretty simple), but there's all sorts of support code that makes this work. If you want it, I'll share, but this can't be made to work in isolation. Either all the types used are bound through my system, or it has to be integrated right into the script engine and we'd probably have to let Witchlord do it.

int select_best_function(const sil::Script::function_set& func_set, const std::vector<std::string>& parm_list)	{		int best_score = -1;		int best = sil::Script::ERROR_NOMATCH;		bool ambiquous = false;		for (sil::Script::function_set::size_type func = 0; func < func_set.size(); ++func)		{			if (func_set[func].parameters.size() != parm_list.size()) continue;			int exact_matches = 0;			bool fail = false;			for (std::vector<std::string>::size_type parm = 0; parm < parm_list.size(); ++parm)			{				bool needs_converted = (parm_list[parm] != func_set[func].parameters[parm].name);				if (needs_converted && func_set[func].parameters[parm].is_reference) 				{					if (!func_set[func].parameters[parm].is_const)					{						fail = true;						break;					}				}				if (!needs_converted) exact_matches += 1;			}			if (fail) continue;			if (exact_matches == best_score) ambiquous = true;			if (exact_matches > best_score)			{				best = func;				best_score = exact_matches;				ambiquous = false;			}		}		if (ambiquous) return sil::Script::ERROR_AMBIQUOUS;		else return best;	}


The algorithm is there. I do a lot of preprocessing to make this possible. Whenever a script is loaded, the functions in it are enumerated, and their declarations are parsed, so that I can store information about their parameters. This is in func_set above - it's the information for every function with the name we are trying to call.
Whenever types are bound, information must be added to tables in order to allow the any class to supply the name of the type (as Angelscript sees it. It's amazing how many people don't grasp the idea that just because a type is named 'foo' in C++ doesn't mean it can't be named 'bar' inside angelscript) and what other types it can be converted too. The function that calls this receives a function name and a list of anys. It uses this to build the func_set and the vector of parameter types.

The actual algorithm is fairly simple. We iterate over the functions in func_set, and first we check the number of parameters. Obviously, if we didn't supply the right number of parameters, it can't match.
Then we compare parameters, counting the number of exact matches. The function with the most exact matches wins. For conversions, we check to see if the conversion is possible. If not, this function can't match. (Note that the only conversion failure I checked for was when using reference types. It will allow you to convert a reference to a const reference of another type, but not to a non-const one. It could allow this, but it might then silently 'lose' output reference values. It should also check if the conversion between types is actually possible at all, but I never implemented it.)
Finally, it keeps a running tab on ambiquity. If it finds two functions with the same number of exact matches, it sets a flag. If it finds one that matches better, it clears it.

Here is the entire source file.

#include "script.h"#include "engine.h"#include "function_call_detail.h"#include <vector>namespace{	int select_best_function(const sil::Script::function_set& func_set, const std::vector<std::string>& parm_list)	{		int best_score = -1;		int best = sil::Script::ERROR_NOMATCH;		bool ambiquous = false;		for (sil::Script::function_set::size_type func = 0; func < func_set.size(); ++func)		{			if (func_set[func].parameters.size() != parm_list.size()) continue;			int exact_matches = 0;			bool fail = false;			for (std::vector<std::string>::size_type parm = 0; parm < parm_list.size(); ++parm)			{				bool needs_converted = (parm_list[parm] != func_set[func].parameters[parm].name);				if (needs_converted && func_set[func].parameters[parm].is_reference) 				{					if (!func_set[func].parameters[parm].is_const)					{						fail = true;						break;					}				}				if (!needs_converted) exact_matches += 1;			}			if (fail) continue;			if (exact_matches == best_score) ambiquous = true;			if (exact_matches > best_score)			{				best = func;				best_score = exact_matches;				ambiquous = false;			}		}		if (ambiquous) return sil::Script::ERROR_AMBIQUOUS;		else return best;	}	std::vector<std::string> make_parm_list(const sil::AnyList& parameters)	{		std::vector<std::string> result;		for (sil::AnyList::const_iterator I = parameters.begin(); I != parameters.end(); ++I)			result.push_back( sil::any_script_typename(*I) );		return result;	}	bool set_all_arguments(		sil::FunctionCall call_instance, 		const sil::AnyList& parameters, 		const sil::Script::parameter_list& type_list)	{		int type_index = 0;		sil::AnyList::const_iterator parameter_iter = parameters.begin();		for (; parameter_iter != parameters.end(); ++parameter_iter, ++type_index)		{			sil::Any converted_value = *parameter_iter;			if (type_list[type_index].name != sil::any_script_typename(converted_value))				converted_value = sil::convert_any( converted_value, type_list[type_index].name );			if (!call_instance->set_argument( type_index, converted_value, type_list[type_index].is_reference ))				return false;		}		return true;	}}int sil::Script::spawn_instance(	sil::FunctionCall& result,	const std::string& function, 	const sil::AnyList& parameters){	if (!this->good_module) return sil::Script::ERROR_BADMODULE;	sil::Script::function_map::iterator func_record = this->functions.find(function);	if (func_record == this->functions.end()) return sil::Script::ERROR_NOMATCH;	int func_id = select_best_function( func_record->second, make_parm_list(parameters) );	if (func_id < 0) return func_id;	sil::FunctionCall temporary_result = new sil::c_FunctionCall( func_record->second.at(func_id).id, this->SCI );	if (!set_all_arguments( temporary_result, parameters, func_record->second.at(func_id).parameters ))	{		return sil::Script::ERROR_BADPARAMETER;	}	temporary_result->start();	this->threads.push_back(temporary_result);	result = temporary_result;	return 0;}	sil::FunctionCall sil::Script::spawn_process(	const std::string& function, 	const sil::AnyList& parameters){	sil::FunctionCall result;	int error_code = this->spawn_instance(result,function,parameters);	if (error_code != 0) return sil::FunctionCall();	return result;}	int sil::Script::execute_function(	sil::Any& return_value,	const std::string& function, 	const sil::AnyList& parameters, 	sil::time::seconds timeout){	sil::FunctionCall func;	int error_code = this->spawn_instance(func,function,parameters);	if (error_code != 0) return error_code;	if (!func) return sil::Script::ERROR_UNKNOWN;	func->execute(timeout);	if (!func->is_done()) return sil::Script::ERROR_DIDNTFINISH;	if (!func->get_return_value(return_value)) return sil::Script::ERROR_CANTGETRETURN;	return 0;}


I'm sorry if it doesn't make sense in isolation. I'll share the entire sil module with you if you want, but be warned that this file is fairly representative of the level of commenting in the entire thing. I actually intend to rewrite the entire thing, next time I have a project that needs scripting.
Quote:
Witchlord, does this function completely replace the SetArgXXX functions, so that I could rewrite this mess


All primitive types can be handled the same way using the GetArgPointer(). Non-primitive types are a bit different from the primitives in that you're getting a pointer to a pointer to the object, since that's how AngelScript stores the objects internally. In case of object handles (that you don't seem to use) you'd get a pointer to the handle.

Quote:
Well, I tried implementing the logic and ended up not doing a good job (read: catastrophic failure), thus it seemed like a good idea to go to the Man himself for this. :) I also tried bundling the type arguments in a form I could pass to MatchFunctions(), but I couldn't work my head around the compiler innards necessary to get that to work.


And here I thought you were the Man. ;)

Once I've implemented enumeration of parameter types for functions, I'll help write a FindBestMatchingFunction() as an external function.

Quote:
It'd probably be less of a change to the API to add a int * parameter to the end with a default null argument. At least then you can keep the success/failure return.


Since all function id's are positive integers, I can still keep the success/failure even if I return the function id. This is how I do it with GetFunctionIdByDecl().


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: All primitive types can be handled the same way using the GetArgPointer(). Non-primitive types are a bit different from the primitives in that you're getting a pointer to a pointer to the object, since that's how AngelScript stores the objects internally.
That complicates things a bit, and it also seems a bit backwards. If I'm passing something by value, why is there a pointer to it on the stack, and not the object itself?
Because it simplifies the compiler and the VM, in that all objects can be handled the same way.

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

SiCrane, I've checked in some of the changes you requested. The function id is now returned when registering functions, and the asIScriptGeneric interface has a method for returning the function id.

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

This topic is closed to new replies.

Advertisement