37

Qual è un buon modo di trattare gli oggetti e di farli parlare tra loro?Oggetti di gioco che parlano tra loro

Fino ad ora tutti i miei giochi di hobby/studente sono stati piccoli, quindi questo problema è stato generalmente risolto in un modo piuttosto brutto, che porta a una stretta integrazione e dipendenze circolari. Il che andava bene per la dimensione dei progetti che stavo facendo.

Tuttavia i miei progetti sono diventati più grandi in termini di dimensioni e complessità e ora voglio iniziare a riutilizzare il codice e rendere la mia testa un posto più semplice.

Il problema principale che ho è generalmente lungo le linee di Player bisogno di conoscere il Map e così fa il Enemy, questo di solito ha disceso nel impostando un sacco di puntatori e avere un sacco di dipendenze, e questo diventa un pasticcio in fretta.

Ho pensato lungo le linee di un sistema di stile di messaggio. ma non vedo davvero come questo riduca le dipendenze, dato che manderei ancora i puntatori ovunque.

PS: Immagino che questo sia stato discusso prima, ma non so come si chiami, solo il bisogno che ho.

risposta

41

MODIFICA: Qui di seguito descrivo un sistema di messaggistica di eventi di base che ho usato ripetutamente .. E mi sono reso conto che entrambi i progetti scolastici sono open source e sul web. Puoi trovare la seconda versione di questo sistema di messaggistica (e un po 'di più) a http://sourceforge.net/projects/bpfat/ .. Divertiti e leggi sotto per una descrizione più dettagliata del sistema!

Ho scritto un sistema di messaggistica generico e l'ho introdotto in una manciata di giochi che sono stati rilasciati sulla PSP e in alcuni software applicativi di livello enterprise. Il punto del sistema di messaggistica consiste nel trasmettere solo i dati necessari per elaborare un messaggio o un evento, a seconda della terminologia che si desidera utilizzare, in modo che gli oggetti non debbano conoscere l'uno dell'altro.

A rapida corsa verso il basso della lista degli oggetti utilizzati per raggiungere questo obiettivo è qualcosa sulla falsariga di:

struct TEventMessage 
{ 
    int _iMessageID; 
} 

class IEventMessagingSystem 
{ 
    Post(int iMessageId); 
    Post(int iMessageId, float fData); 
    Post(int iMessageId, int iData); 
    // ... 
    Post(TMessageEvent * pMessage); 
    Post(int iMessageId, void * pData); 
} 

typedef float(*IEventMessagingSystem::Callback)(TEventMessage * pMessage); 

class CEventMessagingSystem 
{ 
    Init  (); 
    DNit  (); 
    Exec  (float fElapsedTime); 

    Post  (TEventMessage * oMessage); 

    Register (int iMessageId, IEventMessagingSystem* pObject, FObjectCallback* fpMethod); 
    Unregister (int iMessageId, IEventMessagingSystem* pObject, FObjectCallback * fpMethod); 
} 

#define MSG_Startup   (1) 
#define MSG_Shutdown   (2) 
#define MSG_PlaySound   (3) 
#define MSG_HandlePlayerInput (4) 
#define MSG_NetworkMessage  (5) 
#define MSG_PlayerDied   (6) 
#define MSG_BeginCombat  (7) 
#define MSG_EndCombat   (8) 

Ed ora un po 'di una spiegazione. Il primo oggetto, TEventMessage, è l'oggetto di base per rappresentare i dati inviati dal sistema di messaggistica. Di default avrà sempre l'ID del messaggio inviato, quindi se vuoi essere sicuro di aver ricevuto un messaggio che ti aspettavi, puoi farlo (in genere lo faccio solo nel debug).

Il prossimo è la classe di interfaccia che fornisce un oggetto generico per il sistema di messaggistica da utilizzare per la trasmissione durante le richiamate. Inoltre, questo fornisce anche un'interfaccia 'facile da usare' per Post() in diversi tipi di dati al sistema di messaggistica.

Dopodiché abbiamo il nostro Callback typedef, Simply put si aspetta un oggetto del tipo della classe di interfaccia e passerà lungo un puntatore TEventMessage ... Opzionalmente puoi rendere il parametro const ma Ive ha usato l'elaborazione di trickle up prima per cose come lo stack debugging e tale sistema di messaggistica.

Ultimo e al centro è l'oggetto CEventMessagingSystem. Questo oggetto contiene una serie di stack di oggetti di callback (o elenchi o code collegati o comunque che si desidera memorizzare i dati). Gli oggetti di callback, non mostrati sopra, devono mantenere (e sono definiti in modo univoco da) un puntatore all'oggetto così come il metodo da chiamare su quell'oggetto. Quando si registra() si aggiunge una voce sullo stack di oggetti sotto la posizione dell'array id del messaggio. Quando annulli la registrazione() rimuovi quella voce.

Questo è fondamentalmente .. Ora questo ha la clausola che tutto ciò che deve sapere su IEventMessagingSystem e l'oggetto TEventMessage ...ma questo oggetto non dovrebbe cambiare così spesso e passa solo le parti di informazione che sono vitali per la logica dettata dall'evento chiamato. In questo modo un giocatore non ha bisogno di sapere sulla mappa o sul nemico direttamente per l'invio di eventi. Un oggetto gestito può anche chiamare un'API in un sistema più grande, senza bisogno di sapere nulla a riguardo.

Ad esempio: quando un nemico muore, si desidera che riproduca un effetto sonoro. Supponendo che tu abbia un sound manager che eredita l'interfaccia IEventMessagingSystem, devi impostare un callback per il sistema di messaggistica che accetti un TEventMessagePlaySoundEffect o qualcosa del genere. Il Sound Manager dovrebbe quindi registrare questa richiamata quando gli effetti sonori sono abilitati (o annullare la registrazione della richiamata quando si desidera silenziare tutti gli effetti sonori per abilitare/disabilitare le abilità). Successivamente, l'oggetto nemico dovrebbe ereditare anche da IEventMessagingSystem, mettere insieme un oggetto TEventMessagePlaySoundEffect (sarebbe necessario MSG_PlaySound per il suo ID messaggio e quindi l'ID dell'effetto sonoro da riprodurre, sia esso un ID int o il nome del suono effetto) e chiama semplicemente Post (& oEventMessagePlaySoundEffect).

Ora questo è solo un design molto semplice senza implementazione. Se hai esecuzione immediata, non hai bisogno di bufferizzare gli oggetti TEventMessage (cosa che usavo principalmente nei giochi per console). Se ci si trova in un ambiente multi-thread, questo è un modo molto ben definito per oggetti e sistemi in esecuzione in thread separati per comunicare tra loro, ma si desidera conservare gli oggetti TEventMessage in modo che i dati siano disponibili durante l'elaborazione.

Un'altra modifica è per gli oggetti che necessitano solo di dati Post(), è possibile creare un set statico di metodi in IEventMessagingSystem in modo che non debbano ereditarli da essi (che viene utilizzato per facilità di accesso e capacità di callback , non -direttamente- necessario per le chiamate Post()).

Per tutte le persone che citano MVC, è uno schema molto valido, ma è possibile implementarlo in così tanti modi diversi e a diversi livelli. L'attuale progetto su cui sto lavorando professionalmente è un setup MVC circa 3 volte, c'è il MVC globale dell'intera applicazione e quindi progettiamo ogni MV e C è anche un pattern MVC autonomo. Quindi, quello che ho provato a fare qui è spiegato come creare una C che sia abbastanza generica da gestire praticamente qualsiasi tipo di M senza la necessità di entrare in una vista ...

Ad esempio, un oggetto quando "muore" potrebbe voler giocare un effetto sonoro .. Dovresti creare una struct per il Sound System come TEventMessageSoundEffect che eredita dal TEventMessage e aggiunge un ID effetto sonoro (sia esso un Int precaricato, o il nome del file sfx, ma sono tracciati nel tuo sistema). Quindi tutto l'oggetto deve solo mettere insieme un oggetto TEventMessageSoundEffect con il rumore della morte appropriato e chiamare Post (& oEventMessageSoundEffect); oggetto .. Supponendo che il suono non sia disattivato (cosa vorresti annullare la registrazione dei Sound Man.

MODIFICA: Per chiarire un po 'il commento seguente: Qualsiasi oggetto per inviare o ricevere un messaggio ha solo bisogno di conoscere l'interfaccia IEventMessagingSystem e questo è l'unico oggetto che EventMessagingSystem deve conoscere di tutti gli altri oggetti.Questo è ciò che ti dà il distacco.Qualsiasi oggetto che vuole ricevere un messaggio semplicemente Registra (MSG, Object, Callback) per Quindi, quando un oggetto chiama Post (MSG, Data), lo invia a EventMessagingSystem tramite l'interfaccia di cui è a conoscenza, l'EMS notificherà quindi ogni oggetto registrato dell'evento. Si può fare un MSG_PlayerDied che altri sistemi gestiscono, oppure il giocatore può chiamare MSG_PlaySound, MSG_Respawn, ecc. per lasciare che le cose in ascolto di quei messaggi agiscano su di loro. Posta (MSG, Data) come API astratta ai diversi sistemi all'interno di un motore di gioco.

Oh! Un'altra cosa che mi è stata indicata ... Il sistema che descrivo sopra si adatta al modello di Observer nell'altra risposta data. Quindi se vuoi una descrizione più generale per rendere il mio un po 'più sensato, questo è un breve articolo che gli dà una buona descrizione.

Spero che questo aiuti e divertiti!

+1

+1 per la spiegazione approfondita, ma ho anche un commento: hai affermato che * un giocatore non ha bisogno di sapere sulla mappa * per inviare eventi ad esso, ma il tuo esempio implica che un nemico morente deve sapere su ogni altro parte del programma che deve essere notificato. Mi sarei aspettato che mandasse semplicemente una sorta di messaggio "Sono appena morto" e poi permetti al tuo sistema di messaggistica di informare gli ascoltatori interessati a questo evento (suono di riproduzione, punteggio di aggiornamento, ecc.). In questo modo sembra che ogni entità abbia bisogno di inviare un gruppo di messaggi per un singolo evento (suono di riproduzione, aumento del punteggio). O ho capito male? – Groo

+1

@Groo Non sono riuscito ad accorciare abbastanza la mia risposta, quindi l'ho modificata nella mia risposta sopra. – James

+0

Ciao amico, sono passati più di 5 anni dalla tua risposta, ma questo post è venuto fuori quando stavo cercando una semplice idea di pubsub, e devo dire che ho scaricato le fonti, oltre agli standard di codifica che ho Non sono abituato e il fatto che C++ sia avanzato un po 'dal 2005, il codice è molto molto interessante da ricercare e ho usato alcuni scheletri di EMS per il mio gioco in C#. Sembra davvero incredibile e difficile quello che voi tre ragazzi avete fatto, e spero di saperne di più! –

4

Questo probabilmente non si applica solo alle classi di gioco, ma alle classi in senso generale. il modello MVC (model-view-controller) insieme alla tua message pump suggerita è tutto ciò di cui hai bisogno.

"Nemico" e "Player" probabilmente si inseriscono nella parte Modello di MVC, non importa molto, ma la regola generale è che tutti i modelli e le viste interagiscono tramite il controller. Quindi, vorrai mantenere i riferimenti (meglio dei puntatori) a (quasi) tutte le altre istanze di classe da questa classe 'controller', chiamiamola ControlDispatcher. Aggiungi un messaggio pump (varia a seconda della piattaforma che stai codificando), crea un'istanza in primo luogo (prima di qualsiasi altra classe e fa in modo che gli altri oggetti ne facciano parte) o infine (e abbia gli altri oggetti memorizzati come riferimenti in ControlDispatcher).

Ovviamente, la classe ControlDispatcher dovrà probabilmente essere suddivisa ulteriormente in controller più specializzati solo per mantenere il codice per file attorno alle 700-800 linee (questo è il limite per me almeno) e potrebbe anche avere più thread di pompaggio ed elaborazione dei messaggi a seconda delle esigenze.

Acclamazioni

+0

+1 Non c'è bisogno di reinventare roba, sono d'accordo. – Groo

-1

@kellogs suggerimento del MVC è valido, e utilizzato in alcuni giochi, anche se la sua molto più comune nelle applicazioni web e dei quadri. Potrebbe essere eccessivo e troppo per questo.

Vorrei ripensare il tuo design, perché il giocatore ha bisogno di parlare con i nemici? Non potrebbero entrambi ereditare da una classe di attori? Perché gli attori devono parlare alla mappa?

Mentre leggo quello che ho scritto, inizia ad inserirsi in un framework MVC ... Ovviamente ho fatto troppe binari ultimamente. Tuttavia, sarei disposto a scommettere, hanno solo bisogno di sapere cose come, sono in collisione con un altro attore, e hanno una posizione, che comunque dovrebbe essere relativa alla Mappa.

Ecco un'implementazione di Asteroids su cui ho lavorato. Il tuo gioco potrebbe essere, e probabilmente lo è, complesso.

+0

Player and Enemy È necessario conoscere la mappa per navigare, era solo un esempio semplificato. –

15

le soluzioni generiche per la comunicazione tra oggetti evitando accoppiamento stretto:

  1. Mediator pattern
  2. Observer pattern
+1

Il pattern del mediatore è proprio lì nel MVC (dove il controller è il mediatore). +1 per modello Observer.Utilizzato pesantemente in alcune piattaforme. – kellogs

+0

Hmmm .. Dall'articolo a cui sei collegato, 'Relationship Manager' sembra un po 'puzzolente a prima vista, sembra un oggetto divino. Dovrebbe essere una sorta di singleton, che sa tutto di tutti. L'articolo mostra i metodi membri dei singoli oggetti ('Customer.AddOrder',' Customer.RemoveOrder') esponendo i loro componenti interni al "gestore" e consentendo al manager di eseguire il lavoro per loro. Dove è andato allora l'OOP? Inoltre, per testare l'aggiunta di un singolo ordine a un cliente, si suppone che si derubi l'intera classe manager. Preferirei che tu mantenessi solo i primi due link. – Groo

+0

Bella osservazione di te. Rimuovo il link ;-). –

