Search code examples
c#poolresource-management

Managing Pool of Generated Objects


I am working on a project where individual regions of a map are either generated dynamically, or loaded from a file if it has already been generated and saved. Regions are only loaded/generated as needed, and saved and discarded when they aren't anymore. There are several different tasks that will be using one or more regions of this map for various purposes. For instance, one of these tasks will be to draw all currently visible regions (about 9 at any given time). Another is to get information about, or even modify regions. The problem is that these tasks may or may not be working with the same regions as other tasks.

Since these regions are rather large, and are costly to generate, it would be problematic (for these and other reasons) to use different copies for each task. Rather, I think it would be a good idea to create and manage a pool of currently loaded regions. New tasks will first check the pool for their reqired region. They can then use it if it exists, or else create a new one and add it to the pool.

Provided that works, how would I manage this pool? How would I determine if a region is no longer needed by any tasks and can be safely discarded? Am I being silly and overcomplicating this?

I am using c# if that matters to anyone.

Edit: Now that I'm more awake, would it be as simple as incrementing a counter in each region for each place it's used? then discarding it when the counter reaches 0?


Solution

  • Provided that works, how would I manage this pool? How would I determine if a region is no longer needed by any tasks and can be safely discarded?

    A simple way of doing this can be to use weak references:

    public class RegionStore
    {
      // I'm using int as the identifier for a region.
      // Obviously this must be some type that can serve as
      // an ID according to your application's logic.
      private Dictionary<int, WeakReference<Region>> _store = new Dictionary<int, WeakReference<Region>>();
      private const int TrimThreshold = 1000; // Profile to find good value here.
      private int _addCount = 0;
      public bool TryGetRegion(int id, out Region region)
      {
        WeakReference<Region> wr;
        if(!_store.TryGetValue(id, out wr))
          return false;
        if(wr.TryGetTarget(out region))
          return true;
        // Clean up space in dictionary.
        _store.Remove(id);
        return false;
      }
      public void AddRegion(int id, Region region)
      {
        if(++_addCount >= TrimThreshold)
          Trim();
        _store[id] = new WeakReference<Region>(region);
      }
      public void Remove(int id)
      {
        _store.Remove(id);
      }
      private void Trim()
      {
        // Remove dead keys.
        // Profile to test if this is really necessary.
        // If you were fully implementing this, rather than delegating to Dictionary,
        // you'd likely see if this helped prior to an internal resize.
        _addCount = 0;
        var keys = _store.Keys.ToList();
        Region region;
        foreach(int key in keys)
          if(!_store[key].TryGetTarget(out wr))
            _store.Remove(key);
      }
    }
    

    Now you've a store of your Region objects, but that store doesn't prevent them being garbage collected if no other references to them exist.

    Certain task will be modifying regions. In this case I will likely raise an "update" flag in the region object, and from there update all other tasks using it.

    Do note that this will be a definite potential source of bugs in the application as a whole. Mutability complicates any sort of caching. If you can move to a immutable model, it will likely simplify things, but then uses of outdated objects brings its own complications.