Darktable ma własną bibliotekę widżetów GUI, dla suwaków i list rozwijanych (znanych też jako menu rozwijane lub pola wyboru), nazwaną Bauhaus (w kodzie źródłowym znajduje się ona w src/bauhaus/bauhaus.c). Choć jako backendu używają Gtk, obiekty Bauhaus są niestandardowe. A jak wiele rzeczy w Darktable, niestandardowe znaczy zepsute.

W 2022 roku ‍zauważyłem pasożytnicze przerysowania i opóźnienia  podczas ich używania, prowadzące do frustrującego doświadczenia użytkownika: przerysowanie widżetu zdawało się czekać na zakończenie ponownych obliczeń potoku (pipeline), co oznaczało, że użytkownicy nie byli do końca pewni, czy zmiana ich wartości została zapisana, co mogło skłaniać ich do ponawiania próby, uruchamiając kolejny cykl kosztownych ponownych obliczeń i skutecznie zawieszając ich komputer na kilka bardzo frustrujących minut bezużytecznych, pośrednich ponownych obliczeń potoku.

Tak się składa, że narzędzia do statycznej analizy kodu stwierdzają, iż biblioteka Bauhaus jest również 4. najbardziej złożonym plikiem kodu źródłowego  w całym oprogramowaniu Darktable pod względem złożoności cyklomatycznej, z wynikiem 735 i długiem technicznym szacowanym na 1 dzień i 7 godzin. Jeśli nie jesteś programistą — złożoność cyklomatyczna mierzy liczbę różnych ścieżek, jakie może obrać kod, a wysokie wartości sprawiają, że jest on nie tylko trudniejszy do zrozumienia (a więc do debugowania), ale też bardziej podatny na przypadki brzegowe, błędy i dziwne, zależne od kontekstu problemy. Złożoność cyklomatyczna  jest pośrednią miarą prawdopodobieństwa, że ten kod wybuchnie ci w twarz, kiedy najmniej się tego spodziewasz — coś, co warto wziąć pod uwagę, gdy najbardziej płodnymi „programistami" w twoim zespole są nauczycielka szkoły podstawowej, pediatra i konsultant bankowy.

Szczególnie frustrujące jest to, że już wcześniej pracowałem nad uproszczeniem tego pliku, w 2019 roku. 3 lata później było tak, jakbym nic nie zrobił, za sprawą pełzającej funkcjonalności  oraz obsługi MIDI/gamepada. Kiedy pojawiły się pasożytnicze przerysowania, zostałem z niezrozumiałym kodem spaghetti , którego zwyczajnie nie byłem w stanie naprawić. Pierwsza próba poprawienia błędnych rzeczy zaprowadziła w ślepy zaułek, w sierpniu 2022, i zniechęciła mnie. Próba obejścia tego problemu nie miała szans wystarczyć.

Musiałem więc napisać go niemal całkowicie od nowa, co nie było żadną przyjemnością i zajęło mi szaloną liczbę godzin (przestałem liczyć na 3 tygodniach, w pełnym wymiarze, a to była druga próba, w sierpniu-listopadzie 2023).

Czy wiedziałeś, że gdy lista rozwijana ma już fokus (czy to dlatego, że na nią kliknąłeś, czy nadałeś jej fokus skrótem klawiszowym), mogłeś zacząć wpisywać pierwsze litery etykiety, którą chciałeś wybrać, a ona automatycznie wybierała najbliższy element z listy? Ja też nie wiedziałem, zanim nie podjąłem się tego zadania, bo nie jest to nigdzie udokumentowane. Podobnie jak wiele ukrytych funkcji, które tam dodano, aby spełnić dewiacyjne i marginalne przypadki użycia, ale komplikujących strukturę kodu dla wszystkich. (Uwaga, spoiler: tę konkretną funkcję zachowałem, ale inne usunąłem).

