gg.easy.airship 0.1.2143 → 0.1.2144

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
1
  // ReSharper disable InconsistentNaming
2
2
  namespace Code {
3
3
  public static class AirshipConst {
4
- public const int playerVersion = 8;
4
+ public const int playerVersion = 9;
5
5
 
6
6
  /// <summary>
7
7
  /// The server will kick clients that have a playerVersion lower than this value.
8
8
  /// </summary>
9
- public const int minAcceptedPlayerVersionOnServer = 8;
9
+ public const int minAcceptedPlayerVersionOnServer = 9;
10
10
  }
11
11
  }
@@ -205,14 +205,14 @@ namespace Code.Authentication {
205
205
  }
206
206
 
207
207
  })).Then((res) => {
208
- // print($"[Transfer Packet] userIdToken: {userIdToken}, packet response: " + res.Text);
208
+ // print($"[Transfer Packet] {res.Text}");
209
209
  string fullTransferPacket = res.Text;
210
210
  TransferData transferData = JsonUtility.FromJson<TransferData>(fullTransferPacket);
211
211
  tcs.SetResult(new UserData() {
212
212
  uid = transferData.user.uid,
213
213
  username = transferData.user.username,
214
214
  profileImageId = transferData.user.profileImageId,
215
- orgRoleName = transferData.user.orgRoleName,
215
+ orgRoleName = transferData.orgRoleName,
216
216
  fullTransferPacket = fullTransferPacket
217
217
  });
218
218
  }).Catch((err) => {
@@ -12,11 +12,11 @@ public class User {
12
12
  public string usernameLower;
13
13
  public string lastUsernameChangeTime;
14
14
  public string profileImageId;
15
- public string orgRoleName;
16
15
  }
17
16
 
18
17
 
19
18
  [Serializable]
20
19
  public class TransferData {
21
20
  public User user;
21
+ public string orgRoleName;
22
22
  }
@@ -1,14 +1,36 @@
1
1
  using System;
2
+ using System.Buffers;
2
3
  using System.Collections.Generic;
3
4
  using System.IO;
4
5
  using System.IO.Compression;
5
6
  using Mirror;
6
7
  using UnityEngine;
8
+ using UnityEngine.Profiling;
7
9
 
8
10
  namespace Code.Bootstrap {
11
+
12
+ class CachedCompressedFile {
13
+ public byte[] compressedBytes;
14
+ public int compressedLength;
15
+
16
+ public CachedCompressedFile(byte[] compressedBytes, int compressedLength) {
17
+ this.compressedBytes = compressedBytes;
18
+ this.compressedLength = compressedLength;
19
+ }
20
+ }
21
+
9
22
  public static class LuauScriptsDtoSerializer {
23
+ private static Zstd.Zstd zstd = new(1024 * 4);
24
+ private static readonly Dictionary<string, CachedCompressedFile> compressedFileCache = new();
25
+
26
+ [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
27
+ private static void OnReload() {
28
+ compressedFileCache.Clear();
29
+ }
30
+
10
31
  public static void WriteLuauScriptsDto(this NetworkWriter writer, LuauScriptsDto scripts) {
11
- writer.WriteInt(scripts.files.Count);;
32
+ Profiler.BeginSample("WriteLuauScriptsDto");
33
+ writer.WriteInt(scripts.files.Count);
12
34
  foreach (var pair in scripts.files) {
13
35
  string packageId = pair.Key;
14
36
  writer.WriteString(packageId);
@@ -16,22 +38,30 @@ namespace Code.Bootstrap {
16
38
  foreach (var file in pair.Value) {
17
39
  writer.WriteString(file.path);
18
40
 
19
- // Compress the byte array
20
- byte[] compressedBytes;
21
- using (MemoryStream ms = new MemoryStream()) {
22
- using (DeflateStream deflateStream = new DeflateStream(ms, CompressionMode.Compress)) {
23
- deflateStream.Write(file.bytes, 0, file.bytes.Length);
24
- }
25
- compressedBytes = ms.ToArray();
41
+ string cacheId = $"{packageId}:{file.path}";
42
+ if (compressedFileCache.TryGetValue(cacheId, out var cache)) {
43
+ writer.WriteInt(cache.compressedLength);
44
+ writer.WriteBytes(cache.compressedBytes, 0, cache.compressedLength);
45
+ } else {
46
+ // Compress the byte array
47
+ Profiler.BeginSample("Luau Compress");
48
+ var maxCompressionSize = Zstd.Zstd.GetCompressionBound(file.bytes);
49
+ byte[] compressedBytes = new byte[maxCompressionSize];
50
+ var compressedSize = zstd.Compress(file.bytes, compressedBytes);
51
+ writer.WriteInt(compressedSize);
52
+ writer.WriteBytes(compressedBytes, 0, compressedSize);
53
+
54
+ compressedFileCache.Add(cacheId, new CachedCompressedFile(compressedBytes, compressedSize));
26
55
  }
27
- writer.WriteArray(compressedBytes);
56
+
28
57
  writer.WriteBool(file.airshipBehaviour);
58
+ Profiler.EndSample();
29
59
  }
30
60
  }
61
+ Profiler.EndSample();
31
62
  }
32
63
 
33
64
  public static LuauScriptsDto ReadLuauScriptsDto(this NetworkReader reader) {
34
- var totalBytes = reader.Remaining;
35
65
  LuauScriptsDto dto = new LuauScriptsDto();
36
66
  int packagesLength = reader.ReadInt();
37
67
  for (int pkgI = 0; pkgI < packagesLength; pkgI++) {
@@ -44,15 +74,14 @@ namespace Code.Bootstrap {
44
74
  LuauFileDto script = new LuauFileDto();
45
75
  script.path = reader.ReadString();
46
76
 
47
- var byteArray = reader.ReadArray<byte>();
48
- using (MemoryStream compressedStream = new MemoryStream(byteArray)) {
49
- using (DeflateStream deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress)) {
50
- using (MemoryStream outputStream = new MemoryStream()) {
51
- deflateStream.CopyTo(outputStream);
52
- script.bytes = outputStream.ToArray();
53
- }
54
- }
55
- }
77
+ // Read the compressed bytes
78
+ var compressedBytesLen = reader.ReadInt();
79
+ byte[] compressedBytes = ArrayPool<byte>.Shared.Rent(compressedBytesLen);
80
+ reader.ReadBytes(compressedBytes, compressedBytesLen);
81
+
82
+ // Decompress the bytes
83
+ script.bytes = new byte[Zstd.Zstd.GetDecompressionBound(compressedBytes)];
84
+ zstd.Decompress(new ReadOnlySpan<byte>(compressedBytes, 0, compressedBytesLen), script.bytes);
56
85
 
57
86
  script.airshipBehaviour = reader.ReadBool();
58
87
 
@@ -52,7 +52,8 @@
52
52
  "GUID:325984b52e4128546bc7558552f8b1d2",
53
53
  "GUID:3b5390adca4e2bb4791cb930316d6f3e",
54
54
  "GUID:725ee7191c021de4dbf9269590ded755",
55
- "GUID:2a0340569ab0e1245a38e0d6c7b2529b"
55
+ "GUID:2a0340569ab0e1245a38e0d6c7b2529b",
56
+ "GUID:befe48b9a36afc04ea625c93daad910e"
56
57
  ],
57
58
  "includePlatforms": [],
58
59
  "excludePlatforms": [],
@@ -1,11 +1,10 @@
1
1
  using System;
2
- using System.Diagnostics;
3
2
  using System.IO;
4
3
  using System.Threading.Tasks;
5
4
  using Airship.DevConsole;
6
5
  using Code.Http.Internal;
7
6
  using Code.Platform.Shared;
8
- using Code.UI;
7
+ using Code.Player;
9
8
  using JetBrains.Annotations;
10
9
  using Mirror;
11
10
  using UnityEngine;
@@ -56,6 +55,7 @@ namespace Code.Health
56
55
 
57
56
  public struct StartServerProfileMessage : NetworkMessage {
58
57
  public int DurationSecs;
58
+ public bool CallstacksEnabled;
59
59
  }
60
60
 
61
61
  public struct ServerProfileCompleteMessage : NetworkMessage
@@ -88,13 +88,14 @@ namespace Code.Health
88
88
  NetworkClient.RegisterHandler<ClientProfileUploadResponse>(OnClientUploadResponse);
89
89
  }
90
90
 
91
- DevConsole.AddCommand(Command.Create<string, int>(
91
+ DevConsole.AddCommand(Command.Create<string, int, bool>(
92
92
  "profile",
93
93
  "",
94
94
  "Starts and uploads a profile. Once complete the download link will be printed.",
95
95
  Parameter.Create("Context", "Options: Server | Client"),
96
96
  Parameter.Create("Duration", "Duration of profile in seconds (max 5s)"),
97
- (context, d) => {
97
+ Parameter.Create("Callstacks", "Enable callstacks for profile (this is laggy)"),
98
+ (context, d, callstacks) => {
98
99
  if (d is < 0 or > 5) {
99
100
  Debug.LogError("You can only profile for a max of 5s.");
100
101
  return;
@@ -106,10 +107,10 @@ namespace Code.Health
106
107
  "Unable to capture profile log because debug mode is not enabled. Use the development build branch on Steam to enable debug mode.");
107
108
  return;
108
109
  }
109
- StartProfiling(d, null);
110
+ StartProfiling(d, null, callstacks);
110
111
  } else if (context.Equals("server", StringComparison.OrdinalIgnoreCase)) {
111
112
  Debug.Log("Starting a server profile, view server console to monitor progress.");
112
- NetworkClient.Send(new StartServerProfileMessage { DurationSecs = d });
113
+ NetworkClient.Send(new StartServerProfileMessage { DurationSecs = d, CallstacksEnabled = callstacks });
113
114
  }
114
115
  }));
115
116
  }
@@ -142,8 +143,10 @@ namespace Code.Health
142
143
  }
143
144
 
144
145
  public void OnStartProfilingMessage(NetworkConnectionToClient sender, StartServerProfileMessage msg) {
145
- // TODO Validate sender is dev
146
- StartProfiling(msg.DurationSecs, sender);
146
+ var player = PlayerManagerBridge.Instance.GetPlayerInfoByConnectionId(sender.connectionId);
147
+ if (player.IsInGameOrg()) {
148
+ StartProfiling(msg.DurationSecs, sender, msg.CallstacksEnabled);
149
+ }
147
150
  }
148
151
 
149
152
  public async void OnServerProfileCompleteMessage(ServerProfileCompleteMessage msg)
@@ -163,7 +166,7 @@ namespace Code.Health
163
166
  GUIUtility.systemCopyBuffer = data.url;
164
167
  }
165
168
 
166
- public void StartProfiling(int durationSecs, [CanBeNull] NetworkConnectionToClient profileInitiator) {
169
+ public void StartProfiling(int durationSecs, [CanBeNull] NetworkConnectionToClient profileInitiator, bool enableCallstacks) {
167
170
  // TODO check that sender is game dev
168
171
  // if (Profiler.enabled) {
169
172
  // Debug.LogWarning("Profiler is already running.");
@@ -183,12 +186,14 @@ namespace Code.Health
183
186
 
184
187
  Debug.Log($"Starting profiler for {durationSecs} seconds.");
185
188
  Profiler.enabled = true;
189
+ Profiler.enableAllocationCallstacks = enableCallstacks;
186
190
  StopProfilingAfterDelay(logPath, fileName, durationSecs, profileInitiator);
187
191
  }
188
192
 
189
193
  private async void StopProfilingAfterDelay(string logPath, string fileName, float durationSecs, [CanBeNull] NetworkConnectionToClient profileInitiator) {
190
194
  await Task.Delay((int)(durationSecs * 1000));
191
195
  Profiler.enabled = false;
196
+ Profiler.enableAllocationCallstacks = false;
192
197
  var info = new FileInfo(logPath);
193
198
 
194
199
  Debug.Log($"Profiling completed. Retrieving upload URL...");
@@ -173,8 +173,8 @@ public partial class LuauCore : MonoBehaviour {
173
173
  requirePathCallback = requirePathCallback_holder,
174
174
  constructorCallback = constructorCallback_holder,
175
175
  toStringCallback = toStringCallback_holder,
176
- toggleProfilerCallback = toggleProfilerCallback_holder,
177
176
  isObjectDestroyedCallback = isObjectDestroyedCallback_holder,
177
+ getUnityObjectNameCallback = getUnityObjectNameCallback_holder,
178
178
  staticList = stringAddresses.AddrOfPinnedObject(),
179
179
  staticCount = stringCount,
180
180
  isServer = RunCore.IsServer() ? 1 : 0,
@@ -41,8 +41,8 @@ public partial class LuauCore : MonoBehaviour {
41
41
  private LuauPlugin.ConstructorCallback constructorCallback_holder;
42
42
  private LuauPlugin.RequirePathCallback requirePathCallback_holder;
43
43
  private LuauPlugin.ToStringCallback toStringCallback_holder;
44
- private LuauPlugin.ToggleProfilerCallback toggleProfilerCallback_holder;
45
44
  private LuauPlugin.IsObjectDestroyedCallback isObjectDestroyedCallback_holder;
45
+ private LuauPlugin.GetUnityObjectName getUnityObjectNameCallback_holder;
46
46
 
47
47
 
48
48
  private struct AwaitingTask {
@@ -116,8 +116,8 @@ public partial class LuauCore : MonoBehaviour {
116
116
  requirePathCallback_holder = RequirePathCallback;
117
117
  toStringCallback_holder = ToStringCallback;
118
118
  componentSetEnabledCallback_holder = SetComponentEnabledCallback;
119
- toggleProfilerCallback_holder = ToggleProfilerCallback;
120
119
  isObjectDestroyedCallback_holder = IsObjectDestroyedCallback;
120
+ getUnityObjectNameCallback_holder = GetUnityObjectNameCallback;
121
121
  }
122
122
 
123
123
  private static int LuauError(IntPtr thread, string err) {
@@ -190,41 +190,27 @@ public partial class LuauCore : MonoBehaviour {
190
190
 
191
191
  Marshal.Copy(bytes, 0, str, len);
192
192
  }
193
-
194
- [AOT.MonoPInvokeCallback(typeof(LuauPlugin.ToggleProfilerCallback))]
195
- static void ToggleProfilerCallback(int componentId, IntPtr strPtr, int strLen) {
196
- // Disable
197
- if (componentId == -1) {
198
- Profiler.EndSample();
199
- return;
200
- }
201
- // Not tagged to component
202
- if (componentId < -1) {
203
- if (strLen > 0) {
204
- // No need to free strPtr -- it is stack allocated
205
- var str = PtrToStringUTF8(strPtr, strLen);
206
- Profiler.BeginSample($"{str}");
207
- return;
208
- }
209
- }
210
-
211
-
212
- if (AirshipComponent.ComponentIdToScriptName.TryGetValue(componentId, out var componentName)) {
213
- if (strLen > 0) {
214
- var str = PtrToStringUTF8(strPtr, strLen);
215
- Profiler.BeginSample($"{componentName}{str}");
216
- }
217
- else {
218
- Profiler.BeginSample($"{componentName}");
219
- }
220
- }
221
- }
222
193
 
223
194
  [AOT.MonoPInvokeCallback(typeof(LuauPlugin.IsObjectDestroyedCallback))]
224
195
  static int IsObjectDestroyedCallback(int instanceId) {
225
196
  return ThreadDataManager.IsUnityObjectReferenceDestroyed(instanceId) ? 1 : 0;
226
197
  }
227
198
 
199
+ [AOT.MonoPInvokeCallback(typeof(LuauPlugin.GetUnityObjectName))]
200
+ static void GetUnityObjectNameCallback(IntPtr thread, int instanceId, IntPtr str, int maxLen, out int len) {
201
+ var obj = ThreadDataManager.GetObjectReference(thread, instanceId, true, true);
202
+ if (obj is UnityEngine.Object unityObj) {
203
+ var n = unityObj.name;
204
+ var bytes = Encoding.UTF8.GetBytes(n);
205
+ len = bytes.Length > maxLen ? maxLen : bytes.Length;
206
+ Marshal.Copy(bytes, 0, str, len);
207
+
208
+ return;
209
+ }
210
+
211
+ len = 0;
212
+ }
213
+
228
214
  //when a lua thread gc releases an object, make sure our GC knows too
229
215
  [AOT.MonoPInvokeCallback(typeof(LuauPlugin.ObjectGCCallback))]
230
216
  static unsafe int ObjectGcCallback(int instanceId, IntPtr objectDebugPointer) {
@@ -713,16 +699,22 @@ public partial class LuauCore : MonoBehaviour {
713
699
  private static int GetPropertySafeCallback(LuauContext context, IntPtr thread, int instanceId, IntPtr classNamePtr, int classNameSize, IntPtr propertyName, int propertyNameLength) {
714
700
  var ret = 0;
715
701
  try {
716
- ret = GetProperty(context, thread, instanceId, classNamePtr, classNameSize, propertyName, propertyNameLength);
717
- } catch (Exception e) {
702
+ Profiler.BeginSample("GetProperty");
703
+ ret = GetProperty(context, thread, instanceId, classNamePtr, classNameSize, propertyName,
704
+ propertyNameLength);
705
+ }
706
+ catch (Exception e) {
718
707
  ret = LuauError(thread, e.Message);
708
+ } finally {
709
+ Profiler.EndSample();
719
710
  }
720
711
 
721
712
  return ret;
722
713
  }
723
714
 
715
+ // private static readonly ProfilerMarker<string> getPropertyMarker = new ProfilerMarker<string>("LuauCore.GetProperty", "asd");
716
+
724
717
  private static int GetProperty(LuauContext context, IntPtr thread, int instanceId, IntPtr classNamePtr, int classNameSize, IntPtr propertyName, int propertyNameLength) {
725
- Profiler.BeginSample("LuauCore.GetProperty");
726
718
  CurrentContext = context;
727
719
 
728
720
  string propName = LuauCore.PtrToStringUTF8(propertyName, propertyNameLength, out ulong propNameHash);
@@ -756,14 +748,12 @@ public partial class LuauCore : MonoBehaviour {
756
748
  // Type t = propertyInfo.PropertyType;
757
749
  System.Object value = cacheData.Value.propertyInfo.GetValue(null);
758
750
  WritePropertyToThread(thread, value, cacheData.Value.t);
759
- Profiler.EndSample();
760
751
  return 1;
761
752
  }
762
753
 
763
754
  // Get C# event:
764
755
  var eventInfo = objectType.GetRuntimeEvent(propName);
765
756
  if (eventInfo != null) {
766
- Profiler.EndSample();
767
757
  return LuauSignalWrapper.HandleCsEvent(context, thread, staticClassApi, instanceId, propNameHash,
768
758
  eventInfo, true);
769
759
  }
@@ -774,11 +764,9 @@ public partial class LuauCore : MonoBehaviour {
774
764
  Type t = fieldInfo.FieldType;
775
765
  System.Object value = fieldInfo.GetValue(null);
776
766
  WritePropertyToThread(thread, value, t);
777
- Profiler.EndSample();
778
767
  return 1;
779
768
  }
780
769
 
781
- Profiler.EndSample();
782
770
  return LuauError(thread, "ERROR - " + propName + " get property not found on class " + staticClassName);
783
771
  }
784
772
  else {
@@ -788,7 +776,6 @@ public partial class LuauCore : MonoBehaviour {
788
776
  System.Object objectReference = ThreadDataManager.GetObjectReference(thread, instanceId);
789
777
  // Profiler.EndSample();
790
778
  if (objectReference == null) {
791
- Profiler.EndSample();
792
779
  return LuauError(thread,
793
780
  "Error: InstanceId not currently available:" + instanceId + ". propName=" + propName);
794
781
  }
@@ -801,7 +788,6 @@ public partial class LuauCore : MonoBehaviour {
801
788
  if (objectReference is GameObject targetGo) {
802
789
  // var target = (GameObject)objectReference;
803
790
  if (IsAccessBlocked(context, targetGo)) {
804
- Profiler.EndSample();
805
791
  return LuauError(thread,
806
792
  "[Airship] Access denied when trying to read " + targetGo.name + ".");
807
793
  }
@@ -809,7 +795,6 @@ public partial class LuauCore : MonoBehaviour {
809
795
  else if (sourceType.IsAssignableFrom(typeof(Component))) {
810
796
  var target = (Component)objectReference;
811
797
  if (target && IsAccessBlocked(context, target.gameObject)) {
812
- Profiler.EndSample();
813
798
  return LuauError(thread,
814
799
  "[Airship] Access denied when trying to read " + target.name + ".");
815
800
  }
@@ -821,7 +806,6 @@ public partial class LuauCore : MonoBehaviour {
821
806
  if (valueTypeAPI != null) {
822
807
  var retValue = valueTypeAPI.OverrideMemberGetter(context, thread, objectReference, propName);
823
808
  if (retValue >= 0) {
824
- Profiler.EndSample();
825
809
  return retValue;
826
810
  }
827
811
  }
@@ -841,7 +825,6 @@ public partial class LuauCore : MonoBehaviour {
841
825
  try {
842
826
  // Try a fast write on value type (Vector3, int, etc. Not objects)
843
827
  if (FastGetAndWriteValueProperty(thread, objectReference, cacheData.Value)) {
844
- Profiler.EndSample();
845
828
  return 1;
846
829
  }
847
830
 
@@ -876,16 +859,11 @@ public partial class LuauCore : MonoBehaviour {
876
859
  }
877
860
  }
878
861
 
879
- // Profiler.BeginSample("WriteToThread");
880
862
  WritePropertyToThread(thread, value, t);
881
- // Profiler.EndSample();
882
- Profiler.EndSample();
883
863
  return 1;
884
864
  }
885
865
  else {
886
- // Debug.Log("Value was null in dictionary. propName=" + propName + ", object=" + sourceType.Name);
887
866
  WritePropertyToThread(thread, null, null);
888
- Profiler.EndSample();
889
867
  return 1;
890
868
  }
891
869
  }
@@ -902,11 +880,8 @@ public partial class LuauCore : MonoBehaviour {
902
880
  // If we failed to get a reference to a non-primitive, just assume a null value (write nil to the stack):
903
881
  if (!cacheData.Value.propertyInfo.PropertyType.IsPrimitive) {
904
882
  WritePropertyToThread(thread, null, null);
905
- Profiler.EndSample();
906
883
  return 1;
907
884
  }
908
-
909
- Profiler.EndSample();
910
885
  return LuauError(thread, "Failed to get property in dictionary. propName=" + propName +
911
886
  ", object=" +
912
887
  sourceType.Name + ", msg=" + e.Message);
@@ -936,7 +911,6 @@ public partial class LuauCore : MonoBehaviour {
936
911
  // print("key: " + propName + " " + keyInt);
937
912
  // Debug.Log("[Luau]: Dictionary had key but value was null. propName=" + propName + ", sourceType=" + sourceType.Name + ", obj=" + objectReference);
938
913
  WritePropertyToThread(thread, null, null);
939
- Profiler.EndSample();
940
914
  return 1;
941
915
  }
942
916
 
@@ -944,14 +918,12 @@ public partial class LuauCore : MonoBehaviour {
944
918
  object value = dict[propName];
945
919
  Type t = value.GetType();
946
920
  WritePropertyToThread(thread, value, t);
947
- Profiler.EndSample();
948
921
  return 1;
949
922
  }
950
923
  else {
951
924
  // Debug.Log("[Luau]: Dictionary was found but key was not found. propName=" + propName +
952
925
  // ", sourceType=" + sourceType.Name);
953
926
  WritePropertyToThread(thread, null, null);
954
- Profiler.EndSample();
955
927
  return 1;
956
928
  }
957
929
  }
@@ -959,7 +931,6 @@ public partial class LuauCore : MonoBehaviour {
959
931
  // Get C# event:
960
932
  var eventInfo = sourceType.GetRuntimeEvent(propName);
961
933
  if (eventInfo != null) {
962
- Profiler.EndSample();
963
934
  return LuauSignalWrapper.HandleCsEvent(context, thread, objectReference, instanceId, propNameHash,
964
935
  eventInfo, false);
965
936
  }
@@ -970,11 +941,9 @@ public partial class LuauCore : MonoBehaviour {
970
941
  Type t = field.FieldType;
971
942
  System.Object value = field.GetValue(objectReference);
972
943
  WritePropertyToThread(thread, value, t);
973
- Profiler.EndSample();
974
944
  return 1;
975
945
  }
976
946
 
977
- Profiler.EndSample();
978
947
  return LuauError(thread, $"ERROR - ({sourceType.Name}).{propName} property/field not found");
979
948
  }
980
949
  }
@@ -22,8 +22,8 @@ public static class LuauPlugin {
22
22
  public delegate void RequirePathCallback(LuauContext context, IntPtr thread, IntPtr scriptName, int scriptNameLen, IntPtr fileName, int fileNameLen);
23
23
  public delegate void ToStringCallback(IntPtr thread, int instanceId, IntPtr str, int maxLen, out int len);
24
24
  public delegate void ComponentSetEnabledCallback(IntPtr thread, int instanceId, int componentId, int enabled);
25
- public delegate void ToggleProfilerCallback(int componentId, IntPtr str, int strLen);
26
25
  public delegate int IsObjectDestroyedCallback(int instanceId);
26
+ public delegate void GetUnityObjectName(IntPtr thread, int instanceId, IntPtr str, int maxLen, out int len);
27
27
 
28
28
  public static int unityMainThreadId = -1;
29
29
  public static bool s_currentlyExecuting = false;
@@ -62,8 +62,8 @@ public static class LuauPlugin {
62
62
  public RequirePathCallback requirePathCallback;
63
63
  public ConstructorCallback constructorCallback;
64
64
  public ToStringCallback toStringCallback;
65
- public ToggleProfilerCallback toggleProfilerCallback;
66
65
  public IsObjectDestroyedCallback isObjectDestroyedCallback;
66
+ public GetUnityObjectName getUnityObjectNameCallback;
67
67
 
68
68
  public IntPtr staticList;
69
69
  public int staticCount;
@@ -35,7 +35,9 @@ namespace Code.RemoteConsole {
35
35
  private Task writeTask;
36
36
 
37
37
  private void Awake() {
38
- writeTask = Task.Run(ProcessQueue);
38
+ if (RunCore.IsClient() && !RunCore.IsServer()) {
39
+ writeTask = Task.Run(ProcessQueue);
40
+ }
39
41
  }
40
42
 
41
43
  private async Task ProcessQueue() {
@@ -74,8 +76,11 @@ namespace Code.RemoteConsole {
74
76
  Application.logMessageReceived += LogCallback;
75
77
  }
76
78
  NetworkServer.RegisterHandler<RequestServerConsoleStartupLogs>((conn, data) => {
77
- foreach (var startupMessage in startupMessages) {
78
- conn.Send(startupMessage);
79
+ var player = PlayerManagerBridge.Instance.GetPlayerInfoByConnectionId(conn.connectionId);
80
+ if (player.IsInGameOrg()) {
81
+ foreach (var startupMessage in startupMessages) {
82
+ conn.Send(startupMessage);
83
+ }
79
84
  }
80
85
  }, false);
81
86
  }
@@ -127,7 +132,10 @@ namespace Code.RemoteConsole {
127
132
  }
128
133
 
129
134
  private void SendServerLogMessage(string message, LogType logType = LogType.Log, string stackTrace = "") {
130
- if (RunCore.IsServer() && !RunCore.IsEditor()) {
135
+ if (RunCore.IsEditor()) {
136
+ return;
137
+ }
138
+ if (RunCore.IsServer()) {
131
139
  var time = DateTime.Now.ToString("HH:mm:ss");
132
140
  if (this.startupMessages.Count < maxStartupMessages) {
133
141
  this.startupMessages.Add(new ServerConsoleBroadcast() {
@@ -147,10 +155,9 @@ namespace Code.RemoteConsole {
147
155
  stackTrace = stackTrace,
148
156
  time = time,
149
157
  };
150
- // bool sendToReadyOnly = Application.isEditor;
151
158
  foreach (var player in PlayerManagerBridge.Instance.players) {
152
- if (!string.IsNullOrEmpty(player.orgRoleName)) {
153
- player.connectionToClient.Send(packet, Channels.Reliable);
159
+ if (player.IsInGameOrg()) {
160
+ player.connectionToClient.Send(packet);
154
161
  }
155
162
  }
156
163
  }
@@ -412,12 +412,13 @@ namespace Code.Player.Character.MovementSystems.Character {
412
412
  // If we have transitioned to airborne
413
413
  if (!grounded && currentMoveSnapshot.isGrounded) {
414
414
  // Set canJump to the number of ticks of coyote time we have
415
- currentMoveSnapshot.canJump = (byte) Math.Min(Math.Floor(movementSettings.jumpCoyoteTime / Time.fixedDeltaTime), 255);
415
+ currentMoveSnapshot.canJump
416
+ = (byte)Math.Min(Math.Floor(movementSettings.jumpCoyoteTime / Time.fixedDeltaTime), 255);
416
417
  }
417
418
 
418
419
  if (!grounded && !currentMoveSnapshot.isGrounded) {
419
420
  // If we've now ticked once in the air, remove a tick of canJump time.
420
- currentMoveSnapshot.canJump = (byte) Math.Max(currentMoveSnapshot.canJump - 1, 0);
421
+ currentMoveSnapshot.canJump = (byte)Math.Max(currentMoveSnapshot.canJump - 1, 0);
421
422
  }
422
423
 
423
424
  var groundSlopeDir = detectedGround
@@ -474,7 +475,7 @@ namespace Code.Player.Character.MovementSystems.Character {
474
475
  // currentMoveSnapshot.timeSinceWasGrounded <= movementSettings.jumpCoyoteTime &&
475
476
  // currentMoveSnapshot.timeSinceJump > movementSettings.jumpCoyoteTime
476
477
  currentMoveSnapshot.canJump > 0
477
- ) {
478
+ ) {
478
479
  canJump = true;
479
480
  }
480
481
  //the first jump requires grounded, so if in the air bump the currentMoveState.jumpCount up
@@ -575,7 +576,7 @@ namespace Code.Player.Character.MovementSystems.Character {
575
576
  currentMoveSnapshot.isSprinting = false;
576
577
  }
577
578
 
578
- #region CROUCH
579
+ #region CROUCH
579
580
 
580
581
  // Prevent falling off blocks while crouching
581
582
  currentMoveSnapshot.isCrouching = groundedState == CharacterState.Crouching;
@@ -610,7 +611,7 @@ namespace Code.Player.Character.MovementSystems.Character {
610
611
  }
611
612
  }
612
613
 
613
- #endregion
614
+ #endregion
614
615
 
615
616
  // Modify colliders size based on movement state
616
617
  var offsetExtent = movementSettings.colliderGroundOffset / 2;
@@ -826,8 +827,7 @@ namespace Code.Player.Character.MovementSystems.Character {
826
827
  Debug.DrawLine(checkPoint, rayTestHit.point, Color.magenta);
827
828
  GizmoUtils.DrawSphere(rayTestHit.point, .15f, Color.magenta);
828
829
  }
829
- }
830
- else if (drawDebugGizmos_WALLCLIPPING) {
830
+ } else if (drawDebugGizmos_WALLCLIPPING) {
831
831
  GizmoUtils.DrawSphere(checkPoint, .05f, Color.white);
832
832
  Debug.DrawLine(checkPoint, checkPoint + (forwardHit.point - checkPoint), Color.white);
833
833
  }
@@ -978,7 +978,9 @@ namespace Code.Player.Character.MovementSystems.Character {
978
978
  var didStepUp = false;
979
979
  if (movementSettings.detectStepUps && //Want to check step ups
980
980
  (!command.crouch || !movementSettings.preventStepUpWhileCrouching) && //Not blocked by crouch
981
- (movementSettings.assistedLedgeJump || currentMoveSnapshot.canJump > 0) && //Grounded // Used to be currentMoveSnapshot.timeSinceBecameGrounded > .05
981
+ (movementSettings.assistedLedgeJump ||
982
+ currentMoveSnapshot.canJump >
983
+ 0) && //Grounded // Used to be currentMoveSnapshot.timeSinceBecameGrounded > .05
982
984
  Mathf.Abs(newVelocity.x) + Mathf.Abs(newVelocity.z) > .05f) {
983
985
  //Moveing
984
986
  var (hitStepUp, onRamp, pointOnRamp, stepUpVel) = physics.StepUp(rootPosition,
@@ -1015,12 +1017,13 @@ namespace Code.Player.Character.MovementSystems.Character {
1015
1017
  // Prevent falling off blocks while crouching
1016
1018
  if (movementSettings.preventFallingWhileCrouching && !currentMoveSnapshot.prevStepUp &&
1017
1019
  currentMoveSnapshot.isCrouching && !didJump && grounded) {
1018
- var distanceCheck = movementSettings.characterRadius * 4 + newVelocity.magnitude * deltaTime;
1020
+ var distanceCheck = movementSettings.characterRadius * 3 + newVelocity.magnitude * deltaTime;
1019
1021
  var normalizedVel = newVelocity.normalized;
1020
1022
  var projectedPosition = rootPosition + normalizedVel * distanceCheck;
1021
1023
  if (drawDebugGizmos_CROUCH) {
1022
1024
  GizmoUtils.DrawSphere(projectedPosition, .1f, Color.blue, 4, .1f);
1023
1025
  }
1026
+
1024
1027
  var (groundedInMoveDirection, _, _) =
1025
1028
  physics.CheckIfGrounded(projectedPosition, newVelocity, normalizedVel);
1026
1029
  var foundGroundedDir = false;
@@ -1035,7 +1038,6 @@ namespace Code.Player.Character.MovementSystems.Character {
1035
1038
  if (Physics.Raycast(projectedPosition + new Vector3(0, -.25f, 0), -normalizedVel,
1036
1039
  out var cliffHit, distanceCheck,
1037
1040
  movementSettings.groundCollisionLayerMask, QueryTriggerInteraction.Ignore)) {
1038
-
1039
1041
  if (drawDebugGizmos_CROUCH) {
1040
1042
  GizmoUtils.DrawSphere(cliffHit.point, .1f, Color.red, 4, .1f);
1041
1043
  }
@@ -1052,21 +1054,34 @@ namespace Code.Player.Character.MovementSystems.Character {
1052
1054
  var flatPoint = new Vector3(cliffHit.point.x, transform.position.y, cliffHit.point.z);
1053
1055
  //If we are too close to the edge or if there is an obstruction in the way
1054
1056
  if (Vector3.Distance(flatPoint, transform.position) < bumpSize - forwardMargin
1055
- || Physics.Raycast(transform.position + new Vector3(0,.25f, 0), newVelocity, distanceCheck, movementSettings.groundCollisionLayerMask)) {
1057
+ || Physics.Raycast(transform.position + new Vector3(0, .25f, 0), newVelocity, distanceCheck,
1058
+ movementSettings.groundCollisionLayerMask)) {
1056
1059
  //Snap back to the bump distance so you never inch your way to the edge
1057
1060
  //newVelocity = new Vector3(0, newVelocity.y, 0);
1058
1061
  //var newPos = cliffHit.point - normalizedVel * (bumpSize-forwardMargin);
1059
1062
  //transform.position = new Vector3(newPos.x, transform.position.y, newPos.z);
1060
-
1061
- newVelocity = -normalizedVel;
1063
+
1064
+ newVelocity = -cliffHit.normal;
1062
1065
  } else {
1063
1066
  //limit movement dir based on how straight you are walking into the edge
1064
- characterMoveVelocity = Vector3.ProjectOnPlane(characterMoveVelocity, -cliffHit.normal);
1065
- characterMoveVelocity.y = 0;
1066
- characterMoveVelocity *= colliderDot;
1067
- normalizedMoveDir = characterMoveVelocity.normalized;
1067
+ // characterMoveVelocity = Vector3.ProjectOnPlane(characterMoveVelocity, -cliffHit.normal);
1068
+ // characterMoveVelocity.y = 0;
1069
+ // characterMoveVelocity *= colliderDot;
1070
+ // normalizedMoveDir = characterMoveVelocity.normalized;
1068
1071
 
1069
1072
  newVelocity -= colliderDot * -cliffHit.normal;
1073
+ //newVelocity *= Mathf.Max(0, -colliderDot);
1074
+ if (newVelocity.sqrMagnitude < 2f) {
1075
+ newVelocity = Vector3.zero;
1076
+ }
1077
+
1078
+ //With this new velocity are we going to fall off a different ledge?
1079
+ if (!Physics.Raycast(
1080
+ new Vector3(0, 1.25f, 0) + transform.position +
1081
+ newVelocity.normalized * distanceCheck, Vector3.down, 1.5f)) {
1082
+ //Nothing in the direction of the new velocity
1083
+ newVelocity = Vector3.zero;
1084
+ }
1070
1085
  //newVelocity *= colliderDot;
1071
1086
  }
1072
1087
  }
@@ -1076,23 +1091,25 @@ namespace Code.Player.Character.MovementSystems.Character {
1076
1091
  #endregion
1077
1092
 
1078
1093
  #region APPLY FORCES
1094
+
1079
1095
  //Stop character from moveing into colliders (Helps prevent axis aligned box colliders from colliding when they shouldn't like jumping in a voxel world)
1080
1096
  if (movementSettings.preventWallClipping && !currentMoveSnapshot.prevStepUp) {
1081
1097
  var velY = newVelocity.y;
1082
1098
  flatVelocity = new Vector3(newVelocity.x, 0, newVelocity.z);
1083
- var minDistance = (characterRadius + forwardMargin);
1099
+ var minDistance = characterRadius + forwardMargin;
1084
1100
  var forwardDistance = Mathf.Max(flatVelocity.magnitude * deltaTime, minDistance);
1085
1101
  var forwardVector = flatVelocity.normalized * Mathf.Max(forwardDistance, bumpSize);
1086
1102
  //print("Forward vec: " + forwardVector);
1087
1103
 
1088
1104
  //Do raycasting after we have claculated our move direction
1089
1105
  var forwardHits =
1090
- physics.CheckAllForwardHits(rootPosition - flatVelocity.normalized * -forwardMargin, forwardVector, true,
1106
+ physics.CheckAllForwardHits(rootPosition - flatVelocity.normalized * -forwardMargin, forwardVector,
1107
+ true,
1091
1108
  true);
1092
1109
 
1093
1110
  float i = 0;
1094
- string label = "ForwardHitCounts: " + forwardHits.Length + "\n";
1095
- int forcedCount = 0;
1111
+ var label = "ForwardHitCounts: " + forwardHits.Length + "\n";
1112
+ var forcedCount = 0;
1096
1113
  foreach (var forwardHitResult in forwardHits) {
1097
1114
  label += "Hit " + i + " Point: " + forwardHitResult.point + " Normal: " + forwardHitResult.normal;
1098
1115
  //Check if this is a valid wall and not something behind a surface
@@ -1112,7 +1129,8 @@ namespace Code.Player.Character.MovementSystems.Character {
1112
1129
  }
1113
1130
 
1114
1131
  var checkDir = (forwardHit.point - checkPoint).normalized;
1115
- var checkDistance = forwardMargin + Mathf.Max(forwardHit.distance, movementSettings.characterRadius * 2);
1132
+ var checkDistance = forwardMargin +
1133
+ Mathf.Max(forwardHit.distance, movementSettings.characterRadius * 2);
1116
1134
  if (Physics.Raycast(checkPoint, checkDir,
1117
1135
  out var rayTestHit, checkDistance,
1118
1136
  movementSettings.groundCollisionLayerMask, QueryTriggerInteraction.Ignore)) {
@@ -1150,24 +1168,24 @@ namespace Code.Player.Character.MovementSystems.Character {
1150
1168
  //|| forwardHit.distance < bumpSize) {
1151
1169
  colliderDot = 0;
1152
1170
  }
1153
-
1171
+
1154
1172
  // flatVelocity = Vector3.ClampMagnitude(newVelocity,
1155
1173
  // forwardHit.distance - characterRadius - forwardMargin);
1156
1174
  // //print("FLAT VEL: " + flatVelocity);
1157
1175
  // newVelocity.x -= flatVelocity.x;
1158
1176
  // newVelocity.z -= flatVelocity.z;
1159
-
1177
+
1160
1178
  var flatPoint = new Vector3(forwardHit.point.x, transform.position.y, forwardHit.point.z);
1161
1179
  if (Vector3.Distance(flatPoint, transform.position) < minDistance) {
1162
1180
  //Snap back to the bump distance so you never inch your way to the edge
1163
- var newPos = forwardHit.point + forwardHit.normal * (bumpSize+forwardMargin);
1181
+ var newPos = forwardHit.point + forwardHit.normal * (bumpSize + forwardMargin);
1164
1182
  if (forcedCount == 0) {
1165
1183
  transform.position = new Vector3(newPos.x, transform.position.y, newPos.z);
1166
1184
  } else {
1167
1185
  transform.position = new Vector3(
1168
- (transform.position.x + newPos.x) /2f,
1169
- transform.position.y,
1170
- (transform.position.z + newPos.z) /2f);
1186
+ (transform.position.x + newPos.x) / 2f,
1187
+ transform.position.y,
1188
+ (transform.position.z + newPos.z) / 2f);
1171
1189
  }
1172
1190
 
1173
1191
  forcedCount++;
@@ -1175,10 +1193,9 @@ namespace Code.Player.Character.MovementSystems.Character {
1175
1193
 
1176
1194
  // var normalVel = forwardHit.normal * (Math.Abs(newVelocity.x) + Math.Abs(newVelocity.z));
1177
1195
  // newVelocity = new Vector3(normalVel.x, velY , normalVel.z);
1178
- } else {
1179
- }
1180
-
1181
- newVelocity = Vector3.ProjectOnPlane( flatVelocity, forwardHit.normal);
1196
+ } else { }
1197
+
1198
+ newVelocity = Vector3.ProjectOnPlane(flatVelocity, forwardHit.normal);
1182
1199
  //newVelocity.y = 0;
1183
1200
  newVelocity *= colliderDot * .9f;
1184
1201
  newVelocity.y = velY;
@@ -1565,7 +1582,7 @@ namespace Code.Player.Character.MovementSystems.Character {
1565
1582
 
1566
1583
  // TS listens to this to update the local camera.
1567
1584
  // Position will update from reconcile, but we handle look direction manually.
1568
- if (mode == NetworkedStateSystemMode.Authority && isServer && !this.manager.serverGeneratesCommands) {
1585
+ if (mode == NetworkedStateSystemMode.Authority && isServer && !manager.serverGeneratesCommands) {
1569
1586
  RpcSetLookVector(lookVector);
1570
1587
  }
1571
1588
 
@@ -1782,4 +1799,4 @@ namespace Code.Player.Character.MovementSystems.Character {
1782
1799
 
1783
1800
  #endregion
1784
1801
  }
1785
- }
1802
+ }
@@ -59,6 +59,10 @@ public class PlayerInfo : NetworkBehaviour {
59
59
  }
60
60
  }
61
61
 
62
+ public bool IsInGameOrg() {
63
+ return !string.IsNullOrEmpty(this.orgRoleName);
64
+ }
65
+
62
66
  public override void OnStopServer() {
63
67
  PlayerManagerBridge.Instance.HandlePlayerLeave(this);
64
68
  base.OnStopServer();
@@ -115,7 +115,7 @@ namespace Code.Player {
115
115
  Debug.Log($"Players ({this.players.Count}):");
116
116
  int i = 1;
117
117
  foreach (var player in this.players) {
118
- Debug.Log($" {i}. {player.username} - connectionId: {player.connectionId}, userId: {player.userId}");
118
+ Debug.Log($" {i}. {player.username} - connectionId: {player.connectionId}, userId: {player.userId}, orgRole: {player.orgRoleName}");
119
119
  i++;
120
120
  }
121
121
  }));
@@ -167,6 +167,7 @@ namespace Code.Player {
167
167
 
168
168
  private void OnDestroy() {
169
169
  NetworkServer.OnConnectedEvent -= NetworkServer_OnConnected;
170
+ DevConsole.RemoveCommand("players");
170
171
  }
171
172
 
172
173
  public void AddBotPlayer(string username, string userId, string profilePictureId) {
@@ -1,13 +1,16 @@
1
1
  using System;
2
- using System.IO;
3
- using System.IO.Compression;
2
+ using System.Buffers;
3
+ using Code.Zstd;
4
4
  using Mirror;
5
5
  using UnityEngine;
6
+ using UnityEngine.Profiling;
6
7
  using VoxelWorldStuff;
7
8
 
8
9
  public static class ChunkSerializer {
10
+ private static Zstd zstd = new Zstd(1024 * 4);
9
11
 
10
12
  public static void WriteChunk(this NetworkWriter writer, Chunk value) {
13
+ Profiler.BeginSample("WriteChunk");
11
14
  Vector3Int key = value.GetKey();
12
15
 
13
16
  writer.WriteVector3Int(key);
@@ -15,25 +18,29 @@ public static class ChunkSerializer {
15
18
  var voxelDataLengthBytes = value.readWriteVoxel.Length * sizeof(short);
16
19
  var colDataLengthBytes = value.color.Length * sizeof(uint);
17
20
 
21
+ // Keep track of uncompressed byte size of voxels and colors:
18
22
  writer.WriteInt(voxelDataLengthBytes);
19
23
  writer.WriteInt(colDataLengthBytes);
20
24
 
21
25
  // Input byte array
22
- byte[] byteArray = new byte[voxelDataLengthBytes];
23
- Buffer.BlockCopy(value.readWriteVoxel, 0, byteArray, 0, byteArray.Length);
24
- byte[] colArray = new byte[colDataLengthBytes];
25
- Buffer.BlockCopy(value.color, 0, colArray, 0, colArray.Length);
26
-
26
+ byte[] voxelByteAndColorArray = ArrayPool<byte>.Shared.Rent(voxelDataLengthBytes + colDataLengthBytes);
27
+ Buffer.BlockCopy(value.readWriteVoxel, 0, voxelByteAndColorArray, 0, voxelDataLengthBytes);
28
+ Buffer.BlockCopy(value.color, 0, voxelByteAndColorArray, voxelDataLengthBytes, colDataLengthBytes);
29
+
27
30
  // Compress the byte array
28
- byte[] compressedBytes;
29
- using (MemoryStream ms = new MemoryStream()) {
30
- using (DeflateStream deflateStream = new DeflateStream(ms, CompressionMode.Compress)) {
31
- deflateStream.Write(byteArray, 0, byteArray.Length);
32
- deflateStream.Write(colArray, 0, colArray.Length);
33
- }
34
- compressedBytes = ms.ToArray();
35
- }
36
- writer.WriteArray(compressedBytes);
31
+ Profiler.BeginSample("WriteChunk.Compress");
32
+
33
+ var maxCompressionSize = Zstd.GetCompressionBound(voxelByteAndColorArray);
34
+ var compressionBuffer = ArrayPool<byte>.Shared.Rent(maxCompressionSize);
35
+ var voxelDataCompressedSize = zstd.Compress(voxelByteAndColorArray, compressionBuffer);
36
+ writer.WriteInt(voxelDataCompressedSize);
37
+ writer.WriteBytes(compressionBuffer, 0, voxelDataCompressedSize);
38
+
39
+ ArrayPool<byte>.Shared.Return(voxelByteAndColorArray);
40
+ ArrayPool<byte>.Shared.Return(compressionBuffer);
41
+
42
+ Profiler.EndSample();
43
+ Profiler.EndSample();
37
44
  }
38
45
 
39
46
  public static Chunk ReadChunk(this NetworkReader reader) {
@@ -42,23 +49,23 @@ public static class ChunkSerializer {
42
49
 
43
50
  var voxelDataLength = reader.ReadInt();
44
51
  var colorDataLength = reader.ReadInt();
52
+ var compressedBytesLen = reader.ReadInt();
45
53
 
46
54
  Chunk chunk = VoxelWorld.CreateChunk(key);
47
55
 
48
- // byte[] byteArray = new byte[16 * 16 * 16 * 2]; // 2 because they are shorts
49
- byte[] byteArray = reader.ReadArray<byte>();
50
- using (MemoryStream compressedStream = new MemoryStream(byteArray)) {
51
- using (DeflateStream deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress)) {
52
- using (MemoryStream outputStream = new MemoryStream()) {
53
- deflateStream.CopyTo(outputStream);
54
- // chunk.readWriteVoxel = outputStream.ToArray();
55
- var output = outputStream.ToArray();
56
- Buffer.BlockCopy(output, 0, chunk.readWriteVoxel, 0, voxelDataLength);
57
- Buffer.BlockCopy(output, voxelDataLength, chunk.color, 0, colorDataLength);
58
- }
59
- }
60
- }
56
+ byte[] voxelByteAndColorArray = ArrayPool<byte>.Shared.Rent(compressedBytesLen);
57
+
58
+ reader.ReadBytes(voxelByteAndColorArray, compressedBytesLen);
59
+ var decompressedData = ArrayPool<byte>.Shared.Rent(Zstd.GetDecompressionBound(voxelByteAndColorArray));
60
+ zstd.Decompress(new ReadOnlySpan<byte>(voxelByteAndColorArray, 0, compressedBytesLen), decompressedData);
61
+
62
+ Buffer.BlockCopy(decompressedData, 0, chunk.readWriteVoxel, 0, voxelDataLength);
63
+ Buffer.BlockCopy(decompressedData, voxelDataLength, chunk.color, 0, colorDataLength);
64
+
65
+ ArrayPool<byte>.Shared.Return(voxelByteAndColorArray);
66
+ ArrayPool<byte>.Shared.Return(decompressedData);
67
+
61
68
  chunk.MarkKeysWithVoxelsDirty();
62
69
  return chunk;
63
70
  }
64
- }
71
+ }
@@ -27,14 +27,6 @@ namespace Code.Zstd {
27
27
  _ctx = new ZstdContext(scratchBufferSize);
28
28
  }
29
29
 
30
- /// <summary>
31
- /// Compress the data. The compression level can be between <c>Zstd.MinCompressionLevel</c>
32
- /// and <c>Zstd.MaxCompressionLevel</c>. Most use-cases should use <c>Zstd.DefaultCompressionLevel</c>.
33
- /// </summary>
34
- public byte[] Compress(byte[] data, int compressionLevel) {
35
- return Compress(new ReadOnlySpan<byte>(data), compressionLevel);
36
- }
37
-
38
30
  /// <summary>
39
31
  /// Compress the data. The compression level can be between <c>Zstd.MinCompressionLevel</c>
40
32
  /// and <c>Zstd.MaxCompressionLevel</c>. Most use-cases should use <c>Zstd.DefaultCompressionLevel</c>.
@@ -43,13 +35,6 @@ namespace Code.Zstd {
43
35
  return Compress(new ReadOnlySpan<byte>(data, start, length), compressionLevel);
44
36
  }
45
37
 
46
- /// <summary>
47
- /// Compress the data using the default compression level.
48
- /// </summary>
49
- public byte[] Compress(byte[] data) {
50
- return Compress(new ReadOnlySpan<byte>(data));
51
- }
52
-
53
38
  /// <summary>
54
39
  /// Compress the data using the default compression level.
55
40
  /// </summary>
@@ -71,12 +56,24 @@ namespace Code.Zstd {
71
56
  public byte[] Compress(ReadOnlySpan<byte> data, int compressionLevel) {
72
57
  return CompressData(data, compressionLevel, _ctx);
73
58
  }
74
-
59
+
75
60
  /// <summary>
76
- /// Decompress the data.
61
+ /// Compress the data with a destination buffer. This buffer must be the correct size. Use the
62
+ /// <c>Zstd.GetCompressionBound</c> method to ensure the buffer is large enough. The returned
63
+ /// span is a slice of the input destination buffer.
64
+ /// </summary>
65
+ /// <returns>The number of bytes written to the buffer.</returns>
66
+ public int Compress(ReadOnlySpan<byte> data, byte[] dstBuffer, int compressionLevel) {
67
+ return CompressWithBuffer(data, dstBuffer, compressionLevel, _ctx);
68
+ }
69
+
70
+ /// <summary>
71
+ /// Compress the data into the given destination buffer. This buffer must be the correct size.
72
+ /// Use the <c>Zstd.GetCompressionBound</c> method to ensure the buffer is large enough.
77
73
  /// </summary>
78
- public byte[] Decompress(byte[] data) {
79
- return Decompress(new ReadOnlySpan<byte>(data));
74
+ /// <returns>The number of bytes written to the buffer.</returns>
75
+ public int Compress(ReadOnlySpan<byte> data, byte[] dstBuffer) {
76
+ return Compress(data, dstBuffer, DefaultCompressionLevel);
80
77
  }
81
78
 
82
79
  /// <summary>
@@ -92,6 +89,15 @@ namespace Code.Zstd {
92
89
  public byte[] Decompress(ReadOnlySpan<byte> data) {
93
90
  return DecompressData(data, _ctx);
94
91
  }
92
+
93
+ /// <summary>
94
+ /// Decompress the data into the given destination buffer.
95
+ /// Use the <c>Zstd.GetDecompressionBound</c> method to ensure the buffer is large enough.
96
+ /// </summary>
97
+ /// <returns>The number of bytes written to the buffer.</returns>
98
+ public int Decompress(ReadOnlySpan<byte> data, byte[] dstBuffer) {
99
+ return DecompressWithBuffer(data, dstBuffer, _ctx);
100
+ }
95
101
 
96
102
  public void Dispose() {
97
103
  Dispose(true);
@@ -106,6 +112,31 @@ namespace Code.Zstd {
106
112
  Dispose(false);
107
113
  }
108
114
 
115
+ /// <summary>
116
+ /// Get the maximum buffer size needed for compression.
117
+ /// </summary>
118
+ public static int GetCompressionBound(ReadOnlySpan<byte> uncompressedData) {
119
+ var bound = ZSTD_compressBound((ulong)uncompressedData.Length);
120
+ if (ZSTD_isError(bound)) {
121
+ throw new ZstdException(bound);
122
+ }
123
+ return (int)bound;
124
+ }
125
+
126
+ /// <summary>
127
+ /// Get the maximum buffer size needed for decompression.
128
+ /// </summary>
129
+ public static unsafe int GetDecompressionBound(ReadOnlySpan<byte> compressedData) {
130
+ ulong rSize;
131
+ fixed (byte* src = compressedData) {
132
+ rSize = ZSTD_getFrameContentSize(new IntPtr(src), (ulong)compressedData.Length);
133
+ }
134
+ if (ZSTD_isError(rSize)) {
135
+ throw new ZstdException(rSize);
136
+ }
137
+ return (int)rSize;
138
+ }
139
+
109
140
  /// <summary>
110
141
  /// Compress the bytes. The compression level can be between <c>Zstd.MinCompressionLevel</c>
111
142
  /// and <c>Zstd.MaxCompressionLevel</c>. Most use-cases should use <c>Zstd.DefaultCompressionLevel</c>.
@@ -181,6 +212,23 @@ namespace Code.Zstd {
181
212
  return decompressedData;
182
213
  }
183
214
 
215
+ private static unsafe int DecompressWithBuffer(ReadOnlySpan<byte> data, byte[] dstBuffer, ZstdContext ctx) {
216
+ ulong decompressedSize;
217
+ fixed (byte* src = data) {
218
+ fixed (byte* dst = dstBuffer) {
219
+ if (ctx != null) {
220
+ decompressedSize = ZSTD_decompressDCtx(ctx.Dctx, new IntPtr(dst), (ulong)dstBuffer.Length, new IntPtr(src), (ulong)data.Length);
221
+ } else {
222
+ decompressedSize = ZSTD_decompress(new IntPtr(dst), (ulong)dstBuffer.Length, new IntPtr(src), (ulong)data.Length);
223
+ }
224
+ }
225
+ }
226
+ if (ZSTD_isError(decompressedSize)) {
227
+ throw new ZstdException(decompressedSize);
228
+ }
229
+ return (int)decompressedSize;
230
+ }
231
+
184
232
  private static unsafe byte[] CompressWithStack(ReadOnlySpan<byte> data, ulong bound, int compressionLevel, ZstdContext ctx) {
185
233
  var dst = stackalloc byte[(int)bound];
186
234
  ulong compressedSize;
@@ -221,6 +269,24 @@ namespace Code.Zstd {
221
269
  Array.Resize(ref dstBuf, (int)compressedSize);
222
270
  return dstBuf;
223
271
  }
272
+
273
+ private static unsafe int CompressWithBuffer(ReadOnlySpan<byte> data, byte[] dstBuffer, int compressionLevel, ZstdContext ctx) {
274
+ ulong compressedSize;
275
+ fixed (byte* src = data) {
276
+ fixed (byte* dst = dstBuffer) {
277
+ if (ctx != null) {
278
+ compressedSize = ZSTD_compressCCtx(ctx.Cctx, new IntPtr(dst), (ulong)dstBuffer.Length, new IntPtr(src), (ulong)data.Length, compressionLevel);
279
+ }
280
+ else {
281
+ compressedSize = ZSTD_compress(new IntPtr(dst), (ulong)dstBuffer.Length, new IntPtr(src), (ulong)data.Length, compressionLevel);
282
+ }
283
+ }
284
+ }
285
+ if (ZSTD_isError(compressedSize)) {
286
+ throw new ZstdException(compressedSize);
287
+ }
288
+ return (int)compressedSize;
289
+ }
224
290
  }
225
291
 
226
292
  public sealed class ZstdContext : IDisposable {
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gg.easy.airship",
3
- "version": "0.1.2143",
3
+ "version": "0.1.2144",
4
4
  "displayName": "Airship",
5
5
  "unity": "2021.3",
6
6
  "unityRelease": "12f1",