The Social Networks package comes with an example about doing this with the Network field.
If you use a Continuous2D, you'll want to make that your discretization significantly larger than the object you're storing. If you just have a single object, we suggest making the discretization as large or larger than the entire region of the field (so it's just one big cell).
The most common need is probably to put a map in the background of the screen. For this, your SimplePortrayal2D could be an ImagePortrayal2D. Make sure your field's width and height are of the proportions you want. Then add the object to the exact center of the field you've created. Now attach an ImagePortrayal2D along these lines:
myContinuousPortrayal2D.setField(theContinuousField); myContinuousPortrayal2D.setPortrayalForAll(new ImagePortrayal2D( new ImageIcon(getClass().getResource("MyPicture.gif")).getImage()),theContinuousField.width); myDisplay.attach(myContinuousPortrayal2D, "Overlay");
If the image is taller than wide, change theContinuousField.width to theContinuousField.height. If you're filling the entire background, remember to set Display2D's backdrop to null so it doesn't bother drawing it.
FieldPortrayal2D overlay = FieldPortrayal2D() { Font font = new Font("SansSerif", 0, 18); // keep it around for efficiency public void draw(Object object, Graphics2D graphics, DrawInfo2D info) { String s = ""; if (state !=null) s = state.schedule.getTimestamp("Before Simulation", "All Done!"); graphics.setColor(Color.blue); graphics.drawString(s, (int)info.clip.x + 10, (int)(info.clip.y + 10 + font.getStringBounds(s,graphics.getFontRenderContext()).getHeight())); } };You don't need to set the field of this FieldPortrayal2D -- just leave it empty. All you need to do is attach it to your Display and you're on your way.
Note that this method locks to the top-left corner of the clip region. If you have scrolled out a lot, the clip region will be smaller than the window and located in the center of it. If you want to always draw in the top-left corner of the screen, you need to tell the Display2D to stop clipping. Then the clip region will always be the scroll rectangle. However if any of your portrayals spill outside of the clip region, their unclipped drawing will now be visible.
If you're filling the entire background with this method, remember to set Display2D's backdrop to null so it doesn't bother drawing it.
Another way to draw a scale-independent image in the background is to use a TexturePaint wrapped around your image, and use that as the "Paint" to draw the Display2D's backdrop. You could, for example, add this to your setupPortrayals method:
Image i = new ImageIcon(getClass().getResource("MyPicture.gif")).getImage(); BufferedImage b = display.getGraphicsConfiguration().createCompatibleImage(i.getWidth(null), i.getHeight(null)); Graphics g = b.getGraphics(); g.drawImage(i,0,0,i.getWidth(null),i.getHeight(null),null); g.dispose(); display.setBackdrop(new TexturePaint(b, new Rectangle(0,0,i.getWidth(null),i.getHeight(null))));This will tile the image into the scrolled space. Unfortunately, large tiled images are buggy on certain platforms (such as OS X java 1.3.x). So we don't recommend it.
As an example, we will convert Conway's Game of Life from Tutorials 1 and 2 into an applet.
The simulation.classes file, located in the sim/display directory, specifies which files will appear in the pop-up list when the user chooses "New Simulation...". Edit this file to include only those simulations you wish the user to have access to. If you put the word 'ONLY' on a line all by itself, the user will not be able to type in a simulation classname by hand. If the file consists solely of the world 'ONLY', then the "New Simulation..." menu will be grayed out and the user won't be able to make a new simulation at all.
jar cvf mason.jar masonThis dumps the entire mason directory, java files and all, into a jar file called mason.jar. You don't need all those files -- you just need:
To extract just these files and no others from the MASON directory and stuff them into a jar file, the easiest approach is (in UNIX or on a Mac) to go into the mason directory and type
make jar
This is still a big file for users to download. If you really want to shrink this, you'll need to identify which classes and related files have to be extracted. Generally speaking the only images that need to be extracted for MASON to operate are the PNG files in the display and util/gui directories. The HTML files you need are probably just those in the application directories you wish to make available. And you'll need the simulation.classes file. As to the class files: generally you're safe if you just grab all the class files in the mason directory EXCEPT for various sim/app/... applications you don't wish to include. Certain tutorials have big pictures, and you'll save a lot of space by tossing those tutorials out. If you're not doing Java3D, you can toss out the files in the portrayal3d and display3d directories as well.
Your mason.jar file should be located on the website right next to this HTML file. An example is shown on the MASON home page.
If you want to fire up MASON directly on the simulation of your choice, you'll need to change an applet parameter called Simulation in your HTML file. Its value is by default sim.display.Console, but you can change it to a simulation class, for example, sim.app.heatbugs.HeatBugsWithUI
You can also change what is placed on the button by changing the Name parameter. Note that these parameters have to be changed in several locations in our version of the file, because different web browsers handle HTML in subtly different ways.
Note that parallelization uses threads and is subject to race conditions. If your parallel actions perform writes or access the random number generator, you'll need to synchronize on the schedule. Remember not to hold the lock on the schedule for long!
The MASON HeatBugs example shows a variety of approaches to improving the efficiency of the HeatBugs Diffuser, up to and including parallelizing it for multiple processors using ParallelSequence.
Generally speaking there are two kinds of Asynchronous Steppable operations: fast one-shot operations and slow or inifinite-loop operations. Fast one-shot operations can simply fire off in their own thread and do their thing, assuming they're fast enough that the user is willing to wait for them to finish after he has pressed the Stop or Pause buttons or is checkpointing out the simulation. Slow or infinite-loop operations must be able to respond to requests to be paused, resumed, and stopped (killed).
We expect that the use of Asynchronous Steppables should be exceedingly rare. Indeed, adding them to MASON required some retooling and we do not know if it works properly: it's essentially untested at this point. Read the AsynchronousSteppable documentation very closely.
MASON provides WeakSteppable, a wrapper for Steppables which holds onto them as WeakReferences. It also maintains weak Stoppables as well (though that may change). Keep in mind that just because the WeakReference is being held onto no one but the Schedule doesn't mean the Schedule will let go of it: this only occurs when Java needs to reclaim the WeakReference, perhaps during garbage collection.
When the WeakSteppable lets go of its weak reference, the WeakSteppable is still scheduled in the Schedule. It'll just do nothing when the Schedule gets around to stepping it. However, the time will advance to the scheduled point.
Schedule doesn't at present have a way to delete things except when their time is called. The reason is because Schedule uses a data structure called a binary heap to work efficiently. Heaps are good for inserting arbitrary objects and then extracting them in sorted order (in our case, sorted order means order of scheduled time). However, other arbitrary deletion of objects from binary heaps is highly inefficient. Instead, the better way to "delete" an object is to "stop" it and forget about it. It'll drop out of the Schedule in due time. How you "stop" an agent depends on whether or not it's a one-shot agent or a repeating agent, which we discuss next.
One-shot agents. Agents can be scheduled on the Schedule in two ways: one-shot agents and repeating agents. When an agent is scheduled just once, it sits in the Schedule until its time has come. At that point the agent is deleted from the Schedule and its step() method is called. If you're writing a one-shot agent, and you want your agent out of the Schedule, all you have to do is tell your agent to ignore its sole step() method and wait.
But perhaps this is inconvenient: you might want to reuse the agent and its step() method -- perhaps to reschedule it at a future time instead. Having it ignore future step() calls isn't feasible in that case. Then instead you can wrap it in another Steppable which can be stopped. We provide such an object: sim.engine.TentativeStep. When its step() is called, this object will in turn call step() on its subsidiary Steppable unless stop() has been called, at which time it just forgets about the subsidiary object (sets it to null). You can wrap your Steppable in various TentativeSteps and schedule each of them independently on the Schedule, then stop() various ones at your leisure.
Repeating agents. A repeating-scheduled agent has actually just been wrapped in a private 'Repeat' Steppable. When the step() method is called on the 'repeat' object, it simply calls step() on its subsidiary Steppable (your agent), then it reschedules itself in the Schedule for a later date. This 'repeat' object is also a Stoppable: when its stop() method is called, then the subsidiary Steppable is set to null and the 'Repeat' object ceases to function -- no later subsidiar step(), no later rescheduling.
This Stoppable is handed to you when you call scheduleRepeating(...): to delete a repeating object from the Schedule, you merely need to call stop() on the Stoppable and forget about the agent.
SparseGrid2D, SparseGrid3D, Continuous2D, Continuous3D. Use the remove(obj) method declared in these classes' superclass, SparseField. Under no circumstances should you manually remove objects from the Bags of these classes -- there's a lot of unhooking that must be done in different places. Let remove(obj) do the job for you. You can also call removeObjectsAtLocation(location) (and variations thereof) which removes and returns all objects at a given location. And you can call clear() to delete all objects in the entire field.
ObjectGrid2D, ObjectGrid3D. You remove elements from these objects by simply deleting the elements from their underlying arrays. The clear() function will delete all elements for you.
Network. Use removeEdge(edge) to delete an edge from the graph, and removeNode(node) to delete a node from the graph. Once an edge has been removed from the graph, you can reuse it by adding it to another graph, but you cannot change the nodes it is connected to (they will be added to the other graph as well if not already present). You can also delete all nodes and edges from the graph with clear().
IntGrid2D, IntGrid3D, DoubleGrid2D, DoubleGrid3D. There are no removal functions in these fields as there is nothing to "remove".