luxkun / ReGoap

Generic C# GOAP (Goal Oriented Action Planning) library with Unity3d examples
Apache License 2.0
1.03k stars 147 forks source link

fix: Use concurrent queue and dictionary to avoid race conditions. #35

Closed shanecelis closed 3 years ago

shanecelis commented 5 years ago

With Unity2019.1.0a13 the example scene had failures with WorksQueue.Dequeue() initially. Presumably this is because the WorksQueue.Count test and Dequeue() happen at different times. To fix this, I switched WorksQueue to a ConcurrentQueue and use TryDequeue() which dequeues atomically if there is an item available and returns true; otherwise, it returns false.

After that was fixed, a new error popped up when the trees ran out. This appeared to be caused by a race condition between the test for HasKey and Get. To fix this, I switched the dictionaries in ReGoapState to ConcurrentDictionary classes. And I removed Get() in favor of TryGetValue() which avoids that race condition.

The example scene works. All unit tests pass.

I hope these bugs mean that Unity is using more threads than it was before.

wqaetly commented 4 years ago

First of all, thank you for your contribution to ReGoap, but when I use your library, I have a little problem. Take this unit test as an example QQ截图20200428102444 Please pay attention to the cost of each Action QQ截图20200428102454 Then we do this unit test again QQ截图20200428102503 We found that the cost of each Action is inherited from the previous unit test, so that in multiple plannings to achieve certain complex goals, it will produce incorrect results because of the incorrect value.

wqaetly commented 4 years ago

Okay, forgive my offense, I tried to do a test in the original warehouse, the same result。。。

shanecelis commented 4 years ago

I'm seeing the same behavior which you've found in Unity2019.3.10f1. It'd be nice to make a failing test out of this. However, if I roll back to the prior commit 506600c, I get the same behavior where the costs increase. I'd advise making a failing test, then running git bisect to find the failing commit. It doesn't seem like it's related to my changes.

If I were to guess, I'd guess some static variables that are being kept around that should be cleared.

wqaetly commented 4 years ago

In fact, it is the problem of cachedStates and cachedNodes, it will cause the next test to get the cost of the previous test

shanecelis commented 4 years ago

Ha, I understand your comment now. Cheers.

wqaetly commented 4 years ago

Excellent!

wqaetly commented 4 years ago

All we need to do is add a sentence of g = 0 to Recycle () of ReGoapNode.cs QQ截图20200428151753 Then no matter how many times we test, their cost is correct image

wqaetly commented 4 years ago

Speaking of which, in fact, I always have a question, I would like to ask you here Still this example, the "ChopTree" action costs 2,"WorksWood" action costs 5,"MineOre" action costs 10,"SmeltOre" action costs 10,So the result we wanted was ChopTree->WorksWood->MineOre->SmeltOre,but the result given by ReGoap is not what we want。 I have seen the A * algorithm in the ReGoap library and understand how he got this result, but what I really want to ask is how to set the cost if we want the planning result to be exactly as we thought Value, if you can, please tell me how to do it, or use the example above, I'm so grateful to you

jswigart commented 4 years ago

If you want them to prioritize them to gather an expend 1 resource at a time I would probably increase the cost of gather actions if they already have items of another type. You can't really enforce an order of operations without basically representing it in the form of cost modifications, otherwise to A* your plan is equally as good as the plan it came up with cost wise, and the result you get out is largely dependent on the order the actions were evaluated in.

wqaetly commented 4 years ago

If you want them to prioritize them to gather an expend 1 resource at a time I would probably increase the cost of gather actions if they already have items of another type. You can't really enforce an order of operations without basically representing it in the form of cost modifications, otherwise to A* your plan is equally as good as the plan it came up with cost wise, and the result you get out is largely dependent on the order the actions were evaluated in.

thank you for your reply Can I understand you this way? We should not rely on the value of Cost to achieve the planning order we want, but need to increase the conditions, as I did in the picture below image

jswigart commented 4 years ago

Yes, in principle it should be possible to address it that way. If you have multiple actions, one that is higher cost if carrying something already, and the other that is low cost if carrying nothing, that should allow you to bias toward not carrying stuff around and instead towards the actions which make use of that carried resource right away.

jswigart commented 4 years ago

Another possibility could be to generalize it to a "hasRoomInInventory" which you might set to 1 slot, effectively providing the same benefit without needing to do it for each resource type, but it would also give you the flexibility to give an AI multiple inventory slots that he could carry multiple things at a time. If coupled with movement based action evaluations it can generate a most optimal plan, though most people don't integrate movement evaluation into their action planning systems.

wqaetly commented 4 years ago

Another possibility could be to generalize it to a "hasRoomInInventory" which you might set to 1 slot, effectively providing the same benefit without needing to do it for each resource type, but it would also give you the flexibility to give an AI multiple inventory slots that he could carry multiple things at a time. If coupled with movement based action evaluations it can generate a most optimal plan, though most people don't integrate movement evaluation into their action planning systems.

emm, sorry, I didn't understand your hasRoomInInventory statement。。。 Can you give an example? Still want this order that ChopTree->WorksWood->MineOre->SmeltOre

jswigart commented 4 years ago

What I mean is that you have a condition for whether you "hasWood" or "hasRawWood", etc but no conditions that act as a limitation on whether something can be acquired, like a condition based inventory.

Picking up something(anything) could set inventorySpace to false, and actions that refine that resource or otherwise drop it off can turn it back to true. That would act as a more generalized mechanism for controlling the order.

Its probably possible to use an integer base inventory slots condition as well, but a bit more complicated.

wqaetly commented 4 years ago

What I mean is that you have a condition for whether you "hasWood" or "hasRawWood", etc but no conditions that act as a limitation on whether something can be acquired, like a condition based inventory.

Picking up something(anything) could set inventorySpace to false, and actions that refine that resource or otherwise drop it off can turn it back to true. That would act as a more generalized mechanism for controlling the order.

Its probably possible to use an integer base inventory slots condition as well, but a bit more complicated.

Okay, I understand now, thanks

luxkun commented 3 years ago

Accepted and also fixed the issue with the g, thank you both! @shanecelis and @wqaetly