DifferenceAveragersImpl.Mesa
Spreitzer, June 18, 1986 11:55:12 am PDT
DIRECTORY DifferenceAveragers, BasicTime, Buttons, Containers, Imager, IO, Menus, Process, Real, RealFns, Rope, Sliders, VFonts, ViewerClasses, ViewerOps;
DifferenceAveragersImpl: CEDAR MONITOR
LOCKS a USING a: Averager
IMPORTS BasicTime, Buttons, Containers, Imager, IO, Process, Real, RealFns, Rope, Sliders, VFonts, ViewerOps
EXPORTS DifferenceAveragers
=
BEGIN OPEN DifferenceAveragers;
Time: TYPE = BasicTime.GMT;
Averager: TYPE = REF AveragerPrivate;
AveragerPrivate: PUBLIC TYPE = MONITORED RECORD [
logMin, logMax: INT,
min, scale: REAL,
samplePeriod, halfLife, originalHalfLife: Seconds,
w, weight: REAL,
weightedSums: ARRAY Role OF REAL,
lastTime: Time,
container, samplePeriodButton, halfLifeButton: Viewer ← NIL,
valueShowers: ARRAY Role OF Viewer ← ALL[NIL],
Sample: PROC [clientData: REF ANY] RETURNS [add, subtract: REAL],
clientData: REF ANY,
timeToSample: CONDITION,
sampler: PROCESSNIL
];
Role: TYPE = {add, subtract, difference};
lnOneHalf: REAL = RealFns.Ln[0.5];
halfMaxHalfLife: Seconds = LONG[12*60]*60;
sep1: INT ← 1;
sep2: INT ← 2;
timeWidth: INT ← VFonts.StringWidth["12:34:56"];
foreground: Imager.Color ← Imager.MakeGray[0.5];
Create: PUBLIC
PROC
[
viewerInfo: ViewerClasses.ViewerRec ← [],
samplePeriod, halfLife: Seconds,
logMin, logMax: INT,
Sample: PROC [clientData: REF ANY] RETURNS [add, subtract: REAL],
clientData: REF ANYNIL
]
RETURNS [a: Averager]
= {
boundSP: Seconds = BoundSP[samplePeriod, halfLife];
a ← NEW [AveragerPrivate ← [
logMin: logMin,
logMax: logMax,
min: RealFns.Power[10, logMin],
scale: 1.0/(logMax - logMin),
samplePeriod: boundSP,
halfLife: halfLife,
originalHalfLife: halfLife,
w: lnOneHalf/halfLife,
weight: 1.0/(1.0 - RealFns.Power[0.5, REAL[boundSP]/halfLife]),
weightedSums: ALL[0.0],
lastTime: BasicTime.Now[],
container: Containers.Create[viewerInfo],
Sample: Sample,
clientData: clientData
]];
TRUSTED {
Process.InitializeCondition[@a.timeToSample, Process.SecondsToTicks[a.samplePeriod]];
Process.EnableAborts[@a.timeToSample];
};
a.samplePeriodButton ← Buttons.Create[info: [parent: a.container, wx: 0, wy: 0, name: SPBName[a], border: FALSE], proc: TweakSamplePeriod, clientData: a, paint: FALSE];
a.halfLifeButton ← Buttons.Create[info: [parent: a.container, wx: a.samplePeriodButton.wx+a.samplePeriodButton.ww+sep1, wy: 0, border: FALSE, name: HLBName[a]], proc: TweakHalfLife, clientData: a, paint: FALSE];
FOR role: Role IN Role DO
dy: INT = a.container.ch/(Role.LAST.ORD+1);
a.valueShowers[role] ← Sliders.Create[info: [parent: a.container, wx: a.halfLifeButton.wx+a.halfLifeButton.ww+sep1, wy: dy*role.ORD, ww: 10, wh: dy, border: FALSE], orientation: horizontal, foreground: foreground, value: Normalize[a, 0.0], clientData: a, paint: FALSE];
Containers.ChildXBound[a.container, a.valueShowers[role]];
ENDLOOP;
ViewerOps.PaintViewer[a.container, all];
a.sampler ← FORK Sampler[a];
};
GetLayout: PUBLIC PROC [a: Averager, ancestor: Viewer] RETURNS [samplePeriodButton, halfLifeButton, bars: XRange --in client coords of ancestor (screen if NIL)--] = {
Loc: PROC [child: Viewer] RETURNS [loc: XRange] = {
loc ← [
left: ViewerOps.UserToScreenCoords[a.container, child.wx, child.wy].sx -
(IF ancestor # NIL THEN ViewerOps.UserToScreenCoords[ancestor, 0, 0].sx ELSE 0),
width: child.ww];
};
samplePeriodButton ← Loc[a.samplePeriodButton];
halfLifeButton ← Loc[a.halfLifeButton];
bars ← Loc[a.valueShowers[add]];
};
Normalize: PROC [a: Averager, value: REAL] RETURNS [normalized: REAL] = INLINE {
normalized ← MIN[1.0, MAX[RealFns.Log[10, MAX[a.min, ABS[value]]]-a.logMin, 0] * a.scale]};
minFraction: REAL = 1.0/4096.0 --half of the 24 bits of mantissa in a REAL--;
maxRatio: REAL = -RealFns.Log[2.0, minFraction];
minRatio: REAL = -RealFns.Log[2.0, 1-minFraction];
BoundSP: PROC [candidateSamplePeriod, halfLife: Seconds] RETURNS [kosherSamplePeriod: Seconds] = {
kosherSamplePeriod ← Real.Round[MIN[maxRatio*halfLife, MAX[minRatio*halfLife, REAL[candidateSamplePeriod]]]]};
GoodSP: PROC [halfLife: Seconds] RETURNS [aGoodSamplePeriod: Seconds] = {
aGoodSamplePeriod ← 1};
TweakHalfLife: PROC [parent: REF ANY, clientData: REF ANYNIL,
mouseButton: Menus.MouseButton ← red, shift, control: BOOLFALSE]--Buttons.ButtonProc-- = {
a: Averager = NARROW[clientData];
EnterAnd: ENTRY PROC [a: Averager] = {
ENABLE UNWIND => NULL;
newHalfLife: Seconds = SELECT mouseButton FROM
red => MAX[MIN[halfMaxHalfLife, a.halfLife]*2, 1],
yellow => a.originalHalfLife,
blue => MAX[a.halfLife/2, 1],
ENDCASE => ERROR;
IF newHalfLife # a.halfLife THEN {
a.halfLife ← newHalfLife;
a.w ← lnOneHalf/a.halfLife;
Buttons.ReLabel[a.halfLifeButton, HLBName[a]];
InnerSetSamplePeriod[a, BoundSP[a.samplePeriod, a.halfLife]];
};
};
EnterAnd[a];
};
TweakSamplePeriod: PROC [parent: REF ANY, clientData: REF ANYNIL,
mouseButton: Menus.MouseButton ← red, shift, control: BOOLFALSE]--Buttons.ButtonProc-- = {
a: Averager = NARROW[clientData];
SetSamplePeriod[
a,
SELECT mouseButton FROM
red => BoundSP[a.samplePeriod*2, a.halfLife],
yellow => GoodSP[a.halfLife],
blue => BoundSP[MAX[a.samplePeriod/2, 1], a.halfLife],
ENDCASE => ERROR
];
};
SetSamplePeriod: ENTRY PROC [a: Averager, new: Seconds] = {
ENABLE UNWIND => NULL;
InnerSetSamplePeriod[a, new]};
InnerSetSamplePeriod: INTERNAL PROC [a: Averager, new: Seconds] = {
IF new # a.samplePeriod THEN {
a.samplePeriod ← new;
Buttons.ReLabel[a.samplePeriodButton, SPBName[a]];
TRUSTED {Process.SetTimeout[@a.timeToSample, Process.SecondsToTicks[a.samplePeriod]]};
BROADCAST a.timeToSample;
};
};
AsViewer: PUBLIC PROC [a: Averager] RETURNS [v: Viewer] = {
v ← a.container};
Sampler: ENTRY PROC [a: Averager] = {
ENABLE UNWIND => NULL;
DO
D: ARRAY Role OF REAL;
WAIT a.timeToSample;
[D[add], D[subtract]] ← a.Sample[a.clientData];
IF a.container.destroyed THEN EXIT;
{
now: Time = BasicTime.Now[];
period: Seconds = BasicTime.Period[a.lastTime, now];
keep: REAL = RealFns.Exp[a.w * period];
a.lastTime ← now;
D[difference] ← D[add] - D[subtract];
a.weight ← a.weight*keep + 1;
FOR role: Role IN Role DO
a.weightedSums[role] ← keep*a.weightedSums[role] + D[role];
Sliders.SetContents[a.valueShowers[role], Normalize[a, a.weightedSums[role]/a.weight]];
ENDLOOP;
}ENDLOOP;
};
SPBName: PROC [a: Averager] RETURNS [name: ROPE] = {
name ← FmtPeriod[a.samplePeriod].Concat["/"]};
HLBName: PROC [a: Averager] RETURNS [name: ROPE] = {
name ← FmtPeriod[a.halfLife]};
FmtPeriod: PROC [period: Seconds] RETURNS [asRope: ROPE] = {
hours: INT = period/3600;
rem1: INT = period - hours*3600;
minutes: INT = rem1 / 60;
seconds: INT = rem1 - minutes*60;
asRope ← IO.PutFR["%02g:%02g:%02g", [integer[hours]], [integer[minutes]], [integer[seconds]]];
};
END.