Author Topic: [Resource] Simple Task Manager  (Read 1056 times)

A simple task manager that was built into the original TGE FPS kit. I recreated it because I figured it would be really useful to have around. I recreated it from the documentation, and tested it using the given examples (and also a bunch of other tests).

As documented here on pages 367 to 377.

In short, lets you queue up functions to be called in a specified order, with optional delays.

Code: [Select]
////Simple Task Manager
//As documented http://www-rohan.sdsu.edu/~stewart/GPGT/Appendix%20A%20-%20Quick%20References.pdf
// on pages 367 to 377

function newTaskManager(%target) {
%mgr = new ScriptGroup() {
class = SimpleTaskMgr;

target = isObject(%target) ? %target.getID() : 0;
useTarget = isObject(%target);
selfExecution = 0;
defaultTaskDelay = -1; //Defaults to instant
useDefaultDelay = 0;
};

return %mgr;
}

function SimpleTaskMgr::setTarget(%mgr, %target) {
%mgr.target = isObject(%target) ? %target.getID() : 0;
%mgr.useTarget = isObject(%target);
}
function SimpleTaskMgr::clearTarget(%mgr) {
%mgr.target = 0;
%mgr.useTarget = 0;
}
function SimpleTaskMgr::getTarget(%mgr) {
return %mgr.useTarget ? (isObject(%mgr.target) ? %mgr.target : 0) : 0;
}
function SimpleTaskMgr::setDefaultTaskDelay(%mgr, %delay) {
%mgr.defaultTaskDelay = %delay;
}

function SimpleTaskMgr::selfExecuteTasks(%mgr, %useDefaultDelay) {
%mgr.selfExecution = 1;
if(%useDefaultDelay !$= "") {
%mgr.useDefaultDelay = %useDefaultDelay ? 1 : 0;
}

if(%mgr.getCount() <= 0)
return;

%task = %mgr.getObject(0);
if(!isEventPending(%mgr.tickSch)) {
%time = %mgr.useDefaultDelay ? %mgr.defaultTaskDelay : %task.delay;
if(%time == -1) {
%mgr.executeNextTask(); //Instant execution
} else {
%mgr.tickSch = %mgr.schedule(%time, executeNextTask);
}
}
}
function SimpleTaskMgr::stopSelfExecution(%mgr) {
//Doesnt cancel the next function call
//Stops after the next function execution

%mgr.selfExecution = 0;
}


function SimpleTaskMgr::addTask(%mgr, %task, %recycleCount, %preempt, %taskDelay) {
%recycleCount = mFloor(%recycleCount); //Convert to int, just incase
if(%recycleCount == 0) //0 recycles (or left blank) executes once
%recycleCount = 1;
if(%taskDelay $= "") //Undefined time executes instantly
%taskDelay = -1;

%taskObj = new ScriptObject() {
class = EGTask; //I have no idea why its EGTask, just following the documentation

task = %task;
recycleCount = %recycleCount;
preempt = %preempt;
delay = %taskDelay;
};
%mgr.add(%taskObj);

if(%mgr.selfExecution && !isEventPending(%mgr.tickSch)) {
//Start the schedule again
%firstTask = %mgr.getObject(0);
if(!isEventPending(%mgr.tickSch)) {
%time = %mgr.useDefaultDelay ? %mgr.defaultTaskDelay : %firstTask.delay;
if(%time == -1) {
%mgr.executeNextTask(); //Instant execution
} else {
%mgr.tickSch = %mgr.schedule(%time, executeNextTask);
}
}
}

return %taskObj;
}
function SimpleTaskMgr::addTaskFront(%mgr, %task, %recycleCount, %preempt, %taskDelay) {
%task = %mgr.addTask(%task, %recycleCount, %preempt, %taskDelay); //Slightly less efficient, however less code duplication

%mgr.bringToFront(%task);

return %task;
}
function SimpleTaskMgr::clearTasks(%mgr) {
//Delete all tasks
while(%mgr.getCount() > 0) {
%mgr.getObject(0).delete();
}
}

