DIEGO CECATO / NOTE DAL CAMPO

SQLDoom non dimostra che SQL sa fare tutto. Dimostra dove metti il problema

diego cecato//
SQLDoom con logica e renderer SQL dentro CedarDB

Ogni volta che qualcuno riesce a far girare Doom su qualcosa che non dovrebbe far girare Doom, Internet reagisce come previsto: applausi, screenshot, battute e l’inevitabile “can it run Doom?”. SQLDoom merita però qualcosa di più del rituale.

Perché qui la stranezza non è aver infilato il gioco del 1993 dentro un database. La parte interessante è come Lukas Vogel ha trasformato problemi che siamo abituati a pensare come codice procedurale — stato, game loop, collisioni, ordinamento della scena, rendering, multiplayer — in dati, query, transazioni e funzioni eseguite da CedarDB.

Il risultato non è “Doom scritto tutto in SQL”, almeno non nel senso da titolo facile. Il progetto usa un piccolo client Python per leggere l’input, scandire il tempo e mostrare il bitmap finale. Ma la logica di gioco, lo stato e il renderer vivono nel database. Il loop resta a 35 Hz come nell’originale, mentre il rendering è disaccoppiato e può produrre il framebuffer 320×200 fino a circa 60 Hz sul portatile usato dall’autore.

Ed è proprio questa precisazione a rendere SQLDoom interessante: non è una magia. È architettura portata deliberatamente fuori dalla sua zona di comfort.

Il punto non è che SQL sia diventato un linguaggio per videogiochi

Partiamo dalla conclusione più importante: nessuno dovrebbe leggere SQLDoom e decidere che il prossimo motore grafico aziendale va costruito dentro PostgreSQL. Lo stesso Vogel definisce il rendering di Doom in un database un’idea ovviamente pessima.

Il valore dell’esperimento è un altro. Quando costringi un problema dentro un ambiente con primitive diverse da quelle per cui era stato progettato, sei obbligato a separare ciò che è davvero essenziale da ciò che era soltanto una conseguenza dell’implementazione originale.

Doom è un ottimo bersaglio perché il suo formato WAD contiene già strutture sorprendentemente “relazionali”: vertici, linee, settori, oggetti, BSP. SQLDoom importa questi dati in tabelle e costruisce sopra di essi il comportamento del gioco. Non sta fingendo che una tabella sia una CPU. Sta riformulando il problema affinché il database possa applicare le primitive in cui è forte.

La lezione non è “SQL può fare tutto”. È che cambiare il modello del problema spesso conta più che aggiungere potenza allo strumento.

Da BSP e framebuffer a ORDER BY e CTE

Nel write-up tecnico di CedarDB e nel repository di SQLDoom il passaggio più istruttivo è il renderer. Il Doom originale usa alberi BSP per determinare in modo efficiente quali porzioni della scena vadano disegnate e in quale ordine. Nel porting, parte di quel lavoro viene trasformata in dati precomputati e ordinabili.

Una volta che l’informazione necessaria è rappresentata nella forma giusta, un’operazione banale come ORDER BY smette di essere “una query su una tabella” e diventa un pezzo della pipeline grafica. Il renderer conta circa 1.300 righe SQL distribuite su 89 common table expression. Non perché le CTE siano improvvisamente una GPU, ma perché il problema è stato scomposto fino a renderlo esprimibile come trasformazioni di insiemi.

Questo è un pattern molto più generale di Doom. Nello sviluppo software capita continuamente di compensare un modello dati sbagliato con codice applicativo crescente. Query sempre più contorte, cache che correggono query lente, job che riallineano stati incoerenti, API che ricostruiscono relazioni che il dominio non ha mai formalizzato.

SQLDoom fa l’opposto: porta intenzionalmente moltissima semantica nel modello dei dati. È un estremo, certo. Ma gli estremi sono utili perché rendono visibili cose che nei sistemi normali restano nascoste.

Il game loop mostra dove SQL smette di essere “solo query”

Il Doom originale avanza a 35 tic al secondo: ogni tic ha quindi un budget di circa 28,6 millisecondi. SQLDoom mantiene quel ritmo per non alterare le costanti e il comportamento del gioco. La sequenza delle operazioni, inevitabilmente procedurale, viene orchestrata con cedarscript, il linguaggio di scripting di CedarDB simile a PL/pgSQL.

Questa è una distinzione importante. Dire “è tutto una SELECT” sarebbe falso. Il progetto usa SQL, funzioni e scripting lato database per implementare un sistema che ha sia componenti set-based sia una sequenza temporale precisa.

Ed è qui che l’esperimento diventa utile anche fuori dal divertimento tecnico. Le discussioni sui linguaggi tendono spesso a diventare religiose: dichiarativo contro imperativo, database contro application layer, stored procedure contro servizi. SQLDoom mostra una realtà meno elegante e più interessante: i sistemi reali attraversano paradigmi diversi. La domanda utile non è quale paradigma vinca, ma quale parte del problema diventi più semplice quando la metti nel posto giusto.

