optimizations and code simplification for loading persistent object data, fixes for deleting persistent objects

main
Anton Franzluebbers 2024-07-03 23:23:01 -04:00
parent 8c5ed4ed26
commit d02eddfe79
5 changed files with 152 additions and 69 deletions

View File

@ -19,10 +19,12 @@ namespace VELConnect
public class VELConnectManager : MonoBehaviour public class VELConnectManager : MonoBehaviour
{ {
public string velConnectUrl = "http://localhost"; public string velConnectUrl = "http://localhost";
public static string VelConnectUrl public static string VelConnectUrl
{ {
get => instance.velConnectUrl; get => instance.velConnectUrl;
set { set
{
instance.velConnectUrl = value; instance.velConnectUrl = value;
SetDeviceField(new Dictionary<DeviceField, string> SetDeviceField(new Dictionary<DeviceField, string>
{ {
@ -33,6 +35,7 @@ namespace VELConnect
}); });
} }
} }
private static VELConnectManager instance; private static VELConnectManager instance;
public class State public class State
@ -897,7 +900,6 @@ namespace VELConnect
} }
} }
public static void GetRequestCallback(string url, Action<string> successCallback = null, public static void GetRequestCallback(string url, Action<string> successCallback = null,
Action<string> failureCallback = null) Action<string> failureCallback = null)
{ {
@ -940,12 +942,17 @@ namespace VELConnect
private static IEnumerator PostRequestCallbackCo(string url, string postData, private static IEnumerator PostRequestCallbackCo(string url, string postData,
Dictionary<string, string> headers = null, Action<string> successCallback = null, Dictionary<string, string> headers = null, Action<string> successCallback = null,
Action<string> failureCallback = null, string method="POST") Action<string> failureCallback = null, string method = "POST")
{ {
UnityWebRequest webRequest = new UnityWebRequest(url, method); UnityWebRequest webRequest = new UnityWebRequest(url, method);
UploadHandlerRaw uploadHandler = null;
if (!string.IsNullOrEmpty(postData))
{
byte[] bodyRaw = Encoding.UTF8.GetBytes(postData); byte[] bodyRaw = Encoding.UTF8.GetBytes(postData);
UploadHandlerRaw uploadHandler = new UploadHandlerRaw(bodyRaw); uploadHandler = new UploadHandlerRaw(bodyRaw);
webRequest.uploadHandler = uploadHandler; webRequest.uploadHandler = uploadHandler;
}
webRequest.downloadHandler = new DownloadHandlerBuffer(); webRequest.downloadHandler = new DownloadHandlerBuffer();
webRequest.SetRequestHeader("Content-Type", "application/json"); webRequest.SetRequestHeader("Content-Type", "application/json");
if (headers != null) if (headers != null)
@ -971,7 +978,7 @@ namespace VELConnect
break; break;
} }
uploadHandler.Dispose(); uploadHandler?.Dispose();
webRequest.Dispose(); webRequest.Dispose();
} }

View File

