wvcont.cc

00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  *
00005  * WvCont provides "continuations", which are apparently also known as
00006  * semi-coroutines.  See wvcont.h for more details.
00007  */
00008 #include "wvcont.h"
00009 #include "wvtask.h"
00010 #include <assert.h>
00011  
00012 // private data that doesn't need to be in the header
00013 struct WvCont::Data
00014 {
00015     int links;          // the refcount of this Data object
00016     int mydepth;        // this task's depth in the call stack
00017     bool finishing;     // true if we're trying to terminate this task ASAP
00018                         //     (generally because WvCont is being destroyed)
00019     size_t stacksize;
00020     WvTaskMan *taskman;
00021     WvTask *task;
00022     
00023     WvContCallback cb;        // the callback we want to call inside our WvTask
00024     void *ret;
00025     void *p1;
00026     
00027     Data(const WvContCallback &_cb, size_t _stacksize) : cb(_cb)
00028         { links = 1; finishing = false; stacksize = _stacksize; mydepth = 0;
00029              taskman = WvTaskMan::get(); 
00030              task = NULL; report(); }
00031     ~Data();
00032 
00033     void link()
00034         { links++; report(); }
00035     void unlink()
00036         { links--; report(); if (!links) delete this; }
00037     
00038     void report()
00039         { /* printf("%p: links=%d\n", this, links); */ }
00040 };
00041 
00042 
00043 WvCont::Data *WvCont::curdata = NULL;
00044 int WvCont::taskdepth = 0;
00045 
00046 
00047 WvCont::WvCont(const WvCont &cb)
00048 {
00049     data = cb.data;
00050     data->link();
00051 }
00052 
00053 
00054 WvCont::WvCont(const WvContCallback &cb, unsigned long _stacksize)
00055 {
00056     data = new Data(cb, (size_t)_stacksize);
00057 }
00058 
00059 
00060 WvCont::WvCont(Data *data)
00061 {
00062     this->data = data;
00063     data->link();
00064 }
00065 
00066 
00067 WvCont::~WvCont()
00068 {
00069     if (data->links == 1) // I'm the last link, and it's not currently running
00070     {
00071         data->finishing = true;
00072         data->p1 = NULL; // don't re-pass invalid data
00073         while (data->task && data->task->isrunning())
00074             call();
00075     }
00076     
00077     data->unlink();
00078 }
00079 
00080 
00081 WvCont::Data::~Data()
00082 {
00083     assert(!links);
00084     
00085     if (task)
00086         task->recycle();
00087     taskman->unlink();
00088     //printf("%p: deleting\n", this);
00089     report();
00090 }
00091 
00092 
00093 // note: assumes data->task is already running!
00094 void *WvCont::_call(Data *data)
00095 {
00096     Data *olddata = curdata;
00097     curdata = data;
00098     data->link(); // don't delete this context while it's running!
00099     
00100     // enforce the call stack.  If we didn't do this, a yield() five calls
00101     // deep would return to the very top, rather to the second-innermost
00102     // context.
00103     // 
00104     // Note that this implementation has the interesting side-effect of
00105     // short-circuiting recursion (a calls b, b calls c, c calls a), since
00106     // calling 'a' if it's already running means the same as "yield all the
00107     // way back to a", and this loop enforces one-level-at-a-time yielding.
00108     // 
00109     // Because that behaviour is probably undesirable, we make 'mydepth' into
00110     // a member variable instead of just putting it on the stack.  This is
00111     // only needed so that we can have the assert().
00112     assert(!data->mydepth);
00113     data->mydepth = ++taskdepth;
00114     do
00115     {
00116         assert(data->task);
00117         do
00118         {
00119             data->taskman->run(*data->task);
00120             if (data->links == 1)
00121             {
00122                 data->finishing = true; // make WvCont::isok() false
00123                 data->p1 = NULL; // don't re-pass invalid data
00124             }
00125         } while (data->finishing && data->task && data->task->isrunning());
00126         assert(data->links);
00127     } while (taskdepth > data->mydepth);
00128     assert(taskdepth == data->mydepth);
00129     taskdepth--;
00130     data->mydepth = 0;
00131 
00132     void *ret = data->ret;
00133     data->unlink();
00134     curdata = olddata;
00135     return ret;
00136 }
00137 
00138 
00139 void *WvCont::operator() (void *p1)
00140 {
00141     data->ret = reinterpret_cast<void*>(-42);
00142     
00143     if (!data->task)
00144         data->task = data->taskman->start("wvcont", bouncer, data,
00145                                           data->stacksize);
00146     else if (!data->task->isrunning())
00147         data->task->start("wvcont+", bouncer, data);
00148 
00149     assert(data->task);
00150     
00151     data->p1 = p1;
00152     return call();
00153 }
00154 
00155 
00156 WvCont WvCont::current()
00157 {
00158     assert(curdata);
00159     assert(curdata->task == curdata->taskman->whoami());
00160     assert(isok()); // this assertion is a bit aggressive...
00161     return WvCont(curdata);
00162 }
00163 
00164 
00165 void *WvCont::yield(void *ret)
00166 {
00167     assert(curdata);
00168     assert(curdata->task == curdata->taskman->whoami());
00169     
00170     // this assertion is a bit aggressive, but on purpose; a callback that
00171     // does yield() instead of returning when its context should be dying
00172     // is pretty badly behaved.
00173     assert(isok());
00174     
00175     curdata->ret = ret;
00176     curdata->taskman->yield();
00177     return curdata->p1;
00178 }
00179 
00180 
00181 bool WvCont::isok()
00182 {
00183     // if we're not using WvCont, it's not okay to yield
00184     if (!curdata)
00185         return false;
00186 
00187     assert(curdata->task == curdata->taskman->whoami());
00188     return !curdata->finishing;
00189 }
00190 
00191 
00192 void WvCont::bouncer(void *userdata)
00193 {
00194     Data *data = (Data *)userdata;
00195     
00196     // DON'T BE FOOLED!
00197     // all yield() calls stay inside the inner function; our return value
00198     // is only for the final run after data->cb() returns.
00199     data->ret = data->cb(data->p1);
00200 }

Generated on Mon Feb 5 10:54:29 2007 for WvStreams by  doxygen 1.5.1