-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Implement osu!mania-specific health bar display #30867
base: master
Are you sure you want to change the base?
Conversation
osu.Game/Skinning/Skin.cs
Outdated
// should avoid adding legacy health bar to non-legacy skins (unless explicitly added by user). | ||
if (!legacyHealthBars.Any()) | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This implies that if the skin being migrated here has configured the Playfield
target but never configured the non-ruleset HUD
target, then the health bar will not be added.
It's a hard case to handle here because we don't really have a nice way of telling whether a skin is based on LegacySkin
, nor do I want to use that as a condition anywhere anyway. I think it's fine to let those kind of skins manually add their mania health bar by resetting the target or otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've read this comment five times and I still can't understand what you're trying to say. Can you elaborate? Doesn't the HUD target read fall back to an empty array if it fails? Can't you use that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't the HUD target read fall back to an empty array if it fails?
What does "fails" mean here.
Let me attempt to explain how the migration logic works in a case-by-case scenario:
- A skin with no configuration
HUD
andPlayfield
are all null, so the lookups will get the default components fromLegacySkin
andManiaLegacySkinTransformer
if it's a legacy skin, we're gucci.
- A skin with
HUD
configuration but noPlayfield
configurationHUD
is not null andPlayfield
is null- If the
HUD
configuration has a legacy health bar, remove it and store it tolegacyHealthBars
. - Don't do anything for
Playfield
, it's already null, the health bar will be looked up fromManiaLegacySkinTransformer
if it's a legacy skin.
- A skin with
HUD
configuration andPlayfield
configurationHUD
andPlayfield
are both not null- If the
HUD
configuration has a legacy health bar, remove it and store it tolegacyHealthBars
. - If
legacyHealthBars
is not empty, add a legacy mania health bar toPlayfield
.- The condition used here is good enough to ensure if the configured skin is not legacy then no legacy health bar will be added to mania, by checking whether the skin used to have a legacy health bar or not.
- A skin with no
HUD
configuration but aPlayfield
configurationHUD
is null,Playfield
is not null- Don't do anything for
HUD
, it's already null legacyHealthBars
is empty becauseHUD
is null- Don't do anything for
Playfield
sincelegacyHealthBars
is empty- This is the part touched on in this conversation. This is incorrect because if the skin is legacy then there should be a health bar added to
Playfield
by this migration logic, but I'm intentionally ignoring it for simplicity. It should be a rare case and one that can be fixed by the user.
- This is the part touched on in this conversation. This is incorrect because if the skin is legacy then there should be a health bar added to
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might as well write test cases rather than state them word by word.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test cases written within an introduced testing system TestSceneSkinMigration
.
osu.Game/Skinning/Skin.cs
Outdated
int version = layoutInfos.Values.Min(l => l.Version); | ||
|
||
for (int i = version + 1; i <= SkinLayoutInfo.LATEST_VERSION; i++) | ||
applyMigration(i); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be no taking of .Min()
here, and applyMigration()
should be called in the inner loop over layoutInfos.Values
.
Why? Presume you have two LayoutInfo
s with different versions each. LayoutInfo
X is on version 1, and LayoutInfo
Y on version 2. Now when the latest version is bumped to 3, Y will have the migration from version 1 to 2 called twice. Which is only fine if the migration is idempotent, as in it can determine whether it has already run, and bail if it has.
This is a very not obvious property of a migration system; with these you generally assume that a migration is a one-and-done affair. Thus I object to this type of handling, as an unnecessary complication. If targets are allowed to deviate in version, then migrations should be applied on a per-target basis.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and
applyMigration()
should be called in the inner loop over layoutInfos.Values.
Read OP.
The intention is that we should never have a case where there are two layouts with different migration versions, unless the user decided to mess around with the files. And, hopefully our migration system is good enough to not affect layouts which are already migrated even if the migration was ran again, but that's just a loose
assumption.
That being said, with the concern raised both here and essentially #30867 (comment), I will revert this and compare against the instantiation type instead. I realised at the end of the day if a configuration is null then the default components technically rely on the "instantiation type" (what type of skin class the skin is), so it's more logical to read the instantiation type and go from there instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Migration logic has been reverted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intention is that we should never have a case where there are two layouts with different migration versions, unless the user decided to mess around with the files.
You can't assume that people don't mess with the files, because they already do, and will be more likely to do it more after external skin mounting is added.
osu.Game/Skinning/Skin.cs
Outdated
if (resources == null) | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this check... checking for? It's completely inscrutable to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly ignore doing anything if we don't have the thing to do it, it only matters in tests where skins are instantiated without resources. This check existed before in previous migrations so I'm not sure why it's being asked in here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replaced with a debug assertion, normal tests should never run migrations anyway.
osu.Game/Skinning/Skin.cs
Outdated
if (hudLayout == null || !hudLayout.TryGetDrawableInfo(null, out var globalHUDComponents)) | ||
globalHUDComponents = Array.Empty<SerialisedDrawableInfo>(); | ||
|
||
var legacyHealthBars = globalHUDComponents.Where(h => h.Type.Name == nameof(LegacyHealthDisplay)).ToArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this checking the name of the type if you already have the type itself in hand?
var legacyHealthBars = globalHUDComponents.Where(h => h.Type.Name == nameof(LegacyHealthDisplay)).ToArray(); | |
var legacyHealthBars = globalHUDComponents.Where(h => h.Type == typeof(LegacyHealthDisplay)).ToArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I don't know how Type
equality works, and I hope that if it does other stupid things like assembly version or whatever then it won't break this logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will assume it only checks the name. I tried stepping through the equality implementation and ended up with both sides being equal by reference, I don't know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you need clarification here, given that CreateInstance
works with however we're serialising types, this case also has to work. We do seem to be serialising versioning out, but I guess it works fine.
osu.Game/Skinning/Skin.cs
Outdated
// should avoid adding legacy health bar to non-legacy skins (unless explicitly added by user). | ||
if (!legacyHealthBars.Any()) | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've read this comment five times and I still can't understand what you're trying to say. Can you elaborate? Doesn't the HUD target read fall back to an empty array if it fails? Can't you use that?
This reverts commit 76f79ce.
… once" This reverts commit f1b5686.
This is different from `SkinDeserialisationTest` in that layouts can be written programmatically with as much ease, allowing to test migration logic with different scenarios without running the game and exporting skins and attaching them to tests.
I'll update the deserialisation test once the PR is considered ready to merge. |
}); | ||
}); | ||
|
||
// One may argue that if a LegacyHealthDisplay exists in a non-legacy skin, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer these comments are in the actual migration rather than the test.
ie. for this one it should be at https://github.com/ppy/osu/pull/30867/files#diff-e662eed704866ec7b4c4ec234173d642e8628330ab7b5c4480e7f69070ca0e2dR345.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've moved the one highlighted above, but there's also this:
osu/osu.Game.Tests/Skins/TestSceneSkinMigration.cs
Lines 247 to 248 in 5ab9074
// In this case, we must add a health display to the Playfield target, | |
// otherwise on mania the user will not see a health display anymore. |
And there's no direct place to move it to the migration code, and I intended it to be in the test specifically to explain why the test case is written as such.
@frenzibyte Probably safe to update the failing migration tests now, I'm okay with how this is looking. |
I thought the skin deserialisation test failure was happening due to missing types or something, but turns out it was because of addressing #30867 (comment). It appears I don't think this is correct behaviour, it feels quite trippy for a test to load an osk file and not have it migrated. I've rewritten the test to import the skin to a |
AddAssert("hud count = 13", | ||
() => skin.LayoutInfos[GlobalSkinnableContainers.MainHUDComponents].AllDrawables.ToArray(), | ||
() => Has.Length.EqualTo(13)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some may notice the count assertions on some of these tests have changed. This is because previously the test did not count the combo counter migration, which moves the combo counter from the global layout to each ruleset's layout, therefore increasing the AllDrawables
count by 3.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Source looks fine (aside from c12cb25 that I pushed in because the method had a bad name). However, for whatever reason the legacy default skin does not have a health bar in osu! and osu!catch specifically (taiko and mania somehow work):
2024-12-02.10-22-53.mp4
Not sure why this passes tests, but I'm not particularly wanting to investigate this, so leaving it to you to diagnose.
Default ruleset-based implementations of osu! and osu!catch rulesets override the health display set up by LegacySkin... I'll need to investigate how to add appropriate tests for presence of default components. |
Fortunately enough the fix was simple. Added tests to ensure this doesn't slip by, by being critical of every default skinnable component displayed per skin. That area was not being appropriately tested by any of our existing tests so I rolled out a test scene for it, counting a total of 2 new test scenes and a rewrite of an existing one :^). |
// Notably, there may be existing ruleset skin transformers that override the default layout returned here. | ||
// Any added component in here should be considered in the existing transformers if applicable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added comment because I tripped on this one and caused a total disaster (as seen above).
I'm not sure how much of a big deal to make of this but this diff can seemingly cause health bars to be doubled up on skins that already have a health bar in the playfield target. Check out what happens when you import https://osu.ppy.sh/community/forums/topics/1923469?n=1 on master and switch to this branch. For me this happens:
Seems like this'll be the migration doing undesirable things... |
I think it's fine to exclude from migration skins which already have a health bar on the playfield target. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems probably ok although I will complain that this has ballooned to nearly over 1k lines of diff
@peppy not sure if you want to cross-check this or not
I was going to get this in but the deserialisation test conflict is a bit of a pain to fix up. |
Added a migration step to move
LegacyHealthDisplay
from global target to per-ruleset target, as well as adding the mania-specificLegacyHealthDisplay
specification for skins with existing user configuration. This required a minor refactor over the skin migration logic in general, to allow mutating multiple skin layouts in one step (especially when data from the first layout is required to act on the second layout).