Cleaned up and commented version.

This commit is contained in:
Thorbjoern
2025-10-02 00:07:11 +02:00
parent 2b86d9f0a2
commit 4c613c3436
66 changed files with 260 additions and 1020 deletions
@@ -11,6 +11,9 @@ using System.Globalization;
using Oculus.Interaction;
using System.Reactive;
using Postgrest.Interfaces;
using UnityEngine.InputSystem;
// Dieses Script ist für die Wiedergabe des Replays mit den Daten aus der Datenbank zuständig.
namespace GhostSystem
{
@@ -81,18 +84,18 @@ namespace GhostSystem
RoomManager.OnSceneIdReady -= HandleSceneIdReady;
}
private void HandleSupabaseReady()
private void HandleSupabaseReady() // Wen die Verbindung zu Supabase aufgebaut ist, wird versucht, das Replay der Szene zu initialisieren.
{
supabaseReady = true;
TryInitializeReplay();
}
private void HandleSceneIdReady(string id, List<GameObject> doors, List<RecieveHeat> targets)
{
loadingScreen.SetActive(true);
private void HandleSceneIdReady(string id, List<GameObject> doors, List<RecieveHeat> targets) // Sobald die Szene geladen ist, wird ebenfalls versucht, das Replay zu
{ // initialisieren.
loadingScreen.SetActive(true); // Während der erste Batch an Frames aus der Datenbank geladen wird, wird ein Ladekringel angezeigt.
sceneIdReady = true;
loadedSceneId = id;
foreach (GameObject door in doors)
loadedSceneId = id; // die aktuelle SceneID wird gesetzt.
foreach (GameObject door in doors) // Auch hier müssen die Türen zugeordnet werden.
{
TrackedObjectBinding trackedObjectBinding = new TrackedObjectBinding();
trackedObjectBinding.objectId = "Door";
@@ -104,29 +107,29 @@ namespace GhostSystem
TryInitializeReplay();
}
private async UniTaskVoid TryInitializeReplay()
private async UniTaskVoid TryInitializeReplay() // Das Replay wird aber nur initialisiert, wenn Supabase und die Szene bereit sind.
{
if (!supabaseReady || !sceneIdReady) return;
sceneId = loadedSceneId;
awaitingFrames = true;
await LoadAllFrames();
loadingScreen.SetActive(false);
StartReplay();
_ = PeriodicUpdateFrames();
}
await LoadAllFrames(); // Es wird auf die ersten 1000 Frames (siehe Batchsize) bzw. alle verfügbaren Frames (falls unter 1000) gewartet.
loadingScreen.SetActive(false); // Sind die Frames geladen, wird der Ladescreen wieder deaktiviert
StartReplay(); // und das Replay gestartet.
_ = PeriodicUpdateFrames(); // Zusätzlich wird ein Unitask ausgelöst, der immer wieder Frames nachlädt, solange noch Frames in der Datenbank vorhanden sind, die
} // den Filtern entsprechen und noch nicht geladen wurden.
public async UniTask LoadAllFrames()
{
Debug.Log("SceneId used in query: " + sceneId);
var startTime = dateSelector.selectedDateTime();
var endTime = dateSelector.EndTime();
var startTime = dateSelector.selectedDateTime(); // Erstellt einen Zeitstring aus den im UI eingegebenen Session-Daten
var endTime = dateSelector.EndTime(); // und einen zweiten für den Endpunkt der Trainingssession.
if (dateSelector.startTimeInput.text != "")
{
if (dateSelector.startTimeInput.text != "") // Es wird dabei geprüft, ob ein fertiger Timestring in das vorgesehene Feld kopiert wurde. Dann werden die
{ // Dropdowns ignoriert.
startTimeString = dateSelector.startTimeInput.text;
}
else startTimeString = startTime.ToString("o");
@@ -146,36 +149,35 @@ namespace GhostSystem
bool more = true;
List<FrameModel> allFrames = new();
while (more)
{
List<IPostgrestQueryFilter> timestampFilter = new List<IPostgrestQueryFilter>();
while (more) // Solange Frames mit dem Filter (in chronologischer Reihenfolge) in der Datenbank vorhanden sind, wird immer ein neuer Batch von maximal 1000 Frames
{ // heruntergeladen.
List<IPostgrestQueryFilter> timestampFilter = new List<IPostgrestQueryFilter>(); // Es wird eine Filterverkettung für Startzeit und Endzeit erstellt.
timestampFilter.Add(new QueryFilter("timestamp", Postgrest.Constants.Operator.GreaterThanOrEqual, startTimeString));
timestampFilter.Add(new QueryFilter("timestamp", Postgrest.Constants.Operator.LessThan, endTimeString));
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"))
.Filter("scene_id", Postgrest.Constants.Operator.Equals, sceneId) // und neben der SceneID...
.And(timestampFilter) // ...dem SQL-Command hinzugefügt.
.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);
query = query.Filter("user_id", Postgrest.Constants.Operator.Equals, UserFilter); // Wenn die User ID angegebeen wurde, wird auch danach gefiltert.
var result = await query.Get();
var result = await query.Get(); // Es werden die Frames aus der Datenbank geladen
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;
more = result.Models.Count == batchSize; // Und in einer lokalen Liste gespeichert. Ähnlich dem Buffer beim Ghost Recorder.
}
foreach (var frame in allFrames)
foreach (var frame in allFrames) // Die Frames werden den Tracked Objects zugeordnet.
{
if (!objectTracks.ContainsKey(frame.ObjectId))
objectTracks[frame.ObjectId] = new List<FrameModel>();
@@ -262,18 +264,18 @@ namespace GhostSystem
playbackTime = Mathf.Clamp(time, 0f, totalDuration);
}
private void Update()
{
private void Update() // Solange das Replay auf "Play" steht, wird durch die Liste der Frames gegangen und die Positions- und Rotationsdaten aus selbigen angewendet,
{ // wenn die Differenz zwischen Startzeit und Timestamp des Frames der bisherigen Playback-Time entspricht.
if (!isPlaying || objectTracks.Count == 0) return;
playbackTime += Time.deltaTime * PlaybackSpeed;
if (playbackTime > totalDuration)
if (playbackTime > totalDuration) // Erreicht das Replay das Ende der Frame-Liste, wird es entwerder gestoppt
{
if (Loop)
{
playbackTime = 0f;
OnRestartLoop?.Invoke();
OnRestartLoop?.Invoke(); // oder neu gestartet, wenn die Loop-Funktion aktiviert ist.
}
else
isPlaying = false;
@@ -326,31 +328,31 @@ namespace GhostSystem
}
}
public void SetSliderScrubValue(float sliderValue)
public void SetSliderScrubValue(float sliderValue) // der aktuelle Playback-Zeitpunkt wird aktualisiert solange gescrubbt wird.
{
if (isScrubbing)
SetPlaybackTime(sliderValue * totalDuration);
}
public void OnSliderStartDrag()
public void OnSliderStartDrag() // Das Scrubbing wird gestartet, sobald der Slider angefasst wird.
{
isScrubbing = true;
}
public void OnSliderEndDrag()
public void OnSliderEndDrag() // Setzt den aktuellen Playback-Zeitpunkt auf den Timestamp, der dem übergebenen Slider-Wert entspricht.
{
isScrubbing = false;
SetPlaybackTime(playbackSlider.value * totalDuration);
}
public void RemoveDoors()
public void RemoveDoors() // Auch hier gibt es wieder eine Funktion, um vor dem Raumwechsel die Türen aus den Tracked Objects zu entfernen.
{
TrackedObjects.RemoveRange(TrackedObjects.Count - doorCount, doorCount);
doorCount = 0;
}
public void StopAwaitingFrames()
public void StopAwaitingFrames() // Kann zum Szenenwechsel aufgerufen werden, um das aktuelle Replay und das Nachladen von Frames abzubrechen.
{
awaitingFrames = false;
}