‍Lista usprawnień

  • Współrzędne kursora (w wyskakujących oknach) są obliczane tylko w jednym miejscu, a potem przechowywane. To oszczędza mnóstwo pośrednich ponownych obliczeń, z których niektóre były niespójne, ponieważ kod był kopiowany i powielany zamiast używać getterów i setterów . Teraz przesunięcia i zmiany współrzędnych są obsługiwane przez ujednolicone gettery i settery, co oznacza, że każda przyszła zmiana będzie musiała nastąpić tylko w jednym miejscu, a cały kod z tego korzysta.
  • Nowe wartości (z suwaków i list rozwijanych) nie są już wysyłane do potoku pikseli podczas przewijania lub przeciągania, lecz dopiero na końcu. Opiera się to na wyuczonym maszynowo limicie czasu rejestrującym średni czas potrzebny do obliczenia całego potoku. GUI odczeka od 20 ms do 2,5 s, zanim wyśle zmiany do potoku, unikając ponownych obliczeń przy każdym kroku przewijania czy przeciągania, które oznaczają bezużyteczne i zbędne, a zarazem kosztowne obliczenia, które tylko sprawiają, że oprogramowanie się zacina. Podobnie są one teraz wysyłane dopiero przy zdarzeniu zwolnienia przycisku, zamiast przy zdarzeniach naciśnięcia i zwolnienia przycisku (zważywszy, że typowe kliknięcie myszą wysyła oba zdarzenia). Powinno to zaoszczędzić całkiem sporo zbędnych ponownych obliczeń potoku.
  • Widżety są przerysowywane natychmiast przy zdarzeniach użytkownika, zanim nowe wartości zostaną wysłane do potoku pikseli. Zapewnia to natychmiastową informację zwrotną dla użytkownika, nawet jeśli rzeczywisty wynik pikselowy może pojawić się później, i ogranicza frustrację na wolnych komputerach.
  • Nie wysyłaj zdarzeń zmiany wartości, jeśli widżety otrzymały interakcję użytkownika, ale ich wartość faktycznie się nie zmieniła.
  • Przechwytuj kliknięcia na strzałce (prawej strzałce) list rozwijanych. Wcześniej trzeba było kliknąć na etykietę, aby rozwinąć menu rozwijane listy, co było wyjątkowo frustrujące, jeśli przychodziłeś z oprogramowania z porządnym GUI. Sama strzałka nie reagowała na kliknięcia.
  • Nie przewijaj menu rozwijanych list. Ta funkcja zaczęła się jak fajny projekt: wyrównanie aktualnie wybranego elementu z etykietą. Problem w tym, że pozycjonowanie wyskakującego menu rozwijanego jest ostatecznie obsługiwane przez środowisko graficzne — możemy jedynie grzecznie poprosić, aby zrobiło to, czego chcemy, ale nie ma gwarancji, że nasze życzenia zostaną spełnione. Ponadto możliwe jest sprawienie, że menu wyskoczy poza obszar widoczny, nic temu nie zapobiega. W sumie jest to kruche i losowe, lepiej trzymać się domyślnego pozycjonowania okien.

Wycofane funkcje

Możliwość przypisywania skrótów klawiszowych do suwaków i list rozwijanych została na razie usunięta. Suwaki i listy rozwijane są i tak powiązane ze strzałkami i przewijaniem myszy gdy tylko otrzymają fokus1, a wciąż można przypisać przechwytywanie fokusu do skrótu klawiszowego. Obecna logika polega więc na tym, aby zażądać fokusu skrótem klawiszowym, a następnie edytować wartość za pomocą strzałek. Żądania fokusu automatycznie sprawiają też, że widżet staje się widoczny w GUI, przewijając panel boczny, jeśli to konieczne.

Tak czy inaczej, obecny system skrótów będzie musiał zostać całkowicie zastąpiony natywnymi akceleratorami Gtk, które są już używane w menu globalnym (i były używane w Darktable przed 2021 rokiem). Obecnie mamy oba systemy, z których jeden jest potworem złożoności i prawdopodobnie odpowiada za spowolnienia. KISS.

Zastrzeżenia

Lista rozwijana „format" w module eksportu nie inicjalizuje poprawnie swojej wartości. Jest to niestandardowy widżet Bauhaus, który wymaga szczególnej uwagi. Na razie będziesz musiał odświeżyć opcję zapisu lub ponownie wczytać ustawienie predefiniowane.

Kiedy zdzierasz łuszczącą się starą farbę, która trzymała zardzewiałe ściany, ryzykujesz wyrządzenie szkód ubocznych. Zachowaj ostrożność z Anselem, używając AppImage i Win EXE oznaczonych jako Ansel-57ed58d z dzisiejszej nocy, i zgłaszaj wszystko, co dziwne.

Pliki do pobrania

Szczegóły dla zaawansowanych

Obecne zmiany zredukowały:

  • złożoność cyklomatyczną z 735 do 494
  • złożoność kognitywną z 796 do 432
  • dług techniczny z 1 dnia i 7 godzin do 4 godzin i 50 min.

Więcej szczegółów 

Jeśli nie jesteś programistą, te metryki oznaczają po prostu, że kod będzie łatwiejszy i mniej czasochłonny w utrzymaniu w przyszłości, a prawdopodobnie mniej podatny na błędy.


Translated from English by : Claude. In case of conflict, inconsistency or error, the English version shall prevail.

  1. In GUI programming, a widget has the focus when it is the one recording keyboard events. Text entries are the most obvious example, but Darktable hacked that concept to generalize to pretty much every widget. ↩︎