-
-
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
Add skin mounting flow #30226
base: master
Are you sure you want to change the base?
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.
osu.Game/Database/ModelManager.cs
Outdated
/// </summary> | ||
public void AddFile(TModel item, Stream contents, string filename, Realm realm) |
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.
As for why I touched this
From b7883f1
(#30226)
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.
This makes no sense and offers zero safeties.
If the thing has too high overhead, we fix later. Not as part of a huge change. Remove this commit please.
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?
// 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.
skinInfoLive.PerformWrite(skinInfo => | ||
{ | ||
// Not sure if this deletes the files from the storage or just the database. | ||
skinInfo.Files.Clear(); |
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.
As the comment states. Does this also flag the files for deletion? I tried to test it but couldn't conclude whether it does or doesn't. And ModelManager
seems to do roughly the same in its DeleteFile
method. So I'm a bit confused 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.
Don't add comments for this. Ask elsewhere.
And yes, it does. Storage is managed as a separate layer based on file references.
Forgot tests, sorry about that. Will add them in ASAP |
osu.Game/Database/ModelManager.cs
Outdated
/// </summary> | ||
public void AddFile(TModel item, Stream contents, string filename, Realm realm) |
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 makes no sense and offers zero safeties.
If the thing has too high overhead, we fix later. Not as part of a huge change. Remove this commit please.
skinInfoLive.PerformWrite(skinInfo => | ||
{ | ||
// Not sure if this deletes the files from the storage or just the database. | ||
skinInfo.Files.Clear(); |
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.
Don't add comments for this. Ask elsewhere.
And yes, it does. Storage is managed as a separate layer based on file references.
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
@@ -37,7 +37,7 @@ public partial class ExternalEditOverlay : OsuFocusedOverlayContainer | |||
private FillFlowContainer flow = null!; | |||
|
|||
[Cached] | |||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); | |||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); |
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.
It should inherit from the skin editor's colours, no?
skinInfoLive.PerformWrite(skinInfo => | ||
{ | ||
skinInfo.Files.Clear(); | ||
string[] filesInMountedDirectory = Directory.EnumerateFiles(task.Path, "*.*", SearchOption.AllDirectories).Select(f => Path.GetRelativePath(task.Path, f)).ToArray(); | ||
foreach (string file in filesInMountedDirectory) | ||
{ | ||
using var stream = File.OpenRead(Path.Combine(task.Path, file)); | ||
modelManager.AddFile(original, stream, file); | ||
} | ||
}); |
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 think we need to update the skin‘s name and creator if they were modified externally?
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 process should probably match the beatmap reimport one, optimally.
Maybe it can shortcut in the case that ini/json files didn't change, if the full import takes too long (although I'd like to hope it doesn't).
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 was hoping that it would update automatically, since changing the skin.ini and creating a new skin instance for the SkinInfo
sounds like it should update the skin's name in realm, but it probably isn't because we're reusing the old SkinInfo
which would have the old Name
property.
I'll probably end up implementing it as peppy suggested. Before processing the other files, I'll hash the two skin.ini's. If there's a change, I'll investigate what changed and reflect them in realm. If nothing was changed, we're good and we can skip that entire process. Though the full import process doesn't take very long and the shortcut might not be necessary.
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.
Here's a video demonstrating the feature:
osu.mounting.v2.mp4