Cached Preference Settings for Standard Game

I use Standard Game extensively, and something I've noticed is that it refers to the GameSettings at every frame, here:


      // Update music/sound
         if ((settings.isMusic()) || (settings.isSFX())) {
                AudioSystem.getSystem().update();
         }



This is important because the default preferences implementation wraps the Java Preferences API, which uses (on Windows) the registry. I don't actually know what the cost of looking up a value in the registry is, and its entirely possible that the java preference implementation caches frequently looked up values. But regardless, it seems like something that should not be left up to chance/implementation changes.

So: This GameSettings implementation wraps a PreferencesGameSettings class, but stores local copies of most of the important values. When changes are made to the CachedGameSettings, they 'write through' to the backing Preferences, but when values are only read, they read from the local values.

Feel free to use this however you'd like.


import java.util.prefs.Preferences;

import com.jme.system.GameSettings;
import com.jme.system.PreferencesGameSettings;

/**
 * This class wraps a game settings class.
 * It caches the setting's so that you don't refer to the system registry (or the equivalent on Mac/UNIX) each time you read a value.
 * When changes are made, those changes write through to the backing PreferencesGameSettings.
 * @author Sam Bayless
 *
 */
public class CachedGameSettings implements GameSettings {   
   private final  GameSettings settings;
   private int alphaBits;
   private int depth;
   private int depthBits;
   private int framerate;
   private int frequency;
   private int height;
   private String renderer;
   private int samples;
   private int stencilBits;
   private int width;
   private boolean fullScreen;
   private boolean music;
   private boolean sfx;
   private boolean vsync;

   public CachedGameSettings(Preferences node) {
        this( new PreferencesGameSettings(node));
   }
   
   public CachedGameSettings(GameSettings gameSettings) {
        settings = gameSettings;
        refresh();   
   }

   public void refresh() {
      this.alphaBits = settings.getAlphaBits();
      this.depth = settings.getDepth();
      this.depthBits = settings.getDepthBits();
      this.framerate = settings.getFramerate();
      this.frequency = settings.getFrequency();
      this.height = settings.getHeight();
      this.renderer = settings.getRenderer();
      this.samples = settings.getSamples();
      this.stencilBits = settings.getStencilBits();
      this.width = settings.getWidth();
      this.fullScreen = settings.isFullscreen();
      this.music = settings.isMusic();
      this.sfx = settings.isSFX();
      this.vsync = settings.isVerticalSync();
   }

   @Override
   public void clear() throws Exception {
      settings.clear();
      refresh();   
   }

   @Override
   public String get(String name, String defaultValue) {
      //these ones has to fall through to the preference settings to work properly
      return settings.get(name, defaultValue);
   }

   @Override
   public int getAlphaBits() {
      return alphaBits;
   
   }

   @Override
   public boolean getBoolean(String name, boolean defaultValue) {
      return settings.getBoolean(name, defaultValue);
   }

   @Override
   public byte[] getByteArray(String name, byte[] bytes) {
      return settings.getByteArray(name, bytes);
   }

   @Override
   public int getDepth() {
      return depth;
   }

   @Override
   public int getDepthBits() {
      return depthBits;
   }

   @Override
   public double getDouble(String name, double defaultValue) {
      return settings.getDouble(name, defaultValue);
   }

   @Override
   public float getFloat(String name, float defaultValue) {
      return settings.getFloat(name, defaultValue);
   }

   @Override
   public int getFramerate() {
      return framerate;
   }

   @Override
   public int getFrequency() {
      return frequency;
   }

   @Override
   public int getHeight() {
      return height;
   }

   @Override
   public int getInt(String name, int defaultValue) {
      return settings.getInt(name, defaultValue);
   }

   @Override
   public long getLong(String name, long defaultValue) {
      return settings.getLong(name, defaultValue);
   }

   @Override
   public Object getObject(String name, Object obj) {
      return settings.getObject(name, obj);
   }

   @Override
   public String getRenderer() {
      return renderer;
   }

   @Override
   public int getSamples() {
      return samples;
   }

   @Override
   public int getStencilBits() {
      return stencilBits;
   }

