IS4Code / PawnPlus

A SA-MP plugin enhancing the capabilities of the Pawn programming language
MIT License
102 stars 17 forks source link

task_set_result resulting 0? #8

Closed KoertLichtendonk closed 5 years ago

KoertLichtendonk commented 5 years ago

Hey there. I changed my old code a bit, in the old code I stored the ID in a seperate variable, now I instead pass the ID over task_set_result.

https://pastebin.com/pb4YWxGQ

When someone does the /badge command, it executes the SelectPlayerFaction function, which then checks how many factions the player is a member of. If there's only one faction, it should set the ID of that faction as the result. It does set a result, but for some reason it always sends back 0 to the command. I also printed out the value of Iter_First and it does print out the currect value. What is also odd, is that when a player is a member of multiple factions and he goes through the dialog and selects a faction, it does somehow work.

I've tried everything, there's no errors, warnings or anything strange in any of the logs. This is also all of my code in regards to the PlayerInfo[playerid][pAwaitMembershipSelection] task, so it's not being reset or something in the background. I even reverted back to the old style where I stored the information in a seperate variable, and that doesn't work either, leading me to think it has something to do with the "case 1" part I added inbetween, but there's nothing wrong there, so I'm legitimately confused.

IS4Code commented 5 years ago

task_valid returns false for the task then, doesn't it? If you use task_set_result, it will invoke all registered continuations (i.e. paused calls to task_wait) and then automatically delete the task, unless task_keep is called (or task_reset is called inside the continuation).

You can call task_keep to prevent the task from being automatically collected, but you also need to call task_delete in that case. I suggest you not to use a task at all, if you don't want need to wait for anything.

KoertLichtendonk commented 5 years ago

task_valid returns true before task_set_result in SelectPlayerFaction under case 1: is called, just checked it, but task_get_result in /badge returns 0 when it's supposed to return 4.

IS4Code commented 5 years ago

task_valid should return false after task_set_result.

KoertLichtendonk commented 5 years ago

Yes it does, but I still should be able to use task_get_result to get the result after a task has ended, correct? That's what currently does not work for me in this set-up.

IS4Code commented 5 years ago

This behaviour is intended; you should not be able to access the task once is has completed. Notice that your code doesn't delete the task at all, which is fine, since it gets deleted automatically at the point of task_set_result. Otherwise, you would have to call task_delete after it, but then there would be no mechanism for the continuation to extend the lifetime of the task.

Tasks are used to resume paused code; there is usually not much point in keeping the task alive after code that wanted to handle it ended. As I said, if you want to use tasks in a non-standard way (i.e. keeping it alive after its completion or just for storing a value), there is task_keep which can be used any time to disable this autocollection.

new Task:t = task_new();
task_keep(t);
task_set_result(t, 1);
...
task_delete(t);
KoertLichtendonk commented 5 years ago

Ahh, I still believe I found a bug in that case which is the reason I got confused, because task_get_result did return 4 (the value I needed) when characters had multiple factions (meaning they would go through the dialog and once they selected their faction from the dialog the task_await code would continue) even though I had no task_keep on at that time.

It's clear now, thanks for your help.

IS4Code commented 5 years ago

In that case, the task will be available immediately in the continuation (i.e. after task_await) but will be collected once the continuation exits (and the code after task_set_result will then continue as usual).

KoertLichtendonk commented 5 years ago

So, what's the difference between task_set_result in SelectPlayerFaction and task_set_result in the dialog (e.g. why does it work fine in the dialog, and not in the function)? Because that's my intended behavior, to return a faction ID and continue the task_await. It works fine with task_keep now but I'm curious because I don't really get the difference between both scenarios.

IS4Code commented 5 years ago

A call to task_await registers a continuation in the task object. The continuation consists of the code that follows the call to task_await up until the end of the callback.

In the first case, the order of operations is 1) task_set_result, 2) task_await. Since there is no continuation registered when you call task_set_result, nothing will happen, and the task is collected (since it is completed and nothing can wait for it). Further calls are performed on a nonexistent task and thus return invalid values.

In the second case, task_await is called first. This pauses the code, registers the continuation, and exits the callback. Calling task_set_result then will restore the original code and call the continuation. This call actually happens entirely inside task_set_result, so during its execution, the task is still alive. After all continuations are executed, the task is collected.

Using task_keep in the first case prevents task_set_result from deleting the task, so a call to task_await returns immediately (the task is already completed). This way, you must use task_delete to explicitly destroy the task, or you'll end up with a memory leak. You have no other option than to put it after task_await, which is fine in the first case, but in the second case, it deletes the task in the middle of task_set_result, which may potentially result in undefined behaviour. You should not call task_keep and task_delete in the second case.

Also note that task_await is (at least in the latest version) a combination of task_wait and task_get_result. You can simplify your code to:

        new memberSelected = task_awaitPlayerInfo[playerid][pAwaitMembershipSelection]);
KoertLichtendonk commented 5 years ago

Oh damn, thank you for your help, it's been really helpful to me. It seems like there's no way to call SelectPlayerFaction after the continuation is registered, seeing as task_await freezes the code until the task is finished. So I have no choice but to use task_keep and task_delete in both cases. I'll probably do some tests to see if it shows any 'undefined behavior' first.

IS4Code commented 5 years ago

I still think not creating a task when not needed is the best option, but to address your case, I have modified task_set_result to prevent the task object from being destroyed inside a continuation. It will be more or less okay to delete it then (the task will be removed from the task pool, but the actual object will be kept alive until task_set_result ends).