Sign in to follow this  
Deyja

Moving globals out of modules

Recommended Posts

Currently I'm working with an abstraction where every 'entity' has it's own unique module, so that they also have their own set of globals. 'Entities' call functions in the script in response to events; each script represents a certain kind of entity, and each instance of that type needs it's own set of the script's globals. This works fine, except that it means that identicle bytecode is duplicated between modules. If globals wern't stored in the module, I could cut my memory usage quite a bit. Sticking them in the context isn't really an option for me either, as each entity can have several contextes running in it's module at once. Ideally, there would be an entirely seperate structure that stored all the globals that I could pass to the context when I called prepare on it. I'm also creating a brand new context for every event, but, eh, theres no easy way to solve that one. :/

Share this post


Link to post
Share on other sites
I have plans to change the way this works, though I've not begun work on that yet as I want to get the classes fully working first.

The context class will have a different meaning. It will only hold the memory for the global variables.

A new thread interface will be responsible for executing the script functions, using the global variables in the context.

It will also be possible to declare the global variables as shared, in this case the global variables will be shared between all contexts accessing that module.

From what you described, the above design will suit you perfectly.

Regards,
Andreas

Share this post


Link to post
Share on other sites
A new thread interface, you say? So I can do away with my 'script thread' class, then?


#ifndef JM_SCRIPT_THREAD_H
#define JM_SCRIPT_THREAD_H

#include "scriptmanager.h"
#include "../time.h"
#include <boost/intrusive_ptr.hpp>

namespace ScriptInterface
{
class Thread
{
private:
Thread();
public:
static const unsigned int SIT_SUSPENDED = 1;
static const unsigned int SIT_DONE = 2;
static const unsigned int SIT_ABORTED = 4;
static const unsigned int SIT_RUNNING = 8;
static const unsigned int SIT_ERROR = 16;
static const unsigned int SIT_TIMEDOUT = 32;

class i_Imple
{
public:
asIScriptContext* ctx;
unsigned int state;
int func_id;
int ref_count;
i_Imple() : ref_count(0), ctx(0), state(Thread::SIT_ERROR) {}
friend void intrusive_ptr_add_ref(i_Imple* e) { ++e->ref_count; }
friend void intrusive_ptr_release(i_Imple* e) { if (--e->ref_count == 0) delete e; }
~i_Imple() { if (ctx) ctx->Release(); }
};

boost::intrusive_ptr<i_Imple> pimple;

Thread(int func_id);
void suspend();
void resume();
void execute(System::seconds timeout);
void abort();
void timeout();

bool is_done();

};

};

#endif



#include "script_thread.h"

namespace
{
struct thread_info
{
ScriptInterface::Thread* thisp;
System::seconds timeout;
};
}

static void line_callback(asIScriptContext* ctx, thread_info* ti)
{
if (ti->timeout < System::CurrentTime()) ti->thisp->timeout();
}

static void script_suspend()
{
asIScriptContext* ctx = asGetActiveContext();
if (!ctx) return;
ctx->Suspend();
}

REGISTER_FUNCTION("void Suspend()",asFUNCTION(script_suspend),asCALL_CDECL);

ScriptInterface::Thread::Thread(int func_id) : pimple(new i_Imple())
{
if (func_id < 0) return;
pimple->ctx = ScriptInterface::Engine()->CreateContext();
if (!pimple->ctx) return;
pimple->func_id = func_id;
if (pimple->ctx->Prepare(pimple->func_id) != 0) return;
pimple->state = SIT_RUNNING;
}

bool ScriptInterface::Thread::is_done()
{
if (pimple->state == SIT_RUNNING || pimple->state == SIT_SUSPENDED) return false;
return true;
}

void ScriptInterface::Thread::abort()
{
if (!pimple->ctx) return;
if (pimple->ctx->Abort() != 0) pimple->state = SIT_ERROR;
else pimple->state = SIT_ABORTED;
}

void ScriptInterface::Thread::suspend()
{
if (!pimple->ctx) return;
if (pimple->ctx->Suspend() != 0) pimple->state = SIT_ERROR;
else pimple->state = SIT_SUSPENDED;
}

void ScriptInterface::Thread::timeout()
{
if (!pimple->ctx) return;
if (pimple->ctx->Suspend() != 0) pimple->state = SIT_ERROR;
else pimple->state = SIT_TIMEDOUT;
}

void ScriptInterface::Thread::resume()
{
if (!pimple->ctx) return;
if (pimple->state == SIT_SUSPENDED) pimple->state = SIT_RUNNING;
}

