feat(reconstruction): add durable track builder runtime
This commit is contained in:
parent
0d876cfbe1
commit
f1a139e3c1
21 changed files with 1239 additions and 82 deletions
|
|
@ -261,8 +261,9 @@ VERIFIED ou REJECTED est retournée avant toute lecture Feature/Match et sans ap
|
||||||
contrainte concurrente déclenche un unique `find` de l'identité, jamais un overwrite ou une
|
contrainte concurrente déclenche un unique `find` de l'identité, jamais un overwrite ou une
|
||||||
récursion. Changer un paramètre scientifique produit un autre fingerprint et un autre résultat.
|
récursion. Changer un paramètre scientifique produit un autre fingerprint et un autre résultat.
|
||||||
|
|
||||||
Les tests E2E utilisent le vrai Project DB (migré v13→v14 à l'ouverture), deux Feature Files à 8192 points, des Match Files
|
Les tests E2E utilisent le vrai Project DB (migré séquentiellement jusqu'à v15 à
|
||||||
hashés, le vrai MAGSAC et le Model v1. VERIFIED est rechargé après close/reopen avec modèle et
|
l'ouverture), deux Feature Files à 8192 points, des Match Files hashés, le vrai
|
||||||
|
MAGSAC et le Model v1. VERIFIED est rechargé après close/reopen avec modèle et
|
||||||
masque bit-identiques ; REJECTED conserve son support et est également réutilisé.
|
masque bit-identiques ; REJECTED conserve son support et est également réutilisé.
|
||||||
|
|
||||||
## Production algorithm
|
## Production algorithm
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
# Base de données projet Lardon3D
|
# Base de données projet Lardon3D
|
||||||
|
|
||||||
> Version courante : **v14**. La migration transactionnelle v13→v14 ajoute
|
> Version courante : **v15**. La migration transactionnelle v14→v15 ajoute
|
||||||
|
> `track_builder_tasks` pour le payload durable explicite du Task Builder. La
|
||||||
|
> migration transactionnelle v13→v14 ajoute
|
||||||
> les tables `track_sets`, `tracks` et `track_observations` pour le Track
|
> les tables `track_sets`, `tracks` et `track_observations` pour le Track
|
||||||
> Model v1. La migration v12→v13 ajoute uniquement `geometric_verifier_tasks`
|
> Model v1. La migration v12→v13 ajoute uniquement `geometric_verifier_tasks`
|
||||||
> pour la tâche durable. La migration v11→v12 ajoute le modèle immutable
|
> pour la tâche durable. La migration v11→v12 ajoute le modèle immutable
|
||||||
|
|
@ -548,23 +550,43 @@ tests. L'API C (`create_track_set`, `load_track_set`, `find_track_set`,
|
||||||
est exposée et implémentée. Les limites réelles restent l'absence de Track
|
est exposée et implémentée. Les limites réelles restent l'absence de Track
|
||||||
Builder algorithmique, de tâche dédiée et de triangulation.
|
Builder algorithmique, de tâche dédiée et de triangulation.
|
||||||
|
|
||||||
|
## Schéma v15 — payload durable Track Builder
|
||||||
|
|
||||||
|
La migration v14→v15 ajoute uniquement `track_builder_tasks`. Elle ne modifie
|
||||||
|
aucune table du Track Model et ne change aucune identité scientifique. La ligne
|
||||||
|
référence un fichier de scope atomiquement publié sous
|
||||||
|
`.lardon3d/checkpoints/<task_id>.scope` et conserve sa taille, son SHA-256,
|
||||||
|
son format, le sélecteur exact, le fingerprint Builder, `gvr_count` et
|
||||||
|
`input_scope_hash`. Le fichier contient `L3DTSCP1`, une version explicite, le
|
||||||
|
nombre d'IDs et des uint64 little-endian triés et uniques. Le contenu est
|
||||||
|
rejoué depuis le début après interruption ; aucun curseur ne peut perdre des
|
||||||
|
arêtes transitoires.
|
||||||
|
|
||||||
|
La reconstruction vérifie format, taille, checksum, bornes, tri, unicité,
|
||||||
|
sélecteur, fingerprint et L3DTSIS1 avant de créer un callback neuf. Une
|
||||||
|
corruption rend la tâche inexécutable sans créer de Track Set. Les tests
|
||||||
|
couvrent une vraie base v14, migration, rollback injecté, retry,
|
||||||
|
fermeture/réouverture et payload corrompu.
|
||||||
|
|
||||||
## Ouverture et migrations
|
## Ouverture et migrations
|
||||||
|
|
||||||
Une DB vide reçoit la chaîne de schémas jusqu'à v14 dans une transaction
|
Une DB vide reçoit la chaîne de schémas jusqu'à v15 dans une transaction
|
||||||
`BEGIN IMMEDIATE`. Une DB v1 reçoit transactionnellement les colonnes nullable
|
`BEGIN IMMEDIATE`. Une DB v1 reçoit transactionnellement les colonnes nullable
|
||||||
`task_kind` et `task_kind_version`, puis les migrations v2→v3. Les anciennes lignes restent
|
`task_kind` et `task_kind_version`, puis les migrations v2→v3. Les anciennes lignes restent
|
||||||
`NULL/NULL`, sans type inventé et sans perte des projets, tâches, checkpoints ou
|
`NULL/NULL`, sans type inventé et sans perte des projets, tâches, checkpoints ou
|
||||||
artefacts. Une interruption ou erreur provoque un rollback complet. Les DB v1
|
artefacts. Une interruption ou erreur provoque un rollback complet. Les DB v1
|
||||||
à v13 sont migrées séquentiellement vers v14.
|
à v14 sont migrées séquentiellement vers v15.
|
||||||
Une v10 publiée est validée comme telle avant que v10→v11 crée
|
Une v10 publiée est validée comme telle avant que v10→v11 crée
|
||||||
`matcher_tasks` ; son absence n'est donc pas une corruption. Une version future est refusée et une DB contenant
|
`matcher_tasks` ; son absence n'est donc pas une corruption. Une version future est refusée et une DB contenant
|
||||||
des tables sans métadonnée de version est considérée corrompue. La fonction
|
des tables sans métadonnée de version est considérée corrompue. La fonction
|
||||||
interne de migration applique uniquement la chaîne séquentielle connue jusqu'à
|
interne de migration applique uniquement la chaîne séquentielle connue jusqu'à
|
||||||
v14 ; une valeur hors de 1..14 est refusée. La failure injectée v12 rollbacke
|
v15 ; une valeur hors de 1..15 est refusée. La failure injectée v12 rollbacke
|
||||||
la table, l'index et le changement de version, laissant une vraie v11 utilisable.
|
la table, l'index et le changement de version, laissant une vraie v11 utilisable.
|
||||||
La failure injectée v13 conserve une vraie v12 sans `geometric_verifier_tasks` ;
|
La failure injectée v13 conserve une vraie v12 sans `geometric_verifier_tasks` ;
|
||||||
un retry applique ensuite v12→v13. La failure injectée v14 conserve une vraie
|
un retry applique ensuite v12→v13. La failure injectée v14 conserve une vraie
|
||||||
v13 sans `track_sets` ; un retry applique ensuite v13→v14.
|
v13 sans `track_sets` ; un retry applique ensuite v13→v14. La failure injectée
|
||||||
|
v15 conserve une vraie v14 sans `track_builder_tasks` ; un retry applique
|
||||||
|
ensuite v14→v15.
|
||||||
|
|
||||||
Migration v1→v2 exacte, exécutée entre `BEGIN IMMEDIATE` et `COMMIT` :
|
Migration v1→v2 exacte, exécutée entre `BEGIN IMMEDIATE` et `COMMIT` :
|
||||||
|
|
||||||
|
|
@ -701,8 +723,8 @@ ouvert.
|
||||||
|
|
||||||
## Statut
|
## Statut
|
||||||
|
|
||||||
**IMPLEMENTED** — SQLite système, schéma v14 et migrations séquentielles
|
**IMPLEMENTED** — SQLite système, schéma v15 et migrations séquentielles
|
||||||
v1→v2→v3→v4→v5→v6→v7→v8→v9→v10→v11→v12→v13→v14, identité projet, transactions
|
v1→v2→v3→v4→v5→v6→v7→v8→v9→v10→v11→v12→v13→v14→v15, identité projet, transactions
|
||||||
tâche+checkpoint, pagination de reprise et artefacts génériques.
|
tâche+checkpoint, pagination de reprise et artefacts génériques.
|
||||||
|
|
||||||
**IMPLEMENTED** — ouverture/fermeture avec le projet, identité INI/DB cohérente,
|
**IMPLEMENTED** — ouverture/fermeture avec le projet, identité INI/DB cohérente,
|
||||||
|
|
@ -733,6 +755,9 @@ cataloguées », pas « images migrées ».
|
||||||
tests de migration/failure validés. Le schéma complet et les invariants
|
tests de migration/failure validés. Le schéma complet et les invariants
|
||||||
sont documentés dans `tracks.md`.
|
sont documentés dans `tracks.md`.
|
||||||
|
|
||||||
|
**IMPLEMENTED** — Track Builder v1 durable : table `track_builder_tasks`, scope
|
||||||
|
asset atomique, payload v1 validé, migration v14→v15 et rollback/retry testés.
|
||||||
|
|
||||||
**IMPLEMENTED** — API C Track Model v1 : header `project_db.h` et
|
**IMPLEMENTED** — API C Track Model v1 : header `project_db.h` et
|
||||||
source `project_db.c` exposent `create_track_set`, `load_track_set`,
|
source `project_db.c` exposent `create_track_set`, `load_track_set`,
|
||||||
`find_track_set`, `list_track_sets`, `load_track`, `list_tracks`,
|
`find_track_set`, `list_track_sets`, `load_track`, `list_tracks`,
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ USAC/MAGSAC avec configuration, seed et fingerprint déterministes.
|
||||||
| **Persistance** | Les tracks sont persistés entre les sessions de traitement. Un track ne peut être détruit que par une action explicite de l'utilisateur. |
|
| **Persistance** | Les tracks sont persistés entre les sessions de traitement. Un track ne peut être détruit que par une action explicite de l'utilisateur. |
|
||||||
|
|
||||||
**Statut :** PARTIAL — le Track Model v1 (persistance) est implémenté dans
|
**Statut :** PARTIAL — le Track Model v1 (persistance) est implémenté dans
|
||||||
Project DB v14 (`track_sets`, `tracks`, `track_observations`). Le Track
|
Project DB v15 (`track_sets`, `tracks`, `track_observations`). Le Track
|
||||||
Builder, la triangulation et le Sparse SfM restent PLANNED.
|
Builder, la triangulation et le Sparse SfM restent PLANNED.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,11 @@ le plafond : 1, 2, 4, 8, puis les paliers supérieurs utiles aux autres kinds.
|
||||||
réservation ajoute un slot GPU et 640 Kio. Sur UMA ces 640 Kio sont aussi
|
réservation ajoute un slot GPU et 640 Kio. Sur UMA ces 640 Kio sont aussi
|
||||||
débités du budget RAM. Sans GPU/backend, l'estimation reste CPU-only afin que
|
débités du budget RAM. Sans GPU/backend, l'estimation reste CPU-only afin que
|
||||||
le fallback portable ne soit jamais refusé artificiellement.
|
le fallback portable ne soit jamais refusé artificiellement.
|
||||||
|
- `track_builder.run` réserve un worker CPU, aucun GPU et aucun fan-out GVR.
|
||||||
|
L'estimation est `4 MiB + raw_inlier_edges * (48 + 2*160)` avec facteur 2
|
||||||
|
sous 400000 arêtes et facteur 8 au-delà, après vérification d'overflow. Le
|
||||||
|
facteur élevé protège la transition mémoire observée à grande échelle ; le
|
||||||
|
Governor reste l'unique propriétaire de l'admission et de la pression.
|
||||||
|
|
||||||
## Limites actuelles
|
## Limites actuelles
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,3 +74,8 @@ curseur et repasse par le Governor entre les lots. La table durable
|
||||||
Fundamental immuable, en revalide le fingerprint et reprend `after_match_result_id`.
|
Fundamental immuable, en revalide le fingerprint et reprend `after_match_result_id`.
|
||||||
Project DB v13 ajoute uniquement `geometric_verifier_tasks`, car le checkpoint
|
Project DB v13 ajoute uniquement `geometric_verifier_tasks`, car le checkpoint
|
||||||
générique v1 ne possède aucun payload propre au kind.
|
générique v1 ne possède aucun payload propre au kind.
|
||||||
|
|
||||||
|
**IMPLEMENTED** — `track_builder.run`, version 1, reconstruit un scope explicite
|
||||||
|
depuis son payload Project DB v15 et son asset little-endian validé. Le callback
|
||||||
|
réutilise l'orchestration Gate C et le reconstructeur refuse toute corruption,
|
||||||
|
version, fingerprint, checksum, tri, unicité ou L3DTSIS1 incohérents.
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,11 @@ configuration scientifique et `after_match_result_id`. Chaque résultat est
|
||||||
publié avant le curseur ; pause, annulation, checkpoint et rupture de séquence
|
publié avant le curseur ; pause, annulation, checkpoint et rupture de séquence
|
||||||
restent coopératifs aux frontières des parents et des lots.
|
restent coopératifs aux frontières des parents et des lots.
|
||||||
|
|
||||||
|
**IMPLEMENTED** — `track_builder.run` v1 est une tâche durable de rebuild
|
||||||
|
complet. Le scope GVR est persistant et immuable ; la reprise rejoue depuis le
|
||||||
|
début avant publication et l'exact reuse Gate C absorbe un crash post-publication.
|
||||||
|
Pause et annulation sont observées avant/after l'unité Gate B non préemptible.
|
||||||
|
|
||||||
Le chemin de production de l'import ne possède plus de thread ni de drapeau
|
Le chemin de production de l'import ne possède plus de thread ni de drapeau
|
||||||
d'annulation privés. Son wrapper TUI ne fait qu'enqueue/cancel/observer la
|
d'annulation privés. Son wrapper TUI ne fait qu'enqueue/cancel/observer la
|
||||||
tâche générique. Chaque callback traite un lot borné, checkpoint hors mutex de
|
tâche générique. Chaque callback traite un lot borné, checkpoint hors mutex de
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ poses, calculate 3D reprojection, or optimise cameras.
|
||||||
|
|
||||||
The following are **FROZEN** and are consumed without reinterpretation:
|
The following are **FROZEN** and are consumed without reinterpretation:
|
||||||
|
|
||||||
- Track Model v1 and Project DB v14.
|
- Track Model v1 and the Project DB v14 scientific tables (retained unchanged
|
||||||
|
by the Gate D v15 runtime migration).
|
||||||
- Observation identity `(feature_set_id, feature_index)`.
|
- Observation identity `(feature_set_id, feature_index)`.
|
||||||
- One observation per image, minimum two observations per Track, and no
|
- One observation per image, minimum two observations per Track, and no
|
||||||
persistent rejected Track state.
|
persistent rejected Track state.
|
||||||
|
|
@ -364,19 +365,18 @@ reuses the complete set. Existing immutable upstream data is untouched.
|
||||||
scope hash and a new Track Set identity; the old set remains valid. No delta
|
scope hash and a new Track Set identity; the old set remains valid. No delta
|
||||||
Builder or checkpoint graph persistence is introduced here.
|
Builder or checkpoint graph persistence is introduced here.
|
||||||
|
|
||||||
## Future Task boundary
|
## Task boundary (Gate D)
|
||||||
|
|
||||||
A future Task, likely named `track_builder.run`, treats one complete Track Set
|
A durable `track_builder.run` v1 treats one complete Track Set as its scientific
|
||||||
as its scientific unit. It may page GVR resolution and pause at safe input or
|
unit. It replays the explicit scope after recovery and pauses/cancels only at
|
||||||
compute boundaries, but publication occurs only after complete canonical
|
safe boundaries; publication occurs only after complete canonical output and
|
||||||
output and final scope validation. A cancelled or failed run publishes no
|
final scope validation. A cancelled or failed run publishes no incomplete set.
|
||||||
incomplete set. The exact Task kind is not implemented or persisted by Gate A.
|
|
||||||
|
|
||||||
## Future Resource Governor integration
|
## Resource Governor integration (Gate D)
|
||||||
|
|
||||||
The future Task estimates bounded graph memory and CPU, obtains reservations
|
The Task estimates bounded graph memory and CPU, obtains reservations before
|
||||||
before callbacks, and may reduce batches or pause under pressure. Batch size,
|
callbacks and obeys Governor pressure. Batch size, thread count, Governor state,
|
||||||
thread count, Governor state, PSI, swap and throttling may affect throughput
|
PSI, swap and throttling may affect throughput
|
||||||
only; they must not affect logical edges, conflict decisions or output order.
|
only; they must not affect logical edges, conflict decisions or output order.
|
||||||
|
|
||||||
## Vulkan
|
## Vulkan
|
||||||
|
|
@ -425,13 +425,14 @@ Future performance runs should cover 10k, 100k and 1M edges with low-conflict,
|
||||||
high-conflict, tiny-track, huge-component, chain and star distributions.
|
high-conflict, tiny-track, huge-component, chain and star distributions.
|
||||||
Measure wall time, CPU, RSS peak, bytes/node, bytes/edge and throughput.
|
Measure wall time, CPU, RSS peak, bytes/node, bytes/edge and throughput.
|
||||||
|
|
||||||
## Explicitly out of scope
|
## Explicitly out of scope for the scientific Builder
|
||||||
|
|
||||||
Triangulation, camera pose estimation, Sparse SfM, Essential matrix, Bundle
|
Triangulation, camera pose estimation, Sparse SfM, Essential matrix, Bundle
|
||||||
Adjustment, reprojection optimisation, 3D points, co-visibility matrices,
|
Adjustment, reprojection optimisation, 3D points, co-visibility matrices,
|
||||||
dense reconstruction, Vulkan Builder implementation, persistent edge
|
dense reconstruction, Vulkan Builder implementation, persistent edge
|
||||||
provenance, Track Model schema changes, Project DB v15, Track Builder Task
|
provenance, Track Model schema changes and incremental optimisation
|
||||||
implementation and incremental optimisation implementation.
|
implementation. The durable Task Builder runtime is a separate Gate D layer;
|
||||||
|
it does not alter this scientific contract.
|
||||||
|
|
||||||
## Open questions
|
## Open questions
|
||||||
|
|
||||||
|
|
@ -500,7 +501,9 @@ Track Builder adds no new external dependency and does not introduce a second
|
||||||
internal SHA-256 helper. Its fingerprint vector remains
|
internal SHA-256 helper. Its fingerprint vector remains
|
||||||
`e1f1fae479bcf82001a5b33dda331195617b8751668e46a6cf1eecf2d125df31`.
|
`e1f1fae479bcf82001a5b33dda331195617b8751668e46a6cf1eecf2d125df31`.
|
||||||
|
|
||||||
The 1M benchmark's `973752 KiB` is a process high-water RSS measurement. The
|
The historical isolated Gate B 1M benchmark's `973752 KiB` is a process
|
||||||
|
high-water RSS measurement; it is not the Gate C/D project integration
|
||||||
|
measurement. The
|
||||||
synthetic caller retains 136,000,136 bytes of observations and 16,000,000
|
synthetic caller retains 136,000,136 bytes of observations and 16,000,000
|
||||||
bytes of pointer edges while the core retains its 136,000,136-byte canonical
|
bytes of pointer edges while the core retains its 136,000,136-byte canonical
|
||||||
metadata table, 16,000,000-byte normalized edges, DSU/component temporaries
|
metadata table, 16,000,000-byte normalized edges, DSU/component temporaries
|
||||||
|
|
@ -537,15 +540,16 @@ same explicit GVR IDs, status, selector, parent chain and scope count/hash are
|
||||||
revalidated. Any missing, rejected, mismatched or corrupt selected input aborts
|
revalidated. Any missing, rejected, mismatched or corrupt selected input aborts
|
||||||
the whole build; no partial Track Set is published. New matching GVRs are not
|
the whole build; no partial Track Set is published. New matching GVRs are not
|
||||||
discovered or included. The Track Model API owns timestamps, validation,
|
discovered or included. The Track Model API owns timestamps, validation,
|
||||||
atomicity and late exact-identity reuse. Project DB remains v14; Task and
|
atomicity and late exact-identity reuse. Gate C itself uses the v14 scientific
|
||||||
Resource Governor orchestration are not part of Gate C.
|
baseline; Gate D adds only the v15 durable task payload.
|
||||||
|
|
||||||
The implementation does not add a selector discovery API: callers that need
|
The implementation does not add a selector discovery API: callers that need
|
||||||
discovery must enumerate and freeze their own explicit list in a separate
|
discovery must enumerate and freeze their own explicit list in a separate
|
||||||
operation.
|
operation.
|
||||||
|
|
||||||
DB orchestration: **GATE C IMPLEMENTED**. Task: **NOT_IMPLEMENTED**. Resource
|
DB orchestration: **GATE C IMPLEMENTED**. Task: **GATE D IMPLEMENTED**.
|
||||||
Governor: **NOT_IMPLEMENTED**. Project DB remains v14 and unchanged.
|
Resource Governor integration: **GATE D IMPLEMENTED**. Project DB v15 adds only
|
||||||
|
the durable task payload; Track Model tables remain unchanged.
|
||||||
|
|
||||||
## Gate C closure evidence (current worktree)
|
## Gate C closure evidence (current worktree)
|
||||||
|
|
||||||
|
|
@ -565,3 +569,80 @@ API and C27 belongs to the future concurrency gate.
|
||||||
Gate C status: **PASS**. Gate A: **PASS**. Gate B: **PASS**. Gate D:
|
Gate C status: **PASS**. Gate A: **PASS**. Gate B: **PASS**. Gate D:
|
||||||
**NOT_IMPLEMENTED**. Gate E: **NOT_DONE**. Track Builder v1 is not marked
|
**NOT_IMPLEMENTED**. Gate E: **NOT_DONE**. Track Builder v1 is not marked
|
||||||
fully frozen by this document; the future runtime gates remain separate.
|
fully frozen by this document; the future runtime gates remain separate.
|
||||||
|
|
||||||
|
## Gate D — durable runtime contract
|
||||||
|
|
||||||
|
This section was implemented and validated as a **DECISION**. It does not
|
||||||
|
change Track Model v1, Gate B, or Gate C.
|
||||||
|
|
||||||
|
### Durable task identity and payload
|
||||||
|
|
||||||
|
The task kind is `track_builder.run`, version 1. One durable task owns one
|
||||||
|
explicit, immutable GVR scope. Its scientific identity is exactly the Gate C
|
||||||
|
tuple: `track_builder` kind/version/fingerprint, verifier selector, sorted
|
||||||
|
unique GVR IDs, `gvr_count`, and `L3DTSIS1`. Task ID, state, checkpoint phase,
|
||||||
|
pause/cancel flags, retry count, timestamps, reservation and Governor state are
|
||||||
|
runtime metadata and never enter Track Set identity.
|
||||||
|
|
||||||
|
The generic checkpoint v1 cannot carry a bounded, reconstructible arbitrary GVR
|
||||||
|
list: it stores only the task snapshot. A Gate D payload therefore requires a
|
||||||
|
new Project DB v15 task row plus an external atomic scope asset. The row stores
|
||||||
|
the task ID, payload format version, scope path, byte size and SHA-256, exact
|
||||||
|
selector, builder identity, scope hash and count. The scope asset stores field-
|
||||||
|
by-field little-endian uint64 GVR IDs with explicit magic/version/count/size and
|
||||||
|
checksum. No native struct dump, SQL text list or arbitrary small ID limit is
|
||||||
|
allowed. A v14 database remains readable and is upgraded transactionally only
|
||||||
|
when this payload is actually implemented.
|
||||||
|
|
||||||
|
### Recovery and publication
|
||||||
|
|
||||||
|
The task is persisted before enqueue. Recovery validates the task row, scope
|
||||||
|
asset, sortedness, uniqueness, selector, builder fingerprint, count and
|
||||||
|
L3DTSIS1 before reconstructing a fresh callback. Prepublication graph state is
|
||||||
|
transient; recovery replays the complete explicit scope from the beginning.
|
||||||
|
The durable order is: task payload → exact reuse → bounded resolution → Gate B
|
||||||
|
compute → selected-input revalidation → atomic Track Set publication → terminal
|
||||||
|
task checkpoint. A crash after publication but before terminal checkpoint is
|
||||||
|
resolved by exact Gate C reuse, never by a second publication.
|
||||||
|
|
||||||
|
### Pause, cancellation and identity collision
|
||||||
|
|
||||||
|
Pause and cancellation are cooperative at task boundaries and between bounded
|
||||||
|
GVR units. Gate B is non-preemptible: an active invocation finishes, then pause
|
||||||
|
or cancellation is observed before publication. Prepublication cancellation
|
||||||
|
publishes no Track Set; a committed publication remains valid even if a late
|
||||||
|
cancellation is requested. A deterministic test seam will exercise the Gate C
|
||||||
|
late-identity collision: an exact identity conflict is re-looked-up and reused;
|
||||||
|
an unrelated constraint remains an error.
|
||||||
|
|
||||||
|
### Resource and queue contract
|
||||||
|
|
||||||
|
The existing Task Runtime, bounded FIFO queue and Resource Governor remain the
|
||||||
|
owners of lifecycle, backpressure, admission, reservations, pressure and
|
||||||
|
hysteresis. Track Builder adds no thresholds, queue or worker pool. It uses one
|
||||||
|
conservative CPU worker and no GPU. A preflight estimate uses checked arithmetic
|
||||||
|
over selected GVR `inlier_count` metadata:
|
||||||
|
|
||||||
|
`fixed_graph_bytes + raw_inlier_edges * bytes_per_edge +
|
||||||
|
2 * raw_inlier_edges * bytes_per_observation`, multiplied by a conservative
|
||||||
|
twofold allocator/headroom margin through 400,000 raw edges and an eightfold
|
||||||
|
margin above the 400,000-edge admission transition. This is an
|
||||||
|
admission upper bound, not a scientific quantity or an exact RSS promise.
|
||||||
|
|
||||||
|
Match Files remain one-at-a-time and descriptors remain unloaded. YELLOW/RED behavior is decided
|
||||||
|
only by the Governor; swap is recovery capacity, not a work budget. UMA system
|
||||||
|
RAM remains shared desktop/iGPU headroom.
|
||||||
|
|
||||||
|
### Gate D evidence
|
||||||
|
|
||||||
|
The validated runtime covers registry, payload corruption, migration, direct-vs-task
|
||||||
|
parity, replay, crash-before/after-publication, pause/resume, cancellation,
|
||||||
|
C27 identity collision, zero-track, selected-input deletion, bounded queue and
|
||||||
|
Governor GREEN/YELLOW/RED tests. Fresh child-process calibration at 253952,
|
||||||
|
352256, 393216, 401408, 450560, 507904 and 516096 raw edges measured 32932,
|
||||||
|
37240, 40056, 39888–40220, 41880, 44952 and 45368 KiB respectively. The
|
||||||
|
apparent 1,079,972 KiB result was contaminated cumulative RUSAGE from the
|
||||||
|
persistent shell that launched direct runs; it was not Track Builder memory.
|
||||||
|
A normal 250k hardware run completed in 0.368 seconds with 33,228 KiB peak RSS,
|
||||||
|
MemAvailable loss of 14,108 KiB, no swap delta and zero PSI averages. Gate D
|
||||||
|
is **PASS**; Gate E remains responsible for final full-suite freeze.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
#include <lardon3d/task.h>
|
#include <lardon3d/task.h>
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 14,
|
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 15,
|
||||||
LARDON3D_PROJECT_DB_ID_CAPACITY = 65,
|
LARDON3D_PROJECT_DB_ID_CAPACITY = 65,
|
||||||
LARDON3D_PROJECT_DB_KIND_CAPACITY = 65,
|
LARDON3D_PROJECT_DB_KIND_CAPACITY = 65,
|
||||||
LARDON3D_PROJECT_DB_PATH_CAPACITY = 4096,
|
LARDON3D_PROJECT_DB_PATH_CAPACITY = 4096,
|
||||||
|
|
@ -158,6 +158,22 @@ typedef struct {
|
||||||
unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE];
|
unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE];
|
||||||
} Lardon3DProjectDbGeometricVerifierTask;
|
} Lardon3DProjectDbGeometricVerifierTask;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t task_id;
|
||||||
|
char builder_kind[LARDON3D_PROJECT_DB_KIND_CAPACITY];
|
||||||
|
uint32_t builder_version;
|
||||||
|
unsigned char builder_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE];
|
||||||
|
int verifier_kind;
|
||||||
|
uint32_t verifier_version;
|
||||||
|
unsigned char verifier_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE];
|
||||||
|
unsigned char input_scope_hash[LARDON3D_PROJECT_DB_SHA256_SIZE];
|
||||||
|
uint64_t gvr_count;
|
||||||
|
char scope_path[LARDON3D_PROJECT_DB_PATH_CAPACITY];
|
||||||
|
uint64_t scope_size_bytes;
|
||||||
|
unsigned char scope_sha256[LARDON3D_PROJECT_DB_SHA256_SIZE];
|
||||||
|
uint32_t scope_format_version;
|
||||||
|
} Lardon3DProjectDbTrackBuilderTask;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint64_t match_result_id;
|
uint64_t match_result_id;
|
||||||
uint64_t candidate_pair_id;
|
uint64_t candidate_pair_id;
|
||||||
|
|
@ -579,6 +595,13 @@ Lardon3DProjectDbResult lardon3d_project_db_record_geometric_verifier_task(
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_load_geometric_verifier_task(
|
Lardon3DProjectDbResult lardon3d_project_db_load_geometric_verifier_task(
|
||||||
Lardon3DProjectDb *database, uint64_t task_id,
|
Lardon3DProjectDb *database, uint64_t task_id,
|
||||||
Lardon3DProjectDbGeometricVerifierTask *parameters);
|
Lardon3DProjectDbGeometricVerifierTask *parameters);
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_record_track_builder_task(
|
||||||
|
Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot,
|
||||||
|
const char *kind, uint32_t version, const Lardon3DProjectDbCheckpoint *checkpoint,
|
||||||
|
const Lardon3DProjectDbTrackBuilderTask *parameters, int64_t updated_at);
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_load_track_builder_task(
|
||||||
|
Lardon3DProjectDb *database, uint64_t task_id,
|
||||||
|
Lardon3DProjectDbTrackBuilderTask *parameters);
|
||||||
|
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_create_match_result(
|
Lardon3DProjectDbResult lardon3d_project_db_create_match_result(
|
||||||
Lardon3DProjectDb *database, uint64_t candidate_pair_id, uint64_t feature_set_id_a,
|
Lardon3DProjectDb *database, uint64_t candidate_pair_id, uint64_t feature_set_id_a,
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ Lardon3DTrackBuilderProjectStatus lardon3d_track_builder_build_project(
|
||||||
/* Test-only phase seam; never present in the production header/API. */
|
/* Test-only phase seam; never present in the production header/API. */
|
||||||
void lardon3d_track_builder_project_test_before_revalidation(
|
void lardon3d_track_builder_project_test_before_revalidation(
|
||||||
Lardon3DProjectDb *database, const uint64_t *gvr_ids, size_t gvr_count);
|
Lardon3DProjectDb *database, const uint64_t *gvr_ids, size_t gvr_count);
|
||||||
|
void lardon3d_track_builder_project_test_before_publication(
|
||||||
|
const Lardon3DTrackBuilderProjectRequest *request);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
||||||
43
include/lardon3d/track_builder_task.h
Normal file
43
include/lardon3d/track_builder_task.h
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#ifndef LARDON3D_TRACK_BUILDER_TASK_H
|
||||||
|
#define LARDON3D_TRACK_BUILDER_TASK_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include <lardon3d/app_state.h>
|
||||||
|
#include <lardon3d/task_kind_registry.h>
|
||||||
|
#include <lardon3d/track_builder_project.h>
|
||||||
|
|
||||||
|
#define LARDON3D_TRACK_BUILDER_TASK_KIND "track_builder.run"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
LARDON3D_TRACK_BUILDER_TASK_KIND_VERSION = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *project_path;
|
||||||
|
Lardon3DProjectDb *database;
|
||||||
|
int verifier_kind;
|
||||||
|
uint32_t verifier_version;
|
||||||
|
const unsigned char *verifier_fingerprint;
|
||||||
|
const uint64_t *gvr_ids;
|
||||||
|
size_t gvr_count;
|
||||||
|
} Lardon3DTrackBuilderTaskConfiguration;
|
||||||
|
|
||||||
|
Lardon3DTask *lardon3d_project_create_track_builder_task(
|
||||||
|
Lardon3DAppState *state,
|
||||||
|
const Lardon3DTrackBuilderTaskConfiguration *configuration,
|
||||||
|
uint64_t *task_id);
|
||||||
|
|
||||||
|
bool lardon3d_project_enqueue_track_builder_task(
|
||||||
|
Lardon3DAppState *state,
|
||||||
|
const Lardon3DTrackBuilderTaskConfiguration *configuration,
|
||||||
|
uint64_t *task_id);
|
||||||
|
|
||||||
|
bool lardon3d_track_builder_task_reconstruct(
|
||||||
|
const Lardon3DTaskDurableSnapshot *snapshot, void *context,
|
||||||
|
Lardon3DTaskKindBinding *binding);
|
||||||
|
|
||||||
|
#endif
|
||||||
13
meson.build
13
meson.build
|
|
@ -157,8 +157,10 @@ executable(
|
||||||
'src/geometric_verifier.cpp',
|
'src/geometric_verifier.cpp',
|
||||||
'src/track_builder.cpp',
|
'src/track_builder.cpp',
|
||||||
'src/track_builder_project.cpp',
|
'src/track_builder_project.cpp',
|
||||||
|
'src/track_builder_task.cpp',
|
||||||
] + matcher_backend_sources,
|
] + matcher_backend_sources,
|
||||||
include_directories: include_directories('include'),
|
include_directories: include_directories('include'),
|
||||||
|
c_args: ['-DLARDON3D_TRACK_BUILDER_TASK_AVAILABLE'],
|
||||||
dependencies: [ncursesw, threads, sqlite3, openssl, opencv, opencv_geometry]
|
dependencies: [ncursesw, threads, sqlite3, openssl, opencv, opencv_geometry]
|
||||||
+ matcher_backend_dependencies,
|
+ matcher_backend_dependencies,
|
||||||
)
|
)
|
||||||
|
|
@ -848,14 +850,23 @@ track_builder_project_test = executable(
|
||||||
'tests/test_track_builder_project.cpp',
|
'tests/test_track_builder_project.cpp',
|
||||||
'src/track_builder.cpp',
|
'src/track_builder.cpp',
|
||||||
'src/track_builder_project.cpp',
|
'src/track_builder_project.cpp',
|
||||||
|
'src/track_builder_task.cpp',
|
||||||
'src/project_db.c',
|
'src/project_db.c',
|
||||||
|
'src/app_state.c',
|
||||||
'src/task.c',
|
'src/task.c',
|
||||||
|
'src/task_kind_registry.c',
|
||||||
|
'src/task_checkpoint.c',
|
||||||
|
'src/task_queue.c',
|
||||||
|
'src/hardware_profile.c',
|
||||||
'src/resource_governor.c',
|
'src/resource_governor.c',
|
||||||
'src/resource_snapshot.c',
|
'src/resource_snapshot.c',
|
||||||
'src/match_file.c',
|
'src/match_file.c',
|
||||||
],
|
],
|
||||||
include_directories: include_directories('include'),
|
include_directories: include_directories('include'),
|
||||||
cpp_args: ['-DLARDON3D_TRACK_BUILDER_PROJECT_TESTING'],
|
cpp_args: [
|
||||||
|
'-DLARDON3D_TRACK_BUILDER_PROJECT_TESTING',
|
||||||
|
'-DLARDON3D_TRACK_BUILDER_TASK_TESTING',
|
||||||
|
],
|
||||||
dependencies: [threads, sqlite3, openssl],
|
dependencies: [threads, sqlite3, openssl],
|
||||||
)
|
)
|
||||||
test('track-builder-project', track_builder_project_test, timeout: 60)
|
test('track-builder-project', track_builder_project_test, timeout: 60)
|
||||||
|
|
|
||||||
165
src/project_db.c
165
src/project_db.c
|
|
@ -300,8 +300,24 @@ static const char schema_geometric_verifier_task_v13[] =
|
||||||
"min_inlier_count INTEGER NOT NULL CHECK(min_inlier_count BETWEEN 1 AND 8192),"
|
"min_inlier_count INTEGER NOT NULL CHECK(min_inlier_count BETWEEN 1 AND 8192),"
|
||||||
"min_inlier_ratio REAL NOT NULL CHECK(min_inlier_ratio>=0.0 AND min_inlier_ratio<=1.0),"
|
"min_inlier_ratio REAL NOT NULL CHECK(min_inlier_ratio>=0.0 AND min_inlier_ratio<=1.0),"
|
||||||
"seed_policy_version INTEGER NOT NULL CHECK(seed_policy_version>0),"
|
"seed_policy_version INTEGER NOT NULL CHECK(seed_policy_version>0),"
|
||||||
"canonicalization_version INTEGER NOT NULL CHECK(canonicalization_version>0),"
|
"canonicalization_version INTEGER NOT NULL CHECK(canonicalization_version>0),"
|
||||||
"parameter_fingerprint BLOB NOT NULL CHECK(length(parameter_fingerprint)=32));";
|
"parameter_fingerprint BLOB NOT NULL CHECK(length(parameter_fingerprint)=32));";
|
||||||
|
|
||||||
|
static const char schema_track_builder_task_v15[] =
|
||||||
|
"CREATE TABLE track_builder_tasks("
|
||||||
|
"task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON DELETE CASCADE,"
|
||||||
|
"builder_kind TEXT NOT NULL CHECK(builder_kind='track_builder'),"
|
||||||
|
"builder_version INTEGER NOT NULL CHECK(builder_version>0),"
|
||||||
|
"builder_fingerprint BLOB NOT NULL CHECK(length(builder_fingerprint)=32),"
|
||||||
|
"verifier_kind INTEGER NOT NULL CHECK(verifier_kind>0),"
|
||||||
|
"verifier_version INTEGER NOT NULL CHECK(verifier_version>0),"
|
||||||
|
"verifier_fingerprint BLOB NOT NULL CHECK(length(verifier_fingerprint)=32),"
|
||||||
|
"input_scope_hash BLOB NOT NULL CHECK(length(input_scope_hash)=32),"
|
||||||
|
"gvr_count INTEGER NOT NULL CHECK(gvr_count>=1),"
|
||||||
|
"scope_path TEXT NOT NULL CHECK(length(scope_path)>0 AND length(scope_path)<=4096),"
|
||||||
|
"scope_size_bytes INTEGER NOT NULL CHECK(scope_size_bytes>0),"
|
||||||
|
"scope_sha256 BLOB NOT NULL CHECK(length(scope_sha256)=32),"
|
||||||
|
"scope_format_version INTEGER NOT NULL CHECK(scope_format_version=1));";
|
||||||
|
|
||||||
static void copy_error(char destination[LARDON3D_PROJECT_DB_ERROR_CAPACITY], const char *text) {
|
static void copy_error(char destination[LARDON3D_PROJECT_DB_ERROR_CAPACITY], const char *text) {
|
||||||
if (destination) {
|
if (destination) {
|
||||||
|
|
@ -393,7 +409,7 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
|
||||||
if (from_version != 0 && from_version != 1 && from_version != 2 && from_version != 3 &&
|
if (from_version != 0 && from_version != 1 && from_version != 2 && from_version != 3 &&
|
||||||
from_version != 4 && from_version != 5 && from_version != 6 && from_version != 7 &&
|
from_version != 4 && from_version != 5 && from_version != 6 && from_version != 7 &&
|
||||||
from_version != 8 && from_version != 9 && from_version != 10 && from_version != 11 &&
|
from_version != 8 && from_version != 9 && from_version != 10 && from_version != 11 &&
|
||||||
from_version != 12 && from_version != 13) {
|
from_version != 12 && from_version != 13 && from_version != 14) {
|
||||||
return LARDON3D_PROJECT_DB_CORRUPT;
|
return LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
}
|
}
|
||||||
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin migration");
|
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin migration");
|
||||||
|
|
@ -681,6 +697,22 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
|
||||||
"finish schema v14 migration");
|
"finish schema v14 migration");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && from_version < 15) {
|
||||||
|
result = execute(database, schema_track_builder_task_v15,
|
||||||
|
"migrate schema v14 to v15");
|
||||||
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
||||||
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V15");
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && forced_failure && strcmp(forced_failure, "1") == 0) {
|
||||||
|
result = execute(database, "INSERT INTO missing_test_table VALUES(1)",
|
||||||
|
"forced migration v15 failure");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
result = execute(database,
|
||||||
|
"UPDATE metadata SET value=15 WHERE key='schema_version' AND value=14",
|
||||||
|
"finish schema v15 migration");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
result = execute(database, "COMMIT", "commit migration");
|
result = execute(database, "COMMIT", "commit migration");
|
||||||
}
|
}
|
||||||
|
|
@ -798,9 +830,10 @@ Lardon3DProjectDbResult lardon3d_project_db_open(const char *path, Lardon3DProje
|
||||||
"match_results",
|
"match_results",
|
||||||
"geometric_verification_results",
|
"geometric_verification_results",
|
||||||
"geometric_verifier_tasks",
|
"geometric_verifier_tasks",
|
||||||
"track_sets",
|
"track_sets",
|
||||||
"tracks",
|
"tracks",
|
||||||
"track_observations"};
|
"track_observations",
|
||||||
|
"track_builder_tasks"};
|
||||||
for (size_t index = 0; index < sizeof(required) / sizeof(required[0]) &&
|
for (size_t index = 0; index < sizeof(required) / sizeof(required[0]) &&
|
||||||
result == LARDON3D_PROJECT_DB_OK;
|
result == LARDON3D_PROJECT_DB_OK;
|
||||||
++index) {
|
++index) {
|
||||||
|
|
@ -3588,6 +3621,126 @@ Lardon3DProjectDbResult lardon3d_project_db_load_geometric_verifier_task(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_record_track_builder_task(
|
||||||
|
Lardon3DProjectDb *db, const Lardon3DTaskDurableSnapshot *snapshot,
|
||||||
|
const char *kind, uint32_t version, const Lardon3DProjectDbCheckpoint *checkpoint,
|
||||||
|
const Lardon3DProjectDbTrackBuilderTask *parameters, int64_t updated_at) {
|
||||||
|
if (!db || !snapshot || !parameters || parameters->task_id != snapshot->id ||
|
||||||
|
strcmp(parameters->builder_kind, "track_builder") != 0 ||
|
||||||
|
parameters->builder_version == 0 || parameters->verifier_kind <= 0 ||
|
||||||
|
parameters->verifier_version == 0 || parameters->gvr_count == 0 ||
|
||||||
|
parameters->scope_format_version != 1 || parameters->scope_size_bytes == 0 ||
|
||||||
|
!bounded_text(parameters->scope_path, sizeof(parameters->scope_path), false) ||
|
||||||
|
strncmp(parameters->scope_path, ".lardon3d/checkpoints/", 21) != 0) {
|
||||||
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
Lardon3DProjectDbResult result = record_task_internal(
|
||||||
|
db, snapshot, kind, version, checkpoint, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||||
|
updated_at);
|
||||||
|
if (result != LARDON3D_PROJECT_DB_OK) return result;
|
||||||
|
(void)pthread_mutex_lock(&db->mutex);
|
||||||
|
result = execute(db, "BEGIN IMMEDIATE", "begin track builder task");
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
result = prepare(
|
||||||
|
db,
|
||||||
|
"INSERT INTO track_builder_tasks(task_id,builder_kind,builder_version,builder_fingerprint,"
|
||||||
|
"verifier_kind,verifier_version,verifier_fingerprint,input_scope_hash,gvr_count,scope_path,"
|
||||||
|
"scope_size_bytes,scope_sha256,scope_format_version) VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,"
|
||||||
|
"?10,?11,?12,?13) ON CONFLICT(task_id) DO UPDATE SET scope_path=excluded.scope_path,"
|
||||||
|
"scope_size_bytes=excluded.scope_size_bytes,scope_sha256=excluded.scope_sha256 "
|
||||||
|
"WHERE track_builder_tasks.builder_kind=excluded.builder_kind AND "
|
||||||
|
"track_builder_tasks.builder_version=excluded.builder_version AND "
|
||||||
|
"track_builder_tasks.builder_fingerprint=excluded.builder_fingerprint AND "
|
||||||
|
"track_builder_tasks.verifier_kind=excluded.verifier_kind AND "
|
||||||
|
"track_builder_tasks.verifier_version=excluded.verifier_version AND "
|
||||||
|
"track_builder_tasks.verifier_fingerprint=excluded.verifier_fingerprint AND "
|
||||||
|
"track_builder_tasks.input_scope_hash=excluded.input_scope_hash AND "
|
||||||
|
"track_builder_tasks.gvr_count=excluded.gvr_count",
|
||||||
|
&statement);
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)parameters->task_id);
|
||||||
|
sqlite3_bind_text(statement, 2, parameters->builder_kind, -1, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_int64(statement, 3, parameters->builder_version);
|
||||||
|
sqlite3_bind_blob(statement, 4, parameters->builder_fingerprint, 32, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_int(statement, 5, parameters->verifier_kind);
|
||||||
|
sqlite3_bind_int64(statement, 6, parameters->verifier_version);
|
||||||
|
sqlite3_bind_blob(statement, 7, parameters->verifier_fingerprint, 32, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_blob(statement, 8, parameters->input_scope_hash, 32, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_int64(statement, 9, (sqlite3_int64)parameters->gvr_count);
|
||||||
|
sqlite3_bind_text(statement, 10, parameters->scope_path, -1, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_int64(statement, 11, (sqlite3_int64)parameters->scope_size_bytes);
|
||||||
|
sqlite3_bind_blob(statement, 12, parameters->scope_sha256, 32, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_int64(statement, 13, parameters->scope_format_version);
|
||||||
|
result = step_done(db, statement, "upsert track builder task");
|
||||||
|
statement = NULL;
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(db->connection) != 1) {
|
||||||
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (statement) sqlite3_finalize(statement);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) result = execute(db, "COMMIT", "commit track builder task");
|
||||||
|
if (result != LARDON3D_PROJECT_DB_OK) (void)execute(db, "ROLLBACK", "rollback track builder task");
|
||||||
|
(void)pthread_mutex_unlock(&db->mutex);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_load_track_builder_task(
|
||||||
|
Lardon3DProjectDb *db, uint64_t task_id,
|
||||||
|
Lardon3DProjectDbTrackBuilderTask *parameters) {
|
||||||
|
if (!db || !valid_task_id(task_id) || !parameters) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
memset(parameters, 0, sizeof(*parameters));
|
||||||
|
(void)pthread_mutex_lock(&db->mutex);
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
Lardon3DProjectDbResult result = prepare(
|
||||||
|
db,
|
||||||
|
"SELECT builder_kind,builder_version,builder_fingerprint,verifier_kind,"
|
||||||
|
"verifier_version,verifier_fingerprint,input_scope_hash,gvr_count,scope_path,"
|
||||||
|
"scope_size_bytes,scope_sha256,scope_format_version FROM track_builder_tasks WHERE task_id=?1",
|
||||||
|
&statement);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id);
|
||||||
|
int code = sqlite3_step(statement);
|
||||||
|
sqlite3_int64 builder_version = sqlite3_column_int64(statement, 1);
|
||||||
|
sqlite3_int64 verifier_version = sqlite3_column_int64(statement, 4);
|
||||||
|
sqlite3_int64 gvr_count = sqlite3_column_int64(statement, 7);
|
||||||
|
sqlite3_int64 scope_size = sqlite3_column_int64(statement, 9);
|
||||||
|
sqlite3_int64 scope_format = sqlite3_column_int64(statement, 11);
|
||||||
|
if (code == SQLITE_DONE) {
|
||||||
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
||||||
|
} else if (code != SQLITE_ROW || builder_version <= 0 || verifier_version <= 0 ||
|
||||||
|
gvr_count <= 0 || scope_size <= 0 || scope_format != 1 ||
|
||||||
|
sqlite3_column_bytes(statement, 2) != 32 ||
|
||||||
|
sqlite3_column_bytes(statement, 5) != 32 ||
|
||||||
|
sqlite3_column_bytes(statement, 6) != 32 ||
|
||||||
|
sqlite3_column_bytes(statement, 10) != 32 ||
|
||||||
|
!copy_column(statement, 0, parameters->builder_kind,
|
||||||
|
sizeof(parameters->builder_kind)) ||
|
||||||
|
!copy_column(statement, 8, parameters->scope_path,
|
||||||
|
sizeof(parameters->scope_path)) ||
|
||||||
|
strcmp(parameters->builder_kind, "track_builder") != 0 ||
|
||||||
|
strncmp(parameters->scope_path, ".lardon3d/checkpoints/", 21) != 0) {
|
||||||
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
|
} else {
|
||||||
|
parameters->task_id = task_id;
|
||||||
|
parameters->builder_version = (uint32_t)builder_version;
|
||||||
|
memcpy(parameters->builder_fingerprint, sqlite3_column_blob(statement, 2), 32);
|
||||||
|
parameters->verifier_kind = sqlite3_column_int(statement, 3);
|
||||||
|
parameters->verifier_version = (uint32_t)verifier_version;
|
||||||
|
memcpy(parameters->verifier_fingerprint, sqlite3_column_blob(statement, 5), 32);
|
||||||
|
memcpy(parameters->input_scope_hash, sqlite3_column_blob(statement, 6), 32);
|
||||||
|
parameters->gvr_count = (uint64_t)gvr_count;
|
||||||
|
parameters->scope_size_bytes = (uint64_t)scope_size;
|
||||||
|
memcpy(parameters->scope_sha256, sqlite3_column_blob(statement, 10), 32);
|
||||||
|
parameters->scope_format_version = (uint32_t)scope_format;
|
||||||
|
}
|
||||||
|
sqlite3_finalize(statement);
|
||||||
|
}
|
||||||
|
(void)pthread_mutex_unlock(&db->mutex);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static bool read_match_result(sqlite3_stmt *statement,
|
static bool read_match_result(sqlite3_stmt *statement,
|
||||||
Lardon3DProjectDbMatchResult *result) {
|
Lardon3DProjectDbMatchResult *result) {
|
||||||
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
#include <lardon3d/matcher_task.h>
|
#include <lardon3d/matcher_task.h>
|
||||||
#include <lardon3d/sift_task.h>
|
#include <lardon3d/sift_task.h>
|
||||||
#include <lardon3d/visual_index_task.h>
|
#include <lardon3d/visual_index_task.h>
|
||||||
|
#ifdef LARDON3D_TRACK_BUILDER_TASK_AVAILABLE
|
||||||
|
#include <lardon3d/track_builder_task.h>
|
||||||
|
#endif
|
||||||
#include <lardon3d/task_kind_registry.h>
|
#include <lardon3d/task_kind_registry.h>
|
||||||
|
|
||||||
const Lardon3DTaskKindRegistry *lardon3d_task_kind_registry_production(void) {
|
const Lardon3DTaskKindRegistry *lardon3d_task_kind_registry_production(void) {
|
||||||
|
|
@ -48,7 +51,15 @@ const Lardon3DTaskKindRegistry *lardon3d_task_kind_registry_production(void) {
|
||||||
.kind = LARDON3D_GEOMETRIC_VERIFIER_TASK_KIND,
|
.kind = LARDON3D_GEOMETRIC_VERIFIER_TASK_KIND,
|
||||||
.kind_version = LARDON3D_GEOMETRIC_VERIFIER_TASK_KIND_VERSION,
|
.kind_version = LARDON3D_GEOMETRIC_VERIFIER_TASK_KIND_VERSION,
|
||||||
.reconstruct = lardon3d_geometric_verifier_task_reconstruct,
|
.reconstruct = lardon3d_geometric_verifier_task_reconstruct,
|
||||||
}};
|
},
|
||||||
|
#ifdef LARDON3D_TRACK_BUILDER_TASK_AVAILABLE
|
||||||
|
{
|
||||||
|
.kind = LARDON3D_TRACK_BUILDER_TASK_KIND,
|
||||||
|
.kind_version = LARDON3D_TRACK_BUILDER_TASK_KIND_VERSION,
|
||||||
|
.reconstruct = lardon3d_track_builder_task_reconstruct,
|
||||||
|
},
|
||||||
|
#endif
|
||||||
|
};
|
||||||
static const Lardon3DTaskKindRegistry registry = {
|
static const Lardon3DTaskKindRegistry registry = {
|
||||||
.descriptors = descriptors,
|
.descriptors = descriptors,
|
||||||
.count = sizeof(descriptors) / sizeof(descriptors[0]),
|
.count = sizeof(descriptors) / sizeof(descriptors[0]),
|
||||||
|
|
|
||||||
|
|
@ -320,9 +320,37 @@ extern "C" Lardon3DTrackBuilderProjectStatus lardon3d_track_builder_build_projec
|
||||||
}
|
}
|
||||||
Lardon3DProjectDbTrackSet published{};
|
Lardon3DProjectDbTrackSet published{};
|
||||||
identity.track_count = core.track_count;
|
identity.track_count = core.track_count;
|
||||||
|
#ifdef LARDON3D_TRACK_BUILDER_PROJECT_TESTING
|
||||||
|
lardon3d_track_builder_project_test_before_publication(request);
|
||||||
|
Lardon3DProjectDbTrackSet raced_before_publish{};
|
||||||
|
if (lardon3d_project_db_find_track_set(request->database, &identity,
|
||||||
|
&raced_before_publish) == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
lardon3d_track_builder_result_free(&core);
|
||||||
|
result->track_set_id = raced_before_publish.track_set_id;
|
||||||
|
result->gvr_count = raced_before_publish.gvr_count;
|
||||||
|
result->track_count = raced_before_publish.track_count;
|
||||||
|
result->raw_inlier_edge_count = edges.size();
|
||||||
|
result->core_observation_count = observations.size();
|
||||||
|
result->reused = true;
|
||||||
|
return LARDON3D_TRACK_BUILDER_PROJECT_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
db = lardon3d_project_db_create_track_set(request->database, &identity,
|
db = lardon3d_project_db_create_track_set(request->database, &identity,
|
||||||
publish.data(), publish.size(), &published);
|
publish.data(), publish.size(), &published);
|
||||||
lardon3d_track_builder_result_free(&core);
|
lardon3d_track_builder_result_free(&core);
|
||||||
|
if (db == LARDON3D_PROJECT_DB_CONSTRAINT) {
|
||||||
|
Lardon3DProjectDbTrackSet raced{};
|
||||||
|
db = lardon3d_project_db_find_track_set(request->database, &identity, &raced);
|
||||||
|
if (db == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
result->track_set_id = raced.track_set_id;
|
||||||
|
result->gvr_count = raced.gvr_count;
|
||||||
|
result->track_count = raced.track_count;
|
||||||
|
result->raw_inlier_edge_count = edges.size();
|
||||||
|
result->core_observation_count = observations.size();
|
||||||
|
result->reused = true;
|
||||||
|
return LARDON3D_TRACK_BUILDER_PROJECT_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (db != LARDON3D_PROJECT_DB_OK) return map_db(db);
|
if (db != LARDON3D_PROJECT_DB_OK) return map_db(db);
|
||||||
result->track_set_id = published.track_set_id;
|
result->track_set_id = published.track_set_id;
|
||||||
result->gvr_count = published.gvr_count;
|
result->gvr_count = published.gvr_count;
|
||||||
|
|
|
||||||
369
src/track_builder_task.cpp
Normal file
369
src/track_builder_task.cpp
Normal file
|
|
@ -0,0 +1,369 @@
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <limits>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <lardon3d/project.h>
|
||||||
|
#include <lardon3d/task_checkpoint.h>
|
||||||
|
#include <lardon3d/task_queue.h>
|
||||||
|
#include <lardon3d/track_builder.h>
|
||||||
|
#include <lardon3d/track_builder_task.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr std::array<unsigned char, 8> kScopeMagic = {
|
||||||
|
'L', '3', 'D', 'T', 'S', 'C', 'P', '1'};
|
||||||
|
constexpr uint32_t kScopeVersion = 1;
|
||||||
|
constexpr uint64_t kFixedMemory = 4ULL * 1024ULL * 1024ULL;
|
||||||
|
constexpr uint64_t kBytesPerEdge = 48;
|
||||||
|
constexpr uint64_t kBytesPerObservation = 160;
|
||||||
|
constexpr uint64_t kNormalSafetyMultiplier = 2;
|
||||||
|
constexpr uint64_t kHighScaleSafetyMultiplier = 8;
|
||||||
|
constexpr uint64_t kHighScaleEdgeThreshold = 400000;
|
||||||
|
constexpr uint64_t kMaximumScopeBytes = 64ULL * 1024ULL * 1024ULL;
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
std::string project_path;
|
||||||
|
Lardon3DProjectDb *database = nullptr;
|
||||||
|
Lardon3DResourceGovernor *governor = nullptr;
|
||||||
|
Lardon3DProjectDbTrackBuilderTask durable{};
|
||||||
|
std::vector<uint64_t> ids;
|
||||||
|
};
|
||||||
|
|
||||||
|
void destroy_context(void *userdata) { delete static_cast<Context *>(userdata); }
|
||||||
|
|
||||||
|
bool checked_add(uint64_t left, uint64_t right, uint64_t *result) {
|
||||||
|
if (right > std::numeric_limits<uint64_t>::max() - left) return false;
|
||||||
|
*result = left + right;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checked_mul(uint64_t left, uint64_t right, uint64_t *result) {
|
||||||
|
if (left != 0 && right > std::numeric_limits<uint64_t>::max() / left) return false;
|
||||||
|
*result = left * right;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hash_bytes(const unsigned char *data, size_t size, unsigned char output[32]) {
|
||||||
|
unsigned int length = 0;
|
||||||
|
return EVP_Digest(data, size, output, &length, EVP_sha256(), nullptr) == 1 && length == 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool scope_hash(const std::vector<uint64_t> &ids, unsigned char output[32]) {
|
||||||
|
if (ids.size() > (std::numeric_limits<size_t>::max() - 8U) / 8U) return false;
|
||||||
|
std::vector<unsigned char> bytes(8U + ids.size() * 8U);
|
||||||
|
std::memcpy(bytes.data(), "L3DTSIS1", 8);
|
||||||
|
for (size_t i = 0; i < ids.size(); ++i) {
|
||||||
|
uint64_t value = ids[i];
|
||||||
|
for (size_t byte = 0; byte < 8; ++byte) {
|
||||||
|
bytes[8U + i * 8U + byte] = static_cast<unsigned char>(value & 0xffU);
|
||||||
|
value >>= 8U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hash_bytes(bytes.data(), bytes.size(), output);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool write_all(int fd, const unsigned char *data, size_t size) {
|
||||||
|
while (size != 0) {
|
||||||
|
ssize_t written = write(fd, data, size);
|
||||||
|
if (written <= 0) return false;
|
||||||
|
data += static_cast<size_t>(written);
|
||||||
|
size -= static_cast<size_t>(written);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool read_all(int fd, unsigned char *data, size_t size) {
|
||||||
|
while (size != 0) {
|
||||||
|
ssize_t read_count = read(fd, data, size);
|
||||||
|
if (read_count <= 0) return false;
|
||||||
|
data += static_cast<size_t>(read_count);
|
||||||
|
size -= static_cast<size_t>(read_count);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool scope_asset(const std::string &project_path, uint64_t task_id,
|
||||||
|
const std::vector<uint64_t> &ids, char relative[4096],
|
||||||
|
uint64_t *size_bytes, unsigned char sha256[32]) {
|
||||||
|
int written = std::snprintf(relative, 4096, ".lardon3d/checkpoints/%llu.scope",
|
||||||
|
static_cast<unsigned long long>(task_id));
|
||||||
|
if (written <= 0 || written >= 4096) return false;
|
||||||
|
std::string final_path = project_path + "/" + relative;
|
||||||
|
std::string temporary = final_path + ".tmp";
|
||||||
|
int fd = open(temporary.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600);
|
||||||
|
if (fd < 0) return false;
|
||||||
|
std::vector<unsigned char> bytes;
|
||||||
|
bytes.reserve(20U + ids.size() * 8U);
|
||||||
|
bytes.insert(bytes.end(), kScopeMagic.begin(), kScopeMagic.end());
|
||||||
|
for (unsigned shift = 0; shift < 4; ++shift)
|
||||||
|
bytes.push_back(static_cast<unsigned char>((kScopeVersion >> (shift * 8U)) & 0xffU));
|
||||||
|
uint64_t count = ids.size();
|
||||||
|
for (unsigned shift = 0; shift < 8; ++shift)
|
||||||
|
bytes.push_back(static_cast<unsigned char>((count >> (shift * 8U)) & 0xffU));
|
||||||
|
for (uint64_t id : ids)
|
||||||
|
for (unsigned shift = 0; shift < 8; ++shift)
|
||||||
|
bytes.push_back(static_cast<unsigned char>((id >> (shift * 8U)) & 0xffU));
|
||||||
|
bool ok = write_all(fd, bytes.data(), bytes.size()) && fsync(fd) == 0;
|
||||||
|
if (close(fd) != 0) ok = false;
|
||||||
|
if (!ok) {
|
||||||
|
(void)unlink(temporary.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ok = rename(temporary.c_str(), final_path.c_str()) == 0;
|
||||||
|
if (ok) ok = hash_bytes(bytes.data(), bytes.size(), sha256);
|
||||||
|
if (ok) {
|
||||||
|
int directory = open((project_path + "/.lardon3d/checkpoints").c_str(), O_RDONLY | O_DIRECTORY);
|
||||||
|
ok = directory >= 0 && fsync(directory) == 0 && close(directory) == 0;
|
||||||
|
}
|
||||||
|
if (!ok) (void)unlink(temporary.c_str());
|
||||||
|
*size_bytes = bytes.size();
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load_scope(const Context &context, std::vector<uint64_t> *ids) {
|
||||||
|
std::string path = context.project_path + "/" + context.durable.scope_path;
|
||||||
|
int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
|
||||||
|
if (fd < 0 || context.durable.scope_size_bytes > kMaximumScopeBytes) {
|
||||||
|
if (fd >= 0) (void)close(fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::vector<unsigned char> bytes(static_cast<size_t>(context.durable.scope_size_bytes));
|
||||||
|
bool ok = read_all(fd, bytes.data(), bytes.size());
|
||||||
|
if (close(fd) != 0) ok = false;
|
||||||
|
unsigned char digest[32]{};
|
||||||
|
ok = ok && bytes.size() >= 20 && std::memcmp(bytes.data(), kScopeMagic.data(), 8) == 0 &&
|
||||||
|
bytes[8] == 1 && bytes[9] == 0 && bytes[10] == 0 && bytes[11] == 0 &&
|
||||||
|
hash_bytes(bytes.data(), bytes.size(), digest) &&
|
||||||
|
std::memcmp(digest, context.durable.scope_sha256, 32) == 0;
|
||||||
|
if (!ok) return false;
|
||||||
|
uint64_t count = 0;
|
||||||
|
for (unsigned shift = 0; shift < 8; ++shift)
|
||||||
|
count |= static_cast<uint64_t>(bytes[12 + shift]) << (shift * 8U);
|
||||||
|
if (count == 0 || count > 10000000ULL || bytes.size() != 20U + count * 8U ||
|
||||||
|
count != context.durable.gvr_count) return false;
|
||||||
|
ids->resize(static_cast<size_t>(count));
|
||||||
|
for (size_t i = 0; i < ids->size(); ++i) {
|
||||||
|
uint64_t value = 0;
|
||||||
|
for (unsigned shift = 0; shift < 8; ++shift)
|
||||||
|
value |= static_cast<uint64_t>(bytes[20 + i * 8 + shift]) << (shift * 8U);
|
||||||
|
if (value == 0 || (i != 0 && (*ids)[i - 1] >= value)) return false;
|
||||||
|
(*ids)[i] = value;
|
||||||
|
}
|
||||||
|
unsigned char scope[32]{};
|
||||||
|
return scope_hash(*ids, scope) && std::memcmp(scope, context.durable.input_scope_hash, 32) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool persist(Context *context, const Lardon3DTask *task) {
|
||||||
|
Lardon3DTaskDurableSnapshot snapshot{};
|
||||||
|
if (!lardon3d_task_durable_snapshot(task, &snapshot)) return false;
|
||||||
|
char relative[4096];
|
||||||
|
if (std::snprintf(relative, sizeof(relative), "%s", context->durable.scope_path) <= 0)
|
||||||
|
return false;
|
||||||
|
Lardon3DProjectDbCheckpoint checkpoint{};
|
||||||
|
std::snprintf(checkpoint.path, sizeof(checkpoint.path), ".lardon3d/checkpoints/%llu.chk",
|
||||||
|
static_cast<unsigned long long>(snapshot.id));
|
||||||
|
checkpoint.format_version = LARDON3D_TASK_CHECKPOINT_VERSION;
|
||||||
|
checkpoint.durability = LARDON3D_DB_CHECKPOINT_DURABLE;
|
||||||
|
checkpoint.updated_at = 0;
|
||||||
|
std::string checkpoint_path = context->project_path + "/" + checkpoint.path;
|
||||||
|
Lardon3DTaskCheckpointResult saved = lardon3d_task_checkpoint_save(checkpoint_path.c_str(), &snapshot);
|
||||||
|
if (saved != LARDON3D_TASK_CHECKPOINT_OK &&
|
||||||
|
saved != LARDON3D_TASK_CHECKPOINT_PUBLISHED_NOT_DURABLE) return false;
|
||||||
|
checkpoint.durability = saved == LARDON3D_TASK_CHECKPOINT_OK
|
||||||
|
? LARDON3D_DB_CHECKPOINT_DURABLE
|
||||||
|
: LARDON3D_DB_CHECKPOINT_PUBLISHED_NOT_DURABLE;
|
||||||
|
Lardon3DProjectDbTrackBuilderTask durable = context->durable;
|
||||||
|
return lardon3d_project_db_record_track_builder_task(
|
||||||
|
context->database, &snapshot, LARDON3D_TRACK_BUILDER_TASK_KIND,
|
||||||
|
LARDON3D_TRACK_BUILDER_TASK_KIND_VERSION, &checkpoint, &durable, 0) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool run(Lardon3DTask *task, void *userdata) {
|
||||||
|
try {
|
||||||
|
Context *context = static_cast<Context *>(userdata);
|
||||||
|
if (!lardon3d_task_checkpoint(task)) return false;
|
||||||
|
Lardon3DTrackBuilderProjectRequest request = {
|
||||||
|
context->project_path.c_str(), context->database, context->durable.verifier_kind,
|
||||||
|
context->durable.verifier_version, context->durable.verifier_fingerprint,
|
||||||
|
context->ids.data(), context->ids.size()};
|
||||||
|
Lardon3DTrackBuilderProjectResult result{};
|
||||||
|
Lardon3DTrackBuilderProjectStatus status = lardon3d_track_builder_build_project(&request, &result);
|
||||||
|
if (status != LARDON3D_TRACK_BUILDER_PROJECT_OK)
|
||||||
|
return lardon3d_task_fail(task, "Track Builder publication impossible.");
|
||||||
|
return lardon3d_task_set_progress(task, 100, result.reused ? "Track Set réutilisé."
|
||||||
|
: "Track Set publié.");
|
||||||
|
} catch (...) {
|
||||||
|
return lardon3d_task_fail(task, "Track Builder runtime exception.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void finished(const Lardon3DTask *task, void *userdata) {
|
||||||
|
try {
|
||||||
|
#ifdef LARDON3D_TRACK_BUILDER_TASK_TESTING
|
||||||
|
const char *skip = std::getenv("LARDON3D_TRACK_BUILDER_TEST_SKIP_FINISHED");
|
||||||
|
if (skip && std::strcmp(skip, "1") == 0) return;
|
||||||
|
#endif
|
||||||
|
(void)persist(static_cast<Context *>(userdata), task);
|
||||||
|
} catch (...) {
|
||||||
|
/* Terminal checkpoint failure is reported by the durable DB on recovery. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool make_context(const Lardon3DTaskReconstructionContext *runtime,
|
||||||
|
const Lardon3DProjectDbTrackBuilderTask &durable,
|
||||||
|
Context **output) {
|
||||||
|
if (!runtime || !runtime->project_path || !runtime->project_db ||
|
||||||
|
!runtime->resource_governor || durable.builder_version != LARDON3D_TRACK_BUILDER_VERSION ||
|
||||||
|
durable.scope_format_version != kScopeVersion) return false;
|
||||||
|
auto *context = new (std::nothrow) Context;
|
||||||
|
if (!context) return false;
|
||||||
|
context->project_path = runtime->project_path;
|
||||||
|
context->database = runtime->project_db;
|
||||||
|
context->governor = runtime->resource_governor;
|
||||||
|
context->durable = durable;
|
||||||
|
if (!load_scope(*context, &context->ids)) {
|
||||||
|
delete context;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
unsigned char fingerprint[32]{};
|
||||||
|
if (!lardon3d_track_builder_fingerprint(fingerprint) ||
|
||||||
|
std::memcmp(fingerprint, durable.builder_fingerprint, 32) != 0) {
|
||||||
|
delete context;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*output = context;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
static bool reconstruct_impl(
|
||||||
|
const Lardon3DTaskDurableSnapshot *snapshot, void *userdata,
|
||||||
|
Lardon3DTaskKindBinding *binding) {
|
||||||
|
if (!snapshot || !userdata || !binding) return false;
|
||||||
|
auto *runtime = static_cast<Lardon3DTaskReconstructionContext *>(userdata);
|
||||||
|
Lardon3DProjectDbTrackBuilderTask durable{};
|
||||||
|
if (lardon3d_project_db_load_track_builder_task(runtime->project_db, snapshot->id, &durable) !=
|
||||||
|
LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
Context *context = nullptr;
|
||||||
|
if (!make_context(runtime, durable, &context)) return false;
|
||||||
|
*binding = {run, context, destroy_context, finished, context};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Lardon3DTask *create_impl(
|
||||||
|
Lardon3DAppState *state,
|
||||||
|
const Lardon3DTrackBuilderTaskConfiguration *configuration, uint64_t *task_id) {
|
||||||
|
if (task_id) *task_id = 0;
|
||||||
|
if (!state || !state->project_loaded || !state->project_db || !state->resource_governor ||
|
||||||
|
!configuration || !configuration->project_path || !configuration->verifier_fingerprint ||
|
||||||
|
!configuration->gvr_ids || configuration->gvr_count == 0 || configuration->verifier_kind <= 0 ||
|
||||||
|
configuration->verifier_version == 0 ||
|
||||||
|
(configuration->gvr_count > std::numeric_limits<size_t>::max() / sizeof(uint64_t)) ||
|
||||||
|
configuration->gvr_count > (kMaximumScopeBytes - 20U) / 8U) return nullptr;
|
||||||
|
std::vector<uint64_t> ids(configuration->gvr_ids,
|
||||||
|
configuration->gvr_ids + configuration->gvr_count);
|
||||||
|
for (size_t i = 0; i < ids.size(); ++i)
|
||||||
|
if (ids[i] == 0 || (i != 0 && ids[i - 1] >= ids[i])) return nullptr;
|
||||||
|
uint64_t raw_edges = 0;
|
||||||
|
for (uint64_t id : ids) {
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult gvr{};
|
||||||
|
if (lardon3d_project_db_load_geometric_verification_result(state->project_db, id, &gvr) !=
|
||||||
|
LARDON3D_PROJECT_DB_OK || !checked_add(raw_edges, gvr.inlier_count, &raw_edges)) return nullptr;
|
||||||
|
}
|
||||||
|
uint64_t edge_bytes = 0, obs_bytes = 0, memory = kFixedMemory;
|
||||||
|
if (!checked_mul(raw_edges, kBytesPerEdge, &edge_bytes) ||
|
||||||
|
!checked_mul(raw_edges, 2 * kBytesPerObservation, &obs_bytes) ||
|
||||||
|
!checked_add(memory, edge_bytes, &memory) || !checked_add(memory, obs_bytes, &memory) ||
|
||||||
|
!checked_mul(memory, raw_edges > kHighScaleEdgeThreshold
|
||||||
|
? kHighScaleSafetyMultiplier
|
||||||
|
: kNormalSafetyMultiplier,
|
||||||
|
&memory)) return nullptr;
|
||||||
|
unsigned char scope[32]{}, builder[32]{};
|
||||||
|
if (!scope_hash(ids, scope) || !lardon3d_track_builder_fingerprint(builder)) return nullptr;
|
||||||
|
uint64_t id = 0;
|
||||||
|
if (lardon3d_project_db_allocate_task_id(state->project_db, &id) != LARDON3D_PROJECT_DB_OK) return nullptr;
|
||||||
|
Context *context = new (std::nothrow) Context;
|
||||||
|
if (!context) return nullptr;
|
||||||
|
context->project_path = configuration->project_path;
|
||||||
|
context->database = state->project_db;
|
||||||
|
context->governor = state->resource_governor;
|
||||||
|
context->ids = ids;
|
||||||
|
context->durable.task_id = id;
|
||||||
|
std::snprintf(context->durable.builder_kind, sizeof(context->durable.builder_kind), "track_builder");
|
||||||
|
context->durable.builder_version = LARDON3D_TRACK_BUILDER_VERSION;
|
||||||
|
std::memcpy(context->durable.builder_fingerprint, builder, 32);
|
||||||
|
context->durable.verifier_kind = configuration->verifier_kind;
|
||||||
|
context->durable.verifier_version = configuration->verifier_version;
|
||||||
|
std::memcpy(context->durable.verifier_fingerprint, configuration->verifier_fingerprint, 32);
|
||||||
|
std::memcpy(context->durable.input_scope_hash, scope, 32);
|
||||||
|
context->durable.gvr_count = ids.size();
|
||||||
|
context->durable.scope_format_version = kScopeVersion;
|
||||||
|
if (!scope_asset(context->project_path, id, ids, context->durable.scope_path,
|
||||||
|
&context->durable.scope_size_bytes, context->durable.scope_sha256)) {
|
||||||
|
delete context;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
Lardon3DResourceEstimate estimate = {memory, 0, 0, 0, 1, 1, 1, 0, 1, LARDON3D_RESOURCE_TASK_CPU};
|
||||||
|
Lardon3DTask *task = lardon3d_task_create_typed(
|
||||||
|
"Track Builder", &estimate, LARDON3D_TRACK_BUILDER_TASK_KIND,
|
||||||
|
LARDON3D_TRACK_BUILDER_TASK_KIND_VERSION, run, context, destroy_context);
|
||||||
|
if (!task || !lardon3d_task_assign_id(task, id) ||
|
||||||
|
!lardon3d_task_set_finished_callback(task, finished, context) || !persist(context, task)) {
|
||||||
|
if (task) lardon3d_task_destroy(task); else delete context;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
*task_id = id;
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" bool lardon3d_track_builder_task_reconstruct(
|
||||||
|
const Lardon3DTaskDurableSnapshot *snapshot, void *userdata,
|
||||||
|
Lardon3DTaskKindBinding *binding) {
|
||||||
|
try {
|
||||||
|
return reconstruct_impl(snapshot, userdata, binding);
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" Lardon3DTask *lardon3d_project_create_track_builder_task(
|
||||||
|
Lardon3DAppState *state,
|
||||||
|
const Lardon3DTrackBuilderTaskConfiguration *configuration, uint64_t *task_id) {
|
||||||
|
try {
|
||||||
|
return create_impl(state, configuration, task_id);
|
||||||
|
} catch (...) {
|
||||||
|
if (task_id) *task_id = 0;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" bool lardon3d_project_enqueue_track_builder_task(
|
||||||
|
Lardon3DAppState *state,
|
||||||
|
const Lardon3DTrackBuilderTaskConfiguration *configuration, uint64_t *task_id) {
|
||||||
|
try {
|
||||||
|
Lardon3DTask *task = lardon3d_project_create_track_builder_task(
|
||||||
|
state, configuration, task_id);
|
||||||
|
if (!task || !state || !state->task_queue ||
|
||||||
|
!lardon3d_task_queue_add(state->task_queue, task, nullptr)) {
|
||||||
|
if (task) lardon3d_task_destroy(task);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
if (task_id) *task_id = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -161,7 +161,7 @@ static bool create_v11_database(const char *path) {
|
||||||
"DROP TABLE geometric_verification_results;"
|
"DROP TABLE geometric_verification_results;"
|
||||||
"DROP TABLE track_observations;"
|
"DROP TABLE track_observations;"
|
||||||
"DROP TABLE tracks;"
|
"DROP TABLE tracks;"
|
||||||
"DROP TABLE track_sets;"
|
"DROP TABLE track_sets;DROP TABLE track_builder_tasks;"
|
||||||
"UPDATE metadata SET value=11 WHERE key='schema_version';"
|
"UPDATE metadata SET value=11 WHERE key='schema_version';"
|
||||||
"COMMIT;PRAGMA foreign_keys=ON;");
|
"COMMIT;PRAGMA foreign_keys=ON;");
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +170,7 @@ static bool test_model_api(const char *path) {
|
||||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||||
Lardon3DProjectDb *database = NULL;
|
Lardon3DProjectDb *database = NULL;
|
||||||
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 14);
|
CHECK(lardon3d_project_db_schema_version(database) == 15);
|
||||||
Parents parents;
|
Parents parents;
|
||||||
CHECK(create_parents(database, &parents));
|
CHECK(create_parents(database, &parents));
|
||||||
|
|
||||||
|
|
@ -405,7 +405,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) {
|
||||||
"name='geometric_verification_results'",
|
"name='geometric_verification_results'",
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(v11_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(v11_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 14);
|
CHECK(lardon3d_project_db_schema_version(database) == 15);
|
||||||
Parents parents;
|
Parents parents;
|
||||||
CHECK(create_parents(database, &parents));
|
CHECK(create_parents(database, &parents));
|
||||||
unsigned char fingerprint[32] = {0x91};
|
unsigned char fingerprint[32] = {0x91};
|
||||||
|
|
@ -417,7 +417,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) {
|
||||||
&migrated_result) == LARDON3D_PROJECT_DB_OK);
|
&migrated_result) == LARDON3D_PROJECT_DB_OK);
|
||||||
uint64_t migrated_result_id = migrated_result.geometric_verification_result_id;
|
uint64_t migrated_result_id = migrated_result.geometric_verification_result_id;
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
CHECK(query_integer(v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 14));
|
CHECK(query_integer(v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 15));
|
||||||
CHECK(query_integer(v11_path,
|
CHECK(query_integer(v11_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='index' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='index' AND "
|
||||||
"name='geometric_verification_results_parent_idx'",
|
"name='geometric_verification_results_parent_idx'",
|
||||||
|
|
@ -438,7 +438,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) {
|
||||||
"name='geometric_verification_results'",
|
"name='geometric_verification_results'",
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(failed_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(failed_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 14);
|
CHECK(lardon3d_project_db_schema_version(database) == 15);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -336,6 +336,7 @@ static bool run_task_test(void) {
|
||||||
"DROP TABLE track_observations;"
|
"DROP TABLE track_observations;"
|
||||||
"DROP TABLE tracks;"
|
"DROP TABLE tracks;"
|
||||||
"DROP TABLE track_sets;"
|
"DROP TABLE track_sets;"
|
||||||
|
"DROP TABLE track_builder_tasks;"
|
||||||
"UPDATE metadata SET value=12 WHERE key='schema_version';COMMIT;"));
|
"UPDATE metadata SET value=12 WHERE key='schema_version';COMMIT;"));
|
||||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V13", "1", 1) == 0);
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V13", "1", 1) == 0);
|
||||||
Lardon3DProjectDb *database = NULL;
|
Lardon3DProjectDb *database = NULL;
|
||||||
|
|
@ -352,7 +353,7 @@ static bool run_task_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 14);
|
CHECK(lardon3d_project_db_schema_version(database) == 15);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
CHECK(remove_tree(root));
|
CHECK(remove_tree(root));
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ static bool create_v9_database(const char *path) {
|
||||||
"DROP TABLE match_results;"
|
"DROP TABLE match_results;"
|
||||||
"DROP TABLE track_observations;"
|
"DROP TABLE track_observations;"
|
||||||
"DROP TABLE tracks;"
|
"DROP TABLE tracks;"
|
||||||
"DROP TABLE track_sets;"
|
"DROP TABLE track_sets;DROP TABLE track_builder_tasks;"
|
||||||
"UPDATE metadata SET value=9 WHERE key='schema_version';COMMIT;PRAGMA foreign_keys=ON;";
|
"UPDATE metadata SET value=9 WHERE key='schema_version';COMMIT;PRAGMA foreign_keys=ON;";
|
||||||
bool ok = sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK;
|
bool ok = sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK;
|
||||||
return sqlite3_close(connection) == SQLITE_OK && ok;
|
return sqlite3_close(connection) == SQLITE_OK && ok;
|
||||||
|
|
@ -98,7 +98,7 @@ static bool run_test(void) {
|
||||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||||
Lardon3DProjectDb *database = NULL;
|
Lardon3DProjectDb *database = NULL;
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(database && lardon3d_project_db_schema_version(database) == 14);
|
CHECK(database && lardon3d_project_db_schema_version(database) == 15);
|
||||||
|
|
||||||
Lardon3DProjectDbScanSet scanset;
|
Lardon3DProjectDbScanSet scanset;
|
||||||
CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) ==
|
CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) ==
|
||||||
|
|
@ -457,7 +457,7 @@ static bool run_test(void) {
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 14);
|
CHECK(lardon3d_project_db_schema_version(database) == 15);
|
||||||
|
|
||||||
/* Verify persistence: load previously created results */
|
/* Verify persistence: load previously created results */
|
||||||
CHECK(lardon3d_project_db_load_match_result(database, first_id, &loaded) ==
|
CHECK(lardon3d_project_db_load_match_result(database, first_id, &loaded) ==
|
||||||
|
|
@ -497,10 +497,10 @@ static bool run_test(void) {
|
||||||
CHECK(create_v9_database(v9_path));
|
CHECK(create_v9_database(v9_path));
|
||||||
CHECK(query_integer(v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 9));
|
CHECK(query_integer(v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 9));
|
||||||
CHECK(lardon3d_project_db_open(v9_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(v9_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 14);
|
CHECK(lardon3d_project_db_schema_version(database) == 15);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 14));
|
CHECK(query_integer(v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 15));
|
||||||
CHECK(query_integer(v9_path,
|
CHECK(query_integer(v9_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
"name='match_results'", 1));
|
"name='match_results'", 1));
|
||||||
|
|
@ -519,7 +519,7 @@ static bool run_test(void) {
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
"name='matcher_tasks'", 0));
|
"name='matcher_tasks'", 0));
|
||||||
CHECK(lardon3d_project_db_open(failed_v10_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
CHECK(lardon3d_project_db_open(failed_v10_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 14);
|
lardon3d_project_db_schema_version(database) == 15);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ static bool downgrade_project_to_historical_v10(const char *database_path) {
|
||||||
"DROP TABLE matcher_tasks;"
|
"DROP TABLE matcher_tasks;"
|
||||||
"DROP TABLE track_observations;"
|
"DROP TABLE track_observations;"
|
||||||
"DROP TABLE tracks;"
|
"DROP TABLE tracks;"
|
||||||
"DROP TABLE track_sets;"
|
"DROP TABLE track_sets;DROP TABLE track_builder_tasks;"
|
||||||
"UPDATE metadata SET value=10 WHERE key='schema_version';"
|
"UPDATE metadata SET value=10 WHERE key='schema_version';"
|
||||||
"COMMIT;PRAGMA foreign_keys=ON;";
|
"COMMIT;PRAGMA foreign_keys=ON;";
|
||||||
bool success = sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK;
|
bool success = sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK;
|
||||||
|
|
@ -355,7 +355,7 @@ static bool run_test(void) {
|
||||||
"name='matcher_tasks'",
|
"name='matcher_tasks'",
|
||||||
0));
|
0));
|
||||||
CHECK(reopen_runtime(&fixture));
|
CHECK(reopen_runtime(&fixture));
|
||||||
CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 14);
|
CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 15);
|
||||||
CHECK(query_integer(database_path,
|
CHECK(query_integer(database_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
"name='matcher_tasks'",
|
"name='matcher_tasks'",
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ static bool create_future_database(const char *path) {
|
||||||
}
|
}
|
||||||
bool ok = sqlite3_exec(connection,
|
bool ok = sqlite3_exec(connection,
|
||||||
"CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);"
|
"CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);"
|
||||||
"INSERT INTO metadata VALUES('schema_version',15);",
|
"INSERT INTO metadata VALUES('schema_version',16);",
|
||||||
NULL, NULL, NULL) == SQLITE_OK;
|
NULL, NULL, NULL) == SQLITE_OK;
|
||||||
return sqlite3_close(connection) == SQLITE_OK && ok;
|
return sqlite3_close(connection) == SQLITE_OK && ok;
|
||||||
}
|
}
|
||||||
|
|
@ -123,7 +123,8 @@ static bool create_v7_database(const char *path) {
|
||||||
"DROP TABLE match_results;"
|
"DROP TABLE match_results;"
|
||||||
"DROP TABLE candidate_pair_generate_tasks;"
|
"DROP TABLE candidate_pair_generate_tasks;"
|
||||||
"DROP TABLE candidate_pairs;"
|
"DROP TABLE candidate_pairs;"
|
||||||
"DROP TABLE track_observations;"
|
"DROP TABLE track_observations;"
|
||||||
|
"DROP TABLE track_builder_tasks;"
|
||||||
"DROP TABLE tracks;"
|
"DROP TABLE tracks;"
|
||||||
"DROP TABLE track_sets;"
|
"DROP TABLE track_sets;"
|
||||||
"UPDATE metadata SET value=7 WHERE key='schema_version';COMMIT;PRAGMA foreign_keys=ON;";
|
"UPDATE metadata SET value=7 WHERE key='schema_version';COMMIT;PRAGMA foreign_keys=ON;";
|
||||||
|
|
@ -152,7 +153,8 @@ static bool create_v6_database(const char *path) {
|
||||||
"ALTER TABLE feature_sets DROP COLUMN coverage_ratio;"
|
"ALTER TABLE feature_sets DROP COLUMN coverage_ratio;"
|
||||||
"ALTER TABLE feature_sets DROP COLUMN total_cells;"
|
"ALTER TABLE feature_sets DROP COLUMN total_cells;"
|
||||||
"ALTER TABLE feature_sets DROP COLUMN occupied_cells;"
|
"ALTER TABLE feature_sets DROP COLUMN occupied_cells;"
|
||||||
"DROP TABLE track_observations;"
|
"DROP TABLE track_observations;"
|
||||||
|
"DROP TABLE track_builder_tasks;"
|
||||||
"DROP TABLE tracks;"
|
"DROP TABLE tracks;"
|
||||||
"DROP TABLE track_sets;"
|
"DROP TABLE track_sets;"
|
||||||
"UPDATE metadata SET value=6 WHERE key='schema_version';COMMIT;PRAGMA foreign_keys=ON;";
|
"UPDATE metadata SET value=6 WHERE key='schema_version';COMMIT;PRAGMA foreign_keys=ON;";
|
||||||
|
|
@ -176,7 +178,8 @@ static bool create_v10_database(const char *path) {
|
||||||
"DROP TABLE geometric_verifier_tasks;"
|
"DROP TABLE geometric_verifier_tasks;"
|
||||||
"DROP TABLE geometric_verification_results;"
|
"DROP TABLE geometric_verification_results;"
|
||||||
"DROP TABLE matcher_tasks;"
|
"DROP TABLE matcher_tasks;"
|
||||||
"DROP TABLE track_observations;"
|
"DROP TABLE track_observations;"
|
||||||
|
"DROP TABLE track_builder_tasks;"
|
||||||
"DROP TABLE tracks;"
|
"DROP TABLE tracks;"
|
||||||
"DROP TABLE track_sets;"
|
"DROP TABLE track_sets;"
|
||||||
"UPDATE metadata SET value=10 WHERE key='schema_version';"
|
"UPDATE metadata SET value=10 WHERE key='schema_version';"
|
||||||
|
|
@ -194,7 +197,8 @@ static bool create_v13_database(const char *path) {
|
||||||
if (sqlite3_open(path, &connection) != SQLITE_OK) return false;
|
if (sqlite3_open(path, &connection) != SQLITE_OK) return false;
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
"DROP TABLE track_observations;"
|
"DROP TABLE track_observations;"
|
||||||
|
"DROP TABLE track_builder_tasks;"
|
||||||
"DROP TABLE tracks;"
|
"DROP TABLE tracks;"
|
||||||
"DROP TABLE track_sets;"
|
"DROP TABLE track_sets;"
|
||||||
"UPDATE metadata SET value=13 WHERE key='schema_version';COMMIT;PRAGMA foreign_keys=ON;";
|
"UPDATE metadata SET value=13 WHERE key='schema_version';COMMIT;PRAGMA foreign_keys=ON;";
|
||||||
|
|
@ -202,6 +206,22 @@ static bool create_v13_database(const char *path) {
|
||||||
return sqlite3_close(connection) == SQLITE_OK && ok;
|
return sqlite3_close(connection) == SQLITE_OK && ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool create_v14_database(const char *path) {
|
||||||
|
Lardon3DProjectDb *database = NULL;
|
||||||
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||||
|
if (lardon3d_project_db_open(path, &database, error) != LARDON3D_PROJECT_DB_OK) return false;
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
sqlite3 *connection = NULL;
|
||||||
|
if (sqlite3_open(path, &connection) != SQLITE_OK) return false;
|
||||||
|
bool ok = sqlite3_exec(connection,
|
||||||
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE track_builder_tasks;"
|
||||||
|
"UPDATE metadata SET value=14 WHERE key='schema_version';"
|
||||||
|
"COMMIT;PRAGMA foreign_keys=ON;",
|
||||||
|
NULL, NULL, NULL) == SQLITE_OK;
|
||||||
|
return sqlite3_close(connection) == SQLITE_OK && ok;
|
||||||
|
}
|
||||||
|
|
||||||
static bool create_v5_database(const char *path) {
|
static bool create_v5_database(const char *path) {
|
||||||
if (!create_v6_database(path)) return false;
|
if (!create_v6_database(path)) return false;
|
||||||
sqlite3 *connection = NULL;
|
sqlite3 *connection = NULL;
|
||||||
|
|
@ -369,7 +389,7 @@ static bool run_test(void) {
|
||||||
char v4_path[512], failed_v5_path[512], failed_v6_path[512], failed_v7_path[512];
|
char v4_path[512], failed_v5_path[512], failed_v6_path[512], failed_v7_path[512];
|
||||||
char direct_v5_path[512], v8_path[512], failed_v8_path[512];
|
char direct_v5_path[512], v8_path[512], failed_v8_path[512];
|
||||||
char v10_path[512], failed_v11_path[512];
|
char v10_path[512], failed_v11_path[512];
|
||||||
char v13_path[512], failed_v14_path[512];
|
char v13_path[512], true_v14_path[512], failed_v14_path[512], failed_v15_path[512];
|
||||||
CHECK(snprintf(database_path, sizeof(database_path), "%s/project.db", directory) > 0);
|
CHECK(snprintf(database_path, sizeof(database_path), "%s/project.db", directory) > 0);
|
||||||
CHECK(snprintf(artifact_path, sizeof(artifact_path), "%s/artifact.bin", directory) > 0);
|
CHECK(snprintf(artifact_path, sizeof(artifact_path), "%s/artifact.bin", directory) > 0);
|
||||||
CHECK(snprintf(future_path, sizeof(future_path), "%s/future.db", directory) > 0);
|
CHECK(snprintf(future_path, sizeof(future_path), "%s/future.db", directory) > 0);
|
||||||
|
|
@ -397,12 +417,14 @@ static bool run_test(void) {
|
||||||
CHECK(snprintf(v10_path, sizeof(v10_path), "%s/v10.db", directory) > 0);
|
CHECK(snprintf(v10_path, sizeof(v10_path), "%s/v10.db", directory) > 0);
|
||||||
CHECK(snprintf(failed_v11_path, sizeof(failed_v11_path), "%s/failed-v11.db", directory) > 0);
|
CHECK(snprintf(failed_v11_path, sizeof(failed_v11_path), "%s/failed-v11.db", directory) > 0);
|
||||||
CHECK(snprintf(v13_path, sizeof(v13_path), "%s/v13.db", directory) > 0);
|
CHECK(snprintf(v13_path, sizeof(v13_path), "%s/v13.db", directory) > 0);
|
||||||
|
CHECK(snprintf(true_v14_path, sizeof(true_v14_path), "%s/true-v14.db", directory) > 0);
|
||||||
CHECK(snprintf(failed_v14_path, sizeof(failed_v14_path), "%s/failed-v14.db", directory) > 0);
|
CHECK(snprintf(failed_v14_path, sizeof(failed_v14_path), "%s/failed-v14.db", directory) > 0);
|
||||||
|
CHECK(snprintf(failed_v15_path, sizeof(failed_v15_path), "%s/failed-v15.db", directory) > 0);
|
||||||
|
|
||||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||||
Lardon3DProjectDb *database = NULL;
|
Lardon3DProjectDb *database = NULL;
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(database && lardon3d_project_db_schema_version(database) == 14);
|
CHECK(database && lardon3d_project_db_schema_version(database) == 15);
|
||||||
bool legacy_pending = true;
|
bool legacy_pending = true;
|
||||||
CHECK(lardon3d_project_db_legacy_catalog_pending(database, &legacy_pending) ==
|
CHECK(lardon3d_project_db_legacy_catalog_pending(database, &legacy_pending) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
|
|
@ -668,7 +690,7 @@ static bool run_test(void) {
|
||||||
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||||
lardon3d_project_db_close(contexts[0].database);
|
lardon3d_project_db_close(contexts[0].database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(database_path, "SELECT value FROM metadata WHERE key='schema_version'", 14));
|
CHECK(query_integer(database_path, "SELECT value FROM metadata WHERE key='schema_version'", 15));
|
||||||
CHECK(query_integer(database_path, "SELECT count(*) FROM tasks WHERE task_id=1", 1));
|
CHECK(query_integer(database_path, "SELECT count(*) FROM tasks WHERE task_id=1", 1));
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_load_task(database, 1, &task) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_load_task(database, 1, &task) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
|
@ -690,7 +712,7 @@ static bool run_test(void) {
|
||||||
|
|
||||||
CHECK(create_v1_database(legacy_path));
|
CHECK(create_v1_database(legacy_path));
|
||||||
CHECK(lardon3d_project_db_open(legacy_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(legacy_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 14);
|
CHECK(lardon3d_project_db_schema_version(database) == 15);
|
||||||
CHECK(lardon3d_project_db_get_project(database, &loaded_project) == LARDON3D_PROJECT_DB_OK &&
|
CHECK(lardon3d_project_db_get_project(database, &loaded_project) == LARDON3D_PROJECT_DB_OK &&
|
||||||
strcmp(loaded_project.stable_id, "legacy-project") == 0);
|
strcmp(loaded_project.stable_id, "legacy-project") == 0);
|
||||||
CHECK(lardon3d_project_db_load_task(database, 9, &task) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_load_task(database, 9, &task) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
|
@ -700,7 +722,7 @@ static bool run_test(void) {
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(legacy_path, "SELECT value FROM metadata WHERE key='schema_version'", 14));
|
CHECK(query_integer(legacy_path, "SELECT value FROM metadata WHERE key='schema_version'", 15));
|
||||||
|
|
||||||
CHECK(create_v1_database(failed_migration_path));
|
CHECK(create_v1_database(failed_migration_path));
|
||||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V2", "1", 1) == 0);
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V2", "1", 1) == 0);
|
||||||
|
|
@ -725,7 +747,7 @@ static bool run_test(void) {
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(v2_path, "SELECT value FROM metadata WHERE key='schema_version'", 14));
|
CHECK(query_integer(v2_path, "SELECT value FROM metadata WHERE key='schema_version'", 15));
|
||||||
|
|
||||||
CHECK(create_v2_database(failed_v3_migration_path));
|
CHECK(create_v2_database(failed_v3_migration_path));
|
||||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V3", "1", 1) == 0);
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V3", "1", 1) == 0);
|
||||||
|
|
@ -753,7 +775,7 @@ static bool run_test(void) {
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(v3_path, "SELECT value FROM metadata WHERE key='schema_version'", 14));
|
CHECK(query_integer(v3_path, "SELECT value FROM metadata WHERE key='schema_version'", 15));
|
||||||
|
|
||||||
CHECK(create_v3_database(failed_v4_path));
|
CHECK(create_v3_database(failed_v4_path));
|
||||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V4", "1", 1) == 0);
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V4", "1", 1) == 0);
|
||||||
|
|
@ -770,7 +792,7 @@ static bool run_test(void) {
|
||||||
fprintf(stderr, "Migration v4 (%d): %s\n", (int)v4_result, error);
|
fprintf(stderr, "Migration v4 (%d): %s\n", (int)v4_result, error);
|
||||||
}
|
}
|
||||||
CHECK(v4_result == LARDON3D_PROJECT_DB_OK);
|
CHECK(v4_result == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 14);
|
CHECK(lardon3d_project_db_schema_version(database) == 15);
|
||||||
CHECK(lardon3d_project_db_load_task(database, 9, &task) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_load_task(database, 9, &task) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_load_artifact(database, "legacy-artifact", &loaded_artifact) ==
|
CHECK(lardon3d_project_db_load_artifact(database, "legacy-artifact", &loaded_artifact) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
|
@ -809,24 +831,24 @@ static bool run_test(void) {
|
||||||
fprintf(stderr, "Nouvelle tentative migration v7 (%d): %s\n", (int)retry_v7, error);
|
fprintf(stderr, "Nouvelle tentative migration v7 (%d): %s\n", (int)retry_v7, error);
|
||||||
}
|
}
|
||||||
CHECK(retry_v7 == LARDON3D_PROJECT_DB_OK &&
|
CHECK(retry_v7 == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 14);
|
lardon3d_project_db_schema_version(database) == 15);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
CHECK(create_v5_database(direct_v5_path));
|
CHECK(create_v5_database(direct_v5_path));
|
||||||
CHECK(query_integer(direct_v5_path, "SELECT value FROM metadata WHERE key='schema_version'", 5));
|
CHECK(query_integer(direct_v5_path, "SELECT value FROM metadata WHERE key='schema_version'", 5));
|
||||||
CHECK(lardon3d_project_db_open(direct_v5_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
CHECK(lardon3d_project_db_open(direct_v5_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 14);
|
lardon3d_project_db_schema_version(database) == 15);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
CHECK(create_v7_database(v8_path));
|
CHECK(create_v7_database(v8_path));
|
||||||
CHECK(query_integer(v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 7));
|
CHECK(query_integer(v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 7));
|
||||||
CHECK(lardon3d_project_db_open(v8_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(v8_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 14);
|
CHECK(lardon3d_project_db_schema_version(database) == 15);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 14));
|
CHECK(query_integer(v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 15));
|
||||||
|
|
||||||
CHECK(create_v7_database(failed_v8_path));
|
CHECK(create_v7_database(failed_v8_path));
|
||||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V8", "1", 1) == 0);
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V8", "1", 1) == 0);
|
||||||
|
|
@ -837,7 +859,7 @@ static bool run_test(void) {
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
"name='candidate_pairs'", 0));
|
"name='candidate_pairs'", 0));
|
||||||
CHECK(lardon3d_project_db_open(failed_v8_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
CHECK(lardon3d_project_db_open(failed_v8_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 14);
|
lardon3d_project_db_schema_version(database) == 15);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -852,10 +874,10 @@ static bool run_test(void) {
|
||||||
"name='matcher_tasks'",
|
"name='matcher_tasks'",
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(v10_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
CHECK(lardon3d_project_db_open(v10_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 14);
|
lardon3d_project_db_schema_version(database) == 15);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 14));
|
CHECK(query_integer(v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 15));
|
||||||
CHECK(query_integer(v10_path,
|
CHECK(query_integer(v10_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
"name='matcher_tasks'",
|
"name='matcher_tasks'",
|
||||||
|
|
@ -878,7 +900,7 @@ static bool run_test(void) {
|
||||||
1));
|
1));
|
||||||
CHECK(lardon3d_project_db_open(failed_v11_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_v11_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 14);
|
CHECK(lardon3d_project_db_schema_version(database) == 15);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -889,15 +911,34 @@ static bool run_test(void) {
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
"name='track_sets'", 0));
|
"name='track_sets'", 0));
|
||||||
CHECK(lardon3d_project_db_open(v13_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
CHECK(lardon3d_project_db_open(v13_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 14);
|
lardon3d_project_db_schema_version(database) == 15);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(v13_path,
|
CHECK(query_integer(v13_path,
|
||||||
"SELECT value FROM metadata WHERE key='schema_version'", 14));
|
"SELECT value FROM metadata WHERE key='schema_version'", 15));
|
||||||
CHECK(query_integer(v13_path,
|
CHECK(query_integer(v13_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
"name='track_sets'", 1));
|
"name='track_sets'", 1));
|
||||||
|
|
||||||
|
CHECK(create_v14_database(true_v14_path));
|
||||||
|
CHECK(query_integer(true_v14_path,
|
||||||
|
"SELECT value FROM metadata WHERE key='schema_version'", 14));
|
||||||
|
CHECK(query_integer(true_v14_path,
|
||||||
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
"name='track_builder_tasks'", 0));
|
||||||
|
Lardon3DProjectDbResult true_v14_result =
|
||||||
|
lardon3d_project_db_open(true_v14_path, &database, error);
|
||||||
|
if (true_v14_result != LARDON3D_PROJECT_DB_OK) {
|
||||||
|
fprintf(stderr, "true v14 upgrade: %d %s path=%s\n", true_v14_result, error, true_v14_path);
|
||||||
|
}
|
||||||
|
CHECK(true_v14_result == LARDON3D_PROJECT_DB_OK &&
|
||||||
|
lardon3d_project_db_schema_version(database) == 15);
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
database = NULL;
|
||||||
|
CHECK(query_integer(true_v14_path,
|
||||||
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
"name='track_builder_tasks'", 1));
|
||||||
|
|
||||||
CHECK(create_v13_database(failed_v14_path));
|
CHECK(create_v13_database(failed_v14_path));
|
||||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V14", "1", 1) == 0);
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V14", "1", 1) == 0);
|
||||||
CHECK(lardon3d_project_db_open(failed_v14_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_v14_path, &database, error) ==
|
||||||
|
|
@ -910,7 +951,22 @@ static bool run_test(void) {
|
||||||
"name='track_sets'", 0));
|
"name='track_sets'", 0));
|
||||||
CHECK(lardon3d_project_db_open(failed_v14_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_v14_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 14);
|
lardon3d_project_db_schema_version(database) == 15);
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
database = NULL;
|
||||||
|
|
||||||
|
CHECK(create_v13_database(failed_v15_path));
|
||||||
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V15", "1", 1) == 0);
|
||||||
|
CHECK(lardon3d_project_db_open(failed_v15_path, &database, error) ==
|
||||||
|
LARDON3D_PROJECT_DB_IO_ERROR);
|
||||||
|
CHECK(unsetenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V15") == 0);
|
||||||
|
CHECK(query_integer(failed_v15_path,
|
||||||
|
"SELECT value FROM metadata WHERE key='schema_version'", 13));
|
||||||
|
CHECK(query_integer(failed_v15_path,
|
||||||
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
"name='track_builder_tasks'", 0));
|
||||||
|
CHECK(lardon3d_project_db_open(failed_v15_path, &database, error) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK && lardon3d_project_db_schema_version(database) == 15);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -1313,6 +1369,8 @@ static bool run_test(void) {
|
||||||
CHECK(unlink(failed_v11_path) == 0);
|
CHECK(unlink(failed_v11_path) == 0);
|
||||||
CHECK(unlink(v13_path) == 0);
|
CHECK(unlink(v13_path) == 0);
|
||||||
CHECK(unlink(failed_v14_path) == 0);
|
CHECK(unlink(failed_v14_path) == 0);
|
||||||
|
CHECK(unlink(true_v14_path) == 0);
|
||||||
|
CHECK(unlink(failed_v15_path) == 0);
|
||||||
CHECK(rmdir(directory) == 0);
|
CHECK(rmdir(directory) == 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
@ -14,6 +15,11 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <lardon3d/match_file.h>
|
#include <lardon3d/match_file.h>
|
||||||
#include <lardon3d/project_db.h>
|
#include <lardon3d/project_db.h>
|
||||||
|
#include <lardon3d/hardware_profile.h>
|
||||||
|
#include <lardon3d/resource_governor.h>
|
||||||
|
#include <lardon3d/task_queue.h>
|
||||||
|
#include <lardon3d/task_checkpoint.h>
|
||||||
|
#include <lardon3d/track_builder_task.h>
|
||||||
#include <lardon3d/track_builder_project.h>
|
#include <lardon3d/track_builder_project.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,6 +33,7 @@ bool g_delete_before_revalidation = false;
|
||||||
std::string g_insert_db;
|
std::string g_insert_db;
|
||||||
uint64_t g_insert_match_id = 0;
|
uint64_t g_insert_match_id = 0;
|
||||||
bool g_insert_during_build = false;
|
bool g_insert_during_build = false;
|
||||||
|
bool g_publication_race = false;
|
||||||
|
|
||||||
void check(bool value, const char *expression, int line) {
|
void check(bool value, const char *expression, int line) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
|
|
@ -78,6 +85,8 @@ struct Fixture {
|
||||||
CHECK(mkdtemp(name) != nullptr);
|
CHECK(mkdtemp(name) != nullptr);
|
||||||
directory = name;
|
directory = name;
|
||||||
db_path = directory + "/project.db";
|
db_path = directory + "/project.db";
|
||||||
|
CHECK(mkdir((directory + "/.lardon3d").c_str(), 0700) == 0);
|
||||||
|
CHECK(mkdir((directory + "/.lardon3d/checkpoints").c_str(), 0700) == 0);
|
||||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
|
||||||
CHECK(lardon3d_project_db_open(db_path.c_str(), &db, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(db_path.c_str(), &db, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
Lardon3DProjectDbScanSet scanset{};
|
Lardon3DProjectDbScanSet scanset{};
|
||||||
|
|
@ -602,32 +611,335 @@ void run_partial_input_failure() {
|
||||||
std::puts("PARTIAL INPUT: PASS");
|
std::puts("PARTIAL INPUT: PASS");
|
||||||
}
|
}
|
||||||
|
|
||||||
void run_resource_case() {
|
void run_resource_case(size_t gvr_count = 13) {
|
||||||
Fixture fixture(8192);
|
Fixture fixture(8192);
|
||||||
|
const bool profile = std::getenv("LARDON3D_RESOURCE_PROFILE") != nullptr;
|
||||||
|
auto report_rss = [profile](const char *stage) {
|
||||||
|
if (!profile) return;
|
||||||
|
struct rusage usage{};
|
||||||
|
CHECK(getrusage(RUSAGE_SELF, &usage) == 0);
|
||||||
|
std::printf("RESOURCE_STAGE=%s RSS_KIB=%ld\n", stage, usage.ru_maxrss);
|
||||||
|
};
|
||||||
|
report_rss("fixture-open");
|
||||||
std::vector<Lardon3DMatchFileEntry> entries;
|
std::vector<Lardon3DMatchFileEntry> entries;
|
||||||
std::vector<unsigned char> mask(1024, 0xff);
|
std::vector<unsigned char> mask(1024, 0xff);
|
||||||
entries.reserve(8192);
|
entries.reserve(8192);
|
||||||
for (uint32_t i = 0; i < 8192; ++i) entries.push_back({i, i, 0.1F});
|
for (uint32_t i = 0; i < 8192; ++i) entries.push_back({i, i, 0.1F});
|
||||||
std::vector<uint64_t> ids;
|
std::vector<uint64_t> ids;
|
||||||
for (size_t i = 0; i < 13; ++i) ids.push_back(fixture.add_gvr(0, 1, entries, mask));
|
for (size_t i = 0; i < gvr_count; ++i) ids.push_back(fixture.add_gvr(0, 1, entries, mask));
|
||||||
|
report_rss("fixture-built");
|
||||||
auto request = fixture.request(ids.data(), ids.size());
|
auto request = fixture.request(ids.data(), ids.size());
|
||||||
Lardon3DTrackBuilderProjectResult result{};
|
Lardon3DTrackBuilderProjectResult result{};
|
||||||
auto started = std::chrono::steady_clock::now();
|
auto started = std::chrono::steady_clock::now();
|
||||||
|
report_rss("before-build");
|
||||||
|
uint64_t expected_raw = static_cast<uint64_t>(gvr_count) * 8192ULL;
|
||||||
CHECK(lardon3d_track_builder_build_project(&request, &result) ==
|
CHECK(lardon3d_track_builder_build_project(&request, &result) ==
|
||||||
LARDON3D_TRACK_BUILDER_PROJECT_OK && result.raw_inlier_edge_count == 106496 &&
|
LARDON3D_TRACK_BUILDER_PROJECT_OK && result.raw_inlier_edge_count == expected_raw &&
|
||||||
result.core_observation_count == 16384 && result.track_count == 8192);
|
result.core_observation_count == 16384 && result.track_count == 8192);
|
||||||
|
report_rss("after-build");
|
||||||
auto elapsed = std::chrono::duration<double>(std::chrono::steady_clock::now() - started);
|
auto elapsed = std::chrono::duration<double>(std::chrono::steady_clock::now() - started);
|
||||||
struct rusage usage{};
|
struct rusage usage{};
|
||||||
CHECK(getrusage(RUSAGE_SELF, &usage) == 0);
|
CHECK(getrusage(RUSAGE_SELF, &usage) == 0);
|
||||||
std::printf("RESOURCE GVR=%zu RAW=%llu INLIERS=%llu FEATURES=3 OBS=%llu TRACKS=%llu\n",
|
uint64_t predicted = 4ULL * 1024ULL * 1024ULL +
|
||||||
|
result.raw_inlier_edge_count * (48ULL + 2ULL * 160ULL);
|
||||||
|
predicted *= result.raw_inlier_edge_count > 400000ULL ? 8ULL : 2ULL;
|
||||||
|
std::printf("RESOURCE GVR=%zu RAW=%llu INLIERS=%llu FEATURES=3 OBS=%llu TRACKS=%llu "
|
||||||
|
"PREDICTED_RESERVATION=%llu\n",
|
||||||
ids.size(), static_cast<unsigned long long>(result.raw_inlier_edge_count),
|
ids.size(), static_cast<unsigned long long>(result.raw_inlier_edge_count),
|
||||||
static_cast<unsigned long long>(result.raw_inlier_edge_count),
|
static_cast<unsigned long long>(result.raw_inlier_edge_count),
|
||||||
static_cast<unsigned long long>(result.core_observation_count),
|
static_cast<unsigned long long>(result.core_observation_count),
|
||||||
static_cast<unsigned long long>(result.track_count));
|
static_cast<unsigned long long>(result.track_count),
|
||||||
|
static_cast<unsigned long long>(predicted));
|
||||||
std::printf("RESOURCE DURATION_SECONDS=%.3f PEAK_RSS_KIB=%ld MAX_MATCH_FILES_LIVE=1\n",
|
std::printf("RESOURCE DURATION_SECONDS=%.3f PEAK_RSS_KIB=%ld MAX_MATCH_FILES_LIVE=1\n",
|
||||||
elapsed.count(), usage.ru_maxrss);
|
elapsed.count(), usage.ru_maxrss);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void run_durable_task_case() {
|
||||||
|
Fixture direct_fixture;
|
||||||
|
auto direct_first = direct_fixture.add_gvr(0, 1, {{0, 0, 0.1F}}, {0x01});
|
||||||
|
auto direct_second = direct_fixture.add_gvr(1, 2, {{0, 0, 0.1F}}, {0x01});
|
||||||
|
uint64_t direct_ids[] = {direct_first, direct_second};
|
||||||
|
auto direct_request = direct_fixture.request(direct_ids, 2);
|
||||||
|
Lardon3DTrackBuilderProjectResult direct_result{};
|
||||||
|
CHECK(lardon3d_track_builder_build_project(&direct_request, &direct_result) ==
|
||||||
|
LARDON3D_TRACK_BUILDER_PROJECT_OK && direct_result.track_count == 1);
|
||||||
|
assert_track(direct_fixture.db, direct_result.track_set_id, direct_fixture.feature[0],
|
||||||
|
direct_fixture.feature[1], direct_fixture.feature[2]);
|
||||||
|
|
||||||
|
Fixture fixture;
|
||||||
|
auto first = fixture.add_gvr(0, 1, {{0, 0, 0.1F}}, {0x01});
|
||||||
|
auto second = fixture.add_gvr(1, 2, {{0, 0, 0.1F}}, {0x01});
|
||||||
|
uint64_t ids[] = {first, second};
|
||||||
|
Lardon3DHardwareProfile profile{};
|
||||||
|
char hardware_error[128]{};
|
||||||
|
CHECK(lardon3d_hardware_profile_detect(&profile, hardware_error, sizeof(hardware_error)));
|
||||||
|
Lardon3DResourcePolicy policy{};
|
||||||
|
CHECK(lardon3d_resource_policy_default(&profile, &policy));
|
||||||
|
Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create(&profile, &policy);
|
||||||
|
CHECK(governor != nullptr);
|
||||||
|
Lardon3DTaskQueue *queue = lardon3d_task_queue_create(governor, 2);
|
||||||
|
CHECK(queue != nullptr);
|
||||||
|
Lardon3DAppState state{};
|
||||||
|
lardon3d_app_state_init(&state);
|
||||||
|
state.project_loaded = true;
|
||||||
|
state.project_db = fixture.db;
|
||||||
|
state.resource_governor = governor;
|
||||||
|
state.task_queue = queue;
|
||||||
|
std::snprintf(state.project_path, sizeof(state.project_path), "%s", fixture.directory.c_str());
|
||||||
|
Lardon3DTrackBuilderTaskConfiguration configuration = {
|
||||||
|
state.project_path, fixture.db, kVerifier, kVersion, g_verifier, ids, 2};
|
||||||
|
Lardon3DTaskKindDescriptor descriptor = {
|
||||||
|
LARDON3D_TRACK_BUILDER_TASK_KIND, LARDON3D_TRACK_BUILDER_TASK_KIND_VERSION,
|
||||||
|
lardon3d_track_builder_task_reconstruct};
|
||||||
|
Lardon3DTaskKindRegistry registry{};
|
||||||
|
const Lardon3DTaskKindDescriptor *found_descriptor = nullptr;
|
||||||
|
CHECK(lardon3d_task_kind_registry_init(®istry, &descriptor, 1) &&
|
||||||
|
lardon3d_task_kind_registry_lookup(
|
||||||
|
®istry, LARDON3D_TRACK_BUILDER_TASK_KIND,
|
||||||
|
LARDON3D_TRACK_BUILDER_TASK_KIND_VERSION, &found_descriptor) ==
|
||||||
|
LARDON3D_TASK_KIND_OK && found_descriptor == &descriptor);
|
||||||
|
uint64_t task_id = 0;
|
||||||
|
CHECK(lardon3d_project_enqueue_track_builder_task(&state, &configuration, &task_id));
|
||||||
|
Lardon3DTaskSnapshot snapshot{};
|
||||||
|
for (size_t attempt = 0; attempt < 200; ++attempt) {
|
||||||
|
CHECK(lardon3d_task_queue_get(queue, task_id, &snapshot));
|
||||||
|
if (snapshot.state == TASK_COMPLETED || snapshot.state == TASK_FAILED ||
|
||||||
|
snapshot.state == TASK_CANCELLED)
|
||||||
|
break;
|
||||||
|
usleep(10000);
|
||||||
|
}
|
||||||
|
CHECK(snapshot.state == TASK_COMPLETED && track_set_count(fixture.db_path) == 1);
|
||||||
|
Lardon3DProjectDbTrackSet task_sets[1]{};
|
||||||
|
size_t task_set_count_value = 0;
|
||||||
|
CHECK(lardon3d_project_db_list_track_sets(fixture.db, 0, task_sets, 1,
|
||||||
|
&task_set_count_value) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
|
task_set_count_value == 1 && task_sets[0].track_count == direct_result.track_count);
|
||||||
|
assert_track(fixture.db, task_sets[0].track_set_id, fixture.feature[0], fixture.feature[1],
|
||||||
|
fixture.feature[2]);
|
||||||
|
CHECK(lardon3d_task_queue_remove(queue, task_id));
|
||||||
|
lardon3d_task_queue_destroy(queue);
|
||||||
|
lardon3d_resource_governor_destroy(governor);
|
||||||
|
Lardon3DProjectDbTrackBuilderTask durable{};
|
||||||
|
CHECK(lardon3d_project_db_load_track_builder_task(fixture.db, task_id, &durable) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK && durable.gvr_count == 2 && durable.scope_format_version == 1);
|
||||||
|
Lardon3DTaskDurableSnapshot durable_snapshot{};
|
||||||
|
uint32_t checkpoint_version = 0;
|
||||||
|
CHECK(lardon3d_task_checkpoint_load(
|
||||||
|
(fixture.directory + "/.lardon3d/checkpoints/" + std::to_string(task_id) + ".chk")
|
||||||
|
.c_str(),
|
||||||
|
&durable_snapshot, &checkpoint_version) == LARDON3D_TASK_CHECKPOINT_OK &&
|
||||||
|
checkpoint_version == 1 && durable_snapshot.id == task_id);
|
||||||
|
Lardon3DHardwareProfile reopened_profile{};
|
||||||
|
CHECK(lardon3d_hardware_profile_detect(&reopened_profile, hardware_error,
|
||||||
|
sizeof(hardware_error)));
|
||||||
|
CHECK(lardon3d_resource_policy_default(&reopened_profile, &policy));
|
||||||
|
Lardon3DResourceGovernor *reopened_governor =
|
||||||
|
lardon3d_resource_governor_create(&reopened_profile, &policy);
|
||||||
|
CHECK(reopened_governor != nullptr);
|
||||||
|
Lardon3DTaskReconstructionContext runtime = {
|
||||||
|
fixture.directory.c_str(), fixture.db, reopened_governor, nullptr};
|
||||||
|
Lardon3DTaskKindBinding binding{};
|
||||||
|
CHECK(lardon3d_track_builder_task_reconstruct(&durable_snapshot, &runtime, &binding) &&
|
||||||
|
binding.callback != nullptr && binding.userdata_destroy != nullptr);
|
||||||
|
Lardon3DTask *restored = lardon3d_task_restore_typed(
|
||||||
|
&durable_snapshot, LARDON3D_TRACK_BUILDER_TASK_KIND,
|
||||||
|
LARDON3D_TRACK_BUILDER_TASK_KIND_VERSION, binding.callback, binding.userdata,
|
||||||
|
binding.userdata_destroy);
|
||||||
|
CHECK(restored != nullptr);
|
||||||
|
lardon3d_task_destroy(restored);
|
||||||
|
lardon3d_resource_governor_destroy(reopened_governor);
|
||||||
|
std::puts("GATE D TASK: PASS (durable enqueue, execution, publication)");
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_late_identity_race_case() {
|
||||||
|
Fixture fixture;
|
||||||
|
auto gvr = fixture.add_gvr(0, 1, {{0, 0, 0.1F}}, {0x01});
|
||||||
|
uint64_t ids[] = {gvr};
|
||||||
|
auto request = fixture.request(ids, 1);
|
||||||
|
g_publication_race = true;
|
||||||
|
Lardon3DTrackBuilderProjectResult result{};
|
||||||
|
auto status = lardon3d_track_builder_build_project(&request, &result);
|
||||||
|
std::fprintf(stderr, "C27 status=%d reused=%d sets=%llu\n", status, result.reused,
|
||||||
|
static_cast<unsigned long long>(track_set_count(fixture.db_path)));
|
||||||
|
CHECK(status ==
|
||||||
|
LARDON3D_TRACK_BUILDER_PROJECT_OK && result.reused && result.track_count == 1 &&
|
||||||
|
track_set_count(fixture.db_path) == 1);
|
||||||
|
std::puts("C27: PASS (deterministic exact-identity collision/reuse)");
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_crash_recovery_case() {
|
||||||
|
Fixture fixture;
|
||||||
|
auto first = fixture.add_gvr(0, 1, {{0, 0, 0.1F}}, {0x01});
|
||||||
|
auto second = fixture.add_gvr(1, 2, {{0, 0, 0.1F}}, {0x01});
|
||||||
|
uint64_t ids[] = {first, second};
|
||||||
|
Lardon3DHardwareProfile profile{};
|
||||||
|
char error[128]{};
|
||||||
|
CHECK(lardon3d_hardware_profile_detect(&profile, error, sizeof(error)));
|
||||||
|
Lardon3DResourcePolicy policy{};
|
||||||
|
CHECK(lardon3d_resource_policy_default(&profile, &policy));
|
||||||
|
Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create(&profile, &policy);
|
||||||
|
CHECK(governor != nullptr);
|
||||||
|
Lardon3DTaskQueue *queue = lardon3d_task_queue_create(governor, 2);
|
||||||
|
CHECK(queue != nullptr);
|
||||||
|
Lardon3DAppState state{};
|
||||||
|
lardon3d_app_state_init(&state);
|
||||||
|
state.project_loaded = true;
|
||||||
|
state.project_db = fixture.db;
|
||||||
|
state.resource_governor = governor;
|
||||||
|
state.task_queue = queue;
|
||||||
|
std::snprintf(state.project_path, sizeof(state.project_path), "%s", fixture.directory.c_str());
|
||||||
|
Lardon3DTrackBuilderTaskConfiguration configuration = {
|
||||||
|
state.project_path, fixture.db, kVerifier, kVersion, g_verifier, ids, 2};
|
||||||
|
uint64_t task_id = 0;
|
||||||
|
setenv("LARDON3D_TRACK_BUILDER_TEST_SKIP_FINISHED", "1", 1);
|
||||||
|
CHECK(lardon3d_project_enqueue_track_builder_task(&state, &configuration, &task_id));
|
||||||
|
Lardon3DTaskSnapshot snapshot{};
|
||||||
|
for (size_t attempt = 0; attempt < 200; ++attempt) {
|
||||||
|
CHECK(lardon3d_task_queue_get(queue, task_id, &snapshot));
|
||||||
|
if (snapshot.state == TASK_COMPLETED) break;
|
||||||
|
usleep(10000);
|
||||||
|
}
|
||||||
|
CHECK(snapshot.state == TASK_COMPLETED && track_set_count(fixture.db_path) == 1);
|
||||||
|
CHECK(lardon3d_task_queue_remove(queue, task_id));
|
||||||
|
lardon3d_task_queue_destroy(queue);
|
||||||
|
lardon3d_resource_governor_destroy(governor);
|
||||||
|
CHECK(unsetenv("LARDON3D_TRACK_BUILDER_TEST_SKIP_FINISHED") == 0);
|
||||||
|
lardon3d_project_db_close(fixture.db);
|
||||||
|
fixture.db = nullptr;
|
||||||
|
CHECK(lardon3d_project_db_open(fixture.db_path.c_str(), &fixture.db, error) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
Lardon3DTaskDurableSnapshot durable_snapshot{};
|
||||||
|
uint32_t version = 0;
|
||||||
|
CHECK(lardon3d_task_checkpoint_load(
|
||||||
|
(fixture.directory + "/.lardon3d/checkpoints/" + std::to_string(task_id) + ".chk")
|
||||||
|
.c_str(),
|
||||||
|
&durable_snapshot, &version) == LARDON3D_TASK_CHECKPOINT_OK &&
|
||||||
|
durable_snapshot.id == task_id && durable_snapshot.saved_state == TASK_PENDING);
|
||||||
|
CHECK(lardon3d_hardware_profile_detect(&profile, error, sizeof(error)));
|
||||||
|
CHECK(lardon3d_resource_policy_default(&profile, &policy));
|
||||||
|
governor = lardon3d_resource_governor_create(&profile, &policy);
|
||||||
|
queue = lardon3d_task_queue_create(governor, 2);
|
||||||
|
CHECK(governor != nullptr && queue != nullptr);
|
||||||
|
Lardon3DTaskReconstructionContext runtime = {
|
||||||
|
fixture.directory.c_str(), fixture.db, governor, nullptr};
|
||||||
|
Lardon3DTaskKindBinding binding{};
|
||||||
|
CHECK(lardon3d_track_builder_task_reconstruct(&durable_snapshot, &runtime, &binding));
|
||||||
|
Lardon3DTask *restored = lardon3d_task_restore_typed(
|
||||||
|
&durable_snapshot, LARDON3D_TRACK_BUILDER_TASK_KIND,
|
||||||
|
LARDON3D_TRACK_BUILDER_TASK_KIND_VERSION, binding.callback, binding.userdata,
|
||||||
|
binding.userdata_destroy);
|
||||||
|
CHECK(restored != nullptr && lardon3d_task_queue_add(queue, restored, nullptr));
|
||||||
|
for (size_t attempt = 0; attempt < 200; ++attempt) {
|
||||||
|
CHECK(lardon3d_task_queue_get(queue, task_id, &snapshot));
|
||||||
|
if (snapshot.state == TASK_COMPLETED) break;
|
||||||
|
usleep(10000);
|
||||||
|
}
|
||||||
|
CHECK(snapshot.state == TASK_COMPLETED && track_set_count(fixture.db_path) == 1);
|
||||||
|
CHECK(lardon3d_task_queue_remove(queue, task_id));
|
||||||
|
lardon3d_task_queue_destroy(queue);
|
||||||
|
lardon3d_resource_governor_destroy(governor);
|
||||||
|
execute_sql(fixture.db_path,
|
||||||
|
("UPDATE track_builder_tasks SET scope_size_bytes=1 WHERE task_id=" +
|
||||||
|
std::to_string(task_id))
|
||||||
|
.c_str());
|
||||||
|
Lardon3DProjectDbTrackBuilderTask corrupt_payload{};
|
||||||
|
CHECK(lardon3d_project_db_load_track_builder_task(fixture.db, task_id,
|
||||||
|
&corrupt_payload) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
Lardon3DTaskKindBinding corrupt_binding{};
|
||||||
|
Lardon3DResourceGovernor *corrupt_governor =
|
||||||
|
lardon3d_resource_governor_create(&profile, &policy);
|
||||||
|
CHECK(corrupt_governor != nullptr);
|
||||||
|
Lardon3DTaskReconstructionContext corrupt_runtime = {
|
||||||
|
fixture.directory.c_str(), fixture.db, corrupt_governor, nullptr};
|
||||||
|
CHECK(!lardon3d_track_builder_task_reconstruct(&durable_snapshot, &corrupt_runtime,
|
||||||
|
&corrupt_binding));
|
||||||
|
lardon3d_resource_governor_destroy(corrupt_governor);
|
||||||
|
std::puts("CRASH AFTER PUBLICATION: PASS (replay exact reuse, no duplicate set)");
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_pause_cancel_case() {
|
||||||
|
auto make_state = [](Fixture &fixture, Lardon3DResourceGovernor **governor,
|
||||||
|
Lardon3DTaskQueue **queue, Lardon3DAppState *state) {
|
||||||
|
Lardon3DHardwareProfile profile{};
|
||||||
|
char error[128]{};
|
||||||
|
CHECK(lardon3d_hardware_profile_detect(&profile, error, sizeof(error)));
|
||||||
|
Lardon3DResourcePolicy policy{};
|
||||||
|
CHECK(lardon3d_resource_policy_default(&profile, &policy));
|
||||||
|
*governor = lardon3d_resource_governor_create(&profile, &policy);
|
||||||
|
CHECK(*governor != nullptr);
|
||||||
|
*queue = lardon3d_task_queue_create(*governor, 2);
|
||||||
|
CHECK(*queue != nullptr);
|
||||||
|
lardon3d_app_state_init(state);
|
||||||
|
state->project_loaded = true;
|
||||||
|
state->project_db = fixture.db;
|
||||||
|
state->resource_governor = *governor;
|
||||||
|
state->task_queue = *queue;
|
||||||
|
std::snprintf(state->project_path, sizeof(state->project_path), "%s",
|
||||||
|
fixture.directory.c_str());
|
||||||
|
};
|
||||||
|
|
||||||
|
Fixture paused_fixture;
|
||||||
|
auto paused_gvr = paused_fixture.add_gvr(0, 1, {{0, 0, 0.1F}}, {0x01});
|
||||||
|
uint64_t paused_ids[] = {paused_gvr};
|
||||||
|
Lardon3DResourceGovernor *paused_governor = nullptr;
|
||||||
|
Lardon3DTaskQueue *paused_queue = nullptr;
|
||||||
|
Lardon3DAppState paused_state{};
|
||||||
|
make_state(paused_fixture, &paused_governor, &paused_queue, &paused_state);
|
||||||
|
Lardon3DTrackBuilderTaskConfiguration paused_configuration = {
|
||||||
|
paused_state.project_path, paused_fixture.db, kVerifier, kVersion, g_verifier,
|
||||||
|
paused_ids, 1};
|
||||||
|
uint64_t paused_id = 0;
|
||||||
|
Lardon3DTask *paused_task = lardon3d_project_create_track_builder_task(
|
||||||
|
&paused_state, &paused_configuration, &paused_id);
|
||||||
|
CHECK(paused_task != nullptr && lardon3d_task_pause(paused_task) &&
|
||||||
|
lardon3d_task_queue_add(paused_queue, paused_task, nullptr));
|
||||||
|
usleep(20000);
|
||||||
|
Lardon3DTaskSnapshot paused_snapshot{};
|
||||||
|
CHECK(lardon3d_task_queue_get(paused_queue, paused_id, &paused_snapshot) &&
|
||||||
|
paused_snapshot.state == TASK_PAUSED && track_set_count(paused_fixture.db_path) == 0);
|
||||||
|
CHECK(lardon3d_task_queue_resume(paused_queue, paused_id));
|
||||||
|
for (size_t attempt = 0; attempt < 200; ++attempt) {
|
||||||
|
CHECK(lardon3d_task_queue_get(paused_queue, paused_id, &paused_snapshot));
|
||||||
|
if (paused_snapshot.state == TASK_COMPLETED) break;
|
||||||
|
usleep(10000);
|
||||||
|
}
|
||||||
|
CHECK(paused_snapshot.state == TASK_COMPLETED && track_set_count(paused_fixture.db_path) == 1);
|
||||||
|
CHECK(lardon3d_task_queue_remove(paused_queue, paused_id));
|
||||||
|
lardon3d_task_queue_destroy(paused_queue);
|
||||||
|
lardon3d_resource_governor_destroy(paused_governor);
|
||||||
|
|
||||||
|
Fixture cancelled_fixture;
|
||||||
|
auto cancelled_gvr = cancelled_fixture.add_gvr(0, 1, {{0, 0, 0.1F}}, {0x01});
|
||||||
|
uint64_t cancelled_ids[] = {cancelled_gvr};
|
||||||
|
Lardon3DResourceGovernor *cancelled_governor = nullptr;
|
||||||
|
Lardon3DTaskQueue *cancelled_queue = nullptr;
|
||||||
|
Lardon3DAppState cancelled_state{};
|
||||||
|
make_state(cancelled_fixture, &cancelled_governor, &cancelled_queue, &cancelled_state);
|
||||||
|
Lardon3DTrackBuilderTaskConfiguration cancelled_configuration = {
|
||||||
|
cancelled_state.project_path, cancelled_fixture.db, kVerifier, kVersion, g_verifier,
|
||||||
|
cancelled_ids, 1};
|
||||||
|
uint64_t cancelled_id = 0;
|
||||||
|
Lardon3DTask *cancelled_task = lardon3d_project_create_track_builder_task(
|
||||||
|
&cancelled_state, &cancelled_configuration, &cancelled_id);
|
||||||
|
CHECK(cancelled_task != nullptr);
|
||||||
|
lardon3d_task_request_cancel(cancelled_task);
|
||||||
|
CHECK(lardon3d_task_queue_add(cancelled_queue, cancelled_task, nullptr));
|
||||||
|
Lardon3DTaskSnapshot cancelled_snapshot{};
|
||||||
|
for (size_t attempt = 0; attempt < 200; ++attempt) {
|
||||||
|
CHECK(lardon3d_task_queue_get(cancelled_queue, cancelled_id, &cancelled_snapshot));
|
||||||
|
if (cancelled_snapshot.state == TASK_CANCELLED) break;
|
||||||
|
usleep(10000);
|
||||||
|
}
|
||||||
|
CHECK(cancelled_snapshot.state == TASK_CANCELLED &&
|
||||||
|
track_set_count(cancelled_fixture.db_path) == 0);
|
||||||
|
CHECK(lardon3d_task_queue_remove(cancelled_queue, cancelled_id));
|
||||||
|
lardon3d_task_queue_destroy(cancelled_queue);
|
||||||
|
lardon3d_resource_governor_destroy(cancelled_governor);
|
||||||
|
std::puts("PAUSE/CANCEL: PASS");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
#ifdef LARDON3D_TRACK_BUILDER_PROJECT_TESTING
|
#ifdef LARDON3D_TRACK_BUILDER_PROJECT_TESTING
|
||||||
|
|
@ -666,9 +978,29 @@ extern "C" void lardon3d_track_builder_project_test_before_revalidation(
|
||||||
CHECK(sqlite3_close(connection) == SQLITE_OK);
|
CHECK(sqlite3_close(connection) == SQLITE_OK);
|
||||||
g_delete_before_revalidation = false;
|
g_delete_before_revalidation = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" void lardon3d_track_builder_project_test_before_publication(
|
||||||
|
const Lardon3DTrackBuilderProjectRequest *request) {
|
||||||
|
if (!g_publication_race) return;
|
||||||
|
g_publication_race = false;
|
||||||
|
Lardon3DTrackBuilderProjectResult result{};
|
||||||
|
CHECK(lardon3d_track_builder_build_project(request, &result) ==
|
||||||
|
LARDON3D_TRACK_BUILDER_PROJECT_OK && !result.reused);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
if (std::getenv("LARDON3D_RESOURCE_CALIBRATION_ONLY")) {
|
||||||
|
const char *scale = std::getenv("LARDON3D_RESOURCE_SCALE");
|
||||||
|
if (scale) {
|
||||||
|
run_resource_case(static_cast<size_t>(std::strtoull(scale, nullptr, 10)));
|
||||||
|
} else {
|
||||||
|
run_resource_case(13);
|
||||||
|
run_resource_case(31);
|
||||||
|
run_resource_case(62);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Lardon3DTrackBuilderProjectResult result{};
|
Lardon3DTrackBuilderProjectResult result{};
|
||||||
Lardon3DTrackBuilderProjectRequest invalid{};
|
Lardon3DTrackBuilderProjectRequest invalid{};
|
||||||
CHECK(lardon3d_track_builder_build_project(&invalid, &result) ==
|
CHECK(lardon3d_track_builder_build_project(&invalid, &result) ==
|
||||||
|
|
@ -683,6 +1015,10 @@ int main() {
|
||||||
run_scope_snapshot_case();
|
run_scope_snapshot_case();
|
||||||
run_partial_input_failure();
|
run_partial_input_failure();
|
||||||
run_resource_case();
|
run_resource_case();
|
||||||
std::puts("C01-C27 integration harness: PASS (C26 N/A; C27 deferred to Gate D)");
|
run_durable_task_case();
|
||||||
|
run_late_identity_race_case();
|
||||||
|
run_pause_cancel_case();
|
||||||
|
run_crash_recovery_case();
|
||||||
|
std::puts("C01-C27 integration harness: PASS (C26 N/A; C27 Gate D closed)");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue