• Advertisement
  • Popular Tags

  • Popular Now

  • Advertisement
  • Similar Content

    • By RyRyB
      I got into a conversation awhile ago with some fellow game artists and the prospect of signing bonuses got brought up. Out of the group, I was the only one who had negotiated any sort of sign on bonus or payment above and beyond base compensation. My goal with this article and possibly others is to inform and motivate other artists to work on this aspect of their “portfolio” and start treating their career as a business. 
      What is a Sign-On Bonus?
      Quite simply, a sign-on bonus is a sum of money offered to a prospective candidate in order to get them to join. It is quite common in other industries but rarely seen in the games unless it is at the executive level. Unfortunately, conversations centered around artist employment usually stops at base compensation, quite literally leaving money on the table.
      Why Ask for a Sign-On Bonus?
      There are many reasons to ask for a sign-on bonus. In my experience, it has been to compensate for some delta between how much I need vs. how much the company is offering.
      For example, a company has offered a candidate a position paying $50k/year. However, research indicates that the candidate requires $60k/year in order to keep in line with their personal financial requirements and long-term goals. Instead of turning down the offer wholesale, they may ask for a $10k sign on bonus with actionable terms to partially bridge the gap.
      Whatever the reason may be, the ask needs to be reasonable. Would you like a $100k sign-on bonus? Of course! Should you ask for it? Probably not. A sign-on bonus is a tool to reduce risk, not a tool to help you buy a shiny new sports car.
      Aspects to Consider
      Before one goes and asks for a huge sum of money, there are some aspects of sign-on bonus negotiations the candidate needs to keep in mind.
      - The more experience you have, the more leverage you have to negotiate
      - You must have confidence in your role as an employee.
      - You must have done your research. This includes knowing your personal financial goals and how the prospective offer changes, influences or diminishes those goals.
      To the first point, the more experience one has, the better. If the candidate is a junior employee (roughly defined as less than 3 years of industry experience) or looking for their first job in the industry, it is highly unlikely that a company will entertain a conversation about sign-on bonuses. Getting into the industry is highly competitive and there is likely very little motivation for a company to pay a sign-on bonus for one candidate when there a dozens (or hundreds in some cases) of other candidates that will jump at the first offer.
      Additionally, the candidate must have confidence in succeeding at the desired role in the company. They have to know that they can handle the day to day responsibilities as well as any extra demands that may come up during production. The company needs to be convinced of their ability to be a team player and, as a result, is willing to put a little extra money down to hire them. In other words, the candidate needs to reduce the company’s risk in hiring them enough that an extra payment or two is negligible.
      And finally, they must know where they sit financially and where they want to be in the short-, mid-, and long-term. Having this information at hand is essential to the negotiation process.
      The Role Risk Plays in Employment
      The interviewing process is a tricky one for all parties involved and it revolves around the idea of risk. Is this candidate low-risk or high-risk? The risk level depends on a number of factors: portfolio quality, experience, soft skills, etc. Were you late for the interview? Your risk to the company just went up. Did you bring additional portfolio materials that were not online? Your risk just went down and you became more hireable.
      If a candidate has an offer in hand, then the company sees enough potential to get a return on their investment with as little risk as possible. At this point, the company is confident in their ability as an employee (ie. low risk) and they are willing to give them money in return for that ability.
      Asking for the Sign-On Bonus
      So what now? The candidate has gone through the interview process, the company has offered them a position and base compensation. Unfortunately, the offer falls below expectations. Here is where the knowledge and research of the position and personal financial goals comes in. The candidate has to know what their thresholds and limits are. If they ask for $60k/year and the company is offering $50k, how do you ask for the bonus? Once again, it comes down to risk.
      Here is the point to remember: risk is not one-sided. The candidate takes on risk by changing companies as well. The candidate has to leverage the sign-on bonus as a way to reduce risk for both parties.
      Here is the important part:
      A sign-on bonus reduces the company’s risk because they are not commiting to an increased salary and bonus payouts can be staggered and have terms attached to them. The sign-on bonus reduces the candidate’s risk because it bridges the gap between the offered compensation and their personal financial requirements.
      If the sign-on bonus is reasonable and the company has the finances (explained further down below), it is a win-win for both parties and hopefully the beginning a profitable business relationship.
      A Bit about Finances
      First off, I am not a business accountant nor have I managed finances for a business. I am sure that it is much more complicated than my example below and there are a lot of considerations to take into account. In my experience, however, I do know that base compensation (ie. salary) will generally fall into a different line item category on the financial books than a bonus payout. When companies determine how many open spots they have, it is usually done by department with inter-departmental salary caps.
      For a simplified example, an environment department’s total salary cap is $500k/year. They have 9 artists being paid $50k/year, leaving $50k/year remaining for the 10th member of the team. Remember the example I gave earlier asking for $60k/year? The company cannot offer that salary because it breaks the departmental cap. However, since bonuses typically do not affect departmental caps, the company can pull from a different pool of money without increasing their risk by committing to a higher salary.
      Sweetening the Deal
      Coming right out of the gate and asking for an upfront payment might be too aggressive of a play (ie. high risk for the company). One way around this is to attach terms to the bonus. What does this mean? Take the situation above. A candidate has an offer for $50k/year but would like a bit more. If through the course of discussing compensation they get the sense that $10k is too high, they can offer to break up the payments based on terms. For example, a counterpoint to the initial base compensation offer could look like this:
      - $50k/year salary
      - $5k bonus payout #1 after 30 days of successful employment
      - $5k bonus payout #2 after 365 days (or any length of time) of successful employment
      In this example, the candidate is guaranteed $55k/year salary for 2 years. If they factor in a standard 3% cost of living raise, the first 3 years of employment looks like this:
      - Year 0-1 = $55,000 ($50,000 + $5,000 payout #1)
      - Year 1-2 = $56,500 (($50,000 x 1.03%) + $5,000 payout #2)
      - Year 2-3 = $53,045 ($51,500 x 1.03%)
      Now it might not be the $60k/year they had in mind but it is a great compromise to keep both parties comfortable.
      If the Company Says Yes
      Great news! The company said yes! What now? Personally, I always request at least a full 24 hours to crunch the final numbers. In the past, I’ve requested up to a week for full consideration. Even if you know you will say yes, doing due diligence with your finances one last time is always a good practice. Plug the numbers into a spreadsheet, look at your bills and expenses again, and review the whole offer (base compensation, bonus, time off/sick leave, medical/dental/vision, etc.). Discuss the offer with your significant other as well. You will see the offer in a different light when you wake up, so make sure you are not rushing into a situation you will regret.
      If the Company Say No
      If the company says no, then you have a difficult decision to make. Request time to review the offer and crunch the numbers. If it is a lateral move (same position, different company) then you have to ask if the switch is worth it. Only due diligence will offer that insight and you have to give yourself enough time to let those insights arrive. You might find yourself accepting the new position due to other non-financial reasons (which could be a whole separate article!).
      Conclusion/Final Thoughts 
      When it comes to negotiating during the interview process, it is very easy to take what you can get and run. You might fear that in asking for more, you will be disqualifying yourself from the position. Keep in mind that the offer has already been extended to you and a company will not rescind their offer simply because you came back with a counterpoint. Negotiations are expected at this stage and by putting forth a creative compromise, your first impression is that of someone who conducts themselves in a professional manner.
      Also keep in mind that negotiations do not always go well. There are countless factors that influence whether or not someone gets a sign-on bonus. Sometimes it all comes down to being there at the right time at the right place. Just make sure you do your due diligence and be ready when the opportunity presents itself.
      Hope this helps!
    • By Marinka Brussel
      Imagine a game where the characters are not defined by body regions, but rather, each body region consists of thousands of components, which would kind of replicate the real world where we consist of molecules, atoms - that would open up many, many new possibilities for creative gameplay. Can this be done on any scale with today's technology, or would the games simply require too powerful of a computer to even be playable? Are there any theoretical limits to this? Thanks
  • Advertisement
  • Advertisement

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

Recommended Posts

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;
}

 

Share this post


Link to post
Share on other sites
Advertisement

 

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

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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:

 

Share this post


Link to post
Share on other sites

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. :/

Share this post


Link to post
Share on other sites

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.

 

Share this post


Link to post
Share on other sites

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

Edited by Solokiller

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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).

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


  • Advertisement