Advertisement Jump to content
  • Advertisement
  • entries
    359
  • comments
    237
  • views
    189599

About this blog

Current topic is the development of a toy language to test a few syntax ideas.

Entries in this blog

 

No news means Old news.

Thankfully, this little journal was started late, meaning any nice code tidbits I do have are available for discussion when I have been overly lazy/busy. Like this week.

One particular piece of code that I've found to be far more valuable than I anticipated is the Command Set (cmdset). I originally wrote the Command Set class in C++ as an interpreter for a Quake-style console. The general idea was that you could add functions to the Set, and the Set would handle any parsing/type conversions to take a string and execute the function.

Since then it has undergone some modifications. Most notably, the most recent incarnation is in C#, and that cmdset is now an interface, not a solid class. I use it as a means to make strings follow the command pattern. Different concrete derivations include a method which also contains Help information, a 'fallthrough' class which matches any command and passes the string along, and a group of sets to allow additive set building from reference sets.

It's not as beefy as a full fledged interpreter or scripting solution, and (imo) tends to be a bit more generally useful because of it.


On to code. The first and foremost problem for this sort of solution is the conversion of string commandline parameters to non-string function parameters. In C++ (included for interest), I created a static template class to contain type serialization information:


#ifndef TYPE_REGISTRY
#define TYPE_REGISTRY

//#include "stringhash.h"
//#include
#include
#include
#include

using namespace std;
using namespace boost;

const char unknown_type_name[]="unknown";

template
struct def{
typedef function serialization_type;
typedef functionconst char *)> unserialization_type;
// TODO:
// clones, deletors
//

static string name;
static serialization_type serial_funk;
static unserialization_type unserial_funk;


string serialize(T *t){ if(serial_funk.empty()){return("");} return(serial_funk(t));}
T *unserialize(string s){ return(unserialize(s.c_str()));}
T *unserialize(const char *s){ if(unserial_funk.empty()){return(0);} return(unserial_funk(s));}
};


template
struct null_serialization{
string operator()(T *t){return("");}
};

template
struct null_unserialization{
T *operator()(const char *s){return(0);}
};

template
struct ios_serialization{
string operator()(T *t){
if(!t){return("");}
stringstream ss;
ss return(ss.str());
}
};

template
struct ios_unserialization{
T *operator()(const char *s){
stringstream ss(s);
T *t=new T();
ss >> *t;
return(t);
}
};

template
struct intrusive_serialization{
string operator()(T *t){
return(t->serialize());
}
};

template
struct intrusive_unserialization{
T *operator()(const char *s){
return(T::unserialize(s));
}
};

template
struct constructor_unserialization{
T *operator()(const char *s){
return(new T(s));
}
};

template
struct string_serialization{
string operator()(T *t){
return(*t);
}
};



//
// POD types.
//

defint>::serialization_type defint>::serial_funk=ios_serializationint>();
defint>::unserialization_type defint>::unserial_funk=ios_unserializationint>();
string defint>::name="int";

deflong>::serialization_type deflong>::serial_funk=ios_serializationlong>();
deflong>::unserialization_type deflong>::unserial_funk=ios_unserializationlong>();
string deflong>::name="long";

defdouble>::serialization_type defdouble>::serial_funk=ios_serializationdouble>();
defdouble>::unserialization_type defdouble>::unserial_funk=ios_unserializationdouble>();
string defdouble>::name="double";

deffloat>::serialization_type deffloat>::serial_funk=ios_serializationfloat>();
deffloat>::unserialization_type deffloat>::unserial_funk=ios_unserializationfloat>();
string deffloat>::name="float";

def::serialization_type def::serial_funk=string_serialization();
def::unserialization_type def::unserial_funk=constructor_unserialization();
string def::name="string";

defbool>::serialization_type defbool>::serial_funk=ios_serializationbool>();
defbool>::unserialization_type defbool>::unserial_funk=ios_unserializationbool>();
string defbool>::name="bool";


#endif



The correct template could then be picked based on the ARG parameters in boost::function. The C# version naively follows the same practice even though C# has nicer serialization methods... Ignorance is inversly proportional to solution goodness. Alas.



