• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
ZsjaWkaos

Passing an Opaque Pointer to AngelScript

12 posts in this topic

Here is what I would like to do:
[list][*]pass an opaque pointer (OP) from C++ to a function written in AngelScript (AS)[*]not allow AS to keep a copy around after the function has returned[*]allow AS to pass OP around to other functions (either written in C++ and registered in AS or written in AS)[*]use different types for different OPs, AS must not be able to pass the wrong OP to a function![/list]
The reasons for doing it this way are:
[list][*]to decouple any data stored in the OP from AS as much as possible (AS must not have any knowledge about OP's implementation details)[*]to keep any interfacing with AS very basic and simple (no need for any "tricky" reference counting, just passing a pointer around)[*]to avoid overhead from garbage collecting (AS will only keep OP around for the duration of the function call)[/list]
Here is my attempt but as I'm rather new to AS and after some trial and error I still didn't succeed (hence this post :wink:). Code snippet for C++:
[code]
class SomeData{
public:
void release(){
}
};

void print_some_data( SomeData &some_data ){
cout << "SomeData!" << endl;
}

/* ... */
r = engine->RegisterObjectType( "SomeData", 0, asOBJ_REF | asOBJ_SCOPED ); assert( r >= 0 );
r = engine->RegisterGlobalFunction( "void print_some_data( const SomeData &in )", asFUNCTION( print_some_data ), asCALL_CDECL ); assert( r >= 0 );
r = engine->RegisterObjectBehaviour( "SomeData", asBEHAVE_RELEASE, "void f( )", asMETHOD( SomeData, release ), asCALL_THISCALL ); assert( r >= 0 );
[/code]

Code for AS (test.as):
[code]
void main( SomeData some_data ){
print_some_data( some_data );
}
[/code]

I get the error. "test.as (1, 11) : ERR : Parameter type can't be 'SomeData'". I think it is because AS wants to create a new object SomeData but does not know how. However, I just want AS to keep the OP around and not do anything with it, except pass it to some functions.

Is what I want to do possible in AS and if so [i]how[/i]? It seems like an incredibly simple thing to do yet I can't figure it out. Any help will be greatly appreciated!
0

Share this post


Link to post
Share on other sites
[quote name='SiCrane' timestamp='1316870440' post='4865466']
Did you try just registering the pointer as a value type?
[/quote]

Thanks, I haven't thought of treating the OP as a value itself. :)

Here is my current solution that seems to work:
[code]
class SomeData{
public:
int x;
int y;
int z;
};

void print_some_data( SomeData **some_data ){
cout << "SomeData! " << *some_data << ", " << (*some_data)->x << ", " << (*some_data)->y << ", " << (*some_data)->z << endl;
}

static void construct_ptr( void **ptr ){
}

static void copy_construct_ptr( void **other, void **ptr ){
*ptr = *other;
}

static void destruct_ptr( void *ptr ){
}

/* ... */

r = engine->RegisterObjectType( "SomeData", sizeof( SomeData* ), asOBJ_VALUE | asOBJ_APP_CLASS_C | asOBJ_APP_CLASS_DESTRUCTOR ); assert( r >= 0 );
r = engine->RegisterObjectBehaviour( "SomeData", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION( construct_ptr ), asCALL_CDECL_OBJLAST ); assert( r >= 0 );
r = engine->RegisterObjectBehaviour( "SomeData", asBEHAVE_CONSTRUCT, "void f( const SomeData &in )", asFUNCTION( copy_construct_ptr ), asCALL_CDECL_OBJLAST ); assert( r >= 0 );
r = engine->RegisterObjectBehaviour( "SomeData", asBEHAVE_DESTRUCT, "void f()", asFUNCTION( destruct_ptr ), asCALL_CDECL_OBJLAST ); assert( r >= 0 );
[/code]

AS code is same as before.

Interestingly it even does not allow the value to be assigned to a global variable (no copy constructor) but it does allow it to be passed around. The C++ is a bit more verbose than I hoped for because of the double pointer maybe that can be improved somehow. On the AS side everything looks exactly like I want.
0

Share this post


Link to post
Share on other sites
You can simplify some of your C++ code by using references to pointer rather than pointers to pointers. Alternately you could use pass by value rather than pass by reference. Also, it doesn't look like your object flags match up to what they should be for a pointer type. If it hasn't caused you trouble so far, then it probably doesn't matter for your specific compiler, but be aware that your code may not port cleanly to other platforms.
0

Share this post


Link to post
Share on other sites
The code is now greatly simplified, I hope it is also correct.

[code]
class SomeData{
public:
int x;
int y;
int z;
};

void print_some_data( SomeData *some_data ){
cout << "SomeData! " << some_data << ", " << some_data->x << ", " << some_data->y << ", " << some_data->z << endl;
}

/* ... */

r = engine->RegisterObjectType( "SomeData", sizeof( SomeData* ), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS ); assert( r >= 0 );

[/code]

Instead of "asOBJ_APP_CLASS" "asOBJ_APP_PRIMITIVE" also works. However, I'm not passing an AS primitive so I think this is the right way to do it. C++ pointer types are [url="http://stackoverflow.com/questions/146452/what-are-pod-types-in-c"]plain old data[/url] types and I'm passing by value so I think this code should be correct. :)
0

Share this post


Link to post
Share on other sites
A pointer is a primitive in C++, so the correct flag to use is asOBJ_APP_PRIMITIVE.

You will want to register a default constructor behaviour for this type, which will simply make sure the pointer is 0 when the object is first declared. Otherwise you'll likely end up using invalid pointers from time to time.

Regards,
Andreas
0

Share this post


Link to post
Share on other sites
I've updated the C++ code as follows:
[code]
void zero_ptr( void* memory ){
cout << "zero_ptr" << endl;
memory = 0;
}

/* ... */
r = engine->RegisterObjectType( "SomeData", sizeof( SomeData* ), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_PRIMITIVE ); assert( r >= 0 );
r = engine->RegisterObjectBehaviour( "SomeData", asBEHAVE_CONSTRUCT, "void f( )", asFUNCTION( zero_ptr ), asCALL_CDECL_OBJLAST ); assert( r >= 0 );
[/code]

The AS code (this should not be allowed!):
[code]
void main( SomeData some_data ){
SomeData sd = SomeData();
}
[/code]

Unfortunately "zero_ptr" seems to be called very often instead of only for the construction of SomeData. I would like to raise an exception on the construction (or assignment) of SomeData because that shouldn't be possible.

EDIT:
Apparently this is also possible :(:
[code]
void main( SomeData some_data ){
SomeData sd;
}
[/code]
0

Share this post


Link to post
Share on other sites
You've registered the type as a POD. AngelScript will allow the construction and assignment of values of this type. You can set a script exception in the constructor and opAssign method, but if you do not allow the use of constructor/assignment then AngelScript will not be able to pass the value to other functions.



0

Share this post


Link to post
Share on other sites
Does this mean that passing a pointer around (as described in my first post) is an impossible thing to do in AS? Is there any chance that this can be added in a future release (I can take a look at it myself)?
0

Share this post


Link to post
Share on other sites
[quote]
[list][*]pass an opaque pointer (OP) from C++ to a function written in AngelScript (AS)[*]not allow AS to keep a copy around after the function has returned[*]allow AS to pass OP around to other functions (either written in C++ and registered in AS or written in AS)[*]use different types for different OPs, AS must not be able to pass the wrong OP to a function![/list]

[/quote]


Out of these 4 only the second cannot be done, or at least is difficult to do.

The other 3 I would solve as you've done, i.e. by registering the OP as a new value type. This will allow you to guarantee that the OP is always a valid pointer, or null, as the script will not be able to manipulate the actual address. You will also have the guarantee that only the correct type of OP is passed to a function, as AngelScript will not allow any type casting (unless explicitly registered by the application).

However, in order to not allow AS to keep a copy of the OP after the function returns, would mean that you know exactly what AS does with the OP. For example it should be allowed to store the OP in a local variable, but not a global variable, it must not be possible to store the OP in a class member, unless the class itself is guaranteed to die after the function returns. I'm not sure how you expect AngelScript to control this. You could try to do it yourself by enumerating all global variables after the function has returned and destroy the OP that might have been stored, and also look for objects that may be alive that also may keep the OP in one of its members.

If you really want full control over the OP, I suggest you either register it as a singleton with the asOBJ_NOHANDLE flag. This will allow the script to access the value through a registered global property, but not store or copy the value. On the other hand this also won't allow the script to pass the value as a parameter to a function, as accesses to the value has to go through the global property.

Alternatively you can implement a proxy class, with reference counting. Instead of passing the OP to the script, you would pass the proxy. The script would be able to pass the proxy around as a normal reference object, and would even be able to hold multiple copies of it, however as soon as you no longer want the script to have access to the OP the application clears the internal pointer in the proxy object. This won't destroy the actual proxy, but it would turn it into a null pointer that the script can handle at the next access. I do something similar to this in my own game engine. You can also check the 'game' sample in the latest release where I implemented this.
0

Share this post


Link to post
Share on other sites
[quote name='WitchLord' timestamp='1317173348' post='4866658']For example it should be allowed to store the OP in a local variable, but not a global variable, it must not be possible to store the OP in a class member, unless the class itself is guaranteed to die after the function returns. I'm not sure how you expect AngelScript to control this.[/quote]

A class with an OP shouldn't be stored globally. To make it easier I wouldn't mind simply disallowing all global variables (it's good design anyway) I think I can easily patch this in myself with the following code (in as_compiler.cpp, after line 6269):
[code]
if( found ){
Error( "ERROR: NO GLOBAL VARIABLES ALLOWED!", errNode );
return -1;
}
[/code]

All state data between calls will then be stored outside of AS.

This means that I'll be running a modified version of AS. I think making a better solution will require quite some work (disallowing classes with OPs to be stored in global variables) on your end and I don't want to deliver you any more work than necessary.

Unfortunately, I'm still not sure how disallow the following AS code (any pointers willbe greatly appreciated):

[code]
void main() {
SomeData some_data;
some_function( some_data );
}
[/code]

While still allowing:

[code]
void main() {
SomeData some_data = some_other_function();
}
[/code]

The "IsVariableInitialized" returned always "true" for the code I tested it with. I placed the following code right at the beginning/end of "asCCompiler::CompileVariableAccess" hoping that it would return false for the first case of "SomeData".
[code]
cout << " IsVariableInitialized" << name.AddressOf() << ", " << IsVariableInitialized( &ctx->type, errNode ) << endl;
[/code]

[quote name='WitchLord' timestamp='1317173348' post='4866658']Alternatively you can implement a proxy class [..] [/quote]

This is an interesting alternative, I'll consider it. I'm not a big fan of runtime errors when they can be found at compile time. There is also a (small?) amount of additional overhead/complexity.
0

Share this post


Link to post
Share on other sites
You don't have to modify the library to disable use of global variables. Just add a simple check after the compilation, e.g.

[code]
mod->Build();

// Check if global variables were declared
if( mod->GetGlobalVarCount() )
{
// Give error
return -1;
}
[/code]

I may however consider adding an engine property to disable global variables, that would allow you to do this with a simple engine->SetEngineProperty(asEP_DISABLE_GLOBAL_VARS);




Why do you have to disallow the declaration of a local variable of the OP type? The script cannot assign an invalid value to it anyway. It would just initialize to null, which your code is hopefully treating anyway.


However, if you really wish to prevent it, the best place to do it is the asCCompiler::CompileDeclaration(). Give an error in this function unless the declaration is followed by an assignment.


The IsVariableInitialized only works for primitives, as it is assumed that registered types have a proper default value.



Regards,
Andreas
0

Share this post


Link to post
Share on other sites
[quote name='WitchLord' timestamp='1317257416' post='4867020']
You don't have to modify the library to disable use of global variables. Just add a simple check after the compilation, e.g.
[/quote]

Neat! I'll check this out tomorrow, I didn't have time to code anything today.

[quote name='WitchLord' timestamp='1317257416' post='4867020']
Why do you have to disallow the declaration of a local variable of the OP type? The script cannot assign an invalid value to it anyway. It would just initialize to null, which your code is hopefully treating anyway.
[/quote]

That would be great. Even if it's not checked at compile time it would be enough for me to use it. If I can get both techniques working like you described I'll be using AngelCode in my next project. :D

Thanks for your patience and help.
0

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  
Followers 0