This commit is contained in:
fy59 2026-08-07 07:23:57 +02:00
parent 44e8e0892e
commit 4622aa3b93
18 changed files with 1579 additions and 77 deletions

View file

@ -1,7 +1,7 @@
--- ---
description: Explore rapidement fichiers, symboles, appels et dépendances description: Explore rapidement fichiers, symboles, appels et dépendances
mode: subagent mode: subagent
model: opencode/north-mini-code-free model: opencode/mimo-v2.5-free
temperature: 0.1 temperature: 0.1
permission: permission:
edit: deny edit: deny
@ -16,3 +16,39 @@ Travaille en lecture seule. Localise exactement les fichiers, symboles,
appelants, dépendances et documents strictement utiles. Ne lis pas toute appelants, dépendances et documents strictement utiles. Ne lis pas toute
l'architecture par défaut, ne propose pas de réécriture et retourne uniquement l'architecture par défaut, ne propose pas de réécriture et retourne uniquement
les chemins, conclusions et risques synthétiques. les chemins, conclusions et risques synthétiques.
Tu reçois obligatoirement une liste de symboles, modules ou chemins précis.
Si la demande ne fournit aucun élément précis, refuse lexploration et demande
au parent de préciser la cible.
Interdictions absolues :
- pwd ;
- ls ;
- find ;
- glob global ;
- **/*.c ;
- **/*.h ;
- inventaire du dépôt ;
- lecture de toute la documentation.
Utilise uniquement des recherches ciblées avec rg sur les chemins ou symboles
fournis.
Lis au maximum :
- cinq fichiers source ou headers ;
- deux fichiers de tests ;
- deux documents darchitecture.
Retourne seulement :
- fichiers pertinents ;
- symboles pertinents ;
- dépendances directes ;
- risques ;
- informations encore manquantes.
Ne propose aucune nouvelle fonctionnalité.
Ne modifie aucun fichier.

View file

@ -1,7 +1,7 @@
--- ---
description: Orchestre les tickets avec un contexte minimal et sans écrire les sources description: Orchestre les tickets avec un contexte minimal et sans écrire les sources
mode: primary mode: primary
model: opencode/north-mini-code-free model: opencode/mimo-v2.5-free
temperature: 0.1 temperature: 0.1
permission: permission:
edit: edit:
@ -25,8 +25,66 @@ permission:
"lardon-docs": allow "lardon-docs": allow
--- ---
Orchestre sans modifier les sources. Charge AGENTS.md et l'overview déjà Le dépôt courant est déjà la racine de Lardon3D.
injectés, puis utilise explore pour cibler le contexte. N'appelle que les agents
pertinents, sans sous-agent imbriqué. Transmets au seul implémenteur un résumé Ne lance jamais de commande de découverte globale :
court : objectif, contraintes, fichiers, API, invariants et tests. Tiens le
handoff concis à jour après chaque phase. Aucun modèle payant, commit ou push. - `pwd`
- `ls` ou `ls -la`
- `find` sur tout le dépôt
- glob `*`
- inventaire complet des fichiers
- lecture automatique de toute la documentation
AGENTS.md et loverview sont déjà injectés comme instructions projet. Ne les relis
pas intégralement sauf si une information précise manque.
Pour chaque ticket :
1. Lire `.opencode/work/current_ticket.md` sil existe.
2. Identifier uniquement les modules, symboles et fichiers liés au ticket.
3. Utiliser `lardon-explore` avec une requête ciblée.
4. Charger uniquement les documents darchitecture directement pertinents.
5. Appeler seulement les agents nécessaires.
Tout ticket touchant au moins un des éléments suivants exige obligatoirement
`lardon-concurrency` :
- `task`
- `task_queue`
- scheduler
- `resource_governor`
- pthread
- mutex
- variable de condition
- pause ou reprise
- annulation
- réservation
- état ou durée de vie partagés entre threads
Cette règle sapplique même si le ticket ne crée aucun nouveau thread.
6. Transmettre au seul agent implémenteur un résumé court contenant :
- objectif ;
- contraintes ;
- fichiers concernés ;
- API et invariants ;
- tests requis.
7. Mettre à jour le handoff après chaque phase importante.
Le répertoire `.opencode/work/` est préparé localement. Ne vérifie pas son
existence avec Bash. Crée ou mets à jour directement
`.opencode/work/current_ticket.md` avec loutil dédition autorisé.
Ne modifie jamais les sources toi-même.
Nutilise jamais :
- de modèle payant ;
- de sous-agent imbriqué ;
- plusieurs agents décriture simultanément ;
- `git commit` ;
- `git push`.
Les rapports intermédiaires doivent être courts et ne contenir que les
conclusions, risques, fichiers concernés, tests et prochaines actions.

View file

@ -1,7 +1,7 @@
--- ---
description: Exécute les validations Lardon3D sans modifier le code description: Exécute les validations Lardon3D sans modifier le code
mode: subagent mode: subagent
model: opencode/north-mini-code-free model: opencode/mimo-v2.5-free
temperature: 0.1 temperature: 0.1
permission: permission:
edit: deny edit: deny

View file

@ -1,14 +1,125 @@
--- ---
description: Planifier en lecture seule avec un contexte minimal description: Planifier un ticket en lecture seule avec un contexte minimal
agent: lardon-orchestrator agent: lardon-orchestrator
subtask: false subtask: false
--- ---
Planifie ce ticket sans modifier les sources : $ARGUMENTS Planifie le ticket suivant sans modifier les sources :
Lis AGENTS.md et l'overview déjà chargés. Demande à lardon-explore uniquement $ARGUMENTS
les modules concernés. Liste les fichiers et documents à charger, propose un plan
court et indique les agents réellement nécessaires. Écris dans Le contexte permanent est déjà injecté depuis `.opencode/context.md`.
`.opencode/work/current_ticket.md` : titre, objectif, contraintes, fichiers,
documents utiles, plan, travail terminé/restant, fichiers modifiés, commandes, Pour cette commande :
tests passés/restants, décisions, erreurs, modèle et prochaine action sûre.
- n'appelle aucun sous-agent ;
- ne lance aucune commande Bash ;
- ne modifie aucun fichier sauf
`.opencode/work/current_ticket.md`.
Ne lance jamais :
- `pwd`
- `ls`
- `find`
- glob
- inventaire du dépôt
- compilation
- tests
Réponds uniquement à partir :
- du contexte permanent ;
- du texte du ticket ;
- de `.opencode/work/current_ticket.md` s'il existe et concerne déjà ce ticket.
Si une information manque, indique simplement les deux ou trois fichiers qui
devront être lus lors d'une future exploration ciblée.
Ne tente jamais de les découvrir toi-même.
Choisis un seul ticket.
Ne fusionne jamais plusieurs tickets.
Ne propose jamais :
- de métrique ;
- de compteur ;
- de diagnostic ;
- de nouvelle API ;
- d'état TUI ;
- de fonctionnalité annexe ;
- de refactoring non demandé.
Le plan doit uniquement couvrir le périmètre demandé.
Dans la section **Agents nécessaires** :
- n'ajoute que les agents réellement utiles ;
- si le ticket touche `task`, `task_queue`, le scheduler,
`resource_governor`, pthread, mutex, variables de condition,
pause, reprise, annulation, réservations ou tout état partagé,
inclure obligatoirement `lardon-concurrency`,
même si aucun nouveau thread n'est créé.
Écris `.opencode/work/current_ticket.md`
une seule fois avec exactement cette structure :
Le handoff représente uniquement le ticket courant.
Si un nouveau ticket est demandé,
remplacer entièrement `.opencode/work/current_ticket.md`.
Ne jamais conserver plusieurs tickets simultanément.
Ne jamais demander quel ticket utiliser.
# Ticket
## Objectif
## Contraintes
## Fichiers probablement concernés
## Documents nécessaires
## Plan
## Agents nécessaires
## Agents non nécessaires
## Tests futurs
## Risques
## Prochaine action sûre
Contraintes :
- une seule occurrence de chaque rubrique ;
- aucun doublon ;
- aucun code inventé ;
- aucune API inventée ;
- aucune fonctionnalité hors périmètre ;
- aucune modification incrémentale répétée ;
- 80 lignes maximum.
Après l'écriture :
1. relire le fichier une seule fois ;
2. vérifier uniquement :
- structure ;
- absence de doublons ;
- cohérence ;
3. terminer immédiatement.
Ne fais aucun commit.
Ne fais aucun push.
N'utilise aucun modèle payant.
Ne modifie jamais `scan3d/`.

