Sign in to follow this  
Rip7

Ruleset

Recommended Posts

Hi, For a project I need an user hierarchy. For example, a weakling can use only the commands build and attack and an emperor can use build, attack and diplomacy ... Now I want to make a virtual base class user and weakling is derived from this class, emperor is derived from weakling. I have a dynamic parser (using spirit) and when I make for example an object emperor I want to make my parser look for the commands (words) that emperor can do and associate it with a command (a pointer to a member function list whould be ideal). But how can I do this now ? Using RTTI is an option, but then I must make multiple if/else switches in the parser and must manually add the commands to the parser. I can't use a simple loop or something then and I can't simply make a new class later. Multiple dispatch isn't an option either. I have tried it with templates also.
class User
{
 	  public:
	  		 virtual void ArgChecker() =0;
	  		 
      private:
	  		 list<string> PossibleArguments;
};

class Weakling : virtual public User 
{
 	  public:
	  		 void Build(){cout << "Building something: Weakling" << endl;}
	  		 void Attack(){cout << "Attacking something: Weakling" << endl;}
	  		 void ArgChecker() {cout << "Checking arguments: Weakling" << endl;}
//in constructor add all possible arguments 
};

class Emperor : public Weakling
{
 	  public:
                         void ArgChecker() {cout << "Checking arguments: Emperor" << endl;}
	  		 void Diplomacy(){cout << "Supercommand: diplomacy" endl;}
//in constructor add all extra possible arguments
}; 	

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
I did something like this recently.

Here's generally what it consisted of:

- Object containing a list of user rights.
For me, this meant a simple "is_admin()" list.
- User object with a unique indentifier.
Using the user object's pointer is sufficient, though I'd recommend some sort of uid.
- Helper functions to add a user to the list, usually via password.
You don't seem to need this.
- A command syntax map.
You seem to have this with your parser[s]. Essentially something that takes the initial command given, and does a functor lookup. Mine also included parameter types, and did auto conversion. The code:


cmd_set.add("foo int int", foo());
cmd_set.parse("foo 42 1", user);


Would do as you expect, map a function which takes 2 ints, and call foo(42,1); for user.
- A wrapper functor for "Admins only"
This would be a functor [with a templated operator()] which takes another arbitrary functor as a parameter to its constructor. When called, it checks if the caller is a member of the adminlist. If not it returns with a common failure. If so, it calls the stored functor, with the parameters passed to it. The code:


cmd_set.add("foo int int",admins_only(foo()));
cmd_set.parse("foo 42 1", user);


Would do as you expect; check if user is an admin first, and run foo if the user is. If not, cmd_set.parse() will return back the error value [or string, or however you do it]


So, this way, every "if admin" goes through one piece of code. It has one source for the error code. Most importantly of all, for the individual functions, the user rights qualifier is only ever done once, at the point it's added to the global command set. Thus it would only ever need changed in one place should the rights change.

Share this post


Link to post
Share on other sites
Here is some snippets for each in the code I'm currently using. I don't recommend using something like this, because my code is icky :] Plus, this all hasn't undergone rigorous testing yet, so there might be some leaks or gotchas...

[and sorry for the ugly code]

In order then... my admin list is simply:

set<guid> admins;


Where guid is a unique identifier class.

The user class:

struct playerinfo: public gamebase{
// snip snip.
//
// in gamebase:
guid uid;
//
// snip snip.
};



So when a player [or any other game object for that matter] is added to the adminlist, the gamebase.uid is added to the set.

Now, for the command_set, which does the mappings.

But first some discussion about a class I use throughout here:

vine * - A homegrown variant linked list class. A vine * depending on the context is either the container itself or an iterator. Using an Standard Library container of boost::variant should get you the same sort of functionality. Simply, it's 0 or more objects of any type.

Now. Starting at the bottom...

guiaction - A generalized functor interface. Using something like boost::function would probably eliminate the need for this... One of the more icky parts of the code... It was made before I knew formally what functors even were, and modified afterwards:

struct guiaction{
virtual vine *exec(vine *tar,vine *par)=0;
virtual vine *exec(vine *par) {return(exec(target,par));}
virtual vine *exec() {return(exec(target,params));}
void doa(vine *tar, vine *par){vine *v=exec(tar,par);v->nuke(&v);}
void doa(vine *par){doa(target,par);}
void doa(){doa(target,params);}

virtual vine *stringify()=0;
virtual guiaction *clone()=0;
guiaction *me(){return(this);}

vine *target;
vine *params;
guiaction(vine *intt=0, vine *inpp=0):target(intt),params(inpp){}
virtual ~guiaction(){target->nuke(&target);params->nuke(&params);}
};


It has two inheritors, one for global functions [and functors], one for member functions:

template <typename F>
struct globalaction:public guiaction{
F f;

virtual vine *exec(vine *tar, vine *par){return(f(tar,par));}

virtual vine *stringify();
virtual guiaction *clone(){return((new globalaction(f,target->clone(&target),params->clone(&params)))->me());}

globalaction(F inf,vine *intar, vine *inpar):guiaction(intar,inpar),f(inf){}
};

template <typename T, typename F>
struct memberaction:public guiaction{
F f;
T t;

virtual vine *exec(vine *tar, vine *par){return((t->*f)(tar,par));}

virtual vine *stringify();
virtual guiaction *clone(){return((new memberaction(t,f,target->clone(&target),params->clone(&params)))->me());}

memberaction(T inobj,F inf,vine *intar, vine *inpar):t(inobj),f(inf),guiaction(intar,inpar){}
};



So now, the various templates can all be referred to from a guiaction *. But that's kind of annoying, since that requires the programmer to keep track of the guiaction and delete it. Plus, it's pretty annoying to specify template parameters for it every time, so this wrapper class and helper function was made:

struct storega{
guiaction *ga;

storega(guiaction *inga):ga(inga){}
~storega(){delete ga;}
storega(const storega &that){
ga=that.ga->clone();
}
vine *stringify(){return(ga->stringify());}
vine *exec(vine *t, vine *p){return(ga->exec(t,p));}
vine *exec(vine *p){return(ga->exec(p));}
vine *exec(){return(ga->exec());}
void doa(vine *t, vine *p){return(ga->doa(t,p));}
void doa(vine *p){return(ga->doa(p));}
void doa(){return(ga->doa());}
// TODO: other operators.
};

template <typename F>
guiaction *makega(F f,vine *intar, vine *inpar){return(new globalaction<F>(f,intar,inpar));}

template <typename T, typename F>
guiaction *makega(T inobj,F inf,vine *intar, vine *inpar){return(new memberaction<T,F>(inobj,inf,intar,inpar));}




Now the interface plays nicely in Standard Library containers, and anything that returns a vine* and takes 2 vine*'s as parameters can be stored safely [along with it's parameters] in a storega. The templated functions will do auto-detection on the types, make the templated object with those types and return a generic guiaction * for use.

Now. Up to here almost certainly is a less-useful and less-generic re-implimentation of Standard Library or boost functionality. Please use that rather than my stuff. I hope I explained at least enough to allow you to understand what follows.

Now... back to the actual command_set. Well, sort of. First comes a class which is something to define generic syntax storage.

struct syntax_def{
private:
vector<int> paramids;
string keyword;
public:
string fetchname(){return(keyword);}
string human_readable();
bool match(char *c);

vine *parse(char *c);
vine *parse(string &s);

vine *stringify();
vine *unstringify(vine *inv);

syntax_def(const char *syntax);
syntax_def(){
// For stl containers mostly.
keyword="*";
}
};


string syntax_def::human_readable(){
//
// Convert paramids to human readable form.
//
string rtn=keyword;
vector<int>::iterator it;
vector<int>::iterator e=paramids.end();

for (it=paramids.begin();it!=e;++it){
rtn=rtn + " <";
rtn=rtn + type_manager.fetchname(*it);
rtn=rtn + ">";
}
return(rtn);
}



bool syntax_def::match(char *c){
//
//
//
string st;
stringstream ss(c);

ss >> st;
if (st == keyword){
return(1);
}else{
return(0);
}
}



