2013-01-23 5 views
5

Prima di tutto, sono nuovo di LINQ, quindi non conosco bene i dettagli. Sto tentando di usarlo in qualche codice al minuto, e secondo la mia diagnostica sembra essere veloce quanto usare un ciclo for allo stesso modo. Tuttavia, non sono sicuro di quanto sarebbe scalabile in quanto le liste con cui sto lavorando potrebbero aumentare in modo drammatico.Novità di LINQ: è questo il posto giusto per utilizzare LINQ?

Sto usando LINQ come parte di una funzione di rilevamento collisione (che è ancora in lavorazione) e lo sto usando per selezionare l'elenco solo per quelli rilevanti per i controlli.

Ecco la versione LINQ:

partial class Actor { 
    public virtual bool checkActorsForCollision(Vector2 toCheck) { 
     Vector2 floored=new Vector2((int)toCheck.X, (int)toCheck.Y); 

     if(!causingCollision) // skip if this actor doesn't collide 
      return false; 

     foreach(
      Actor actor in 
      from a in GamePlay.actors 
      where a.causingCollision==true&&a.isAlive 
      select a 
      ) 
      if(// ignore offscreen collisions, we don't care about them 
       (actor.location.X>GamePlay.onScreenMinimum.X) 
       && 
       (actor.location.Y>GamePlay.onScreenMinimum.Y) 
       && 
       (actor.location.X<GamePlay.onScreenMaximum.X) 
       && 
       (actor.location.Y<GamePlay.onScreenMaximum.Y) 
       ) 
       if(actor!=this) { // ignore collisions with self 
        Vector2 actorfloor=new Vector2((int)actor.location.X, (int)actor.location.Y); 

        if((floored.X==actorfloor.X)&&(floored.Y==actorfloor.Y)) 
         return true; 
       } 

     return false; 
    } 
} 

Questo è il mio metodo precedente:

partial class Actor { 
    public virtual bool checkActorsForCollision(Vector2 toCheck) { 
     Vector2 floored=new Vector2((int)toCheck.X, (int)toCheck.Y); 

     if(!causingCollision) // skip if this actor doesn't collide 
      return false; 

     for(int i=0; i<GamePlay.actors.Count; i++) 
      if(// ignore offscreen collisions, we don't care about them 
       (GamePlay.actors[i].location.X>GamePlay.onScreenMinimum.X) 
       && 
       (GamePlay.actors[i].location.Y>GamePlay.onScreenMinimum.Y) 
       && 
       (GamePlay.actors[i].location.X<GamePlay.onScreenMaximum.X) 
       && 
       (GamePlay.actors[i].location.Y<GamePlay.onScreenMaximum.Y) 
       ) 
       if(// ignore collisions with self 
        (GamePlay.actors[i].isAlive) 
        && 
        (GamePlay.actors[i]!=this) 
        && 
        (GamePlay.actors[i].causingCollision) 
        ) { 
        Vector2 actorfloor= 
         new Vector2(
          (int)GamePlay.actors[i].location.X, 
          (int)GamePlay.actors[i].location.Y 
          ); 

        if((floored.X==actorfloor.X)&&(floored.Y==actorfloor.Y)) 
         return true; 
       } 

     return false; 
    } 
} 

Al minuto, sia eseguito in pochissimo tempo (ma eseguire numerose volte al secondo), ma man mano che il progetto si costruisce e diventa più complesso, questo si troverà ad affrontare molti più oggetti contemporaneamente e il codice per verificare le collisioni sarà più dettagliato.

+0

Il tuo codice è completamente corretto. LINQ è abbastanza performante, estremamente performante in alcuni casi, meno in alcuni altri. La cosa migliore che puoi fare è provare e confrontare. –

+1

"Tuttavia, non sono sicuro di quanto sarebbe scalabile in quanto le liste con cui sto lavorando potrebbero aumentare in modo drammatico". Allora dovresti misurarlo. Dovresti essere in grado di creare facilmente grandi dati di test ed eseguire il tuo codice su questo. – svick

+0

Se sono necessarie prestazioni migliori, è necessario sostituire il rilevamento delle collisioni O (n^2) con un algoritmo che si adatta meglio. – CodesInChaos

risposta

11

Il tuo codice sembra abbastanza buono; Non sono un grande fan del cambiamento del codice di lavoro, ma se volessi riscriverlo per renderlo più facile da leggere, ecco cosa farei:

In primo luogo, astrarre il predicato "è fuori dallo schermo". Forse renderlo un metodo di GamePlay. Questo compito di verificare ogni volta se le coordinate sono nei limiti è (1) un dettaglio di implementazione e (2) rendere difficile la lettura del codice. È possibile che in futuro si disponga di un meccanismo più sofisticato per decidere se un oggetto è sullo schermo o meno.

