Monday, March 5, 2012

Dart Mud... Progress 2

So I decided it's time for another progress report on my Dart based MUD. This project has be a lot of fun, and giving me experience with the dart:io libraries including Files, ServerSockets, and Sockets. Before I get into too much, I will mention that there is a new Integrated Dart Build available. Version 4760 (though SDK appears to be build number 4759). You can find your download here. Since using this build I have been getting errors and warnings when using the dart:io library. However it doesn't prevent my project from running properly. Just distracting thinking I have an error when I don't.

To follow up on my previous post, I have begun using git locally to archive changes and keep a bare-bones changelog system. Already I see this being helpful in keeping track of what I have changed or done, and additionally, in being able to revert changes easily. Once I clean up some of the code and add a plethora of comments, I may pop the project up onto Github as well.

My User Object now allows for the creation of a new character, automatic saving of a user and loading user (and password) from a save file. To save character information I'm currently placing the information I want saved into a map, and using saving it as a JSON object. In the future I can see some issues I may need to work out, but since I haven't decided on how things such as Objects, equipment etc will be implemented yet I don't know yet how much of an issue they will be.

As part of adding to the User object, I decided to create a separate Login object as well which will handle the accepting of username/password and loading the character and creating a new User object from the loaded information. Additionally the Login object also handles creating a new account, posing the various questions (username, password, password confirmation etc), then creating a new User object based off of the data provided. This way a full User object isn't created until credentials have been verified, and also prevents User from showing up in the user list, or receiving broadcast messages etc before they have completed the Login process.

As part of the work to separate the Login class from the User class, I also created a separate 'Connection' class as a wrapper over top of the raw sockets. Primarily this just creates a couple of convenience functions over the Socket for write, writeLine, readLine, set the lineHandler and close the Socket. I made this its own library, so it is imported into the Server which creates the initial socket and then wraps the Connection around it, and then hands it off to the mudlib (which is its own library). The mudlib also imports the Connection class. The mudlib then passes the Connection off to a new Login object which uses that to communicate to the user (again as opposed to the raw socket). The connection object is then passed off to the newly created User object once it is created. User object adds a couple more convenience functions as well but mostly works with the Connection object as well.

I've added a couple more commands as well. Now able to 'say' to the local room, change the default prompt (which does save to the User file), and of course the always abused 'emote' command. Additionally I've got a good start on a simple line editor (and a simple command wrapper for it to see the results). The Editor is a separate object which will take over the Input/Output streams of a socket from the user. It does not remove the user itself, nor does it take the user's connection object. Rather it just updates the Connection object to use the Editor's lineHandler.

The editor has two modes, Command mode and input put. By default you start in command mode, from which you can either insert or append which will put you into the input mode. To exit the input mode, one just needs type a period (.) on a blank line. The editor works similarly to 'ed' but a little prettier. (For instance a prompt of ': ' in command mode and prompt of '~ ' in input mode). Currently implemented commands in the editor: a - append, i - insert, q - quit, h - help, p - print line, d - delete line. In the future some commands such as p and d will accept a range argument as well however this is yet not implemented. There is no 'save', if you quit it will return whatever is in the buffer.

The editor is called by the User object. The User object in turn is called by some other command or object (most likely a command which will do something with the output). To start the edit the User object has startEdit method called on it with a callback function as an argument. Once the editor finishes it calls the method doneEdit in the User object which resets the proper line handlers, and then calls the previous callback function with the String returned from the Editor.

When I first started working with the Editor I thought the obvious solution to use it would be a StringBuffer. However after only a few minutes of implementing it with the StringBuffer I found it lacked much of the basic functionality I needed, such as getting a specific line from the buffer, or removing a line from the buffer. I also can only add to the end of the Buffer and not to any particular point in the buffer. So that didn't last long at all.
When I looked at the StringBufferImpl class in the library, I saw that it was nothing more than a List of Strings. So I decided to do the same. So I created two List of strings. One for the full Buffer, and one for a temporary buffer. The temporary buffer is used in input/edit mode. Upon leaving input mode back to command mode then the temporary buffer is inserted into the full buffer.
To accomplish this, I originally tried using List#setRange. However I quickly discovered that wouldn't work as it would just overwrite the range, as opposed to inserting at that point. Additionally if the temporary buffer would exceed the length of full buffer, then it would throw an exception. Even though the list is an extendable list. So I tried List#insertRange, and that expanded the List as required, but it will only initialize the new range to the same value. You can't insert the range with with list as the new values. So it required first using insertRange to add the space for the temporary list, then setRange to change those values to be the strings in the temporary buffer.

Another area of interest is that I have implemented a RoomManager. Eventually the RoomManager will be expanded to have time outs so once a room has gone 'stale' (no activity in it for x number of minutes) it will reset the room. And, if still not used after y number of minutes, the object will be unreferenced for the GC to clean up as it will. The first part of this however, was trying to to determine how I can efficiently 'dynamically' load the rooms when someone tries to access them, or alternatively reference an already loaded room. Since I can't use eval to load a file at this point, the code has to be loaded on start up.
So I created two maps, each map has a key of a room identifier. One map holds the rooms themselves if they are loaded. The other, roomFuncs holds functions which create the room. Rooms are added by calling the add method on the RoomManager, which takes a callback as an argument. The add method then runs the call back to create the room. It gets the room's unique identifier to use as the key. Then it stores the callback in the roomFuncs map. Then when it comes time to access the room, we call the method putIfAbsent method on the room map, passing our roomId and a reference to roomFuncs[roomId] which returns the callback to create the room if the object is not already loaded. The putIfAbsent is a nice little method, and quite handy for this.

Well that's about all for now anyways. I must say I'm having a blast writing this simple mud, and while I have a long way to go for a full featured mud, it is virtually usable at this point. Dart is a lot of fun!

No comments:

Post a Comment