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

Move unnecessary properties from BeatmapInfo / realm to IBeatmap #28473

Merged
merged 22 commits into from
Dec 2, 2024

Conversation

bdach
Copy link
Collaborator

@bdach bdach commented Jun 13, 2024

RFC. PRing for early feedback.

This is basically #23418 but hopefully better (because it moves out more stuff, and hopefully contains less naming discussions). This removes everything that was feasible to remove from BeatmapInfo, therefore bypassing the reference handling nightmare that causes #20883.

I'm necroing that because it came up in recent discussions about persisting the new grid settings somewhere (see discord, cc @OliBomby).

Some things still remain:

  • BeatmapInfo.BeatDivisor - can't really be moved out of realm because it's used for filtering in song select
  • BeatmapInfo.EditorTimestamp could go live in the .osu file but I didn't want to make that change here as I would rather keep this diff strictly menial.

I'm not very happy about how much this makes IBeatmap grow but I don't see much wiggle room. Any effort to constrain interfaces any more to specific usages (diffcalc, editor) is basically killed by WorkingBeatmap / conversion and requires at least introduction of generic messes and multiple scoped overloads so I decided I'd rather not.

@bdach bdach added type:code-quality realm deals with local realm database labels Jun 13, 2024
@bdach bdach requested a review from a team June 13, 2024 13:36
Copy link
Contributor

@smoogipoo smoogipoo left a comment

Choose a reason for hiding this comment

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

I suppose that in principle I'm not super against this, but it does blurry the definition for what should and should not be put in BeatmapInfo a bit.

You could say that BeatmapInfo is everything to be databased, in which case I'd probably expect the epilepsy warning to be put there, since it's being used in a context where it's expected to be available relatively "free-of-cost"?

Comment on lines 18 to +19
public BeatmapInfo BeatmapInfo = new BeatmapInfo();
public IBeatmap Beatmap { get; set; } = new Beatmap();
Copy link
Contributor

Choose a reason for hiding this comment

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

This is kinda weird...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So is everything else being populated with empty objects in this class. I was just following precedent, if you will.

Copy link
Contributor

@smoogipoo smoogipoo Jun 17, 2024

Choose a reason for hiding this comment

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

My problem here is two-fold. The first is that I'm struggling to follow the code to even ensure this is being written to right now, because it doesn't look like it is? The second is I think this goes above the "everything else" for me.

One thing I thought about but hesitated from posting is to split the properties out into a BeatmapProperties or maybe even into BeatmapMetadata. It feels wrong to be passing Beatmap around which historically has been used very close to gameplay...

To go further, both BeatmapInfo and Beatmap look wrong to me.

  • If we're not going to have a general "class of info" that we're passing around, then I think EpilepsyWarning/WidescreenStoryboard should be moved into Storyboard.
  • The remaining references to BeatmapInfo need to be removed - it looks like there's two:
    • ReplacesBackground (BeatmapInfo.Metadata.BackgroundFile) - probably BackgroundFile should be duplicated as a property here.
    • GetStoragePathFromStoryboardPath the directionality looks inverted. It should be DrawableStoryboard doing the lookups on resource stores, not what is effectively a model class otherwise.

Perhaps this would be best as a pre-pass.

Copy link
Collaborator Author

@bdach bdach Jun 17, 2024

Choose a reason for hiding this comment

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

One thing I thought about but hesitated from posting is to split the properties out into a BeatmapProperties or maybe even into BeatmapMetadata

That's what #23418 tried to do but got stunlocked on naming. If we can agree on a name or a place I don't see why not.

Copy link
Member

Choose a reason for hiding this comment

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

One thing I thought about but hesitated from posting is to split the properties out into a BeatmapProperties or maybe even into BeatmapMetadata. It feels wrong to be passing Beatmap around which historically has been used very close to gameplay...

