Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
LiamOverett-AMRC opened this issue Mar 7, 2024 · 2 comments

Comments

@LiamOverett-AMRC
Copy link

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.

@chkothe
Copy link
Member

chkothe commented Mar 8, 2024

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.

@LiamOverett-AMRC
Copy link
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
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants