zxl
/
CTT
forked from Cal/CTT
1
0
Fork 0
CTT/Unity/Assets/Editor/XAsset/BuildRules.cs

370 lines
12 KiB
C#

//
// BuildRules.cs
//
// Author:
// fjy <jiyuan.feng@live.com>
//
// Copyright (c) 2020 fjy
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using Sirenix.OdinInspector;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.Serialization;
namespace libx
{
public enum NameBy
{
Explicit,
Path,
Directory,
TopDirectory
}
[Serializable]
public class RuleAsset
{
public string bundle;
public string path;
}
[Serializable]
public class RuleBundle
{
public string name;
public string[] assets;
}
[Serializable]
public class BuildRule
{
[Tooltip("搜索路径")] public string searchPath;
[Tooltip("搜索通配符,多个之间请用,(逗号)隔开")] public string searchPattern;
[Tooltip("命名规则")] public NameBy nameBy = NameBy.Path;
[Tooltip("Explicit的名称")] public string assetBundleName;
public string[] GetAssets()
{
string[] patterns = searchPattern.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
if (!Directory.Exists(searchPath))
{
Debug.LogWarning("Rule searchPath not exist:" + searchPath);
return new string[0];
}
var getFiles = new List<string>();
foreach (string item in patterns)
{
string[] files = Directory.GetFiles(searchPath, item, SearchOption.AllDirectories);
foreach (string file in files)
{
if (Directory.Exists(file)) continue;
string ext = Path.GetExtension(file).ToLower();
if ((ext == ".fbx" || ext == ".anim") && !item.Contains(ext)) continue;
if (!BuildRules.ValidateAsset(file)) continue;
string asset = file.Replace("\\", "/");
getFiles.Add(asset);
}
}
return getFiles.ToArray();
}
}
public class BuildRules : SerializedScriptableObject
{
private readonly Dictionary<string, string> _asset2Bundles = new Dictionary<string, string>();
private readonly Dictionary<string, string[]> _conflicted = new Dictionary<string, string[]>();
private readonly List<string> _duplicated = new List<string>();
private readonly Dictionary<string, HashSet<string>> _tracker = new Dictionary<string, HashSet<string>>();
[Header("Patterns")]
public string searchPatternAsset = "*.asset";
public string searchPatternController = "*.controller";
public string searchPatternDir = "*";
public string searchPatternMaterial = "*.mat";
public string searchPatternPng = "*.png";
public string searchPatternPrefab = "*.prefab";
public string searchPatternScene = "*.unity";
public string searchPatternText = "*.txt,*.bytes,*.json,*.csv,*.xml,*htm,*.html,*.yaml,*.fnt";
public static bool nameByHash = true;
[Tooltip("构建的版本号")]
[Header("Builds")]
public int version;
[Tooltip("BuildPlayer 的时候被打包的场景")] public SceneAsset[] scenesInBuild = new SceneAsset[0];
public BuildRule[] rules = new BuildRule[0];
[Header("Assets")]
[ReadOnly]public RuleAsset[] ruleAssets = new RuleAsset[0];
[ReadOnly]public RuleBundle[] ruleBundles = new RuleBundle[0];
#region API
public int AddVersion()
{
version += 1;
EditorUtility.SetDirty(this);
AssetDatabase.SaveAssets();
return version;
}
public void Apply()
{
Clear();
CollectAssets();
AnalysisAssets();
OptimizeAssets();
Save();
}
public AssetBundleBuild[] GetBuilds()
{
var builds = new List<AssetBundleBuild>();
foreach (RuleBundle bundle in ruleBundles)
{
builds.Add(new AssetBundleBuild
{
assetNames = bundle.assets,
assetBundleName = bundle.name
});
}
return builds.ToArray();
}
#endregion
#region Private
internal static bool ValidateAsset(string asset)
{
if (!asset.StartsWith("Assets/")) return false;
string ext = Path.GetExtension(asset).ToLower();
return ext != ".dll" && ext != ".cs" && ext != ".meta" && ext != ".js" && ext != ".boo";
}
private static bool IsScene(string asset)
{
return asset.EndsWith(".unity");
}
private static string RuledAssetBundleName(string name)
{
if (nameByHash)
{
return Utilitys.GetMD5Hash(name) + Assets.Extension;
}
return name.Replace("\\", "/").ToLower() + Assets.Extension;
}
private void Track(string asset, string bundle)
{
if (!_tracker.TryGetValue(asset, out HashSet<string> assets))
{
assets = new HashSet<string>();
_tracker.Add(asset, assets);
}
assets.Add(bundle);
if (assets.Count > 1)
{
_asset2Bundles.TryGetValue(asset, out string bundleName);
if (string.IsNullOrEmpty(bundleName))
{
_duplicated.Add(asset);
}
}
}
private Dictionary<string, List<string>> GetBundles()
{
var bundles = new Dictionary<string, List<string>>();
foreach (var item in _asset2Bundles)
{
string bundle = item.Value;
if (!bundles.TryGetValue(bundle, out List<string> list))
{
list = new List<string>();
bundles[bundle] = list;
}
if (!list.Contains(item.Key)) list.Add(item.Key);
}
return bundles;
}
private void Clear()
{
_tracker.Clear();
_duplicated.Clear();
_conflicted.Clear();
_asset2Bundles.Clear();
}
private void Save()
{
var getBundles = GetBundles();
ruleBundles = new RuleBundle[getBundles.Count];
int i = 0;
foreach (var item in getBundles)
{
ruleBundles[i] = new RuleBundle
{
name = item.Key,
assets = item.Value.ToArray()
};
i++;
}
EditorUtility.ClearProgressBar();
EditorUtility.SetDirty(this);
AssetDatabase.SaveAssets();
}
private void OptimizeAssets()
{
int i = 0, max = _conflicted.Count;
foreach (var item in _conflicted)
{
if (EditorUtility.DisplayCancelableProgressBar(string.Format("优化冲突{0}/{1}", i, max), item.Key,
i / (float) max)) break;
string[] list = item.Value;
foreach (string asset in list)
if (!IsScene(asset))
_duplicated.Add(asset);
i++;
}
for (i = 0, max = _duplicated.Count; i < max; i++)
{
string item = _duplicated[i];
if (EditorUtility.DisplayCancelableProgressBar(string.Format("优化冗余{0}/{1}", i, max), item,
i / (float) max)) break;
OptimizeAsset(item);
}
}
private void AnalysisAssets()
{
var getBundles = GetBundles();
int i = 0, max = getBundles.Count;
foreach (var item in getBundles)
{
string bundle = item.Key;
if (EditorUtility.DisplayCancelableProgressBar(string.Format("分析依赖{0}/{1}", i, max), bundle,
i / (float) max)) break;
var assetPaths = getBundles[bundle];
if (assetPaths.Exists(IsScene) && !assetPaths.TrueForAll(IsScene))
_conflicted.Add(bundle, assetPaths.ToArray());
string[] dependencies = AssetDatabase.GetDependencies(assetPaths.ToArray(), true);
if (dependencies.Length > 0)
foreach (string asset in dependencies)
if (ValidateAsset(asset))
Track(asset, bundle);
i++;
}
}
private void CollectAssets()
{
for (int i = 0, max = rules.Length; i < max; i++)
{
BuildRule rule = rules[i];
if (EditorUtility.DisplayCancelableProgressBar(string.Format("收集资源{0}/{1}", i, max), rule.searchPath,
i / (float) max))
break;
ApplyRule(rule);
}
var list = new List<RuleAsset>();
foreach (var item in _asset2Bundles)
list.Add(new RuleAsset
{
path = item.Key,
bundle = item.Value
});
list.Sort((a, b) => string.Compare(a.path, b.path, StringComparison.Ordinal));
ruleAssets = list.ToArray();
}
private void OptimizeAsset(string asset)
{
if (asset.EndsWith(".shader"))
_asset2Bundles[asset] = RuledAssetBundleName("shaders");
else
_asset2Bundles[asset] = RuledAssetBundleName(asset);
}
private void ApplyRule(BuildRule rule)
{
string[] assets = rule.GetAssets();
switch (rule.nameBy)
{
case NameBy.Explicit:
{
foreach (string asset in assets) _asset2Bundles[asset] = RuledAssetBundleName(rule.assetBundleName);
break;
}
case NameBy.Path:
{
foreach (string asset in assets) _asset2Bundles[asset] = RuledAssetBundleName(asset);
break;
}
case NameBy.Directory:
{
foreach (string asset in assets)
_asset2Bundles[asset] = RuledAssetBundleName(Path.GetDirectoryName(asset));
break;
}
case NameBy.TopDirectory:
{
int startIndex = rule.searchPath.Length;
foreach (string asset in assets)
{
string dir = Path.GetDirectoryName(asset);
if (!string.IsNullOrEmpty(dir))
//if (!dir.Equals(rule.searchPath))
{
int pos = dir.IndexOf("\\", startIndex-1, StringComparison.Ordinal);
if (pos != -1) dir = dir.Substring(0, pos);
}
_asset2Bundles[asset] = RuledAssetBundleName(dir);
}
break;
}
default:
throw new ArgumentOutOfRangeException();
}
}
#endregion
}
}