-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Add skin mounting flow #30226
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
Add skin mounting flow #30226
Conversation
…lay down framework for abstracting logic
The GetFile method in AddFile has a huge overhead, given we're doing this in a loop. Since we clear the files in the skin, we already know there won't be any existing files, so we can skip all of that 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.
Going to leave some comments here for reviewers on why I did certain things and any concerns I have. Apologies if I shouldn't be doing it this way, please correct me for future reference if so.
foreach (var realmFile in model.Files) | ||
// Detach files from the model to avoid realm contention when copying to the external location. | ||
// This is safe as we are not modifying the model in any way. | ||
foreach (var realmFile in model.Files.Detach()) |
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 ran into an issue where detaching the skin doesn't actually end up detaching the files, resulting in a Realms.Exceptions.RealmException: Realm accessed from incorrect thread.
exception.
I couldn't find a way to fix it apart from this. Fortunately, we only access the realm files here to mount them, so we aren't modifying the files in a way that requires them to be attached.
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.
Two questions:
- Where / when is the "skin" being "detached"?
- Does detach even... work for
SkinInfo
? It sure isn't registered in the automapper detach machinery.
osu/osu.Game/Database/RealmObjectExtensions.cs
Lines 171 to 179 in 74675c8
c.CreateMap<RealmKeyBinding, RealmKeyBinding>(); | |
c.CreateMap<BeatmapMetadata, BeatmapMetadata>(); | |
c.CreateMap<BeatmapUserSettings, BeatmapUserSettings>(); | |
c.CreateMap<BeatmapDifficulty, BeatmapDifficulty>(); | |
c.CreateMap<RulesetInfo, RulesetInfo>(); | |
c.CreateMap<ScoreInfo, ScoreInfo>(); | |
c.CreateMap<RealmUser, RealmUser>(); | |
c.CreateMap<RealmFile, RealmFile>(); | |
c.CreateMap<RealmNamedFileUsage, RealmNamedFileUsage>(); |
Maybe adding SkinInfo
in there just fixes this. I dunno.
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 skin is being detached here
CurrentSkin.Value.SkinInfo
in SkinEditor
returns a Live<SkinInfo>
. My original idea for fixing the issue with "realm being accessed from an incorrect thread" was to detach the Live<SkinInfo>
so that we're only working with a SkinInfo
. Though, simply stopping here (without detaching Files
) doesn't work and results in the thread error. Adding in the .Detach()
to model.Files
fixes it.
I got here via trial and error so I don't know for certain. But my best guess is that it likely has to do with RealmNamedFileUsage
being in the automapper detach machinery in which you specified. Maybe it's only detaching the Files
that ends up fixing the issue, not necessarily the detaching of the Live<SkinInfo>
.
At any rate, I guess detaching the Live<SkinInfo>
serves only to convert from Live<SkinInfo>
to SkinInfo
. But a quick test without detaching the Live<SkinInfo>
seems to work fine. So maybe that's unnecessary?
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.
self-resolved via 2e0e7ff
|
||
// Create a new skin instance to ensure the skin is reloaded | ||
// If there's a better way to reload the skin, this should be replaced with it. | ||
currentSkin.Value = newSkinInfo.CreateInstance(skins); |
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 is where most of my confidence in the code quality of this PR declines. This was the only way I found that would refresh the skin instantly. I looked through at least 15 different classes, and even tests, to figure out a proper way of reloading skins. Was left empty handed and came up with this which works. The reason I feel unconfident about it is because it feels rather hacky.
Forgot tests, sorry about that. Will add them in ASAP |
Seems back to front. Like you should be creating the tests to develop the feature. Adding as an afterthought is mostly pointless for something like this? But let's see what you come up with. |
This reverts commit b7883f1.
I don't like this "simple" approach. Maybe simple for you, but not for a user. And also means you could potentially exit the skin editor or make edits in it while mounted, then overwrite those edits. There should be a big button for the user to click once done external editing. |
I can look into making an overlay equivalent of |
Sorry, misclick |
I've managed to get an overlay component working. It's more or less a port of Here's a video demonstrating what the flow looks like now: osu.mounting.v2.mp4 |
Figured this made sense since we're using purple buttons. It doesn't really seem to change anything visually though
Peppy spoke about using a shortcut and/or hashes to determine if the skin.ini is changed, and if so, then to rename the skin. In my opinion, hashing and doing numerous comparisons is probably less efficient than just syncing the SkinInfo's name during the update. This is an easy solution that does what it needs to.
Since the last review has been addressed, this should be good to go for another round. |
@smallketchup82 resolved conflicts for you, please double check when able |
@bdach Thanks! I didn't notice the merge conflicts. Your resolution looks good 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.
The settings menu can be opened when the skin is externally mounted. Meaning you can change skin while the skin is externally mounted. Doing this results in fundamentally broken behaviour and it should not even be permissible to enter such a state.
Also disallows using the random skin keybind when the external edit overlay is open. SkinEditor should already be disabling it, but I figured we might as well add this in for redundancy
Thanks for catching that! I've fixed it in 7a5e613. Not sure if that's how or where you wanted it to be done, but it works well and disallows opening the settings menu when the external edit overlay is visible. |
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 heavily dislike basically the entirety of 7a5e613 and I think the root cause of all that is this class. Why is this an overlay at all? Why is it not a blocking screen like the editor implementation of this is?
Making the external skin edit thing a blocking screen should fix most of the problems that commit is trying to special-case. With the exception of the random skin hotkey, probably - that one I would argue needs better high-level handling, via the current skin bindable getting disabled or something. And even the commit message says the random thing handling may not be necessary at all...?
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 created this as an overlay because I initially designed it to work around the skin editor. To me, an overlay on top of another overlay seems simpler than hiding the skin editor overlay, pushing the external edit screen, and then showing the skin editor overlay once that’s done, or any other method that involves screen usage. This was a choice I made for conceptual simplicity. My entire idea was to add the overlay version of the external edit screen for the skin editor, and then move the beatmap editor to use the overlay version in a future PR.
To be blunt, if your only issue is 7a5e613, I can revert that. It was an addition to future-proof the external edit overlay (for example, if you mount a skin outside the context of the skin editor). However, as the commit description states, it’s optional and redundant since the skin editor already performs the same task.
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.
To be blunt, if your only issue is 7a5e613, I can revert that
Uh... how does that work? That commit's fixing #30226 (review) is it not? At least the weird explicit "suppress settings being opened if the external edit overlay is visible" logic would need to remain, would it not?
Basically the fact that OsuGame
has to get involved in interactions between these two extremely specific pieces of the game is making the commit really hard to accept for me.
To me, an overlay on top of another overlay seems simpler than hiding the skin editor overlay, pushing the external edit screen, and then showing the skin editor overlay once that’s done, or any other method that involves screen usage
Did you actually try it?
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 idea of pushing a screen (if we're assuming the screen will be pushed to the game's main stack) likely isn't going to work well with gameplay, which is one of the common cases for the skin editor.
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.
Well then I don't know what the solution is, but I stand rather unmoved in opposition to OsuGame
having to coordinate these two specific pieces deep inside the game. It feels not good. OsuGame
is the last resort for stuff like 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.
I haven't looked into the PR code yet, but can agree that it definitely should not live in OsuGame
.
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.
Sorry for the delay in response, I've been busy studying for exams.
At least the weird explicit "suppress settings being opened if the external edit overlay is visible" logic would need to remain, would it not?
Yes, that's my bad. You're right here.
Did you actually try it?
On a local branch yes. It suffers the issue that peppy mentioned (opening skin editor then pressing external edit mid gameplay takes you outside of gameplay). This happens even when paused which takes a good amount of usability away from this feature. I can confirm that it otherwise works.
but I stand rather unmoved in opposition to
OsuGame
having to coordinate these two specific pieces deep inside the game. It feels not good
I have no issue with moving it out of OsuGame
, the question I have is where? Should I move this settings panel side? Or overlay side (i.e. DI settings panel to ExternalEditOverlay
and disable it within ExternalEditOverlay
)? A bit of direction here would be useful.
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.
self-resolved via b82bf22
You can't hide a drawable outside of update thread.
The fact that the stuff "just worked" previously due to one load-bearing detach in a random location is really scary because a lot of this was just not written the way it is supposed to be.
I've done a pass on this to hopefully bring it over the finish line but disclaimer that I am letting tests run on CI and have not attempted to run tests locally. Testing was mostly manual. If nothing funny falls out of CI I'll request a second pass over the code because I changed much. |
/// Write changes to realm asynchronously, guaranteeing order of execution. | ||
/// </summary> | ||
/// <param name="action">The work to run.</param> | ||
public Task<T> WriteAsync<T>(Func<Realm, T> action) |
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 is just an overload copy-pasted from non-generic WriteAsync()
and adjusted to work, to support returning data from WriteAsync()
calls.
osu.Game/OsuGame.cs
Outdated
@@ -1227,7 +1225,7 @@ protected override void LoadComplete() | |||
loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); | |||
loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true); | |||
loadComponentSingleFile(skinEditor = new SkinEditorOverlay(ScreenContainer), overlayContent.Add, true); | |||
loadComponentSingleFile(externalEditOverlay = new ExternalEditOverlay(), overlayContent.Add, true); | |||
loadComponentSingleFile(new ExternalEditOverlay(), overlayContent.Add, true); |
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.
A bit painful to have this at a global level like this.
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 9e524878dc..394917dc62 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -1225,7 +1225,6 @@ protected override void LoadComplete()
loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true);
loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true);
loadComponentSingleFile(skinEditor = new SkinEditorOverlay(ScreenContainer), overlayContent.Add, true);
- loadComponentSingleFile(new ExternalEditOverlay(), overlayContent.Add, true);
loadComponentSingleFile(new LoginOverlay
{
diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs
index 344dcc0d66..8b52c6b43c 100644
--- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs
+++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs
@@ -49,9 +49,15 @@ public partial class SkinEditorOverlay : OverlayContainer, IKeyBindingHandler<Gl
[Resolved]
private IPerformFromScreenRunner? performer { get; set; }
+ [Resolved]
+ private IOverlayManager? overlayManager { get; set; }
+
[Cached]
public readonly EditorClipboard Clipboard = new EditorClipboard();
+ [Cached]
+ private readonly ExternalEditOverlay externalEditOverlay = new ExternalEditOverlay();
+
[Resolved]
private OsuGame game { get; set; } = null!;
@@ -86,6 +92,13 @@ private void load(OsuConfigManager config)
config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins);
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ overlayManager?.RegisterBlockingOverlay(externalEditOverlay);
+ }
+
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
switch (e.Action)
could work? at least in manual testing it looks to still work.
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.
applied with adjustments (disposal of the Disposable
that RegisterBlockingOverlay()
returns) in 2c22158
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.
Fine apart from proposed change.
osu.mounting.v2.mp4
Similarly to the analogous option in beatmap editor, this allows arbitrary external modifications to skins. In particular this means that it is now possible to change gameplay assets without exporting and re-emporting the skin.
This pull request expands on the mounting logic from beatmaps and integrates it with the skin editor, thereby introducing external editing functionality for skins.
I have applied my current knowledge of realm and threading to ensure the code is at the very least presentable. However, I acknowledge that further refinement and evaluation may be necessary. I am open to any feedback and suggestions to improve this implementation.
This is intended to be a stopgap solution until Skin Editor gains support for editing skin files visually.