PDA

View Full Version : [CICLO 15] Storia 2


Jocchan
01-05-2006, 01:31
Storia 2: Bugfix e refactoring. Introduzione di un sistema di playback dei log.



Punti cardine da tenere a mente durante i lavori:

* Mai fare a gara a chi finisce il task per primo, meglio procedere con calma, altrimenti perderemo molto più tempo in seguito
* Evitiamo di complicarci la vita, esiste di certo una soluzione più semplice di quella che abbiamo pensato di implementare
* MAI aggiungere elementi non richiesti esplicitamente dai task: se mai serviranno, se ne parlerà nelle prossime storie
* Comunichiamo il più possibile, se qualcosa non è chiaro discutiamone tutti i dettagli fino ad eliminare ogni dubbio, anche il più insignificante
* Postare sempre la test list PRIMA di mettere mano al codice




Refactoring:
- Environment (cdimauro)
- Grid e trasformazione di BigGem in Droppable
- Omogenizazzione degli update degli State
- Action: definire meglio le responsabilità rispetto a Grid
- SoundBank
- TextureBank
-va finito il refactoring di Droppable ed AbstractDroppable creando le ultime interfacce ed eliminando alcune duplicazioni.
-sono presenti metodi non testati...
-Tutti i TODO del codice dovrebbero sparire in fretta perchè poi si finisce a portarseli dietro a lungo (addirittura c'è un test commentato con scritta una cosa tipo "qualcuno lo faccia passare" )

Bonfo
02-05-2006, 00:35
Io proporrei un bel refactoring e conseguente controllo delle Action. :D

Secondo me, molti metodi di Grid dovrebbero migrare in queste classi: si semplificherebbe grid e le Action diventerebbero molto più chiare, leggibili e probabilmente più semplici. ;)

Primo esempio: in UpdateFallsAction sembra che grid.updateAlsoDroppedGem() venga richiamata 2 volte sulle gemme di una BigGem :mbe: :mbe:

Insomma...grid deve proprio dimagrire :Prrr:

fek
02-05-2006, 10:00
Procedi pure al refactoring.

Bonfo
02-05-2006, 12:47
Io l'ho proposto :flower:
..ma mica ho il tempo per farlo :mc: :muro: :muro:

Ieri ho dato un occhiata....è un matassa mica facile da sbrogliare :cry:

VICIUS
02-05-2006, 12:49
Visto che le parti di codice da toccare in questa storia sono completamente diverse da quelle della prima storia potete cominciare anche subito a fare refactoring e a modificare.
Non avendo una idea precisa di quello che c'è da fare ho preferito non scrivere task però fate come bonfo. Prima di partire a fare qualcosa provate a scrivere due righe e a proporle sul forum.

ciao ;)

thebol
02-05-2006, 12:57
mi offro per il playbak del log
direi 5 giorni(sempre che nn lo dividiate in vari task )

VICIUS
02-05-2006, 13:13
mi offro per il playbak del log
direi 5 giorni(sempre che nn lo dividiate in vari task )
Se ci scrivi qualche piccolo task giusto per capire come intendi procedere ti possiamo dare una mano tutti quanti.

ciao ;)

cionci
02-05-2006, 13:18
Per il playback del log bisogna sincronizzare strettamente i vari timer del gioco...

Nel senso che bisogna fare 1 reactToInput ogni K update...inoltre anche tutti gli altri timeStamp ottenuti nel gioco devono essere sincronizzati con quelli del timer di update... Questo comporta un refactoring di timer che secondo me potrebbe essere di questo tipo:


timer.setTimeStamp(); <---da richiamare una sola volta per ogni ciclo di update
timer.getTimeStamp(); <---che dovrà essere richiamata da TUTTI i metodi che ora hanno bisogno di un timestamp

In questo modo la sinconizzazione dovrebbe essere perfetta...

thebol
02-05-2006, 13:27
Se ci scrivi qualche piccolo task giusto per capire come intendi procedere ti possiamo dare una mano tutti quanti.

ciao ;)

devo pensarci, cmq un idea preliminare potrebbe essere:

precaricare il file di log, con gli eventi key da generare
lavorare a livello di input o inputReactor(devo vedere bene) per assegnare gli eventi al tick giusto

il resto dovrebbe funzionare come ora.

Forse non importa neanche disabilitare i tasti del secondPlayer.

Le uniche perplessita possono essere sul precaricamento del log, potrei leggerlo mano a mano e poi usarlo, per evitare problemi di log molto lunghi, ma preferirei il caricamento manuale. Cmq si puo fare una classe che getta l'evento da assegnare, e poi si cambia l'implementazione.

thebol
02-05-2006, 13:33
altra cosa da valutare è l'inserimento della pair, ma mi sembra che nel log venga scritto il seed, percui bisogna settare nel caso della demo il seed a mano.

Bonfo
02-05-2006, 13:40
devo pensarci, cmq un idea preliminare potrebbe essere:

precaricare il file di log, con gli eventi key da generare
lavorare a livello di input o inputReactor(devo vedere bene) per assegnare gli eventi al tick giusto

il resto dovrebbe funzionare come ora.

Forse non importa neanche disabilitare i tasti del secondPlayer.

Le uniche perplessita possono essere sul precaricamento del log, potrei leggerlo mano a mano e poi usarlo, per evitare problemi di log molto lunghi, ma preferirei il caricamento manuale. Cmq si puo fare una classe che getta l'evento da assegnare, e poi si cambia l'implementazione.

Sarebbe bello che il log fosse incapsulato da una classe "Player" che, costruito con un timer, al momento giusto genera la pressione del tasto :D
Praticamente un generatore di evanti basato su file ;)

Bonfo
02-05-2006, 13:47
Problema che richiede consiglio.
Vi faccio notare una piccola differenza:

bigGem.canMoveDown(grid)

grid.gemCanMoveDown(gem)

I due metodi sono esattamente opposti come impostazione. Qui salta all'occhio il problema che la BigGem non è un droppable. Bisogna riuscire in questa impresa...se qualcuno sa già che è impossibile parli adesso o taccia per sempre :D :D

Ed ora guardando bene è uscito pure questo:

public void draw(EngineInterface engine)
{
background.draw(engine);

forEachGem(new DrawGemsAction(this, engine));

for(BigGem bigGem : bigGems)
{
bigGem.draw(engine);
}
}

e questo

gem.getSprite().draw(engine);

Perchè solo BigGem implementa Drawable e i Droppable no??
Anche questo è da fare :D

Ufo13
02-05-2006, 13:59
Bonfo, BigGem può sicuramente essere resa più omogenea... Io proprio finirei di gestirla dome aggregato di Gem ma bisogna prima terminare il refactoring di Droppable che avevamo iniziato e che mi pare sia in sospeso :P

Bonfo
02-05-2006, 14:11
Bonfo, BigGem può sicuramente essere resa più omogenea... Io proprio finirei di gestirla dome aggregato di Gem ma bisogna prima terminare il refactoring di Droppable che avevamo iniziato e che mi pare sia in sospeso :P

Sospeso :eek: :eek:
E che manca...??? :D

Io stavo guardando ora se è lecito far si che i Droppable implemntino anche Drawable...infatti mentre BigGem gestisce internamente la Sprite, le gemme non fanno nulla, facciamo tutto noi a mano.

Questa differenza non mi piace molto. :nonsifa:

thebol
02-05-2006, 14:21
Sarebbe bello che il log fosse incapsulato da una classe "Player" che, costruito con un timer, al momento giusto genera la pressione del tasto :D
Praticamente un generatore di evanti basato su file ;)

la mia idea è circa questa. Bisogna solo capire per bene a quale livello conviene inserirsi.

fek
02-05-2006, 14:34
Il playback non deve essere legato strettamente al timer del gioco e agli update dell'engine.

Quello che mi interessa (e lo spirito del task) e' arrivare a poter scrivere un test di questo tipo:


LogPlayback playback = new LogPlayback("log.txt");

int gameTurn = 100;

playback.Run(gameTurn);

Grid playerOne = playback.getPlayerOneGrid();
Grid playerTwo = playback.getPlayerOneGrid();


E da qui in poi mi interessa poter fare operazioni sulle due griglie e poter fare delle assert. Questo ci permettera', dato un log di un bug, di ricreare il bug in un test e poter testare il relativo fix.

Ovviamente l'esecuzione del singolo test non deve impiegare dei minuti, e per questo non voglio che il playback sia legato ad un timer "reale", ma solo al concetto di game turn. Se il concetto non esiste, va aggiunto.

Bonfo
02-05-2006, 14:35
Altra cosa che non mi piace. Ovvero che è meglio rendere molto più chiara:
in grid c'è:
move(float,float);
moveTo(float,float);

Se mi dite che la differenza è chiara sto zitto...altrimenti :Prrr:

VICIUS
02-05-2006, 14:38
Problema che richiede consiglio.
Vi faccio notare una piccola differenza:

bigGem.canMoveDown(grid)

grid.gemCanMoveDown(gem)

I due metodi sono esattamente opposti come impostazione. Qui salta all'occhio il problema che la BigGem non è un droppable. Bisogna riuscire in questa impresa...se qualcuno sa già che è impossibile parli adesso o taccia per sempre :D :D

Ed ora guardando bene è uscito pure questo:

