338 lines
11 KiB
JavaScript
338 lines
11 KiB
JavaScript
|
|
//
|
|
// TODO: Window resizing needs finer-grain control
|
|
// TODO: Take into account where user has moved the windows
|
|
// TODO: Controls need automatic resizing within their parent windows
|
|
//
|
|
|
|
|
|
Settings = (function()
|
|
{
|
|
function Settings()
|
|
{
|
|
this.IsPaused = false;
|
|
}
|
|
|
|
return Settings;
|
|
|
|
})();
|
|
|
|
|
|
Remotery = (function()
|
|
{
|
|
// crack the url and get the parameter we want
|
|
var getUrlParameter = function getUrlParameter( search_param)
|
|
{
|
|
var page_url = decodeURIComponent( window.location.search.substring(1) ),
|
|
url_vars = page_url.split('&'),
|
|
param_name,
|
|
i;
|
|
|
|
for (i = 0; i < url_vars.length; i++)
|
|
{
|
|
param_name = url_vars[i].split('=');
|
|
|
|
if (param_name[0] === search_param)
|
|
{
|
|
return param_name[1] === undefined ? true : param_name[1];
|
|
}
|
|
}
|
|
};
|
|
|
|
function Remotery()
|
|
{
|
|
this.WindowManager = new WM.WindowManager();
|
|
this.Settings = new Settings();
|
|
|
|
// "addr" param is ip:port and will override the local store version if passed in the URL
|
|
var addr = getUrlParameter( "addr" );
|
|
if ( addr != null )
|
|
this.ConnectionAddress = "ws://" + addr + "/rmt";
|
|
else
|
|
this.ConnectionAddress = LocalStore.Get("App", "Global", "ConnectionAddress", "ws://127.0.0.1:17815/rmt");
|
|
|
|
this.Server = new WebSocketConnection();
|
|
this.Server.AddConnectHandler(Bind(OnConnect, this));
|
|
|
|
// Create the console up front as everything reports to it
|
|
this.Console = new Console(this.WindowManager, this.Server);
|
|
|
|
// Create required windows
|
|
this.TitleWindow = new TitleWindow(this.WindowManager, this.Settings, this.Server, this.ConnectionAddress);
|
|
this.TitleWindow.SetConnectionAddressChanged(Bind(OnAddressChanged, this));
|
|
this.TimelineWindow = new TimelineWindow(this.WindowManager, this.Settings, this.Server, Bind(OnTimelineCheck, this));
|
|
this.TimelineWindow.SetOnHover(Bind(OnSampleHover, this));
|
|
this.TimelineWindow.SetOnSelected(Bind(OnSampleSelected, this));
|
|
|
|
this.NbSampleWindows = 0;
|
|
this.SampleWindows = { };
|
|
this.FrameHistory = { };
|
|
this.SelectedFrames = { };
|
|
this.NameMap = { };
|
|
|
|
this.Server.AddMessageHandler("SMPL", Bind(OnSamples, this));
|
|
this.Server.AddMessageHandler("SSMP", Bind(OnSampleName, this));
|
|
|
|
// Kick-off the auto-connect loop
|
|
AutoConnect(this);
|
|
|
|
// Hook up resize event handler
|
|
DOM.Event.AddHandler(window, "resize", Bind(OnResizeWindow, this));
|
|
OnResizeWindow(this);
|
|
|
|
// Hook up browser-native canvas refresh
|
|
this.DisplayFrame = 0;
|
|
this.LastKnownPause = this.Settings.IsPaused;
|
|
var self = this;
|
|
(function display_loop()
|
|
{
|
|
window.requestAnimationFrame(display_loop);
|
|
DrawTimeline(self);
|
|
})();
|
|
}
|
|
|
|
|
|
function AutoConnect(self)
|
|
{
|
|
// Only attempt to connect if there isn't already a connection or an attempt to connect
|
|
if (!self.Server.Connected())
|
|
self.Server.Connect(self.ConnectionAddress);
|
|
|
|
// Always schedule another check
|
|
window.setTimeout(Bind(AutoConnect, self), 2000);
|
|
}
|
|
|
|
|
|
function OnConnect(self)
|
|
{
|
|
// Connection address has been validated
|
|
LocalStore.Set("App", "Global", "ConnectionAddress", self.ConnectionAddress);
|
|
}
|
|
|
|
|
|
function OnAddressChanged(self, node)
|
|
{
|
|
// Update and disconnect, relying on auto-connect to reconnect
|
|
self.ConnectionAddress = node.value;
|
|
self.Server.Disconnect();
|
|
|
|
// Give input focus away
|
|
return false;
|
|
}
|
|
|
|
|
|
function DrawTimeline(self)
|
|
{
|
|
// Has pause state changed?
|
|
if (self.Settings.IsPaused != self.LastKnownPaused)
|
|
{
|
|
// When switching TO paused, draw one last frame to ensure the sample text gets drawn
|
|
self.LastKnownPaused = self.Settings.IsPaused;
|
|
self.TimelineWindow.DrawAllRows();
|
|
return;
|
|
}
|
|
|
|
// Don't waste time drawing the timeline when paused
|
|
if (self.Settings.IsPaused)
|
|
return;
|
|
|
|
// requestAnimationFrame can run up to 60hz which is way too much for drawing the timeline
|
|
// Assume it's running at 60hz and skip frames to achieve 10hz instead
|
|
// Doing this instead of using setTimeout because it's better for browser rendering (or; will be once WebGL is in use)
|
|
// TODO: Expose as config variable because high refresh rate is great when using a separate viewiing machine
|
|
if ((self.DisplayFrame % 10) == 0)
|
|
self.TimelineWindow.DrawAllRows();
|
|
|
|
self.DisplayFrame++;
|
|
}
|
|
|
|
|
|
function DecodeSample(self, data_view_reader)
|
|
{
|
|
var sample = {};
|
|
|
|
// Get name hash and lookup name it map
|
|
sample.name_hash = data_view_reader.GetUInt32();
|
|
sample.name = self.NameMap[sample.name_hash];
|
|
|
|
// If the name doesn't exist in the map yet, request it from the server
|
|
if (sample.name == undefined)
|
|
{
|
|
// Meanwhile, store the hash as the name
|
|
sample.name = { "string": sample.name_hash };
|
|
self.NameMap[sample.name_hash] = sample.name;
|
|
self.Server.Send("GSMP" + sample.name_hash);
|
|
}
|
|
|
|
// Get the rest of the sample data
|
|
sample.id = data_view_reader.GetUInt32();
|
|
sample.colour = data_view_reader.GetStringOfLength(7);
|
|
sample.us_start = data_view_reader.GetUInt64();
|
|
sample.us_length = data_view_reader.GetUInt64();
|
|
sample.us_self = data_view_reader.GetUInt64();
|
|
sample.call_count = data_view_reader.GetUInt32();
|
|
sample.recurse_depth = data_view_reader.GetUInt32();
|
|
|
|
// Calculate dependent properties
|
|
sample.ms_length = (sample.us_length / 1000.0).toFixed(3);
|
|
sample.ms_self = (sample.us_self / 1000.0).toFixed(3);
|
|
|
|
// Recurse into children
|
|
sample.children = [];
|
|
DecodeSampleArray(self, data_view_reader, sample.children);
|
|
|
|
return sample;
|
|
}
|
|
|
|
|
|
function DecodeSampleArray(self, data_view_reader, samples)
|
|
{
|
|
var nb_samples = data_view_reader.GetUInt32();
|
|
for (var i = 0; i < nb_samples; i++)
|
|
{
|
|
var sample = DecodeSample(self, data_view_reader);
|
|
samples.push(sample)
|
|
}
|
|
}
|
|
|
|
|
|
function DecodeSamples(self, data_view_reader)
|
|
{
|
|
// Message-specific header
|
|
var message = { };
|
|
message.thread_name = data_view_reader.GetString();
|
|
message.nb_samples = data_view_reader.GetUInt32();
|
|
message.sample_digest = data_view_reader.GetUInt32();
|
|
|
|
// Read samples
|
|
message.samples = [];
|
|
message.samples.push(DecodeSample(self, data_view_reader));
|
|
|
|
return message;
|
|
}
|
|
|
|
|
|
function OnSamples(self, socket, data_view)
|
|
{
|
|
// Discard any new samples while paused
|
|
if (self.Settings.IsPaused)
|
|
return;
|
|
|
|
// Binary decode incoming sample data
|
|
var message = DecodeSamples(self, new DataViewReader(data_view, 8));
|
|
var name = message.thread_name;
|
|
|
|
// Add to frame history for this thread
|
|
var thread_frame = new ThreadFrame(message);
|
|
if (!(name in self.FrameHistory))
|
|
self.FrameHistory[name] = [ ];
|
|
var frame_history = self.FrameHistory[name];
|
|
frame_history.push(thread_frame);
|
|
|
|
// Discard old frames to keep memory-use constant
|
|
var max_nb_frames = 10000;
|
|
var extra_frames = frame_history.length - max_nb_frames;
|
|
if (extra_frames > 0)
|
|
frame_history.splice(0, extra_frames);
|
|
|
|
// Create sample windows on-demand
|
|
if (!(name in self.SampleWindows))
|
|
{
|
|
self.SampleWindows[name] = new SampleWindow(self.WindowManager, name, self.NbSampleWindows);
|
|
self.SampleWindows[name].WindowResized(self.TimelineWindow.Window, self.Console.Window);
|
|
self.NbSampleWindows++;
|
|
MoveSampleWindows(this);
|
|
}
|
|
|
|
// Set on the window and timeline
|
|
self.SampleWindows[name].OnSamples(message.nb_samples, message.sample_digest, message.samples);
|
|
self.TimelineWindow.OnSamples(name, frame_history);
|
|
}
|
|
|
|
|
|
function OnSampleName(self, socket, data_view)
|
|
{
|
|
// Add any names sent by the server to the local map
|
|
var data_view_reader = new DataViewReader(data_view, 4);
|
|
var name_hash = data_view_reader.GetUInt32();
|
|
var name = data_view_reader.GetString();
|
|
self.NameMap[name_hash].string = name;
|
|
}
|
|
|
|
|
|
function OnTimelineCheck(self, name, evt)
|
|
{
|
|
// Show/hide the equivalent sample window and move all the others to occupy any left-over space
|
|
var target = DOM.Event.GetNode(evt);
|
|
self.SampleWindows[name].SetVisible(target.checked);
|
|
MoveSampleWindows(self);
|
|
}
|
|
|
|
|
|
function MoveSampleWindows(self)
|
|
{
|
|
// Stack all windows next to each other
|
|
var xpos = 0;
|
|
for (var i in self.SampleWindows)
|
|
{
|
|
var sample_window = self.SampleWindows[i];
|
|
if (sample_window.Visible)
|
|
sample_window.SetXPos(xpos++, self.TimelineWindow.Window, self.Console.Window);
|
|
}
|
|
}
|
|
|
|
|
|
function OnSampleHover(self, thread_name, hover)
|
|
{
|
|
// Hover only changes sample window contents when paused
|
|
var sample_window = self.SampleWindows[thread_name];
|
|
if (sample_window && self.Settings.IsPaused)
|
|
{
|
|
if (hover == null)
|
|
{
|
|
// When there's no hover, go back to the selected frame
|
|
if (self.SelectedFrames[thread_name])
|
|
{
|
|
var frame = self.SelectedFrames[thread_name];
|
|
sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples);
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
// Populate with sample under hover
|
|
var frame = hover[0];
|
|
sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function OnSampleSelected(self, thread_name, select)
|
|
{
|
|
// Lookup sample window set the frame samples on it
|
|
if (select && thread_name in self.SampleWindows)
|
|
{
|
|
var sample_window = self.SampleWindows[thread_name];
|
|
var frame = select[0];
|
|
self.SelectedFrames[thread_name] = frame;
|
|
sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples);
|
|
}
|
|
}
|
|
|
|
|
|
function OnResizeWindow(self)
|
|
{
|
|
// Resize windows
|
|
var w = window.innerWidth;
|
|
var h = window.innerHeight;
|
|
self.Console.WindowResized(w, h);
|
|
self.TitleWindow.WindowResized(w, h);
|
|
self.TimelineWindow.WindowResized(w, h, self.TitleWindow.Window);
|
|
for (var i in self.SampleWindows)
|
|
self.SampleWindows[i].WindowResized(self.TimelineWindow.Window, self.Console.Window);
|
|
}
|
|
|
|
|
|
return Remotery;
|
|
})(); |