Friday, January 29, 2016

Handle Body Reference Counting in Garbage Collected Dart


Oh, what the hell. I finished yesterday hand-waving over a particular implementation. Let's see if I can actually implement a reference counting handle body in Dart. There may be no practical application for this in a garbage collected language like Dart, but we'll see...

I continue to use Shape as the abstraction in my bridge pattern exploration. It will now serve double duty as the handle class, holding references to the body implementation. Well, it already did that (which is rather the point of the pattern), but now it needs to reference, dereference, and delete implementors as well.

The implementor is DrawingApi, which knows that subclasses will, among other things, know how to draw a circle:
abstract class DrawingApi {
  void drawCircle(double x, double y, double radius);
}
To maintain reference counts, DrawingApi needs a _refCount instance variable along with ref() and deref() methods to increase and decrease that counter:
abstract class DrawingApi {
  int _refCount = 0;
  void ref() {
    _refCount++;
    print('  $this Increased refcount to $_refCount');
  }
  void deref() {
    _refCount--;
    print('  $this Decreased refcount to $_refCount');
  }
  void drawCircle(double x, double y, double radius);
}
In client code, I can create an instance of two subclasses of DrawingApi:
main() {
  var api1 = new DrawingApi1(),
      api2 = new DrawingApi2();
  // ...
}
Then I initially set those as the "drawer" (the thing that draws, not a thing that holds stuff in desks) property for Circle instances:
main() {
  // ...
  var circle1 = new Circle(1.0, 2.0, 3.0)..drawer = api1,
      circle2 = new Circle(0.0, 6.0, 1.0)..drawer = api1,
      circle3 = new Circle(2.0, 2.0, 1.5)..drawer = api2;
  // ...
}
The Circle class is a concrete implementor of the Shape abstraction / handle class. So assigning api1 to two difference Circle instances needs to update the number of references to api1 accordingly.

I more or less copy the code from the handle / body example from the bridge pattern chapter in the Gang of Four book. The drawer() setter is responsible for noting the new reference, and noting the derefence for the existing object. If the reference count goes to zero, the handle class needs to delete the reference, which I do by assigning the _drawingApi instance variable to null:
abstract class Shape {
  DrawingApi _drawingApi;

  set drawer(DrawingApi other) {
    other.ref();
    if (_drawingApi != null) {
      _drawingApi.deref();
      if (_drawingApi._refCount == 0) {
        print('  ** Deleting no longer used $_drawingApi **');
        _drawingApi = null;
      }
    }
    _drawingApi = other;
  }
}
It is here that I finally realize that there is no point to doing any of this—at least not in this example. Even without explicitly setting _drawingApi to null, it gets assigned to a different value on the following line. Once that happens, Dart itself notes that there is one fewer references to the object and, if zero, schedules the object for garbage collection.

I don't know why I thought this might be necessary in some cases. I do get easily confused in the presence of C++.

Anyhow, back in my client code, I draw those circles with the mixture of api1 and api2, then update the drawers so that everyone is using api2:
main() {
  // ...
  circle1.draw();
  circle2.draw();
  circle3.draw();

  circle1.drawer = api2;
  circle2.drawer = api2;

  api1 = api2 = null;

  circle1.draw();
  circle2.draw();
  circle3.draw();
}
When I run this code, I get what I expect. The first time through a mixture of api1 and api2 drawers are used, when I switch to all-api2, the deletion of the api2 reference is noted, then api2 drawing commences:
./bin/draw.dart
  Instance of 'DrawingApi1' Increased refcount to 1
  Instance of 'DrawingApi1' Increased refcount to 2
  Instance of 'DrawingApi2' Increased refcount to 1
[DrawingApi1] circle at (1.0, 2.0) with radius 3.000
[DrawingApi1] circle at (0.0, 6.0) with radius 1.000
[DrawingApi2] circle at (2.0, 2.0) with radius 1.500
  Instance of 'DrawingApi2' Increased refcount to 2
  Instance of 'DrawingApi1' Decreased refcount to 1
  Instance of 'DrawingApi2' Increased refcount to 3
  Instance of 'DrawingApi1' Decreased refcount to 0
  ** Deleting no longer used Instance of 'DrawingApi1' **
[DrawingApi2] circle at (1.0, 2.0) with radius 3.000
[DrawingApi2] circle at (0.0, 6.0) with radius 1.000
[DrawingApi2] circle at (2.0, 2.0) with radius 1.500
So it works, but Dart already had me covered with garbage collection. The effort is not a complete waste. I did clear up whatever C++ confusion I was suffering. Potentially more important, I have client code that can demonstrate sharing implementors such that garbage collection will reclaim them when they go unused.

Play with the code on DartPad: https://dartpad.dartlang.org/43585b1f6e0f1403fc5a.


Day #79

No comments:

Post a Comment