I want to implement an asynchronous task scheduling. This is my code:
let gameTick = 0;
const tasks: Record<string, Function[]> = {};
function sleep(t: number) {
const targetTick = gameTick + t;
if (!tasks[targetTick]) {
tasks[targetTick] = [];
}
return new Promise<void>(resolve => {
tasks[targetTick].push(resolve);
});
}
async function task1() {
print('start task1');
for (let i = 0; i < 3; i++) {
print(`execute: ${i}`);
await sleep(2);
}
}
// task1();
for (gameTick = 0; gameTick < 10; gameTick++) {
print(`tick: ${gameTick}`);
tasks[gameTick]?.forEach(f => f())
if (gameTick == 2) task1();
}
This code behaves differently in the TypeScriptToLua environment and in the node.js environment, which confuses me.
TypeScriptToLua | nodejs |
---|---|
tick: 0 | tick: 0 |
tick: 1 | tick: 1 |
tick: 2 | tick: 2 |
start task1 | start task1 |
execute: 0 | execute: 0 |
tick: 3 | tick: 3 |
tick: 4 | tick: 4 |
execute: 1 | tick: 5 |
tick: 5 | tick: 6 |
tick: 6 | tick: 7 |
execute: 2 | tick: 8 |
tick: 7 | tick: 9 |
tick: 8 | execute: 1 |
tick: 9 |
I can understand the process of TypeScriptToLua, which is equivalent to resuming the coroutine after resolve and immediately executing the code after await. But I don’t understand NodeJs. Why does it continue to execute after the loop ends, and only execute once?
2
Answers
The problem is that promises are asynchronous, but your game loop is synchronous. When you call
resolve()
, that does not directly continue executing the coroutine. It only resolves the promise, which schedules any registered promise reactions (such as the continuation of the asynchronous code thatawait
ed that promise, or callback functions added via.then()
) to run soon, in the next microtask. So you’ll need to give it some time to do that before running the next game tick:Timing errors
The nodejs code is working as programmed. This answer does not look into lua code output except to say it’s not following the same timing rules as JavaScipt.
The timing error arises because
executes a synchronous loop, calling
task1
withgameTick
set to 2, and exiting the loop withgameTick
set to 10.The first iteration of the loop in
task1
callssleep(2)
which sets up a task forgameTick
= 4, beforeawait
causestask1
to return a promise to the caller.The loop in step 1 continues iterating and fulfills the task promise for
gameTick
4.The loop in 1 has finished, and the
await
operator intask1
returns to its surrounding loop code after "execute 0" has printed. The next iteration of the loop intask1
callssleep(2)
withi
set to 1 andgameTick
set to 10 (by completion of the loop in 1).sleep(2)
sets up a promise for a task atgameTick
12. This promise is never fulfilled in posted code, leavingtask1
waiting for it withexecute 1
left as the last line printed.Short Answer
The await doesn’t return because the promise hasn’t been resolved.
Solution
Redesign of algorithm, code implementation and/or testing code as appropriate to where the problem with the posted code originates.
Debugging undertaken
The following snippet adds some output to show the value of
i
andgameTick
in more places and log whenawait
returns. It "fixes" the problem by resolving promises in a timer callback after the hang.However it’s not a solution – the third iteration if the loop in
task2
reintroduces an unresolved pending promise.