838 lines
28 KiB
C#
838 lines
28 KiB
C#
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Windows.Forms;
|
|
|
|
using AgentInterface;
|
|
|
|
namespace Agent
|
|
{
|
|
/*
|
|
* Everything required to handle storing the data for drawing the progress bars
|
|
*/
|
|
public partial class SwarmAgentWindow : Form
|
|
{
|
|
class BarColour
|
|
{
|
|
public Color Colour;
|
|
public Color BorderColour;
|
|
public Pen Border;
|
|
public SolidBrush FillBrush;
|
|
|
|
public BarColour( Color InColour )
|
|
{
|
|
Colour = InColour;
|
|
BorderColour = Color.Black;
|
|
|
|
Border = new Pen( BorderColour );
|
|
FillBrush = new SolidBrush( Colour );
|
|
}
|
|
}
|
|
|
|
private static Dictionary<string, BarColour> BarColours = null;
|
|
private static Dictionary<string, string> BarNames = null;
|
|
|
|
public static void CreateBarColours( SwarmAgentWindow Owner )
|
|
{
|
|
BarColours = new Dictionary<string, BarColour>();
|
|
for( int Index = 0; Index < ( int )EProgressionState.Num; Index++ )
|
|
{
|
|
BarColours.Add( Enum.GetName( typeof( EProgressionState ), Index ), new BarColour( AgentApplication.Options.VisualizerColors[Index] ) );
|
|
}
|
|
|
|
// Refresh the key
|
|
Owner.ExportingBox.BackColor = AgentApplication.Options.VisualizerColors[( int )EProgressionState.InstigatorConnected];
|
|
Owner.StartingBox.BackColor = AgentApplication.Options.VisualizerColors[( int )EProgressionState.BeginJob];
|
|
Owner.EmitPhotonBox.BackColor = AgentApplication.Options.VisualizerColors[( int )EProgressionState.Preparing0];
|
|
Owner.IrradianceCacheBox.BackColor = AgentApplication.Options.VisualizerColors[( int )EProgressionState.Preparing2];
|
|
Owner.ProcessingBox.BackColor = AgentApplication.Options.VisualizerColors[( int )EProgressionState.Processing0];
|
|
Owner.ExportingResultsBox.BackColor = AgentApplication.Options.VisualizerColors[( int )EProgressionState.ExportingResults];
|
|
Owner.ImportingBox.BackColor = AgentApplication.Options.VisualizerColors[( int )EProgressionState.Finished];
|
|
|
|
BarNames = new Dictionary<string, string>();
|
|
BarNames.Add( EProgressionState.TaskTotal.ToString(), "Task total" );
|
|
BarNames.Add( EProgressionState.TasksInProgress.ToString(), "Tasks in progress" );
|
|
BarNames.Add( EProgressionState.TasksCompleted.ToString(), "Tasks completed" );
|
|
BarNames.Add( EProgressionState.Idle.ToString(), "Idle" );
|
|
BarNames.Add( EProgressionState.InstigatorConnected.ToString(), "Exporting to local cache" );
|
|
BarNames.Add( EProgressionState.RemoteConnected.ToString(), "Connected to remote, starting job" );
|
|
BarNames.Add( EProgressionState.Exporting.ToString(), "Exporting to local cache" );
|
|
BarNames.Add( EProgressionState.BeginJob.ToString(), "Lightmass starting up" );
|
|
BarNames.Add( EProgressionState.Blocked.ToString(), "Waiting for remote to become available" );
|
|
BarNames.Add( EProgressionState.Preparing0.ToString(), "Emitting direct photons" );
|
|
BarNames.Add( EProgressionState.Preparing1.ToString(), "Emitting indirect photons" );
|
|
BarNames.Add( EProgressionState.Preparing2.ToString(), "Calculating irradiance photons" );
|
|
BarNames.Add( EProgressionState.Preparing3.ToString(), "Caching irradiance photons" );
|
|
BarNames.Add( EProgressionState.Processing0.ToString(), "Processing mappings" );
|
|
BarNames.Add( EProgressionState.FinishedProcessing0.ToString(), "Finished processing mappings" );
|
|
BarNames.Add( EProgressionState.Processing1.ToString(), "Processing1" );
|
|
BarNames.Add( EProgressionState.FinishedProcessing1.ToString(), "Finished processing1" );
|
|
BarNames.Add( EProgressionState.Processing2.ToString(), "Processing2" );
|
|
BarNames.Add( EProgressionState.FinishedProcessing2.ToString(), "Finished processing2" );
|
|
BarNames.Add( EProgressionState.Processing3.ToString(), "Processing3" );
|
|
BarNames.Add( EProgressionState.FinishedProcessing3.ToString(), "Finished processing3" );
|
|
BarNames.Add( EProgressionState.ExportingResults.ToString(), "Exporting results" );
|
|
BarNames.Add( EProgressionState.ImportingResults.ToString(), "Importing results" );
|
|
BarNames.Add( EProgressionState.Finished.ToString(), "Finishing" );
|
|
BarNames.Add( EProgressionState.RemoteDisconnected.ToString(), "Remote disconnected" );
|
|
BarNames.Add( EProgressionState.InstigatorDisconnected.ToString(), "Instigator disconnected" );
|
|
BarNames.Add( EProgressionState.Preparing4.ToString(), "Skylight Radiosity");
|
|
}
|
|
|
|
public class ProgressionEvent
|
|
{
|
|
public string Machine = null;
|
|
public int ThreadNum = 0;
|
|
public EProgressionState State = EProgressionState.Idle;
|
|
public DateTime Time = DateTime.UtcNow;
|
|
|
|
public ProgressionEvent( string InMachine, int InThreadNum, EProgressionState NewState )
|
|
{
|
|
Machine = InMachine;
|
|
ThreadNum = InThreadNum;
|
|
State = NewState;
|
|
}
|
|
}
|
|
|
|
public class ProgressionThreadSample
|
|
{
|
|
public EProgressionState State = EProgressionState.Idle;
|
|
public DateTime StartTime = DateTime.UtcNow;
|
|
public DateTime EndTime = DateTime.UtcNow;
|
|
public int NumTimestamps = 0;
|
|
|
|
public ProgressionThreadSample(EProgressionState NewState)
|
|
{
|
|
State = NewState;
|
|
NumTimestamps = 0;
|
|
}
|
|
|
|
public void AddTimestamp( DateTime EventTime )
|
|
{
|
|
if ( NumTimestamps == 0 )
|
|
{
|
|
StartTime = EventTime;
|
|
}
|
|
EndTime = EventTime;
|
|
NumTimestamps++;
|
|
}
|
|
}
|
|
|
|
public class ProgressionSample
|
|
{
|
|
public EProgressionState State = EProgressionState.Idle;
|
|
public DateTime StartTime = DateTime.UtcNow;
|
|
public DateTime EndTime = DateTime.UtcNow;
|
|
public List<ProgressionThreadSample> ThreadSamples = null;
|
|
public Rectangle Bounds;
|
|
public double Duration;
|
|
public int NumTimestamps = 0;
|
|
|
|
public ProgressionSample()
|
|
{
|
|
}
|
|
|
|
public ProgressionSample( EProgressionState NewState, DateTime EventTime )
|
|
{
|
|
State = NewState;
|
|
StartTime = EventTime;
|
|
EndTime = EventTime;
|
|
Duration = 0.0;
|
|
NumTimestamps = 1;
|
|
}
|
|
|
|
public void AddTimestamp( DateTime EventTime )
|
|
{
|
|
if ( NumTimestamps == 0 )
|
|
{
|
|
StartTime = EventTime;
|
|
}
|
|
EndTime = EventTime;
|
|
NumTimestamps++;
|
|
}
|
|
}
|
|
|
|
public class Progression
|
|
{
|
|
/*
|
|
* List of state changes and times thereof for all threads
|
|
*/
|
|
public List<ProgressionSample> ProgressionSamples = new List<ProgressionSample>();
|
|
|
|
private const int BarBorder = 4;
|
|
|
|
/** Total width of all progress bars (within the 2nd column). */
|
|
public int ProgressTotalWidth = 0;
|
|
|
|
public Progression()
|
|
{
|
|
ProgressionSamples.Add( new ProgressionSample() );
|
|
ProgressionSamples.Add( new ProgressionSample() );
|
|
}
|
|
|
|
public void DrawBackground( Graphics Gfx, Rectangle Bounds )
|
|
{
|
|
Color FillColor = SystemColors.ControlLightLight;
|
|
ProgressionSample Sample = ProgressionSamples[ProgressionSamples.Count - 1];
|
|
if (Sample.State == EProgressionState.RemoteDisconnected || Sample.State == EProgressionState.InstigatorDisconnected)
|
|
{
|
|
FillColor = SystemColors.ControlLight;
|
|
}
|
|
|
|
Gfx.Clear( FillColor );
|
|
}
|
|
|
|
private void DrawFilledBar( Graphics Gfx, EProgressionState State, int Left, int Top, int Width, int Height )
|
|
{
|
|
Rectangle Bar = new Rectangle( Left, Top, Width, Height );
|
|
|
|
Gfx.FillRectangle( BarColours[State.ToString()].FillBrush, Bar );
|
|
|
|
Pen Border = BarColours[State.ToString()].Border;
|
|
Gfx.DrawRectangle( Border, Bar );
|
|
}
|
|
|
|
public int Draw( DateTime Start, float ZoomLevel, Graphics Gfx, Rectangle Bounds )
|
|
{
|
|
int Rightmost = 0;
|
|
int Offset = BarBorder + Bounds.Left;
|
|
// Work out initial offset so all the bars are on the same time rule
|
|
ProgressionSample LastSample = ProgressionSamples[ProgressionSamples.Count - 1];
|
|
int Top = Bounds.Top + BarBorder;
|
|
int Height = Bounds.Height - ( BarBorder * 2 );
|
|
|
|
for( int BarIndex = 1; BarIndex < ProgressionSamples.Count; BarIndex++ )
|
|
{
|
|
ProgressionSample NewSample = ProgressionSamples[BarIndex];
|
|
|
|
// Render progress bars
|
|
if( NewSample.ThreadSamples == null )
|
|
{
|
|
int Left = ( int )( ( NewSample.StartTime - Start ).TotalSeconds * ZoomLevel );
|
|
int Right = ( int )( ( NewSample.EndTime - Start ).TotalSeconds * ZoomLevel );
|
|
if( Right - Left > 2 )
|
|
{
|
|
DrawFilledBar( Gfx, NewSample.State, Left + Offset, Top, Right - Left, Height );
|
|
NewSample.Bounds = new Rectangle( Left + Offset, Top, Right - Left, Height );
|
|
NewSample.Duration = (NewSample.EndTime - NewSample.StartTime).TotalSeconds;
|
|
|
|
if( Right > Rightmost )
|
|
{
|
|
Rightmost = Right;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Render a line per thread with ticks for new jobs
|
|
int HeightPerThread = Math.Max(Height / NewSample.ThreadSamples.Count, 2);
|
|
int ThreadTop = Top;
|
|
int AccumulatedThreadHeight = 0;
|
|
int BarThreadLeft = 0;
|
|
int BarThreadRight = 0;
|
|
int BarThreadLeftMost = Int32.MaxValue;
|
|
int BarThreadRightMost = 0;
|
|
|
|
foreach( ProgressionThreadSample ThreadSample in NewSample.ThreadSamples )
|
|
{
|
|
if (AccumulatedThreadHeight < Height)
|
|
{
|
|
// Draw the thread line
|
|
BarThreadLeft = (int)(ZoomLevel * (ThreadSample.StartTime - Start).TotalSeconds);
|
|
|
|
if (BarIndex < (ProgressionSamples.Count - 1) || ThreadSample.StartTime != ThreadSample.EndTime)
|
|
{
|
|
BarThreadRight = (int)(ZoomLevel * (ThreadSample.EndTime - Start).TotalSeconds);
|
|
}
|
|
else if (LastSample.State == EProgressionState.RemoteDisconnected || LastSample.State == EProgressionState.InstigatorDisconnected)
|
|
{
|
|
BarThreadRight = (int)(ZoomLevel * (LastSample.StartTime - Start).TotalSeconds);
|
|
}
|
|
else
|
|
{
|
|
BarThreadRight = (int)(ZoomLevel * (DateTime.UtcNow - Start).TotalSeconds);
|
|
}
|
|
|
|
// Make sure each bar is at least 1 unit wide, and maintain extrema.
|
|
if (BarThreadRight <= BarThreadLeft)
|
|
{
|
|
BarThreadRight = BarThreadLeft + 1;
|
|
}
|
|
if (BarThreadLeft < BarThreadLeftMost)
|
|
{
|
|
BarThreadLeftMost = BarThreadLeft;
|
|
}
|
|
if (BarThreadRight > BarThreadRightMost)
|
|
{
|
|
BarThreadRightMost = BarThreadRight;
|
|
}
|
|
|
|
Rectangle ThreadBar = new Rectangle(BarThreadLeft + Offset, ThreadTop + 1, BarThreadRight - BarThreadLeft, HeightPerThread - 1);
|
|
BarColour ThreadBarColor = BarColours[ThreadSample.State.ToString()];
|
|
Gfx.FillRectangle(ThreadBarColor.FillBrush, ThreadBar);
|
|
|
|
ThreadTop += HeightPerThread;
|
|
AccumulatedThreadHeight += HeightPerThread;
|
|
}
|
|
}
|
|
|
|
// Add a border
|
|
NewSample.Bounds = new Rectangle( BarThreadLeftMost + Offset, Top, BarThreadRightMost - BarThreadLeftMost, Height );
|
|
NewSample.Duration = NewSample.Bounds.Width / ZoomLevel;
|
|
Gfx.DrawRectangle( Pens.Black, NewSample.Bounds );
|
|
|
|
if( BarThreadRightMost > Rightmost )
|
|
{
|
|
Rightmost = BarThreadRightMost;
|
|
}
|
|
}
|
|
}
|
|
|
|
ProgressTotalWidth = Rightmost;
|
|
return ( Rightmost );
|
|
}
|
|
}
|
|
|
|
public class Progressions
|
|
{
|
|
/*
|
|
* Overall progression status
|
|
*/
|
|
public int NumTasks = 0;
|
|
public int NumRetiredTasks = 0;
|
|
public int NumRunningTasks = 0;
|
|
|
|
/*
|
|
* The time the instigator connected
|
|
*/
|
|
public DateTime StartTime = DateTime.UtcNow;
|
|
|
|
/*
|
|
* The collection that contains the timing info per machine
|
|
*
|
|
* Each entry in the list represents a thread
|
|
*/
|
|
public ReaderWriterDictionary<string, Progression> MachineProgressions = new ReaderWriterDictionary<string, Progression>();
|
|
|
|
public void DrawOverallProgressBar( Graphics Gfx, Rectangle Bounds )
|
|
{
|
|
Gfx.Clear( Color.White );
|
|
|
|
float PercentComplete = 0.0f;
|
|
float PercentRunning = 0.0f;
|
|
if( NumTasks > 0 )
|
|
{
|
|
PercentComplete = ( float )NumRetiredTasks / ( float )NumTasks;
|
|
PercentRunning = ( float )NumRunningTasks / ( float )NumTasks;
|
|
}
|
|
|
|
Rectangle CompleteBar = new Rectangle( Bounds.X, Bounds.Y, ( int )( Bounds.Width * PercentComplete ) + 1, Bounds.Height );
|
|
Rectangle RunningBar = new Rectangle( Bounds.X + ( int )( Bounds.Width * PercentComplete ) + 1, Bounds.Y, ( int )( Bounds.Width * PercentRunning ) + 1, Bounds.Height );
|
|
|
|
Gfx.FillRectangle( BarColours[EProgressionState.FinishedProcessing3.ToString()].FillBrush, CompleteBar );
|
|
Gfx.FillRectangle( BarColours[EProgressionState.FinishedProcessing2.ToString()].FillBrush, RunningBar );
|
|
|
|
Pen Border = BarColours[EProgressionState.BeginJob.ToString()].Border;
|
|
Rectangle BorderBounds = new Rectangle( Bounds.X, Bounds.Y, Bounds.Width - 1, Bounds.Height - 1 );
|
|
Gfx.DrawRectangle( Border, BorderBounds );
|
|
|
|
string ProgressString = ( PercentComplete * 100.0f ).ToString( "F2" ) + "%";
|
|
Font DrawFont = new Font( AgentApplication.Options.TextFont.ToString(), 9.75F );
|
|
SizeF StringSize = Gfx.MeasureString( ProgressString, DrawFont );
|
|
int X = BorderBounds.X + ( BorderBounds.Width - ( int )StringSize.Width ) / 2;
|
|
int Y = BorderBounds.Y + ( BorderBounds.Height - ( int )StringSize.Height ) / 2;
|
|
Gfx.DrawString( ProgressString, DrawFont, Brushes.Black, X, Y );
|
|
DrawFont.Dispose();
|
|
}
|
|
|
|
/*
|
|
* Process a state change timing event
|
|
*/
|
|
public bool ProcessEvent( ProgressionEvent Event )
|
|
{
|
|
// Overall progress info
|
|
if( Event.State < EProgressionState.Idle )
|
|
{
|
|
switch( Event.State )
|
|
{
|
|
case EProgressionState.TaskTotal:
|
|
NumTasks = Event.ThreadNum;
|
|
break;
|
|
|
|
case EProgressionState.TasksCompleted:
|
|
NumRetiredTasks = Event.ThreadNum;
|
|
break;
|
|
|
|
case EProgressionState.TasksInProgress:
|
|
NumRunningTasks = Event.ThreadNum;
|
|
break;
|
|
}
|
|
|
|
return ( true );
|
|
}
|
|
|
|
// Handle per machine stats
|
|
Progression MachineProgression = null;
|
|
if( !MachineProgressions.TryGetValue( Event.Machine, out MachineProgression ) )
|
|
{
|
|
MachineProgressions.Add( Event.Machine, new Progression() );
|
|
MachineProgressions.TryGetValue( Event.Machine, out MachineProgression );
|
|
}
|
|
|
|
List<ProgressionSample> Samples = MachineProgression.ProgressionSamples;
|
|
ProgressionSample LastSample = Samples[Samples.Count - 1];
|
|
|
|
// If this machine has disconnected, ignore any further messages
|
|
if (LastSample.State == EProgressionState.RemoteDisconnected || LastSample.State == EProgressionState.InstigatorDisconnected)
|
|
{
|
|
return ( true );
|
|
}
|
|
|
|
// Coarse tracking on a per machine basis
|
|
bool bCreateNewSample =
|
|
(LastSample.State != Event.State) ||
|
|
(Event.ThreadNum >= 0 && LastSample.ThreadSamples == null) ||
|
|
(Event.ThreadNum < 0 && LastSample.ThreadSamples != null);
|
|
|
|
if( bCreateNewSample == false )
|
|
{
|
|
// Just updating the same event, add timestamp
|
|
LastSample.AddTimestamp( Event.Time );
|
|
}
|
|
else
|
|
{
|
|
// If the previous sample have proper Start and End timestamps, add one
|
|
if( LastSample.NumTimestamps <= 1 )
|
|
{
|
|
LastSample.AddTimestamp( Event.Time );
|
|
}
|
|
// If the previous sample has individual thread samples, make sure they're all ended
|
|
if( ( LastSample.ThreadSamples != null ) &&
|
|
( LastSample.ThreadSamples.Count > 0 ) )
|
|
{
|
|
foreach( ProgressionThreadSample NextThreadSample in LastSample.ThreadSamples )
|
|
{
|
|
if( NextThreadSample.NumTimestamps <= 1 )
|
|
{
|
|
NextThreadSample.AddTimestamp( Event.Time );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( Event.ThreadNum < 0 )
|
|
{
|
|
if ( bCreateNewSample )
|
|
{
|
|
Samples.Add( new ProgressionSample( Event.State, Event.Time ) );
|
|
}
|
|
}
|
|
else if( Event.ThreadNum >= 0 )
|
|
{
|
|
if ( bCreateNewSample )
|
|
{
|
|
// Previous sample was of a different kind, so create a new per-thread instance.
|
|
LastSample = new ProgressionSample( Event.State, Event.Time );
|
|
LastSample.ThreadSamples = new List<ProgressionThreadSample>();
|
|
Samples.Add( LastSample );
|
|
}
|
|
|
|
// Optional more detailed per thread tracking
|
|
while( LastSample.ThreadSamples.Count <= Event.ThreadNum )
|
|
{
|
|
List<ProgressionThreadSample> ThreadSamples = new List<ProgressionThreadSample>();
|
|
ProgressionThreadSample ThreadSample = new ProgressionThreadSample( Event.State );
|
|
LastSample.ThreadSamples.Add( ThreadSample );
|
|
}
|
|
|
|
LastSample.ThreadSamples[Event.ThreadNum].AddTimestamp( Event.Time );
|
|
}
|
|
|
|
// If we've had a disconnect message, make sure all progressions are terminated
|
|
if( Event.State == EProgressionState.InstigatorDisconnected )
|
|
{
|
|
Samples.Add( new ProgressionSample( Event.State, Event.Time ) );
|
|
|
|
// Make sure all progress bars stop as there will be no more messages
|
|
foreach( Progression ProgressionMachine in MachineProgressions.Values )
|
|
{
|
|
List<ProgressionSample> ProgressionSamples = ProgressionMachine.ProgressionSamples;
|
|
LastSample = ProgressionSamples[ProgressionSamples.Count - 1];
|
|
|
|
if (LastSample.State != EProgressionState.RemoteDisconnected && LastSample.State != EProgressionState.InstigatorDisconnected)
|
|
{
|
|
ProgressionSamples.Add( new ProgressionSample( EProgressionState.RemoteDisconnected, Event.Time ) );
|
|
}
|
|
}
|
|
|
|
return ( true );
|
|
}
|
|
|
|
return ( bCreateNewSample );
|
|
}
|
|
|
|
/*
|
|
* Advance all active timers
|
|
*/
|
|
public bool Tick()
|
|
{
|
|
bool Invalidate = false;
|
|
|
|
foreach( Progression MachineProgression in MachineProgressions.Values )
|
|
{
|
|
List<ProgressionSample> Samples = MachineProgression.ProgressionSamples;
|
|
ProgressionSample Sample = Samples[Samples.Count - 1];
|
|
if (Sample.State != EProgressionState.RemoteDisconnected && Sample.State != EProgressionState.InstigatorDisconnected)
|
|
{
|
|
Sample.AddTimestamp( DateTime.UtcNow );
|
|
Invalidate = true;
|
|
}
|
|
}
|
|
|
|
return ( Invalidate );
|
|
}
|
|
}
|
|
|
|
private bool VisualiserGridViewResized = false;
|
|
|
|
/*
|
|
* Work out ideal height of progress bars
|
|
*/
|
|
private int CalculateIdealBarHeight( int NumMachines )
|
|
{
|
|
int TotalHeight = VisualiserGridView.Height - ( VisualiserGridView.ColumnHeadersHeight * 2 );
|
|
int RowHeight = TotalHeight / ( NumMachines * 8 );
|
|
|
|
if( RowHeight < 3 )
|
|
{
|
|
RowHeight = 3;
|
|
}
|
|
|
|
if( RowHeight > 12 )
|
|
{
|
|
RowHeight = 12;
|
|
}
|
|
|
|
return( RowHeight * 8 );
|
|
}
|
|
|
|
private static int CompareByStateAndTime( KeyValuePair<string, Progression> A, KeyValuePair<string, Progression> B )
|
|
{
|
|
int LastIndexA = A.Value.ProgressionSamples.Count - 1;
|
|
int LastIndexB = B.Value.ProgressionSamples.Count - 1;
|
|
// Start with state
|
|
if( A.Value.ProgressionSamples[LastIndexA].State == EProgressionState.Blocked )
|
|
{
|
|
if( B.Value.ProgressionSamples[LastIndexB].State == EProgressionState.Blocked )
|
|
{
|
|
// Now sort on the time
|
|
if( A.Value.ProgressionSamples[0].StartTime < B.Value.ProgressionSamples[0].StartTime )
|
|
{
|
|
return -1;
|
|
}
|
|
else if( A.Value.ProgressionSamples[0].StartTime == B.Value.ProgressionSamples[0].StartTime )
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( B.Value.ProgressionSamples[LastIndexB].State == EProgressionState.Blocked )
|
|
{
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
// Now sort on the time
|
|
if( A.Value.ProgressionSamples[0].StartTime < B.Value.ProgressionSamples[0].StartTime )
|
|
{
|
|
return -1;
|
|
}
|
|
else if( A.Value.ProgressionSamples[0].StartTime == B.Value.ProgressionSamples[0].StartTime )
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Populate the gridview if the number of connected machines has changed
|
|
*/
|
|
public void PopulateGridView()
|
|
{
|
|
if( VisualiserGridViewResized || ProgressionData.MachineProgressions.Count != VisualiserGridView.Rows.Count )
|
|
{
|
|
VisualiserGridView.Rows.Clear();
|
|
|
|
// Sort the machines by state and then by time
|
|
List< KeyValuePair<string, Progression> > CopyOfProgressionData =
|
|
new List< KeyValuePair<string, Progression> >( ProgressionData.MachineProgressions.Copy() );
|
|
|
|
// Disable the sort for now, because lots of movement is distracting
|
|
//CopyOfProgressionData.Sort( CompareByStateAndTime );
|
|
|
|
List<string> MachineNames = new List<string>();
|
|
foreach( KeyValuePair<string, Progression> NextPair in CopyOfProgressionData )
|
|
{
|
|
MachineNames.Add( NextPair.Key );
|
|
}
|
|
|
|
// Always move the local machine name to the top of the list
|
|
if( MachineNames.Remove(System.Net.Dns.GetHostName()) )
|
|
{
|
|
MachineNames.Insert( 0, System.Net.Dns.GetHostName());
|
|
}
|
|
|
|
// Find ideal row height dependent on number of agents
|
|
int RowHeight = CalculateIdealBarHeight( MachineNames.Count );
|
|
|
|
foreach( string Machine in MachineNames )
|
|
{
|
|
DataGridViewRow Row = new DataGridViewRow();
|
|
Row.Height = RowHeight;
|
|
|
|
DataGridViewTextBoxCell NameCell = new DataGridViewTextBoxCell();
|
|
NameCell.Value = Machine;
|
|
Row.Cells.Add( NameCell );
|
|
|
|
DataGridViewTextBoxCell ProgressCell = new DataGridViewTextBoxCell();
|
|
Row.Cells.Add( ProgressCell );
|
|
|
|
VisualiserGridView.Rows.Add( Row );
|
|
}
|
|
|
|
VisualiserGridViewResized = false;
|
|
VisualiserGridView.Invalidate();
|
|
}
|
|
|
|
// Handle the horizontal scroll bar
|
|
VisualiserGridView.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
|
|
|
|
// Get the largest width of each row
|
|
int ProgressionWidth = 0;
|
|
foreach( Progression ProgressionMachine in ProgressionData.MachineProgressions.Values )
|
|
{
|
|
if ( ProgressionMachine.ProgressTotalWidth > ProgressionWidth )
|
|
{
|
|
ProgressionWidth = ProgressionMachine.ProgressTotalWidth;
|
|
}
|
|
}
|
|
ProgressionWidth += 10; // Add a small margin.
|
|
|
|
// Figure out the desired width of the 2nd column.
|
|
int ColumnWidth = VisualiserGridView.Columns[1].Width;
|
|
if ( ColumnWidth <= ProgressionWidth || ColumnWidth > (ProgressionWidth + 200) )
|
|
{
|
|
ColumnWidth = ProgressionWidth + 200;
|
|
}
|
|
|
|
// Make sure it never goes smaller than the container
|
|
int ContainerWidth = VisualiserGridView.Width - VisualiserGridView.Columns[0].Width - 3;
|
|
if( ColumnWidth < ContainerWidth )
|
|
{
|
|
ColumnWidth = ContainerWidth;
|
|
}
|
|
|
|
// WINDOWS BUG: no width larger than 64k
|
|
if( ColumnWidth > 65536 )
|
|
{
|
|
ColumnWidth = 65536;
|
|
}
|
|
|
|
if( VisualiserGridView.Columns[1].Width != ColumnWidth )
|
|
{
|
|
VisualiserGridView.Columns[1].Width = ColumnWidth;
|
|
VisualiserGridView.Invalidate();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Draw a scale in a column header
|
|
*/
|
|
private const int TickBorder = 4;
|
|
|
|
private void DrawScale( DateTime StartTime, float ZoomLevel, Graphics Gfx, Rectangle Bounds )
|
|
{
|
|
int Offset = 0;
|
|
int Count = 1;
|
|
while( Offset < Bounds.Width )
|
|
{
|
|
Offset = ( int )( 15.0f * ZoomLevel * Count );
|
|
|
|
int Left = Bounds.Left + Offset;
|
|
int Width = 1;
|
|
int Top = Bounds.Top + TickBorder;
|
|
int Height = Bounds.Height - ( TickBorder * 2 );
|
|
|
|
if( ( Count & 3 ) != 0 )
|
|
{
|
|
Top += TickBorder;
|
|
Height -= TickBorder * 2;
|
|
}
|
|
|
|
Rectangle Bar = new Rectangle( Left, Top, Width, Height );
|
|
Gfx.DrawRectangle( Pens.Black, Bar );
|
|
|
|
Count++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Paint a cell using the progression data
|
|
*/
|
|
private void ProgressCellPaint( object sender, DataGridViewCellPaintingEventArgs e )
|
|
{
|
|
if( e.ColumnIndex <= 0 || e.RowIndex < -1 )
|
|
{
|
|
e.Handled = false;
|
|
return;
|
|
}
|
|
|
|
if( ProgressionData != null )
|
|
{
|
|
if( e.RowIndex == -1 )
|
|
{
|
|
e.PaintBackground( e.CellBounds, false );
|
|
DrawScale( ProgressionData.StartTime, AgentApplication.Options.VisualiserZoomLevel, e.Graphics, e.CellBounds );
|
|
}
|
|
else
|
|
{
|
|
string Machine = ( string )VisualiserGridView.Rows[e.RowIndex].Cells[0].Value;
|
|
Progression MachineProgression = null;
|
|
if( ProgressionData.MachineProgressions.TryGetValue( Machine, out MachineProgression ) )
|
|
{
|
|
BufferedGraphicsContext Current = BufferedGraphicsManager.Current;
|
|
BufferedGraphics Offscreen = Current.Allocate( e.Graphics, e.CellBounds );
|
|
|
|
// Fill the background dependent on whether the machine has finished or not
|
|
MachineProgression.DrawBackground( Offscreen.Graphics, e.CellBounds );
|
|
|
|
// Draw all the progress bars to the offscreen buffer
|
|
MachineProgression.Draw( ProgressionData.StartTime, AgentApplication.Options.VisualiserZoomLevel, Offscreen.Graphics, e.CellBounds );
|
|
|
|
// Copy the offscreen buffer to the screen
|
|
Offscreen.Render();
|
|
Offscreen.Dispose();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Just clear if there's no progression data
|
|
e.PaintBackground( e.CellBounds, false );
|
|
}
|
|
|
|
e.Handled = true;
|
|
}
|
|
|
|
private void MouseWheelHandler( object sender, MouseEventArgs e )
|
|
{
|
|
float ZoomLevel = AgentApplication.Options.VisualiserZoomLevel + ( e.Delta / 120.0f );
|
|
|
|
if( ZoomLevel < 1.0f )
|
|
{
|
|
ZoomLevel = 1.0f;
|
|
}
|
|
else if( ZoomLevel > 100.0f )
|
|
{
|
|
ZoomLevel = 100.0f;
|
|
}
|
|
|
|
AgentApplication.Options.VisualiserZoomLevel = ZoomLevel;
|
|
VisualiserGridView.Invalidate();
|
|
}
|
|
|
|
private void MouseMoveHandler(object sender, MouseEventArgs e)
|
|
{
|
|
Point GridClientPos = this.VisualiserGridView.PointToClient( MousePosition );
|
|
DataGridView.HitTestInfo HitInfo = this.VisualiserGridView.HitTest( GridClientPos.X, GridClientPos.Y );
|
|
bool bShowingToolTip = false;
|
|
if ( HitInfo.Type == DataGridViewHitTestType.ColumnHeader && HitInfo.ColumnIndex == 1 )
|
|
{
|
|
float Timestamp = GridClientPos.X - HitInfo.ColumnX;
|
|
Timestamp /= AgentApplication.Options.VisualiserZoomLevel;
|
|
Point AgentClientPos = PointToClient( MousePosition );
|
|
string ToolTipText = String.Format( "Time: {0:g4} seconds", Timestamp );
|
|
BarToolTip.Show(ToolTipText, this, AgentClientPos.X, AgentClientPos.Y, 60000 );
|
|
bShowingToolTip = true;
|
|
}
|
|
else if ( HitInfo.Type == DataGridViewHitTestType.Cell && HitInfo.ColumnIndex == 1 )
|
|
{
|
|
DataGridViewCell cell = this.VisualiserGridView.Rows[HitInfo.RowIndex].Cells[1];
|
|
string Machine = ( string )VisualiserGridView.Rows[HitInfo.RowIndex].Cells[0].Value;
|
|
Progression MachineProgression = null;
|
|
if ( ProgressionData.MachineProgressions.TryGetValue( Machine, out MachineProgression ) )
|
|
{
|
|
for( int BarIndex = 1; BarIndex < MachineProgression.ProgressionSamples.Count; BarIndex++ )
|
|
{
|
|
ProgressionSample Sample = MachineProgression.ProgressionSamples[BarIndex];
|
|
if ( Sample.Bounds.Contains(GridClientPos) )
|
|
{
|
|
Point AgentClientPos = PointToClient( MousePosition );
|
|
string BarName = BarNames[Sample.State.ToString()];
|
|
string ToolTipText = String.Format( "{0} ({1:g4} seconds)", BarName, Sample.Duration );
|
|
BarToolTip.Show(ToolTipText, this, AgentClientPos.X, AgentClientPos.Y, 60000 );
|
|
bShowingToolTip = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !bShowingToolTip )
|
|
{
|
|
BarToolTip.Hide( this );
|
|
}
|
|
}
|
|
|
|
private void MouseLeaveHandler(object sender, EventArgs e)
|
|
{
|
|
BarToolTip.Hide( this );
|
|
}
|
|
|
|
private void GridViewSelectionChanged( object sender, EventArgs e )
|
|
{
|
|
VisualiserGridView.ClearSelection();
|
|
}
|
|
|
|
private void GridViewResized( object sender, EventArgs e )
|
|
{
|
|
VisualiserGridViewResized = true;
|
|
}
|
|
|
|
private void OverallProgressBarPaint( object sender, PaintEventArgs e )
|
|
{
|
|
if( ProgressionData != null )
|
|
{
|
|
BufferedGraphicsContext Current = BufferedGraphicsManager.Current;
|
|
BufferedGraphics Offscreen = Current.Allocate( e.Graphics, OverallProgressBar.ClientRectangle );
|
|
|
|
ProgressionData.DrawOverallProgressBar( Offscreen.Graphics, OverallProgressBar.ClientRectangle );
|
|
|
|
// Copy the offscreen buffer to the screen
|
|
Offscreen.Render();
|
|
Offscreen.Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|