Saturday, October 20, 2007

Superfast Model Driven Development with EMF (III)

Load and Save

In the previous postings (part I and part II) I've created an EMF model to hold the definition of an adventure game. I've used the EMF-generated editors to create an adventure and wrote a little bit of code to run the game.

In this posting I'm going to look at how we can use the EMF built-in serialization as a very easy way to load and save the game state. At the end I will also show how to run the program from the command line outside of Eclipse.

To implement Load and Save, I'm adding two new actions to every location in the game (a player can load and save in every game location): SaveAction and LoadAction.

The SaveAction creates a new SaveGame instance and stores the current location and the current inventory in it.

Looking at the model again, you can see that the SaveGame contains an Inventory object but only has a relation to the Location. This has an impact wrt to the content of the file that we're saving to. The contained items are actually inlined in the file where the referenced items are stored as hrefs.

A sample adventure.save file would look like this. The format of this XML file is called XMI, but as you can see, it reads as very typical XML. You can see that the inventory is actually contained in the file but the location is pointed to. When we save the file, it is in the same Resource Set as the My.adventure resource, so that's how they know about each other. The href URL also explains why we need an ID attribute. It is used in there. The ID attribute value cannot have a space in it so that's why it's generally handy to use a separate attribute for it.
<adv:savegame version="2.0" xmi="http://www.omg.org/XMI" adv="http://adventure">
  <inventory>
    <items name="Spade" id="spade"/ >
  </inventory>
  <location
    href="file:/C:/EMF_Adventure/bin/My.adventure#desert"/>
</adv:savegame>



The adventure.save file is created using the following code:
SaveGame sg =
  AdventureFactory.eINSTANCE.createSaveGame();
sg.setLocation(Status.location);
sg.setInventory(Status.inventory);


String fileName = System.getProperty("java.io.tmpdir") +
  "adventure.save";
Resource resource = Status.resourceSet.createResource(
  URI.createFileURI(fileName));
resource.getContents().add(sg);
try {
  resource.save(Collections.emptyMap());
  System.out.println("Game saved to " + fileName);
  return true;
} catch (IOException e) {
  e.printStackTrace();
} finally {
  Status.resourceSet.getResources().remove(resource);
}
  1. First we create and fill the SaveGame object.
  2. Then we add a new Resource, stored in a file called adventure.save in the temp dir, to the ResourceSet used by the game.
  3. Add the SaveGame object to the new Resource.
  4. call resource.save()
  5. Finally remove the new resource from the game's ResourceSet again as we don't want it to change after this.
And we're done.

Loading data from an XMI file is very similar. The LoadAction does the following:
String fileName = System.getProperty("java.io.tmpdir") +
  "adventure.save";
Resource resource = Status.resourceSet.getResource(
  URI.createFileURI(fileName), true);
try {
  SaveGame sg = (SaveGame) EcoreUtil.getObjectByType(
  resource.getContents(),
  AdventurePackage.eINSTANCE.getSaveGame());
  if (sg != null) {
    Status.location = sg.getLocation();
    Status.inventory = sg.getInventory();
 

    System.out.println("Game loaded from " + fileName);
    return true;
  }
} finally {
  Status.resourceSet.getResources().remove(resource);
}
  1. Create and add a resource that points to the adventure.save file. In this case we use ResourceSet.getResource() instead of ResourceSet.createResource().
  2. Then we look up all of the contents of the file and find the SaveGame object in it. Since the adventure.save file really should only contain a single SaveGame object, resource.getContents().iterator().next() would also do the job.
  3. Once we have our SaveGame object, simply change the current location of the game to be the one loaded and change the inventory to be the one from the savegame.
  4. Finally, remove the adventure.save resource from the resourcset and were done.
Well, not quite, there is actually a little problem with the above code in that we should actually revisit all the locations of the adventure game and reset the items that belong there, taking into account the items that we've just loaded, but I'll leave that for another day :)

Oh, and I'm adding the following 2 lines to Status.initializeActions() so that my load and save actions are available from every location in the game:
actions.add(new SaveAction());
actions.add(new LoadAction());

Running the program from the command line

We're not writing an Eclipse application here, we're only using Eclipse and EMF as a tool to build our app. So we just want to run everything from the commandline. To do this, add to the following jars to your CLASSPATH or copy them into your project. For EMF that comes with Eclipse 3.3.1 the needed libraries are the following, but the actual number could be slightly different if you are using a different version of Eclipse (see the EMF FAQ for more info):
  • org.eclipse.emf.common_2.3.0.v200709252135.jar
  • org.eclipse.emf.ecore_2.3.1.v200709252135.jar
  • org.eclipse.emf.ecore.change_2.3.0.v200709252135.jar
  • org.eclipse.emf.ecore.xmi_2.3.1.v200709252135.jar
Now I can run my adventure game from the command line with:

set CLASSPATH=.;
  org.eclipse.emf.common_2.3.0.v200709252135.jar;
  org.eclipse.emf.ecore_2.3.1.v200709252135.jar;
  org.eclipse.emf.ecore.change_2.3.0.v200709252135.jar;
  org.eclipse.emf.ecore.xmi_2.3.1.v200709252135.jar
java game.Main

Welcome to Adventure Island. This is the start of the adventure game. You are at the house of a Viking.
Hints: (q)uit, (i)nventory, (w)est, (e)ast, save, load, you see a Spade
Please enter command:

All the code above is available in the Subversions repository at http://coderthoughts.googlecode.com/svn/trunk/emf_adventure for more info on how to check it out look here. Want all the projects including the generated code, get it here: http://coderthoughts.googlecode.com/svn/trunk/emf_adventure_full

7 comments:

Chris Aniszczyk (zx) said...

I enjoyed reading your EMF game posts! Really cool stuff!

Welcome to PlanetEclipse!

Marcelo Paternostro said...

Awesome example. Thanks for sharing!

swiety said...

Excellent stuff, thanks a lot!

'gro said...

Man, this should be teached in college.

Really complete and fun example to introduce students to the goodnes of EMF.

bye.

Jevon said...

Wow, that is a really cool example of using EMF!

Jeroen said...

Really cool posts !
Are you planning to create an RCP application using the generated editors?
That would be really nice! (I'm just starting with eclipse development, i'm always struggeling settings things up, plugin depedancies etc).

Good work,
Jeroen

Cristiano GaviĆ£o said...

Hi, very nice posts...

I could run the standalone example...

But it's possible to use EMF in other OSGI container too?

I could see that org.eclipse.emf.ecore bundle requires eclipse runtime core bundle too..
If I understood I should install all other bundles to supply dependecies...