Identification, cover loading, and Couch Mode fixes on top of 1.7.0 - #37
Merged
Conversation
Sorting by rating was hard to read, because the score that decided the order was not on the card. The metadata line now ends with the score beside the source and the playtime, brighter than the rest of the line so a sorted grid reads down the column. Games without a rating show nothing rather than a placeholder, and the source name gives up the width the score needs so it elides instead of pushing the line off the card.
Automatic identification already existed, but it accepted a match only when the cleaned local title equalled the catalogue title exactly, and the cleaner knew only No-Intro style tags. Measured against a real 1,397 game set, 99% of titles still carried dump text after cleaning, so almost nothing could ever match and every game fell to manual identification. The cleaner now removes a parenthesised group only when every part of it is a region, language, release flag, revision or translation note, so "(NA, Rev 1)" and a multi-clause translation credit both go while "(& Knuckles)" and "(Digital Deluxe Edition)" stay. Square bracket flags go too, and a sorted article moves back to the front, with or without a subtitle after it. Comparison ignores a leading article, which the catalogues disagree about. That same set now cleans completely. Where several catalogue entries share a name and platform, the most rated one wins instead of the match being abandoned. A regional duplicate or a compilation beside the game no longer costs an identification. Retro consoles keep their own box art: Libretro scans and GameTDB covers are authentic, so those systems skip SteamGridDB entirely rather than replacing real artwork with a fan-made portrait. Switch, Wii U and PS4 carry only a square icon that crops badly, so they still ask for one, as do PC games.
A stored match carried only a timestamp, so an answer decided by older rules stood until its rating aged out. A matching fix therefore reached an existing library a month later, one entry at a time, and a shelf marked "Needs identification" kept looking broken in the meantime. That is why Super Mario All-Stars stayed unidentified after the cleaner learned to read its dump tag. Entries now record the version of the rules that decided them, and an entry decided by older rules is stale however recently it was written. Raising that version is how a future matching change reaches everyone, with no backfill command and nothing for anyone to run by hand. A match the user chose is now protected from automatic re-identification. It could previously be replaced during an ordinary rating refresh, and picking the most rated entry among equal titles made that more likely. Every connection in settings reports its own state through one shared rule. RetroAchievements and IGDB showed no status at all, so a working RetroAchievements connection looked like a failed one, and Steam said OPTIONAL where the others said NOT CONNECTED. Steam and RetroAchievements need both their fields before they count as connected, and a provider that answered with an invalid key, a private profile or a rate limit says to check its settings.
A downloaded portrait replaced whatever cover a game already had, including Steam's official 600x900 capsule. That is a downgrade: fan art in place of the publisher's own artwork. The rule is now about the artwork rather than the system. A portrait is fetched only when the game's own art is missing or cannot serve as a cover, so anything already portrait shaped is left alone, retro consoles keep their box scans, and Switch, Wii U and PS4 dumps, which carry a square icon that crops a logo off the card, may still take one. That decision has to read the source's own artwork, not the resolved cover, or a portrait already downloaded would justify keeping itself. A role now exposes what the source provides, before any user choice or download. The queue paused half a second between games on top of a 350 ms pause before each IGDB call, and SteamGridDB's calls were not paced at all. Both providers now pace themselves at the request, so the gap between games only has to yield to the event loop. Together with the games that no longer make three SteamGridDB calls each, a library of mostly Steam and retro games finishes in a fraction of the requests it made before.
A card under Super Nintendo read "RetroArch · Nintendo - SNES / SFC (SNES9X)", where the heading above it already says Super Nintendo. The core name pushed the playtime and the rating to the end of the line, and the rating was the first thing to elide. Inside a console the card now names only its source, so the fields that differ between games have the room.
Switching a source filter rebuilds the grid, and every cover blinked back to a placeholder before returning. Two causes, both measured on a real library over the same thirty second window: 8,478 image loads before this change, 5,604 after. A delegate is created before layout gives it a size, so binding the image source straight through started a decode at the cover's full size while the frame was still zero, then decoded again at the size actually wanted. The second decode missed the cache, so art that was already on screen had to be read from disk again. The image now waits for the card to have a size, which also stops a 600x900 cover being decoded at full size for a thumbnail. The fade then made the reload obvious. A cover already decoded is ready within a frame or two, and fading those in is what made a filter change look like the library was reloading. The fade is kept for art that genuinely has to come from disk or the network. Portraits are also cleaned up on their own now. A portrait downloaded over artwork a game's own source provides is dropped as the library settles, so the Steam capsules that fan art had replaced come back without anyone running anything. Steam is judged by source rather than by the file on disk, because its official capsule downloads on demand and a game whose capsule had not arrived yet would otherwise still look like it needed fan art.
Nine sources scan at startup and each one signals as it loads, and the combined library answered every signal with a full reset. A reset destroys and recreates every card, so covers disappeared and came back several times before the grid settled. Measured over the first thirty seconds on a 1,464 game library: before 26 full resets, 18 of 25 changing nothing at all after 0 full resets, 7 appends, 19 rescans that said nothing The library is now composed first and compared before anything is emitted. A rescan that finds the same games says nothing, since the view is already right and a real change to a game arrives through dataChanged. A source that finished scanning and added games appends them, which keeps every card already on screen along with its artwork and the selection. A full reset is left for what it is meant for: games genuinely moving or disappearing.
Ratings and artwork only arrived if you pressed Update and left the window open long enough. A library of 1,464 games needs about half an hour that way, the queue lived only in memory, and closing the app threw away the progress and started again from the top. Games near the end, such as the eight Nintendo 64 titles, never got their turn. The pass now continues on its own once the library has settled, picks up games added later, and resumes across launches, since a game that already has a current answer is skipped. Pressing Stop keeps it stopped until the next launch or an explicit Update, so it never fights the person using it. It also identifies what you are looking at first. Opening a console reorders what is still pending so those games come first, rather than reordering the work already done. On a real library it identified 38 games in the first minute with nothing pressed.
Scrolling a few rows and back made every card read and decode its artwork from disk again, and show a placeholder while it did. Qt keeps only a couple of megabytes of images no view is currently showing, roughly twenty covers at the size a card asks for, which is less than a single screenful. So the cache was guaranteed to miss for any library worth having, and cacheBuffer could not help: measured against a real library, changing it moved nothing. Covers now go through Omakade's own provider, which decodes straight to the size the card wants and keeps the result. Measured over the first forty-five seconds on a 1,464 game library: 136 covers decoded from disk, 1,071 served from memory, and not one decoded twice. The cache is bounded at 128 MB rather than sized to the library. Holding everything would grow with both the number of games and the cover size setting, so the largest libraries would be the ones to blow the budget, and it would be spent on artwork nobody is near. A few hundred covers is several screens in either direction, which is the distance people scroll and come back from. Past that a thumbnail decodes again in a few milliseconds, which was never the problem; the problem was a limit far below one screen. Also orders identification by system in turn once what is on screen is done, so eight Nintendo 64 games and two Dreamcast games cannot sit behind 1,387 SNES ROMs. That is why those two consoles still had no ratings.
Locks in the startup fix so it cannot quietly come back. A source that rescans and finds the same games must emit nothing, a source that finds more must append them, and only a game genuinely moving or disappearing may reset. Reverting the fix makes this fail with five resets where none belong.
The Couch Mode bar spent equal room on Settings and Desktop, which are rarely touched, while Source and Sort sat inside Browse. Seeing only Steam games with nothing on screen to say Steam was the only source selected read as a broken library rather than a filter. Source and Sort are now bar controls that state their current value, so the filter is always visible and Emulated is one press away. Source opens Browse on its source list, which is still the way to every other filter, so the bar loses a control rather than gaining three. All, Favorites and Recent become one cycling Show control, and the console toggle carries its state in its highlight rather than in its label. Ten controls become eight, and the widest possible labels still fit at 720p. Grid cards gained the rating as a badge in a fixed corner of the artwork, which lets the eye read a column of ratings down the grid the way a line of text under the card cannot at television distance. Cards without a rating have no badge. Playtime joins the source line, which had room for it.
…of filters Narrowing the library leaves the grid holding delegates for the games that just left. Those delegates stay in the scene, stay visible, and carry index -1, so a move down from the organize row could land on a card nobody can see. Focusing it set the grid's current row to -1 and opening it asked for row -1, which is the "Unknown game" page with no title and no source. A delegate that stands for no row now leaves the focus chain, so navigation cannot reach it, and it refuses to be focused, activated or favourited. That is the fix; the rest is depth. The organize row gained the explicit downward target every other control already had, rather than falling back to picking whatever was nearest. Activating from either grid checks the row count as well as the sign, and openGame refuses a row outside the library instead of opening an empty page. Reverting the delegate change fails the new test. Back now walks out of the library the way you walked in: out of a console, then out of a search, then out of a source, and finally out of anything else still narrowing the view, which goes together so back always reaches the whole library rather than asking for a press per filter. Levels that are not active are skipped, so RetroArch inside Nintendo 64 is one press from RetroArch and two from all sources. Both views are covered, since both watch the same library.
Retro systems were exempt from portraits on the grounds that their box scans are authentic. Measured against a real library, that only holds for systems whose boxes were printed portrait: NES 4 portrait, 0 the wrong shape N64 0 portrait, 9 the wrong shape SNES 0 portrait, 111 the wrong shape Dreamcast 0 portrait, 2 the wrong shape So the exemption kept authentic artwork on NES, where it looks right, and kept wide cartons and square cases everywhere else, where they cannot fill a card without being cropped or letterboxed. The rule is now the artwork's shape alone. A box printed portrait is kept whatever system it came from; a box printed wide or square takes a portrait, which reads better on a card even when it is fan made. Artwork arriving later is reconsidered, since the startup sweep applies this same rule and will drop a portrait once authentic artwork turns up. Portraits are stored as JPEG rather than PNG. Covers are photographs, and lossless was costing about 750 KB each: a thousand of them would have filled the artwork cache on its own and then spent its life evicting and re-downloading. On twelve real portraits from this library JPEG at quality 92 is 4.5 times smaller, 161 KB against 726 KB, which takes a full SNES set from 983 MB to 218 MB and well inside the default limit. Portraits already stored as PNG keep working and are still counted by the cache trim.
Just over half the library was matching. Logging what IGDB actually returned for every failure showed the search was rarely the problem: of 34 failures, only 5 came back empty, while 22 came back with the right game and were then thrown away by our own platform check. IGDB catalogues a Japanese release under its own machine. Alcahest, Accele Brid and Ace wo Nerae are all returned by name and all listed on platform 58, the Super Famicom, while we accepted only the Super Nintendo's 19. A system now maps to every platform its games can be listed under, so a shelf of imports is no longer discarded after being found. Two smaller causes went with it. Accents are a spelling difference rather than a different game, so a cartridge labelled Pokemon Stadium 2 now matches the catalogue's Pokémon Stadium 2. A licensed game is often catalogued with its publisher in front, so DuckTales matches Disney's DuckTales. Measured by clearing every failed entry and running the pass again over the same library: of the eighty games re-decided, sixty-seven matched, where every one of them had failed before.
Every control in the identify panel is gated on the service being idle, and the library pass now runs on its own, so the service was almost never idle. Search IGDB, Not This Game and Choose Portrait sat permanently disabled, and the status line reported on whatever game the background happened to be working through. Opening the panel now stands the queue down and holds it, rather than dropping it, so nothing already worked out is repeated. The status line shows the state of the game in front of you instead of the background's. Closing the panel puts the queue back and carries on where it left off.
A game opened from Omakade filled a fraction of the screen and had to be toggled fullscreen by hand. RetroArch here is configured with video_fullscreen = "false", so it opened a normal window, Hyprland tiled it beside Omakade, and nothing asked for anything else. That is not the emulator misbehaving and it is not the compositor: a launcher should say what it wants. Each emulator is now asked explicitly, using the flag it actually documents, checked against the installed binaries rather than assumed: RetroArch --fullscreen Cemu --fullscreen PCSX2 -fullscreen shadPS4 -f true Dolphin -C Dolphin.Display.Fullscreen=True, having no flag of its own Ryujinx is left alone. Its launcher prints no options and its fullscreen state lives in its own configuration, so there is nothing here to pass that I could verify rather than guess at.
Some ROM sets number their files, so a Game Boy Advance cartridge arrives as "1636 - Pokemon Fire Red". Searching a catalogue for that finds nothing. The number is only removed after the title as written has already failed, so a game that genuinely begins with a number, 1080 Snowboarding or 1942 or 007 Racing, is searched for as written and never mangled. A hand search is never affected by a retry left over from the background pass.
The regional platforms, accents, publisher prefixes and catalogue numbers all changed how a match is decided, and none of them raised kMatchVersion. So the 575 entries already marked as needing identification stayed marked, on rules that no longer applied, and would never have been retried. That is the exact failure the version exists to prevent, and a comment asking the next person to remember was not enough. Every identification rule now folds into one fingerprint that a test pins. Changing any of them fails that test until kMatchVersion is raised alongside, so a library cannot be left holding answers the rules would no longer give. Verified by adding a platform and watching the test fail.
Keys are read from the keyring on a worker thread, so the library can settle before they get back. The pass looked once, found no connection, and had nothing to bring it back, so a whole library sat unidentified until something else happened to change. Measured on a real library: twelve minutes after launch, none of the 674 entries awaiting the new rules had been looked at. Credentials arriving now re-arms the pass, for IGDB and for SteamGridDB alike.
Nothing had been identified for hours and the cause was not the matching rules. Four services keep credentials, Steam, RetroAchievements, IGDB and SteamGridDB, and each read its key on its own worker thread as the app started. libsecret builds its GObject types on first use and two threads doing that at the same moment race, so the registration failed with "cannot register existing type" and every schema built afterwards was invalid. Ten of those errors landed on the same millisecond. Every lookup then failed with "the attribute 'service' was not found in the password schema", no credential loaded, and a pass that needs a connection quietly did nothing. Keyring calls are now serialised. Two more faults were hiding behind that one. The pass connected to the library's signals and waited for the next change, but sources that load from their own database have already filled it, so those rows arrived before anything was listening and the wait never ended. It now looks at what is already there. And a pass that declined because a key was still being read had nothing to bring it back, so it now tries again shortly, while wanting credentials outright still waits to be told. A key that fails to load no longer ends the pass either, since ratings do not depend on it. Verified on the real library: it now settles, finds the connections, and queues 1,468 games, where before it queued none.
Two places decided it and they disagreed. Queuing asked needsIdentifying, which knows about the rule version. Processing then judged by the timestamp alone, so every game queued because the rules had changed was dequeued, sent straight to the artwork step, and never identified again. Its recorded rule version never moved, so it queued again next time, forever. The queue drained at four games a second and wrote nothing, which is why the library looked frozen while the app was in fact busy. Both now ask the same question. On the real library, re-identification went from one game to thirty-one in forty seconds.
Prioritising what is on screen only listened for rows coming and going. Opening a console swaps the whole view in one pass and reports that as a layout change, so walking into a console reordered nothing and its games waited behind the whole library. That is the opposite of what the prioritising was for. Layout changes now count, and a game whose details are open goes straight to the front rather than taking its turn.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #36. Everything here was written today in response to testing, and several commits are fixes for regressions introduced by earlier commits in this same branch. That history is worth knowing while reviewing: the end state is tested, but it was not arrived at cleanly.
Rendering and loading
Identification
Four separate faults, found in this order:
Matching itself: regional platforms (a Super Famicom cartridge is platform 58, not 19, which was 22 of 34 failures), accents, publisher prefixes, and ROM catalogue numbers. A fingerprint test now fails if any matching rule changes without raising
kMatchVersion, because I changed the rules once and forgot, leaving 575 entries stuck.Covers
A portrait is fetched only when a game's own artwork is missing or cannot serve as a cover, decided by shape rather than by system. Steam keeps its official capsules, NES keeps its portrait boxes, N64 and SNES take portraits. Portraits store as JPEG: 161 KB against 726 KB on twelve real files, taking a full SNES set from 983 MB to 218 MB.
Couch Mode and navigation
Checks
97 CTest cases pass. Six are new and each was verified to fail without its fix. Remote CI has not run on either branch before now. No aarch64 package has been built for this candidate.