Checkpointing is done using Java's serialization facility. The checkpoint file is a standard Java serialized file, but has been gzipped. Only model objects are checkpointed out, not visualization objects.
If you want to checkpoint out on one platform and read in on another platform, your classes must all have the same serialversionUIDs. Different compilers will provide different serialversionUIDs to non-static inner classes (including anonymous classes) and the outer classes they are nested within. If you've got inner classes in your model code, you must provide your own hard-coded serialversionUID. See Tutorial3 for an extended discussion of this. None of this matters if you're writing and reading on the same platform (and with the same compiler).
Some Java classes cannot be checkpointed: for example, various Graphics2D classes. This presents a problem if you rely on them in your model. You may need to make custom serializations for your classes which use these classes. See the MavDemo model code for an example (particularly the Obstacle.java file).
Communicating Between the Model Thread and the Visualization Thread
MASON's visualization system runs the model underneath in a separate thread. This can create race conditions for visualization tools. Here are some synchronization rules for you to follow.
How it works. The Console first calls start(), then creates the underlying play thread. After the thread dies, the Console calls finish(). Thus code in the start() and finish() methods are always executed in the plain Swing event thread.
The model thread does the following:
Also, items stepped in the GUIState's "extreme" queues are done so while holding onto the schedule lock.
Thus locking on the schedule is one way to know that you're in a safe situation with regard to the underlying model thread (it's waiting for you). Additionally, you should be aware that repaints() can occur at any time.
Most event loop and repaint things should be done by acquiring the schedule lock before getting information from the model. Remember to make your locks as brief as possible.
If that's so, then how do we stop the thread from the pause or stop button? Because the play thread blocks ont he event queue at step #5, consider the situation of pausing or stopping it. The play thread has blocked. The user pressed the stop button. We try to join() the play thread. Now the event loop has blocked on the play thread and the play thread is blocking on the event loop. Deadlock! To deal with this, we repeatedly interrupt() (with a 50ms spin-wait) the play thread until it responds with an "okay, okay, I'm dying, sheesh" signal, and then we join() it.
General Locking Rules
Your other option is to just bag it and risk the race condition. If you're
just setting a boolean value or an integer or a float, you're probably safe
here. If you're setting a double or a long, or anything bigger (an array?
class?), you're unsafe.
Using MASON's Multi-threaded Model Facilities
MASON provides two opportunities for parallel threading at the model level: parallel Sequences and asynchronous Steppables.
A ParallelSequence is a Steppable which holds a set of subsidiary Steppables, and which can be scheduled on the Schedule. Unlike other Sequences which iterate through these Steppables in series, the ParallelSequence calls all of its subsidiary Steppables in parallel, each in its own thread, then waits until they have completed before it returns. The HeatBugs example code shows one approach to parallelizing the model using a ParallelSequence, resulting in nearly a two-times speedup on a dual-processor machine. ParallelSequences can be serialized.
An AsynchronousSteppable is a Steppable which, when stepped, runs in its own thread. Such Steppables may run in an infinite loop in their own thread forever until Stopped either by a later action in the Schedule or by the end of the simulation; or they may fire off a separate thread to do a one-shot asynchronous task in the background and then quit. AsynchronousSteppables can be serialized.
There are important differences between these two parallel models. ParallelSequences perform their actions in sync with the schedule: the ParallelSequence is stepped, it spawns its threads and lets them run, it waits for them to complete, and then it returns from its step. Thus all the actions of a ParallelSequence are done in one timestep and the schedule waits for them to finish. But an AsynchronousSteppable runs in the background, even while the schedule is being paused, and has no notion of "time" with regard to the schedule at all.
As usual, you need to be very careful when using multi-threaded code. ParallelSequences merely need to be written such that each separate Steppable in the sequence doesn't step on other steppables' toes. AsynchronousSteppables are much more fraught with peril -- when modifying or reading from the model, they need to lock on the Schedule to make sure that neither the Swing event thread nor the underlying model thread are reading/writing the model as well. That goes for the random number generator as well.
AsynchronousSteppables are new to MASON and are certain to have a number of bugs. If you synchronize on the Schedule in an AsynchronousSteppable, and the model thread is trying to call your stop method, we might have a race condition. Our cursory glance doesn't show any obvious race condition we've overlooked in the code, but one never knows.
Both ParallelSequences and (especially) AsynchronousSteppables may break guarantees of duplication (see below) because things do not operate in sync any more.
strictfp and Duplication
MASON is designed to make it as easy as possible for you to have duplicatable results,
meaning that they will run the same way on MASON regardless of platform. However,
because of the different implementations of operations with floating point
numbers (such as +, -, /, *, %), Java operations with doubles and singles
are not guaranteed to return identical results on different platforms,
unless you take the following steps:
This is supposed to guarantee identical results; though some platforms still can get into trouble because they violate the spec. But it's as close as you can get.
Note that strictfp and StrictMath are slower than plain floating-point math. So
we've tried to make it possible for you to choose either to use them or to not use them (they're turned off by default). If you want platform-independent duplication, the following classes in the core of the simcore library should use the
strictfp keyword declaration:
Additionally, the following classes should be set to use StrictMath class methods rather than Math class methods:
Last, you'll need to add the "strictfp" keyword to the front of all classes in your model; usually
this means your SimState and Steppable subclasses. Anonymous classes
can't be strictfp: you'll need to move them to declared classes instead
if they contain floating point math operations and you want them to be strictfp.
MacOS X Notes
If you're doing 2D drawing, by all means, use Java 1.3.1. It's much faster at drawing than 1.4.1 on the Mac (it's hardware accelerated). If you're doing 3D drawing, you have no choice but to use 1.4.1. Due to 1.3.1 bugs, if you're making a movie, you definitely want to use 1.4.1. And if you're only running command-line MASON with no graphics, 1.4.1 is significantly faster. If you've installed 1.4.1, you can still change a given terminal window to treat the java command as Java 1.3.1 rather than 1.4.1 like this:
alias java /System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Home/bin/java
MacOS X is slow at jumping from window to window to draw stuff, so you want to minimize this as much as possible:
Newer versions of jikes are particularly obnoxious about style warnings. You can turn these style warnings off with the --nowarn option.
MASON's models don't use much memory: but Java3D sure does! Java3D is a memory hog extraordinaire and it's not hard to require 200 megabytes for a small 50x50x50 cluster of objects.
Unfortunately, Java's default heap sizes are in the 20 Megabyte range and it does not increase them dynamically. To manually increase your heap size to, say, 100 Megabytes, you can do:
java -Xmx100M -Xms100M ...
This is also useful for making your graphics run faster if you're using the Stretched Image method for drawing grids of rectangles, particularly in Linux, which garbage-collects a lot if you're using the Stretched Image method. The big thing you want to get rid of is a rapid rate of "Full" Garbage collections (ordinary collections are no big deal). You can see ordinary and "full" collections occurring with:
java -verbose:gc ...
If you'd like to profile your code, the easiest way is to run:
java -Xprof ...
This prints out a profiling statement for each thread after the thread terminates, showing the piggiest interpreted, compiled, and native functions.