public void draw(EngineInterface engine)
{
background.draw(engine);

forEachGem(new DrawGemsAction(this, engine));

for(BigGem bigGem : bigGems)
{
bigGem.draw(engine);
}
}

e questo

gem.getSprite().draw(engine);

Perchè solo BigGem implementa Drawable e i Droppable no??
Anche questo è da fare :D
Non è male come idea. Se riusciamo ad implementare Droppables in bigGem oltre a usare uno stile comune per entrambi possiamo unire i due foreach. Magari rinominado GemAction forEachGem in DroppableAction forEachDroppable.

ciao ;)

VICIUS
02-05-2006, 14:49
Grid è pieno di metodi che riguardano solo le BigGem e questo mi puzza.
Ci sono funzioni come "public boolean gemCanMoveDown(Droppable gem)" che hanno nel nome Gem e poi richiedono un Droppable. Anche questa confusione non mi piace :)

thebol
02-05-2006, 15:04
Il playback non deve essere legato strettamente al timer del gioco e agli update dell'engine.

Quello che mi interessa (e lo spirito del task) e' arrivare a poter scrivere un test di questo tipo:


LogPlayback playback = new LogPlayback("log.txt");

int gameTurn = 100;

playback.Run(gameTurn);

Grid playerOne = playback.getPlayerOneGrid();
Grid playerTwo = playback.getPlayerOneGrid();


E da qui in poi mi interessa poter fare operazioni sulle due griglie e poter fare delle assert. Questo ci permettera', dato un log di un bug, di ricreare il bug in un test e poter testare il relativo fix.

Ovviamente l'esecuzione del singolo test non deve impiegare dei minuti, e per questo non voglio che il playback sia legato ad un timer "reale", ma solo al concetto di game turn. Se il concetto non esiste, va aggiunto.

il gameTurn pensavo ci fosse, ma non cè...

in teoria è facile ricavarselo dal log(cè il timeStamp per ogni azione e sapendo il rate degli update si ricava in fretta), ma in effetti sarebbe piu carino introdurlo(anche nella scrittura dei log), in modo da avere una cosa piu uniforme. E cmq perdere il timeStamp dal log in favore del gameTurn non penso sia un problema

fek
02-05-2006, 15:07
Non è male come idea. Se riusciamo ad implementare Droppables in bigGem oltre a usare uno stile comune per entrambi possiamo unire i due foreach. Magari rinominado GemAction forEachGem in DroppableAction forEachDroppable.

ciao ;)

A suo tempo questa era l'idea: BigGem come Droppable. Se qualcuno ci vuole provare e' ben venuto. Anche i metodi pubblici di Grid che riguardano BigGem sono molto sospetti come hai detto. Grid ha ancora bisogno di molto lavoro.

il gameTurn pensavo ci fosse, ma non cè...

in teoria è facile ricavarselo dal log(cè il timeStamp per ogni azione e sapendo il rate degli update si ricava in fretta), ma in effetti sarebbe piu carino introdurlo(anche nella scrittura dei log), in modo da avere una cosa piu uniforme. E cmq perdere il timeStamp dal log in favore del gameTurn non penso sia un problema

Perfetto allora. Preferisco scrivere il gameturn invece del timestamp nel log.

Prima di iniziare ci puoi scrivere un'elenco di mini task che hai in mente per completare il lavoro? Se dovessero essere troppi, possiamo spezzare il task in due o piu' task.

thebol
02-05-2006, 15:24
Perfetto allora. Preferisco scrivere il gameturn invece del timestamp nel log.

Prima di iniziare ci puoi scrivere un'elenco di mini task che hai in mente per completare il lavoro? Se dovessero essere troppi, possiamo spezzare il task in due o piu' task.

-Introduzione gameTurn
-Caricamento file di log in una struttura dati su cui iterare(ci possono pero essere piu azioni per turno)
per incominciare.

Poi ho un dubbio.

Nella mia idea a ogni gameTurn, playField va a riempire l'oggetto input(che sara slegato dall'oggetto keyboard) con i valori prelevati dal log.

La classe input, contiene una lista dei tasti che hanno subito una modifica dello stato nell'ultimo gameTurn. Tasti logici(UP, DOWN, BUTTON1, etc), non fisici.

Ma noi nel log scriviamo il nome della classe che implementa l'azione.

Si puo utilizzare il mapping fra tasto logico <-> keyEventHandler per fare il percorso contrario(anche se con l' hashmap presente in inputReactor non è proprio il massimo) .

Oppure scrivere nel log non il nome della classe(che potrebbe anche cambiare), ma il bottone logico.

Io propendo per quest'ultima ipotesi :)

VICIUS
02-05-2006, 15:31
Attenti. Quello che viene scritto nel log È il gameturn non il timestamp.

ciao ;)

cionci
02-05-2006, 15:31
Per il playback del log bisogna sincronizzare strettamente i vari timer del gioco...

Nel senso che bisogna fare 1 reactToInput ogni K update...inoltre anche tutti gli altri timeStamp ottenuti nel gioco devono essere sincronizzati con quelli del timer di update... Questo comporta un refactoring di timer che secondo me potrebbe essere di questo tipo:


timer.setTimeStamp(); <---da richiamare una sola volta per ogni ciclo di update
timer.getTimeStamp(); <---che dovrà essere richiamata da TUTTI i metodi che ora hanno bisogno di un timestamp

In questo modo la sinconizzazione dovrebbe essere perfetta...
Ripropongo questa cosa...senza la quale qualsiasi riproduzione del log sarebbe impossibile o comunque errata...

VICIUS
02-05-2006, 15:35
[...]
E da qui in poi mi interessa poter fare operazioni sulle due griglie e poter fare delle assert. Questo ci permettera', dato un log di un bug, di ricreare il bug in un test e poter testare il relativo fix.

Ovviamente l'esecuzione del singolo test non deve impiegare dei minuti, e per questo non voglio che il playback sia legato ad un timer "reale", ma solo al concetto di game turn. Se il concetto non esiste, va aggiunto.
Se usiamo un MockTimer possiamo mandarlo veloce quanto ci pare. L'unica limitazione sarà la velocità della macchina su cui esegue.

ciao ;)

cionci
02-05-2006, 15:40
Attenti. Quello che viene scritto nel log È il gameturn non il timestamp.
Confermo...
Allora prima di tutto proporrei un refactoring del modo in cui il gioco scrive il log...

- Dividiamo la classe LogFile in LogWriter e LogReader...

- Il log di un turno di gioco deve essere immesso in un struttura e non in una stringa come abbiamo fatto adesso (era YAGNI)...

- Serializziamo la struttura e scriviamo su file


Per riprodurlo:

- deserializziamo una struttura alla volta (volendo anche tutte non mi sembra un problema)


Magari non sarà più leggibile (non ho mai provato sinceramente) ma tanto ora non ci serve leggerlo...in tal caso possiamo anche eliminare l'informazione sulle gems pair visto che è superflua...

thebol
02-05-2006, 15:47
Attenti. Quello che viene scritto nel log È il gameturn non il timestamp.

ciao ;)

inputServed :|

pensavo fosse un altra cosa:|

ok, il gameTurn ce l'abbiamo gia nel log

thebol
02-05-2006, 15:59
Per il playback del log bisogna sincronizzare strettamente i vari timer del gioco...

Nel senso che bisogna fare 1 reactToInput ogni K update...inoltre anche tutti gli altri timeStamp ottenuti nel gioco devono essere sincronizzati con quelli del timer di update... Questo comporta un refactoring di timer che secondo me potrebbe essere di questo tipo:


timer.setTimeStamp(); <---da richiamare una sola volta per ogni ciclo di update
timer.getTimeStamp(); <---che dovrà essere richiamata da TUTTI i metodi che ora hanno bisogno di un timestamp

In questo modo la sinconizzazione dovrebbe essere perfetta...

in questa maniera non potremmo pure evitare di propagare timer, ma solo il timeStamp?(e poi il conceto del getTimeStamp..)

cionci
02-05-2006, 16:00
in questa maniera non potremmo pure evitare di propagare timer, ma solo il timeStamp?(e poi il conceto del getTimeStamp..)
Sicuramente... Va benissimo... Lo dicevo per facilitare il refactoring...

thebol
02-05-2006, 16:04
Sicuramente... Va benissimo... Lo dicevo per facilitare il refactoring...

vero, non ci avevo pensato

fek
02-05-2006, 16:04
Ripropongo questa cosa...senza la quale qualsiasi riproduzione del log sarebbe impossibile o comunque errata...

Io preferirei che il log sia ad un livello piu' alto del timer e si basi sui game turn. Ogni game turn dovrebbe essere processato atomicamente e gli eventi al suo interno dovrebbero essere in ordine di esecuzione.

Esempio:

Gamturn 0

player one up
player two left
diamond insert

Il player al game turn 0 legge gli eventi e li riproduce: dovrebbe essere totalmente deterministico e se non lo e' lo rendiamo deterministico. Non c'e' il concetto di timer perche' l'ordinamento degli eventi e' stato gia' fatto a monte durante la registrazione.

In questo modo ci semplifichiamo notevolmente la vita ed abbiamo gia' un'ottima base di partenza per il multiplayer. Non voglio iniziare ad impazzire con la sincronizzazione del timer e dei timestamp.

Togliete timer di mezzo dai log, e mantenete gli eventi ad alto livello. Keep it simple.

fek
02-05-2006, 16:07
Confermo...
Allora prima di tutto proporrei un refactoring del modo in cui il gioco scrive il log...

- Dividiamo la classe LogFile in LogWriter e LogReader...

- Il log di un turno di gioco deve essere immesso in un struttura e non in una stringa come abbiamo fatto adesso (era YAGNI)...

- Serializziamo la struttura e scriviamo su file


Per riprodurlo:

- deserializziamo una struttura alla volta (volendo anche tutte non mi sembra un problema)


Magari non sarà più leggibile (non ho mai provato sinceramente) ma tanto ora non ci serve leggerlo...in tal caso possiamo anche eliminare l'informazione sulle gems pair visto che è superflua...

Possiamo sempre serializzare e deserializzare la struttura in stringhe leggibili, non e' un grosso problema. Secondo me si puo' procedere con questo refactoring prima. E dividere il LogReader e LogWriter.

cionci
02-05-2006, 16:12
Io preferirei che il log sia ad un livello piu' alto del timer e si basi sui game turn. Ogni game turn dovrebbe essere processato atomicamente e gli eventi al suo interno dovrebbero essere in ordine di esecuzione.

Esempio:

Gamturn 0

player one up
player two left
diamond insert

Il player al game turn 0 legge gli eventi e li riproduce: dovrebbe essere totalmente deterministico e se non lo e' lo rendiamo deterministico. Non c'e' il concetto di timer perche' l'ordinamento degli eventi e' stato gia' fatto a monte durante la registrazione.

In questo modo ci semplifichiamo notevolmente la vita ed abbiamo gia' un'ottima base di partenza per il multiplayer. Non voglio iniziare ad impazzire con la sincronizzazione del timer e dei timestamp.

Togliete timer di mezzo dai log, e mantenete gli eventi ad alto livello. Keep it simple.
In ogni caso il refactoring del timer che avevo proposto sarebbe comunque necessario... Questo perchè bisogna legare i vari tempi di attesa al GameTurn... Cosa che assolutamente adesso non avviene...

Non c'è da impazzire... Se vuoi me ne posso occupare io di questo refactoring... Anzi, ho in mente un metodo assolutamente indolore...forse...

Anzi...faccio una modifica che dovrebbe sincronizzare tutti i timer con una sola riga di codice...

La posso mettere temporaneamente nel repository ? Poi la valutiamo...

fek
02-05-2006, 16:38
Valutiamolo prima, perche' non mi e' assolutamente chiara la necessita' del refactoring (colpa mia che non sono molto familiare con il codice del logging).

Al livello di astrazione in cui vedo il logging io, il concetto di timer non dovrebbe giocare alcun ruolo, quindi non mi e' chiaro il refactoring che proponi.

thebol
02-05-2006, 16:45
Valutiamolo prima, perche' non mi e' assolutamente chiara la necessita' del refactoring (colpa mia che non sono molto familiare con il codice del logging).

Al livello di astrazione in cui vedo il logging io, il concetto di timer non dovrebbe giocare alcun ruolo, quindi non mi e' chiaro il refactoring che proponi.

forse non è una necessita strettamente legata alla gestione dei log, ma in effetti sarebbe piu corretto prendre il timeStamp una volta per loop di gameLoop. O almeno che quello usato nel check di reacrToInput di playField venga propagato a gridController.reactToInput.

In effetti ho notato a volte che il comportamento dell'input non è proprio deterministico...

tipo che premi a sx continuamente e a volte la gemma nn si incastra all'estrema sx, poi a un certo punto ce la fa..

cionci
02-05-2006, 16:47
I timer all'interno del gioco funzionano così:

if(lastTimeStamp + timeBetweenEvents >= timer.getTime())
{
lastTimeStamp += timeBetweenEvents;
doSomething();
}

timeBetweenEvents = 100;
lastTimeStamp = 300;

Se timer.getTime() == 399 l'evento avviene nel prossimo gameTurn, se timer.getTime() == 400 l'evento avviene in questo gameTurn...

La differenza fra i due valori può essere dovuta anche solamente a fattori di capacità di calcolo di due calcolatori diversi (o semplicemente all'assegnamento del timeslice dello scheduler)... Di conseguenza la stessa sequenza di eventi (movimenti della gems pair) produrrebbe comunque game play diversi su run diverse del gioco...

Fanno parte di questi eventi ad esempio i tempi di attesa fra i crush, o l'attesa per la nuova gemsPair...

fek
02-05-2006, 16:51
Esatto. Nell'esempio che hai fatto:


if(lastTimeStamp + timeBetweenEvents > timer.getTime())
{
lastTimeStamp += timeBetweenEvents;
doSomething();
}


doSomething() deve generare l'evento che viene messo in ordine con gl'altri eventi. Come scrivevo, la generazione e l'ordinamento degli eventi deve essere fatto a monte, e poi scritto il risultato nel log assieme al suo game turn.

Quindi un evento crush viene scritto come tale e il tempo di attesa processato a monte. C'e' solo da fare un attimo di refactoring per introdurre questo concetto di generazione degli eventi.

cionci
02-05-2006, 16:53
Quindi bisogna loggare proprio tutto ??? IMHO diventa complicato... Bisogna praticamente propagare il logger ovunque...

Se riusciamo a rendere questa cosa predeterminata a monte non è meglio ?

Facciamo meno modifiche ed introduciamo meno codice... Sicuramente è più semplice nell'altro modo...

fek
02-05-2006, 16:56
Quindi bisogna loggare proprio tutto ??? IMHO diventa complicato...

Se riusciamo a rendere questa cosa predeterminata a monte non è meglio ?

Facciamo meno modifiche ed introduciamo meno codice... Sicuramente è più semplice nell'altro modo...

Ma no, voglio loggare a valle, solo il risultato degli eventi, non voglio loggare la pressione dei tasti. Proprio perche' voglio loggare meno e concetti piu' astratti. Cosi' non dobbiamo portarci dietro concetti come il timer ed e' piu' facile da mantenere.

In pratica sto chiedendo un esercizio di "decoupling". Se vuoi prima fare nell'altro modo perche' arrivi prima a qualcosa che funziona, mi sta anche bene, ma poi devi fare refactoring verso questa soluzione, perche' e' cio' che chiedo nei requisiti del task.

cionci
02-05-2006, 16:58
Ecco l'idea che avevo avuto per propagare lo stesso timeStamp a chiunque utilizzi il timer:

public class Timer implements TimerInterface
{
private long time;
private long timeStamp = 0;


public Timer()
{
time = System.nanoTime();
}


public long getTime()
{
return timeStamp / 1000000;
}


public void advance(long timeOffset)
{
try
{
Thread.sleep(timeOffset);
}
catch(InterruptedException e)
{
;
}

timeStamp = System.nanoTime() - time;
}

}

Advance viene chiamato ogni volta che viene terminato il game loop... In questo modo chiunque utilizza Timer vede lo stesso timeStamp...

cionci
02-05-2006, 16:59
Ma no, voglio loggare a valle, solo il risultato degli eventi, non voglio loggare la pressione dei tasti. Proprio perche' voglio loggare meno e concetti piu' astratti. Cosi' non dobbiamo portarci dietro concetti come il timer ed e' piu' facile da mantenere.
Nessuno ha detto di loggare i tasti...ora vengono loggate le azioni... Ed il log che si fa adesso basta ed avanza per riprodurre interamente gli eventi... Ovviamente con l'aiuto del MockTimer...

fek
02-05-2006, 17:00
cionci, e' un hack grosso come una casa. Un Timer non e' un qualcosa che tiene traccia dei timestamp. Un Timer conta il tempo e basta, gli stai dando un'altra responsabilita' e questo porta a grossi disastri.

Ora, se e' una soluzione temporanea mi sta bene, se e' la tua soluzione finale no. Va rifattorizzata subito.

fek
02-05-2006, 17:01
Nessuno ha detto di loggare i tasti...ora vengono loggate le azioni... Ed il log che si fa adesso basta ed avanza per riprodurre interamente gli eventi... Ovviamente con l'aiuto del MockTimer...

Ok, i requisiti del task ti chiedono di farlo senza aiuto del MockTimer. Il task e' concluso quando posso scrivere quel test che ho richiesto senza un MockTimer di mezzo.

cionci
02-05-2006, 17:09
Ora, se e' una soluzione temporanea mi sta bene, se e' la tua soluzione finale no. Va rifattorizzata subito.
Chiamiamolo TimeStamp allora...dopo tutto è giusto che si chiami così...e ripeto... Questo è IMHO un refactoring da fare indipendentemente se useremo o meno questa possibilità per riprodurre il log...

thebol
02-05-2006, 17:11
doSomething() deve generare l'evento che viene messo in ordine con gl'altri eventi. Come scrivevo, la generazione e l'ordinamento degli eventi deve essere fatto a monte, e poi scritto il risultato nel log assieme al suo game turn.

Quindi un evento crush viene scritto come tale e il tempo di attesa processato a monte. C'e' solo da fare un attimo di refactoring per introdurre questo concetto di generazione degli eventi.

In questa maniera(lavorando su eventi) si sposterebbe il livello di intervento su inputReactor, sostituendolo con una classe che chiami gli eventHandler opportuni.

cionci
02-05-2006, 17:14
Ok, i requisiti del task ti chiedono di farlo senza aiuto del MockTimer. Il task e' concluso quando posso scrivere quel test che ho richiesto senza un MockTimer di mezzo.
Ok, ma valutiamo anche il traffico... Aumentando gli eventi aumenta il traffico e diminuisce la possibilità di utilizzare il gioco su un modem 56K...

fek
02-05-2006, 17:15
Chiamiamolo TimeStamp allora...dopo tutto è giusto che si chiami così...e ripeto... Questo è IMHO un refactoring da fare indipendentemente se useremo o meno questa possibilità per riprodurre il log...

Sono pedante oggi, scusami :D

Vuoi creare una classe TimeStamp per che cosa di preciso? Non mi e' chiaro. (Sono pedante e lento oggi).

Una piccola nota su quello che e' accaduto in questa discussione. Lo scambio di opinioni ha portato ad una migliore definizione da parte mia dei requisiti del task, che prima della discussione erano molti laschi.

Questo dimostra che discutere un task e' una cosa sempre molto buona.

Ora, quello che ti chiedo sembra una violazione di YAGNI, perche' tu mi proponi una soluzione che richiede meno codice e io te ne chiedo una con piu' codice. Sembra che io sia in violazione, ma non lo sono perche' sono il Coach :D

A parte gli scherzi, dopo la discussione sono cambiati i requisiti quindi la tua soluzione non e' piu' la soluzione giusta al problema di soddisfare i (nuovi) requisiti del task "Riprodurre il log senza ausilio di MockTimer", ma e' soluzione dei requisiti vecchi.

YAGNI si applica quando tutti i requisiti sono soddisfatti e impone che lo siano con meno codice possibile.

cionci
02-05-2006, 17:16
Ripeto ragazzi...provate il gioco con quell'hack o come lo volete chiamare... Abbassate InputRate a 50 e vedrete aumentare la precisione del gioco... Ad esempio negli spostamenti laterali...

IMHO è da introdurre poi fate come volete...indipendentemente dal fatto di usare il mocktimer nel log...

fek
02-05-2006, 17:18
Ok, ma valutiamo anche il traffico... Aumentando gli eventi aumenta il traffico e diminuisce la possibilità di utilizzare il gioco su un modem 56K...

Stai discutendo il design del codice in base a valutazioni di carattere prestazionale che non appaiono nei requisiti? ;)

I requisiti del networking (per ora), non parlano di model 56k quindi questo E' YAGNI. Se i requisiti lo richiederanno e se la banda che consumiamo sara' eccessiva una volta fatto il profiling, allora valuteremo le soluzioni a quel problema. Ora non le valutiamo e ci atteniamo strettamente ai requisiti.

fek
02-05-2006, 17:19
Ripeto ragazzi...provate il gioco con quell'hack o come lo volete chiamare... Abbassate InputRate a 50 e vedrete aumentare la precisione del gioco... Ad esempio negli spostamenti laterali...

IMHO è da introdurre poi fate come volete...indipendentemente dal fatto di usare il mocktimer nel log...

Joc, puoi valutare una build con questo fix per favore? La decisione qui diventa tua.

cionci
02-05-2006, 17:21
Aspettate che vi metto su un test (fatto a mano, non JUnit) che vi dimostra la maggiore precisione...

cionci
02-05-2006, 17:24
Ok...allora avviate il gioco premete da subito i tasti sinistra e basso (se rischiate di riempire la colonna centrale, prima dell'inserimento di una nuova gemspair lasciate il tasto sinistra e premete destra contemporaneamente) di entrambi i giocatori...vedrete che i campi gioco dopo un po' si evolveranno in maniera diversa...

thebol
02-05-2006, 17:26
Ok, i requisiti del task ti chiedono di farlo senza aiuto del MockTimer. Il task e' concluso quando posso scrivere quel test che ho richiesto senza un MockTimer di mezzo.

non penso sia possibile, a meno che tu nn voglia aspettare il tempo della partita prima di vedere finire il test.

thebol
02-05-2006, 17:27
Ok...allora avviate il gioco premete da subito i tasti sinistra e basso (se rischiate di riempire la colonna centrale, prima dell'inserimento di una nuova gemspair lasciate il tasto sinistra e premete destra contemporaneamente) di entrambi i giocatori...vedrete che i campi gioco dopo un po' si evolveranno in maniera diversa...

era il non determinismo di cui parlavo qualche post fa :)

fek
02-05-2006, 17:29
non penso sia possibile, a meno che tu nn voglia aspettare il tempo della partita prima di vedere finire il test.

Nei requisiti c'e' di non dover aspettare il tempo della partita ;)

Tutto e' possibile. E spesso e' possibile anche in maniera piu' semplice.

fek
02-05-2006, 17:31
Ok...allora avviate il gioco premete da subito i tasti sinistra e basso (se rischiate di riempire la colonna centrale, prima dell'inserimento di una nuova gemspair lasciate il tasto sinistra e premete destra contemporaneamente) di entrambi i giocatori...vedrete che i campi gioco dopo un po' si evolveranno in maniera diversa...

Appena Joc lo conferma, questo diventa un bug e va risolto. Approposito, dobbiamo tenere aggiornato l'elenco dei bug e Joc deve produrre ogni tanto una storia per correggerli.

thebol
02-05-2006, 17:42
Nei requisiti c'e' di non dover aspettare il tempo della partita ;)

Tutto e' possibile. E spesso e' possibile anche in maniera piu' semplice.

Quando entro nello stato waitStateBeforeNewGemPair, come faccio a dirgli che sono passati n ms e che puo passare allo stato successivo?

Si potrebbero saltare tutte queste attese creando un mockTimer settato a 9999999999999999, ma sempre mockTimer è.

cionci
02-05-2006, 17:48
Quando entro nello stato waitStateBeforeNewGemPair, come faccio a dirgli che sono passati n ms e che puo passare allo stato successivo?

Semplicemente non ci entri... Riproduci gli eventi...quindi sai che al turno N verrà generata una nuova gemspair...

Comunque a questo punto inputServed deve cambiare... Ora vengono contati i turni di input... Per loggare questi eventi deve contare gli update...

thebol
02-05-2006, 17:54
Semplicemente non ci entri... Riproduci gli eventi...quindi sai che al turno N verrà generata una nuova gemspair...

Comunque a questo punto inputServed deve cambiare... Ora vengono contati i turni di input... Per loggare questi eventi deve contare gli update...

mmm

percui generiamo anche gli eventi tipo Crush(possono essere a catena) nel log?

Jocchan
02-05-2006, 17:58
Cionci, ho provato a settare InputRate a 50 (ma anche a 5, pensando ci fosse un errore di battitura nel post, visto che hai detto di "ridurlo" ed il suo valore iniziale è 10).
Non mi pare ci sia una maggiore precisione, cambia solo la rapidità con cui le stesse cose vengono eseguite, ma le imprecisioni rimangono uguali, e credo si presentino più o meno dopo lo stesso numero di drop.

fek
02-05-2006, 17:58
mmm

percui generiamo anche gli eventi tipo Crush(possono essere a catena) nel log?

Direi di si'. Come dice cionci, tu serializzi l'output della macchina a stati, non il suo input. E la cosa e' comodissima. Immagina di avere un bug al turno 200, tu fai il playback fino al turno 199, poi lanci la macchina a stati con gl'input giusti e vedi come si comporta. Se si comporta "male", scrivi le assert che lo verificano e poi fai passare il test correggendo la macchina a stati. Ma nota che anche dopo le modifiche della macchina a stati, il log riproduce sempre e comunque la stessa configurazione perche' lo hai disaccoppiato dalla macchina a stati stessa.

Fare questo e' esattamente il requisito del task, e' il motivo per il quale abbiamo quel task.

Jocchan
02-05-2006, 17:59
Appena Joc lo conferma, questo diventa un bug e va risolto. Approposito, dobbiamo tenere aggiornato l'elenco dei bug e Joc deve produrre ogni tanto una storia per correggerli.

Questo sembra verificarsi indipendentemente da InputRate.
E' un bug perchè potrebbe crearci problemi con l'online.

fek
02-05-2006, 18:00
Questo sembra verificarsi indipendentemente da InputRate.
E' un bug perchè potrebbe crearci problemi con l'online.

Ok, va aggiunto alla lista dei bug e corretto. Puoi valutare il fix di cionci?

Jocchan
02-05-2006, 18:03
Ok, va aggiunto alla lista dei bug e corretto. Puoi valutare il fix di cionci?

L'ho già fatto, e il bug resta. Basta fare più prove e si presenta comunque, dopo più o meno lo stesso numero di coppie create.

cionci
02-05-2006, 18:08
L'ho già fatto, e il bug resta. Basta fare più prove e si presenta comunque, dopo più o meno lo stesso numero di coppie create.
Il fix non è diminuire InputRate... Sono due o tre righe di codice... Comunque hai un PVT...

Jocchan
02-05-2006, 18:20
Il fix non è diminuire InputRate... Sono due o tre righe di codice... Comunque hai un PVT...

Errore mio. Provo stasera e vediamo ;)

thebol
02-05-2006, 19:57
Direi di si'. Come dice cionci, tu serializzi l'output della macchina a stati, non il suo input. E la cosa e' comodissima. Immagina di avere un bug al turno 200, tu fai il playback fino al turno 199, poi lanci la macchina a stati con gl'input giusti e vedi come si comporta. Se si comporta "male", scrivi le assert che lo verificano e poi fai passare il test correggendo la macchina a stati. Ma nota che anche dopo le modifiche della macchina a stati, il log riproduce sempre e comunque la stessa configurazione perche' lo hai disaccoppiato dalla macchina a stati stessa.

