-- Bitmap cache for Chipmonk
-- last modified by McCreight, March 29, 1983  11:47 AM
-- written by McCreight, January 11, 1983  2:22 PM
-- temporarily set up for color only

DIRECTORY
  BitBltDefs,
  ChipOrient,
  InlineDefs,
  MiscDefs,
  multiGraphicsDefs,
  ppddefs,
  ppdefs,
  ppCache,
  ProcessDefs,
  ZoneAllocDefs;

ppCacheImpl: MONITOR
  IMPORTS BitBltDefs, ChipOrient, InlineDefs, MiscDefs, multiGraphicsDefs,
    ppddefs, ppdefs, ProcessDefs, ZoneAllocDefs
  EXPORTS ppdefs, ppCache =
  BEGIN OPEN ppdefs, ppddefs, ChipOrient;

--   T y p e s

  Cache: PUBLIC TYPE = CacheHead;

  CacheHeadPtr: TYPE = LONG POINTER TO CacheHead ← NIL;
  CacheHead: TYPE = RECORD [
    locks: CARDINAL ← 1,
    biggestSave: locNum ← 0,
    bsFinished: BOOLEAN ← FALSE,
    firstPaint: CacheMapPtr
    ];

  CacheMapPtr: TYPE = LONG POINTER TO CacheMap ← NIL;
  CacheMap: TYPE = RECORD
    [
    locked: BOOLEAN ← TRUE,
    ob: ppdefs.obPtr ← NIL,
    descr: CacheDescr,
    nextThisOb, nextLRU, prevLRU: CacheMapPtr ← NIL,
    raster: RasterDesc,
    bits: SEQUENCE size: CARDINAL OF CARDINAL
    ];

  CacheDescr: TYPE = RECORD[
    isColor: BOOLEAN ← FALSE,
    scale: INTEGER ← 0,
    orient: ppdefs.orientationIndex ← 0,
    posMod: Point ← [0, 0]  -- .x and .y IN [0..grayPeriod*ScaleD)
    ];
  
  colBitsPerPixel: CARDINAL = 4;
  logColBitsPerPixel: CARDINAL = 2;

  GrayOffsetValue: TYPE = [0..15] ← 0; -- repeat period of stipples
  GrayOffset: TYPE = RECORD[x, y: GrayOffsetValue];
  GrayTablePtr: TYPE = LONG POINTER TO GrayTable ← NIL;
  GrayTable: TYPE = ARRAY level OF multiGraphicsDefs.GrayPattern;

