Resources | developer.brewmp.com Resources | developer.brewmp.com

Developer

resources

Examples

The following examples are written in pseudocode, and are intended as a guide.

Basic example

Clients (observers) create Signals and pass them to the server (observed) for notification. For example:

IEnv_CreateInstance(me->piEnv, AEECLSID_SignalCBFactory,
         &piSignalCBFactory);

/* create a signal */
ISignalCBFactory_CreateSignal(piSignalCBFactory, me_Notify, me,
         &piSignal, &me->piSignalCtl); 

/* register my Signal with IMyServer */
IMyServer_OnChange(me->piMyServer, piSignal); 

/* don't need this anymore, IMyServer has it now */
ISignal_Release(piSignal); 

ISignalCBFactory_Release(piSignalCBFactory); 

The client holds the ISignalCtl, not the ISignal. Like AEECallbacks, signals can only be given to one service at a time. Signals are one-shot, but they're also single-registration. The server holds the signal until the client detaches it by calling ISignalCtl_Detach(), or as a side-effect of ISignalCtl_Release(). Instead of re-registering with IMyServer after each call to me_Notify, the client can call ISignalCtl_Enable() instead, as follows:

static void me_Notify(void* p)
{
   Me* me = (Me*)p;
 
   /* do IMyServer_OnChange() work */
   if (  ) {
      /* re-enable Signal given to IMyServer */
      ISignalCtl_Enable(me->piSignalCtl); 
   }
} 

In the client's destructor, the signal can be released as follows:

/* detach Signal given to IMyServer */
IQI_RELEASEIF(me->piSignalCtl);  

The server (observed) creates a SignalBus, places clients' (observers') signals on it, then calls ISignalBus methods to notify any clients, as follows:

In the server's constructor, the SignalBus can be created as follows:

IEnv_CreateInstance(me->piEnv, AEECLSID_SignalBus,
        &me->piSignalBus);

In the server's registration function, signals are added to the SignalBus as follows:

static int MyServer_OnChange(MyServer* me, ISignal* piSignal)
{
   /* add Signal to Bus */
   return ISignalBus_Add(me->piSignalBus, piSignal); 
} 

In the server's notification function, signals are set as follows:

static int MyServer_ChangeHappened(MyServer* me)
{
   /* set any enabled Signals on the Bus */
   return ISignalBus_Strobe(me->piSignalBus); 
} 

Advanced example

"Specific state" semantics of the observed: If the server's registration API looks more like OnStateReached() than OnStateChanged(), use a SignalBus to directly reflect the condition of the object having reached this state. For example, a FIFO would have two buses, one for each state (readable and writeable):

static int FIFO_Write(FIFO* me, const bytes* cpBytes, int nBytesLen)
{
   int nWrote = MIN(me->nBufLen - me->nWriteP, nBytesLen);
   memmove(me->pBuf + me->nWriteP, cpBytes, nWrote);
   me->nWriteP += nWrote;
   if (nWrote > 0) {
      ISignalBus_Set(me->piSignalBusReadable); /* data avail, set readable */
   }
   if (me->nWriteP >= me->nBufLen) {
      ISignalBus_Clear(me->piSignalBusWriteable); /* no room left, clear                                                        writable */
   }
   return nWrote;
}

static int FIFO_Read(FIFO* me, bytes* cpBytes, int nBytesLen)
{
   int nRead = MIN(me->nWriteP, nBytesLen);
   memmove(pbBytes, me->pBuf, nRead); /* do the read */
   memmove(me->pBuf, me->pBuf + nRead, nWriteP - nRead); /* shift data */
   me->nWriteP -= nRead;
   if (me->nWriteP < me->nBufLen) {
      ISignalBus_Set(me->piSignalBusWriteable); /* room left, set writeable */
   }
   if (me->nWriteP <= 0) {
      ISignalBus_Clear(me->piSignalBusReadable); /* no data left, clear                                                             readable */
   }
   return nRead;
}

The FIFO does not care whether anyone is watching. SignalBus takes care of it. Further, if the FIFO reaches the readable state, and later a client calls Readable() with an enabled Signal, the SignalBus automatically sets the Signal.

"Specific state" semantics, where the state is not directly known by server: In the last example, the FIFO knew directly when it was readable. This is not always the case. Sometimes the state of the object depends on the state of a sub-object.

Consider the case of an SSL socket layered on top of a TCP socket. When a client registers for readable on SSL, SSL must chain to the TCP socket's readable. Servers do not watch directly whether their clients' Signals are enabled. Further, leaving SSL's "TCP Readable" Signal enabled all the time will cause SSL to run a readable "hard loop" if SSL's client never calls Read() to clear the TCP readable. ISignalBus_OnEnable() can be called to discover when clients have enabled their signals.

int SSL_New(IEnv* piEnv, void** ppOut)
{
   SSL* me;
   
   nErr = IENV_ERRMALLOCREC(SSL,&me);
   ... ;
   IEnv_CreateInstance(piEnv, AEECLSID_SignalCBFactory, &piSignalFactory);
   /* go get a bus for readable */
   IEnv_CreateInstance(piEnv, AEECLSID_SignalBus, &me->piSignalBusReadable);
  
   /* get a signal for readable on-enable */
   ISignalCBFactory_CreateSignal(piSignalFactory, SSL_OnReadableEnable, me,
                                piSignal, &me->piSignalCtlOnReadableEnable);
   /* have Bus tell me when someone cares about readable */
   ISignalBus_OnEnable(me->piSignalBusReadable, piSignal);
   ISignal_Release(piSignal);
   /* get a signal for TCP readable */
   ISignalCBFactory_CreateSignal(piSignalFactory, SSL_TCPReadable, me,
                                 piSignal, &me->piSignalCtlTCPReadable);
   
   TCP_OnReadable(me->pTCP, piSignal);
   ISignal_Release(piSignal);
}

static void SSL_OnTCPReadable(SSL* me)
{
   /* my underlying TCP socket is readable, tell my clients */
   ISignalBus_Strobe(me->piSignalBusReadable);
}

static void SSL_OnReadableEnable(SSL* me)
{
   /* my clients are interested in my readable, register
      for my TCP socket's readable */
   ISignalCtl_Enable(me->piSignalCtlTCPReadable);
   /* re-enable the signal that just fired */
   ISignalCtl_Enable(me->piSignalCtlOnReadableEnable);
}