0

stare attenti con "un sistema di stile del messaggio", probabilmente dipende dalla realizzazione, ma di solito perderai il controllo di tipo statico e potrai quindi eseguire alcuni errori molto difficili da eseguire il debug. Si noti che i metodi dell'oggetto chiamante sono già un sistema simile a un messaggio.

Probabilmente manchi semplicemente alcuni livelli di astrazione, ad esempio per la navigazione un giocatore può utilizzare un navigatore invece di sapere tutto sulla mappa stessa. Dici anche che this has usually descended into setting lots of pointers, quali sono quei puntatori? Probabilmente, li stai dando ad un'astrazione sbagliata? .. Far conoscere oggetti agli altri direttamente, senza passare attraverso interfacce e intermediari, è un modo semplice per ottenere un design strettamente accoppiato.

+0

Sì, li ho assegnati direttamente, il che credo sia il mio problema. –

0

La messaggistica è sicuramente un ottimo modo per andare, ma i sistemi di messaggistica possono avere molte differenze. Se vuoi mantenere le tue classi belle e pulite, scrivile per ignorare un sistema di messaggistica e invece fagli prendere dipendenze da qualcosa di semplice come un "ILocationService" che può essere implementato per pubblicare/richiedere informazioni da cose come la classe Map . Mentre finirai con più lezioni, saranno piccole, semplici e incoraggeranno un design pulito.

