Files
RoomAwareVR/Assets/_Scripts/GhostSystem/GhostReplayManager.cs
T

333 lines
12 KiB
C#

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<TrackedObjectBinding> 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<string, List<FrameModel>> objectTracks = new();
private bool supabaseReady = false;
private bool sceneIdReady = false;
private string loadedSceneId;
private int doorCount;
private List<RecieveHeat> 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<GameObject> doors, List<RecieveHeat> targets)
{
sceneIdReady = true;
loadedSceneId = id;
foreach (GameObject door in doors)
{
TrackedObjectBinding trackedObjectBinding = new TrackedObjectBinding();
trackedObjectBinding.objectId = "Door";
trackedObjectBinding.targetObject = door.GetComponentInChildren<OneGrabRotateTransformer>().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<FrameModel> allFrames = new();
while (more)
{
List<IPostgrestQueryFilter> timestampFilter = new List<IPostgrestQueryFilter>();
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<FrameModel>()
.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<FrameModel>();
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<IPostgrestQueryFilter> timestampFilter = new List<IPostgrestQueryFilter>();
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<FrameModel>()
.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<FrameModel>();
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;
}
}
}