-- File: NetDirCache.mesa,  Last Edit: HGM March 12, 1981  12:59 PM

DIRECTORY
  String USING [EquivalentString],
  Storage USING [Node, Free],

  StatsDefs USING [StatCounterIndex, StatIncr],
  NameServerDefs USING [
    CacheEntry, CacheEntryObject, SearchNetDirForName, SearchNetDirForAddress],
  PupTypes USING [PupAddress];

NetDirCache: MONITOR
  IMPORTS String, Storage, StatsDefs, NameServerDefs EXPORTS NameServerDefs =
  BEGIN OPEN StatsDefs, NameServerDefs;

  PupAddress: TYPE = PupTypes.PupAddress;
  CacheEntry: TYPE = NameServerDefs.CacheEntry;
  CacheEntryObject: TYPE = NameServerDefs.CacheEntryObject;

  firstCacheEntry: CacheEntry ← NIL;
  cacheLimit: CARDINAL ← 400;
  cacheWordsInUse: CARDINAL ← 0;
  sequenceNumber: CARDINAL ← 0;

  statHits, statMisses, statNone, statFile: PUBLIC StatCounterIndex;


  EmptyCacheEntryObject: CacheEntryObject =
    [next: NIL, size: SIZE[CacheEntryObject], count: 1, sequence: 0,
      names: DESCRIPTOR[NIL, 0], addrs: DESCRIPTOR[NIL, 0]];

  SetCacheSize: PUBLIC PROCEDURE [n: CARDINAL] = BEGIN cacheLimit ← n; END;

  BumpCacheSize: PUBLIC PROCEDURE [n: INTEGER] =
    BEGIN cacheLimit ← cacheLimit + n; END;


  FlushWholeCache: PUBLIC ENTRY PROCEDURE =
    BEGIN
    WHILE firstCacheEntry # NIL DO FlushOneEntry[]; ENDLOOP;
    IF cacheWordsInUse # 0 THEN ERROR CacheShouldBeEmptyNow;
    END;

  EnumerateCache: PUBLIC ENTRY PROCEDURE [proc: PROCEDURE [CacheEntry]] =
    BEGIN
    FOR ce: CacheEntry ← firstCacheEntry, ce.next UNTIL ce = NIL DO
      proc[ce]; ENDLOOP;
    END;

  GetCacheLocation: PUBLIC PROCEDURE RETURNS [POINTER TO CacheEntry] =
    BEGIN RETURN[@firstCacheEntry]; END;

  SearchCacheForName: PUBLIC ENTRY PROCEDURE [key: STRING]
    RETURNS [ce: CacheEntry] =
    BEGIN
    previous: CacheEntry;
    FOR ce ← firstCacheEntry, ce.next UNTIL ce = NIL DO
      FOR i: CARDINAL IN [0..LENGTH[ce.names]) DO
	IF String.EquivalentString[key, ce.names[i]] THEN
	  BEGIN
	  StatIncr[statHits];
	  IF LENGTH[ce.addrs] = 0 THEN StatIncr[statMisses];
	  ce.count ← ce.count + 1;
	  IF firstCacheEntry # ce THEN
	    BEGIN -- move this entry to the head of the list
	    previous.next ← ce.next;
	    ce.next ← firstCacheEntry;
	    firstCacheEntry ← ce;
	    END;
	  RETURN;
	  END;
	ENDLOOP;
      previous ← ce;
      ENDLOOP;
    StatIncr[statNone];
    END;

  SearchCacheForAddress: PUBLIC ENTRY PROCEDURE [key: PupAddress]
    RETURNS [ce: CacheEntry] =
    BEGIN
    previous: CacheEntry;
    FOR ce ← firstCacheEntry, ce.next UNTIL ce = NIL DO
      FOR i: CARDINAL IN [0..LENGTH[ce.addrs]) DO
	IF key = ce.addrs[i] THEN
	  BEGIN
	  StatIncr[statHits];
	  IF LENGTH[ce.addrs] = 0 THEN StatIncr[statMisses];
	  ce.count ← ce.count + 1;
	  IF firstCacheEntry # ce THEN
	    BEGIN -- move this entry to the head of the list
	    previous.next ← ce.next;
	    ce.next ← firstCacheEntry;
	    firstCacheEntry ← ce;
	    END;
	  RETURN;
	  END;
	ENDLOOP;
      previous ← ce;
      ENDLOOP;
    StatIncr[statNone];
    END;

  ForceNameIntoCache: PUBLIC ENTRY PROCEDURE [key: STRING]
    RETURNS [ce: CacheEntry] =
    BEGIN
    mine: CacheEntryObject ← EmptyCacheEntryObject;
    IF ~NameServerDefs.SearchNetDirForName[key, @mine] THEN RETURN[NIL];
    StatIncr[statFile];
    ce ← AddToCache[@mine];
    END;

  ForceAddressIntoCache: PUBLIC ENTRY PROCEDURE [key: PupAddress]
    RETURNS [ce: CacheEntry] =
    BEGIN
    mine: CacheEntryObject ← EmptyCacheEntryObject;
    IF ~NameServerDefs.SearchNetDirForAddress[key, @mine] THEN RETURN[NIL];
    StatIncr[statFile];
    ce ← AddToCache[@mine];
    END;

  NothingInCacheToFlush: ERROR = CODE;
  CacheShouldBeEmptyNow: ERROR = CODE;

  FlushOneEntry: INTERNAL PROCEDURE =
    BEGIN
    previous, ce: CacheEntry;
    IF firstCacheEntry = NIL THEN ERROR NothingInCacheToFlush;
    ce ← firstCacheEntry;
    IF firstCacheEntry.next = NIL THEN firstCacheEntry ← NIL
    ELSE
      BEGIN
      WHILE ce.next # NIL DO previous ← ce; ce ← ce.next; ENDLOOP;
      previous.next ← NIL;
      END;
    cacheWordsInUse ← cacheWordsInUse - ce.size;
    FOR i: CARDINAL IN [0..LENGTH[ce.names]) DO
      Storage.Free[ce.names[i]]; ENDLOOP;
    IF BASE[ce.names] # NIL THEN Storage.Free[BASE[ce.names]];
    IF BASE[ce.addrs] # NIL THEN Storage.Free[BASE[ce.addrs]];
    Storage.Free[ce];
    END;


  AddToCache: INTERNAL PROCEDURE [his: CacheEntry] RETURNS [ce: CacheEntry] =
    BEGIN
    WHILE his.size + cacheWordsInUse > cacheLimit DO FlushOneEntry[]; ENDLOOP;
    ce ← Storage.Node[SIZE[CacheEntryObject]];
    ce↑ ← his↑;
    ce.next ← firstCacheEntry;
    ce.sequence ← (sequenceNumber ← sequenceNumber + 1);
    firstCacheEntry ← ce;
    cacheWordsInUse ← cacheWordsInUse + ce.size;
    END;

  END.