Advertisement

Registering type conversion functions

Started by January 08, 2005 02:38 AM
7 comments, last by WitchLord 19 years, 10 months ago
Hi, I've upgraded to WIP2 and thats sorted out all my string passing problems. Now Im back to trying to get type conversion working. I'm only really intrested in converting the basic math types to string, not the other way around. I have registered my conversion function as:

engine->RegisterObjectBehaviour("string", asBEHAVE_ASSIGNMENT, "string &f(int &in)", asFUNCTION(IntToString), asCALL_CDECL_OBJLAST);

My first implementation of IntToString looked like this:

string & IntToString(int &i)
{
	stringstream stream;
	stream << i;
	return stream.str();
}

Which doesn't work as its returning a reference to a temporary local object. So I tried the slighly more hideous:

string & IntToString(int &i)
{
	string * str = new string;

	stringstream stream;
	stream << i;
	*str = stream.str();
	return *str;
}

Which solves the problem of returning a local variable, but leaks memory, and doesn't work. The problem as I see it is, the VM allocates a new string to recieve the contents of the asBEHAVE_ASSIGNMENT function call, but there is no way for IntToString to know where that string is. The normal string::operator= which gets registered (and works) is working on itself (its a member function of the string object to be assigned to). I can think of two solutions. 1, use helper functions like AssignIntToString(string&,int&). Thats not really what I want to do as it means you cant write more natural expressions like file="file" + 2; 2, subclass std::string and add member functions for converting from int,float,etc. Any thoughts? Am I just missing something really obvious? Do you want bug reports of script code that triggers asserts in the compiler? If you do,

	string a = "a";
	string b = "b";
	string c = "c";

	string d = b + c = a;

Alan
"There will come a time when you believe everything is finished. That will be the beginning." -Louis L'Amour
Quote: Angelscript Docs
int RegisterObjectBehaviour(const char *datatype,
asDWORD behaviour,
const char *declaration,
asUPtr funcPointer,
asDWORD callConv);

The following functions must be registered as object members, i.e. with datatype set to the type name. The allowed calling conventions are asCALL_THISCALL and asCALL_CDECL_OBJLAST.

asBEHAVE_ASSIGNMENT =, (copy) OBJ &f(OBJ &) OBJ &OBJ::f(OBJ &other), OBJ &f(OBJ &other, OBJ &dst)


This to me would suggest that you could do something like this (however I don't have the latest version installed so can't test it, but worth a try)
string & IntToString(int &i, string &dest){	istringstream stream;	stream << i;	dest = stream.str();	return dest;}


Perhaps then you would need to overload the addition operator (which is a global function, this has caught me out before!), so you could do this:
string myString = "Bitmap" + 3 + ".bmp"


Just a stab in the dark, hope it helps.
Advertisement
OK, I've stopped being lazy and downloaded the latest version of AS to test out the function below and it does work! Here's the basic declarations needed:
engine->RegisterObjectBehaviour("string", asBEHAVE_ASSIGNMENT, "string & f(int ∈)", asFUNCTION(AssignIntToString), asCALL_CDECL_OBJLAST);engine->RegisterGlobalBehaviour(asBEHAVE_ADD, "string f(string ∈, int ∈)", asFUNCTION(AddIntToString), asCALL_CDECL);

and here's the full (messy) code, I just hacked the "testexecutescript.cpp" file to this:
#include <iostream>#include <string>#include <sstream>#include "stdstring.h"#include "utils.h"static asIScriptEngine * engine;std::string scriptCode ="int main()\n""{\n""	string test;\n""	test = \"Hello\";\n""	test = 3;\n""	test = \"Number:\" + 3;\n""	return 0;\n""}";std::string & AssignIntToString(int & i, std::string & dest){	std::ostringstream stream;	stream << i;	dest = stream.str(); //Assign to string	return dest;}std::string AddIntToString(std::string & dest, int & i){	std::ostringstream stream;	stream << i;	dest += stream.str(); //Add it onto string	return dest;}int LoadScript(){	// Give the code to the script engine	int r = engine->AddScriptSection(0, "Test", scriptCode.c_str(), scriptCode.size(), 0);	if (r < 0)	{		std::cout << "An error occured while adding the script section." << std::endl;		return r;	}	RegisterStdString(engine);	engine->RegisterObjectBehaviour("string", asBEHAVE_ASSIGNMENT, "string & f(int &in)", asFUNCTION(AssignIntToString), asCALL_CDECL_OBJLAST);	engine->RegisterGlobalBehaviour(asBEHAVE_ADD, "string f(string &in, int &in)", asFUNCTION(AddIntToString), asCALL_CDECL);	return 0;}int CompileScript(){	// Create an output stream that will receive information about the build	COutStream out;	int r = engine->Build(0, &out);	if (r < 0)	{		std::cout << "Failed to compile script." << std::endl;		return -1;	}	// If we wish to build again, the script sections has to be added again.	// Now we will verify the interface of the script functions we wish to call	const char * buffer = engine->GetFunctionDeclaration(engine->GetFunctionIDByName(0, "main"));	if( buffer == 0 )	{		std::cout << "Failed to retrieve declaration of function 'main'." << std::endl;		return -1;	}	return 0;}bool ExecuteScript(){	// Create a context in which the script will be executed. 	asIScriptContext * ctx;	int r = engine->CreateContext(&ctx);	if (r < 0)	{		std::cout << "Failed to create a context." << std::endl;		return true;	}	// Prepare the context for execution	r = ctx->Prepare(engine->GetFunctionIDByName(0, "main"));	if (r < 0)	{		std::cout << "Failed to prepare context." << std::endl;		return true;	}	// Execute script	r = ctx->Execute();	if (r < 0)	{		std::cout << "Unexpected error during script execution." << std::endl;		return true;	}	if (r == asEXECUTION_EXCEPTION)	{		std::cout << "An exception occured during execution." << std::endl;		// Print exception description		int funcID = ctx->GetExceptionFunction();		std::cout << "func: " << engine->GetFunctionName(funcID) << std::endl;		std::cout << "line: " << ctx->GetExceptionLineNumber() << std::endl;		std::cout << "desc: " << ctx->GetExceptionString() << std::endl;	}	ctx->Release();	return false;}int main(){	engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);	if (LoadScript() < 0 )	    return -1;	CompileScript();	ExecuteScript();	engine->Release();	engine = NULL;	system("PAUSE");	return 0;}

