Skip to content

Commit

Permalink
Improvements from v0.7.2-beta (#10)
Browse files Browse the repository at this point in the history
* Improvements from v0.7.2-beta

* Added EditorNetworkManager

* Updated Unity package version

---------

Co-authored-by: John Detter <[email protected]>
  • Loading branch information
jdetter and John Detter authored Nov 1, 2023
1 parent df2c648 commit 08f9b88
Show file tree
Hide file tree
Showing 6 changed files with 608 additions and 293 deletions.
155 changes: 47 additions & 108 deletions Scripts/ClientCache.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Linq;
using System.Net.Http.Headers;
using System.Reflection;
using Google.Protobuf;
using ClientApi;
using SpacetimeDB.SATS;
using System.Numerics;
using System.Runtime.CompilerServices;

namespace SpacetimeDB
{
Expand All @@ -19,19 +16,42 @@ public class ByteArrayComparer : IEqualityComparer<byte[]>
{
public bool Equals(byte[] left, byte[] right)
{
if (left == null || right == null)
if (ReferenceEquals(left, right))
{
return left == right;
return true;
}

return left.SequenceEqual(right);
if (left == null || right == null || left.Length != right.Length)
{
return false;
}

return EqualsUnvectorized(left, right);

}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool EqualsUnvectorized(byte[] left, byte[] right)
{
for (int i = 0; i < left.Length; i++)
{
if (left[i] != right[i])
{
return false;
}
}

return true;
}

public int GetHashCode(byte[] key)
public int GetHashCode(byte[] obj)
{
if (key == null)
throw new ArgumentNullException(nameof(key));
return key.Sum(b => b);
int hash = 17;
foreach (byte b in obj)
{
hash = hash * 31 + b;
}
return hash;
}
}

Expand All @@ -45,9 +65,6 @@ public int GetHashCode(byte[] key)
// Maps from primary key to type value
public readonly Dictionary<byte[], (AlgebraicValue, object)> entries;

// Maps from primary key to decoded value
public readonly ConcurrentDictionary<byte[], (AlgebraicValue, object)> decodedValues;

public Type ClientTableType
{
get => clientTableType;
Expand Down Expand Up @@ -97,48 +114,11 @@ public TableCache(Type clientTableType, AlgebraicType rowSchema, Func<AlgebraicV
GetPrimaryKeyTypeFunc = (Func<AlgebraicType, AlgebraicType>)clientTableType.GetMethod("GetPrimaryKeyType", BindingFlags.Static | BindingFlags.Public)
?.CreateDelegate(typeof(Func<AlgebraicType, AlgebraicType>));
entries = new Dictionary<byte[], (AlgebraicValue, object)>(new ByteArrayComparer());
decodedValues = new ConcurrentDictionary<byte[], (AlgebraicValue, object)>(new ByteArrayComparer());
}

public bool GetDecodedValue(byte[] pk, out AlgebraicValue value, out object obj)
{
if (decodedValues.TryGetValue(pk, out var decoded))
{
value = decoded.Item1;
obj = decoded.Item2;
return true;
}

value = null;
obj = null;
return false;
}

/// <summary>
/// Decodes the given AlgebraicValue into the out parameter `obj`.
/// </summary>
/// <param name="pk">The primary key of the row associated with `value`.</param>
/// <param name="value">The AlgebraicValue to decode.</param>
/// <param name="obj">The domain object for `value`</param>
public void SetDecodedValue(byte[] pk, AlgebraicValue value, out object obj)
{
if (decodedValues.TryGetValue(pk, out var existingObj))
{
obj = existingObj.Item2;
}
else
{
var decoded = (value, decoderFunc(value));
decodedValues[pk] = decoded;
obj = decoded.Item2;
}
}

/// <summary>
/// Decodes the given AlgebraicValue into the out parameter `obj`.
/// Does NOT cache the resulting value! This should only be used with rows
/// that don't participate in the usual client cache lifecycle, i.e. OneOffQuery.
/// </summary>
/// <param name="value">The AlgebraicValue to decode.</param>
/// <param name="obj">The domain object for `value`</param>
public void SetAndForgetDecodedValue(AlgebraicValue value, out object obj)
Expand All @@ -149,63 +129,34 @@ public void SetAndForgetDecodedValue(AlgebraicValue value, out object obj)
/// <summary>
/// Inserts the value into the table. There can be no existing value with the provided pk.
/// </summary>
/// <returns></returns>
public object InsertEntry(byte[] rowPk)
/// <returns>True if the row was inserted, false if the row wasn't inserted because it was a duplicate.</returns>
public bool InsertEntry(byte[] rowPk, AlgebraicValue value)
{
if (entries.TryGetValue(rowPk, out var existingValue))
{
// Debug.LogWarning($"We tried to insert a database row that already exists. table={Name} RowPK={Convert.ToBase64String(rowPk)}");
return existingValue.Item2;
}

if (GetDecodedValue(rowPk, out var value, out var obj))
if (entries.ContainsKey(rowPk))
{
entries[rowPk] = (value, obj);
return obj;
return false;
}

// Read failure
SpacetimeDBClient.instance.Logger.LogError(
$"Read error when converting row value for table: {clientTableType.Name} rowPk={Convert.ToBase64String(rowPk)} (version issue?)");
return null;
}

/// <summary>
/// Updates an entry. Returns whether or not the update was successful. Updates only succeed if
/// a previous value was overwritten.
/// </summary>
/// <param name="pk">The primary key that uniquely identifies this row</param>
/// <param name="newValueByteString">The new for the table entry</param>
/// <returns>True when the old value was removed and the new value was inserted.</returns>
public bool UpdateEntry(ByteString pk, ByteString newValueByteString)
{
// We have to figure out if pk is going to change or not
throw new InvalidOperationException();

// Insert the row into our table
entries[rowPk] = (value, decoderFunc(value));
return true;
}

/// <summary>
/// Deletes a value from the table.
/// </summary>
/// <param name="rowPk">The primary key that uniquely identifies this row</param>
/// <returns></returns>
public object DeleteEntry(byte[] rowPk)
public bool DeleteEntry(byte[] rowPk)
{
if (entries.TryGetValue(rowPk, out var value))
{
entries.Remove(rowPk);
return value.Item2;
}

// SpacetimeDB is asking us to delete something we don't have, this makes no sense. We can
// fabricate the deletion by trying to look it up in our local decode table.
if (decodedValues.TryGetValue(rowPk, out var decodedValue))
{
SpacetimeDBClient.instance.Logger.LogWarning("Deleting value that we don't have (using cached value)");
return decodedValue.Item2;
return true;
}

SpacetimeDBClient.instance.Logger.LogWarning("Deleting value that we don't have (no cached value available)");
return null;
return false;
}

/// <summary>
Expand Down Expand Up @@ -235,23 +186,9 @@ public AlgebraicValue GetPrimaryKeyValue(AlgebraicValue row)
return GetPrimaryKeyValueFunc != null ? GetPrimaryKeyValueFunc.Invoke(row) : null;
}

public AlgebraicType GetPrimaryKeyType(AlgebraicType row)
{
return GetPrimaryKeyTypeFunc != null ? GetPrimaryKeyTypeFunc.Invoke(row) : null;
}

public bool ComparePrimaryKey(byte[] rowPk1, byte[] rowPk2)
public AlgebraicType GetPrimaryKeyType()
{
if (!decodedValues.TryGetValue(rowPk1, out var v1))
{
return false;
}
if (!decodedValues.TryGetValue(rowPk2, out var v2))
{
return false;
}

return (bool)ComparePrimaryKeyFunc.Invoke(rowSchema, v1.Item1, v2.Item1);
return GetPrimaryKeyTypeFunc != null ? GetPrimaryKeyTypeFunc.Invoke(rowSchema) : null;
}
}

Expand Down Expand Up @@ -320,5 +257,7 @@ public int Count(string name)
}