vine *syntax_def::parse(char *c){
//
//
//
if (!match(c)){return(0);}

string st;
stringstream ss(c);

vine *rtn=0;

vector<int>::iterator it=paramids.begin();
vector<int>::iterator e=paramids.end();

ss >> st;
if (st==keyword){
// nothing, just pull the command name from the line.
}else{
if (it!=e){


if (st[0]=='\"'){
st.replace(0,1,"");
string quotestr;
getline(ss,quotestr);
string::size_type pos=quotestr.find_first_of("\"");
if (pos == quotestr.npos) { pos = quotestr.length(); }
st=st+quotestr.substr(0,pos);
ss.clear();
if (pos != quotestr.length()){pos++;}
ss.str(quotestr.substr(pos,quotestr.length()));
}

(((new vine(st.c_str())))->unstringify(*it))->splice(&rtn);
++it;
}
}
while(it!=e && ss.good()){
ss >> st;
if (st[0]=='\"'){
st.replace(0,1,"");
string quotestr;
getline(ss,quotestr);
string::size_type pos=quotestr.find_first_of("\"");
if (pos == quotestr.npos) { pos = quotestr.length(); }
st=st+quotestr.substr(0,pos);
ss.clear();
if (pos != quotestr.length()){pos++;}
ss.str(quotestr.substr(pos,quotestr.length()));
//cout << "postfixed: " << ss.str() << "\n";
}

(((new vine(st.c_str())))->unstringify(*it))->splice(&rtn);
++it;
}
return(rtn);
}


vine *syntax_def::parse(string &s){
//
//
//
char *c=(char *)malloc(s.length()+1);
vine *v;
strncpy(c,s.c_str(),s.length());
v=parse(c);
free(c);
return(v);
}



syntax_def::syntax_def(const char *syntax){
//
//
//
string st;
stringstream ss(syntax);
int x;

ss >> st;
if (st.empty()){
// bogus entry...
keyword="*";
return;
}else{
keyword=st;
}
while (1){
if (!ss.good()){
return;
}
ss >> st;
if (st.length()==0 ){
return;
}
x=type_manager.fetchid(st.c_str());
if (x!=type_manager.failure()){
paramids.push_back(x);
}
}
}


vine *syntax_def::stringify(){
//
//
//
return(new vine((human_readable()).c_str()));
}


vine *syntax_def::unstringify(vine *inv){
//
//
//
string st;
string::size_type pos;
int x;
syntax_def *sd;

if (!inv || inv->type!=type_manager.fetchid("cmd")){return(0);}

stringstream ss((char *)inv->data);
st = (char *)inv->data ;

pos=st.find("<");
while(pos!=st.npos){
st.replace(pos,1,"");
pos=st.find("<");
}
pos=st.find(">");
while(pos!=st.npos){
st.replace(pos,1,"");
pos=st.find(">");
}
sd=new syntax_def(st.c_str());
if (sd->fetchname()=="*"){
delete sd;
return(0);
}
return(new vine(type_manager.fetchid("syntax_def"),sd));

}



registered_type <syntax_def,class_stringify<syntax_def>, class_unstringify<syntax_def> > *syntax_def_type = new registered_type<syntax_def,class_stringify<syntax_def>, class_unstringify<syntax_def> >("syntax_def",1);




struct guiaction_syntax : public syntax_def{
storega ga;
vine *exec(char *c);

guiaction_syntax(const char *c,guiaction *inga=makega(devnull,0,0)):ga(inga),syntax_def(c){}
};


vine *guiaction_syntax::exec(char *c){
//
//
//
vine *v=parse(c);
ga.exec(v);
v->nuke(&v);
}

registered_type <guiaction_syntax,class_stringify<guiaction_syntax>, class_unstringify<guiaction_syntax> > *guiaction_syntax_type = new registered_type<guiaction_syntax,class_stringify<guiaction_syntax>, class_unstringify<guiaction_syntax> >("guiaction_syntax",1);


The paramids is simply a list of ints which correlates to the vine's internal type data. Also, the vine class holds stringify/unstringify [essntially serialization info] about all of the types it can hold. That's also why after each class definition is the monsterous template stuff.

But you said you've already got nifty parsers, so this is rather redundant. Note though that the inheriting guiaction_syntax stores an arbitrary function with the syntax.

The command set is nothing more than a grouping of these:

class command_set{
private:
hash_map<string,guiaction_syntax *> cmdmap;
typedef hash_map<string,guiaction_syntax *>::iterator mapit;
lookup<
guiaction_syntax,
hash_map<string,guiaction_syntax *>,
string
> fetchgas;
public:
vine *syntax(string);
string str_syntax(string);
vine *stringify();
void add(string,guiaction *);
vine *cmd(vine *,string);
vine *cmd(vine *,vine *);
vine *cmdout(vine *,vine *);
bool match(string);
vine *syntax_check(string);
vine *syntax_check(vine *);

command_set(){}
~command_set();
};

