<> <> <> DIRECTORY BasicTime USING [ PulsesToSeconds, GetClockPulses, Now, GMT, Period ], Atom USING [ PropList, PutPropOnList, GetPropFromList, RemPropFromList, DottedPair, DottedPairNode ], Process USING [ Pause, SecondsToTicks ], CedarProcess USING [ Abort, CheckAbort, Fork, ForkableProc, GetStatus, Join, Process, Status ], RPC USING [ CallFailed ], ComputeServerClient USING [ RemoteSuccess, StartService ], ComputeClientExtras USING [ RemoteProcessSite ], ComputeClientInternal USING [ ControllerInterface ], ComputeServerControllerRpcControl USING [ InterfaceRecord ], FS USING [ StreamOpen ], IO USING [ Close, PutRope, STREAM, SetIndex, UnsafeGetBlock, UnsafePutBlock ], Rope USING [ ROPE, Cat, Substr, Index, Length, Equal ], Convert USING [ RopeFromCard, RopeFromInt, RopeFromReal, RealFromRope, RopeFromTime, TimeFromRope ], Real USING [ Fix, RoundC, Float, RoundI ], Imager USING [ Rectangle ], Pixels USING [ PixelBuffer, Extent, GetScanSeg, PutScanSeg, SampleSetSequence, SampleSet, GetSampleSet, PixelOp ], SampleMapOps USING [ GetPointer ], ThreeDBasics USING [ Box, ClipState, Context, NatSequence, Pair, Quad, RealSequence, Rectangle, RGB, ShapeInstance, ShapeSequence, SixSides, Triple, Vertex ], ThreeDScenes USING [ AddAlphaBuffer, Create, DisplayFromVM, Error, FillInBackGround, FillViewPort, GetShading, ReadScene, SetEyeSpace, SetWindow, SetViewPort, WriteScene, XfmToDisplay, XfmToEyeSpace ], ThreeDMisc USING [ CombineBoxes, CopyContextData, CopyDisplayData, StartLog, FlushLog, PrependWorkingDirectory, MakeFrame ]; DistributedDisplayImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, CedarProcess, ComputeServerClient, ComputeClientExtras, ComputeClientInternal, Convert, FS, IO, Pixels, Process, Real, Rope, RPC, SampleMapOps, ThreeDMisc, ThreeDScenes EXPORTS ThreeDMisc ~ BEGIN <> Context: TYPE ~ ThreeDBasics.Context; ShapeSequence: TYPE ~ ThreeDBasics.ShapeSequence; ShapeInstance: TYPE ~ ThreeDBasics.ShapeInstance; BoolSequence: TYPE ~ RECORD [ length: NAT _ 0, data: SEQUENCE maxLength: NAT OF BOOLEAN]; Pair: TYPE ~ ThreeDBasics.Pair; -- RECORD [ x, y: REAL]; Triple: TYPE ~ ThreeDBasics.Triple; RealSequence: TYPE ~ ThreeDBasics.RealSequence; RGB: TYPE ~ ThreeDBasics.RGB; Rectangle: TYPE ~ ThreeDBasics.Rectangle; NatSequence: TYPE ~ ThreeDBasics.NatSequence; IntSequence: TYPE ~ RECORD [ length: NAT _ 0, data: SEQUENCE maxLength: NAT OF INT ]; StreamPair: TYPE ~ RECORD [ in, out: IO.STREAM ]; CountedCondition: TYPE ~ RECORD [ count: NAT, condition: CONDITION ]; <> stopMe: BOOLEAN _ FALSE; -- external stop signal masterTimeOut: REAL _ 1800; -- 1/2 hr, max time for a multiprocess picture remoteProcessTimeOut: INT _ 300; -- 5 min., max time for remote process maxProcClonesAllowed: NAT ~ 3; -- times process may be replicated to get better service procClonesAllowed: NAT _ 3; timesKept: NAT ~ 5; -- number of timings recorded and summed costPerSecond: INT _ 200; -- cost measure for 1 second compute time imageSpaceDivision: BOOLEAN _ FALSE; -- for forcing image subdivision method computingRemote: BOOLEAN _ TRUE; -- flag for forcing local execution computingSerially: BOOLEAN _ FALSE; -- flag for forcing serialization noCostAdjustments: BOOLEAN _ FALSE; -- flag for skipping optimization with cost estimation serverStats: Rope.ROPE _ NIL; -- Parameters for Server statistics serverStatsTime: REAL _ 0.0; serverStatsWanted: BOOLEAN _ FALSE; serverStatsOut: IO.STREAM _ NIL; minimumUsefulServerPortion: REAL _ .5; showBoxes, showGaps: BOOLEAN _ FALSE; -- pedagogical aids showBoxCoverage: REAL _ 0.2; <> Ceiling: PROC[ in: REAL ] RETURNS[ out: INTEGER ] ~ { out _ Real.RoundI[in]; IF Real.Float[out] < in THEN out _ out + 1; }; Floor: PROC[ in: REAL ] RETURNS[ out: INTEGER ] ~ { out _ Real.RoundI[in]; IF Real.Float[out] > in THEN out _ out - 1; }; RopeFromSeconds: PROC[seconds: REAL] RETURNS[Rope.ROPE] ~ { RETURN[ Rope.Cat[ Convert.RopeFromReal[ Real.Fix[100.0 * seconds] / 100.0 ] ] ]; }; ElapsedTime: PROC[startTime: REAL] RETURNS[Rope.ROPE] ~ { timeX100: REAL _ 100.0 *(BasicTime.PulsesToSeconds[BasicTime.GetClockPulses[]] - startTime); RETURN[ Rope.Cat[ Convert.RopeFromReal[ Real.Fix[timeX100] / 100.0 ], " s. " ] ]; }; CurrentTime: PROC[] RETURNS[REAL] ~ { RETURN[ BasicTime.PulsesToSeconds[BasicTime.GetClockPulses[]] ]; }; AddTimes: PROC[msg: Rope.ROPE, times: REF RealSequence] RETURNS[REF RealSequence] ~ { newTimes: REF RealSequence _ NEW[RealSequence[timesKept]]; pos, pos2: NAT _ 0; pos _ MIN[ Rope.Index[msg, pos, "set-up: "] + 8, Rope.Length[msg] ]; pos2 _ Rope.Index[msg, pos, "s."]; IF pos2 <= pos THEN RETURN [times] ELSE newTimes[0] _ Convert.RealFromRope[ Rope.Substr[msg, pos, pos2 - pos] ]; times[0] _ times[0] + newTimes[0]; pos _ MIN[ Rope.Index[msg, pos, "rendering: "] + 11, Rope.Length[msg] ]; pos2 _ Rope.Index[msg, pos, "s."]; newTimes[1] _ Convert.RealFromRope[ Rope.Substr[msg, pos, pos2 - pos] ]; times[1] _ times[1] + newTimes[1]; pos _ MIN[ Rope.Index[msg, pos, "output: "] + 8, Rope.Length[msg] ]; pos2 _ Rope.Index[msg, pos, "s."]; newTimes[2] _ Convert.RealFromRope[ Rope.Substr[msg, pos, pos2 - pos] ]; times[2] _ times[2] + newTimes[2]; pos _ MIN[ Rope.Index[msg, pos, "total: "] + 7, Rope.Length[msg] ]; pos2 _ Rope.Index[msg, pos, "s."]; IF pos2 <= pos THEN newTimes[3] _ newTimes[0] + newTimes[1] + newTimes[2] -- no total ELSE newTimes[3] _ Convert.RealFromRope[ Rope.Substr[msg, pos, pos2 - pos] ]; times[3] _ times[3] + newTimes[3]; pos _ MIN[ Rope.Index[msg, pos, "matted in: "] + 11, Rope.Length[msg] ]; pos2 _ Rope.Index[msg, pos, "s."]; newTimes[4] _ 0.0; IF pos2 > pos THEN newTimes[4] _ Convert.RealFromRope[Rope.Substr[msg, pos, pos2 - pos]]; times[4] _ times[4] + newTimes[4]; RETURN [times]; }; <> RemoteStop: PUBLIC ENTRY PROC[] ~ { stopMe _ TRUE; }; GetProcess: ENTRY PROC[processCount: REF CountedCondition] ~ { ENABLE UNWIND => NULL; WHILE processCount.count <= 0 DO WAIT processCount.condition; ENDLOOP; processCount.count _ processCount.count - 1; }; ReleaseProcess: ENTRY PROC[processCount: REF CountedCondition] ~ { processCount.count _ processCount.count + 1; NOTIFY processCount.condition; }; SortShapes: PROC[context: REF Context, numForksHint: NAT] RETURNS [REF NatSequence] ~ { shapeOrder: REF NatSequence; <> { shape: REF ShapeSequence _ context.shapes; shapeOrder _ NEW[ NatSequence[ context.shapes.length - context.lights.length] ]; FOR i: NAT IN [0..context.shapes.length) DO IF shape[i] # NIL AND Atom.GetPropFromList[shape[i].props, $Hidden] = NIL THEN IF shape[i].clipState # out AND shape[i].surface # NIL THEN { <> FOR j: NAT DECREASING IN [0..shapeOrder.length) DO IF shape[i].centroid.ez < shape[shapeOrder[j]].centroid.ez THEN { shapeOrder[j+1] _ shapeOrder[j]; shapeOrder[j] _ i; } ELSE { shapeOrder[j+1] _ i; EXIT; }; ENDLOOP; IF shapeOrder.length = 0 THEN shapeOrder[0] _ i; shapeOrder.length _ shapeOrder.length + 1; }; ENDLOOP; }; <> { occlusionList: LIST OF REF ShapeInstance; Overlap: PROC[box1, box2: ThreeDBasics.Box] RETURNS[ BOOLEAN] ~ { IF box1.left > box2.right OR box2.left > box1.right OR box1.bottom > box2.top OR box2.bottom > box1.top THEN RETURN[FALSE] ELSE RETURN[TRUE]; }; FOR i: NAT IN [0..shapeOrder.length) DO occlusionList _ NIL; FOR j: NAT IN [0..i) DO IF Overlap[context.shapes[shapeOrder[i]].screenExtent, context.shapes[shapeOrder[j]].screenExtent] THEN occlusionList _ CONS[context.shapes[shapeOrder[j]], occlusionList]; ENDLOOP; context.shapes[shapeOrder[i]].props _ Atom.PutPropOnList[ -- list of occluding shapes context.shapes[shapeOrder[i]].props, $Occlusions, occlusionList ]; ENDLOOP; }; RETURN[ shapeOrder ]; }; CostHeuristic: PROC[shape: REF ShapeInstance] RETURNS[seconds: INT] ~ { << Start with area of bounding box>> cost: INT _ INT[(shape.screenExtent.right - shape.screenExtent.left)] * (shape.screenExtent.top - shape.screenExtent.bottom); <> IF shape.type # $ConvexPolygon THEN cost _ cost * 3; <> IF ThreeDScenes.GetShading[shape, $Transmittance] # NIL AND shape.insideVisible THEN cost _ cost * 2; <> IF ThreeDScenes.GetShading[ shape, $TextureMap ] # NIL THEN cost _ cost * 2; <> IF ThreeDScenes.GetShading[ shape, $ShadingProcs ] # NIL THEN cost _ cost * 4; <> seconds _ cost / costPerSecond; }; ServerUsage: ENTRY PROC[process: CedarProcess.Process] RETURNS[server: Rope.ROPE, pctAvailable: REAL ] ~ { <> <> ENABLE UNWIND => NULL; IF computingRemote = FALSE THEN RETURN[ NIL, 1.0 ]; -- not using compute server server _ ComputeClientExtras.RemoteProcessSite[process.process]; IF CurrentTime[] - serverStatsTime > 5.0 THEN { newserverStats: Rope.ROPE; success: ATOM; controllerInterface: ComputeServerControllerRpcControl.InterfaceRecord _ ComputeClientInternal.ControllerInterface; IF controllerInterface # NIL THEN [success, newserverStats] _ controllerInterface.clientStubGenericToController[ interface: controllerInterface, requestCode: $ServerLoads, requestString: NIL ! RPC.CallFailed => CONTINUE ]; IF success = $success THEN { serverStats _ newserverStats; serverStatsTime _ CurrentTime[]; IF serverStatsOut # NIL THEN serverStatsOut.PutRope[Rope.Cat["\n\n", newserverStats]]; }; }; <> <> <> <<- aveBackgroundLoad: the average number of ready background processes.>> <<- nonBackgroundCPULoad: fraction of CPU used that was not idle or background>> <<- CPULoad: CPU load returned by Watch expressed as a fraction.>> <<- FOM: Figure of Merit - same as SummonerInfo - 0.0 is idle>> <<- numberOfCurrentRequests: count of Summoner requests on the server>> pctAvailable _ 101.0; -- indicates no answer IF server # NIL THEN { aveBackgroundLoad, nonBackgroundCPULoad: REAL _ 0.0; pos: INT _ Rope.Index[serverStats, 0, server] + Rope.Length[server] + 1; pos2: INT _ Rope.Index[serverStats, pos, ","]; IF pos2 > pos THEN { -- if not at end of string aveBackgroundLoad _ Convert.RealFromRope[ Rope.Substr[serverStats, pos, pos2 - pos] ]; pos _ pos2 + 1; pos2 _ Rope.Index[serverStats, pos, ","]; nonBackgroundCPULoad _ Convert.RealFromRope[ Rope.Substr[serverStats, pos, pos2 - pos] ]; IF nonBackgroundCPULoad > 1.0 OR nonBackgroundCPULoad < 0.0 OR aveBackgroundLoad < -1.0 OR aveBackgroundLoad > 100.0 THEN SIGNAL ThreeDScenes.Error[[ $MisMatch, Rope.Cat[server, " - ", serverStats] ]]; nonBackgroundCPULoad _ MAX[0.0, MIN[1.0, nonBackgroundCPULoad]]; aveBackgroundLoad _ MAX[0.0, MIN[5.0, aveBackgroundLoad]]; IF serverStatsOut # NIL AND nonBackgroundCPULoad > 0.0 THEN { serverStatsOut.PutRope[ Rope.Cat[ "\n", server] ]; serverStatsOut.PutRope[ Rope.Cat[ " - load: ", Convert.RopeFromReal[nonBackgroundCPULoad], " pctAvailable: ", Convert.RopeFromReal[ (1.0 - nonBackgroundCPULoad) / MAX[1.0, aveBackgroundLoad] ] ] ]; }; }; pctAvailable _ (1.0 - nonBackgroundCPULoad) / MAX[1.0, aveBackgroundLoad]; }; }; KillProcs: PROC[ context: REF Context, procList: LIST OF CedarProcess.Process ] ~ { FOR procs: LIST OF CedarProcess.Process _ procList, procs.rest UNTIL procs = NIL DO CedarProcess.Abort[procs.first]; ENDLOOP; }; JoinProcs: PROC[ context: REF Context, procList: LIST OF CedarProcess.Process, log: IO.STREAM, startTime: REAL ] ~ { <> procTimes: REF RealSequence _ NEW[ RealSequence[5] ]; FOR i: NAT IN [0..5) DO procTimes[i] _ 0.0; ENDLOOP; log.PutRope[Rope.Cat[" Processes all forked at: ", ElapsedTime[startTime], "\n" ]]; FOR procs: LIST OF CedarProcess.Process _ procList, procs.rest UNTIL procs = NIL DO status: CedarProcess.Status _ busy; result: REF; returnMsg: Rope.ROPE; WHILE status = busy DO -- wait loop for process completion IF context.stopMe OR CurrentTime[] - startTime > masterTimeOut -- bailouts THEN { KillProcs[context, procs]; RETURN[]; }; status _ CedarProcess.GetStatus[procs.first]; IF status # busy THEN LOOP; Process.Pause[ Process.SecondsToTicks[1] ]; ENDLOOP; [status, result] _ CedarProcess.Join[procs.first]; -- wait for process completion procs.first _ NIL; -- drop REF returnMsg _ NARROW[result]; IF status # done THEN returnMsg _ Rope.Cat[" process failed - ", returnMsg] ELSE [procTimes] _ AddTimes[returnMsg, procTimes]; -- get timings log.PutRope[Rope.Cat[returnMsg, "\n" ] ]; ENDLOOP; procList _ NIL; -- drop REF log.PutRope[Rope.Cat["Totals - set-up: ", Convert.RopeFromReal[procTimes[0]] ] ]; log.PutRope[Rope.Cat[" rendering: ", Convert.RopeFromReal[procTimes[1]] ] ]; log.PutRope[Rope.Cat[" output: ", Convert.RopeFromReal[procTimes[2]] ] ]; log.PutRope[Rope.Cat[" Grand Total: ", Convert.RopeFromReal[procTimes[3]] ] ]; log.PutRope[Rope.Cat[" Matting: ", Convert.RopeFromReal[procTimes[4]], "\n"] ]; }; GetTempContext: PROC[context: REF Context, name: Rope.ROPE, killBackground: BOOLEAN, shapes: REF ShapeSequence, cost, processCount, startTime: REF] RETURNS[tmpCtx: REF Context] ~ { <> tmpCtx _ NEW[ Context ]; ThreeDMisc.CopyContextData[dstCtx: tmpCtx, srcCtx: context]; ThreeDMisc.CopyDisplayData[dstCtx: tmpCtx, srcCtx: context]; IF killBackground THEN tmpCtx.props _ Atom.PutPropOnList[tmpCtx.props, $BackGround, NIL] -- no bckgrd ELSE tmpCtx.props _ Atom.PutPropOnList[ -- copy background from context tmpCtx.props, $BackGround, Atom.GetPropFromList[context.props, $BackGround] ]; tmpCtx.props _ Atom.PutPropOnList[tmpCtx.props, $ComputeCost, cost ]; tmpCtx.props _ Atom.PutPropOnList[tmpCtx.props, $ProcessCount, processCount]; tmpCtx.props _ Atom.PutPropOnList[tmpCtx.props, $StartTime, startTime]; tmpCtx.props _ Atom.PutPropOnList[tmpCtx.props, $SubImageName, name]; <> tmpCtx.display.props _ Atom.PutPropOnList[tmpCtx.display.props, $ImagerContext, NIL]; tmpCtx.shapes _ NEW[ ShapeSequence[ context.lights.length + shapes.length] ]; FOR i: NAT IN [0..context.lights.length) DO -- retain all light sources tmpCtx.shapes[i] _ NEW[ ShapeInstance _ context.lights[i]^ ]; ENDLOOP; FOR i: NAT IN [0 .. shapes.length) DO j: NAT _ i + context.lights.length; tmpCtx.shapes[j] _ NEW[ ShapeInstance _ shapes[i]^ ]; -- add each shape <> tmpCtx.shapes[j].props _ tmpCtx.shapes[j].shadingProps _ NIL; FOR list: Atom.PropList _ shapes[i].props, list.rest UNTIL list = NIL DO element: Atom.DottedPair _ NEW[Atom.DottedPairNode _ list.first^]; tmpCtx.shapes[j].props _ CONS[element, tmpCtx.shapes[j].props]; ENDLOOP; FOR list: Atom.PropList _ shapes[i].shadingProps, list.rest UNTIL list = NIL DO element: Atom.DottedPair _ NEW[Atom.DottedPairNode _ list.first^]; tmpCtx.shapes[j].shadingProps _ CONS[element, tmpCtx.shapes[j].shadingProps]; ENDLOOP; ENDLOOP; }; AddShape: PROC[ shapes: REF ShapeSequence, newShape: REF ShapeInstance ] RETURNS [REF ShapeSequence] ~ { size: NAT _ IF shapes # NIL THEN shapes.length + 1 ELSE 1; tmpShapes: REF ShapeSequence _ NEW[ ShapeSequence[size] ]; IF shapes # NIL THEN FOR i: NAT IN [0..shapes.length) DO tmpShapes[i] _ shapes[i]; ENDLOOP; -- copy list tmpShapes[tmpShapes.length-1] _ newShape; RETURN[tmpShapes]; }; DeleteShape: PROC[ shapes: REF ShapeSequence, oldShape: REF ShapeInstance ] RETURNS [REF ShapeSequence] ~ { tmpShapes: REF ShapeSequence _ NEW[ ShapeSequence[shapes.length - 1] ]; FOR i: NAT IN [0..shapes.length) DO IF Rope.Equal[shapes[i].name, oldShape.name] THEN { -- find matching shape FOR j: NAT IN [0..i) DO tmpShapes[j] _ shapes[j]; ENDLOOP; -- copy up to oldshape FOR j: NAT IN [i..shapes.length-1) DO tmpShapes[j] _ shapes[j+1]; ENDLOOP; -- remove EXIT; }; ENDLOOP; RETURN[tmpShapes]; }; <> RemoteMakeFrame: PUBLIC PROC[context: REF Context, numForksHint: NAT _ 0] ~ { <> log: IO.STREAM _ NARROW[ Atom.GetPropFromList[context.props, $Log] ]; shape: REF ShapeSequence _ context.shapes; shapeOrder: REF NatSequence; startTime: REAL _ CurrentTime[]; context.stopMe _ FALSE; -- release remote stop flag IF serverStatsWanted THEN serverStatsOut _ FS.StreamOpen[ fileName: ThreeDMisc.PrependWorkingDirectory[context, Rope.Cat["Temp/", "ServerStats"]], accessOptions: $create ]; IF serverStatsTime = 0.0 THEN serverStatsTime _ startTime; -- initialize time IF context.renderMode # $Dorado24 AND context.renderMode # $FullColor THEN SIGNAL ThreeDScenes.Error[[$MisMatch, "Full color display expected"]]; IF log = NIL THEN log _ ThreeDMisc.StartLog[context]; -- open log file if not yet done <> FOR i: NAT IN [0.. shape.length) DO IF Atom.GetPropFromList[shape[i].props, $Hidden] = NIL OR shape[i].type = $Light THEN IF shape[i].vtcesInValid THEN { shape[i].clipState _ ThreeDScenes.XfmToEyeSpace[context, shape[i]]; IF shape[i].clipState # out THEN ThreeDScenes.XfmToDisplay[context, shape[i] ]; }; ENDLOOP; <> IF NOT imageSpaceDivision THEN shapeOrder _ SortShapes[context, numForksHint]; IF numForksHint > 0 THEN { log.PutRope[Rope.Cat["\n", Convert.RopeFromCard[numForksHint], " processors"] ]; IF imageSpaceDivision THEN log.PutRope[" - slices "]; IF noCostAdjustments THEN log.PutRope[" - No Costing "]; }; log.PutRope[Rope.Cat["\nObject transform and sort: ", ElapsedTime[startTime] ]]; <> ThreeDScenes.FillViewPort[context, [0.0, 0.0, 0.0] ]; -- clear all pixel bits <> IF imageSpaceDivision THEN ForkSubImageProcs[context, startTime, log, numForksHint] ELSE ForkSubSceneProcs[context, shapeOrder, startTime, log, numForksHint]; <> IF NOT imageSpaceDivision THEN { ThreeDMisc.CombineBoxes[context]; -- get combined bounding box on scene ThreeDScenes.FillInBackGround[context]; -- load background }; log.PutRope[Rope.Cat["All done at ", ElapsedTime[startTime], "\n\n" ] ]; ThreeDMisc.FlushLog[context]; IF serverStatsOut # NIL THEN IO.Close[serverStatsOut]; }; ForkSubImageProcs: PROC[context: REF Context, startTime: REAL, log: IO.STREAM, numForksHint: NAT] ~ { Edge: TYPE ~ RECORD [shape, position: NAT, entering: BOOLEAN]; EdgeSequence: TYPE ~ RECORD [length: NAT _ 0, s: SEQUENCE maxLength: NAT OF Edge]; shapeEdgeOrder: REF EdgeSequence _ NEW[ EdgeSequence[ 2 * (context.shapes.length - context.lights.length) ] ]; processCount: REF CountedCondition _ NEW[CountedCondition]; procList: LIST OF CedarProcess.Process _ NIL; shape: REF ShapeSequence _ context.shapes; cost: REF RealSequence _ NEW[ RealSequence[shape.length] ]; totalCost, averageCost: REAL _ 0; IF NOT noCostAdjustments THEN { <> FOR i: NAT IN [0..shape.length) DO IF shape[i] # NIL AND Atom.GetPropFromList[shape[i].props, $Hidden] = NIL THEN IF shape[i].clipState # out AND shape[i].surface # NIL THEN { <> cost[i] _ CostHeuristic[shape[i]]; totalCost _ totalCost + cost[i] }; ENDLOOP; IF numForksHint # 0 THEN { averageCost _ totalCost / numForksHint; -- base division on no. of processors processCount.count _ numForksHint -- limit processors (2 processes per processor) } ELSE { <> SIGNAL ThreeDScenes.Error[[$MisMatch, "No processors?"]]; }; <> FOR i: NAT IN [0..shape.length) DO IF shape[i] # NIL AND Atom.GetPropFromList[shape[i].props, $Hidden] = NIL THEN IF shape[i].clipState # out AND shape[i].surface # NIL THEN { cost[i] _ cost[i] / ( shape[i].screenExtent.right + 1 - shape[i].screenExtent.left ); }; ENDLOOP; <<>> <> FOR s: NAT IN [0..shape.length) DO IF shape[s] # NIL AND Atom.GetPropFromList[shape[s].props, $Hidden] = NIL THEN IF shape[s].clipState # out AND shape[s].surface # NIL THEN { <> FOR i: NAT IN [0..2) DO position: NAT _ IF i = 0 THEN shape[s].screenExtent.left ELSE shape[s].screenExtent.right; left: BOOLEAN _ i = 0; EnterEdge: PROC[place, shape: NAT] ~ { shapeEdgeOrder[place].shape _ s; shapeEdgeOrder[place].position _ position; shapeEdgeOrder[place].entering _ left; }; FOR p: NAT DECREASING IN [0..shapeEdgeOrder.length) DO IF position < shapeEdgeOrder[p].position THEN { shapeEdgeOrder[p+1] _ shapeEdgeOrder[p]; EnterEdge[p, s]; } ELSE { EnterEdge[p+1, s]; EXIT; }; ENDLOOP; IF shapeEdgeOrder.length = 0 THEN EnterEdge[0, s]; shapeEdgeOrder.length _ shapeEdgeOrder.length + 1; ENDLOOP; }; ENDLOOP; }; <<>> <> { currentPlace: NAT _ 0; currentCost: REAL _ 0.0; activeShapes: REF ShapeSequence _ NIL; lastPos: REAL _ 0.0; thisPos: REAL _ shapeEdgeOrder[currentPlace].position; FOR i: NAT IN [0..numForksHint) DO -- do side to side sliceCost: REAL _ 0.0; activeShapesInSlice: REF ShapeSequence _ NIL; <> viewPortSize: REAL _ MAX[context.viewPort.w, context.viewPort.h]; vuPtWindow: Imager.Rectangle _ [ x: MAX[ 0.0, context.viewPort.h - context.viewPort.w ] / viewPortSize, y: MAX[ 0.0, context.viewPort.w - context.viewPort.h ] / viewPortSize, w: 1.0, h: 1.0 ]; left: REAL _ lastPos; right: REAL _ context.viewPort.w; top: REAL _ context.viewPort.h; bottom: REAL _ 0; forkedProcess: CedarProcess.Process; tmpCtx: REF Context; IF context.stopMe THEN { KillProcs[ context, procList ]; RETURN[]; }; -- escape hatch IF noCostAdjustments THEN { left _ i * right / numForksHint; right _ (i+1) * right / numForksHint; activeShapesInSlice _ NEW[ ShapeSequence[ context.shapes.length - context.lights.length ] ]; FOR i: NAT IN [0..activeShapesInSlice.length) DO j: NAT _ i + context.lights.length; activeShapesInSlice[i] _ context.shapes[j]; ENDLOOP; IF numForksHint # 0 THEN processCount.count _ numForksHint ELSE processCount.count _ 200/2; sliceCost _ 1.0; -- to avoid divide errors, etc. } ELSE { <> IF activeShapes # NIL THEN { -- grab all currently active shapes activeShapesInSlice _ NEW[ ShapeSequence[activeShapes.length] ]; FOR i: NAT IN [0..activeShapes.length) DO activeShapesInSlice[i] _ activeShapes[i]; ENDLOOP; }; WHILE TRUE DO costLeft: REAL _ averageCost - sliceCost; -- ideal slice cost - accumulated cost IF costLeft > currentCost * (thisPos - lastPos) THEN { -- finishing shape, get the next one sliceCost _ sliceCost + currentCost * (thisPos - lastPos); -- add this shape IF shapeEdgeOrder[currentPlace].entering THEN { -- moving into shape currentCost _ currentCost + cost[shapeEdgeOrder[currentPlace].shape]; activeShapes _ AddShape[activeShapes, shape[ shapeEdgeOrder[currentPlace].shape] ]; activeShapesInSlice _ AddShape[activeShapesInSlice, shape[ shapeEdgeOrder[currentPlace].shape] ]; } ELSE { -- moving out of shape currentCost _ currentCost - cost[shapeEdgeOrder[currentPlace].shape]; activeShapes _ DeleteShape[activeShapes, shape[ shapeEdgeOrder[currentPlace].shape] ]; }; lastPos _ thisPos; currentPlace _ currentPlace + 1; IF currentPlace < shapeEdgeOrder.length THEN thisPos _ shapeEdgeOrder[currentPlace].position ELSE EXIT; -- off end of sorted shape array, must be last slice } ELSE { -- slice ends in middle of shape oldPos: REAL _ lastPos; lastPos _ right _ lastPos + costLeft / currentCost; -- no new shapes sliceCost _ sliceCost + currentCost * (lastPos - oldPos); EXIT; }; ENDLOOP; }; <> tmpCtx _ GetTempContext[ context: context, name: Rope.Cat["Slice", Convert.RopeFromCard[i] ], -- get a unique name killBackground: FALSE, shapes: activeShapesInSlice, cost: NEW[ INT _ Real.RoundC[sliceCost] ], processCount: processCount, startTime: NEW[ REAL _ startTime ] ]; IF i = numForksHint-1 THEN tmpCtx.props _ Atom.PutPropOnList[ tmpCtx.props, $RightMostSlice, NEW[BOOLEAN _ TRUE] ]; <> left _ MAX[0, left]; -- limit window width right _ MIN[context.viewPort.w-1, right]; -- kludge to keep in bounds ThreeDScenes.SetViewPort[ tmpCtx, [ left + context.viewPort.x, bottom + context.viewPort.y, right - left, top - bottom ] ]; ThreeDScenes.SetWindow[ tmpCtx, [ x: (2.0 * left / viewPortSize) - 1.0 + vuPtWindow.x, -- window range is -1.0  +1.0 y: (2.0 * bottom / viewPortSize) - 1.0 + vuPtWindow.y, w: 2.0 * (right - left) / viewPortSize, h: 2.0 * (top - bottom) / viewPortSize ] ]; <> forkedProcess _ CedarProcess.Fork[RenderRemote, tmpCtx ]; -- fork process procList _ CONS[ forkedProcess, procList]; -- save on list for later IF computingSerially THEN [] _ CedarProcess.Join[forkedProcess]; -- wait for process if serializing ENDLOOP; }; <> JoinProcs[context: context, procList: procList, log: log, startTime: startTime]; }; ForkSubSceneProcs: PROC[context: REF Context, shapeOrder: REF NatSequence, startTime: REAL, log: IO.STREAM, numForksHint: NAT] ~ { processCount: REF CountedCondition _ NEW[CountedCondition]; allProcsList: LIST OF CedarProcess.Process _ NIL; cost: REF IntSequence _ NEW[ IntSequence[ shapeOrder.maxLength] ]; totalCost, averageCost: INT _ 0; <> IF noCostAdjustments THEN { averageCost _ 1; FOR i: NAT IN [0..shapeOrder.maxLength) DO cost[i] _ 1; ENDLOOP; IF numForksHint # 0 THEN processCount.count _ numForksHint ELSE processCount.count _ 200/2; } ELSE { shape: REF ShapeSequence _ context.shapes; FOR j: NAT IN [0..shapeOrder.length) DO <> i: NAT _ shapeOrder[j]; cost[j] _ CostHeuristic[shape[i]]; totalCost _ totalCost + cost[j] ENDLOOP; IF numForksHint # 0 THEN { averageCost _ totalCost / numForksHint; -- base division on no. of processors processCount.count _ numForksHint -- limit processors (2 processes per processor) } ELSE { averageCost _ totalCost / shapeOrder.length; -- base division on no. of objects processCount.count _ 200/2; -- no suggestion, keep safely under cedar limit of 300 }; }; <> FOR i: NAT IN [0..shapeOrder.length) DO -- do front to back screenExtent: ThreeDBasics.Box _ context.shapes[shapeOrder[i]].screenExtent; procList: LIST OF CedarProcess.Process _ NIL; baseName: Rope.ROPE _ context.shapes[shapeOrder[i]].name; imageCount: NAT _ Real.RoundC[ Real.Float[ cost[i] ] / averageCost ]; IF imageCount < 1 THEN imageCount _ 1; IF context.stopMe THEN { KillProcs[ context, allProcsList ]; RETURN[]; }; -- escape hatch FOR j: NAT IN [0..imageCount) DO -- do for each slice of object <> viewPortSize: REAL _ MAX[context.viewPort.w, context.viewPort.h]; vuPtWindow: Imager.Rectangle _ [ -- size of viewPort within unit square x: -1.0 + MAX[ 0.0, context.viewPort.h - context.viewPort.w ] / viewPortSize, y: -1.0 + MAX[ 0.0, context.viewPort.w - context.viewPort.h ] / viewPortSize, w: 2.0, h: 2.0 ]; left: REAL _ screenExtent.left + (Real.Float[j] / imageCount) * (screenExtent.right - screenExtent.left); right: REAL _ screenExtent.left + (Real.Float[j+1] / imageCount) * (screenExtent.right - screenExtent.left); top: REAL _ screenExtent.top; bottom: REAL _ screenExtent.bottom; <> forkedProcess: CedarProcess.Process; tmpCtx: REF Context; shapes: REF ShapeSequence _ NEW[ ShapeSequence[1] ]; shapes[0] _ context.shapes[shapeOrder[i]]; tmpCtx _ GetTempContext[ context: context, name: Rope.Cat[ baseName, Convert.RopeFromCard[j] ], -- get a unique name killBackground: TRUE, shapes: shapes, cost: NEW[ INT _ cost[i] / imageCount ], processCount: processCount, startTime: NEW[ REAL _ startTime ] ]; IF j = imageCount-1 THEN tmpCtx.props _ Atom.PutPropOnList[ tmpCtx.props, $RightMostSlice, NEW[BOOLEAN _ TRUE] ]; <> left _ MAX[0.0, left - 1.0]; -- spread window by a pixel on each side right _ MIN[context.viewPort.w-1.0, right + 1.0]; ThreeDScenes.SetViewPort[ tmpCtx, [ left + context.viewPort.x, bottom + context.viewPort.y, right - left, top - bottom ] ]; ThreeDScenes.SetWindow[ tmpCtx, [ x: (2.0 * left / viewPortSize) + vuPtWindow.x, -- window range is -1.0  +1.0 y: (2.0 * bottom / viewPortSize) + vuPtWindow.y, w: 2.0 * (right - left) / viewPortSize, h: 2.0 * (top - bottom) / viewPortSize ] ]; <> forkedProcess _ CedarProcess.Fork[RenderRemote, tmpCtx ]; -- fork process procList _ CONS[ forkedProcess, procList]; -- save on list for later allProcsList _ CONS[ forkedProcess, allProcsList]; IF computingSerially THEN [] _ CedarProcess.Join[forkedProcess]; -- wait for process if serializing ENDLOOP; context.shapes[shapeOrder[i]].props _ Atom.PutPropOnList[ -- store proc refs with shape context.shapes[shapeOrder[i]].props, $RemoteProc, procList -- leave list of proc refs here ]; IF showBoxes AND computingSerially THEN { -- bounding boxes for pedagogical purposes area: Pixels.Extent _ [ x: screenExtent.left, y: screenExtent.bottom, w: screenExtent.right - screenExtent.left, h: screenExtent.top - screenExtent.bottom ]; pixelValues: Pixels.SampleSet _ Pixels.GetSampleSet[4]; color: REF RGB _ NARROW[ThreeDScenes.GetShading[ context.shapes[shapeOrder[i]], $Color ] ]; pixelValues[0] _ Real.Fix[color.R * 255.0]; pixelValues[1] _ Real.Fix[color.G * 255.0]; pixelValues[2] _ Real.Fix[color.B * 255.0]; pixelValues[3] _ Real.Fix[showBoxCoverage * 255.0]; -- coverage Pixels.PixelOp[ context.display, area, pixelValues, $WriteUnder ]; }; ENDLOOP; <> JoinProcs[ context, allProcsList, log, startTime ]; FOR i: NAT IN [0..shapeOrder.length) DO -- clear out process REFs context.shapes[shapeOrder[i]].props _ Atom.RemPropFromList[ context.shapes[shapeOrder[i]].props, $RemoteProc ]; ENDLOOP; }; RenderRemote: CedarProcess.ForkableProc ~ { <> context: REF Context _ NARROW[data]; startTime: REAL _ NARROW[ Atom.GetPropFromList[context.props, $StartTime], REF REAL ]^; lastTime: REAL _ CurrentTime[]; fileName: Rope.ROPE _ ThreeDMisc.PrependWorkingDirectory[ context, Rope.Cat[ "Temp/", NARROW[ Atom.GetPropFromList[context.props, $SubImageName] ] ] ]; input: IO.STREAM _ NIL; ref: REF _ Atom.GetPropFromList[context.props, $RightMostSlice]; rightMostSlice: BOOLEAN _ IF ref # NIL THEN NARROW[ ref, REF BOOLEAN ]^ ELSE FALSE; forkedProc: ARRAY[0..maxProcClonesAllowed) OF CedarProcess.Process; procIO: ARRAY[0..maxProcClonesAllowed) OF REF StreamPair; serverName: ARRAY[0..maxProcClonesAllowed) OF Rope.ROPE; serverPctAvail: ARRAY[0..maxProcClonesAllowed) OF REAL; returnMsg: Rope.ROPE _ Rope.Cat[ NARROW[ Atom.GetPropFromList[context.props, $SubImageName] ], " - " ]; estComputeTime: INT _ NARROW[ Atom.GetPropFromList[context.props, $ComputeCost], REF INT ]^; times: REF RealSequence _ NEW[ RealSequence[5] ]; FOR i: NAT IN [0..5) DO times[i] _ 0.0; ENDLOOP; returnMsg _ Rope.Cat[returnMsg, "est. ", Convert.RopeFromInt[estComputeTime], " s., "]; results _ returnMsg; <> FOR i:NAT IN [0..procClonesAllowed) DO IF i > 0 THEN { returnMsg _ Rope.Cat[returnMsg, "\n "]; FOR k: NAT IN [0..i) DO returnMsg _ Rope.Cat[ returnMsg, serverName[k], " " ]; IF CurrentTime[] - lastTime >= (i) * 2 * estComputeTime THEN returnMsg _ Rope.Cat[returnMsg, "over time limit, "] ELSE returnMsg _ Rope.Cat[returnMsg, Convert.RopeFromReal[ serverPctAvail[k] ], " too busy, "]; ENDLOOP; }; procIO[i] _ NEW[ StreamPair ]; procIO[i].in _ FS.StreamOpen[ fileName: Rope.Cat[ fileName, "-in", Convert.RopeFromCard[i] ], accessOptions: $create ]; procIO[i].out _ FS.StreamOpen[ fileName: Rope.Cat[ fileName, "-out", Convert.RopeFromCard[i] ], accessOptions: $create ]; <> ThreeDScenes.WriteScene[context, procIO[i].out]; -- get scene to remote proc IO.SetIndex[procIO[i].out, 0]; -- reset index for downstream read returnMsg _ Rope.Cat[returnMsg, "Started proc. at: ", ElapsedTime[startTime], " "]; <> IF i = 0 THEN GetProcess[NARROW[ Atom.GetPropFromList[context.props, $ProcessCount] ]]; forkedProc[i] _ CedarProcess.Fork[CallComputeServer, procIO[i] ]; -- fork process <> WHILE CurrentTime[] - lastTime < (i+1) * 2 * estComputeTime -- allow 2*estimated time OR NOT computingRemote OR procClonesAllowed = 1 DO -- these stop timeout KillChildren: PROC[ i: NAT ] ~ { FOR k: NAT IN [0..i] DO CedarProcess.Abort[ forkedProc[k] ]; ENDLOOP; ERROR ABORTED; }; serversAvailable: BOOLEAN _ FALSE; CedarProcess.CheckAbort[ ! ABORTED => KillChildren[i] ]; -- abort on request Process.Pause[ Process.SecondsToTicks[1] ]; -- pause for a second FOR j: NAT IN [0..i] DO -- check on each forked process result: REF; status: CedarProcess.Status _ CedarProcess.GetStatus[ forkedProc[j] ]; IF status = done OR procClonesAllowed = 1 -- don't timeout if no clones allowed THEN { -- finished! FOR k: NAT IN [0..i] DO -- abort other procs IF k # j THEN { returnMsg _ Rope.Cat[ returnMsg, "\n aborted proc on ", ComputeClientExtras.RemoteProcessSite[forkedProc[k].process], " " ]; CedarProcess.Abort[ forkedProc[k] ]; }; ENDLOOP; [status, result] _ CedarProcess.Join[ forkedProc[j] ]; -- get proc results ReleaseProcess[NARROW[ Atom.GetPropFromList[context.props, $ProcessCount] ]]; returnMsg _ Rope.Cat[ returnMsg, NARROW[result, Rope.ROPE] ]; input _ procIO[j].in; EXIT; -- some process finished, stop checking } ELSE IF status = aborted THEN EXIT -- go try again if aborted ELSE { -- check on remote processor utilization [ serverName[j], serverPctAvail[j] ] _ ServerUsage[ forkedProc[j] ]; IF serverPctAvail[j] > minimumUsefulServerPortion THEN serversAvailable _ TRUE -- based on 5 second samples ELSE { server: Rope.ROPE _ serverName[j]; }; -- place for debug }; IF NOT serversAvailable AND i < procClonesAllowed-1 THEN EXIT; -- lousy service, get another server ENDLOOP; IF input # NIL THEN EXIT; -- some process finished, stop waiting ENDLOOP; IF input # NIL THEN EXIT; -- some process finished, stop cloning new ones ENDLOOP; IF input = NIL THEN { -- clean up processes on timeout returnMsg _ Rope.Cat[returnMsg, " process forker timed-out!! "]; FOR i: NAT IN [0..procClonesAllowed) DO CedarProcess.Abort[ forkedProc[i] ]; IF i = 0 THEN ReleaseProcess[ NARROW[ Atom.GetPropFromList[ context.props, $ProcessCount] ] ]; ENDLOOP; }; <> times _ AddTimes[returnMsg, times]; returnMsg _ Rope.Cat[returnMsg, "\n cost factor: ", RopeFromSeconds[ times[3] / estComputeTime]]; returnMsg _ Rope.Cat[returnMsg, " remote ovrhd: ", -- compute server overhead RopeFromSeconds[ CurrentTime[] - startTime - times[3] ], " s. " ]; IF input # NIL THEN IO.SetIndex[input, 0]; -- reset index for returning pixel stream <> IF NOT imageSpaceDivision THEN { lastTime _ CurrentTime[]; FOR i: NAT IN [0..context.shapes.length) DO IF Atom.GetPropFromList[context.shapes[i].props, $Hidden] = NIL THEN { -- no lights <> occlusionList: LIST OF REF ShapeInstance _ NARROW[ Atom.GetPropFromList[ context.shapes[i].props, $Occlusions ] ]; FOR shapes: LIST OF REF ShapeInstance _ occlusionList, shapes.rest UNTIL shapes = NIL DO procList: LIST OF CedarProcess.Process _ NARROW[ Atom.GetPropFromList[ shapes.first.props, $RemoteProc ] ]; FOR procs: LIST OF CedarProcess.Process _ procList, procs.rest UNTIL procs = NIL DO [] _ CedarProcess.Join[procs.first]; -- wait for process completion ENDLOOP; ENDLOOP; }; ENDLOOP; returnMsg _ Rope.Cat[returnMsg, " waited for: ", ElapsedTime[lastTime] ]; }; <> lastTime _ CurrentTime[]; IF input # NIL THEN { xPos: NAT _ Floor[context.viewPort.x]; yPos: NAT _ Floor[context.viewPort.y]; width: NAT _ Ceiling[context.viewPort.w]; outWidth: NAT _ IF rightMostSlice OR imageSpaceDivision THEN width ELSE width-2; height: NAT _ Ceiling[context.viewPort.h]; writeOp: ATOM _ IF imageSpaceDivision THEN $Write ELSE $WriteUnder; scanSeg: REF Pixels.SampleSetSequence _ Pixels.GetScanSeg[ context.display, 0, 0, width ]; IF showGaps AND NOT rightMostSlice THEN outWidth _ outWidth - 2; FOR y: NAT IN [ 0 .. height ) DO <> FOR i: NAT IN [0.. context.display.samplesPerPixel) DO TRUSTED { IF IO.UnsafeGetBlock[ -- uses 16 bits per byte (wasting net bandwidth) self: input, block: [ base: LOOPHOLE[SampleMapOps.GetPointer[scanSeg[i], 0, width]], count: 2*width ] ] = 2*width THEN NULL ELSE { returnMsg _ Rope.Cat[returnMsg, "Unexpected end of stream"]; -- too short GOTO GiveUp; }; }; ENDLOOP; <> Pixels.PutScanSeg[context.display, 0, y, outWidth, scanSeg, writeOp]; ENDLOOP; EXITS GiveUp => NULL; }; returnMsg _ Rope.Cat[returnMsg, " matted in: ", ElapsedTime[lastTime] ]; returnMsg _ Rope.Cat[returnMsg, "\n done at: ", ElapsedTime[startTime] ]; RETURN[returnMsg]; }; CallComputeServer: CedarProcess.ForkableProc ~ { <> procIO: REF StreamPair _ NARROW[data]; input: IO.STREAM _ procIO.in; output: IO.STREAM _ procIO.out; found: BOOLEAN; success: ComputeServerClient.RemoteSuccess _ false; successMsg, procMsg, server: Rope.ROPE _ NIL; IF NOT computingRemote THEN [success, procMsg, server] _ RenderFromStream[ -- test code, no remote calls Convert.RopeFromTime[from: BasicTime.Now[], end: seconds], output, input ] ELSE [ found: found, success: success, remoteMsg: procMsg, serverInstance: server ] _ ComputeServerClient.StartService[ service: "Render3dFromStream", cmdLine: Convert.RopeFromTime[from: BasicTime.Now[], end: seconds], in: output, out: input, -- in/out as seen from server queueService: TRUE, timeToWait: remoteProcessTimeOut, -- estimate of time to wait before giving up retries: 3 -- default number of retries ! ANY => ERROR ABORTED ]; SELECT success FROM true => {}; false => { IF ~found THEN successMsg _ "compute server command not found" ELSE successMsg _ "compute server success was false (Compute Server bug?)"; }; timeOut => successMsg _ "timeOut"; commandNotFound => successMsg _ "commandNotFound"; aborted => successMsg _ "command aborted"; communicationFailure => successMsg _ "communicationFailure"; cantImportController => successMsg _ "cantImportController"; cantImportServer => successMsg _ "cantImportServer"; serverTooBusy => successMsg _ "serverTooBusy"; clientNotRunning => successMsg _ "clientNotRunning"; ENDCASE => successMsg _ "unknown Compute Server error code"; successMsg _ Rope.Cat["Server: ", server, " - ", successMsg, "\n"]; IF procMsg # NIL THEN successMsg _ Rope.Cat[successMsg, " Remote: ", procMsg]; RETURN[successMsg]; }; RenderFromStream: PROC[cmd: Rope.ROPE, input, output: IO.STREAM] RETURNS[success: ComputeServerClient.RemoteSuccess, msg, server: Rope.ROPE] ~ { <> startTime, lastTime: REAL _ CurrentTime[]; procTime: BasicTime.GMT _ BasicTime.Now[]; scanSeg: REF Pixels.SampleSetSequence; displaycontext: REF Context; context: REF Context _ ThreeDScenes.Create[]; serverDelay: INT _ BasicTime.Period[ Convert.TimeFromRope[cmd], procTime ]; msg _ Rope.Cat[" delay: ", Convert.RopeFromInt[ serverDelay], " s. " ]; ThreeDScenes.ReadScene[context, input]; ThreeDScenes.DisplayFromVM[ displaycontext, -- get display bits of proper size Ceiling[context.viewPort.w], Ceiling[context.viewPort.h], $FullColor ]; ThreeDScenes.AddAlphaBuffer[displaycontext]; context.display _ displaycontext.display; -- give display bits to context ThreeDScenes.SetEyeSpace[context]; -- update for actual display size msg _ Rope.Cat[" set-up: ", ElapsedTime[startTime] ]; lastTime _ CurrentTime[]; ThreeDMisc.MakeFrame[context]; -- render image msg _ Rope.Cat[msg, " rendering: ", ElapsedTime[lastTime] ]; lastTime _ CurrentTime[]; <> FOR y: NAT IN [0 .. Ceiling[context.viewPort.h] ) DO scanSeg _ Pixels.GetScanSeg[context.display, 0, y, Ceiling[context.viewPort.w], scanSeg]; FOR i: NAT IN [0.. context.display.samplesPerPixel) DO TRUSTED { IO.UnsafePutBlock[ -- uses 16 bits per byte (wasting net bandwidth) self: output, block: [ base: LOOPHOLE[SampleMapOps.GetPointer[scanSeg[i], 0, scanSeg[i].length]], count: 2*scanSeg[i].length ] ]; }; ENDLOOP; ENDLOOP; msg _ Rope.Cat[msg, " output: ", ElapsedTime[lastTime], " total: ", ElapsedTime[startTime] ]; context _ displaycontext _ NIL; -- kill off everything, no state saved success _ true; server _ "local"; }; END.