I get this, but is there an actually requirement for the split? In stable, for instance, we have an .osu file. And we have a Beatmap. And they are 1:1. Should we not just stick with that for simplicity? Am I missing a case where passing "too much" in one class is an issue?

Also I'm still confused. I can't find any write usages of Storyboard.Beatmap or its properties (except one test), even though it's read from. @bdach can you clarify here?

JetBrains Rider-EAP 2024-07-23 at 03 22 39

JetBrains Rider-EAP 2024-07-23 at 03 23 24

Copy link
Member

Choose a reason for hiding this comment

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

Judging from master code, it looks like the Beatmap would need to be set around here to work correctly:

protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also I'm still confused. I can't find any write usages of Storyboard.Beatmap or its properties (except one test), even though it's read from.

Yeah that's just a bug. Good spot, although I did try to preempt this PR by saying it's basically a draft of the general shape with no testing other than checking that unit tests pass.

Judging from master code, it looks like the Beatmap would need to be set around here to work correctly:

Looks close, but the reality is uglier than that; see 1d4d806. tl;dr is that storyboards go through a complete separate decoding path:

protected override Storyboard GetStoryboard()
{
Storyboard storyboard;
if (BeatmapInfo.Path == null)
return new Storyboard();
try
{
string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path);
var beatmapFileStream = GetStream(fileStorePath);
if (beatmapFileStream == null)
{
Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} not found on disk at expected location {fileStorePath})", level: LogLevel.Error);
return new Storyboard();
}
using (var reader = new LineBufferedReader(beatmapFileStream))
{
var decoder = Decoder.GetDecoder<Storyboard>(reader);
Stream storyboardFileStream = null;
string mainStoryboardFilename = getMainStoryboardFilename(BeatmapSetInfo.Metadata);
if (BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.Equals(mainStoryboardFilename, StringComparison.OrdinalIgnoreCase))?.Filename is string
storyboardFilename)
{
string storyboardFileStorePath = BeatmapSetInfo?.GetPathForFile(storyboardFilename);
storyboardFileStream = GetStream(storyboardFileStorePath);
if (storyboardFileStream == null)
Logger.Log($"Storyboard failed to load (file {storyboardFilename} not found on disk at expected location {storyboardFileStorePath})", level: LogLevel.Error);
}
if (storyboardFileStream != null)
{
// Stand-alone storyboard was found, so parse in addition to the beatmap's local storyboard.
using (var secondaryReader = new LineBufferedReader(storyboardFileStream))
storyboard = decoder.Decode(reader, secondaryReader);
}
else
storyboard = decoder.Decode(reader);
}
}
catch (Exception e)
{
Logger.Error(e, "Storyboard failed to load");
storyboard = new Storyboard();
}
storyboard.BeatmapInfo = BeatmapInfo;
return storyboard;
}

and thus all IBeatmap properties that Storyboard needs to read correctly must be populated by LegacyStoryboardDecoder.

I guess this is a relevant point of discussion to the general direction here, and I'm not sure what to make of that revelation. In a way this may be more correct because this separate decoding path reads both the .osb (if present) and the .osu as a fallback, so the resultant behaviour may be more correct here (needs stable cross-check) - but that's also a weak edge case that probably has never been an issue until now

The fact that decoding would need to be split and/or duplicated based on what beatmaps care about and storyboards care about could be seen as bad. Although that is arguably already a problem because that exists:

case "UseSkinSprites":
storyboard.UseSkinSprites = pair.Value == "1";
break;

if (Beatmap.Value.BeatmapInfo.EpilepsyWarning)
if (Beatmap.Value.Beatmap.EpilepsyWarning)
Copy link
Contributor

Choose a reason for hiding this comment

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

Have you tested whether this is blocking on the beatmap load? This looks kinda scary to me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I haven't tested this until now (see mention in OP that this probably needs more testing). I was interested in getting the shape of this right first and foremost, and was only going to go over specifics later. The extent of what is to be moved out is negotiable.

