Advertisement

Trying to call a global property's function fails when accessing its own members

Started by July 05, 2017 03:45 PM
8 comments, last by WitchLord 7 years, 4 months ago

Hi, I'm new to angelcode and have a nooby question. I have a class called "DebugConsole" which simply prints debug messages to the screen when you call its public "println" method. That works perfectly fine in C++, but is causing issues when I try to do it from angelscript.

The way it works is that DebugConsole owns an std::vector of pointers to TextNodes (which are nodes in my engine that render strings in the render loop). This means that the DebugConsole only works if it has a valid array of those textnodes. However, when I call "println" on the DebugConsole handle, which I've registered as a global property, I know that the println function is successfully called because I'm printing logs to stdout, but when it tries to access the array of textnodes, it seems to find garbage data. This makes me suspect that the pointer Angelscript has is invalid.

Here is how I'm initializing everything:


//Register type
engine->RegisterObjectType("DebugConsole", 0, asOBJ_REF|asOBJ_NOCOUNT);

//Register method
engine->RegisterObjectMethod("DebugConsole","void println(string)", asMETHOD(DebugConsole, println), asCALL_THISCALL);

...

//This works, confirming that 'dbg' points to a valid DebugConsole instance
dbg->println("Hello from C++!"); 

//Register handle as global property
game->angel->getScriptEngine()->RegisterGlobalProperty("DebugConsole@ Console", dbg);

//Compilation; works. (see below for createModuleFromSource impl.)
int ret = game->angel->createModuleFromSource("testscr",src,true);
if(ret!=0){
    Log::i("playscene", "compilation failed!");
}else{
    game->angel->addContextByDecl(0, "testscr", "void main()");
    //above just calls CContextMgr::AddContext(engine, engine->GetModule("testscr")->GetFunctionByDecl("void main()"), false);
}


//Angelscript source:
void main(){
    string msg = "Hello from script!";
    Console.println(msg);
}

 

Now here is the implementation of the println method in DebugConsole:


void DebugConsole::println(std::string ln){
    //Print stuff to stdout for debugging
    Log::I() << "DebugConsole::println('" << str << "');" << std::endl;
    Log::I() << "numLines: " << numLines << std::endl;
    Log::I() << "lines.size: " << lines.size() << std::endl;

    lines[cline]->setText(str);
    cline++;
    if(cline>=lines.size())
        cline = 0;
}

 

Finally, when I run the program, this is what the output looks like:

First, the "Hello from C++" line is called, and it works as expected:

Quote

DebugConsole::println: 'Hello from C++!'

numLines: 5

lines.size: 5

Then, the Angelscript is compiled and executed, and this is printed before the program crashes:

Quote

DebugConsole::println: 'Hello from script!'

numLines: 1414866944

lines.size: 18446744073709545184

As you can see, numLines/lines.size are just a bunch of random values, yet the function is successfully printing the debug stuff.

 

