-
-
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.
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).