// ------------------------------------------------------------------------------------------------ #include "Core/Tasks.hpp" #include "Core.hpp" #include "Library/Chrono.hpp" // ------------------------------------------------------------------------------------------------ #include // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ namespace SqMod { // ------------------------------------------------------------------------------------------------ SQMOD_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_t id, int32_t 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_t >::From(id); mType = ConvTo< uint8_t >::From(type); // Grab the virtual machine once HSQUIRRELVM vm = SqVM(); // 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 = 0; } // ------------------------------------------------------------------------------------------------ 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 = SqVM(); // 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_t 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, static_cast< SQBool >(false), static_cast< SQBool >(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 auto delta = int32_t((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_t id, int32_t 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().GetObj(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_t id, int32_t 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).GetObj(); } 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, static_cast< Iterator >(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_t id, int32_t type, SQInteger & pos, HSQUIRRELVM vm) { // Grab the top of the stack const SQInteger top = sq_gettop(vm); // Was there a callback or tag specified? if (top <= 1) { return sq_throwerror(vm, "Missing task callback or tag"); } SQRESULT res = SQ_OK; // Fetch the task identifier type const SQObjectType ot = sq_gettype(vm, 2); // Are we looking for a task with a specific tag? if (ot == OT_STRING) { // Attempt to retrieve the value from the stack as a string StackStrF tag(vm, 2); // Have we failed to retrieve the string? if (SQ_FAILED(tag.Proc(true))) { return tag.mRes; // Propagate the error! } // Attempt to find the requested task for (const auto & t : s_Tasks) { if (t.mEntity == id && t.mType == type && t.mTag.compare(0, String::npos, tag.mPtr) == 0) { pos = static_cast< SQInteger >(&t - s_Tasks); // Store the index of this element } } } // Validate the callback type else if (ot != OT_CLOSURE && ot != OT_NATIVECLOSURE) { return sq_throwerror(vm, "Invalid callback type"); } // Should we include the iterations in the criteria? else 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 } // Grab the hash of the callback object const SQHash chash = sq_gethash(vm, 2); // 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 } // Grab the hash of the callback object const SQHash chash = sq_gethash(vm, 2); // 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 { // Grab the hash of the callback object const SQHash chash = sq_gethash(vm, 2); // 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_t id, int32_t 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) { sq_pushbool(vm, SQFalse); // Unable to locate such task } else { // Release task resources s_Tasks[pos].Terminate(); // Reset the timer s_Intervals[pos] = 0; // A task was successfully removed sq_pushbool(vm, SQTrue); } // Specify that we return a value return 1; } // ------------------------------------------------------------------------------------------------ SQInteger Tasks::Exists(int32_t id, int32_t 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, static_cast< SQBool >(pos >= 0)); // Specify that we're returning a value return 1; } // ------------------------------------------------------------------------------------------------ const Tasks::Task & Tasks::FindByTag(int32_t id, int32_t type, StackStrF & tag) { // Attempt to find the requested task for (const auto & t : s_Tasks) { if (t.mEntity == id && t.mType == type && t.mTag == tag.mPtr) { return t; // Return this task instance } } // Unable to find such task STHROWF("Unable to find a task with tag ({})", tag.mPtr); // Should not reach this point but if it did, we have to return something SQ_UNREACHABLE } // ------------------------------------------------------------------------------------------------ void Tasks::Cleanup(int32_t id, int32_t 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 terminate tasks. */ void TerminateTasks() { Tasks::Deinitialize(); } /* ------------------------------------------------------------------------------------------------ * Forward the call to cleanup certain tasks. */ void CleanupTasks(int32_t id, int32_t type) { Tasks::Cleanup(id, type); } /* ------------------------------------------------------------------------------------------------ * Forward the call to register tasks. */ void Register_Tasks(HSQUIRRELVM vm) { Tasks::Register(vm); } } // Namespace:: SqMod