• Advertisement
Sign in to follow this  

Delayed Calls

This topic is 640 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

Hi,

For scripting of my game I need some way to delay certain calls for some amount of seconds (no bigger precision needed for now). It seems that closures would greatly simplify this, but as far as I know they're not available yet? I will write down what I plan:

  1. A global function int call_out(string func_decl, int time_in_seconds, <params>)
  2. <params> part is a bit problematic as sadly Angelscript doesn't allow variadic arguments. I have two ideas on this:
    1. Use some array<variant> or dictionary to store all function arguments
    2. Register call_out function as as_CALL_GENERIC for 0, 1, 2 ... n arguments like void call_out(string, int, ?&in), void call_out(string, int, ?&in, ?&in) and so on, then insepct asIScriptGeneric object for parameters based on what I retrieve from called function declaration (I could even verify if the correct amount and type of arguments is provided I suppose)
  3. As this won't work as closure, it probably will have to be limited to 2 cases:
    1. call_out happens in object's method, func_decl has to be either global function or that object's method (of course it will be executed on the same object instance as the one that called the call_out)
    2. call_out happens in function, func_decl has to be global function only (as the caller is not an object)

Before I dig into some implementation details here are few usage examples:

### Example 1 ###

class Object
{
    string str;

    void foo()
    {
       print("Foo!\n");
       call_out("void bar()", 5);
    }

    void bar()
    {
       print("Bar is " + str + "!\n");
    }
}

Object obj;
obj.str = "baz";
obj.foo();

results in:

Foo!
# 5 seconds delay
Bar is baz!

### Example 2 ###

int main() 
{
   call_out("void print(string)", 10, "Foo\n");
}

results in:
# 10 seconds delay
Foo!

So what I got now, in some pseudo code as it's not yet working (plus doesn't even cover all possible paths):

engine->RegisterGlobalFunction("void call_out(const string &in, int &in, ?&in)", 
    asFUNCTION(CallOut), asCALL_GENERIC);

void CallOut(asIScriptGeneric* gen)
{
   std::string* decl = *(std::string**)gen->GetAddressOfArg(0);
   int32_t seconds = *(int32_t*)gen->GetAddressOfArg(1);
   
   asIScriptContext* ctx = asGetActiveContext();
   asIScriptFunction* func = ctx->GetFunction(0); // retrieve the caller 
   void* obj = ctx->GetThisPointer(0);
   if (obj) 
   {
      // this is the case where caller is object
      type = ctx->GetThisTypeId(0);
      asIScriptEngine* engine = ctx->GetEngine();
      asITypeInfo* typeInfo = engine->GetTypeInfoById(type);
      asIScriptFunction* method = typeInfo->GetMethodByDecl(decl->c_str());
 
      // ^ this gives the object's method that we want to call, or null if it wasn't found
      // after this we need to collect arguments from asIScriptGeneric and push the call_out
      // struct into a queue
    }
} 

 

Next idea is to have some global class (or registered as engine's user data so I can retrieve it in CallOut()) that will manage all registered call_outs. All that CallOut() function does is retrieve what we want to call, when and with what params, then put it on queue so it gets executed in the right time. I'm not sure if the above code even makes sense, I followed different AS objects so that I could retrieve information I thought is necessary to make this work, I had to use asGetActiveContext to retrieve information about the caller (so I know where to look for a called function, as the assumption is that we either call function from the object we're in, or a global one).
 
The biggest conceptual problem I have now is how to store parameters I retrieve from asIScriptGeneric for later use. I thought about struct for every call_out like:
 
struct call_out {
     asIScriptFunction*   func;
     int  seconds;
     <params>
}
 
 
What should I use for <params> here? 
 

Maybe someone already figured this out and have some working code? If not, is my approach even slightly on track or completly off, will result in undefined behavior or will be terribly slow?

 

Best regards,

noizex

Share this post


Link to post
Share on other sites
Advertisement

The utility library i linked in another thread has a solution to this problem:

https://github.com/SamVanheer/AngelscriptUtils/blob/9c024dfcf80ff280cd96f4ffa714de0efb291e0e/src/Angelscript/ScriptAPI/CASScheduler.h

https://github.com/SamVanheer/AngelscriptUtils/blob/9c024dfcf80ff280cd96f4ffa714de0efb291e0e/src/Angelscript/ScriptAPI/CASScheduler.cpp

 

The scheduler lets you schedule function calls for execution at a later point in time. Varargs are supported, with limited conversion support.

 

The library is WIP, but you should be able to figure out how it all works.

 

The argument storing is handled by CASArguments:

https://github.com/SamVanheer/AngelscriptUtils/blob/9c024dfcf80ff280cd96f4ffa714de0efb291e0e/src/Angelscript/wrapper/CASArguments.h

https://github.com/SamVanheer/AngelscriptUtils/blob/9c024dfcf80ff280cd96f4ffa714de0efb291e0e/src/Angelscript/wrapper/CASArguments.cpp

 

When given an asIScriptGeneric, it will store off all of the arguments. Primitive types, enums and value types are copied, reference types are addref'd.

It also works with C++ va_list.

Share this post


Link to post
Share on other sites

Thanks! Looks like exactly what I needed and has way more functionality than I expected from my implementation. Would be great if things like this could make their way as official add_ons to AS, it would be easier to add them to any project if they were more isolated. But even if I don't use it directly, that's a lot of great ideas :)

Share this post


Link to post
Share on other sites

@noizex:

 

Have you taken a look at the context manager add-on where I implemented support for co-routines. It shouldn't be too difficult to change that one to have a delayed call.

 

For the variable arguments that can be passed to the co-routines, I chose to use the dictionary to hold those.

 

Also, even though AngelScript still doesn't support closures, you should be able to use delegates if you wish to use delayed calls to an object method.

 

 

@Sam:

 

That utils library of yours appears to be a real gold mine. :)

Share this post


Link to post
Share on other sites

Yeah I already have some working (though not even close to being 100% safe & bulletproof) concept done, thanks to Solokiller's hints and code. Can execute delayed function with up to 4 arguments, it also takes into account existence of "this" object (if the call happened inside object instance) and throws runtime error if the object is not present at the call time (using weakref flag, really great thing). 

 

One thing that could help is some kind of varargs, for now I just register the same function several times like:

 

call_out(string func_decl, int delay, ?&in arg1);

call_out(string func_decl, int delay, ?&in arg1, ?&in arg2);

call_out(string func_decl, int delay, ?&in arg1, ?&in arg2, ?&in arg3);

 

and so on. Would it be possible to add varargs concept to AS so I could just register it once:

 

call_out(string func_decl, int delay, ...);

 

and then retrieve args from asIScriptGeneric (not sure how to do this without generic call, but probably using va_list?)

 

This could also allow adding some string scanning/formatting capabilities that are quite hard to do without variable arguments support. Every arg on varargs list could act like ?&in argument acts right now, and the function writer would have to guarantee that it's safe (for example just allowing string + primitive types which would be enough for basic formatting).

Edited by noizex

Share this post


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

  • Advertisement