View file

@ -1,13 +1,104 @@
--- ---
description: Reprise rapide du handoff avec Nemotron description: Reprendre rapidement un ticket avec Nemotron à partir du handoff
agent: lardon-orchestrator agent: lardon-orchestrator
subtask: false subtask: false
--- ---
Reprends le ticket avec lardon-build-backup. Lis uniquement le handoff, le diff Reprends le ticket suivant :
et les fichiers qu'ils citent. Ne refais ni exploration ni architecture si les
informations suffisent. Après l'implémentation, utilise lardon-build-light en $ARGUMENTS
lecture seule pour une revue locale simple. N'appelle ni lardon-review ni
lardon-concurrency : signale l'absence de revue indépendante forte et diffère Le contexte permanent de Lardon3D est déjà chargé via
toute revue de concurrence sensible jusqu'au prochain passage Codex. Mets le `.opencode/context.md`.
handoff à jour avec la prochaine action sûre.
Commence toujours par :
1. lire `.opencode/work/current_ticket.md` ;
2. lire le diff courant.
Ne recharge jamais :
- AGENTS.md ;
- l'overview ;
- la documentation générale ;
- les documents d'architecture.
Ne relance jamais `lardon-explore` ou `lardon-architect` si le handoff contient
déjà :
- l'objectif ;
- les contraintes ;
- les fichiers concernés ;
- les invariants ;
- les validations réalisées ;
- le travail restant.
Ne lance une exploration supplémentaire que si une information indispensable est
absente.
Transmets à `lardon-build-backup` uniquement :
- l'objectif ;
- les contraintes ;
- les fichiers concernés ;
- les API concernées ;
- les invariants ;
- le travail restant ;
- la prochaine action sûre.
Ne renvoie jamais tout le handoff au modèle d'implémentation.
Après l'implémentation :
1. appeler `lardon-tests` une seule fois ;
2. utiliser `lardon-build-light` uniquement pour une première revue locale
simple ;
3. mettre à jour le handoff.
Ne pas appeler :
- `lardon-review` ;
- `lardon-concurrency` ;
car ils utilisent le même modèle que l'implémentation de secours.
Si le ticket touche notamment :
- pthread ;
- mutex ;
- condition variables ;
- scheduler ;
- task_queue ;
- resource_governor ;
- réservations ;
- synchronisation ;
signaler explicitement :
- qu'aucune revue indépendante forte n'a été réalisée ;
- que la revue de concurrence est différée au prochain passage Codex.
Le handoff doit rester court et contenir uniquement :
- objectif ;
- contraintes ;
- fichiers concernés ;
- travail effectué ;
- validations exécutées ;
- erreurs rencontrées ;
- limites restantes ;
- prochaine action sûre.
Règles :
- un seul agent écrit les sources ;
- aucun sous-agent imbriqué ;
- aucun modèle payant ;
- aucun commit ;
- aucun push ;
- aucune modification de `scan3d/`.
Objectif principal :
Reprendre un ticket en quelques secondes avec le plus petit contexte possible,
sans refaire l'analyse déjà présente dans le handoff.

View file

@ -4,11 +4,72 @@ agent: lardon-orchestrator
subtask: false subtask: false
--- ---
Traite ce petit ticket : $ARGUMENTS Traite le petit ticket suivant :
Utilise lardon-explore pour cibler les fichiers, puis un seul lardon-build-light $ARGUMENTS
pour modifier. N'appelle architect, concurrency ou docs que si leur besoin est
avéré ; si le ticket est sensible, arrête et recommande le workflow normal. Le contexte permanent de Lardon3D est déjà chargé via `.opencode/context.md`.
Appelle ensuite lardon-tests pour compilation, tests ciblés et
`git diff --check`, puis une revue seulement si le diff le justifie. Maintiens Ne relis AGENTS.md, loverview ou la documentation générale que si une information
le handoff concis. Aucun commit, push ou fallback payant. précise manque.
Ce workflow est réservé aux modifications locales, simples et clairement
délimitées.
Neffectue jamais :
- dinventaire global du dépôt ;
- de `glob "*"` ;
- de `find` sur tout le dépôt ;
- de lecture complète de la documentation ;
- de refactoring darchitecture non demandé.
Procédure :
1. Lire `.opencode/work/current_ticket.md` sil existe et sil concerne ce ticket.
2. Utiliser `lardon-explore` avec une recherche strictement ciblée.
3. Identifier uniquement :
- les fichiers à modifier ;
- les dépendances directes ;
- les tests concernés ;
- les éventuels documents à mettre à jour.
4. Vérifier que le ticket convient au mode léger.
5. Utiliser un seul `lardon-build-light` pour limplémentation.
6. Appeler `lardon-tests` une seule fois après limplémentation pour :
- compiler avec Clang ;
- exécuter les tests ciblés ;
- exécuter `git diff --check`.
7. Demander une revue uniquement si le diff le justifie réellement.
8. Mettre à jour le handoff avec un résumé concis.
Nappelle pas `lardon-architect`, `lardon-concurrency` ou `lardon-docs` sauf si
leur intervention est manifestement nécessaire.
Arrête le workflow léger et recommande `/lardon-ticket` ou Codex si le ticket
touche notamment :
- une API publique structurante ;
- `task`, `task_queue` ou `resource_governor` ;
- pthread, mutex ou condition variables ;
- la durée de vie complexe de ressources ;
- la persistance ou le rollback ;
- le scheduler, un futur DAG ou les réservations ;
- un refactoring réparti sur plusieurs modules ;
- une fondation sensible du projet.
Dans ce cas, ne tente aucune modification risquée avec MiMo.
Le handoff doit rester court et contenir seulement :
- objectif ;
- fichiers concernés ;
- travail effectué ;
- validations ;
- erreurs ;
- limites ;
- prochaine action sûre.
Ne fais aucun commit.
Ne fais aucun push.
Nutilise aucun modèle payant.
Ne modifie jamais `scan3d/`.

