reworking persistence to be per-object, working on system to persist spawned objects

dev
Anton Franzluebbers 2024-03-08 16:15:17 -05:00
parent b16de5434d
commit a68d12aab5
10 changed files with 458 additions and 3227 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -71,7 +71,7 @@ namespace VELConnect
public class DataBlock public class DataBlock
{ {
public readonly string id; public string id;
public readonly DateTime created; public readonly DateTime created;
public readonly DateTime updated; public readonly DateTime updated;
public string block_id; public string block_id;
@ -929,12 +929,41 @@ namespace VELConnect
webRequest.Dispose(); webRequest.Dispose();
} }
public static void SetDataBlock(string blockId, State.DataBlock dataBlock) public static void SetDataBlock(State.DataBlock dataBlock, Action<State.DataBlock> successCallback = null)
{
PostRequestCallback(instance.velConnectUrl + "/api/collections/DataBlock/records", JsonConvert.SerializeObject(dataBlock, Formatting.None,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
}), null, s =>
{
if (successCallback != null)
{
State.DataBlock resp = JsonConvert.DeserializeObject<State.DataBlock>(s);
successCallback?.Invoke(resp);
}
});
}
/// <summary>
/// Setting with a block ID will update the existing block, otherwise it will create a new one
/// </summary>
/// <param name="blockId"></param>
/// <param name="dataBlock"></param>
/// <param name="successCallback"></param>
public static void SetDataBlock([CanBeNull] string blockId, State.DataBlock dataBlock, Action<State.DataBlock> successCallback = null)
{ {
PostRequestCallback(instance.velConnectUrl + "/data_block/" + blockId, JsonConvert.SerializeObject(dataBlock, Formatting.None, new JsonSerializerSettings PostRequestCallback(instance.velConnectUrl + "/data_block/" + blockId, JsonConvert.SerializeObject(dataBlock, Formatting.None, new JsonSerializerSettings
{ {
NullValueHandling = NullValueHandling.Ignore NullValueHandling = NullValueHandling.Ignore
})); }), null, s =>
{
if (successCallback != null)
{
State.DataBlock resp = JsonConvert.DeserializeObject<State.DataBlock>(s);
successCallback?.Invoke(resp);
}
});
} }
public static void GetDataBlock(string blockId, Action<State.DataBlock> successCallback = null, Action<string> failureCallback = null) public static void GetDataBlock(string blockId, Action<State.DataBlock> successCallback = null, Action<string> failureCallback = null)

View File