void ScriptInterface::Thread::execute(System::seconds timeout)
{
if (!pimple->ctx) return;
if (pimple->state != SIT_RUNNING) return;
thread_info ti;
ti.thisp = this;
ti.timeout = timeout;
pimple->ctx->SetLineCallback(asFUNCTION(line_callback),&ti,asCALL_CDECL);
int r_code = pimple->ctx->Execute();
if (pimple->state == SIT_ERROR) return;
if (r_code == asEXECUTION_FINISHED)
{
pimple->state = SIT_DONE;
}
else if (r_code == asEXECUTION_SUSPENDED && pimple->state == SIT_TIMEDOUT)
{
pimple->state = SIT_RUNNING;
}
else if (r_code == asEXECUTION_SUSPENDED)
{
pimple->state = SIT_SUSPENDED;
}
else if (r_code == asEXECUTION_ABORTED)
{
pimple->state = SIT_ABORTED;
}
else if (r_code == asEXECUTION_EXCEPTION)
{
pimple->state = SIT_ERROR;
}
else
{
pimple->state = SIT_ERROR;
}
}



Granted, parts of it won't make much sense without the rest of my 'script interface' stuff. But you should be able to tell by the interface how it works; will the new stuff work like that?

Share this post


Link to post
Share on other sites
I thought you might be interested. My current system allows me to do things like ---


//test.ass
string ONE;
bool TWO;
int THREE;
uint FOUR;
float FIVE;
double SIX;




//test_resource.jml
<sheep = "TEMPLATE">
<sprite = "SPRITE">
<image = "IMAGE">
<!file = "sheep.tga">
</image>
<!rect = "0 0 32 64">
<!offset = "0 -16">
<!blit_style = "fast">
</sprite>
<!size = "32 48">
<!script = "test.ass">
<parameters>
<!ONE [string] = "Hello World!">
<!TWO [bool] = "true">
<!THREE [int] = "-12345">
<!FOUR [uint] = "12345">
<!FIVE [float] = "12.345">
<!SIX [double] = "123.45">
</parameters>
</sheep>



Unfortunatly, some of the code that makes this work is OMFGUGLY. For example, I have to keep track of a map if a wrapper around type_info to strings to resolve the proper name for C++ types in angelscript. I have a big chain of type-checking elseif's just to change a single typename in a call to a templated function. Sometimes this bloody language really drives me nuts.

This function actually sets globals.

template <typename cpp_type>
bool SetGlobal(const std::string& name, const cpp_type& value)
{
if (!good_module) return false;
std::string ang_type = AutoBind::type_name<cpp_type>();
int g_id = ScriptInterface::Engine()->GetGlobalVarIDByDecl(module_name.c_str(),(ang_type + " " + name).c_str());
if (g_id < 0) return false;
void * g_ptr = ScriptInterface::Engine()->GetGlobalVarPointer(g_id);
if (!g_ptr) return false;
cpp_type * c_ptr = static_cast<cpp_type*>(g_ptr);
(*c_ptr) = value;
return true;
}



It's called by this function...


#define GOPTION(T) else if (GI->second.isOfType<T>()) { s = r->script_module.SetGlobal(GI->first,GI->second.reference<T>()); }

/* snip */

for (std::map<std::string,Any>::iterator GI = ed.script_globals.begin(); GI != ed.script_globals.end(); ++GI)
{
bool s = false;
if (false) {}
GOPTION(std::string)
GOPTION(int)
GOPTION(Tile::WeakEntity)
GOPTION(Tile::Map)
GOPTION(Graphics::Sprite)
GOPTION(float)
GOPTION(double)
GOPTION(unsigned int)
GOPTION(bool)

if (!s) System::log << "Error setting global " << GI->first << "\n";
}



Did I mention it was OMFGUGLY? To put it simply, if the type isn't in that list of GOPTIONs, it can't be a script global. Period. And the name resolution system is so nice... but still has to have a similiar list for adding all the built-in angelscript typenames to the system!

Share this post


Link to post
Share on other sites
The script thread class will work much the same way that the script context does today in terms of executing the scripts. The difference is that various threads may share the same context, and also when there are no longer any threads executing the context can be kept without the extra memory overhead of the script stack. The script context will in other words be much more lightweight and the solution more flexible.


Share this post


Link to post
Share on other sites
That should help a lot. As I mentioned before, one of my biggest memory problems write now is needing a seperate context for each script thread. With your new design, your new contextes will replace my modules (Which I currently must have one of for each entity) and my overall memory usage will be greatly reduced. :)

I could do it more efficiently now, but then the scripts would have to jump through hoops. I've really designed it to make the scripts as straightforward as possible now.

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

Sign in to follow this