@ -1,7 +1,10 @@
using System; using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using UnityEngine; using UnityEngine;
using UnityEngine.Networking;
using VelNet; using VelNet;
namespace VELConnect namespace VELConnect
@ -10,6 +13,9 @@ namespace VELConnect
{ {
public static VelConnectPersistenceManager instance; public static VelConnectPersistenceManager instance;
// The items in this list are used when loading existing data from the server on room join
private List<VelNetPersist> sceneObjects = new List<VelNetPersist>();
private void Awake() private void Awake()
{ {
instance = this; instance = this;
@ -27,19 +33,57 @@ namespace VELConnect
private void OnJoinedRoom(string roomName) private void OnJoinedRoom(string roomName)
{ {
// if we're the first to join this room StartCoroutine(OnJoinedRoomCo(roomName));
}
private IEnumerator OnJoinedRoomCo(string roomName)
{
foreach (VelNetPersist velNetPersist in sceneObjects)
{
velNetPersist.loading = true;
}
int pagesLeft = 1;
int totalCounter = 200;
int page = 1;
List<VELConnectManager.PersistObject> allResults = new List<VELConnectManager.PersistObject>();
while (pagesLeft > 0)
{
if (totalCounter < 0)
{
Debug.LogError("Too many pages of persisted objects. This must be a bug.");
break;
}
using UnityWebRequest webRequest =
UnityWebRequest.Get(VELConnectManager.VelConnectUrl + $"/api/collections/PersistObject/records?filter=(app='{Application.productName}')&page={page++}");
yield return webRequest.SendWebRequest();
switch (webRequest.result)
{
case UnityWebRequest.Result.ConnectionError:
case UnityWebRequest.Result.DataProcessingError:
case UnityWebRequest.Result.ProtocolError:
Debug.LogError("Error: " + webRequest.error + "\n" + Environment.StackTrace);
Debug.LogError("Failed to get persisted spawned objects");
yield break;
break;
case UnityWebRequest.Result.Success:
string text = webRequest.downloadHandler.text;
VELConnectManager.RecordList<VELConnectManager.PersistObject> obj =
JsonConvert.DeserializeObject<VELConnectManager.RecordList<VELConnectManager.PersistObject>>(text);
allResults.AddRange(obj.items);
pagesLeft = obj.totalPages - obj.page;
totalCounter--;
break;
}
}
// Spawn items if we're the first to join this room
if (VelNetManager.Players.Count == 1) if (VelNetManager.Players.Count == 1)
{ {
VELConnectManager.GetRequestCallback( List<VELConnectManager.PersistObject> spawnedItems = allResults.Where(i => i.spawned && i.room == VelNetManager.Room).ToList();
VELConnectManager.VelConnectUrl + foreach (VELConnectManager.PersistObject persistObject in spawnedItems)
$"/api/collections/PersistObject/records?filter=(app='{Application.productName}')",
s =>
{
VELConnectManager.RecordList<VELConnectManager.PersistObject> obj =
JsonConvert.DeserializeObject<VELConnectManager.RecordList<VELConnectManager.PersistObject>>(s);
obj.items = obj.items.Where(i => i.spawned && i.room == VelNetManager.Room).ToList();
foreach (VELConnectManager.PersistObject persistObject in obj.items)
{ {
if (string.IsNullOrEmpty(persistObject.data)) if (string.IsNullOrEmpty(persistObject.data))
{ {
@ -53,13 +97,49 @@ namespace VELConnect
persist.persistId = persistObject.id; persist.persistId = persistObject.id;
persist.LoadData(persistObject); persist.LoadData(persistObject);
} }
}, s => { Debug.LogError("Failed to get persisted spawned objects", this); }); }
// load data for scene objects
List<VELConnectManager.PersistObject> sceneObjectData = allResults.Where(i => i.room == VelNetManager.Room).ToList();
foreach (VelNetPersist velNetPersist in sceneObjects)
{
List<VELConnectManager.PersistObject> thisObjectData = sceneObjectData.Where(i => i.network_id == velNetPersist.networkObject.sceneNetworkId.ToString()).ToList();
switch (thisObjectData.Count)
{
case < 1:
Debug.LogError($"[VelNetPersist] No data found for {velNetPersist.name} (network_id='{velNetPersist.networkObject.sceneNetworkId}')");
velNetPersist.loading = false;
continue;
case > 1:
Debug.LogError(
$"[VelNetPersist] Multiple records found for app='{Application.productName}' && room='{VelNetManager.Room}' && network_id='{velNetPersist.networkObject.sceneNetworkId}'. Deleting all but the first one.");
IEnumerable<VELConnectManager.PersistObject> toDelete = thisObjectData.Skip(1);
foreach (VELConnectManager.PersistObject persistObject in toDelete)
{
VELConnectManager.PostRequestCallback(VELConnectManager.VelConnectUrl + "/api/collections/PersistObject/records/" + persistObject.id, null, null, null,
Debug.LogError, method: "DELETE");
}
break;
}
velNetPersist.LoadData(thisObjectData.FirstOrDefault());
} }
} }
public static void RegisterSceneObject(VelNetPersist obj)
{
instance.sceneObjects.Add(obj);
}
public static void UnregisterSceneObject(VelNetPersist obj)
{
instance.sceneObjects.Remove(obj);
}
// We don't need to register objects, because they will do that automatically when they spawn if they have the VelNetPersist component // We don't need to register objects, because they will do that automatically when they spawn if they have the VelNetPersist component
// We need to unregister objects when they are destroyed because destroying could happen because we left the scene // We need to unregister objects when they are destroyed because destroying could happen because we left the scene (which shouldn't delete it from the server)
public static void UnregisterObject(NetworkObject obj) public static void DestroySpawnedObject(NetworkObject obj)
{ {
VelNetPersist[] persistedComponents = obj.GetComponents<VelNetPersist>(); VelNetPersist[] persistedComponents = obj.GetComponents<VelNetPersist>();
if (persistedComponents.Length > 1) if (persistedComponents.Length > 1)

View File

@ -12,7 +12,7 @@ namespace VELConnect
{ {
private const float interval = 5f; private const float interval = 5f;
private double nextUpdate; private double nextUpdate;
private bool loading; public bool loading;
private const bool debugLogs = false; private const bool debugLogs = false;
public string persistId; public string persistId;
@ -30,17 +30,15 @@ namespace VELConnect
private void OnEnable() private void OnEnable()
{ {
VelNetManager.OnJoinedRoom += OnJoinedRoom; if (networkObject.isSceneObject)
{
VelConnectPersistenceManager.RegisterSceneObject(this);
}
} }
private void OnDisable() private void OnDisable()
{ {
VelNetManager.OnJoinedRoom -= OnJoinedRoom; VelConnectPersistenceManager.UnregisterSceneObject(this);
}
private void OnJoinedRoom(string roomName)
{
Load();
} }
private void Load() private void Load()
@ -52,31 +50,32 @@ namespace VELConnect
{ {
// It looks like a PocketBase bug is preventing full filtering from happening: // It looks like a PocketBase bug is preventing full filtering from happening:
// $"/api/collections/PersistObject/records?filter=(app='{Application.productName}' && room='{VelNetManager.Room}' && network_id='{networkObject.sceneNetworkId}')", // $"/api/collections/PersistObject/records?filter=(app='{Application.productName}' && room='{VelNetManager.Room}' && network_id='{networkObject.sceneNetworkId}')",
VELConnectManager.GetRequestCallback( // VELConnectManager.GetRequestCallback(
VELConnectManager.VelConnectUrl + // VELConnectManager.VelConnectUrl +
$"/api/collections/PersistObject/records?filter=(app='{Application.productName}')", // $"/api/collections/PersistObject/records?filter=(app='{Application.productName}')",
s => // s =>
{ // {
VELConnectManager.RecordList<VELConnectManager.PersistObject> obj = // VELConnectManager.RecordList<VELConnectManager.PersistObject> obj =
JsonConvert.DeserializeObject<VELConnectManager.RecordList<VELConnectManager.PersistObject>>(s); // JsonConvert.DeserializeObject<VELConnectManager.RecordList<VELConnectManager.PersistObject>>(s);
obj.items = obj.items.Where(i => i.network_id == networkObject.sceneNetworkId.ToString() && i.room == VelNetManager.Room).ToList(); // obj.items = obj.items.Where(i => i.network_id == networkObject.sceneNetworkId.ToString() && i.room == VelNetManager.Room).ToList();
if (obj.items.Count < 1) // if (obj.items.Count < 1)
{ // {
Debug.LogError("[VelNetPersist] No data found for " + name); // Debug.LogError("[VelNetPersist] No data found for " + name);
loading = false; // loading = false;
return; // return;
} // }
else if (obj.items.Count > 1) // else if (obj.items.Count > 1)
{ // {
Debug.LogError( // Debug.LogError(
$"[VelNetPersist] Multiple records found for app='{Application.productName}' && room='{VelNetManager.Room}' && network_id='{networkObject.sceneNetworkId}'. Using the first one."); // $"[VelNetPersist] Multiple records found for app='{Application.productName}' && room='{VelNetManager.Room}' && network_id='{networkObject.sceneNetworkId}'. Using the first one.");
} // }
//
LoadData(obj.items.FirstOrDefault()); // LoadData(obj.items.FirstOrDefault());
}, s => { loading = false; }); // }, s => { loading = false; });
} }
else else
{ {
Debug.LogError("TODO idk when this would happen");
VELConnectManager.GetRequestCallback(VELConnectManager.VelConnectUrl + "/api/collections/PersistObject/records/" + persistId, s => VELConnectManager.GetRequestCallback(VELConnectManager.VelConnectUrl + "/api/collections/PersistObject/records/" + persistId, s =>
{ {
VELConnectManager.PersistObject obj = JsonConvert.DeserializeObject<VELConnectManager.PersistObject>(s); VELConnectManager.PersistObject obj = JsonConvert.DeserializeObject<VELConnectManager.PersistObject>(s);
@ -160,7 +159,7 @@ namespace VELConnect
} }
} }
public void Delete(Action<VELConnectManager.PersistObject> successCallback = null) public void Delete()
{ {
if (string.IsNullOrEmpty(persistId)) if (string.IsNullOrEmpty(persistId))
{ {
@ -168,12 +167,8 @@ namespace VELConnect
return; return;
} }
VELConnectManager.PostRequestCallback(VELConnectManager.VelConnectUrl + "/api/collections/PersistObject/records/" + persistId, null, null, VELConnectManager.PostRequestCallback(VELConnectManager.VelConnectUrl + "/api/collections/PersistObject/records/" + persistId,
s => null, null, null, Debug.LogError,
{
VELConnectManager.PersistObject resp = JsonConvert.DeserializeObject<VELConnectManager.PersistObject>(s);
successCallback?.Invoke(resp);
}, Debug.LogError,
method: "DELETE"); method: "DELETE");
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "edu.uga.engr.vel.vel-connect", "name": "edu.uga.engr.vel.vel-connect",
"displayName": "VEL-Connect", "displayName": "VEL-Connect",
"version": "5.0.2", "version": "5.0.3",
"unity": "2019.1", "unity": "2019.1",
"description": "Web-based configuration for VR applications", "description": "Web-based configuration for VR applications",
"keywords": [], "keywords": [],

View File

@ -49,6 +49,7 @@ export type DeviceRecord = {
current_room?: string current_room?: string
data: RecordIdString data: RecordIdString
friendly_name?: string friendly_name?: string
friendlier_name?: string
last_online?: IsoDateString last_online?: IsoDateString
modified_by?: string modified_by?: string
os_info?: string os_info?: string