ConwayLife Sprint3

Introduction

Realizzazione del in Java del GAME OF LIFE DI CONWAY, cui analisi del dominio applicativo è stata svolta nello Sprint 1
Nel presente Sprint 3 la GUI deve essere realizzata attraverso una pagina HTML fornita da un Web-server, che costituisce un componente esterno all'applicazione.

Requirements

  1. dotare il gioco Life. di una pagina HTML come dispositivo di I/O
  2. la pagina deve costituire un componente esterno alla applicazione secondo la architettura riportata in IoJavalin esterno alla applicazione
  3. il gestore del gioco sarà l'utente che ha aperto per primo (owner) una pagina HTML collegata al gioco. In altre parole, solo la pagina dell'owner avrà pulsanti di comando START/STOP/CLEAN/EXIT attivi
  4. la pagina HTML deve essere aggiornata in modo automatico man mano il gioco procede
  5. un utente non owner che si collega mentre il gioco è in corso, dovrebbe vedere lo stato attuale della griglia in modo corretto
  6. il deployment del gioco deve avvenire mediante Docker.

Requirement analysis

  • Considerando quanto ottenuto dall'analisi conseguita Sprint 1, si vuole fare in modo che LifeController sia pilotato tramite una pagina HTML. Tale pagina HTML deve essere fruita attraverso un browser e pertanto veicolata attraverso un server.
  • R2: Questo requisito impone di estendere il sistema sw in modo tale che la GUI venga erogata da un server Javalin, come componente esterno all'applicazione, ovvero un server stand-alone, che fornisce la pagina HTML realizzando di fatto un sistema distribuito.



    Il componente che utilizza il server viene indicato col nome IoJavalin
    IOJavalin è parte di un componente, ovvero un servizio, esterno all'applicazione che deve relazionarsi con LifeController. Pertanto applicazione e Server Javalin devono poter comunicare scambiandosi messagg, nello svolgimento dei rispettivi servizi.
  • L'interazione applicazione-servizio avvenire: Utilizzando una connessione Websocket sulla porta 8080, ovvero la stessa aperta dal servizio che eroga la pagina, come specificato dal committente. Tuttavia in linea di principio potrebbe avvenire anche con altri protocolli

Problem analysis

In quanto sistema distribuito, sono presenti due parti che devono interagire in modo opportuno scambiandosi messaggi:
  • L'applicazione (conseguita nello Sprint 1), che chiamiamo lifectrl.
  • Il server che eroga la pagina HTML, che chiamiamo guiserver
Lifectrl si deve manifestare al guiserver aprendo una connessione via webseocket sulla porta 8080. La comunicazione tra browser e il server è bidirezionale e deve rimanere aperta per tutto il tempo necessario.


  • Analisi della comunicazione pagina-server
    La pagina comunica con il guiserver usando stringhe strutturate, in modo che guiserver possa ricavare dalla stringa, oggetti coerenti con l'interfaccia IApplMessage:
    		msg( MSGID, dispatch, SENDER, guiserver, CONTENT, SEQNUM )
    	
    Ciò viene fatto in particolare per rendere il livello applicativo tecchnology-independent.
    I messaggi inviati di tipo dispatch, ovvero fire and forget, in quanto non richiedono una risposta, ma vogliono solo creare un'azione lato server.
    Inizialmente la pagina ha come nome "unknown", che verrà usato come nome del sender alla prima connessione
    		msg( MSGID, dispatch, unknown, guiserver, CONTENT, SEQNUM )
    	
    Il Server risponde alla richiesta di connessione di una nuova pagina, creando ed assegnando alla pagina un nuovo nome (pagename). Inoltre per far fede al Requisito R3, è necessario memorizzare la prima connessione che prende il ruolo di owner.
    		msg( MSGID, dispatch, pagename, guiserver, CONTENT, SEQNUM )
    	
    Dopo la connessione possono essere inviati dei comandi dalla pagina al server, formalizzati come dispatch, sempre in ossequio al requisito R3, solo il coller1 deve potre inviare i comandi. Il content varia a seconda del tipo di comando:
    		msg( do, dispatch, caller1, guiserver, CONTENT, SEQNUM )
    	
    CONTENT potrà essere una semplice String quale start/stop/clear/canvasready
    Quando una pagina invia dispatch al guiserver dovrà inviare a sua volta un messaggio di comando a lifectrl. Per semplificare il codice, i messaggi inviati dalla pagina al server possono avere come destinatorio direttamente il nome lifeCtrl.
    		msg( do, dispatch, caller1, lifeCtrl, start, SEQNUM )
    	


  • Analisi della comunicazione Server-Pagina
    Nella comunicazione tra il Server e la Pagina, i messaggi non sono Strutturati, come prima, per evitare di obbligare la pagina a comunicare con messaggi strutturati in questo modo. Pertano il server invia alla pagina comandi espressi da semplici stringhe
    		ID:name //par dare il valore name al nome della pagina
    		cell(X,Y) //per commutare lo stato di una cella (griglia granulare)
    		[[false,false, ... ]] //per specificare lo stato di una griglia globale
    	
    Messaggi siffatti possno essere interpretati e gestiti direttamente in JavaScript dalla pagina

  • Analisi delle comunicazioni guiserver-applicazione
    Tutta la logica di interazione tra l'applicazione e il guiserver deve essere gestita dalla classe OutInGuiInteraction, che implementa IOutDev nel componente applicativo. Ereditando dallo sprint 2, il sistema applicativo si configura nel seguente modo:
    			LifeInterface life = new Life( 20,20 );
    			IOutDev iodevgui = new OutInGuiInteraction( ); //dispositivo di I/O
    			GameController cc = new LifeControllerAdhoc(life, iodevgui) ;
    			((OutInGuiInteraction) iodevgui).setController(cc); //iniezione del controller
    		
    OutInGuiInteraction implementa la classe IObserver per gestire comunicazioni bidirezionali asincrone. Tutti i messaggi (ri)trasemssi dal guiserver verranno percepiti e gestiti atraverso i metodi update. Il controller lifeCtrl viene pertanto opportunamente invocato dal metodo update, gestendo i comandi in arrivo e inviando a sua volta messaggi di comando al guiserver.
    Questi messaggi sono formalizzabili come dispatch che indicano al guiserver di modificare la visualizzazione della griglia.
    				CommUtils.buildDispatch("lifectrl", "eval", msg, "guiserver" );
    			

