mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2024-11-14 11:47:15 +01:00
525 lines
16 KiB
C++
525 lines
16 KiB
C++
// ------------------------------------------------------------------------------------------------
|
|
#include "Tasks.hpp"
|
|
#include "Core.hpp"
|
|
#include "Library/Chrono.hpp"
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
#include <utility>
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
namespace SqMod {
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
SQMODE_DECL_TYPENAME(Typename, _SC("SqTask"))
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
Tasks::Time Tasks::s_Last = 0;
|
|
Tasks::Time Tasks::s_Prev = 0;
|
|
Tasks::Interval Tasks::s_Intervals[SQMOD_MAX_TASKS];
|
|
Tasks::Task Tasks::s_Tasks[SQMOD_MAX_TASKS];
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void Tasks::Task::Init(HSQOBJECT & func, HSQOBJECT & inst, Interval intrv, Iterator itr, Int32 id, Int32 type)
|
|
{
|
|
// Initialize the callback hash
|
|
mHash = 0;
|
|
// Initialize the callback objects
|
|
mFunc = LightObj(func);
|
|
mInst = LightObj(inst);
|
|
// Initialize the task options
|
|
mIterations = itr;
|
|
mInterval = intrv;
|
|
// Initialize the entity information
|
|
mEntity = ConvTo< Int16 >::From(id);
|
|
mType = ConvTo< Uint8 >::From(type);
|
|
// Grab the virtual machine once
|
|
HSQUIRRELVM vm = DefaultVM::Get();
|
|
// Remember the current stack size
|
|
const StackGuard sg(vm);
|
|
// Is there a valid function?
|
|
if (!mFunc.IsNull())
|
|
{
|
|
// Push the callback on the stack
|
|
sq_pushobject(vm, mFunc);
|
|
// Grab the hash of the callback object
|
|
mHash = sq_gethash(vm, -1);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void Tasks::Task::Release()
|
|
{
|
|
mHash = 0;
|
|
mTag.clear();
|
|
mFunc.Release();
|
|
mInst.Release();
|
|
mData.Release();
|
|
mIterations = 0;
|
|
mInterval = 0;
|
|
mEntity = -1;
|
|
mType = -1;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
Tasks::Interval Tasks::Task::Execute()
|
|
{
|
|
// Are we even a valid task?
|
|
if (INVALID_ENTITY(mEntity))
|
|
{
|
|
return 0; // Dunno how we got here but it ends now
|
|
}
|
|
// Grab the virtual machine once
|
|
HSQUIRRELVM vm = DefaultVM::Get();
|
|
// Push the function on the stack
|
|
sq_pushobject(vm, mFunc);
|
|
// Push the environment on the stack
|
|
sq_pushobject(vm, mSelf);
|
|
// Push function parameters, if any
|
|
for (Uint32 n = 0; n < mArgc; ++n)
|
|
{
|
|
sq_pushobject(vm, mArgv[n].mObj);
|
|
}
|
|
// Make the function call and store the result
|
|
const SQRESULT res = sq_call(vm, mArgc + 1, false, ErrorHandling::IsEnabled());
|
|
// Pop the callback object from the stack
|
|
sq_pop(vm, 1);
|
|
// Validate the result
|
|
if (SQ_FAILED(res))
|
|
{
|
|
Terminate(); // Destroy ourself on error
|
|
}
|
|
// Decrease the number of iterations if necessary
|
|
if (mIterations && (--mIterations) == 0)
|
|
{
|
|
Terminate(); // This routine reached the end of it's life
|
|
}
|
|
// Return the current interval
|
|
return mInterval;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void Tasks::Process()
|
|
{
|
|
// Is this the first call?
|
|
if (s_Last == 0)
|
|
{
|
|
s_Last = Chrono::GetCurrentSysTime();
|
|
// We'll do it text time
|
|
return;
|
|
}
|
|
// Backup the last known time-stamp
|
|
s_Prev = s_Last;
|
|
// Get the current time-stamp
|
|
s_Last = Chrono::GetCurrentSysTime();
|
|
// Calculate the elapsed time
|
|
const Int32 delta = Int32((s_Last - s_Prev) / 1000L);
|
|
// Process all active tasks
|
|
for (Interval * itr = s_Intervals; itr != (s_Intervals + SQMOD_MAX_TASKS); ++itr)
|
|
{
|
|
// Is this task valid?
|
|
if (*itr)
|
|
{
|
|
// Decrease the elapsed time
|
|
(*itr) -= delta;
|
|
// Have we completed the routine interval?
|
|
if ((*itr) <= 0)
|
|
{
|
|
// Execute and reset the elapsed time
|
|
(*itr) = s_Tasks[itr - s_Intervals].Execute();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void Tasks::Initialize()
|
|
{
|
|
std::memset(s_Intervals, 0, sizeof(s_Intervals));
|
|
// Transform all task instances to script objects
|
|
for (auto & t : s_Tasks)
|
|
{
|
|
// This is fine because they'll always outlive the virtual machine
|
|
t.mSelf = LightObj(&t);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void Tasks::Register(HSQUIRRELVM vm)
|
|
{
|
|
RootTable(vm).Bind(Typename::Str,
|
|
Class< Task, NoDestructor< Task > >(vm, Typename::Str)
|
|
// Meta-methods
|
|
.SquirrelFunc(_SC("_typename"), &Typename::Fn)
|
|
.Func(_SC("_tostring"), &Task::ToString)
|
|
// Properties
|
|
.Prop(_SC("Tag"), &Task::GetTag, &Task::SetTag)
|
|
.Prop(_SC("Entity"), &Task::GetInst)
|
|
.Prop(_SC("Func"), &Task::GetFunc, &Task::SetFunc)
|
|
.Prop(_SC("Data"), &Task::GetData, &Task::SetData)
|
|
.Prop(_SC("Interval"), &Task::GetInterval, &Task::SetInterval)
|
|
.Prop(_SC("Iterations"), &Task::GetIterations, &Task::SetIterations)
|
|
.Prop(_SC("Arguments"), &Task::GetArguments)
|
|
.Prop(_SC("Inst"), &Task::GetInst)
|
|
// Member Methods
|
|
.FmtFunc(_SC("SetTag"), &Task::SetTag)
|
|
.Func(_SC("Terminate"), &Task::Terminate)
|
|
.Func(_SC("GetArgument"), &Task::GetArgument)
|
|
// Static functions
|
|
.StaticFunc(_SC("Used"), &Tasks::GetUsed)
|
|
);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void Tasks::Deinitialize()
|
|
{
|
|
// Release any script resources that the tasks might store
|
|
for (auto & t : s_Tasks)
|
|
{
|
|
t.Terminate();
|
|
t.mSelf.Release();
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
LightObj & Tasks::FindEntity(Int32 id, Int32 type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case ENT_BLIP: return Core::Get().GetBlip(id).mObj;
|
|
case ENT_CHECKPOINT: return Core::Get().GetCheckpoint(id).mObj;
|
|
case ENT_KEYBIND: return Core::Get().GetKeybind(id).mObj;
|
|
case ENT_OBJECT: return Core::Get().GetObject(id).mObj;
|
|
case ENT_PICKUP: return Core::Get().GetPickup(id).mObj;
|
|
case ENT_PLAYER: return Core::Get().GetPlayer(id).mObj;
|
|
case ENT_VEHICLE: return Core::Get().GetVehicle(id).mObj;
|
|
default: return NullLightObj();
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
SQInteger Tasks::FindUnused()
|
|
{
|
|
for (const auto & t : s_Tasks)
|
|
{
|
|
if (INVALID_ENTITY(t.mEntity))
|
|
{
|
|
return (&t - s_Tasks); // Return the index of this element
|
|
}
|
|
}
|
|
// No available slot
|
|
return -1;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
SQInteger Tasks::Create(Int32 id, Int32 type, HSQUIRRELVM vm)
|
|
{
|
|
// Locate the identifier of a free slot
|
|
const SQInteger slot = FindUnused();
|
|
// See if we have where to store this task
|
|
if (slot < 0)
|
|
{
|
|
return sq_throwerror(vm, "Reached the maximum number of tasks");
|
|
}
|
|
// Grab the top of the stack
|
|
const SQInteger top = sq_gettop(vm);
|
|
// See if too many arguments were specified
|
|
if (top > 12) /* 4 base + 8 parameters = 12 */
|
|
{
|
|
return sq_throwerror(vm, "Too many parameters specified");
|
|
}
|
|
// Was there was a callback specified?
|
|
else if (top <= 1)
|
|
{
|
|
return sq_throwerror(vm, "Missing task callback");
|
|
}
|
|
// Validate the callback type
|
|
else if (sq_gettype(vm, 2) != OT_CLOSURE && sq_gettype(vm, 2) != OT_NATIVECLOSURE)
|
|
{
|
|
return sq_throwerror(vm, "Invalid callback type");
|
|
}
|
|
// Prepare an entity instance object
|
|
HSQOBJECT inst;
|
|
// Attempt to retrieve the entity instance
|
|
try
|
|
{
|
|
inst = FindEntity(id, type).GetObject();
|
|
}
|
|
catch (const std::exception & e)
|
|
{
|
|
return sq_throwerror(vm, e.what());
|
|
}
|
|
// Prepare the function object
|
|
HSQOBJECT func;
|
|
// Fetch the specified callback
|
|
SQRESULT res = sq_getstackobj(vm, 2, &func);
|
|
// Validate the result
|
|
if (SQ_FAILED(res))
|
|
{
|
|
return res; // Propagate the error
|
|
}
|
|
|
|
// The number of iterations and interval to execute the task
|
|
SQInteger intrv = 0, itr = 0;
|
|
// Was there an interval specified?
|
|
if (top > 2)
|
|
{
|
|
// Grab the interval from the stack
|
|
res = sq_getinteger(vm, 3, &intrv);
|
|
// Validate the result
|
|
if (SQ_FAILED(res))
|
|
{
|
|
return res; // Propagate the error
|
|
}
|
|
}
|
|
// Was there a number of iterations specified?
|
|
if (top > 3)
|
|
{
|
|
// Grab the iterations from the stack
|
|
res = sq_getinteger(vm, 4, &itr);
|
|
// Validate the result
|
|
if (SQ_FAILED(res))
|
|
{
|
|
return res; // Propagate the error
|
|
}
|
|
}
|
|
|
|
// At this point we can grab a reference to our slot
|
|
Task & task = s_Tasks[slot];
|
|
// Were there any arguments specified?
|
|
if (top > 4)
|
|
{
|
|
// Grab a pointer to the arguments array
|
|
Argument * args = task.mArgv;
|
|
// Reset the argument counter
|
|
task.mArgc = 0;
|
|
// Grab the specified arguments from the stack
|
|
for (SQInteger i = 5; i <= top; ++i)
|
|
{
|
|
res = sq_getstackobj(vm, i, &(args[task.mArgc].mObj));
|
|
// Validate the result
|
|
if (SQ_FAILED(res))
|
|
{
|
|
// Clear previous arguments
|
|
task.Clear();
|
|
// Propagate the error
|
|
return res;
|
|
}
|
|
// Keep a strong reference to the argument
|
|
sq_addref(vm, &(args[task.mArgc].mObj));
|
|
// Increase the argument counter
|
|
++task.mArgc;
|
|
}
|
|
}
|
|
|
|
// Alright, at this point we can initialize the slot
|
|
task.Init(func, inst, intrv, itr, id, type);
|
|
// Now initialize the timer
|
|
s_Intervals[slot] = intrv;
|
|
// Push the tag instance on the stack
|
|
sq_pushobject(vm, task.mSelf);
|
|
// Specify that this function returns a value
|
|
return 1;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
SQInteger Tasks::Find(Int32 id, Int32 type, SQInteger & pos, HSQUIRRELVM vm)
|
|
{
|
|
// Grab the top of the stack
|
|
const SQInteger top = sq_gettop(vm);
|
|
// Was there a callback specified?
|
|
if (top <= 1)
|
|
{
|
|
return sq_throwerror(vm, "Missing task callback");
|
|
}
|
|
|
|
SQRESULT res = SQ_OK;
|
|
// Grab the hash of the callback object
|
|
const SQHash chash = sq_gethash(vm, 2);
|
|
// Should we include the iterations in the criteria?
|
|
if (top > 3)
|
|
{
|
|
SQInteger intrv = 0;
|
|
// Grab the interval from the stack
|
|
res = sq_getinteger(vm, 3, &intrv);
|
|
// Validate the result
|
|
if (SQ_FAILED(res))
|
|
{
|
|
return res; // Propagate the error
|
|
}
|
|
// Attempt to find the requested task
|
|
for (const auto & t : s_Tasks)
|
|
{
|
|
if (t.mHash == chash && t.mEntity == id && t.mType == type && t.mInterval == intrv)
|
|
{
|
|
pos = static_cast< SQInteger >(&t - s_Tasks); // Store the index of this element
|
|
}
|
|
}
|
|
}
|
|
// Should we include the interval in the criteria?
|
|
else if (top > 2)
|
|
{
|
|
SQInteger intrv = 0, sqitr = 0;
|
|
// Grab the interval from the stack
|
|
res = sq_getinteger(vm, 3, &intrv);
|
|
// Validate the result
|
|
if (SQ_FAILED(res))
|
|
{
|
|
return res; // Propagate the error
|
|
}
|
|
// Grab the iterations from the stack
|
|
res = sq_getinteger(vm, 4, &sqitr);
|
|
// Validate the result
|
|
if (SQ_FAILED(res))
|
|
{
|
|
return res; // Propagate the error
|
|
}
|
|
// Cast iterations to the right type
|
|
const Iterator itr = ConvTo< Iterator >::From(sqitr);
|
|
// Attempt to find the requested task
|
|
for (const auto & t : s_Tasks)
|
|
{
|
|
if (t.mHash == chash && t.mEntity == id && t.mType == type && t.mInterval == intrv && t.mIterations == itr)
|
|
{
|
|
pos = static_cast< SQInteger >(&t - s_Tasks); // Store the index of this element
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Attempt to find the requested task
|
|
for (const auto & t : s_Tasks)
|
|
{
|
|
if (t.mHash == chash && t.mEntity == id && t.mType == type)
|
|
{
|
|
pos = static_cast< SQInteger >(&t - s_Tasks); // Store the index of this element
|
|
}
|
|
}
|
|
}
|
|
// We could not find such task
|
|
return res;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
SQInteger Tasks::Remove(Int32 id, Int32 type, HSQUIRRELVM vm)
|
|
{
|
|
// Default to not found
|
|
SQInteger pos = -1;
|
|
// Perform a search
|
|
SQRESULT res = Find(id, type, pos, vm);
|
|
// Did the search failed?
|
|
if (SQ_FAILED(res))
|
|
{
|
|
return res; // Propagate the error
|
|
}
|
|
// Did we find anything?
|
|
else if (pos < 0)
|
|
{
|
|
return sq_throwerror(vm, "Unable to locate such task");
|
|
}
|
|
else
|
|
{
|
|
// Release task resources
|
|
s_Tasks[pos].Terminate();
|
|
// Reset the timer
|
|
s_Intervals[pos] = 0;
|
|
}
|
|
// Specify that we don't return anything
|
|
return 0;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
SQInteger Tasks::Exists(Int32 id, Int32 type, HSQUIRRELVM vm)
|
|
{
|
|
// Default to not found
|
|
SQInteger pos = -1;
|
|
// Perform a search
|
|
SQRESULT res = Find(id, type, pos, vm);
|
|
// Did the search failed?
|
|
if (SQ_FAILED(res))
|
|
{
|
|
return res; // Propagate the error
|
|
}
|
|
// Push a boolean on whether this task was found
|
|
sq_pushbool(vm, pos >= 0);
|
|
// Specify that we're returning a value
|
|
return 1;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
const Tasks::Task & Tasks::FindByTag(Int32 id, Int32 type, StackStrF & tag)
|
|
{
|
|
// Attempt to find the requested task
|
|
for (const auto & t : s_Tasks)
|
|
{
|
|
if (t.mEntity == id && t.mType == type && t.mTag.compare(tag.mPtr) == 0)
|
|
{
|
|
return t; // Return this task instance
|
|
}
|
|
}
|
|
// Unable to find such task
|
|
STHROWF("Unable to find a task with tag (%s)", tag.mPtr);
|
|
// Should not reach this point but if it did, we have to return something
|
|
return s_Tasks[SQMOD_MAX_TASKS]; // Intentional Buffer overflow!
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void Tasks::Cleanup(Int32 id, Int32 type)
|
|
{
|
|
for (auto & t : s_Tasks)
|
|
{
|
|
if (t.mEntity == id && t.mType == type)
|
|
{
|
|
t.Terminate();
|
|
// Also disable the timer
|
|
s_Intervals[&t - s_Tasks] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Forward the call to process tasks.
|
|
*/
|
|
void ProcessTasks()
|
|
{
|
|
Tasks::Process();
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Forward the call to initialize tasks.
|
|
*/
|
|
void InitializeTasks()
|
|
{
|
|
Tasks::Initialize();
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Forward the call to register tasks.
|
|
*/
|
|
void RegisterTask(HSQUIRRELVM vm)
|
|
{
|
|
Tasks::Register(vm);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Forward the call to terminate tasks.
|
|
*/
|
|
void TerminateTasks()
|
|
{
|
|
Tasks::Deinitialize();
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Forward the call to cleanup certain tasks.
|
|
*/
|
|
void CleanupTasks(Int32 id, Int32 type)
|
|
{
|
|
Tasks::Cleanup(id, type);
|
|
}
|
|
|
|
} // Namespace:: SqMod
|