Il multiplayer è forse la parte più seria dell’esperimento

La battuta è Doom nel database. La parte che mi interessa di più, però, è il deathmatch.

Un database porta con sé proprietà che in un server multiplayer dovresti costruire o coordinare esplicitamente: autenticazione, controllo degli accessi, concorrenza, snapshot consistenti e transazioni. SQLDoom sfrutta proprio queste caratteristiche. Un tic può essere racchiuso tra BEGIN e COMMIT, così i giocatori osservano uno stato coerente prima o dopo l’aggiornamento, non una versione intermedia in cui metà delle conseguenze è già stata applicata e metà no.

Il repository descrive circa 110 tabelle e oltre 100 funzioni, ma i ruoli dei quattro giocatori possono interagire soltanto attraverso poche funzioni API autorizzate. È un uso quasi didattico di due concetti che nei sistemi business diventano rapidamente dolorosi: atomicità dello stato e superficie minima di accesso.

Quando il database diventa il luogo in cui vive lo stato autorevole, la transazione non è un dettaglio di persistenza: diventa una regola del dominio.

È lo stesso tipo di problema che ritorna quando più processi o agenti devono lavorare sullo stesso stato. Nel mio articolo su come coordinare molti agenti che scrivono insieme, il punto era proprio che il repository o il protocollo non bastano se manca un modello esplicito di ownership, consistenza e commit. SQLDoom lo rende visibile con razzi e demoni, che almeno sono più fotogenici di una race condition su un gestionale.

La demo è anche un benchmark involontario di architettura

Secondo il progetto, la logica riesce a mantenere i 35 tic al secondo e il renderer può arrivare a 60 Hz su un AMD Ryzen 7 7840U, con cali nelle scene più impegnative. Ars Technica ha verificato il progetto e ne descrive l’architettura, pur riportando prestazioni peggiori nella demo online rispetto all’esecuzione locale.

Non trasformerei questi numeri in una gara fra database. SQLDoom richiede oggi CedarDB perché usa cedarscript, anche se il repository osserva che il codice potrebbe essere portato a PL/pgSQL. Non è un benchmark comparativo e non dimostra che un database sia il posto efficiente in cui renderizzare videogiochi.

Dimostra però qualcosa di più utile: l’overhead di un’astrazione non può essere giudicato senza guardare il piano complessivo. Se il motore riesce a trasformare query complesse in piani sufficientemente efficienti, una soluzione assurda sulla carta può diventare sorprendentemente funzionale. Questo non la rende una buona architettura di prodotto. La rende un ottimo esperimento per capire il confine dell’astrazione.

La vera lezione: spostare il confine, non copiare la soluzione

Ci sono almeno tre cose che porterei via da SQLDoom in un progetto normale.

  • Modellare prima di orchestrare. Se trasformi correttamente il dominio in dati e invarianti, spesso serve meno codice per coordinare il resto.
  • Usare le garanzie del componente che possiede lo stato. Transazioni, snapshot e controllo accessi non sono accessori se il database è la fonte autorevole.
  • Separare il confine I/O dal cuore del sistema. In SQLDoom Python fa volutamente poco: input, timing, visualizzazione. Questa separazione rende evidente dove vive davvero la logica.

È una lezione simile a quella che emerge quando un cambio di infrastruttura rompe gli assunti impliciti del software: il problema raramente è soltanto la tecnologia nuova. È scoprire quali responsabilità avevamo assegnato a un componente senza averle mai rese esplicite.

SQLDoom porta questa domanda al limite. Quanto del comportamento di un’applicazione può essere espresso come trasformazione di dati? Quanto conviene lasciare al database? Dove serve davvero una sequenza procedurale? Quali garanzie ottieni gratis cambiando il confine?

La risposta pratica non sarà “metti tutto in SQL”. Per fortuna.

Ma dopo aver visto Doom renderizzato da una query, diventa un po’ più difficile accettare senza pensarci il contrario: “questa cosa va per forza nell’application layer perché si è sempre fatto così”. E per un esperimento apparentemente assurdo, è già un risultato molto serio.

Domande frequenti

SQLDoom è interamente in SQL?

No. Logica, stato e renderer sono nel database; un piccolo client Python gestisce input, timing e visualizzazione.

Perché usa CedarDB?

Perché sfrutta SQL e cedarscript, il linguaggio di scripting lato database di CedarDB.

Che cosa mostra sull'uso di SQL?

Che SQL può esprimere molto più del CRUD quando il problema è modellato come dati, trasformazioni, funzioni e transazioni.

Qual è la lezione architetturale?

Capire dove collocare stato e responsabilità, sfruttando le garanzie del componente che possiede lo stato senza trasformare il database in un game engine.