View file

@ -1,15 +1,102 @@
--- ---
description: Reprendre un ticket gratuit avec Nemotron après indisponibilité DeepSeek description: Reprendre un ticket après indisponibilité de DeepSeek
agent: lardon-orchestrator agent: lardon-orchestrator
subtask: false subtask: false
--- ---
Lis d'abord `.opencode/work/current_ticket.md` et le diff. Si le handoff suffit, Reprends le ticket suivant :
ne refais pas l'exploration. Transmets seulement l'objectif, les fichiers, les
invariants, le travail restant et la prochaine action à lardon-build-backup. $ARGUMENTS
Après reprise, appelle lardon-tests puis lardon-build-light en lecture seule pour
une première revue locale simple. N'appelle ni lardon-review ni Le contexte permanent de Lardon3D est déjà chargé via
lardon-concurrency, car ils utilisent le même modèle que l'implémenteur. Si le `.opencode/context.md`.
diff touche la concurrence, réserve sa revue au prochain passage Codex et
signale explicitement l'absence de revue indépendante forte. Mets à jour le Commence toujours par :
handoff après chaque phase. Aucun modèle payant.
1. lire `.opencode/work/current_ticket.md` ;
2. lire le diff courant.
Si ces deux éléments décrivent correctement le ticket, ne relis pas :
- AGENTS.md ;
- l'overview ;
- la documentation générale ;
- les documents d'architecture.
Ne relance pas `lardon-explore` si le handoff contient déjà :
- l'objectif ;
- les contraintes ;
- les fichiers concernés ;
- les invariants ;
- le travail restant.
Relance `lardon-explore` uniquement lorsqu'une information indispensable est
absente.
Transmets à `lardon-build-backup` uniquement :
- l'objectif ;
- les contraintes ;
- les fichiers concernés ;
- les API concernées ;
- les invariants ;
- le travail restant ;
- la prochaine action sûre.
Ne renvoie jamais tout le handoff au modèle d'implémentation.
Après l'implémentation :
1. appeler `lardon-tests` une seule fois ;
2. utiliser `lardon-build-light` uniquement pour une première revue locale
simple ;
3. mettre à jour le handoff.
Ne pas appeler :
- `lardon-review` ;
- `lardon-concurrency` ;
car ils utilisent le même modèle que l'implémentation de secours.
Si le ticket touche notamment :
- pthread ;
- mutex ;
- condition variables ;
- scheduler ;
- task_queue ;
- resource_governor ;
- réservations ;
- synchronisation ;
signaler explicitement :
- qu'aucune revue indépendante forte n'a été réalisée ;
- que la revue de concurrence est différée au prochain passage Codex.
Le handoff doit rester court et contenir uniquement :
- objectif ;
- contraintes ;
- fichiers concernés ;
- travail effectué ;
- validations exécutées ;
- erreurs rencontrées ;
- limites restantes ;
- prochaine action sûre.
Règles :
- un seul agent écrit les sources ;
- aucun sous-agent imbriqué ;
- aucun modèle payant ;
- aucun commit ;
- aucun push ;
- aucune modification de `scan3d/`.
Objectif principal :
Reprendre un ticket en quelques secondes, avec le plus petit contexte possible,
sans refaire les analyses déjà présentes dans le handoff.

View file

@ -1,19 +1,83 @@
--- ---
description: Traiter un ticket gratuit avec contexte progressif et handoff description: Traiter un ticket complet avec contexte progressif et handoff
agent: lardon-orchestrator agent: lardon-orchestrator
subtask: false subtask: false
--- ---
Traite ce ticket : $ARGUMENTS Traite le ticket suivant :
1. Initialise `.opencode/work/current_ticket.md` avec les rubriques prescrites. $ARGUMENTS
2. Demande à lardon-explore les seuls fichiers et documents utiles.
3. Charge uniquement ce contexte. Appelle lardon-architect seulement pour une
abstraction importante, puis transmets un résumé concis à lardon-build.
4. Après l'implémentation, appelle lardon-tests une fois, puis lardon-review une
fois. Ajoute lardon-concurrency uniquement pour du code concurrent.
5. Mets le handoff à jour après chaque phase avec conclusions, commandes,
erreurs et prochaine action, sans documentation, sources ou logs complets.
Un seul agent écrit les sources. Aucun sous-agent imbriqué, commit, push, Le contexte permanent de Lardon3D est déjà chargé via
fallback payant ou modification de scan3d/. `.opencode/context.md`.
Considère également AGENTS.md et le handoff courant comme déjà disponibles.
Ne les relis que lorsqu'une information précise manque.
N'effectue jamais :
- d'inventaire global du dépôt ;
- de `glob "*"` ;
- de `find` sur tout le dépôt ;
- de lecture complète de toute la documentation.
Procédure :
1. Initialiser `.opencode/work/current_ticket.md` avec les rubriques prescrites.
2. Utiliser `lardon-explore` avec une recherche strictement ciblée.
3. Identifier uniquement :
- les fichiers à modifier ;
- les dépendances directes ;
- les tests concernés ;
- les documents réellement utiles.
4. Charger uniquement ce contexte.
5. Appeler `lardon-architect` uniquement si une évolution d'architecture est
réellement nécessaire.
6. Transmettre à `lardon-build` un résumé concis contenant :
- objectif ;
- contraintes ;
- API concernées ;
- invariants ;
- fichiers ;
- validations attendues.
7. Après l'implémentation :
- appeler `lardon-tests` une seule fois ;
- appeler `lardon-review` une seule fois ;
- appeler `lardon-concurrency` uniquement si le ticket touche :
- pthread ;
- mutex ;
- condition variables ;
- scheduler ;
- task_queue ;
- resource_governor ;
- code concurrent.
8. Mettre à jour le handoff après chaque phase importante.
Le handoff doit rester court et contenir uniquement :
- objectif ;
- contraintes ;
- fichiers concernés ;
- travail effectué ;
- validations exécutées ;
- erreurs rencontrées ;
- limites restantes ;
- prochaine action sûre.
Règles :
- un seul agent écrit les sources ;
- aucun sous-agent imbriqué ;
- aucun agent inutile ;
- aucun modèle payant ;
- aucun commit ;
- aucun push ;
- aucune modification de `scan3d/`.
Privilégier systématiquement :
- le plus petit contexte possible ;
- le moins d'agents possible ;
- une seule compilation complète ;
- une seule revue ;
- une seule analyse de concurrence lorsque nécessaire.

341
.opencode/context.md Normal file
View file

