Annoraaq / grid-engine

A grid based movement engine compatible with Phaser3.
https://annoraaq.github.io/grid-engine/
Apache License 2.0
239 stars 20 forks source link

Creating a follower after using `setPosition` lags the world #274

Closed ok1a closed 1 year ago

ok1a commented 2 years ago

Hi.

I've currently got followers added in my game world. Followers can be added and removed with no problem.

However, when I attempt to use setPosition before spawning a follower -- everything functions well until said follower is spawned in. When the follower is spawned in, the world begins to lag uncontrollably.

Here is the bug in action: https://www.loom.com/share/5a49744b121d44fcad0f423ae0e7baaa I attempted to show you the bug occurring after spawning a follower and then using setPosition, as well as using setPosition prior to any follower spawning.

Note that my follower creation code is simply adding a character and using .follow(). Remove is a bit more complicated than that, but the bug occurs prior to removing a follower.

This video also displays the bug occurring in #273.

zewa666 commented 2 years ago

@ok1a setPosition takes an optional 3rd param being the layer. have you tried setting that?

ok1a commented 2 years ago

@zewa666 Wow. I have no idea how I missed that. Seems like that fixed my issues in both #273 and #274, so I'll close these.

I understand it fixing #273, but I'm surprised it fixed #274.

Thanks a lot!

zewa666 commented 2 years ago

I could only assume that the calculation gets expensive when it needs to check through all available charLayers, hence the bug. if you open up the devtools and analyze the js execution you'd most likely find the answer.

Annoraaq commented 2 years ago

Maybe there is some bug in follow() that makes it slow whenever the character to be followed is on an unreachable char layer. I will check that out. Thanks for letting us know πŸ‘

Annoraaq commented 2 years ago

I could not replicate it. But there are some missing pieces such as:

So the best thing would be a minimum breaking example. But since it is working now, I think it is not worth the effort 😁

ok1a commented 2 years ago

Replying because although the above was fixed by specifying the layer when using setPosition, I am experiencing a new bug: Creating a character and having them follow me (the WASD moving player)... it works well, until the follower has their path trapped. If follower has path trapped, and cannot proceed, the world will begin to lag after I myself have made 10-30 movements away from the follower. Prior to 10-30 movements, the world is okay. Seems like the path computation must be really expensive and ever growing as I continue to move farther away into the world.

Annoraaq commented 2 years ago

What parameters do you use for the follow() call?

Can you elaborate more on what you mean by "path trapped"?

ok1a commented 2 years ago

For follow(), I use the same parameters as I listed prior above.

this.gridEngine.follow(followerId, playerId, 0);. I have verified that this also occurs when using: this.gridEngine.follow(followerId, playerId, 0, true);.

Path blocked/trapped is referring to being stuck behind a collision tile. I've included a demo below for clarity. https://www.loom.com/share/f0312f2b88994f52a961c2a87d6dcc05

Annoraaq commented 2 years ago

Thanks, I can reproduce this now on a large map πŸ‘ . I will come back with updates.

ok1a commented 2 years ago

Oh no, thank you @Annoraaq. Appreciate you and all of your hard work :)

ok1a commented 2 years ago

I was thinking of handling this locally, in the meantime, by setting a subscription to follower movements and determining the delta between playerPos and followerPos. If the delta was something like +5 or +10 tiles away, I would despawn the follower.

Annoraaq commented 2 years ago

