Uno dei primi problemi che affronta uno scripter LSL non esperto (ma anche uno esperto!) è quello di creare un file di testo esterno allo script, dove salvare i valori iniziali di una variabile.
Questa operazione è estremamente utile per aumentare la modularità di uno script e per attuare delle modifiche senza dover modificare e ricompilare il codice.
In script aventi una complessità medio-alta, la tecnica di salvare informazioni in un file di testo esterno è estremamente diffusa.
Il presente articolo si prefigge l’obiettivo di semplificare e rendere automatica una operazione di questo tipo. Lo script prevede una fase di impostazione delle variabile ed una fase di validazione delle stesse.
Inizio con il postare il codice completo del template per poi discutere le parti essenziali con un esempio.
// ReadConfiguration // // Il seguente template viene usato per leggere un file di configurazione. // Il file di configurazione può essere formattato nel seguente modo: // <var> Singola variabile // <var>=<var> Variabile <var> che deve essere inizializzata con il valore in <var> // # line Linea di commento // // @version 1.0 // @author MarcoDuff Palen (https://www.marcoduff.com/) // Variabili string configNotecard; integer line; key requestId; // Legge i singoli comandi di configurazione. // // @param var Nome della variabile letta (sempre totalmente maiuscola). // @param val Valore della variabile associata o una stringa vuota in caso di nessuna inizializzazione. readConfig(string var, string val) { } // Valida la configurazione. // // @return Restituisce TRUE se la configurazione è valida, FALSE altrimenti. integer validateConfig() { return TRUE; } // Stato di inizializzazione. default { state_entry() { configNotecard = llGetScriptName()+".config"; line = 0; requestId = llGetNotecardLine(configNotecard,line); } state_exit() { line = -1; requestId = NULL_KEY; } dataserver(key request_id, string data) { if(requestId==request_id) { if(data!=EOF) { data = llStringTrim(data,STRING_TRIM); if(llSubStringIndex(data,"#")!=0&&llStringLength(data)>0) { list dataList = llParseString2List(data,["="],[]); string var = llToUpper(llStringTrim(llList2String(dataList,0),STRING_TRIM)); string val = ""; if(llGetListLength(dataList)==2) val = llStringTrim(llList2String(dataList,1),STRING_TRIM); readConfig(var,val); } line++; requestId = llGetNotecardLine(configNotecard,line); } else { integer validate = validateConfig(); if(validate == TRUE) state ConfigOkState; else state ConfigErrorState; } } } } // Stato se la configurazione è andata a buon fine. state ConfigOkState { changed(integer change) { if(change & CHANGED_INVENTORY) llResetScript(); } } // Stato se la configurazione è errata. state ConfigErrorState { changed(integer change) { if(change & CHANGED_INVENTORY) llResetScript(); } }
Lo script non fa altro che cercare il file di testo nel contents dell’oggetto chiamato come lo script e con estenzione .config, leggere tutte le righe del file ignorando le linee vuote e le linee che iniziano con il carattere #, validare le variabili lette e portarsi nello stato ConfigOkState se tutto è andato a buon fine o nello stato ConfigErrorState in caso di errori.
Questo significa che lo stato di default della nostra applicazione diventa ConfigOkState. Le uniche righe di codice da modificare nel template sono, infatti, l’inserimeto delle variabili utili al problema, il contenuto delle due funzioni readConfig e validateConfig e gli stati ConfigOkState e ConfigErrorState.
Facciamo un esempio che spiega il funzionamento del template.
Supponiamo di voler creare un oggetto che visualizzi un testo sopra di esso. Il testo ed il colore dell’oggetto devono essere specificati in un file di testo esterno allo script. Il testo deve sempre specificato, mentre il colore, se non specificato, deve essere nero.
Il modo più semplice per lavorare con questo template è creare prima il file di testo di configurazione. Suppunendo di voler visualizzare il testo “Prova riuscita!!!” di colore blu, il file di testo risulta essere il seguente:
# Questa è una prova di configurazione dello script ReadConfiguration # Text indica il testo da visualizzare text = Prova riuscita!!! # Color è opzionale, fai i tentativi commentando o impostando la variabile! color = <0.0,0.0,1.0>
Notate il massiccio uso di commenti (righe che iniziano con il carattere #) che verranno ignorati, insieme alle righe vuote, dallo script.
Come possiamo notare dal file di testo, per soddisfare questo problema abbiamo sicuramente bisogno di due variabili: la prima di tipo string per salvare il testo da visualizzare, la seconda di tipo vector per il colore.
Questo significa che al template bisogna aggiungere in testa il seguente codice:
string text; vector color;
l’impostazione di queste due variabili viene fatto all’interno della funzione readConfig. Questa funzione, infatti, riceve in input due variabili chiamate var e val che contengono rispettivamente la parte sinistra e quella destra rispetto al simbolo = di ogni riga letta nel file di configurazione (se non è presente il simbolo = allora viene valorizzata solo la variabile var). La variabile var ha la particolarità di essere sempre in maiuscolo, mentre la variabile val mantiene i caratteri originali.
Per fare un esempio, quando lo script leggerà la riga “text = Prova riuscita!!!” richiamerà la funzione readConfig passando per var il valore “TEXT” mentre per val il valore “Prova riuscita!!!”.
Detto questo, modifichiamo l’interno della funzione readConfig per impostare le nostre variabili text e color in questo modo:
readConfig(string var, string val) { if(var=="TEXT") text = val; else if(var=="COLOR") color = (vector)val; else llOwnerSay("[WARNING] Variabile "+var+" alla linea "+(string)(line+1)+" non prevista."); }
in questo modo quando verranno letti i due valori dal file di testo, questi verranno opportunamente impostati nelle variabili. Notate che in questa particolare implementazione, se viene letto un comando non previsto viene dato un warning all’owner dello script che ha un utile feedback (pensate a quante volte avete perso tempo inutile per un semplice errore di battitura come scrivere “calor” invece di “color” o simili… mettere feedback di questo tipo è sempre molto utile e assolutamente consigliato). Notate anche come ho utilizzato la variabile line che indica la linea corrente di lettura del file (p.s.: ho dovuto incrementarla di 1 visto che la prima riga di un file di testo parte da 0!).
Se vogliamo inserire una validazione o vogliamo mettere dei valori di default per una variabile, dobbiamo modificare il contenuto della funzione validateConfig (se non vogliamo farlo, ci basta lasciare il return TRUE;). Nel nostro esempio dobbiamo controllare che il testo sia stato inserito, visto che lo consideriamo obbligatorio, mentre dobbiamo controllare che se il colore non è stato inserito dobbiamo impostarlo a Nero. La modifica da attuare è la seguente:
integer validateConfig() { if(color == ZERO_VECTOR) color = <0.0,0.0,0.0>; if(llStringLength(text)==0) llOwnerSay("[ERROR] Non hai impostato la variabile TEXT"); else return TRUE; return FALSE; }
se noto che la variabile color non è stata impostato, lo imposta io a nero. Se noto che la variabile text non è stata impostata stampo un messaggio di errore. Notare che la funzione restituisce TRUE esclusivamente nel caso in cui la variabile text viene impostata, in tutti gli altri casi restituisce FALSE. Il risultato della funzione TRUE o FALSE impone il passaggio di stato rispettivamente a ConfigOkState a ConfigErrorState.
A questo punto non ci resta che riempire lo stato ConfigOkState con il codice che avremmo scritto nello stato default di un normale script ma supponendo che le variabili a noi utili siano già state impostate, mentre lo stato ConfigErrorState con eventuali messaggi di errore. Ad esempio:
state ConfigOkState { state_entry() { llSetText(text,color,1.0); } changed(integer change) { if(change & CHANGED_INVENTORY) llResetScript(); } } state ConfigErrorState { state_entry() { llOwnerSay("Errore di configurazione nel file "+configNotecard); } changed(integer change) { if(change & CHANGED_INVENTORY) llResetScript(); } }
Notate che tutti e due gli stati gestiscono l’evento changed in modo da captare eventuali modifiche del file di configurazione e resettarsi in automatico (ovviamente se non si vuole questa estenzione basta cancellare l’evento!).
Per chiarezza posto il codice completo dell’esempio:
// ReadConfiguration // // Il seguente template viene usato per leggere un file di configurazione. // Il file di configurazione può essere formattato nel seguente modo: // <var> Singola variabile // <var>=<val> Variabile <var> che deve essere inizializzata con il valore in <val> // # line Linea di commento // // @version 1.0 // @author MarcoDuff Palen (https://www.marcoduff.com/) // QUI INSERISCO LE VARIABILI DA RIEMPIRE TRAMITE IL FILE DI CONFIGURAZIONE string text; vector color; // Variabili string configNotecard; integer line; key requestId; // Legge i singoli comandi di configurazione. // // @param var Nome della variabile letta (sempre totalmente maiuscola). // @param val Valore della variabile associata o una stringa vuota in caso di nessuna inizializzazione. readConfig(string var, string val) { // QUI INSERISCO I SETTAGGI DELLE VARIABILI llOwnerSay("[INFO] Reading "+var+" = "+val+" on line "+(string)(line+1)); if(var=="TEXT") text = val; else if(var=="COLOR") color = (vector)val; else llOwnerSay("[WARNING] Variabile "+var+" alla linea "+(string)(line+1)+" non prevista."); } // Valida la configurazione. // // @return Restituisce TRUE se la configurazione è valida, FALSE altrimenti. integer validateConfig() { // QUI INSERISCO L'EVENTUALE VALIDAZIONE DELLE VARIABILI // Il colore non è una variabile richiesta, se non viene impostata la imposto a nero if(color == ZERO_VECTOR) color = <0.0,0.0,0.0>; // Il testo è una variabile fondamentale, mando un messaggio di errore if(llStringLength(text)==0) llOwnerSay("Non hai impostato la variabile TEXT"); else return TRUE; return FALSE; } // Funzione per la visualizzazione dell'output. // // @param message Messaggio da visualizzare. // @param color Colore di visualizzazione. showOutput(string message, vector color) { llSetText(message,color,1.0); } // Stato di inizializzazione. default { state_entry() { configNotecard = llGetScriptName()+".config"; showOutput("Reading "+configNotecard,<0.0,1.0,0.0>); line = 0; requestId = llGetNotecardLine(configNotecard,line); } state_exit() { line = -1; requestId = NULL_KEY; showOutput("",ZERO_VECTOR); } dataserver(key request_id, string data) { if(requestId==request_id) { if(data!=EOF) { data = llStringTrim(data,STRING_TRIM); if(llSubStringIndex(data,"#")!=0&&llStringLength(data)>0) { list dataList = llParseString2List(data,["="],[]); string var = llToUpper(llStringTrim(llList2String(dataList,0),STRING_TRIM)); string val = ""; if(llGetListLength(dataList)==2) val = llStringTrim(llList2String(dataList,1),STRING_TRIM); readConfig(var,val); } line++; requestId = llGetNotecardLine(configNotecard,line); } else { integer validate = validateConfig(); if(validate == TRUE) state ConfigOkState; else state ConfigErrorState; } } } } // Stato se la configurazione è andata a buon fine. state ConfigOkState { state_entry() { // SE TUTTO E' ANDATO BENE VISUALIZZO IL TESTO SULL'OGGETTO CON I PARAMETRI DEL FILE DI CONFIGURAZIONE llSetText(text,color,1.0); } changed(integer change) { if(change & CHANGED_INVENTORY) llResetScript(); } } // Stato se la configurazione è errata. state ConfigErrorState { state_entry() { // SE TUTTO E' ANDATO MALE VISUALIZZO UN ERRORE llOwnerSay("Errore di configurazione nel file "+configNotecard); } changed(integer change) { if(change & CHANGED_INVENTORY) llResetScript(); } }
Buono Scripting LSL a tutti!