[lmud-devel] Re: Lisp MUD development
Brandon Edens
brandon at cs.uri.edu
Fri Mar 9 14:52:51 UTC 2007
On Thu, Mar 08, 2007 at 06:37:04PM -0600, Thomas A Lenius wrote:
> i think locking is one reason to use a decent db (postgresql in our
> possible implementation)
Yeah, it is going to be an issue. For some reason I thought file operations in
*nix were atomic. At least they aren't in Linux. I ran a cat > yoyo.txt test
from two bash instances and was randomly merging data into each.
I'll wait until it becomes something serious then introduce locking. I could
also channel saves through a single mechanism to ensure that multiple threads
aren't saving the same data at the same time.
I've used cl-sql both the mysql uffi wrapper and the postgresql network adapter.
I've got to admit that Kevin did a great job with that. My only complaint is the
usage of uffi instead of cffi but he wrote uffi so can we blame him?
> also it simplifies porting. the storage format can remain a blackbox; all
> clients need to know is sql, i.e., have a driver. compare with keeping
> track of details for a custom fs level format
Actually, this is what I was going to write about so I'll just toss it out right
now. Still waiting on the acceptance of the Sourceforge project. Let me give you
the basic mechanics for how I'm doing save/load.
This will be a bit abbreviated and is definitely a candidate for macro goodness
because I have near identical machinery between players, objects, and mobs.
(defclass player ()
((username
:initarg :username
:initform "")
(password
:initarg :password
:initform "")))
(defmethod to-list ((player player))
"Converts a player object into a list."
(with-slots (username password) player
(list 'PLAYER :username username :password password)))
(defmethod print-list ((player player) &optional (stream t))
"Prints out the player information in list format."
(format stream "~s" (to-list player)))
(defmethod print-object ((player player) stream)
"Print object overloaded for players."
(print-unreadable-object (player stream :type t :identity t)
(print-list player stream)))
(defmethod save ((player player))
"Saves a player to disk."
(with-slots (username) player
(log-debug "Saved player ~a." username)
(write-player username (to-list player))))
(defun make-player (data)
"Makes a player from given data (most likely a (PLAYER ... list))."
(etypecase data
(cons
(apply 'make-instance 'player (rest data)))))
(defun read-player (username)
"Loads and reads a player with given username from disk."
(with-open-file (stream (merge-pathnames *player-directory*
(format nil "~a.lisp" (string-downcase username)))
:direction :input
:if-does-not-exist :error)
(with-standard-io-syntax
(read stream))))
(defun write-player (username data)
"Saves a player with the given username to disk."
(with-open-file (stream (merge-pathnames *player-directory*
(add-lisp-suffix
(space-to-underscore
(string-downcase username))))
:direction :output
:if-exists :overwrite
:if-does-not-exist :create)
(with-standard-io-syntax
(print data stream))))
Here's an example of it's usage (this is my real player class).
BMUD> (make-instance 'player :username "tom" :password (hash-password "ping-pong") :firstname "Thomas" :lastname "Jones" :email "tjones at yoyo.com")
#<PLAYER (PLAYER :USERNAME "tom" :PASSWORD
"f2764ee70e739a32a7aa4de35b005184290d50f51b1bb9cc27573f275d8ebb33"
:FIRSTNAME "Thomas" :LASTNAME "Jones" :EMAIL "tjones at yoyo.com"
:CREATED-TIMESTAMP 3382439955 :TOTAL-TIME-PLAYED 0 :PLAYER-KILLS 0
:PLAYER-DEATHS 0 :MOB-KILLS 0 :MOB-DEATHS 0 :MOB NIL :PROMPT-COLOR
:GREY :CONNECT-TIME 3382439955 :IDLE-TIME 0 :TITLE "") {10024583F1}>
BMUD>
I save this new player.
BMUD> (save (make-instance 'player :username "tom" :password (hash-password "ping-pong") :firstname "Thomas" :lastname "Jones" :email "tjones at yoyo.com"))
Now to show what it looks like on the filesystem.
brandon at bruno ~/bmud/var/players $ cat tom.lisp
(BMUD::PLAYER :USERNAME "tom" :PASSWORD "f2764ee70e739a32a7aa4de35b005184290d50f51b1bb9cc27573f275d8ebb33" :FIRSTNAME "Thomas" :LASTNAME "Jones" :EMAIL "tjones at yoyo.com" :CREATED-TIMESTAMP 3382439987 :TOTAL-TIME-PLAYED 0 :PLAYER-KILLS 0 :PLAYER-DEATHS 0 :MOB-KILLS 0 :MOB-DEATHS 0 :MOB NIL :PROMPT-COLOR :GREY :CONNECT-TIME 3382439987 :IDLE-TIME 0 :TITLE "")
Now I go ahead and load the player "tom".
BMUD> (read-player "tom")
(PLAYER :USERNAME "tom" :PASSWORD
"f2764ee70e739a32a7aa4de35b005184290d50f51b1bb9cc27573f275d8ebb33" :FIRSTNAME
"Thomas" :LASTNAME "Jones" :EMAIL "tjones at yoyo.com" :CREATED-TIMESTAMP
3382439987 :TOTAL-TIME-PLAYED 0 :PLAYER-KILLS 0 :PLAYER-DEATHS 0 :MOB-KILLS 0
:MOB-DEATHS 0 :MOB NIL :PROMPT-COLOR :GREY :CONNECT-TIME 3382439987 :IDLE-TIME
0 :TITLE "")
BMUD> (make-player (read-player "tom"))
#<PLAYER (PLAYER :USERNAME "tom" :PASSWORD
"f2764ee70e739a32a7aa4de35b005184290d50f51b1bb9cc27573f275d8ebb33"
:FIRSTNAME "Thomas" :LASTNAME "Jones" :EMAIL "tjones at yoyo.com"
:CREATED-TIMESTAMP 3382439987 :TOTAL-TIME-PLAYED 0 :PLAYER-KILLS 0
:PLAYER-DEATHS 0 :MOB-KILLS 0 :MOB-DEATHS 0 :MOB NIL :PROMPT-COLOR
:GREY :CONNECT-TIME 3382439987 :IDLE-TIME 0 :TITLE "") {10028F9001}>
If you see anything that could be further improved please let me know. I'm
probably going to go back to using random serial number's for mobs and objects
soon. ie bmud/var/mobs/mob-3382440161-2AED195510E176A-1CFAE095F174DF5.lisp
for instance.
(defun generate-random-serial-number ()
"Generates a random serial number using current time and some random numbers.
We're hoping that through the massive size of the number that we might be able to
produce something suitably random."
(format nil "~10,'0d-~15,'0x-~15,'0x"
(get-universal-time)
(random 999999999999999999)
(random 999999999999999999)))
I might actually store them under the mob's current "zone" location to ease
loading all the mobs last held in a zone when the zone is loaded back into the
game. Of course you run the risk of possibly duplicating mobs; if you move a mob
from one zone to another, then don't properly remove the old mob after saving it
in its new zone location.
Some of the benefits of this system is that it is data is stored in lists and is
parseable by Common Lisp. It is trivial to add new features to
mobs/objects/players. Even if you needed to modify all of the existing records
to remove some existing feature, well we're talking about normal *nix text
processing tools like grep/sed/awk/etc...
> but i haven't looked at what anyone else is doing so maybe i'm off.
> performance is one reason i can see for going without a db
That is a big one for me. Saving/Loading mobs is O(1) on the filesystem. Also
because my game is persistent I'm going to be modifying game world contents
constantly.
I'm writing the game to run on my Gentoo GNU/Linux machine. I don't really care
about Mac OSX or Windows. That's probably the wrong attitude. I guess if SBCL
runs there I'll make the effort to ensure that it works. Right now I just want
to get something off the ground. I mean in the end we're talking about Common
Lisp aka we're not talking about programming in assembly, heh.
If anyone knows why this is a terrible idea I'm all ears. I feel like I'm in
uncharted waters most of the time.
Brandon
--
Brandon Edens brandon at cs.uri.edu
http://www.brandonedens.org
key 0x55438F48
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: not available
URL: <https://mailman.common-lisp.net/pipermail/lmud-devel/attachments/20070309/4f4586e3/attachment.sig>
More information about the lmud-devel
mailing list