Fare questo e' esattamente il requisito del task, e' il motivo per il quale abbiamo quel task.
ci ho messo un po a metabolizzarla, ma ora l'idea mi piace :)

ho solo un dubbio pero.

non rischiamo di avere un log troppo slegato dal timer?

in una situazione in cui o manca un evento o ne abbiamo uno di troppo, ma non sapremmo perche manca quell'evento(anche se questa è una cosa legata al debugging in realtime), se il problema fosse il timming non riusciremmo a saperlo.

Invece con un timer, da poter avanzare a piacimento(come si fa adesso nei test) si ricreerebbe l'esatta situazione di gioco normale)

cionci
02-05-2006, 19:59
Invece con un timer, da poter avanzare a piacimento(come si fa adesso nei test, si ricreerebbe l'esatta situazione di gioco normale)
Ovviamente NON DEVE mancare un evento...

Bonfo
02-05-2006, 20:11
Ragazzi...questo log mi sta facendo impazzire.
Ho capito a cosa servirebbe....ma a me :cry:

Allora:
- il log deve fornire i dati che permettano di capire che è successo nel gioco.
- deve essere possibile ripetere le azioni senza il concetto di tempo, ovvero non bisogna aspettare 2 min per il test, ma con il concetto di ordine, quindi di poter andare in un punto qualunque del log.

A questo punto mi verrebbe da dire che il vero log è l'elenco delle posizioni, dei tipi e dei colori di tutti gli oggetti in grid...oltre all'insieme dei tasti premuti.
Questo permetterebbe di capire cosa fa l'utente e come le griglie rispondono.

Bene...per me questa idea è follia pura!!!! :muro: :muro:

Quindi mi chiedo...cosa mi sono perso per strada?? :mbe:

thebol
02-05-2006, 21:16
Ovviamente NON DEVE mancare un evento...
è quello il bug :D

cionci
02-05-2006, 21:24
A questo punto mi verrebbe da dire che il vero log è l'elenco delle posizioni, dei tipi e dei colori di tutti gli oggetti in grid...oltre all'insieme dei tasti premuti.
Questo permetterebbe di capire cosa fa l'utente e come le griglie rispondono.
Non proprio... Esce la gems pair X-Y.... La gems pair X-Y va a cadere alle coordinate (3, 4), (2, 4)...

Le varie GemsPair si sommano...accumulandosi sul fondo della griglia...

Avviene un crush che elimina le gemme (3, 4), (3, 5), (3, 6)...e così via...

fek
02-05-2006, 21:38
Ragazzi...questo log mi sta facendo impazzire.
Ho capito a cosa servirebbe....ma a me :cry:

Allora:
- il log deve fornire i dati che permettano di capire che è successo nel gioco.
- deve essere possibile ripetere le azioni senza il concetto di tempo, ovvero non bisogna aspettare 2 min per il test, ma con il concetto di ordine, quindi di poter andare in un punto qualunque del log.

A questo punto mi verrebbe da dire che il vero log è l'elenco delle posizioni, dei tipi e dei colori di tutti gli oggetti in grid...oltre all'insieme dei tasti premuti.
Questo permetterebbe di capire cosa fa l'utente e come le griglie rispondono.

Bene...per me questa idea è follia pura!!!! :muro: :muro:

Quindi mi chiedo...cosa mi sono perso per strada?? :mbe:


Ok, implementa questo. Se funziona, il problema dov'e'? :)

Keep It Simple.

Jocchan
03-05-2006, 12:15
Cionci, l'exe che mi hai mandato non mi funzia.
Credo sia un problema di path, visto che un messaggio di errore di windows dice che non ho i diritti per accedere alla periferica specificata.

Jocchan
03-05-2006, 14:00
Finalmente sono riuscito a provare il fix di Cionci, e va che è una meraviglia.
Dopo diverse prove, ho l'impressione che il gioco renda al meglio con InputRate settato a 40.

fek
03-05-2006, 14:43
Allora possiamo proseguire con il refactoring di cionci.

thebol
03-05-2006, 20:46
ricapitolando sul playback del log...che se fa?

volendo proseguire con il loggare tutti gli eventi per poi riprodurli, direi che la prima cosa da fare è individuare tutti gli eventi e loggarli.


anche se dopo averci pensato oggi, quest'idea del riproddurre solo gli stati non mi convince, preferirei un playback legato al timer...

fek
04-05-2006, 01:20
Si', il primo passo e' individuare tutti gli eventi e loggarli. Il playback slegato dal timer e' un requisito del task, quindi va fatto cosi'.

thebol
06-05-2006, 11:23
ci sto lavorando su, e ho notato che il gemspair.update, era nell'update di gridcontroller. Per evitare di doverla loggare, l'ho spostata in gemsundercontrollState. Qua fallivano dei test, ma è bastato togliere un update in waitstateBeforenewgemspair quando ritorna gemsundercontrollState.