function SimpleTaskMgr::executeNextTask(%mgr) {
if(%mgr.getCount() <= 0)
return "";

%task = %mgr.getObject(0);
%result = %task.execute();

if(!isObject(%mgr)) //If the manager was deleted during execution (eg with TERMINATE# token)
return "";

//If the LOCK# token is in the task, dont store the return value for current task
if(strPos(%task.task, "LOCK#") == -1)
%mgr.lastReturnVal = %result;

if(%task.recycleCount == -1 || %task.recycleCount-- > 0) {
if(%task.preempt) {
//Dont push to back, leave at front
} else {
%mgr.pushToBack(%task);
}
} else {
%mgr.pushToBack(%task); //This is to make sure the set keeps its order
%task.delete();
}

if(%mgr.selfExecution && %mgr.getCount() > 0) {
//Start the schedule for the next execution
%task = %mgr.getObject(0);

if(!isEventPending(%mgr.tickSch)) {
%time = %mgr.useDefaultDelay ? %mgr.defaultTaskDelay : %task.delay;
if(%time == -1) {
%mgr.executeNextTask(); //Instant execution
} else {
%mgr.tickSch = %mgr.schedule(%time, executeNextTask);
}
}
}

return %result;
}


function EGTask::execute(%task) {
%mgr = %task.getGroup();

%eval = %task.task;

//LOCK# is handled in SimpleTaskMgr::executeNextTask, but we still need to remove it
%eval = strReplace(%eval, "LOCK#", "");
%eval = strReplace(%eval, "LASTRET#", %task.getGroup() @ ".lastReturnVal"); //Replace with last return value
%eval = strReplace(%eval, "TASKMGR#", %task.getGroup()); //Replace with %mgr
%eval = strReplace(%eval, "TASK#", %task.getID()); //Replace with task.getID();

%eval = strReplace(%eval, "%this", %mgr.getID()); //Allows for "func(%this.val)"

switch$(%eval) {
case "TERMINATE#": //Delete mgr
%mgr.delete();
//echo("Terminate");
return "";
case "NULL#": //Empty task, used for delays
//echo("Null");
return "";
default:
//Need to use eval (unfortunately) to keep to documentation
if(%mgr.useTarget && strPos(%eval, "STMT#") == -1) {
if(isObject(%target = %mgr.target)) {
%result = eval(%mgr.target @ "." @ %eval);
//echo("eval(\"" @ %mgr.target @ "." @ %eval @ "\");");
}
} else {
//Execute as a stand alone function, not a method
%result = eval(strReplace(%eval, "STMT#", ""));
//echo("eval(\"" @ strReplace(%eval, "STMT#", "") @ "\");");
}

return %result;
}

}

function EGTask::setTaskDelay(%task, %delay) {
%task.delay = %delay;
}
task = %task;
recycleCount = %recycleCount;
preempt = %preempt;
delay = %taskDelay;
};
%mgr.add(%task);

if(%mgr.selfExecution && !isEventPending(%mgr.tickSch)) {
//Start the schedule again
%firstTask = %mgr.getObject(0);
if(!isEventPending(%mgr.tickSch)) {
%time = %mgr.useDefaultDelay ? %mgr.defaultTaskDelay : %firstTask.delay;
if(%time == -1) {
%mgr.executeNextTask(); //Instant execution
} else {
%mgr.tickSch = %mgr.schedule(%time, executeNextTask);
}
}
}

return %task;
}
function SimpleTaskMgr::addTaskFront(%mgr, %task, %recycleCount, %preempt, %taskDelay) {
%task = %mgr.addTask(%task, %recycleCount, %preempt, %taskDelay); //Slightly less efficient, however less code duplication

%mgr.bringToFront(%task);

return %task;
}
function SimpleTaskMgr::clearTasks(%mgr) {
//Delete all tasks
while(%mgr.getCount() > 0) {
%mgr.getObject(0).delete();
}
}

