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 }