Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support custom input prediction (WIP) #83

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

caspark
Copy link
Contributor

@caspark caspark commented Nov 13, 2024

Introduces a new associated type parameter for Config that controls the input prediction approach used.

See extensive documentation on InputPredictor and its implementations.


This addresses #69 (cc @johanhelsing, @aleksa2808) and accidentally also addresses #74 (cc @MaxCWhitehead ).

Some off the cuff design decisions I made when I hacked this together (before I got carried away writing documentation):

  • A) I am using an associated type parameter to plumb the input predictor down to the input queue. I did this out of convenience/laziness originally because I want this change myself, but it does mean that user code would have to change to add in that extra associated type parameter. If there's interest in merging this, it should be totally possible to modify this to pass a function/closure to SessionBuilder instead and pass a pointer to that down the 2-3 layers to InputQueue (and that's probably a better approach).
  • B) We still invoke the user provided prediction function for cases where there was no previous frame. I figure this is useful for those folks for whom zeroed() does not equate to "no input", like @MaxCWhitehead over in Default predicted input on first frame (zeroed Input) assumes zeroed is equivalent to no movement #74.
  • C) I let folks opt out of providing a prediction by returning None, in which case we just fall back to using zeroed(). This makes it a tiny bit more convenient to implement a prediction function if you are okay with using zeroed() as your default in the case where there is no prior input to base your prediction on, but it's super minor. Might be better to just require a prediction to be made.
  • D) There's no way to know what frame number the prediction is for, nor what the frame number of the last confirmed prediction is, nor what player it is for. I can imagine some advanced input predictor trying to guess what the next position of a player's joystick might be based on rate of change previously, but I don't have that use case so I am wary of going down that rabbit hole because without the real use case I'd probably design something that's useless.

I am not strongly opinionated about any of those, so please let me know what your preferences are and I will adjust the code to suit. If you are also not strongly opinionated, I recommend:

  • A: convert to SessionBuilder param instead of associated type to reduce impact on existing codebases
  • B: leave as-is (it's useful)
  • C: require a prediction (don't let people opt out)
  • D: leave as-is (can always make the predictor more flexible later by adding a second more capable type of input predictor)

But I haven't made those changes yet because they'd be wasted if you don't want to merge something along the lines of this PR! And also I've already spent a few hours on this today so I need to get back to actually implementing rollback in my game ;)

Introduces a new associated type parameter for Config that controls the
input prediction approach used.

See extensive documentation on InputPredictor and its implementations.
@caspark caspark changed the title feat: Support custom input prediction feat: Support custom input prediction (WIP) Nov 13, 2024
/// [RepeatLastInputPredictor] is a good default.
///
/// See documentation for [InputPredictor] for more information.
type InputPredictor: InputPredictor<Self::Input>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: I also need to update the Sync+Send variant of the Config struct to include this param.

Copy link
Contributor

@johanhelsing johanhelsing left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool that you're working on this. I think it could add some nice flexibility while still leaving simple usage simple :)

Don't have time to actually review this, so just commenting on the API, and showing my appreciation :D

EDIT: also, I see you addressed some of this in the pr body. Sorry I didn't read it properly.

/// it is typically good to return `None`, `Some(I::default())`, or a Some with a value of `I`
/// that means "this player sent no input" for your game.
///
fn predict(previous: Option<I>) -> Option<I>;
Copy link
Contributor

@johanhelsing johanhelsing Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the benefit of having this return an Option? Couldn't you just choose a ZeroedInputPredictor instead? or zero the input yourself if a previous input is not available?

Also is only the previous input enough? Maybe you'd want to estimate the velocity of change for an input and extrapolate?

Maybe take a slice of I if possible?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the benefit of having this return an Option?

Yep, see point C in PR body (as per your edit to your parent comment); in hindsight I agree it's silly and would be better to not allow returning Option.

Also is only the previous input enough?

Partially discussed this too under point D in PR body; exposing more information is possible but I don't personally have a use case for it (and seems like nobody else has requested it?), so I'm wary of making the API unnecessarily complicated (or not useful) to support that - or worse, introducing a subtle bug, as the code in InputQueue::input() is already a bit fiddly.

take a slice of I if possible

Conceptually that'd be reasonable, but the inputs are stored in a Vec-based ring buffer so we'd have to pass 2 slices or do a bit of copying to achieve that. A good example of how the implementation/API would get complicated by supporting such more advanced input predictors!

A less invasive (for ggrs) way to do it might be to pass the frame number for both the current prediction request and the last confirmed prediction over to the input predictor, plus the player handle; then the input predictor can store as much of that as it wants in some separate data store. I had partially implemented that at one point before I backed it out - totally doable but some more data needs to be passed to InputQueue::input() to make it happen - if anyone ever actually requests it, of course!

@gschup gschup marked this pull request as draft November 20, 2024 07:00
@gschup
Copy link
Owner

gschup commented Nov 20, 2024

converted to draft as you stated (WIP) in the title.

@caspark
Copy link
Contributor Author

caspark commented Nov 20, 2024

Sure, makes sense to have this be a draft PR. But if I do spend the time to clean this up by addressing the points I suggested in the ways that I suggested, are you willing to merge it? (subject to further feedback from review then of course)

I don't want to spend some time polishing this if you're not happy with the approach I'm taking :)

@gschup
Copy link
Owner

gschup commented Nov 20, 2024

In that case, please wait a little while until I find time to look at it properly. In general, I like the idea a lot, seems like a useful option for special cases.

@caspark
Copy link
Contributor Author

caspark commented Dec 14, 2024

FYI I intend to rebase & rework this PR once #82 has been merged (and ideally after I've merged a PR to add logging as per #89, assuming we're aligned on adding logging of some kind), since right now this PR is using bytemuck stuff that #82 removes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants