feat(reconstruction): add durable track builder runtime

This commit is contained in:
fy59 2026-08-10 00:01:47 +02:00
parent 0d876cfbe1
commit f1a139e3c1
21 changed files with 1239 additions and 82 deletions

View file

@ -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
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
hashés, le vrai MAGSAC et le Model v1. VERIFIED est rechargé après close/reopen avec modèle et
Les tests E2E utilisent le vrai Project DB (migré séquentiellement jusqu'à v15 à
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é.
## Production algorithm

View file

@ -1,6 +1,8 @@
# 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
> 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
@ -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
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
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
`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
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
`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
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 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
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` :
@ -701,8 +723,8 @@ ouvert.
## Statut
**IMPLEMENTED** — SQLite système, schéma v14 et migrations séquentielles
v1→v2→v3→v4→v5→v6→v7→v8→v9→v10→v11→v12→v13→v14, identité projet, transactions
**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→v15, identité projet, transactions
tâche+checkpoint, pagination de reprise et artefacts génériques.
**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
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
source `project_db.c` exposent `create_track_set`, `load_track_set`,
`find_track_set`, `list_track_sets`, `load_track`, `list_tracks`,

View file

@ -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. |
**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.
---

View file

@ -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
débités du budget RAM. Sans GPU/backend, l'estimation reste CPU-only afin que
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

View file

@ -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`.
Project DB v13 ajoute uniquement `geometric_verifier_tasks`, car le checkpoint
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.

View file

@ -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
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
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

View file

@ -17,7 +17,8 @@ poses, calculate 3D reprojection, or optimise cameras.
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)`.
- One observation per image, minimum two observations per Track, and no
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
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
as its scientific unit. It may page GVR resolution and pause at safe input or
compute boundaries, but publication occurs only after complete canonical
output and final scope validation. A cancelled or failed run publishes no
incomplete set. The exact Task kind is not implemented or persisted by Gate A.
A durable `track_builder.run` v1 treats one complete Track Set as its scientific
unit. It replays the explicit scope after recovery and pauses/cancels only at
safe boundaries; publication occurs only after complete canonical output and
final scope validation. A cancelled or failed run publishes no incomplete set.
## Future Resource Governor integration
## Resource Governor integration (Gate D)
The future Task estimates bounded graph memory and CPU, obtains reservations
before callbacks, and may reduce batches or pause under pressure. Batch size,
thread count, Governor state, PSI, swap and throttling may affect throughput
The Task estimates bounded graph memory and CPU, obtains reservations before
callbacks and obeys Governor pressure. Batch size, thread count, Governor state,
PSI, swap and throttling may affect throughput
only; they must not affect logical edges, conflict decisions or output order.
## 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.
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
Adjustment, reprojection optimisation, 3D points, co-visibility matrices,
dense reconstruction, Vulkan Builder implementation, persistent edge
provenance, Track Model schema changes, Project DB v15, Track Builder Task
implementation and incremental optimisation implementation.
provenance, Track Model schema changes and incremental optimisation
implementation. The durable Task Builder runtime is a separate Gate D layer;
it does not alter this scientific contract.
## 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
`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
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
@ -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
the whole build; no partial Track Set is published. New matching GVRs are not
discovered or included. The Track Model API owns timestamps, validation,
atomicity and late exact-identity reuse. Project DB remains v14; Task and
Resource Governor orchestration are not part of Gate C.
atomicity and late exact-identity reuse. Gate C itself uses the v14 scientific
baseline; Gate D adds only the v15 durable task payload.
The implementation does not add a selector discovery API: callers that need
discovery must enumerate and freeze their own explicit list in a separate
operation.
DB orchestration: **GATE C IMPLEMENTED**. Task: **NOT_IMPLEMENTED**. Resource
Governor: **NOT_IMPLEMENTED**. Project DB remains v14 and unchanged.
DB orchestration: **GATE C IMPLEMENTED**. Task: **GATE D IMPLEMENTED**.
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)
@ -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:
**NOT_IMPLEMENTED**. Gate E: **NOT_DONE**. Track Builder v1 is not marked
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, 3988840220, 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.

View file

