Module Weak
Author: Christian.Queinnec@lip6.fr

## Module `Weak`

Un pointeur faible (weak pointer) est un pointeur dont la zone mémoire pointée est récupérable à tout moment par le GC. Il peut être surprenant de parler d'une valeur qui peut disparaître à tout instant. En fait il faut voir ces pointeurs faibles comme un réservoir de valeurs encore disponibles. Cela s'avère particulièrement utile quand les ressources mémoire sont petites par rapport aux éléments à conserver. Le cas classique est la gestion d'un cache mémoire : une valeur peut être perdue, mais elle reste directement accessible tant qu'elle existe.
En Objective CAML on ne peut pas manipuler directement des pointeurs faibles, mais uniquement des tableaux de pointeurs faibles. Le module `Weak` définit le type abstrait 'a Weak.t correspondant au type 'a option array, vecteur de pointeurs faibles de type `'a`. Le type concret 'a option est défini de la manière suivante :

    type 'a option = None | Some of 'a;;

Les fonctions principales de ce module sont définies à la figure 9.14.

> fonction | type
> `create` | int -> 'a t
> `set` | 'a t -> int -> 'a option -> unit
> `get` | 'a t -> int -> 'a option
> `check` | 'a t -> int -> bool

> Figure 9.14 : fonctions principales du module `Weak`

La fonction `create` alloue l'espace d'un tableau de pointeurs faibles dont chaque élément est initialisé par `None`. La fonction `set` range une valeur de type 'a option à un indice donné. La fonction `get` retourne la valeur d'indice n contenue dans un tableau de pointeurs faibles. La valeur retournée est alors référencée dans l'ensemble des racines du GC et n'est plus récupérable tant que dure ce référencement. On vérifie l'existence effective d'une valeur soit en utilisant la fonction `check`, soit par filtrage sur les motifs du type 'a option. La première solution est indépendante de la représentation des pointeurs faibles.
Les fonctions habituelles sur les structures linéaires existent aussi : `length`, pour la longueur, `fill` et `blit` pour les copies de parties de tableaux.

#### Exemple : cache d'images

Dans une application de construction d'images, il n'est pas rare de travailler sur plusieurs images. Quand on passe d'une image à une autre, la première est sauvegardée dans un fichier et la suivante est chargée à partir d'un autre fichier. On ne conserve en général que les noms des dernières images traitées. Pour éviter des accès au disque trop fréquents tout en n'occupant pas trop d'espace mémoire, on utilise un cache mémoire qui contient les dernières images chargées. Le contenu du cache mémoire peut être libéré si nécessaire. On l'implante par un tableau de pointeurs faibles, laissant à la charge du GC la décision de libération de ces images. Le chargement d'une image cherche tout d'abord dans le cache si l'image y est, si oui elle devient l'image courante, sinon son fichier est lu.
On définit une table des images de la manière suivante :

    # type table_of_images = {
       size : int;
       mutable ind : int;
       mutable name : string;
       mutable current : Graphics.color array array;
       cache : ( string * Graphics.color array array) Weak.t } ;;

Le champ `size` donne la taille du tableau ; le champ `ind`, l'indice de l'image courante ; le champ `name`, le nom de de l'image courante ; le champ `current`, l'image courante et le champ `cache` contient le tableau de pointeurs faibles sur les images. Il contient les dernières images chargées et leur nom.
La fonction `init_table` initialise la table avec une première image.

    # let open_image filename =
       let ic = open_in filename
       in let i = ((input_value ic) : Graphics.color array array)
       in ( close_in ic ; i ) ;;
    val open_image : string -> Graphics.color array array = <fun>

    # let init_table n filename =
       let i = open_image filename
       in let c =  Weak.create n
       in Weak.set c 0 (Some (filename,i)) ;
          { size=n; ind=0; name = filename; current = i; cache = c } ;;
    val init_table : int -> string -> table_of_images = <fun>

Le chargement d'une nouvelle image conservera l'image courante dans la table et chargera la nouvelle. Pour ce faire, il faut tout d'abord tenter de trouver l'image dans le cache.

    # exception Found of int * Graphics.color array array ;;
    # let search_table filename table =
       try
         for i=0 to table.size-1 do
           if i<>table.ind then match Weak.get table.cache i with
                  Some (n,img) when n=filename  -> raise (Found (i,img))
                | _ -> ()
           done ;
         None
       with Found (i,img) -> Some (i,img) ;;

    # let load_table filename table =
       if table.name = filename then ()  (* l'image est l'image courante *)
       else
         match search_table filename table with
           Some (i,img) ->
           (* l'image trouvée devient l'image courante *)
             table.current <- img ;
             table.name <- filename ;
             table.ind <- i
         | None ->
           (* l'image n'est pas dans le cache, il faut la  charger *)
           (* trouver une place vide dans le cache *)
             let i = ref 0 in
               while (!i<table.size && Weak.check table.cache !i) do incr i done ;
               (* si aucune n'est libre, on en prend une pleine *)
                 ( if !i=table.size then i:=(table.ind+1) mod table.size ) ;
                 (* on charge l'image à cette place
                   et elle devient l'image courante *)
                 table.current <- open_image filename ;
                 table.ind <- !i ;
                 table.name <- filename ;
                 Weak.set table.cache table.ind (Some (filename,table.current)) ;;
    val load_table : string -> table_of_images -> unit = <fun>

La fonction `load_table` teste si l'image demandée est l'image courante, sinon elle vérifie dans le cache si celle-ci existe, si ce n'est pas le cas elle la charge du disque; dans les deux cas elle en fait l'image courante.
Pour tester ce programme, on utilise la fonction d'affichage du cache suivante :

    # let print_table table =
       for i = 0 to table.size-1 do
         match Weak.get table.cache ((i+table.ind) mod table.size) with
           None -> print_string "[] "
         | Some (n,_) -> print_string n ; print_string " "
       done ;;
    val print_table : table_of_images -> unit = <fun>

Puis on teste le programme suivant :

    # let t = init_table 10 "IMAGES/animfond.caa" ;;
    val t : table_of_images =
      {size=10; ind=0; name="IMAGES/animfond.caa";
       current=
        [|[|7437734; 7437734; 7437734; 7437734; 7437734; 7437734; 7437734;
            7437734; 7437734; 7437734; 7437734; 7437734; 7440038; 7440038; ...|];
          ...|];
       cache=...}
    # load_table "IMAGES/anim.caa" t ;;
    - : unit = ()
    # print_table t ;;
    IMAGES/anim.caa [] [] [] [] [] [] [] [] IMAGES/animfond.caa - : unit = ()

Cette technique de cache peut s'adapter à différentes applications.