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

Slider tail volume is not saved properly #28587

Closed
TheMask3 opened this issue Jun 25, 2024 · 6 comments · Fixed by #28619
Closed

Slider tail volume is not saved properly #28587

TheMask3 opened this issue Jun 25, 2024 · 6 comments · Fixed by #28619
Assignees
Labels
area:editor priority:1 Very important. Feels bad without fix. Affects the majority of users. ruleset/osu!

Comments

@TheMask3
Copy link

TheMask3 commented Jun 25, 2024

Type

Game behaviour

Bug description

Try changing slider volumes in editor, while their volume changes are indeed applied when you replay the beatmap in editor, their volumes are not actually saved. After you save the beatmap, quit, and enter editor again, all slider volumes are reset.

(I also noticed an editor performance regression where osu would easily stall for a few hundred milliseconds when there's a slider on timeline while replaying beatmap in editor, the worst spike I've seen lasts more than 3 seconds but that's another topic...)

Screenshots or videos

bandicam.2024-06-25.21-05-05-150.mp4
bandicam.2024-06-25.21-05-50-977.mp4

Version

2024.625.0-lazer

Logs

compressed-logs.zip

@bdach
Copy link
Collaborator

bdach commented Jun 25, 2024

Just to confirm that we're looking at the same thing: I can reproduce this, but only when changing the volume of the slider tail.
The rest appears to work fine, and as far as I can see on your videos, the tail was the only thing that you were touching.

Is this correct? Is this the problem only with the volume of the tail, or are you seeing this for all slider node volumes and I need to keep looking?

@TheMask3
Copy link
Author

Just to confirm that we're looking at the same thing: I can reproduce this, but only when changing the volume of the slider tail. The rest appears to work fine, and as far as I can see on your videos, the tail was the only thing that you were touching.

Is this correct? Is this the problem only with the volume of the tail, or are you seeing this for all slider node volumes and I need to keep looking?

Yep, middle node volumes are saved and further tests show their volumes are copied to tails after undo.

@bdach bdach changed the title Slider volumes are not saved and will be reverted to default values after undo Slider tail volume is not saved properly Jun 25, 2024
@bdach
Copy link
Collaborator

bdach commented Jun 25, 2024

Okay, that's little solace, but at least it's not as bad as I initially feared.

Reason for this happening is that the tail samples are not actually attached to any hitobject:

// The samples should be attached to the slider tail, however this can only be done if LastTick is removed otherwise they would play earlier than they're intended to.
// (see mapping logic in `CreateNestedHitObjects` above)
//
// For now, the samples are played by the slider itself at the correct end time.
TailSamples = this.GetNodeSamples(repeatCount + 1);

Therefore, the encoding logic that converts per-object samples to stable control points / "green lines" does not see tail samples at all:

IEnumerable<SampleControlPoint> collectSampleControlPoints(IEnumerable<HitObject> hitObjects)
{
foreach (var hitObject in hitObjects)
{
if (hitObject.Samples.Count > 0)
{
int volume = hitObject.Samples.Max(o => o.Volume);
int customIndex = hitObject.Samples.Any(o => o is ConvertHitObjectParser.LegacyHitSampleInfo)
? hitObject.Samples.OfType<ConvertHitObjectParser.LegacyHitSampleInfo>().Max(o => o.CustomSampleBank)
: -1;
yield return new LegacyBeatmapDecoder.LegacySampleControlPoint { Time = hitObject.GetEndTime(), SampleVolume = volume, CustomSampleBank = customIndex };
}
foreach (var nested in collectSampleControlPoints(hitObject.NestedHitObjects))
yield return nested;
}
}

and thus ends up using the middle node volume.

The fix may be to just assign the samples to the tail hitobject after all, but ensure care that they don't play back at legacy last tick time as the relevant comment states...? @ppy/team-client @OliBomby thoughts, opinions?

@OliBomby
Copy link
Contributor

The legacy encoder encodes sample information by putting control points on the end times of hitobjects and nested hitobjects. This is problematic for the the slider tail which coincides with the end time of the slider, because only one control point can exist at a time.

Also there is an asymmetry in the encoder/decoder that is partially responsible for this issue. The encoder recursively looks for samples in nested hitobjects while the decoder only looks at top-level hitobjects and checks if they inherit IHasRepeats (which is the more correct way to handle things imo since we cant trust nested hitobjects to have persistent state).

For a fix I think you have to solve this asymmetry and then implement a special offset for the last node sample so it doesnt overlap with the control point for the sliderbody samples.

@bdach bdach added the priority:1 Very important. Feels bad without fix. Affects the majority of users. label Jun 25, 2024
@bdach
Copy link
Collaborator

bdach commented Jun 25, 2024

The encoder recursively looks for samples in nested hitobjects while the decoder only looks at top-level hitobjects and checks if they inherit IHasRepeats (which is the more correct way to handle things imo since we cant trust nested hitobjects to have persistent state)

This is a fair shout actually. When investigating this I initially thought that that was weird and instinctively started writing the logic to encode NodeSamples for IHasRepeats instead, before catching myself and realising this normally is handled by nested objects.

The one slight problem with that is that you don't necessarily know when a node's time is. We can probably pretend it's not a problem and assume uniform duration distribution and be fine with using Duration / SpanCount()... until we're not (but that would probably only be a problem with something like a custom ruleset, I think for the main 4 rulesets it should be more than fine).

@OliBomby
Copy link
Contributor

We can probably pretend it's not a problem and assume uniform duration distribution and be fine with using Duration / SpanCount()

We already made this assumption in many places like the repeats in the editor timeline or the legacy beatmap decoder. I think its safe even for custom rulesets, because the legacy format and hitobject interfaces lack the ability to encode non-uniform node samples.

@bdach bdach moved this from Needs discussion to Needs implementation in osu! client development focus Jun 26, 2024
@bdach bdach moved this from Needs implementation to In Review in osu! client development focus Jul 2, 2024
@github-project-automation github-project-automation bot moved this from In Review to Done in osu! client development focus Jul 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:editor priority:1 Very important. Feels bad without fix. Affects the majority of users. ruleset/osu!
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

3 participants