Skip to content

Commit 08f9b88

Browse files
authored
Improvements from v0.7.2-beta (#10)
* Improvements from v0.7.2-beta * Added EditorNetworkManager * Updated Unity package version --------- Co-authored-by: John Detter <no-reply@boppygames.gg>
1 parent df2c648 commit 08f9b88

6 files changed

Lines changed: 608 additions & 293 deletions

File tree

Scripts/ClientCache.cs

Lines changed: 47 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
using System;
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
4-
using System.ComponentModel.Design;
5-
using System.Linq;
6-
using System.Net.Http.Headers;
74
using System.Reflection;
8-
using Google.Protobuf;
9-
using ClientApi;
105
using SpacetimeDB.SATS;
6+
using System.Numerics;
7+
using System.Runtime.CompilerServices;
118

129
namespace SpacetimeDB
1310
{
@@ -19,19 +16,42 @@ public class ByteArrayComparer : IEqualityComparer<byte[]>
1916
{
2017
public bool Equals(byte[] left, byte[] right)
2118
{
22-
if (left == null || right == null)
19+
if (ReferenceEquals(left, right))
2320
{
24-
return left == right;
21+
return true;
2522
}
2623

27-
return left.SequenceEqual(right);
24+
if (left == null || right == null || left.Length != right.Length)
25+
{
26+
return false;
27+
}
28+
29+
return EqualsUnvectorized(left, right);
30+
31+
}
32+
33+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
34+
private bool EqualsUnvectorized(byte[] left, byte[] right)
35+
{
36+
for (int i = 0; i < left.Length; i++)
37+
{
38+
if (left[i] != right[i])
39+
{
40+
return false;
41+
}
42+
}
43+
44+
return true;
2845
}
2946

30-
public int GetHashCode(byte[] key)
47+
public int GetHashCode(byte[] obj)
3148
{
32-
if (key == null)
33-
throw new ArgumentNullException(nameof(key));
34-
return key.Sum(b => b);
49+
int hash = 17;
50+
foreach (byte b in obj)
51+
{
52+
hash = hash * 31 + b;
53+
}
54+
return hash;
3555
}
3656
}
3757

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

48-
// Maps from primary key to decoded value
49-
public readonly ConcurrentDictionary<byte[], (AlgebraicValue, object)> decodedValues;
50-
5168
public Type ClientTableType
5269
{
5370
get => clientTableType;
@@ -97,48 +114,11 @@ public TableCache(Type clientTableType, AlgebraicType rowSchema, Func<AlgebraicV
97114
GetPrimaryKeyTypeFunc = (Func<AlgebraicType, AlgebraicType>)clientTableType.GetMethod("GetPrimaryKeyType", BindingFlags.Static | BindingFlags.Public)
98115
?.CreateDelegate(typeof(Func<AlgebraicType, AlgebraicType>));
99116
entries = new Dictionary<byte[], (AlgebraicValue, object)>(new ByteArrayComparer());
100-
decodedValues = new ConcurrentDictionary<byte[], (AlgebraicValue, object)>(new ByteArrayComparer());
101-
}
102-
103-
public bool GetDecodedValue(byte[] pk, out AlgebraicValue value, out object obj)
104-
{
105-
if (decodedValues.TryGetValue(pk, out var decoded))
106-
{
107-
value = decoded.Item1;
108-
obj = decoded.Item2;
109-
return true;
110-
}
111-
112-
value = null;
113-
obj = null;
114-
return false;
115117
}
116118

117119
/// <summary>
118120
/// Decodes the given AlgebraicValue into the out parameter `obj`.
119121
/// </summary>
120-
/// <param name="pk">The primary key of the row associated with `value`.</param>
121-
/// <param name="value">The AlgebraicValue to decode.</param>
122-
/// <param name="obj">The domain object for `value`</param>
123-
public void SetDecodedValue(byte[] pk, AlgebraicValue value, out object obj)
124-
{
125-
if (decodedValues.TryGetValue(pk, out var existingObj))
126-
{
127-
obj = existingObj.Item2;
128-
}
129-
else
130-
{
131-
var decoded = (value, decoderFunc(value));
132-
decodedValues[pk] = decoded;
133-
obj = decoded.Item2;
134-
}
135-
}
136-
137-
/// <summary>
138-
/// Decodes the given AlgebraicValue into the out parameter `obj`.
139-
/// Does NOT cache the resulting value! This should only be used with rows
140-
/// that don't participate in the usual client cache lifecycle, i.e. OneOffQuery.
141-
/// </summary>
142122
/// <param name="value">The AlgebraicValue to decode.</param>
143123
/// <param name="obj">The domain object for `value`</param>
144124
public void SetAndForgetDecodedValue(AlgebraicValue value, out object obj)
@@ -149,63 +129,34 @@ public void SetAndForgetDecodedValue(AlgebraicValue value, out object obj)
149129
/// <summary>
150130
/// Inserts the value into the table. There can be no existing value with the provided pk.
151131
/// </summary>
152-
/// <returns></returns>
153-
public object InsertEntry(byte[] rowPk)
132+
/// <returns>True if the row was inserted, false if the row wasn't inserted because it was a duplicate.</returns>
133+
public bool InsertEntry(byte[] rowPk, AlgebraicValue value)
154134
{
155-
if (entries.TryGetValue(rowPk, out var existingValue))
156-
{
157-
// Debug.LogWarning($"We tried to insert a database row that already exists. table={Name} RowPK={Convert.ToBase64String(rowPk)}");
158-
return existingValue.Item2;
159-
}
160-
161-
if (GetDecodedValue(rowPk, out var value, out var obj))
135+
if (entries.ContainsKey(rowPk))
162136
{
163-
entries[rowPk] = (value, obj);
164-
return obj;
137+
return false;
165138
}
166-
167-
// Read failure
168-
SpacetimeDBClient.instance.Logger.LogError(
169-
$"Read error when converting row value for table: {clientTableType.Name} rowPk={Convert.ToBase64String(rowPk)} (version issue?)");
170-
return null;
171-
}
172-
173-
/// <summary>
174-
/// Updates an entry. Returns whether or not the update was successful. Updates only succeed if
175-
/// a previous value was overwritten.
176-
/// </summary>
177-
/// <param name="pk">The primary key that uniquely identifies this row</param>
178-
/// <param name="newValueByteString">The new for the table entry</param>
179-
/// <returns>True when the old value was removed and the new value was inserted.</returns>
180-
public bool UpdateEntry(ByteString pk, ByteString newValueByteString)
181-
{
182-
// We have to figure out if pk is going to change or not
183-
throw new InvalidOperationException();
139+
140+
// Insert the row into our table
141+
entries[rowPk] = (value, decoderFunc(value));
142+
return true;
184143
}
185144

186145
/// <summary>
187146
/// Deletes a value from the table.
188147
/// </summary>
189148
/// <param name="rowPk">The primary key that uniquely identifies this row</param>
190149
/// <returns></returns>
191-
public object DeleteEntry(byte[] rowPk)
150+
public bool DeleteEntry(byte[] rowPk)
192151
{
193152
if (entries.TryGetValue(rowPk, out var value))
194153
{
195154
entries.Remove(rowPk);
196-
return value.Item2;
197-
}
198-
199-
// SpacetimeDB is asking us to delete something we don't have, this makes no sense. We can
200-
// fabricate the deletion by trying to look it up in our local decode table.
201-
if (decodedValues.TryGetValue(rowPk, out var decodedValue))
202-
{
203-
SpacetimeDBClient.instance.Logger.LogWarning("Deleting value that we don't have (using cached value)");
204-
return decodedValue.Item2;
155+
return true;
205156
}
206157

207158
SpacetimeDBClient.instance.Logger.LogWarning("Deleting value that we don't have (no cached value available)");
208-
return null;
159+
return false;
209160
}
210161

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

238-
public AlgebraicType GetPrimaryKeyType(AlgebraicType row)
239-
{
240-
return GetPrimaryKeyTypeFunc != null ? GetPrimaryKeyTypeFunc.Invoke(row) : null;
241-
}
242-
243-
public bool ComparePrimaryKey(byte[] rowPk1, byte[] rowPk2)
189+
public AlgebraicType GetPrimaryKeyType()
244190
{
245-
if (!decodedValues.TryGetValue(rowPk1, out var v1))
246-
{
247-
return false;
248-
}
249-
if (!decodedValues.TryGetValue(rowPk2, out var v2))
250-
{
251-
return false;
252-
}
253-
254-
return (bool)ComparePrimaryKeyFunc.Invoke(rowSchema, v1.Item1, v2.Item1);
191+
return GetPrimaryKeyTypeFunc != null ? GetPrimaryKeyTypeFunc.Invoke(rowSchema) : null;
255192
}
256193
}
257194

@@ -320,5 +257,7 @@ public int Count(string name)
320257
}
321258

322259
public IEnumerable<string> GetTableNames() => tables.Keys;
260+
261+
public IEnumerable<TableCache> GetTables() => tables.Values;
323262
}
324263
}

Scripts/EditorNetworkManager.cs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System;
2+
using System.Text;
3+
using System.Threading.Tasks;
4+
using UnityEngine;
5+
6+
namespace SpacetimeDB
7+
{
8+
public class EditorNetworkManager
9+
{
10+
SpacetimeDB.WebSocket webSocket;
11+
public bool IsConnected => isConnected;
12+
private bool isConnected;
13+
14+
public event Action<string,ClientApi.Event.Types.Status> onTransactionComplete;
15+
16+
public static string GetTokenKey()
17+
{
18+
var key = "spacetimedb.identity_token";
19+
#if UNITY_EDITOR
20+
// Different editors need different keys
21+
key += $" - {Application.dataPath}";
22+
#endif
23+
return key;
24+
}
25+
26+
public EditorNetworkManager(string host, string database)
27+
{
28+
var options = new SpacetimeDB.ConnectOptions
29+
{
30+
//v1.bin.spacetimedb
31+
//v1.text.spacetimedb
32+
Protocol = "v1.bin.spacetimedb",
33+
};
34+
webSocket = new SpacetimeDB.WebSocket(new SpacetimeDB.UnityDebugLogger(), options);
35+
36+
var token = PlayerPrefs.HasKey(GetTokenKey()) ? PlayerPrefs.GetString(GetTokenKey()) : null;
37+
webSocket.OnConnect += () =>
38+
{
39+
Debug.Log("Connected");
40+
isConnected = true;
41+
};
42+
43+
webSocket.OnConnectError += (code, message) =>
44+
{
45+
Debug.Log($"Connection error {message}");
46+
};
47+
48+
webSocket.OnClose += (code, error) =>
49+
{
50+
Debug.Log($"Websocket closed");
51+
isConnected = false;
52+
};
53+
54+
webSocket.OnMessage += OnMessageReceived;
55+
56+
if (!host.StartsWith("http://") && !host.StartsWith("https://") && !host.StartsWith("ws://") &&
57+
!host.StartsWith("wss://"))
58+
{
59+
host = $"ws://{host}";
60+
}
61+
62+
webSocket.Connect(token, host, database, Address.Random());
63+
}
64+
65+
private void OnMessageReceived(byte[] bytes)
66+
{
67+
var message = ClientApi.Message.Parser.ParseFrom(bytes);
68+
if(message.TypeCase == ClientApi.Message.TypeOneofCase.TransactionUpdate)
69+
{
70+
var reducer = message.TransactionUpdate.Event.FunctionCall.Reducer;
71+
var status = message.TransactionUpdate.Event.Status;
72+
onTransactionComplete?.Invoke(reducer, status);
73+
}
74+
}
75+
76+
public async void CallReducer(string reducer, params object[] args)
77+
{
78+
if(!isConnected)
79+
{
80+
Debug.Log("Not connected");
81+
}
82+
83+
var _message = new SpacetimeDBClient.ReducerCallRequest
84+
{
85+
fn = reducer,
86+
args = args,
87+
};
88+
Newtonsoft.Json.JsonSerializerSettings _settings = new Newtonsoft.Json.JsonSerializerSettings
89+
{
90+
Converters = { new SpacetimeDB.SomeWrapperConverter(), new SpacetimeDB.EnumWrapperConverter() },
91+
ContractResolver = new SpacetimeDB.JsonContractResolver(),
92+
};
93+
var json = Newtonsoft.Json.JsonConvert.SerializeObject(_message, _settings);
94+
webSocket.Send(Encoding.ASCII.GetBytes("{ \"call\": " + json + " }"));
95+
}
96+
97+
public void Update()
98+
{
99+
webSocket.Update();
100+
}
101+
102+
public void Close()
103+
{
104+
if (webSocket != null)
105+
{
106+
webSocket.Close();
107+
}
108+
}
109+
}
110+
}

Scripts/EditorNetworkManager.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)