-- InscriptImpl.mesa
-- Manage Inscript file
--  Last Edited by Stone June 14, 1982 4:32 pm
--  Last Edited by McGregor, 14-Sep-81 16:38:27
--  Last Edited by Swinehart, April 29, 1982 5:13 pm
--  << This version requires that the Inscript file be resident.  Its default size has
--	therefore been made much smaller than previous versions (16 pages, of which
--	at least 8 have predictable contents (steady state)>>
--  << This version also assumes (and ensures) that there is but one inscript
--	per module instance.>> 

DIRECTORY
  CedarSnapshot USING [ Register, RollbackProc ],
  ClassInscript,
  Directory USING [ CreateFile, DeleteFile, Error, LookupUnlimited ],
  File USING [ Capability, GetSize, MakePermanent, nullCapability ],
  DCSFileTypes USING [ tLeaderPage ],
  Inline USING [ BITAND, BITSHIFT, LongCOPY, LowHalf ],
  Intime USING [ EventTime, EventTimeDifference, MsTicks, ReadEventTime ],
  Process USING [
    Abort, Aborted, Detach, EnableAborts, MsecToTicks, SecondsToTicks, Ticks ],
  Rope USING [ ROPE, ToRefText ],
  Space USING [ Create, --CreateUniformSwapUnits,-- Delete, ForceOut,
	--GetHandle,-- Handle, --Kill,-- LongPointerFromPage, Map,
	--PageFromLongPointer, -- virtualMemory, VMPageNumber, wordsPerPage ],
  SpecialSpace USING [
	MakeCodeResident, 	MakeGlobalFrameResident,
	MakeResident, MakeSwappable ],
  TerminalMultiplex USING [ RegisterNotifier, SwapAction ]
  ;
-- <<Denotes variables that would have to move into object for multiple files.>>

InscriptImpl: MONITOR -- invariant: File contents consistent

  IMPORTS
    CedarSnapshot, Inline, Intime, Process, Rope, Dir: Directory, File,
    Space, SpecialSpace, TerminalMultiplex
  EXPORTS ClassInscript
  SHARES ClassInscript =
  BEGIN OPEN ClassInscript, Intime;

  InscriptData: TYPE = LONG POINTER TO InscriptState;
  InscriptObject: PUBLIC TYPE = InscriptState;
  ROPE:TYPE=Rope.ROPE;
  
InscriptState: TYPE = RECORD [refCount: INT];
-- << One inscript per instance; state entirely contained in frame.
 --     NewStdInscript ups ref count; destroy downs it.>>
 -- file information
    inscriptFileName: Rope.ROPE←NIL; -- OK, but . . . 
    inscriptFile: File.Capability;
    inscriptSpace: Space.Handle;

 -- mapping information
    earliestPage: InscriptPageNumber ← 0;
    latestRecordedD: InscriptPageDescBody←nullDescBody;
    subspaceSize: CARDINAL; -- # of pages of inscr. file mapped as a unit to insc. VM
    spaceBase: InscriptPage;
    spaceLimit: InscriptPage;

 -- output page information:
    writeDescriptor: PageDescriptor=@writeD; -- all entries into file use this
    writeD: InscriptPageDescBody←nullDescBody;
    maintProcess: PROCESS; -- background, writes dirty pages occasionally
    
-- this and that
    actualPageMask: WORD; --<<>>
    subspaceMask: WORD; --<<>>
    hintPageNumber: ActualIPN; --<<>>
    hintPage: InscriptPage;
    minPagesInUse: ActualIPN;   --<<]>>;

  -- <<Everything is built in, for now; possibly multiple simultaneous inscripts using this code wouldn't work.>>
  stdInscriptState: InscriptState;
  stdInscript: InscriptData=@stdInscriptState;

  InscriptError: PUBLIC ERROR [code: InscriptErrorCode] = CODE;


  -- Types, specifications for the low-level Ring-buffer inscript
  --   file implemented here

  Internal: ERROR[code: CARDINAL] = CODE;
  pageSize: INTEGER=Space.wordsPerPage;
  PageIndex: TYPE = [0..pageSize);

  PageDescriptor: TYPE = LONG POINTER TO InscriptPageDescBody;
  -- <<Must be 4 words long, or must change opaque type in ClassInscript>>
  InscriptPageDescBody: PUBLIC TYPE = RECORD [
    pageNumber: InscriptPageNumber,
    iP: InscriptPage, -- base of current page.
    index: PageIndex ];
    
  nullDescBody: InscriptPageDescBody =[pageNumber:-1,iP:NIL,index:NULL];
    
  InscriptPage: TYPE = LONG POINTER TO InscriptPageValue;
  InscriptPageValue: TYPE = MACHINE DEPENDENT RECORD [
    seal: CARDINAL, -- and this one --
    data: SELECT OVERLAID * FROM
     entryPage=> [ 
         length: CARDINAL, -- excludes this word --
         entries: ARRAY [0..nInscriptPageIndex) OF WORD],
     hintPage=> [
         earliestPage: CARDINAL,
          latestPage: CARDINAL,
          subspaceSize: CARDINAL,
          rest: ARRAY [0..nInscriptPageIndex-2) OF WORD ],
     ENDCASE];
  nInscriptPageIndex: PageIndex = pageSize - 2;
  inscriptPageSeal: CARDINAL = 154275B;

  ActualIPN: TYPE = INTEGER;
  nullAIPN: ActualIPN=-1;
  Time0: OrderKey=[0,0,0];

