// vim:foldmethod=marker:syntax=lpc:noexpandtab // $Id: master.c,v 1.143 2008/04/22 22:43:56 lynx Exp $ // // this is a simpler version of group/master, and it is actually in use // [actual as in effectively, not current, which is the german meaning] // // part of the implementation of PSYC multicasting.. send stuff back where // the _enter came from, send just one copy no matter how many have entered // from there. the group/slave will take care of the other side. // // group/master and group/slave are used for smaller size rooms, where each // person present should be visible. if you need a large scale multicast // solution, build up a link network using place/master and place/slave. // // see http://about.psyc.eu/Routing for the big picture on routing. // // should we rename the directories into net/context/master and slave, since a // context sounds more abstract than a group? i wouldn't say that a user's // context delivering presence subscriptions is a "group", but it certainly // is a "context." // local debug messages - turn them on by using psyclpc -DDcontext= #ifdef Dcontext # undef DEBUG # define DEBUG Dcontext #endif #include #include #include #include #ifdef CONTEXT_STATE // {{{ # define HEADER_ONLY # include "../state.c" # undef HEADER_ONLY #endif // }}} inherit NET_PATH "entity"; #ifdef HISTORY_COUNT int _ccount; // persistent! #endif // these data structures are allocated for every single place and user // as route is used by all of them to do presence or general smarticasting. #ifdef PERSISTENT_MASTERS mapping _routes; #else volatile mapping _routes, _u; #endif #ifdef CONTEXT_STATE // {{{ volatile mapping _costate, _cmemory; volatile mapping ctemp, cunused; #endif // }}} #ifdef PERSISTENT_SLAVES int revision; // persistent revision counter #endif stoned(); // used by /routes and /reload routes(recover) { if (recover) { PT(("%O recovering routes %O\n", ME, recover)) ASSERT("routes() recovering", mappingp(recover), recover) _routes = recover; } return _routes; } create() { _routes = ([ ]); #ifdef HISTORY_COUNT _ccount = 0; #endif #ifdef CONTEXT_STATE // {{{ _costate = ([ ]); ctemp = ([ ]); _cmemory = m_allocate(0, 2); #endif // }}} ::create(); } // der castmsg muss irgendwie anders.. sonst kriegen wir den state nicht hin. castmsg(source, mc, data, vars) { mixed route; mixed noa; // Nickname of local user Or Amount of people on route // in TIDILY mode should be Mapping of people on route #ifdef FORK // {{{ string buf; // as ldmud isn't a threading enviroment there shouldn't be a chance of // psycd getting destructed while we're residing in this function. // so we're ensuring its existence only once. #endif // }}} P2(("%O castmsg(%O,%O,%O..) for %O\n", ME, source,mc,data, _routes)) D3(P2(("%O vars = %O\n", ME, vars))) // _context is an MMP variable, so we use it internally with objectp vars["_context"] = ME; #ifdef PERSISTENT_SLAVES vars["_number_revision"] = revision; #endif // in theory.. m_delete(vars, "_source") but not necessary m_delete(vars, "_target"); #ifdef HISTORY_COUNT vars["_count"] = _ccount++; // wraps at MAXINT.. right? hehe #endif foreach (route, noa : _routes) { #if defined(TIDILY) && defined(MEMBERS_BY_SOURCE) P3(("place:each(%O,t=%O,s=%O,mc=%O,d=%O,v=%O)\n", mappingp(noa) ? sizeof(noa) : noa, route, source, mc, data, vars)) #else P3(("place:each(%O,t=%O,s=%O,mc=%O,d=%O,v=%O)\n", noa, route, source, mc, data, vars)) #endif // if (route == source) return; // skip sender if (!route) { if (stringp(noa)) { // object has been destructed // maybe there's a new one if (route = find_person(noa)) { #ifndef PERSISTENT_MASTERS #ifdef MEMBERS_BY_NICK _u[noa] = route; #else _u[route] = noa; #endif #else P1(("%O lost route to %O\n", ME, noa)) #endif } else { // why source? that's silly! call_out(#'stoned, 1, source, noa); continue; // oops! why was this a return; !??? } } else { // if (mappingp/intp(noa)) // noa contains (_amount_)members monitor_report("_failure_destruct_circuit", psyctext("[_nick_place] detected the loss " "of a circuit with [_amount_members] " "members.", ([ "_nick_place" : MYNICK, #if defined(TIDILY) && defined(MEMBERS_BY_SOURCE) "_amount_members" : sizeof(noa), #else "_amount_members" : noa, #endif ]))); m_delete(_routes, route); } } P4(("group/master sees vars as %O\n", vars)) // btw, fippo, could you please resume what happens if we // try not to copy(vars) ? i like docs in the middle of source // that give me a reason not to break it ;) if (objectp(route)) { route -> msg(source, mc, data, copy(vars)); } else { // gateways to other schemes please make their own // copy of the vars if they need to mess with them. sendmsg(route, mc, data, vars, source); } } return 1; } #if 0 //def PERSISTENT_MASTERS protected load(file) { ::load(file); DT(if (sizeof(_routes)) PP(("Found routes in %O: %O\n", ME, _routes || "nothing"));) } #endif #if defined(TIDILY) && defined(MEMBERS_BY_SOURCE) # define RM(SOURCE, ORIGIN) \ m_delete(_routes[ORIGIN], SOURCE); \ unless (sizeof(_routes[ORIGIN])) m_delete(_routes, ORIGIN); #else # define RM(SOURCE, ORIGIN) \ if (_routes[ORIGIN]) \ unless (--_routes[ORIGIN]) m_delete(_routes, ORIGIN); #endif remove_member(source, origin) { mixed t; P2(("%O remove_member(%O, %O)\n", ME, source, origin)) if (origin && ( #if 1 //defined(TIDILY) && defined(MEMBERS_BY_SOURCE) stringp(t = origin) || #endif t = origin->qOrigin()) #ifdef FORK && t += "/$cslave" #endif && member(_routes, t)) { RM(source, t); } else if (member(_routes, source)) { // local object or xmpp user m_delete(_routes, source); } else if (stringp(source)) { // guessing route from hostname of psyc address // try to avoid using remove_member() without origin // but this becomes necessary when we want to // remove an object or route manually mixed u; if (u = find_target_handler(source)) { t = u->qOrigin(); RM(source, t); P1(( "%O guessing origin %O for %O, successful? routes are %O\n", ME, t, source, _routes)) } else { t = 0; if (u = parse_uniform(source)) t = u[URoot]; unless (t) t = source; /* das hier geschah, als ich bei frischgestartetem server mit einem remote jabberisten die freundschaft zum user#fippo entfernte: EXCEPTION at line 185 of net/group/master.c in object net/psyc/user#fippo: Bad arg 1 to m_delete(): got 'number', expected 'mapping'. die datenstruktur war nicht initialisiert? da muss doch vorher schon an anderer stelle was schiefgelaufen sein...? */ RM(source, t); P1(( "%O parsing origin %O from %O, successful? routes are %O\n", ME, t, source, _routes)) } } else { // happens when doing /unfr. it's when the implied unfriend // comes back from the other side. P2(("%O encountered unnecessary remove of %O from %O\n", ME, source, _routes)) } } insert_member(source, origin) { mixed t; P2(("%O insert_member(%O, %O)\n", ME, source, origin)) if (objectp(origin) && (t = origin->qOrigin())) { #ifdef FORK t = t + "/$cslave"; #endif #if defined(TIDILY) && defined(MEMBERS_BY_SOURCE) unless (member(_routes, t)) _routes[t] = m_allocate(1, 0); m_add(_routes[t], source); #else _routes[t]++; #endif } else if (stringp(origin)) { #ifdef FORK origin = origin + "/$cslave"; #endif #if defined(TIDILY) && defined(MEMBERS_BY_SOURCE) unless (member(_routes, origin)) _routes[origin] = m_allocate(1, 0); m_add(_routes[origin], source); #else _routes[origin]++; #endif } else { #ifdef PERSISTENT_MASTERS _routes[source] = 1; // gimme the nick #else _routes[source] = 1; // castmsg expects the nick here. duh. // should we do that for objectp(source)? #endif } #ifdef PERSISTENT_SLAVES // would be nice to do it here, but that's not correct //revision += 1; #endif } // code duplicaton is faster than others #ifdef CONTEXT_STATE // {{{ // // code duplication is okay for enhanced efficiency, but please not // by mouse pasting. use #include "shared_code.i" please. TODO. // you can even ifdef small differences of the two versions! // int outstate(string target, string key, mixed value, int hascontext) { int mod; unless (hascontext) return ::outstate(target, key, value, hascontext); if (key[0] == '_') { mod = ':'; } else { mod = key[0]; key = key[1..]; } unless (ctemp) ctemp = ([ ]); unless (cunused) cunused = copy(_costate); m_delete(cunused, key); switch(mod) { case ':': if (_cmemory[key, 1] == -1) break; if (member(_cmemory, key)) { if (_cmemory[key] == value) { if (_cmemory[key, 1] == STATE_MAX2) { break; } if (_cmemory[key, 1] == STATE_MIN2) { ctemp["="+key] = value; _costate[key] = value; return 0; } _cmemory[key, 1]++; } else if (_cmemory[key, 1] - 1 == STATE_MIN2) { ctemp["="+key] = ""; m_add(_cmemory, key, value, 1); } else if (_cmemory[key, 1] <= STATE_MIN2) { m_add(_cmemory, key, value, 1); } else _cmemory[key, 1]--; } else m_add(_cmemory, key, value, 1); break; case '=': if ("" == value) { m_delete(_costate, key); break; } if (member(_costate, key) && _costate[key] == value) { return 0; } m_add(_cmemory, key, value, -1); _costate[key] = value; break; case '+': _augment(_costate, key, value); break; case '-': _diminish(_costate, key, value); break; default: raise_error("Illegal variable modifier in '" + key + "' encountered in group/master:outstate.\n"); return 0; } return 1; } mapping state(string target, int hascontext) { mapping t2 = ([ ]), t; unless (hascontext) return ::state(target, hascontext); if (cunused) { foreach (string key : cunused) { t2[":" + key] = ""; } cunused = 0; } t = ctemp; ctemp = ([ ]); // rock hard optimization unless (sizeof(t)) return t2; unless (sizeof(t2)) return t; return t2 + t; } #endif // }}}