Like I said, very messy, but it was just to prove that it works.

Happy coding!
Thanks a lot, that really helps. I must have read that paragraph of the docs a dozen times and somehow not realised what it was saying.

Anyway, using your code I think I found an Angelscript bug:

This script will run fine:

string a = "a";a = 2;


But this script will generate a runtime error of "Can't implicitly convert from 'const uint' to 'string&'":

string a = 1;


It only seems to cause problems when you try and assign a number in the same statement you declare the variable (so string a; a = 1; works fine).

Alan
"There will come a time when you believe everything is finished. That will be the beginning." -Louis L'Amour
That does sound strange. My guess would be that the compiler is trying to use a constructor that takes an int, instead of creating a new string and then assigning it. You can register constructors that take parameters, so perhaps you also need to provide a constructor that takes an int.

I'm not too sure about this though, probably the best man to tell you would be witchlord.
desertcube: Thanks for helping out.

Alan: I'm not very good with writing manuals (probably because I think it is so boring). Do you have a suggestion on how I could improve that part of the text to make it more understandable?

The problem with initializing the string with an integer in the declaration is most likely a bug in the library. The compiler isn't intelligent enough (yet) to use a constructor that takes an integer to initialize the code, so I'll have to find out why the assignment doesn't work in the declarations. It shouldn't be that difficult, I'll probably have the fix for WIP 3.

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

Advertisement
I've found the bug, and I'm currently working on the fix for it.

In the meantime I would like to suggest a slightly different implementation for the functions that you register with the library. These have a slight improvement in efficiency since the library doesn't have to make sure the integer value is a reference, which means less copying when using constants.

static string &AssignIntToString(int i, string &dest){	ostringstream stream;	stream << i;	dest = stream.str(); 	return dest;}static string &AddAssignIntToString(int i, string &dest){	ostringstream stream;	stream << i;	dest += stream.str(); 	return dest;}static string AddStringInt(string &str, int i){	ostringstream stream;	stream << i;	str += stream.str(); 	return str;}static string AddIntString(int i, string &str){	ostringstream stream;	stream << i;	return stream.str() + str;}// Automatic conversion from intr = engine->RegisterObjectBehaviour("string", asBEHAVE_ASSIGNMENT, "string &f(int)", asFUNCTION(AssignIntToString), asCALL_CDECL_OBJLAST); assert( r >= 0 );r = engine->RegisterObjectBehaviour("string", asBEHAVE_ADD_ASSIGN, "string &f(int)", asFUNCTION(AddAssignIntToString), asCALL_CDECL_OBJLAST); assert( r >= 0 );r = engine->RegisterGlobalBehaviour(asBEHAVE_ADD,         "string f(string &in, int)", asFUNCTION(AddStringInt), asCALL_CDECL); assert( r >= 0 );r = engine->RegisterGlobalBehaviour(asBEHAVE_ADD,         "string f(int, string &in)", asFUNCTION(AddIntString), asCALL_CDECL); assert( r >= 0 );


These four functions allow you to do the following operations:

string s;int i;s = i;s += i;s = s + i;s = i + s;


You could also implement a constructor that take an int to allow the script writer to write: string(1), but that could perhaps be confused for an operation that creates a string with a length of 1. Just a thought, it's up to you to decide how to do it.

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:
I'm not very good with writing manuals (probably because I think it is so boring). Do you have a suggestion on how I could improve that part of the text to make it more understandable?


The biggest problem I had was having to guess at syntax for how the script/c++ function should match up. In particular, for the version where you pass dest as the second parameter I assumed that when you registered the function you would have to tell AS about which version you wanted it to call (else, how does it know when to pass dest and when not to):

// correctRegisterObjectBehaviour("string", asBEHAVE_ASSIGNMENT, "string &f(int)", asFUNCTION(AssignIntToString)...// what I assumedRegisterObjectBehaviour("string", asBEHAVE_ASSIGNMENT, "string &f(int, string &)", asFUNCTION(AssignIntToString)


Even just some small snippets of code in there to show what syntax you should use with which function declaration would be a huge help. Also, all the current docs use a generic keyword TYPE as an example for the return and parameter type. At first I wasn't even sure if these were allowed to be different types (maybe it could be clarified that they can).

Alan
"There will come a time when you believe everything is finished. That will be the beginning." -Louis L'Amour
Thanks, I'll update the docs with your suggestion.

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