02 Oct, 2008, Vassi wrote in the 21st comment:
Votes: 0
DavidHaley said:
Vassi said:
I guess my question is, is being tied down to a handler-based system too restrictive as it is?

If you're just talking about networking, then FWIW I think it's perfectly reasonable to define a callback interface of sorts to handle events from the sockets. If the interface is general enough, it's hard to imagine somebody wanting something else. After all there are only so many events that occur on sockets…

If you're talking about generic handler interfaces for other events, then things get more complicated. Clearly, the space of available events might be quite different from game to game. Still, a generic event handling mechanism would be fine, as long as the events themselves are left fairly open-ended.


I'm glad you brought that up. I'm sticking with C# interfaces for the majority of the design because interfaces don't provide any functionality so, in effect, you have to do your own thing, while still being able to take advantage of the infrastructure. So to go back to events, your event action\callback needs to implement the following:

public interface ITimedAction
{
void Execute();
long ExecutionTime { get; set; }
long ModifiedTime { get; set; }
bool Persists { get; set; }
bool Canceled { get; set; }
string WriteOut();
}


The idea is that your implementing class would have all the data it needs to perform its callback when Execute is called, or you could create your own generic class that holds a delegate type that commands can use, either way you'll do what works best for you.

One thing that I just thought of though is that I don't provide any way to search for or remove a TimedAction once it's in the queue, though, which I'm debating (searching in particular since you'd need some kind of key to search against). In any case, though, if you hold on to the instance of your action you can still modify it's execution time to make it happen later, or flag it as canceled. In order to make it execute earlier, you'd actually have to cancel the previous event and make a new one - which I just realized now from typing this out =\. That, again, has to do with how the queue is sorted on insertion.

Edit: Persists and writeout will probably be removed when I finally release it, initially the thought was that if an action was associated with a player it could be flagged as persistant and then WriteOut would spit out some kind of serialization string, but I figure that's another edge-case that can be handled by custom code. For instance, spell effects would persist if a player logged off (or would be re-evaluated once they log in to see if any of them have expired - except for bleed\poison) but other timed actions (like regen) would just drop off.


V
02 Oct, 2008, David Haley wrote in the 22nd comment:
Votes: 0
Things like persisting events is where you get complicated. The meaning of persistence might be interpreted differently from game to game. Furthermore, you might want repeating events, or events that have the option of conditionally putting themselves back on the queue, etc. Of course, these two things can both be handled by simply creating a new event, so the event queue itself doesn't have to know about this difference: it can just pop something off, execute it, and be done with it. (The event itself would be responsible for inserting itself back into the queue.)

You can implement searching with a visitor pattern instead of using a search key. Add a method like "Query" or "Matches" that takes some generic search object as a parameter. You would then implement the query method based on the available queries, using runtime typing for instance. Presumably, not all queries are relevant to all objects, so it wouldn't be too onerous to have to implement the querying in the event itself. This makes the event handler remain generic, because you are not imposing a specific searching mechanism.

Of course you can turn it around, and pass in a predicate function that, given an event, returns true or false depending on whether or not it matches. Then it is the predicate that needs to figure out what kind of event it's looking at.

In either case, I would recommend against putting any kind of search smarts in the generic event handler, because of the space of available searches. In fact, even in a specific event handler for a specific game, I would recommend against it.
02 Oct, 2008, Vassi wrote in the 23rd comment:
Votes: 0
DavidHaley said:
Things like persisting events is where you get complicated. The meaning of persistence might be interpreted differently from game to game. Furthermore, you might want repeating events, or events that have the option of conditionally putting themselves back on the queue, etc. Of course, these two things can both be handled by simply creating a new event, so the event queue itself doesn't have to know about this difference: it can just pop something off, execute it, and be done with it. (The event itself would be responsible for inserting itself back into the queue.)


That's more or less the conclusion I came to, particularly with repeating events. It would, however, be kind of trivial to add an Interval and Count properties and have the event queue do the resetting for you, I just never thought about it until now. Worth it? It's about as trivial to do the same on your own.

Quote
You can implement searching with a visitor pattern instead of using a search key. Add a method like "Query" or "Matches" that takes some generic search object as a parameter. You would then implement the query method based on the available queries, using runtime typing for instance. Presumably, not all queries are relevant to all objects, so it wouldn't be too onerous to have to implement the querying in the event itself. This makes the event handler remain generic, because you are not imposing a specific searching mechanism.


You're a genius. In fact, the priority queue I modified (from a library called NGenerics) already implements the Visitor pattern for the heap. You'd just need to implement their IVisitor pattern (Visit(T) and bool HasCompleted) and you'd be good to go. I'll open this up as a static member.

Thanks for the feedback.


V
02 Oct, 2008, David Haley wrote in the 24th comment:
Votes: 0
Glad to be of help. :smile:

