/*============================================================================= Name : task.c Purpose : Functions for implementing a non-preemptive task manager. Created 6/18/1997 by lmoloney Copyright Relic Entertainment, Inc. All rights reserved. =============================================================================*/ #include #include #include #include "types.h" #include "debug.h" #include "memory.h" #include "demo.h" #include "TitanInterfaceC.h" #include "task.h" /*============================================================================= Data: =============================================================================*/ //task structures taskdata *taskData[TSK_NumberTasks]; //pointers to active tasks sdword taskMaxTask; //highest indexed task enabled sdword taskModuleInit = FALSE; //speed of task timer, in Hz real32 taskFrequency; //global handle of task currently being executed taskhandle taskCurrentTask = -1; //global branch pointers void *taskFunctionReturn = NULL; //location returned to from task functions void *taskFunctionExit = NULL; //location branched to for exiting task functions void *taskFunctionContinue = NULL; //location branched to for returning from a task function //saved context for task calling static udword taskESISave; static udword taskEDISave; static udword taskESPSave; static udword taskEBPSave; static udword taskEBXSave; //data and entry points for taskStackSave/Restore void *taskStackSaveEntry = NULL; //location branched to for taskStackSave() void *taskStackRestoreEntry = NULL; //location branched to for taskStackSave() udword taskStackSaveESP; //stack registers are saved here udword taskStackSaveEBP; udword taskStackSaveESI; //temporary ESI/EDI save udword taskStackSaveEDI; sdword taskStackSaveDWORDS; //number of extra dwords to save sdword taskStackSaveDWORDSParameter; //number of extra dwords to save real32 taskTimeDelta = 0.0f; real32 taskTimeElapsed = 0.0f; //time elapsed, in seconds, since program started #if TASK_STACK_SAVE_HACK udword taskSaveRestoreHackIndex; #endif udword taskProcessIndex = 0; //number of calls being executed on this task sdword taskNumberCalls; //number of times to call the task #if TASK_STACK_SAVE sdword taskLargestLocals = 0; #endif /*============================================================================= Functions: =============================================================================*/ #pragma optimize("gy", off) //turn on stack frame (we need ebp for these functions) /*----------------------------------------------------------------------------- Test task function -----------------------------------------------------------------------------*/ #if TASK_TEST void taskTestFunction(void) { register sdword index; register sdword aba; index = 300; aba = 24; taskYield(0); for (index = 0; index < 4; index++) { dbgMessagef("\nTask test function called %d times.", index); aba++; taskYield(0); } taskExit(); } #endif //TASK_TEST /*----------------------------------------------------------------------------- Name : taskStartup Description : Initialize the task module. Inputs : frequency - frequency, in Hz of the timer to be used. Outputs : All taskData structures cleared Return : OKAY on success, ERROR if failure ----------------------------------------------------------------------------*/ sdword taskStartup(udword frequency) { sdword index; #if TASK_VERBOSE_LEVEL >= 1 dbgMessagef("\ntaskInit: Task module started using a frequency of %dHz", frequency); #endif dbgAssert(taskModuleInit == FALSE); for (index = 0; index < TSK_NumberTasks; index++) { taskData[index] = NULL; } taskMaxTask = 0; //no tasks taskFrequency = (real32)frequency; //save the frequency taskModuleInit = TRUE; //and say module init //call the task handler dispatcher once so that it will set return pointers OK taskExecuteAllPending(0); #if TASK_TEST //test this crazy module { taskhandle handle; handle = taskStart(taskTestFunction, 1, 4096, TF_OncePerFrame); taskRename(handle, "Task this you commie bastards!"); taskExecuteAllPending(1); taskExecuteAllPending(1); taskExecuteAllPending(1); taskExecuteAllPending(1); taskExecuteAllPending(1); taskExecuteAllPending(1); } #endif //TASK_TEST #if TASK_STACK_SAVE_HACK taskSaveRestoreHackIndex = 0; #endif return(OKAY); } /*----------------------------------------------------------------------------- Name : taskShutdown Description : Close the task manager by clearing all tasks. Inputs : void Outputs : Clears all task structures to unused and frees stack memory (if any) Return : void ----------------------------------------------------------------------------*/ void taskShutdown(void) { sdword index; #if TASK_VERBOSE_LEVEL >= 1 dbgMessage("\ntaskClose: closing task module"); #endif if (!taskModuleInit) { return; } for (index = 0; index < taskMaxTask; index++) { if (taskData[index] != NULL) { memFree(taskData[index]); //free structure and stack associated with this task taskData[index] = NULL; } } taskModuleInit = FALSE; } /*----------------------------------------------------------------------------- Name : taskPointerAlloc Description : Allocate a task pointer out of global task data list Inputs : void Outputs : sets allocated task pointer to -1 Return : index of newly allocated task Note : generates a fatal error if no task found ----------------------------------------------------------------------------*/ sdword taskPointerAlloc(void) { sdword index; for (index = 0; index < TSK_NumberTasks; index++) //look for a free task { if (taskData[index] == NULL) //if free { taskData[index] = (void *)0xffffffff; taskMaxTask = max(taskMaxTask, index + 1); //update max task if needed return(index); } } #if TASK_ERROR_CHECKING dbgFatalf(DBG_Loc, "All %d task pointers in use.", taskMaxTask); #else return(ERROR); #endif } /*----------------------------------------------------------------------------- Name : taskStart Description : Start a specific task Inputs : function - entry point of task period - period between consecutive calls stacksize - size of task's local stack flags - control execution of task Outputs : taskdata structure allocated and initialized Return : handle to task for later manipulation of task Note : if a taskdata structure or stack RAM cannot be allocated, the function will generate a fatal error. ----------------------------------------------------------------------------*/ taskhandle taskStart(taskfunction function, real32 period, udword flags) { static taskhandle handle = ERROR; taskdata *newTask; static void *taskFunctionContinueSave; static udword taskESISave; static udword taskEDISave; static udword taskESPSave; static udword taskEBPSave; static udword taskEBXSave; #if TASK_STACK_SAVE static udword taskESPSaveInitial; sdword taskESPDiff; #endif taskInitCheck(); dbgAssert(function != NULL); dbgAssert(period > 0.0f); //allocate a new task and it's stack in one fell swoop newTask = memAlloc(sizeof(taskdata), "taskData", NonVolatile); handle = taskPointerAlloc(); //alloc and save the taskData[handle] = newTask; //newest task pointer #if TASK_VERBOSE_LEVEL >= 2 dbgMessagef("\ntaskStart: starting task at 0x%x at %d Hz, flags 0x%x using handle %d at 0x%x", function, 1.0f/period, flags, handle, taskData[handle]); #endif newTask->flags = flags | TF_Allocated; //make task in use and running newTask->function = function; newTask->ticks = 0; //no residual ticks dbgAssert(period * taskFrequency < (real32)SDWORD_Max); newTask->ticksPerCall = (udword)(period * taskFrequency);//save ticks per call #if DBG_STACK_CONTEXT _asm { mov eax, esp mov dbgStackBase, eax } #endif//DBG_STACK_CONTEXT //now that stack is created OK, let's call it's handler function to start it taskFunctionContinueSave = taskFunctionContinue; _asm { mov eax, OFFSET taskContinued mov [taskFunctionContinue], eax //set point to continue from } //taskCurrentTask = handle; _asm { mov eax, esi mov [taskESISave], eax //save esi,edi mov eax, edi mov [taskEDISave], eax mov eax, esp mov [taskESPSave], eax #if TASK_STACK_SAVE mov [taskESPSaveInitial], eax #endif mov eax, ebp mov [taskEBPSave], eax mov eax, ebx mov [taskEBXSave], eax mov eax, function jmp eax //call the function } taskContinued: _asm { mov edx, handle //get edx->taskdata structure shl edx, 2 add edx, OFFSET taskData mov edx, [edx] pop eax //get return IP from stack (jumped here via a CALL) mov [edx + TOF_Context + 24], eax //eip mov eax, ebx mov [edx + TOF_Context + 0], eax //ebx mov eax, ecx mov [edx + TOF_Context + 4], eax //ecx mov eax, edi mov [edx + TOF_Context + 8], eax //edi mov eax, esi mov [edx + TOF_Context + 12], eax //esi mov eax, esp mov [edx + TOF_Context + 16], eax //esp mov eax, ebp mov [edx + TOF_Context + 20], eax //ebp } _asm { mov eax, [taskESISave] //restore esi,edi,ebp,esp mov esi, eax mov eax, [taskEDISave] mov edi, eax mov eax, [taskESPSave] mov esp, eax mov eax, [taskEBPSave] mov ebp, eax mov eax, [taskEBXSave] mov ebx, eax } #if TASK_STACK_SAVE taskESPDiff = (sdword)(taskESPSaveInitial - taskData[handle]->esp); dbgAssert(taskESPDiff >= 0); dbgAssert(taskESPDiff < BIT8); if (taskESPDiff > 0) { taskLargestLocals = max(taskLargestLocals, taskESPDiff); taskData[handle] = memRealloc(taskData[handle], sizeof(taskdata) + taskESPDiff, "task+Stack", NonVolatile); memcpy((ubyte *)(taskData[handle] + 1), ((ubyte *)taskData[handle]->esp), taskESPDiff);//save the extra stack stuff } taskData[handle]->nBytesStack = taskESPDiff; #endif //TASK_STACK_SAVE //taskCurrentTask = -1; taskFunctionContinue = taskFunctionContinueSave; return(handle); } /*----------------------------------------------------------------------------- Name : taskStop Description : Destroy the specified task (stop and deallocate) Inputs : handle - handle of task returned from taskStart() Outputs : .. Return : void ----------------------------------------------------------------------------*/ void taskStop(taskhandle handle) { taskInitCheck(); dbgAssert(handle >= 0); dbgAssert(handle < taskMaxTask); dbgAssert(taskData[handle] != NULL); #if TASK_VERBOSE_LEVEL >= 2 // dbgMessagef("\ntaskDestroy: destroying task %d", handle); #endif memFree(taskData[handle]); //free the memory taskData[handle] = NULL; //kill the task if (handle == taskMaxTask - 1) //if freeing the max task { taskMaxTask--; } } /*----------------------------------------------------------------------------- Name : taskExecuteAllPending Description : Execute all pending tasks Inputs : ticks - number of system timer ticks since the last time called Outputs : all pending tasks executed Return : ? Note : Because the stack is corrupted in this function, we can use no local variables or passed parameters. ----------------------------------------------------------------------------*/ static sdword tTicks, tLocalTicks; sdword taskExecuteAllPending(sdword ticks) { #if TASK_STACK_SAVE static ubyte localStack[BIT8]; static ubyte *currentESP; static udword localStackSize; static udword biggestStackSize; #endif /* #if TASK_STACK_CHECKING //set the stack underflow detection cookie udword validationCookie; #endif */ #if TASK_VERBOSE_LEVEL >= 2 if (ticks > 0) { dbgMessagef("\ntaskExecuteAllPending: executing tasks for %d ticks", ticks); } #endif #if DBG_STACK_CONTEXT _asm { mov eax, esp mov dbgStackBase, eax } #endif//DBG_STACK_CONTEXT if (taskModuleInit == FALSE) { //don't do a taskInitCheck because this sometimes gets called on exiting, //after all the tasks are shut down. return(OKAY); } //taskInitCheck(); if (demDemoRecording) { demNumberTicksSave(ticks); } else if (demDemoPlaying) { ticks = demNumberTicksLoad(ticks); } taskTimeDelta = ticks / taskFrequency; taskTimeElapsed += taskTimeDelta; tTicks = ticks; //save the ticks parameter dbgAssert(taskCurrentTask == -1); //verify we're not in any task currently //save the global task execution pointers _asm { mov eax, OFFSET taskContinued mov [taskFunctionContinue], eax mov eax, OFFSET taskReturned mov [taskFunctionReturn], eax mov [taskFunctionExit], eax } #if TASK_STACK_SAVE //save a chunk of the stack that might get overwritten by some of the tasks biggestStackSize = taskLargestLocals; if (biggestStackSize > 0) { _asm { mov [currentESP], esp } memcpy(localStack, currentESP, biggestStackSize); } #endif for (taskCurrentTask = 0; taskCurrentTask < taskMaxTask; taskCurrentTask++) { if (taskData[taskCurrentTask] != NULL) //if this task is enabled { if (bitTest(taskData[taskCurrentTask]->flags, TF_Paused))//don't process if paused continue; if (TitanActive) titanPumpEngine(); //calculate number of calls if (bitTest(taskData[taskCurrentTask]->flags, TF_OncePerFrame)) { //if not real-time but frame-time // Don't do this if check, because if you do it is possible that hundreds of other tasks will get executed // and this task will starve when the game AI gets heavy. Luke, later check if there's a better way to do this. /* if (taskData[taskCurrentTask]->ticks == 0) //see if time to execute { */ taskNumberCalls = 1; //flag to call once taskData[taskCurrentTask]->ticks = taskData[taskCurrentTask]->ticksPerCall; /* } */ taskData[taskCurrentTask]->ticks--; } else { //else it's a real-time task tLocalTicks = tTicks + taskData[taskCurrentTask]->ticks;//add leftover ticks from last frame taskData[taskCurrentTask]->ticks = tLocalTicks %//save leftover ticks for next frame taskData[taskCurrentTask]->ticksPerCall; taskNumberCalls = tLocalTicks / taskData[taskCurrentTask]->ticksPerCall;//compute number of calls for this frame #if TASK_MAX_TICKS taskNumberCalls = min(taskNumberCalls, TSK_MaxTicks); //make sure not too many calls #endif //TASK_MAX_TICKS } #if TASK_VERBOSE_LEVEL >= 3 if (taskNumberCalls) { dbgMessagef("\ntaskExecuteAllPending: executing %d calls of task handle %d", taskNumberCalls, taskCurrentTask); } #endif //!!! needed? _asm { //save the current register context mov eax, esi mov [taskESISave], eax //save esi,edi,esp,ebp mov eax, edi mov [taskEDISave], eax mov eax, esp mov [taskESPSave], eax mov eax, ebp mov [taskEBPSave], eax mov eax, ebx mov [taskEBXSave], eax } taskProcessIndex++; #if TASK_STACK_SAVE localStackSize = taskData[taskCurrentTask]->nBytesStack; if (localStackSize > 0) { //if this task uses some local stack static sdword count; static udword *src, *dest; dest = (udword *)taskData[taskCurrentTask]->esp; src = (udword *)(taskData[taskCurrentTask] + 1); for (count = localStackSize / 4; count > 0; count--, src++, dest++) { *dest = *src; } //memcpy(((ubyte *)taskData[taskCurrentTask]->esp), (ubyte *)(taskData[taskCurrentTask] + 1), localStackSize); } #endif //TASK_STACK_SAVE for (; taskNumberCalls > 0; taskNumberCalls--) { /* #if TASK_STACK_CHECKING //set the stack underflow detection cookie validationCookie = TSK_StackValidation + taskCurrentTask; #endif */ _asm { //restore register context from the taskdata structure mov edx, taskCurrentTask //get edx->taskdata structure shl edx, 2 add edx, OFFSET taskData mov eax, [edx] mov edx, [eax + TOF_Context + 0] //ebx mov ebx, edx mov edx, [eax + TOF_Context + 4] //ecx mov ecx, edx mov edx, [eax + TOF_Context + 8] //edi mov edi, edx mov edx, [eax + TOF_Context + 12] //esi mov esi, edx mov edx, [eax + TOF_Context + 16] //esp mov esp, edx mov edx, [eax + TOF_Context + 20] //ebp mov ebp, edx mov edx, [eax + TOF_Context + 24] //eip jmp edx //jump to the task } taskContinued: //save register context to the taskdata structure _asm { mov edx, taskCurrentTask //get edx->taskdata structure shl edx, 2 add edx, OFFSET taskData mov edx, [edx] pop eax //get return IP from stack (jumped here via a CALL) mov [edx + TOF_Context + 24], eax //eip mov eax, ebx mov [edx + TOF_Context + 0], eax //ebx mov eax, ecx mov [edx + TOF_Context + 4], eax //ecx mov eax, edi mov [edx + TOF_Context + 8], eax //edi mov eax, esi mov [edx + TOF_Context + 12], eax //esi //#if TASK_STACK_CHECKING mov eax, esp mov [edx + TOF_Context + 16], eax //esp mov eax, ebp mov [edx + TOF_Context + 20], eax //ebp //#endif } /* #if TASK_STACK_CHECKING if (taskData[taskCurrentTask]->esp < taskESPSave) { //check for stack underflow dbgFatalf(DBG_Loc, "Stack underflow: 0x%x < 0x%x handle %d", taskData[taskCurrentTask]->esp, taskESPSave, taskCurrentTask); } #endif //TASK_STACK_CHECKING */ } //!!! needed? _asm { mov eax, [taskESISave] //restore esi,edi,ebp,esp mov esi, eax mov eax, [taskEDISave] mov edi, eax mov eax, [taskESPSave] mov esp, eax mov eax, [taskEBPSave] mov ebp, eax mov eax, [taskEBXSave] mov ebx, eax } #if TASK_STACK_SAVE if (localStackSize > 0) { //if this task uses some local stack static sdword count; static udword *src, *dest; src = (udword *)taskData[taskCurrentTask]->esp; dest = (udword *)(taskData[taskCurrentTask] + 1); for (count = localStackSize / 4; count > 0; count--, src++, dest++) { *dest = *src; } //memcpy((ubyte *)(taskData[taskCurrentTask] + 1), ((ubyte *)taskData[taskCurrentTask]->esp), localStackSize); } #endif //TASK_STACK_SAVE continue; taskReturned: _asm { pop eax //remove the IP from the stack because it got here from a CALL mov eax, [taskESISave] //restore esi,edi,ebp,esp mov esi, eax mov eax, [taskEDISave] mov edi, eax mov eax, [taskESPSave] mov esp, eax mov eax, [taskEBPSave] mov ebp, eax mov eax, [taskEBXSave] mov ebx, eax } taskStop(taskCurrentTask); //kill the task } } taskCurrentTask = -1; //not currently executing any tasks #if TASK_STACK_SAVE //restore a chunk of the stack that might have been overwritten by some of the tasks if (biggestStackSize > 0) { memcpy(currentESP, localStack, biggestStackSize); } #endif return(OKAY); } /*----------------------------------------------------------------------------- Name : taskPause Description : Pauses selected task. Inputs : handle - handle of task Outputs : Sets the paused bit of specified taskData structure Return : void ----------------------------------------------------------------------------*/ void taskPause(taskhandle handle) { taskInitCheck(); dbgAssert(handle >= 0); dbgAssert(handle < taskMaxTask); dbgAssert(taskData[handle] != NULL); bitSet(taskData[handle]->flags, TF_Paused); } /*----------------------------------------------------------------------------- Name : taskResume Description : Resumes selected task. Inputs : handle - handle of task Outputs : Clears the paused bit of specified taskData structure Return : void ----------------------------------------------------------------------------*/ void taskResume(taskhandle handle) { taskInitCheck(); dbgAssert(handle >= 0); dbgAssert(handle < taskMaxTask); dbgAssert(taskData[handle] != NULL); bitClear(taskData[handle]->flags, TF_Paused); } /*----------------------------------------------------------------------------- Name : taskSavePauseStatus Description : Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void taskSavePauseStatus(void) { sdword index; taskInitCheck(); for (index = 0; index < taskMaxTask; index++) //for all tasks { if (taskData[index] != NULL) //if task active { if (bitTest(taskData[index]->flags, TF_Paused)) //save the pause bit { bitSet(taskData[index]->flags, TF_PauseSave); } else { bitClear(taskData[index]->flags, TF_PauseSave); } // deliberately do not pause task, just make sure TF_PauseSave gets set correctly } } } /*----------------------------------------------------------------------------- Name : taskFreezeAll Description : Freeze all tasks. Inputs : void Outputs : Copies paused bit into pause save bit, then sets pause bit of all active tasks. Return : void ----------------------------------------------------------------------------*/ void taskFreezeAll(void) { sdword index; taskInitCheck(); for (index = 0; index < taskMaxTask; index++) //for all tasks { if (taskData[index] != NULL) //if task active { if (bitTest(taskData[index]->flags, TF_Paused)) //save the pause bit { bitSet(taskData[index]->flags, TF_PauseSave); } else { bitClear(taskData[index]->flags, TF_PauseSave); } bitSet(taskData[index]->flags, TF_Paused); //pause this task } } } /*----------------------------------------------------------------------------- Name : taskResumesAll Description : Resumes all previously unpaused tasks. Inputs : void Outputs : Copies pause save bit into paused bit. Return : void Note : It is up to the calling party to ensure that the next call to taskExecuteAllPending() does not try to execute all the task ticks which would have happended while the tasks were paused. ----------------------------------------------------------------------------*/ void taskResumeAll(void) { sdword index; taskInitCheck(); for (index = 0; index < taskMaxTask; index++) //for all tasks { if (taskData[index] != NULL) //if task active { if (bitTest(taskData[index]->flags, TF_PauseSave)) //save the pause bit { bitSet(taskData[index]->flags, TF_Paused); } else { bitClear(taskData[index]->flags, TF_Paused); } } } } /*----------------------------------------------------------------------------- Name : taskPeriodSet Description : Set frequency for task Inputs : handle - handle of task to adjust period - period of task Outputs : Return : old period ----------------------------------------------------------------------------*/ real32 taskPeriodSet(taskhandle handle, real32 period) { real32 old; taskInitCheck(); dbgAssert(handle >= 0); dbgAssert(handle < taskMaxTask); dbgAssert(taskData[handle] != NULL); old = (real32)taskData[handle]->ticksPerCall / taskFrequency; taskData[handle]->ticksPerCall = (udword)(period * taskFrequency); return(old); } //////////////// /// CALLBACK TASK HANDLING //////////////// taskhandle babyTaskHandle = -1; void taskCallBackProcess(void); void taskCallBackInit() { listInit(&callbacks.babies); //initialize linked list of babies babyTaskHandle = taskStart(taskCallBackProcess, BABY_CallBackPeriod, 0); } void taskCallBackShutDown() { if (babyTaskHandle != -1) { taskStop(babyTaskHandle); } listDeleteAll(&callbacks.babies); } BabyCallBack *taskCallBackRegister(babyFuncCB callback, udword num, void *data, real32 callintime) { BabyCallBack *baby; //(maybe do check of time to see if it is volatile?) baby = memAlloc(sizeof(BabyCallBack), "BabyCallBack", Volatile); baby->babyCallBackFunc = callback; baby->numparm = num; baby->dataparm = data; baby->timeToExecute = callintime; baby->timeStarted = taskTimeElapsed; listAddNode(&callbacks.babies, &baby->babylink,baby); return(baby); } void taskCallBackRemove(BabyCallBack *babytogobyebye) { BabyCallBack *baby; Node *babynode; babynode = callbacks.babies.head; while(babynode != NULL) { baby = (BabyCallBack *) listGetStructOfNode(babynode); if(baby == babytogobyebye) { //returned TRUE, so delete listDeleteNode(babynode); return; } babynode = babynode->next; } } void taskCallBackProcess(void) { static BabyCallBack *baby; static Node *babynode,*tempnode; taskYield(0); while(1) { taskStackSaveCond(0); babynode = callbacks.babies.head; while(babynode != NULL) { baby = (BabyCallBack *) listGetStructOfNode(babynode); if(taskTimeElapsed - baby->timeStarted >= baby->timeToExecute) { //time elapsed so execute callback if((baby->babyCallBackFunc)(baby->numparm, baby->dataparm, baby)) { //returned TRUE, so delete tempnode = babynode->next; listDeleteNode(babynode); babynode = tempnode; continue; } //reset task time baby->timeStarted = taskTimeElapsed; } babynode = babynode->next; } //break; taskStackRestoreCond(); taskYield(0); } }