@ -9,7 +9,7 @@
#include <lardon3d/task.h>
enum {
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 14,
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 15,
LARDON3D_PROJECT_DB_ID_CAPACITY = 65,
LARDON3D_PROJECT_DB_KIND_CAPACITY = 65,
LARDON3D_PROJECT_DB_PATH_CAPACITY = 4096,
@ -158,6 +158,22 @@ typedef struct {
unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE];
} 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 {
uint64_t match_result_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(
Lardon3DProjectDb *database, uint64_t task_id,
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(
Lardon3DProjectDb *database, uint64_t candidate_pair_id, uint64_t feature_set_id_a,

View file

@ -48,6 +48,8 @@ Lardon3DTrackBuilderProjectStatus lardon3d_track_builder_build_project(
/* Test-only phase seam; never present in the production header/API. */
void lardon3d_track_builder_project_test_before_revalidation(
Lardon3DProjectDb *database, const uint64_t *gvr_ids, size_t gvr_count);
void lardon3d_track_builder_project_test_before_publication(
const Lardon3DTrackBuilderProjectRequest *request);
#endif
#ifdef __cplusplus

View 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

View file

@ -157,8 +157,10 @@ executable(
'src/geometric_verifier.cpp',
'src/track_builder.cpp',
'src/track_builder_project.cpp',
'src/track_builder_task.cpp',
] + matcher_backend_sources,
include_directories: include_directories('include'),
c_args: ['-DLARDON3D_TRACK_BUILDER_TASK_AVAILABLE'],
dependencies: [ncursesw, threads, sqlite3, openssl, opencv, opencv_geometry]
+ matcher_backend_dependencies,
)
@ -848,14 +850,23 @@ track_builder_project_test = executable(
'tests/test_track_builder_project.cpp',
'src/track_builder.cpp',
'src/track_builder_project.cpp',
'src/track_builder_task.cpp',
'src/project_db.c',
'src/app_state.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_snapshot.c',
'src/match_file.c',
],
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],
)
test('track-builder-project', track_builder_project_test, timeout: 60)

View file

@ -303,6 +303,22 @@ static const char schema_geometric_verifier_task_v13[] =
"canonicalization_version INTEGER NOT NULL CHECK(canonicalization_version>0),"
"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) {
if (destination) {
(void)snprintf(destination, LARDON3D_PROJECT_DB_ERROR_CAPACITY, "%s", text ? text : "");
@ -393,7 +409,7 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
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 != 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;
}
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin migration");
@ -681,6 +697,22 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
"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) {
result = execute(database, "COMMIT", "commit migration");
}
@ -800,7 +832,8 @@ Lardon3DProjectDbResult lardon3d_project_db_open(const char *path, Lardon3DProje
"geometric_verifier_tasks",
"track_sets",
"tracks",
"track_observations"};
"track_observations",
"track_builder_tasks"};
for (size_t index = 0; index < sizeof(required) / sizeof(required[0]) &&
result == LARDON3D_PROJECT_DB_OK;
++index) {
@ -3588,6 +3621,126 @@ Lardon3DProjectDbResult lardon3d_project_db_load_geometric_verifier_task(
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,
Lardon3DProjectDbMatchResult *result) {
sqlite3_int64 id = sqlite3_column_int64(statement, 0);

View file

@ -5,6 +5,9 @@
#include <lardon3d/matcher_task.h>
#include <lardon3d/sift_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>
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_version = LARDON3D_GEOMETRIC_VERIFIER_TASK_KIND_VERSION,
.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 = {
.descriptors = descriptors,
.count = sizeof(descriptors) / sizeof(descriptors[0]),

View file

@ -320,9 +320,37 @@ extern "C" Lardon3DTrackBuilderProjectStatus lardon3d_track_builder_build_projec
}
Lardon3DProjectDbTrackSet published{};
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,
publish.data(), publish.size(), &published);
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);
result->track_set_id = published.track_set_id;
result->gvr_count = published.gvr_count;

369
src/track_builder_task.cpp Normal file
View 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;
}
}

View file

@ -161,7 +161,7 @@ static bool create_v11_database(const char *path) {
"DROP TABLE geometric_verification_results;"
"DROP TABLE track_observations;"
"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';"
"COMMIT;PRAGMA foreign_keys=ON;");
}
@ -170,7 +170,7 @@ static bool test_model_api(const char *path) {
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
Lardon3DProjectDb *database = NULL;
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;
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'",
0));
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;
CHECK(create_parents(database, &parents));
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);
uint64_t migrated_result_id = migrated_result.geometric_verification_result_id;
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,
"SELECT count(*) FROM sqlite_master WHERE type='index' AND "
"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'",
0));
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);
return true;
}