@ -0,0 +1,341 @@
# Lardon3D Persistent Context
## Project
Lardon3D est un moteur de photogrammétrie Linux écrit en C17.
Objectifs principaux :
- stabilité absolue ;
- faible consommation mémoire ;
- traitement par lots adaptatifs ;
- aucune saturation du système ;
- TUI toujours réactive ;
- architecture modulaire.
Le viewer Vulkan sera un processus séparé lisant uniquement des snapshots validés.
---
## Build
Compiler avec :
CC=clang meson setup build --wipe
meson compile -C build -j8
Validation minimale :
meson test -C build --print-errorlogs
git diff --check
Utiliser ASan/UBSan et TSan uniquement lorsque le ticket touche la mémoire ou la concurrence.
---
## Git
Interdictions :
- jamais git add -A
- jamais git commit
- jamais git push
- jamais modifier scan3d/
- jamais modifier tri_photos.py
Toujours fournir :
- liste exacte des fichiers du ticket
- validations exécutées
- limites restantes
- message de commit recommandé
---
## Architecture
Séparation stricte :
TUI
Scheduler
Governor
Workers
Résultats atomiques
Le thread ncurses ne fait jamais de travail métier.
Le scheduler ne décide jamais des ressources.
Le gouverneur est le seul propriétaire des budgets.
---
## Resource Governor
Estimate
Reservation
Scheduler
Worker
Les réservations sont obligatoires avant toute exécution.
RAM iGPU partagée = RAM système.
La zram n'est jamais un budget de travail.
---
## Coding rules
C17 uniquement.
Pas de variables globales d'état.
Pas de system().
Pas de popen().
Pas d'allocation infinie.
Toujours des buffers bornés.
Sorties atomiques.
Rollback complet en cas d'échec.
---
## Current architecture
Modules disponibles :
- project
- import
- import_task
- image_catalog
- image_view
- task
- task_queue
- hardware_profile
- resource_snapshot
- resource_governor
---
## Active workflow
1. explorer uniquement les fichiers utiles
2. architect uniquement si nécessaire
3. un seul agent d'écriture
4. tests
5. revue
6. handoff
Ne jamais relire tout le dépôt.
Ne jamais faire de glob "*".
Ne jamais parcourir tous les fichiers.
---
## Models
Primary orchestration
MiMo V2.5 Free
Implementation
DeepSeek V4 Flash Free
Backup implementation
Nemotron 3 Ultra Free
Exploration
North Mini Code Free
Architecture / Review / Concurrency
Nemotron 3 Ultra Free
Documentation
Ling 3.0 Flash Free
Aucun modèle payant.
---
## If DeepSeek fails
Reprendre depuis :
.opencode/work/current_ticket.md
Ne jamais refaire :
- exploration
- architecture
- revue
si le handoff contient déjà ces informations.
---
## Project roadmap
Fondations terminées :
- TUI modulaire
- projets persistants
- import asynchrone
- catalogue d'images
- image_view
- moteur de tâches
- scheduler FIFO
- gouverneur de ressources
- réservations
- intégration scheduler ↔ governor
- sélection de la première tâche admissible
- documentation d'architecture
Ordre obligatoire des prochains tickets :
1. séquences de traitement adaptatives pilotées par le Resource Governor
2. contre-pression (backpressure) et bornage des files
3. checkpoints et persistance des tâches
4. DAG minimal de dépendances
5. priorités
6. pools CPU / GPU / IO
7. migration de l'import vers le scheduler générique
8. publication incrémentale
9. viewer Vulkan
Ne jamais proposer un ticket déjà terminé.
Ne jamais sauter une étape de cette feuille de route sans justification explicite.
---
## Mission
Toujours privilégier :
- stabilité
- simplicité
- déterminisme
- faible mémoire
- traitements séquencés
- protection du système
avant les performances brutes.
État actuel important :
- Le scheduler sait déjà sauter une tâche en WAIT en tête de file.
- Il sélectionne la première tâche admissible dans lordre FIFO.
- Ce ticket est terminé et ne doit pas être reproposé.
---
## Revue de concurrence obligatoire
Tout ticket touchant `task`, `task_queue`, le scheduler,
`resource_governor`, pthread, mutex, variables de condition, pause, reprise,
annulation, réservations ou états partagés exige `lardon-concurrency`.
Cette règle sapplique même si aucun nouveau thread nest créé.
---
## Design philosophy
Chaque étape du pipeline doit être :
- indépendante ;
- interruptible ;
- reprenable ;
- observable ;
- testable ;
- déterministe.
Chaque tâche doit pouvoir être découpée en petits lots afin de :
- limiter l'utilisation mémoire ;
- garder une TUI fluide ;
- permettre une adaptation dynamique des ressources ;
- éviter toute saturation du système.
La stabilité du système hôte est prioritaire sur les performances brutes.
---
## Long-term objective
Lardon3D doit devenir un moteur de photogrammétrie entièrement piloté par un
scheduler adaptatif.
Le scheduler décide uniquement :
- quand lancer une étape ;
- avec quel lot ;
- dans quel ordre.
Le Resource Governor décide uniquement :
- si les ressources permettent l'exécution ;
- la taille optimale des lots ;
- quand différer une tâche.
Le viewer Vulkan ne participe jamais aux calculs.
Il affiche uniquement des snapshots validés produits par le pipeline.
L'utilisateur doit voir le résultat apparaître progressivement pendant les
calculs, sans attendre la fin complète de la reconstruction.
Toutes les étapes doivent pouvoir être interrompues puis reprises sans perdre
les résultats déjà validés.
---
## Optimisation
Privilégier toujours :
- plusieurs petits traitements ;
- plusieurs petites allocations ;
- plusieurs petites copies ;
plutôt qu'une grosse opération monolithique.
Lardon3D doit être optimisé pour des machines disposant de peu de mémoire
(16 à 32 Go) et d'un iGPU partagé.

View file

