-- TEditBitmapCacheImpl.mesa; Edited by McGregor on  4-Dec-81  9:48:21

DIRECTORY

  Inline USING [LongNumber],
  TextEdit USING [Offset],
  TextNode USING [Location],
  TEditBitmapCache USING [Bitmap],
  TEditCompile USING [maxCacheEntries, maxCacheSize, useCache];

TEditBitmapCacheImpl: MONITOR

EXPORTS TEditBitmapCache = BEGIN

LRUVal: TYPE = LONG CARDINAL ← 0;

CacheEntry: TYPE = RECORD [
	lru: LRUVal,
	key: TextNode.Location,
	bitmap: TEditBitmapCache.Bitmap
	];

CacheIndex: TYPE = [0..TEditCompile.maxCacheSize);

cache: REF ARRAY CacheIndex OF CacheEntry ← NEW[ARRAY CacheIndex OF CacheEntry];
cacheFree: CacheIndex ← TEditCompile.maxCacheEntries;

lruCount: LRUVal;

Fetch: PUBLIC ENTRY PROCEDURE [key: TextNode.Location]
	RETURNS [TEditBitmapCache.Bitmap] =
	BEGIN	-- returns NIL if no cache hit
	entry: CacheIndex;
	found: BOOLEAN;
	IF TEditCompile.useCache THEN BEGIN
		[found, entry] ← Find[key];
		IF ~found THEN RETURN [NIL];
		cache[entry].lru ← NextLRU[];
		RETURN[cache[entry].bitmap];
		END
	ELSE RETURN[NIL];
	END;

AddCacheEntry: PUBLIC ENTRY PROCEDURE [key: TextNode.Location,
	bitmap: TEditBitmapCache.Bitmap] =
	BEGIN
	entry: CacheIndex ← Hash[key];
	IF TEditCompile.useCache THEN BEGIN
		IF bitmap=NIL OR key.node=NIL THEN ERROR;
		IF cacheFree=0 THEN FlushLRU;
		UNTIL (cache[entry].bitmap=NIL) OR (key=cache[entry].key) DO
			entry ← Next[entry];
			ENDLOOP;
		IF key=cache[entry].key THEN RemoveEntry[entry];
		cache[entry] ← [NextLRU[], key, bitmap];
		cacheFree ← cacheFree - 1;
		END;
	END;
	
FlushCacheEntry: PUBLIC ENTRY PROCEDURE [key: TextNode.Location] =
	BEGIN	-- flushes all in node if key.where = LAST[Offset]
	entry: CacheIndex;
	found: BOOLEAN;
	IF TEditCompile.useCache THEN BEGIN
		IF key.where=LAST[TextEdit.Offset] THEN BEGIN
			FOR entry IN CacheIndex DO
				IF cache[entry].bitmap#NIL AND cache[entry].key.node=key.node
					THEN RemoveEntry[entry];
				ENDLOOP;
			END
		ELSE BEGIN
			[found, entry] ← Find[key];
			IF found THEN RemoveEntry[entry];
			END;
		END;
	END;
	
FlushOldCacheRange: PUBLIC ENTRY PROCEDURE [key: TextNode.Location] =
	BEGIN	-- flushes all in node if last referenced earlier than key and whose
		-- where is greater or equal to than the key
	entry: CacheIndex;
	found: BOOLEAN;
	threshold: LRUVal;
	IF TEditCompile.useCache THEN BEGIN
		[found, entry] ← Find[key];
		IF ~found THEN ERROR;
		threshold ← cache[entry].lru;
		FOR entry IN CacheIndex DO
			IF cache[entry].bitmap#NIL AND cache[entry].key.node=key.node AND
			cache[entry].lru<threshold AND cache[entry].key.where>=key.where
				THEN RemoveEntry[entry];
			ENDLOOP;
		END;
	END;
		