Vassi said:
That's more or less the conclusion I came to, particularly with repeating events. It would, however, be kind of trivial to add an Interval and Count properties and have the event queue do the resetting for you, I just never thought about it until now. Worth it? It's about as trivial to do the same on your own.

Yeah, I think that in this case it's simple enough that it probably is worth it. There aren't that many ways to repeat an event:
(a) repeat every X seconds, Y times
(b) repeat every X seconds, forever
and in any case, allow the event execution to return (or otherwise pass) a status code indicating that it should not repeat, which lets you stop a repetition before it's over. (This is useful if you have an event on a character, and that character is found to be gone – no point repeating the event.)

But sure, I think it's fairly innocuous to make this part of the general framework.
05 Oct, 2008, exeter wrote in the 25th comment:
Votes: 0
The one thing I can say for certain I'd like in a C# MUD codebase is the ability to run it on Linux using Mono.
08 Oct, 2008, Weeks wrote in the 26th comment:
Votes: 0
http://code.google.com/p/csharpmudserver...

It's really bare-bones at the moment, and as the description states, it's more a for-fun goof-off project and learning ground for me than a serious effort, though I have some ideas in the back of my head for a fairly nice game. Feel free to check it out. It's MIT, so you're free to tear it down to the network layer and redo it and call it your own or just build on what I have there. Whatever. I'd appreciate it if you let me know what you think, good or bad.
09 Oct, 2008, Weeks wrote in the 27th comment:
Votes: 0
By the way, if you look at the Doc/Notes.txt files for either the Lib or Data projects, those are basically stream-of-conscious notes I took some weeks ago and are by no means actual documentation (or even remotely accurate at this point). I plan on actually writing out some wiki documentation on this thing at the google code site later.
09 Oct, 2008, Vassi wrote in the 28th comment:
Votes: 0
I'll definitely take a look. Of the various C#\.NET codebases that I've seen only TigerMUD and WheelMUD have actually compiled and run with any degree of sophistication, TigerMUD is officially dead and WheelMUD is constantly being scrapped and re-written.

We need to represent, soon, and I seem to be taking my time about it.

When do you think your own MUD will be testable?
09 Oct, 2008, Weeks wrote in the 29th comment:
Votes: 0
As far as timelines, since it's a side project I have none to give really. I basically put as much time into it as I have spare between work, family and my EVE Online crew. Though, normally if I sit down and single out a feature I can knock it out in a few hours.

As I said in my blog post, next up is character creation, plus some more generic objects (clothing, which will require enumeration of body parts on a character, basically as "hardpoints" for wieldable items, also gives way to weapons, armor, and other such decorations), character inventories and some expansion/refinement of the "look" command, which is always one of the more important pieces of code in a lib. I've also got a chat channel system on the drawing board and will likely look to write an i3 client within the next month or so. With that done, I'll probably take off some time to document the hell out of the codebase before going further. I don't want to get too much further in without at least having XML docs in place, and what I really want is to use the Google Code wiki pages to fully document the whole system.

Some things I don't like so far, I don't have a true telnet implementation in place. It's a glorified socket server with a budding mudlib strapped to it. In order to be taken seriously I need to get on the ball with the telnet code such that I can detect and set client terminal settings, including possibly supporting MXP. I need to refactor some of the Lib code so that the structure makes a little more sense. The MUD.Lib.Daemons.Login is not a daemon. It's an input trap, a utility. It and a few other things need to be moved around and reworked a tad. Also, I'd like to separate out actual game-specific code, the World namespace in MUD.Lib in particular. I'd like that to be its own project so it supports a little easier plug-and-play and opening that code up to builders via SVN while leaving the base lib, driver, data and network code secure. As far as scripting solutions I have a few in mind but haven't narrowed it down. It basically comes down to what I want to learn how to do and that will determine how I approach the problem.
09 Oct, 2008, Vassi wrote in the 30th comment:
Votes: 0
Weeks said:
As far as timelines, since it's a side project I have none to give really. I basically put as much time into it as I have spare between work, family and my EVE Online crew. Though, normally if I sit down and single out a feature I can knock it out in a few hours.

As I said in my blog post, next up is character creation, plus some more generic objects (clothing, which will require enumeration of body parts on a character, basically as "hardpoints" for wieldable items, also gives way to weapons, armor, and other such decorations), character inventories and some expansion/refinement of the "look" command, which is always one of the more important pieces of code in a lib. I've also got a chat channel system on the drawing board and will likely look to write an i3 client within the next month or so. With that done, I'll probably take off some time to document the hell out of the codebase before going further. I don't want to get too much further in without at least having XML docs in place, and what I really want is to use the Google Code wiki pages to fully document the whole system.