public static class typdict {
public delegate object destr(string s);
private static Dictionary typemap = new Dictionary();
public static void Add(Type t, destr f) {
typemap.Add(t, f);
}
public static object Parse(Type t, string s) {
if (!typemap.ContainsKey(t)) {
return (null);
}
return (typemap[t](s));
}

public static void Initialize() {
Add(typeof(int), delegate(string s) { return ((object)int.Parse(s)); });
Add(typeof(float), delegate(string s) { return ((object)float.Parse(s)); });
Add(typeof(string), System.Convert.ToString);
}
}



The second problem is command definition. In C++ this was rather easy. boost::function objects contain their type information. Tie a string to a function, pass the args as template parameters, and there you go.

C# it's a little trickier. Generics won't do the specification nicely (void is not a valid type for genericness it seems...). So for C# I required an explicit Delegate to be passed rather than a method. Reflection then allows type deduction. Here is the most common Command Definition. (note the base class contains the command name string, as well as the tokenize() function. The tokenize function is essentially a big Regex.Split() with an ugly regex ("split on a full line on whitespace, unless that white space is between double-quotes"). In C++, boost::spirit was used, and was much cleaner).)

[and pardon the variable naming... it was my second week or so with C#]


public class cmd_del : cmddef {
protected Delegate cmd;
protected Type[] argts;
protected Type rtnt;

cmd_del(string cn, Delegate c, Type rt, params Type[] ats)
: base(cn) {
cmd = c;
rtnt = rt;
argts = ats;
}
protected override Type rtntype() { return (rtnt); }
protected override Type[] argtypes() { return (argts); }
public override object Parse(string cmdargs) {
ArrayList argz = new ArrayList();
string[] sargs = tokenize(cmdargs);
if (sargs.Length != argts.Length) {
return (null);
}
if (argts.Length == 0) {
return (cmd.DynamicInvoke());
}
for (int x = 0; x argz.Add(typdict.Parse(argts[x], sargs[x]));
}
object[] oargs = argz.ToArray();
return (cmd.DynamicInvoke(oargs));
}
public cmd_del(string cn, Delegate d)
: base(cn) {
cmd = d;
rtnt = d.Method.ReturnType;
List alargs = new List();
foreach (ParameterInfo p in d.Method.GetParameters()) {
alargs.Add(p.ParameterType);
}
argts = alargs.ToArray();
}
}




Once there, it's fairly simple to create the basic cmdset class:



public abstract class cmd_interface {
public abstract object Parse(string cmdline);
public abstract string[] cmd_names();
public abstract string[] cmd_usages();
public abstract bool Has(string cmd);
public abstract int Count { get;}
}
public class cmdset :cmd_interface {
protected Hashtable cmdlist = new Hashtable();
public logging_strategy logstrat = new logging_strategy();
// TODO: logging, events, usage lists, cmdlists.
public override object Parse(string cmdline) {
//
// Parse quake-style console command.
//
// Returns null on cmdlack.
//
string cmd;
string argz;
Regex cmdrx = new Regex(@"^(\w+)(?:[\s$]*)(.*)$");
Match rxmc = cmdrx.Match(cmdline);
if (!rxmc.Success) {
logstrat.log("cmdset: failed to parse: \"" + cmdline + "\"\n");
return (null);
}
cmd = rxmc.Groups[1].Value;
if (!cmdlist.ContainsKey(cmd)) {
logstrat.log("cmdset: Unknown command \"" + cmd + "\"\n");
return (null);
}
if (rxmc.Groups.Count == 1) {
argz = "";
} else {
argz = rxmc.Groups[2].Value;
}
return ((cmdlist[cmd] as cmddef).Parse(argz));
}
public virtual void Add(cmddef cmdf) {
cmdlist.Add(cmdf.cmdname, cmdf);
}
public override bool Has(string cmdname){
return(cmdlist.ContainsKey(cmdname));
}
public override string[] cmd_usages() {
List rtn = new List(cmdlist.Count);
foreach (DictionaryEntry de in cmdlist) {
rtn.Add((de.Value as cmddef).usage());
}
return (rtn.ToArray());
}
public override string[] cmd_names() {
List rtn = new List(cmdlist.Count);
foreach (DictionaryEntry de in cmdlist) {
rtn.Add((de.Value as cmddef).cmdname);
}
return (rtn.ToArray());
}
public override int Count {
get { return (cmdlist.Count); }
}
}



Thus you have a relatively simple class to provide plugable 'commands' for string input handling. While this sort of thing was originally meant for quake-style console input, and server console input one of the more interesting places I've found for it is in threading.

Combined with some code to facilitate inter-thread communication, the cmdset provides a common abstraction between thread communication and whatever methods the thread impliments.

From my threaded sound class:

cs.Add(new cmd_del("play",new SoundEventType(this.PlaySound)));
cs.Add(new cmd_del("stop",new SoundEventType(this.StopSound)));
cs.Add(new cmd_del("pause",new SoundEventType(this.PauseSound)));


With the common communication, thread management and command set classes, all that needs done to thread-ify a class is to add the command definitions to an initialization method.

And there you go. A little tidbit of stuff to promote thought discussion and threats against my wellbeing.

Telastyn

Telastyn

 

Oh look, I was right.

Sure enough, waste of time. At least the last few places have been tech specific, with technical recruiters who at least knew the distinction between say... systems administration and software engineering.

Telastyn

Telastyn

 

I hate consultantcies.

I quite dislike applying for a job, only to find that it's not really a job but a consultantcy [or a 'placement agency'] which happens to have a job. Then, they want me to pull out my suit, drive 30 minutes into the city, pay $10 for parking [one of the downsides to Minneapolis], and then 30 minutes back; all for some 15 minute interview to make sure I don't have the plague.

Of course, by the time all that occurs the job is likely long gone and I'm lucky to hear from my interviewer again. Just a complete waste of time. God knows my afternoon would be better off looking for jobs or even playing Civ4, but at this point I can't ignore even the longest possibility.

Telastyn

Telastyn

 

Sleep is for the weak?

No. Sleep is for people that actually want to get stuff done the next day. At least after you leave college...

After getting perhaps 5 hours of sleep total Friday and Saturday nights, nothing really got done. I don't even have wonderful stories of drunken revalry, just a blanket stealing wife, a dog with a tiny bladder and the Minnesota weather report.

Telastyn

Telastyn

 

Still, not much to report.

I did a minimal amount of work today. Basic class outlines only. I did though get some hockey in, which should help a bit with motivation.

Telastyn

Telastyn

 

Not much to report.

Design doc was met with minimal feedback [thanks repliers], which wasn't too unexpected. The feedback was slightly negative where I expected, and positive where I was doubtful, so low levels of goodness all around.

Unfortunately, between on edge waiting on that, being awoken by the dog in the middle of last night, and job hunting... no real work got done today. Not so bad considering the doc work earlier in the week, but still.

Goal for tomarrow: build skeleton classes for Moe data. Unrealistic I know, but we'll see. If motivation strikes and the dog cooperates, it's not beyond what I can get done.

Telastyn

Telastyn

 

Yay. Minor goal reached.

Basic Design Doc complete! I posted it here to get feedback from random strangers. Being nearly hermitlike does have the remarkable downside of not having a few good friends to bounce ideas off of...

Telastyn

Telastyn

 

Design Doc work continues

Work on the design doc continues. It's been quite some time since I've actually used word, so the goings are a little slow. Anyways, up to about 13 pages, a little over half done. It's good to get this stuff in one spot on [electronic] paper.

It certainly beats listening to the vapid blithering on irc or many of the forums as of late... *shakes old man cane at the world*

Telastyn

Telastyn

 

Greetings!

After a not so short time spent merely abusing the forums, I have tossed in some cash for a yearly subscription. God knows I've gotten more than that in advice, and will probably save more than that in book discounts.

Unemployment continues.

I dislike large consultancies. They are perhaps the antithesis of unions. Instead of unionizing against companies for higher pay and better benefits, companies go to consultancies, which give them the cheapest worker. Since the company doesn't actually hire the guy, they don't need to provide benefits, or treat them like a proper human since he's just going away after the 6 month contract.

Perhaps I'm too paranoid.

Hobby work sort of continues. Lack of sleep and generally being bummed out by unemployment make for poor motivators. I did though manage to doodle up the beginnings of a formal design doc for moe, based off of sunandshadow's one from a recent post in game design. Hopefully I will get the basics jotted down in word and then post them here or in game design for criticism and feedback.

Alas, I doubt too much will come of it; either flames about something petty or deafening silence.

Anyways, greetings! Thanks for coming by and reading. Here's to me actually remembering to post not too infrequently...

Telastyn

Telastyn

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!