Passing an object from a context to the other by first going through C++

Started by
4 comments, last by pifor106 13 years, 8 months ago
Hi,

I'm getting my hands a bit deeper into AS and I've ran into some stuff that I can't find documentation for. First, I'll describe our engine a bit so hopefully you get a better idea of what I'm trying to achieve.

The game engine in which I'm trying to integrate AngelScript is heavily based on a component design. Everything in the engine is a component, and the entity concept does not exist. Even the content of the main loop is a special type of component.

In our engine, a component can declare dependencies that must be bound within our Editors. For example, let's say I implement a wandering cat's behavior. I'll need a dependency on it's animation system and it's rigid body. All of this is done through the framework and does not need lot's of typing by the end-user. Using a strong framework, it's easy for us, developpers of the said framework, to enforce and automate some normally cumbersome & error-prone functionalities, such as memory management, serialization, updates, etc... Add a complete tool set of this and code reflexion and you get a pretty flexible and adaptable game engine.

So, up to now, we would code our game code in C++ directly within a Game project built on top of the framework. However, as project grow bigger, it is becoming longer and longer to compile and a development iteration is getting a bit longer. There came in the need for scripts.

Now, as everything is a component, and we don't want to change the workflow's philosophy that much for our game programmers, we would really like to implement a component directly within scripts.

Basically, a component is 4 default function of initialization and de-initialization and a custom, per-module define, update function if any. For example, an AI component has an Update() function. An animation system component has an Animate(const f32 deltaTime) function.

Now, let's say I wish to define two components using script, and I want both to communicate with each other, but each one has it's own lifetime and scope ( we're heavily based on dynamic loading ... )

So I have defined a ScriptedAIInstance which holds a asIScriptObject* and a context. When it's function Update is called, it calls the Update function defined within the script. So far so good, this work perfectly and without any trouble.

Now, I would like one instance's Update to do something like that :

In AngelScript :

class A
{

void DoSomething()
{
//...
}

};

class B
{

void Update()
{
A@ a = GetComponent("A", 0);

a.DoSomething();
}

};

I've tried exposing a GetComponent() function and using the asGetActiveContext method. The trouble is, I don't know what kind of objects I need to pass. Is there a base object that I can use for that ? Or something such as a void* that I can cast from the script side ?

void* GetComponent(const String& name)
{
AIScriptInstance* instance = static_cast<AIScriptInstance*>(asGetActiveContext());
return instance->GetLinkedInstance().GetAsIScriptObject();
}

Thanks for any help that you may give me. I'm really new to AS and I really want it to work within our engine given all the cool feature it supports.




Advertisement
After much trial and error I finally got to succeed doing what I wanted. However, since I'm no AngelScript Guru, I'd like some advice as to whether I'm doing something horribly wrong or if it's fine.

Here my test script :

class A
{
u32 m_value;

void Init()
{
m_value = 0;
}

void Update()
{
Print("\n Value from A : ");
Print(m_value);

m_value++;
}

u32 GetValue()
{
return m_value;
}
};

class B
{
void Update()
{
A@ a;
GetComponent(@a);

Print("\n Value from B :");
Print(a.GetValue());
}
};

I have registered my GetComponent() function as below, really simple/prototype stuff for now as I'm still making learning tests :

ANGELSCRIPT_CHECKED_CALL(m_engine->RegisterGlobalFunction("void GetComponent(?& out)", asFUNCTION(Wrappers::GetComponent), asCALL_CDECL));

Finally, the GetComponent function is implemented as below :

//------------------------------------------------------------------------
static void GetComponent(void* component, int)
{
AIScriptInstance* instanceOwner = static_cast<AIScriptInstance*>(asGetActiveContext()->GetUserData());

instanceOwner->GetComponent()->GetAsIScriptObject()->AddRef();
*static_cast<void**>(component) = instanceOwner->GetComponent()->GetAsIScriptObject();
}

I had a problem because I didn't add the AddRef call on the returned object. My question is :

Do I need to release this reference at some point ? Am I increasing infenitely the ref count of my object ? My AIScriptInstance holds an instance of the asIScriptObject and it's also releasing it once he's destroyed.

Thanks a lot,

Pierre


Quote:
static void GetComponent(void* component, int)
{
AIScriptInstance* instanceOwner = static_cast<AIScriptInstance*>(asGetActiveContext()->GetUserData());

instanceOwner->GetComponent()->GetAsIScriptObject()->AddRef();
*static_cast<void**>(component) = instanceOwner->GetComponent()->GetAsIScriptObject();
}


This works, however you need to add a type check so you do not return the wrong script class. The second parameter is the type id, you should compare this with the type id of the script object to make sure it matches.

The reference will be released by the script engine when it is done with it, so you do not have to do that.

---

With this approach you will be forced to compile all script classes into the same script module. Otherwise they will not understand each other.

I suggest you abstract the script classes with interfaces, in order to allow you to separate the script code in separate modules. The script modules work similar to C++ dlls, where each have their own code space and global variables. With separate modules you would be able to load modules on demand, or even recompile them on the fly during testing phases.



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

Thanks once again, that clarified things.

On a side note, thanks for the input on interfaces. I really want to limit the coupling of data types to avoid having the universe to build upon a live modification of the code. My design, both in the GUI ( because I'm building a GUI for edition and remote debugging on consoles ) is to actually display modules such as it is done within Visual Studio. ( Each module being the equivalent of an assembly )

My main problem with interfaces, however, is that they must be registered in C++ code through the asIScriptEngine class right ? I was actually wondering if it was possible to register an interface through script. I highly doubt that I can register an interface from the script, even if I wrapped the RegisterInterface/RegisterInterfaceMethod in C++ code since a script relying on that interface being registered may be loaded prior to the script register interface being executed. Is that right ?

Finally, since I have base instance component with a dependency on a script component ( the script text itself ) in my code who's responsibility is to manage the asIScriptObject, deferring calls to it, managing the access to externally held script & engine objects, I was thinking of raising a signal, caught by all instances, through the script component should it be reloaded ( through our live edition network bridge ) to actually trigger a rebuild of the asIScriptModule. I haven't actually tried yet, but judging from previous posts, I think it's a reasonable idea.

The only areas which I see as being problematic are what to do with the asIScriptObject that I already have and how to handle properties which have been added/removed/modified without modifying the other values. I guess I'll find out tomorrow :)

Anyway, the more I play with AS, the more I like it.

Keep up the good work,

Pierre
Interfaces can be declared from within the scripts themselves as well. Like this:

interface IMyInterface{  void function();}


If two modules declare the same interface, it will share the same type id, thus be interchangeable between the modules.

Only interfaces that must be known to the application must be registered from the application using RegisterInterface. If you for example want to register a function that takes a specific inteface in a parameter, you will need to register that interface first.

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

Wonderful ! That's even better than I had hoped.

This topic is closed to new replies.

Advertisement