As far as the answer goes, I believe it to be "generally it will not be", because the game-wide async load process will have already completed by now:

osu/osu.Game/OsuGame.cs

Lines 799 to 803 in 57688c2

private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> beatmap)
{
beatmap.OldValue?.CancelAsyncLoad();
beatmap.NewValue?.BeginAsyncLoad();
}

But sure, I can agree with not moving this out. I was in two minds about doing this myself, but am prepared to revert this part. It just seemed to me like it wasn't impossible to move it so I did it, to cover absolutely everything that can be moved without significant changes.

Copy link
Member

Choose a reason for hiding this comment

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

An argument here (just for hypothetical purposes, since i kinda want to nuke this warning completely) is that we may want this to be databased as metadata to show at song select or be searchable. In such a case, we wouldn't want to move it out of BeatmapInfo, correct?

To make it very clear, I'm still fine with it being moved out if it makes more sense (TBD).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we may want this to be databased as metadata to show at song select or be searchable. In such a case, we wouldn't want to move it out of BeatmapInfo, correct?

That is my understanding yes.

@bdach
Copy link
Collaborator Author

bdach commented Jun 18, 2024

Generally I'll just bring up that while this is a draft I'd like to see movement on this or an alternative solution since the underlying issue is currently blocking editor improvements that users want to see too (like bookmarks which don't work because they get dropped by the dumb BeatmapInfo reference juggling logic).

@OliBomby
Copy link
Contributor

I'll try to put in my 2 cents.

If the BeatmapInfo is for realm databased properties, I suggest renaming it to BeatmapDatabasedInfo or BeatmapRealmInfo. That will help convey its purpose and explain which properties you want in that class.

For the properties which should not be databased, I suggest putting them in classes like BeatmapGeneral for general properties and BeatmapEditor for editor properties. This mirrors the sections that exist in the .osu format and follows the precedent set by the BeatmapMetadata and BeatmapDifficulty classes.

@peppy
Copy link
Member

peppy commented Jun 22, 2024

Generally I'll just bring up that while this is a draft I'd like to see movement on this or an alternative solution since the underlying issue is currently blocking editor improvements that users want to see too (like bookmarks which don't work because they get dropped by the dumb BeatmapInfo reference juggling logic).

I have this on my review queue, if you need some assurance that it hasn't been forgotten.

@@ -69,6 +70,43 @@ public interface IBeatmap
/// </summary>
double GetMostCommonBeatLength();

double AudioLeadIn { get; internal set; }
Copy link
Member

Choose a reason for hiding this comment

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

I guess my biggest concern is requiring these in the IBeatmap interface (and the delegation requirement of implementations). Which is probably not a huge concern at all.

I'm neutral on moving stuff into a separate class, will require further discussion to ascertain what we're looking for here, as touched on in other comment thread.

bdach added a commit to bdach/osu that referenced this pull request Aug 20, 2024
In my view this is a nice change, but do note that on its own it does
nothing to fix ppy#29492, because of
`BeatmapInfo` reference management foibles when opening the editor. See
also: ppy#20883 (comment),
ppy#28473.
@peppy peppy self-requested a review September 14, 2024 19:50
Copy link
Member

@peppy peppy left a comment

Choose a reason for hiding this comment

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

Finally got around to doing a second pass on this.

Apart from the remaining comments in this review pass, I'm on board with getting this in (with whatever further testing @bdach wants to do) in the interest of moving forward.

I'm not 100% sure on the final home of a lot of these properties, but the first step is getting them out of BeatmapInfo, which is what this PR achieves.

if (Beatmap.Value.BeatmapInfo.EpilepsyWarning)
if (Beatmap.Value.Beatmap.EpilepsyWarning)
Copy link
Member

Choose a reason for hiding this comment

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

An argument here (just for hypothetical purposes, since i kinda want to nuke this warning completely) is that we may want this to be databased as metadata to show at song select or be searchable. In such a case, we wouldn't want to move it out of BeatmapInfo, correct?

To make it very clear, I'm still fine with it being moved out if it makes more sense (TBD).

osu.Game/Database/RealmAccess.cs Outdated Show resolved Hide resolved
peppy
peppy previously approved these changes Nov 22, 2024
@bdach
Copy link
Collaborator Author

bdach commented Nov 22, 2024

@smoogipoo before I move on this further, did you want to check this over again or is it fine to proceed with these changes as is?

Notably this will 100% conflict with #30826.

@bdach
Copy link
Collaborator Author

bdach commented Nov 26, 2024

I have done my checks and only found 46d1f00, so undrafting.

At this point about I'm not sure how to proceed with the following:

@bdach bdach marked this pull request as ready for review November 26, 2024 10:42
Copy link
Contributor

@smoogipoo smoogipoo left a comment

Choose a reason for hiding this comment

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

@smoogipoo before I move on this further, did you want to check this over again or is it fine to proceed with these changes as is?

I'm not sure what sort of review you want on this (i.e. for merge or for if it's a good direction?), but I've done a shallow pass and am roughly fine with it.

@@ -69,6 +70,43 @@ public interface IBeatmap
/// </summary>
double GetMostCommonBeatLength();

double AudioLeadIn { get; internal set; }
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the internal set; part temporary? Maybe just set; is fine.

I'm not sure what the future is for this interface so I'm not suggesting a change - just asking.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The spirit of that modifier was to have editor flows working (which depend on being able to mutate IBeatmaps) while keeping up a semblance of being immutable to external consumers. To what degree this matters given other things such as Breaks or ControlPointInfo are already doing away with that illusion and just doing { get; set; } is disputable I guess. set; would be fine by me if it's fine by everyone else.

@bdach
Copy link
Collaborator Author

bdach commented Nov 26, 2024

I'm not sure what sort of review you want on this (i.e. for merge or for if it's a good direction?),

