A great example of when DefaultGeneration can really simplify your code is when you need to store your values in a list. This could be generalized by demonstrating that DefaultGeneration is useful when first time prep-work is needed for a set of values.

var unitByLocationCache = new Map<int, int, List<Unit>>();
unitByLocationCache.DefaultGeneration = (x, y) => new List<Unit>();

foreach (var unit in GameUnits)
{
  unitByLocationCache[unit.X, unit.Y].Add(unit);
}
Perhaps my most favorite feature of Map is DefaultGeneration. By default accessing a non-existent key will throw a KeyNotFoundException (exactly as Dictionary behaves). However, often in my code I find that when a key does not exist, I have a very simple action to take to create that key.

Consider you have a bunch of enums that use Attributes to add metadata.

[AttributeUsage(AttributeTargets.Enum)]
public class TileMetadataAttribute : Attribute
{
  public bool CanWalkOn { get; set; }
  
  //such as digging or scooping (without need for altering first such as with rugs or stone floors which need to be demolished first)
  public bool CanRemove { get; set; }

  //percentage of speed allow (e.g. .25 = 25% speed, or a 75% reduction)
  public double SpeedMod { get; set; }
}

public enum TileType
{
  [TileMetadata(CanWalkOn = true, CanRemove = false, SpeedMod = .8)]
  NaturalStone,
  [TileMetadata(CanWalkOn = true, CanRemove = false, SpeedMod = .9)]
  HewnStone,
  [TileMetadata(CanWalkOn = true, CanRemove = false, SpeedMod = 1.0)]
  SmoothStone,
  [TileMetadata(CanWalkOn = false, CanRemove = true, SpeedMod = .25)]
  Water,
  [TileMetadata(CanWalkOn = true, CanRemove = true, SpeedMod = .7)]
  Sand,
  [TileMetadata(CanWalkOn = true, CanRemove = false, SpeedMod = .9)]
  Rug,
  [TileMetadata(CanWalkOn = true, CanRemove = true, SpeedMod = .75)]
  Pebble,
  [TileMetadata(CanWalkOn = true, CanRemove = true, SpeedMod = .5)]
  Swamp,
  [TileMetadata(CanWalkOn = true, CanRemove = true, SpeedMod = .85)]
  Forest,
  [TileMetadata(CanWalkOn = true, CanRemove = true, SpeedMod = .9)]
  Meadow,
  //etc
}

You can easily pull the metadata using reflection.

public void HandleActiveTile(TileType tileType)
{
  var tileMetadata = (TileMetadataAttribute)(typeof(TileType).GetCustomAttributes(typeof(TileMetadataAttribute), false)[0]);

  HandleWalkMode(tileMetadata.CanWalkOn);
  HandleRemoveMode(tileMetadata.CanRemove);
  HandleSpeedMode(tileMetadata.SpeedMod);
}

However, pulling attribute data using reflection has a performance hit. Perhaps this method is going to be called millions of times. A great solution for this situation is to use caching; cache the metadata found with reflection so you only load it once. Using a map to create this cache will save you a lot of work. Go here to skip the following solution using a dictionary.

Dictionary<TileType, TileMetadataAttribute> TileMetadataCache { get; set; }
///
  TimeMetadataCache = new Dictionary<TileType, TileMetadataAttribute>();
///
public void HandleActiveTile(TileType tileType)
{
  TileMetadataAttribute tileMetadata;

  if (!TileMetadataCache.ContainsKey(tileType))
  {
    tileMetadata = (TileMetadataAttribute)(typeof(TileType).GetCustomAttributes(typeof(TileMetadataAttribute), false)[0]);   
    TileMetadataCache[tileType] = tileMetadata;
  }
  else
  {
    tileMetadata = TileMetadataCache[tileType];
  }

  HandleWalkMode(tileMetadata.CanWalkOn);
  HandleRemoveMode(tileMetadata.CanRemove);
  HandleSpeedMode(tileMetadata.SpeedMod);
}

Now that is nice. We only load the metadata once. Now we find that we need to pull this metadata is many places. So in each place we start to copy and paste this solution. Well no one likes to copy paste code like this, we should put it in a method of its own. Perhaps something like the following:

public TileMetadataAttribute GetMetdaData(TileType tileType)
{
  TileMetadataAttribute tileMetadata;

  if (!TileMetadataCache.ContainsKey(tileType))
  {
    tileMetadata = (TileMetadataAttribute)(typeof(TileType).GetCustomAttributes(typeof(TileMetadataAttribute), false)[0]);   
    TileMetadataCache[tileType] = tileMetadata;
  }
  else
  {
    tileMetadata = TileMetadataCache[tileType];
  }

  return tileMetadata;
}

If you had been using Map from the beginning, this fiasco could have been greatly simplifed. Consider the following:
public Map<TileType, TileMetadataAttribute> TileMetadataCache { get; set; }
///
  TileMetadataCache = new Map<TileType, TileMetadataAttribute>();
  TileMetadataCache.DefaultGeneration = (tileType) => 
    (TileMetadataAttribute)(typeof(TileType).GetCustomAttributes(typeof(TileMetadataAttribute), false)[0]);
///

public void HandleActiveTile(TileType tileType)
{
  //now it is safe to use the cache
  //even the FIRST time!
  var tileMetadata = TileMetadataCache[tileType];

  HandleWalkMode(tileMetadata.CanWalkOn);
  HandleRemoveMode(tileMetadata.CanRemove);
  HandleSpeedMode(tileMetadata.SpeedMod);
}

Last edited Jun 24, 2011 at 8:01 PM by payonel, version 7

Comments

No comments yet.