LLM for Unity  v3.0.3
Create characters in Unity with LLMs!
Loading...
Searching...
No Matches
LLMBuilder.cs
Go to the documentation of this file.
1
3using UnityEditor;
4using UnityEngine;
5using System.IO;
6using System.Collections.Generic;
7using System;
8
9#if UNITY_EDITOR
10namespace LLMUnity
11{
16 public class LLMBuilder : AssetPostprocessor
17 {
18 static List<StringPair> movedPairs = new List<StringPair>();
19 public static string BuildTempDir = Path.Combine(Directory.GetParent(Application.dataPath).FullName, "LLMUnityBuild");
20 static string movedCache = Path.Combine(BuildTempDir, "moved.json");
21
22 [InitializeOnLoadMethod]
23 private static void InitializeOnLoad()
24 {
25 Reset();
26 }
27
28 public static string PluginDir(string platform, bool relative = false)
29 {
30 string pluginDir = Path.Combine("Plugins", platform, "LLMUnity");
31 if (!relative) pluginDir = Path.Combine(Application.dataPath, pluginDir);
32 return pluginDir;
33 }
34
35 public static void Retry(System.Action action, int retries = 10, int delayMs = 100)
36 {
37 for (int i = 0; i < retries; i++)
38 {
39 try
40 {
41 action();
42 return;
43 }
44 catch (IOException e)
45 {
46 if (i == retries - 1) LLMUnitySetup.LogError(e.Message, true);
47 System.Threading.Thread.Sleep(delayMs);
48 }
49 catch (System.UnauthorizedAccessException e)
50 {
51 if (i == retries - 1) LLMUnitySetup.LogError(e.Message, true);;
52 System.Threading.Thread.Sleep(delayMs);
53 }
54 }
55 }
56
63 public static void HandleActionFileRecursive(string source, string target, Action<string, string> actionCallback)
64 {
65 if (File.Exists(source))
66 {
67 string targetDir = Path.GetDirectoryName(target);
68 if (targetDir != "" && !Directory.Exists(targetDir)) Directory.CreateDirectory(targetDir);
69 Retry(() => actionCallback(source, target));
70 }
71 else if (Directory.Exists(source))
72 {
73 Directory.CreateDirectory(target);
74 List<string> filesAndDirs = new List<string>();
75 filesAndDirs.AddRange(Directory.GetFiles(source));
76 filesAndDirs.AddRange(Directory.GetDirectories(source));
77 foreach (string path in filesAndDirs)
78 {
79 HandleActionFileRecursive(path, Path.Combine(target, Path.GetFileName(path)), actionCallback);
80 }
81 }
82 }
83
89 public static void CopyWithOverwrite(string source, string target)
90 {
91 File.Copy(source, target, true);
92 }
93
99 public static void CopyPath(string source, string target)
100 {
102 }
103
109 public static void MovePath(string source, string target)
110 {
111 HandleActionFileRecursive(source, target, File.Move);
112 DeletePath(source);
113 }
114
119 public static bool DeletePath(string path)
120 {
121 string[] allowedDirs = new string[] { LLMUnitySetup.GetAssetPath(), BuildTempDir, PluginDir("Android"), PluginDir("iOS"), PluginDir("VisionOS") };
122 bool deleteOK = false;
123 foreach (string allowedDir in allowedDirs) deleteOK = deleteOK || LLMUnitySetup.IsSubPath(path, allowedDir);
124 if (!deleteOK)
125 {
126 LLMUnitySetup.LogError($"Safeguard: {path} will not be deleted because it may not be safe");
127 return false;
128 }
129 if (File.Exists(path)) Retry(() => File.Delete(path));
130 else if (Directory.Exists(path)) Retry(() => Directory.Delete(path, true));
131 return true;
132 }
133
134 static void AddMovedPair(string source, string target)
135 {
136 movedPairs.Add(new StringPair { source = source, target = target });
137 File.WriteAllText(movedCache, JsonUtility.ToJson(new ListStringPair { pairs = movedPairs }, true));
138 }
139
140 static void AddTargetPair(string target)
141 {
142 AddMovedPair("", target);
143 }
144
145 static bool MoveAction(string source, string target, bool addEntry = true)
146 {
147 Action<string, string> moveCallback;
148 if (File.Exists(source) || Directory.Exists(source)) moveCallback = MovePath;
149 else return false;
150
151 if (addEntry) AddMovedPair(source, target);
152 moveCallback(source, target);
153 return true;
154 }
155
156 static bool CopyAction(string source, string target, bool addEntry = true)
157 {
158 Action<string, string> copyCallback;
159 if (File.Exists(source)) copyCallback = File.Copy;
160 else if (Directory.Exists(source)) copyCallback = CopyPath;
161 else return false;
162
163 if (addEntry) AddTargetPair(target);
164 copyCallback(source, target);
165 return true;
166 }
167
168 static void CopyActionAddMeta(string source, string target)
169 {
170 CopyAction(source, target);
171 AddTargetPair(target + ".meta");
172 }
173
174 static void AddActionAddMeta(string target)
175 {
176 AddTargetPair(target);
177 AddTargetPair(target + ".meta");
178 }
179
180 static string MobileSuffix(BuildTarget buildTarget)
181 {
182 return (buildTarget == BuildTarget.Android) ? "so" : "a";
183 }
184
185 static string MobilePluginPath(BuildTarget buildTarget, string arch, bool relative = false)
186 {
187 string os = buildTarget.ToString();
188 return Path.Combine(PluginDir(os, relative), arch, $"libllamalib_{os.ToLower()}.{MobileSuffix(buildTarget)}");
189 }
190
195 public static void BuildLibraryPlatforms(BuildTarget buildTarget)
196 {
197 List<string> platforms = new List<string>();
198 bool checkCUBLAS = false;
199 switch (buildTarget)
200 {
201 case BuildTarget.StandaloneWindows:
202 case BuildTarget.StandaloneWindows64:
203 platforms.Add("win-x64");
204 checkCUBLAS = true;
205 break;
206 case BuildTarget.StandaloneLinux64:
207 platforms.Add("linux-x64");
208 checkCUBLAS = true;
209 break;
210 case BuildTarget.StandaloneOSX:
211 platforms.Add("osx-x64");
212 platforms.Add("osx-arm64");
213 break;
214 case BuildTarget.Android:
215 platforms.Add("android-arm64");
216 platforms.Add("android-x64");
217 break;
218 case BuildTarget.iOS:
219 platforms.Add("ios-arm64");
220 break;
221#if UNITY_2022_3_OR_NEWER
222 case BuildTarget.VisionOS:
223 platforms.Add("visionos-arm64");
224 break;
225#endif
226 }
227
228 foreach (string source in Directory.GetDirectories(LLMUnitySetup.libraryPath))
229 {
230 string sourceName = Path.GetFileName(source);
231 if (!platforms.Contains(sourceName))
232 {
233 string target = Path.Combine(BuildTempDir, sourceName);
234 MoveAction(source, target);
235 MoveAction(source + ".meta", target + ".meta");
236 }
237 }
238
239 if (checkCUBLAS)
240 {
241 List<string> exclusionKeywords = LLMUnitySetup.CUBLAS ? new List<string>() { "tinyblas" } : new List<string>() { "cublas", "cudart" };
242 foreach (string platform in platforms)
243 {
244 string platformDir = Path.Combine(LLMUnitySetup.libraryPath, platform, "native");
245 foreach (string source in Directory.GetFiles(platformDir))
246 {
247 string sourceName = Path.GetFileName(source);
248 foreach (string exclusionKeyword in exclusionKeywords)
249 {
250 if (sourceName.Contains(exclusionKeyword))
251 {
252 string target = Path.Combine(BuildTempDir, platform, "native", sourceName);
253 MoveAction(source, target);
254 MoveAction(source + ".meta", target + ".meta");
255 break;
256 }
257 }
258 }
259 }
260 }
261
262 bool isVisionOS = false;
263#if UNITY_2022_3_OR_NEWER
264 isVisionOS = buildTarget == BuildTarget.VisionOS;
265#endif
266 if (buildTarget == BuildTarget.Android || buildTarget == BuildTarget.iOS || isVisionOS)
267 {
268 foreach (string platform in platforms)
269 {
270 string source = Path.Combine(LLMUnitySetup.libraryPath, platform, "native", $"libllamalib_{platform}.{MobileSuffix(buildTarget)}");
271 string target = MobilePluginPath(buildTarget, platform.Split("-")[1].ToUpper());
272 string pluginDir = PluginDir(buildTarget.ToString());
273 MoveAction(source, target);
274 MoveAction(source + ".meta", target + ".meta");
275 AddActionAddMeta(pluginDir);
276 }
277 }
278 }
279
280 static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload)
281 {
282 List<BuildTarget> buildTargets = new List<BuildTarget>() { BuildTarget.iOS, BuildTarget.Android };
283#if UNITY_2022_3_OR_NEWER
284 buildTargets.Add(BuildTarget.VisionOS);
285#endif
286 foreach (BuildTarget buildTarget in buildTargets)
287 {
288 string platformDir = Path.Combine("Assets", PluginDir(buildTarget.ToString(), true));
289 if (!Directory.Exists(platformDir)) continue;
290 foreach (string archDir in Directory.GetDirectories(platformDir))
291 {
292 string arch = Path.GetFileName(archDir);
293 string pathToPlugin = Path.Combine("Assets", MobilePluginPath(buildTarget, arch, true));
294 for (int i = 0; i < movedAssets.Length; i++)
295 {
296 if (movedAssets[i] == pathToPlugin)
297 {
298 var importer = AssetImporter.GetAtPath(pathToPlugin) as PluginImporter;
299 if (importer != null && importer.isNativePlugin)
300 {
301 importer.SetCompatibleWithPlatform(buildTarget, true);
302 importer.SetPlatformData(buildTarget, "CPU", arch);
303 AssetDatabase.ImportAsset(pathToPlugin);
304 }
305 }
306 }
307 }
308 }
309 }
310
314 public static void BuildModels()
315 {
316 LLMManager.Build(CopyActionAddMeta);
317 if (File.Exists(LLMUnitySetup.LLMManagerPath)) AddActionAddMeta(LLMUnitySetup.LLMManagerPath);
318 }
319
323 public static void Build(BuildTarget buildTarget)
324 {
325 DeletePath(BuildTempDir);
326 Directory.CreateDirectory(BuildTempDir);
327 BuildLibraryPlatforms(buildTarget);
328 BuildModels();
329 }
330
334 public static void Reset()
335 {
336 if (!File.Exists(movedCache)) return;
337 List<StringPair> movedPairs = JsonUtility.FromJson<ListStringPair>(File.ReadAllText(movedCache)).pairs;
338 if (movedPairs == null) return;
339
340 bool refresh = false;
341 foreach (var pair in movedPairs)
342 {
343 if (pair.source == "")
344 {
345 refresh |= DeletePath(pair.target);
346 }
347 else
348 {
349 if (File.Exists(pair.source) || Directory.Exists(pair.source))
350 {
351 refresh |= DeletePath(pair.target);
352 }
353 else
354 {
355 refresh |= MoveAction(pair.target, pair.source, false);
356 }
357 }
358 }
359 if (refresh) AssetDatabase.Refresh();
360 DeletePath(movedCache);
361 }
362 }
363}
364#endif
Class implementing the LLMUnity builder.
Definition LLMBuilder.cs:17
static void Reset()
Resets the libraries back to their original state.
static void MovePath(string source, string target)
Moves a source file to a target file.
static bool DeletePath(string path)
Deletes a path after checking if we are allowed to.
static void HandleActionFileRecursive(string source, string target, Action< string, string > actionCallback)
Performs an action for a file or a directory recursively.
Definition LLMBuilder.cs:63
static void BuildModels()
Bundles the model information.
static void BuildLibraryPlatforms(BuildTarget buildTarget)
Moves libraries in the correct place for building.
static void Build(BuildTarget buildTarget)
Bundles the models and libraries.
static void CopyPath(string source, string target)
Copies a source file to a target file.
Definition LLMBuilder.cs:99
static void CopyWithOverwrite(string source, string target)
Overwrites a target file based on the source file.
Definition LLMBuilder.cs:89
Class implementing the LLM model manager.
static void Build(Action< string, string > copyCallback)
Saves the model manager to disk along with models that are not (or can't) be downloaded for the build...
Class implementing helper functions for setup and process management.
static string LLMManagerPath
Path of file with build information for runtime.
static string libraryPath
LlamaLib path.