ai.muna.muna 0.0.45 → 0.0.47

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.
Files changed (33) hide show
  1. package/Editor/Build/AndroidBuildHandler.cs +3 -3
  2. package/Editor/Build/iOSBuildHandler.cs +4 -4
  3. package/Editor/Build/macOSBuildHandler.cs +6 -6
  4. package/Editor/MunaMenu.cs +9 -11
  5. package/Plugins/Android/Muna.aar +0 -0
  6. package/Plugins/macOS/Function.dylib.meta +26 -25
  7. package/README.md +1 -1
  8. package/Runtime/Beta/OpenAI/SpeechService.cs +6 -0
  9. package/Runtime/Beta/OpenAI/Types.cs +0 -1
  10. package/Runtime/Beta/Remote/RemotePredictionService.cs +43 -42
  11. package/Runtime/Beta/{Remote → Types}/Value.cs +2 -2
  12. package/{Unity → Runtime/Beta}/Types.meta +1 -1
  13. package/Runtime/Muna.cs +1 -1
  14. package/Unity/API/PredictionCacheClient.cs +44 -51
  15. package/Unity/Converters/Color.cs +46 -0
  16. package/Unity/Converters/Color.cs.meta +2 -0
  17. package/Unity/Converters/Rect.cs +230 -0
  18. package/Unity/Converters/Rect.cs.meta +2 -0
  19. package/Unity/Converters/Vector2.cs +44 -0
  20. package/Unity/Converters/Vector2.cs.meta +2 -0
  21. package/Unity/Converters/Vector3.cs +45 -0
  22. package/Unity/Converters/Vector3.cs.meta +2 -0
  23. package/Unity/Converters/Vector4.cs +46 -0
  24. package/Unity/Converters/Vector4.cs.meta +2 -0
  25. package/Unity/Converters.meta +8 -0
  26. package/Unity/Internal/MunaSettings.cs +1 -1
  27. package/Unity/Internal/PredictionCache.cs +160 -0
  28. package/Unity/Internal/PredictionCache.cs.meta +2 -0
  29. package/Unity/MunaUnity.cs +68 -20
  30. package/package.json +1 -1
  31. package/Unity/Types/CachedPrediction.cs +0 -36
  32. package/Unity/Types/CachedPrediction.cs.meta +0 -11
  33. /package/Runtime/Beta/{Remote → Types}/Value.cs.meta +0 -0
