Titolo: PipeX - Data Processing Pipeline Framework
Gruppo: Matteo Ranzi
Data: 3 Gennaio 2026
Versione: 1.2
- Matteo Ranzi (matteo.ranzi@studenti.unitn.it)
- Descrizione del progetto
- Elenco e descrizione dei Requisiti
- Attività svolte per la realizzazione della soluzione
- Attività di implementazione
- Altre indicazioni utili
- Gestione Errori ed Eccezioni
- Bibliografia
- Dichiarazione sull'uso dell'IA
PipeX è un framework C++11 progettato per la costruzione di pipeline di elaborazione dati modulari, componibili ed efficienti. Il framework permette di definire flussi di lavoro lineari in cui i dati vengono generati, trasformati, filtrati e infine consumati da una serie di nodi interconnessi.
L'obiettivo principale di PipeX è fornire un'interfaccia C++ moderna e pulita per la creazione di pipeline, incoraggiando la modularità e la riusabilità attraverso l'uso di nodi basati su template. Il sistema è progettato per essere estensibile, permettendo agli utenti di implementare facilmente nuovi tipi di nodi personalizzati.
Caratteristiche principali:
- Architettura Ibrida: Core basato su template per massime prestazioni, con componenti compilati per la gestione delle risorse e funzionalità specifiche.
- Compatibilità C++11: Utilizza funzionalità moderne del linguaggio (smart pointers, lambda functions, move semantics).
- Type Safety & Polymorphism: Utilizza il pattern CRTP (Curiously Recurring Template Pattern) per combinare polimorfismo statico e sicurezza dei tipi, riducendo l'overhead delle chiamate virtuali dove possibile e garantendo che i tipi di dati tra i nodi siano coerenti.
- Gestione della Memoria: Uso estensivo di
std::unique_ptrper la gestione automatica della memoria e il trasferimento di proprietà dei dati lungo la pipeline. - Sistema di Debug: Macro configurabili per il tracciamento del flusso di dati e del ciclo di vita degli oggetti.
- Supporto Metadati: I dati che fluiscono nella pipeline possono trasportare metadati (es. frequenza di campionamento audio, dimensioni immagine) che vengono propagati e verificati dai nodi.
Per compilare ed eseguire PipeX sono necessari i seguenti requisiti:
- Compilatore C++: Compatibile con lo standard C++11 o superiore (GCC, Clang, MSVC).
- CMake: Versione 3.31.6 o superiore per la gestione della build.
- GoogleTest: Framework per i test unitari (scaricato automaticamente da CMake tramite
FetchContent). - Sistema Operativo: Cross-platform (testato su macOS, compatibile con Linux e Windows).
Dentro la cartella del progetto, eseguire i seguenti comandi nel terminale:
- Creazione directory di build:
mkdir build
cd build- Configurazione:
I parametri di configurazione di seguito mostrati singolarmente sono da intendersi come parametri da impostare in un unico comando cmake, separati da spazi.
2.a Configurazione di base
In fase di configurazione è possibile specificare il tipo di build desiderato (Release impostato di default).
Ad esempio, se si preferisce una build di Debug (con log dettagliati):
cmake .. -DCMAKE_BUILD_TYPE=DebugAltrimenti, per una build di Release (solo log di errori):
cmake ..Nota: CMake scaricherà automaticamente GoogleTest se non presente.
2.b Configurazione Log di Debug:
È possibile controllare il livello di verbosità dei log di debug configurando le variabile CMake RELEASE_PRINT_DEBUG_LEVEL
e DEBUG_PRINT_DEBUG_LEVEL, che impostano il livello di log rispettivamente per le build di tipo Release e Debug.
I livelli disponibili sono:
0(NONE): Nessun log.1(ERROR): Solo errori (Release build default).2(WARN): Errori e avvisi.3(INFO): Log completi (Debug build default).
Esempio per disabilitare i log nella build di Release:
cmake -DRELEASE_PRINT_DEBUG_LEVEL=0 ..È inoltre possibile sovrascrivere il livello di log sei singoli moduli tramite le variabili:
APP_RELEASE_PRINT_DEBUG_LEVEL: livello di log per l'applicazione principale in Release.APP_DEBUG_PRINT_DEBUG_LEVEL: livello di log per l'applicazione principale in Debug.TESTS_RELEASE_PRINT_DEBUG_LEVEL: livello di log per i test in Release.TESTS_DEBUG_PRINT_DEBUG_LEVEL: livello di log per i test in Debug.PIPEX_RELEASE_PRINT_DEBUG_LEVEL: livello di log per il core framework PipeX in Release.PIPEX_DEBUG_PRINT_DEBUG_LEVEL: livello di log per il core framework PipeX in Debug.SANDBOX_RELEASE_PRINT_DEBUG_LEVEL: livello di log per la sandbox di sperimentazione in Release.SANDBOX_DEBUG_PRINT_DEBUG_LEVEL: livello di log per la sandbox di sperimentazione in Debug.
2.b Attivazione/disattivazione test e sandbox:
Per abilitare o disabilitare la compilazione dei test e della sandbox, utilizzare le seguenti opzioni:
PIPEX_BUILD_TESTS: ON/OFF (default ON)PIPEX_BUILD_SANDBOX: ON/OFF (default OFF)
Esempio per abilitare la sandbox senza testing:
cmake -DPIPEX_BUILD_TESTS=OFF -DPIPEX_BUILD_SANDBOX=ON ..- Compilazione:
cmake --build .- Esecuzione Applicazione:
./app- Esecuzione Test:
Gli eseguibili dei test si trovano in build/tests/ e vengono creati solamente se la variabile PIPEX_BUILD_TESTS è stata impostata su ON durante la configurazione con CMake.
Per eseguire tutti i test:
ctest --output-on-failureSe si preferisce compilare manualmente senza CMake, è necessario assicurarsi di includere la directory include per i file header e collegare le librerie necessarie.
Assicurarsi anche di creare le cartelle output/audio e output/image prima di eseguire l'applicazione di esempio contenuta in main.cpp.
Comando g++ per compilazione di base (no tests), impostando il livello di debug a ERROR:
g++ src/main.cpp \
src/PipeX/PipeX.cpp \
src/PipeX/Image/PPM_ImagePreset_Source.cpp \
src/PipeX/Audio/WAV_AudioPreset_Source.cpp \
-I ./include \
-DPRINT_DEBUG_LEVEL=1 \
-DPIPEX_PRINT_DEBUG_ENABLEDIl processo di sviluppo è stato iterativo e si è articolato nelle seguenti fasi principali:
-
Analisi dei Requisiti e Progettazione Architetturale:
- Studio dello Stato dell'Arte: Analisi di framework esistenti (GStreamer, ffmpeg) per comprendere i pattern di progettazione delle pipeline dati.
- Definizione dell'Architettura: Progettazione del sistema basato su un Engine centrale (
PipeXEngine) che gestisce pipeline indipendenti. Scelta del pattern CRTP (Curiously Recurring Template Pattern) per i nodi al fine di combinare l'interfaccia polimorfica necessaria alla pipeline (INode) con le ottimizzazioni di tipo statico all'interno dei nodi specifici. - Design del Sistema di Tipi: Ideazione del meccanismo di Type Erasure (
IData->Data<T>) per permettere il passaggio di dati eterogenei attraverso un'interfaccia comune, garantendo al contempo la Type Safety a runtime tramite controlli dinamici.
-
Implementazione del Core Framework:
- Sviluppo delle interfacce base (
INode,IData) e della classe templateNodeCRTP, che costituisce il cuore logico del framework gestendo l'unwrapping e il wrapping automatico dei dati. - Implementazione dei nodi primitivi generici:
Source(generazione),Sink(terminazione),Filter(selezione) eTransformer(modifica), utilizzandostd::functionper permettere la definizione della logica tramite lambda expression. - Implementazione della classe
Pipelineper la gestione sequenziale dei nodi e delPipeXEngineper l'orchestrazione parallela.
- Sviluppo delle interfacce base (
-
Sviluppo delle Estensioni (Proof of Concept):
- Modulo Immagini: Definizione delle strutture dati per immagini PPM. Implementazione di nodi specifici come
GainExposure(algoritmo sigmoide per contrasto/esposizione) e nodi di I/O per file.ppm. - Modulo Audio: Definizione di buffer audio e metadati WAV. Implementazione di algoritmi DSP (Digital Signal Processing) complessi come filtri Biquad (
EQ_BellCurve) e modulazione di ampiezza (AmplitudeModulation), oltre alla gestione dell'header WAV per l'I/O. - Integrazione Metadati: Estensione del sistema
IDataper trasportare metadati (IMetadata) essenziali per l'elaborazione (es. sample rate audio, dimensioni immagine), con controlli di compatibilità a runtime.
- Modulo Immagini: Definizione delle strutture dati per immagini PPM. Implementazione di nodi specifici come
-
Refactoring, Ottimizzazione e Thread Safety:
- Gestione Concorrenza: Identificazione di criticità nell'I/O su console durante l'esecuzione parallela. Sviluppo della classe
Console_threadsafee dei relativi nodi (ConsoleSource_ts,ConsoleSink_ts) per sincronizzare l'accesso astd::couttramite mutex. - Correzione Bug Critici: Risoluzione di problemi legati alla Move Semantics (es. copie involontarie nei nodi filtro) e alla gestione della memoria (uso corretto di
std::unique_ptr). - Sistema di Build: Configurazione avanzata di CMake per la gestione delle dipendenze (GoogleTest), livelli di debug configurabili e compilazione cross-platform.
- Gestione Concorrenza: Identificazione di criticità nell'I/O su console durante l'esecuzione parallela. Sviluppo della classe
-
Testing e Validazione:
- Integrazione del framework GoogleTest.
- Scrittura di Unit Test per verificare la logica dei singoli nodi.
- Scrittura di Integration Test per validare il flusso dati in pipeline complesse e la corretta propagazione delle eccezioni (es.
TypeMismatchExpection).
Il design di PipeX si basa su una gerarchia di classi che sfrutta sia l'ereditarietà classica che il polimorfismo statico tramite CRTP.
classDiagram
class PipeXEngine {
+static getPipexEngine() PipeXEngine*
+newPipeline(name) Pipeline&
+start()
}
class Pipeline {
-string name
-vector~unique_ptr~INode~~ nodes
+addNode(node)
+run()
}
class INode {
<<Abstract>>
+clone() unique_ptr~INode~
+process(input) unique_ptr~IData~
}
class NodeCRTP["NodeCRTP<Derived, InputT, OutputT, MetadataT>"] {
<<Template>>
#processImpl(input) unique_ptr~vector~OutputT~~
#extractInputData(data)
#wrapOutputData(data)
}
class Source~T~ {
-Function sourceFunction
}
class Sink~T~ {
-Function sinkFunction
}
class Filter~T~ {
-Predicate predicateFilter
}
class Transformer["Transformer<InT, OutT>"] {
-Function transformerFunction
}
class IData {
<<Abstract>>
+shared_ptr~IMetadata~ metadata
}
class Data["Data<T>"] {
+T value
}
class IMetadata {
<<Abstract>>
}
PipeXEngine "1" *-- "*" Pipeline : manages
Pipeline "1" *-- "*" INode : contains
INode <|-- NodeCRTP
NodeCRTP <|-- Source
NodeCRTP <|-- Sink
NodeCRTP <|-- Filter
NodeCRTP <|-- Transformer
INode ..> IData : uses
IData <|-- Data
IData "1" o-- "0..1" IMetadata : has
-
PipeXEngine(Singleton):- Ruolo: Punto di ingresso principale e gestore delle pipeline.
- Metodi:
getPipexEngine()(accesso istanza),newPipeline()(crea e registra una nuova pipeline),start()(avvia in parallelo l'esecuzione di tutte le pipeline registrate).
- Relazioni: Contiene una lista di oggetti
Pipeline. - Concorrenza: Il metodo
start()avvia ogni pipeline registrata in un thread dedicato, permettendo l'esecuzione parallela di flussi di dati indipendenti.
-
Pipeline:- Ruolo: Rappresenta una singola catena di elaborazione dati.
- Proprietà:
name(identificativo),nodes(lista di puntatori aINode).
- Metodi:
addNode<NodeType>(args...)(aggiunge un nodo alla catena),run()(esegue la pipeline sequenzialmente).
- Relazioni: Aggrega oggetti
INode.
-
INode(Abstract Base Class):- Ruolo: Interfaccia base polimorfica per tutti i nodi. Permette alla
Pipelinedi memorizzare nodi eterogenei. - Metodi:
clone()(pattern Prototype per la copia),process()(interfaccia pubblica di elaborazione che accetta/ritornaIData).
- Ruolo: Interfaccia base polimorfica per tutti i nodi. Permette alla
-
NodeCRTP<Derived, InputT, OutputT, MetadataT>:- Ruolo: Classe base template che implementa la logica comune di gestione dei tipi.
- Funzionalità: Gestisce l'unwrapping dei dati da
IDataastd::vector<InputT>e il wrapping dell'output inIData. Gestisce la propagazione e validazione dei metadati. - Relazioni: Eredita da
INode.
-
Nodi Primitivi (
Source,Sink,Filter,Transformer,Processor): Ogni nodo primitivo sovrascrive e implementa il metodostd::vector<OutputT> processImpl(std::vector<InputT>), richiamando al suo interno la lambda function ricevuta come parametro del costruttore.Source<T>: Genera dati. Non ha input. Utilizza unastd::function<InputT()>per produrre dati.Sink<T>: Consuma dati. Non produce output per la pipeline successiva. Utilizza unastd::function<void(const T&)>per consumare dati.Filter<T>: Filtra i dati in base a un predicato (std::function<bool(const T&)>). Input e Output sono dello stesso tipo.Transformer<InputT, OutputT>: Trasforma i dati da un tipo all'altro tramite una funzione di trasformazionestd::function<OutputT(InputT&)>.Processor<InputT, OutputT>: Riceve l'intero buffer di datistd::vector<InputT>, permettendo una manipolazione che prevede l'uso di tutti i dati che scorrono nella pipeline. A sua volta, restituisce l'intero buffer di datistd::vector<OutputT>. Per fare ciò, utilizza la funzione:std::function<std::vector<OutputT>(std::vector<InputT>&)>
-
IDataeData<T>:- Ruolo: Wrapper per i dati che fluiscono tra i nodi (Type Erasure).
IDataè l'interfaccia base,Data<T>è l'implementazione concreta che contienestd::vector<T>. - Proprietà:
metadata(puntatore aIMetadata).
- Ruolo: Wrapper per i dati che fluiscono tra i nodi (Type Erasure).
Uno degli aspetti critici di PipeX è la capacità di gestire una pipeline eterogenea in cui i nodi possono avere tipi di input e output differenti (es. NodeA<int, float> -> NodeB<float, string>). Poiché la classe Pipeline deve memorizzare una lista generica di nodi, non può conoscere a tempo di compilazione i tipi esatti di ogni stadio.
Per risolvere questo problema, PipeX utilizza la tecnica della Type Erasure:
- Interfaccia Comune: Tutti i dati scambiati tra i nodi sono incapsulati in oggetti che implementano l'interfaccia base astratta
IData. - Wrapper Concreto: La classe template
Data<T>eredita daIDatae contiene il payload effettivo (std::vector<T>). - Polimorfismo: La
Pipelinevede solo puntatori aIData. Passa questi puntatori al metodoprocess(std::unique_ptr<IData>)del nodo successivo. - Estrazione Sicura: All'interno del nodo (nella classe base
NodeCRTP), il puntatoreIDataviene convertito dinamicamente (tramitedynamic_casto meccanismi simili verificati) nel tipo concreto attesoData<InputT>. Se il tipo non corrisponde, viene lanciata un'eccezioneTypeMismatchExpection.
Diagramma del Flusso di Type Erasure
flowchart LR
subgraph Node A ["Node A (Source)"]
Gen[Generate T] --> WrapA["Wrap in Data<T>"]
end
subgraph Pipe [Pipeline]
Pass1[Forward IData*]
end
subgraph Node B ["Node B (Transformer)"]
RecvB[Receive IData*] --> CastB{"Cast to<br/>Data<T>?"}
CastB -- Yes --> ExtractB["Extract vector<T>"]
CastB -- No --> Err[Throw TypeMismatch]
ExtractB --> ProcB[Process T -> U]
ProcB --> WrapB["Wrap in Data<U>"]
end
WrapA --> Pass1 --> RecvB
Il framework è stato esteso con nodi specifici per l'elaborazione di immagini e audio, dimostrando la flessibilità del design.
Per migliorare la leggibilità, i diagrammi sono organizzati per tipologia di nodo primitivo:
Source, Transformer e Sink.
1. Sorgenti (Sources)
Rappresentano i nodi generatori di dati per le estensioni Immagini e Audio.
classDiagram
class Source["Source<T, M>"]
class PPM_ImagePreset_Source {
+PPM_ImagePreset_Source(...)
}
class WAV_SoundPreset_Source {
+WAV_SoundPreset_Source(...)
}
Source <|-- PPM_ImagePreset_Source : T=PPM_Image, M=PPM_Metadata
Source <|-- WAV_SoundPreset_Source : T=WAV_AudioBuffer, M=WAV_Metadata
2. Trasformatori (Transformers) Rappresentano i nodi che modificano i dati in transito.
classDiagram
class Transformer["Transformer<In, Out, M>"]
class GainExposure {
+GainExposure(gain, contrast)
}
class EQ_BellCurve {
+EQ_BellCurve(freq, Q, gain)
}
class AmplitudeModulation {
+AmplitudeModulation(freq, depth)
}
Transformer <|-- GainExposure : In=PPM_Image, Out=PPM_Image
Transformer <|-- EQ_BellCurve : In=WAV_AudioBuffer, Out=WAV_AudioBuffer
Transformer <|-- AmplitudeModulation : In=WAV_AudioBuffer, Out=WAV_AudioBuffer
3. Terminatori (Sinks) Rappresentano i nodi che consumano i dati finali della pipeline.
classDiagram
class Sink["Sink<T, M>"]
class PPM_Image_Sink {
+PPM_Image_Sink(filename)
}
class WAV_Sound_Sink {
+WAV_Sound_Sink(filename)
}
Sink <|-- PPM_Image_Sink : T=PPM_Image, M=PPM_Metadata
Sink <|-- WAV_Sound_Sink : T=WAV_AudioBuffer, M=WAV_Metadata
Di seguito vengono descritti i nodi specifici implementati per le estensioni Audio e Immagini, con i relativi parametri di configurazione.
1. Estensione Immagini (PPM)
| Nodo | Tipo | Descrizione | Parametri Costruttore |
|---|---|---|---|
PPM_ImagePreset_Source |
Source | Genera immagini sintetiche basate su pattern predefiniti. | • node_name: Nome del nodo.• width, height: Dimensioni immagine.• preset: ID del pattern (es. gradiente).• count: Numero di immagini da generare. |
GainExposure |
Transformer | Regola esposizione e contrasto usando una curva sigmoidea per simulare la risposta della pellicola. | • node_name: Nome del nodo.• gain: Regolazione esposizione (in stop).• contrast: Fattore di contrasto (default 1.0). |
PPM_Image_Sink |
Sink | Salva le immagini su disco in formato PPM (P3). | • node_name: Nome del nodo.• filename: Percorso base del file di output (verrà aggiunto un indice e l'estensione). |
2. Estensione Audio (WAV)
| Nodo | Tipo | Descrizione | Parametri Costruttore |
|---|---|---|---|
WAV_SoundPreset_Source |
Source | Genera flussi audio sintetici (toni puri o rumore). | • node_name: Nome del nodo.• nStreams: Numero di tracce da generare.• sampleRate: Frequenza di campionamento (es. 44100).• bitsPerSample: Profondità in bit (es. 16).• durationSec: Durata in secondi.• preset: Tipo di suono (0: Sinusoide, 1: White Noise, 2: Pink Noise). |
EQ_BellCurve |
Transformer | Applica un filtro equalizzatore parametrico (Peaking EQ) del secondo ordine (Biquad). | • node_name: Nome del nodo.• centerFrequency: Frequenza centrale in Hz.• qFactor: Fattore Q (larghezza di banda).• gainDB: Guadagno/Attenuazione in dB. |
AmplitudeModulation |
Transformer | Applica un effetto Tremolo modulando l'ampiezza del segnale con un LFO. | • node_name: Nome del nodo.• rateHz: Frequenza dell'oscillatore (LFO) in Hz.• depth: Intensità dell'effetto (0.0 - 1.0). |
WAV_Sound_Sink |
Sink | Salva i buffer audio su disco in formato WAV standard. | • node_name: Nome del nodo.• filename: Percorso base del file di output. |
Il framework supporta due modalità principali per definire la logica di elaborazione:
-
Derivazione (Inheritance): Utilizzata per creare componenti riutilizzabili, complessi o configurabili.
- Si crea una nuova classe che eredita da un nodo primitivo (
Source,Transformer,Filter, etc.). - Il costruttore della classe derivata configura il nodo base (spesso passando una lambda che cattura
this). - Esempio:
GainExposureeredita daTransformer. Il suo costruttore accetta parametri (gain, contrast) e configura la funzione di trasformazione interna per usare il metodo privatoapplyGain. Questo incapsula la logica complessa (algoritmo sigmoide) e i parametri in una classe pulita.
- Si crea una nuova classe che eredita da un nodo primitivo (
-
Uso Diretto (Lambda Functions): Utilizzata per operazioni semplici, "one-off" o prototipazione rapida.
- Si istanzia direttamente un nodo primitivo (
Transformer,Filter, etc.). - Si passa una lambda function al costruttore che definisce la logica.
- Esempio:
Transformer<int, int>("Doubling", [](int& x){ return x * 2; }). Non è necessario creare una classeDoublerNodeper un'operazione così semplice.
- Si istanzia direttamente un nodo primitivo (
Questa dualità permette di bilanciare rapidità di sviluppo (lambda) e ingegnerizzazione del software (classi dedicate).
Il seguente diagramma illustra un esempio di interazione tra l'utente, l'engine e i nodi durante la configurazione e l'esecuzione di una pipeline, evidenziando il flusso dei dati e dei metadati.
sequenceDiagram
autonumber
participant User as Main / User
participant Engine as PipeXEngine
participant Pipe as Pipeline
participant Src as Source Node
participant Trn as Transformer Node
participant Snk as Sink Node
rect rgb(249, 249, 249)
note right of User: Configuration Phase
User->>Engine: getPipeXEngine()
Engine-->>User: return PipeXEngine Reference
User->>Engine: newPipeline("MyPipeline")
Engine-->>User: return Pipeline Reference
User->>Pipe: addNode<Source>(...)
User->>Pipe: addNode<Transformer>(...)
User->>Pipe: addNode<Sink>(...)
end
rect rgb(233, 233, 233)
note right of User: Execution Phase
User->>Engine: start()
note right of Engine: Launches each Pipeline<br/>in a separate Thread
Engine->>Pipe: run()
rect rgb(240, 248, 255)
note right of Pipe: Data Flow Loop
Pipe->>Src: process(nullptr)
activate Src
note right of Src: Generate Data<br/>(Input from File/Console)
Src-->>Pipe: return Data + Metadata
deactivate Src
Pipe->>Trn: process(Data)
activate Trn
note right of Trn: 1. Extract Input<br/>2. Check Metadata<br/>3. Transform<br/>4. Wrap Output
Trn-->>Pipe: return NewData + Metadata
deactivate Trn
Pipe->>Snk: process(NewData)
activate Snk
note right of Snk: Consume Data<br/>(Write to File/Console)
Snk-->>Pipe: return nullptr
deactivate Snk
end
Pipe-->>Engine: complete
Engine-->>User: return
end
-
Generazione ed Elaborazione Immagini (PPM):
- Attori: Utente (tramite
main.cpp). - Oggetti:
PPM_ImagePreset_Source(genera immagine pattern),GainExposure(applica filtro esposizione),PPM_Image_Sink(scrive file .ppm).
- Flusso: La sorgente crea un'immagine in memoria -> Il nodo GainExposure modifica i valori dei pixel -> Il Sink scrive il risultato su disco.
- Attori: Utente (tramite
-
Generazione ed Elaborazione Audio (WAV):
- Attori: Utente.
- Oggetti:
WAV_SoundPreset_Source(genera rumore/toni),EQ_BellCurve(filtro equalizzatore),AmplitudeModulation(effetto tremolo),WAV_Sound_Sink(scrive file .wav).
- Flusso: Generazione buffer audio -> Applicazione filtri DSP -> Salvataggio file WAV.
Non viene utilizzato un database tradizionale (SQL/NoSQL). I dati sono gestiti come flussi in memoria e persistiti su File System:
- File Immagine (.ppm): Formato Portable Pixel Map (P3 o P6). File di testo o binari semplici che rappresentano griglie di pixel RGB.
- File Audio (.wav): Formato WAVE standard. Contiene un header (RIFF) con metadati (sample rate, bit depth, canali) seguito dai dati audio PCM grezzi.
L'interazione è principalmente programmatica (definita nel main.cpp) e tramite Console (CLI) per l'output di debug e log.
Definizione della pipeline nel codice sorgente
- Input: In base al tipo di
Source: console, file, vettore catturato nella lambda - Output: In base al tipo di
Sink: console, file, vettore catturato nella lambda. Esempio audio/video:- File generati nelle cartelle
output/image/eoutput/audio/. - Log dettagliati su console che mostrano: creazione nodi, flusso dati, chiamate a costruttori/distruttori (utile per debug gestione memoria).
- File generati nelle cartelle
Esempio di output console (con livello di debug ERROR):
Running PipeXEngine with 3 pipelines...
Running pipeline "PPM Image generation"...
Valid pipeline "PPM Image generation" starting execution with 3 nodes.
Running pipeline "WAV Audio generation with Amplitude Modulation"...
Running pipeline "WAV Audio generation"...
Valid pipeline "WAV Audio generation" starting execution with 3 nodes.
***Pipeline "WAV Audio generation" execution completed.
[DEBUG_ERROR] [PipeX] [PipeXEngine] PipeXException exception in pipeline "WAV Audio generation with Amplitude Modulation": PipeX Library exception: Invalid Pipeline "WAV Audio generation with Amplitude Modulation": Cannot run pipeline, invalid configuration: missing Sink node
***Pipeline "PPM Image generation" execution completed.
my_extended_cpp_standard: Una libreria di utilità personalizzata (inclusa nel progetto) che fornisce helper per la gestione della memoria (my_memory.h) e type traits (my_type_traits.h), estendendo le funzionalità standard del C++11.- GoogleTest: Framework standard de-facto per il testing C++, integrato tramite CMake FetchContent per garantire che i test siano sempre eseguibili in un ambiente isolato.
Il cuore di PipeX si basa su diversi pattern architetturali e idiomi C++ avanzati per garantire performance, flessibilità e sicurezza dei tipi.
-
Curiously Recurring Template Pattern (CRTP):
- Utilizzo: Nella classe
NodeCRTP<Derived, ...>. - Scopo: Realizzare polimorfismo statico. Permette alla classe base di invocare metodi dell'implementazione derivata (
processImpl) senza il costo di una chiamata virtuale (vtable lookup) per la logica interna, mantenendo comunque l'interfaccia virtualeINodesolo per la gestione esterna della pipeline.
- Utilizzo: Nella classe
-
Type Erasure:
- Utilizzo: Classi
IDataeData<T>. - Scopo: Permettere alla
Pipelinedi gestire una collezione eterogenea di dati. I nodi comunicano tramite puntatori aIData(tipo cancellato), e il tipo reale viene recuperato a runtime tramitedynamic_castsicuro all'interno dei nodi, garantendo che ogni nodo riceva esattamente il tipo di dato che si aspetta.
- Utilizzo: Classi
-
Template Method Pattern:
- Utilizzo: Nel metodo
process()diINode/NodeCRTP. - Scopo: Definire lo scheletro dell'algoritmo di elaborazione (estrazione input -> pre-hook -> elaborazione -> post-hook -> wrapping output) nella classe base, delegando i dettagli specifici alle sottoclassi.
- Utilizzo: Nel metodo
-
Singleton Pattern:
- Utilizzo: Classe
PipeXEngine. - Scopo: Garantire l'esistenza di un'unica istanza di coordinamento per l'esecuzione delle pipeline e la gestione delle risorse globali.
- Utilizzo: Classe
-
RAII (Resource Acquisition Is Initialization):
- Utilizzo: Gestione di
std::unique_ptrper nodi e dati. - Scopo: Garantire che la memoria e le risorse vengano rilasciate automaticamente e in modo deterministico, prevenendo memory leak anche in caso di eccezioni.
- Utilizzo: Gestione di
-
Factory Method Pattern:
- Utilizzo: Metodo
addNode<NodeType>(args...)inPipeline. - Scopo: Permettere la creazione di nodi di tipo specifico senza esporre la logica di istanziazione all'utente, facilitando l'aggiunta di nuovi tipi di nodi in futuro.
- Utilizzo: Metodo
Il codice sorgente è organizzato separando chiaramente le interfacce (header files) dalle implementazioni, seguendo una struttura modulare.
-
include/PipeX/: Contiene tutti gli header files del framework.PipeXEngine.h: Definizione del motore principale e gestione delle pipeline.Pipeline.h: Definizione della classe Pipeline.nodes/: Definizioni dei nodi.- Nodi interfaccia (
INode, templateNodeCRTP). - Nodi primitivi come
Source,Sink,Filter,Transformer,AggregatoreProcessor). - Nodi specifici per estensioni (Immagini, Audio, thread-safe console).
- Nodi interfaccia (
data/: Definizioni per il sistema di tipi (IData,Data<T>).metadata/: Gestione dei metadati associati ai dati.errors/: Definizioni delle eccezioni personalizzate.utils/: Utility varie.debug/: Strumenti per il debug e logging.
-
src/: Contiene i file sorgente.cpp.main.cpp: Entry point dell'applicazione dimostrativa.PipeX/: Implementazioni specifiche del framework.PipeX.cpp: Implementazione di funzionalità core non-template.Image/: Implementazioni specifiche per l'elaborazione immagini (es. nodi PPM).Sound/: Implementazioni specifiche per l'elaborazione audio (es. nodi WAV, filtri DSP).
PipeX implementa un robusto sistema di gestione degli errori basato su eccezioni, permettendo di intercettare e gestire problemi sia in fase di configurazione che di esecuzione. Tutte le eccezioni derivano dalla classe base PipeXException.
| Eccezione | Descrizione | Scenario Tipico |
|---|---|---|
PipeXException |
Classe base per tutte le eccezioni del framework. | Catch generico per errori del framework. |
InvalidPipelineException |
Errore strutturale nella definizione della pipeline. | Tentativo di aggiungere un nodo non valido o configurazione incompleta. |
NodeNameConflictException |
Conflitto di nomi tra i nodi. | Aggiunta di due nodi con lo stesso nome nella stessa pipeline (i nomi devono essere univoci per permettere l'operazione di rimozione nodo tramite il suo nome). |
InvalidOperation |
Operazione non consentita nello stato corrente. | Tentativo di modificare una pipeline mentre l'engine è in esecuzione (isRunning == true). |
TypeMismatchExpection |
Errore di tipo a runtime tra due nodi. | Il Nodo A produce int ma il Nodo B si aspetta string. Rilevato durante extractInputData. |
MetadataTypeMismatchException |
Incompatibilità dei metadati. | Un nodo riceve metadati diversi da quelli previsti, o metadati mancanti dove richiesti. |
Il framework garantisce che un'eccezione in una pipeline non comprometta l'esecuzione delle altre pipeline parallele gestite da PipeXEngine.
- C++ Reference: https://en.cppreference.com/ - Riferimento per standard C++11, smart pointers, e type traits.
- CMake Documentation: https://cmake.org/documentation/ - Guida per la configurazione del sistema di build.
- GoogleTest User Guide: https://google.github.io/googletest/ - Documentazione per il framework di testing.
- Audio EQ Cookbook (Robert Bristow-Johnson): Riferimento standard per le formule dei filtri Biquad audio.
- Netpbm Format Specification: http://netpbm.sourceforge.net/doc/ppm.html - Specifiche del formato immagine PPM.
- CRTP (Curiously Recurring Template Pattern): https://en.cppreference.com/w/cpp/language/crtp.html
- Functors in C++: https://www.geeksforgeeks.org/cpp/functors-in-cpp/
- Lambda Expressions: https://en.cppreference.com/w/cpp/language/lambda.html
- STL Containers: https://cplusplus.com/reference/stl/
- RAII (Resource Acquisition Is Initialization): https://en.cppreference.com/w/cpp/language/raii.html
- SFINAE: https://www.geeksforgeeks.org/cpp/substitution-failure-is-not-an-error-sfinae-in-cpp/
- STL Streams Iterators: https://www.geeksforgeeks.org/cpp/stdistream_iterator-stdostream_iterator-c-stl/
- Factory Method Pattern: https://refactoring.guru/design-patterns/factory-method
- Smart Pointers: https://en.cppreference.com/book/intro/smart_pointers
- std::function: https://www.geeksforgeeks.org/cpp/std-function-in-cpp/
- Universal References: https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
- Forwarding References: https://en.cppreference.com/w/cpp/utility/forward.html
- make_unique: https://www.geeksforgeeks.org/cpp/cpp-14-make_unique/
- Parameter Pack: https://en.cppreference.com/w/cpp/language/parameter_pack.html
- Command Pattern (Deferred Execution): https://refactoring.guru/design-patterns/command
- Type Traits: https://en.cppreference.com/w/cpp/header/type_traits.html
- enable_if: https://en.cppreference.com/w/cpp/types/enable_if.html
- decltype: https://en.cppreference.com/w/cpp/language/decltype.html
- make_unique: Exception-Safe Function Calls - Herb Sutter's Blog
- Bjarne Stroustrup's website:(https://www.stroustrup.com/) - Risorse e articoli sul C++ dallo stesso creatore del linguaggio.
- Google C++ Style Guide: (https://google.github.io/styleguide/cppguide.html) - Linee guida per la scrittura di codice C++ leggibile e manutenibile.
- Refactoring Guru - Design Patterns: (https://refactoring.guru/design-patterns/cpp) - Risorse sui pattern di progettazione in C++.
- Copilot: (https://github.com/copilot/)) - Strumento di supporto alla scrittura del codice basato su AI.
- ChatGPT: (https://chat.openai.com/) - Strumento di supporto alla scrittura del codice basato su AI.
Gli strumenti di AI (ChatGPT e Github Copilot) sono stati utilizzati esclusivamente per suggerimenti di refactoring, supporto nella creazione di commenti/documentazione e supporto per il debug e per la correzione degli errori. Tutta la logica, il design e l'implementazione sono stati realizzati autonomamente dall'autore del progetto, che si assume la piena responsabilità della correttezza del codice finale.
