1
0
mirror of https://github.com/VCMP-SqMod/SqMod.git synced 2024-11-08 00:37:15 +01:00

Implement timers for official plug-in compatibility layer.

This commit is contained in:
Sandu Liviu Catalin 2021-03-20 15:47:32 +02:00
parent ed07b17c22
commit 1297635b89
2 changed files with 396 additions and 184 deletions

View File

@ -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

View File

@ -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:
/* --------------------------------------------------------------------------------------------