public IEnumerable<string> GetTableNames() => tables.Keys;

public IEnumerable<TableCache> GetTables() => tables.Values;
}
}
110 changes: 110 additions & 0 deletions Scripts/EditorNetworkManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

namespace SpacetimeDB
{
public class EditorNetworkManager
{
SpacetimeDB.WebSocket webSocket;
public bool IsConnected => isConnected;
private bool isConnected;

public event Action<string,ClientApi.Event.Types.Status> onTransactionComplete;

public static string GetTokenKey()
{
var key = "spacetimedb.identity_token";
#if UNITY_EDITOR
// Different editors need different keys
key += $" - {Application.dataPath}";
#endif
return key;
}

public EditorNetworkManager(string host, string database)
{
var options = new SpacetimeDB.ConnectOptions
{
//v1.bin.spacetimedb
//v1.text.spacetimedb
Protocol = "v1.bin.spacetimedb",
};
webSocket = new SpacetimeDB.WebSocket(new SpacetimeDB.UnityDebugLogger(), options);

var token = PlayerPrefs.HasKey(GetTokenKey()) ? PlayerPrefs.GetString(GetTokenKey()) : null;
webSocket.OnConnect += () =>
{
Debug.Log("Connected");
isConnected = true;
};

webSocket.OnConnectError += (code, message) =>
{
Debug.Log($"Connection error {message}");
};

webSocket.OnClose += (code, error) =>
{
Debug.Log($"Websocket closed");
isConnected = false;
};

webSocket.OnMessage += OnMessageReceived;

if (!host.StartsWith("http://") && !host.StartsWith("https://") && !host.StartsWith("ws://") &&
!host.StartsWith("wss://"))
{
host = $"ws://{host}";
}

webSocket.Connect(token, host, database, Address.Random());
}

private void OnMessageReceived(byte[] bytes)
{
var message = ClientApi.Message.Parser.ParseFrom(bytes);
if(message.TypeCase == ClientApi.Message.TypeOneofCase.TransactionUpdate)
{
var reducer = message.TransactionUpdate.Event.FunctionCall.Reducer;
var status = message.TransactionUpdate.Event.Status;
onTransactionComplete?.Invoke(reducer, status);
}
}

public async void CallReducer(string reducer, params object[] args)
{
if(!isConnected)
{
Debug.Log("Not connected");
}

var _message = new SpacetimeDBClient.ReducerCallRequest
{
fn = reducer,
args = args,
};
Newtonsoft.Json.JsonSerializerSettings _settings = new Newtonsoft.Json.JsonSerializerSettings
{
Converters = { new SpacetimeDB.SomeWrapperConverter(), new SpacetimeDB.EnumWrapperConverter() },
ContractResolver = new SpacetimeDB.JsonContractResolver(),
};
var json = Newtonsoft.Json.JsonConvert.SerializeObject(_message, _settings);
webSocket.Send(Encoding.ASCII.GetBytes("{ \"call\": " + json + " }"));
}

public void Update()
{
webSocket.Update();
}

public void Close()
{
if (webSocket != null)
{
webSocket.Close();
}
}
}
}
11 changes: 11 additions & 0 deletions Scripts/EditorNetworkManager.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 08f9b88

Please sign in to comment.