LLM for Unity  v3.0.1
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-universal");
212 break;
213 case BuildTarget.Android:
214 platforms.Add("android-arm64");
215 platforms.Add("android-x64");
216 break;
217 case BuildTarget.iOS:
218 platforms.Add("ios-arm64");
219 break;
220#if UNITY_2022_3_OR_NEWER
221 case BuildTarget.VisionOS:
222 platforms.Add("visionos-arm64");
223 break;
224#endif
225 }
226
227 foreach (string source in Directory.GetDirectories(LLMUnitySetup.libraryPath))
228 {
229 string sourceName = Path.GetFileName(source);
230 if (!platforms.Contains(sourceName))
231 {
232 string target = Path.Combine(BuildTempDir, sourceName);
233 MoveAction(source, target);
234 MoveAction(source + ".meta", target + ".meta");
235 }
236 }
237
238 if (checkCUBLAS)
239 {
240 List<string> exclusionKeywords = LLMUnitySetup.CUBLAS ? new List<string>() { "tinyblas" } : new List<string>() { "cublas", "cudart" };
241 foreach (string platform in platforms)
242 {
243 string platformDir = Path.Combine(LLMUnitySetup.libraryPath, platform, "native");
244 foreach (string source in Directory.GetFiles(platformDir))
245 {
246 string sourceName = Path.GetFileName(source);
247 foreach (string exclusionKeyword in exclusionKeywords)
248 {
249 if (sourceName.Contains(exclusionKeyword))
250 {
251 string target = Path.Combine(BuildTempDir, platform, "native", sourceName);
252 MoveAction(source, target);
253 MoveAction(source + ".meta", target + ".meta");
254 break;
255 }
256 }
257 }
258 }
259 }
260
261 bool isVisionOS = false;
262#if UNITY_2022_3_OR_NEWER
263 isVisionOS = buildTarget == BuildTarget.VisionOS;
264#endif
265 if (buildTarget == BuildTarget.Android || buildTarget == BuildTarget.iOS || isVisionOS)
266 {
267 foreach (string platform in platforms)
268 {
269 string source = Path.Combine(LLMUnitySetup.libraryPath, platform, "native", $"libllamalib_{platform}.{MobileSuffix(buildTarget)}");
270 string target = MobilePluginPath(buildTarget, platform.Split("-")[1].ToUpper());
271 string pluginDir = PluginDir(buildTarget.ToString());
272 MoveAction(source, target);
273 MoveAction(source + ".meta", target + ".meta");
274 AddActionAddMeta(pluginDir);
275 }
276 }
277 }
278
279 static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload)
280 {
281 List<BuildTarget> buildTargets = new List<BuildTarget>() { BuildTarget.iOS, BuildTarget.Android };
282#if UNITY_2022_3_OR_NEWER
283 buildTargets.Add(BuildTarget.VisionOS);
284#endif
285 foreach (BuildTarget buildTarget in buildTargets)
286 {
287 string platformDir = Path.Combine("Assets", PluginDir(buildTarget.ToString(), true));
288 if (!Directory.Exists(platformDir)) continue;
289 foreach (string archDir in Directory.GetDirectories(platformDir))
290 {
291 string arch = Path.GetFileName(archDir);
292 string pathToPlugin = Path.Combine("Assets", MobilePluginPath(buildTarget, arch, true));
293 for (int i = 0; i < movedAssets.Length; i++)
294 {
295 if (movedAssets[i] == pathToPlugin)
296 {
297 var importer = AssetImporter.GetAtPath(pathToPlugin) as PluginImporter;
298 if (importer != null && importer.isNativePlugin)
299 {
300 importer.SetCompatibleWithPlatform(buildTarget, true);
301 importer.SetPlatformData(buildTarget, "CPU", arch);
302 AssetDatabase.ImportAsset(pathToPlugin);
303 }
304 }
305 }
306 }
307 }
308 }
309
313 public static void BuildModels()
314 {
315 LLMManager.Build(CopyActionAddMeta);
316 if (File.Exists(LLMUnitySetup.LLMManagerPath)) AddActionAddMeta(LLMUnitySetup.LLMManagerPath);
317 }
318
322 public static void Build(BuildTarget buildTarget)
323 {
324 DeletePath(BuildTempDir);
325 Directory.CreateDirectory(BuildTempDir);
326 BuildLibraryPlatforms(buildTarget);
327 BuildModels();
328 }
329
333 public static void Reset()
334 {
335 if (!File.Exists(movedCache)) return;
336 List<StringPair> movedPairs = JsonUtility.FromJson<ListStringPair>(File.ReadAllText(movedCache)).pairs;
337 if (movedPairs == null) return;
338
339 bool refresh = false;
340 foreach (var pair in movedPairs)
341 {
342 if (pair.source == "")
343 {
344 refresh |= DeletePath(pair.target);
345 }
346 else
347 {
348 if (File.Exists(pair.source) || Directory.Exists(pair.source))
349 {
350 refresh |= DeletePath(pair.target);
351 }
352 else
353 {
354 refresh |= MoveAction(pair.target, pair.source, false);
355 }
356 }
357 }
358 if (refresh) AssetDatabase.Refresh();
359 DeletePath(movedCache);
360 }
361 }
362}
363#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.