Jump to content
  • Advertisement
Sign in to follow this  
Wavesonics

Persistent objects

This topic is 3495 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Now that I am more comfortable with AS I'm taking a look at the design I implemented. And maybe someone can suggest a better way forward here. I have this class "Turret" which I instance many times in the course of a game. But what each instance looks like and does is defined by XML and AS. So the main thing that you can define in AS is what happens when the turret fires. So when the turret does indeed fire, it calls out to a:
Quote:
void fire( Turret@ t, Creep@ c, World@ w );
AS function which takes arguments to help it be somewhat statefull. But my issue is, I want the scripters to have much more flexibility. Right now they can only store and manipulate the basic information that all turrets have actually stored in the C++ class: - rate of fire - range ect... It would be ideal if the scripters could define script variables when the objects are constructed, and each instance would have it's own set of these (obviously). Is there anyway to do this strictly in the script engine? To remain state full per object instance? The other issue I have is, There are functions that can be run on both the client and server (common functions), and then there are functions that can only be run on the Client (graphics related things). Right now thats not an issue. There are 2 separate functions in 2 separate modules to handle this, and they have their config groups set accordingly. But this gets thrown into the mix i suppose when trying to define some way for scripts to create variables about the objects which can be access from both sides (not across a network mind you, but simply on a client which uses both the common functions as well as the client only functions.

Share this post


Link to post
Share on other sites
Advertisement
In my opinion the C++ class should hold as little information as possible. Only what the game engine must know in order to control the game entities. The rest of the information, rate of fire, range, etc should be held by the script entity. That way you're separating game engine from the actual game, making it possible to change the game without having to change the game engine.

In my own game that I'm working on I'm doing it as follows:

In the game engine I have a CGameObject, that holds only the most basic properties, like position, velocity, and 3D mesh. It also holds a reference to a script class, that I call CEntityController.

The CEntityController is implemented in the script and holds all the information that the script needs, e.g. health, ammo, etc. The CEntityController implements the necessary event handlers that the game engine calls as needed, e.g. onThink, onTouch, onCollide, onTimer, etc. The script class only has to implement the event handlers it actually want to handle. The CEntityController also holds a reference to the CGameObject in the game engine so that it can manipulate it.

To allow the game engine to destroy CGameObjects without having to worry about who references it, I implement a weak link, called CGameObjectLink. All objects that refer to the CGameObject do so through the CGameObjectLink. The CGameObject also holds a reference to its CGameObjectLink so that it can sever the connection at will.

My game is single player only so I haven't begun thinking about how to implement client/server logic. However I imagine that you'd want to implement both client and server controllers in the same physical file, and then use a preprocessor to only compile what belongs on the server side or the client side respectively. Doing that you would avoid having to duplicate functionality that would be common to both client and server. (Of course, you might not have any functionality that is common between the server and client.)


Share this post


Link to post
Share on other sites
First question is, do you mean AS's preprocessor?

If so, where can I find some docs on that?


*EDIT*
I guess my real question is, how can I have my application instance a script class, keep it alive, and, i guess, keep a handle to it for calling methods on it?
*EDIT*


Second:
Quote:

CEntityController is implemented in the script and holds all the information that the script needs

...

The script class only has to implement the event handlers it actually want to handle.


2 things:
1) script globals are kept alive per module (or is it per context) right? So if I kept alive a module(or context) for each class instance, i could keep alive script variables for each class instance right?

2) I'm not exactly sure, what you mean by "implement in the script"
Would it be something like:

// Entity.cpp
// ==========

class Entity {
public:
void onTouch() {
// Call out to a script function
}
};

// SomeEntity.as
// ==========
void onTouch() [
// Perform some action
}







*edit* i just found how to define classes in AS, very nice :)

[Edited by - Wavesonics on January 22, 2009 12:13:35 PM]

Share this post


Link to post
Share on other sites
AngelScript doesn't have a proper pre-processor yet. The CScriptBuilder add-on does some pre-processing and supports #include directives and metadata, but it does not yet support macros and more complex pre-processing directives.

It should be quite easy to add that functionality though. You only need #ifdef and perhaps #ifndef anyway.

You can also use a generic pre-processor. Omnisu.com used to have one, but the author stopped development on it, and seems to be fully dedicated to arts nowadays.

Script globals are kept in the module (not the context), so yes, you could maintain one module for each class instance. However, each module also holds its own copy of functions, so you would have a large memory consumption if you duplicate modules.

You can declare classes in the scripts. The application can then instanciate these classes.

