diff --git a/Runtime/Components/TuningVariablesComponent.cs b/Runtime/Components/TuningVariablesComponent.cs
new file mode 100644
index 00000000..6ac6dac7
--- /dev/null
+++ b/Runtime/Components/TuningVariablesComponent.cs
@@ -0,0 +1,274 @@
+using UnityEngine;
+using System.Collections;
+using System.Collections.Generic;
+
+///
+/// gets variables from the cognitive3d backend to configure your app
+///
+
+//fetches the variables immediately if the participantId is already set at session start
+//if not, it will wait 5 seconds for a participantId to be set
+//if the timer elapses, use the deviceId as the argument
+//if fetchVariablesAutomatically is false, call Cognitive3D.TuningVariables.FetchVariables
+
+//CONSIDER custom editor to display tuning variables
+
+//set session properties DONE
+//static generic funciton to get values DONE
+//locally cache in network class DONE
+//read from local cache if it fails DONE
+
+namespace Cognitive3D
+{
+ public static class TuningVariables
+ {
+ static Dictionary tuningVariables = new Dictionary();
+
+ public delegate void onTuningVariablesAvailable();
+ ///
+ /// Called after tuning variables are available (also called after a delay if no response)
+ ///
+ public static event onTuningVariablesAvailable OnTuningVariablesAvailable;
+ internal static void InvokeOnTuningVariablesAvailable() { if (OnTuningVariablesAvailable != null) { OnTuningVariablesAvailable.Invoke(); } }
+
+ internal static void Reset()
+ {
+ tuningVariables.Clear();
+ }
+ internal static void SetVariable(TuningVariableItem entry)
+ {
+ tuningVariables.Add(entry.name, entry);
+ }
+
+ ///
+ /// Returns a variable of a type using the variableName. Returns the default value if no variable is found
+ ///
+ /// The expected type of variable
+ /// The name of the variable to read
+ /// The value to return if no variable is found
+ /// The value of the tuning varible, or the defaultValue if not found
+ public static T GetValue(string variableName, T defaultValue)
+ {
+ Cognitive3D.TuningVariableItem returnItem;
+ if (tuningVariables.TryGetValue(variableName, out returnItem))
+ {
+ if (typeof(T) == typeof(string))
+ {
+ return (T)(object)returnItem.valueString;
+ }
+ if (typeof(T) == typeof(bool))
+ {
+ return (T)(object)returnItem.valueBool;
+ }
+ if (typeof(T) == typeof(int) || typeof(T) == typeof(long) || typeof(T) == typeof(float) || typeof(T) == typeof(double))
+ {
+ return (T)(object)returnItem.valueInt;
+ }
+ }
+ return defaultValue;
+ }
+
+ public static Dictionary GetAllTuningVariables()
+ {
+ return tuningVariables;
+ }
+ }
+
+ [System.Serializable]
+ public class TuningVariableItem
+ {
+ public string name;
+ public string description;
+ public string type;
+ public int valueInt;
+ public string valueString;
+ public bool valueBool;
+
+ public override string ToString()
+ {
+ if (type == "string")
+ {
+ return string.Format("name:{0}, description:{1}, type:{2}, value:{3}", name, description, type, valueString);
+ }
+ if (type == "int")
+ {
+ return string.Format("name:{0}, description:{1}, type:{2}, value:{3}", name, description, type, valueInt);
+ }
+ if (type == "bool")
+ {
+ return string.Format("name:{0}, description:{1}, type:{2}, value:{3}", name, description, type, valueBool);
+ }
+
+ return string.Format("name:{0}, description:{1}, type:{2}", name, description, type);
+ }
+ }
+}
+
+
+namespace Cognitive3D.Components
+{
+ #region Json
+ internal class TuningVariableCollection
+ {
+ public List tuningVariables = new List();
+ }
+ #endregion
+
+ [DisallowMultipleComponent]
+ [AddComponentMenu("Cognitive3D/Components/Tuning Variables Component")]
+ public class TuningVariablesComponent : AnalyticsComponentBase
+ {
+ static bool hasFetchedVariables;
+ ///
+ /// the delay to hear a response from our backend. If there is no response in this time, try to use a local cache of variables
+ ///
+ const float requestTuningVariablesTimeout = 3;
+ ///
+ /// the delay waiting for participant id to be set (if not already set at the start of the session)
+ ///
+ public float waitForParticipantIdTimeout = 5;
+ ///
+ /// if true, uses the participant id (possibly with a delay) to get tuning variables. Otherwise, use the device id
+ ///
+ public bool useParticipantId = true;
+ ///
+ /// if true, sends identifying data to retrieve variables as soon as possible
+ ///
+ public bool fetchVariablesAutomatically = true;
+
+ protected override void OnSessionBegin()
+ {
+ base.OnSessionBegin();
+ Cognitive3D_Manager.OnPostSessionEnd += Cognitive3D_Manager_OnPostSessionEnd;
+
+ if (fetchVariablesAutomatically)
+ {
+ if (useParticipantId)
+ {
+ //get variables if participant id is already set
+ if (!string.IsNullOrEmpty(Cognitive3D_Manager.ParticipantId))
+ {
+ FetchVariables(Cognitive3D_Manager.ParticipantId);
+ }
+ else
+ {
+ //listen for event
+ Cognitive3D_Manager.OnParticipantIdSet += Cognitive3D_Manager_OnParticipantIdSet;
+ //also start a timer
+ StartCoroutine(DelayFetch());
+ }
+ }
+ else //just use the hardware id to identify the user
+ {
+ FetchVariables(Cognitive3D_Manager.DeviceId);
+ }
+ }
+ }
+
+ IEnumerator DelayFetch()
+ {
+ yield return new WaitForSeconds(waitForParticipantIdTimeout);
+ FetchVariables(Cognitive3D_Manager.DeviceId);
+ }
+
+ private void Cognitive3D_Manager_OnParticipantIdSet(string participantId)
+ {
+ FetchVariables(participantId);
+ }
+
+ public static void FetchVariables()
+ {
+ if (!hasFetchedVariables)
+ {
+ NetworkManager.GetTuningVariables(Cognitive3D_Manager.DeviceId, TuningVariableResponse, requestTuningVariablesTimeout);
+ }
+ }
+
+ public static void FetchVariables(string participantId)
+ {
+ if (!hasFetchedVariables)
+ {
+ NetworkManager.GetTuningVariables(participantId, TuningVariableResponse, requestTuningVariablesTimeout);
+ }
+ }
+
+ static void TuningVariableResponse(int responsecode, string error, string text)
+ {
+ if (hasFetchedVariables)
+ {
+ Util.logDevelopment("TuningVariableResponse called multiple times!");
+ return;
+ }
+
+ if (responsecode != 200)
+ {
+ Util.logDevelopment("Tuning Variable reponse code " + responsecode + " " + error);
+ }
+
+ try
+ {
+ var tvc = JsonUtility.FromJson(text);
+ if (tvc != null)
+ {
+ TuningVariables.Reset();
+ foreach (var entry in tvc.tuningVariables)
+ {
+ TuningVariables.SetVariable(entry);
+
+ if (entry.type == "string")
+ {
+ Cognitive3D_Manager.SetSessionProperty(entry.name, entry.valueString);
+ }
+ else if (entry.type == "int")
+ {
+ Cognitive3D_Manager.SetSessionProperty(entry.name, entry.valueInt);
+ }
+ else if (entry.type == "boolean")
+ {
+ Cognitive3D_Manager.SetSessionProperty(entry.name, entry.valueBool);
+ }
+ }
+ }
+ else
+ {
+ Util.logDevelopment("TuningVariableCollection is null");
+ }
+ }
+ catch (System.Exception e)
+ {
+ Debug.LogException(e);
+ }
+
+ hasFetchedVariables = true;
+ TuningVariables.InvokeOnTuningVariablesAvailable();
+ }
+
+ private void Cognitive3D_Manager_OnPostSessionEnd()
+ {
+ hasFetchedVariables = false;
+ Cognitive3D_Manager.OnParticipantIdSet -= Cognitive3D_Manager_OnParticipantIdSet;
+ Cognitive3D_Manager.OnPostSessionEnd -= Cognitive3D_Manager_OnPostSessionEnd;
+ }
+
+ public override string GetDescription()
+ {
+ return "Retrieves variables from the Cognitive3D server to adjust the user's experience";
+ }
+
+ public override bool GetWarning()
+ {
+ return false;
+ }
+
+ [ContextMenu("Test Fetch with DeviceId")]
+ void TestFetch()
+ {
+ FetchVariables(Cognitive3D_Manager.DeviceId);
+ }
+ [ContextMenu("Test Fetch with Random GUID")]
+ void TestFetchGUID()
+ {
+ FetchVariables(System.Guid.NewGuid().ToString());
+ }
+ }
+}
diff --git a/Runtime/Components/TuningVariablesComponent.cs.meta b/Runtime/Components/TuningVariablesComponent.cs.meta
new file mode 100644
index 00000000..977549f9
--- /dev/null
+++ b/Runtime/Components/TuningVariablesComponent.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c2813dd9237bf1c4ead0e6276a28ed1e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/Internal/CognitiveStatics.cs b/Runtime/Internal/CognitiveStatics.cs
index d3e8a27e..220a68a5 100644
--- a/Runtime/Internal/CognitiveStatics.cs
+++ b/Runtime/Internal/CognitiveStatics.cs
@@ -143,6 +143,12 @@ internal static string PostExitpollResponses(string questionsetname, int questio
return string.Concat(Cognitive3D_Preferences.Instance.Protocol, "://", Cognitive3D_Preferences.Instance.Gateway, "/v", version,"/questionSets/", questionsetname, "/",questionsetversion.ToString(), "/responses");
}
+ //GET request question set
+ internal static string GetTuningVariableURL(string userId)
+ {
+ return string.Concat(Cognitive3D_Preferences.Instance.Protocol, "://", Cognitive3D_Preferences.Instance.Gateway, "/v", version, "/tuningvariables?identifier=", userId);
+ }
+
///
/// Creates the GET request endpoint with access token and search parameters
///
diff --git a/Runtime/Internal/ILocalTuningVariables.cs b/Runtime/Internal/ILocalTuningVariables.cs
new file mode 100644
index 00000000..dcb3380d
--- /dev/null
+++ b/Runtime/Internal/ILocalTuningVariables.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+using UnityEngine;
+
+namespace Cognitive3D
+{
+ internal interface ILocalTuningVariables
+ {
+ bool GetTuningVariables(out string text);
+ void WriteTuningVariables(string text);
+ }
+
+ internal class TuningVariablesLocalDataHandler : ILocalTuningVariables
+ {
+ readonly string localTuningVariablePath;
+
+ public TuningVariablesLocalDataHandler(string path)
+ {
+ localTuningVariablePath = path;
+ }
+
+ public bool GetTuningVariables(out string text)
+ {
+ try
+ {
+ if (File.Exists(localTuningVariablePath + "tuningVariables"))
+ {
+ text = File.ReadAllText(localTuningVariablePath + "tuningVariables");
+ return true;
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.LogException(e);
+ }
+ text = "";
+ return false;
+ }
+
+ public void WriteTuningVariables(string text)
+ {
+ if (!Directory.Exists(localTuningVariablePath))
+ Directory.CreateDirectory(localTuningVariablePath);
+
+ try
+ {
+ File.WriteAllText(localTuningVariablePath + "tuningVariables", text);
+ }
+ catch (Exception e)
+ {
+ Debug.LogException(e);
+ }
+ }
+ }
+}
diff --git a/Runtime/Internal/ILocalTuningVariables.cs.meta b/Runtime/Internal/ILocalTuningVariables.cs.meta
new file mode 100644
index 00000000..3252f983
--- /dev/null
+++ b/Runtime/Internal/ILocalTuningVariables.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 26f7878bbdb61b34d833431294758211
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/Internal/NetworkManager.cs b/Runtime/Internal/NetworkManager.cs
index bd75ec6f..f05a99a4 100644
--- a/Runtime/Internal/NetworkManager.cs
+++ b/Runtime/Internal/NetworkManager.cs
@@ -29,19 +29,17 @@ internal class NetworkManager : MonoBehaviour
static HashSet activeRequests = new HashSet();
internal ICache runtimeCache;
- internal ILocalExitpoll exitpollCache;
int lastDataResponse = 0;
const float cacheUploadInterval = 2;
const float minRetryDelay = 60;
const float maxRetryDelay = 240;
- internal void Initialize(ICache runtimeCache, ILocalExitpoll exitpollCache)
+ internal void Initialize(ICache runtimeCache)
{
DontDestroyOnLoad(gameObject);
instance = this;
this.runtimeCache = runtimeCache;
- this.exitpollCache = exitpollCache;
}
System.Collections.IEnumerator WaitForExitpollResponse(UnityWebRequest www, string hookname, Response callback, float timeout)
@@ -502,6 +500,95 @@ private float GetExponentialBackoff(float retryDelay)
return minRetryDelay;
}
+ public static void GetTuningVariables(string participantId, Response callback, float timeout)
+ {
+ string url = CognitiveStatics.GetTuningVariableURL(participantId);
+ var request = UnityWebRequest.Get(url);
+ request.SetRequestHeader("Content-Type", "application/json");
+ request.SetRequestHeader("X-HTTP-Method-Override", "GET");
+ request.SetRequestHeader("Authorization", CognitiveStatics.ApplicationKey);
+ request.SendWebRequest();
+
+ instance.StartCoroutine(instance.WaitForTuningVariableResponse(request, callback, timeout));
+ }
+
+ System.Collections.IEnumerator WaitForTuningVariableResponse(UnityWebRequest www, Response callback, float timeout)
+ {
+ float time = 0;
+ while (time < timeout)
+ {
+ yield return null;
+ if (www.isDone) break;
+ time += Time.deltaTime;
+ }
+
+ var headers = www.GetResponseHeaders();
+ int responsecode = (int)www.responseCode;
+ lastDataResponse = responsecode;
+ //check cvr header to make sure not blocked by capture portal
+
+#if UNITY_WEBGL
+ //webgl cors issue doesn't seem to accept this required header
+ if (!headers.ContainsKey("cvr-request-time"))
+ {
+ headers.Add("cvr-request-time", string.Empty);
+ }
+#endif
+
+ if (!www.isDone)
+ Util.logWarning("Network::WaitForTuningVariableResponse timeout");
+ if (responsecode != 200)
+ Util.logWarning("Network::WaitForTuningVariableResponse responsecode is " + responsecode);
+
+ if (headers != null)
+ {
+ if (!headers.ContainsKey("cvr-request-time"))
+ Util.logWarning("Network::WaitForTuningVariableResponse does not contain cvr-request-time header");
+ }
+
+ if (!www.isDone || responsecode != 200 || (headers != null && !headers.ContainsKey("cvr-request-time")))
+ {
+ if (Cognitive3D_Preferences.Instance.LocalStorage)
+ {
+ string text;
+ if (Cognitive3D_Manager.TuningVariableHandler.GetTuningVariables(out text))
+ {
+ if (callback != null)
+ {
+ callback.Invoke(responsecode, "", text);
+ }
+ }
+ else
+ {
+ if (callback != null)
+ {
+ callback.Invoke(responsecode, "", "");
+ }
+ }
+ }
+ else
+ {
+ if (callback != null)
+ {
+ callback.Invoke(responsecode, "", "");
+ }
+ }
+ }
+ else
+ {
+ if (callback != null)
+ {
+ callback.Invoke(responsecode, www.error, www.downloadHandler.text);
+ }
+ if (Cognitive3D_Preferences.Instance.LocalStorage)
+ {
+ Cognitive3D_Manager.TuningVariableHandler.WriteTuningVariables(www.downloadHandler.text);
+ }
+ }
+ www.Dispose();
+ activeRequests.Remove(www);
+ }
+
//skip network cleanup if immediately/manually destroyed
//gameobject is destroyed at end of frame
//issue if ending session/destroy gameobject/new session all in one frame
diff --git a/Runtime/Scripts/Cognitive3D_Manager.cs b/Runtime/Scripts/Cognitive3D_Manager.cs
index a5156a81..1d12a10b 100644
--- a/Runtime/Scripts/Cognitive3D_Manager.cs
+++ b/Runtime/Scripts/Cognitive3D_Manager.cs
@@ -156,6 +156,7 @@ public void BeginSession()
#endif
ExitpollHandler = new ExitPollLocalDataHandler(Application.persistentDataPath + "/c3dlocal/exitpoll/");
+ TuningVariableHandler = new TuningVariablesLocalDataHandler(Application.persistentDataPath + "/c3dlocal/tuningvariables/");
if (Cognitive3D_Preferences.Instance.LocalStorage)
{
@@ -172,7 +173,7 @@ public void BeginSession()
GameObject networkGo = new GameObject("Cognitive Network");
networkGo.hideFlags = HideFlags.HideInInspector | HideFlags.HideInHierarchy;
NetworkManager = networkGo.AddComponent();
- NetworkManager.Initialize(DataCache, ExitpollHandler);
+ NetworkManager.Initialize(DataCache);
GameplayReferences.Initialize();
DynamicManager.Initialize();
@@ -826,7 +827,15 @@ public static void FlushData()
public static event onLevelLoaded OnLevelLoaded;
private static void InvokeLevelLoadedEvent(Scene scene, LoadSceneMode mode, bool newSceneId) { if (OnLevelLoaded != null) { OnLevelLoaded(scene, mode, newSceneId); } }
+ public delegate void onParticipantIdSet(string participantId);
+ ///
+ /// Called after a participant id is set. May be called multiple times
+ ///
+ public static event onParticipantIdSet OnParticipantIdSet;
+ private static void InvokeOnParticipantIdSet(string participantId) { if (OnParticipantIdSet != null) { OnParticipantIdSet.Invoke(participantId); } }
+
internal static ILocalExitpoll ExitpollHandler;
+ internal static ILocalTuningVariables TuningVariableHandler;
internal static ICache DataCache;
internal static NetworkManager NetworkManager;
@@ -986,6 +995,7 @@ public static void SetParticipantId(string id)
}
ParticipantId = id;
SetParticipantProperty("id", id);
+ InvokeOnParticipantIdSet(id);
}
///