using System.Linq; using System.Diagnostics; using AnimatorAsCode.V1; using UnityEditor; using UnityEngine; using VRC.SDK3.Avatars.Components; using VRC.SDK3.Dynamics.Constraint.Components; using System.Collections.Generic; using UnityEditor.Animations; using System; namespace gay.lilyy.SoldAvatarBootstrap { public class AvatarAssets { public AacFlBase aac; public GameObject root; public AnimatorController fx; public AacFlClip emptyClip; public bool experimentalEnabled; // public bool isPC; } public class AvatarBootstrapCore { private static AnimatorController GetController(AvatarDefinition definition) { string metaPath = definition.FXLayerPath + ".meta"; string originalGuid = null; if (System.IO.File.Exists(metaPath)) { foreach (var line in System.IO.File.ReadAllLines(metaPath)) { if (line.StartsWith("guid: ")) { originalGuid = line.Substring(6).Trim(); break; } } } if (AssetDatabase.LoadAssetAtPath(definition.FXLayerPath) != null) { AssetDatabase.DeleteAsset(definition.FXLayerPath); AssetDatabase.Refresh(); } var newController = AnimatorController.CreateAnimatorControllerAtPath(definition.FXLayerPath); if (originalGuid != null && System.IO.File.Exists(metaPath)) { var lines = System.IO.File.ReadAllLines(metaPath); for (int i = 0; i < lines.Length; ++i) { if (lines[i].StartsWith("guid: ")) { lines[i] = "guid: " + originalGuid; } } System.IO.File.WriteAllLines(metaPath, lines); AssetDatabase.Refresh(); } return AssetDatabase.LoadAssetAtPath(definition.FXLayerPath); } public static void RunForCurrentAvatar(AvatarDefinition definition) { var allLayerGroups = LayerGroup.Instances.ToList(); var layerGroups = allLayerGroups .Where(lg => lg.TargetDefinitions.Contains(definition)) .ToList(); // Start overall timer var overallStopwatch = Stopwatch.StartNew(); AvatarLogger.LogInfo("Starting SoldAvatarBootstrap generation for definition: " + definition.DisplayName); GameObject root = null; foreach (var candidate in GameObject.FindObjectsOfType(true)) { if (definition.IsApplicable(candidate.gameObject)) { AvatarLogger.LogInfo($"Found suitable avatar: {candidate.gameObject.name}"); root = candidate.gameObject; break; } } if (root == null) { AvatarLogger.LogError($"No suitable avatar found for definition: {definition.DisplayName}"); return; } // Skip if the root GameObject or the component itself is disabled if (!root.activeSelf || !root.gameObject.activeInHierarchy) { AvatarLogger.LogWarning($"Avatar root '{root.name}' is disabled or inactive. Skipping."); return; } AvatarAssets assets = new() { root = root, // isPC = EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneWindows // || EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneWindows64, fx = GetController(definition) }; // Time AAC initialization var initStopwatch = Stopwatch.StartNew(); AvatarLogger.LogInfo("Initializing Animator As Code..."); assets.aac = AacV1.Create(new AacConfiguration { SystemName = definition.SystemName, AnimatorRoot = root.transform, DefaultValueRoot = root.transform, AssetKey = GUID.Generate().ToString(), AssetContainer = assets.fx, ContainerMode = AacConfiguration.Container.Everything, DefaultsProvider = new AacDefaultsProvider(true) }); assets.emptyClip = assets.aac.NewClip(); initStopwatch.Stop(); AvatarLogger.LogInfo($"AAC initialization completed in {initStopwatch.ElapsedMilliseconds}ms"); // Process layer groups with individual timing // Filter out the template LayerGroup (by SystemName) // AACLogger.LogInfo($"Processing {layerGroups.Count} layer groups..."); AvatarLogger.LogInfo($"Processing {layerGroups.Count} layer groups..."); var totalLayerGroupTime = 0L; var processedCount = 0; var skippedCount = 0; foreach (var layerGroup in layerGroups) { var layerStopwatch = Stopwatch.StartNew(); bool skipped = false; if (!layerGroup.enabled) { AvatarLogger.LogWarning($"Skipping layer group: {layerGroup.DisplayName} (disabled)"); skipped = true; } if (!skipped) { AvatarLogger.LogInfo($"Running layer group: {layerGroup.DisplayName}"); layerGroup.Run(assets); processedCount++; } else { skippedCount++; } layerStopwatch.Stop(); totalLayerGroupTime += layerStopwatch.ElapsedMilliseconds; AvatarLogger.LogInfo($"Layer group '{layerGroup.DisplayName}' completed in {layerStopwatch.ElapsedMilliseconds}ms"); } // Final timing summary overallStopwatch.Stop(); AvatarLogger.LogSuccess($"SoldAvatarBootstrap generation completed successfully!"); AvatarLogger.LogInfo($"=== TIMING SUMMARY ==="); AvatarLogger.LogInfo($"Total time: {overallStopwatch.ElapsedMilliseconds}ms ({overallStopwatch.Elapsed.TotalSeconds:F2}s)"); AvatarLogger.LogInfo($"AAC initialization: {initStopwatch.ElapsedMilliseconds}ms"); AvatarLogger.LogInfo($"Layer groups: {totalLayerGroupTime}ms (processed: {processedCount}, skipped: {skippedCount})"); AvatarLogger.LogInfo($"Average per layer group: {(processedCount > 0 ? totalLayerGroupTime / processedCount : 0)}ms"); // replace the animator controller var animatorControllers = root.GetComponent().baseAnimationLayers .ToList(); animatorControllers.RemoveAll(layer => layer.type == VRCAvatarDescriptor.AnimLayerType.FX); animatorControllers.Add(new VRCAvatarDescriptor.CustomAnimLayer { type = VRCAvatarDescriptor.AnimLayerType.FX, animatorController = assets.fx }); root.GetComponent().baseAnimationLayers = animatorControllers.ToArray(); } } }