Jump to content
  • Advertisement
Sign in to follow this  
Alan Kemp

Registering type conversion functions

This topic is 5058 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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

Share this post


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

Share this post


Link to post
Share on other sites
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 &in)", asFUNCTION(AssignIntToString), asCALL_CDECL_OBJLAST);
engine->RegisterGlobalBehaviour(asBEHAVE_ADD, "string f(string &in, int &in)", 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!

Share this post


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

Share this post


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

Share this post


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

Share this post


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

Share this post


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



// correct
RegisterObjectBehaviour("string", asBEHAVE_ASSIGNMENT, "string &f(int)", asFUNCTION(AssignIntToString)...

// what I assumed
RegisterObjectBehaviour("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

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!