--UpdateCacheNodeOffsets: PUBLIC ENTRY TEditDocument.UpdateNodeOffsetsProc =
--	BEGIN
--	IF TEditCompile.useCache THEN BEGIN
--		FOR entry:CacheIndex IN CacheIndex DO
--			IF cache[entry].bitmap#NIL AND cache[entry].key.node=pos.node AND
--			  cache[entry].key.where>=pos.where THEN BEGIN
--				cache[entry].key.where ← SELECT op FROM
--					add	=>	cache[entry].key.where + delta
--					ENDCASE	=>	cache[entry].key.where - delta;
--				MoveEntry[entry, cache[entry].key];
--				END;
--			ENDLOOP;
--		END;
--	END;
		
FlushLRU: INTERNAL PROCEDURE = BEGIN
	minEntry, entry: CacheIndex;
	minVal: LRUVal ← LAST[LRUVal];
	FOR entry IN CacheIndex DO
		IF cache[entry].bitmap#NIL AND cache[entry].lru<minVal THEN BEGIN
			minVal ← cache[entry].lru;
			minEntry ← entry;
			END;
		ENDLOOP;
	RemoveEntry[minEntry];
	END;

MoveEntry: INTERNAL PROCEDURE [entry: CacheIndex, newKey: TextNode.Location] = BEGIN
	-- this is to patch around the horrible problem of not having persistant names
	-- for text lines.  If we change the key values of entries in the cache, we
	-- need to physically move them so that the hash function we use still gets a hit.
	bitmap: TEditBitmapCache.Bitmap ← cache[entry].bitmap;
	newEntry: CacheIndex ← Hash[newKey];
	cache[entry].bitmap ← NIL;	-- clear old value
	cache[entry].key ← [NIL, 0];
	UNTIL (cache[newEntry].bitmap=NIL) OR (newKey=cache[newEntry].key) DO
		newEntry ← Next[newEntry];
		ENDLOOP;
	IF newKey=cache[newEntry].key THEN cacheFree ← cacheFree + 1;
	-- the above case can (unobviously) occur when an intervening entry has been
	-- deleted, allowing for an older, identically keyed object to reside in the cache
	cache[newEntry] ← [NextLRU[], newKey, bitmap];
	END;
	
Find: INTERNAL PROCEDURE [key: TextNode.Location]
	RETURNS [found: BOOLEAN, entry: CacheIndex] = BEGIN
	entry ← Hash[key];
	UNTIL cache[entry].key=key DO
		IF cache[entry].bitmap=NIL THEN RETURN [FALSE, entry];
		entry ← Next[entry];
		ENDLOOP;
	RETURN[TRUE, entry];
	END;

EmptyCache: INTERNAL PROCEDURE = BEGIN
	-- Flush everything in the cache
	FOR entry: CacheIndex IN CacheIndex DO
		IF cache[entry].bitmap#NIL THEN RemoveEntry[entry]
		ENDLOOP;
	IF cacheFree#TEditCompile.maxCacheEntries THEN ERROR;
	END;
	
RemoveEntry: INTERNAL PROCEDURE [entry: CacheIndex] = BEGIN
	IF cacheFree=TEditCompile.maxCacheEntries THEN ERROR;
	cache[entry] ← [0, [NIL, 0], NIL];	-- throw this away
	cacheFree ← cacheFree + 1;
	END;

Hash: INTERNAL PROCEDURE [key: TextNode.Location] RETURNS [CacheIndex] =
	INLINE BEGIN OPEN Inline;
	RETURN [(LOOPHOLE[key.node, LongNumber].lowbits +
		LOOPHOLE[key.where, LongNumber].lowbits) MOD TEditCompile.maxCacheSize]
	END;

Next: INTERNAL PROCEDURE [current: CacheIndex] RETURNS [CacheIndex] = INLINE BEGIN
	RETURN[(current+3) MOD TEditCompile.maxCacheSize];
	END;
	
NextLRU: INTERNAL PROCEDURE RETURNS [LRUVal] = INLINE BEGIN
	RETURN[lruCount ← lruCount+1];
	END;
	

END.