NewStdInscript: PUBLIC PROCEDURE [fileName: Rope.ROPE,
	KeyProc: KEyProc, ComProc: COmProc, initializeFile: BOOLEAN ← FALSE,
	lnFileSize: INTEGER←4, lnGroupSize: INTEGER←2] RETURNS [Inscript] = {
  ENABLE InscriptError => { IF code=invalidInscriptFile THEN Space.Delete[inscriptSpace]; };
  Complain: PROC={ IF ~initializeFile THEN ERROR InscriptError[invalidInscriptFile]; };
  
  refCount: INT=stdInscript.refCount+1;
  fN: LONG STRING=LOOPHOLE[Rope.ToRefText[fileName]];
  fileLength: ActualIPN=TwoToThe[lnFileSize]+2; --1 for leader page, 1 for my trailer

  IF refCount#1 THEN ERROR Internal[6]; -- not now
  IF lnGroupSize>=lnFileSize OR lnGroupSize < 2 THEN
    	ERROR InscriptError[invalidInscriptSpecs];
  
  inscriptFileName←fileName;
  inscriptSpace←Space.Create[size: fileLength-1, parent: Space.virtualMemory];
  inscriptFile←File.nullCapability;   
  inscriptFile←Dir.LookupUnlimited[fileName: fN!
  	Dir.Error=>IF type=fileNotFound THEN CONTINUE ELSE ERROR Internal[4]];
  DO
    IF inscriptFile=File.nullCapability THEN { -- new file
      Complain[];
      inscriptFile←Dir.CreateFile[fileName: fN,
		size: fileLength-1, fileType: DCSFileTypes.tLeaderPage];
      File.MakePermanent[inscriptFile];  EXIT; }
    ELSE {
      IF Inline.LowHalf[File.GetSize[inscriptFile]]=fileLength THEN EXIT;
      Complain[]; Dir.DeleteFile[fileName: fN]; inscriptFile←File.nullCapability; };
    ENDLOOP;
  -- Correct-sized file exists.
  Space.Map[space: inscriptSpace, window: [file: inscriptFile, base: 1]];
  -- Space.CreateUniformSwapUnits[parent: inscriptSpace, size: 1];
  
  -- File calculations
  subspaceSize←TwoToThe[lnGroupSize];
  actualPageMask←Inline.BITSHIFT[-1,lnFileSize-16]; --//
  subspaceMask←Inline.BITSHIFT[-1,lnGroupSize]; --//
  minPagesInUse←fileLength-2-2*subspaceSize; --//
  hintPageNumber←fileLength-2; --//
  spaceBase←AccessIP[0];
  spaceLimit←hintPage←AccessIP[hintPageNumber];
  
  earliestPage←0; writeD←nullDescBody; writeD.pageNumber←0;
  IF initializeFile THEN []←SetPage[stdInscript, writeDescriptor, 0, TRUE]
  ELSE { -- file init: old
    pD: PageDescriptor=@latestRecordedD; -- use as temp here
    KeyFrom: PROC[pN: InscriptPageNumber] RETURNS [OrderKey] = {
	IF SetPage[stdInscript, pD, pN] THEN
	  RETURN[KeyProc[stdInscript, pD!InscriptError=>IF code=invalidPageKey THEN CONTINUE]];
	RETURN[Time0]};
    latestTime, nextTime: OrderKey; latestPage, maxLP: InscriptPageNumber;
    IF hintPage.seal#inscriptPageSeal OR hintPage.subspaceSize#subspaceSize THEN Complain[];
    earliestPage←Inline.BITAND[hintPage.earliestPage,actualPageMask]; -- normalize
    latestPage←writeD.pageNumber←hintPage.latestPage-(hintPage.earliestPage-earliestPage);
    maxLP←latestPage+fileLength-3; -- search max distance
    latestTime←KeyFrom[latestPage];
    IF latestTime=Time0 THEN ERROR InscriptError[invalidInscriptFile];
    FOR pageNumber: InscriptPageNumber IN [latestPage+1..maxLP) DO
      nextTime←KeyFrom[pageNumber];
      IF ComProc[latestTime, nextTime] THEN EXIT
      ELSE { latestTime←nextTime; latestPage←pageNumber; };
      ENDLOOP;
    IF ~SetPage[stdInscript, writeDescriptor, latestPage] THEN ERROR Internal[1];
    writeD.index ← writeD.iP.length; };
  -- Final init
  latestRecordedD←writeD;
  InscriptNotifier[coming];		-- lock inscript space, enable recording.
  maintProcess ← FORK PageMaintainer[];
  Process.Detach[maintProcess];
  stdInscript.refCount←refCount;
  RETURN[stdInscript]; };

  Release: PUBLIC ENTRY PROCEDURE [self: InscriptData] RETURNS [nilInscript: Inscript] =
    BEGIN
    ENABLE UNWIND => NULL;
    IF (self.refCount←self.refCount-1)#0 THEN RETURN[NIL[Inscript]];
    Process.Abort[maintProcess];
    InscriptNotifier[going]; -- <<superfluous?>>
    Space.Delete[inscriptSpace];
    inscriptFileName←NIL;
    RETURN[NIL[Inscript]];
    END;

  GetPageLimits: PUBLIC ENTRY PROCEDURE [self: InscriptData]
    RETURNS [earliestPageNo: InscriptPageNumber, latestPageNo: InscriptPageNumber] = {
    RETURN[earliestPageNo: earliestPage, latestPageNo: writeD.pageNumber]; };

  ResetPageDescriptor: PUBLIC ENTRY PROCEDURE [self: InscriptData, descriptor: PageDescriptor] = {
    IF descriptor#NIL THEN descriptor↑←nullDescBody}; -- now invalid

  CopyPageDescriptor: PUBLIC ENTRY PROCEDURE [
    self: InscriptData, dest: PageDescriptor, source: PageDescriptor] = {
      dest↑ ← source↑; };

  WaitForEntry: PUBLIC ENTRY PROCEDURE [
    self: InscriptData, waitMode: WaitMode, waitInterval: MsTicks ← waitALongTime,
    descriptor: PageDescriptor, waitStartTime: LONG POINTER TO EventTime ← NIL]
    RETURNS [moreEntries: BOOLEAN] = {
    ENABLE UNWIND => NULL;
    t0, t1: EventTime;
    waitTime: MsTicks ← waitInterval;
    IF waitMode = timed AND waitStartTime = NIL THEN {
      t0 ← ReadEventTime[]; waitStartTime ← @t0; };
    DO
 --   IF descriptor.pageNumber > writeD.pageNumber THEN ERROR Internal[5] --
      --ELSE-- IF descriptor.pageNumber < writeD.pageNumber OR
         descriptor.index<writeD.index THEN RETURN[TRUE];
      SELECT waitMode FROM
	dontWait => RETURN[FALSE];
	forever => waitTime←waitALongTime;
	timed => {
	  dif: MsTicks;
	  t1 ← ReadEventTime[];
	  dif ← EventTimeDifference[@t1, waitStartTime];
	  IF dif >= waitInterval THEN RETURN[FALSE];
	  waitTime ← waitInterval - dif; };
	ENDCASE;
      Wait[Process.MsecToTicks[waitTime]];
      ENDLOOP; };

-- Cleans up descriptor, if necessary
-- Reads the indicated pages
-- success: page available, has seal; failure: page unavailable, or does not have seal (uninit.)
-- If init is TRUE, assumes page contents is valueless, initializes page

  SetPage: PUBLIC PROCEDURE [
    self: InscriptData, descriptor: PageDescriptor,
    pageNumber: InscriptPageNumber, init: BOOLEAN←FALSE] RETURNS [success: BOOLEAN←TRUE] = {
    IF ~IsValidPd[descriptor] OR descriptor.pageNumber # pageNumber THEN {
      iP: InscriptPage;
      IF earliestPage > pageNumber OR writeD.pageNumber < pageNumber THEN RETURN[FALSE];
      descriptor.iP←iP←AccessIP[  IPNToActual[ descriptor.pageNumber←pageNumber ]  ];
      IF init THEN { iP.length←0; iP.seal ← inscriptPageSeal; }
      -- Next test useful only during initialization, in FindTimeSeam --
      ELSE IF iP.seal # inscriptPageSeal THEN success←FALSE; };
    descriptor.index ← 0; };

-- Read the next page, set descriptor.
-- Failure: same as SetPage
-- Raises InscriptError[descriptorUninitialized] if there's no current page.
  AdvancePage: PUBLIC PROCEDURE [self: InscriptData, descriptor: PageDescriptor]
    RETURNS [success: BOOLEAN] = {
    pageNumber: InscriptPageNumber=descriptor.pageNumber+1;
    IF ~IsValidPd[descriptor] THEN ERROR InscriptError[descriptorUninitialized];
    IF earliestPage > pageNumber OR writeD.pageNumber < pageNumber THEN RETURN[FALSE];
    descriptor.pageNumber←pageNumber;
    descriptor.iP←descriptor.iP+pageSize; IF LC[descriptor.iP]>=LC[spaceLimit] THEN descriptor.iP←spaceBase;
    descriptor.index←0;
    RETURN[TRUE]; };

  SetWritePage: PUBLIC ENTRY PROCEDURE [self: InscriptData] RETURNS [success: BOOLEAN] = {
    writeD.pageNumber←writeD.pageNumber+1;
    writeD.iP←writeD.iP+pageSize; IF LC[writeD.iP]>=LC[spaceLimit] THEN writeD.iP←spaceBase;
    writeD.iP.seal←inscriptPageSeal;
    writeD.index←writeD.iP.length←0;
    earliestPage ← Inline.BITAND[MAX[earliestPage, writeD.pageNumber-minPagesInUse], subspaceMask];
    NOTIFY pageDone;
    RETURN[TRUE]; };

  ReadEntry: PUBLIC ENTRY PROCEDURE [
    self: InscriptData, descriptor: PageDescriptor,
    destination: LONG POINTER TO UNSPECIFIED, LengthProc: LengthProcType]
    RETURNS [success: BOOLEAN] = {
    ENABLE UNWIND => NULL;
    index: PageIndex ← descriptor.index;
    len: CARDINAL;
    entry: LONG POINTER TO UNSPECIFIED;
    pageNumber: InscriptPageNumber = descriptor.pageNumber;
    inscriptPage: InscriptPage=descriptor.iP;
    IF ~IsValidPd[descriptor] THEN ERROR InscriptError[descriptorUninitialized];
    IF earliestPage > pageNumber -- OR pageNumber > writeD.pageNumber -- THEN 
      ERROR InscriptError[entryOutOfBounds];
    IF index >= inscriptPage.length THEN RETURN[FALSE];
    entry ← @(inscriptPage.entries[index]);
    Inline.LongCOPY[from: entry, to: destination, nwords: (len←LengthProc[entry])];
 --   IF index+len > inscriptPage.length THEN RETURN[FALSE];
    descriptor.index ← index + len;
    RETURN[TRUE]; };

  -- Put entry in a buffer (indicate if page filled up).  See MaintainPage for actual file write
  WriteEntry: PUBLIC ENTRY PROCEDURE [
    self: InscriptData, entry: LONG DESCRIPTOR FOR ARRAY OF WORD]
    RETURNS [success: BOOLEAN] = {
    ENABLE UNWIND => NULL;
    newIndex: CARDINAL = writeD.index+LENGTH[entry];
    IF newIndex > nInscriptPageIndex THEN RETURN[FALSE];
    Inline.LongCOPY[BASE[entry], LENGTH[entry], @writeD.iP.entries[writeD.index]];
    writeD.index ← writeD.iP.length ← newIndex;
    maintDue←FALSE; -- non-preemptive notify for page maintainer
    BROADCAST entriesWaiting;
    RETURN[TRUE]; };
    

-- Utilities, Cleanup process, mapping functions, machine extensions

PageMaintainer: PROC = {
-- Monitored only where required.   Says here.
-- For the current page, this function merely keeps the inscript file
-- tracking reality approximately; for pages that are finished, no other process will
-- be changing them again.  Says here.

  DO -- forever
    ENABLE Process.Aborted => EXIT; -- (almost forever)
    SELECT MaintainPage[FALSE] FROM
      dirty=> Space.ForceOut[inscriptSpace];
      aborted=>EXIT;
      ENDCASE; ENDLOOP; };
  
 dirtyThreshhold: INTEGER=2;
 MaintainPage: ENTRY PROC[forceDirty: BOOLEAN] RETURNS [{dirty, aborted}] = { 
  DO
    ENABLE Process.Aborted=>EXIT;
    IF ~forceDirty THEN {
		dif: INTEGER;
		WAIT pageDone;
    	dif←writeD.pageNumber-latestRecordedD.pageNumber;
    	IF latestRecordedD=writeD OR (dif<dirtyThreshhold AND ~maintDue) THEN {
      		maintDue←TRUE; LOOP }; };
    maintDue←FALSE;
    latestRecordedD←writeD;
    hintPage.seal←inscriptPageSeal;
    hintPage.earliestPage←earliestPage;
    hintPage.latestPage←latestRecordedD.pageNumber;
    hintPage.subspaceSize←subspaceSize;
    RETURN[dirty]; ENDLOOP;
  RETURN[aborted];};


  AccessIP: PROCEDURE[actualIPN: ActualIPN]
  	RETURNS[page: InscriptPage] = INLINE
      {RETURN[LOOPHOLE[Space.LongPointerFromPage[Space.VMPageNumber[inscriptSpace]+actualIPN]]];};

  IPNToActual: PROCEDURE [pN: InscriptPageNumber]
    RETURNS [ActualIPN] = INLINE -- Assert 0<=pN
    {RETURN[Inline.BITAND[pN, actualPageMask]]; };
      
  IsValidPd: PROCEDURE[pD: PageDescriptor] RETURNS [BOOLEAN] = INLINE
  	{ RETURN[pD.iP#NIL]};
	
  LC:PROC[iP:InscriptPage]RETURNS[LONG CARDINAL]=INLINE{RETURN[LOOPHOLE[iP]]};

-- Wait on entriesWaiting for specified time

  Wait: INTERNAL PROCEDURE [t: CARDINAL] = INLINE {
    entriesWaiting.timeout←t; WAIT entriesWaiting};

  TwoToThe: PROC[ln: CARDINAL] RETURNS [CARDINAL] = INLINE {
    RETURN[Inline.BITSHIFT[1, ln]]};

  entriesWaiting: CONDITION;
  pageDone: CONDITION;
  maintTicks: Process.Ticks=Process.SecondsToTicks[6];
  maintDue: BOOLEAN←FALSE;

  -- Initialization --

inscriptCurrent: INTEGER ← 0;

InscriptNotifier: PROC[action: TerminalMultiplex.SwapAction] = {
	SELECT action FROM
		coming => {
			IF inscriptCurrent = 0 THEN SpecialSpace.MakeResident[inscriptSpace]; 
			inscriptCurrent ← inscriptCurrent + 1; };
		going => {
			inscriptCurrent ← inscriptCurrent - 1;
			IF inscriptCurrent = 0 THEN {
			  SpecialSpace.MakeSwappable[inscriptSpace];
			  []←MaintainPage[TRUE];
			  Space.ForceOut[inscriptSpace]; }};
		ENDCASE=>ERROR; };

InscriptRBProc: CedarSnapshot.RollbackProc={
	InscriptNotifier[coming]; };

InscriptCPProc: PROC[ASP: PROC[Space.Handle]] = {
  InscriptNotifier[going]; ASP[inscriptSpace];}; 

  pageDone.timeout ← maintTicks;
  -- NOTE: Must be locked because of known planned use by other abstractions! --
  []←Intime.ReadEventTime[]; -- make sure Time starts first.
  SpecialSpace.MakeGlobalFrameResident[InscriptImpl];
  SpecialSpace.MakeCodeResident[InscriptImpl];
  Process.EnableAborts[@pageDone];
  Process.EnableAborts[@entriesWaiting];
  TerminalMultiplex.RegisterNotifier[InscriptNotifier];
  CedarSnapshot.Register[c: InscriptCPProc, r: InscriptRBProc];
END.
   
  
  -- Currently unneeded
  
    InitPage: PROC[inscriptPage: InscriptPage] = INLINE {
    	Space.Kill[IPToSubspace[inscriptPage]]; };

IPToSubspace: PROCEDURE[inscriptPage: InscriptPage] RETURNS[h: Space.Handle] = INLINE
  	{ RETURN[Space.GetHandle[Space.PageFromLongPointer[inscriptPage]]]; };