620 lines
25 KiB
C#
620 lines
25 KiB
C#
#if UNITY_EDITOR
|
|
|
|
using FishNet.Configuring;
|
|
using FishNet.Managing.Object;
|
|
using FishNet.Object;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityDebug = UnityEngine.Debug;
|
|
|
|
namespace FishNet.Editing.PrefabCollectionGenerator
|
|
{
|
|
internal sealed class Generator : AssetPostprocessor
|
|
{
|
|
public Generator()
|
|
{
|
|
if (!_subscribed)
|
|
{
|
|
_subscribed = true;
|
|
EditorApplication.update += OnEditorUpdate;
|
|
}
|
|
}
|
|
~Generator()
|
|
{
|
|
if (_subscribed)
|
|
{
|
|
_subscribed = false;
|
|
EditorApplication.update -= OnEditorUpdate;
|
|
}
|
|
}
|
|
|
|
#region Types.
|
|
private struct SpecifiedFolder
|
|
{
|
|
public string Path;
|
|
public bool Recursive;
|
|
|
|
public SpecifiedFolder(string path, bool recursive)
|
|
{
|
|
Path = path;
|
|
Recursive = recursive;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Public.
|
|
/// <summary>
|
|
/// True to ignore post process changes.
|
|
/// </summary>
|
|
public static bool IgnorePostProcess = false;
|
|
#endregion
|
|
|
|
#region Private.
|
|
/// <summary>
|
|
/// Last asset to import when there was only one imported asset and no other changes.
|
|
/// </summary>
|
|
private static string _lastSingleImportedAsset = string.Empty;
|
|
/// <summary>
|
|
/// Cached DefaultPrefabObjects reference.
|
|
/// </summary>
|
|
private static DefaultPrefabObjects _cachedDefaultPrefabs;
|
|
/// <summary>
|
|
/// True to refresh prefabs next update.
|
|
/// </summary>
|
|
private static bool _retryRefreshDefaultPrefabs;
|
|
/// <summary>
|
|
/// True if already subscribed to EditorApplication.Update.
|
|
/// </summary>
|
|
private static bool _subscribed;
|
|
/// <summary>
|
|
/// True if ran once since editor started.
|
|
/// </summary>
|
|
[System.NonSerialized]
|
|
private static bool _ranOnce;
|
|
/// <summary>
|
|
/// Last paths of updated nobs during a changed update.
|
|
/// </summary>
|
|
[System.NonSerialized]
|
|
private static List<string> _lastUpdatedNamePaths = new List<string>();
|
|
/// <summary>
|
|
/// Last frame changed was updated.
|
|
/// </summary>
|
|
[System.NonSerialized]
|
|
private static int _lastUpdatedFrame = -1;
|
|
/// <summary>
|
|
/// Length of assets strings during the last update.
|
|
/// </summary>
|
|
[System.NonSerialized]
|
|
private static int _lastUpdatedLengths = -1;
|
|
#endregion
|
|
|
|
public static string[] GetPrefabFiles(string startingPath, HashSet<string> excludedPaths, bool recursive)
|
|
{
|
|
//Opportunity to exit early if there are no excluded paths.
|
|
if (excludedPaths.Count == 0)
|
|
{
|
|
string[] strResults = Directory.GetFiles(startingPath, "*.prefab", SearchOption.AllDirectories);
|
|
return strResults;
|
|
}
|
|
//starting path is excluded.
|
|
if (excludedPaths.Contains(startingPath))
|
|
return new string[0];
|
|
|
|
//Folders remaining to be iterated.
|
|
List<string> enumeratedCollection = new List<string>() { startingPath };
|
|
//Only check other directories if recursive.
|
|
if (recursive)
|
|
{
|
|
//Find all folders which aren't excluded.
|
|
for (int i = 0; i < enumeratedCollection.Count; i++)
|
|
{
|
|
string[] allFolders = Directory.GetDirectories(enumeratedCollection[i], "*", SearchOption.TopDirectoryOnly);
|
|
for (int z = 0; z < allFolders.Length; z++)
|
|
{
|
|
string current = allFolders[z];
|
|
//Not excluded.
|
|
if (!excludedPaths.Contains(current))
|
|
enumeratedCollection.Add(current);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Valid prefab files.
|
|
List<string> results = new List<string>();
|
|
//Build files from folders.
|
|
int count = enumeratedCollection.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
string[] r = Directory.GetFiles(enumeratedCollection[i], "*.prefab", SearchOption.TopDirectoryOnly);
|
|
results.AddRange(r);
|
|
}
|
|
|
|
return results.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes paths which may overlap each other, such as sub directories.
|
|
/// </summary>
|
|
private static void RemoveOverlappingFolders(List<SpecifiedFolder> folders)
|
|
{
|
|
for (int z = 0; z < folders.Count; z++)
|
|
{
|
|
for (int i = 0; i < folders.Count; i++)
|
|
{
|
|
//Do not check against self.
|
|
if (i == z)
|
|
continue;
|
|
|
|
//Duplicate.
|
|
if (folders[z].Path.Equals(folders[i].Path, System.StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
UnityDebug.LogError($"The same path is specified multiple times in the DefaultPrefabGenerator settings. Remove the duplicate to clear this error.");
|
|
folders.RemoveAt(i);
|
|
break;
|
|
}
|
|
|
|
/* We are checking if i can be within
|
|
* z. This is only possible if i is longer
|
|
* than z. */
|
|
if (folders[i].Path.Length < folders[z].Path.Length)
|
|
continue;
|
|
/* Do not need to check if not recursive.
|
|
* Only recursive needs to be checked because
|
|
* a shorter recursive path could contain
|
|
* a longer path. */
|
|
if (!folders[z].Recursive)
|
|
continue;
|
|
|
|
//Compare paths.
|
|
string zPath = GetPathWithSeparator(folders[z].Path);
|
|
string iPath = zPath.Substring(0, zPath.Length);
|
|
//If paths match.
|
|
if (iPath.Equals(zPath, System.StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
UnityDebug.LogError($"Path {folders[i].Path} is included within recursive path {folders[z].Path}. Remove path {folders[i].Path} to clear this error.");
|
|
folders.RemoveAt(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
string GetPathWithSeparator(string txt)
|
|
{
|
|
return txt.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
|
|
+ Path.DirectorySeparatorChar;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a message to attach to logs if objects were dirtied.
|
|
/// </summary>
|
|
private static string GetDirtiedMessage(PrefabGeneratorConfigurations settings, bool dirtied)
|
|
{
|
|
if (!settings.SaveChanges && dirtied)
|
|
return " One or more NetworkObjects were dirtied. Please save your project.";
|
|
else
|
|
return string.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates prefabs by using only changed information.
|
|
/// </summary>
|
|
public static void GenerateChanged(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, PrefabGeneratorConfigurations settings = null)
|
|
{
|
|
if (settings == null)
|
|
settings = Configuration.Configurations.PrefabGenerator;
|
|
if (!settings.Enabled)
|
|
return;
|
|
|
|
bool log = settings.LogToConsole;
|
|
Stopwatch sw = (log) ? Stopwatch.StartNew() : null;
|
|
|
|
DefaultPrefabObjects prefabCollection = GetDefaultPrefabObjects(settings);
|
|
//No need to error if nto found, GetDefaultPrefabObjects will.
|
|
if (prefabCollection == null)
|
|
return;
|
|
|
|
int assetsLength = (importedAssets.Length + deletedAssets.Length + movedAssets.Length + movedFromAssetPaths.Length);
|
|
List<string> changedNobPaths = new List<string>();
|
|
|
|
System.Type goType = typeof(UnityEngine.GameObject);
|
|
IterateAssetCollection(importedAssets);
|
|
IterateAssetCollection(movedAssets);
|
|
|
|
//True if dirtied by changes.
|
|
bool dirtied;
|
|
//First remove null entries.
|
|
int startCount = prefabCollection.GetObjectCount();
|
|
prefabCollection.RemoveNull();
|
|
dirtied = (prefabCollection.GetObjectCount() != startCount);
|
|
//First index which new objects will be added to.
|
|
int firstAddIndex = (prefabCollection.GetObjectCount() - 1);
|
|
|
|
//Iterates strings adding prefabs to collection.
|
|
void IterateAssetCollection(string[] c)
|
|
{
|
|
foreach (string item in c)
|
|
{
|
|
System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(item);
|
|
if (assetType != goType)
|
|
continue;
|
|
|
|
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(item);
|
|
if (nob != null)
|
|
{
|
|
changedNobPaths.Add(item);
|
|
prefabCollection.AddObject(nob, true);
|
|
dirtied = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//To prevent out of range.
|
|
if (firstAddIndex < 0 || firstAddIndex >= prefabCollection.GetObjectCount())
|
|
firstAddIndex = 0;
|
|
dirtied |= prefabCollection.SetAssetPathHashes(firstAddIndex);
|
|
|
|
if (log && dirtied)
|
|
UnityDebug.Log($"Default prefab generator updated prefabs in {sw.ElapsedMilliseconds}ms.{GetDirtiedMessage(settings, dirtied)}");
|
|
|
|
//Check for redundancy.
|
|
int frameCount = Time.frameCount;
|
|
int changedCount = changedNobPaths.Count;
|
|
if (frameCount == _lastUpdatedFrame && assetsLength == _lastUpdatedLengths && (changedCount == _lastUpdatedNamePaths.Count) && changedCount > 0)
|
|
{
|
|
bool allMatch = true;
|
|
for (int i = 0; i < changedCount; i++)
|
|
{
|
|
if (changedNobPaths[i] != _lastUpdatedNamePaths[i])
|
|
{
|
|
allMatch = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If the import results are the same as the last attempt, on the same frame
|
|
* then there is likely an issue saving the assets. */
|
|
if (allMatch)
|
|
{
|
|
//Unset dirtied to prevent a save.
|
|
dirtied = false;
|
|
//Log this no matter what, it's critical.
|
|
UnityDebug.LogError($"Default prefab generator had a problem saving one or more assets. " +
|
|
$"This usually occurs when the assets cannot be saved due to missing scripts or serialization errors. " +
|
|
$"Please see above any prefabs which could not save any make corrections.");
|
|
}
|
|
|
|
}
|
|
//Set last values.
|
|
_lastUpdatedFrame = Time.frameCount;
|
|
_lastUpdatedNamePaths = changedNobPaths;
|
|
_lastUpdatedLengths = assetsLength;
|
|
|
|
EditorUtility.SetDirty(prefabCollection);
|
|
if (dirtied && settings.SaveChanges)
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates prefabs by iterating all files within settings parameters.
|
|
/// </summary>
|
|
public static void GenerateFull(PrefabGeneratorConfigurations settings = null, bool forced = false)
|
|
{
|
|
if (settings == null)
|
|
settings = Configuration.Configurations.PrefabGenerator;
|
|
if (!forced && !settings.Enabled)
|
|
return;
|
|
bool log = settings.LogToConsole;
|
|
|
|
Stopwatch sw = (log) ? Stopwatch.StartNew() : null;
|
|
List<NetworkObject> foundNobs = new List<NetworkObject>();
|
|
HashSet<string> excludedPaths = new HashSet<string>(settings.ExcludedFolders);
|
|
|
|
//If searching the entire project.
|
|
if (settings.SearchScope == (int)SearchScopeType.EntireProject)
|
|
{
|
|
foreach (string path in GetPrefabFiles("Assets", excludedPaths, true))
|
|
{
|
|
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(path);
|
|
if (nob != null)
|
|
foundNobs.Add(nob);
|
|
}
|
|
}
|
|
//Specific folders.
|
|
else if (settings.SearchScope == (int)SearchScopeType.SpecificFolders)
|
|
{
|
|
List<SpecifiedFolder> folders = GetSpecifiedFolders(settings.IncludedFolders.ToList());
|
|
RemoveOverlappingFolders(folders);
|
|
|
|
foreach (SpecifiedFolder sf in folders)
|
|
{
|
|
//If specified folder doesn't exist then continue.
|
|
if (!Directory.Exists(sf.Path))
|
|
continue;
|
|
|
|
foreach (string path in GetPrefabFiles(sf.Path, excludedPaths, sf.Recursive))
|
|
{
|
|
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(path);
|
|
if (nob != null)
|
|
foundNobs.Add(nob);
|
|
}
|
|
}
|
|
}
|
|
//Unhandled.
|
|
else
|
|
{
|
|
UnityDebug.LogError($"{settings.SearchScope} is not handled; default prefabs will not generator properly.");
|
|
}
|
|
|
|
DefaultPrefabObjects prefabCollection = GetDefaultPrefabObjects(settings);
|
|
//No need to error if not found, GetDefaultPrefabObjects will throw.
|
|
if (prefabCollection == null)
|
|
return;
|
|
|
|
//Clear and add built list.
|
|
prefabCollection.Clear();
|
|
prefabCollection.AddObjects(foundNobs, false);
|
|
bool dirtied = prefabCollection.SetAssetPathHashes(0);
|
|
|
|
int newCount = prefabCollection.GetObjectCount();
|
|
if (log)
|
|
{
|
|
string dirtiedMessage = (newCount > 0) ? GetDirtiedMessage(settings, dirtied) : string.Empty;
|
|
UnityDebug.Log($"Default prefab generator found {newCount} prefabs in {sw.ElapsedMilliseconds}ms.{dirtiedMessage}");
|
|
}
|
|
//Only set dirty if and save if prefabs were found.
|
|
if (newCount > 0)
|
|
{
|
|
EditorUtility.SetDirty(prefabCollection);
|
|
if (settings.SaveChanges)
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Iterates folders building them into SpecifiedFolders.
|
|
/// </summary>
|
|
private static List<SpecifiedFolder> GetSpecifiedFolders(List<string> folders)
|
|
{
|
|
List<SpecifiedFolder> results = new List<SpecifiedFolder>();
|
|
//Remove astericks.
|
|
foreach (string path in folders)
|
|
{
|
|
int pLength = path.Length;
|
|
if (pLength == 0)
|
|
continue;
|
|
|
|
bool recursive;
|
|
string p;
|
|
//If the last character indicates resursive.
|
|
if (path.Substring(pLength - 1, 1) == "*")
|
|
{
|
|
p = path.Substring(0, pLength - 1);
|
|
recursive = true;
|
|
}
|
|
else
|
|
{
|
|
p = path;
|
|
recursive = false;
|
|
}
|
|
|
|
results.Add(new SpecifiedFolder(p, recursive));
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the DefaultPrefabObjects file.
|
|
/// </summary>
|
|
private static DefaultPrefabObjects GetDefaultPrefabObjects(PrefabGeneratorConfigurations settings = null)
|
|
{
|
|
if (settings == null)
|
|
settings = Configuration.Configurations.PrefabGenerator;
|
|
|
|
//Load the prefab collection
|
|
string defaultPrefabsPath = settings.DefaultPrefabObjectsPath;
|
|
defaultPrefabsPath = defaultPrefabsPath.Replace(@"\", "/");
|
|
string fullDefaultPrefabsPath = (defaultPrefabsPath.Length > 0) ? Path.GetFullPath(defaultPrefabsPath) : string.Empty;
|
|
|
|
//If cached prefabs is not the same path as assetPath.
|
|
if (_cachedDefaultPrefabs != null)
|
|
{
|
|
string unityAssetPath = AssetDatabase.GetAssetPath(_cachedDefaultPrefabs);
|
|
string fullCachedPath = (unityAssetPath.Length > 0) ? Path.GetFullPath(unityAssetPath) : string.Empty;
|
|
if (fullCachedPath != fullDefaultPrefabsPath)
|
|
_cachedDefaultPrefabs = null;
|
|
}
|
|
|
|
//If cached is null try to get it.
|
|
if (_cachedDefaultPrefabs == null)
|
|
{
|
|
//Only try to load it if file exist.
|
|
if (File.Exists(fullDefaultPrefabsPath))
|
|
{
|
|
_cachedDefaultPrefabs = AssetDatabase.LoadAssetAtPath<DefaultPrefabObjects>(defaultPrefabsPath);
|
|
if (_cachedDefaultPrefabs == null)
|
|
{
|
|
//If already retried then throw an error.
|
|
if (_retryRefreshDefaultPrefabs)
|
|
{
|
|
UnityDebug.LogError("DefaultPrefabObjects file exists but it could not be loaded by Unity. Use the Fish-Networking menu to Refresh Default Prefabs.");
|
|
}
|
|
else
|
|
{
|
|
UnityDebug.Log("DefaultPrefabObjects file exists but it could not be loaded by Unity. Trying to reload the file next frame.");
|
|
_retryRefreshDefaultPrefabs = true;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_cachedDefaultPrefabs == null)
|
|
{
|
|
string fullPath = Path.GetFullPath(defaultPrefabsPath);
|
|
UnityDebug.Log($"Creating a new DefaultPrefabsObject at {fullPath}.");
|
|
string directory = Path.GetDirectoryName(fullPath);
|
|
|
|
if (!Directory.Exists(directory))
|
|
{
|
|
Directory.CreateDirectory(directory);
|
|
AssetDatabase.Refresh();
|
|
}
|
|
|
|
_cachedDefaultPrefabs = ScriptableObject.CreateInstance<DefaultPrefabObjects>();
|
|
AssetDatabase.CreateAsset(_cachedDefaultPrefabs, defaultPrefabsPath);
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
|
|
if (_cachedDefaultPrefabs != null && _retryRefreshDefaultPrefabs)
|
|
UnityDebug.Log("DefaultPrefabObjects found on the second iteration.");
|
|
return _cachedDefaultPrefabs;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called every frame the editor updates.
|
|
/// </summary>
|
|
private static void OnEditorUpdate()
|
|
{
|
|
if (!_retryRefreshDefaultPrefabs)
|
|
return;
|
|
|
|
GenerateFull();
|
|
_retryRefreshDefaultPrefabs = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called by Unity when assets are modified.
|
|
/// </summary>
|
|
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
|
|
{
|
|
if (Application.isPlaying)
|
|
return;
|
|
//If retrying next frame don't bother updating, next frame will do a full refresh.
|
|
if (_retryRefreshDefaultPrefabs)
|
|
return;
|
|
//Post process is being ignored. Could be temporary or user has disabled this feature.
|
|
if (IgnorePostProcess)
|
|
return;
|
|
/* Don't iterate if updating or compiling as that could cause an infinite loop
|
|
* due to the prefabs being generated during an update, which causes the update
|
|
* to start over, which causes the generator to run again, which... you get the idea. */
|
|
if (EditorApplication.isCompiling)
|
|
return;
|
|
|
|
DefaultPrefabObjects prefabCollection = GetDefaultPrefabObjects();
|
|
if (prefabCollection == null)
|
|
return;
|
|
PrefabGeneratorConfigurations settings = Configuration.Configurations.PrefabGenerator;
|
|
|
|
if (prefabCollection.GetObjectCount() == 0)
|
|
{
|
|
//If there are no prefabs then do a full rebuild. Odds of there being none are pretty much nill.
|
|
GenerateFull(settings);
|
|
}
|
|
else
|
|
{
|
|
int totalChanges = importedAssets.Length + deletedAssets.Length + movedAssets.Length + movedFromAssetPaths.Length;
|
|
//Nothing has changed. This shouldn't occur but unity is funny so we're going to check anyway.
|
|
if (totalChanges == 0)
|
|
return;
|
|
|
|
//normalizes path.
|
|
string dpoPath = Path.GetFullPath(settings.DefaultPrefabObjectsPath);
|
|
//If total changes is 1 and the only changed file is the default prefab collection then do nothing.
|
|
if (totalChanges == 1)
|
|
{
|
|
//Do not need to check movedFromAssetPaths because that's not possible for this check.
|
|
if ((importedAssets.Length == 1 && Path.GetFullPath(importedAssets[0]) == dpoPath)
|
|
|| (deletedAssets.Length == 1 && Path.GetFullPath(deletedAssets[0]) == dpoPath)
|
|
|| (movedAssets.Length == 1 && Path.GetFullPath(movedAssets[0]) == dpoPath))
|
|
return;
|
|
|
|
/* If the only change is an import then check if the imported file
|
|
* is the same as the last, and if so check into returning early.
|
|
* For some reason occasionally when files are saved unity runs postprocess
|
|
* multiple times on the same file. */
|
|
string imported = (importedAssets.Length == 1) ? importedAssets[0] : null;
|
|
if (imported != null && imported == _lastSingleImportedAsset)
|
|
{
|
|
//If here then the file is the same. Make sure it's already in the collection before returning.
|
|
System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(imported);
|
|
//Not a gameObject, no reason to continue.
|
|
if (assetType != typeof(GameObject))
|
|
return;
|
|
|
|
NetworkObject nob = AssetDatabase.LoadAssetAtPath<NetworkObject>(imported);
|
|
//If is a networked object.
|
|
if (nob != null)
|
|
{
|
|
//Already added!
|
|
if (prefabCollection.Prefabs.Contains(nob))
|
|
return;
|
|
}
|
|
}
|
|
else if (imported != null)
|
|
{
|
|
_lastSingleImportedAsset = imported;
|
|
}
|
|
}
|
|
|
|
|
|
bool fullRebuild = settings.FullRebuild;
|
|
/* If updating FN. This needs to be done a better way.
|
|
* Parsing the actual version file would be better.
|
|
* I'll get to it next release. */
|
|
if (!_ranOnce)
|
|
{
|
|
_ranOnce = true;
|
|
fullRebuild = true;
|
|
}
|
|
else
|
|
{
|
|
CheckForVersionFile(importedAssets);
|
|
CheckForVersionFile(deletedAssets);
|
|
CheckForVersionFile(movedAssets);
|
|
CheckForVersionFile(movedFromAssetPaths);
|
|
}
|
|
|
|
/* See if any of the changed files are the version file.
|
|
* A new version file suggests an update. Granted, this could occur if
|
|
* other assets imported a new version file as well but better
|
|
* safe than sorry. */
|
|
void CheckForVersionFile(string[] arr)
|
|
{
|
|
string targetText = "VERSION.txt".ToLower();
|
|
int targetLength = targetText.Length;
|
|
|
|
for (int i = 0; i < arr.Length; i++)
|
|
{
|
|
string item = arr[i];
|
|
int itemLength = item.Length;
|
|
if (itemLength < targetLength)
|
|
continue;
|
|
|
|
item = item.ToLower();
|
|
int startIndex = (itemLength - targetLength);
|
|
if (item.Substring(startIndex, targetLength) == targetText)
|
|
{
|
|
fullRebuild = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fullRebuild)
|
|
GenerateFull(settings);
|
|
else
|
|
GenerateChanged(importedAssets, deletedAssets, movedAssets, movedFromAssetPaths, settings);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|