I checked it out and indeed the pathfinding is the bottleneck. I was able to optimize it a bit by using bidirectional search instead of BFS (#85). I did some further straightforward optimizations. However, while making it much faster there is still a lag. I think the best optimization is to use Jump Point Search. I created #277 for this.

TorbenTK commented 1 year ago

I've run into the same thing as described here, though my conditions are a little different (using 1 NPC only, v2.24):

  1. I assign the NPC to follow the player using .follow()
  2. I call .stopMovement() for the NPC so it will no longer follow.
  3. I setPosition() the player.
  4. I may call .follow() afterwards to again follow the player

They are both on the same 'ground' layer and the map has only limited obstacles which don't overcomplicate the path it needs to find.

Weirdly enough, I get the lag even when my NPC has stopped following and when I'm moving away from it. The lag increases the more distance I create between player and NPC. I wouldn't expect this as the NPC no longer needs to follow.

Does grid-engine keep trying to calculate a path continuously if I once assigned follow() to a game object, even though it doesn't need to follow anymore? If so, can I (somehow) remove the follow assignment entirely?

Edit: made some changes to my comment as I was live testing whilst writing.

Annoraaq commented 1 year ago

That does at least partially sound like a bug to me and not only a performance issue, because after stopMovement() there should not happen any pathfinding anymore.

I will investigate. How large is the map and the typical distance at which you start observing the lag?

Annoraaq commented 1 year ago

I can confirm that there is a bug that causes the pathfinding to continue, even after calling stopMovement(). I am already working on a fix. However, it will not solve the performance issue you described.

The number of obstacles should not make too much of a difference, because the algorithm that is used (Bidirectional search) is kind of a BFS from start and end at the same time. That means it does not use a heuristic, like A does. But A is slower than bidirectional search in general. But bidirectional search can't be used in graphs with weights. So using A* would also not solve the issue because it would optimize for best case scenarios with little obstacles, but make it worse for scenarios with many obstacles.

That means it would be interesting to know whether the performance issues you observe are caused by the limits of bidirectional search, or if there is a bug in the algorithm's implementation (or elsewhere). That is why I am interested in the size of the map.

TorbenTK commented 1 year ago

Sorry for the delay, it took a while before I could get back to developing. I've tested with a playarea as small as 38x20 (map: 40x30). The current playarea is 50x23 (map: 66x40). Each tile is 32 pixels. The map has a ge_charLayer layer which holds tiles with collision (ge_collide), so largely like the demo's in the docs.

To provide some additional background on how I'm currently using this feature of Grid Engine (I ran a number of tests just now):

In my game, the NPC chases the player (follow). Once it is within a certain range of the player, it tries to hit them with a multi-tile attack. It stops moving to accomplish this (stopMovement). Should the player be inside these tiles whilst the attack happens, they teleport (setPosition). Once the attack pattern has finished, it is told to chase (follow) the player again. If the player wins, the NPC stops chasing entirely (stopMovement) and the player is free to move around the map without threat of being attacked. This is when I started noticing there was lag.

My player moves at the speed of 6, my NPC at 4. No other plugins are running besides Phaser, Rex’s OutlinePipelinePlugin (which I’m not using here) and the Grid Engine. Music is looping in the background, but this is captured inside another scene and not in the one GridEngine is used in.

I've only noticed lag after moving away 25 tiles in a straight line, or about 31 in a snake path. The lag is seriously bad after 60+ tiles. It strangely only happens if, before this, the game has passed about ~50 seconds of game time. Within that time, the NPC must have attacked for the lag to occur post-game.

Annoraaq commented 1 year ago

Thanks for all the details. I will take a closer look.

The problem with the ongoing pathfinding after calling stopMovement should be fixed with the just released version 2.25.1.

Annoraaq commented 1 year ago

Actually, the fix could already fix your problem. The observables were not properly unsubscribed on stopMovement. So for every time you call stopMovement and start a new follow movement, there will be one pathfinding on each position change of the player (6 times per second in your case). That means if you stopped and started the follow movement n times. There will be n*6 pathfinding runs each second. Depending on the size of n this can make the game unplayable.

So let me know if the performance issues got better, because the scenario you described (map size etc) should easily be handled by the algorithm.

TorbenTK commented 1 year ago

That makes sense, thanks for the explanation.

I've tested it and I no longer experience a drop in performance. The fix seems to have done the trick. πŸ‘

Annoraaq commented 1 year ago

Nice to hear. It should get more performant in the future with further optimizations.

Annoraaq commented 1 year ago

@ok1a Is this still an issue? The above mentioned bug with stopMovement could already solve that issue.

There will still be pathfinding performance optimizations in the next release. However, if the above bugfix already solved it, we can close this issue already.

Annoraaq commented 1 year ago

I will close this one since it could be already solved by fixing the previously mentioned bug and also the pathfinding optimizations. Let me know if it is still an issue.