Web socket component

Hi all,

I’m trying to make a component to receive data from a web socket. The code of the current version is very simple and it actually works, but in a strange way; so I’d be glad for any insights on its behavior.

The component has a text input parameter for the url (as an item), and a text output, also as a single string.
It uses the WebSocketSharp library to connect and manage the stream.
This is the code responsible for the connection and data output:

WebSocket ws = new WebSocket(@url);
ws.Connect();
ws.OnMessage += (sender3, e3) => DA.SetData(0, e3.Data);

And here is a larger part of the component code: https://pastebin.com/W21fshJJ

The main issue is that although the data does change in the output parameter when receiving new messages from the web socket, the solution itself isn’t recomputed, so other components do not receive an updated data.
I was able to solve this by adding a “Data” component connected to the output of this component, and refreshing it with a timer every second or so. But I suspect there should be a better solution.

The other problem is that the output data is not a single item, as it’s supposed to be, but a tree with two branches: first branch with a single item, and the second branch with the text received from the web socket.
The second weird solution was to disable the component after it has connected to the web socket stream - this way it keeps receiving messages, and the output of the component is a single string.

Here is an example of how it looks in GH:

So, the component does work, and can already be useful, but I’d be glad to avoid awkward solutions to these two issues.

Thanks,
Michael

1 Like

You are assigning e3.Data to the output after the component has been updated, so you need to update it again when you receive the message, something like

ws.OnMessage += (sender3, e3) => { DA.SetData(0, e3.Data); this.ExpireSolution(true); }

but keep in mind that the component (and its downstream components) will run once before receiving the message. Then you have to handle this or prevent the component from completing SolveInstance until it receives the message.
The second strange behavior I think is because you are assigning to the output a data in a phase outside the component’s native flow (after it is updated), leaving behind all the internal processes it needs to do to complete the new update. @DavidRutten can help you hack into this in the right way.

I would assign the retrieved data to a class level variable inside the component, assign it from that variable to the correct output from within SolveInstance, and make the OnMessage callback first assign the data and then call ExpireSolution(true);

Hi @Dani_Abalde and @DavidRutten, thank you for prompt reply, works perfect so far!
In addition to the message (string) variable I declared two other variables outside SolveInstance, to prevent the component from creating a new web socket connection each time it’s recomputed:

  1. The WebSocket object; so it’s created just once.
  2. A boolean that indicates if the connection to web socket has been established already
        string message = "temp";
        bool isWs;
        WebSocket ws;

        protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            pManager.AddTextParameter("URL", "url", "Web socket url", GH_ParamAccess.item);
        }

        protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
        {
            pManager.AddTextParameter("D", "Data", "", GH_ParamAccess.item);
        }

        protected override void SolveInstance(IGH_DataAccess DA)
        {
            string url = "";
            if (!isWs)
            {
                if (!DA.GetData(0, ref url)) return;
                ws = new WebSocket(@url);
                ws.Connect();
                isWs = true;
                ws.OnMessage += (sender, e) =>
                {
                    message = e.Data;
                    this.ExpireSolution(true);
                };
                ws.OnClose += (sender, e) => ws.Connect();
            }
            DA.SetData(0, message);
        }

Thanks for your advises!

2 Likes

Well, there is another bug happening after the changes -
I have a panel connected to the output of the web socket. The data in the panel is updated after every new message from the web socket (as expected), but if I’m moving or resizing the panel while it is receiving new data it causes GH to crash after a few seconds:
First alert:


Second alert:

…and then RSOD (GH version of BSOD :slightly_smiling_face:)
*as you can see, even after the errors the WS stream is still connected and the panel text is updated.

I thought this might be caused by too frequent updates of the data from WS. Does it make sense?
If so, will it help to store the data in a variable inside the component on every web socket message, but to output it in regular intervals, let’s say, every 100-500 ms?

Thanks,
Michael

To be fair the RSOD is really the RSOSYFN*. It’s a protective measure which can be disabled from the View or display menu (can never remember which one).

And is this happening on the UI thread, or are you just calling methods on whatever thread the message came in on?

* The red screen of save-your-file-now!

Not really. The computer cares about the ordering of events, but not how quickly they follow each other. It is possible that rapid succession of some events may highlight a problem that was there all along though.

I wasn’t able to find this in any of the menus, however, I’m not sure it’s a good idea to disable protective stuff.

I guess it’s the second option, but it seems to be beyond my knowledge in gh development.
In general: OnMessage event stores the message text in a component class variable and calls ExpireSolution. Then, the data from the class variable is sent to the first output in the next solution.

The first might lead to problems due to non-atomic writes/reads of that class level variable, this can be solved by putting a lock on an object every time you write or read the data (it’ll be atomic if you’re assigning single class references, it won’t be if you’re assigning more than one, or value-types whose memory layout exceeds 64-bits).

The latter is just in general a really bad idea if you’re not on the UI thread.

Here’s what I recommend for access to your class level variables:

private readonly object _lock = new object();
private string _socketData1;
private string _socketData2;
private int[] _socketData3;

// inside the socket message handler:
lock (_lock)
{
  _socketData1 = socket.GetData1();
  _socketData2 = socket.GetData2();
  _socketData3 = socket.GetData3();
}

// inside SolveInstance():
lock (_lock)
{
  DA.SetData(0, _socketData1);
  DA.SetData(1, _socketData2);
  DA.SetDataList(2, _socketData3);
}

As for the expiration, you’re probably better off scheduling a solution on the GH_Document a few milliseconds in the future. You’ll have to provide a callback for the schedule so you can expire your component before it happens:

// Inside the socket message handler:
GH_Document doc = OnPingDocument();
if (doc != null)
  doc.ScheduleSolution(10, ScheduleCallback);

// your callback delegate.
private void ScheduleCallback(GH_Document doc)
{
  ExpireSolution(false);
}

Thanks again David, you are a genius!
Although the debugging in this case is just dragging a panel around the canvas hoping it won’t crash, the issue seems to be solved.

1 Like

Should we Invoke the processing method on UI thread in OnMessage routine?

Which processing method? If you call methods on Grasshopper that change the state of the application, such as ExpireSolution(), then you need to make sure you’re doing so on the UI thread. You can use Rhino.RhinoApp.InvokeOnUiThread for this.

The only method in Grasshopper that doesn’t suffer from this thread-cross-contamination is GH_Document.ScheduleSolution.