// snip snip.

void command_set::add(string s,guiaction *ga){
//
// Add guiaction ga with syntax s to the command set.
//
// The guiaction is lost to the command_set which will clean it up.
//
guiaction_syntax *gas;
guiaction_syntax *gascheck;
string gasname;

if (!ga){
gas=new guiaction_syntax(s.c_str(),makega(devnull,0,0));
}else{
gas=new guiaction_syntax(s.c_str(),ga);
}
gasname=gas->fetchname();
if (gasname == "*"){
// creation failure? Abort.
delete gas;
return;
}
gascheck=fetchgas(gasname,cmdmap);
if (gascheck){
// Overwrite old entry.
mapit it=cmdmap.find(gasname);

delete it->second;
cmdmap.erase(gasname);
}
cmdmap[gasname]=gas;
}

vine *command_set::cmd(vine *tar,string c){
//
// Attempt to execute command defined by string c.
//
// tar will be passed as the guiaction target. Defines the caller [for multiplayer command parsing].
// called functions must not modify tar.
//

guiaction_syntax *gas;
stringstream ss(c);
string st;
vine *v;
vine *rtn=0;

ss >> st;
gas=fetchgas(st,cmdmap);
if (!gas){
// unknown command.
(new vine(st.c_str()))->top(&rtn);
(new vine("Unknown command: "))->top(&rtn);
return(rtn->vinecat());

}
v=gas->parse(c);
rtn=gas->ga.exec(tar,v);
v->nuke(&v);
return(rtn);
}

// snip snip.



The cmd_set can then be used to define a series of commands with their functions like I described above. For the admin_only, here's the actual functor wrapper I'm using [remember that admins is merely a global set for now...]:


struct admin_only{
//
// A functor modifying functor, which will short circut the functor chain
// if caller [target] is not in the admins list.
//
storega ga;
vine *operator()(vine *target, vine *params){
vine *v;
playerinfo *pptr;

if (!target){
//
// if null, just run it.
//
return(ga.exec(target,params));
}
if (target->type != type_manager.fetchid("player")){
return(new vine("Caller not player? Abort."));
}
pptr=(playerinfo *)target->data;
if (admins.find(pptr->uid)!=admins.end()){
return(ga.exec(target,params));
}else{
return(new vine("Admin rights required."));
}
}

admin_only(storega inga):ga(inga){}
};


So, this functor simply wraps around another, adding functionality to it. This is in the large round about way I got to being able to do this:

server_cmdset.add("quit", makega( admin_only( storega( makega( server_shutdown("Server shutdown by admin request."),0,0))),0,0));

Which is a long, obtuse way to simply say "if an admin enters the command 'quit' shutdown the server." Still far, far better than a giant switch statement or if/else tree.

Share this post


Link to post
Share on other sites
Quote:
Original post by yukit0
Hmmm what about using functors and inheriting the function for the several cases? Wouldn't that be simpler?


Inheritance over-rides, it does not extend.


struct A{
virtual void operator()(){
cout << "A";
}
};

struct B: public A{
virtual void operator()(){
cout << "B";
}
};

int main(){
A a;
B b;

a();
b();
}




will print AB for example not AAB. You could put some magicry in the constructors to automatically build a list of functions to run, but that would be less versatile than the wrapper sort of functor in my example above.

Share this post


Link to post
Share on other sites
Right now, you are using inheritance:


class Emperor : public Weakling


It do not sound good. From a formal point of view, an emperor is not a weakling, thus your Emperor class should not inherits the Weakling class.

It is a typical case where you should favor composition over inheritance, because both Emperor and Weakling are user roles, not user types. You can define You classes like this:


// USING PATTERN: strategy (see GoF)
class Role { ... };
class User
{
Role *mRole;
};
class Weakling : public Role { ... };
class Emperor : public Role { ... };


Now, you have to implement actions:


// USING PATTERN: command (see GoF)
class Action
{
public:
typedef sometypeyoudefined Result;
virtual Action::Result process(a list of parameters) = 0;
};

class Role
{
public:
// redefine for Emperor and Weakling
virtual Action::Result doAction(Action *action) = 0;
};

