Skip to content

Commit

Permalink
async: default function arguments, finish documenting
Browse files Browse the repository at this point in the history
  • Loading branch information
samtupy committed May 15, 2024
1 parent 9a56809 commit b1e1cc4
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 12 deletions.
13 changes: 7 additions & 6 deletions doc/src/references/builtin/concurrency/classes/async/!async.nvgt
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
/**
A template class that allows one to very easily call any function in nvgt on another thread and fetch that function's return value after the call has complete.
1. async\<T\>();
2. async\<T\>(?&in function, ?&in arg1 = null, ?&in arg2 = null, ...);
2. async\<T\>(const ?&in function, const ?&in arg1, const ?&in arg2, ...);
## Template arguments:
* T: The return type of the function being called.
## Arguments (2):
* ?&in function: The function that should be called, passing anything that is not a function will result in an exception.
* ?&in arg1 = null: The function's first argument, defaulted to null so that you do not need to pass more arguments than needed.
* ?&in arg2 = null: The function's second argument, passing invalid or mismatched datatypes as any of these arguments will cause an exception.
* ... Up to 10 arguments (can be expanded later if needed).
* const ?&in function: The function that should be called, passing anything that is not a function will result in an exception.
* const ?&in arg1: The function's first argument, you do not need to pass more arguments than needed.
* const ?&in arg2: The function's second argument, passing invalid or mismatched datatypes as any of these arguments will cause an exception.
* ... Up to 15 arguments.
## Remarks:
Generally speaking, this is the most convenient method of applying multithreading to your nvgt application. While lower level methods of dealing with threads require the programmer to create functions with specific signatures that usually don't allow for return values, this class allows you to quite transparently call any function in the application from a function in your script to the alert box built into NVGT on another thread while still maintaining the return value of such a function for later retrieval.
For ease of use, the constructor of this class actually calls the function provided and passes the given arguments to it. Therefor, the standard use case for this class is to create an instance of it, then to first call instance.wait() or instance.try_wait(ms) before retrieving the instance.value variable when the function's return value is needed, after either instance.wait() returns or after instance.try_wait() returns true.
For ease of use, the constructor of this class actually calls the function provided and passes the given arguments to it. Therefor, the standard use case for this class is to create an instance of it, then to first call instance.wait() or instance.try_wait(ms) before retrieving the instance.value variable when the function's return value is needed, after either instance.wait() returns or after instance.try_wait() returns true. Using the instance.complete/instance.failed properties with your own waiting logic is also perfectly acceptable and sometimes recommended.
The one drawback that makes this class look a little less pretty is that if you wish to call functions that are part of a class, you must create funcdefs for the signature of the function you want to call, then wrap the object's function in an instance of the funcdef. For example if a class contained a function bool load(string filename), you must declare funcdef void my_void_funcdef(string); and if you then had an instance of such a class called my_object, you must initialize the async object like async\<bool\>(my_void_funcdef(my_object.load), "my_file.txt"); however it is a relatively minor drawback.
Be aware that this class can throw exceptions if you do not pass arguments to the function correctly, this is because the function call happens completely at runtime rather than being prepared at compilation time like the rest of the script.
Internally, this function is registered with Angelscript multiple times with an expanding number of arguments, meaning that it is OK to pass only the number of arguments you need to a function you want to call even though this function's signature seems to indicate that the dynamically typed arguments here don't have a default value.
*/

// Example:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

