Skip to content

Is it better to have a Resolver per Inlet object instead of a "master" Resolver Monobehaviour?  #63

Description

@LiamOverett-AMRC

Other implementaions of liblsl (e.g. pylsl, liblsl-Matlab and liblsl-rust say that if you're looking for a specific stream, the prefered method is to use resolve_byprop().

"""Resolve all streams with a specific value for a given property.

If the goal is to resolve a specific stream, this method is preferred over
resolving all streams and then selecting the desired one.

Keyword arguments:
prop -- The StreamInfo property that should have a specific value (e.g.,
"name", "type", "source_id", or "desc/manufaturer").
value -- The string value that the property should have (e.g., "EEG" as
the type property).
minimum -- Return at least this many streams. (default 1)
timeout -- Optionally a timeout of the operation, in seconds. If the
timeout expires, less than the desired number of streams
(possibly none) will be returned. (default FOREVER)

Returns a list of matching StreamInfo objects (with empty desc field), any
of which can subsequently be used to open an inlet.

Example: results = resolve_Stream_byprop("type","EEG")

"""

///<summary>
/// Copy-Pasted the base Resolver class from the LSL4Unity toolbox to correct
/// for a few bugs in the code.
///
/// </summary>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using LSL;
namespace LSL4Unity.Utils
{
/// <summary>
/// Encapsulates the lookup logic for LSL streams with an event based appraoch
/// your custom stream inlet implementations could be subscribed to the OnStreamFound
/// </summary>
public class Resolver : MonoBehaviour
{
public List<StreamInfo> knownStreams = new List<StreamInfo>();
// public List<StreamInfoWrapper> knownStreams = new List<StreamInfoWrapper>;
public float forgetStreamAfter = 1.0f;
private ContinuousResolver resolver;
public delegate void StreamFound(StreamInfo streamInfo); // Declare callback signature when stream found.
public StreamFound OnStreamFound; // delegate instance to hold callbacks.
public delegate void StreamLost(StreamInfo streamInfo);
public StreamLost OnStreamLost;
public bool Resolve
{
get { return (OnStreamFound != null || OnStreamLost != null); }
set { }
}
public void Start()
{
resolver = new ContinuousResolver(forgetStreamAfter);
StartCoroutine(resolveContinuously());
}
public bool IsStreamAvailable(out StreamInfo info, string streamName = "", string streamType = "", string hostName = "")
{
var result = knownStreams.Where(i =>
(streamName == "" || i.name().Equals(streamName)) &&
(streamType == "" || i.type().Equals(streamType)) &&
(hostName == "" || i.type().Equals(hostName))
);
if (result.Any())
{
info = result.First();
return true;
}
else
{
info = null;
return false;
}
}
private IEnumerator resolveContinuously()
{
// We don't bother checking the resolver unless we have any registered callbacks.
// This gives other objects time to setup and register before streams go into knownStreams!
while (Resolve)
{
var results = resolver.results();
foreach (var item in knownStreams)
{
if (!results.Any(r => r.name().Equals(item.name())))
{
OnStreamLost?.Invoke(item);
}
}
// remove lost streams from cache
knownStreams.RemoveAll(s => !results.Any(r => r.name().Equals(s.name())));
// add new found streams to the cache
foreach (var item in results)
{
if (!knownStreams.Any(s => s.name() == item.name() ))
{
Debug.Log(string.Format("Found new Stream {0}", item.name()));
// var newStreamInfo = new StreamInfoWrapper(item);
knownStreams.Add(item); // newStreamInfo);
OnStreamFound?.Invoke(item); // newStreamInfo);
}
}
yield return new WaitForSecondsRealtime(0.1f);
}
yield return null;
}
}
}

liblsl-Csharp and LSL4Unity don't explicitly mention what the preffered method of finding a stream is and LSL4Unity uses a singular Resolver Monobehaviour that finds all the streams on the network. Would it be better to have the resolver be within the Inlet Monobehaviour instead and to call the ContinuousResolver(string prop, string value, double forget_after = 5.0) : base(dll.lsl_create_continuous_resolver_byprop(prop, value, forget_after)) { } constructor? This would then be in keeping with the other implementations of liblsl, especially as the Inlet class is alread asking for the stream name and type.

Activity

  1. chkothe commented on Mar 8, 2024

    @chkothe
    Member

    So the reason the code samples in most other environments use resolve_byprop etc is that in those cases it's often fine that the call is blocking, and it's easier to get started with LSL this way. For Unity, if you don't want blocking calls, then it's usually better to use a ContinuousResolver rather than the standalone resolve functions. If you do use continuous resolvers, then it will be a bit lighter weight on the local network to use just one of them at a time, since each of them generates its own network traffic (query packets and response packets, if there are any matches) while the object is alive.

  2. LiamOverett-AMRC commented on Mar 8, 2024

    @LiamOverett-AMRC
    Author

    There are ways around the resolver being blocking within an inlet monobehaviour (e.g. coroutines, async/await or Unity 2023's Awaitable) but all of these methods will probably end up checking against a bool or if the inlet object == null within the Update loop and returning if conditions aren't met.

    Regarding the lightweightness of the continuous resolvers, and this is for my understanding, is it better to have x byprop or bypred resolvers if there are y streams on the network but x < y? Or is still better to have one resolver regardless of how many streams Unity is hooking into?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions