sabato 21 febbraio 2026

GDScript - Array (parte 2), gestione della memoria, il tipo universale Variant

Godot - GDScriptIn GDScript gli array standard sono strutture dinamiche gestite automaticamente da Godot: "struttura dinamica" significa che si possono aggiungere o rimuovere elementi in qualsiasi momento, "gestione automatica" significa che che il motore gestisce automaticamente il ridimensionamento della memoria e la riorganizzazione degli elementi. Sono ideali per contenere dati eterogenei e sono progettati per bilanciare flessibilità e prestazioni. Comprendere come vengono allocati, condivisi e liberati dalla memoria è fondamentale per evitare bug e problemi di performance, soprattutto quando si lavora con strutture dati complesse o aggiornate frequentemente.

Oltre agli array standard, in GDScript esiste un'altra categoria di array (PackedArray) gestita in modo differente che al momento non trattiamo (sarà oggetto di un articolo successivo).

Il tipo universale Variant e gli array standard

In GDScript, il Variant è il tipo di dato base universale del motore: è un contenitore in grado di memorizzare ogni tipo di dato supportato da Godot, senza che sia necessario specificarlo a priori. Indipendentemente dal suo contenuto, un Variant ha una dimensione fissa in memoria: 24 byte nelle versioni a 64 bit di Godot a partire da Godot 4, 20 byte per i sistemi a 32 bit.

Il tipo Variant è un tipo di dato generico che può contenere qualsiasi tipo di valore, incluso un array: ogni variabile in GDScript è di tipo Variant, e questo tipo è progettato per essere abbastanza flessibile da rappresentare qualsiasi cosa, da numeri a oggetti complessi. Quando si assegna un array a una variabile, questa variabile è, in realtà, di tipo Variant. Questo permette agli array di essere utilizzati in modo fluido con altri tipi di dato senza la necessità di dichiarazioni esplicite. Ad esempio, la seguente scrittura definisce la variabile a come Variant che contiene un riferimento a un oggetto di tipo array:

var a = [1, 2, 3] # 'a' è di tipo Variant che contiene un array

A sua volta, un array in GDScript è una lista di Variant. Questo lo rende molto flessibile ma meno efficiente in termini di memoria rispetto ad array specializzati come i PackedArray, dove i dati sono memorizzati nel loro formato nativo (per es. 4 byte per interi a 32 bit).

NOTA BENE: tecnicamente un array tipizzato in GDScript rimane un array basato su Variant, ma con un "vincolo" di sicurezza aggiunto. Ad esempio, un Array[int] è ancora una lista di Variant "mascherata" da array di interi: ogni elemento occupa ancora lo spazio di un Variant (20-24 byte), non risparmia memoria rispetto a un array non tipizzato. Però il motore di Godot aggiunge un controllo ogni volta che si inserisce un elemento, assicurandosi che corrisponda al tipo dichiarato. Sebbene la struttura dati sia la stessa, il compilatore di Godot può ottimizzare l'accesso agli elementi e l'IDE fornisce un autocompletamento più preciso.

Per ulteriori informazioni sui Variant consultare la documentazione ufficiale: https://docs.godotengine.org/en/stable/classes/class_variant.html

Gestione della memoria con gli array 

Gli array sono tipi di riferimento gestiti tramite un sistema di reference counting (conteggio dei riferimenti).  Quando si assegna un array a ad una nuova variabile b di tipo array, non si sta creando una copia, ma un nuovo riferimento allo stesso oggetto in memoria. L'oggetto viene rimosso solo quando il conteggio dei riferimenti scende a zero.

var a = [1, 2, 3]
var b = a # copia il riferimento, l'indirizzo di memoria

Array come riferimenti

Questo significa che qualsiasi modifica fatta agli elementi di uno qualsiasi degli array che condividono lo stesso riferimento in memoria si ripercuoterà anche sugli altri.

ESEMPIO: modifica di elementi tra array con memoria condivisa

OUTPUT dell'esecuzione (estendi l'area di testo se necessario):


Svuotare un array

In GDScript, esistono tre modi per svuotare completamente un array, ognuno con un impatto leggermente diverso sulla memoria e sui riferimenti:

1. Usare il metodo clear()

È il metodo migliore e quello suggerito perché è ottimizzato per svuotare l'array in modo diretto. Tutte tutte le variabili array con riferimenti che puntano all'array svuotato vedranno il cambiamento, cioè l'array si svuota per tutte.

var a = [1, 2, 3]
b = a
a.clear() # Risultato: []
print(a) # Stampa: []
print(b) # Stampa: []

Dal punto di vista tecnico, la memoria precedentemente allocata (capacity) non viene ridotta anche se l'array è vuoto, in modo da non dove eseguire una nuova operazione di allocazione della memoria in caso di aggiungano nuovi elementi dopo lo svuotamento.

2. Usare il metodo resize() impostando a zero il numero di elementi

var a = [1, 2, 3]
b = a
a.resize(0) # Risultato: []
print(a) # Stampa: []
print(b) # Stampa: []

All'atto pratico resize(0) ha lo stesso risultato di clear(), tuttavia è meno efficiente poiché chiama una funzione più generica che deve prima controllare se la nuova dimensione è maggiore o minore di quella attuale. Questo metodo si usa solitamente in algoritmi dove la dimensione dell'array è dinamica e calcolata di volta in volta, mentre la dimensione 0 è solo un caso limite.

