Buona domanda! Prima di arrivare alle domande specifiche che mi hai posto, dirò: non sottovalutare il potere della semplicità. Tenpn ha ragione. Tieni presente che tutto ciò che stai cercando di fare con questi approcci è trovare un modo elegante per rinviare una chiamata di funzione o disaccoppiare il chiamante dalla chiamata. Posso raccomandare le coroutine come un modo sorprendentemente intuitivo per alleviare alcuni di questi problemi, ma è un po 'fuori tema. A volte, stai meglio chiamando semplicemente la funzione e vivendo con il fatto che l'entità A è accoppiata direttamente all'entità B. Vedi YAGNI.
Detto questo, ho usato ed ero soddisfatto del modello di segnale / slot combinato con il semplice passaggio di messaggi. L'ho usato in C ++ e Lua per un titolo iPhone abbastanza riuscito che aveva un programma molto stretto.
Per il caso segnale / slot, se voglio che l'entità A faccia qualcosa in risposta a qualcosa che l'entità B ha fatto (es. Sbloccare una porta quando qualcosa muore) potrei avere l'entità A iscriversi direttamente all'evento di morte dell'entità B. O forse l'entità A si abbonerebbe a ciascuno di un gruppo di entità, incrementerebbe un segnalino su ogni evento sparato e sbloccherebbe la porta dopo la morte di N di esse. Inoltre, "gruppo di entità" e "N di esse" sarebbero in genere definiti dal progettista nei dati di livello. (A parte questo, questa è un'area in cui le coroutine possono davvero brillare, ad esempio WaitForMultiple ("Morire", entA, entB, entC); door.Unlock ();)
Ma ciò può diventare ingombrante quando si tratta di reazioni strettamente legate al codice C ++ o di eventi di gioco intrinsecamente effimeri: infliggere danni, ricaricare armi, debug, feedback AI basato sulla posizione guidato dal giocatore. È qui che il passaggio dei messaggi può colmare le lacune. In sostanza si riduce a qualcosa del tipo "dì a tutte le entità in quest'area di subire danni in 3 secondi" o "ogni volta che completi la fisica per capire chi ho sparato, dì loro di eseguire questa funzione di script". È difficile capire come farlo bene usando pubblica / abbonati o segnale / slot.
Questo può essere facilmente eccessivo (rispetto all'esempio di tenpn). Può anche essere gonfio inefficiente se hai molta azione. Ma nonostante i suoi inconvenienti, questo approccio "messaggi ed eventi" si combina molto bene con il codice di gioco con script (ad esempio in Lua). Il codice dello script può definire e reagire ai propri messaggi ed eventi senza preoccuparsi del codice C ++. E il codice dello script può facilmente inviare messaggi che attivano il codice C ++, come cambiare livello, riprodurre suoni o anche solo lasciare che un'arma stabilisca quanti danni fornisce il messaggio di TakeDamage. Mi ha fatto risparmiare un sacco di tempo perché non dovevo scherzare costantemente con Luabind. E mi ha permesso di conservare tutto il mio codice luabind in un posto, perché non ce n'era molto. Se correttamente accoppiato,
Inoltre, la mia esperienza con il caso d'uso n. 2 è che è meglio gestirlo come evento nell'altra direzione. Invece di chiedere quale sia lo stato dell'entità, attiva un evento / invia un messaggio ogni volta che lo stato fa un cambiamento significativo.
In termini di interfacce, tra l'altro, ho finito con tre classi per implementare tutto questo: EventHost, EventClient e MessageClient. EventHosts crea slot, EventClients si abbona / si connette ad essi e MessageClients associa un delegato a un messaggio. Si noti che il target delegato di MessageClient non deve necessariamente essere lo stesso oggetto proprietario dell'associazione. In altre parole, MessageClients può esistere esclusivamente per inoltrare messaggi ad altri oggetti. FWIW, la metafora host / client è in qualche modo inappropriata. Source / Sink potrebbe essere concetti migliori.
Mi dispiace, ho vagato un po 'lì. È la mia prima risposta :) Spero che abbia senso.