Which type of concurrency would be ideal for my problem?


I am currently working on a game. I can't provide code yet as that part is still in the phase of design.

Design background

In this game, multiple actors (mostly computer-controlled) are present.

Their routines are mostly just the basic stuff of their state machines, seeing if their state ended, if their data has to be altered (for example if an actor stands in an elevator, the position has to be updated).

However, due the nature of the user controls (3D, 3rd person from more above, in a platformer world) I expect that running and jumping can result in clunky results, missing the platforms the player wants to jump on etc.

Because of this I want to create a "jump assist" system, that adjusts the jump height and direction a bit to land on possible targets.

This calculation is expected to be much more consuming than the regular actor routines. But luckily I can estimate when the results are needed a bit earlier than at the time of the jump.

For example, the moment a jump starts, I know where the actor will land. The movement is defined as floor-times (one foot touches the floor, speed and direction control is possible) and air-times (no foot touches the floor, controls are only queued for the time the floor is touched again).

Because I know where the actor lands, and that the actor's position is not changing during floor-times (it is purely visually seeming to move due the used animations), I have the entire air time of the current jump and the following floor phase time to calculate the jump assist of the jump that follows from that upcoming landing/jump-off position.

The work that is done in these calculations are determining which points can be reached (window of possible jump variations in height and direction, collisions with obstacles in between, etc) and to sort them by priority.

So what I want to do is to use concurrency to start the calculation at the point of the first jump and receive the results at the following jump.

I try to make it more abstract at this point. Hopefully this here is a proper way to visualize:

  • Each :blue_square: represents a routine in my game where some calculations are done during one tick (20ms).
  • Some routines ( :red_square: ) start a more expensive calculation.
  • Some routines ( :green_square: ) read the results of the actor's previously started calculation.

_tick_ :one: :two: :three: :four: :five: :six: ...

actor1 :blue_square: :blue_square: :blue_square: :blue_square: :blue_square: :blue_square: ...
actor2 :blue_square: :red_square: :blue_square: :blue_square: :green_square: :blue_square: ...
actor3 :blue_square: :blue_square: :blue_square: :blue_square: :blue_square: :blue_square: ...

  • In principle it could happen that multiple actors be :red_square: at the same tick. Regularily the expensive calculations are at least overlapping in their time frames of start to end.
  • Naturally there can't be more expensive calculations happening than there are actors.
  • On average the number of expensive calculations is far below the number of actors as they tend to just stand around or walk slowly which doesn't trigger expensive calculations.
  • The expensive calculations usually have to be finished within 5 to multiple dozen ticks. I know at the time of starting them how much time I have. (optimization potential?)
  • If the calculation is not finished when their results are needed, the current tick awaits the result even if that exceeds the targeted 20ms limit.
  • The 20ms target per tick is variable, I want to support slow-motion scenes and also fast-forwarding. This means the ticks per realtime is variable, but the ticks per game-world time is constant.
  • The sub calculations of each expensive calculation are parallelizable too and will make use of rayon

Now I looked into a few concurrency crates. I already know I will use rayon for the base actor routines. The actors are in a vector and each tick they are all proccessed in a par_iter_mut.
I read that tokio is more used for IO stuff and not for calculations. rayon seems to be suboptimal here too because I don't have a vector of these tasks that start and end at the same time. Instead, as I hope was able to visualize, they appear independently from each other.
crossbeam seems to be more what I am looking for but I could not find a proper showcase of it's features to pick a fitting one for me.
I think it is generally a bad idea to spawn a thread at each extensive calculation and instead I should maintain a pool of threads to give them workloads when needed.

I am thankful for advices. :smiley:

When looking for solutions to similar problems, I was recommended switchyard. It presents the interface of an async executor, but unlike typical async executors it expects the tasks it is given might be compute-heavy. So, you can spawn a task at :red_square: and retrieve the result at :green_square:, either in a blocking fashion or putting the rest of your frame processing inside the switchyard and having it await.

I haven't yet used the library myself, though.

1 Like

Thanks, I will look into that. :slight_smile: But I am far away from a prototype so I cannot tell soon how well it performs.

EDIT: The priority parameter looks very interesting for me, so I can give tasks a higher priority the closer their deadline is.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.