@ -1,10 +1,11 @@
{ {
"$schema": "https://opencode.ai/config.json", "$schema": "https://opencode.ai/config.json",
"default_agent": "lardon-orchestrator", "default_agent": "lardon-orchestrator",
"model": "opencode/north-mini-code-free", "model": "opencode/mimo-v2.5-free",
"subagent_depth": 1, "subagent_depth": 1,
"instructions": [ "instructions": [
"AGENTS.md", "AGENTS.md",
".opencode/context.md",
"docs/architecture/overview.md" "docs/architecture/overview.md"
], ],
"permission": { "permission": {

View file

@ -44,10 +44,47 @@ pendant `PAUSED`. Ses ressources restent donc indisponibles pour les autres
tâches. Ce choix évite de reprendre un callback avec un contrat qui aurait été tâches. Ce choix évite de reprendre un callback avec un contrat qui aurait été
attribué entre-temps à un autre travail. attribué entre-temps à un autre travail.
## Exécution séquencée adaptative
Une tâche peut s'exécuter en plusieurs séquences (lots) successives. Le
callback appelle `lardon3d_task_sequence_break()` pour libérer sa réservation
courante, capturer un nouvel instantané système, obtenir une nouvelle
réservation auprès du gouverneur et reprendre avec un contrat actualisé.
Le cycle d'une séquence est strict :
```text
Callback
→ sequence_break
→ Release Reservation courante
→ Snapshot système
→ Governor → Reservation
→ Contrat actualisé
→ Reprise du callback
```
Le task conserve une référence au gouverneur et à sa réservation courante.
`lardon3d_task_start` stocke la réservation initiale ; à la fin de l'exécution,
il libère la réservation courante si elle est encore détenue. Le worker de la
file libère ensuite la réservation d'origine, qui est déjà libérée si une
séquence a eu lieu : cet appel est alors sans effet.
Invariants préservés :
- Aucun callback ne démarre sans réservation active.
- Une seule réservation active par tâche à tout instant.
- La réservation est toujours valide avant et après un `sequence_break`.
- Le contrat est mis à jour atomiquement sous le mutex de la tâche.
- Si pause ou annulation est demandée pendant le `sequence_break`, la fonction
retourne `false` et la tâche est annulée.
- Si le gouverneur répond `WAIT` ou `REJECT`, la fonction retourne `false` et
la tâche passe en échec sans bloquer le callback.
- La progression est conservée entre les séquences.
## Limites et extensions ## Limites et extensions
La file possède un seul worker, reste strictement FIFO et ne gère ni priorité La file possède un seul worker, reste strictement FIFO et ne gère ni priorité
ni dépendance. Une notification explicite est nécessaire lorsqu'un composant ni dépendance. Une notification explicite est nécessaire lorsqu'un composant
extérieur libère une réservation. Les prochaines étapes pourront ajouter un extérieur libère une réservation. Les prochaines étapes pourront ajouter un
DAG, des priorités, des séquences de lots adaptatives et des pools distincts DAG, des priorités, des pools distincts CPU, IO et GPU sans déplacer les
CPU, IO et GPU sans déplacer les décisions de ressources hors du gouverneur. décisions de ressources hors du gouverneur.

View file

@ -15,12 +15,11 @@ entrée, sortie et cache :
recommandé `opencode/nemotron-3-ultra-free` en cas de saturation 503. recommandé `opencode/nemotron-3-ultra-free` en cas de saturation 503.
- `opencode/nemotron-3-ultra-free` : architecture, concurrence, revue et repli - `opencode/nemotron-3-ultra-free` : architecture, concurrence, revue et repli
de l'agent principal. de l'agent principal.
- `opencode/north-mini-code-free` : exploration rapide et validations. - `opencode/mimo-v2.5-free` : exploration rapide et validations.
- `opencode/laguna-s-2.1-free` : testé comme reviewer sur Lardon3D ; résultats - `opencode/laguna-s-2.1-free` : testé comme reviewer sur Lardon3D ; résultats
jugés insuffisants. Non utilisé dans les rôles actifs. jugés insuffisants. Non utilisé dans les rôles actifs.
- `opencode/ling-3.0-flash-free` : documentation. - `opencode/ling-3.0-flash-free` : documentation.
- `opencode/longcat-2.0-free` : disponible, non configuré actuellement. - `opencode/longcat-2.0-free` : disponible, non configuré actuellement.
- `opencode/mimo-v2.5-free` : disponible, non configuré actuellement.
## Gratuité non confirmée ## Gratuité non confirmée
@ -63,24 +62,66 @@ OpenCode actuelle.
## Optimisation du quota ## Optimisation du quota
L'agent par défaut est `lardon-orchestrator` sur North Mini Code Free. Il charge Lagent par défaut est `lardon-orchestrator` sur MiMo V2.5 Free.
seulement `AGENTS.md` et l'overview, puis demande à North Mini Code une
exploration ciblée. DeepSeek est réservé à l'implémentation complexe ; il ne sert
ni à l'orchestration, ni aux tests, ni à la documentation. Nemotron traite les
reprises, revues et analyses d'architecture ou de concurrence, MiMo les petits
tickets et Ling la documentation.
Les commandes `lardon-plan`, `lardon-small`, `lardon-ticket` et Le contexte permanent est chargé depuis `.opencode/context.md`. Lorchestrateur
`lardon-ticket-backup` chargent progressivement le contexte. Elles ne lisent pas ne relit pas automatiquement AGENTS.md, loverview ni toute la documentation
automatiquement toute l'architecture et transmettent uniquement objectif, darchitecture. Il utilise uniquement le handoff courant et les documents
contraintes, fichiers, API, invariants, risques et tests. Les rapports de succès directement liés au ticket.
restent courts. Le handoff ignoré `.opencode/work/current_ticket.md` permet une
reprise sans rejouer toute l'analyse.
Nemotron ne relit jamais sa propre implémentation lorsqu'il a servi de backup. MiMo assure :
MiMo fournit alors seulement une première revue locale simple. La revue de
concurrence sensible est différée jusqu'au prochain passage Codex, et le workflow - lorchestration ;
signale explicitement qu'aucune revue indépendante forte n'a eu lieu. - lexploration ciblée ;
- les tests ;
- les petits tickets.
DeepSeek V4 Flash Free est réservé à limplémentation complexe. Il ne sert ni à
lorchestration, ni à lexploration, ni aux tests, ni à la documentation.
Nemotron 3 Ultra Free assure :
- les reprises après indisponibilité de DeepSeek ;
- larchitecture ;
- la concurrence ;
- la revue générale.
Ling 3.0 Flash Free est réservé à la documentation.
North Mini Code Free nest plus utilisé dans les rôles actifs. Il reste mentionné
dans linventaire historique comme modèle testé puis écarté en raison de résultats
jugés insuffisants et trop erratiques sur Lardon3D.
Les commandes `lardon-plan`, `lardon-small`, `lardon-ticket`,
`lardon-ticket-backup` et les commandes de reprise utilisent un contexte
progressif.
Elles ne lisent jamais automatiquement tout le dépôt ni toute larchitecture.
Elles transmettent uniquement :
- objectif ;
- contraintes ;
- fichiers concernés ;
- API ;
- invariants ;
- risques ;
- tests requis ;
- prochaine action sûre.
Les rapports de succès restent courts. Le handoff ignoré
`.opencode/work/current_ticket.md` permet une reprise sans rejouer lexploration
ou lanalyse déjà terminée.
Nemotron ne relit jamais sa propre implémentation lorsquil a servi de backup.
Dans ce cas :
- MiMo fournit seulement une première revue locale simple ;
- la revue de concurrence sensible est différée jusquau prochain passage Codex ;
- le workflow signale explicitement quaucune revue indépendante forte na eu
lieu.
Aucun modèle payant nest utilisé automatiquement.
## Chaîne de secours ## Chaîne de secours

View file

@ -81,6 +81,17 @@ bool lardon3d_task_execution_contract(
const Lardon3DTask *task, const Lardon3DTask *task,
Lardon3DTaskExecutionContract *contract Lardon3DTaskExecutionContract *contract
); );
/* Libère la réservation courante, en obtient une nouvelle auprès du gouverneur
* et met à jour le contrat. À appeler uniquement depuis le callback en cours
* d'exécution. Retourne false si pause/annulation est demandée ou si le
* gouverneur répond WAIT/REJECT (la tâche passe alors en échec). */
bool lardon3d_task_sequence_break(
Lardon3DTask *task,
Lardon3DResourceGovernor *governor,
Lardon3DResourceReservation **out_reservation,
Lardon3DTaskExecutionContract *out_contract
);
unsigned int lardon3d_task_sequence_count(const Lardon3DTask *task);
bool lardon3d_task_reject(Lardon3DTask *task, const char *message); bool lardon3d_task_reject(Lardon3DTask *task, const char *message);
const char *lardon3d_task_state_name(Lardon3DTaskState state); const char *lardon3d_task_state_name(Lardon3DTaskState state);

View file

@ -111,6 +111,20 @@ task_test = executable(
test('task', task_test, timeout: 30) test('task', task_test, timeout: 30)
sequential_task_test = executable(
'test-sequential-task',
sources: [
'tests/test_sequential_task.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
],
include_directories: include_directories('include'),
dependencies: [threads],
)
test('sequential-task', sequential_task_test, timeout: 30)
task_queue_test = executable( task_queue_test = executable(
'test-task-queue', 'test-task-queue',
sources: [ sources: [

View file

@ -23,6 +23,9 @@ struct Lardon3DTask {
bool pause_requested; bool pause_requested;
bool cancel_requested; bool cancel_requested;
bool executing; bool executing;
Lardon3DResourceGovernor *governor;
Lardon3DResourceReservation *current_reservation;
unsigned int sequence_count;
}; };
static bool static bool
@ -125,6 +128,8 @@ lardon3d_task_start(
return false; return false;
} }
task->executing = true; task->executing = true;
task->governor = governor;
task->current_reservation = (Lardon3DResourceReservation *)reservation;
task->contract = (Lardon3DTaskExecutionContract) { task->contract = (Lardon3DTaskExecutionContract) {
.batch_size = information.batch_size, .batch_size = information.batch_size,
.memory_bytes = information.memory_bytes, .memory_bytes = information.memory_bytes,
@ -143,6 +148,7 @@ lardon3d_task_start(
while (task->pause_requested && !task->cancel_requested) { while (task->pause_requested && !task->cancel_requested) {
task->state = TASK_PAUSED; task->state = TASK_PAUSED;
copy_text(task->message, sizeof(task->message), "Tâche en pause."); copy_text(task->message, sizeof(task->message), "Tâche en pause.");
(void)pthread_cond_broadcast(&task->condition);
(void)pthread_cond_wait(&task->condition, &task->mutex); (void)pthread_cond_wait(&task->condition, &task->mutex);
} }
if (task->cancel_requested) { if (task->cancel_requested) {
@ -169,6 +175,13 @@ lardon3d_task_start(
finish_locked(task, TASK_COMPLETED, "Tâche terminée."); finish_locked(task, TASK_COMPLETED, "Tâche terminée.");
} }
(void)pthread_mutex_unlock(&task->mutex); (void)pthread_mutex_unlock(&task->mutex);
if (task->current_reservation) {
(void)lardon3d_resource_governor_release(
governor,
task->current_reservation
);
task->current_reservation = NULL;
}
return true; return true;
} }
@ -265,6 +278,146 @@ lardon3d_task_checkpoint(Lardon3DTask *task)
return continuing; return continuing;
} }
bool
lardon3d_task_sequence_break(
Lardon3DTask *task,
Lardon3DResourceGovernor *governor,
Lardon3DResourceReservation **out_reservation,
Lardon3DTaskExecutionContract *out_contract
)
{
if (!task || !governor || !out_reservation || !out_contract) {
return false;
}
*out_reservation = NULL;
(void)pthread_mutex_lock(&task->mutex);
if (!task->executing || is_terminal(task->state)) {
(void)pthread_mutex_unlock(&task->mutex);
return false;
}
while (task->pause_requested && !task->cancel_requested) {
task->state = TASK_PAUSED;
copy_text(task->message, sizeof(task->message), "Tâche en pause.");
(void)pthread_cond_broadcast(&task->condition);
(void)pthread_cond_wait(&task->condition, &task->mutex);
}
if (task->cancel_requested) {
(void)pthread_mutex_unlock(&task->mutex);
return false;
}
task->state = TASK_RUNNING;
Lardon3DResourceReservation *previous = task->current_reservation;
task->current_reservation = NULL;
(void)pthread_mutex_unlock(&task->mutex);
if (previous) {
(void)lardon3d_resource_governor_release(governor, previous);
}
Lardon3DResourceDecision decision;
Lardon3DResourceReservation *next = NULL;
bool reserved = lardon3d_resource_governor_reserve_available(
governor,
&task->estimate,
&decision,
&next
);
(void)pthread_mutex_lock(&task->mutex);
if (task->cancel_requested) {
if (next) {
(void)lardon3d_resource_governor_release(governor, next);
}
finish_locked(
task,
TASK_CANCELLED,
"Tâche annulée pendant sequence_break."
);
(void)pthread_mutex_unlock(&task->mutex);
return false;
}
while (task->pause_requested && !task->cancel_requested) {
task->state = TASK_PAUSED;
copy_text(task->message, sizeof(task->message), "Tâche en pause.");
(void)pthread_cond_broadcast(&task->condition);
(void)pthread_cond_wait(&task->condition, &task->mutex);
}
if (task->cancel_requested) {
if (next) {
(void)lardon3d_resource_governor_release(governor, next);
}
finish_locked(task, TASK_CANCELLED, "Tâche annulée.");
(void)pthread_mutex_unlock(&task->mutex);
return false;
}
if (!reserved || !next) {
task->current_reservation = NULL;
finish_locked(
task,
TASK_FAILED,
"Impossible de réserver les ressources de la séquence."
);
(void)pthread_mutex_unlock(&task->mutex);
return false;
}
if (decision.kind == LARDON3D_RESOURCE_WAIT
|| decision.kind == LARDON3D_RESOURCE_REJECT) {
task->current_reservation = NULL;
finish_locked(
task,
TASK_FAILED,
decision.reason[0] ? decision.reason
: "Ressources impossibles à réserver."
);
(void)pthread_mutex_unlock(&task->mutex);
return false;
}
Lardon3DResourceReservationInfo information;
if (!lardon3d_resource_reservation_get_active(
governor,
next,
&information
)) {
(void)lardon3d_resource_governor_release(governor, next);
task->current_reservation = NULL;
finish_locked(
task,
TASK_FAILED,
"Réservation de séquence invalide."
);
(void)pthread_mutex_unlock(&task->mutex);
return false;
}
task->current_reservation = next;
task->contract = (Lardon3DTaskExecutionContract) {
.batch_size = information.batch_size,
.memory_bytes = information.memory_bytes,
.gpu_memory_bytes = information.gpu_memory_bytes,
.cpu_threads = information.cpu_threads,
.gpu_slots = information.gpu_slots,
.io_slots = information.io_slots,
};
task->has_contract = true;
++task->sequence_count;
*out_reservation = next;
*out_contract = task->contract;
(void)pthread_mutex_unlock(&task->mutex);
return true;
}
unsigned int
lardon3d_task_sequence_count(const Lardon3DTask *task)
{
if (!task) {
return 0;
}
Lardon3DTask *mutable_task = (Lardon3DTask *)task;
(void)pthread_mutex_lock(&mutable_task->mutex);
unsigned int count = task->sequence_count;
(void)pthread_mutex_unlock(&mutable_task->mutex);
return count;
}
bool bool
lardon3d_task_set_progress( lardon3d_task_set_progress(
Lardon3DTask *task, Lardon3DTask *task,

View file

@ -139,6 +139,10 @@ queue_worker(void *context)
"Réservation de ressources invalide." "Réservation de ressources invalide."
); );
} }
/* La tâche peut avoir libéré et re-réservé via sequence_break pendant
* son callback. Dans ce cas la réservation d'origine est déjà libérée
* et cet appel est sans effet ; la réservation courante de la tâche a
* é libérée par lardon3d_task_start. */
(void)lardon3d_resource_governor_release( (void)lardon3d_resource_governor_release(
queue->governor, queue->governor,
reservation reservation

View file

@ -0,0 +1,392 @@
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <lardon3d/task.h>
#define CHECK(condition) \
do { \
if (!(condition)) { \
(void)fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \
return false; \
} \
} while (0)
typedef struct {
Lardon3DResourceGovernor *governor;
Lardon3DResourceReservation *initial_reservation;
unsigned int expected_breaks;
unsigned int breaks_done;
size_t batch_sizes[8];
Lardon3DTaskExecutionContract initial_contract;
bool got_initial_contract;
} SeqContext;
typedef struct {
Lardon3DResourceGovernor *governor;
} WaitContext;
typedef struct {
Lardon3DResourceGovernor *governor;
pthread_mutex_t sync_mutex;
pthread_cond_t sync_cond;
bool about_to_break;
bool cancel_ready;
} CancelContext;
typedef struct {
Lardon3DTask *task;
Lardon3DResourceGovernor *governor;
Lardon3DResourceReservation *reservation;
} StartContext;
static void
short_pause(long nanoseconds)
{
struct timespec duration = {.tv_sec = 0, .tv_nsec = nanoseconds};
(void)nanosleep(&duration, NULL);
}
static bool
seq_callback(Lardon3DTask *task, void *userdata)
{
SeqContext *ctx = userdata;
if (!lardon3d_task_execution_contract(task, &ctx->initial_contract)) {
return false;
}
ctx->got_initial_contract = true;
Lardon3DResourceReservation *previous = ctx->initial_reservation;
for (unsigned int index = 0; index < ctx->expected_breaks; ++index) {
if (!lardon3d_task_checkpoint(task)) {
return false;
}
/* Vérifier que la réservation précédente est encore valide AVANT le break */
if (previous && !lardon3d_resource_governor_reservation_is_valid(
ctx->governor,
previous
)) {
return false;
}
Lardon3DResourceReservation *reservation = NULL;
Lardon3DTaskExecutionContract contract;
if (!lardon3d_task_sequence_break(
task,
ctx->governor,
&reservation,
&contract
)) {
return false;
}
if (!lardon3d_resource_governor_reservation_is_valid(
ctx->governor,
reservation
)) {
return false;
}
ctx->batch_sizes[index] = contract.batch_size;
previous = reservation;
++ctx->breaks_done;
unsigned int progress = (unsigned int)(
((index + 1) * 100) / ctx->expected_breaks
);
if (!lardon3d_task_set_progress(task, progress, "Séquence.")) {
return false;
}
short_pause(2000000);
}
return true;
}
static bool
wait_callback(Lardon3DTask *task, void *userdata)
{
WaitContext *ctx = userdata;
Lardon3DResourceReservation *reservation = NULL;
Lardon3DTaskExecutionContract contract;
return lardon3d_task_sequence_break(
task,
ctx->governor,
&reservation,
&contract
);
}
static bool
cancel_callback(Lardon3DTask *task, void *userdata)
{
CancelContext *ctx = userdata;
if (!lardon3d_task_checkpoint(task)) {
return false;
}
(void)pthread_mutex_lock(&ctx->sync_mutex);
ctx->about_to_break = true;
(void)pthread_cond_broadcast(&ctx->sync_cond);
while (!ctx->cancel_ready) {
(void)pthread_cond_wait(&ctx->sync_cond, &ctx->sync_mutex);
}
(void)pthread_mutex_unlock(&ctx->sync_mutex);
Lardon3DResourceReservation *reservation = NULL;
Lardon3DTaskExecutionContract contract;
return lardon3d_task_sequence_break(
task,
ctx->governor,
&reservation,
&contract
);
}
static void *
start_task(void *context)
{
StartContext *start = context;
return (void *)(uintptr_t)(lardon3d_task_start(
start->task,
start->governor,
start->reservation
) ? 1 : 0);
}
static bool
wait_for_state(Lardon3DTask *task, Lardon3DTaskState expected)
{
for (size_t attempt = 0; attempt < 5000; ++attempt) {
Lardon3DTaskSnapshot snapshot;
if (!lardon3d_task_snapshot(task, &snapshot)) {
return false;
}
if (snapshot.state == expected) {
return true;
}
short_pause(1000000);
}
return false;
}
static bool
run_sequential_test(void)
{
const Lardon3DResourceEstimate estimate = {
.memory_bytes_per_item = 1000,
.minimum_batch_size = 1,
.maximum_batch_size = 1000,
.desired_cpu_threads = 1,
};
Lardon3DHardwareProfile profile = {
.logical_cpu_count = 16,
.page_size_bytes = 4096,
.memory_total_bytes = UINT64_MAX,
.cpu_architecture = "test",
};
Lardon3DResourcePolicy policy = {
.maximum_cpu_load_ratio = 1.0,
.maximum_io_pressure_avg10 = 100.0,
.io_slot_capacity = 1,
};
Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create(
&profile,
&policy
);
CHECK(governor);
Lardon3DResourceSnapshot resource_snapshot = {
.memory_available_bytes = 20000,
.cpu_load_1m = 0.0,
};
Lardon3DResourceDecision decision;
Lardon3DResourceReservation *reservation;
CHECK(lardon3d_resource_governor_reserve(
governor,
&resource_snapshot,
&estimate,
&decision,
&reservation
));
CHECK(decision.kind == LARDON3D_RESOURCE_START
|| decision.kind == LARDON3D_RESOURCE_REDUCE_BATCH);
CHECK(decision.batch_size == 20);
SeqContext ctx = {
.governor = governor,
.initial_reservation = reservation,
.expected_breaks = 3,
};
Lardon3DTask *task = lardon3d_task_create(
"Séquences",
&estimate,
seq_callback,
&ctx
);
CHECK(task);
StartContext start = {task, governor, reservation};
pthread_t thread;
CHECK(pthread_create(&thread, NULL, start_task, &start) == 0);
CHECK(wait_for_state(task, TASK_RUNNING));
CHECK(lardon3d_task_join(task));
void *thread_result;
CHECK(pthread_join(thread, &thread_result) == 0);
CHECK((uintptr_t)thread_result == 1);
CHECK(ctx.got_initial_contract);
CHECK(ctx.breaks_done == 3);
CHECK(ctx.initial_contract.batch_size == 20);
CHECK(ctx.batch_sizes[0] == 1000);
CHECK(ctx.batch_sizes[1] == 1000);
CHECK(ctx.batch_sizes[2] == 1000);
CHECK(ctx.batch_sizes[0] != ctx.initial_contract.batch_size);
CHECK(lardon3d_task_sequence_count(task) == 3);
Lardon3DTaskSnapshot snapshot;
CHECK(lardon3d_task_snapshot(task, &snapshot));
CHECK(snapshot.state == TASK_COMPLETED);
CHECK(snapshot.progress == 100);
CHECK(lardon3d_resource_governor_reservation_count(governor) == 0);
lardon3d_task_destroy(task);
lardon3d_resource_governor_destroy(governor);
return true;
}
static bool
run_wait_test(void)
{
const Lardon3DResourceEstimate estimate = {
.minimum_batch_size = 1,
.maximum_batch_size = 100,
.desired_cpu_threads = 1,
.desired_io_slots = 1,
};
Lardon3DHardwareProfile profile = {
.logical_cpu_count = 16,
.page_size_bytes = 4096,
.memory_total_bytes = UINT64_MAX,
.cpu_architecture = "test",
};
Lardon3DResourcePolicy policy = {
.maximum_cpu_load_ratio = 1.0,
.maximum_io_pressure_avg10 = 0.0,
.io_slot_capacity = 1,
};
Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create(
&profile,
&policy
);
CHECK(governor);
Lardon3DResourceSnapshot resource_snapshot = {
.memory_available_bytes = UINT64_MAX,
.cpu_load_1m = 0.0,
.io_pressure_known = false,
};
Lardon3DResourceDecision decision;
Lardon3DResourceReservation *reservation;
CHECK(lardon3d_resource_governor_reserve(
governor,
&resource_snapshot,
&estimate,
&decision,
&reservation
));
WaitContext ctx = {.governor = governor};
Lardon3DTask *task = lardon3d_task_create(
"Attente",
&estimate,
wait_callback,
&ctx
);
CHECK(task);
CHECK(lardon3d_task_start(task, governor, reservation));
Lardon3DTaskSnapshot snapshot;
CHECK(lardon3d_task_snapshot(task, &snapshot));
CHECK(snapshot.state == TASK_FAILED);
CHECK(lardon3d_resource_governor_reservation_count(governor) == 0);
lardon3d_task_destroy(task);
lardon3d_resource_governor_destroy(governor);
return true;
}
static bool
run_cancel_test(void)
{
const Lardon3DResourceEstimate estimate = {
.minimum_batch_size = 1,
.maximum_batch_size = 100,
.desired_cpu_threads = 1,
};
Lardon3DHardwareProfile profile = {
.logical_cpu_count = 16,
.page_size_bytes = 4096,
.memory_total_bytes = UINT64_MAX,
.cpu_architecture = "test",
};
Lardon3DResourcePolicy policy = {
.maximum_cpu_load_ratio = 1.0,
.maximum_io_pressure_avg10 = 100.0,
.io_slot_capacity = 1,
};
Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create(
&profile,
&policy
);
CHECK(governor);
Lardon3DResourceSnapshot resource_snapshot = {
.memory_available_bytes = UINT64_MAX,
.cpu_load_1m = 0.0,
};
Lardon3DResourceDecision decision;
Lardon3DResourceReservation *reservation;
CHECK(lardon3d_resource_governor_reserve(
governor,
&resource_snapshot,
&estimate,
&decision,
&reservation
));
CancelContext ctx = {.governor = governor};
CHECK(pthread_mutex_init(&ctx.sync_mutex, NULL) == 0);
CHECK(pthread_cond_init(&ctx.sync_cond, NULL) == 0);
Lardon3DTask *task = lardon3d_task_create(
"Annulation",
&estimate,
cancel_callback,
&ctx
);
CHECK(task);
StartContext start = {task, governor, reservation};
pthread_t thread;
CHECK(pthread_create(&thread, NULL, start_task, &start) == 0);
(void)pthread_mutex_lock(&ctx.sync_mutex);
while (!ctx.about_to_break) {
(void)pthread_cond_wait(&ctx.sync_cond, &ctx.sync_mutex);
}
(void)pthread_mutex_unlock(&ctx.sync_mutex);
lardon3d_task_request_cancel(task);
(void)pthread_mutex_lock(&ctx.sync_mutex);
ctx.cancel_ready = true;
(void)pthread_cond_broadcast(&ctx.sync_cond);
(void)pthread_mutex_unlock(&ctx.sync_mutex);
CHECK(lardon3d_task_join(task));
CHECK(pthread_join(thread, NULL) == 0);
Lardon3DTaskSnapshot snapshot;
CHECK(lardon3d_task_snapshot(task, &snapshot));
CHECK(snapshot.state == TASK_CANCELLED);
CHECK(lardon3d_resource_governor_reservation_count(governor) == 0);
lardon3d_task_destroy(task);
(void)pthread_cond_destroy(&ctx.sync_cond);
(void)pthread_mutex_destroy(&ctx.sync_mutex);
lardon3d_resource_governor_destroy(governor);
return true;
}
int
main(void)
{
if (!run_sequential_test()) {
return EXIT_FAILURE;
}
if (!run_wait_test()) {
return EXIT_FAILURE;
}
if (!run_cancel_test()) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View file

@ -157,7 +157,7 @@ run_test(void)
void *thread_result; void *thread_result;
CHECK(pthread_join(thread, &thread_result) == 0); CHECK(pthread_join(thread, &thread_result) == 0);
CHECK((uintptr_t)thread_result == 1); CHECK((uintptr_t)thread_result == 1);
CHECK(lardon3d_resource_governor_release(governor, reservation)); CHECK(!lardon3d_resource_governor_release(governor, reservation));
CHECK(lardon3d_task_snapshot(task, &snapshot)); CHECK(lardon3d_task_snapshot(task, &snapshot));
CHECK(snapshot.state == TASK_COMPLETED); CHECK(snapshot.state == TASK_COMPLETED);
CHECK(snapshot.progress == 100); CHECK(snapshot.progress == 100);
@ -178,7 +178,7 @@ run_test(void)
lardon3d_task_request_cancel(task); lardon3d_task_request_cancel(task);
CHECK(lardon3d_task_join(task)); CHECK(lardon3d_task_join(task));
CHECK(pthread_join(thread, NULL) == 0); CHECK(pthread_join(thread, NULL) == 0);
CHECK(lardon3d_resource_governor_release(governor, reservation)); CHECK(!lardon3d_resource_governor_release(governor, reservation));
CHECK(lardon3d_task_snapshot(task, &snapshot)); CHECK(lardon3d_task_snapshot(task, &snapshot));
CHECK(snapshot.state == TASK_CANCELLED); CHECK(snapshot.state == TASK_CANCELLED);
CHECK(snapshot.progress < 100); CHECK(snapshot.progress < 100);
@ -190,7 +190,7 @@ run_test(void)
governor, &resource_snapshot, &estimate, &decision, &reservation governor, &resource_snapshot, &estimate, &decision, &reservation
)); ));
CHECK(lardon3d_task_start(task, governor, reservation)); CHECK(lardon3d_task_start(task, governor, reservation));
CHECK(lardon3d_resource_governor_release(governor, reservation)); CHECK(!lardon3d_resource_governor_release(governor, reservation));
CHECK(lardon3d_task_snapshot(task, &snapshot)); CHECK(lardon3d_task_snapshot(task, &snapshot));
CHECK(snapshot.state == TASK_FAILED); CHECK(snapshot.state == TASK_FAILED);
CHECK(strcmp(snapshot.message, "Erreur contrôlée.") == 0); CHECK(strcmp(snapshot.message, "Erreur contrôlée.") == 0);