Virtual Proxy Pattern



In this article I discuss how to implement the virtual proxy pattern in
Ada95.

Discussion

We'd like to optimize the amount of time it takes to open an image-rich
document.  The problem now is that every image in the file is getting
streamed in from disk and converted to its in-memory representation, and
this processing takes too long.

We can save start-up time by postponing initialization of an image until
it comes time to actually render it.  This technique is probably
familiar to those of you who have done X Windows programming.  Creating
every single dialog in the application up front can take a while, so you
wait until the user issues a pop-up request and just create the dialog
on-the-fly.

We perform this optimization by inserting image proxies in our document
instead of images.  An image proxy caches the information necessary to
initialize the image, but delays actual initialization until it receives
a request that can only be handled by the image itself.

Implementation

The example here closely follows the C++ example in the GoF book.  There
are two types, Image_Type and Image_Proxy, and both derive from the root
of the type hierarchy, Root_Graphic_Type.

In our implementation, the root type is declared in package Graphics,
the root of the subsystem:

package Graphics is

  type Root_Graphic_Type (<>) is abstract tagged limited private;
...
   procedure Draw
     (Graphic : access Root_Graphic_Type;
      Point   : in     Point_Type) is abstract;

   


   type Graphic_Handle is private;

   type Root_Graphic_Access is access all Root_Graphic_Type'Class;

   function "+" (Handle : Graphic_Handle) return Root_Graphic_Access;
...
end Graphics;


If you've been following my recent posts, you will recognize certain
idioms.  The tagged type is limited and indefinite to control how
instances are created, and there's a handle type for automatic memory
management.  The primitive operations take access parameters so that
dereferencing of access objects is done implicitly.

Following standard convention, the derived types are declared in
children of the package containing the declaration of the parent type.
Package Image_Proxies thus looks like this:

package Graphics.Image_Proxies is

   type Image_Proxy is new Root_Graphic_Type with private;

   

   function New_Image_Proxy (File : String) return Graphic_Handle;
...
end Graphics.Image_Proxies;


An image proxy has a pointer to an image, and a buffer for the image
data file name.  When asked to perform an operation requiring an actual
image, the image proxy will construct the image on-the-fly, passing the
file name to the image constructor.  The representation of the image
proxy therefore looks like this:

   type Image_Proxy is
     new Root_Graphic_Type with record
        Image  : Graphic_Handle;
        Name   : Unbounded_String;
        Extent : Point_Type;
     end record;


Proxy operations are implemented by creating the image if it doesn't
already exist, and then forwarding the same request to the image.
Here's a typical implementation:

   procedure Draw
     (Proxy : access Image_Proxy;
      Point : in     Point_Type) is
   begin
      ...
      Draw (Get_Image (Proxy), Point);
   end;


Get_Image is a local utility function that creates the image.  If the
image doesn't exist, then it calls the image constructor, passing it the
image data file name (that the proxy received during its own
construction):

   function Get_Image
     (Proxy : access Image_Proxy) return Root_Graphic_Access is

      use Images;
   begin

      if Proxy.Image = Null_Handle then
         Proxy.Image := New_Image (To_String (Proxy.Name));
      end if;

      return +Proxy.Image;

   end Get_Image;


Get_Image sets the image pointer of the proxy as a side-effect of the
call.  (This is possible because Image_Proxy is an access parameter.)
The last thing it does is to "dereference" the handle returned by the
image constructor, and return that as the result of the function.  That
value in turn is used by the caller as the argument to an image
operation.

The only other interesting operation is Free, which places the object
back into storage.  We need to finalize any resources being used by the
object prior to finalization of the object itself.  Free for the image
proxy looks like this:

   procedure Free (Proxy : access Image_Proxy) is

      use Proxy_Storage;
   begin

      Proxy.Name := Null_Unbounded_String;

      Proxy.Image := Null_Handle;

      Do_Free (Proxy);

   end Free;


Free finalizes the image by assigning the value null to the image
pointer.  Image reclamation is done automatically as a side-effect of
the assignment, because the image is designated by a smart pointer.

Matt



The files below are in a format suitable for use with gnatchop.

--STX
with Graphics.Images;
with Graphics.Storage;

with Ada.Text_IO; use Ada.Text_IO;