Some things I don't like so far, I don't have a true telnet implementation in place. It's a glorified socket server with a budding mudlib strapped to it. In order to be taken seriously I need to get on the ball with the telnet code such that I can detect and set client terminal settings, including possibly supporting MXP. I need to refactor some of the Lib code so that the structure makes a little more sense. The MUD.Lib.Daemons.Login is not a daemon. It's an input trap, a utility. It and a few other things need to be moved around and reworked a tad. Also, I'd like to separate out actual game-specific code, the World namespace in MUD.Lib in particular. I'd like that to be its own project so it supports a little easier plug-and-play and opening that code up to builders via SVN while leaving the base lib, driver, data and network code secure. As far as scripting solutions I have a few in mind but haven't narrowed it down. It basically comes down to what I want to learn how to do and that will determine how I approach the problem.


Telnet is not that difficult after you wrap your mind around it. I think this place has a pastebin where I can post my Telnet interpreter (all you have to do is feed it a byte array) if you like, I won't be releasing anything in chunks for a while, but that much I can at least put out.
09 Oct, 2008, elanthis wrote in the 31st comment:
Votes: 0
TELNET is incredibly simple. By far the easiest way to think about it is as a state machine.

Your protocol handler has several states:
TEXT
IAC
DO
DONT
WILL
WONT
SB
SB_IAC

Your state machine just iterates over each input byte and handles it according to the current state, invoking state changes as necessary.

You start out in the TEXT state. Any character you receive goes into your input text buffer (user input if you're writing a server, or MUD output if you're writing a client), except for the IAC byte. That switches you to the IAC state. The IAC state always switches out: to the DO, DONT, WILL, WONT, or SB states if you get one of those bytes, or back to TEXT otherwise (after interpreting the byte received). The SB state just puts whatever byte it receives into your sub request buffer, except for the IAC byte – that switches into the SB_IAC state. In the SB_IAC state, you either get an SE byte, on which you then process the contents of the sub request buffer and reset to the TEXT state, or you switch back to the SB state after interpreting the byte.

That's really all there is to it for the base protocol. Getting error handling for broken servers/clients can take a little more work, but it's not difficult. Usually it's just a matter of deciding what to do when you receive an unexpected byte after an IAC in either the IAC or SB_IAC states.

Make sure that when you send bytes over the connection, you always escape the IAC byte with the IAC IAC byte pair for regular text.

Option negotiation (DO/DONT/WILL/WONT) is a little complex to grok at first, but not hard. There's the "correct" way to do it and the "good enough for the real-world" way. I always go for the latter, which is pretty simple – respond as appropriate to any option negotiation request, and ignore ones you don't understand.

The correct way is a major pain, as it requires you to keep two tables – one for each end of the connection – for all 255 option values, each of which can be in one of several states including ON, OFF, UNKNOWN, etc. That approach is really not necessary, though, as the only thing it really solves is guaranteeing (if you do it right) that you won't get stuck in a loop where a broken client keeps sending a particular option request and you keep replying to it – it lets you know what responses you've already given for each option, as well as knowing which responses the other end has given you. I have yet to see any major (or minor) MUD client or server that requires this, though.

If you're writing a client, I usually suggest writing your ANSI escape sequence interpreter as a state machine as well.

Far too many servers and clients don't use a state machine and instead try to buffer up bytes and then look for patterns of bytes, which often don't take into account the realities of network behavior (they don't handle TELNET or ANSI codes – or sometimes even user input lines – that aren't returned whole by a call to read()/recv(), which is totally wrong but unfortunately works often enough that a lot of coders don't even believe it's a real problem) and are a lot more complicated to work with than a simple state machine.

I used to have a much better write-up on TELNET and how to implement it somewhere on the net; not sure where it went to these days. Possibly on advogato.org?

DISCLAIMER: I wrote all of the above off the top of my head without proof-reading, so be aware I might have mispoke in the protocol explanation – don't take the above as truth without double checking it. :)
10 Oct, 2008, Vassi wrote in the 32nd comment:
Votes: 0
elanthis said:
The correct way is a major pain, as it requires you to keep two tables – one for each end of the connection – for all 255 option values, each of which can be in one of several states including ON, OFF, UNKNOWN, etc. That approach is really not necessary, though, as the only thing it really solves is guaranteeing (if you do it right) that you won't get stuck in a loop where a broken client keeps sending a particular option request and you keep replying to it – it lets you know what responses you've already given for each option, as well as knowing which responses the other end has given you. I have yet to see any major (or minor) MUD client or server that requires this, though.


Unnecessary as it might be, implementing it this way in .NET is pretty simple thanks to Generic Dictionaries. Make one struct\class to keep track whether the option is on, if you've started a negotiation, or whether you're supporting it at all and then store that by using the option byte as it's key. If you don't have it in your dictionary, assume all three are false, otherwise react based on whatever you've got stored.


V
18 Oct, 2008, Kjwah wrote in the 33rd comment:
Votes: 0
Weeks said:
I apologise, I didn't see that.

This was me, literally, this morning, only it was "Shouldn't you be at work??"

http://www.xkcd.com/386/


lol. That is so me..

Well, that's what my girlfriend just said. :p
20.0/33