In secondo luogo, allontanare l'operazione di pavimentazione vettoriale. Forse renderlo un metodo di Vector. Si noti che questo metodo dovrebbe restituire un nuovo vettore, non mutare il vettore esistente.

In terzo luogo, creare un operatore di uguaglianza sui vettori.

In quarto luogo, denominare il metodo migliore. Un predicato dovrebbe avere la forma "IsFoo" o "HasFoo". Lo hai formulato come un comando, non come una domanda.

In quinto luogo, non è necessario un ciclo.

In sesto luogo, è strano dire somebool == true. Dì solo somebool. Il primo significa "se è vero che questo bool è vero", che è inutilmente complicato.

Vediamo come questo scuote out:

public virtual bool HasCollisionWithAnyActor(Vector2 toCheck) 
{ 
    // "Easy out": if this actor does not cause collisions then 
    // we know that it is not colliding with any actor. 
    if (!causingCollision) 
     return false; 

    Vector2 floored = toCheck.Floor(); 

    var collidingActors = 
     from actor in GamePlay.actors 
     where actor != this 
     where actor.causingCollision 
     where actor.isAlive 
     where GamePlay.IsOnScreen(actor.location) 
     where actor.location.Floor() == floored 
     select actor; 

    return collidingActors.Any(); 
} 

Guardate quanto più facile che legge rispetto alla versione del metodo! Niente di tutto questo con le coordinate X e Y. Fai metodi di supporto per tutto ciò che riguarda lo scutwork. Il codice ora esprime chiaramente la semantica: dimmi se ci sono collisioni con altri attori viventi che causano collisione sullo schermo.

+0

Grazie mille per l'elenco dei suggerimenti, tutti hanno molto senso. La classe Vector proviene da XNA stessa quindi non ero sicuro di apportare alcuna modifica a questo, ma vedendo quello che mi avrebbe dato, potrei benissimo farlo. Per il sesto, trovo che lasciare l'intera cosa "== true" o "== false" aiuti in alcuni casi per la leggibilità, credo che alla fine lo compilino comunque, se non sbaglio. – Lyise

+2

@Lyise: se non si ha accesso alla classe vettoriale, è sempre possibile scrivere i metodi di estensione. –

+0

@Lyise: considera questa risposta come la soluzione, è ciò che Linq per. –

2

Qui la versione LINQ è più veloce della precedente perché si è dimenticato di creare una variabile locale per memorizzare GamePlay.actors[i]. Quindi, l'accesso all'array actors se eseguito un sacco di volte nel ciclo for.

public virtual bool checkActorsForCollision(Vector2 toCheck) 
{ 
    Vector2 floored = new Vector2((int) toCheck.X, (int) toCheck.Y); 

    if (!causingCollision) // skip if this actor doesn't collide 
    return false; 

    for (int i = 0; i < GamePlay.actors.Count; i++) 
    { 
     Actor actor = GamePlay.actors[i]; 
     // ignore offscreen collisions, we don't care about them 
     if ((actor.location.X > GamePlay.onScreenMinimum.X) && 
      (actor.location.Y > GamePlay.onScreenMinimum.Y) && 
      (actor.location.X < GamePlay.onScreenMaximum.X) && 
      (actor.location.Y < GamePlay.onScreenMaximum.Y)) 
     { 

      if ((actor.isAlive) && (actor != this) && 
       (actor.causingCollision)) // ignore collisions with self 
      { 
       Vector2 actorfloor = 
        new Vector2((int) actor.location.X, 
           (int) actor.location.Y); 
       if ((floored.X == actorfloor.X) && 
        (floored.Y == actorfloor.Y)) 
        return true; 
      } 
     } 
    } 
    return false; 
} 

Ora, LINQ come ottime prestazioni in generale. Ma ci sono alcuni casi in cui è meglio usare la classica versione for. Devi trovare il giusto equilibrio tra la compatibilità del tuo codice e le prestazioni (confrontando la versione LINQ e la versione for). Da parte mia, io uso e abuso di LINQ perché mi piace molto la sua sintassi e la sua flessibilità.

Ecco una versione completa LINQ:

return (from actor in GamePlay.actors 
     where actor.causingCollision && a.isAlive 
     where actor != this 
     where (actor.location.X > GamePlay.onScreenMinimum.X) && 
       (actor.location.Y > GamePlay.onScreenMinimum.Y) && 
       (actor.location.X < GamePlay.onScreenMaximum.X) && 
       (actor.location.Y < GamePlay.onScreenMaximum.Y) 
     select new Vector2((int)actor.location.X, 
          (int)actor.location.Y)).Any(actorfloor => (floored.X == actorfloor.X) && 
                    (floored.Y == actorfloor.Y)); 
+1

