Updated Remotery.
This commit is contained in:
Родитель
fd6ab494ff
Коммит
8914ad09e5
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -67,6 +67,11 @@ documented just below this comment.
|
|||
#define RMT_USE_OPENGL 0
|
||||
#endif
|
||||
|
||||
// Allow Metal profiling
|
||||
#ifndef RMT_USE_METAL
|
||||
#define RMT_USE_METAL 0
|
||||
#endif
|
||||
|
||||
// Initially use POSIX thread names to name threads instead of Thread0, 1, ...
|
||||
#ifndef RMT_USE_POSIX_THREADNAMES
|
||||
#define RMT_USE_POSIX_THREADNAMES 0
|
||||
|
@ -129,9 +134,14 @@ documented just below this comment.
|
|||
#define IFDEF_RMT_USE_D3D11(t, f) f
|
||||
#endif
|
||||
#if RMT_ENABLED && RMT_USE_OPENGL
|
||||
#define IFDEF_RMT_USE_OPENGL(t, f) t
|
||||
#define IFDEF_RMT_USE_OPENGL(t, f) t
|
||||
#else
|
||||
#define IFDEF_RMT_USE_OPENGL(t, f) f
|
||||
#define IFDEF_RMT_USE_OPENGL(t, f) f
|
||||
#endif
|
||||
#if RMT_ENABLED && RMT_USE_METAL
|
||||
#define IFDEF_RMT_USE_METAL(t, f) t
|
||||
#else
|
||||
#define IFDEF_RMT_USE_METAL(t, f) f
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -237,7 +247,7 @@ typedef enum rmtError
|
|||
RMT_ERROR_D3D11_FAILED_TO_CREATE_QUERY, // Failed to create query for sample
|
||||
|
||||
// OpenGL error messages
|
||||
RMT_ERROR_OPENGL_ERROR, // Generic OpenGL error, no real need to expose more detail since app will probably have an OpenGL error callback registered
|
||||
RMT_ERROR_OPENGL_ERROR, // Generic OpenGL error, no need to expose detail since app will need an OpenGL error callback registered
|
||||
|
||||
RMT_ERROR_CUDA_UNKNOWN,
|
||||
} rmtError;
|
||||
|
@ -424,6 +434,26 @@ typedef struct rmtCUDABind
|
|||
RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_EndOpenGLSample())
|
||||
|
||||
|
||||
#define rmt_BindMetal(command_buffer) \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, _rmt_BindMetal(command_buffer));
|
||||
|
||||
#define rmt_UnbindMetal() \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, _rmt_UnbindMetal());
|
||||
|
||||
#define rmt_BeginMetalSample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, { \
|
||||
static rmtU32 rmt_sample_hash_##name = 0; \
|
||||
_rmt_BeginMetalSample(#name, &rmt_sample_hash_##name); \
|
||||
})
|
||||
|
||||
#define rmt_BeginMetalSampleDynamic(namestr) \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, _rmt_BeginMetalSample(namestr, NULL))
|
||||
|
||||
#define rmt_EndMetalSample() \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, _rmt_EndMetalSample())
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
|
@ -485,6 +515,17 @@ struct rmt_EndOpenGLSampleOnScopeExit
|
|||
};
|
||||
#endif
|
||||
|
||||
#if RMT_USE_METAL
|
||||
extern "C" RMT_API void _rmt_EndMetalSample(void);
|
||||
struct rmt_EndMetalSampleOnScopeExit
|
||||
{
|
||||
~rmt_EndMetalSampleOnScopeExit()
|
||||
{
|
||||
_rmt_EndMetalSample();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -502,6 +543,9 @@ struct rmt_EndOpenGLSampleOnScopeExit
|
|||
#define rmt_ScopedOpenGLSample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, rmt_BeginOpenGLSample(name)); \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, rmt_EndOpenGLSampleOnScopeExit rmt_ScopedOpenGLSample##name);
|
||||
#define rmt_ScopedMetalSample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, rmt_BeginMetalSample(name)); \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, rmt_EndMetalSampleOnScopeExit rmt_ScopedMetalSample##name);
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -553,10 +597,23 @@ RMT_API void _rmt_BeginOpenGLSample(rmtPStr name, rmtU32* hash_cache);
|
|||
RMT_API void _rmt_EndOpenGLSample(void);
|
||||
#endif
|
||||
|
||||
#if RMT_USE_METAL
|
||||
RMT_API void _rmt_BeginMetalSample(rmtPStr name, rmtU32* hash_cache);
|
||||
RMT_API void _rmt_EndMetalSample(void);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#if RMT_USE_METAL
|
||||
#ifdef __OBJC__
|
||||
RMT_API void _rmt_BindMetal(id command_buffer);
|
||||
RMT_API void _rmt_UnbindMetal();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // RMT_ENABLED
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
#include <Foundation/NSThread.h>
|
||||
#include <Foundation/NSDictionary.h>
|
||||
#include <Foundation/NSString.h>
|
||||
|
||||
#import <Metal/Metal.h>
|
||||
|
||||
|
||||
// Store command buffer in thread-local so that each thread can point to its own
|
||||
void SetCommandBuffer(id command_buffer)
|
||||
{
|
||||
NSMutableDictionary* thread_data = [[NSThread currentThread] threadDictionary];
|
||||
thread_data[@"rmtMTLCommandBuffer"] = command_buffer;
|
||||
}
|
||||
id GetCommandBuffer()
|
||||
{
|
||||
NSMutableDictionary* thread_data = [[NSThread currentThread] threadDictionary];
|
||||
return thread_data[@"rmtMTLCommandBuffer"];
|
||||
}
|
||||
|
||||
|
||||
void _rmt_BindMetal(id command_buffer)
|
||||
{
|
||||
SetCommandBuffer(command_buffer);
|
||||
}
|
||||
|
||||
|
||||
void _rmt_UnbindMetal()
|
||||
{
|
||||
SetCommandBuffer(0);
|
||||
}
|
||||
|
||||
|
||||
// Needs to be in the same lib for this to work
|
||||
unsigned long long rmtMetal_usGetTime();
|
||||
|
||||
|
||||
static void SetTimestamp(void* data)
|
||||
{
|
||||
*((unsigned long long*)data) = rmtMetal_usGetTime();
|
||||
}
|
||||
|
||||
|
||||
void rmtMetal_MeasureCommandBuffer(unsigned long long* out_start, unsigned long long* out_end, unsigned int* out_ready)
|
||||
{
|
||||
id command_buffer = GetCommandBuffer();
|
||||
[command_buffer addScheduledHandler:^(id <MTLCommandBuffer>){ SetTimestamp(out_start); }];
|
||||
[command_buffer addCompletedHandler:^(id <MTLCommandBuffer>){ SetTimestamp(out_end); *out_ready = 1; }];
|
||||
}
|
|
@ -23,7 +23,7 @@ Compiling
|
|||
directories to add Remotery/lib path. The required library ws2_32.lib should be picked
|
||||
up through the use of the #pragma comment(lib, "ws2_32.lib") directive in Remotery.c.
|
||||
|
||||
* Mac OS X (XCode) - simply add lib/Remotery.c and lib/Remotery.h to your program.
|
||||
* Mac OS X (XCode) - simply add lib/Remotery.c, lib/Remotery.h and lib/Remotery.mm to your program.
|
||||
|
||||
* Linux (GCC) - add the source in lib folder. Compilation of the code requires -pthreads for
|
||||
library linkage. For example to compile the same run: cc lib/Remotery.c sample/sample.c
|
||||
|
@ -37,7 +37,8 @@ You can define some extra macros to modify what features are compiled into Remot
|
|||
RMT_USE_TINYCRT 0 Used by the Celtoys TinyCRT library (not released yet)
|
||||
RMT_USE_CUDA 0 Assuming CUDA headers/libs are setup, allow CUDA profiling
|
||||
RMT_USE_D3D11 0 Assuming Direct3D 11 headers/libs are setup, allow D3D11 GPU profiling
|
||||
RMT_USE_OPENGL 0 Allow OpenGL GPU profiling (standalone except you must link to OpenGL which you already do if you use it)
|
||||
RMT_USE_OPENGL 0 Allow OpenGL GPU profiling (dynamically links OpenGL libraries on available platforms)
|
||||
RMT_USE_METAL 0 Allow Metal profiling of command buffers
|
||||
|
||||
|
||||
Basic Use
|
||||
|
@ -176,6 +177,24 @@ your OpenGL device and context, ensure you notify Remotery before shutting down
|
|||
rmt_UnbindOpenGL();
|
||||
|
||||
|
||||
Sampling Metal GPU activity
|
||||
---------------------------
|
||||
|
||||
Remotery can sample Metal command buffers issued to the GPU from multiple threads. As the Metal API does not
|
||||
support finer grained profiling, samples will return only the timing of the bound command buffer, irrespective
|
||||
of how many you issue. As such, make sure you bind and sample the command buffer for each call site:
|
||||
|
||||
rmt_BindMetal(mtl_command_buffer);
|
||||
rmt_ScopedMetalSample(command_buffer_name);
|
||||
|
||||
The C API supports begin/end also:
|
||||
|
||||
rmt_BindMetal(mtl_command_buffer);
|
||||
rmt_BeginMetalSample(command_buffer_name);
|
||||
...
|
||||
rmt_EndMetalSample();
|
||||
|
||||
|
||||
Applying Configuration Settings
|
||||
-------------------------------
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ Console = (function()
|
|||
// Setup log requests from the server
|
||||
this.Server = server;
|
||||
server.SetConsole(this);
|
||||
server.AddMessageHandler("LOG", Bind(OnLog, this));
|
||||
server.AddMessageHandler("LOGM", Bind(OnLog, this));
|
||||
}
|
||||
|
||||
|
||||
|
@ -65,9 +65,11 @@ Console = (function()
|
|||
}
|
||||
|
||||
|
||||
function OnLog(self, socket, message)
|
||||
function OnLog(self, socket, data_view)
|
||||
{
|
||||
self.AppTextBuffer = LogText(self.AppTextBuffer, message.text);
|
||||
var data_view_reader = new DataViewReader(data_view, 4);
|
||||
var text = data_view_reader.GetString();
|
||||
self.AppTextBuffer = LogText(self.AppTextBuffer, text);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
|
||||
//
|
||||
// Simple wrapper around DataView that auto-advances the read offset and provides
|
||||
// a few common data type conversions specific to this app
|
||||
//
|
||||
DataViewReader = (function ()
|
||||
{
|
||||
function DataViewReader(data_view, offset)
|
||||
{
|
||||
this.DataView = data_view;
|
||||
this.Offset = offset;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetUInt32 = function ()
|
||||
{
|
||||
var v = this.DataView.getUint32(this.Offset, true);
|
||||
this.Offset += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetUInt64 = function ()
|
||||
{
|
||||
var v = this.DataView.getFloat64(this.Offset, true);
|
||||
this.Offset += 8;
|
||||
return v;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetStringOfLength = function (string_length)
|
||||
{
|
||||
var string = "";
|
||||
for (var i = 0; i < string_length; i++)
|
||||
{
|
||||
string += String.fromCharCode(this.DataView.getInt8(this.Offset));
|
||||
this.Offset++;
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetString = function ()
|
||||
{
|
||||
var string_length = this.GetUInt32();
|
||||
return this.GetStringOfLength(string_length);
|
||||
}
|
||||
|
||||
return DataViewReader;
|
||||
})();
|
|
@ -20,12 +20,37 @@ Settings = (function()
|
|||
|
||||
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();
|
||||
|
||||
this.ConnectionAddress = LocalStore.Get("App", "Global", "ConnectionAddress", "ws://127.0.0.1:17815/rmt");
|
||||
// "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));
|
||||
|
||||
|
@ -43,8 +68,10 @@ Remotery = (function()
|
|||
this.SampleWindows = { };
|
||||
this.FrameHistory = { };
|
||||
this.SelectedFrames = { };
|
||||
this.NameMap = { };
|
||||
|
||||
this.Server.AddMessageHandler("SAMPLES", Bind(OnSamples, this));
|
||||
this.Server.AddMessageHandler("SMPL", Bind(OnSamples, this));
|
||||
this.Server.AddMessageHandler("SSMP", Bind(OnSampleName, this));
|
||||
|
||||
// Kick-off the auto-connect loop
|
||||
AutoConnect(this);
|
||||
|
@ -80,10 +107,6 @@ Remotery = (function()
|
|||
{
|
||||
// Connection address has been validated
|
||||
LocalStore.Set("App", "Global", "ConnectionAddress", self.ConnectionAddress);
|
||||
|
||||
self.TimelineWindow.ResetTimeRange();
|
||||
self.FrameHistory = { };
|
||||
self.SelectedFrames = { };
|
||||
}
|
||||
|
||||
|
||||
|
@ -115,7 +138,8 @@ Remotery = (function()
|
|||
|
||||
// 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)
|
||||
// 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();
|
||||
|
||||
|
@ -123,15 +147,74 @@ Remotery = (function()
|
|||
}
|
||||
|
||||
|
||||
function OnSamples(self, socket, message)
|
||||
function DecodeSample(self, data_view_reader)
|
||||
{
|
||||
var name = message.thread_name;
|
||||
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 = sample.name_hash;
|
||||
self.Server.Send("GSMP" + sample.name);
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
// 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;
|
||||
return;
|
||||
|
||||
// Add to frame history for this thread
|
||||
// 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] = [ ];
|
||||
|
@ -142,7 +225,7 @@ Remotery = (function()
|
|||
var max_nb_frames = 10000;
|
||||
var extra_frames = frame_history.length - max_nb_frames;
|
||||
if (extra_frames > 0)
|
||||
frame_history.splice(0, extra_frames);
|
||||
frame_history.splice(0, extra_frames);
|
||||
|
||||
// Create sample windows on-demand
|
||||
if (!(name in self.SampleWindows))
|
||||
|
@ -159,6 +242,16 @@ Remotery = (function()
|
|||
}
|
||||
|
||||
|
||||
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] = name;
|
||||
}
|
||||
|
||||
|
||||
function OnTimelineCheck(self, name, evt)
|
||||
{
|
||||
// Show/hide the equivalent sample window and move all the others to occupy any left-over space
|
||||
|
|
|
@ -72,7 +72,7 @@ SampleWindow = (function()
|
|||
if (this.SampleDigest != sample_digest)
|
||||
{
|
||||
this.RootRow.Rows.ClearIndex("_ID");
|
||||
var index = UpdateSamples(this.RootRow, samples, 0, "");
|
||||
var index = UpdateAllSampleFields(this.RootRow, samples, 0, "");
|
||||
this.SampleDigest = sample_digest;
|
||||
|
||||
// Clear out any left-over rows
|
||||
|
@ -85,8 +85,8 @@ SampleWindow = (function()
|
|||
|
||||
else if (this.Visible)
|
||||
{
|
||||
// Otherwise just update the existing sample times
|
||||
UpdateSampleTimes(this.RootRow, samples);
|
||||
// Otherwise just update the existing sample fields
|
||||
UpdateChangedSampleFields(this.RootRow, samples, "");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ SampleWindow = (function()
|
|||
{
|
||||
var cell_data =
|
||||
{
|
||||
_ID: i,
|
||||
_ID: i,
|
||||
Name: "",
|
||||
Control: new WM.Label()
|
||||
};
|
||||
|
@ -114,7 +114,7 @@ SampleWindow = (function()
|
|||
}
|
||||
|
||||
|
||||
function UpdateSamples(parent_row, samples, index, indent)
|
||||
function UpdateAllSampleFields(parent_row, samples, index, indent)
|
||||
{
|
||||
for (var i in samples)
|
||||
{
|
||||
|
@ -131,21 +131,24 @@ SampleWindow = (function()
|
|||
row.CellData._ID = sample.id;
|
||||
parent_row.Rows.AddRowToIndex("_ID", sample.id, row);
|
||||
|
||||
// Set sample name and colour
|
||||
// Record sample name for later comparison
|
||||
row.CellData.Name = sample.name;
|
||||
|
||||
// Set sample name and colour
|
||||
var name_node = row.CellNodes["Name"];
|
||||
name_node.innerHTML = indent + sample.name;
|
||||
DOM.Node.SetColour(name_node, sample.colour);
|
||||
|
||||
row.CellData.Control.SetText(sample.us_length);
|
||||
|
||||
index = UpdateSamples(parent_row, sample.children, index, indent + " ");
|
||||
index = UpdateAllSampleFields(parent_row, sample.children, index, indent + " ");
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
function UpdateSampleTimes(parent_row, samples)
|
||||
function UpdateChangedSampleFields(parent_row, samples, indent)
|
||||
{
|
||||
for (var i in samples)
|
||||
{
|
||||
|
@ -153,9 +156,20 @@ SampleWindow = (function()
|
|||
|
||||
var row = parent_row.Rows.GetBy("_ID", sample.id);
|
||||
if (row)
|
||||
row.CellData.Control.SetText(sample.us_length);
|
||||
{
|
||||
row.CellData.Control.SetText(sample.us_length);
|
||||
|
||||
UpdateSampleTimes(parent_row, sample.children);
|
||||
// Sample name will change when it switches from hash ID to network-retrieved
|
||||
// name. Quickly check that before re-applying the HTML for the name.
|
||||
if (row.CellData.Name != sample.name)
|
||||
{
|
||||
var name_node = row.CellNodes["Name"];
|
||||
row.CellData.Name = sample.name;
|
||||
name_node.innerHTML = indent + sample.name;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateChangedSampleFields(parent_row, sample.children, indent + " ");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -356,8 +356,9 @@ TimelineRow = (function()
|
|||
ctx.strokeRect(offset_x + 0.5, offset_y + 0.5, size_x - 1, size_y - 1);
|
||||
}
|
||||
|
||||
// Draw sample names clipped to the bounds of the sample
|
||||
if (draw_text)
|
||||
// Draw sample names clipped to the bounds of the sample
|
||||
// Also reject tiny samples with no space to render text
|
||||
if (draw_text && size_x > 8)
|
||||
{
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
|
|
|
@ -52,6 +52,7 @@ WebSocketConnection = (function()
|
|||
Log(this, "Connecting to " + address);
|
||||
|
||||
this.Socket = new WebSocket(address);
|
||||
this.Socket.binaryType = "arraybuffer";
|
||||
this.Socket.onopen = Bind(OnOpen, this);
|
||||
this.Socket.onmessage = Bind(OnMessage, this);
|
||||
this.Socket.onclose = Bind(OnClose, this);
|
||||
|
@ -80,13 +81,13 @@ WebSocketConnection = (function()
|
|||
}
|
||||
|
||||
|
||||
function CallMessageHandlers(self, message_name, message)
|
||||
function CallMessageHandlers(self, message_name, data_view)
|
||||
{
|
||||
if (message_name in self.MessageHandlers)
|
||||
{
|
||||
var handlers = self.MessageHandlers[message_name];
|
||||
for (var i in handlers)
|
||||
handlers[i](self, message);
|
||||
handlers[i](self, data_view);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,9 +121,15 @@ WebSocketConnection = (function()
|
|||
|
||||
function OnMessage(self, event)
|
||||
{
|
||||
var message = JSON.parse(event.data);
|
||||
if ("id" in message)
|
||||
CallMessageHandlers(self, message.id, message);
|
||||
var data_view = new DataView(event.data);
|
||||
|
||||
var id = String.fromCharCode(
|
||||
data_view.getInt8(0),
|
||||
data_view.getInt8(1),
|
||||
data_view.getInt8(2),
|
||||
data_view.getInt8(3));
|
||||
|
||||
CallMessageHandlers(self, id, data_view);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/Button.js"></script>
|
||||
|
||||
<!-- Main Application -->
|
||||
<script type="text/javascript" src="Code/DataViewReader.js"></script>
|
||||
<script type="text/javascript" src="Code/Console.js"></script>
|
||||
<script type="text/javascript" src="Code/WebSocketConnection.js"></script>
|
||||
<script type="text/javascript" src="Code/TitleWindow.js"></script>
|
||||
|
|
Загрузка…
Ссылка в новой задаче