La messaggistica è più che un semplice disaccoppiamento, ma consente anche di passare a un'architettura più asincrona, concorrente e reattiva. Patterns of Enterprise Integration di Gregor Hophe è un grande libro che parla di buoni schemi di messaggistica. Erlang OTP o l'implementazione di Scala del modello degli attori mi hanno fornito molte indicazioni.

3

Ecco un sistema di eventi accurato scritto per C++ 11 che è possibile utilizzare. Usa modelli e puntatori intelligenti e lambda per i delegati. È molto flessibile Qui sotto troverai anche un esempio. Mandami una email a [email protected] se hai domande a riguardo.

Ciò che queste classi offrono è un modo per inviare eventi con dati arbitrari ad essi associati e un modo semplice per associare direttamente funzioni che accettano tipi di argomenti già convertiti che il sistema esegue e verifica la conversione corretta prima di chiamare il delegato.

In pratica, ogni evento deriva dalla classe IEventData (è possibile chiamarla IEvent se lo si desidera). Ogni "frame" che si chiama ProcessEvents() a quel punto il sistema di eventi scorre attraverso tutti i delegati e chiama i delegati che sono stati forniti da altri sistemi che hanno sottoscritto ogni tipo di evento. Chiunque può scegliere a quali eventi vorrebbero iscriversi, poiché ogni tipo di evento ha un ID univoco. È inoltre possibile utilizzare lambda di sottoscrivere eventi come questo: addListener (MyEvent :: ID(), [&] (shared_ptr ev) { fare la tua cosa} ..

In ogni caso, qui è la classe con tutta l'attuazione :

#pragma once 

#include <list> 
#include <memory> 
#include <map> 
#include <vector> 
#include <functional> 

class IEventData { 
public: 
    typedef size_t id_t; 
    virtual id_t GetID() = 0; 
}; 

typedef std::shared_ptr<IEventData> IEventDataPtr; 
typedef std::function<void(IEventDataPtr&)> EventDelegate; 

class IEventManager { 
public: 
    virtual bool AddListener(IEventData::id_t id, EventDelegate proc) = 0; 
    virtual bool RemoveListener(IEventData::id_t id, EventDelegate proc) = 0; 
    virtual void QueueEvent(IEventDataPtr ev) = 0; 
    virtual void ProcessEvents() = 0; 
}; 


#define DECLARE_EVENT(type) \ 
    static IEventData::id_t ID(){ \ 
     return reinterpret_cast<IEventData::id_t>(&ID); \ 
    } \ 
    IEventData::id_t GetID() override { \ 
     return ID(); \ 
    }\ 

class EventManager : public IEventManager { 
public: 
    typedef std::list<EventDelegate> EventDelegateList; 

    ~EventManager(){ 
    } 
    //! Adds a listener to the event. The listener should invalidate itself when it needs to be removed. 
    virtual bool AddListener(IEventData::id_t id, EventDelegate proc) override; 

    //! Removes the specified delegate from the list 
    virtual bool RemoveListener(IEventData::id_t id, EventDelegate proc) override; 

    //! Queues an event to be processed during the next update 
    virtual void QueueEvent(IEventDataPtr ev) override; 

    //! Processes all events 
    virtual void ProcessEvents() override; 
private: 
    std::list<std::shared_ptr<IEventData>> mEventQueue; 
    std::map<IEventData::id_t, EventDelegateList> mEventListeners; 

}; 

//! Helper class that automatically handles removal of individual event listeners registered using OnEvent() member function upon destruction of an object derived from this class. 
class EventListener { 
public: 
    //! Template function that also converts the event into the right data type before calling the event listener. 
    template<class T> 
    bool OnEvent(std::function<void(std::shared_ptr<T>)> proc){ 
     return OnEvent(T::ID(), [&, proc](IEventDataPtr data){ 
      auto ev = std::dynamic_pointer_cast<T>(data); 
      if(ev) proc(ev); 
     }); 
    } 
protected: 
    typedef std::pair<IEventData::id_t, EventDelegate> _EvPair; 
    EventListener(std::weak_ptr<IEventManager> mgr):_els_mEventManager(mgr){ 

    } 
    virtual ~EventListener(){ 
     if(_els_mEventManager.expired()) return; 
     auto em = _els_mEventManager.lock(); 
     for(auto i : _els_mLocalEvents){ 
      em->RemoveListener(i.first, i.second); 
     } 
    } 

    bool OnEvent(IEventData::id_t id, EventDelegate proc){ 
     if(_els_mEventManager.expired()) return false; 
     auto em = _els_mEventManager.lock(); 
     if(em->AddListener(id, proc)){ 
      _els_mLocalEvents.push_back(_EvPair(id, proc)); 
     } 
    } 
private: 
    std::weak_ptr<IEventManager> _els_mEventManager; 
    std::vector<_EvPair>  _els_mLocalEvents; 
    //std::vector<_DynEvPair> mDynamicLocalEvents; 
}; 

e il file cpp:.

#include "Events.hpp" 

using namespace std; 

bool EventManager::AddListener(IEventData::id_t id, EventDelegate proc){ 
    auto i = mEventListeners.find(id); 
    if(i == mEventListeners.end()){ 
     mEventListeners[id] = list<EventDelegate>(); 
    } 
    auto &list = mEventListeners[id]; 
    for(auto i = list.begin(); i != list.end(); i++){ 
     EventDelegate &func = *i; 
     if(func.target<EventDelegate>() == proc.target<EventDelegate>()) 
      return false; 
    } 
    list.push_back(proc); 
} 

bool EventManager::RemoveListener(IEventData::id_t id, EventDelegate proc){ 
    auto j = mEventListeners.find(id); 
    if(j == mEventListeners.end()) return false; 
    auto &list = j->second; 
    for(auto i = list.begin(); i != list.end(); ++i){ 
     EventDelegate &func = *i; 
     if(func.target<EventDelegate>() == proc.target<EventDelegate>()) { 
      list.erase(i); 
      return true; 
     } 
    } 
    return false; 
} 

void EventManager::QueueEvent(IEventDataPtr ev) { 
    mEventQueue.push_back(ev); 
} 

void EventManager::ProcessEvents(){ 
    size_t count = mEventQueue.size(); 
    for(auto it = mEventQueue.begin(); it != mEventQueue.end(); ++it){ 
     printf("Processing event..\n"); 
     if(!count) break; 
     auto &i = *it; 
     auto listeners = mEventListeners.find(i->GetID()); 
     if(listeners != mEventListeners.end()){ 
      // Call listeners 
      for(auto l : listeners->second){ 
       l(i); 
      } 
     } 
     // remove event 
     it = mEventQueue.erase(it); 
     count--; 
    } 
} 

io uso una classe EventListener per motivi di convenienza come classe base per qualsiasi classe che vorrebbe ascoltare eventi Se si deriva la classe di ascolto da questa classe e fornirla w Con il tuo gestore eventi, puoi utilizzare la comoda funzione OnEvent (..) per registrare i tuoi eventi. E la classe base annuncerà automaticamente la tua classe derivata da tutti gli eventi quando viene distrutta. Questo è molto comodo poiché dimenticando di rimuovere un delegato dal gestore eventi quando la tua classe viene distrutta quasi certamente causerà il crash del tuo programma.

Un modo semplice per ottenere un ID di tipo univoco per un evento semplicemente dichiarando una funzione statica nella classe e quindi lanciando il suo indirizzo in un int. Poiché ogni classe avrà questo metodo su indirizzi diversi, può essere utilizzata per l'identificazione univoca degli eventi di classe. Puoi anche lanciare typename() su un int per ottenere un id univoco, se lo desideri. Ci sono diversi modi per farlo.

ecco un esempio su come utilizzare questo:

#include <functional> 
#include <memory> 
#include <stdio.h> 
#include <list> 
#include <map> 

#include "Events.hpp" 
#include "Events.cpp" 

using namespace std; 

class DisplayTextEvent : public IEventData { 
public: 
    DECLARE_EVENT(DisplayTextEvent); 

    DisplayTextEvent(const string &text){ 
     mStr = text; 
    } 
    ~DisplayTextEvent(){ 
     printf("Deleted event data\n"); 
    } 
    const string &GetText(){ 
     return mStr; 
    } 
private: 
    string mStr; 
}; 

class Emitter { 
public: 
    Emitter(shared_ptr<IEventManager> em){ 
     mEmgr = em; 
    } 
    void EmitEvent(){ 
     mEmgr->QueueEvent(shared_ptr<IEventData>(
      new DisplayTextEvent("Hello World!"))); 
    } 
private: 
    shared_ptr<IEventManager> mEmgr; 
}; 

class Receiver : public EventListener{ 
public: 
    Receiver(shared_ptr<IEventManager> em) : EventListener(em){ 
     mEmgr = em; 

     OnEvent<DisplayTextEvent>([&](shared_ptr<DisplayTextEvent> data){ 
      printf("It's working: %s\n", data->GetText().c_str()); 
     }); 
    } 
    ~Receiver(){ 
     mEmgr->RemoveListener(DisplayTextEvent::ID(), std::bind(&Receiver::OnExampleEvent, this, placeholders::_1)); 
    } 
    void OnExampleEvent(IEventDataPtr &data){ 
     auto ev = dynamic_pointer_cast<DisplayTextEvent>(data); 
     if(!ev) return; 
     printf("Received event: %s\n", ev->GetText().c_str()); 
    } 
private: 
    shared_ptr<IEventManager> mEmgr; 
}; 

int main(){ 
    auto emgr = shared_ptr<IEventManager>(new EventManager()); 


    Emitter emit(emgr); 
    { 
     Receiver receive(emgr); 

     emit.EmitEvent(); 
     emgr->ProcessEvents(); 
    } 
    emit.EmitEvent(); 
    emgr->ProcessEvents(); 
    emgr = 0; 

    return 0; 
} 
Problemi correlati