summaryrefslogtreecommitdiffstats
path: root/cacheserver/friend.cpp
diff options
context:
space:
mode:
authorkcwu <kcwu@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2006-03-13 00:07:51 +0800
committerkcwu <kcwu@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2006-03-13 00:07:51 +0800
commit4c7f964f84820c2de93782c86fd67ff3d3fd19a1 (patch)
tree65f61437ae4ad552dd0aab1f7ba0b3b478912b1c /cacheserver/friend.cpp
parent172aadf47091fcbb431a9dd06aa8fadcac29257b (diff)
downloadpttbbs-4c7f964f84820c2de93782c86fd67ff3d3fd19a1.tar
pttbbs-4c7f964f84820c2de93782c86fd67ff3d3fd19a1.tar.gz
pttbbs-4c7f964f84820c2de93782c86fd67ff3d3fd19a1.tar.bz2
pttbbs-4c7f964f84820c2de93782c86fd67ff3d3fd19a1.tar.lz
pttbbs-4c7f964f84820c2de93782c86fd67ff3d3fd19a1.tar.xz
pttbbs-4c7f964f84820c2de93782c86fd67ff3d3fd19a1.tar.zst
pttbbs-4c7f964f84820c2de93782c86fd67ff3d3fd19a1.zip
rewrite core function of tumpserver, maintaining friend relation 300 timers faster.
git-svn-id: http://opensvn.csie.org/pttbbs/trunk/pttbbs@3286 63ad8ddf-47c3-0310-b6dd-a9e9d9715204
Diffstat (limited to 'cacheserver/friend.cpp')
-rw-r--r--cacheserver/friend.cpp352
1 files changed, 352 insertions, 0 deletions
diff --git a/cacheserver/friend.cpp b/cacheserver/friend.cpp
new file mode 100644
index 00000000..4f8e9663
--- /dev/null
+++ b/cacheserver/friend.cpp
@@ -0,0 +1,352 @@
+#define NDEBUG
+#include <algorithm>
+#include <cassert>
+
+// for some constant and type
+#include "bbs.h"
+
+/* for each login of user,
+ * input: my index, friend[MAX_FRIEND] of uid, reject[MAX_REJECT] of uid,
+ * for all my relation,
+ * output: his index, his uid, relation to me, relation to him
+ */
+/* 除了 user 及 utmp 之外, 全部的 ref index 都是雙向的, 確保 insert & delete O(1) */
+/* 當沒有人 refer 時則 resource recycle */
+
+typedef int Uid;
+typedef int Idx;
+
+
+struct Relation {
+ Uid him;
+ short him_offset;
+
+ Relation(Uid _him, short _him_offset=-1) :him(_him),him_offset(_him_offset) {}
+};
+
+template<class T>
+struct freelist {
+ static const int KEEP = 64;
+ static T* list[8][KEEP]; // 2^0~2^7
+ static int tail[8];
+
+#define IS_2xxN(a) (a && (a&(a-1))==0)
+ static T* alloc(int n) {
+ assert(n>0);
+ if(n<256 && IS_2xxN(n) && sizeof(T)*n<65536) {
+ int t=n;
+ int slot;
+ for(slot=0; t>1; t/=2)
+ slot++;
+ assert(0<=slot && slot<8);
+ if(tail[slot]) {
+ return list[slot][--tail[slot]];
+ }
+ }
+ return (T*)malloc(sizeof(T)*n);
+ }
+ static void free(T* p, int n) {
+ assert(n>0);
+ if(n<256 && IS_2xxN(n) && sizeof(T)*n<65536) {
+ int t=n;
+ int slot;
+ for(slot=0; t>1; t/=2)
+ slot++;
+ assert(0<=slot && slot<8);
+ if(tail[slot]<KEEP) {
+ list[slot][tail[slot]++]=p;
+ return;
+ }
+ }
+ ::free(p);
+ }
+};
+
+template<class T> T* freelist<T>::list[8][KEEP];
+template<class T> int freelist<T>::tail[8]={0};
+
+template<class T,int MIN_ROOM = 8, class S = int>
+struct myvector {
+ // 大致上想要 STL vector, 但 STL 的 capacity 只會增加不會縮小
+ // (後來發現, 以 online friend 來說, capacity 不會縮小其實沒什麼影響)
+ // 此外, pointer 在 64bit 機器上要 8bytes, basic overhead 8*3 bytes,
+ // 但我的資料量沒那麼大, 改用 S(int or short) 存 size & capacity 比較省
+ T *base;
+ S room, n;
+
+ myvector() :base(0),room(0),n(0) {}
+ ~myvector() {
+ clear();
+ }
+ S append(T data) {
+ if(room<n+1)
+ resizefor(n+1);
+ base[n++]=data;
+ return n-1;
+ }
+ void pop_back() {
+ assert(n>0);
+ n--;
+ resizefor(n);
+ }
+ void clear() {
+ n=0;
+ resizefor(n);
+ }
+ /*
+ T& operator[](int idx) {
+ return base[idx];
+ }
+ */
+
+ void resizefor(S size) {
+ assert(size>=n);
+ if(size==0) {
+ if(base) freelist<T>::free(base, room);
+ base=0;
+ room=0;
+ } else {
+ S origroom=room;
+ if(room==0)
+ room=MIN_ROOM;
+ while(room<size) room=S(room*2);
+ if(size<MIN_ROOM) size=MIN_ROOM;
+ while(room/2>size) room=S(room/2);
+ if(room!=origroom || base==0) {
+ //base=(T*)realloc(base, sizeof(T)*room);
+ T* tmp=freelist<T>::alloc(room);
+ assert(tmp);
+ if(n>0)
+ memcpy(tmp, base, sizeof(T)*n);
+ if(base!=0)
+ freelist<T>::free(base, origroom);
+ base=tmp;
+ }
+ assert(base);
+ }
+ }
+};
+
+template<class R,class B>
+struct RelationList: public myvector<Relation, 8, short> {
+ RelationList() :myvector<Relation, 8, short>() {}
+ void add(Uid me, Uid him) {
+ assert(me!=him);
+ RelationList<B,R>& bl=R::backlist(him);
+ short me_offset=append(Relation(him));
+ short him_offset=bl.append(Relation(me,me_offset));
+
+ setbackoffset(me_offset,him_offset);
+ assert(bl.base[him_offset].him==me);
+ assert(bl.base[him_offset].him_offset==me_offset);
+ }
+ void deleteall(Uid me) {
+ for(int i=0; i<n; i++) {
+ RelationList<B,R>& bl=R::backlist(base[i].him);
+ assert(bl.base[base[i].him_offset].him==me);
+ assert(bl.base[base[i].him_offset].him_offset==i);
+ bl.delete_half(base[i].him_offset);
+ //try_recycle(base[i].him); // dirty
+ }
+ clear();
+ }
+ public:
+ void setbackoffset(short which,short offset) {
+ assert(0<=which && which<n);
+ base[which].him_offset=offset;
+ }
+ void delete_half(short offset) {
+ assert(0<=offset && offset<n);
+ if(offset<n-1) {
+ base[offset]=base[n-1];
+ R::backlist(base[offset].him).setbackoffset(base[offset].him_offset,offset);
+ }
+ pop_back();
+ }
+ friend void RelationList<B,R>::add(Uid me, Uid him);
+ friend void RelationList<B,R>::deleteall(Uid me);
+ friend void RelationList<B,R>::delete_half(short offset);
+};
+
+struct Like;
+struct Likeby;
+struct Hate;
+struct Hateby;
+struct Like: public Relation {
+ Like(Uid _him, short _him_offset=-1) :Relation(_him,_him_offset) {}
+ static RelationList<Likeby,Like>& backlist(Uid him);
+};
+struct Likeby: public Relation {
+ Likeby(Uid _him, short _him_offset=-1) :Relation(_him,_him_offset) {}
+ static RelationList<Like,Likeby>& backlist(Uid him);
+};
+struct Hate: public Relation {
+ Hate(Uid _him, short _him_offset=-1) :Relation(_him,_him_offset) {}
+ static RelationList<Hateby,Hate>& backlist(Uid him);
+};
+struct Hateby: public Relation {
+ Hateby(Uid _him, short _him_offset=-1) :Relation(_him,_him_offset) {}
+ static RelationList<Hate,Hateby>& backlist(Uid him);
+};
+
+
+struct Utmp {
+ Utmp() {
+ for(int i=0; i<USHM_SIZE; i++)
+ utmp[i]=-1;
+ }
+ /*
+ Uid& operator[](int idx) {
+ return utmp[idx];
+ }
+ */
+ public:
+ Uid utmp[USHM_SIZE];
+};
+Utmp utmp;
+
+struct BBSUser {
+ Uid me;
+ int online;
+ /* assume utmplist is short, so just use plain vector and linear search */
+ myvector<int,2> utmplist;
+
+ RelationList<Like,Likeby> like;
+ RelationList<Hate,Hateby> hate;
+ RelationList<Likeby,Like> likeby;
+ RelationList<Hateby,Hate> hateby;
+
+ BBSUser() :me(-1),online(0),utmplist(),like(),hate(),likeby(),hateby() {}
+ BBSUser(Uid uid) :me(uid),online(0),utmplist(),like(),hate(),likeby(),hateby() {}
+
+ void login(int utmpidx, const Uid likehim[MAX_FRIEND], const Uid hatehim[MAX_REJECT]) {
+ if(online>0) {
+ /* multiple login 的話, 以最後一次的 like/hate 為準 */
+ like.deleteall(me);
+ hate.deleteall(me);
+ }
+ utmp.utmp[utmpidx]=me;
+ utmplist.append(utmpidx);
+ online++;
+ assert(online==utmplist.n);
+ for(int i=0; i<MAX_FRIEND && likehim[i]; i++)
+ if(likehim[i]!=me)
+ like.add(me, likehim[i]);
+ for(int i=0; i<MAX_REJECT && hatehim[i]; i++)
+ if(hatehim[i]!=me)
+ hate.add(me, hatehim[i]);
+ }
+
+ void logout(int utmpidx) {
+ assert(utmp.utmp[utmpidx]==me);
+ assert(online==utmplist.n);
+ for(int i=0; i<utmplist.n; i++)
+ if(utmplist.base[i]==utmpidx) {
+ utmplist.base[i]=utmplist.base[utmplist.n-1];
+ utmplist.pop_back();
+ break;
+ }
+ utmp.utmp[utmpidx]=-1;
+ online--;
+ assert(online==utmplist.n);
+ like.deleteall(me);
+ hate.deleteall(me);
+ }
+ bool isfree() const {
+ return online==0 && like.n==0 && hate.n==0 && likeby.n==0 && hateby.n==0;
+ }
+};
+
+struct UserList {
+ BBSUser users[MAX_USERS];
+
+ UserList() {
+ for(int i=0; i<MAX_USERS; i++)
+ users[i].me=i;
+ }
+ void login(Uid uid, Idx idx, const Uid likehim[MAX_FRIEND], const Uid hatehim[MAX_REJECT]) {
+ assert(uid<MAX_USERS);
+ assert(idx<USHM_SIZE);
+ /* 由於不會收到 logout event, 因此 logout 只發生在 utmp override */
+ if(utmp.utmp[idx]!=-1) users[utmp.utmp[idx]].logout(idx);
+ users[uid].login(idx, likehim, hatehim);
+ }
+};
+
+struct UserList userlist;
+RelationList<Likeby,Like>& Like::backlist(Uid him) { return userlist.users[him].likeby; }
+RelationList<Like,Likeby>& Likeby::backlist(Uid him) { return userlist.users[him].like; }
+RelationList<Hateby,Hate>& Hate::backlist(Uid him) { return userlist.users[him].hateby; }
+RelationList<Hate,Hateby>& Hateby::backlist(Uid him) { return userlist.users[him].hate; }
+
+struct Result {
+ Uid who;
+ int bits;
+ Result(Uid _who, int _bits) :who(_who),bits(_bits) {}
+ bool operator<(const Result& b) const {
+ return who<b.who;
+ }
+};
+
+int reverse_friend_stat(int stat)
+{
+ int stat1 = 0;
+ if (stat & IFH) stat1 |= HFM;
+ if (stat & IRH) stat1 |= HRM;
+ if (stat & HFM) stat1 |= IFH;
+ if (stat & HRM) stat1 |= IRH;
+ return stat1;
+}
+
+extern "C" void utmplogin(int uid, int index, const int like[MAX_FRIEND], const int hate[MAX_REJECT])
+{
+ /* login */
+ userlist.login(uid, index, like, hate);
+}
+
+extern "C" int genfriendlist(int uid, int index, ocfs_t *fs, int maxfs)
+{
+ /* collect data */
+ BBSUser& u=userlist.users[uid];
+ myvector<Result,64,short> work;
+ for(int i=0; i<u.like.n; i++) work.append(Result(u.like.base[i].him, IFH));
+ for(int i=0; i<u.hate.n; i++) work.append(Result(u.hate.base[i].him, IRH));
+ for(int i=0; i<u.likeby.n; i++) work.append(Result(u.likeby.base[i].him, HFM));
+ for(int i=0; i<u.hateby.n; i++) work.append(Result(u.hateby.base[i].him, HRM));
+
+ /* sort */
+ std::sort(work.base, work.base+work.n);
+ /* merge */
+ if(work.n>0) {
+ int newn=1;
+ for(int i=1; i<work.n; i++)
+ if(work.base[i].who==work.base[newn-1].who) {
+ work.base[newn-1].bits|=work.base[i].bits;
+ } else {
+ work.base[newn++]=work.base[i];
+ }
+ work.n=newn;
+ }
+ /* fill */
+ int nfs=0;
+ for(int i=0; i<work.n && nfs<maxfs; i++) {
+ BBSUser& h=userlist.users[work.base[i].who];
+ for(int j=0; j<h.utmplist.n && nfs<maxfs; j++) {
+ int rstat=reverse_friend_stat(work.base[i].bits);
+ fs[nfs].index=h.utmplist.base[j];
+ fs[nfs].uid=h.me;
+ fs[nfs].friendstat=(work.base[i].bits<<24)|h.utmplist.base[j];
+ fs[nfs].rfriendstat=(rstat<<24)|index;
+ nfs++;
+ }
+ }
+ return nfs;
+}
+
+extern "C" void utmplogoutall(void)
+{
+ for(int i=0; i<USHM_SIZE; i++)
+ if(utmp.utmp[i]!=-1)
+ userlist.users[utmp.utmp[i]].logout(i);
+}
+