Test plans

Al fine di testare la corretta interazione tra i componenti, si possono realizzare caller specifici.
Si ipotizza pertanto l'esecuzione di un caller che implementa IObserver, cui routine è quella di aprire una connessione websocket alla porta 8080, ed inviare il messaggio di comando al guiserver, simulando l'interazione guiserver-applicazione. In particolare si simula un comando di dispatch inviato da lifeCtrl a guiserver per modificare lo stato della cella (5,7), indicando di modificare la visualizzazione della griglia, come farebbe il controller man mano che il gioco procede. Ci aspettiamo pertanto che il guiserver ricevuto il messaggio, proceda a fare il forward a tutti i caller attualemnte attivi come pagine HTML, il messaggio per produrre l'aggiornamento corretto, modificando la cella cell(5,7).
	public class CallerServerInteraction  implements IObserver{  
		private String name;
	   
	   protected void sendCellChange( ) throws Exception {
		   Interaction wsconn = WsConnection.create("localhost:8080", "eval", this);
		   //((Connection) wsconn).setTrace(true);
		   //build the dispatch message, switch cell 5, 7
			IApplMessage cmdmsg = CommUtils.buildDispatch("lifeCtrl", "eval", "cell(5,7)", "guiserver"  );
			CommUtils.outblue(name + " sending " + cmdmsg);
			wsconn.forward(cmdmsg);
			
			CommUtils.delay(2000);
			CommUtils.outblue("CallerServerInteraction | BYE "  );
   		}
	}     

Project

  1. Comunicazione server - pagina:
    Aprire la connessione webseocket per comunicare con guiserver, la pagina viene erogata dal server:
    			//se il server è nella stessa macchina, usa localhost:8080
    			socketToGui = new WebSocket("ws://localhost:8080/eval");
    			socketToGui = new WebSocket("ws://"+window.location.host+"/eval");
    		
    Come si diceva in analisi il server invia alla pagina comandi espressi da semplici stringhe, che verranno interpretate dal codice JavaScript scritto nella pagina.
    Si specifica alla ricezione di un messaggio, 2 casi principali di dispatching:
    • Handshake di identità: La pagina estrae e memorizza il proprio identificatore in pageId, che verrà usato come SENDER in tutti i messaggi successivi verso il server.
      					if (event.data.startsWith("ID:")) {
      						pageId = event.data.split(":")[1];
      					}
      				
    • Ricezione della griglia completa: può essere impostata come griglia globale o come griglia granulare
      La griglia pertanto può essere impostata:
      1. Come griglia globale attraverso l'uso di canvas:
        Il server invia la griglia serializzata come array JSON bidimensionale. Viene deserializzata e passata a draw(), schedulata al prossimo frame del browser tramite requestAnimationFrame.
        						//messaggi del tipo [[false,false, ... ]]
        						if( event.data.includes("[[")) {
        							const grid = JSON.parse(event.data);
        							requestAnimationFrame(() => draw(grid));
        						}
        					
        Draw(grid) fa rendering su canvas:
        						ctx.clearRect(0, 0, canvas.width, canvas.height);  // 1. pulisce tutto
        						const currentRows = grid.length;                    // 2. dimensioni dinamiche
        						const currentCols = grid[0].length;
        
        						for (let r = 0; r < currentRows; r++) {
        							for (let c = 0; c < currentCols; c++) {
        								ctx.fillStyle = grid[r][c] ? "#ff0000" : "#00ff00";  // 3. stato vivo/morto
        								ctx.fillRect(c * CELL_SIZE, r * CELL_SIZE, CELL_SIZE - 1, CELL_SIZE - 1);
        							}
        						}
        					
      2. Come griglia granulare ovvero serie di celle quadrate, ciascuna con ID cell-i-j incapsulata in un tag. Pertanto in questo caso:
        						if (event.data.startsWith("cell(")) {
        							coords = event.data.replace("cell(", "").replace(")", "").split(",");
        							updateCellColor(coords[0], coords[1], coords[2]);
        						}
        
        					
        Ogni cella ha un indirizzo fisico nel documento HTML che è l'ID e lo stato vivomorto è appreso direttamente dall'elemento tramite una classe CSS:
        						//nel metodo function updateCellColor(newX, newY,color)
        						cellxy = document.getElementById(`cell(${newX},${newY})`);
        					

Testing

Deployment

Maintenance



By studentName email: gregorio.bussolari@studio.unibo.it, greg GIT repo: https://github.com/GregorioBussolari/iss2026Unibo.git