@@ -15,12 +15,12 @@ namespace Muna.Editor.Build {
15
15
  using UnityEditor.Android;
16
16
  using UnityEditor.Build.Reporting;
17
17
  using API;
18
+ using Internal;
18
19
  using Services;
19
- using MunaSettings = Internal.MunaSettings;
20
20
 
21
21
  internal sealed class AndroidBuildHandler : BuildHandler, IPostGenerateGradleAndroidProject {
22
22
 
23
- private static List<CachedPrediction> cache;
23
+ private static List<PredictionCache.CachedPrediction> cache;
24
24
  private static Dictionary<AndroidArchitecture, string> ArchToClientId = new() {
25
25
  [AndroidArchitecture.ARMv7] = @"android-armeabi-v7a",
26
26
  [AndroidArchitecture.ARM64] = @"android-arm64-v8a",
@@ -49,7 +49,7 @@ namespace Muna.Editor.Build {
49
49
  clientId: target,
50
50
  configurationId: @""
51
51
  )).Result;
52
- return new CachedPrediction(prediction, target);
52
+ return prediction.AsCached(target);
53
53
  } catch (AggregateException ex) {
54
54
  Debug.LogWarning($"Muna: Failed to embed {tag} predictor with error: {ex.InnerException}. Predictions with this predictor will likely fail at runtime.");
55
55
  return null;
@@ -16,7 +16,7 @@ namespace Muna.Editor.Build {
16
16
  using UnityEditor.Build;
17
17
  using UnityEditor.Build.Reporting;
18
18
  using API;
19
- using MunaSettings = Internal.MunaSettings;
19
+ using Internal;
20
20
 
21
21
  #if UNITY_IOS || UNITY_VISIONOS
22
22
  using UnityEditor.iOS.Xcode;
@@ -25,7 +25,7 @@ namespace Muna.Editor.Build {
25
25
 
26
26
  internal sealed class iOSBuildHandler : BuildHandler, IPostprocessBuildWithReport {
27
27
 
28
- private List<CachedPrediction> cache;
28
+ private List<PredictionCache.CachedPrediction> cache;
29
29
  private static readonly Dictionary<BuildTarget, string> ClientIds = new () {
30
30
  [BuildTarget.iOS] = @"ios-arm64",
31
31
  [BuildTarget.VisionOS] = @"visionos-arm64"
@@ -37,7 +37,7 @@ namespace Muna.Editor.Build {
37
37
  var projectSettings = MunaProjectSettings.instance;
38
38
  var settings = MunaSettings.Create(projectSettings.accessKey);
39
39
  var embeds = GetEmbeds();
40
- var cache = new List<CachedPrediction>();
40
+ var cache = new List<PredictionCache.CachedPrediction>();
41
41
  var clientId = ClientIds[report.summary.platform];
42
42
  foreach (var embed in embeds) {
43
43
  var client = new DotNetClient(embed.url, embed.accessKey);
@@ -50,7 +50,7 @@ namespace Muna.Editor.Build {
50
50
  clientId: clientId,
51
51
  configurationId: @""
52
52
  )).Result;
53
- return new CachedPrediction(prediction, clientId);
53
+ return prediction.AsCached(clientId);
54
54
  } catch (AggregateException ex) {
55
55
  Debug.LogWarning($"Muna: Failed to embed {tag} predictor with error: {ex.InnerException}. Predictions with this predictor will likely fail at runtime.");
56
56
  return null;
@@ -15,17 +15,17 @@ namespace Muna.Editor.Build {
15
15
  using UnityEditor.Build;
16
16
  using UnityEditor.Build.Reporting;
17
17
  using API;
18
+ using Internal;
18
19
  using Services;
19
- using MunaSettings = Internal.MunaSettings;
20
20
 
21
- #if UNITY_STANDALONE_OSX
21
+ #if UNITY_STANDALONE_OSX
22
22
  using UnityEditor.iOS.Xcode;
23
23
  using UnityEditor.iOS.Xcode.Extensions;
24
- #endif
24
+ #endif
25
25
 
26
26
  internal sealed class macOSBuildHandler : BuildHandler, IPostprocessBuildWithReport {
27
27
 
28
- private List<CachedPrediction> cache;
28
+ private List<PredictionCache.CachedPrediction> cache;
29
29
  private static readonly string[] ClientIds = new[] {
30
30
  "macos-arm64",
31
31
  "macos-x86_64"
@@ -41,7 +41,7 @@ namespace Muna.Editor.Build {
41
41
  var settings = MunaSettings.Create(projectSettings.accessKey);
42
42
  // Embed predictors
43
43
  var embeds = GetEmbeds();
44
- var cache = new List<CachedPrediction>();
44
+ var cache = new List<PredictionCache.CachedPrediction>();
45
45
  foreach (var embed in embeds) {
46
46
  var client = new DotNetClient(embed.url, embed.accessKey);
47
47
  var muna = new Muna(client);
@@ -54,7 +54,7 @@ namespace Muna.Editor.Build {
54
54
  clientId: clientId,
55
55
  configurationId: @""
56
56
  )).Result;
57
- return new CachedPrediction(prediction, clientId);
57
+ return prediction.AsCached(clientId);
58
58
  }
59
59
  catch (AggregateException ex) {
60
60
  Debug.LogWarning($"Muna: Failed to embed {tag} predictor with error: {ex.InnerException}. Predictions with this predictor will likely fail at runtime.");
@@ -7,35 +7,33 @@ namespace Muna.Editor {
7
7
 
8
8
  using System.IO;
9
9
  using UnityEditor;
10
+ using Internal;
10
11
 
11
12
  internal static class MunaMenu {
12
13
 
13
14
  private const int BasePriority = -50;
14
15
 
15
- [MenuItem(@"Muna/Muna " + Muna.Version, false, BasePriority)]
16
+ [MenuItem(@"Tools/Muna/Muna " + Muna.Version, false, BasePriority)]
16
17
  private static void Version() { }
17
18
 
18
- [MenuItem(@"Muna/Muna " + Muna.Version, true, BasePriority)]
19
+ [MenuItem(@"Tools/Muna/Muna " + Muna.Version, true, BasePriority)]
19
20
  private static bool EnableVersion() => false;
20
21
 
21
- [MenuItem(@"Muna/Get Access Key", false, BasePriority + 1)]
22
+ [MenuItem(@"Tools/Muna/Get Access Key", false, BasePriority + 1)]
22
23
  private static void GetAccessKey() => Help.BrowseURL(@"https://muna.ai/settings/developer");
23
24
 
24
- [MenuItem(@"Muna/Explore Predictors", false, BasePriority + 2)]
25
+ [MenuItem(@"Tools/Muna/Explore Predictors", false, BasePriority + 2)]
25
26
  private static void OpenExplore() => Help.BrowseURL(@"https://muna.ai/explore");
26
27
 
27
- [MenuItem(@"Muna/View the Docs", false, BasePriority + 3)]
28
+ [MenuItem(@"Tools/Muna/View the Docs", false, BasePriority + 3)]
28
29
  private static void OpenDocs() => Help.BrowseURL(@"https://docs.muna.ai");
29
30
 
30
- [MenuItem(@"Muna/Report an Issue", false, BasePriority + 4)]
31
+ [MenuItem(@"Tools/Muna/Report an Issue", false, BasePriority + 4)]
31
32
  private static void ReportIssue() => Help.BrowseURL(@"https://github.com/muna-ai/muna-unity");
32
33
 
33
- [MenuItem(@"Muna/Clear Predictor Cache", false, BasePriority + 5)]
34
+ [MenuItem(@"Tools/Muna/Clear Predictor Cache", false, BasePriority + 5)]
34
35
  private static void ClearPredictorCache() {
35
- Directory.Delete(
36
- global::Muna.API.PredictionCacheClient.PredictorCachePath,
37
- true
38
- );
36
+ Directory.Delete(PredictionCache.PredictorCachePath, true);
39
37
  UnityEngine.Debug.Log("Muna: Cleared predictor cache.");
40
38
  }
41
39
  }
Binary file
@@ -2,7 +2,7 @@ fileFormatVersion: 2
2
2
  guid: 62aec3fdf35a340f78ab3a076fc7ca3e
3
3
  PluginImporter:
4
4
  externalObjects: {}
5
- serializedVersion: 3
5
+ serializedVersion: 2
6
6
  iconMap: {}
7
7
  executionOrder: {}
8
8
  defineConstraints: []
@@ -11,52 +11,53 @@ PluginImporter:
11
11
  isExplicitlyReferenced: 0
12
12
  validateReferences: 1
13
13
  platformData:
14
- Android:
14
+ - first:
15
+ :
16
+ second:
15
17
  enabled: 0
16
- settings:
17
- AndroidLibraryDependee: UnityLibrary
18
- AndroidSharedLibraryType: Executable
19
- CPU: ARMv7
20
- Any:
18
+ settings: {}
19
+ - first:
20
+ : Any
21
+ second:
21
22
  enabled: 0
22
23
  settings:
23
- Exclude Android: 1
24
24
  Exclude Editor: 0
25
25
  Exclude Linux64: 1
26
26
  Exclude OSXUniversal: 0
27
- Exclude WebGL: 1
28
27
  Exclude Win: 1
29
28
  Exclude Win64: 1
30
- Exclude iOS: 1
31
- Editor:
29
+ - first:
30
+ Editor: Editor
31
+ second:
32
32
  enabled: 1
33
33
  settings:
34
34
  CPU: ARM64
35
35
  DefaultValueInitialized: true
36
36
  OS: OSX
37
- Linux64:
37
+ - first:
38
+ Standalone: Linux64
39
+ second:
38
40
  enabled: 0
39
41
  settings:
40
- CPU: AnyCPU
41
- OSXUniversal:
42
+ CPU: x86_64
43
+ - first:
44
+ Standalone: OSXUniversal
45
+ second:
42
46
  enabled: 1
43
47
  settings:
44
48
  CPU: ARM64
45
- Win:
46
- enabled: 0
47
- settings:
48
- CPU: AnyCPU
49
- Win64:
49
+ - first:
50
+ Standalone: Win
51
+ second:
50
52
  enabled: 0
51
53
  settings:
52
- CPU: AnyCPU
53
- iOS:
54
+ CPU: x86
55
+ - first:
56
+ Standalone: Win64
57
+ second:
54
58
  enabled: 0
55
59
  settings:
56
- AddToEmbeddedBinaries: false
57
- CPU: AnyCPU
58
- CompileFlags:
59
- FrameworkDependencies:
60
+ CPU: x86_64
60
61
  userData:
61
62
  assetBundleName:
62
63
  assetBundleVariant:
package/README.md CHANGED
@@ -16,7 +16,7 @@ Add the following items to your Unity project's `Packages/manifest.json`:
16
16
  }
17
17
  ],
18
18
  "dependencies": {
19
- "ai.muna.muna": "0.0.45"
19
+ "ai.muna.muna": "0.0.47"
20
20
  }
21
21
  }
22
22
  ```
@@ -190,6 +190,12 @@ namespace Muna.Beta.OpenAI {
190
190
  StreamFormat streamFormat,
191
191
  object acceleration
192
192
  ) => {
193
+ // Check response format
194
+ if (responseFormat != ResponseFormat.PCM)
195
+ throw new ArgumentException($"Cannot create speech with response format {responseFormat} because only `ResponseFormat.PCM` is supported.");
196
+ // Check stream format
197
+ if (streamFormat != StreamFormat.Audio)
198
+ throw new ArgumentException($"Cannot create speech with stream format {streamFormat} because only `StreamFormat.Audio` is supported.");
193
199
  // Build prediction input map
194
200
  var inputMap = new Dictionary<string, object?> {
195
201
  [inputParam.name] = input,
@@ -35,7 +35,6 @@ namespace Muna.Beta.OpenAI {
35
35
  /// <summary>
36
36
  /// Convert the `BinaryData` to a byte array.
37
37
  /// </summary>
38
- /// <returns></returns>
39
38
  public byte[] ToArray() => data;
40
39
 
41
40
  /// <summary>
@@ -69,51 +69,52 @@ namespace Muna.Beta.Services {
69
69
 
70
70
  internal RemotePredictionService(MunaClient client) => this.client = client;
71
71
 
72
- private async Task<Value> ToValue(object? value) => value switch { // INCOMPLETE // Image
73
- null => new Value { type = Dtype.Null },
74
- float x => new Value { data = await Upload(new [] { x }.ToStream()), type = Dtype.Float32, shape = new int[0] },
75
- double x => new Value { data = await Upload(new [] { x }.ToStream()), type = Dtype.Float64, shape = new int[0] },
76
- sbyte x => new Value { data = await Upload(new [] { x }.ToStream()), type = Dtype.Int8, shape = new int[0] },
77
- short x => new Value { data = await Upload(new [] { x }.ToStream()), type = Dtype.Int16, shape = new int[0] },
78
- int x => new Value { data = await Upload(new [] { x }.ToStream()), type = Dtype.Int32, shape = new int[0] },
79
- long x => new Value { data = await Upload(new [] { x }.ToStream()), type = Dtype.Int64, shape = new int[0] },
80
- byte x => new Value { data = await Upload(new [] { x }.ToStream()), type = Dtype.Uint8, shape = new int[0] },
81
- ushort x => new Value { data = await Upload(new [] { x }.ToStream()), type = Dtype.Uint16, shape = new int[0] },
82
- uint x => new Value { data = await Upload(new [] { x }.ToStream()), type = Dtype.Uint32, shape = new int[0] },
83
- ulong x => new Value { data = await Upload(new [] { x }.ToStream()), type = Dtype.Uint64, shape = new int[0] },
84
- bool x => new Value { data = await Upload(new [] { x }.ToStream()), type = Dtype.Bool, shape = new int[0] },
85
- float[] x => new Value { data = await Upload(x.ToStream()), type = Dtype.Float32, shape = new [] { x.Length } },
86
- double[] x => new Value { data = await Upload(x.ToStream()), type = Dtype.Float64, shape = new [] { x.Length } },
87
- sbyte[] x => new Value { data = await Upload(x.ToStream()), type = Dtype.Int8, shape = new [] { x.Length } },
88
- short[] x => new Value { data = await Upload(x.ToStream()), type = Dtype.Int16, shape = new [] { x.Length } },
89
- int[] x => new Value { data = await Upload(x.ToStream()), type = Dtype.Int32, shape = new [] { x.Length } },
90
- long[] x => new Value { data = await Upload(x.ToStream()), type = Dtype.Int64, shape = new [] { x.Length } },
91
- byte[] x => new Value { data = await Upload(x.ToStream()), type = Dtype.Uint8, shape = new [] { x.Length } },
92
- ushort[] x => new Value { data = await Upload(x.ToStream()), type = Dtype.Uint16, shape = new [] { x.Length } },
93
- uint[] x => new Value { data = await Upload(x.ToStream()), type = Dtype.Uint32, shape = new [] { x.Length } },
94
- ulong[] x => new Value { data = await Upload(x.ToStream()), type = Dtype.Uint64, shape = new [] { x.Length } },
95
- bool[] x => new Value { data = await Upload(x.ToStream()), type = Dtype.Bool, shape = new [] { x.Length } },
96
- Tensor<float> x => new Value { data = await Upload(x.data.ToStream()), type = Dtype.Float32, shape = x.shape },
97
- Tensor<double> x => new Value { data = await Upload(x.data.ToStream()), type = Dtype.Float64, shape = x.shape },
98
- Tensor<sbyte> x => new Value { data = await Upload(x.data.ToStream()), type = Dtype.Int8, shape = x.shape },
99
- Tensor<short> x => new Value { data = await Upload(x.data.ToStream()), type = Dtype.Int16, shape = x.shape },
100
- Tensor<int> x => new Value { data = await Upload(x.data.ToStream()), type = Dtype.Int32, shape = x.shape },
101
- Tensor<long> x => new Value { data = await Upload(x.data.ToStream()), type = Dtype.Int64, shape = x.shape },
102
- Tensor<byte> x => new Value { data = await Upload(x.data.ToStream()), type = Dtype.Uint8, shape = x.shape },
103
- Tensor<ushort> x => new Value { data = await Upload(x.data.ToStream()), type = Dtype.Uint16, shape = x.shape },
104
- Tensor<uint> x => new Value { data = await Upload(x.data.ToStream()), type = Dtype.Uint32, shape = x.shape },
105
- Tensor<ulong> x => new Value { data = await Upload(x.data.ToStream()), type = Dtype.Uint64, shape = x.shape },
106
- Tensor<bool> x => new Value { data = await Upload(x.data.ToStream()), type = Dtype.Bool, shape = x.shape },
107
- string x => new Value { data = await Upload(x.ToStream(), mime: @"text/plain"), type = Dtype.String },
108
- IList x => new Value { data = await Upload(JsonConvert.SerializeObject(x).ToStream(), mime: @"application/json"), type = Dtype.List },
109
- IDictionary x => new Value { data = await Upload(JsonConvert.SerializeObject(x).ToStream(), mime: @"application/json"), type = Dtype.Dict },
110
- Image x => new Value { data = "", type = Dtype.Image },
111
- Stream x => new Value { data = await Upload(x), type = Dtype.Binary },
72
+ private async Task<RemoteValue> ToValue(object? value) => value switch { // INCOMPLETE // Image
73
+ null => new() { type = Dtype.Null },
74
+ float x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Float32, shape = new int[0] },
75
+ double x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Float64, shape = new int[0] },
76
+ sbyte x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Int8, shape = new int[0] },
77
+ short x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Int16, shape = new int[0] },
78
+ int x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Int32, shape = new int[0] },
79
+ long x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Int64, shape = new int[0] },
80
+ byte x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Uint8, shape = new int[0] },
81
+ ushort x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Uint16, shape = new int[0] },
82
+ uint x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Uint32, shape = new int[0] },
83
+ ulong x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Uint64, shape = new int[0] },
84
+ bool x => new() { data = await Upload(new [] { x }.ToStream()), type = Dtype.Bool, shape = new int[0] },
85
+ float[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Float32, shape = new [] { x.Length } },
86
+ double[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Float64, shape = new [] { x.Length } },
87
+ sbyte[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Int8, shape = new [] { x.Length } },
88
+ short[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Int16, shape = new [] { x.Length } },
89
+ int[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Int32, shape = new [] { x.Length } },
90
+ long[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Int64, shape = new [] { x.Length } },
91
+ byte[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Uint8, shape = new [] { x.Length } },
92
+ ushort[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Uint16, shape = new [] { x.Length } },
93
+ uint[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Uint32, shape = new [] { x.Length } },
94
+ ulong[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Uint64, shape = new [] { x.Length } },
95
+ bool[] x => new() { data = await Upload(x.ToStream()), type = Dtype.Bool, shape = new [] { x.Length } },
96
+ Tensor<float> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Float32, shape = x.shape },
97
+ Tensor<double> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Float64, shape = x.shape },
98
+ Tensor<sbyte> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Int8, shape = x.shape },
99
+ Tensor<short> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Int16, shape = x.shape },
100
+ Tensor<int> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Int32, shape = x.shape },
101
+ Tensor<long> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Int64, shape = x.shape },
102
+ Tensor<byte> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Uint8, shape = x.shape },
103
+ Tensor<ushort> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Uint16, shape = x.shape },
104
+ Tensor<uint> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Uint32, shape = x.shape },
105
+ Tensor<ulong> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Uint64, shape = x.shape },
106
+ Tensor<bool> x => new() { data = await Upload(x.data.ToStream()), type = Dtype.Bool, shape = x.shape },
107
+ string x => new() { data = await Upload(x.ToStream(), mime: @"text/plain"), type = Dtype.String },
108
+ IList x => new() { data = await Upload(JsonConvert.SerializeObject(x).ToStream(), mime: @"application/json"), type = Dtype.List },
109
+ IDictionary x => new() { data = await Upload(JsonConvert.SerializeObject(x).ToStream(), mime: @"application/json"), type = Dtype.Dict },
110
+ Image x => new() { data = "", type = Dtype.Image },
111
+ Stream x => new() { data = await Upload(x), type = Dtype.Binary },
112
112
  Enum x => await ToValue(x.ToObject()),
113
+ RemoteValue x => x,
113
114
  _ => throw new InvalidOperationException($"Failed to serialize value '{value}' of type `{value.GetType()}` because it is not supported"),
114
115
  };
115
116
 
116
- private async Task<object?> ToObject(Value value) { // INCOMPLETE // Image
117
+ private async Task<object?> ToObject(RemoteValue value) { // INCOMPLETE // Image
117
118
  if (value.type == Dtype.Null)
118
119
  return null;
119
120
  using var stream = await Download(value.data!);
@@ -159,7 +160,7 @@ namespace Muna.Beta.Services {
159
160
 
160
161
  [Preserve, Serializable]
161
162
  private class RemotePrediction : Prediction {
162
- public new Value[]? results;
163
+ public new RemoteValue[]? results;
163
164
  }
164
165
  #endregion
165
166
  }
@@ -5,7 +5,7 @@
5
5
 
6
6
  #nullable enable
7
7
 
8
- namespace Muna.Beta.Services {
8
+ namespace Muna.Beta {
9
9
 
10
10
  using System;
11
11
  using System.IO;
@@ -18,7 +18,7 @@ namespace Muna.Beta.Services {
18
18
  /// Remote prediction value.
19
19
  /// </summary>
20
20
  [Preserve, Serializable]
21
- internal class Value {
21
+ public sealed class RemoteValue {
22
22
 
23
23
  /// <summary>
24
24
  /// Value URL.
@@ -1,5 +1,5 @@
1
1
  fileFormatVersion: 2
2
- guid: 0c449c8b8a355457aa40b183e847e588
2
+ guid: 13abad7ab83f14becbb10f8b4482e27a
3
3
  folderAsset: yes
4
4
  DefaultImporter:
5
5
  externalObjects: {}
package/Runtime/Muna.cs CHANGED
@@ -94,7 +94,7 @@ namespace Muna {
94
94
 
95
95
  #region --Operations--
96
96
  public readonly MunaClient client;
97
- public const string Version = @"0.0.45";
97
+ public const string Version = @"0.0.47";
98
98
  internal const string URL = @"https://api.muna.ai/v1";
99
99
  #endregion
100
100
  }
@@ -7,13 +7,12 @@
7
7
 
8
8
  namespace Muna.API {
9
9
 
10
- using System;
11
10
  using System.Collections.Generic;
12
11
  using System.IO;
13
12
  using System.Linq;
14
13
  using System.Threading.Tasks;
15
14
  using UnityEngine;
16
- using Newtonsoft.Json;
15
+ using Internal;
17
16
  using Services;
18
17
 
19
18
  /// <summary>
@@ -32,9 +31,8 @@ namespace Muna.API {
32
31
  /// <param name="cache">Prediction cache.</param>
33
32
  public PredictionCacheClient(
34
33
  string url,
35
- string? accessKey,
36
- List<CachedPrediction>? cache = default
37
- ) : base(url, accessKey) => this.cache = cache ?? new();
34
+ string? accessKey
35
+ ) : base(url, accessKey) { }
38
36
 
39
37
  /// <summary>
40
38
  /// Perform a request to a Muna REST endpoint.
@@ -52,11 +50,9 @@ namespace Muna.API {
52
50
  Dictionary<string, string>? headers = default
53
51
  ) where T : class {
54
52
  // Check payload
55
- var tag = payload?.TryGetValue(@"tag", out var t) ?? false ? t as string : null;
56
- var clientId = payload?.TryGetValue(@"clientId", out var id) ?? false ? id as string : null;
57
- var configurationId = payload?.TryGetValue(@"configurationId", out var configuration) ?? false ?
58
- configuration as string :
59
- null;
53
+ var tag = GetValue<string>(payload, @"tag");
54
+ var clientId = GetValue<string>(payload, @"clientId");
55
+ var configurationId = GetValue<string>(payload, @"configurationId");
60
56
  if (
61
57
  method != @"POST" ||
62
58
  path != @"/predictions" ||
@@ -65,38 +61,35 @@ namespace Muna.API {
65
61
  string.IsNullOrEmpty(configurationId)
66
62
  )
67
63
  return await base.Request<T>(method, path, payload, headers);
68
- // Get cached prediction
69
- var sanitizedTag = tag.Substring(1).Replace("/", "_");
70
- var cachePath = Path.Combine(
71
- PredictorCachePath,
64
+ // Get embedded prediction if available
65
+ var cache = MunaSettings.Instance!.cache;
66
+ var embeddedPrediction = cache.FirstOrDefault(p =>
67
+ p.tag == tag &&
68
+ MatchClientIds(p.clientId!, clientId)
69
+ );
70
+ // Load from prediction cache
71
+ if (PredictionCache.Get(
72
+ tag,
72
73
  clientId,
73
74
  configurationId,
74
- $"{sanitizedTag}.json"
75
- );
76
- var cachedPrediction = TryLoadCachedPrediction(cachePath);
77
- if (cachedPrediction != null)
75
+ embeddedPrediction?.resources,
76
+ out var cachedPrediction
77
+ ))
78
78
  return cachedPrediction as T;
79
- // Create prediction
80
- var predictionId = cache.FirstOrDefault(p => p.tag == tag && p.clientId == clientId)?.id;
79
+ // Create prediction and cache
81
80
  var prediction = await base.Request<Prediction>(
82
81
  method: @"POST",
83
82
  path: @"/predictions",
84
- payload: new () {
83
+ payload: new() {
85
84
  [@"tag"] = tag,
86
85
  [@"clientId"] = clientId,
87
86
  [@"configurationId"] = configurationId,
88
- [@"predictionId"] = predictionId,
87
+ [@"predictionId"] = embeddedPrediction?.id,
89
88
  },
90
89
  headers
91
90
  );
92
91
  prediction!.resources = await Task.WhenAll(prediction.resources.Select(GetCachedResource));
93
- // Write
94
- var predictionJson = JsonConvert.SerializeObject(
95
- new CachedPrediction(prediction, clientId),
96
- Formatting.Indented
97
- );
98
- Directory.CreateDirectory(Path.GetDirectoryName(cachePath));
99
- File.WriteAllText(cachePath, predictionJson);
92
+ PredictionCache.Add(prediction.AsCached(clientId, configurationId));
100
93
  // Return
101
94
  return prediction as T;
102
95
  }
@@ -104,15 +97,9 @@ namespace Muna.API {
104
97
 
105
98
 
106
99
  #region --Operations--
107
- private readonly List<CachedPrediction> cache;
108
- private static string CacheRoot => Application.isEditor ?
109
- Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), @".fxn") :
110
- Path.Combine(Application.persistentDataPath, @"fxn");
111
- private static string ResourceCachePath => Path.Combine(CacheRoot, @"cache");
112
- internal static string PredictorCachePath => Path.Combine(CacheRoot, @"predictors");
113
100
 
114
101
  private async Task<PredictionResource> GetCachedResource(PredictionResource resource) {
115
- var path = PredictionService.GetResourcePath(resource, ResourceCachePath);
102
+ var path = PredictionService.GetResourcePath(resource, PredictionCache.ResourceCachePath);
116
103
  if (!File.Exists(path)) {
117
104
  Directory.CreateDirectory(Path.GetDirectoryName(path));
118
105
  using var dataStream = await Download(resource.url);
@@ -122,22 +109,28 @@ namespace Muna.API {
122
109
  return new PredictionResource { type = resource.type, url = $"file://{path}" };
123
110
  }
124
111
 
125
- private static Prediction? TryLoadCachedPrediction(string path) {
126
- if (!File.Exists(path))
127
- return null;
128
- var json = File.ReadAllText(path);
129
- var prediction = JsonConvert.DeserializeObject<Prediction>(json)!;
130
- var resources = prediction.resources.Select(res => new PredictionResource {
131
- type = res.type,
132
- url = $"file://{PredictionService.GetResourcePath(res, ResourceCachePath)}",
133
- name = res.name
134
- }).ToArray();
135
- if (!resources.All(res => File.Exists(new Uri(res.url).LocalPath))) {
136
- File.Delete(path);
137
- return null;
112
+ private static T? GetValue<T>(
113
+ Dictionary<string, object?>? payload,
114
+ string key
115
+ ) {
116
+ if (payload?.TryGetValue(key, out var value) ?? false)
117
+ return (T?)value;
118
+ else
119
+ return default;
120
+ }
121
+
122
+ private static bool MatchClientIds(string a, string b) {
123
+ if (a == b)
124
+ return true;
125
+ if (a.Contains("android") && b.Contains("android")) {
126
+ var ARM32 = new[] { "armeabi-v7a", "armv7l", "armv8l" };
127
+ var ARM64 = new[] { "arm64", "aarch64", "armv8" };
128
+ if (ARM32.Any(s => a.Contains(s)) && ARM32.Any(s => b.Contains(s)))
129
+ return true;
130
+ if (ARM64.Any(s => a.Contains(s)) && ARM64.Any(s => b.Contains(s)))
131
+ return true;
138
132
  }
139
- prediction.resources = resources;
140
- return prediction;
133
+ return false;
141
134
  }
142
135
  #endregion
143
136
  }
@@ -0,0 +1,46 @@
1
+ /*
2
+ * Muna
3
+ * Copyright © 2025 NatML Inc. All rights reserved.
4
+ */
5
+
6
+ #nullable enable
7
+
8
+ namespace Muna.Converters {
9
+
10
+ using System;
11
+ using UnityEngine;
12
+ using Newtonsoft.Json;
13
+ using Newtonsoft.Json.Linq;
14
+
15
+ /// <summary>
16
+ /// Convert an array field to a `Color`.
17
+ /// The array MUST contain three or four numbers.
18
+ /// </summary>
19
+ public sealed class ArrayToColorConverter : JsonConverter<Color> {
20
+
21
+ public override void WriteJson(
22
+ JsonWriter writer,
23
+ Color value,
24
+ JsonSerializer serializer
25
+ ) {
26
+ var obj = new JArray { value.r, value.g, value.b, value.a };
27
+ obj.WriteTo(writer);
28
+ }
29
+
30
+ public override Color ReadJson(
31
+ JsonReader reader,
32
+ Type type,
33
+ Color existing,
34
+ bool hasExisting,
35
+ JsonSerializer s
36
+ ) {
37
+ var arr = JArray.Load(reader);
38
+ return new Color(
39
+ r: (float)arr[0],
40
+ g: (float)arr[1],
41
+ b: (float)arr[2],
42
+ a: arr.Count > 3 ? (float)arr[3] : 1f
43
+ );
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 64482ee46d27541c0a981e4688bfeaad