If you’ve studied computer science or something related, at some (early) point you should have learned about data structures, specifically lists, queues and stacks. If you haven’t, you’d only have to learn that a list is a list, a queue is a queue (first in first out) and a stack is a stack (first in last out), that contain elements ordered somehow.
In Tanuki Tail, I’ve implemented the three things as linked lists with no header, that hold 4 byte data. This is conceived this way so you can use that space to store a pointer to anything. These lists implement insertions and deletions in the top and the bottom, as well as searches (with a comparison function parameter) and deletions in the middle, making the lists being able to work as stacks and queues. In fact, they started as stacks (for scripts), but found out that at some point I needed to ‘peek’ inside the stack.
But, this is barely exciting. What I actually wanted to talk about is todolists, which is a type that I’ve surprisingly implemented with a list. TO-DO lists. They behave like a queue, and their purpose is to add simultaneity to graphical events.
The point of this is to create graphical events atomically. So, instead of:
void slide(Bar* foo, int to, int step)
which slides it to to with a step step, you’d have
int slide(Bar* foo, int to, int step)
which approaches it to to with a step step, and returns whether it is in to or not.
And whenever you want to slide foo to to, add the task (slide,foo,to,step) to a given todolist, and before the VBL, call function todolist_run on that todolist.
The todolist tasks are stored in structs that contain a task identifier (enumerated) and an array of int parameters (up to 5, which should be enough to hold most tasks (and if it’s not, there’s always the possibility of storing the parameters in an array and make the associated function work with that array). When adding the tasks, I generally cast the type todotask over an array of casted ints, for example:
todo_addTask(&todo2, (todoTask) {DRAGSPRITEV, {(u32)actionButtons[5].sprobj, 152, 5}});
(actionButtons[5].sprobj is a pointer)
And then the todolist running function does this:
case DRAGSPRITEV:
if (DragSpriteV((SpriteObj*)task->parameter[0], task->parameter[1], task->parameter[2]))
todo_addTask(todo,(todoTask){DRAGSPRITEV,{task->parameter[0], task->parameter[1], task->parameter[2]}});
break;
In other words, makes a sprite approach a spot, and if it’s not done, enqueues the task for the next time you run the list. Actually, the way it’s designed it’d be possible to call any sort of function and use any kind of criteria on whether or not add a new task, and what kind of task to add (for example, I could have done todo_addTask(todo,(todoTask){DRAGSPRITEV,{task->parameter[0], task->parameter[1], task->parameter[2]}+1}); and make it slide faster and faster with time.)
So, how does it work?
Whenever the running function is called, a task with type ENDOP is added. Then, the elements are read in order, and a switch statement is in charge of doing the logic for each task type case. If they generate new tasks, they are placed after the ENDOP task. Finally, as you may have guessed, the ENDOP task in the end operation task which ends the task operations. Its purpose is to place mark that separates old tasks from new tasks.
There’s a global todolist, but it’s possible to create new ones. since lists are defined as pointers to the first element, doing todolist newlist = Empty_List32; is enough to create a new one with a correct initialization, and no data is allocated (so there’s no worry about freeing it). I’m using this, for example, in the inventory menu, to control some buttons independiently from the general todolist, so I can check if all tasks are done by checking if the list equals Empty_List32 (== NULL). I’ve also put a task that runs a given todolist, which may bome in helpful in places that only use the general task and you want it to run more than one list.
I’ve also added the possibility of erasing all tasks of a certain kind, or all tasks of a certain kind with same first parameter (for example, erase all tasks that move a certain sprite). This is very needed when doing opposite effects (for example: gradually setting screen brightness, first darker, and then cleaner. Every cycle, they negate each other and no effect will be seen. Also, if you’re waiting for them to finish, you get an infinite loop (true story)).
If you’re a fan of simultaneity, you totally should implement something like this. It’s easy and once it’s done, it’s quick to add new tasks. This could have other uses. In short, it’s “a queue of self-filling parametrized actions”. Sky is the limit.
In the released demo, you can see the todolist effects, for example, when you run the script right in the beginning, and the health and stamina is charging while the script is being run.
COMMENTARIES COMMENTED