@ -0,0 +1,108 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using UnityEngine;
using VELConnect;
using VelNet;
namespace VELConnect
{
public class VelConnectPersistenceManager : MonoBehaviour
{
public static VelConnectPersistenceManager instance;
public class SpawnedObjectData
{
public string prefabName;
public string base64ObjectData;
public string networkId;
public int componentIdx;
}
private void Awake()
{
instance = this;
}
private void OnEnable()
{
VelNetManager.OnJoinedRoom += OnJoinedRoom;
}
private void OnDisable()
{
VelNetManager.OnJoinedRoom -= OnJoinedRoom;
}
private void OnJoinedRoom(string roomName)
{
if (VelNetManager.Players.Count == 0)
{
string spawnedObjects = VELConnectManager.GetRoomData("spawned_objects", "[]");
List<string> spawnedObjectList = JsonConvert.DeserializeObject<List<string>>(spawnedObjects);
List<NetworkObject> spawnedNetworkObjects = new List<NetworkObject>();
GetSpawnedObjectData(spawnedObjectList, (list) =>
{
foreach (SpawnedObjectData obj in list)
{
NetworkObject spawnedObj = spawnedNetworkObjects.Find(i => i.networkId == obj.networkId);
if (spawnedObj == null)
{
spawnedObj = VelNetManager.NetworkInstantiate(obj.prefabName);
spawnedNetworkObjects.Add(spawnedObj);
}
spawnedObj.syncedComponents[obj.componentIdx].ReceiveBytes(Convert.FromBase64String(obj.base64ObjectData));
}
});
}
}
private class DataBlocksResponse
{
public List<VELConnectManager.State.DataBlock> items;
}
private static void GetSpawnedObjectData(List<string> spawnedObjectList, Action<List<SpawnedObjectData>> callback)
{
VELConnectManager.GetRequestCallback($"/api/collections/DataBlock/records?filter=({string.Join(" || ", "id=\"" + spawnedObjectList + "\"")})", (response) =>
{
DataBlocksResponse parsedResponse = JsonConvert.DeserializeObject<DataBlocksResponse>(response);
callback(parsedResponse.items.Select(i => new SpawnedObjectData()
{
networkId = i.block_id.Split("_")[-1],
componentIdx = int.Parse(i.block_id.Split("_").Last()),
prefabName = i.TryGetData("name"),
base64ObjectData = i.TryGetData("state")
}).ToList());
});
}
public static void RegisterObject(NetworkObject obj)
{
instance.StartCoroutine(instance.RegisterObjectCo(obj));
}
private IEnumerator RegisterObjectCo(NetworkObject obj)
{
// upload all the persisted components, then add those components to the room data
VelNetPersist[] persistedComponents = obj.GetComponents<VelNetPersist>();
List<VELConnectManager.State.DataBlock> responses = new List<VELConnectManager.State.DataBlock>();
double startTime = Time.timeAsDouble;
foreach (VelNetPersist velNetPersist in persistedComponents)
{
velNetPersist.Save(s => { responses.Add(s); });
}
while (responses.Count < persistedComponents.Length && Time.timeAsDouble - startTime < 5)
{
yield return null;
}
VELConnectManager.SetRoomData("spawned_objects", JsonConvert.SerializeObject(responses.Select(i => i.block_id).ToList()));
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 85006f287450ecc4caedd7925e1198e4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using UnityEngine; using UnityEngine;
using VelNet; using VelNet;
@ -7,8 +9,16 @@ namespace VELConnect
{ {
public class VelNetPersist : MonoBehaviour public class VelNetPersist : MonoBehaviour
{ {
public SyncState syncState; private class ComponentState
private string Id => $"{Application.productName}_{VelNetManager.Room}_{syncState.networkObject.sceneNetworkId}_{syncState.networkObject.syncedComponents.IndexOf(syncState)}"; {
public int componentIdx;
public string state;
}
public SyncState[] syncStateComponents;
private string Id => $"{Application.productName}_{VelNetManager.Room}_{syncStateComponents.FirstOrDefault()?.networkObject.sceneNetworkId}";
private const float interval = 5f; private const float interval = 5f;
private double nextUpdate; private double nextUpdate;
private bool loading; private bool loading;
@ -19,7 +29,7 @@ namespace VELConnect
if (Time.timeAsDouble > nextUpdate && VelNetManager.InRoom && !loading) if (Time.timeAsDouble > nextUpdate && VelNetManager.InRoom && !loading)
{ {
nextUpdate = Time.timeAsDouble + interval + UnityEngine.Random.Range(0, interval); nextUpdate = Time.timeAsDouble + interval + UnityEngine.Random.Range(0, interval);
if (syncState.networkObject.IsMine) if (syncStateComponents.FirstOrDefault()?.networkObject.IsMine == true)
{ {
Save(); Save();
} }
@ -45,60 +55,77 @@ namespace VELConnect
{ {
loading = true; loading = true;
if (debugLogs) Debug.Log($"[VelNetPersist] Loading {Id}"); if (debugLogs) Debug.Log($"[VelNetPersist] Loading {Id}");
if (syncState == null)
{
Debug.LogError("SyncState is null for Persist", this);
return;
}
if (syncState.networkObject == null)
{
Debug.LogError("Network Object is null for SyncState", syncState);
return;
}
VELConnectManager.GetDataBlock(Id, data => VELConnectManager.GetDataBlock(Id, data =>
{ {
if (!data.data.TryGetValue("state", out string d)) if (!data.data.TryGetValue("components", out string d))
{ {
Debug.LogError($"[VelNetPersist] Failed to parse {Id}"); Debug.LogError($"[VelNetPersist] Failed to parse {Id}");
return; return;
} }
if (syncState == null)
List<ComponentState> componentData = JsonConvert.DeserializeObject<List<ComponentState>>(d);
if (componentData.Count != syncStateComponents.Length)
{ {
Debug.LogError("[VelNetPersist] Object doesn't exist anymore"); Debug.LogError($"[VelNetPersist] Different number of components");
return;
}
for (int i = 0; i < syncStateComponents.Length; i++)
{
syncStateComponents[i].UnpackState(Convert.FromBase64String(componentData[i].state));
} }
syncState.UnpackState(Convert.FromBase64String(d));
if (debugLogs) Debug.Log($"[VelNetPersist] Loaded {Id}"); if (debugLogs) Debug.Log($"[VelNetPersist] Loaded {Id}");
loading = false; loading = false;
}, s => }, s => { loading = false; });
{
loading = false;
});
} }
private void Save()
public void Save(Action<VELConnectManager.State.DataBlock> successCallback = null)
{ {
if (debugLogs) Debug.Log($"[VelNetPersist] Saving {Id}"); if (debugLogs) Debug.Log($"[VelNetPersist] Saving {Id}");
if (syncState == null)
if (syncStateComponents.FirstOrDefault()?.networkObject == null)
{ {
Debug.LogError("SyncState is null for Persist", this); Debug.LogError("First SyncState doesn't have a NetworkObject", this);
return; return;
} }
if (syncState.networkObject == null)
List<ComponentState> componentData = new List<ComponentState>();
foreach (SyncState syncState in syncStateComponents)
{ {
Debug.LogError("Network Object is null for SyncState", syncState); if (syncState == null)
return; {
Debug.LogError("SyncState is null for Persist", this);
return;
}
if (syncState.networkObject == null)
{
Debug.LogError("Network Object is null for SyncState", syncState);
return;
}
componentData.Add(new ComponentState()
{
componentIdx = syncState.networkObject.syncedComponents.IndexOf(syncState),
state = Convert.ToBase64String(syncState.PackState())
});
} }
VELConnectManager.SetDataBlock(Id, new VELConnectManager.State.DataBlock() VELConnectManager.SetDataBlock(Id, new VELConnectManager.State.DataBlock()
{ {
id = Id,
block_id = Id,
category = "object_persist", category = "object_persist",
data = new Dictionary<string, string> data = new Dictionary<string, string>
{ {
{ "name", syncState.networkObject.name }, { "name", syncStateComponents.FirstOrDefault()?.networkObject.name },
{ "state", Convert.ToBase64String(syncState.PackState()) } { "components", JsonConvert.SerializeObject(componentData) }
} }
}); }, s => { successCallback?.Invoke(s); });
} }
} }
} }

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": "4.0.7", "version": "4.0.8",
"unity": "2019.1", "unity": "2019.1",
"description": "Web-based configuration for VR applications", "description": "Web-based configuration for VR applications",
"keywords": [], "keywords": [],

View File

@ -1,88 +1,87 @@
module velaboratory/velconnect module velaboratory/velconnect
go 1.18 go 1.22.1
require ( require (
github.com/labstack/echo/v5 v5.0.0-20220201181537-ed2888cfa198 github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
github.com/pocketbase/dbx v1.10.0 github.com/pocketbase/dbx v1.10.1
github.com/pocketbase/pocketbase v0.16.7 github.com/pocketbase/pocketbase v0.22.3
) )
require ( require (
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go v1.44.289 // indirect github.com/aws/aws-sdk-go v1.50.32 // indirect
github.com/aws/aws-sdk-go-v2 v1.18.1 // indirect github.com/aws/aws-sdk-go-v2 v1.25.2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 // indirect
github.com/aws/aws-sdk-go-v2/config v1.18.27 // indirect github.com/aws/aws-sdk-go-v2/config v1.27.6 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.26 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.6 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.2 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.70 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.29 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.2 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.35.0 // indirect github.com/aws/aws-sdk-go-v2/service/s3 v1.51.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.20.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.19.2 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.28.3 // indirect
github.com/aws/smithy-go v1.13.5 // indirect github.com/aws/smithy-go v1.20.1 // indirect
github.com/disintegration/imaging v1.6.2 // indirect github.com/disintegration/imaging v1.6.2 // indirect
github.com/domodwyer/mailyak/v3 v3.6.0 // indirect github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.15.0 // indirect github.com/fatih/color v1.16.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/ganigeorgiev/fexpr v0.3.0 // indirect github.com/ganigeorgiev/fexpr v0.4.0 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/google/wire v0.5.0 // indirect github.com/google/wire v0.6.0 // indirect
github.com/googleapis/gax-go/v2 v2.11.0 // indirect github.com/googleapis/gax-go/v2 v2.12.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/spf13/cast v1.5.1 // indirect github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
gocloud.dev v0.30.0 // indirect gocloud.dev v0.36.0 // indirect
golang.org/x/crypto v0.10.0 // indirect golang.org/x/crypto v0.21.0 // indirect
golang.org/x/image v0.8.0 // indirect golang.org/x/image v0.15.0 // indirect
golang.org/x/mod v0.11.0 // indirect golang.org/x/net v0.22.0 // indirect
golang.org/x/net v0.11.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/oauth2 v0.9.0 // indirect golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.9.0 // indirect golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.9.0 // indirect golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.10.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.10.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.168.0 // indirect
google.golang.org/api v0.128.0 // indirect google.golang.org/appengine v1.6.8 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/grpc v1.62.1 // indirect
google.golang.org/grpc v1.56.1 // indirect google.golang.org/protobuf v1.33.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
lukechampine.com/uint128 v1.3.0 // indirect modernc.org/libc v1.41.0 // indirect
modernc.org/cc/v3 v3.41.0 // indirect modernc.org/mathutil v1.6.0 // indirect
modernc.org/ccgo/v3 v3.16.14 // indirect modernc.org/memory v1.7.2 // indirect
modernc.org/libc v1.24.1 // indirect modernc.org/sqlite v1.29.2 // indirect
modernc.org/mathutil v1.5.0 // indirect modernc.org/strutil v1.2.0 // indirect
modernc.org/memory v1.6.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/sqlite v1.23.1 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.1.0 // indirect modernc.org/token v1.1.0 // indirect
) )

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,8 @@ import (
"encoding/json" "encoding/json"
"log" "log"
"net/http" "net/http"
"os"
"strings"
_ "velaboratory/velconnect/pb_migrations" _ "velaboratory/velconnect/pb_migrations"
@ -19,16 +21,18 @@ func main() {
app := pocketbase.New() app := pocketbase.New()
// loosely check if it was executed using "go run" // loosely check if it was executed using "go run"
// isGoRun := strings.HasPrefix(os.Args[0], os.TempDir()) isGoRun := strings.HasPrefix(os.Args[0], os.TempDir())
migratecmd.MustRegister(app, app.RootCmd, &migratecmd.Options{ migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
// enable auto creation of migration files when making collection changes // enable auto creation of migration files when making collection changes in the Admin UI
// (the isGoRun check is to enable it only during development) // (the isGoRun check is to enable it only during development)
Automigrate: true, Automigrate: isGoRun,
}) })
app.OnBeforeServe().Add(func(e *core.ServeEvent) error { app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
// or you can also use the shorter e.Router.GET("/articles/:slug", handler, middlewares...) // or you can also use the shorter e.Router.GET("/articles/:slug", handler, middlewares...)
e.Router.GET("/*", apis.StaticDirectoryHandler(os.DirFS("./pb_public"), false))
e.Router.POST("/data_block/:block_id", func(c echo.Context) error { e.Router.POST("/data_block/:block_id", func(c echo.Context) error {
dao := app.Dao() dao := app.Dao()
@ -260,7 +264,7 @@ func main() {
} }
} }
func mergeDataBlock(requestData *models.RequestData, record *models.Record) { func mergeDataBlock(requestData *models.RequestInfo, record *models.Record) {
// get the new data // get the new data
newData, hasNewData := requestData.Data["data"] newData, hasNewData := requestData.Data["data"]