Open sunjay opened 7 years ago
Easily reproducible by setting pendingGrowth
to 100
in snake.js
. This results in the snake growing to over 100 size on start.
Noticing this happens when astar takes way too long. Seeing times of over 300 ms sometimes. That's usually when this happens.
Log screenshot:
The extra time causes the messages sent to the AI process to build up. This then completely throws off the algorithm.
Here's an article about message lag, though really this is a problem with the algorithm taking too long.
Another interesting link: MessageChannel API
Idea to solve this: debounce processing of updates until a later time. Then process all updates at once, running the actual AI actions after the fact.
This may result in the AI missing a critical turn. If more than one message is being processed after the debounce, scrap the path and generate a new one from the current state. This may result in cascading events like this one, but it's worth a try. I think it might cascade because this problem is caused by a path find taking too long. Refinding the path will likely take even longer. It might be faster to just find a way back on to the previous path if possible, though this is getting complex.
Do not debounce for longer than 5 ms. 0 ms would be preferred if that is even possible.
If more than one message is being processed after the debounce, scrap the path and generate a new one from the current state. This may result in cascading events like this one, but it's worth a try.
To solve this, when applying each update, check if the head is the first turn in the already planned path. If it is, and if this is not the very last update, scrap the path and recalculate since we clearly missed a turn.
With this, we won't always have to scrap the path in case of lag.
Proposed message queue implementation:
https://github.com/sunjay/snake/commit/8e4e041675aa30dd1d6704693f5501e70db80468
As feared, the message queue simply causes the lag problem to cascade. This is because the first turn in the path is always the next tile and if messages build up, this always causes a complete recalculation of the path. Since the calculation was expensive to begin with, throwing away all that effort and trying again is resulting in the same message build up and further failures.
If we can figure out a good enough representation of traversable space (as defined in https://github.com/sunjay/snake/issues/5#issuecomment-234030009). It may be possible to solve the lag issue through an entirely new path finding method.
Rather than traversing each tile, if we can represent the traversable space efficiently, we can make it possible to do the pathfinding in two small and fast stages.
The idea here is to first find a path to the goal through the large rectangles of traversable space. Then, once you know exactly which sets of tiles you need to traverse, you can go through each rectangle of traversable space and figure out what the path should be.
The interesting part about this is that you can now pathfind incrementally after finding the first general path through the traversable space. You don't need to find each specific path immediately. You have the option to take it one traversable space rectangle at a time (even lazily!).
This diagram shows how you could sequence each specific pathfinding step. You start by finding the path through 1. Then in the time it takes to traverse that, you find the path through 2. Then 3, then 4 to the goal. The exact traversable space rectangles would look somewhat different as the snake moved about, but the idea is the same. Running through the algorithm outlined above would produce the correct traversable space path for the snake to plan through.
The revelation of this method is that you can break the problem down into these smaller problems of finding paths through traversable space. When you're in a traversable space segment, you know where you're coming from and where you're going. For number 2, you know where you'll enter from number 1 and you know that you're aiming for somewhere in number 3. This dramatically reduces the amount of pathfinding you have to do. It reduces the problem into a set of subgoals that can be solved much easier than finding the full path from the start in 1 and the goal in 4.
This new algorithm also informs step 2 of the main algorithm described in https://github.com/sunjay/snake/issues/5.
With the empty space representation, there should be a way to optimize for most open space when leaving the current goal.
In order to save on memory and access costs, eventually it may be necessary to use JavaScript typed arrays. I don't actually think JavaScript can handle all of this in the largely immutable infrastructure I've setup, but I'm certainly going to try my best.
Intermittently, the AI will start to lag and just completely die. This is visible on the debug path being rendered because the path's yellow line begins to move unexpectedly even after the snake has died.
Typically this happens after the snake has reached a length of 100.
Demonstration