package body Graphics.Image_Proxies is

   package Proxy_Storage is
     new Graphics.Storage.Generic_Graphic (Image_Proxy);


   function Get_Image
     (Proxy : access Image_Proxy) return Root_Graphic_Access is

      use Images;

   begin

      if Proxy.Image = Null_Handle then
         Proxy.Image := New_Image (To_String (Proxy.Name));
      end if;

      return +Proxy.Image;

   end Get_Image;


   procedure Draw
     (Proxy : access Image_Proxy;
      Point : in     Point_Type) is
   begin
      Put_Line ("drawing image by way of proxy");
      Draw (Get_Image (Proxy), Point);
   end;


   procedure Handle_Mouse
     (Proxy : access Image_Proxy;
      Event : in     Event_Type) is
   begin
      Put_Line ("handling mouse by way of proxy");
      Handle_Mouse (Get_Image (Proxy), Event);
   end;


   function Get_Extent
     (Proxy : access Image_Proxy) return Point_Type is
   begin
      Put_Line ("getting extent by way of proxy");

      if Proxy.Extent = Zero_Point then
         Proxy.Extent := Get_Extent (Get_Image (Proxy));
      end if;

      return Proxy.Extent;
   end;


   function New_Image_Proxy (File : String) return Graphic_Handle is

      use Proxy_Storage;

      Handle : constant Graphic_Handle := New_Graphic;

      Proxy : constant Graphic_Access := Ref (Handle);

   begin

      Proxy.Name := To_Unbounded_String (File);

      Proxy.Extent := Zero_Point;

      return Handle;

   end New_Image_Proxy;


   procedure Free (Proxy : access Image_Proxy) is

      use Proxy_Storage;

   begin

      Proxy.Name := Null_Unbounded_String;

      Proxy.Image := Null_Handle;

      Do_Free (Proxy);

   end Free;

end Graphics.Image_Proxies;
with Ada.Strings.Unbounded;  use Ada.Strings.Unbounded;

package Graphics.Image_Proxies is

   type Image_Proxy is new Root_Graphic_Type with private;

   procedure Draw
     (Proxy : access Image_Proxy;
      Point : in     Point_Type);

   procedure Handle_Mouse
     (Proxy : access Image_Proxy;
      Event : in     Event_Type);

   function Get_Extent
     (Proxy : access Image_Proxy) return Point_Type;


   function New_Image_Proxy (File : String) return Graphic_Handle;

private

   type Image_Proxy is
     new Root_Graphic_Type with record
        Image  : Graphic_Handle;
        Name   : Unbounded_String;
        Extent : Point_Type;
     end record;

   procedure Free (Proxy : access Image_Proxy);

end Graphics.Image_Proxies;
with Graphics.Storage;
with Ada.Text_IO;      use Ada.Text_IO;