   @Override
   public int getWidth() {
      return width;
   }

   @Override
   public boolean isFullscreen() {
      return fullScreen;
   }

   @Override
   public boolean isMusic() {
      return music;
   }

   @Override
   public boolean isSFX() {
      return sfx;
   }

   @Override
   public boolean isVerticalSync() {
      return vsync;
   }

   @Override
   public void set(String name, String value) {
      settings.set(name, value);
      
   }

   @Override
   public void setAlphaBits(int alphaBits) {
      settings.setAlphaBits(alphaBits);
      this.alphaBits = alphaBits;
   }

   @Override
   public void setBoolean(String name, boolean value) {
      settings.setBoolean(name, value);
      
   }

   @Override
   public void setByteArray(String name, byte[] bytes) {
      settings.setByteArray(name, bytes);
      
   }

   @Override
   public void setDepth(int depth) {
      settings.setDepth(depth);
      this.depth = depth;
   }

   @Override
   public void setDepthBits(int depthBits) {
      settings.setDepthBits(depthBits);
      this.depthBits = depthBits;
   }

   @Override
   public void setDouble(String name, double value) {
      settings.setDouble(name, value);

   }

   @Override
   public void setFloat(String name, float value) {
      settings.setFloat(name, value);

   }

   @Override
   public void setFramerate(int framerate) {
      settings.setFramerate(framerate);
      this.framerate = framerate;
   }

   @Override
   public void setFrequency(int frequency) {
      settings.setFrequency(frequency);
      this.frequency = frequency;
   }

   @Override
   public void setFullscreen(boolean fullscreen) {
      settings.setFullscreen(fullscreen);
      this.fullScreen = fullscreen;
   }

   @Override
   public void setHeight(int height) {
      settings.setHeight(height);
      this.height = height;
   }

   @Override
   public void setInt(String name, int value) {
      settings.setInt(name, value);

   }

   @Override
   public void setLong(String name, long value) {
      settings.setLong(name, value);

   }

   @Override
   public void setMusic(boolean musicEnabled) {
      settings.setMusic(musicEnabled);
      this.music = musicEnabled;
      
   }

   @Override
   public void setObject(String name, Object obj) {
      settings.setObject(name, obj);

   }

   @Override
   public void setRenderer(String renderer) {
      settings.setRenderer(renderer);
      this.renderer = renderer;
   }

   @Override
   public void setSFX(boolean sfxEnabled) {
      settings.setSFX(sfxEnabled);
      this.sfx = sfxEnabled;
   }

   @Override
   public void setSamples(int samples) {
      settings.setSamples(samples);
      this.samples = samples;
   }

   @Override
   public void setStencilBits(int stencilBits) {
      settings.setStencilBits(stencilBits);
      this.stencilBits = stencilBits;
   }

   @Override
   public void setVerticalSync(boolean vsync) {
      settings.setVerticalSync(vsync);
      this.vsync = vsync;
   }

   @Override
   public void setWidth(int width) {
      settings.setWidth(width);
      this.width = width;
   }

}

hehe…though probably beneficial what you wrote, probably better to fix StandardGame to not reference those every update if there's a performance impact.  Can you verify there is in fact a performance impact?

Well, the preference API is highly platform specific, but in the case of windows… looks like calling the pref api takes about half an ms each update. So, seems worth doing something about.


looks like calling the pref api takes about half an ms each update

Half ms? When most games have a budget of about 17 ms, you certainly can't afford to waste 1 ms just for doing that kind of thing. StandardGame should be fixed to use cached versions of those values.

Agreed.

Sounds like a very nice catch. Thanks sbayless.



What is the best way to solve this?

Well, one option is to use the CachedGameSettings I provided at the top of this thread.

Otherwise, you have to alter StandardGame to cache those particular values itself. But that might get a bit complicated if users are able to enable and disable audio in game… Probably the easiest/safest option is to use my cached game settings for now.

Doing a check at init and then caching a boolean value to represent if audio support should be available in StandardGame would be available.  I haven't got jME checked out, so is someone else available to make this fix and apply it?