diff --git a/Runtime/Scripts/BaseOutlet.cs b/Runtime/Scripts/BaseOutlet.cs index a2a4a21..c96c355 100644 --- a/Runtime/Scripts/BaseOutlet.cs +++ b/Runtime/Scripts/BaseOutlet.cs @@ -14,14 +14,16 @@ public abstract class ABaseOutlet : MonoBehaviour private bool UniqueFromInstanceId = true; public abstract List ChannelNames { get; } - public virtual int ChannelCount { get { return ChannelNames.Count; } } + + public virtual int ChannelCount + { + get { return ChannelNames.Count; } + } + public abstract channel_format_t Format { get; } protected StreamOutlet outlet; protected TData[] sample; - // A singleton of a TimeSync object can ensure that all pushes that happen on the same moment (update/fixed/late etc) - // on the same frame will have the same timestamp. - private TimeSync timeSync; // Add an XML element for each channel. The automatic version adds only channel labels. Override to add unit, location, etc. protected virtual void FillChannelsHeader(XMLElement channels) @@ -36,8 +38,6 @@ protected virtual void FillChannelsHeader(XMLElement channels) protected virtual void Start() { // TODO: Check StreamName, StreamType, and moment are not null. - - timeSync = gameObject.GetComponent(); sample = new TData[ChannelCount]; var hash = new Hash128(); @@ -49,19 +49,22 @@ protected virtual void Start() ExtendHash(hash); double dataRate = IrregularRate ? LSL.LSL.IRREGULAR_RATE : LSLCommon.GetSamplingRateFor(moment); - StreamInfo streamInfo = new StreamInfo(StreamName, StreamType, ChannelCount, dataRate, Format, hash.ToString()); + StreamInfo streamInfo = + new StreamInfo(StreamName, StreamType, ChannelCount, dataRate, Format, hash.ToString()); // Build XML header. See xdf wiki for recommendations: https://github.com/sccn/xdf/wiki/Meta-Data XMLElement acq_el = streamInfo.desc().append_child("acquisition"); acq_el.append_child_value("manufacturer", "LSL4Unity"); XMLElement channels = streamInfo.desc().append_child("channels"); FillChannelsHeader(channels); - + outlet = new StreamOutlet(streamInfo); } // Override to extend hash - protected virtual void ExtendHash(Hash128 hash) { } + protected virtual void ExtendHash(Hash128 hash) + { + } protected abstract bool BuildSample(); protected abstract void pushSample(double timestamp = 0); @@ -69,39 +72,37 @@ protected virtual void ExtendHash(Hash128 hash) { } // Update is called once per frame protected virtual void FixedUpdate() { - if (moment == MomentForSampling.FixedUpdate && outlet != null) - { - if (BuildSample()) - pushSample((timeSync != null) ? timeSync.FixedUpdateTimeStamp : 0.0); - } + if (moment != MomentForSampling.FixedUpdate || outlet == null) return; + + if (!BuildSample()) return; + + pushSample(TimeSync.Instance ? TimeSync.Instance.FixedUpdateTimestamp : 0.0); } protected virtual void Update() { - if (outlet != null) + if (outlet == null) return; + + if (!BuildSample()) return; + + if (moment == MomentForSampling.Update) { - if (BuildSample()) - { - if (moment == MomentForSampling.Update) - { - pushSample((timeSync != null) ? timeSync.UpdateTimeStamp : 0.0); - } - else if (moment == MomentForSampling.EndOfFrame) - { - // Send as close to render-time as possible. Use this to log stimulus events. - StartCoroutine(PushAfterRendered()); - } - } + pushSample(TimeSync.Instance ? TimeSync.Instance.UpdateTimestamp : 0.0); + } + else if (moment == MomentForSampling.EndOfFrame) + { + // Send as close to render-time as possible. Use this to log stimulus events. + StartCoroutine(PushAfterRendered()); } } protected virtual void LateUpdate() { - if (moment == MomentForSampling.LateUpdate && outlet != null) - { - if (BuildSample()) - pushSample((timeSync != null) ? timeSync.LateUpdateTimeStamp : 0.0); - } + if (moment != MomentForSampling.LateUpdate || outlet == null) return; + + if (!BuildSample()) return; + + pushSample(TimeSync.Instance ? TimeSync.Instance.LateUpdateTimestamp : 0.0); } IEnumerator PushAfterRendered() @@ -114,7 +115,11 @@ IEnumerator PushAfterRendered() public abstract class AFloatOutlet : ABaseOutlet { - public override channel_format_t Format { get { return channel_format_t.cf_float32; } } + public override channel_format_t Format + { + get { return channel_format_t.cf_float32; } + } + protected override void pushSample(double timestamp = 0) { if (outlet == null) @@ -125,7 +130,11 @@ protected override void pushSample(double timestamp = 0) public abstract class ADoubleOutlet : ABaseOutlet { - public override channel_format_t Format { get { return channel_format_t.cf_double64; } } + public override channel_format_t Format + { + get { return channel_format_t.cf_double64; } + } + protected override void pushSample(double timestamp = 0) { if (outlet == null) @@ -136,7 +145,11 @@ protected override void pushSample(double timestamp = 0) public abstract class AIntOutlet : ABaseOutlet { - public override channel_format_t Format { get { return channel_format_t.cf_int32; } } + public override channel_format_t Format + { + get { return channel_format_t.cf_int32; } + } + protected override void pushSample(double timestamp = 0) { if (outlet == null) @@ -147,7 +160,11 @@ protected override void pushSample(double timestamp = 0) public abstract class ACharOutlet : ABaseOutlet { - public override channel_format_t Format { get { return channel_format_t.cf_int8; } } + public override channel_format_t Format + { + get { return channel_format_t.cf_int8; } + } + protected override void pushSample(double timestamp = 0) { if (outlet == null) @@ -158,7 +175,11 @@ protected override void pushSample(double timestamp = 0) public abstract class AStringOutlet : ABaseOutlet { - public override channel_format_t Format { get { return channel_format_t.cf_string; } } + public override channel_format_t Format + { + get { return channel_format_t.cf_string; } + } + protected override void pushSample(double timestamp = 0) { if (outlet == null) @@ -169,7 +190,11 @@ protected override void pushSample(double timestamp = 0) public abstract class AShortOutlet : ABaseOutlet { - public override channel_format_t Format { get { return channel_format_t.cf_int16; } } + public override channel_format_t Format + { + get { return channel_format_t.cf_int16; } + } + protected override void pushSample(double timestamp = 0) { if (outlet == null) diff --git a/Runtime/Scripts/TimeSync.cs b/Runtime/Scripts/TimeSync.cs index 755ec83..388638d 100644 --- a/Runtime/Scripts/TimeSync.cs +++ b/Runtime/Scripts/TimeSync.cs @@ -1,67 +1,71 @@ using UnityEngine; - namespace LSL4Unity.Utils { /// - /// This singleton should provide an dedicated timestamp for each update call or fixed update LSL sample! - /// So that each sample provided by an Unity3D app has the same timestamp - /// Important! Make sure that the script is called before the default execution order! + /// A singleton to provide a dedicated timestamp for each update call: FixedUpdate, Update and LateUpdate. + /// Each sample provide by a Unity app should have the same timestamp. /// - [ScriptOrder( -1000 )] - public class TimeSync : MonoBehaviour + /// + /// Important! Make sure that the Update methods within the class are called before the default execution order! + /// The ScriptOrder attribute takes care of that for the user. + /// + [ScriptOrder(-1000)] + public sealed class TimeSync : MonoBehaviour { - private static TimeSync instance; - public static TimeSync Instance - { - get - { - return instance; - } - } + [Tooltip("Flag for making the script not destroyable on load.")] [SerializeField] + private bool dontDestroyOnLoad = false; - private double fixedUpdateTimeStamp; - public double FixedUpdateTimeStamp - { - get - { - return fixedUpdateTimeStamp; - } - } + /// + /// Singleton instance of TimeSync class + /// + public static TimeSync Instance { get; private set; } + + /// + /// Timestamp at FixedUpdate + /// + public double FixedUpdateTimestamp { get; private set; } - private double updateTimeStamp; - public double UpdateTimeStamp + /// + /// Timestamp at Update + /// + public double UpdateTimestamp { get; private set; } + + /// + /// Timestamp at LateUpdate + /// + public double LateUpdateTimestamp { get; private set; } + + + private void Awake() { - get + // Create Singleton or destroy if an instance already exists + if (Instance && Instance != this) { - return updateTimeStamp; + Destroy(this); } - } - - private double lateUpdateTimeStamp; - public double LateUpdateTimeStamp - { - get { return lateUpdateTimeStamp; } - } + else + { + Instance = this; - void Awake() - { - TimeSync.instance = this; + // Set don't destroy on load based on flag + if (dontDestroyOnLoad) DontDestroyOnLoad(this); + } } - void FixedUpdate() + private void FixedUpdate() { - fixedUpdateTimeStamp = LSL.LSL.local_clock(); + FixedUpdateTimestamp = LSL.LSL.local_clock(); } - void Update() + private void Update() { - updateTimeStamp = LSL.LSL.local_clock(); + UpdateTimestamp = LSL.LSL.local_clock(); } - void LateUpdate() + private void LateUpdate() { - lateUpdateTimeStamp = LSL.LSL.local_clock(); + LateUpdateTimestamp = LSL.LSL.local_clock(); } } } \ No newline at end of file diff --git a/Runtime/Scripts/TimeSync.cs.meta b/Runtime/Scripts/TimeSync.cs.meta index c2922e3..da36ab8 100644 --- a/Runtime/Scripts/TimeSync.cs.meta +++ b/Runtime/Scripts/TimeSync.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 873d45d18db8f14409781ddee1823d2f +guid: 1ea5f44c498c406d989f261b3267e7b5 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/package.json b/package.json index 03c0f3b..8739fcd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.labstreaminglayer.lsl4unity", - "version": "1.16.0", + "version": "1.16.1", "description": "labstreaminglayer for Unity", "displayName": "labstreaminglayer for Unity", "unity": "2020.3",