Solo che ora mi fallisce il famigerato testAllTexutresLoadBeforeStartPlaying!
mi restituisce 46 invece che 45 :(


ps. mi pare di aver capito che è quel problema sull'ordine dei test eseguiti...
ho fatto revert per nulla :(

Bonfo
06-05-2006, 11:26
Questi update a "casaccio" negli State devono essere messi a posto :mad: :mad: :mad:

thebol
06-05-2006, 11:36
Questi update a "casaccio" negli State devono essere messi a posto :mad: :mad: :mad:

avevo pensato a un sistema per risolvere un po la cosa...
mettere un metodo isUpdatable e setUpdatable.

nell'update di gridController ce un


while (state.isupdatable())
{
state = state.update;
}
state.setUpdatable();




gli stati quando restituiscono uno stato settano se deve essere eseguito subito o no.

Centralizzi la chiamata della state.update, ma non so se sia vantaggioso farlo.

thebol
06-05-2006, 11:38
avevo pensato a un sistema per risolvere un po la cosa...
mettere un metodo isUpdatable e setUpdatable.

nell'update di gridController ce un


while (state.isupdatable())
{
state = state.update;
}
state.setUpdatable();




gli stati quando restituiscono uno stato settano se deve essere eseguito subito o no.

Centralizzi la chiamata della state.update, ma non so se sia vantaggioso farlo.

fra l'altro in questa maniera, potrei gestire i log degli state come i log del inputReactor.

Adesso invece ho messo uno stringBuffer in gridController, a cui gli state appenderanno la loro stringa.

Bonfo
06-05-2006, 11:43
avevo pensato a un sistema per risolvere un po la cosa...
mettere un metodo isUpdatable e setUpdatable.


Secondo me non è quello il probelma.
In realtà basta decidere se il return degli state deve essere:
return new state();
oppure
return new state().update();

Il funzionamento rimane il medesimo. Per le temporizazzioni non so, ma anche quelle dovrebbero essere mantenute.
Il problema è modificare i test di conseguenza, che non sempre è molto ovvio.

PS: in realtà non capisco perchè alcuni state non dovrebbero essere updatable ?? :mbe: :mbe:

thebol
06-05-2006, 11:48
Secondo me non è quello il probelma.
In realtà basta decidere se il return degli state deve essere:
return new state();
oppure
return new state().update();

Il funzionamento rimane il medesimo. Per le temporizazzioni non so, ma anche quelle dovrebbero essere mantenute.
Il problema è modificare i test di conseguenza, che non sempre è molto ovvio.

PS: in realtà non capisco perchè alcuni state non dovrebbero essere updatable ?? :mbe: :mbe:

la mia soluzione serviva per centralizzare il luogo di chiamata della update degli state.
Dove ora (negli state) viene fatto

return new stateXXX();


diverrebbe

State state = new stateXXX();
state.setNotUpdatable();
return state



mentre

return new stateXXX().update();


diverrebbe


State state = new stateXXX();
state.setUpdatable();
return state;


il codice aumenta, pero si centralizza per certi versi la gestione degli state.

Non so se sia conveniente, aspetto consigli dall'alto :)

fek
07-05-2006, 11:24
il codice aumenta, pero si centralizza per certi versi la gestione degli state.

Non so se sia conveniente, aspetto consigli dall'alto :)

Se il codice aumenta, si sceglie la soluzione piu' semplice che risolve il problema.

Quel setUpdatable mi sa' tanto di getType mascherato. Provate a risolvere il problema con il polimorfismo, non settando dei flag.

Jocchan
07-05-2006, 12:01
Ho aggiunto la lista di refactoring postata nell'altro topic all'inizio di questo, in modo da potervi fare sempre riferimento. Ci sono altri refactoring svolti o in corso, così aggiorno l'elenco? Aggiornatemi, plz :p

cisc
07-05-2006, 20:16
secondo me le BigGem non sono completamente assimilabili a dei Droppable, sono delle entità che non cadono come tutti gli altri pezzi, ma che si formano dopo, e in qualche modo sono degli oggetti attivi quanto i bauli...nel senso che hanno il potere di modificare lo stato di Grid di più di una cella..quindi secondo me va cercata una qualche soluzione alternativa a quella attuale, ma non bisogna assimilare le bigGem direttamente ai droppable..

Bonfo
07-05-2006, 20:55
secondo me le BigGem non sono completamente assimilabili a dei Droppable, sono delle entità che non cadono come tutti gli altri pezzi, ma che si formano dopo, e in qualche modo sono degli oggetti attivi quanto i bauli...nel senso che hanno il potere di modificare lo stato di Grid di più di una cella..quindi secondo me va cercata una qualche soluzione alternativa a quella attuale, ma non bisogna assimilare le bigGem direttamente ai droppable..

Non sono d'accordo...comunque la cosa mi sembra molto interessante :D :D
Mi farebbe piacere che ci esponessi approfonditamente questo pensiero e cosa ti frulla per la testa. Mi sa che ne escono fuori un sacco di belle cosine!!! :D

RACCONTACI!!! ;)

cisc
07-05-2006, 23:16
questa sera non ho avuto modo di approfondire lo studio del codice in questione, cmq, le bigGem non le vedo tanto droppable magari creare un'interfaccia gridElement (un nome a caso, non badateci troppo adesso), che generalizza il più possibile una Droppable, e che magari si basi non più sul concetto di cella, ma di gruppo di celle...in questo modo si naturalizza la formazione di una bigGem, semplicemente sostituendo ad un gridElement di cella unitaria, una gridElement che occupa più celle...bisognerebbe ovviamente verificare quale sia la soluzione più opportuna con la matrice che al momento memorizza la griglia di gemme, cmq la via penso sia quella di considerare le bigGem non come un agglomerato di gemme, ma come una gemma + grande..che non cade, ma che nasce già bloccata, può solo scendere da "bloccata", dopo una crush... come vedete ancora le idee non sono molto chiare, sto vagliando una serie di soluzioni...domani sera cerco di concretizzare tutte queste idee che mi girano per la testa in una soluzione più chiara e definita, intanto le butto così, chissà che possano essere sviluppate o ampliate da qualcun altro nel frattempo...

cdimauro
08-05-2006, 09:20
ci sto lavorando su, e ho notato che il gemspair.update, era nell'update di gridcontroller. Per evitare di doverla loggare, l'ho spostata in gemsundercontrollState. Qua fallivano dei test, ma è bastato togliere un update in waitstateBeforenewgemspair quando ritorna gemsundercontrollState.

Solo che ora mi fallisce il famigerato testAllTexutresLoadBeforeStartPlaying!
mi restituisce 46 invece che 45 :(

ps. mi pare di aver capito che è quel problema sull'ordine dei test eseguiti...
ho fatto revert per nulla :(
Sì, perché vuol dire che è stata aggiunta qualche altra nuova texture che viene utilizzata dal gioco e che non è stata inserita nell'elenco di quelle da precaricare (aggiornando anche il numerino).

Sto lavorando a questo problema.

fek
08-05-2006, 10:10
Sì, perché vuol dire che è stata aggiunta qualche altra nuova texture che viene utilizzata dal gioco e che non è stata inserita nell'elenco di quelle da precaricare (aggiornando anche il numerino).

Sto lavorando a questo problema.

Questo problema ha assoluta priorita'. Questo test deve essere reso autonomo il prima possibile.

cdimauro
08-05-2006, 10:27
OK. Appena finisco alcune rifattorizzazioni su Environment (che mi renderebbero la vita più facile) è la prima cosa a cui metterò mano.

fek
09-05-2006, 15:24
Mi aggiornate sullo stato del task playback?

E' possibile dati i refactoring concluderlo entro la fine del Ciclo o va posticipato? Abbiamo bisogno di piu' visibilita' sul suo stato.

thebol
09-05-2006, 15:34
Mi aggiornate sullo stato del task playback?

E' possibile dati i refactoring concluderlo entro la fine del Ciclo o va posticipato? Abbiamo bisogno di piu' visibilita' sul suo stato.

per stasera dovrei finire di completare il log.

poi cè da fare il reader del log, e un oggetto che dopo aver letto il log esegua in serie i vari eventi/stati.

fino al reader entro fine ciclo ci si arriva(si potrebbe incominciare gia a farlo adesso, per i log degli eventi dei tasti) per l'esecutore invece non penso...

fek
09-05-2006, 15:36
per stasera dovrei finire di completare il log.

poi cè da fare il reader del log, e un oggetto che dopo aver letto il log esegua in serie i vari eventi/stati.

Ottimo, mi fai un break down di questi task, per favore? Abbiamo decisamente sottovalutato il task e serve che sia spezzato in sotto task, cosi' lo possiamo schedulare meglio nella prossima Storia se necessario.

thebol
09-05-2006, 15:37
Ottimo, mi fai un break down di questi task, per favore? Abbiamo decisamente sottovalutato il task e serve che sia spezzato in sotto task, cosi' lo possiamo schedulare meglio nella prossima Storia se necessario.

cavolo, piu veloce del mio edit :asd:

fra l'altro il legame fra il reader e l'esecutore non è banalissimo...

thebol
09-05-2006, 22:38
devo fare qualche test a livello di playField e fare il log per il stoneFallState e poi la prima parte è spero finita.

curiosità...
mi dimenticavo di resettare il log degli stati, ed ero arrivato a 200MB di log per field in meno di un minuto :asd:

thebol
09-05-2006, 23:35
mi appresto a committare...

il log ora è molto verboso, sopratutto per i molti gemspairOnControllState loggati...

si potrebbe risolvere questa cosa, facendolo loggare solo quando esce con il numero di volte che è stato eseguito.

altra cosa, il codice che scrive il log è sempre uguale(tranne che per stoneFallState), si potrebbe creare una classe abstract di gridControllerState e fare un metodo abstract li dentro(poi chi ha esigenze particolari lo reimplementa).

fek
10-05-2006, 10:34
altra cosa, il codice che scrive il log è sempre uguale(tranne che per stoneFallState), si potrebbe creare una classe abstract di gridControllerState e fare un metodo abstract li dentro(poi chi ha esigenze particolari lo reimplementa).

YAGNI. Andiamo dritti alla soluzione del problema ora.

thebol
10-05-2006, 11:05
YAGNI. Andiamo dritti alla soluzione del problema ora.

ora cè da fare il reader del log, da pensare se eseguire il log mano a mano che lo si legge, oppure tutto in una volta e memorizzare quello che ci serve in una struttura ad hoc.

Poi scelta la soluzione, fare l'esecutore.

fek
10-05-2006, 11:16
ora cè da fare il reader del log, da pensare se eseguire il log mano a mano che lo si legge, oppure tutto in una volta e memorizzare quello che ci serve in una struttura ad hoc.

Poi scelta la soluzione, fare l'esecutore.

Qual e' la piu' semplice a tuo avviso?

thebol
10-05-2006, 12:09
Qual e' la piu' semplice a tuo avviso?
direi un LogReader che legge mano a mano...

Legge una riga, prende la classe(usando la reflection, visto che ci scriviamo il nome della classe), eventualmente i parametri. Poi si lancia in "qualche maniera" lo stato o l'evento.

Si potrebbe creare un interfaccia comune per i PlaybackableEvent, con un metodo che accetti una stringa(il parametro) che sara poi trattato di conseguenza dal evento/stato.

thebol
10-05-2006, 12:12
direi un LogReader che legge mano a mano...

Legge una riga, prende la classe(usando la reflection, visto che ci scriviamo il nome della classe), eventualmente i parametri. Poi si lancia in "qualche maniera" lo stato o l'evento.

Si potrebbe creare un interfaccia comune per i PlaybackableEvent, con un metodo che accetti una stringa(il parametro) che sara poi trattato di conseguenza dal evento/stato.

Se si segue l'idea dell'interfaccia, dopo averla definita si puo procedere con 2 task separati.

-leggere il file di log, trovare l'evento e passargli l'eventuale stringa dei parametri
-implementare per gli eventi il metodo dell'interfaccia.

naturalmente all'inizio della lettura del log si deve leggere il seed, e poi si processano le righe/eventi.

cdimauro
10-05-2006, 12:28
Finita anche la rifattorizzazione (e non) di Engine/Texture. Adesso il pooling delle texture è stato completamente spostato da Texture a EngineInterface (e relative implementazioni: Engine e MockEngine), e da statico è divenuto dinamico, eliminando quindi il problema dei test ballerini.

Inoltre ho introdotto anche TestEngine per testare il funzionamento dell'engine (prima non esistevano test) e del relativo pooling.

Nota MOLTO dolente è che, a causa del precaricamento di tutte le texture alla creazione di ogni GameLoop, il tempo di build con Ant è aumentato di UN ORDINE DI GRANDEZZA (sulla mia macchina da circa 1 minuto sono passato a 8-10 minuti). :(

Formalmente le rifattorizzazioni di cui m'ero fatto carico per questo ciclo sono terminate, ma come ho detto continuerò a rifattorizzare a prescindere, perché ho in mente diverse altre cose da sistemare, primo fra tutti il tempo di esecuzione di Ant enormemente aumentato.
Per domani avrei in mente di estrarre un'interfaccia da Texture e differenziarne le implementazioni: quella di LWJGL per il codice di produzione, mentre una ad hoc per i test (MockTexture? :p) che si occupi soltanto di simulare il caricamento di una texture. In quest'ultimo caso il come dipende anche dai test: se fanno uso intensivo di getWidth() e getHeight() mi posso scordare questa rifattorizzazione, perché ho comunque bisogno di poter leggere i relativi file ed estrarre queste informazioni.

Una nota: per quanto riguarda i nomi da attribuire alle interfacce, avete qualche idea? EngineInterface, TextureInterface, KeyboardInterface, ecc. mi sembrano bruttine: vorrei trovare loro qualche verbo adatto, ma per il momento non mi viene in mente niente. :(

cionci
10-05-2006, 12:37
Cesare ci vogliono circa 2 GB di Ram per far girare tutti i test da Eclipse... E' impossibile andare avanti così... La Ram deve essere in qualche modo liberata... Invece secondo me si accumulano texture nel precaricamento...

thebol
10-05-2006, 12:44
Cesare ci vogliono circa 2 GB di Ram per far girare tutti i test da Eclipse... E' impossibile andare avanti così... La Ram deve essere in qualche modo liberata... Invece secondo me si accumulano texture nel precaricamento...

probabilmente perche eclipse non fa il fork, percui le texture si accumulano.

fek
10-05-2006, 12:49
direi un LogReader che legge mano a mano...

Legge una riga, prende la classe(usando la reflection, visto che ci scriviamo il nome della classe), eventualmente i parametri. Poi si lancia in "qualche maniera" lo stato o l'evento.

Va bene il LogReader che legge mano a mano. Preferirei non usare reflection e NON scrivere il nome della classe, ma il tipo di evento. Non voglio che il log sia accoppiato con l'implementazione, in particolare col nome di una classe.

cionci
10-05-2006, 12:49
probabilmente perche eclipse non fa il fork, percui le texture si accumulano.
Sì, ma non si devono accumulare... L'engine deve fare ciao ciao alle texture quando viene chiamata la engine.shutdown, invece non avviene...
Magari anche chiamando esplicitamente il garbage collector se è necessario...

fek
10-05-2006, 12:51
Nota MOLTO dolente è che, a causa del precaricamento di tutte le texture alla creazione di ogni GameLoop, il tempo di build con Ant è aumentato di UN ORDINE DI GRANDEZZA (sulla mia macchina da circa 1 minuto sono passato a 8-10 minuti). :(

Ok, questa nota e' troppo dolente e va risolta immediatamente.

Due possibili soluzioni:
1) si crea un mock del comportamento di precaricamento e si precaricano le texture solo quando effettivamente questo serve in un test
2) si distruggono esplicitamente le texture alla fine di ogni test che usa un game loop


Preferisco decisamente la soluzione 1). E' un problema molto urgente questo.

fek
10-05-2006, 12:53
Sì, ma non si devono accumulare... L'engine deve fare ciao ciao alle texture quando viene chiamata la engine.shutdown, invece non avviene...
Magari anche chiamando esplicitamente il garbage collector se è necessario...

Troppo hacky, quando c'e' bisogno di invocare manualmente il garbage collector e' di solito sintomo che stiamo sbagliando qualcosa di grosso noi.

Io non caricherei le texture, in fondo non ci servono i dati delle texture in nessun test, oppure creerei un proxy per le texture che durante i test non carica assolutamente nulla se non, magari, i dati ad alto livello dell'immagine (dimensione, bpp, nome).

cionci
10-05-2006, 12:54
Preferisco decisamente la soluzione 1). E' un problema molto urgente questo.
Le texture vengono già distrutte (gameLoop.quit()), ma non viene liberata la memoria... Per quello ti dicevo del garbage collector...

thebol
10-05-2006, 12:54
Sì, ma non si devono accumulare... L'engine deve fare ciao ciao alle texture quando viene chiamata la engine.shutdown, invece non avviene...
Magari anche chiamando esplicitamente il garbage collector se è necessario...

nei test non viene (penso chiamata) la engine.shutdown.

si dovrebbe implementare la tearDrop nella classe test che quasi tutte implementano(e che magari sarebbe meglio che tutte implementassero.

cionci
10-05-2006, 12:55
Prendi TestGameLoop... Viene richiamata engine.shutDown() tramite gameLoop.quit(), ma la memoria non viene liberata... Ci vogliono oltre 400 MB di memoria solo per TestGameLoop...

thebol
10-05-2006, 12:57
Prendi TestGameLoop... Viene richiamata engine.shutDown() tramite gameLoop.quit(), ma la memoria non viene liberata... Ci vogliono oltre 400 MB di memoria solo per TestGameLoop...
stikazzi

fek
10-05-2006, 13:04
Prendi TestGameLoop... Viene richiamata engine.shutDown() tramite gameLoop.quit(), ma la memoria non viene liberata... Ci vogliono oltre 400 MB di memoria solo per TestGameLoop...

Allora non bisogna caricare le texture fisicamente ma solo creare un proxy. Chi se ne occupa?

thebol
10-05-2006, 13:30
Va bene il LogReader che legge mano a mano. Preferirei non usare reflection e NON scrivere il nome della classe, ma il tipo di evento. Non voglio che il log sia accoppiato con l'implementazione, in particolare col nome di una classe.

OK.

fra le cose da fare allora subentrano:
--cambiare nomenclatura per il log
--creare un associazione fra nome e evento/stato.

Questa associazione verra usata da il reader del log per lanciare gli eventi opportuni(il giro di prima).

Potrebbe essere interessante far usare questa associazione anche a chi scrive il log.

Quest'oggetto associazione potrebbe avere un metodo getEventByName e getNameByEvent. Gli stati/eventi a questo punto dovrebbero solo generare una stringa parametro.



[da qui in poi sono pensieri da valutare per un successivo refactoring]

Per fare questo si potrebbe usare l'interfaccia PlaybackableEvent per avere un getParamString.

In questa maniera inputReactor e gridController negli update farebbero circa questo:


nameEvent = Associazione.getNameByEvent(evento);
evento.do();
param = evento.getParamString();


fattibile per inputReactor, ma non per gridController.
Questo perche uno stato all'interno della update puo restituire un altro stato che ne restituisce un altro ancora, e ci perderemmo quello di mezzo.

Per fare questo ci vorrebbe il refactoring che avevo gia proposto di centralizzare la chiamata alla state.update();

[fine pensieri futuri]

fek
10-05-2006, 13:36
Mi sembra un buon piano. Inizia con la prima parte, vediamo come si struttura il codice e poi, se necessario, rifattorizziamo GridController di conseguenza. Ma abbiamo bisogno di vedere la soluzione che funziona, prima di pensare a ulteriori refactoring. Ragionate "giorno per giorno", non cercate di creare il gran piano che funziona di sicuro tutto in un colpo solo.

thebol
10-05-2006, 13:39
Mi sembra un buon piano. Inizia con la prima parte, vediamo come si struttura il codice e poi, se necessario, rifattorizziamo GridController di conseguenza. Ma abbiamo bisogno di vedere la soluzione che funziona, prima di pensare a ulteriori refactoring. Ragionate "giorno per giorno", non cercate di creare il gran piano che funziona di sicuro tutto in un colpo solo.

infatti mi sono accorto di guardare troppo avanti, ma ho voluto cmq concludere il discorso anche solo come promemoria :).

Sono refactoring che probabilmente mi semplificherebbero la vita, ma il tempo per farli sarebbe sicuramente maggiore del guadagno :fagiano:

Bonfo
10-05-2006, 13:43
Mi sa che nel prossimo ciclo va aggiunta una bella storia di solo refactoring dei TEST !!!! :D :D

P.S.: per ciò che riguarda il log mi sono perso...sono completamente al buio :(
TheBol mi fai una speigazione come ad un bimbo di 5 anni ?? :flower:

cdimauro
10-05-2006, 13:46
Sì, ma non si devono accumulare... L'engine deve fare ciao ciao alle texture quando viene chiamata la engine.shutdown, invece non avviene...
Magari anche chiamando esplicitamente il garbage collector se è necessario...
E' esattamente ciò che succede:
public void shutDown()
{
cleanupTextures();
shuttedDown = true;
}

private void cleanupTextures()
{
// I HATE CHECKSTYLE!!!
for(Texture texture : textures.values())
{
texture.cleanup();
}

textures.clear();
}
Tutte le texture vengono liberate allo shutdown di (Mock)Engine, SE IL METODO VIENE CHIAMATO.

Nel caso di GameLoop.quit, ciò avviene, ma i problemi rilevati permangono.

cdimauro
10-05-2006, 13:47
Le texture vengono già distrutte (gameLoop.quit()), ma non viene liberata la memoria... Per quello ti dicevo del garbage collector...
Esattamente.

cdimauro
10-05-2006, 13:49
Allora non bisogna caricare le texture fisicamente ma solo creare un proxy. Chi se ne occupa?
Io.

cdimauro
10-05-2006, 14:26
Problema sistemato. Ho introdotto un Texture.createForTesting che non utilizza LWJGL per "caricare" le texture in fase di test, ma ne simula il comportamento.

Adesso a eseguire la build impiega la metà del tempo rispetto a prima della mia rifattorizzazione di Engine & Texture, e l'occupazione di memoria è drasticamente ridotta. ;)

Per adesso è una buona pezza perché risolve il problema: appena ho tempo rifattorizzo verso la soluzione più elegante.

thebol
11-05-2006, 13:00
nella modalita demo devo riprodurre anche l'evento escapeCommandHandler?

fek
11-05-2006, 13:44
nella modalita demo devo riprodurre anche l'evento escapeCommandHandler?

Direi di no.

thebol
12-05-2006, 13:11
Ho un problema.

Ho unificato gli state e i commandHandler come LogEvent.

Per per far andare gli state ho bisogno del gridController, mentre per i commandHandler ho bisogno del inputReactor.

L'inputReactor e contenuto in gridController ma non cè il getter.


Ora potrei mettere nell'interfaccia del LogEvent un metodo useForLog(GridController) e mette il getter di inputReactor in gridController.

Oppure divido i logEvent in logCommandHandler e logState, e a ogni interfaccia passo l'oggetto che serve(ma mi servira ancora il getter del inputReactor in gridController, visto che viene creato li dentro).

Io per ora continuerei con la mono interfaccia.

fek
12-05-2006, 13:13
Se sono due 'cose' diverse, allora sono due interfacce diverse. Una entita' = Una responsabilita'.

Bonfo
12-05-2006, 13:44
Male che vada "inverti" la chiamata, ovvero:

interfaccia Loggable (un semplice metodo log()).
Il logger non fa altro che,alla notifica di un cambiamento, invocare il metodo log.
Così è GridController che ha la responsabilità di decidere di cosa loggare di se stesso.(e mi sembra anche sensato ;) )

Cosa ne dite??

cdimauro
12-05-2006, 14:09
Possiamo fare un punto della situazione dettagliato delle rifattorizzazioni mancanti, in modo da capire quale è il loro stato, dare un'occhiata al codice e cercare di farsi un'idea di come si dovrebbe procedere?

thebol
12-05-2006, 14:47
Male che vada "inverti" la chiamata, ovvero:

interfaccia Loggable (un semplice metodo log()).
Il logger non fa altro che,alla notifica di un cambiamento, invocare il metodo log.
Così è GridController che ha la responsabilità di decidere di cosa loggare di se stesso.(e mi sembra anche sensato ;) )

Cosa ne dite??

Il problema non cè l'ho mentre sto loggando, ma mentre dovrei rieseguire l'evento.

Se sono due 'cose' diverse, allora sono due interfacce diverse. Una entita' = Una responsabilita'.

Gli oggetti sono diversi, ma a livello di chi li usa in questo ambito sono la stessa cosa(un evento precedentemente loggato da eseguire).

fek
12-05-2006, 15:09
Gli oggetti sono diversi, ma a livello di chi li usa in questo ambito sono la stessa cosa(un evento precedentemente loggato da eseguire).

L'interfaccia verso il mondo esterno quindi e' uguale?

thebol
12-05-2006, 15:15
L'interfaccia verso il mondo esterno quindi e' uguale?

Sarebbe il mio obbiettivo.

In questa maniera scorrendo il log, mi procuro un evento(state o commandHandler che sia), lo resetto(per i casi in cui sia un evento a tempo), e poi lo eseguo.



Ho fatto pero la domanda perche nativamente i 2 oggetti sono diversi e accettano per essere eseguiti 2 parametri diversi(ma che si potrebbero accomunare in gridController).

fek
12-05-2006, 17:42
Ho fatto pero la domanda perche nativamente i 2 oggetti sono diversi e accettano per essere eseguiti 2 parametri diversi(ma che si potrebbero accomunare in gridController).


Allore due oggetti diversi. Poi quando riesci a unificare l'interfaccia, puoi fonderli o, meglio, creare un Adapater da uno all'altro.

thebol
12-05-2006, 17:50
Allore due oggetti diversi. Poi quando riesci a unificare l'interfaccia, puoi fonderli o, meglio, creare un Adapater da uno all'altro.

La mia idea era usare come adapter il metodo della nuova interfaccia.

Tipo usedOnPlaybackLog(GridController);

da gridController riesco a risalire a tutto quello che mi serve per entrambi gli oggetti. Poi chi usa nativamente gridController fa solo un redirect, gli altri prendono gli oggetti che gli servono e li passano.

fek
12-05-2006, 18:09
Keep It Simple. Implementa la cosa piu' semplice che funziona per ora.

thebol
12-05-2006, 18:11
Keep It Simple. Implementa la cosa piu' semplice che funziona per ora.

OK
mi è piu semplice l'interfaccia comune, vado con quella.

thebol
13-05-2006, 19:25
Ho un problema sullo stato stoneFall.

StoneFall viene creato passandogli il pattern
Il pattern passato è una variabile static in CrushState, e cio mi crea dei problemi perche eseguendo questo stato dovrei ogni volta crearlo o passargli il pattern.

Visto che in crushState il pattern non viene toccato(apparte essere passato a stoneFall), proporrei di spostare l'oggetto pattern in environement. In questa maniera rimane "globale" ma solo a livello di environament, e i test possono continuare a passare.

Il Pattern verrebbe scritto nell log all'inizio come il seed.

Infatti nel tentativo di spostare la variabile statica da crushState a stonefallstate, ho avuto dei problemi perche alcuni test creavano un pattern e poi lo usavano per creare StoneFallState e per delle assert.

Dopo questo ulteriore refactoring mi manca poco a committare il logManager.

Dopo di che manca solo il logReader e il tutto dovrebbe andare.

thebol
13-05-2006, 20:09
Ho un problema sullo stato stoneFall.

StoneFall viene creato passandogli il pattern
Il pattern passato è una variabile static in CrushState, e cio mi crea dei problemi perche eseguendo questo stato dovrei ogni volta crearlo o passargli il pattern.

Visto che in crushState il pattern non viene toccato(apparte essere passato a stoneFall), proporrei di spostare l'oggetto pattern in environement. In questa maniera rimane "globale" ma solo a livello di environament, e i test possono continuare a passare.

Il Pattern verrebbe scritto nell log all'inizio come il seed.

Infatti nel tentativo di spostare la variabile statica da crushState a stonefallstate, ho avuto dei problemi perche alcuni test creavano un pattern e poi lo usavano per creare StoneFallState e per delle assert.

Dopo questo ulteriore refactoring mi manca poco a committare il logManager.

Dopo di che manca solo il logReader e il tutto dovrebbe andare.


questa soluzione funziona, e migliore di una variabile statica come era prima, ma nel complesso fa cagare :|

il pattern in environament non ci sta a dire un cazzo.

Pero ora come ora non so come risolvere il problema.

cionci
13-05-2006, 20:14
Boh...magari si può serializzare il pattern e scriverlo come parametro del cambiamento di stato...

thebol
13-05-2006, 20:17
Altro problema...
Nell attuale implementazione, quando si arriva all waitBeforeNewGemsPair, questo puo essere loggato n volte, perche appunto aspetta tot millisecondi e poi crea la nuova gemsPair.

Ora visto che il playback deve essere slegato dal tempo la mia idea era quella di fare in modo che quando si eseguisse uno stato, si facesse in modo che venisse sempre eseguito(le condizioni di wait forzate a false).

Queste 2 cose pero non vanno assieme, l'unica soluzione sarebbe loggare l'evento solo quando viene realmente eseguito qualcosa, non solo quando ci si passa.

Ma per me si falserebbe troppo il playback a questo punto...

thebol
13-05-2006, 20:20
Boh...magari si può serializzare il pattern e scriverlo come parametro del cambiamento di stato...

per come stavo pensando la cosa, gli eventi venivano creati una volta solo e poi eseguti in serie.

Nell'esecuzione passo l'environament e gridController.

il pattern forse starebbe meglio in gridController...visto che ha l'esigenza di essere creato una sola volta per partita.

Bonfo
13-05-2006, 20:21
Ho un problema sullo stato stoneFall.
StoneFall viene creato passandogli il pattern
Il pattern passato è una variabile static in CrushState, e cio mi crea dei problemi perche eseguendo questo stato dovrei ogni volta crearlo o passargli il pattern.


Lo fatto io per risolvere un bug prima della FP..lo sapevo non fosse bellissimo..ma era da fare :D

Forse quella vriabile lì deve finire in PlayField.

thebol
14-05-2006, 22:53
Altro problema...
Nell attuale implementazione, quando si arriva all waitBeforeNewGemsPair, questo puo essere loggato n volte, perche appunto aspetta tot millisecondi e poi crea la nuova gemsPair.

Ora visto che il playback deve essere slegato dal tempo la mia idea era quella di fare in modo che quando si eseguisse uno stato, si facesse in modo che venisse sempre eseguito(le condizioni di wait forzate a false).

Queste 2 cose pero non vanno assieme, l'unica soluzione sarebbe loggare l'evento solo quando viene realmente eseguito qualcosa, non solo quando ci si passa.

Ma per me si falserebbe troppo il playback a questo punto...

che devo fare?

io volendo posso andare avanti su questa strada(loggare uno stato solo quando succede qualcosa), ma secondo me sarebbe da rivedere il task(e suddiviederlo a priori in sotto task).

fek
15-05-2006, 10:23
che devo fare?

io volendo posso andare avanti su questa strada(loggare uno stato solo quando succede qualcosa), ma secondo me sarebbe da rivedere il task(e suddiviederlo a priori in sotto task).

Il task va suddiviso in sottotask e costituisce una sua Storia adesso. Il Ciclo e' chiuso. Aspettiamo che Jocchan ci dia le nuove storie oggi.

Jocchan
15-05-2006, 11:17
Le storie sono già praticamente pronte, ma prima di postarle dovrei parlare con te o Vic per discuterne alcuni dettagli. Ora sto andando alle poste, tra un'oretta e mezza ci sono (chiamatemi su msn per favore, così appena torno sistemiamo).