function SimpleTaskMgr::executeNextTask(%mgr) {
if(%mgr.getCount() <= 0)
return "";

%task = %mgr.getObject(0);
%result = %task.execute();

//If the LOCK# token is in the task, dont store the return value for current task
if(strPos(%task.task, "LOCK#") == -1)
%mgr.lastReturnVal = %result;

if(%task.recycleCount == -1 || %task.recycleCount-- > 0) {
if(%task.preempt) {
//Dont push to back, leave at front
} else {
%mgr.pushToBack(%task);
}
} else {
%mgr.pushToBack(%task); //This is to make sure the set keeps its order
%task.delete();
}

if(%mgr.selfExecution && %mgr.getCount() > 0) {
//Start the schedule for the next execution
%task = %mgr.getObject(0);

if(!isEventPending(%mgr.tickSch)) {
%time = %mgr.useDefaultDelay ? %mgr.defaultTaskDelay : %task.delay;
if(%time == -1) {
%mgr.executeNextTask(); //Instant execution
} else {
%mgr.tickSch = %mgr.schedule(%time, executeNextTask);
}
}
}

return %result;
}


function EGTask::execute(%task) {
%mgr = %task.getGroup();

//LOCK# is handled in SimpleTaskMgr::executeNextTask
%task.task = strReplace(%task.task, "LASTRET#", %task.getGroup() @ ".lastReturnVal;"); //Replace with last return value
%task.task = strReplace(%task.task, "TASKMGR#", %task.getGroup()); //Replace with %mgr
%task.task = strReplace(%task.task, "TASK#", %task.getID()); //Replace with task.getID();

%task.task = strReplace(%task.task, "%this.", %mgr.getID() @ "."); //Allows for "func(%this.val)"

switch$(%task.task) {
case "TERMINATE#": //Delete mgr
%mgr.delete();
return "";
case "NULL#": //Empty task, used for delays
return "";
default:
//Need to use eval (unfortunately) to keep to documentation
if(%mgr.useTarget && strPos(%task.task, "STMT#") == -1) {
if(isObject(%target = %mgr.target)) {
%result = eval(%mgr.target @ "." @ %task.task);
//echo("eval(\"" @ %mgr.target @ "." @ %task.task @ "\");");
}
} else {
//Execute as a stand alone function, not a method
%result = eval(strReplace(%task.task, "STMT#", ""));
//echo("eval(\"" @ strReplace(%task.task, "STMT#", "") @ "\");");
}

return %result;
}

}

function EGTask::setTaskDelay(%task, %delay) {
%task.delay = %delay;
}
« Last Edit: May 28, 2015, 01:51:49 PM by boodals 2 »

Could you also give an example of how this can be used?


Could you also give an example of how this can be used?

The documentation has a bunch of demonstrations, but without context. Ill make some up here too.

Say you were making a simple AI. The bot will wander around until AIPlayer::OnBotSeeTarget is called, it will then aim at the target and repeatedly fire its weapon, until its dead.

Code: [Select]
function AIPlayer::CreateTaskManager(%bot) { //Call this when the bot is created
%bot.taskMgr = newTaskManager(%bot);
%bot.taskMgr.selfExecuteTasks(false); //Makes the manager execute the tasks automatically
%bot.taskMgr.addTask("wander(10);", -1, 0, 1000); //Every 1000ms, wander around (actually just calls %bot.wander(10); )
}

function AIPlayer::OnBotSeeTarget(%bot, %target) {
%bot.taskMgr.addTaskFront("fireWeapon();", 10, 1, 200); //Every 200ms, fire weapon, up to 10 times. After that, it will go back to wandering.
%bot.taskMgr.addTaskFront("aimAt(%target);"); //As soon as the bot sees a target, aim at them (assume this is instant) (this will be put before fireWeapon) (this will only be done once)
}

function AIPlayer::OnBotTargetDead(%bot, %target) {
%bot.taskMgr.clearTasks();
%bot.taskMgr.addTask("wander(10);", -1, 0, 1000); //Wander again
}



Yeah that isn't the best example. Its really quite powerful though. Read the top half of the documentation.


How about infinite chunked terrain generation? You want to generate chunks nearest to the player, but want to schedule it out so that it doesnt lag the server. Just make a manager, %mgr.setDefaultTaskDelay(500); %mgr.selfExecuteTasks(true); and then add chunks to be generated, and every 500ms it will generate the next one on the list.
« Last Edit: May 28, 2015, 02:14:39 PM by boodals 2 »

holy mingus this is awesome

Thanks, this looks really useful.

I've been considering making an 'Advanced' Task Manager, which would be similar to this, except designed myself. There's several features i'd love to have (such as tasks looping until a condition is met), however I want to keep this version entirely to the documentation given. Would anyone else want that?