Memoiren eines Typen, der zu viel Zeit damit verbracht hat, den Mist anderer Leute wegzuräumen und für ihre schlechten Entscheidungen zu bezahlen, Folge #zu viele.
Verlauf und Stile per Kopieren/Einfügen zu übertragen, sind Kernfunktionen in Ansel und diejenige, die ihm den Titel „Workflow-App" verdient (oder auch nicht). Aber es ist auch eine der intern am schwersten korrekt umzusetzenden. Nutzer sehen eine Liste von Bearbeitungen, doch unter der Haube hängen diese Bearbeitungen von der Reihenfolge der Pipeline, den Modulinstanzen und den Masken ab. Wenn zwei Bilder unterschiedliche Pipeline-Topologien haben, kann naives Kopieren von Bearbeitungen zu Inkonsistenzen führen.
Dieses Update macht die Verlaufszusammenführung robust, konsistent und transparent, nach mühseliger Arbeit des Aufräumens und Vereinfachens von Code. Es führt außerdem eine klare Fehlerbehandlung ein, wenn eine perfekte Zusammenführung mathematisch unmöglich ist.
Eine kurze Geschichte des schlechten Designs
Bis Anfang 2019 war Darktable um eine feste Pipeline herum konzipiert: Module hatten eine Reihenfolge, die zur Kompilierzeit durch ein Python-Skript festgelegt wurde, erstellt von Darktables Gründer Johannes Hanika im Jahr 2011 . Das Einzige, worauf man sich bei Jo verlassen kann, ist, dass seine Mathematik stimmt, und so macht dieses Skript genau das, was unter solchen Umständen getan werden sollte:
- Programmierer Module deklarieren lassen,
- sie definieren lassen, welche Module vor jedes Modul gehören sollen, auf eine bequeme und faule Weise, die es erlaubt, einfach zu sagen „A sollte vor C stehen", „B sollte vor A stehen", „D sollte vor A stehen" usw.,
- das in einen gerichteten Graphen verwandeln, der einfach das mathematische Objekt ist, das all diese Beschränkungen zusammenklebt,
- den gerichteten Graphen mit einem der topologischen Sortier -Algorithmen lösen, die bis in die 1960er Jahre zurückreichen,
- die Indexnummer jedes Moduls in die Pipeline schreiben, fertig.
Leider bedeutete diese feste Pipeline, dass eine spätere Änderung der relativen Reihenfolge der Module ältere Verläufe kaputt machen würde. Was ärgerlich war, weil einige Designfehler bei der Position der Anzeigetransformation und des Basiskurven-Moduls gemacht wurden, die früh in der Pipeline geschahen und die Farben in HDR-Situationen verrücktspielen ließen. Um das zu beheben, musste ich das Filmic-Modul als eigenes Modul erstellen, um es an das Ende der Pipeline setzen zu können. Und alle anderen anzeigebezogenen Module hätten in der Codebasis dupliziert werden müssen, um sie dort einfügen zu können, wo nötig, ohne ältere Bearbeitungen (einschließlich meiner eigenen) kaputt zu machen. Es versteht sich von selbst, dass die feste Pipeline ihre Zeit gehabt hatte, und ich war ein Befürworter dieser Änderung.
Mit einer festen Pipeline war das Zusammenführen von Verläufen, also das Kopieren/Einfügen von Verläufen zwischen Bildern oder das Anwenden von Stilen auf Bilder (was letztlich dasselbe ist), einfach: Es ging nur darum, Modulparameter und Masken 1:1 zu ersetzen. Sogar mit Mehrfachinstanz-Modulen, die um 2014 eingeführt worden waren, war es nicht so schlimm, weil alle Instanzen gezwungen waren, aufeinanderfolgend zu sein und relativ zueinander nummeriert. Nachdem also die fehlenden Instanzen an einer vorhersehbaren und invarianten Position erstellt worden waren (in sequenzieller Reihenfolge nach der Basisinstanz), ging es immer noch darum, Modulparameter und Masken 1:1 zu ersetzen.
Aber die dringend benötigte Funktion zur Neuordnung der Pipeline, zur Laufzeit und durch Nutzer, die mit Darktable 3.0 kam, wurde auf die denkbar schlechteste Weise umgesetzt. Der Entwickler, der sie implementierte, handhabte die Indizierung mit Arrays von Fließkomma-Prioritäten, was nicht die richtige Datenstruktur für das Problem ist. Hinzu kommt, dass der Code für die Verlaufsverwaltung vor der Erweiterung nicht refaktoriert und vereinfacht, sondern mit minimalen Änderungen an Ort und Stelle hineingehackt wurde, was ihn wirklich komplex und undurchschaubar machte. Pascal Obry, der die Änderung 2019 akzeptiert hatte, musste 2020 das gesamte Backend zur Pipeline-Neuordnung neu schreiben , unter Verwendung der passenden Datenstruktur für die anstehende Aufgabe (verkettete Listen ), weil die vorherige brüchig und nicht wartbar war.
Aber das Zusammenführen von Pipelines, die unterschiedliche Anzahlen von Modulen haben können, angeordnet an unvorhersehbaren und nicht-invarianten Stellen, kann auf keine andere Weise erfolgen als durch topologische Sortierung zur Laufzeit, weil es nicht mehr bloß ein Problem von Verläufen (also Momentaufnahmen von Modulparametern) ist, sondern ein zweifaches Problem, das Verläufe, aber auch die Pipeline-Topologie umfasst. Mit anderen Worten: Wir müssen lösen, wo Modulinstanzen einzufügen sind, die in der Quell-Pipeline existieren, aber nicht in der Ziel-Pipeline. Und dennoch wurde das Python-Skript, das dies zur Kompilierzeit erledigte, 2019 gelöscht, und die Funktion wurde nie nach C portiert.
Die Art und Weise also, wie Darktable bis heute das Zusammenführen von Pipelines handhabt, geschieht über Heuristiken, die aus dem Paradigma der festen Pipeline zusammengehackt und im Allgemeinen kaputt sind, außer in den schönen Fällen, die den Bedingungen der Pipelines von 2018 und früher entsprechen:
- Wenn Ihre Quell- und Ziel-Pipelines dieselbe Anzahl von Modulen haben, gleich angeordnet, läuft alles gut.
- Wenn Ihre Quell-Pipeline zusätzliche Modulinstanzen im Vergleich zum Ziel hat, diese Instanzen aber alle unmittelbar nach der Basisinstanz sitzen, läuft immer noch alles gut,
- aber… wenn Sie zusätzliche Instanzen haben, ob in der Quell- oder der Ziel-Pipeline, und sie in der Pipeline verschoben wurden, dann ist das Verhalten unspezifiziert, unvorhersehbar, und ich habe seit mehr als 5 Jahren den Verlauf kopiert und eingefügt und dabei die Luft angehalten,
- außerdem gibt es eine Liste von „Fence"-Modulen, die eine bestimmte relative Reihenfolge benötigen (wie z. B. Farbkalibrierung, die unbedingt nach Eingabefarbprofil stehen muss), aber es gibt keine Möglichkeit, sie zu erzwingen, keine Möglichkeit, sie zu korrigieren, wenn schlechte Reihenfolgen auftreten, nur stille Fehlermeldungen in der Konsole.
Kombinieren Sie das mit Rastermasken, wo das Modul, das eine Rastermaske wiederverwendet, unbedingt später in der Pipe sitzen muss als das Modul, das sie erzeugt, und wo Sie übrigens den Erzeuger zusammen mit dem Verbraucher kopieren möchten, oder zumindest eine Warnung erhalten möchten, wenn Sie es nicht tun… und Sie haben das Rezept für den Wahnsinn.
In der Praxis müssen Sie jedes Bild öffnen und den Modulstapel in der Dunkelkammer überprüfen, was quälend langsam ist.
Weil Darktable durch schlechtes Prioritätenmanagement definiert ist, hat eine Menge Kosmetik viel Arbeit erhalten: Es lässt Sie Ihre Bilder mit PlayStation-Gamepads bearbeiten, es steht kurz davor, Deep-Learning-Maskierungsfunktionen zu bekommen, aber es schafft es immer noch, die grundlegenden Kernfunktionen zu vermasseln, ohne Verbesserung in 6 Jahren trotz der scheinbaren Aktivität im Projekt.
Das Problem (TL;DR)
Zuvor versuchte das Einfügen des Verlaufs, die Module nacheinander zusammenzuführen, während es voranging. Das funktionierte in einfachen Fällen, wurde aber unzuverlässig, wenn:
- das Quellbild eine andere Pipeline-Reihenfolge hatte,
- es mehrere Instanzen desselben Moduls gab,
- oder Masken und Überblenden im Spiel waren.
Weil die Zusammenführung nicht das gesamte Reihenfolgeproblem löste, konnte das Ergebnis von einer Menge Dinge abhängen. In manchen Fällen konnte die endgültige Pipeline am Ende inkonsistent mit dem Verlaufsstapel sein.
Die Lösung
Ich habe einen topologischen Sortieralgorithmus in C implementiert. Oder genauer gesagt, ChatGPT hat es getan und ich habe es überprüft (mehr dazu weiter unten). 304 Codezeilen, was für C sehr wenig ist.
Wir behandeln die Pipeline-Reihenfolge nun als Menge von Beschränkungen, die global gelöst werden müssen, nicht Modul für Modul. In der Praxis bedeutet das:
- Wir lösen zuerst die Pipeline-Topologie und zuletzt den Entwicklungsverlauf (Modulparameter). Zwei klare Schritte, die eine Fehlerbehandlung möglich machen.
- Die Zusammenführung berechnet eine einzige, gültige Pipeline-Reihenfolge, die sowohl Quelle als auch Ziel erfüllt, wann immer möglich, egal wie viele Modulinstanzen und wie sie angeordnet sind.
- Wenn Beschränkungen inkompatibel sind, kann Ansel den Konflikt erklären und fragen, welche Reihenfolge beibehalten werden soll.
- Wenn ein Konflikt unlösbar ist, meldet Ansel ihn klar, anstatt eine kaputte oder instabile Pipeline zu erzeugen, und gibt Ihnen Optionen, ihn zu beheben.
- Beschränkungen werden auch auf Module gelegt, die Rastermasken verwenden und verbrauchen, und es wird eine Warnung ausgegeben, wenn ein Rastermasken-Nutzer ohne den Erzeuger kopiert wird.
- Fest codierte Beschränkungen werden auf Module gelegt, die einander aus technischen Gründen voraussetzen (Lichterrekonstruktion vor Entrasterung, Eingabefarbprofil vor Farbkalibrierung usw.). Diese Beschränkungen werden genauso wie andere behandelt und gemeinsam gelöst (kein Sonderfall).
Was den zweiten Teil betrifft, den eigentlichen Verlauf, muss hier eine Sache klargestellt werden. Der Verlauf von Darktable und Ansel gleicht sehr einer Rückgängig/Wiederholen-Liste von Momentaufnahmen der Modulparameter: Jedes Verlaufselement ist mit einem Modul verknüpft und stellt dessen internen Zustand von Parametern und Masken dar. Beim Laden des Verlaufs in die Pipeline-Knoten (Knoten sind die Pixelfilter, die an die „Module" angehängt sind, die Sie in der Benutzeroberfläche sehen) lesen wir den Verlauf von unten nach oben und kopieren jedes Element/jede Momentaufnahme in die Module, was bedeutet, dass spätere Momentaufnahmen frühere immer überschreiben.
Das heißt, der Verlauf ist nach Zeitpunkt der Nutzermodifikation geordnet (nochmals… denken Sie an eine Rückgängig/Wiederholen-Liste von Momentaufnahmen), nicht nach der Reihenfolge der Pipeline-Knoten. Aber wo es verwirrend wird, ist, dass Verlaufselemente auch die Pipeline-Position eines Moduls speichern, was bedeutet, dass eine Modul-Neuordnung Verlaufselemente hinterlässt. Das verwirrte die Darktable-Entwickler und noch viel mehr die Nutzer. Also lassen Sie uns beides konzeptionell trennen, sowohl in unseren Köpfen als auch in der Software.
Die Topologie (Reihenfolge der Pipeline-Knoten) ist unabhängig vom Verlauf. Anders als Darktable, das alles über Verlaufselemente verwaltet (einschließlich der Pipeline-Reihenfolge), was sowohl für das Verständnis als auch für die Codekomplexität aktiv schädlich ist (die gehen ohnehin zusammen), lösen wir die Pipeline als eigenständige Sammlung von Knoten (Modulen) und ordnen dann die Knoten wieder ihrem letzten Verlaufsstadium zu. Es hat mich ein paar Jahre gekostet, all die überwältigende Verschleierung in dieser Software zu durchschauen, vergraben in kopiertem Code, und das Licht zu sehen: Einmal abstrahiert, ist das Problem ziemlich einfach.
Sobald also die Topologie gelöst ist, bleiben uns 3 Verlaufszusammenführungsmodi:
- replace: Der Quellverlauf ersetzt das gesamte Ziel, obligatorische Module wie Entrasterung für RAW-Bilder könnten dennoch oben hinzugefügt werden (sodass das Kopieren/Einfügen zwischen JPEG und RAW sicher ist). Dieser Modus führt zu keiner topologischen Sortierung, es ist eine direkte Kopie von Verlauf und Pipeline-Reihenfolge.
- append: Der Quellverlauf kommt oben auf das Ziel, sodass Verlaufselemente, die auf dieselben Module in Quelle und Ziel abzielen, von der Quelle überschrieben werden,
- appstart: Der Quellverlauf kommt unten unter das Ziel, sodass Verlaufselemente, die auf dieselben Module in Quelle und Ziel abzielen, vom Ziel überschrieben werden.
Die durch topologische Sortierung gelöste Pipeline-Reihenfolge wird in den letzten Verlaufselementen aktualisiert, sowohl im append- als auch im appstart-Modus, was bedeutet, dass ein Zurückgehen im Verlauf auch die topologische Sortierung rückgängig macht. Verläufe werden beim Zusammenführen absichtlich nicht komprimiert, sodass Nutzer die Möglichkeit behalten, die Zusammenführung sowohl über die Rückgängig/Wiederholen-Funktionen als auch durch Zurückgehen in der Verlaufs-Werkzeugbox in der Dunkelkammer vor dem Zeitpunkt der Zusammenführung rückgängig zu machen.
Kurz gesagt: Das Einfügen des Verlaufs ist jetzt deterministisch, sicher, nicht-destruktiv, sogar bei komplexen Bearbeitungen.
Verwendung
Es wird einen Quellverlauf geben (den Sie kopieren) und einen Zielverlauf (in den Sie einfügen). Dasselbe wird bei Stilen angewendet, wenn sie neu implementiert werden; der Quellverlauf wird durch den Stil statt durch ein anderes Bild definiert, und der Rest wird gleich sein.
Im globalen Menü Bearbeiten → Verlaufs-Einfügemodus können Sie zwischen append, appstart oder replace wählen. Die Einstellung ist global über die gesamte Anwendung. Das bestimmt, welcher Verlauf (Quelle oder Ziel) Vorrang hat, indem er gemeinsame Module überschreibt.
In Bearbeiten → Knoten-Einfügemodus können Sie Modulreihenfolge kopieren ein-/ausschalten. Wenn aus, wird die Pipeline-Reihenfolge des Ziels unverändert beibehalten. Wenn ein, versuchen wir unser Bestes, die Quell-Pipeline-Reihenfolge in das Ziel zu importieren.
Wie zuvor haben Sie im Menü Bearbeiten die Optionen, alles zu kopieren/einzufügen oder nur ausgewählte Module (über das modale Fenster). Globale Tastenkürzel sind verfügbar und nutzerdefinierbar. Beachten Sie, dass das Kopieren/Einfügen von Verläufen in der Dunkelkammeransicht ausdrücklich verboten ist, sogar aus dem Filmstreifen, weil es mehrdeutig ist zu bestimmen, ob Sie zwischen Vorschaubildern, von Vorschaubild zu Hauptbild oder umgekehrt kopieren möchten. Auf dem Leuchttisch wählen Sie die Quelle, kopieren, wählen das Ziel, fügen ein, und alles ist klar.
Nun gibt es eine wichtige Annahme, die man im Kopf haben muss: Module, die denselben Instanznamen haben (standardmäßig die Instanznummer oder ein nutzerdefinierter Name), werden als dieselbe Entität in Ziel- und Quellverlauf betrachtet. Also wird jedes Belichtung (Himmel) mit jedem anderen Belichtung (Himmel)-Modul zusammengeführt (Groß-/Kleinschreibung beachtend), und es sollte nur eine Belichtung (Himmel)-Instanz in Ziel- und Quellverlauf geben. Zuvor verwendete der Code Instanznummern, was brüchiger ist, weil sie von der Software vorgegeben und in der Reihenfolge der Erstellung inkrementiert werden, was für Nutzer keine Bedeutung hat.
Benutzeroberfläche und Fehlerbehandlung
Das Schöne an der neuen Lösung ist, dass Sie die Dunkelkammer nicht öffnen müssen, um das Chaos zu sehen, das Sie durch das Kopieren/Einfügen von Müll angerichtet haben; Sie können es überprüfen, bevor Ihren Bearbeitungen Schaden zugefügt wird, auf dem Leuchttisch. Auch wenn der Solver keine Lösung findet, was bei inkompatiblen Beschränkungen (A sollte von B vorausgegangen werden, aber B sollte von A vorausgegangen werden) oder Zyklen (mehr dazu unten) passiert, ist er in der Lage, zu sagen, was fehlschlägt, es zu melden und entweder Nutzereingaben zur Behebung anzufordern oder auf den vernünftigsten Pfad zurückzufallen. Lassen Sie es mich Ihnen zeigen:
Triviale Zyklen
Belichtung 1 steht im Quellverlauf vor Belichtung, aber im Zielverlauf danach. Die Menge der Beschränkungen endet mit Belichtung → Belichtung 1 → Belichtung, was nicht durchführbar ist. Hier ist, was in Ansel passiert:

Diese trivialen Zyklen, die unmittelbare Nachbarn betreffen, werden vor dem Lösen abgefangen, sodass sie den Kontrollfluss nicht unterbrechen.
Nicht-triviale Zyklen
Diese nicht-trivialen Zyklen betreffen mehrere Module und können nicht erkannt werden, bevor versucht wird, den gerichteten Graphen zu lösen. Wenn das passiert:

In diesem Fall gibt es nichts zu tun: Wir werden automatisch erneut versuchen, indem wir die Zielreihenfolge verwenden, da dies typischerweise auftritt, wenn versucht wird, die Quellreihenfolge in das Ziel zusammenzuführen.
Vergessene Rastermasken
Jedes Modul, das eine Rastermaske verwendet, sollte zusammen mit seinem maskenerzeugenden Modul kopiert werden, es sei denn, Sie planen, das später selbst zu lösen. Nur für den Fall, dass es ein Fehler ist, wenn Sie das versuchen:

Sie erhalten jetzt die Möglichkeit, die Zusammenführung abzubrechen, falls das nicht das war, was Sie wollten.
Die Benutzeroberfläche des Zusammenführungsberichts (neu)
Grundlagen
Der Berichtsdialog ist darauf ausgelegt, eine einfache Nutzerfrage zu beantworten: „was genau ist mit meiner Pipeline passiert?"
Er zeigt vier Pipelines nebeneinander:
- Original (die Ziel-Pipeline vor der Zusammenführung),
- Quelle (das Bild, aus dem Sie kopiert haben),
- Override (wo Quellbearbeitungen Zielbearbeitungen ersetzten),
- Ziel (die endgültige Pipeline nach der Zusammenführung).

Jede Spalte listet die aktiven Modulinstanzen auf, in der Reihenfolge der Benutzeroberfläche. Diese Ansicht enthält zusätzliche Markierungen:
- Klammern
[name]kennzeichnen Module, die neu eingefügt wurden. - Ein Sternchen
*kennzeichnet Module, die Masken verwenden. - Eine fette Beschriftung kennzeichnet Module, deren relative Position sich zwischen Quelle und Ziel geändert hat.
- Override-Pfeile zeigen, wo der Quellverlauf tatsächlich Zielbearbeitungen ersetzte (mit
→*, wenn auch Masken überschrieben wurden).
Zuckerguss
Die Zielspalte ist per Drag & Drop neu anordbar, was bedeutet, dass Sie, wenn Sie mit dem Ergebnis der topologischen Sortierung nicht zufrieden sind, es jetzt selbst korrigieren können, noch bevor es in Ihrer Datenbank und XMP gespeichert wird, und ohne die Dunkelkammer öffnen zu müssen. So können Sie die endgültige Pipeline manuell anpassen, bevor Sie sie akzeptieren, oder alles rückgängig machen und den Verlauf nicht zurückschreiben.
Wenn Sie neu anordnen:
- wird die Pipeline-Reihenfolge sofort aktualisiert,
- werden die Verlaufseinträge konsistent mit der neuen Reihenfolge gehalten,
- und die Berichtsansicht aktualisiert ihre Beschriftungen und „verschoben"-Markierungen entsprechend.
Dies ist als Sicherheitsventil gedacht: Selbst wenn die berechnete Reihenfolge gültig ist, haben Sie immer noch eine einfache Möglichkeit, sie feinzujustieren.
Warum das wichtig ist
Dies verbessert direkt Workflows, die Stapelbearbeitung mit komplexen Pipelines beinhalten:
- Kopieren von Bearbeitungen zwischen Bildern,
- Mischen von RAW- und JPEG-Quellen/-Zielen,
- und aufwändige Mehrfachinstanz- oder maskenbasierte Bearbeitungen.
Das Ziel ist es, das Einfügen des Verlaufs vorhersehbar zu machen, selbst wenn die zugrunde liegenden Pipelines sich unterscheiden. Diese Zuverlässigkeit ist besonders wichtig für fortgeschrittene Bearbeitungen, bei denen kleine Unterschiede in der Reihenfolge die Ergebnisse verändern können.
Diese Änderung fügt keine auffälligen neuen Funktionen hinzu — sie macht eine der meistgenutzten Funktionen vertrauenswürdig. Verlaufszusammenführungen verhalten sich jetzt so, wie Nutzer es erwarten: konsistente Ergebnisse, klare Berichterstattung und sichere Rückfalloptionen, wenn Beschränkungen kollidieren.
Und ich verstehe nicht, warum, 6 Jahre später, die It-Works-For-Me®-Typen, die Darktable in Zeitlupe zum Absturz bringen, nicht in Betracht gezogen haben, eine so grundlegende und dennoch kritische Funktion zu verbessern. Wenn das nicht nach falschen Prioritäten schreit, dann weiß ich nicht, was.
Was es möglich gemacht hat
Ich möchte hier betonen, dass diese ganze Neuschreibung möglich wurde, weil ich zuerst das Backend zur Verlaufsverwaltung in Ansel fast vollständig neu geschrieben habe, da es ein Chaos war:
- Es gab überall duplizierte Funktionen, die dieselbe Operation viele Male durchführten, aber versteckt in aufrufenden/aufgerufenen Funktionen überall in der Software, einige verursachten Dateisystem-I/O (XMP-Schreiben) ohne Grund, eine schrieb den Verlauf jedes Mal zurück, wenn wir die Dunkelkammer öffneten (was den Zeitstempel letzte Änderung vermasselte),
- Es gab mehrere ineinander verschachtelte Thread-Locks, die praktisch jede Änderung unmöglich machten, ohne die Dinge in einen Deadlock zu bringen,
- Es gab SQLite3-Verlaufsabrufcode, der mit C-Code verstrickt war, viele duplizierte SQL-Abfragen, keine davon thread-sicher (weil SQLite3 selbst nicht thread-sicher ist), also habe ich den gesamten SQL-Code in einer C-Schnittstelle vergraben, die die Thread-Sicherheit zentral handhabt, und jetzt ruft der gesamte C-Code Verlaufsinformationen aus der Bibliotheksdatenbank mit einer einzigen API ab, was bedeutet, dass wir wissen, dass alles, was den Verlauf liest, ihn überall in der App gleich liest,
- Einige Teile des Verlaufslesens, der Initialisierung und der Zusammenführung wurden in SQL erledigt (unter Nutzung von
JOIN-Anweisungen, was Sinn ergibt, aber…), und einige andere wurden in C erledigt (weil die Sicherheitsprüfungen der Module und die Initialisierung der Voreinstellungen offensichtlich C sind). Das führte zu dummen Dingen wie dem manuellen Neu-Indizieren von Verlaufselementen in C vor dem Speichern in der Datenbank zwischen transienten Schreibvorgängen (weil SQLite3 nicht garantiert, dass die Verlaufselemente in derselben Reihenfolge in der Datenbank gespeichert werden, in der sie übergeben wurden… dafür gibt es Primärschlüssel). Also habe ich das Ganze in C neu geschrieben, was vielleicht etwas langsamer ist, aber die Datenkonsistenz sicherstellt: Verläufe werden genau gleich behandelt, egal ob wir sie zum Zusammenführen laden, die Dunkelkammer öffnen oder ein Bild exportieren. Wenn es irgendwo einen Fehler gibt, wird er überall sein und wir werden ihn früher finden, außerdem werden wir ihn nur an einer Stelle beheben. - Der Code zur Verlaufsverwaltung war auch mit GUI-Code verstrickt, aber er kann auch aus der
ansel-cli(ohne GUI) laufen, was zu vielen Heuristiken führte, die an vielen Stellen prüften, ob wir eine GUI hatten oder nicht.
Sobald also all diese Hausmeisterarbeit erledigt war, begann ich, die Struktur dessen zu erkennen, was tatsächlich getan wurde und getan werden musste. Von da an führte eine Vereinfachung zur nächsten, bis ChatGPT 5.2 Codex den Rest erledigte. Vor 2 Wochen wurde das noch vollständig manuell gemacht und trieb mich viele Male in den Wahnsinn. Es ist wirklich nur Farbe, die diese Wände zusammenhielt, abgeblätterte Farbe, und der Versuch, es aufzuräumen, zerstörte viele Dinge, weil nichts in dieser Software modular war (also gekapselt). Etwas, das man an einer Stelle ändert, hat unerwartete Folgen anderswo, weshalb wir Kapselung, Modularität und Entwurfsmuster haben, weil die Programmiersprache C nicht für komplexe Desktop-Anwendungen wie diese entworfen wurde, und sie braucht wirklich die Disziplin der Entwickler, um nicht zu dem Albtraum zu werden, der sie ist.
Das ist vollständig vibecoded
Die Verlaufsbereinigung lief also seit 2023, während ich Burnout und softwarebedingte Depression bewältigte. Das ist eine beschissene Lebensqualität, Sie haben keine Ahnung. Diejenigen, die denken, ich übertreibe, wissen nicht, was es bedeutet, die Gehirnfäkalien anderer Leute mehr als 3 Jahre lang wegzuschaufeln. Denn ich habe eine Zeit gekannt, in der all das, wenn nicht besser, so doch zumindest weniger kompliziert und besser handhabbar war. Bis der Wahnsinn von COVID-19 zuschlug und Idioten zu viel freie Zeit in den Händen hatten, die sie nutzten, um etwas zu zerstören, das mehr oder weniger funktionierte.
Und dann erfuhr ich vor 2 Wochen von ChatGPT 5.2 Codex und installierte es im VS-Code-Editor. Es kostete mich also 3 Tage Arbeit, all die Dinge zu tun, die ich hier präsentiert habe. Ohne ChatGPT wären es solide 3 Wochen gewesen, plus das nie endende Herumbasteln an GTK-Kleinigkeiten. Lassen Sie uns über die Erfahrung sprechen.
Ich bin anderer Meinung als diejenigen, die uns glauben machen wollen, GenAI sei nur ein Werkzeug. Ein Werkzeug funktioniert nur in meiner Hand. Nicht wenn ich schlafe. Ich habe ChatGPT für mich arbeiten lassen, während ich das Abendessen kochte (ja, es ist so langsam). Man kommuniziert nicht mit einem Werkzeug, man benutzt es einfach nach bestem Vermögen. Im Falle eines Versagens, nun, manche geben dem Werkzeug die Schuld, aber wir alle wissen, was das bedeutet. Das Problem ist, dass ChatGPT keine Knöpfe oder Schieberegler hat, es interpretiert, was man ihm sagt, und nicht unbedingt so, wie man es meint. Und dann ergreift ein Werkzeug keine Initiative. Nun, ChatGPT hat sicherlich eine Meinung dazu, wie Code aussehen sollte, und manchmal muss man dagegen ankämpfen.
GenAI ist ein Praktikant. Ein Praktikant hat keine Erfahrung und weiß nur, was in der Schule gelehrt wird. Ein Praktikant kann frische neue Ideen einbringen, die Ihre Gewohnheiten herausfordern, und ebenso wahnwitzige Vorschläge, die nicht im Entferntesten für Ihren Kontext relevant und manchmal sogar nicht machbar sind. Aber ein Praktikant muss unter enger Aufsicht arbeiten und klare, eindeutige Anweisungen erhalten. ChatGPT ist viel mehr ein Praktikant als ein Werkzeug.
ChatGPT macht viele Fehler, und sie sind heimtückisch, weil sie mitten in völlig gültigem Zeug vergraben sind. Es hat einige seltsame Obsessionen (wie das NULL-Prüfen jedes Zeigers, von dem wir bereits wissen, dass er nicht NULL sein kann). Man muss es also wirklich im Auge behalten. Obwohl das Überprüfen und Beheben seiner Fehler immer noch schneller ist, als den ganzen Code selbst zu schreiben, ganz zu schweigen davon, dass mein erstes Karpaltunnelsyndrom-Problem vor 10 Jahren war, also ist es immer so viel, was man nicht tippt. Außerdem macht es Fehler in der Logik, aber keine Tippfehler, und zumindest viel weniger als ich selbst.
Aber wo ChatGPT Codex glänzt, ist in 2 Dingen.
Erstens das mühsame Spiel, Funktionen über die Codebasis hinweg zu grepen, um den Lebenszyklus von Daten herauszufinden (zurückzuentwickeln) und alle Aufrufstellen zu prüfen, um ein mentales Modell dessen aufzubauen, was vor sich geht. Das dauert ewig, es ist kognitiv sehr anspruchsvoll, besonders in einer so beschissenen Codebasis. ChatGPT wirkt Wunder, um Dutzende von Dateien zu durchqueren, Muster zu extrahieren, herauszufinden, was faktorisiert werden könnte, und Ausführungssequenzen zu verfolgen. Lassen Sie uns ganz klar sein, dass das in einer gut gepflegten Codebasis nicht nötig sein sollte, weil der Code in Modulen selbst gekapselt wäre, isoliert vom Rest. Aber ChatGPT hat viel geholfen, die Dinge modularer zu machen.
Zweitens alles, was GTK und GLib betrifft. Die sind im Web schlecht dokumentiert, und viele idiomatische Interaktionsmuster sind nur GTK-Entwicklern bekannt. ChatGPT hat offensichtlich jede Menge Open-Source-Code aufgenommen und kann viel besseren Boilerplate-GUI-Code produzieren, als ich könnte (oder mich darum scheren würde). Wie auch immer, vor ChatGPT wurde das zu mühseligen Sitzungen des Googelns von Informationen, und ich kann seit etwa 2020 keine relevanten technischen Informationen mehr auf Google finden, als sie ihre Algorithmen änderten, um alles aggressiv zu hinterfragen. Aber ich arbeite, um Probleme zu lösen, und all die GUI-Boilerplate-Funktionen, die Widgets und ihre Eigenschaften in einem deklarativen Stil initialisieren, sind meiner Intelligenz nicht würdig, es geht nur darum, keine Tippfehler einzuführen.
Aber um eine bessere Vorstellung zu bekommen, hier ist die Art von Prompts, die ich ihm geben musste, um das zu bauen, was ich gerade präsentiert habe:
now, in _hm_try_merge_iop_order_topologically(), build early in the function a GHashtable of all modules IDs tied to mod_list, then to dev_src->iop, then to dev_dest->iop. These will be useful to compute intersection of sets later. don’t modify dev_dest->iop_order_list. For all item in the sorted list (item being a node ID tied to a module op and multi_name) :
- find out if a corresponding module instance exists in dev_dest->iop, if not create it. Because dev_dest->iop is already inited and sanitized upstream, we can safely assume that every module not found should be inserted as a new instance. If the module instance ID is found in the input mod_list, the whole content of the module (parameters, blendop, etc.) should be copied between the source instance to the destination instance. Mind the deep copies that need to happen.
- overwrite all module->iop_order values with the new index number we just found by solving
- rebuild dev_dest->iop_order_list from scratch and update the module->multi_priority accordingly
now, in dt_history_merge_module_list_into_image_advanced, the temp history needs to be built as follow:
- deimplement the force_new_modules path for now, we will come back to it later and differently,
- build a temporary history as follow: for each module in mod_list:
- get the associated history item from dev_src->history (that would be the last one matching this module on the history stack),
- get the pipeline ordering info (iop_order, instance, multi_priority) from the corresponding module in dev_dest->iop
- update the existing history item from dev_src->history with pipeline ordering, since it may have changed after the topological sort, from the original history item,
- add this history entry to the temporary history
- concatenate the temporary history with dev_dest->history, first or last depending of append or appstart mode.
Try to use methods from history.c and dev_history.c as much as possible for the history to/from module handling. Extend the existing ones if you only need minor changes.
No, revert that. It’s not ok to delete history entries past the history_end in general. Whatever is in dev->history should go into the DB. Also, it’s not a problem because the history_end is also saved into DB. The problem here is that random history items are added when reading back the history from DB. Everything up to writing history, which happened in C, was ok. Find out why we get extra history entries when reading back from DB, compared to what we have at writing time before.
at the end of dt_history_merge in history_merge.c , I want you to show a report popup window. A text label will first tell “Copy, merging pipeline in {MERGE_MODE} and history in {STRATEGY} mode”, where {MERGE_MODE} depends on merge_iop_order (merge or destination), and {STRATEGY} depends on strategy. Then I want a GtkTreeView in list mode, with 3 columns:
- the source of the copy, with image ID and filename (not the full path),
- the override,
- the destination of the copy, with image ID and filename.
In columns 1. and 3., each row will show the module instances, starting with their pipeline order, module->name and module->multi_name. Only enabled modules will show. The column 2 will draw an arrow between source and destination instances when the source history overrides the destination history. This is done by checking, in the destination history, if the last entry targeting this module matches destination history or source. In case it matches both, show nothing since it’s not an override. The pipeline nodes will be shown in reverse order to match GUI ordering, since it’s a kind of layer stack. They should both be aligned on the bottom so the early steps have a chance to be on the same row until topology diverges between both pipes
Eine Sache, die ich herausgefunden habe, ist, dass man mit ChatGPT definitiv zu spezifisch sein und es gegen eine Wand fahren kann. Wenn das passiert, ist die beste Vorgehensweise, manuell zu übernehmen.
Die Energiekosten dieses Dings sind unerträglich, aber sagen wir, geteilt durch die etwa 900 Typen, die Ansel auf GitHub mit einem Stern versehen haben (ich habe keine Download-Statistiken), ist es für das Gemeinwohl. Es ist einfach eine effizientere Art, meinen Hirnschmalz dafür zu sparen, darüber nachzudenken, was getan werden sollte (Design und Architektur), statt wie es zu tun ist. Wahrscheinlich nicht so, wie Kinder heutzutage vibecoden, allerdings.
Als Nächstes: Stile.
Translated from English by : ChatGPT, Claude. In case of conflict, inconsistency or error, the English version shall prevail.