-- Copyright (C) 1986  by Xerox Corporation. All rights reserved. 
-- StartStateImpl.mesa
-- NFS		27-May-86 13:35:44
-- MEW		23-May-86 17:09:01

DIRECTORY
  CRuntime USING [CleanUp, GlobalFrameHandle, RemoveConfig, Restart, Start, z],
  CString USING [CString],
  File USING [File],
  Heap USING [CreateUniform],
  MFile USING [GetCreateDate, Handle],
  MLoader USING [Error, Handle, Load, Object, Unload, VersionMismatch],
  MLoaderImpl USING [Handle],
  PrincOps USING [GlobalFrameHandle],
  Space USING [Error],
  SpecialMFile USING [GetCapaWithAccess],
  SpecialRuntime USING [GlobalFrameFromProgram],
  StartState USING [abortOutcome, EnumerateProc, normalOutcome],
  Stream USING [Handle],
  Time USING [Packed];

StartStateImpl: MONITOR
  IMPORTS CRuntime, Heap, MFile, MLoader, Space, SpecialMFile, SpecialRuntime
  EXPORTS StartState
  SHARES MLoaderImpl =
  {

  loadTableSize: CARDINAL = 50;
  LTIndex: TYPE = [0..loadTableSize);
  Handle: TYPE = LONG POINTER TO Object;
  Object: PUBLIC TYPE = RECORD [
    file: File.File,
    create: Time.Packed,
    lh: MLoader.Handle,
    running: BOOLEAN,
    lti: LTIndex,
    link: Handle];
  LoadTable: TYPE = ARRAY LTIndex OF Handle;
  loadTable: LONG POINTER TO LoadTable;

  zone: PUBLIC UNCOUNTED ZONE ← Heap.CreateUniform[
    initial: 1, objectSize: Object.SIZE];

  normalOutcome: INTEGER = StartState.normalOutcome;
  abortOutcome: INTEGER = StartState.abortOutcome;

  GetHandle: PUBLIC ENTRY PROCEDURE [file: MFile.Handle]
    RETURNS [h: Handle, canRestart: BOOLEAN] = {
    ENABLE UNWIND => NULL;
    ff: File.File = SpecialMFile.GetCapaWithAccess[file];
    lti: LTIndex = LTHash[ff];
    create: Time.Packed = MFile.GetCreateDate[file];
    ssh: Handle ← loadTable[lti];
    DO
      SELECT TRUE FROM
        ssh = NIL => {
          ssh ← zone.NEW[Object ← [ff, create, NIL, TRUE, lti, loadTable[lti]]];
          loadTable[lti] ← ssh;
          canRestart ← FALSE;
          EXIT;
          };
        ssh.file = ff AND ssh.create = create AND ~ssh.running => {
          ssh.running ← TRUE; canRestart ← ssh.lh # NIL; EXIT; };
        ENDCASE => ssh ← ssh.link;
      ENDLOOP;
    RETURN[ssh, canRestart];
    };

  LTHash: PROCEDURE [file: File.File] RETURNS [LTIndex] = INLINE {
    RETURN[CARDINAL[LOOPHOLE[file.fileID, INT]] MOD loadTableSize]; };

  LoadError: PUBLIC ERROR [message: LONG STRING] = CODE;
  VersionMismatch: PUBLIC SIGNAL [module: LONG STRING] = CODE;
  UnloadError: PUBLIC ERROR [
    message: LONG STRING, instancesAlreadyUnloaded: CARDINAL] = CODE;

  Load: PUBLIC PROCEDURE [h: Handle, fh: MFile.Handle] = {
    ENABLE UNWIND => {ClearRunning[h]; SetLoadHandle[h, NIL]; };
    h.lh ← MLoader.Load[
      fh !
      MLoader.Error =>
        ERROR LoadError[
          SELECT code FROM
            badCode => "not /-a code"L,
            invalidParameters => "not in valid bcd format"L,
            missingCode => "missing code"L,
            exportedTypeClash => "exported type clash"L,
            lookupFailure => "look up failure"L,
            gftFull => "out of space in MDS"L,
            loadStateFull => "load state full"L,
            ENDCASE => "unknown loader error"L];
      MLoader.VersionMismatch => {SIGNAL VersionMismatch[module]; RESUME };
      Space.Error =>  -- Since MLoader doesn't catch it
        ERROR LoadError["Space error while loading"L]];
    };

  Unload: PUBLIC ENTRY PROCEDURE [h: MLoader.Handle] = {
    ENABLE UNWIND => NULL;
    searcher, follower: Handle;
    ssh: Handle = HandleForMLoaderHandle[h];
    IF ssh = NIL THEN ERROR UnloadError["Invalid Handle"L, 0];
    IF ssh.running THEN ERROR UnloadError["Program running"L, 0];
    searcher ← loadTable[ssh.lti];
    follower ← NIL;
    UNTIL searcher = ssh DO follower ← searcher; searcher ← searcher.link; ENDLOOP;
    UnloadAndRemove[ssh, follower ! MLoader.Error => ERROR UnloadError[string, 0]];
    };

  HandleForMLoaderHandle: INTERNAL PROCEDURE [lh: MLoader.Handle]
    RETURNS [h: Handle] = {
    FOR lti: LTIndex IN LTIndex DO
      h ← loadTable[lti];
      WHILE h # NIL DO IF h.lh = lh THEN RETURN; h ← h.link; ENDLOOP;
      ENDLOOP;
    };

  UnloadFromFile: PUBLIC ENTRY PROC [file: MFile.Handle]
    RETURNS [instancesUnloaded: CARDINAL ← 0] = {
    -- Unloads every loaded instance.  Does not compare create dates.
    ENABLE UNWIND => NULL;
    ff: File.File = SpecialMFile.GetCapaWithAccess[file];
    lti: LTIndex = LTHash[ff];
    searcher, follower: Handle;
    searcher ← loadTable[lti];
    follower ← NIL;
    WHILE searcher # NIL DO
      IF searcher.file = ff AND searcher.lh # NIL THEN {
        IF searcher.running THEN
          ERROR UnloadError["Program instance running"L, instancesUnloaded];
        UnloadAndRemove[
          searcher, follower !
          MLoader.Error => UnloadError[string, instancesUnloaded]];
        instancesUnloaded ← instancesUnloaded.SUCC;
        searcher ← IF follower = NIL THEN loadTable[lti] ELSE follower.link;
        }
      ELSE {follower ← searcher; searcher ← searcher.link; };
      ENDLOOP;
    };

  UnloadAndRemove: INTERNAL PROCEDURE [h, precedingHandle: Handle] = {
    CRuntime.RemoveConfig[GFForHandle[h]];
    MLoader.Unload[h.lh];
    IF precedingHandle = NIL THEN loadTable[h.lti] ← h.link
    ELSE precedingHandle.link ← h.link;
    zone.FREE[@h];
    };

  UnloadUnstartedProgram: PUBLIC ENTRY PROC [h: Handle] = {
    <<Unloads and removes from start state, but doesn't check running
    and doesn't remove config from C Runtime.>>
    ENABLE UNWIND => NULL;
    searcher, follower: Handle;
    searcher ← loadTable[h.lti];
    follower ← NIL;
    UNTIL searcher = h DO follower ← searcher; searcher ← searcher.link; ENDLOOP;
    IF follower = NIL THEN loadTable[h.lti] ← h.link ELSE follower.link ← h.link;
    MLoader.Unload[h.lh ! MLoader.Error => ERROR UnloadError[string, 0]];
    zone.FREE[@h];
    };

  Start: PUBLIC PROCEDURE [
    h: Handle, argc: CARDINAL, argv: LONG POINTER TO CString.CString,
    stdin, stdout, stderr: Stream.Handle]
    RETURNS [outcome: INTEGER ← normalOutcome] = {
    ENABLE UNWIND => ClearRunning[h];
    gf: PrincOps.GlobalFrameHandle = GFForHandle[h];
    outcome ← CRuntime.Start[
      GFForHandle[h], argc, argv, stdin, stdout, stderr !
      UNWIND => CRuntime.CleanUp[]];
    CRuntime.CleanUp[];
    ClearRunning[h];
    };

  Restart: PUBLIC PROCEDURE [
    h: Handle, argc: CARDINAL, argv: LONG POINTER TO CString.CString,
    stdin, stdout, stderr: Stream.Handle]
    RETURNS [outcome: INTEGER ← normalOutcome] = {
    ENABLE UNWIND => ClearRunning[h];
    outcome ← CRuntime.Restart[
      GFForHandle[h], argc, argv, stdin, stdout, stderr !
      UNWIND => CRuntime.CleanUp[]];
    CRuntime.CleanUp[];
    ClearRunning[h];
    };


  StartOrRestart: PUBLIC PROCEDURE [
    file: MFile.Handle, argc: CARDINAL, argv: LONG POINTER TO CString.CString,
    stdin, stdout, stderr: Stream.Handle] RETURNS [outcome: INTEGER] = {
    ssh: Handle;
    restart: BOOLEAN;
    [ssh, restart] ← GetHandle[file];
    IF restart THEN RETURN[Restart[ssh, argc, argv, stdin, stdout, stderr]]
    ELSE {
      [] ← Load[ssh, file];
      RETURN[Start[ssh, argc, argv, stdin, stdout, stderr]];
      };
    };

  GFForHandle: PROCEDURE [ssh: Handle] RETURNS [gf: PrincOps.GlobalFrameHandle] = {
    p: PROGRAM = LOOPHOLE[ssh.lh, MLoaderImpl.Handle].program;
    gf ← SpecialRuntime.GlobalFrameFromProgram[p];
    };

  EnumerateHandles: PUBLIC ENTRY PROCEDURE [
    file: MFile.Handle, proc: StartState.EnumerateProc] = {
    ff: File.File = SpecialMFile.GetCapaWithAccess[file];
    lti: LTIndex = LTHash[ff];
    ssh: Handle ← loadTable[lti];
    WHILE ssh # NIL DO
      IF ssh.file = ff THEN IF proc[ssh, ssh.lh] THEN EXIT;
      ssh ← ssh.link;
      ENDLOOP;
    };

  GetLoadHandle: PUBLIC ENTRY PROCEDURE [h: Handle] RETURNS [MLoader.Handle] = {
    ENABLE UNWIND => NULL; RETURN[h.lh]; };

  SetLoadHandle: PUBLIC ENTRY PROCEDURE [h: Handle, lh: MLoader.Handle] = {
    ENABLE UNWIND => NULL; h.lh ← lh; };

  ClearRunning: ENTRY PROCEDURE [h: Handle] = {
    ENABLE UNWIND => NULL; h.running ← FALSE; };

  loadTable ← CRuntime.z.NEW[LoadTable ← ALL[NIL]];

  }.