using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Linq; using System; using UnityEngine.UI; using Postgrest; using Cysharp.Threading.Tasks; using UnityEngine.Rendering.Universal; using System.Globalization; using Oculus.Interaction; using System.Reactive; using Postgrest.Interfaces; namespace GhostSystem { public class GhostReplayManager : MonoBehaviour { [Header("Replay Settings")] public string sceneId; public string UserFilter = null; // optional: filtere nach User-ID public bool Loop = true; public DateTime selectedStartTime; public DateSelector dateSelector; [Tooltip("Optional: Begrenze die Frame-Abfrage auf einen Zeitraum ab Startzeit.")] public bool LimitTimeRange = true; [Tooltip("Zeitraum in Minuten, der ab Startzeit ausgewertet wird.")] public int TimeRangeMinutes = 15; [Header("Target Objects")] public List TrackedObjects; [Header("Playback")] public float PlaybackSpeed = 1f; public bool AutoStart = true; public Slider playbackSlider; private bool isScrubbing = false; private bool isPlaying = false; private float playbackTime = 0f; private float totalDuration = 0f; private string firstTimestamp = ""; private string lastTimestamp = ""; private Dictionary> objectTracks = new(); private bool supabaseReady = false; private bool sceneIdReady = false; private string loadedSceneId; private int doorCount; private List heatTargets; private DateTime latestLoadedTimestamp; [Serializable] public class TrackedObjectBinding { public string objectId; public GameObject targetObject; } private async void OnEnable() { SupabaseManager.OnSuperbaseReady += HandleSupabaseReady; RoomManager.OnSceneIdReady += HandleSceneIdReady; } private void OnDisable() { SupabaseManager.OnSuperbaseReady -= HandleSupabaseReady; RoomManager.OnSceneIdReady -= HandleSceneIdReady; } private void HandleSupabaseReady() { supabaseReady = true; TryInitializeReplay(); } private void HandleSceneIdReady(string id, List doors, List targets) { sceneIdReady = true; loadedSceneId = id; foreach (GameObject door in doors) { TrackedObjectBinding trackedObjectBinding = new TrackedObjectBinding(); trackedObjectBinding.objectId = "Door"; trackedObjectBinding.targetObject = door.GetComponentInChildren().gameObject; TrackedObjects.Add(trackedObjectBinding); doorCount++; } heatTargets = targets; TryInitializeReplay(); } private async UniTaskVoid TryInitializeReplay() { if (!supabaseReady || !sceneIdReady) return; sceneId = loadedSceneId; await LoadAllFrames(); StartReplay(); _ = PeriodicUpdateFrames(); } public async UniTask LoadAllFrames() { Debug.Log("SceneId used in query: " + sceneId); Debug.Log("StartTime filter: " + dateSelector.selectedDateTime().ToString("o")); Debug.Log("EndTime filter: " + dateSelector.EndTime().ToString("o")); var startTime = dateSelector.selectedDateTime(); var endTime = dateSelector.EndTime(); objectTracks.Clear(); int batchSize = 1000; int offset = 0; bool more = true; List allFrames = new(); while (more) { List timestampFilter = new List(); timestampFilter.Add(new QueryFilter("timestamp", Postgrest.Constants.Operator.GreaterThanOrEqual, startTime.ToString("o"))); timestampFilter.Add(new QueryFilter("timestamp", Postgrest.Constants.Operator.LessThan, endTime.ToString("o"))); var query = SupabaseManager.instance.supabase .From() .Filter("scene_id", Postgrest.Constants.Operator.Equals, sceneId) //.Where(x => x.Timestamp >= "timestamp '2025-06-14 10:27:00' and" x => x.Timestamp.timestamp <= "timestamp '2025-06-14 10:29:00'") //.Filter("timestamp", Postgrest.Constants.Operator.GreaterThanOrEqual, startTime.ToString("o")) .And(timestampFilter) //.Filter("timestamp", Postgrest.Constants.Operator.LessThan, endTime.ToString("o")) .Order("timestamp", Postgrest.Constants.Ordering.Ascending) .Limit(batchSize) // Limit, wie viele Frames in einem Batch geladen werden. (Standardmäßig 1000) .Offset(offset); // Um zu wissen, wo der nächste Batch anfängt if (!string.IsNullOrEmpty(UserFilter)) query = query.Filter("user_id", Postgrest.Constants.Operator.Equals, UserFilter); var result = await query.Get(); if (result.Models.Count == 0) break; if (result.Models[0].SceneId != sceneId) break; Debug.LogError("Model Count: " + result.Models.Count); allFrames.AddRange(result.Models); offset += batchSize; more = result.Models.Count == batchSize; } foreach (var frame in allFrames) { if (!objectTracks.ContainsKey(frame.ObjectId)) objectTracks[frame.ObjectId] = new List(); objectTracks[frame.ObjectId].Add(frame); } if (allFrames.Count > 1) { latestLoadedTimestamp = allFrames.Last().TimestampParsed; totalDuration = (float)(latestLoadedTimestamp - allFrames.First().TimestampParsed).TotalSeconds; firstTimestamp = allFrames.First().TimestampParsed.ToLongTimeString(); lastTimestamp = latestLoadedTimestamp.ToLongTimeString(); } Debug.LogWarning($"Loaded {allFrames.Count} frames across {objectTracks.Count} objects."); } private async UniTaskVoid PeriodicUpdateFrames() // lädt alle 10 s neue Frames nach, falls verfügbar { while (true) { await UniTask.Delay(TimeSpan.FromSeconds(10)); await LoadNewFramesAfter(latestLoadedTimestamp); } } private async UniTask LoadNewFramesAfter(DateTime afterTime) // lädt einen neuen Batch nach. { var endTime = dateSelector.EndTime(); List timestampFilter = new List(); timestampFilter.Add(new QueryFilter("timestamp", Postgrest.Constants.Operator.GreaterThan, afterTime.ToString("o"))); timestampFilter.Add(new QueryFilter("timestamp", Postgrest.Constants.Operator.LessThan, endTime.ToString("o"))); var query = SupabaseManager.instance.supabase .From() .Filter("scene_id", Postgrest.Constants.Operator.Equals, sceneId) .And(timestampFilter) //.Filter("timestamp", Postgrest.Constants.Operator.GreaterThan, afterTime.ToString("o")) //.Filter("timestamp", Postgrest.Constants.Operator.LessThanOrEqual, endTime.ToString("o")) .Order("timestamp", Postgrest.Constants.Ordering.Ascending); if (!string.IsNullOrEmpty(UserFilter)) query = query.Filter("user_id", Postgrest.Constants.Operator.Equals, UserFilter); var result = await query.Get(); var newFrames = result.Models; foreach (var frame in newFrames) { if (!objectTracks.ContainsKey(frame.ObjectId)) objectTracks[frame.ObjectId] = new List(); objectTracks[frame.ObjectId].Add(frame); } if (newFrames.Count > 0) { latestLoadedTimestamp = newFrames.Last().TimestampParsed; totalDuration = (float)(latestLoadedTimestamp - objectTracks.Values.SelectMany(f => f).First().TimestampParsed).TotalSeconds; } Debug.Log($"Appended {newFrames.Count} new frames."); } public void StartReplay() // Für das Play UI-Element, um das Replay zu starten { isPlaying = true; playbackTime = 0f; Debug.LogError("Replay started: total duration = " + totalDuration + " s, First: " + firstTimestamp + ", Last: " + lastTimestamp); } public void PauseReplay() => isPlaying = !isPlaying; public void StopReplay() // Für das Stop UI-Element, um das Replay zu beenden { isPlaying = false; playbackTime = 0f; } public void SetPlaybackTime(float time) // Für das Slider UI-Element zum Scrubben { playbackTime = Mathf.Clamp(time, 0f, totalDuration); } private void Update() { if (!isPlaying || objectTracks.Count == 0) return; playbackTime += Time.deltaTime * PlaybackSpeed; if (playbackTime > totalDuration) { if (Loop) { playbackTime = 0f; foreach (RecieveHeat target in heatTargets) { target.SetHeat(0); } } else isPlaying = false; } DateTime currentTimestamp = objectTracks.Values .SelectMany(list => list) .First().TimestampParsed + TimeSpan.FromSeconds(playbackTime); foreach (var binding in TrackedObjects) { if (!objectTracks.ContainsKey(binding.objectId)) continue; var frames = objectTracks[binding.objectId]; if (frames.Count < 2) continue; FrameModel a = null, b = null; for (int i = 0; i < frames.Count - 1; i++) { var tA = frames[i].TimestampParsed; var tB = frames[i + 1].TimestampParsed; if (tA <= currentTimestamp && tB >= currentTimestamp) { a = frames[i]; b = frames[i + 1]; break; } } if (a != null && b != null) { double tA = a.TimestampParsed.Subtract(DateTime.UnixEpoch).TotalSeconds; double tB = b.TimestampParsed.Subtract(DateTime.UnixEpoch).TotalSeconds; double tCurrent = currentTimestamp.Subtract(DateTime.UnixEpoch).TotalSeconds; float t = Mathf.Clamp01((float)((tCurrent - tA) / (tB - tA))); Vector3 pos = Vector3.Lerp(a.Position, b.Position, t); Quaternion rot = Quaternion.Slerp(a.Rotation, b.Rotation, t); binding.targetObject.transform.SetPositionAndRotation(pos, rot); } } if (!isScrubbing && playbackSlider != null && totalDuration > 0f) { playbackSlider.value = playbackTime / totalDuration; } } public void SetSliderScrubValue(float sliderValue) { if (isScrubbing) SetPlaybackTime(sliderValue * totalDuration); } public void OnSliderStartDrag() { isScrubbing = true; } public void OnSliderEndDrag() { isScrubbing = false; SetPlaybackTime(playbackSlider.value * totalDuration); } public void RemoveDoors() { TrackedObjects.RemoveRange(TrackedObjects.Count - doorCount, doorCount); doorCount = 0; } } }