-- E X T E R N A L   (to module monitor)   P r o c e d u r e s

  CachingIsEnabled: PUBLIC PROC RETURNS [BOOLEAN] =
    {RETURN[enableCache]};

  DisableCache: PUBLIC PROC =
    BEGIN
    IF enableCache THEN
      BEGIN
      enableCache ← FALSE;
      WHILE cacheHead.nextLRU#cacheHead DO
        FlushObjectCache[cacheHead.nextLRU.ob];
        ENDLOOP;
      -- at this point, nobody has a pointer to any cache
      cacheZone ← ZoneAllocDefs.DestroyAnXMZone[cacheZone];
      END;
    END;

  EnableCache: PUBLIC PROC =
    BEGIN
    IF NOT enableCache THEN
      BEGIN
      cacheZone ← ZoneAllocDefs.GetAnXMZone[];
      cacheHead ← cacheZone.NEW[CacheMap[0]];
      cacheHead.nextLRU ← cacheHead.prevLRU ← cacheHead;
      colCacheGrayTables ← cacheZone.NEW[ARRAY [0..4) OF GrayTable];
      enableCache ← TRUE;
      END;
    END;

  FlushEntireCache: PUBLIC PROC =
    {IF enableCache THEN {DisableCache[]; EnableCache[]}};

  DrawWithCaching: PUBLIC PROC [ob: obPtr, orient: orientationIndex, x, y: locNum,
    pr: POINTER TO drRecord, dest: Raster] =
    BEGIN
    OrWithCaching[ob: ob, orient: orient, x: x, y: y, pr: pr, dest: dest];
    SaveWithCaching[ob: ob, orient: orient, x: x, y: y, pr: pr, dest: dest];
    END;

  overflowMentioned: BOOLEAN ← FALSE;

  OrWithCaching: PROC [ob: obPtr, orient: orientationIndex, x, y: locNum,
    pr: POINTER TO drRecord, dest: Raster, inhibitRoot: BOOLEAN ← FALSE] =
    BEGIN OPEN multiGraphicsDefs;
    
    CachedColorOr: PROC [ob: obPtr, orient: orientationIndex, x, y: locNum,
      pr: POINTER TO drRecord] RETURNS [drawn: BOOLEAN] =
      BEGIN -- assumes already clipped

      OrColorGray: PROC [x1, y1, x2, y2: locNum, l: level, pr: POINTER TO Rect] =
        BEGIN OPEN InlineDefs;
        BitBltGrayPtr: TYPE = LONG POINTER TO ARRAY [0..4) OF CARDINAL;
        IF NOT showColorLevel[l] THEN RETURN;
        BLT.blk.sourcetype ← gray;
        BLT.blk.dlbca ← c.raster.map;
        BLT.blk.dbmr ← c.raster.scanLineWords;
        BLT.blk.dlx ← BITSHIFT[
          cscaleDelta[start: c.descr.posMod.x, delta: x1], logColBitsPerPixel];
        BLT.blk.dty ← cscaleDelta[start: c.descr.posMod.y, delta: y1];
        BLT.blk.dw ← MAX[colBitsPerPixel, BITSHIFT[
          cscaleDelta[start: c.descr.posMod.x, delta: x2], logColBitsPerPixel]-
          BLT.blk.dlx];
        BLT.blk.dh ← MAX[1, cscaleDelta[start: c.descr.posMod.y, delta: y2]-BLT.blk.dty];
        LOOPHOLE[LONG[@BLT.blk.gray0], BitBltGrayPtr]↑ ← LOOPHOLE[@grayTable[l]
            [InlineDefs.BITAND[c.descr.posMod.y/cScaleD+BLT.blk.dty, 3]], BitBltGrayPtr]↑;
        IF compileChecks THEN
          BEGIN
          IF BLT.blk.dlx+BLT.blk.dw>CARDINAL[c.raster.nPixels.x] THEN
            BEGIN
            IF NOT overflowMentioned THEN
              BEGIN
              overflowMentioned ← TRUE;
              MiscDefs.CallDebugger["Raster bounds fault, OK to proceed"];
              END;
            BLT.blk.dw ← MAX[0, c.raster.nPixels.x-INTEGER[BLT.blk.dlx]];
            END;
          IF BLT.blk.dty+BLT.blk.dh>CARDINAL[c.raster.nPixels.y] THEN
            BEGIN
            IF NOT overflowMentioned THEN
              BEGIN
              overflowMentioned ← TRUE;
              MiscDefs.CallDebugger["Raster bounds fault, OK to proceed"];
              END;
            BLT.blk.dh ← MAX[0, c.raster.nPixels.y-INTEGER[BLT.blk.dty]];
            END;
          END;
        BitBltDefs.BITBLT[@BLT.blk];
        END; -- of OrColorGray

      c: CacheMapPtr;
      grayTable: GrayTablePtr;
      h: CacheHeadPtr;
      size: Point = Size[size: [ob.size[0]+2*wellSurround, ob.size[1]+2*wellSurround],
        orient: orient];
      sx: LONG INTEGER = cscale[size.x];
      sy: LONG INTEGER = cscale[size.y];
      descr: CacheDescr;

      IF MAX[sx, sy] > 2*MAX[colHeight, colWidth] OR
        sx*sy > maxColPixelsPerCacheMap OR
        (inhibitRoot AND ob=rootOb) THEN RETURN[FALSE];

      h ← SetupCacheHead[ob]; -- locks h

      descr ← [
        isColor: TRUE,
        scale: cScale,
        orient: orient,
        posMod: [x: Mod[cxoff+x, colGrayOffset.x*cScaleD],
          y: Mod[cyoff+y, colGrayOffset.y*cScaleD]]];

      FOR c ← h.firstPaint, c.nextThisOb WHILE c#NIL DO
        IF c.descr=descr THEN {MostRecentlyUsed[c]; EXIT};
        REPEAT
          FINISHED =>
            BEGIN -- make up a bitmap for this object
            size: Point = Size[size: [ob.size[0]+2*wellSurround, ob.size[1]+2*wellSurround],
              orient: orient];
            rasterSize: PixelPoint = IF descr.isColor 
              THEN [x: colBitsPerPixel*(cscaleDelta[start: descr.posMod.x, delta: size.x]+1),
                y: cscaleDelta[start: descr.posMod.y, delta: size.y]+1]
              ELSE [x: cscaleDelta[start: descr.posMod.x, delta: size.x]+1,
                y: cscaleDelta[start: descr.posMod.y, delta: size.y]+1];
            IF (c ← NewCacheMap[rasterSize])#NIL THEN
              BEGIN
              drR: drRecord ← pr↑;
              grayTable ← @colCacheGrayTables[c.descr.posMod.x/cScaleD];
              drR.r ← drR.bigr ← [x1: 0, y1: 0, x2: size.x, y2: size.y];
              c.ob ← ob;
              c.descr ← descr;
              NewMapUsed[c];
              drR.orArea ← OrColorGray;
              OrWithCaching[ob: c.ob, orient: c.descr.orient, x: wellSurround,
                y: wellSurround, pr: @drR, dest: @c.raster, inhibitRoot: TRUE
                ! UNWIND => UnlockCacheMap[c]];
              END
            ELSE {UnlockCacheHead[h]; RETURN[FALSE]};
            END;
        ENDLOOP;

      OrCachedBitmap[dest: dest, source: @c.raster,
        offset: [x: colBitsPerPixel*cscale[x-wellSurround], y: cscale[y-wellSurround]]];
        -- paint the source raster c at location x y in the raster dest

      UnlockCacheMap[c];
      RETURN[TRUE];
      END; -- of CachedColorOr

    rootOb: obPtr = ob;
    dr: drRecord ← [
      r: pr.r, bigr: pr.bigr,
      orArea: pr.orArea, saveArea: NullSave, outl: NullOutl, dtxt: NullText,
      minSize: pr.minSize,
      quickDraw: (SELECT TRUE FROM
      NOT enableCache, permittedCacheK<=0 => NIL,
      pr.orArea=orColArea AND cScale<=9 => CachedColorOr,
      ENDCASE => NIL)];
    ob.p.drawme[orient][ob: rootOb, x: x, y: y, pr: @dr];
    END; -- of OrWithCaching

  SaveWithCaching: PROC [ob: obPtr, orient: orientationIndex, x, y: locNum,
    pr: POINTER TO drRecord, dest: Raster, inhibitRoot: BOOLEAN ← FALSE] =
    BEGIN

    CachedColorSave: PROC [ob: obPtr, orient: orientationIndex, x, y: locNum,
      pr: POINTER TO drRecord] RETURNS [drawn: BOOLEAN] =
      BEGIN -- assumes already clipped
      h: CacheHeadPtr = ob.cache;
      RETURN[drawn: h#NIL AND h.bsFinished AND cscale[h.biggestSave]<2];
      END; -- of CachedColorSave

    rootOb: obPtr = ob;
    dr: drRecord ← [
      r: pr.r, bigr: pr.bigr,
      orArea: NullOr, saveArea: pr.saveArea, outl: NullOutl, dtxt: NullText,
      minSize: pr.minSize,
      quickDraw: (SELECT TRUE FROM
      NOT enableCache, permittedCacheK<=0 => NIL,
      pr.orArea=orColArea AND cScale<=9 => CachedColorSave,
      ENDCASE => NIL)];
    ob.p.drawme[orient][ob: ob, x: x, y: y, pr: @dr];
    END;


  SetupCacheGrayTables: PUBLIC PROC [] = -- called when color map changes
    BEGIN OPEN multiGraphicsDefs;
    IF enableCache THEN
      BEGIN
      xOffset, yOffset: GrayOffsetValue;
      colGrayOffset ← [1, 1]; -- find the shortest repeat pattern of all colors in use
      FOR l: level IN level DO
        colGray: GrayPattern = GetColorGray[orLtab[l]];
        FOR xOffset IN [1..4] DO
          FOR i: GrayPatternIndex IN GrayPatternIndex DO
            IF colGray[i] # Cycle[colGray[i], xOffset] THEN EXIT;
            REPEAT
              FINISHED => EXIT;
            ENDLOOP;
          ENDLOOP;
        colGrayOffset.x ← LCM[colGrayOffset.x, xOffset];
        FOR yOffset IN [1..4] DO
          FOR i: GrayPatternIndex IN GrayPatternIndex DO
            IF colGray[i] # colGray[(i+yOffset) MOD 4] THEN EXIT;
            REPEAT
              FINISHED => EXIT;
            ENDLOOP;
          ENDLOOP;
        colGrayOffset.y ← LCM[colGrayOffset.y, yOffset];
        ENDLOOP;
      FOR l: level IN level DO
        colGray: GrayPattern = GetColorGray[orLtab[l]];
        FOR xOffset IN [0..colGrayOffset.x) DO
          FOR i: GrayPatternIndex IN GrayPatternIndex DO
            colCacheGrayTables[xOffset][l][i] ←
              Cycle[colGray[i], xOffset*colBitsPerPixel];
            ENDLOOP;
          ENDLOOP;
        ENDLOOP;
      END;
    END;


  OrCachedBitmap: PROC [source, dest: Raster, offset: PixelPoint] =
    BEGIN
    sourceR: PixelRect =
      [x1: offset.x, y1: offset.y,
      x2: offset.x+source.nPixels.x-1, y2: offset.y+source.nPixels.y-1];
    destR: PixelRect = [x1: 0, y1: 0, x2: dest.nPixels.x-1, y2: dest.nPixels.y-1];
    resultR: PixelRect = ClipRect[sourceR, destR];
    IF Empty[resultR] THEN RETURN;
    BLT.blk.sourcetype ← block;
    BLT.blk.dlbca ← dest.map;
    BLT.blk.dbmr ← dest.scanLineWords;
    BLT.blk.slbca ← source.map;
    BLT.blk.sbmr ← source.scanLineWords;
    BLT.blk.dw ← resultR.x2-resultR.x1+1;
    BLT.blk.dh ← resultR.y2-resultR.y1+1;
    BLT.blk.dlx ← resultR.x1;
    BLT.blk.dty ← resultR.y1;
    BLT.blk.slx ← resultR.x1-offset.x;
    BLT.blk.sty ← resultR.y1-offset.y;
    BitBltDefs.BITBLT[@BLT.blk];
    END;


  SetupCacheHead: PROC [rootOb: obPtr] RETURNS [hRoot: CacheHeadPtr] =
    BEGIN
    IF MakeCacheHead[rootOb].new THEN
      BEGIN

      QuickSaveProc: PROC [ob: obPtr, orient: orientationIndex,
        x, y: locNum, pr: POINTER TO drRecord] RETURNS [drawn: BOOLEAN] =
        BEGIN
        h: CacheHeadPtr;
        IF ob = rootOb THEN RETURN[FALSE]
        ELSE
          BEGIN
          h ← SetupCacheHead[ob];
          hRoot.biggestSave ← MAX[hRoot.biggestSave, h.biggestSave];
          h.locks ← h.locks-1;
          RETURN[TRUE];
          END;
        END;

      SaveRectProc: PROC [x1, y1, x2, y2: locNum, l: level, pr: POINTER TO Rect] =
        {hRoot.biggestSave ← MAX[hRoot.biggestSave, x2-x1, y2-y1]};

      dr: drRecord ← [
        r: univ, bigr: univ,
        orArea: NullOr, saveArea: SaveRectProc, outl: NullOutl, dtxt: NullText,
        minSize: 1, quickDraw: QuickSaveProc];
      hRoot ← rootOb.cache;
      hRoot.biggestSave ← 0;
      rootOb.p.drawme[0][ob: rootOb, x: 0, y: 0, pr: @dr];
      hRoot.bsFinished ← TRUE;
      END
    ELSE hRoot ← rootOb.cache;
    END; -- of SetupCacheHead


  NullOr, NullSave: PROC [x1, x2, y1, y2: locNum, l: level, pr: POINTER TO Rect] =
    {NULL};
  NullOutl: PROC [x1, x2, y1, y2: locNum, c: color, pr: POINTER TO Rect] = {NULL};
  NullText: PROC [x1, x2, y1, y2: locNum, s: STRING, pr: POINTER TO Rect] = {NULL};

  cscaleDelta: PROC [start, delta: locNum] RETURNS [Pixel] = INLINE
    {RETURN[cscale[start+delta]-cscale[start]]};

  cscale: PROC [x: locNum] RETURNS [Pixel] = INLINE
    {RETURN[NarrowToInteger[LONG[cScaleN]*x]/cScaleD]};

  Cycle: PROC [value: UNSPECIFIED, count: INTEGER] RETURNS [UNSPECIFIED] = INLINE
    BEGIN OPEN InlineDefs;
    count ← Mod[count, 16]; -- mask to 4 bits
    RETURN[BITSHIFT[value, count]+BITSHIFT[value, count-16]];
    END;

  NarrowToInteger: PROC [x: LONG INTEGER] RETURNS [INTEGER] = INLINE
    {IF x<FIRST[INTEGER] OR x>LAST[INTEGER] THEN ERROR OutOfRange
      ELSE RETURN[InlineDefs.LowHalf[x]]};

  GCD: PROC [m,n: INTEGER] RETURNS [INTEGER] =
    BEGIN -- greatest common divisor
    r: INTEGER;
    WHILE (r ← m MOD n) > 0 DO
      m ← n;
      n ← r;
      ENDLOOP;
    RETURN[n];
    END;

  LCM: PROC [m,n: INTEGER] RETURNS [INTEGER] =
    -- least common multiple
    {RETURN[m*(n/GCD[m, n])]};
    

--   E N T R Y   P r o c e d u r e s

  FlushObjectCache: PUBLIC ENTRY PROC [ob: obPtr] =
    BEGIN ENABLE ANY => MiscDefs.CallDebugger["Signalling out of monitor"];
    WHILE ob.cache#NIL DO
      h: CacheHeadPtr = ob.cache;
      IF h.firstPaint#NIL THEN FreeCacheMap[h.firstPaint]
      ELSE
        BEGIN
        waitingProcesses ← waitingProcesses+1;
        WHILE h.locks>0 DO WAIT UnlockedCache ENDLOOP;
        waitingProcesses ← waitingProcesses-1;
        IF ob.cache#NIL THEN
          BEGIN
          cacheZone.FREE[@ob.cache];
          ob.cache ← NIL;
          END; -- ELSE somebody else freed it while we were waiting
        END;
      ENDLOOP;
    END;

  MostRecentlyUsed: ENTRY PROC [c: CacheMapPtr] =
    BEGIN ENABLE ANY => MiscDefs.CallDebugger["Signalling out of monitor"];
    ExtractLRU[c];
    InsertLRU[c];
    END;

  NewMapUsed: ENTRY PROC [c: CacheMapPtr] =
    BEGIN ENABLE ANY => MiscDefs.CallDebugger["Signalling out of monitor"];
    h: CacheHeadPtr ← c.ob.cache;
    c.nextThisOb ← h.firstPaint;
    h.firstPaint ← c;
    h.locks ← h.locks+1;
    InsertLRU[c];
    END;

  MakeCacheHead: ENTRY PROC [ob: obPtr] RETURNS [new: BOOLEAN] =
    BEGIN ENABLE ANY => MiscDefs.CallDebugger["Signalling out of monitor"];
    h: CacheHeadPtr = ob.cache;
    IF h = NIL THEN
      BEGIN
      ob.cache ← cacheZone.NEW[CacheHead ←  []];
      RETURN[TRUE];
      END
    ELSE {h.locks ← h.locks+1; RETURN[FALSE]};
    END;

  NewCacheMap: ENTRY PROC [rasterSize: PixelPoint] RETURNS [c: CacheMapPtr] =
    BEGIN ENABLE ANY => MiscDefs.CallDebugger["Signalling out of monitor"];
    scanWidthInWords: INTEGER = (rasterSize.x+15)/16;
    bitMapSize: INTEGER = NarrowToInteger[
      LONG[scanWidthInWords]*LONG[rasterSize.y]];
    IF bitMapSize<=0 THEN ERROR;
    IF NOT FlushLRUVictims[SIZE[CacheMap[bitMapSize]]].sufficientSpace THEN
      RETURN[NIL];
    c ← cacheZone.NEW[CacheMap[bitMapSize] ←
      [locked: TRUE,
      raster: [nPixels: rasterSize,
        scanLineWords: scanWidthInWords, map: NULL],
      bits: NULL]];
    c.raster.map ← LOOPHOLE[@c.bits[0]];
    c.bits[0] ← 0; -- turn it to background color of 0
    IF bitMapSize>1 THEN
      InlineDefs.LongCOPY[from: @c.bits[0], to: @c.bits[1], nwords: bitMapSize-1];
    END;

  UnlockCacheMap: ENTRY PROC [c: CacheMapPtr] =
    BEGIN ENABLE ANY => MiscDefs.CallDebugger["Signalling out of monitor"];
    h: CacheHeadPtr = c.ob.cache;
    c.locked ← FALSE;
    h.locks ← h.locks-1; -- also unlocks head
    IF waitingProcesses>0 THEN BROADCAST UnlockedCache;
    END;

  UnlockCacheHead: ENTRY PROC [h: CacheHeadPtr] =
    BEGIN ENABLE ANY => MiscDefs.CallDebugger["Signalling out of monitor"];
    h.locks ← h.locks-1;
    IF h.locks=0 AND waitingProcesses>0 THEN BROADCAST UnlockedCache;
    END;

--   I N T E R N A L   P r o c e d u r e s


  FlushLRUVictims: INTERNAL PROC [wordsNeeded: LONG INTEGER] RETURNS
    [sufficientSpace: BOOLEAN] =
    BEGIN
    rover: CacheMapPtr ← cacheHead.prevLRU;
    WHILE ZoneAllocDefs.XMZoneWordsInUse[cacheZone]+wordsNeeded >
      LONG[permittedCacheK]*1000 AND rover#cacheHead DO
      c: CacheMapPtr = rover;
      rover ← rover.prevLRU;
      IF NOT rover.locked THEN FreeCacheMap[c];
      ENDLOOP;
    RETURN[ZoneAllocDefs.XMZoneWordsInUse[cacheZone]+wordsNeeded <=
      LONG[permittedCacheK]*1000]; 
    END;

  FreeCacheMap: INTERNAL PROC [c: CacheMapPtr] =
    BEGIN
    ob: obPtr;
    h: CacheHeadPtr;
    waitingProcesses ← waitingProcesses+1;
    WHILE c.locked DO WAIT UnlockedCache ENDLOOP;
    waitingProcesses ← waitingProcesses-1;
    ob ← c.ob;
    h ← ob.cache;
    IF h.firstPaint=c THEN h.firstPaint ← c.nextThisOb
      ELSE
        FOR pc: CacheMapPtr ← h.firstPaint, pc.nextThisOb WHILE pc.nextThisOb#c DO
          REPEAT
            FINISHED => pc.nextThisOb ← c.nextThisOb;
          ENDLOOP;
    ExtractLRU[c];
    cacheZone.FREE[@c];
    h.locks ← h.locks-1;
    END;

  ExtractLRU: INTERNAL PROC [c: CacheMapPtr] = INLINE
    BEGIN
    c.nextLRU.prevLRU ← c.prevLRU;
    c.prevLRU.nextLRU ← c.nextLRU;
    END;

  InsertLRU: INTERNAL PROC [c: CacheMapPtr] = INLINE
    BEGIN
    c.nextLRU ← cacheHead.nextLRU;
    c.prevLRU ← cacheHead;
    cacheHead.nextLRU.prevLRU ← c;
    cacheHead.nextLRU ← c;
    c.locked ← TRUE;
    END;

--   S i g n a l s   &   C o n d i t i o n s 

  OutOfRange: ERROR = CODE;
  UnlockedCache: CONDITION;


--   M o d u l e   I n i t i a l i z a t i o n

  enableCache: BOOLEAN ← FALSE;
  cacheZone: UNCOUNTED ZONE ← NIL;
  cacheHead: CacheMapPtr ← NIL;
  waitingProcesses: CARDINAL ← 0;
  colCacheGrayTables: LONG POINTER TO ARRAY [0..4) OF GrayTable ← NIL;
  BLT: multiGraphicsDefs.BLTBlockPtr ← multiGraphicsDefs.AllocateBLT[];
  colGrayOffset: GrayOffset ← [x: 1, y: 1];

  permittedCacheK: PUBLIC INTEGER ← 100;
  maxColPixelsPerCacheMap: PUBLIC LONG INTEGER ← 20000;
  maxBWPixelsPerCacheMap: PUBLIC LONG INTEGER ← 40000;

  BLT.blk.destalt ← FALSE;
  BLT.blk.sty ← 0; --DORADO looks here
  BLT.blk.function ← paint;

  ProcessDefs.InitializeCondition[@UnlockedCache, ProcessDefs.MsecToTicks[2000]];
  EnableCache[];

  END.