package util.propnet.serialization; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.security.MessageDigest; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import sun.misc.BASE64Encoder; import util.configuration.ProjectConfiguration; import util.gdl.grammar.Gdl; import util.gdl.grammar.GdlPool; import util.gdl.grammar.GdlProposition; import util.gdl.grammar.GdlTerm; import util.logging.GamerLogger; import util.propnet.architecture.Component; import util.propnet.architecture.PropNet; import util.propnet.architecture.components.Proposition; import util.statemachine.Role; /** * PropNetCache provides a mechanism for loading saved propnets based on their * known GDL description. Each propnet is saved in a file determined by the MD5 * hash of the GDL description, along with a copy of the description, so that * when loading the PropNetCache can verify that it is getting the shef.network for * the right game. Serialized networks are stored on disk, compressed. * * This is designed to be used to supplement the existing @PropNetFactory, * which can generate a propnet given the game description. For a few games, * like checkers and Zhadu, generating the propnet is time-intensive and also * memory-intensive, and isn't feasible to do using the fixed-point iteration * approach taken in PropNetFactory. However, we'd still like to be able to * test propnet-based players on these games. Thus, in these cases, the propnet * can be generated by another system, and loaded into the player through the * PropNetCache at runtime. * * IMPORTANT NOTE: This is not intended for use during real competitions. * It does not handle rulesheet obfuscation at all, nor is it designed to, * and depending on a pre-built cache of serialized propnets for specific * games feels contrary to the spirit of general game playing competitions. * * PropNetCache is designed for the following use cases: * * 1. Allowing players to load in propnets for games which they would * otherwise not be able to generate propnets for, for experimentation * and classroom competitions. * * 2. Allowing players to load in propnets for games quickly, for running * faster unit tests and benchmarks. * * USAGE NOTE: When using this class, you may run into stack overflow errors * when loading very large networks, unless you increase your program's stack * space using the "-Xss20m" command line flag. If you still have problems, * increase the number "20" in that flag to something larger. The flag controls * how many megabytes of space are allocated to the stack. * * @author Sam Schreiber */ public class PropNetCache { /** * getCacheFile computes the filename for the cache file based on * the MD5 digest of the GDL description of the game, with a few minor * tweaks to make the result into a more reasonable filename. All of the * cached propnet files are stored in the "propNetCacheDirectory" that is * defined in the ProjectConfiguration. */ private static File getCacheFile(List<Gdl> description) { String gdlHash; try { MessageDigest md = MessageDigest.getInstance("MD5"); for(Gdl gdl : description) { md.update(gdl.toString().getBytes()); } gdlHash = new BASE64Encoder().encode(md.digest()).replace("=", "0").replace("/","_").replace("+","."); } catch(Exception e) { GamerLogger.logStackTrace("StateMachine", e); return null; } String cacheFilename = "propnet_" + gdlHash + ".net"; File theCacheFile = new File(ProjectConfiguration.propNetCacheDirectory, cacheFilename); return theCacheFile; } @SuppressWarnings("unchecked") public static PropNet loadNetworkFromCache(List<Gdl> description) { File theCacheFile = getCacheFile(description); if(!theCacheFile.exists()) { GamerLogger.log("StateMachine", "Could not find propnet in cache."); return null; } GamerLogger.log("StateMachine", "Loading propnet from cache file: " + theCacheFile.getName()); // Deserialize the original description and shef.network. PropNet theRawNetwork; List<Gdl> oldDescription; FileInputStream inStream; GZIPInputStream zipStream; ObjectInputStream objStream; try { inStream = new FileInputStream(theCacheFile); zipStream = new GZIPInputStream(inStream); objStream = new ObjectInputStream(zipStream); oldDescription = (List<Gdl>)objStream.readObject(); theRawNetwork = (PropNet)objStream.readObject(); objStream.close(); } catch(Exception e) { GamerLogger.logStackTrace("StateMachine", e); return null; } // Verify that the string representations of the descriptions match. if(description.size() != oldDescription.size()){ GamerLogger.log("StateMachine", "When loading propnet from cache, found description length mismatch."); return null; } for(int i = 0; i < description.size(); i++) { if(!description.get(i).toString().equalsIgnoreCase(oldDescription.get(i).toString())) { GamerLogger.log("StateMachine", "When loading propnet from cache, found descriptions differed."); return null; } } Set<Proposition> toAdd = new HashSet<Proposition>(); for (Component c : theRawNetwork.getComponents()) { if (c instanceof Proposition) continue; Proposition dummy = new Proposition(GdlPool.getConstant("anon")); Set<Component> outputs = new HashSet<Component>(c.getOutputs()); for (Component out : outputs) { if (out instanceof Proposition) continue; out.removeInput(c); c.removeOutput(out); c.addOutput(dummy); dummy.addInput(c); dummy.addOutput(out); out.addInput(dummy); toAdd.add(dummy); } } for (Proposition p : toAdd) theRawNetwork.addComponent(p); // Immerse all of the GDL that we just deserialized. GamerLogger.log("StateMachine", "Loaded propnet from cache. Immersing in GDL pool..."); for(Component c : theRawNetwork.getComponents()) { if(!(c instanceof Proposition)) continue; Proposition p = (Proposition)c; p.setName((GdlTerm)GdlPool.immerse(p.getName())); } List<Role> immersedRoles = new ArrayList<Role>(); for(Role r : theRawNetwork.getRoles()) { immersedRoles.add(new Role((GdlProposition)GdlPool.immerse(r.getName()))); } return new PropNet(immersedRoles, theRawNetwork.getComponents()); } public static void saveNetworkToCache(List<Gdl> description, PropNet theNetwork) { File theCacheFile = getCacheFile(description); FileOutputStream outStream; GZIPOutputStream zipStream; ObjectOutputStream objStream; try { outStream = new FileOutputStream(theCacheFile); zipStream = new GZIPOutputStream(outStream); objStream = new ObjectOutputStream(zipStream); objStream.writeObject(description); objStream.writeObject(theNetwork); objStream.close(); } catch(Exception e) { GamerLogger.logStackTrace("StateMachine", e); }; } }