[MUD-Dev] [adv-mud] Spellbound Hierarchy and Keys (fwd)
J C Lawrence
claw at kanga.nu
Thu Jan 20 20:39:29 CET 2000
------- Forwarded Message
From: "phlUID" <phluid at mindless.com>
To: "Advanced Mud Listserv" <adv-mud at egroups.com>
Date: Thu, 20 Jan 2000 22:31:48 -0500
I would like to share something small I've been working on
and see if anyone has any opinions on perhaps a better way to
go about doing this:
On Spellbound, every object in the game is given it's own
database number. Help files, races, spells, objects, rooms,
mobiles, exits, worlds, areas, classes, populations, etc.
There are generically two types of database numbers: Pure,
which is just a simple unique number given to every object,
and Hybrid, which is used in the case of Object's and
Mobs. For example, a room is object #43, while the sword
in my inventory is #45:1 and the same type of sword in
my friend's inventory is #45:2. The second number is
referred to as an Entity ID because they refer to specific
Entity's instead of Entries. There is an Entry class for
#45 called ObjectEntry, and there is an associated Entity
class for ObjectEntry called Object. This concept is the
same as DIKU's use of OBJ_INDEX_DATA and OBJ_DATA.
Both Entities and Entries are inheriting from the Savable
class. Everything in the game is Savable more or less,
allowing for the game to remain persistent even across
reboots. Anything that is Savable can be editted in OLC
by using the special attribute manipulation commands built
into the server. Every savable has Attributes, such as
Name, Size, Weight, Owner, and these attributes have
a table of rules that define valid ranges for setting the
actual variables underneath.
The overall hierarchy looks something like this:
Streamable
| |
HIERARCHY OF SPELLBOUND | +---------------------+
(Inheritence Relationships) | Eventable |
| | | | |
Savable------------- | +----------+ | +----+ |
| \| | System | |
+--Entry--+ +--Entity-+ | |
| | | | | | | | | | | Descriptor
+--+ | | | | +---*Room----+ | | | +--+
| | | | +-----*Area------+ | | |
| +--+ | +-------*Exit--------+ +--+ |
| | +------------+ | |
| +-*ObjectEntry | #Object---+ |
+---*CharacterEntry | #Character---+
| |
* - Perminent +-----*Player
# - Persistent
The Streamable class is how general communication is done
between objects, such as sending text to a player or echoing
text to a room. The code just does something like this:
PlayerBob << "{RDon't Panic{x\n\r" << flushl;
The flushl is detected and a subsequent flush( ) is called
on the Streamable class, which flushes the stream buffer.
The actual flush( ) must be defined to make Streamable no
longer an abstract class. The flush( ) for player's empies
out the text to their descriptors (any entity may have
multiple descriptors, ala MUSH.)
I've been using this hierarchy for quite a while, and I've
never had any problems with it.
Anyways.
One day I was coding and thinking about a generic MUD coding
problem:
Currently, Spellbound uses pointers directly to objects
like normal DIKU mud. A room has pointers to the people
that are in the room. An event has a pointer to it's
target. A descriptor has a pointer to it's Incarnation
(Entity). What started to irk me was the fact that these
pointers require a lot of messy cleanup procedures for
when you are destroying the object that they are all
pointing too. This is made worse by the object orientness
of C++, so I began to think of alternate ways of storing
pointers.
The first thing that I wanted to be able to do is validate
pointers. This way I can leave it up to the code that
references these pointers to deal with invalid objects.
Example time.
Bob is in a room. Joe is attacking bob, and has a pointer
to him as a victim. Fred is casting 'Poop Breath' on
Bob. There is an event in the room that is about to make
Bob explode. There is also three mobs using pathfinding
throughout the world to find Bob and hunt him down. However,
Bob somehow upset the admins and they suddenly decide to
@nuke him (destroy him). Now, it is possible for the code
to inform the ENTIRE game that Bob is no longer in existence,
but this would be the most bloatedly laggy thing I can think
of, expessially when we are dealing with objects that are
very temporary, such as Spell Affects.
To give a little more backround, my database saving routine
has a method of converting pointer's into their Database
Numbers and optional Entity IDs so that loading and saving
does not become a problem. The database saving/loading
mechanism also has 4 stages, in which it loads plaintext
data first, pointer data next, entity plaintext data, and
then entity pointer data. This is so that the world can
be reassembled without worrying about object's that don't
exist when you are trying to get pointers to them.
This started my thinking into the idea that it would be
possible to replicate this system to make sure that what
I am referencing is actually there. My database uses
a regular array to return pointer's to DB numbers, so
there is no CPU time involved looking through tables or
linked lists. If I want object #43, I just call
Database.getEntry( 43 ) and it returns a pointer to
that entry.
At this point I made the Key object. A Key is a very
small class that stores 3 number's about the pointer we
want to save. Instead of using a raw pointer to what
I need, I just store the database number, the entity ID,
and a unique identifier. Every savable in the game needs
to have a unique identifier to make sure that we are
actually talking about the right object. This identifier
consists of a count of exactly how many nano or milliseconds
from the epoch. Assuming that no two object's can be created
at the exact same time, this gives each object ever created
a unique identifier.
Lets say I am Fred and I'm attacking Bob. Now, instead of
having a pointer directly to Bob, I have a Key which is
holding Bob's database number, entity ID, and time identifier.
Lets say this is #23, 0, and 48942809324932723. Whenever I
need to get the pointer to Bob to send a message or do
damage, I just ask the database to get me Entity #23:0. I
then need to compare my time identifier with his, because
it is possible that #23 was destroyed and then recreated
as a Fruit Cake, and I'll suddenly be attacking a Fruit Cake
instead of Bob. After a quick validation that our time
identifiers are still the same, I know that my pointer is
valid and I can safely attack Bob.
If the database couldn't find #23, entity 0, or the time
identifier was wrong, I would end up with a NULL pointer,
which will just stop my action and inform Fred that Bob
is no longer there. Simple, effecient error control.
The neat thing about this is that it is also very simple.
The only real question that I had is if anyone can see
ways to improve this process. On the technical side, I
need a way of grabbing the time( ) in an extremely
precise manner, so that even if I create 100 object's in
the same cpu cycle I will end up with unique identifiers
for every object ever created.
Amos Wetherbee,
The Spellbound Project
------- End of Forwarded Message
_______________________________________________
MUD-Dev maillist - MUD-Dev at kanga.nu
http://www.kanga.nu/lists/listinfo/mud-dev
More information about the mud-dev-archive
mailing list