Python for Massively Multiplayer
Virtual Worlds
Jason Asbahr
Origin Systems, Inc.
jason@asbahr.com
This paper presents a brief overview of the Python foundation of UO2*, the next-generation massively multiplayer virtual world from Origin Systems, Inc.
[ * NOTE: development code name, final name to be announced ]
1.
Introduction
In the early 1990s, before the rest of the world heard about the Internet, the primary source of technical hype and excitement was “virtual reality”. Early virtual reality systems showed exciting promise, but most implementations were expensive, fragile, and immature [3]. In the mid-to-late 1990s, as the public began streaming onto the Internet, virtual reality was most often popularly associated with VRML, an attempt to merge the display technologies of virtual reality with the networking technologies of the Web [7]. Today, virtual reality lives on in the form of massively multiplayer virtual worlds, systems that allow hundreds of thousands of distributed users to participate in rich entertainment simulations. It is the interactive entertainment, or game, industry that acts as the primary driving force for innovation in this space.
UO2
is Origin System’s next-generation virtual world, an imaginary planet inhabited
by all manner of fantastic, mythical, and technological creatures, places, and
things. To enter this world, users run
client software locally, connect to remote UO2 servers, and create one or more player
character avatars. Player
characters are highly customizable alternate identities who can walk around,
talk, interact with objects, fight with other characters, learn new skills, and
generally live life under the direction of the user. While characters start off life with basic skills, abilities, and
equipment, they gradually become more powerful through exploration of the
virtual world, battling monsters and opponents, and solving quests.
The
new world of UO2 builds on the legacy of the swords and sorcery Ultima fiction
originally created by Richard Garriott [1].
Like the original Ultima Online, UO2 brings thousands of players
together in an artificial world.
However, in addition to adding new chapters to the Ultima story, UO2
also expands on several technical fronts, enabling a system flexible enough for
the rapid creation of many future virtual worlds. The system has an entirely
new technological foundation of a robust server architecture and network
protocol on the back end and an advanced real-time 3D graphics engine on the
front end. Further, UO2 combines a
highly data-driven design with simulation classes on both client and server,
using the standard and well supported dynamic object oriented language,
Python.
This
paper will address three key ideas in the philosophy of the UO2 architecture.
The first is the structure of the logical simulation domain; the second, the
split between the maintenance of simulation reality and the presentation of
that reality to users; and finally, the means by which the simulation utilizes
a relational database for behavior definition and object state storage.
2.
Simulation Domain
The
Ultima universe is a reality similar to ours, approximately human scaled, with
all the abstractions and operational rules one would expect to find in a domain
similar the world we all live in, with one important difference. The difference is magic, a force which twists the most straightforward of simulation
design into a hairy tangle, if not carefully managed. For example, the logic of an ordinary sword is fairly
straightforward, but our designers would like that sword to acquire special
behavior if it is enchanted by a wizard, say to do extract damage against
certain types of undead creatures.
Behavioral adjustments like this need to be able to happen often, and at
runtime, with no code changes or server downtime. Python serves this requirement particularly well.
UO2
represents entities active in the virtual world using the SimObject pattern
[2]. High-level SimObjects implemented
in Python represent the logical simulation domain of the Ultima fictional
universe. SimObjects represent both
instances of characters, houses, and weapons, and encode the behavior and
interaction of such objects. Associated
Body instances implemented in C++ represent the physical aspect of the
simulated objects. Body instances have
mass and dimensionality, collide with each other, travel through space, animate
in the world, and react to physical forces such as gravity.
UO2
defines several simulation subclasses, such as Character, Armor, and Weapon, and so on, which establish the
base behavior for such objects in an “Earth-normal” environment. Operational simulation logic defined by the
SimObject subclasses is split into two phases, a test phase and execution
phase. All tests defined in the
test phase must pass before any changes to the simulation are allowed to happen
in the execution phase.
Base
behavior of the SimObject subclasses is extended by an approach that combines
inheritance and delegation via a mechanism we call “Properties”. Properties are additional pieces of behavior
implemented as unbound Python functions grouped together in modules. Known interfaces on the base classes are
established as protocol sets which can be extended in a chained manner
by Properties. This approach allows
both the test and execution phases of particular SimObject actions to be
augmented by layering additional Properties onto the instance. Attaching a Property to an instance at
runtime has the effect of changing the behavior of the instance as if a new
subclass had somehow been inserted into the inheritance hierarchy for that
instance only. Properties attached
at the class level have the effect of augmenting behavior for all existing and
future instances of that class.
As
a simplified example, a Property might extend the CanUse and Use
interfaces to perform additional checks on the testing phase and execution
phase of the use of an Item. Given an
ordinary instance of a Sword, a subclass of the Weapon class which is in turn a
subclass of Item, a character can pick it up, wield it, swing it around, and so
on. But for our example, a wizard has
enchanted this sword so that it is magically more powerful against undead
creatures, such as zombies, vampires, and skeleton warriors. As a side effect, the enchanted sword can
now only be wielded by those characters who are pure of heart, those who have
performed good deeds and have never stolen or cheated at dice. On the server, this results in the attachment
of two new Properties, UndeadSlaying and PureHeart.
Attaching
a Property to an instance triggers the processing of the list of methods which
that Property augments, causing a reference to each unbound function of the
Property to be acquired and stored in a collection maintained by the
instance. In the example, CanUse
and Use are known augmentation hook points, and UndeadSlaying defines a Use
augmentation function while PureHeart defines a CanUse function, as
shown in Figure 2.
When
a character attempts to swing the sword, CanUse is called on the
instance, which in turn calls all CanUse augmentation functions in its
internal property collection. In this
case, PureHeart performs an additional check during this test phase to
determine if the character’s karma is sufficiently good. In
the case that a character’s karma is sufficiently bad, the PureHeart CanUse return false and immediately breaks out of the calling chain. No Use methods are called
because all tests must pass before any simulation execution happens. However, in the case that the
character’s karma is good, the PureHeart CanUse method return true and calling continues down the CanUse chain.
Assuming all other CanUse
tests pass, the code goes on to call Use,
which performs the usual work of the sword, and then calls all of the property
execution functions. This includes the Use function defined by the UndeadSlaying Property,
which performs a check to determine if the target is undead, and if so, to
increase the amount of damage done to the target.
3.
Client Interpretation, Server Arbitration
The
Python portions of the UO2 architecture ride on an advanced third-generation
design for threaded servers, message passing, network communication, and
sophisticated 3D animation and rendering. Python interpreters run on both clients and servers.
On
the server-side, the simulated world is divided up into arbitrary regions, each
region running on one AreaServer process, as shown in Figure 1. SimObjects in the world may cross server
boundaries at will, either by teleportation from one location to any other or
simply by “walking” from one region to another across the boundary. Server boundaries can be adjusted to balance
simulation load across AreaServer processes, and crossing server boundaries is
completely transparent to the client.
A
user’s primary experience of the virtual world is that presented by the client.
The UO2 client is a sophisticated engine for graphics and sound, enabling
highly detailed and customizable avatars, special effects, foley, and
soundtracks, which adapt to dramatic action in the world. The client process runs locally on the
user’s computer, generating real-time 3D graphics, text, and sound representing
the state of the simulation. The client
also accepts input from the user and translating that input into requests for
the user’s character avatar to perform actions on the server.
Once
connected to the server, the client exchanges information in an encrypted
stream, receiving simulation updates and sending action requests. Interaction at the logical simulation level
is in the form of remote method invocations between Python SimObject instances
and service Singletons on the server and client using a custom Python mechanism
similar to Java’s RMI [9] or Pyro [4].
Interactions at the physical simulation level are handled through
specific C++ message classes for high speed processing of position and
orientation.
An
important design decision made early on in the life of this project was that
the server would never trust the client, meaning that all simulation decisions
are made by the server. In UO2,
SimObjects running on the servers represent the “One True” version of
reality. SimObjects running on clients
represent proxies of SimObjects on the server, and are subject to correction
and adjustment based on server arbitration.
This
puts the client in the position of making requests for the server to perform if
and only if all checks for that request pass on the server. In practical terms, the split between client
request and server arbitration helps to maintain a consistent view of simulated
reality across all clients. A user who
attempts to modify the state of the shared simulation by hacking their own
client will, at best, merely invalidate their local representation of the
simulation.
4.
Data Driven Classes and Properties
A
primary strength of the system is the degree to which the simulation is
data-driven. The AreaServers
communicate with a DBServer, which presents a relational database to the
simulation. An encompassing set of
tables, views, and stored procedures, which we refer to as the Master Game
Database, captures the data for the archetypes of our fictional world. The Master Game Database allows world
builders and designers to significantly expand the world with new entities and
behaviors simply by entering new data and without changing code.
The DBServer loads a handful of startup tables that define other tables to load and how to deal with them. Python code running on the AreaServer processes the startup tables differently depending on the kind of table. Some tables can be simply converted into symbolic “constant modules” in the Python runtime, which are imported and referenced by code or by other tables. Examples of these modules include ParserCommand, PlayerMessage, and EffectContext. Other more complex tables are loaded for additional processing and association as runtime data, such as PublishedMesh, CombatStroke, and StartingCity. Of course, tables can be both, such as GameAttribute, Race, and EquipmentSlot, which require additional processing but still present convenient constants that other code can use.
The most complex of the tables loaded and processed by AreaServers are those which define new subclasses of the standard base classes. Tables exist for each of the “leaf core classes” of the standard inheritance hierarchy, with additional rows for each subclass of the base class. Each row defines values for class attributes and a set of 0..n Properties which can be attached to the class. On AreaServer start, the contents of these tables are loaded and new subclasses are constructed at runtime from this data. Properties associated with classes at this level enable all instances of that class to gain the additional test and execution behavior. This powerful feature allows designers to specify all variations of the core classes simply by changing data and mixing and matching Properties.
Since
the set of SimObject instances on all servers constitutes the entire state of
the virtual world, it necessary to archive SimObjects to disk over time. The archival preserves an updated state of
the world and to be enables the restoration of world state in the case of
server shutdown. The DBServer again
comes into play here, consisting of a set of tables for storing SimObject
state, which we refer to as the Runtime Game Database. AreaServers periodically serialize SimObject
instances using a modified version of the standard Python cPickle [8] and
transmit the serialized data to the DBServer.
New instances are serialized immediately upon creation and added via SQL
INSERTs. When serialized again,
instance data is archived using SQL UPDATEs.
The system is also flexible enough to handle changing server boundaries,
so that SimObject instances are automatically reloaded into the appropriate
AreaServer region encompassed by the new boundary settings.
6.
Future Work
The
UO2 system will continue to be expand, forming the basis of a reusable shared
software architecture for future Origin Systems products. The flexible nature of the UO2 system allows
for easy experimentation, allowing rapid expansion and adjustment. User feedback will drive the incorporation
of new features and the general improvement of the virtual world experience as
we go forward. Periodic major additions
will be released to the user base over time, including the capability for users
to own houses in the virtual world, tame and train virtual pets, and develop
new professions within the game.
In
addition to entertainment purposes, technology like the UO2 system has enormous
potential for educational use [3].
Other advanced forms of computer interaction, such as wearable computing
[6] will require new metaphors and interaction models, some of which may be
perfectly suited for networked virtual world presentation.
I
believe that the future of the Internet and the personal computing experience
in general will be highly interactive, rich, and immersive three-dimensional
persistent virtual simulations.
Languages like Python and the development communities that grow up
around it are a critical part of this future.
I
want to express special thanks to everyone on the UO2 team at Origin, it’s been
a wonderful experience to work with such a talented, dedicated, and selfless
group of professionals. Thanks to my
friends and reviewers, Michael Crawford, Heather Dority, John Edwards, Edward
Robinson, and David Stidolph. Also
special thanks to the Python community members who have given of their personal
time, David Ascher, David Beazley, Paul Dubois, Mark Hammond, and Mark Lutz.
[1]
Addams, S., The Official Book of Ultima, Compute Books, 1992.
[2]
Asbahr, J., “Beyond: A Portable Virtual World Simulation Framework”,
Proceedings of the 7th International Python Conference, Houston,
Texas, November 10-13, 1998.
[3] Bruckman, A., “Community Support for
Constructionist Learning”, ACM Conference on Computer Supported Cooperative
Work, Seattle, Washington, November 14-18, 1998.
[4]
de Jong, I., Pyro Home Page, http://www.xs4all.nl/~irmen/ap/pyro.html.
[5] Laurel, B., “Post-Virtual Reality: After the Hype is Over”, Computers as Theatre, Addison-Wesley, 1993, pp. 199-214.
[6]
Picard, R., “Affective Wearables”, Affective Computing, MIT Press, 1997,
pp. 227-246.
[7]
Pesce, M., VRML – Browsing and Building Cyberspace, New Riders Publishing,
Indianapolis, Indiana, 1995.
[8]
PythonLabs, cPickle – An Alternate Implementation of Pickle,
http://www.python.org/doc/current/lib/module-cPickle.html.
[9] Sun Microsystems, Inc., Java Remote Method
Invocation (RMI) Home Page, http://java.sun.com/products/jdk/rmi/index.html.

