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();
}
}
}