class Emperor : public Role
{
Action::Result doAction(Action *action)
{
return action->process(some parameters);
}
};

class User
{
public:
Action::Result doAction(Action *action)
{
return mRole->doAction(action);
}
};


Now:
* you can define whatever action you want. The action code is in the action class (in the process method), thus you are not limited to a set of known operations - it is easier to extend your model.
* you can define whatever role you want. A priest ? nothing simpler: define a new class Priest : public Role, and that's it.
* you can change the role at runtime - yes, a weakling can become an emperor or a priest.
* it may have a small issue: you need to feed the User with a correct Action - for example, if the User has a Role of Weakling, you can't tell him to build a new city. To correct this, you can use rtti in the derived role classes: for example, you can inherit the emperors actions from an EmperorAction class (which inherits from Action) and then test if the action object is an EmperorAction in the process() method. Another way which do not need rtti is to add an action registration mecanism into the derived Role classes and then to consider than an action which is not registered in a particular Role cannot be processed by this Role. This is quite similar to Telastyn's cmdset, and this is the solution I'd implement.

As you can see, this design offer many opportunities which may be harder to develop (and far harder to generalize) with your previous design.

HTH,

Share this post


Link to post
Share on other sites
Thanks for your post, this is exactly what I need. It's very flexible and easy to use. I considered not using inheritence because I tought that a emperor and a weakling had an "is-a" relation so I dropped that solution immediately. But your absolutely right your option is much much better.

Thanks very much you both.

P.S: One question though, how would you make the registration mechanism ?

[Edited by - Rip7 on May 31, 2005 10:18:59 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by Rip7
Thanks for your post, this is exactly what I need. It's very flexible and easy to use. I considered not using inheritence because I tought that a emperor and a weakling had an "is-a" relation so I dropped that solution immediately. But your absolutely right your option is much much better.

Thanks very much you both.

P.S: One question though, how would you make the registration mechanism ?


I had a reflexion about this yesterday evening, and here two options which can be used to check whether you are allowed to apply an action on a user or not, depending on the user role:

1) register the action in an action list: you need to have a run-time identifier to identify a particular action. This can be the action name, or the action class name, or a simple integer. In the following example, I use an action name.


class ActionNameList
{
std::vector<std::string> mActionNames;
public:
void add(const std::string& actionName);
bool verify(const std::string& actionName); // search the name
};

class Emperor : public Role
{
private:
static ActionNameList mActionList;
bool verifyAction(Action *a) { return mActionList.verify(action->name()); }
public:
void addActionToList(Action *a) { mActionList.add(action->name()); }
Action::Result doAction(Action *action)
{
if (verifyAction(action))
{
return action->process(list of params);
}
return Action::error;
}
};


This is simple to implement, extensible, but may be slow if you have a lot of action (because each time you want to process an action, you have to verify if the action name is in the action name list or the role).

2) another possiblity is to use a role check policy; here we choose to verify the name of the role class (because it is simple).

class NameCheckPolicy
{
public:
virtual verify(const std::string& name) = 0;
};

class Action
{
NameCheckPolicy *mPolicy;
public:
Action(RoleCheckPolicy *policy) : mPolicy(policy) { }
bool verify(const std::string& name) { return mPolicy->verify(name); }
};

class Emperor : public Role
{
private:
std::string mName;
bool verifyAction(Action *a) { return action->verify(name()); }
public:
std::string name() const { return mName; }
Action::Result doAction(Action *action)
{
if (verifyAction(action))
{
return action->process(list of params);
}
return Action::error;
}
};

You can then define as many derived NameCheckPolicy as you want. For example, I'd define NoNameCheckPolicy, which doesn't check the name at all, SingleNameCheckPolicy, which checks the name against one single value, and MultipleNameCheckPolicy, which checks the name against multiple values.

Let's face it, checking names (either in the action list case or in the name checking policy case) is not a very good solution, because a small error in a string can ruin the whole system. Fortunately, there is a solution which helps, because it gives us a string that we don't control and which is guaranteed to not be modified: using the typeid() keyword returns a typeid object, fro which we can get the class name using the name() method.

typeid() do not find the real name of an object - it gives the current type of the object. For example:

class A { };
class B : public A { };
A *a = new B();
std::cout << typeid(a).name() << std::endl;

Writes "A" on the console output.

HTH,

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this