From 1297635b8953e18e09ac9684eaaf1795050f4147 Mon Sep 17 00:00:00 2001 From: Sandu Liviu Catalin Date: Sat, 20 Mar 2021 15:47:32 +0200 Subject: [PATCH] Implement timers for official plug-in compatibility layer. --- module/Core/Routine.cpp | 562 +++++++++++++++++++++++++++------------- module/Core/Routine.hpp | 18 +- 2 files changed, 396 insertions(+), 184 deletions(-) diff --git a/module/Core/Routine.cpp b/module/Core/Routine.cpp index 52285873..e61f71ab 100644 --- a/module/Core/Routine.cpp +++ b/module/Core/Routine.cpp @@ -71,194 +71,393 @@ void Routine::Deinitialize() } } +// ------------------------------------------------------------------------------------------------ +struct RoutineBuilder +{ + HSQUIRRELVM mVM{nullptr}; + SQRESULT mRes{SQ_OK}; + SQInteger mTop{0}; + SQInteger mSlot{-1}; + HSQOBJECT mEnv{}; + HSQOBJECT mFunc{}; + HSQOBJECT mInst{}; + SQInteger mInterval{0}; + SQInteger mIterations{0}; + + /* -------------------------------------------------------------------------------------------- + * Base constructor. + */ + RoutineBuilder(HSQUIRRELVM vm, SQInteger slot) + : mVM(vm), mRes(), mTop(sq_gettop(vm)), mSlot(slot), mEnv(), mFunc(), mInst(), mInterval(0), mIterations(0) + { + sq_resetobject(&mEnv); + sq_resetobject(&mFunc); + sq_resetobject(&mInst); + } + + /* -------------------------------------------------------------------------------------------- + * Copy/Move constructor (disabled). + */ + RoutineBuilder(const RoutineBuilder &) = delete; + RoutineBuilder(RoutineBuilder &&) = delete; + + /* -------------------------------------------------------------------------------------------- + * Destructor. + */ + ~RoutineBuilder() = default; + + /* -------------------------------------------------------------------------------------------- + * Copy/Move assignment operator (disabled). + */ + RoutineBuilder & operator = (const RoutineBuilder &) = delete; + RoutineBuilder & operator = (RoutineBuilder &&) = delete; + + /* -------------------------------------------------------------------------------------------- + * Initialize routine creation. + */ + SQRESULT Begin() + { + mRes = SQ_OK; + // See if we have where to store this routine + if (mSlot < 0) + { + mRes = sq_throwerror(mVM, "Reached the maximum number of active routines"); + } + // See if too many arguments were specified + else if (mTop >= 20) /* 5 base + 14 parameters = 19 */ + { + mRes = sq_throwerror(mVM, "Too many parameters specified"); + } + // Was there was an environment specified? + else if (mTop <= 1) + { + mRes = sq_throwerror(mVM, "Missing routine environment"); + } + // Was there was a callback specified? + else if (mTop <= 2) + { + mRes = sq_throwerror(mVM, "Missing routine callback"); + } + // Validate the callback type + else if (sq_gettype(mVM, 3) != OT_CLOSURE && sq_gettype(mVM, 3) != OT_NATIVECLOSURE) + { + mRes = sq_throwerror(mVM, "Invalid callback type"); + } + // Return the result + return mRes; + } + + /* -------------------------------------------------------------------------------------------- + * Extract target. + */ + SQRESULT Target(SQInteger env, SQInteger func) + { + // Get the type of the environment object + const SQObjectType type = sq_gettype(mVM, env); + // Whether to default to the root table + bool use_root = (type == OT_NULL); + + // Is the specified environment a boolean (true) value? + if (type == OT_STRING) + { + StackStrF val(mVM, env); + // Attempt to generate the string value + mRes = val.Proc(false); + // Have we failed to retrieve the string? + if (SQ_FAILED(mRes)) + { + return mRes; // Propagate the error! + } + // If the string is empty or "root" then we use the root table + else if (!val.mLen || sqmod_stricmp(val.mPtr, "root") == 0) + { + use_root = true; + } + // If the string is "self" then we leave it null and default to self + else if (sqmod_stricmp(val.mPtr, "self") == 0) + { + use_root = false; // Just in case + } + } + + // Is the specified environment a null value? + if (use_root) + { + // Push the root table on the stack + sq_pushroottable(mVM); + // Attempt to retrieve the table object + mRes = sq_getstackobj(mVM, -1, &mEnv); + // Preserve the stack state + sq_poptop(mVM); + } + // Should we treat it as a valid environment object? + else if (type != OT_STRING) + { + mRes = sq_getstackobj(mVM, env, &mEnv); // Just retrieve the specified environment + } + // Can we attempt to retrieve the callback? + if (SQ_SUCCEEDED(mRes)) + { + // Fetch the specified callback object + mRes = sq_getstackobj(mVM, func, &mFunc); + } + // Return result + return mRes; + } + + /* -------------------------------------------------------------------------------------------- + * Extract options. + */ + SQRESULT Opts(SQInteger iv, SQInteger it) + { + // Was there an interval specified? + if (mTop >= iv) + { + // Grab the interval from the stack + mRes = sq_getinteger(mVM, iv, &mInterval); + // Validate the result + if (SQ_FAILED(mRes)) + { + return mRes; // Propagate the error + } + } + + // Was there a number of iterations specified? + if (mTop >= it) + { + // Grab the iterations from the stack + mRes = sq_getinteger(mVM, it, &mIterations); + // Validate the result + if (SQ_FAILED(mRes)) + { + return mRes; // Propagate the error + } + } + + // Successfully processed + return SQ_OK; + } + + /* -------------------------------------------------------------------------------------------- + * Create instance. + */ + SQRESULT Inst() + { + // Attempt to create a routine instance + try + { + DeleteGuard< Routine > dg(new Routine()); + ClassType< Routine >::PushInstance(mVM, dg.Get()); + dg.Release(); + } + catch (const std::exception & e) + { + return (mRes = sq_throwerrorf(mVM, "Unable to create the routine instance: %s", e.what())); + } + + // Fetch the created routine object + mRes = sq_getstackobj(mVM, -1, &mInst); + // Validate the result + if (SQ_FAILED(mRes)) + { + return mRes; // Propagate the error + } + + // Successfully created + return SQ_OK; + } + + /* -------------------------------------------------------------------------------------------- + * Extract arguments. + */ + SQRESULT Args(SQInteger idx) + { + // At this point we can grab a reference to our slot + Routine::Instance & inst = Routine::s_Instances[mSlot]; + // Were there any arguments specified? + if (mTop >= idx) + { + // Grab a pointer to the arguments array + Routine::Argument * args = inst.mArgv; + // Reset the argument counter + inst.mArgc = 0; + // Grab the specified arguments from the stack + for (SQInteger i = idx; i <= mTop; ++i) + { + mRes = sq_getstackobj(mVM, i, &(args[inst.mArgc].mObj)); + // Validate the result + if (SQ_FAILED(mRes)) + { + // Clear previous arguments + inst.Clear(); + // Propagate the error + return mRes; + } + // Keep a strong reference to the argument + sq_addref(mVM, &(args[inst.mArgc].mObj)); + // Increase the argument counter + ++inst.mArgc; + } + } + + // Successfully processed + return SQ_OK; + } + + /* -------------------------------------------------------------------------------------------- + * Finish routine creation. + */ +#ifdef VCMP_ENABLE_OFFICIAL + SQRESULT Finish(bool refs = false) +#else + SQRESULT Finish() +#endif + { + // Grab a reference to our slot + Routine::Instance & inst = Routine::s_Instances[mSlot]; + // Attempt to retrieve the routine from the stack and associate it with the slot + try + { + Var< Routine * >(mVM, -1).value->m_Slot = ConvTo< uint32_t >::From(mSlot); + } + catch (const std::exception & e) + { + // Clear extracted arguments + inst.Clear(); + // Now it's safe to throw the error + return (mRes = sq_throwerrorf(mVM, "Unable to create the routine instance: %s", e.what())); + } + + // Alright, at this point we can initialize the slot + inst.Init(mEnv, mFunc, mInst, mInterval, static_cast< Routine::Iterator >(mIterations)); + // Now initialize the timer + Routine::s_Intervals[mSlot] = mInterval; +#ifdef VCMP_ENABLE_OFFICIAL + // Drop the temporary callback reference + if (refs) + { + sq_release(mVM, &mFunc); + } +#endif + // Successfully finished + return SQ_OK; + } +#ifdef VCMP_ENABLE_OFFICIAL + /* -------------------------------------------------------------------------------------------- + * Initialize timer creation. + */ + SQRESULT BeginComp() + { + mRes = SQ_OK; + // See if we have where to store this timers + if (mSlot < 0) + { + mRes = sq_throwerror(mVM, "Reached the maximum number of active routines"); + } + // See if too many arguments were specified + else if (mTop >= 19) /* 4 base + 14 parameters = 18 */ + { + mRes = sq_throwerror(mVM, "Too many parameters specified"); + } + // Was there was a callback specified? + else if (mTop <= 1) + { + mRes = sq_throwerror(mVM, "Missing timer callback"); + } + // Validate the callback type + else if (sq_gettype(mVM, 2) != OT_STRING) + { + mRes = sq_throwerror(mVM, "Invalid callback type"); + } + // Return the result + return mRes; + } + + /* -------------------------------------------------------------------------------------------- + * Extract target. + */ + SQRESULT TargetComp(SQInteger func) + { + StackStrF val(mVM, func); + // Attempt to generate the string value + mRes = val.Proc(false); + // Have we failed to retrieve the string? + if (SQ_FAILED(mRes)) + { + return mRes; // Propagate the error! + } + // Push the root table on the stack + sq_pushroottable(mVM); + // Attempt to retrieve the table object + mRes = sq_getstackobj(mVM, -1, &mEnv); + // Have we failed to retrieve the string? + if (SQ_FAILED(mRes)) + { + return mRes; // Propagate the error! + } + // Push the callback name on the stack + sq_pushobject(mVM, val.mObj); + // Attempt to retrieve the value from the root table + mRes = sq_get(mVM, -2); + // Do we have the callback on the stack? + if (SQ_SUCCEEDED(mRes)) + { + // Attempt to retrieve the function object + mRes = sq_getstackobj(mVM, -1, &mFunc); + // Keep a reference to it for now + if (SQ_SUCCEEDED(mRes)) + { + sq_addref(mVM, &mFunc); + } + // Pop the callback and root table + sq_pop(mVM, 2); + } + else + { + sq_poptop(mVM); // Pop the root table + } + // Return the result + return mRes; + } +#endif +}; + // ------------------------------------------------------------------------------------------------ SQInteger Routine::Create(HSQUIRRELVM vm) { - // Locate the identifier of a free slot - const SQInteger slot = FindUnused(); - // See if we have where to store this routine - if (slot < 0) + RoutineBuilder b(vm, FindUnused()); + // Initialize routine + if (SQ_FAILED(b.Begin()) || + SQ_FAILED(b.Target(2, 3)) || + SQ_FAILED(b.Opts(4, 5)) || + SQ_FAILED(b.Args(6)) || + SQ_FAILED(b.Inst()) || + SQ_FAILED(b.Finish())) { - return sq_throwerror(vm, "Reached the maximum number of active routines"); + return b.mRes; // Propagate result } - // Grab the top of the stack - const SQInteger top = sq_gettop(vm); - // See if too many arguments were specified - if (top >= 20) /* 5 base + 14 parameters = 19 */ - { - return sq_throwerror(vm, "Too many parameters specified"); - } - // Was there was an environment specified? - else if (top <= 1) - { - return sq_throwerror(vm, "Missing routine environment"); - } - // Was there was a callback specified? - else if (top <= 2) - { - return sq_throwerror(vm, "Missing routine callback"); - } - // Validate the callback type - else if (sq_gettype(vm, 3) != OT_CLOSURE && sq_gettype(vm, 3) != OT_NATIVECLOSURE) - { - return sq_throwerror(vm, "Invalid callback type"); - } - - SQRESULT res = SQ_OK; - // Prepare an object for the environment - HSQOBJECT env; - // Get the type of the environment object - const SQObjectType etype = sq_gettype(vm, 2); - // Whether to default to the root table - bool use_root = etype == OT_NULL; - // Is the specified environment a boolean (true) value? - if (etype == OT_STRING) - { - // Attempt to generate the string value - StackStrF val(vm, 2); - // Have we failed to retrieve the string? - if (SQ_FAILED(val.Proc())) - { - return val.mRes; // Propagate the error! - } - // If the string is empty or "root" then we use the root table - else if (!val.mLen || sqmod_stricmp(val.mPtr, "root") == 0) - { - use_root = true; - } - // If the string is "self" then we leave it null and default to self - else if (sqmod_stricmp(val.mPtr, "self") == 0) - { - sq_resetobject(&env); // Make sure environment is null - use_root = false; // Just in case - } - } - // Is the specified environment a null value? - if (use_root) - { - // Push the root table on the stack - sq_pushroottable(vm); - // Attempt to retrieve the table object - res = sq_getstackobj(vm, -1, &env); - // Preserve the stack state - sq_poptop(vm); - } - // Should we treat it as a valid environment object? - else if (etype != OT_STRING) - { - sq_getstackobj(vm, 2, &env); // Just retrieve the specified environment - } - // Validate the result - if (SQ_FAILED(res)) - { - return res; // Propagate the error - } - - // Prepare an object for the function - HSQOBJECT func; - // Fetch the specified callback object - res = sq_getstackobj(vm, 3, &func); - // Validate the result - if (SQ_FAILED(res)) - { - return res; // Propagate the error - } - - // The number of iterations and interval to execute the routine - SQInteger intrv = 0, itr = 0; - // Was there an interval specified? - if (top > 3) - { - // Grab the interval from the stack - res = sq_getinteger(vm, 4, &intrv); - // Validate the result - if (SQ_FAILED(res)) - { - return res; // Propagate the error - } - } - // Was there a number of iterations specified? - if (top > 4) - { - // Grab the iterations from the stack - res = sq_getinteger(vm, 5, &itr); - // Validate the result - if (SQ_FAILED(res)) - { - return res; // Propagate the error - } - } - - // Attempt to create a routine instance - try - { - DeleteGuard< Routine > dg(new Routine()); - ClassType< Routine >::PushInstance(vm, dg.Get()); - dg.Release(); - } - catch (const std::exception & e) - { - return sq_throwerrorf(vm, "Unable to create the routine instance: %s", e.what()); - } - // Prepare an object for the routine - HSQOBJECT obj; - // Fetch the created routine object - res = sq_getstackobj(vm, -1, &obj); - // Validate the result - if (SQ_FAILED(res)) - { - return res; // Propagate the error - } - - // At this point we can grab a reference to our slot - Instance & inst = s_Instances[slot]; - // Were there any arguments specified? - if (top > 5) - { - // Grab a pointer to the arguments array - Argument * args = inst.mArgv; - // Reset the argument counter - inst.mArgc = 0; - // Grab the specified arguments from the stack - for (SQInteger i = 6; i <= top; ++i) - { - res = sq_getstackobj(vm, i, &(args[inst.mArgc].mObj)); - // Validate the result - if (SQ_FAILED(res)) - { - // Clear previous arguments - inst.Clear(); - // Propagate the error - return res; - } - // Keep a strong reference to the argument - sq_addref(vm, &(args[inst.mArgc].mObj)); - // Increase the argument counter - ++inst.mArgc; - } - } - - // Attempt to retrieve the routine from the stack and associate it with the slot - try - { - Var< Routine * >(vm, -1).value->m_Slot = ConvTo< uint32_t >::From(slot); - } - catch (const std::exception & e) - { - // Clear extracted arguments - inst.Clear(); - // Now it's safe to throw the error - return sq_throwerrorf(vm, "Unable to create the routine instance: %s", e.what()); - } - - // Alright, at this point we can initialize the slot - inst.Init(env, func, obj, intrv, static_cast< Iterator >(itr)); - // Now initialize the timer - s_Intervals[slot] = intrv; // We have the created routine on the stack, so let's return it return 1; } - +#ifdef VCMP_ENABLE_OFFICIAL +// ------------------------------------------------------------------------------------------------ +SQInteger Routine::CreateOfficial(HSQUIRRELVM vm) +{ + RoutineBuilder b(vm, FindUnused()); + // Initialize routine + if (SQ_FAILED(b.BeginComp()) || + SQ_FAILED(b.TargetComp(2)) || + SQ_FAILED(b.Opts(3, 4)) || + SQ_FAILED(b.Args(5)) || + SQ_FAILED(b.Inst()) || + SQ_FAILED(b.Finish(true))) + { + return b.mRes; // Propagate result + } + // We have the created routine on the stack, so let's return it + return 1; +} +#endif // ------------------------------------------------------------------------------------------------ bool Routine::IsWithTag(StackStrF & tag) { @@ -361,6 +560,9 @@ void Register_Routine(HSQUIRRELVM vm) RootTable(vm).FmtFunc(_SC("SqFindRoutineByTag"), &Routine::FindByTag); RootTable(vm).FmtFunc(_SC("SqIsRoutineWithTag"), &Routine::IsWithTag); RootTable(vm).FmtFunc(_SC("SqTerminateRoutineWithTag"), &Routine::TerminateWithTag); +#ifdef VCMP_ENABLE_OFFICIAL + RootTable(vm).SquirrelFunc(_SC("NewTimer"), &Routine::CreateOfficial); +#endif } } // Namespace:: SqMod diff --git a/module/Core/Routine.hpp b/module/Core/Routine.hpp index f59d7a43..d46355a8 100644 --- a/module/Core/Routine.hpp +++ b/module/Core/Routine.hpp @@ -6,19 +6,23 @@ // ------------------------------------------------------------------------------------------------ namespace SqMod { +// ------------------------------------------------------------------------------------------------ +struct RoutineBuilder; + /* ------------------------------------------------------------------------------------------------ * Execute callbacks after specific intervals of time. */ class Routine { + friend struct RoutineBuilder; public: /* -------------------------------------------------------------------------------------------- * Simplify future changes to a single point of change. */ - typedef int64_t Time; + typedef int64_t Time; typedef SQInteger Interval; - typedef uint32_t Iterator; + typedef uint32_t Iterator; typedef LightObj Argument; private: @@ -40,7 +44,7 @@ private: bool mQuiet; // Whether this instance is allowed to handle errors. bool mEndure; // Whether this instance is allowed to terminate itself on errors. bool mExecuting; // Whether this instance is currently being executed. - uint8_t mArgc; // The number of arguments that the routine must forward. + uint8_t mArgc; // The number of arguments that the routine must forward. Argument mArgv[14]; // The arguments that the routine must forward. /* ---------------------------------------------------------------------------------------- @@ -329,6 +333,7 @@ public: } // Unable to find such routine STHROWF("Unable to find a routine with tag ({})", tag.mPtr); + SQ_UNREACHABLE; // Should not reach this point but if it did, we have to return something #ifdef __clang__ #pragma clang diagnostic push @@ -366,7 +371,12 @@ public: * Create a routine with the specified parameters. */ static SQInteger Create(HSQUIRRELVM vm); - +#ifdef VCMP_ENABLE_OFFICIAL + /* -------------------------------------------------------------------------------------------- + * Create a routine with the specified parameters using the official compatibility layer. + */ + static SQInteger CreateOfficial(HSQUIRRELVM vm); +#endif protected: /* --------------------------------------------------------------------------------------------