View file

@ -336,6 +336,7 @@ static bool run_task_test(void) {
"DROP TABLE track_observations;"
"DROP TABLE tracks;"
"DROP TABLE track_sets;"
"DROP TABLE track_builder_tasks;"
"UPDATE metadata SET value=12 WHERE key='schema_version';COMMIT;"));
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V13", "1", 1) == 0);
Lardon3DProjectDb *database = NULL;
@ -352,7 +353,7 @@ static bool run_task_test(void) {
0));
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);
lardon3d_project_db_close(database);
CHECK(remove_tree(root));
return true;

View file

@ -54,7 +54,7 @@ static bool create_v9_database(const char *path) {
"DROP TABLE match_results;"
"DROP TABLE track_observations;"
"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;";
bool ok = sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK;
return sqlite3_close(connection) == SQLITE_OK && ok;
@ -98,7 +98,7 @@ static bool run_test(void) {
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
Lardon3DProjectDb *database = NULL;
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;
CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) ==
@ -457,7 +457,7 @@ static bool run_test(void) {
database = NULL;
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 */
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(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_schema_version(database) == 14);
CHECK(lardon3d_project_db_schema_version(database) == 15);
lardon3d_project_db_close(database);
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,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='match_results'", 1));
@ -519,7 +519,7 @@ static bool run_test(void) {
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='matcher_tasks'", 0));
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);
database = NULL;

View file

@ -94,7 +94,7 @@ static bool downgrade_project_to_historical_v10(const char *database_path) {
"DROP TABLE matcher_tasks;"
"DROP TABLE track_observations;"
"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';"
"COMMIT;PRAGMA foreign_keys=ON;";
bool success = sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK;
@ -355,7 +355,7 @@ static bool run_test(void) {
"name='matcher_tasks'",
0));
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,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='matcher_tasks'",

View file

@ -103,7 +103,7 @@ static bool create_future_database(const char *path) {
}
bool ok = sqlite3_exec(connection,
"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;
return sqlite3_close(connection) == SQLITE_OK && ok;
}
@ -124,6 +124,7 @@ static bool create_v7_database(const char *path) {
"DROP TABLE candidate_pair_generate_tasks;"
"DROP TABLE candidate_pairs;"
"DROP TABLE track_observations;"
"DROP TABLE track_builder_tasks;"
"DROP TABLE tracks;"
"DROP TABLE track_sets;"
"UPDATE metadata SET value=7 WHERE key='schema_version';COMMIT;PRAGMA foreign_keys=ON;";
@ -153,6 +154,7 @@ static bool create_v6_database(const char *path) {
"ALTER TABLE feature_sets DROP COLUMN total_cells;"
"ALTER TABLE feature_sets DROP COLUMN occupied_cells;"
"DROP TABLE track_observations;"
"DROP TABLE track_builder_tasks;"
"DROP TABLE tracks;"
"DROP TABLE track_sets;"
"UPDATE metadata SET value=6 WHERE key='schema_version';COMMIT;PRAGMA foreign_keys=ON;";
@ -177,6 +179,7 @@ static bool create_v10_database(const char *path) {
"DROP TABLE geometric_verification_results;"
"DROP TABLE matcher_tasks;"
"DROP TABLE track_observations;"
"DROP TABLE track_builder_tasks;"
"DROP TABLE tracks;"
"DROP TABLE track_sets;"
"UPDATE metadata SET value=10 WHERE key='schema_version';"
@ -195,6 +198,7 @@ static bool create_v13_database(const char *path) {
static const char sql[] =
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
"DROP TABLE track_observations;"
"DROP TABLE track_builder_tasks;"
"DROP TABLE tracks;"
"DROP TABLE track_sets;"
"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;
}
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) {
if (!create_v6_database(path)) return false;
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 direct_v5_path[512], v8_path[512], failed_v8_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(artifact_path, sizeof(artifact_path), "%s/artifact.bin", 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(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(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_v15_path, sizeof(failed_v15_path), "%s/failed-v15.db", directory) > 0);
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
Lardon3DProjectDb *database = NULL;
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;
CHECK(lardon3d_project_db_legacy_catalog_pending(database, &legacy_pending) ==
LARDON3D_PROJECT_DB_OK &&
@ -668,7 +690,7 @@ static bool run_test(void) {
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
lardon3d_project_db_close(contexts[0].database);
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(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);
@ -690,7 +712,7 @@ static bool run_test(void) {
CHECK(create_v1_database(legacy_path));
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 &&
strcmp(loaded_project.stable_id, "legacy-project") == 0);
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_close(database);
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(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_close(database);
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(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_close(database);
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(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);
}
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_artifact(database, "legacy-artifact", &loaded_artifact) ==
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);
}
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);
database = NULL;
CHECK(create_v5_database(direct_v5_path));
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 &&
lardon3d_project_db_schema_version(database) == 14);
lardon3d_project_db_schema_version(database) == 15);
lardon3d_project_db_close(database);
database = NULL;
CHECK(create_v7_database(v8_path));
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_schema_version(database) == 14);
CHECK(lardon3d_project_db_schema_version(database) == 15);
lardon3d_project_db_close(database);
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(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 "
"name='candidate_pairs'", 0));
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);
database = NULL;
@ -852,10 +874,10 @@ static bool run_test(void) {
"name='matcher_tasks'",
0));
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);
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,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='matcher_tasks'",
@ -878,7 +900,7 @@ static bool run_test(void) {
1));
CHECK(lardon3d_project_db_open(failed_v11_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);
database = NULL;
@ -889,15 +911,34 @@ static bool run_test(void) {
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='track_sets'", 0));
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);
database = NULL;
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,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"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(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V14", "1", 1) == 0);
CHECK(lardon3d_project_db_open(failed_v14_path, &database, error) ==
@ -910,7 +951,22 @@ static bool run_test(void) {
"name='track_sets'", 0));
CHECK(lardon3d_project_db_open(failed_v14_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);
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);
database = NULL;
@ -1313,6 +1369,8 @@ static bool run_test(void) {
CHECK(unlink(failed_v11_path) == 0);
CHECK(unlink(v13_path) == 0);
CHECK(unlink(failed_v14_path) == 0);
CHECK(unlink(true_v14_path) == 0);
CHECK(unlink(failed_v15_path) == 0);
CHECK(rmdir(directory) == 0);
return true;
}

