Monday, April 9, 2018

Parameter Management

Dragons Abound  is controlled by a long list of parameters implemented as properties on a Javascript object.  At the moment there are about 2000 of these parameters.  There is a Javascript object that holds the world generation parameters, and another that holds the map generation parameters.  The start of the map parameters object looks like this:
var defaultMapParams = {
 // SVG rendering mode
 renderingMode: 'crispEdges',
 renderingMode: 'optimizeSpeed',
 // Color parameters
 colorsUseGrayScale: 0.10, // 10% of maps?
 colorsUseDesaturation: 0.20, // 20% of maps?
 // Saturation Range
 colorsSatRange: [.50, .70] ...
There are various problems with this approach -- finding a specific parameter in 2000 parameters isn't particularly easy, for instance.  One problem is that I often want to use a particular configuration of parameters for a run.  For example, I might want to use the parameter settings that product a map that looks like the "Skies of Fire" map:
This requires setting 186 values.  Obviously, it is impossible to do that by hand -- and then remember the default values to change them back!

My solution to that was to create something called “styles".  A style is just a set of parameter values that can be used to override the default values.  So I have a style for “Skies of Fire", which starts out like this:
  sof: {
     landPatternChance: 0,
     colorsWaterForce: [240, 228, 218],
     colorsLandForce: [225, 205, 182],
            forestMassShowChance: 0,
     oceanPatternChance: 0...
So when I want to generate a map in the “Skies of Fire" style, I just set the map style to point to the “sof" parameter values.  The first thing the program does when it starts up is look up the style and then copy all those values on top of the default values.

That worked pretty well for a while, but it soon became apparent that it would be helpful to have multiple styles.  For example, I have a style called “bestLabels" that sets parameters for the simulated annealing algorithm that places the labels to take longer and try harder to find good label placements:
      bestLabels: {
            saEnergyFactors: [500, 1000, 2000],
            saLabelQuality: 5000
      },
To make a map in the “Skies of Fire" style with the good label placement, I need to use both styles.  So I modified start up to look for a list of styles and copy them one after the other onto the parameter object.

A problem with this approach popped up when I started generating city icons.  One feature I have in city icons is to use different styles of icons for different countries.  But when I apply a “city icon style" by copying it into the parameter object, I wipe out all the default values on the object.  The when I switch to a different style, I have a problem if a parameter is left at the value for the first style instead of being reverted back to the default.  The problem is actually worse than that, because sometimes I want to generate one house out of the icon in a different style.  It became clear that I needed a way to temporarily change parameters.

My solution was to create a function called getProp() that takes a list of parameter objects and looks through them in order to find a value for a parameter.  Thanks to the Javascript spread syntax , it's easy to write a pretty flexible function to do this:
function getProp(name, ...sources) {
 // Looks through the sources in order to
 // see if we can find a value for name
 for(let i=0;i<sources.length;i++) {
     if (sources[i] &&
         sources[i][name] != undefined) return sources[i][name];
 };
 return null;
};
Rather than destructively copying a style onto a parameter object, using getProp() I can leave it separate and just include it in the list of sources ahead of the parameter object.  For example, to look for a property first on a roof, then on a house, then on a house style and then on the default parameters, I would call:
const rr = Utils.getProp('ciRoofRatiosPoint', roof, house, mapParams.houseStyle, mapParams);

Unfortunately for me, I didn't implement this approach until I started work on city icons, so it's only used in that part of the code.  Adding it into the rest of the code would seem to be very difficult -- I'd have to find every spot where I look up a parameter and replaced it with a call to getProp().  And frankly, it's kind of ugly to have getProp() all over the place.  However, Javascript is very flexible, and there's a way to seamlessly add this functionality to the parameter objects without having to rewrite all the existing code.

In programming, it's often useful to be able to go back and “retrofit" new behavior onto an existing functionality.  For example, suppose you were storing a person's age as the age field on an object:
     person.age = 100;
The way Javascript objects work, you can set the age property to any value.  But in this case, you might like to modify the behavior of the age property to complain if you set the age to something odd:
     person.age = 'young';
     // Error!
This is a case where you'd like to change what happens when you set the age property.

You can do exactly this with something called Proxy .  Proxy lets you replace an existing function with a new function -- even for some built-in functions like “getting" and “setting" an Object value.  I won't go into all the details, but for my need, the basic idea is to replace the “getting" function on the parameter values to something like getProp().  So when I get a property value by doing something like:
     params.useCountryBorders
or like:
     params['useCountryBorders']
what will actually happen is that the params object will look through a list of styles first, just as getProp() did.  I can even make this recursive, so the styles themselves might look into substyles for a value, and so on.

With getProp() I picked the styles to look through by passing them in as arguments to the function.  (And I could do as many as I wanted thanks to the spread operator.)  When I'm doing something like
     params.useCountryBorders
how will the params object know what styles to look through for a value for 'useCountryBorders'?

One possibility is to use Proxy to add some new functions, such as addStyle() and removeStyle(), and use these functions to maintain the list of styles currently in effect.  However, it's probably easier to keep a list of the styles in a specific property on the params object, e.g., “styles".  So every parameter object knows to look through all the objects on it's own styles property when looking up a property value.  If I want to temporarily use the “tower" style, I can do something like this:
     params.styles.push(towerStyle);
     //  Use params
     params.styles.pop();
I push towerStyle onto the list of styles, do something (e.g., draw a tower) and then pop off the towerStyle when I'm done using it.

Less obvious is that if I implement a Proxy for getting a value off of my parameters object, I have to implement a similar function on setting a value on the parameter object.  To see why, suppose the default value for the showCities parameter is false, and I'm using a style which changes that value to true.  That works fine, and I get showCities as expected.   Suppose I now want to temporarily turn off the showing of cities so that (for example) the underground cities of the dwarves don't show up on the map.  To do that, I set showCities on my parameter object to false.  What happens when I next look up the value of showCities?  Well, because I have a Proxy on the parameter object, it looks into the styles and finds the value of showCities on the style -- which is still true!  When this happens, we say the value on the style object “shadows" the value on the parameter object.

To avoid this, I need to put a Proxy for setting a property on the parameter object as well.  This Proxy looks through the styles and sets the property on the first style where that property has a value.  This way, when I change a property value it won't get shadowed by another value on a style.

As I've described, my approach to parameter management has changed multiple times during development.  The current approach with styles and Proxy() seems to be working well for the moment, but I won't be surprised if I have to change it again in the future.

2 comments:

  1. At last I've catched up with your posts, been following the procedural generation of maps for a while, and your development on the matter has me astounded. Hope to keep seeing your results, congrats for your work.

    ReplyDelete
  2. Looked around but didn't see anything that said. Is this available for use? - Thanks

    ReplyDelete

Note: Only a member of this blog may post a comment.