Grazie per tutto questo. Non mi ero reso conto che il riferimento a GamePlay.actors [i] sarebbe stato più lento della creazione di una copia locale. Ora che posso vedere da quello che hai detto (e altri), penso che trasferirò più di quella funzione in una versione LINQ che sembra molto più leggibile. – Lyise

+2

Molte risposte qui si concentrano sulle prestazioni di LINQ in termini di _raw speed_, ma non penso che sia proprio quello di cui dovresti preoccuparti. Il vero problema con l'utilizzo di LINQ in un gioco XNA - e specialmente in una funzione di rilevamento di collisione che verrà probabilmente chiamata più volte per fotogramma - è la sua tendenza a gettare spazzatura su tutto l'heap gestito, spesso in modo invisibile. –

+1

@ColeCampbell Ecco perché tutte le risposte suggeriscono di misurare le prestazioni. Ma è esatto che l'overhead dell'uso di LINQ è visibile solo in alcune situazioni. –

1

non vedo alcun problema con l'utilizzo LINQ qui. Se volete sapere davvero se si stanno aumentando le prestazioni si dovrebbe fare qualche meassuring con Diagnostics.StopWatch

http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.elapsedticks.aspx

Inoltre, è possibile utilizzare ancora di più LINQ come questo per rendere la funzione un po 'più compatta.

return (from actor in (from a in GamePlay.actors 
            where a.CausingCollision == true && a.IsAlive 
            select a) 
        where (actor.location.X > GamePlay.onScreenMinimum.X) && (actor.location.Y > GamePlay.onScreenMinimum.Y) && (actor.location.X < GamePlay.onScreenMaximum.X) && (actor.location.Y < GamePlay.onScreenMaximum.Y) 
        where actor != this 
        select new Vector2((int) actor.location.X, (int) actor.location.Y)).Any(actorfloor => (floored.X == actorfloor.X) && (floored.Y == actorfloor.Y)); 
+0

Dai numeri che ho ottenuto con StopWatch, i due sembravano abbastanza simili, che la dimensione della lista che sto usando attualmente è il motivo per cui ho pensato che sarebbe valso la pena chiedere qui come bene, prima che mi imbatto in un problema più in basso. Grazie mille per la versione LINQ del resto, penso che sarà il mio prossimo passo ora che suona come LINQ sarebbe la strada migliore da percorrere. – Lyise

1

io non sono sicuro se ho capito la domanda, ma in generale penso che sia sempre una soluzione migliore per usare LINQ invece di creare ulteriori loop e if/else chiamate.

FYI: è anche possibile utilizzare il LINQ-Methodes invece delle parole-chiave, come:

foreach(Actor actor in from a in GamePlay.actors 
          where a.causingCollision == true 
          && a.isAlive 
          select a) 
{ 
    //... 
} 

è lo stesso:

foreach(Actor actor in GamePlay.actors.Where(a => a.causingCollision == true && a.isAlive)) 
{ 
    //... 
} 

il ritorno in entrambi i casi è un IEnumerable filtrato. La mia opinione personale è che i metodi sono più facili da leggere e capire. Nel campo Where (...) puoi inserire un'espressione lambda completa per selezionare completamente solo gli attori che desideri.

+0

Grazie per l'informazione, non ero consapevole che sarei stato in grado di farlo anche io. Avevo letto di usare quel modulo, ma non ero sicuro di quale sarebbe stato meglio quando (se c'è una differenza). – Lyise

1

LINQ è abbastanza ottimizzato e il suo output è lo stesso della scrittura manuale dei file. A proposito, i metodi di estensione sono molto più leggibili. Il vostro esempio sarà simile a questa:

public virtual bool checkActorsForCollision(Vector2 toCheck) 
{ 
    Vector2 floored = new Vector2((int)toCheck.X, (int)toCheck.Y); 

    // skip if this actor doesn't collide 
    if (!causingCollision) 
    { 
     return false; 
    } 

    return GamePlay.actors.Where(actor =>  
      (actor.causingCollision == true) 
      && actor.isAlive 
      // ignore offscreen collisions, we don't care about them 
      && (actor.location.X > GamePlay.onScreenMinimum.X) 
      && (actor.location.Y > GamePlay.onScreenMinimum.Y)   
      && (actor.location.X < GamePlay.onScreenMaximum.X) 
      && (actor.location.Y < GamePlay.onScreenMaximum.Y) 
      // ignore collisions with self 
      && (actor != this)) 
     .Select(actor => new Vector2((int)actor.location.X, (int)actor.location.Y)) 
     .Any(actorFloor => 
      (floored.X == actorfloor.X) 
      && (floored.Y == actorfloor.Y)); 
} 

Non c'è alcun motivo per non usare LINQ per "motivi di prestazioni" se non si è profilata l'applicazione.