Memoires van een kerel die te veel tijd besteedde aan het opruimen van andermans stront en het betalen voor hun slechte beslissingen, aflevering #te veel.
Bewerkingsgeschiedenis kopiëren/plakken en stijlen zijn kernfuncties in Ansel, en degene die het zijn titel „workflow-app" doet verdienen (of niet). Maar het is ook een van de moeilijkste om intern goed te krijgen. Gebruikers zien een lijst met bewerkingen, maar onder de motorkap zijn die bewerkingen afhankelijk van de pipeline-volgorde, de module-instanties en de maskers. Als twee afbeeldingen verschillende pipeline-topologieën hebben, kan het naïef kopiëren van bewerkingen tot inconsistenties leiden.
Deze update maakt het samenvoegen van geschiedenis robuust, consistent en transparant, na vervelend werk van code opschonen en vereenvoudigen. Het introduceert ook duidelijke foutafhandeling wanneer een perfecte samenvoeging wiskundig onmogelijk is.
Een korte geschiedenis van slecht ontwerp
Tot begin 2019 was Darktable ontworpen rond een vaste pipeline: modules hadden een volgorde die tijdens het compileren werd bepaald door een Python-script , gemaakt door de oprichter van Darktable, Johannes Hanika, in 2011 . Het enige wat je van Jo mag verwachten is dat hij zijn wiskunde goed heeft, en dus doet dit script precies wat er in zulke omstandigheden gedaan zou moeten worden:
- laat programmeurs modules declareren,
- laat hen definiëren welke modules vóór elke module moeten komen, op een luie en handige manier die je toestaat gewoon te zeggen „A moet vóór C", „B moet vóór A", „D moet vóór A", enzovoort.
- zet dat om in een gerichte graaf , wat gewoon het wiskundige object is dat al die beperkingen aan elkaar lijmt,
- los de gerichte graaf op met een van de topologische sorteer -algoritmen, die teruggaan tot de jaren 1960,
- schrijf het indexnummer van elke module in de pipeline, en klaar.
Helaas betekende die vaste pipeline dat het later wijzigen van de relatieve volgorde van modules oudere geschiedenissen zou breken. Wat vervelend was, omdat er enkele ontwerpfouten waren gemaakt met de positie van de schermtransformatie en de basiscurve-module, die vroeg in de pipeline plaatsvonden en de kleuren in HDR-situaties gek deden worden. Om dat op te lossen moest ik de filmic-module als een aparte module creëren, om die aan het einde van de pipeline te kunnen plaatsen. En alle andere schermgerefereerde modules zouden in de codebase gedupliceerd moeten worden om ze te kunnen invoegen waar nodig zonder oudere bewerkingen (waaronder de mijne) te breken. Onnodig te zeggen dat de vaste pipeline zijn tijd had gehad, en ik was een voorstander van deze verandering.
Met een vaste pipeline was het samenvoegen van geschiedenissen, dat wil zeggen geschiedenissen kopiëren/plakken tussen afbeeldingen of stijlen toepassen op afbeeldingen (wat uiteindelijk hetzelfde is), makkelijk: het was slechts een kwestie van module-parameters en maskers 1:1 vervangen. Zelfs met multi-instantiemodules, die rond 2014 waren geïntroduceerd, was het niet zo erg omdat alle instanties gedwongen werden opeenvolgend te zijn, en ten opzichte van elkaar genummerd. Dus, na het creëren van de ontbrekende instanties, op een positie die voorspelbaar en invariant was (in opeenvolgende volgorde na de basisinstantie), was het nog steeds een kwestie van module-parameters en maskers 1:1 vervangen.
Maar de broodnodige functie om de pipeline te herordenen, tijdens runtime en door gebruikers, die met Darktable 3.0 kwam, werd op de slechtst mogelijke manier gedaan. De ontwikkelaar die het implementeerde handelde de indexering af met arrays van drijvende-komma-prioriteiten, wat niet de juiste datastructuur voor het probleem is. Bovendien werd de code voor geschiedenisbeheer niet herzien en vereenvoudigd vóór uitbreiding, maar ter plekke ingehackt met minimale wijzigingen, wat het echt complex en onleesbaar maakte. Pascal Obry, die de verandering in 2019 had geaccepteerd, moest in 2020 de hele backend voor pipeline-herordening herschrijven , met de juiste datastructuur voor de taak (gelinkte lijsten ), omdat de vorige broos en onhoudbaar was.
Maar het samenvoegen van pipelines, die verschillende aantallen modules kunnen hebben, geordend op onvoorspelbare en niet-invariante plaatsen, kan op geen enkele andere manier gedaan worden dan met topologisch sorteren tijdens runtime, want het is niet langer een louter probleem van geschiedenissen (oftewel momentopnames van module-parameters), het is een tweeledig probleem dat geschiedenissen omvat maar ook pipeline-topologie. Met andere woorden: we moeten oplossen waar module-instanties ingevoegd moeten worden die wel in de bron-pipeline bestaan maar niet in de bestemming. En toch werd het Python-script dat dat tijdens het compileren deed in 2019 verwijderd, en de functie werd nooit naar C geporteerd.
Dus, de manier waarop Darktable tot op de dag van vandaag het samenvoegen van pipelines afhandelt is via heuristieken die uit het vaste-pipeline-paradigma zijn gehackt en in het algemeen kapot zijn, behalve in de mooie gevallen die overeenkomen met de voorwaarden van de pipelines van 2018 en eerder:
- als je bron- en bestemmings-pipelines hetzelfde aantal modules hebben, op dezelfde manier geordend, gaat alles goed.
- als je bron-pipeline extra module-instanties heeft, vergeleken met de bestemming, maar die instanties zitten allemaal onmiddellijk na de basisinstantie, gaat alles nog steeds goed,
- maar… als je extra instanties hebt, of het nu in de bron- of in de bestemmings-pipeline is, en ze zijn verplaatst in de pipeline, dan is het gedrag ongespecificeerd, onvoorspelbaar, en ik heb al meer dan 5 jaar geschiedenis zitten kopiëren/plakken met ingehouden adem,
- ook is er een lijst met „fence"-modules die een specifieke relatieve volgorde nodig hebben (zoals color calibration absoluut na input color profile moet komen), maar geen manier om ze af te dwingen, geen manier om ze te repareren als er verkeerde ordeningen optreden, alleen stille foutmeldingen in de console.
Combineer dat met rastermaskers, waarbij de module die een rastermasker hergebruikt absoluut later in de pipe moet zitten dan de module die het produceert, en waarbij je overigens misschien de producent samen met de consument wilt kopiëren, of op zijn minst een waarschuwing wilt krijgen als je dat niet doet… en je hebt het recept voor waanzin.
In de praktijk zul je elke afbeelding moeten openen en de modulestapel in de donkere kamer moeten controleren, wat pijnlijk traag is.
Omdat Darktable gedefinieerd wordt door slecht prioriteitenbeheer, is er veel werk gestoken in veel cosmetica: het laat je je foto’s bewerken met PlayStation-gamepads, het staat op het punt maskeringsfuncties met deep learning te krijgen, maar het slaagt er nog steeds in de kernbeginselen te verkloten, zonder enige verbetering in 6 jaar ondanks de schijnbare activiteit in het project.
Het probleem (TL;DR)
Voorheen probeerde het plakken van geschiedenis de modules gaandeweg één voor één samen te voegen. Dat werkte in eenvoudige gevallen maar werd onbetrouwbaar wanneer:
- de bronafbeelding een andere pipeline-volgorde had,
- er meerdere instanties van dezelfde module waren,
- of er maskers en mengen bij betrokken waren.
Omdat de samenvoeging niet het hele ordeningsprobleem oploste, kon het resultaat van heel veel dingen afhangen. In sommige gevallen kon de uiteindelijke pipeline inconsistent worden met de geschiedenisstapel.
De oplossing
Ik heb een topologisch sorteeralgoritme in C geïmplementeerd. Of nauwkeuriger gezegd, ChatGPT deed het en ik controleerde het (meer daarover hieronder). 304 regels code, wat, voor C, heel weinig is.
We behandelen de pipeline-volgorde nu als een verzameling beperkingen die globaal opgelost moeten worden, niet module‑voor‑module. In de praktijk betekent dit:
- We lossen eerst de pipeline-topologie op, en de bewerkingsgeschiedenis (module-parameters) als laatste. Twee duidelijke stappen die foutafhandeling mogelijk maken.
- De samenvoeging berekent één enkele, geldige pipeline-volgorde die zowel de bron als de bestemming waar mogelijk bevredigt, ongeacht hoeveel module-instanties er zijn en hoe ze geordend zijn.
- Als beperkingen onverenigbaar zijn, kan Ansel het conflict uitleggen en vragen welke ordening behouden moet blijven.
- Als een conflict onoplosbaar is, meldt Ansel het duidelijk in plaats van een kapotte of instabiele pipeline te produceren en geeft je opties om het te repareren.
- Beperkingen worden ook opgelegd aan modules die rastermaskers gebruiken en verbruiken, en er wordt een waarschuwing gegeven als een gebruiker van een rastermasker wordt gekopieerd zonder de producent.
- Hardgecodeerde beperkingen worden opgelegd aan modules die elkaar om technische redenen vóór zich nodig hebben (reconstructie van hooglichten vóór demozaïceren, input color profile vóór color calibration, enz.). Die beperkingen worden hetzelfde behandeld als de andere en samen opgelost (geen speciaal geval).
Wat betreft het tweede deel, de geschiedenis in de eigenlijke zin, moet hier één ding worden verduidelijkt. De geschiedenis van Darktable en Ansel lijkt sterk op een ongedaan maken/opnieuw-lijst van momentopnames van module-parameters: elk geschiedenisitem is gekoppeld aan een module, en vertegenwoordigt de interne staat van parameters en maskers ervan. Bij het laden van de geschiedenis in de pipeline-knooppunten (knooppunten zijnde de pixelfilters die gekoppeld zijn aan de „modules" die je in de GUI ziet), lezen we de geschiedenis van onder naar boven en kopiëren we elk item/momentopname naar de modules, wat betekent dat latere momentopnames altijd voorrang hebben op eerdere.
Dat wil zeggen, de geschiedenis is geordend op tijd van gebruikerswijziging (nogmaals… denk aan een ongedaan maken/opnieuw-lijst van momentopnames), niet op de volgorde van de pipeline-knooppunten. Maar waar het verwarrend wordt is dat geschiedenisitems ook de pipeline-positie van een module opslaan, wat betekent dat het herordenen van modules geschiedenisitems achterlaat. Dat verwarde de ontwikkelaars van Darktable, en veel meer de gebruikers. Dus laten we de twee conceptueel splitsen, zowel in onze gedachten als in de software.
Topologie (ordening van pipeline-knooppunten) staat los van geschiedenis. Anders dan Darktable, dat alles via geschiedenisitems beheert (inclusief de pipeline-volgorde), wat actief schadelijk is zowel wat betreft begrip als codecomplexiteit (die gaan sowieso samen), lossen wij de pipeline op als een op zichzelf staande verzameling knooppunten (modules), en koppelen we vervolgens de knooppunten weer aan hun laatste geschiedenisfase. Het kostte me een paar jaar om door alle overweldigende onleesbaarheid in deze software heen te kijken, begraven in gekopieerde-en-geplakte code, en het licht te zien: eenmaal geabstraheerd is het probleem vrij eenvoudig.
Dus, zodra de topologie is opgelost, blijven ons 3 modi voor het samenvoegen van geschiedenis over:
- replace: de bron-geschiedenis vervangt de hele bestemming, verplichte modules zoals demozaïceren voor RAW-afbeeldingen kunnen er nog steeds bovenop worden toegevoegd (zodat kopiëren/plakken veilig is tussen JPEG en RAW). Deze modus leidt tot geen topologisch sorteren, het is een directe kopie van geschiedenis en pipeline-volgorde.
- append: de bron-geschiedenis gaat bovenop de bestemming, dus geschiedenisitems die op dezelfde modules in bron en bestemming gericht zijn worden overschreven door de bron,
- appstart: de bron-geschiedenis gaat onderaan de bestemming, dus geschiedenisitems die op dezelfde modules in bron en bestemming gericht zijn worden overschreven door de bestemming.
De pipeline-volgorde die door topologisch sorteren is opgelost wordt bijgewerkt in de laatste geschiedenisitems, zowel in de append- als de appstart-modus, wat betekent dat teruggaan in de geschiedenis ook het topologisch sorteren zal terugdraaien. Geschiedenissen worden bij het samenvoegen bewust niet gecomprimeerd, zodat gebruikers de mogelijkheid behouden om de samenvoeging terug te draaien, zowel met de functies voor ongedaan maken/opnieuw, als door terug te gaan in de geschiedenis-toolbox, in de donkere kamer, vóór het punt van samenvoegen.
Kortom: het plakken van geschiedenis is nu deterministisch, veilig, niet-destructief, zelfs voor complexe bewerkingen.
Hoe te gebruiken
Er zal één bron-geschiedenis zijn (die je kopieert), en één bestemmings-geschiedenis (waar je plakt). Hetzelfde zal worden toegepast met stijlen wanneer die opnieuw geïmplementeerd worden; de bron-geschiedenis wordt dan gedefinieerd door de stijl in plaats van door een andere afbeelding, en de rest blijft hetzelfde.
In het globale menu Bewerken → Modus voor plakken van geschiedenis kun je kiezen tussen append, appstart of replace. De instelling is globaal voor de hele applicatie. Dat bepaalt welke geschiedenis (bron of bestemming) voorrang krijgt door gemeenschappelijke modules te overschrijven.
In Bewerken → Modus voor plakken van knooppunten kun je Modulevolgorde kopiëren aan/uit zetten. Als het uit staat, wordt de pipeline-volgorde van de bestemming behouden zoals die is. Als het aan staat, doen we ons best om de pipeline-volgorde van de bron in de bestemming te importeren.
Zoals voorheen krijg je in het menu Bewerken de opties om alles te kopiëren/plakken, of alleen geselecteerde modules (via het modale venster). Er zijn globale sneltoetsen beschikbaar en door de gebruiker te bewerken. Merk op dat het kopiëren/plakken van geschiedenissen expliciet verboden is in de weergave van de donkere kamer, zelfs vanuit de filmstrip, omdat het dubbelzinnig is om te bepalen of je tussen miniaturen wilt kopiëren, van miniatuur naar hoofdafbeelding of andersom. In de lichttafel selecteer je de bron, kopieer je, selecteer je de bestemming, plak je, en is alles duidelijk.
Nu is er een belangrijke aanname om in gedachten te houden: modules die dezelfde instantienaam hebben (standaard het instantienummer, of een door de gebruiker gedefinieerde naam) worden beschouwd als dezelfde entiteit in de bestemmings- en bron-geschiedenissen. Dus elke Exposure (sky) zal worden samengevoegd met elke andere Exposure (sky)-module (hoofdlettergevoelig), en er zou slechts één Exposure (sky)-instantie moeten zijn in de bestemmings- en bron-geschiedenissen. Voorheen gebruikte de code instantienummers, wat brozer is omdat ze door de software worden opgelegd en opgehoogd in de volgorde van creatie, wat geen betekenis heeft voor gebruikers.
GUI en foutafhandeling
Het mooie van de nieuwe oplossing is dat je de donkere kamer niet hoeft te openen om de puinhoop te zien die je hebt gecreëerd door rommel te kopiëren/plakken; je kunt het beoordelen voordat er schade aan je bewerkingen wordt aangericht, in de lichttafel. Ook wanneer de oplosser geen oplossing vindt, wat gebeurt bij onverenigbare beperkingen (A moet worden voorafgegaan door B, maar B moet worden voorafgegaan door A) of cykels (meer hieronder), is hij in staat te vertellen wat er faalt, het te melden en ofwel gebruikersinvoer te vragen om het te repareren ofwel terug te vallen op het meest zinnige pad. Laat me het je laten zien:
Triviale cykels
Exposure 1 komt vóór Exposure in de bron-geschiedenis, maar erna in de bestemmings-geschiedenis. De verzameling beperkingen komt uit op Exposure → Exposure 1 → Exposure, wat onuitvoerbaar is. Dit is wat er gebeurt in Ansel:

Deze triviale cykels met onmiddellijke buren worden vóór het oplossen opgevangen, zodat ze de besturingsstroom niet onderbreken.
Niet-triviale cykels
Die niet-triviale cykels betrekken meerdere modules en kunnen niet worden gedetecteerd voordat wordt geprobeerd de gerichte graaf op te lossen. Wanneer dat gebeurt:

In dit geval is er niets te doen: we zullen automatisch opnieuw proberen met de bestemmingsvolgorde, aangezien het doorgaans optreedt bij het proberen samen te voegen van de bronvolgorde in de bestemming.
Vergeten rastermaskers
Elke module die een rastermasker gebruikt zou samen met zijn maskerproducerende module gekopieerd moeten worden, tenzij je van plan bent dat later zelf op te lossen. Voor het geval het een vergissing is, als je dat probeert:

Je krijgt de kans om de samenvoeging nu meteen af te breken als dat niet was wat je wilde.
De GUI van het samenvoegrapport (nieuw)
Basis
De rapportdialoog is ontworpen om een eenvoudige gebruikersvraag te beantwoorden: „wat is er precies met mijn pipeline gebeurd?"
Hij toont vier pipelines naast elkaar:
- Origineel (de bestemmings-pipeline vóór de samenvoeging),
- Bron (de afbeelding waarvan je hebt gekopieerd),
- Override (waar bron-bewerkingen bestemmings-bewerkingen vervingen),
- Bestemming (de uiteindelijke pipeline na de samenvoeging).

Elke kolom toont de actieve module-instanties, in GUI-volgorde. Deze weergave bevat aanvullende markeringen:
- Vierkante haken
[naam]geven modules aan die nieuw zijn ingevoegd. - Een asterisk
*geeft modules aan die maskers gebruiken. - Een vet label geeft modules aan waarvan de relatieve positie tussen bron en bestemming is veranderd.
- Override-pijlen tonen waar de bron-geschiedenis daadwerkelijk bestemmings-bewerkingen verving (met
→*wanneer ook maskers werden overschreven).
Snoepgoed
De bestemmingskolom is herordenbaar met slepen en neerzetten, wat betekent dat als je niet tevreden bent met het resultaat van het topologisch sorteren, je het nu meteen zelf kunt repareren, voordat het zelfs maar naar je database en XMP is opgeslagen, en zonder de donkere kamer te hoeven openen. Hiermee kun je de uiteindelijke pipeline handmatig aanpassen voordat je hem accepteert, of alles terugdraaien en de geschiedenis niet terugschrijven.
Wanneer je herordent:
- wordt de pipeline-volgorde onmiddellijk bijgewerkt,
- worden geschiedenisitems consistent gehouden met de nieuwe volgorde,
- en werkt de rapportweergave zijn labels en „verplaatst"-markeringen dienovereenkomstig bij.
Dit is bedoeld als een veiligheidsklep: zelfs als de berekende volgorde geldig is, heb je nog steeds een eenvoudige manier om hem bij te stellen.
Waarom dit belangrijk is
Dit verbetert direct workflows waarbij batchbewerking met complexe pipelines betrokken is:
- bewerkingen kopiëren tussen afbeeldingen,
- RAW- en JPEG-bronnen/-bestemmingen mengen,
- en zware multi‑instantie- of maskergebaseerde bewerkingen.
Het doel is om het plakken van geschiedenis voorspelbaar te maken, zelfs wanneer de onderliggende pipelines verschillen. Die betrouwbaarheid is vooral belangrijk voor geavanceerde bewerkingen waarbij kleine ordeningsverschillen de resultaten kunnen veranderen.
Deze verandering voegt geen opzichtige nieuwe functies toe — het maakt een van de meest gebruikte functies betrouwbaar. Het samenvoegen van geschiedenis gedraagt zich nu zoals gebruikers verwachten: consistente resultaten, duidelijke rapportage en veilige terugvalopties wanneer beperkingen conflicteren.
En ik begrijp niet waarom, 6 jaar later, de It Works For Me®-kerels die Darktable in slow motion laten crashen er niet aan gedacht hebben om zo’n basale maar kritieke functie te verbeteren. Als dat geen verkeerde prioriteiten schreeuwt, dan weet ik het ook niet meer.
Wat het mogelijk maakte
Ik wil hier benadrukken dat deze hele herschrijving mogelijk werd gemaakt omdat ik eerst de backend voor geschiedenisafhandeling in Ansel bijna volledig herschreef, aangezien die een puinhoop was:
- Er waren overal gedupliceerde functies, die dezelfde bewerking vele malen uitvoerden maar verborgen in aanroepende/aangeroepen functies overal in de software, sommige veroorzaakten bestandssysteem-I/O (XMP schrijven) zonder reden, één schreef de geschiedenis terug elke keer dat we de donkere kamer openden (wat de tijdstempel van laatste wijziging verpestte),
- Er waren verschillende verweven thread-locks die het in feite onmogelijk maakten iets te wijzigen zonder dingen vast te laten lopen in een deadlock,
- Er was SQLite3-code voor het ophalen van geschiedenis verweven met C-code, veel gedupliceerde SQL-query’s, geen enkele daarvan thread-safe (omdat SQLite3 zelf niet thread-safe is), dus heb ik alle SQL-code begraven in een C-interface die thread-veiligheid centraal afhandelt, en nu haalt alle C-code geschiedenisinformatie uit de bibliotheekdatabase op met één enkele API, wat betekent dat we weten dat alles wat geschiedenis leest die overal in de app op dezelfde manier zal lezen,
- Sommige delen van het lezen, initialiseren en samenvoegen van geschiedenis werden gedaan in SQL (met gebruik van
JOIN-statements, wat zin heeft, maar…), en sommige andere werden gedaan in C (omdat veiligheidscontroles van modules en initialisatie van presets uiteraard C zijn). Dat leidde tot domme dingen zoals het handmatig herindexeren van geschiedenisitems in C voordat ze naar de database werden opgeslagen tussen tijdelijke schrijfbewerkingen door (omdat SQLite3 niet garandeert dat de geschiedenisitems in de database worden opgeslagen in dezelfde volgorde waarin ze zijn doorgegeven… daar zijn primaire sleutels voor). Dus heb ik het hele ding in C herschreven, wat mogelijk iets langzamer is maar dataconsistentie garandeert: geschiedenissen worden op exact dezelfde manier afgehandeld, of we ze nu laden om ze samen te voegen, we de donkere kamer openen of een afbeelding exporteren. Als er ergens een bug zit, zit die overal en zullen we hem eerder vinden, plus we zullen hem op slechts één plek repareren. - De code voor geschiedenisbeheer was ook verweven met GUI-code, maar het kan ook draaien vanuit de
ansel-cli(zonder GUI), wat leidde tot veel heuristieken die op veel plaatsen controleerden of we een GUI hadden of niet.
Dus, zodra al dat conciërgewerk was gedaan, begon ik de structuur te zien van wat er daadwerkelijk werd gedaan en gedaan moest worden. Vandaar leidde de ene vereenvoudiging tot de andere, totdat ChatGPT 5.2 Codex de rest deed. Tot 2 weken geleden werd dat nog volledig handmatig gedaan en maakte het me vele malen gek. Het is echt gewoon verf die die muren bij elkaar hield, afgebladderde verf, en het proberen op te schonen vernietigde veel dingen omdat niets in deze software modulair was (oftewel afgesloten). Iets wat je op één plek verandert heeft onverwachte gevolgen elders, en daarom hebben we encapsulatie, modulariteit en ontwerppatronen, want de programmeertaal C was niet ontworpen voor complexe desktopapplicaties als deze, en het vereist echt discipline van de ontwikkelaar om te voorkomen dat het de nachtmerrie wordt die het is.
Dit is volledig vibecoded
De opschoning van de geschiedenis liep dus al sinds 2023, terwijl ik een burn-out en door software veroorzaakte depressie beheerde. Dat is een waardeloze kwaliteit van leven, je hebt geen idee. Degenen die denken dat ik overdrijf weten niet wat het inhoudt om meer dan 3 jaar lang andermans hersenpoep te scheppen. Want ik heb een tijd gekend waarin dat alles, zo niet beter, dan toch minder gecompliceerd en beter beheersbaar was. Tot de waanzin van COVID-19 toesloeg, en idioten te veel vrije tijd in handen kregen, die ze gebruikten om iets te vernietigen dat ruwweg werkte.
En toen ontdekte ik 2 weken geleden ChatGPT 5.2 Codex, en installeerde het binnen de VS Code-editor. Dus het kostte me 3 dagen werk om alle dingen te doen die ik hier heb gepresenteerd. Zonder ChatGPT zou het een solide 3 weken zijn geweest, plus het eindeloze gepruts met GTK-details. Laten we het over de ervaring hebben.
Ik ben het oneens met degenen die ons willen doen geloven dat GenAI slechts een gereedschap is. Een gereedschap werkt alleen in mijn hand. Niet als ik slaap. Ik heb ChatGPT voor me laten werken terwijl ik het avondeten kookte (ja, het is zo traag). Je communiceert niet met een gereedschap, je gebruikt het gewoon zo goed als je kunt. In geval van mislukking geven sommigen de schuld aan het gereedschap, maar we weten allemaal wat dat betekent. Het probleem is dat ChatGPT geen knoppen of schuifregelaars heeft, het interpreteert wat je het vertelt, en niet noodzakelijk hoe je het bedoelt. En dan neemt een gereedschap geen initiatief. Nou, ChatGPT heeft zeker een mening over hoe code eruit zou moeten zien, en soms moet je ertegen vechten.
GenAI is een stagiair. Een stagiair heeft geen ervaring en weet alleen wat er op school wordt onderwezen. Een stagiair kan frisse nieuwe ideeën aandragen die je gewoontes uitdagen, en net zo goed onzinnige suggesties, die niet in de verste verte relevant zijn voor je context en soms zelfs niet haalbaar. Maar een stagiair moet onder nauw toezicht werken en duidelijke, ondubbelzinnige instructies krijgen. ChatGPT is veel meer een stagiair dan een gereedschap.
ChatGPT maakt veel fouten, en ze zijn geniepig omdat ze verstopt zitten midden in perfect geldige dingen. Het heeft enkele rare obsessies (zoals het NULL-controleren van elke pointer waarvan we al weten dat die niet NULL kan zijn). Dus je moet echt opletten. Hoewel het beoordelen en repareren van zijn fouten nog steeds sneller is dan alle code zelf schrijven, om nog maar te zwijgen van mijn eerste carpaletunnelsyndroom-probleem van 10 jaar geleden, dus het is altijd zoveel om niet te typen. Bovendien maakt het fouten in logica, maar geen typefouten, en in ieder geval een stuk minder dan ikzelf.
Maar waar ChatGPT Codex uitblinkt, is in 2 dingen.
Ten eerste, het vervelende spel van functies door de codebase greppen om (via reverse-engineering) de levenscyclus van data uit te vinden en alle aanroeppunten te controleren om een mentaal model te bouwen van wat er aan de hand is. Dat duurt eeuwen, het is cognitief zeer veeleisend, vooral in een codebase die zo waardeloos is. ChatGPT werkt wonderen om tientallen bestanden te doorlopen, patronen te extraheren, uit te vinden wat gefactoriseerd zou kunnen worden en uitvoeringssequenties te volgen. Laten we heel duidelijk zijn dat dat in een goed onderhouden codebase geen noodzaak zou moeten zijn omdat de code zelf-afgesloten zou zijn in modules, geïsoleerd van de rest. Maar ChatGPT hielp enorm om dingen modulairder te maken.
Ten tweede, alles wat met GTK en GLib te maken heeft. Die zijn slecht gedocumenteerd op het web, en veel idiomatische interactiepatronen zijn alleen bekend bij GTK-ontwikkelaars. ChatGPT heeft duidelijk veel open-sourcecode verwerkt en kan veel betere boilerplate-GUI-code produceren dan ik zou kunnen (of zin in heb). Hoe dan ook, vóór ChatGPT werd dat vervelende sessies van info googelen, en ik kan sinds 2020 of zo geen relevante technische info meer op Google vinden, toen ze hun algoritmen wijzigden om alles agressief te tweede-raden. Maar ik werk om problemen op te lossen, en al die boilerplate-GUI-functies die widgets en hun eigenschappen in een declaratieve stijl initialiseren zijn mijn intelligentie niet waard, het is alleen maar proberen geen typefouten te introduceren.
Maar om er een beter idee van te krijgen, hier is het soort prompts dat ik het moest geven om te bouwen wat ik zojuist heb gepresenteerd:
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
Eén ding dat ik ontdekte is dat je zeker te specifiek kunt zijn met ChatGPT en het tegen een muur kunt laten aanlopen. Als dat gebeurt, is de beste handelswijze om het handmatig over te nemen.
De energiekosten van dat ding zijn onverdraaglijk, maar laten we zeggen, gedeeld door de ongeveer 900 kerels die Ansel een ster op GitHub gaven (ik heb geen downloadstatistieken), het is voor het grotere goed. Het is gewoon een efficiëntere manier om mijn hersensappen te sparen om na te denken over wat er gedaan moet worden (ontwerp en architectuur), in plaats van hoe het te doen. Waarschijnlijk niet hoe kinderen tegenwoordig vibecoden, echter.
Volgende: stijlen.
Translated from English by : Claude. In case of conflict, inconsistency or error, the English version shall prevail.