Mostly the former probably, just to ensure you're not completely in disagreement with a change to something this core or something of that sort.

@smoogipoo
Copy link
Contributor

Something that's just come to mind... If we generally think of Beatmap as something that's parsed, and a BeatmapInfo as something that's databased, then I'm not sure the correct structure is for the former to contain the latter. Especially if we're going to run into these questions of whether a property should be databased or not.

It may be simpler to consider duplicating the properties in both - that is:

IBeatmapInfo
{
    bool EpilepsyWarning { get; }
}

Beatmap : IBeatmapInfo
{
    bool EpilepsyWarning { get; }
}

BeatmapInfo : IBeatmapInfo
{
    bool EpilepsyWarning { get; }
}

I'm not sure how to approach the above, just laying the seeds for something to think about.

@peppy
Copy link
Member

peppy commented Nov 27, 2024

It may be simpler to consider duplicating the properties in both - that is

makes logical sense on surface level, but dunno how annoying this would be to actually work with. not sure i'd push for that to be attempted as part of this PR or maybe something that comes later on if we figure there are reasons to do that.

@bdach
Copy link
Collaborator Author

bdach commented Nov 27, 2024

not sure i'd push for that to be attempted as part of this PR

I didn't read it as required for this PR fwiw, just a follow up to think about.

@bdach
Copy link
Collaborator Author

bdach commented Dec 2, 2024

Soooo is there anything I should be doing at this point to get this merged?

@peppy peppy merged commit 5b2558c into ppy:master Dec 2, 2024
9 of 10 checks passed
@bdach bdach deleted the beatmap-info-purge branch December 2, 2024 07:24
bdach added a commit to bdach/osu that referenced this pull request Dec 3, 2024
Not sure why I didn't do that in ppy#28473...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
realm deals with local realm database size/XL type:code-quality
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Editor does not read properties ignored on BeatmapInfo correctly
4 participants