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

View File

@ -1,7 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Networking;
using VelNet;
namespace VELConnect
@ -10,6 +13,9 @@ namespace VELConnect
{
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()
{
instance = this;
@ -27,39 +33,113 @@ namespace VELConnect
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)
{
VELConnectManager.GetRequestCallback(
VELConnectManager.VelConnectUrl +
$"/api/collections/PersistObject/records?filter=(app='{Application.productName}')",
s =>
List<VELConnectManager.PersistObject> spawnedItems = allResults.Where(i => i.spawned && i.room == VelNetManager.Room).ToList();
foreach (VELConnectManager.PersistObject persistObject in spawnedItems)
{
if (string.IsNullOrEmpty(persistObject.data))
{
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();
Debug.LogError("Persisted object has no data");
continue;
}
foreach (VELConnectManager.PersistObject persistObject in obj.items)
NetworkObject spawnedObj = VelNetManager.NetworkInstantiate(persistObject.name, Convert.FromBase64String(persistObject.data));
VelNetPersist persist = spawnedObj.GetComponent<VelNetPersist>();
persist.persistId = persistObject.id;
persist.LoadData(persistObject);
}
}
// 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)
{
if (string.IsNullOrEmpty(persistObject.data))
{
Debug.LogError("Persisted object has no data");
continue;
}
NetworkObject spawnedObj = VelNetManager.NetworkInstantiate(persistObject.name, Convert.FromBase64String(persistObject.data));
VelNetPersist persist = spawnedObj.GetComponent<VelNetPersist>();
persist.persistId = persistObject.id;
persist.LoadData(persistObject);
VELConnectManager.PostRequestCallback(VELConnectManager.VelConnectUrl + "/api/collections/PersistObject/records/" + persistObject.id, null, null, null,
Debug.LogError, method: "DELETE");
}
}, s => { Debug.LogError("Failed to get persisted spawned objects", this); });
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 need to unregister objects when they are destroyed because destroying could happen because we left the scene
public static void UnregisterObject(NetworkObject obj)
// 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 DestroySpawnedObject(NetworkObject obj)
{
VelNetPersist[] persistedComponents = obj.GetComponents<VelNetPersist>();
if (persistedComponents.Length > 1)

View File

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

View File

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

View File

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