// Example:
void main() {
async<string> result(input_box, "name", "please enter your name", "unnamed");
async<string> result(input_box, "name", "please enter your name");
result.wait(); // Input_box creates a dialog on it's own thread so the remark about windowing doesn't apply in this situation.
alert("test", "hello " + result.value); // May not focus automatically because from a different thread than the input box.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
Returns true if an async function call has thrown an exception.
const bool failed;
## Remarks:
This is a shorthand version of executing the expression (async.try_wait(0) and async.exception != ""), provided for syntactical ease.
*/

// Example
string throw_exception_randomly() {
if (random_bool(50)) throw("oh no!");
return "yo yo";
}
void main() {
async<string> result(throw_exception_randomly);
result.wait();
if (result.failed) alert("oops", result.exception);
else alert("success", result.value);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
// Example:
void main() {
// Lets demonstrate the edge cases mentioned above as most examples in the documentation for this class show off this property being used normally.
async<string> result1(input_box, "type text", "enter a value", "");
async<string> result1(input_box, "type text", "enter a value");
alert("test", result1.value); // The main thread will block until result1.value is available. Be careful!
async<sound@> result2; // This is not connected to a function, maybe the object could be reassigned to a result later.
sound@ s = result2.value; // Will throw an exception!
Expand Down
33 changes: 29 additions & 4 deletions src/threading.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <Poco/ThreadPool.h>
#include <angelscript.h>
#include <scriptdictionary.h>
#include <scripthelper.h>
#include <obfuscate.h>
#include "nvgt.h"
#include "pocostuff.h"
Expand Down Expand Up @@ -84,22 +85,41 @@ class async_result : public RefCountedObject, public Runnable {
for (unsigned int i = 0; i < func->GetParamCount(); i++) {
// In this context, param will be the argument as being received by the calling function and arg will be the argument as being passed to this async::call function.
int param_typeid, arg_typeid;
asITypeInfo*param_type, * arg_type;
asDWORD param_flags, arg_flags;
const char* param_default;
asITypeInfo* arg_type;
int success = func->GetParam(i, &param_typeid, &param_flags, nullptr, &param_default);
if (success < 0) {
aCtx->SetException(format("Angelscript error %d while setting art %u of async call to %s", success, i, std::string(func->GetDeclaration())).c_str());
engine->ReturnContext(ctx);
return false;
}
if (gen->GetArgCount() -2 <= i || param_default && gen->GetArgTypeId(i + 2) == asTYPEID_VOID) {
if (gen->GetArgCount() -2 <= i || param_default) {
if (!param_default) {
aCtx->SetException("Not enough arguments");
engine->ReturnContext(ctx);
return false;
}
break;
// We must initialize the default arguments ourselves.
param_type = engine->GetTypeInfoById(param_typeid);
if (param_typeid & asTYPEID_MASK_OBJECT && !(param_typeid & asTYPEID_OBJHANDLE)) {
// Create a copy of the object.
void* obj = engine->CreateScriptObject(param_type);
if (!obj) {
aCtx->SetException(format("Cannot create empty object for default assign of argument %u of async function call", i + 1).c_str());
engine->ReturnContext(ctx);
return false;
}
value_args[obj] = param_type;
*(void**)ctx->GetAddressOfArg(i) = obj;
}
success = ExecuteString(engine, format("return %s;", std::string(param_default)).c_str(), *(void**)ctx->GetAddressOfArg(i), param_typeid);
if (success < 0) {
aCtx->SetException(format("Angelscript error %d while setting default argument %u in async call to %s", success, i + 1, std::string(func->GetDeclaration())).c_str());
engine->ReturnContext(ctx);
return false;
}
continue;
}
arg_typeid = gen->GetArgTypeId(i +2);
arg_type = engine->GetTypeInfoById(arg_typeid);
Expand Down Expand Up @@ -349,7 +369,12 @@ void RegisterThreading(asIScriptEngine* engine) {
engine->RegisterGlobalFunction(_O("thread_pool& get_thread_pool_default() property"), asFUNCTION(ThreadPool::defaultPool), asCALL_CDECL);
engine->RegisterObjectType("async<class T>", 0, asOBJ_REF | asOBJ_TEMPLATE);
engine->RegisterObjectBehaviour("async<T>", asBEHAVE_FACTORY, "async<T>@ f(int&in)", asFUNCTION(async_unprepared_factory), asCALL_CDECL);
engine->RegisterObjectBehaviour("async<T>", asBEHAVE_FACTORY, "async<T>@ f(int&in, ?&in, ?&in = null, ?&in = null, ?&in = null, ?&in = null, ?&in = null, ?&in = null, ?&in = null, ?&in = null, ?&in = null, ?&in = null)", asFUNCTION(async_factory), asCALL_GENERIC);
std::string filler; // It would seem for now that the best way is to register as many factories as we support number of arguments+1, for a couple of reasons too long to explain in a comment.
for (int i = 0; i < 16; i++) {
filler += "const ?&in";
engine->RegisterObjectBehaviour("async<T>", asBEHAVE_FACTORY, std::string(std::string("async<T>@ f(int&in, ") + filler + ")").c_str(), asFUNCTION(async_factory), asCALL_GENERIC);
filler += ", ";
}
engine->RegisterObjectBehaviour("async<T>", asBEHAVE_ADDREF, "void f()", asMETHODPR(async_result, duplicate, () const, void), asCALL_THISCALL);
engine->RegisterObjectBehaviour("async<T>", asBEHAVE_RELEASE, "void f()", asMETHODPR(async_result, release, () const, void), asCALL_THISCALL);
engine->RegisterObjectMethod("async<T>", "const T& get_value() property", asMETHOD(async_result, get_value), asCALL_THISCALL);
Expand Down

0 comments on commit b1e1cc4

Please sign in to comment.