🎉 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!

native array support

Started by
27 comments, last by Deyja 19 years, 8 months ago
The thing is that the native arrays that AngelScript has (or will have in next WIP release) doesn't have a clean C++ interface like a registered vector does. Also a native array object can't be moved outside the engine, as it relies on the functions registered with the engine. (Though I'm trying to figure out how to work around this)

That is why I believe your generic vector binding will be a very good add-on for the library. It will also serve as an excellent example on how to register other object types.

No, I don't have any special needs. After all, I haven't begun using AngelScript myself in any project.

Just register the most obvious stuff, like the [] operator, resize(), length(), etc.

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
There are a few things I'm not clear on myself.

Operator[] - this should return a reference to the contained type. I'm not sure how this will interact with angelscript. I seem to remember failing horribly at binding an operator[] for std::string.
I think i have also failed at the [] operator.
Deyja are you on an IM?

My usernames are:
MSN: rimmer_41@hotmail.com
AIM: Katachasm
icq: 11585596
Yahoo: havokeachday
Okay; it works, but I think I may have found a bug - or atleast a pressing need for a certain feature. I originally tested with ints, and everything worked fine. Then I tried with int8s, and it broke. The function push_back on the int8 vector expected an int8, of course. I called it with an integer literal. AngelScript did not convert it, but called the function anyway - crash, boom, die, as you probably expected. Theres a simple workaround demonstrated by my test script.

Usage: RegisterVector<int>("intvec","int",engine)

as_vector.h
#ifndef JM_AS_VECTOR_H#define JM_AS_VECTOR_H#include <vector>#include <string>#include "angelscript.h"#include <assert.h>template <typename T>class vectorRegisterHelper{public:	static void Construct(std::vector<T>* in)	{		new (in) std::vector<T>();	}				static void Destruct(std::vector<T>* in)	{		in->~vector();	}		static void CopyConstruct(const std::vector<T>& rhs, std::vector<T>* in)	{		new (in) std::vector<T>(rhs);	}	static void NumConstruct(int size, std::vector<T>* in)	{		new (in) std::vector<T>(size);	}	static std::vector<T>& Assign(const std::vector<T>& rhs, std::vector<T>* lhs)	{		*lhs = rhs;		return *lhs;	}	static T& Index(int i, std::vector<T>* lhs)	{		return (*lhs);	}	static int Size(std::vector<T>* lhs)	{		return lhs->size();	}	static void Resize(int size, std::vector<T>* lhs)	{		lhs->resize(size);	}	static void PushBack(const T& in, std::vector<T>* lhs)	{		lhs->push_back(in);	}	static void PopBack(std::vector<T>* lhs)	{		lhs->pop_back();	}};template <typename T>RegisterVector(const std::string V_AS, //The typename of the vector inside AS			   const std::string T_AS, //Template parameter typename in AS - must already be			   asIScriptEngine* engine)			//registered (or be primitive type)!!{	assert(engine && "Passed NULL engine pointer to registerVector");	int error_code = 0;	error_code = engine->RegisterObjectType(V_AS.c_str(), sizeof(std::vector<T>), asOBJ_GUESS);	assert(error_code == 0 && "Failed to register object type");		error_code = engine->RegisterObjectBehaviour(V_AS.c_str(), 		asBEHAVE_CONSTRUCT, 		"void f()", 		asFUNCTION(vectorRegisterHelper<T>::Construct), 		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register constructor");		error_code = engine->RegisterObjectBehaviour(V_AS.c_str(),		asBEHAVE_DESTRUCT,		"void f()",		asFUNCTION(vectorRegisterHelper<T>::Destruct),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register destructor");		error_code = engine->RegisterObjectBehaviour(V_AS.c_str(),		asBEHAVE_CONSTRUCT,		(std::string("void f( const ")+V_AS+"&)").c_str(),		asFUNCTION(vectorRegisterHelper<T>::CopyConstruct),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register copy constructor");		error_code = engine->RegisterObjectBehaviour(V_AS.c_str(),		asBEHAVE_CONSTRUCT,		"void f(int)",		asFUNCTION(vectorRegisterHelper<T>::NumConstruct),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register construct(size)");	error_code = engine->RegisterObjectBehaviour(V_AS.c_str(),		asBEHAVE_INDEX,		(T_AS+"& f(int)").c_str(),		asFUNCTION(vectorRegisterHelper<T>::Index),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register operator[]");		error_code = engine->RegisterObjectBehaviour(V_AS.c_str(),		asBEHAVE_ASSIGNMENT,		(V_AS+"& f(const "+V_AS+"&)").c_str(),		asFUNCTION(vectorRegisterHelper<T>::Assign),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register operator=");		error_code = engine->RegisterObjectMethod(V_AS.c_str(),		"int size()",		asFUNCTION(vectorRegisterHelper<T>::Size),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register size");		error_code = engine->RegisterObjectMethod(V_AS.c_str(),		"void resize(int)",		asFUNCTION(vectorRegisterHelper<T>::Resize),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register resize");		error_code = engine->RegisterObjectMethod(V_AS.c_str(),		(std::string("void push_back(const ")+T_AS+"&)").c_str(),		asFUNCTION(vectorRegisterHelper<T>::PushBack),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register push_back");		error_code = engine->RegisterObjectMethod(V_AS.c_str(),		"void pop_back()",		asFUNCTION(vectorRegisterHelper<T>::PopBack),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register pop_back");}#endif


Test script (You'll need the output functions in my preprocessor. And you'll need to write your own int8 print function.)
#warning "testing error output."void Test(){	Print("Entering vector test function.\n");	TestInt();	TestChar();}void TestInt(){	Print("\n\nTesting with ints.\n");	Print("Declaring vector A, constructing with size 5.\n");	intvec A(5);	Print("Sive of A is ");	Print(A.size());	Print("\nPushing element into A. Size is now ");	A.push_back(6);	Print(A.size());	Print("\n");	Print("Testing copy constructor and assignment operator.\n");	intvec B(A);	Print("Size of B is ");	Print(B.size());	Print("\n");	A.pop_back();	Print("Popped from A. Size of B is ");	Print(B.size());	Print("\n");	Print("Size of A is ");	Print(A.size());	Print("\n");	Print("Assigning B to A. Size of A is ");	A = B;	Print(A.size());	Print("\n");	Print("Resizing A.\n");	A.resize(8);	Print("Size of A is ");	Print(A.size());	Print("\n");	Print("Testing operator[].\n");	Print("A[1] is ");	Print(A[1]);	Print("\nAssigning to A[1]. A[1] is ");	A[1] = 20;	Print(A[1]);	Print("\n");}void TestChar(){	Print("\n\nTesting with chars.\n");	Print("Declaring vector A, constructing with size 5.\n");	cvec A(5);	Print("Sive of A is ");	Print(A.size());	Print("\nPushing element into A.\n"); 	Print("Problem here: AngelScript will not implicitly convert an int to an int8, which will make the program crash.\n");	//Workaround for possible bug.	int8 t = 6;	A.push_back(t);	Print("Size is now ");	Print(A.size());	Print("\n");	Print("Testing copy constructor and assignment operator.\n");	cvec B(A);	Print("Size of B is ");	Print(B.size());	Print("\n");	A.pop_back();	Print("Popped from A. Size of B is ");	Print(B.size());	Print("\n");	Print("Size of A is ");	Print(A.size());	Print("\n");	Print("Assigning B to A. Size of A is ");	A = B;	Print(A.size());	Print("\n");	Print("Resizing A.\n");	A.resize(8);	Print("Size of A is ");	Print(A.size());	Print("\n");	Print("Testing operator[].\n");	Print("A[1] is ");	Print(A[1]);	Print("\nAssigning to A[1]. A[1] is ");	A[1] = 20;	Print(A[1]);	Print("\n");}
You've registered the indexing operator correctly. Though by default I'd prefer if you added an out of bounds check. I could do that myself of course.

Would you like to add a copyright notice (and/or license) to the top of the file before I release it with the library?

I'll look into the int8 problem as soon as I get the time. I'm trying to finish up the array support for a first public release right now.

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

The problem seems to be that Angelscript does not implicitly convert primitives. I tried a cast workaround, but discovered that Angelscript does not understand the (type)expression syntax. The type(expression) syntax works, though.

I come from a C background. Thus, I tend to think array bounds checking is just uneccesary fluf. A properly written program doesn't overwrite bounds anyhow. I'll have to go see about raising exceptions in Angelscript to do bounds checking properly. Don't release it yet. I'll rip off your copyright header and bend it to my own means - and I also still need to do a insert and erase functions. And iterators, if anyone wants them. (For a vector, the iterator is just a pointer to the element type - but it will need ranged versions of most of the methods.)
I agree with you that in most situations bounds checking isn't necessary, but for scripting it is an absolute necessity as you don't know who will write the scripts. A script shouldn't be able to crash the application in anyway.

I don't think you need to register the iterators, at least not in the first version.


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

/*   std::vector binding library for AngelScript   Copyright (c) 2004 Anthony "JM" Casteel   This software is provided 'as-is', without any express or implied    warranty. In no event will the authors be held liable for any    damages arising from the use of this software.   Permission is granted to anyone to use this software for any    purpose, including commercial applications, and to alter it and    redistribute it freely, subject to the following restrictions:   1. The origin of this software must not be misrepresented; you       must not claim that you wrote the original software. If you use	  this software in a product, an acknowledgment in the product 	  documentation would be appreciated but is not required.   2. Altered source versions must be plainly marked as such, and       must not be misrepresented as being the original software.   3. This notice may not be removed or altered from any source       distribution.   The original version of this library can be located at:   http://www.angelcode.com/angelscript/   AngelScript Copyright (c) 2003-2004 Andreas Jönsson (andreas@angelcode.com)   Anthony "JM" Casteel   DeyjaL@AOL.com*/#ifndef JM_AS_VECTOR_H#define JM_AS_VECTOR_H#define AS_VECTOR_CHECKBOUNDS#include <vector>#include <string>#include <assert.h>#include "angelscript.h"template <typename T>class vectorRegisterHelper{public:	static void Construct(std::vector<T>* in)	{		new (in) std::vector<T>();	}				static void Destruct(std::vector<T>* in)	{		in->~vector();	}		static void CopyConstruct(const std::vector<T>& rhs, std::vector<T>* in)	{		new (in) std::vector<T>(rhs);	}	static void NumConstruct(int size, std::vector<T>* in)	{		new (in) std::vector<T>(size);	}	static std::vector<T>& Assign(const std::vector<T>& rhs, std::vector<T>* lhs)	{		*lhs = rhs;		return *lhs;	}	static T& Index(int i, std::vector<T>* lhs)	{#ifdef AS_VECTOR_ASSERTBOUNDS		assert(i >= 0 && i < lhs->size() && "Array index out of bounds.");#endif#ifdef AS_VECTOR_CHECKBOUNDS		if (i < 0 || i >= lhs->size())		{			asIScriptContext* context = asGetActiveContext();			context->SetException("Array Index Out of Bounds.");		}#endif		return (*lhs);	}	static int Size(std::vector<T>* lhs)	{		return lhs->size();	}	static void Resize(int size, std::vector<T>* lhs)	{		lhs->resize(size);	}	static void PushBack(const T& in, std::vector<T>* lhs)	{		lhs->push_back(in);	}	static void PopBack(std::vector<T>* lhs)	{		lhs->pop_back();	}	static void Erase(int i, std::vector<T>* lhs)	{		lhs->erase(&Index(i,lhs));	}	static void Insert(int i, const T& e, std::vector<T>* lhs)	{		lhs->insert( &Index(i,lhs), e);	}};template <typename T>RegisterVector(const std::string V_AS, //The typename of the vector inside AS			   const std::string T_AS, //Template parameter typename in AS - must already be			   asIScriptEngine* engine)			//registered (or be primitive type)!!{	assert(engine && "Passed NULL engine pointer to registerVector");	int error_code = 0;	error_code = engine->RegisterObjectType(V_AS.c_str(), sizeof(std::vector<T>), asOBJ_GUESS);	assert(error_code == 0 && "Failed to register object type");		error_code = engine->RegisterObjectBehaviour(V_AS.c_str(), 		asBEHAVE_CONSTRUCT, 		"void f()", 		asFUNCTION(vectorRegisterHelper<T>::Construct), 		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register constructor");		error_code = engine->RegisterObjectBehaviour(V_AS.c_str(),		asBEHAVE_DESTRUCT,		"void f()",		asFUNCTION(vectorRegisterHelper<T>::Destruct),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register destructor");		error_code = engine->RegisterObjectBehaviour(V_AS.c_str(),		asBEHAVE_CONSTRUCT,		(std::string("void f( const ")+V_AS+"&)").c_str(),		asFUNCTION(vectorRegisterHelper<T>::CopyConstruct),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register copy constructor");		error_code = engine->RegisterObjectBehaviour(V_AS.c_str(),		asBEHAVE_CONSTRUCT,		"void f(int)",		asFUNCTION(vectorRegisterHelper<T>::NumConstruct),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register construct(size)");	error_code = engine->RegisterObjectBehaviour(V_AS.c_str(),		asBEHAVE_INDEX,		(T_AS+"& f(int)").c_str(),		asFUNCTION(vectorRegisterHelper<T>::Index),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register operator[]");		error_code = engine->RegisterObjectBehaviour(V_AS.c_str(),		asBEHAVE_ASSIGNMENT,		(V_AS+"& f(const "+V_AS+"&)").c_str(),		asFUNCTION(vectorRegisterHelper<T>::Assign),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register operator=");		error_code = engine->RegisterObjectMethod(V_AS.c_str(),		"int size()",		asFUNCTION(vectorRegisterHelper<T>::Size),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register size");		error_code = engine->RegisterObjectMethod(V_AS.c_str(),		"void resize(int)",		asFUNCTION(vectorRegisterHelper<T>::Resize),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register resize");		error_code = engine->RegisterObjectMethod(V_AS.c_str(),		(std::string("void push_back(const ")+T_AS+"&)").c_str(),		asFUNCTION(vectorRegisterHelper<T>::PushBack),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register push_back");		error_code = engine->RegisterObjectMethod(V_AS.c_str(),		"void pop_back()",		asFUNCTION(vectorRegisterHelper<T>::PopBack),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register pop_back");	error_code = engine->RegisterObjectMethod(V_AS.c_str(),		"void erase(int)",		asFUNCTION(vectorRegisterHelper<T>::Erase),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register erase");	error_code = engine->RegisterObjectMethod(V_AS.c_str(),		(std::string("void insert(int, const ")+T_AS+"&)").c_str(),		asFUNCTION(vectorRegisterHelper<T>::Insert),		asCALL_CDECL_OBJLAST);	assert(error_code == 0 && "Failed to register insert");}#endif


Operator[] is now bounds-checked. The bounds checking strategy is configurable with #defines. It can assert, raise an exception, or do nothing at all. The new erase and insert functions piggyback on the index function, so they are bounds-checked too.

It needs tested. So please, someone, put it to use!

This topic is closed to new replies.

Advertisement