Memorias de un tipo que pasó demasiado tiempo limpiando la mierda de los demás y pagando por sus malas decisiones, episodio #demasiados.
Copiar/pegar historiales y estilos son funciones fundamentales en Ansel, y las que hacen que merezca (o no) su título de «aplicación de flujo de trabajo». Pero también están entre las más difíciles de acertar internamente. Los usuarios ven una lista de ediciones, pero por debajo esas ediciones dependen del orden del pipeline, de las instancias de módulos y de las máscaras. Si dos imágenes tienen topologías de pipeline distintas, copiar ediciones de forma ingenua puede producir inconsistencias.
Esta actualización hace que la fusión de historiales sea robusta, consistente y transparente, tras un tedioso trabajo de limpieza y simplificación del código. También introduce un manejo claro de errores cuando una fusión perfecta es matemáticamente imposible.
Una breve historia de mal diseño
Hasta principios de 2019, Darktable estaba diseñado en torno a un pipeline fijo: los módulos tenían un orden que se decidía en tiempo de compilación mediante un script de Python , creado por el fundador de Darktable, Johannes Hanika, en 2011 . Lo único que puedes esperar de Jo es que acierte con las matemáticas, y por eso este script hace exactamente lo que debería hacerse en tales circunstancias:
- dejar que los programadores declaren módulos,
- dejar que definan qué módulos deben ir antes de cada módulo, de una manera perezosa y cómoda que permite decir simplemente «A debe ir antes de C», «B debe ir antes de A», «D debe ir antes de A», etc.
- convertir eso en un grafo dirigido , que es simplemente el objeto matemático que pega todas esas restricciones,
- resolver el grafo dirigido con uno de los algoritmos de ordenación topológica , que se remontan a los años 1960,
- escribir el número de índice de cada módulo en el pipeline, y listo.
Por desgracia, ese pipeline fijo significaba que cambiar el orden relativo de los módulos en cualquier momento posterior rompería los historiales antiguos. Lo cual era un fastidio porque se cometieron algunos errores de diseño con la posición de la transformación de pantalla y del módulo de curva base, que ocurrían al principio del pipeline y hacían que los colores se volvieran locos en situaciones HDR. Para arreglarlo, tuve que crear el módulo filmic como otro módulo, para poder ponerlo al final del pipeline. Y todos los demás módulos referidos a la pantalla habrían tenido que duplicarse en el código base para poder insertarlos donde hiciera falta sin romper ediciones antiguas (incluidas las mías). Ni que decir tiene que el pipeline fijo había cumplido su ciclo, y yo era partidario de este cambio.
Con un pipeline fijo, fusionar historiales entre sí, es decir, copiar/pegar historiales entre imágenes o aplicar estilos a imágenes (que acaba siendo lo mismo), era fácil: bastaba con reemplazar los parámetros y las máscaras de los módulos 1:1. Incluso con los módulos multi-instancia, que se habían introducido hacia 2014, no era tan complicado porque todas las instancias estaban obligadas a ser consecutivas y numeradas relativamente entre sí. Así que, tras crear las instancias que faltaban, en una posición que era predecible e invariante (en orden secuencial después de la instancia base), seguía siendo reemplazar los parámetros y las máscaras de los módulos 1:1.
Pero la muy necesaria función de reordenación del pipeline, en tiempo de ejecución y por parte de los usuarios, que llegó con Darktable 3.0, se hizo de la peor manera posible. El desarrollador que la implementó gestionó la indexación con arreglos de prioridades en coma flotante, que no es la estructura de datos adecuada para el problema. Además, el código de gestión de historiales no se refactorizó ni se simplificó antes de extenderlo, sino que se metió a la fuerza con cambios mínimos, lo que lo hizo realmente complejo y ofuscado. Pascal Obry, que había aceptado el cambio en 2019, tuvo que reescribir todo el backend de reordenación del pipeline en 2020, usando la estructura de datos adecuada para la tarea en cuestión (listas enlazadas ), porque la anterior era frágil e imposible de mantener.
Pero fusionar pipelines, que pueden tener distintos números de módulos, ordenados en lugares impredecibles y no invariantes, no puede hacerse de ninguna otra manera que usando ordenación topológica en tiempo de ejecución, porque ya no es un mero problema de historiales (es decir, instantáneas de parámetros de módulos), es un problema doble que incluye los historiales pero también la topología del pipeline. En otras palabras: necesitamos resolver dónde insertar las instancias de módulos que existen en el pipeline de origen pero no en el de destino. Y sin embargo, el script de Python que hacía eso en tiempo de compilación se eliminó en 2019, y la función nunca se portó a C.
Así que, la manera en que Darktable, a día de hoy, gestiona la fusión de pipelines es mediante heurísticas apañadas a partir del paradigma del pipeline fijo y rotas en general, salvo en los casos bonitos que coinciden con las condiciones de los pipelines de 2018 y anteriores:
- si tus pipelines de origen y de destino tienen el mismo número de módulos ordenados de la misma manera, todo va bien.
- si tu pipeline de origen tiene instancias de módulos adicionales, comparado con el de destino, pero todas esas instancias están situadas inmediatamente después de la instancia base, todo sigue yendo bien,
- pero… si tienes instancias adicionales, ya sea en el pipeline de origen o en el de destino, y se han desplazado en el pipeline, entonces el comportamiento es indeterminado, impredecible, y llevo copiando y pegando historiales conteniendo la respiración desde hace más de 5 años,
- además, hay una lista de módulos «valla» (fence) que necesitan un orden relativo específico (como calibración de color que necesita ir absolutamente después de perfil de color de entrada), pero no hay manera de imponerlo, ni manera de arreglarlo cuando ocurren órdenes incorrectos, solo mensajes de error silenciosos en la consola.
Combina eso con las máscaras ráster, donde el módulo que reutiliza una máscara ráster necesita absolutamente situarse más tarde en el pipe que el módulo que la produce, y donde, por cierto, puede que quieras copiar el productor junto con el consumidor, o al menos recibir un aviso si no lo haces… y tienes la receta para la locura.
En la práctica, necesitarás abrir cada imagen y comprobar la pila de módulos en el cuarto oscuro, lo cual es angustiosamente lento.
Como Darktable se define por una mala gestión de prioridades, muchos aspectos cosméticos han recibido mucho trabajo: te dejará editar tus fotos con mandos de PlayStation, está a punto de conseguir funciones de enmascarado por aprendizaje profundo, pero aún así se las arregla para joder los fundamentos básicos, sin ninguna mejora en 6 años pese a la aparente actividad en el proyecto.
El problema (TL;DR)
Anteriormente, el pegado de historial intentaba fusionar los módulos uno a uno sobre la marcha. Eso funcionaba en casos sencillos pero se volvía poco fiable cuando:
- la imagen de origen tenía un orden de pipeline distinto,
- había múltiples instancias del mismo módulo,
- o intervenían máscaras y fusión.
Como la fusión no resolvía el problema de ordenación completo, el resultado podía depender de muchas cosas. En algunos casos, el pipeline final podía acabar siendo inconsistente con la pila del historial.
La solución
Implementé un algoritmo de ordenación topológica en C. O más exactamente, lo hizo ChatGPT y yo lo revisé (más sobre esto abajo). 304 líneas de código, lo cual, para C, es muy poco.
Ahora tratamos el orden del pipeline como un conjunto de restricciones que debe resolverse de forma global, no módulo a módulo. En la práctica, esto significa:
- Resolvemos primero la topología del pipeline, y el historial de revelado (parámetros de los módulos) al final. Dos pasos claros que hacen posible el manejo de errores.
- La fusión calcula un único orden de pipeline válido que satisface tanto el origen como el destino siempre que sea posible, sin importar cuántas instancias de módulos haya ni cómo estén ordenadas.
- Si las restricciones son incompatibles, Ansel puede explicar el conflicto y preguntar qué orden preservar.
- Si un conflicto no tiene solución, Ansel lo informa con claridad en lugar de producir un pipeline roto o inestable, y te da opciones para arreglarlo.
- También se ponen restricciones sobre los módulos que usan y consumen máscaras ráster, y se emite un aviso si se copia un usuario de máscara ráster sin el productor.
- Se ponen restricciones codificadas en duro sobre los módulos que se requieren mutuamente antes, por razones técnicas (reconstrucción de luces antes del interpolación cromática, perfil de color de entrada antes de calibración de color, etc.). Esas restricciones se manejan igual que las demás y se resuelven todas juntas (sin caso especial).
En cuanto a la segunda parte, el historial propiamente dicho, hay que aclarar una cosa aquí. El historial de Darktable y Ansel es muy parecido a una lista de deshacer/rehacer de instantáneas de parámetros de un módulo: cada elemento del historial está vinculado a un módulo, y representa su estado interno de parámetros y máscaras. Al cargar el historial en los nodos del pipeline (los nodos son los filtros de píxeles asociados a los «módulos» que ves en la interfaz), leemos el historial de abajo hacia arriba y copiamos cada elemento/instantánea en los módulos, lo que significa que las instantáneas posteriores siempre anulan a las anteriores.
Es decir, el historial está ordenado por el momento de modificación del usuario (de nuevo… piensa en una lista de deshacer/rehacer de instantáneas), no por el orden de los nodos del pipeline. Pero, donde se vuelve confuso es que los elementos del historial también almacenan la posición en el pipeline de un módulo, lo que significa que reordenar módulos deja elementos de historial. Eso confundió a los desarrolladores de Darktable, y mucho más a los usuarios. Así que separemos ambos conceptualmente, tanto en nuestras mentes como en el software.
La topología (el orden de los nodos del pipeline) es independiente del historial. A diferencia de Darktable, que gestiona todo a través de los elementos del historial (incluido el orden del pipeline), lo cual es activamente perjudicial tanto para la comprensión como para la complejidad del código (que van de la mano de todas formas), nosotros resolvemos el pipeline como una colección independiente de nodos (módulos), y luego reemparejamos los nodos con su última etapa de historial. Me llevó un par de años ver a través de toda la abrumadora ofuscación que hay en este software, enterrada en código copiado y pegado, y ver la luz: una vez abstraído, el problema es bastante fácil.
Así que, una vez resuelta la topología, nos quedan 3 modos de fusión de historial:
- reemplazar: el historial de origen reemplaza todo el destino; los módulos obligatorios como el interpolación cromática para imágenes RAW aún podrían añadirse encima (de modo que copiar/pegar sea seguro entre JPEG y RAW). Este modo no conlleva ninguna ordenación topológica, es una copia directa del historial y del orden del pipeline.
- añadir encima (append): el historial de origen va encima del destino, de modo que los elementos del historial que apuntan a los mismos módulos en origen y destino son anulados por el origen,
- añadir debajo (appstart): el historial de origen va debajo del destino, de modo que los elementos del historial que apuntan a los mismos módulos en origen y destino son anulados por el destino.
El orden del pipeline resuelto por la ordenación topológica se actualiza en los últimos elementos del historial, tanto en el modo append como en appstart, lo que significa que retroceder en el historial también revertirá la ordenación topológica. Los historiales no se comprimen a propósito al fusionar, para que los usuarios conserven la capacidad de revertir la fusión, ya sea usando las funciones de deshacer/rehacer, o retrocediendo en la caja de herramientas del historial, en el cuarto oscuro, hasta antes del punto de fusión.
En resumen: el pegado de historial ahora es determinista, seguro, no destructivo, incluso para ediciones complejas.
Cómo usarlo
Habrá un historial de origen (el que copias) y un historial de destino (donde pegas). Lo mismo se aplicará con los estilos cuando se reimplementen; el historial de origen quedará definido por el estilo en lugar de por otra imagen, y el resto será igual.
En el menú global Editar → Modo de pegado del historial, puedes elegir entre append, appstart o replace. La configuración es global para toda la aplicación. Eso determina qué historial (origen o destino) tiene prioridad al anular los módulos comunes.
En Editar → Modo de pegado de nodos, puedes activar/desactivar Copiar el orden de los módulos. Si está desactivado, el orden del pipeline del destino se mantiene tal cual. Si está activado, hacemos todo lo posible por importar el orden del pipeline de origen al destino.
Como antes, en el menú Editar, tienes las opciones de copiar/pegar todo, o solo los módulos seleccionados (a través de la ventana modal). Hay atajos globales disponibles y editables por el usuario. Ten en cuenta que copiar y pegar historiales está explícitamente prohibido en la vista de cuarto oscuro, incluso desde la tira de película, porque es ambiguo determinar si quieres copiar entre miniaturas, de una miniatura a la imagen principal o al revés. En la mesa de luz, seleccionas el origen, copias, seleccionas el destino, pegas, y todo queda claro.
Ahora bien, hay que tener en cuenta una suposición importante: los módulos que tienen el mismo nombre de instancia (el número de instancia por defecto, o un nombre definido por el usuario) se consideran la misma entidad en los historiales de destino y de origen. Así, cada Exposición (cielo) se fusionará con cualquier otro módulo Exposición (cielo) (distinguiendo mayúsculas y minúsculas), y solo debería haber una instancia Exposición (cielo) en los historiales de destino y de origen. Anteriormente, el código usaba números de instancia, lo cual es más frágil porque los impone el software y se incrementan en el orden de creación, que no tiene ningún significado para los usuarios.
Interfaz y manejo de errores
La belleza de la nueva solución es que no tienes que abrir el cuarto oscuro para ver el desastre que creaste al copiar y pegar basura; puedes revisarlo antes de que se haga ningún daño a tus ediciones, en la mesa de luz. Además, cuando el solucionador no consigue encontrar una solución, lo que ocurre con restricciones incompatibles (A debería ir precedido por B, pero B debería ir precedido por A) o ciclos (más abajo), es capaz de decir qué falla, informarlo, y o bien solicitar la intervención del usuario para arreglarlo o recurrir al camino más sensato. Déjame que te lo enseñe:
Ciclos triviales
Exposición 1 va antes de Exposición en el historial de origen, pero después en el historial de destino. El conjunto de restricciones acaba con Exposición → Exposición 1 → Exposición, lo cual es inviable. Esto es lo que ocurre en Ansel:

