142 lines
4.3 KiB
C#

using System;
using System.IO;
using System.Text;
using UnityEngine;
using UnityEngine.SceneManagement;
public class CsvLogger : MonoBehaviour
{
public static CsvLogger Instance { get; private set; }
[Header("File")]
[SerializeField] private string filePrefix = "session";
[Header("Optional Pose Sampling")]
[SerializeField] private bool logTransform = false;
[SerializeField] private Transform target; // e.g., Main Camera (HMD) or a controller
[SerializeField] private float sampleHz = 5f; // samples per second (0 = off)
// Deploying in WebGL does not allow for disk write, so we need
// a special way to download the data from the memory buffer as a web file.
#if UNITY_WEBGL && !UNITY_EDITOR
private StringBuilder bufferWebGL;
#else
private StreamWriter writer;
#endif
private string filename;
private string filepath;
private float nextSampleTime;
private bool isConfigured = false;
private void Awake()
{
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject);
}
private void Start()
{
//// Uncomment if you want the logging to begin automatically.
// StartLogger();
}
private void OnApplicationQuit()
{
if(isConfigured) StopLogger();
}
private void Update()
{
if (!logTransform || target == null || sampleHz <= 0f) return;
if (Time.unscaledTime >= nextSampleTime)
{
nextSampleTime = Time.unscaledTime + (1f / sampleHz);
LogInternal("transform_sample", "");
}
}
public void StartLogger()
{
if(isConfigured) StopLogger();
string ts = DateTime.Now.ToString("yyyyMMdd_HHmmss");
filename = $"{filePrefix}_{ts}.csv";
string dir = Application.persistentDataPath;
filepath = Path.Combine(dir, filename);
#if UNITY_WEBGL && !UNITY_EDITOR
bufferWebGL = new StringBuilder(4096);
bufferWebGL.AppendLine("timestamp_utc,scene,event,details,px,py,pz,rx,ry,rz,rw");
// Small breadcrumb to find the file path in Console:
Debug.Log($"[CsvLoggerWebGL] buffering in memory. File will download when stopped: {filename}");
#else
writer = new StreamWriter(filepath, false, Encoding.UTF8);
writer.WriteLine("timestamp_now,scene,event,details,px,py,pz,rx,ry,rz,rw");
writer.Flush();
// Small breadcrumb to find the file path in Console:
Debug.Log($"[CsvLogger] Writing to: {filepath}");
#endif
isConfigured = true;
}
public void StopLogger()
{
#if UNITY_WEBGL && !UNITY_EDITOR
var bytes = Encoding.UTF8.GetBytes(bufferWebGL.ToString());
// In WEBGL: Download file when closing
if (WebGLFileSaver.IsSavingSupported())
{
Debug.Log($"[CsvLoggerWebGL] File saved as: {filename}");
WebGLFileSaver.SaveFile(bytes, filename);
}
#else
Debug.Log($"[CsvLogger] File available at: {filepath}");
writer?.Flush();
writer?.Close();
#endif
isConfigured = false; // a new logger must be started
}
public static void LogEvent(string eventToLog, string details = "")
{
if (Instance == null) return;
Instance.LogInternal(eventToLog, details);
}
private void LogInternal(string evt, string details)
{
if(!isConfigured) return;
string tstamp = DateTime.Now.ToString("o"); // ISO-8601 UTC
string scene = SceneManager.GetActiveScene().name;
// Default empty pose
float px = 0, py = 0, pz = 0, rx = 0, ry = 0, rz = 0, rw = 1;
// Record the transform of the target
if (target != null)
{
Vector3 p = target.position;
Quaternion r = target.rotation;
px = p.x; py = p.y; pz = p.z;
rx = r.x; ry = r.y; rz = r.z; rw = r.w;
}
// Escape commas in details by wrapping in quotes if needed
if (!string.IsNullOrEmpty(details) && details.Contains(",")) details = $"\"{details}\"";
#if UNITY_WEBGL && !UNITY_EDITOR
bufferWebGL.AppendLine($"{tstamp},{scene},{evt},{details},{px},{py},{pz},{rx},{ry},{rz},{rw}");
#else
writer.WriteLine($"{tstamp},{scene},{evt},{details},{px},{py},{pz},{rx},{ry},{rz},{rw}");
writer.Flush();
#endif
}
}