VL : WebSocketsSharp example

WebSocketsSharp is listed in the .NET NuGets list which ships with VL, so I thought I’d have a go at patching a WebSocketsSharp server.

Here’s where I’m at right now:

i’m trying to approach the code:

using System;
using WebSocketSharp;
using WebSocketSharp.Server;

namespace Example
{
  public class Laputa : WebSocketBehavior
  {
    protected override void OnMessage (MessageEventArgs e)
    {
      var msg = e.Data == "BALUS"
                ? "I've been balused already..."
                : "I'm not available now.";

      Send (msg);
    }
  }

  public class Program
  {
    public static void Main (string[] args)
    {
      var wssv = new WebSocketServer ("ws://dragonsnest.far");
      wssv.AddWebSocketService<Laputa> ("/Laputa");
      wssv.Start ();
      Console.ReadKey (true);
      wssv.Stop ();
    }
  }
}

Since inheritance is not available (and WebSocketBehavior is a base class not an interface) I’m not sure how I would define my own behaviour which I could pass to AddWebSocketService. Furthermore, how I might pass my custom class into the generic arguments of this function. Perhaps that’s why the error message shows up? Because such a pattern is currently impossible?

Or is there a way and I just dont know it :)

I’ve done something similiar last December and put that node called WebSocketServer in latest alpha build as an experimental version. I’ve also created a little help patch for it you’ll find in lib/packs/VL.CoreLib/help with more explanation and also known issues and ideas how it could get improved in general. But the help patch should work - tested with Chrome Simple WebSocket Client. You should be able to simply open it by dropping it onto vvvv.

So how was it done? Well yes, you can’t inherit from a base class in VL - you can only implement interfaces. So implementing an interface would’ve also been an option in the above mentioned node, but what I did was writing my own little Behavior implementation in C# which takes delegates where you get access to the Behavior (or session, don’t really like the naming) object. As you’ll see in the source code the classes introduce a type parameter TState - this is something special and I think not really documented feature in VL which allows you to program stateful regions in VL. So when you create that node the node browser will ask you as node or region, the region choice is there because the node takes delegates as inputs and the stateful choice is there because of that TState parameter which gets consumed by VL and replaced with an anonymous type which contains all the fields necessary to support patches making use of state - like putting a Counter or LFO inside, or simply storing something in a field. You’ll also see that in the help patch that the Behaviour object gets stored in a local field so later OnMessage calls still have access to it.

I’ll attach the source code of the node to this post because our CoreLib is still not public (other topic).

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WebSocketSharp;
using WebSocketSharp.Server;

namespace VL.Lib.IO.Socket
{
    public class WebSocketServer<TState> : IDisposable
    {
        public class Behavior : WebSocketBehavior
        {
            private readonly Timer FTimer;
            private readonly Func<Behavior, TState> FCreate;
            private readonly Func<TState, MessageEventArgs, TState> FUpdate;
            private readonly Action<TState> FDispose;
            private TState FState;
            private DateTime FLastMessage = DateTime.Now;

            public Behavior(int timeout, Func<Behavior, TState> create, Func<TState, MessageEventArgs, TState> update, Action<TState> dispose)
            {
                FCreate = create;
                FUpdate = update;
                FDispose = dispose;
                if (timeout > 0)
                {
                    FTimer = new Timer(_ =>
                    {
                        var elapsedTime = DateTime.Now - FLastMessage;
                        if (elapsedTime > TimeSpan.FromSeconds(timeout))
                            Sessions.CloseSession(ID);
                    }, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(timeout));
                }
            }

            public void SendString(string data) => base.Send(data);
            public new void Send(byte[] data) => base.Send(data);
            public new void SendAsync(byte[] data, Action<bool> completed) => base.SendAsync(data, completed);

            protected override void OnOpen()
            {
                FState = FCreate(this);
            }

            protected override void OnMessage(MessageEventArgs e)
            {
                FLastMessage = DateTime.Now;
                FState = FUpdate.Invoke(FState, e);
            }

            protected override void OnClose(CloseEventArgs e)
            {
                FTimer?.Dispose();
                FDispose(FState);
                base.OnClose(e);
            }
        }

        readonly WebSocketServer FServer;

        public WebSocketServer(
            string url,
            string path, 
            int timeout,
            Func<Behavior, TState> create, 
            Func<TState, MessageEventArgs, TState> update,
            Action<TState> dispose)
        {
            FServer = new WebSocketServer(url)
            {
                KeepClean = true,
            };
            FServer.AddWebSocketService(path, () => new Behavior(timeout, create, update, dispose));
            FServer.Start();
        }

        public void Dispose()
        {
            FServer.Stop();
        }
    }
}
1 Like

i don’t see it in the mentioned help folder, but tinkered a bit around with i. maybe that’s helpful

1 Like

Thanks, it’s in there now.

Thanks all for the very quick responses!

Concerning TState. It seems that TState is the generic parameter of your WebSocketserver class, and does not have any qualifications (e.g. : where TState : BaseState or something like that).

In this case, does VL look for the specific name TState within the generic arguments, and then understand what to do with it? Or it doesn’t actually matter what you call it on your side, so long as your class fits the signature?

I guess this will all come out in the documentation later, and for the time being there is enough of an example here to look into whenever I need it.

And thanks @sebl for the screenshot demonstrating clearly how this all works on the VL side!

It looks for that specific name TState - consider it a hack which worked fine so far ;)

1 Like

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.