Estos ciclos triviales que implican a vecinos inmediatos se detectan antes de resolver, de modo que no interrumpen el flujo de control.
Ciclos no triviales
Esos ciclos no triviales implican a varios módulos y no pueden detectarse antes de intentar resolver el grafo dirigido. Cuando eso ocurre:

En este caso, no hay nada que hacer: reintentaremos automáticamente usando el orden de destino, ya que suele ocurrir al intentar fusionar el orden de origen en el destino.
Máscaras ráster olvidadas
Cualquier módulo que use una máscara ráster debería copiarse junto con su módulo productor de la máscara, a menos que planees resolver eso tú mismo más tarde. Por si acaso es un error, si lo intentas:

Tienes la oportunidad de abortar la fusión ahora mismo si no era lo que querías.
La interfaz del informe de fusión (nueva)
Fundamentos
El diálogo del informe está diseñado para responder a una simple pregunta del usuario: «¿qué le pasó exactamente a mi pipeline?»
Muestra cuatro pipelines uno al lado del otro:
- Original (el pipeline de destino antes de la fusión),
- Origen (la imagen desde la que copiaste),
- Anulación (donde las ediciones de origen reemplazaron a las de destino),
- Destino (el pipeline final tras la fusión).

Cada columna lista las instancias de módulos activas, en orden de interfaz. Esta vista incluye marcadores adicionales:
- Los corchetes
[name]indican módulos que se insertaron de nuevo. - Un asterisco
*indica módulos que usan máscaras. - Una etiqueta en negrita indica módulos cuya posición relativa cambió entre origen y destino.
- Las flechas de anulación muestran dónde el historial de origen reemplazó realmente a las ediciones de destino (con
→*cuando también se anularon las máscaras).
Golosinas
La columna de destino es reordenable con arrastrar y soltar, lo que significa que si no estás contento con el resultado de la ordenación topológica, puedes arreglarlo tú mismo ahora mismo, antes incluso de que se guarde en tu base de datos y XMP, y sin tener que abrir el cuarto oscuro. Esto te permite ajustar manualmente el pipeline final antes de aceptarlo, o revertir todo y no volver a escribir el historial.
Cuando reordenas:
- el orden del pipeline se actualiza de inmediato,
- las entradas del historial se mantienen consistentes con el nuevo orden,
- y la vista del informe actualiza sus etiquetas y marcadores de «movido» en consecuencia.
Esto está pensado como una válvula de seguridad: aunque el orden calculado sea válido, sigues teniendo una manera sencilla de afinarlo.
Por qué esto importa
Esto mejora directamente los flujos de trabajo que implican edición por lotes con pipelines complejos:
- copiar ediciones entre imágenes,
- mezclar orígenes/destinos RAW y JPEG,
- y ediciones pesadas multi-instancia o basadas en máscaras.
El objetivo es hacer que el pegado de historial sea predecible, incluso cuando los pipelines subyacentes difieren. Esa fiabilidad es especialmente importante para las ediciones avanzadas, donde pequeñas diferencias de orden pueden cambiar los resultados.
Este cambio no añade nuevas funciones llamativas: hace que una de las funciones más usadas sea fiable. Las fusiones de historial ahora se comportan como los usuarios esperan: resultados consistentes, informes claros y recursos seguros cuando las restricciones entran en conflicto.
Y no entiendo por qué, 6 años después, los tipos del «It Works For Me®» que estrellan Darktable a cámara lenta no consideraron mejorar una función tan básica pero tan crítica. Si eso no grita prioridades equivocadas, no sé qué lo hará.
Qué lo hizo posible
Quiero recalcar aquí que toda esta reescritura fue posible porque primero reescribí casi por completo el backend de gestión de historiales en Ansel, ya que era un desastre:
- Había funciones duplicadas por todas partes, que realizaban la misma operación muchas veces pero ocultas en funciones que llamaban/eran llamadas por todo el software, algunas induciendo E/S del sistema de archivos (escritura de XMP) sin motivo, una que reescribía el historial cada vez que abríamos el cuarto oscuro (lo que jodía la marca de tiempo del último cambio),
- Había varios bloqueos de hilo entrelazados que básicamente hacían imposible cualquier cambio sin provocar interbloqueos,
- Había código de obtención de historial de SQLite3 enredado dentro del código C, muchas consultas SQL duplicadas, ninguna de ellas segura para hilos (porque SQLite3 en sí no es seguro para hilos), así que enterré todo el código SQL dentro de una interfaz en C que gestiona la seguridad de hilos de forma centralizada, y ahora todo el código C obtiene la información del historial de la base de datos de la biblioteca con una única API, lo que significa que sabemos que todo lo que lee el historial lo leerá igual en todas partes de la aplicación,
- Algunas partes de la lectura, inicialización y fusión del historial se hacían en SQL (aprovechando las sentencias
JOIN, lo cual tiene sentido, pero…), y algunas otras se hacían en C (porque las comprobaciones de seguridad de los módulos y la inicialización de los preajustes son obviamente C). Eso llevaba a cosas estúpidas como reindexar manualmente los elementos del historial en C antes de guardarlos en la base de datos entre escrituras transitorias (porque SQLite3 no garantiza que los elementos del historial se guarden en la base de datos en el mismo orden en que se pasaron… para eso están las claves primarias). Así que reescribí todo en C, lo que puede ser algo más lento pero garantiza la consistencia de los datos: los historiales se manejan exactamente de la misma manera, ya sea que los carguemos para fusionarlos, que abramos el cuarto oscuro o que exportemos una imagen. Si hay un fallo en algún sitio, estará en todas partes y lo encontraremos antes, además de que lo arreglaremos en un solo lugar. - El código de gestión de historiales también estaba enredado con el código de la interfaz, pero también puede ejecutarse desde
ansel-cli(sin interfaz), lo que llevaba a muchas heurísticas que comprobaban si teníamos interfaz o no, en muchos sitios.
Así que, una vez hecho todo ese trabajo de conserjería, empecé a ver la estructura de lo que realmente se hacía y necesitaba hacerse. A partir de ahí, una simplificación llevó a otra, hasta que ChatGPT 5.2 Codex hizo el resto. Hasta hace 2 semanas, eso aún se hacía enteramente a mano y me volvió loco muchas veces. Es realmente solo pintura lo que sostenía esas paredes, pintura descascarada, e intentar limpiarla destruía muchas cosas porque nada en este software era modular (es decir, encerrado). Algo que cambias en un sitio tiene consecuencias inesperadas en otro, que es por lo que tenemos encapsulación, modularidad y patrones de diseño, porque el lenguaje de programación C no fue diseñado para aplicaciones de escritorio complejas como esta, y realmente necesita la disciplina del desarrollador para evitar convertirse en la pesadilla que es.
Esto está enteramente vibecodeado
Así que la limpieza del historial venía sucediendo desde 2023, gestionando el agotamiento y la depresión inducida por el software. Es una calidad de vida de mierda, no tienes ni idea. Los que piensan que exagero no saben lo que supone palear las heces cerebrales de otras personas durante más de 3 años. Porque yo conocí una época en la que todo aquello era, si no mejor, al menos menos complicado y más manejable. Hasta que golpeó la locura de la COVID-19, y los idiotas tuvieron demasiado tiempo libre entre manos, que usaron para destruir algo que funcionaba a grandes rasgos.
Y entonces descubrí ChatGPT 5.2 Codex hace 2 semanas, y lo instalé dentro del editor VS Code. Así que me llevó 3 días de trabajo hacer todo lo que he presentado aquí. Sin ChatGPT, habrían sido unas 3 semanas de las buenas, más el interminable trasteo con los detallitos de GTK. Hablemos de la experiencia.
Estoy en desacuerdo con quienes intentan hacernos creer que la IA generativa es simplemente una herramienta. Una herramienta funciona solo en mi mano. No mientras duermo. He hecho que ChatGPT trabaje para mí mientras yo cocinaba la cena (sí, es así de lento). No te comunicas con una herramienta, simplemente la usas lo mejor que puedes. En caso de fallo, bueno, algunos culpan a la herramienta, pero todos sabemos lo que eso significa. El problema es que ChatGPT no tiene botones ni deslizadores, interpreta lo que le dices, y no necesariamente como lo quieres decir. Y además una herramienta no toma iniciativa. Pues bien, ChatGPT seguro que tiene una opinión sobre cómo debería verse el código, y a veces hay que pelearse con él.
La IA generativa es un becario. Un becario no tiene experiencia y solo sabe lo que se enseña en la escuela. Un becario puede aportar ideas frescas y nuevas que desafían tus costumbres, y sugerencias delirantes por igual, que no son ni remotamente relevantes para tu contexto y a veces ni siquiera factibles. Pero un becario necesita trabajar bajo supervisión estrecha y recibir instrucciones claras y no ambiguas. ChatGPT es mucho más un becario que una herramienta.
ChatGPT comete muchos errores, y son sigilosos porque están enterrados en medio de cosas perfectamente válidas. Tiene algunas obsesiones raras (como comprobar si es NULL cada puntero que ya sabemos que no puede ser NULL). Así que realmente hay que vigilarlo. Aunque revisar y corregir sus errores sigue siendo más rápido que escribir todo el código yo mismo, por no mencionar que mi primer problema de síndrome del túnel carpiano fue hace 10 años, así que siempre es un poco menos que no teclear. Además, comete errores de lógica, pero ningún error tipográfico, y al menos muchos menos que yo mismo.
Pero donde ChatGPT Codex brilla es en 2 cosas.
Primero, el tedioso juego de hacer grep de funciones por todo el código base para averiguar (aplicar ingeniería inversa a) el ciclo de vida de los datos y comprobar todos los puntos de llamada para construir un modelo mental de lo que está pasando. Eso lleva una eternidad, es muy exigente cognitivamente, especialmente en un código base tan malo. ChatGPT hace maravillas para recorrer docenas de archivos, extraer patrones, averiguar qué podría factorizarse y seguir secuencias de ejecución. Dejemos muy claro que, en un código base bien mantenido, eso no debería ser una necesidad porque el código estaría autocontenido en módulos, aislado del resto. Pero ChatGPT ayudó mucho a hacer las cosas más modulares.
Segundo, todo lo relacionado con GTK y GLib. Están mal documentados en la web, y muchos patrones idiomáticos de interacción solo los conocen los desarrolladores de GTK. ChatGPT obviamente ha ingerido montones de código de código abierto y puede producir código de interfaz repetitivo mucho mejor que yo (o que lo que me apetece). En cualquier caso, antes de ChatGPT, eso se convertía en tediosas sesiones de buscar información en Google, y no encuentro ninguna información técnica relevante en Google desde 2020 más o menos, cuando cambiaron sus algoritmos para cuestionar agresivamente todo. Pero yo trabajo para resolver problemas, y todas esas funciones repetitivas de la interfaz que inicializan widgets y sus propiedades en un estilo declarativo no son dignas de mi inteligencia, solo intentan no introducir errores tipográficos.
Pero para que te hagas una mejor idea, aquí está el tipo de prompts que tuve que darle para construir lo que acabo de presentar:
ahora, en _hm_try_merge_iop_order_topologically(), construye al principio de la función una GHashtable de todos los IDs de módulos ligados a mod_list, luego a dev_src->iop, y luego a dev_dest->iop. Estas serán útiles para calcular la intersección de conjuntos más tarde. no modifiques dev_dest->iop_order_list. Para todo elemento de la lista ordenada (siendo el elemento un ID de nodo ligado a un op de módulo y a multi_name):
- averigua si existe una instancia de módulo correspondiente en dev_dest->iop, si no créala. Como dev_dest->iop ya está inicializado y saneado aguas arriba, podemos asumir con seguridad que todo módulo no encontrado debería insertarse como una nueva instancia. Si el ID de la instancia de módulo se encuentra en la mod_list de entrada, todo el contenido del módulo (parámetros, blendop, etc.) debería copiarse de la instancia de origen a la instancia de destino. Ten en cuenta las copias profundas que deben ocurrir.
- sobrescribe todos los valores module->iop_order con el nuevo número de índice que acabamos de encontrar al resolver
- reconstruye dev_dest->iop_order_list desde cero y actualiza el module->multi_priority en consecuencia
ahora, en dt_history_merge_module_list_into_image_advanced, el historial temporal necesita construirse de la siguiente manera:
- desimplementa el camino force_new_modules por ahora, volveremos a él más tarde y de otra forma,
- construye un historial temporal de la siguiente manera: para cada módulo en mod_list:
- obtén el elemento de historial asociado de dev_src->history (que sería el último que coincide con este módulo en la pila del historial),
- obtén la información de ordenación del pipeline (iop_order, instance, multi_priority) del módulo correspondiente en dev_dest->iop
- actualiza el elemento de historial existente de dev_src->history con la ordenación del pipeline, ya que puede haber cambiado tras la ordenación topológica, respecto al elemento de historial original,
- añade esta entrada de historial al historial temporal
- concatena el historial temporal con dev_dest->history, al principio o al final según el modo append o appstart.
Intenta usar los métodos de history.c y dev_history.c tanto como sea posible para el manejo de historial hacia/desde módulo. Extiende los existentes si solo necesitas cambios menores.
No, revierte eso. En general no está bien borrar entradas de historial más allá del history_end. Lo que esté en dev->history debería ir a la BD. Además, no es un problema porque el history_end también se guarda en la BD. El problema aquí es que se añaden elementos de historial aleatorios al releer el historial desde la BD. Todo hasta la escritura del historial, que ocurría en C, estaba bien. Averigua por qué obtenemos entradas de historial adicionales al releer desde la BD, comparado con lo que tenemos en el momento de la escritura antes.
al final de dt_history_merge en history_merge.c, quiero que muestres una ventana emergente de informe. Una etiqueta de texto dirá primero “Copiar, fusionando el pipeline en modo {MERGE_MODE} y el historial en modo {STRATEGY}”, donde {MERGE_MODE} depende de merge_iop_order (merge o destination), y {STRATEGY} depende de strategy. Luego quiero un GtkTreeView en modo lista, con 3 columnas:
- el origen de la copia, con el ID de imagen y el nombre de archivo (no la ruta completa),
- la anulación,
- el destino de la copia, con el ID de imagen y el nombre de archivo.
En las columnas 1. y 3., cada fila mostrará las instancias de módulos, empezando por su orden en el pipeline, module->name y module->multi_name. Solo se mostrarán los módulos activados. La columna 2 dibujará una flecha entre las instancias de origen y destino cuando el historial de origen anule al de destino. Esto se hace comprobando, en el historial de destino, si la última entrada que apunta a este módulo coincide con el historial de destino o con el de origen. En caso de que coincida con ambos, no muestres nada ya que no es una anulación. Los nodos del pipeline se mostrarán en orden inverso para coincidir con el orden de la interfaz, ya que es una especie de pila de capas. Ambos deberían estar alineados por abajo para que los pasos iniciales tengan la posibilidad de estar en la misma fila hasta que la topología diverja entre ambos pipes
Una cosa que descubrí es que definitivamente puedes ser demasiado específico con ChatGPT y llevarlo a un callejón sin salida. Cuando eso pasa, el mejor curso de acción es tomar el control manualmente.
El coste energético de esa cosa es insoportable, pero digamos que, dividido entre los aproximadamente 900 tipos que le dieron estrella a Ansel en Github (no tengo estadísticas de descargas), es por el bien común. Simplemente es una manera más eficiente de ahorrar mi jugo cerebral para pensar en qué debería hacerse (diseño y arquitectura), en lugar de cómo hacerlo. Probablemente no sea como vibecodean los chavales hoy en día, eso sí.
Lo siguiente: los estilos.
Translated from English by : ChatGPT, Claude. In case of conflict, inconsistency or error, the English version shall prevail.