using System.Collections; using System.Collections.Generic; using UnityEngine; using Cysharp.Threading.Tasks; using Newtonsoft.Json; using System; using Oculus.Interaction; using System.IO; namespace GhostSystem { public class GhostRecorderBatch : MonoBehaviour { public string SceneId; public float SampleRate = 0.05f; public int BatchSize = 10; public float FlushInterval = 2f; public List TrackedObjects; private int doorCount; private float _sampleTimer; private float _flushTimer; private bool _isRecording; private bool supabaseReady = false; private bool sceneIdReady = false; private readonly List _frameBuffer = new(); private string _offlinePath; public static event Action OnRecord; public static event Action OnStopRecord; private void OnEnable() { SupabaseManager.OnSuperbaseReady += HandleSupabaseReady; RoomManager.OnSceneIdReady += HandleSceneIdReady; } private void OnDisable() { SupabaseManager.OnSuperbaseReady -= HandleSupabaseReady; RoomManager.OnSceneIdReady -= HandleSceneIdReady; } private void Start() { _offlinePath = Path.Combine(Application.persistentDataPath, "offline_log.jsonl"); } private void HandleSupabaseReady() { supabaseReady = true; } private void HandleSceneIdReady(string id, List doors) { SceneId = id; sceneIdReady = true; foreach (GameObject door in doors) { var rotator = door.GetComponentInChildren(); if (rotator != null) { TrackedObjects.Add(rotator.transform); doorCount++; } } } private void Update() { if (!_isRecording) return; _sampleTimer += Time.deltaTime; _flushTimer += Time.deltaTime; if (_sampleTimer >= SampleRate) { _sampleTimer = 0f; RecordSample(); } /*if (_frameBuffer.Count >= BatchSize || _flushTimer >= FlushInterval) { _flushTimer = 0f; _ = FlushBatchAsync(); // Fire and forget }*/ } private void RecordSample() { foreach (var obj in TrackedObjects) { var frame = new GhostFrame(SceneId, obj.name, obj.position, obj.rotation); _frameBuffer.Add(FrameModel.FromGhostFrame(frame)); } } private async UniTaskVoid FlushBatchAsync() { if (_frameBuffer.Count == 0 || !supabaseReady) return; var batch = new List(_frameBuffer); _frameBuffer.Clear(); Debug.Log("Flush Frame Buffer"); Debug.Log("Buffer: " + batch.Count); try { var response = await SupabaseManager.instance.supabase.From().Insert(batch); if (!response.ResponseMessage.IsSuccessStatusCode) { Debug.LogWarning($"Batch insert failed: {response.ResponseMessage.StatusCode}, fallback to offline."); await FallbackToOfflineAsync(batch); } } catch (Exception ex) { Debug.LogWarning($"Insert failed, fallback to offline: {ex.Message}"); await FallbackToOfflineAsync(batch); } } private async UniTask FallbackToOfflineAsync(List batch) { try { using var writer = new StreamWriter(_offlinePath, append: true); foreach (var model in batch) { string jsonLine = JsonConvert.SerializeObject(model); await writer.WriteLineAsync(jsonLine); } } catch (Exception ex) { Debug.LogError($"Offline write failed: {ex.Message}"); } } public void StartRecording() { if (!supabaseReady || !sceneIdReady) return; _isRecording = true; OnRecord?.Invoke(); Debug.Log($"Recording {SceneId}"); } public void StopRecording() { _isRecording = false; sceneIdReady = false; OnStopRecord?.Invoke(); Debug.Log($"Stop recording: {SceneId}"); Debug.Log("Framebuffer: " + _frameBuffer.Count + "Frames"); if (doorCount > 0 && TrackedObjects.Count >= doorCount) { TrackedObjects.RemoveRange(TrackedObjects.Count - doorCount, doorCount); } doorCount = 0; } public void Flush() { _flushTimer = 0f; _ = FlushBatchAsync(); // Fire and forget } } }