Problem with getting AS to see a pointer =/

Started by
8 comments, last by WitchLord 16 years, 8 months ago
Okay, so I've got a RoomCollection pointer, "all_rooms", in my ScriptingEngine class. Upon initialization, the pointer is given the address of the RoomCollection that holds all of my game's Rooms. It won't go out of scope, because if it were to, ScriptingEngine would go out of scope too, so that's not the problem here. However, when exposing said pointer like this:

   // register RoomCollection
   r = engine->RegisterObjectType("RoomCollection", sizeof(RoomCollection), asOBJ_CLASS); assert (r >= 0);
   r = engine->RegisterObjectMethod("RoomCollection", "Room& Get_Room(int id)", asMETHOD(RoomCollection,Get_Room), asCALL_THISCALL); assert (r >= 0);
   r = engine->RegisterObjectMethod("RoomCollection", "Room& Get_Room_By_Index(int index)", asMETHOD(RoomCollection,Get_Room_By_Index), asCALL_THISCALL); assert (r >= 0);
   r = engine->RegisterObjectMethod("RoomCollection", "uint Get_Size()", asMETHOD(RoomCollection,Get_Size), asCALL_THISCALL); assert (r >= 0);
   r = engine->RegisterObjectBehaviour("RoomCollection", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(RoomCollection_Cons), asCALL_CDECL_OBJLAST); assert (r >= 0);
   r = engine->RegisterObjectBehaviour("RoomCollection", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(RoomCollection_Dest), asCALL_CDECL_OBJLAST); assert (r >= 0);
   
   // register all rooms
   r = engine->RegisterGlobalProperty("RoomCollection rooms", all_rooms); assert (r >= 0);

However, whenever I try to access any piece of "rooms" in the script, it doesn't return anything. For instance, this:

   uint size = rooms.Get_Size();
   engine.Print("here's size: " + size);

prints nothing, and after this line no other "engine.Print()" will work. The engine doesn't report any build errors, or any errors whatsoever. I've exposed other objects before, but never an object pointer. Am I missing something here?
Advertisement
What is the C++ definition/declaration of "all_rooms"? Does it have a valid value at the time of registration with AngelScript?

I'm thinking it might be null, and when you call GetSize, an exception occurs inside AngelScript and it prevents the script from continuing.

What is the return value of asIScriptEngine::Execute[String] (depending on which you call to run your script)? Is it 0, or an error code?

While this isn't the problem, you probably want to register your "RoomCollection" object type with size 0, instead of sizeof(RoomCollection). This will prevent you from creating a RoomCollection in your script -- and then it will only be valid to access it through your "rooms" global. Also, if you do this, you don't have to register a constructor and destructor since RoomCollection will never be made in script.
Quote:Original post by midnite
What is the C++ definition/declaration of "all_rooms"? Does it have a valid value at the time of registration with AngelScript?


// in class ScriptingEngineRoomCollection *all_rooms;// in Init()all_rooms = NULL;


Now that you mention it, it was set to NULL originally when it was registered with AS. However, the reason why it was designated a pointer was so that additional changes could be accessed in the scripts; does AS not access the variable this pointer points to at all?

This is how it is called:
script_engine.Set_Rooms(&rooms);script_engine.Init();


Quote:
I'm thinking it might be null, and when you call GetSize, an exception occurs inside AngelScript and it prevents the script from continuing.

What is the return value of asIScriptEngine::Execute[String] (depending on which you call to run your script)? Is it 0, or an error code?


It would seem that you are correct. It's throwing a "null pointer" exception. However, as stated before, by the time a script is actually executed, the RoomCollection that all_rooms points to should be filled with data.

Quote:
While this isn't the problem, you probably want to register your "RoomCollection" object type with size 0, instead of sizeof(RoomCollection). This will prevent you from creating a RoomCollection in your script -- and then it will only be valid to access it through your "rooms" global. Also, if you do this, you don't have to register a constructor and destructor since RoomCollection will never be made in script.


Modifications made.

I'm finding it somewhat difficult to explain this problem thoroughly... so please don't hesitate to ask me more questions if you require more info about the problem.

Thanks!
When you do this...

r = engine->RegisterGlobalProperty("RoomCollection rooms", all_rooms); assert (r >= 0);


... AngelScript will store the address that all_rooms points to, not the address of all_rooms itself. Changing the address that all_rooms points to after you've registered the property won't affect the address that AngelScript holds.

If you want to be able to exchange the RoomCollection property without recompiling the scripts, then you must register it as an object handle, e.g:

r = engine->RegisterGlobalProperty("RoomCollection @rooms", &all_rooms); assert(r >= 0);


(You'll need to register the behaviours ADDREF and RELEASE for the type, even if they are only dummy functions that do nothing.)

If you register the property like this, then AngelScript is holding the address of the all_rooms variable, thus if you change the value of the variable, AngelScript will know about it.

It seems that you'd actually want the first alternative, i.e. the one you're already using, you just need to initialize the all_rooms variable with the correct address before registering the global property.

Regards,
Andreas

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:Original post by WitchLord
It seems that you'd actually want the first alternative, i.e. the one you're already using, you just need to initialize the all_rooms variable with the correct address before registering the global property.


Actually, I think he wants the handle way. He says that he will be changing the value of rooms while the application is running -- so, the only way to handle that is to register it as a handle.

Okay, I've found that the big problem with my code was that statements like Room r = rooms.Get_Room(6); crashed the program, so I added a asBEHAVE_ASSIGNMENT behaviour to Room and everything's fine, except for one thing.

Not sure if this is related or not... but...

   // this works as expected   string name = rooms.Get_Room(6).Get_Name();   engine.Print(name);      // this causes a debug assertion failure   engine.Print(rooms.Get_Room(6).Get_Name());


Room::Get_Name() returns a const string&, and as you can see, works fine if I assign it to an AS string first. Why does a direct call crash it?
Quote:Original post by midnite
Quote:Original post by WitchLord
It seems that you'd actually want the first alternative, i.e. the one you're already using, you just need to initialize the all_rooms variable with the correct address before registering the global property.


Actually, I think he wants the handle way. He says that he will be changing the value of rooms while the application is running -- so, the only way to handle that is to register it as a handle.


all_rooms is a pointer to rooms. Rooms may change, but the address of rooms that all_rooms holds will not.
Quote:Original post by derefed
Okay, I've found that the big problem with my code was that statements like Room r = rooms.Get_Room(6); crashed the program, so I added a asBEHAVE_ASSIGNMENT behaviour to Room and everything's fine, except for one thing.

Not sure if this is related or not... but...

*** Source Snippet Removed ***

Room::Get_Name() returns a const string&, and as you can see, works fine if I assign it to an AS string first. Why does a direct call crash it?


Hmm...

How is the engine.Print() method registered? And how is it implemented in C++?

My guess is that AngelScript is trying to call the ADDREF behaviour of the string type, which would try to increment a reference counter that doesn't exist.

It may be a problem with AngelScript though, so I'll investigate 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:Original post by WitchLord
Hmm...

How is the engine.Print() method registered? And how is it implemented in C++?



engine.Print() implementation:
class ScriptingEngine{   ...   string output;   ...}void ScriptingEngine::PrintString(const string &str){   output.append(str);}


Registration:
   RegisterScriptString(engine);         r = engine->RegisterObjectType("ScriptingEngine", sizeof(ScriptingEngine), asOBJ_CLASS); assert (r >= 0);   r = engine->RegisterObjectMethod("ScriptingEngine", "void Print(const string &str)", asMETHOD(ScriptingEngine,PrintString), asCALL_THISCALL); assert (r >= 0);      r = engine->RegisterGlobalProperty("ScriptingEngine engine", this); assert (r >= 0);
OK, the problem is as I suspected. AngelScript tries to call ADDREF on the string reference you're returning from Get_Name(), it does this so that it can make sure the string reference is guaranteed to stay alive while passing it to the Print() method.

You can fix this particular scenario by changing your registration of the Print() method to:

   r = engine->RegisterObjectMethod("ScriptingEngine", "void Print(const string &str)", asMETHOD(ScriptingEngine,PrintString), asCALL_THISCALL); assert (r >= 0);


I just exchanged the str for in.

By telling AngelScript that this is only an input reference, AngelScript takes a different approach to guaranteeing the lifetime of the object, i.e. it copies the string to a temporary variable instead of calling ADDREF.

However, you'll still have the same problem if, for example the script declares a function that takes a string as an inout reference, and then calls this function with the return value of Get_Name(). So maybe it is better to change your Get_Name() function to return a real asCScriptString reference instead of just a reference to a std::string, or maybe a new asCScriptString returned as object handle, for example:

asCScriptString *Room::Get_Name_Wrapper(){   return new asCScriptString(Get_Name());}engine->RegisterObjectMethod("Room", "string @ Get_Name()", asMETHOD(Room,Get_Name_Wrapper), asCALL_THISCALL); 


You could also register the std::string directly with AngelScript, instead of asCScriptString, but then you wouldn't be able to use object handles, nor inout references, though this may not be a problem for you.

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