Observable
was not serializable
Java has two standard classes to implement the Observer pattern:
Observer
and Observable
. Observers can register
themself to an Observable
, which notifies all registered
observers when it has changed some internal data. By inheriting
from this Observable
class, a user can create observable
software components.
The registration of the observers is internally done by Observable
in a private Vector
. The problem is that Sun for some
reason didn't make Observable
serializable. This means you
can't create a subclass of Observable
that can be serialized,
since the private Vector
that contains all the observers
is lost in the serialization process.
This posed a big problem, since the Observer pattern was used extensivily in the package {@link quest.global.game}, which implements the logic of an Amazing Quest game. The root object that defines a complete game ({@link quest.global.game.GameLogic}) is constructed at the game server, and then sent to all the clients. Because all the registered observers inside that object are lost during the serialization and deserialization of the observable parts, clients received a crippled version of the object.
We made a workaround, {@link quest.global.util.SerializableObservable},
that extends Observable
by making it serializable. This is
done by providing the same functionality as the base class, but saving
the data (the registered observers) also in the derived class. This redundant
data is correctly serialized. During the deserialization process, the
SerializableObservable
restores the data in the base class
with its redundant information. This way the registred observers were
not lost, and the code could still use the Java Observer
class.
ObjectInputStream
issuesWe had to make a design decission of how to internally support multiple concurrent connections with the server. The decission we made afterwards posed that it was not possible to detect client timeouts.
The default Java (Sun) paradigm is to use a single thread per listening client. At that time we found that paradigm outrageous because the server would have to support possibly hundreds to thousands of clients, and that would produce much threading overhead. This threading overhead could of course be minimalized if the host operating system's scheduler is sophisticated enough. Because we didn't want to depend on the operating system that much in order to let the server function reasonably fast, we chose to use a single thread for receiving the messages, which spawns seperate threads for handling the responses.
This way we had to rely on the available
method of
ObjectInputStream
to return the amount of bytes that could be
read from the input stream. The problem was that this method
didn't return non-zero when the other side had (wanted to) sent an object over this connection.
Possible because ObjectInputstream
and ObjectOutputstream
classes use a rendez-vous scheme that has to be hidden from the user. We circumvented
this problem by using the SocketInputstream the ObjectInputStream was based on. Here the
available
method worked reliably.
Also it occurred to us that it wasn't possible to determine whether a connection
was terminated, using the available
method. This was a serious problem
because the termination could only be determined by sending or trying to receive
a message or object. We found it strange that in the available
method the
IOException
exception was declared in the API, but never thrown. The
documentation wasn't clear about when this exception would be raised. A glance at
Sun's Java forum at java.sun.com made clear out of numerous developer discussions that
Sun didn't (want to) actively support the single-thread solution. Because the only solution
to the single-thread problem would be a hack, Sun will probably succeed in
enforcing their thread-per-client solution.
(This way they would probably force heavy-load servers to use an OS/system that would support this solution (read: sun products).)
We generated the following solutions to this problem:
The 3D maze that makes up the games' board contains a number of treasures that are identified by their color and symbol. This is implemented by using a black-and-white texture for each symbol, and mapping this onto a 3D box (which represents a treasure) with a certain color. By blending the texture with the color of the box, we needed only one texture for each symbol, instead of needing a seperate texture for each combination of a color and a symbol.
The problem is that the same blending behaviors differently on various execution platforms. In Windows98, the blending results in black boxes with a colored symbol on it. In Windows NT, the same code results in colored boxed with a black symbol on it. So much for platform-independency.
Since this doesn't hinder the game from being played (you can still recognize the treasures) we just accept this behavior of Java3D.
Loading of the applet in netscape under SunOS resulted in a ModifyThreadGroup exception when the classpath included the path where the classes resided. Typically when including '.' in the classpath and starting netscape in the root of the quest directory, Java produced a couple of SecurityExceptions. The simple solution was to just start netscape in another directory, the real reason why netscape did value CLASSPATH over the Java plugin settings couldn't be determined.
To run Java3D programs it has to be installed, of course. The Windows NT machines didn't have a decent Java installation at all untill the start of the 2001/2002 college year, so we had to test everything on UNIX machines. Getting the JavaPlugin to work under UNIX Netscape was an exercise on it's own, by the way.
The problem was that Java3D program only ran on hardware OpenGL accelerated X-servers, since the installed OpenGL software libraries where too old to be used by Java3D. There was only one machine available for students with a special 3D graphics card (the one the student assistant room). Since the game was meant to run with multiple client, testing the behavior of the program with multiple clients became almost impossible.
We discovered that by using Exceed on the Windows NT machines (which are all installed with a 3D graphics card) we could use the Java installation of UNIX and the OpenGL capabilities of these computers. This method was still not very satisfactory, since the Exceed-layer in between made the system very slow, and a lot of fonts and keybindings weren't available this way. This crippled the program and the testing of it a lot.
At the start of the new college year, all the Windows NT machines were installed with the latest JDK and Java3D version, so finally we got a decent testing environment. Much of the testing of multiple clients is therefore done in the last week before the deadline, so we weren't able remove all the bugs involving multiple clients.
We experienced a lot of crashes during the testing. Sometimes the JavaPlugin just freezes, and the runtime system of Java3D also crashes sometimes. It even made Windows NT crash a number of times, which isn't done very easily. Since it is still being developed, the relativily unstable nature of Java3D is understandable, but irritating sometimes. Especially the testing of multiple clients was hindered by it, since once a while a client just crashed.
The only available webserver we could get shell permission on to run a server for our game was the raderboot. We needed a shell account to run the quest server on because of a java applet sandbox restriction: the applet can only open connections with the server where the codebase resides. The machine on which the webserver runs is a SPARCstation 5 with a 70Mhz processor and 64Mb of physical RAM. Furthermore a practical course 'Inleiding Gegevensverwerking'(IGV) was being held in spring 2001 on the same machine, on which participants used the Mysql server also operating there. On busy days it took for ages to retrieve the applet over the webserver and to start the server. Using an external server (out of reach of the NFS we used) would introduce file synchronization problems and an upload delay. Because of this we chose to use the raderboot nevertheless as webserver.
The interface of the game, which uses Java3D, was made by Mathijs den Burger:
"Understanding how to work with Java3D took some time. The first challenge was to show some 3D object at all. You have to create a standard scene graph part which defines how the user views the scene before Java3D will display your 3D world. The demo's that come with Java3D mostly use the default SimpleUniverseManager to handle that part automatically. This class hides a lot of things I wanted to control myself (such as placing the viewer at a certain position in the 3D world), so it took a while before I understood how to do this part myself and was able to show simple 3D objects in a window.
The theory of the scene graph was pretty clear, but using it was a different story. When I understood this part, building the static scene was possible. The next difficult part were the animations: most examples that come with Java3D perform a continues animation (like spinning a cube), but the program would have to perform a different animation each time (e.g. to let the user walk through the maze). This was solved by generating the desired behavior at runtime, and adding it to the scene graph.
The last problem was to select objects in the 3D scene. The basic selection was possible by using the classes about picking in Java3D. This I used in a little framework for selecting 3D objects, by defining
PickableObjects
about which aPickBehavior
could dispatchPickEvents
to interestedPickListeners
. This provided a very usefull way of handling the selection of 3D objects.A lot of Java3D's functionality isn't used in this game. Especially the methods to load complex 3D models and display all kinds of fancy 3D objects aren't used. But this practical work wasn't about 3D modeling of course."
The interface of the login procedure and chat environment was made by Sander Brandenburg:
"Because I've worked with Swing in the practical course "Software Engineering" there wasn't much new to me. Though this time I used much of the nifty 'Document' features that visualize panels on which you can draw colored or styled text etc. The biggest problem was how to structure the GUI in such a way that it was easy to communicate with eachother. Though the search, help and ranking overviews are quite distinct features, the chat, globalchat and channeloverview had to communicate with eachother in order to decide whether to enable or disable certain actions (buttons). The Singleton design pattern aided me in solving this, because all windows had to be instantiated only once.
Most of the GUI work was done using GridBagLayout as the layoutmanager, and emacs as editing tool. Because GridBagLayout offers lots of flexibility over bunches of variables, it was hard to design the GUI in text mode and evaluate them in Java, which takes a while to load. Still, sometimes a frame was layouted differently (read: strangely) in CDE X, KDE2 X, Exceed under Windows NT and plain Windows NT, which gave us a hard time."
The central server was made by Merijn Evertse:
"Jaah eeuh...zat ik net lekker te poepen komt er een bronstige berggnoe voorbijgevlogen die m'n Windows 2000 bak onderschijt."
Tot zover Merijn's commentaar :)