Here's an overview of what I have to better explain what I'm saying. I'm rewriting this from memory so it may not be quite accurate:

CGameObject - C++

class CGameObjectLink
{
public:
CGameObjectLink(CGameObject *o) {refCount = 1; object = o;}
void AddRef();
void Release();

void SetVelocity(const vector3 & v) { if( object ) object->SetVelocity(v); }

int refCount;
CGameObject *object;
};

class CGameObject
{
public:
CGameObject() {link = new CGameObjectLink(this);}
~CGameObject() { if( link ) link->object = 0; }

void onThink()
{
if( controller )
{
scriptMgr->CallOnThink(controller);
}
}

void SetVelocity(const vector3 &v);

CGameObjectLink *link;
asIScriptStruct *controller;
vector3 velocity;
};




CEntityController - AngelScript

// The IEntityController is just an empty interface that allow
// the application to identify this class as a controller object
class CPlayer : IEntityController
{
CPlayer(CGameObject @entity)
{
this.entity = entity;
}

void onThink()
{
entity.SetVelocity(vector3(1,0,0));
}

CGameObject @entity;
int health;
int ammo;
}

IEntityController @Factory(CGameObject @entity)
{
return CPlayer(entity);
}



After the script has been compiled, the application enumerates the types in the module to find the class that implements the IEntityController interface. Once it knows which class it is, it stores away the function id for the event handlers that the class implements. To create new entity controllers the application calls the factory function that it finds by calling GetFunctionIdByDecl("IEntityController @Factory(CGameObject @)");

Share this post


Link to post
Share on other sites
Ok so here is what I've put together:
-------------------------------------

I should have 1 single module which defines the AS class. (1 module per AS class)

I should also have an AS interface for the application's class that the AS class will be representing. And of course, the AS class will implement the interface.

The Link class in the application is registered as a reference counted class to the AS engine.

And to create one of these Link objects, i never instantiate one via it's constructor in the application, but I instead call the factory method from the script engine, which should create both the application Link object, and the AS object?

So, the new AS object will have all it's AS defined functions and variables, but under the hood is sitting my Link object?

If that is all correct then I think I am getting this. One last thing though.
Quote:

scriptMgr->CallOnThink(controller);


*edit* I see controller is of type asIScriptStruct which is used to represent an AS class instance, is that correct? If so, where do I get the pointer to the new instance from?

Also, in your setup, say the game object does delete it's GameObject, but the Link object and thus the script object are still alive. What prevents them from leaking? If the engine is *dumb* so to speak, shouldn't just the scripts be in charge of deleting objects, and thus you could combine GameObject and the Link class?

Thanks for the help!

Btw, now that I'm getting a handle on what your talking about here, it's pretty awesome and powerful!

Share this post


Link to post
Share on other sites
I have same problem.
I don't understand how I obtain and use in C++ returned refrence to created script object.

When I use:

int id = pModule->GetFunctionIdByDecl("IEntityController @Factory()");
pScriptContext->Prepare(id);
pScriptContext->Execute();

So, I need call (for example) each program frame script method
CPlayer.onThing();

How I can do this (call specified script method) from C++?

EDIT: I think, it is done by calling
scriptMgr->CallOnThink(controller); in your example. Can you post code of this scriptMgr, please?

And second question is how I can destroy script object created in IEntityController @Factory() when I don't need it in C++ (player object in C++ is destroyed?)

EDIT2: I thing, that I got it. I ispired in test_interface.cpp
In your example you create object in script by

IEntityController @Factory(CGameObject @entity)
{
return CPlayer(entity);
}

But in test_interface.cpp is used c++ code:

pObj = pEngine->CreateScriptObject(typeId).

Can I safely use this method? And when I destroy parent C++ object I simply call pObj->release() ?


PS: Sory about my horrible english...

[Edited by - RakeX on January 23, 2009 5:15:41 AM]

Share this post


Link to post
Share on other sites
Quote:
I should have 1 single module which defines the AS class. (1 module per AS class)

I should also have an AS interface for the application's class that the AS class will be representing. And of course, the AS class will implement the interface.

The Link class in the application is registered as a reference counted class to the AS engine.


Correct

Quote:

And to create one of these Link objects, i never instantiate one via it's constructor in the application, but I instead call the factory method from the script engine, which should create both the application Link object, and the AS object?


The link object is created by the CGameObject's constructor.