View file

@ -1,5 +1,6 @@
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <chrono>
#include <fcntl.h>
@ -14,6 +15,11 @@
extern "C" {
#include <lardon3d/match_file.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>
}
@ -27,6 +33,7 @@ bool g_delete_before_revalidation = false;
std::string g_insert_db;
uint64_t g_insert_match_id = 0;
bool g_insert_during_build = false;
bool g_publication_race = false;
void check(bool value, const char *expression, int line) {
if (!value) {
@ -78,6 +85,8 @@ struct Fixture {
CHECK(mkdtemp(name) != nullptr);
directory = name;
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]{};
CHECK(lardon3d_project_db_open(db_path.c_str(), &db, error) == LARDON3D_PROJECT_DB_OK);
Lardon3DProjectDbScanSet scanset{};
@ -602,32 +611,335 @@ void run_partial_input_failure() {
std::puts("PARTIAL INPUT: PASS");
}
void run_resource_case() {
void run_resource_case(size_t gvr_count = 13) {
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<unsigned char> mask(1024, 0xff);
entries.reserve(8192);
for (uint32_t i = 0; i < 8192; ++i) entries.push_back({i, i, 0.1F});
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());
Lardon3DTrackBuilderProjectResult result{};
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) ==
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);
report_rss("after-build");
auto elapsed = std::chrono::duration<double>(std::chrono::steady_clock::now() - started);
struct rusage usage{};
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),
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.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",
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(&registry, &descriptor, 1) &&
lardon3d_task_kind_registry_lookup(
&registry, 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
#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);
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
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{};
Lardon3DTrackBuilderProjectRequest invalid{};
CHECK(lardon3d_track_builder_build_project(&invalid, &result) ==
@ -683,6 +1015,10 @@ int main() {
run_scope_snapshot_case();
run_partial_input_failure();
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;
}