@Dooley
I am sorry, I suspected such a circumstance but thrown the code same xP

Let me explain. The point is that if you want to get rid of engines entity action scheduler, as 3Run suggested, you have to build your own which involves some sort of object and function management. That is basically what my example do. It happens that once you have a function scheduler working, with little changes you get a state machines manager by the same prize. I have cleaned, commented and formatted the code above so now it works as a little transparent module to include in any project:

scheduler.h

Code
#ifndef _SCHEDULER_H_
#define _SCHEDULER_H_

//-------------------------------------------------------------------------------------------

#define SCHEDULER_MAX_ITEMS    1000 // Maximum amount of simultaneous objects into the scheduler

//-------------------------------------------------------------------------------------------

typedef struct SCHEDULER_ITEM
{
	void *object;               // A generic pointer to an object of an undetermined type
	void remover(void *item);   // A function pointer that removes the pointed object
	void *state(void *item);    // A function pointer to the current state function
} SCHEDULER_ITEM;

// Static scheduler items
SCHEDULER_ITEM schItems[SCHEDULER_MAX_ITEMS];
int schItemCount = 0;

//-------------------------------------------------------------------------------------------

/* Add an object to the scheduler
 *  object:  a pointer to an object of any kind. It can be NULL.
 *  remover: function that removes the object. It can be NULL, but the object will not be removed.
 *  state:   function to be called each frame with the object as parameter. Mandatory!
 *           void *myAction(TypeOfTheObject *object) 
 *           {
 *	             ...
 *	             return myAction; // Return NULL in order to remove the object and the scheduler item
 *           }
 */
void schItemAdd(void *object, void *remover, void *state)
{
	if(schItemCount == SCHEDULER_MAX_ITEMS) // No space enough?
		return;
	
	SCHEDULER_ITEM *item = schItems + schItemCount; // Get a pointer to the next last scheduler item
	schItemCount += 1; // Increase the item counter;
	
	item->object = object; // Init the scheduler item 
	item->state = state;
	item->remover = remover;
}

//-------------------------------------------------------------------------------------------

/* Remove all the objects and items in the scheduler
 *  
 */
void schItemRemoveAll()
{
	SCHEDULER_ITEM *item = schItems;
	SCHEDULER_ITEM *itemLast = item + schItemCount;
	for(; item < itemLast; ++item)
		if(item->remover != NULL)
			item->remover(item->object);
	schItemCount = 0;
}

//-------------------------------------------------------------------------------------------

/* Scheduler
 *  
 */
void scheduler()
{
	int index = 0;
	SCHEDULER_ITEM *item = schItems;
	for(; index < schItemCount; ++index, ++item) // Loop through the whole list of scheduled items
	{
		// Call the state function with the object as parameter and save the return on itself, so next time will call the current return
		item->state = item->state(item->object); 
		
		// Continue through the list when the return is not NULL
		if(item->state != NULL)
			continue;
		
		// Otherway, proceed to delete the object
		if(item->remover != NULL) // Call the object remover function stated at itemAdd function call
			item->remover(item->object); 
		
		// Decrease the item counter
		schItemCount -= 1; 
		
		// Copy the last scheduled item to the place where the deleted one is
		memcpy(item, schItems + schItemCount, sizeof(SCHEDULER_ITEM)); 
		// This is the fastest deletion but sorting gets messed. 
		// In case the sorting is important, it will need to copy the whole tail down instead.
		// memcpy(item, item + 1, sizeof(SCHEDULER_ITEM) * (schItemCount - index))
		
		// Decrease the for loop variables, so it computes the same item again, the item overwritten by the memcpy instruction
		index -= 1;
		item -= 1;
	}
}

#endif



You don't really need to understand what is going on, but there you go:

The basis is the SCHEDULER_ITEM data structure which saves each scheduled objects data. The scheduler works over a static list of SCHEDULER_ITEM and loops through it each frame. The list starts empty. The objects are added to the scheduler through 'schItemAdd' which adds a new SCHEDULER_ITEM to the list. The list items are deleted when their state function call returns NULL. The object can also be removed at the same time.

You would only need to include 'scheduler.h', set 'scheduler' function as 'on_frame' event and set 'schItemRemoveAll' as 'on_exit' event. You might, of course, call this functions differently. f.e: as a part of larger looping and close functions.
Code
#include <acknex.h>
#include "scheduler.h"
...
void main()
{
   on_frame = scheduler;
   on_exit = schItemRemoveAll;
}


At this point, you can start adding objects to the scheduler. Lets imagine we want to make an entity on WED be managed by the scheduler. It will need assign an action to it, as it is commonly done, so the engine would call the action on level load.

An action is usually composed by three blocks that can be built as three functions:
- initialization
- loop
- deletion

The initialization remains in the action, but the loop content and the deletion are passed to the scheduler as parameters of 'schItemAdd' in the form of functions. Something like this:
Code
void *crazyLoop(ENTITY *ent)
{
	ent->x = random(100) - 50;
	ent->y = random(100) - 50;
	ent->z = random(100) - 50;
	
	if (key_space)
		return NULL;
	
	return crazyLoop;
}

void crazyRemove(ENTITY *ent)
{
	str_remove((STRING*)ent->skill1);
	ent_remove(ent);
}

action actCrazy()
{ 
	my->skill1 = str_create("my crazy name");
	
	schItemAdd(me, crazyRemove, crazyLoop);
}


This setup will start by the action asociated to an entity in WED which creates a string, saves it into a skill, and includes the entity into the scheduler list with its removing and looping functions.

The removing function removes the string created on initialization and the entity itself.

The looping funtion needs to be of a particular signature:
Code
void *loopingFunction(TypeOfTheObject *object)
{
	...
	return loopingFunction;
}


The return of the function must be a function (void* or NULL) and the parameter should be of the type of the object, an 'ENTITY*' in the example. As far as a function returns its function name, it will act as a loop with a 'wait'. When NULL is returned, the scheduler calls the object deletion function and also deletes the scheduler item from its internal list.

You will need to call 'schItemRemoveAll' before level changes.

That is all the basics. That is how you can delete all the loops from the entity actions. As far as I know there is no way to make it faster.

All this function returning stuff might sound strange but it is mandatory because the scheduler function is running over the static array and it should not be modified by other functions while its execution, which executes every scheduled object function. That is why the looping function must return something in order to make the scheduler know that the list have to be shortened when removing an an object and modify the list localy inside the scheduler. Once you need to take care of the return of the looping functions, it can return pointers to functions by the same prize which acts as object oriented state machine.

The returning function pointer is the function that will be called next frame. This way you can build each state as a separate function and control the execution flux by the return of the functions, as I did in the first example where every state runs for a while until certain condition.

Notice that you can include items in the scheduler with no object or remover function. If there is no object, the scheduler will call the looping function same, but with a null parameter. It might serve to manage global variable changes or whatever. If there is no remover function, the scheduler will not remove the object asociated.

Also notice that the object can be of any kind, entities, panels, bitmaps, custom structs, whatever you need. It is like having engine actions for everything.


Last edited by txesmi; 08/14/20 10:27. Reason: Bug fix