I also have a CGameObjectFactory, that knows how to create new game objects. It reads a configuration file for each game object type. This file holds information such as 3D mesh, controller script file, etc. Using this information it can create a CGameObject, then ask the CScriptMgr to create a script controller that it will bind to the CGameObject.

Obviously the scripts may request the creation of new CGameObject's, by calling a registered function 'SpawnObject' and passing the game object type. This method will then get a handle to a CGameObjectLink.

One script controller object will never see another controller. The controllers always communicate with each others through the CGameObjectLink.

Quote:

So, the new AS object will have all it's AS defined functions and variables, but under the hood is sitting my Link object?

If that is all correct then I think I am getting this. One last thing though.
Quote:

scriptMgr->CallOnThink(controller);


*edit* I see controller is of type asIScriptStruct which is used to represent an AS class instance, is that correct? If so, where do I get the pointer to the new instance from?


The CScriptMgr also has a function CreateController() which takes the name of the script file. It will load the script file into a new module (unless already done before) and then call the Factory function in the script to instanciate a new controller object.

Quote:

Also, in your setup, say the game object does delete it's GameObject, but the Link object and thus the script object are still alive. What prevents them from leaking? If the engine is *dumb* so to speak, shouldn't just the scripts be in charge of deleting objects, and thus you could combine GameObject and the Link class?

Thanks for the help!

Btw, now that I'm getting a handle on what your talking about here, it's pretty awesome and powerful!


Yes, you can combine the CGameObject and link object, however, then you also have to deal with cyclic references that prevent you from easily detecting dead objects. AngelScript has a built in garbage collector that can take care of this if you register the proper behaviours for the CGameObject, however as garbage collection is quite heavy, you'll want to avoid it if you can.

The Link object is a one way link to the CGameObject. The Link object will never keep any objects alive by itself. It is a weak link, which I believe is the standard term for what it does.

As resource management is such an important part in a game engine, you don't want to put that in the hands of script writers. The game engine should be the one responsible for making sure the resource management is adequate.

The scripts can still tell the game engine to kill a game object if necessary (for example when the entity dies), and because of the link object, the game engine can just delete the game object without having to worry about dangling pointers that might refer to the non-existent memory.




Share this post


Link to post
Share on other sites
Quote:
Original post by RakeX
I have same problem.


Here's a bit of code that I hope will explain things:


asIScriptStruct *CScriptMgr::CreateEntityController(CGameObjectLink *link)
{
int id = pModule->GetFunctionIdByDecl("IEntityController @Factory(CGameObject@)");
pScriptContext->Prepare(id);
pScriptContext->SetArgObject(0, link);
pScriptContext->Execute();

asIScriptStruct *object = (asIScriptStruct*)pScriptContext->GetReturnObject();

// Increase the reference with the pointer that the game engine will keep
object->AddRef();

return object;
}

void CScriptMgr::CallOnThink(asIScriptStruct *controller)
{
asIObjectType *type = controller->GetObjectType();
int id = type->GetMethodIdByDecl("void onThink()");
pScriptContext->Prepare(id);
pScriptContext->SetObject(controller);
pScriptContext->Execute();
}


Of course, the above code needs better error handling, and you also want to cache the function ids so that you don't have to look them up with each call.

To destroy the object after you're done with it, you just call asIScriptStruct::Release() to free the reference you hold.

The asIScriptEngine::CreateScriptObject() method can also be used to create the objects, however, this method can only use the default constructor for those objects, so you won't be able to pass in any arguments during the construction.

Share this post


Link to post
Share on other sites
Great, this really clears things up.

So you create a GameObject, which in turn creates a link.
Then, you bind the link to the script object by calling the specific constructor and passing in a handle to the link object.

And that script object's life time is guaranteed pretty much until the application terminates it? (Unless of course I provide a way for the script to try an terminate it)

Now that I've got this down, I gotta figure out an elegant way to divide up and apply config groups to to the proper client/server functions. Maybe 2 separate classes...

Would be great to have inheritance here, hint hint ;)

Share this post


Link to post
Share on other sites
The references goes something like this:


script object --> link object <--> game object
^ /
+-----------------------------------+


When the game object is deleted, it releases it's references to the script object and link object, which will in turn delete the script object and possibly the link object (if it is the last reference).

The deletion of the game object can originate from the script or the game engine could periodically sweep the list of game objects to delete unneeded ones (probably during the loop to update the objects).

I'm slowly building up courage to start implementing inheritance. [wink] I believe I've cleared out all obstacles for it now, and may start working on it for version 2.15.2 (no promises though).

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!