package body Graphics.Images is

   package Image_Storage is
     new Storage.Generic_Graphic (Image_Type);

   use Image_Storage;


   procedure Draw (Image : access Image_Type;
                   Point : in     Point_Type) is
   begin
      Put_Line ("drawing image with name '" &
                To_String (Image.Name) &
                "' at point " &
                Get_Image (Point));
   end Draw;


   procedure Handle_Mouse (Image : access Image_Type;
                           Event : in     Event_Type) is
   begin
      Put_Line ("image with name '" &
                To_String (Image.Name) &
                "' is handling mouse with event " &
                Event_Type'Image (Event));
   end Handle_Mouse;


   function Get_Extent
     (Image : access Image_Type) return Point_Type is
   begin
      return Image.Extent;
   end;


   function New_Image (File : String) return Graphic_Handle is

      Handle : constant Graphic_Handle := New_Graphic;

      Image : constant Graphic_Access := Ref (Handle);

   begin

      Image.Name := To_Unbounded_String (File);

      Image.Extent := Point_Type'(X => 80, Y => 132);

      return Handle;

   end New_Image;


   procedure Free (Image : access Image_Type) is
   begin
      Image.Name := Null_Unbounded_String;
      Do_Free (Image);
   end;

end Graphics.Images;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

package Graphics.Images is

   type Image_Type is new Root_Graphic_Type with private;

   procedure Draw (Image : access Image_Type;
                   Point : in     Point_Type);

   procedure Handle_Mouse (Image : access Image_Type;
                           Event : in     Event_Type);

   function Get_Extent
     (Image : access Image_Type) return Point_Type;


   function New_Image (File : String) return Graphic_Handle;

private

   type Image_Type is
     new Root_Graphic_Type with record
        Name   : Unbounded_String;
        Extent : Point_Type;
     end record;

   procedure Free (Image : access Image_Type);

end Graphics.Images;




package body Graphics.Storage is

   package body Generic_Graphic is

      Free_List : Root_Graphic_Access;


      function New_Graphic return Root_Graphic_Access is
      begin

         if Free_List = null then

            declare
               Graphic : constant Graphic_Access :=
                 Graphic_Access'(new Graphic_Type);
            begin
               return Root_Graphic_Access (Graphic);
            end;

         else

            declare
               Graphic : constant Root_Graphic_Access :=
                 Free_List;
            begin
               Free_List := Free_List.Next;
               Graphic.Next := null;

               return Graphic;
            end;

         end if;

      end New_Graphic;


      function New_Graphic return Graphic_Handle is

         Graphic : constant Root_Graphic_Access := New_Graphic;

         Handle : Graphic_Handle;

      begin

         Handle.Graphic := Graphic;

         Handle.Graphic.Count := 1;

         return Handle;

      end New_Graphic;


      function Ref (Handle : Graphic_Handle) return Graphic_Access is
      begin
         return Graphic_Access (Handle.Graphic);
      end;


      procedure Do_Free (Graphic : access Graphic_Type) is
      begin
         Graphic.Next := Free_List;
         Free_List := Root_Graphic_Access (Graphic);
      end;

   end Generic_Graphic;

end Graphics.Storage;


private package Graphics.Storage is

   generic
      type Graphic_Type is new Root_Graphic_Type with private;
   package Generic_Graphic is

      type Graphic_Access is access all Graphic_Type;

      function New_Graphic return Graphic_Handle;

      function Ref (Handle : Graphic_Handle) return Graphic_Access;


      procedure Do_Free (Graphic : access Graphic_Type);

   end Generic_Graphic;

end Graphics.Storage;


package body Graphics is

   function Get_Image (Point : Point_Type) return String is

      X : constant String := Integer'Image (Point.X);
      Y : constant String := Integer'Image (Point.Y);
   begin
      return "(" & X (2 .. X'Last) & "," & Y & ")";
   end;



   function "+" (Handle : Graphic_Handle) return Root_Graphic_Access is
   begin
      return Handle.Graphic;
   end;


   procedure Free (Graphic : access Root_Graphic_Type) is
   begin
      null;
   end;


   procedure Adjust (Handle : in out Graphic_Handle) is
   begin
      if Handle.Graphic /= null then

         Handle.Graphic.Count := Handle.Graphic.Count + 1;

      end if;
   end Adjust;


   procedure Finalize (Handle : in out Graphic_Handle) is
   begin
      if Handle.Graphic /= null then

         Handle.Graphic.Count := Handle.Graphic.Count - 1;

         if Handle.Graphic.Count = 0 then
            Free (Handle.Graphic);
         end if;

      end if;
   end Finalize;


end Graphics;



with Ada.Finalization; use Ada.Finalization;

package Graphics is

   type Root_Graphic_Type (<>) is abstract tagged limited private;

   type Root_Graphic_Access is access all Root_Graphic_Type'Class;


   type Point_Type is
      record
         X, Y : Integer;
      end record;

   Zero_Point : constant Point_Type := (X => 0, Y => 0);

   function Get_Image (Point : Point_Type) return String;

   type Event_Type is (Mouse_Up, Mouse_Down, Button_Press);


   procedure Draw
     (Graphic : access Root_Graphic_Type;
      Point   : in     Point_Type) is abstract;

   procedure Handle_Mouse
     (Graphic : access Root_Graphic_Type;
      Event   : in     Event_Type) is abstract;

   function Get_Extent
     (Graphic : access Root_Graphic_Type) return Point_Type is abstract;


   type Graphic_Handle is private;

   Null_Handle : constant Graphic_Handle;

   function "+" (Handle : Graphic_Handle) return Root_Graphic_Access;

private

   type Root_Graphic_Type is
     abstract tagged limited record
        Count : Natural;
        Next  : Root_Graphic_Access;
     end record;

   procedure Free (Graphic : access Root_Graphic_Type);


   type Graphic_Handle is
     new Controlled with record
        Graphic : Root_Graphic_Access;
     end record;

   procedure Adjust (Handle : in out Graphic_Handle);

   procedure Finalize (Handle : in out Graphic_Handle);

   Null_Handle : constant Graphic_Handle :=
     (Controlled with Graphic => null);

end Graphics;



with Graphics;                use Graphics;
with Graphics.Images;         use Graphics.Images;
with Graphics.Image_Proxies;  use Graphics.Image_Proxies;

with Ada.Text_IO; use Ada.Text_IO;

procedure Test_Proxy is

   procedure Do_Stuff_To
     (Graphic : access Root_Graphic_Type'Class) is
   begin

      Draw (Graphic, Point_Type'(3, 4));
      New_Line;

      Handle_Mouse (Graphic, Mouse_Up);
      New_Line;

      declare
         Extent : constant Point_Type := Get_Extent (Graphic);
      begin
         Put_Line ("extent is " & Get_Image (Extent));
         New_Line;
      end;

   end Do_Stuff_To;


   Image : constant Graphic_Handle :=
     New_Image ("myimage.dat");

   Proxy : constant Graphic_Handle :=
     New_Image_Proxy ("myimageproxy.dat");

begin

   Put_Line ("do stuff to image:");
   New_Line;
   Do_Stuff_To (+Image);

   New_Line;
   Put_Line ("do stuff to proxy:");
   New_Line;
   Do_Stuff_To (+Proxy);

end Test_Proxy;

Contributed by: Matthew Heaney
Contributed on: March 8, 1999
License: Public Domain
Back