3. Riassegnazione dell'array all'array vuoto[]

Assegnare un array vuoto [] ad una variabile array preesistente, ha l'effetto di creare un nuovo array; le altre variabili che puntavano (cioè avevano un riferimento) al vecchio array NON verranno svuotate.

var a = [1, 2, 3]
b = a
a = []
print(a) # Stampa: []
print(b) # Stampa: [1, 2, 3]

Copia di array

Copiare correttamente un array è fondamentale per evitare bug legati alla modifica involontaria di dati, poiché gli array sono tipi di riferimento (quindi sono oggetti). Il metodo principale fornito da GDScript è duplicate(<param_bool_deep>). Questo metodo accetta un argomento opzionale booleano deep (predefinito a false) che determina il tipo di copia.

L'allocazione e la deallocazione della memoria viene gestita mediante conteggio dei riferimenti (reference counting), un metodo che tiene conto del numero di oggetti che "puntano" (cioè si riferiscono) alla stessa locazione di memoria. Il conteggio aumenta di una unità quando un oggetto "punta" una specifica locazione di memoria, decresce di una unità quando lo stesso oggetto non la punta più; quando il conteggio dei riferimenti per una specifica locazione arriva a zero, l'area di memoria viene liberata.

Gli elementi di un array possono essere o tipi "primitivi" (int, float, bool) oppure oggetti (altri array, dizionari, oggetti più complessi come i nodi delle scene del motore di gioco). In figura vediamo una rappresentazione schematica ella memoria per un array definito con l'istruzione
var a = [1, 2, [3, 4]]
L'array è costituito da 3 elementi, i primi due (i numeri 1 e 2) sono tipi primitivi, il terzo è il (sub)array [3, 4] (a sua volta un oggetto, gestito con un riferimento); in figura gli indirizzi di memoria sono rappresentati da frecce:

Array in memoria e reference counting

Copia superficiale (SHALLOW COPY)

Se a è un array, la sua copia superficiale (o shallow copy) si esegue con a.duplicate(false) oppure (omettendo il parametro) semplicemente con a.duplicate(). Questo tipo di copia crea un nuovo array, dove gli elementi che contengono tipi primitivi (int, float, bool) vengono duplicati (cioè sono copiati per valore), mentre per gli elementi che contengono oggetti (altri array, dizionari, altri tipi di oggetto) viene copiato il riferimento (cioè l'indirizzo di memoria dell'oggetto). L'array originale e la sua copia quindi condividono gli stessi riferimenti agli oggetti annidati.

Ad esempio, ecco cosa succede in memoria se si esegue una copia superficiale dell'array della figura precedente: per i primi due elementi (di tipo int) viene copiato il valore, mentre per il terzo, che è un oggetto sub-array, viene copiato il riferimento.
NOTA BENE: questo significa che se si modifica uno dei due elementi del sub-array comune (a[2][0] coincide con b[2][0]a[2][1] coincide con b[2][1]), il cambiamento si ripercuoterà su entrambi gli array. Invece se si modifica direttamente il terzo elemento a[2] oppure b[2], significa che viene modificato il suo valore (che è un indirizzo di memoria) ma non il contenuto dell'oggetto da esso puntato, quindi la modifica riguarda solo l'array che viene modificato (vedi esempio che segue la figura).

Shallow copy (copia superficiale)

ESEMPIO: b è un array ottenuto come copia superficiale di un array a contenente come terzo elemento un oggetto sub-array. La modifica dei singoli elementi del sub-array si ripercuote sia su a che su b. Invece la modifica del valore del terzo elemento di a sovrascrive ill riferimento in memoria originario, per cui solo a risulta modificato.

Copia superficiale (shallow copy)

OUTPUT dell'esecuzione (estendi l'area di testo se necessario):


Copia profonda (DEEP COPY)

La copia profonda (o deep copy) di un array a, si esegue con a.duplicate(true). Questo tipo di copia crea un nuovo array, dove gli elementi che contengono tipi primitivi (int, float, bool) vengono duplicati (cioè sono copiati per valore), mentre tutti gli elementi che contengono oggetti (altri array, dizionari) vengono copiati ricorsivamente (e a qualunque livello di profondità), ossia viene creata una nuova istanza di ogni array o dizionario annidato che si incontra. La copia e l'originale diventano completamente indipendenti, modificare un oggetto dentro la copia non avrà alcun effetto sull'originale (e viceversa). La copia profonda è indispensabile per configurazioni complesse o stati di gioco salvati dove ogni istanza deve poter evolvere separatamente.

Copia profonda (deep copy)

NOTA IMPORTANTE: anche con la copia profonda, gli oggetti che sono istanze di classi o nodi del motore di gioco, non vengono duplicati automaticamente ma si continuerà ad avere riferimenti allo stesso oggetto originale. Per questo tipo di oggetti serve un'operazione di clonazione manuale.

ESEMPIO: b è un array ottenuto come copia profonda di un array a. L'array originale e la sua copia sono indipendenti, le modifiche sull'uno non si ripercuotono sull'altro.

Copia profonda (deep copy) di array

OUTPUT dell'esecuzione (estendi l'area di testo se necessario):



Precedente Indice Successivo