So what could be the issue here? DebugConsole is supposed to be a singleton that will outlast any script, which is why I disabled reference counting and didn't implement any ctors/dtors. The pointer I'm passing to RegisterGlobalProperty is a valid one that has been tested to work (it was also initialized on the heap, so it's not getting deleted automatically or anything like that, at least afaik). The object method registration is seemingly fine since it is being called successfully and even printing debug logs to stdout.

I appreciate any help!

 

createModuleFromSource implementation:


int createModuleFromSource(const char *mname, const char *src, bool compile){
    int r;
    CScriptBuilder builder;
    r = builder.StartNewModule(engine, mname);
    if(r<0){
        Log::e("angel", "Failed to create module");
        return 1;
    }
    r = builder.AddSectionFromMemory(mname, src, strlen(src), 0);
    if(r<0){
        Log::e("angel", "Failed to add section");
        return 2;
    }
    if(compile){
        r = builder.BuildModule();
        if(r<0){
            Log::e("angel", "Failed to compile module");
            return 3;
        }
    }
    return 0;
}

 

 


game->angel->getScriptEngine()->RegisterGlobalProperty("DebugConsole@ Console", dbg);

This line tells AngelScript that the property "Console" is a handle (a.k.a. pointer) of type DebugConsole. RegisterGlobalProperty expects to receive the address to the value of the type. So you should be informing the address of the pointer to the DebugConsole.

Two alternate solutions:

1. add & in front of dbg to inform the address of the pointer as the argument.

2. change the declaration to "DebugConsole Console" to tell AngelScript that the property is not a handle, but is the actual object.

I suspect you really want to use alternative 2, unless you intend to allow the script to reassign the property to a different instance of a DebugConsole object in some circumstances.

 

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

Advertisement

Thanks for the response, that actually clears up a few things that I didn't quite get while reading through the documentation.

I did as you suggested and changed it from a handle to a regular object. It still doesn't work though, but it's a different issue I think. Once the function is called the program still crashes, but instead of numLines/lines.size being random numbers, they're zero.

At first I thought it was making a shallow copy of the object, so I added some logging to the copy constructor, but that's never being called.

So then I decided to print out 'this' pointer in the println message, and when I did that I saw the problem: the pointer Angelscript is using isn't the same one I gave it. Whatever that pointer is pointing to isn't a valid DebugConsole (which needs to be configured before use).

Here is my new registration code:


game->angel->getScriptEngine()->RegisterGlobalProperty("DebugConsole Console", dbg);

I just got rid of the handle in the declaration because you were right about me not wanting that.

I added this line to DebugConsole::println:


Log::I() << "DebugConsole == " << this << std::endl;

And the output now looks something like this:

Quote

DebugConsole::println: 'Hello from C++!'

DebugConsole == 0x2ceacc0

numLines: 5

lines.size: 5

DebugConsole::println: 'Hello from script!'

DebugConsole == 0x2cfacbf

numLines: 0

lines.size: 0

Any idea why this could be happening? I've been wrestling with this issue for a while, and am wondering if it'd be easier if I just created a proper factory for this class. Or just used a wrapper function without passing any pointers to the scripting engine at all.

How is the dbg global variable declared in C++?

What is its class hierarchy, is it a class with no base classes, does it use multiple inheritance, etc.

Also, try using a const reference to the string. There may be an issue with pass by value use:

 

DebugConsole doesn't inherit from anything else. It's a very simple class I made just for testing my angelscript setup. Here's the full source for it:


//DebugConsole.h
//#includes omitted

class DebugConsole{
    int numLines;
    bool visible = true;
    std::vector<TextNode*> lines;
    TTF_Font *font;

    SDL_Rect pos;
    int vdir = 1;

    FontWorker::stTextAtlas regAtlas;
    FontWorker::stTextAtlas warnAtlas;
    FontWorker::stTextAtlas errorAtlas;

    unsigned int cline;

    public:
        DebugConsole();
        DebugConsole (const DebugConsole &cpy);
        virtual ~DebugConsole();
        void setup(int, Graph *, TTF_Font *, SDL_Renderer *r);
        DebugConsole& operator<<(const std::string&);
        void println(const std::string&);
    private:
        void createNodes(SDL_Renderer *);
};

and


//DebugConsole.cpp
//#includes omitted

DebugConsole::DebugConsole():pos(){
    //ctor
    Log::I("DebugConsole") << "DebugConsole::CTOR" << std::endl;
}

DebugConsole::DebugConsole(const DebugConsole &cpy){
    //copy
    Log::I("DebugConsole") << "DebugConsole::COPY-CTOR" << std::endl;
}

DebugConsole::~DebugConsole(){
    //dtor
    Log::I("DebugConsole") << "DebugConsole::DTOR" << std::endl;
}

void DebugConsole::setup(int nlines, Graph *scenegraph, TTF_Font *f, SDL_Renderer *r){
    numLines = nlines;
    font = f;
    createNodes(r);
    for(auto &n: lines)
        scenegraph->insertNode(n);

    for(int i = 0; i<numLines; i++){
        lines[i]->rect.x = pos.x;
        lines[i]->rect.y = pos.y + (40 * i * vdir);
    }
}

void DebugConsole::createNodes(SDL_Renderer *r){
    regAtlas = FontWorker
        ::generateTextAtlasASCIIPrint(
            r,
            font,
            1024,
            256,
            [](TTF_Font *f, Uint16 c) -> SDL_Surface *{
                return TTF_RenderGlyph_Solid(f, c, {0, 255, 0, 255});
            }
        );
    warnAtlas = FontWorker
        ::generateTextAtlasASCIIPrint(
            r,
            font,
            1024,
            256,
            [](TTF_Font *f, Uint16 c) -> SDL_Surface *{
                return TTF_RenderGlyph_Shaded(f, c, {0, 0, 255, 255}, {255, 255, 0, 255});
            }
        );
    errorAtlas = FontWorker
        ::generateTextAtlasASCIIPrint(
            r,
            font,
            1024,
            256,
            [](TTF_Font *f, Uint16 c) -> SDL_Surface *{
                return TTF_RenderGlyph_Blended(f, c, {255, 255, 255, 255});
            }
        );

    for(int i=0; i<numLines; i++){
        TextNode *tn = new TextNode("", 0, 800);
        tn->setAtlas(errorAtlas.atlas);
        tn->setAtlasMap(regAtlas.atlasmap);
        lines.push_back(tn);
    }
}

DebugConsole& DebugConsole::operator<<(const std::string &str){
    Log::I() << "DebugConsole::operator<<('" << str << "');" << std::endl;
    Log::I() << "numLines: " << numLines << std::endl;
    Log::I() << "lines.size: " << lines.size() << std::endl;
    lines[cline]->setText(str);
    cline++;
    if(cline>=lines.size())
        cline = 0;
    return *this;
}

void DebugConsole::println(const std::string &ln){
    Log::I() << "DebugConsole::println: '" << ln << "'" << std::endl;
    Log::I() << "DebugConsole == " << this << std::endl;
    Log::I() << "numLines: " << numLines << std::endl;
    Log::I() << "lines.size: " << lines.size() << std::endl;
    *this << ln;
}

Notice that I changed the println function to accept a const reference as you suggested. It didn't have any effect as far as I could tell. The thread you linked to mentioned that it was an issue with MSVC, but in my case I'm using GCC on Linux, so that's probably why.

Anyways, regarding how 'dbg' is declared, it's just a private member of the class that's calling all of the script loading stuff. So something like this:


//PlayScene.h
class PlayScene: public Scene{
private:
  	//...  
	DebugConsole *dbg;
	
public:
    //...
    int load() override;
    int setupScene() override;
}

//PlayScene.cpp
int PlayScene::setupScene(){
	dbg = new DebugConsole();
  	dbg->setup(...);
  	return 0;
}

int PlayScene::load(){
	setupScene();
  
  	//<script loading/init stuff from my previous posts>
}

 

And here is the output of my program with everything related to angelscript/DebugConsole

Quote

I[angel]  AS_64BIT_PTR AS_LINUX AS_X64_GCC << output from 'asGetLibraryOptions()'
I[angel] AngelWorker initialized
E[angel] Registering ReferenceType DebugConsole
E[angel] Registering ReferenceType Game
I[angel] AngelScript engine configured
I[refgen] void println(const string &in)
I[DebugConsole] DebugConsole::CTOR
I[playscene] About to compile script:
-------
void main(){
    string msg = "Hello from script!";
    LogI(msg);
    Console.println(msg);
}
-------
I[] DebugConsole::println: 'Hello from C++!'
I[] DebugConsole == 0x3994960
I[] numLines: 5
I[] lines.size: 5
I[] DebugConsole::operator<<('Hello from C++!');
I[] numLines: 5
I[] lines.size: 5
I[] Game loaded successfully!
I[] Hello from script!
I[] DebugConsole::println: 'Hello from script!'
I[] DebugConsole == 0x39a495f
I[] numLines: 0
I[] lines.size: 0
I[] DebugConsole::operator<<('Hello from script!');
I[] numLines: 0
I[] lines.size: 0
Segmentation fault (core dumped)

Note, "LogI" in the script is a global function that I registered with angelscript which calls the stdout logging stuff (the 'Log::I() <<' stuff). That function actually works and is called successfully by angelscript with no complaints. Also, I registered a class called "Game", but didn't register any members and it's never used.

What I don't understand is why the pointer to DebugConsole that I'm giving angelscript via "RegisterGlobalProperty" isn't the same one being used when my script tries to call a method from DebugConsole.

I'm sure I could probably work around this issue by using a different approach, like just having a couple of global functions for printing log messages (like 'LogI') instead of a global singleton property, but not understanding this behavior concerns me. :/

Ah, you just posted the code that I asked for :)

I'll take a closer look at this later, perhaps do a little debugging to figure out what the problem is.

 

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

Are you sure setupScene isn't being called more than once? That could explain the different addresses.

Yeah, I'm sure of that. While it's very possible that my code is simply messed up somewhere I've been overlooking, I strongly suspect that the issue is related to angelcode because that's the only part I haven't combed over 100 times.

This is just a for-fun/toy project I only work on occasionally, but it's also pretty old. I don't even remember where I got the angelcode binaries to statically link. I might have built it myself, or got it from somewhere else. I highly doubt that I modified anything, but it's very possible that I messed up something in the build/configuration (if I did build it myself). Regardless, stepping through that source is going to be a bit of a time committment and I don't know when exactly I'll be able to do it for this project.

I'm going to try to rebuild the library with the latest sources, and maybe try it on Windows too. I'll update if I learn anything new.

I did a quick test today with your code, and I didn't have any problem calling println from the script on the registered Console property. The this pointer used when called from AngelScript is the same as when called from C++.

You say you're using an old version of AngelScript. Can you show me which version it is? I do remember fixing a few bugs with RegisterGlobalProperty in the past (though it has been a really long time since then).

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