示例#1
0
        // ----------------------------------------------------------

        public static void PopulateAssetEndUsers(string rootAsset, AssetDependencies assetDependencies)
        {
            var dependencies = assetDependencies.GetAssetDependencies();

            if (!dependencies.ContainsKey(rootAsset))
            {
                return;
            }

            var selectedAssetDependencies = dependencies[rootAsset];

            if (selectedAssetDependencies.Users.Count <= 0)
            {
                // asset isn't used by any other asset
                return;
            }

            var destination = selectedAssetDependencies.GetEndUserLabels();

            if (destination.Count > 0)
            {
                // already assigned
                return;
            }

            PopulateAssetEndUsers(rootAsset, destination, assetDependencies);
        }
示例#2
0
        public static void CreateForAllAssets(AssetDependencies data, BuildReportTool.BuildInfo buildInfo, bool debugLog = false)
        {
            if (buildInfo == null)
            {
                return;
            }

            var startingOpenSet = new Queue <string>();

            if (buildInfo.UsedAssets != null && buildInfo.UsedAssets.All != null)
            {
                var allUsedAssets = buildInfo.UsedAssets.All;
                for (int n = 0, len = allUsedAssets.Length; n < len; ++n)
                {
                    startingOpenSet.Enqueue(allUsedAssets[n].Name);
                }
            }

            if (buildInfo.UnusedAssets != null && buildInfo.UnusedAssets.All != null)
            {
                var allUnusedAssets = buildInfo.UnusedAssets.All;
                for (int n = 0, len = allUnusedAssets.Length; n < len; ++n)
                {
                    startingOpenSet.Enqueue(allUnusedAssets[n].Name);
                }
            }

            Create(data, startingOpenSet, debugLog);
        }
示例#3
0
        public static void CheckSelectionDependenciesRecursive()
        {
            var selectedAssetPath = AssetDatabase.GetAssetPath(Selection.activeObject);

            var startingOpenSet = new Queue <string>();

            startingOpenSet.Enqueue(selectedAssetPath);

            var assetDependencies = new AssetDependencies();

            Create(assetDependencies, startingOpenSet, true, true);
        }
        public static void Create(AssetDependencies data, BuildReportTool.BuildInfo.SceneInBuild[] scenes, bool debugLog = false)
        {
            var startingOpenSet = new Queue <string>();

            // we'll start with the scenes to be included in the build:
            for (int n = 0, len = scenes.Length; n < len; ++n)
            {
                if (scenes[n].Enabled && !string.IsNullOrEmpty(scenes[n].Path))
                {
                    startingOpenSet.Enqueue(scenes[n].Path);
                }
            }

            Create(data, startingOpenSet, debugLog);
        }
        // ==================================================================================

        public static void CreateFromIncludedScenes(AssetDependencies data, bool debugLog = false, bool recursiveGetDependencies = false)
        {
            var startingOpenSet = new Queue <string>();

            // we'll start with the scenes to be included in the build:
            var scenes = EditorBuildSettings.scenes;

            for (int n = 0, len = scenes.Length; n < len; ++n)
            {
                if (scenes[n].enabled && !string.IsNullOrEmpty(scenes[n].path))
                {
                    startingOpenSet.Enqueue(scenes[n].path);
                }
            }

            Create(data, startingOpenSet, debugLog, recursiveGetDependencies);
        }
示例#6
0
        public static void CreateForUsedAssetsOnly(AssetDependencies data, BuildReportTool.BuildInfo buildInfo,
                                                   bool debugLog = false)
        {
            if (buildInfo == null)
            {
                return;
            }

            var startingOpenSet = new Queue <string>();

            // we'll start with the scenes to be included in the build:
            var scenes = buildInfo.ScenesInBuild;

            if (scenes != null)
            {
                for (int n = 0, len = scenes.Length; n < len; ++n)
                {
                    if (scenes[n].Enabled && !string.IsNullOrEmpty(scenes[n].Path))
                    {
                        startingOpenSet.Enqueue(scenes[n].Path);
                    }
                }
            }

            // plus all Resources assets (since they are not referred
            // to in any scenes, we have to add them explicitly)
            if (buildInfo.UsedAssets != null && buildInfo.UsedAssets.All != null)
            {
                var allUsedAssets = buildInfo.UsedAssets.All;
                for (int n = 0, len = allUsedAssets.Length; n < len; ++n)
                {
                    if (!string.IsNullOrEmpty(allUsedAssets[n].Name) &&
                        allUsedAssets[n].Name.IndexOf("/Resources/", StringComparison.OrdinalIgnoreCase) > -1)
                    {
                        startingOpenSet.Enqueue(allUsedAssets[n].Name);
                    }
                }
            }

            Create(data, startingOpenSet, debugLog);
        }
示例#7
0
    void _OpenBuildInfo(string filepath)
    {
        if (string.IsNullOrEmpty(filepath))
        {
            return;
        }

        _finishedOpeningFromThread = false;
        GetValueMessage            = "Opening...";
        BuildReportTool.BuildInfo loadedBuild = BuildReportTool.Util.OpenSerializedBuildInfo(filepath, false);

        if (BuildReportTool.Util.BuildInfoHasContents(loadedBuild))
        {
            _buildInfo = loadedBuild;
            _lastOpenedBuildInfoFilePath = filepath;
        }
        else
        {
            Debug.LogError("Build Report Tool: Invalid data in build info file: " + filepath);
        }


        var assetDependenciesFilePath = BuildReportTool.Util.GetAssetDependenciesFilenameFromBuildInfo(filepath);

        if (System.IO.File.Exists(assetDependenciesFilePath))
        {
            var loadedAssetDependencies = BuildReportTool.Util.OpenSerializedAssetDependencies(assetDependenciesFilePath);
            if (loadedAssetDependencies != null)
            {
                _assetDependencies = loadedAssetDependencies;
            }
        }

        _finishedOpeningFromThread = true;

        GetValueMessage = "";
    }
示例#8
0
        public static void PopulateAssetEndUsers(string rootAsset, List <GUIContent> destination, AssetDependencies assetDependencies)
        {
            var dependencies = assetDependencies.GetAssetDependencies();

            if (!dependencies.ContainsKey(rootAsset))
            {
                return;
            }

            var selectedAssetDependencies = dependencies[rootAsset];

            if (destination.Count > 0)
            {
                // already assigned
                return;
            }

            var usersFlattened = selectedAssetDependencies.UsersFlattened;

            if (usersFlattened.Count <= 0)
            {
                // asset has no flattened users list
                destination.Clear();
                return;
            }

            // count: 2
            // availableExistingIdx: 0
            //
            // 1st primary user found:
            //  count <= availableExistingIdx
            //  2 <= 0 ? false: 1st primary user put to idx 0
            //  availableExistingIdx: 1
            //
            // 2nd primary user found:
            //  2 <= 1 ? false: 2nd primary user put to idx 1
            //  availableExistingIdx: 2
            //
            // 3rd primary user found:
            //  2 <= 2 ? true: 3rd primary user added as new entry to list (at idx 2)
            //  count: 3
            //  availableExistingIdx: 3
            //
            // while (count > availableExistingIdx)
            //  3 > 3 ? false: stop while loop

            // count : 0
            // availableExistingIdx: 0
            //
            // 1st primary user found:
            //  count <= availableExistingIdx
            //  0 <= 0 ? true: 1st primary user added as new entry to list (at idx 0)
            //  count: 1
            //  availableExistingIdx: 1
            //
            // while (count > availableExistingIdx)
            //  1 > 1 ? false: stop while loop

            // count: 2
            // availableExistingIdx: 0
            //
            // 1st primary user found:
            //  count <= availableExistingIdx
            //  2 <= 0 ? false: 1st primary user put to idx 0
            //  availableExistingIdx: 1
            //
            // while (count > availableExistingIdx)
            //  2 > 1 ? true: removed idx 1 of list
            //  1 > 1 ? false: stop while loop

            // count: 2
            // availableExistingIdx: 0
            //
            // while (count > availableExistingIdx)
            //  2 > 0 ? true: removed idx 1 of list
            //  1 > 0 ? true: removed idx 0 of list
            //  0 > 0 ? false: stop while loop

            int availableExistingIdx = 0;

            for (int n = 0, len = usersFlattened.Count; n < len; ++n)
            {
                if (usersFlattened[n].AssetPath.IsSceneFile() ||
                    usersFlattened[n].AssetPath.IsInResourcesFolder())
                {
                    var assetFilename = System.IO.Path.GetFileName(usersFlattened[n].AssetPath);

                    var alreadyInList = false;
                    for (int alreadyN = 0, alreadyLen = availableExistingIdx; alreadyN < alreadyLen; ++alreadyN)
                    {
                        if (destination[alreadyN].text.Equals(assetFilename, StringComparison.OrdinalIgnoreCase))
                        {
                            alreadyInList = true;
                            break;
                        }
                    }

                    if (alreadyInList)
                    {
                        continue;
                    }

                    if (destination.Count <= availableExistingIdx)
                    {
                        destination.Add(new GUIContent(
                                            assetFilename,
                                            AssetDatabase.GetCachedIcon(usersFlattened[n].AssetPath),
                                            usersFlattened[n].AssetPath));
                    }
                    else
                    {
                        destination[availableExistingIdx].text    = assetFilename;
                        destination[availableExistingIdx].image   = AssetDatabase.GetCachedIcon(usersFlattened[n].AssetPath);
                        destination[availableExistingIdx].tooltip = usersFlattened[n].AssetPath;
                    }

                    ++availableExistingIdx;
                }
            }

            while (destination.Count > availableExistingIdx)
            {
                destination.RemoveAt(destination.Count - 1);
            }
        }
示例#9
0
        // ==================================================================================

        static void Create(AssetDependencies data, Queue <string> openSet, bool debugLog = false,
                           bool recursiveGetDependencies = false)
        {
            var assetDependencies = data.GetAssetDependencies();

            assetDependencies.Clear();

            // -------------------------------------------------------------

            // assets that we've finished inspecting
            var closedSet = new HashSet <string>();

            // -------------------------------------------------------------

            while (openSet.Count > 0)
            {
                var newAssetToInspect = openSet.Dequeue();

                if (string.IsNullOrEmpty(newAssetToInspect))
                {
                    continue;
                }

                closedSet.Add(newAssetToInspect);

                string[] foundDependencies = GetDependencies(newAssetToInspect, recursiveGetDependencies);

                if (foundDependencies.Length <= 0)
                {
                    // this asset doesn't use others
                    continue;
                }

                StringBuilder stringBuilder = null;
                if (debugLog)
                {
                    stringBuilder = new StringBuilder();
                    stringBuilder.Append("Assets used by ");
                    stringBuilder.Append(newAssetToInspect);
                    stringBuilder.Append(" (");
                    stringBuilder.Append(foundDependencies.Length.ToString());
                    stringBuilder.Append("):\n");
                }

                DependencyEntry assetsUsed;
                if (assetDependencies.ContainsKey(newAssetToInspect))
                {
                    assetsUsed = assetDependencies[newAssetToInspect];
                }
                else
                {
                    assetsUsed = new DependencyEntry();
                    assetDependencies.Add(newAssetToInspect, assetsUsed);
                }

                // -------------------------------------------------------------

                // we're looping through the assets that newAssetToInspect uses
                // so add them to the `DependencyEntry.Uses` list
                for (int n = 0, len = foundDependencies.Length; n < len; ++n)
                {
                    if (string.IsNullOrEmpty(foundDependencies[n]))
                    {
                        continue;
                    }

                    if (foundDependencies[n].Equals(newAssetToInspect, StringComparison.Ordinal))
                    {
                        // for some reason, the API reports assets to be using their own selves
                        // so skip it when they have the same value
                        continue;
                    }

                    if (debugLog)
                    {
                        stringBuilder.Append(n.ToString());
                        stringBuilder.Append(". ");
                        stringBuilder.Append(foundDependencies[n]);
                        stringBuilder.Append("\n");
                    }

                    // --------------------------------------------------------

                    if (!assetsUsed.Uses.Contains(foundDependencies[n]))
                    {
                        assetsUsed.Uses.Add(foundDependencies[n]);
                    }

                    // --------------------------------------------------------

                    // now record that dependency as being used by `newAssetToInspect`
                    DependencyEntry assetsUsedByEdit;
                    if (assetDependencies.ContainsKey(foundDependencies[n]))
                    {
                        assetsUsedByEdit = assetDependencies[foundDependencies[n]];
                    }
                    else
                    {
                        assetsUsedByEdit = new DependencyEntry();
                        assetDependencies.Add(foundDependencies[n], assetsUsedByEdit);
                    }

                    if (!assetsUsedByEdit.Users.Contains(newAssetToInspect))
                    {
                        assetsUsedByEdit.Users.Add(newAssetToInspect);
                    }

                    // --------------------------------------------------------

                    if (closedSet.Contains(foundDependencies[n]))
                    {
                        // this asset was already searched through. skip it.
                        continue;
                    }

                    if (!openSet.Contains(foundDependencies[n]))
                    {
                        // add to list of assets to search through
                        openSet.Enqueue(foundDependencies[n]);
                    }
                }

                if (debugLog)
                {
                    Debug.Log(stringBuilder.ToString());
                }
            }

            // ====================================================================
            // Create the AssetUserFlattened List
            // This is the recursively calculated users of the asset.
            // Indent Levels signify which asset uses which.

            List <AssetUserFlattened> openFlattenedSet = new List <AssetUserFlattened>();

            foreach (var pair in assetDependencies)
            {
                var assetUsers = pair.Value.Users;

                if (assetUsers.Count == 0)
                {
                    continue;
                }

                var usersFlattened = new List <AssetUserFlattened>(assetUsers.Count);
                pair.Value.UsersFlattened = usersFlattened;

                openFlattenedSet.Clear();

                for (int n = 0, len = assetUsers.Count; n < len; ++n)
                {
                    AssetUserFlattened newEntry = new AssetUserFlattened(assetUsers[n], 1
#if BRT_ASSET_DEPENDENCY_DEBUG
                                                                         , "[initial]"
#endif
                                                                         );

                    // put them in reverse since always take from the end of the openFlattenedSet
                    openFlattenedSet.Insert(0, newEntry);
                }

                var endlessLoopGuard = 0;
                while (openFlattenedSet.Count > 0)
                {
                    ++endlessLoopGuard;
                    if (endlessLoopGuard >= 9999)                     // that ought to be enough for real-world usage (I hope)
                    {
                        break;
                    }


                    var userAsset = openFlattenedSet[openFlattenedSet.Count - 1];
                    openFlattenedSet.RemoveAt(openFlattenedSet.Count - 1);


                    var assetUsed = GetAssetUsageParent(usersFlattened, usersFlattened.Count - 1);
                    if (Contains(usersFlattened, userAsset, assetUsed))
                    {
                        // `userAsset` is already added previously in this list in same indent level and same parent
                        // can't add this because it would be a duplicate entry

                        continue;
                    }

                    usersFlattened.Add(userAsset);

                    if (userAsset.AssetPath.Equals(pair.Key, StringComparison.OrdinalIgnoreCase))
                    {
                        // no need to go inside this further because this is
                        // already what we're primarily going inside currently
                        userAsset.CyclicDependency = true;
#if BRT_ASSET_DEPENDENCY_DEBUG
                        userAsset.DebugInfo = "[selected cyclic]";
#endif
                        usersFlattened[usersFlattened.Count - 1] = userAsset;
                        continue;
                    }

                    var    userAssetIsFbxUsingMaterial = false;
                    string materialAssignedAsDefault   = null;

                    if (usersFlattened.Count >= 2)
                    {
                        // note: usersFlattened[usersFlattened.Count - 1] is userAsset
                        //

                        var noNeedToAddUsersOfUser = false;

                        if (userAsset.IndentLevel > 1)
                        {
                            var currentIndentLevel = userAsset.IndentLevel - 1;

                            if (debugLog && pair.Key.Contains("Runtime-Only DLL/TextMeshPro.dll") &&
                                userAsset.AssetPath.Contains("AbilityEntry Settings.asset"))
                            {
                                Debug.LogFormat(
                                    "=========================================================\nChecking for cyclic dependencies for {0} at index {1}\nLooking for Indent Level {2}",
                                    userAsset.AssetPath, (usersFlattened.Count - 1).ToString(), currentIndentLevel);
                            }

                            // start just before our own position in the list, then go backwards
                            for (int traceN = usersFlattened.Count - 2; traceN >= 0; --traceN)
                            {
                                if (usersFlattened[traceN].IndentLevel > currentIndentLevel)
                                {
                                    // not the indent level we're looking for

                                    if (debugLog && pair.Key.Contains("Runtime-Only DLL/TextMeshPro.dll") &&
                                        userAsset.AssetPath.Contains("AbilityEntry Settings.asset"))
                                    {
                                        Debug.LogFormat("Skipped element {0} since its indent level is: {1} (looking for {2})",
                                                        traceN, usersFlattened[traceN].IndentLevel.ToString(), currentIndentLevel);
                                    }

                                    continue;
                                }

                                // at this point, the current element is above our indent level, meaning it's what the asset uses

                                if (debugLog && pair.Key.Contains("Runtime-Only DLL/TextMeshPro.dll") &&
                                    userAsset.AssetPath.Contains("AbilityEntry Settings.asset"))
                                {
                                    Debug.LogFormat("Now at element {0}: {1} at indent level {2}. Checking if same as {3}",
                                                    traceN, usersFlattened[traceN].AssetPath, usersFlattened[traceN].IndentLevel.ToString(),
                                                    userAsset.AssetPath);
                                }

                                currentIndentLevel = usersFlattened[traceN].IndentLevel - 1;

                                if (usersFlattened[traceN].AssetPath
                                    .Equals(userAsset.AssetPath, StringComparison.OrdinalIgnoreCase))
                                {
                                    // this is an asset we've been in earlier.
                                    // should not add users of this asset, because that includes the
                                    // one we've already been in, and it would be an endless recursion
                                    userAsset.CyclicDependency = true;
#if BRT_ASSET_DEPENDENCY_DEBUG
                                    userAsset.DebugInfo = "[earlier indent cyclic]";
#endif
                                    usersFlattened[usersFlattened.Count - 1] = userAsset;
                                    noNeedToAddUsersOfUser = true;


                                    if (debugLog && pair.Key.Contains("Runtime-Only DLL/TextMeshPro.dll") &&
                                        userAsset.AssetPath.Contains("AbilityEntry Settings.asset"))
                                    {
                                        Debug.LogFormat(
                                            "Element {0} is found to be same so latest entry is marked as cyclic dependency. check is done.",
                                            traceN);
                                    }

                                    break;
                                }

                                if (usersFlattened[traceN].IndentLevel <= 1)
                                {
                                    // went through the last asset in this usage chain, no need to look further

                                    if (debugLog && pair.Key.Contains("Runtime-Only DLL/TextMeshPro.dll") &&
                                        userAsset.AssetPath.Contains("AbilityEntry Settings.asset"))
                                    {
                                        Debug.LogFormat("Element {0} has indent level of {1} so aborting the check",
                                                        traceN, usersFlattened[traceN].IndentLevel.ToString());
                                    }

                                    break;
                                }
                            }

                            if (debugLog && pair.Key.Contains("Runtime-Only DLL/TextMeshPro.dll") &&
                                userAsset.AssetPath.Contains("AbilityEntry Settings.asset"))
                            {
                                Debug.LogFormat(
                                    "=========================================================\nDone checking for cyclic dependencies for {0} at index {1}",
                                    userAsset.AssetPath, (usersFlattened.Count - 1).ToString());
                            }
                        }

                        if (!noNeedToAddUsersOfUser)
                        {
                            for (int n = usersFlattened.Count - 2; n >= 0; --n)
                            {
                                if (usersFlattened[n].AssetPath == userAsset.AssetPath)
                                {
                                    // users of this user has already been shown in earlier entries
                                    // although from a different usage chain

#if BRT_ASSET_DEPENDENCY_DEBUG
                                    userAsset.DebugInfo += " [already visited]";
#endif
                                    // update the entry
                                    usersFlattened[usersFlattened.Count - 1] = userAsset;

                                    noNeedToAddUsersOfUser = true;

                                    break;
                                }
                            }
                        }

                        int idxOfPrevious;
                        if (!noNeedToAddUsersOfUser &&
                            IsFileTypeBeforeAnother(usersFlattened, usersFlattened.Count - 1,
                                                    ".mat", ".fbx", out idxOfPrevious))
                        {
                            // a Material being used by an FBX is only as a default, initial value for that FBX.
                            // after the FBX is instantiated, we have no guarantee that Material is still being
                            // used, so additional checks are needed

                            // check the assets that use the FBX file, meaning check the places where that FBX
                            // is instantiated or referenced, which are either prefabs, scenes, or scriptable objects.
                            //
                            // do those prefabs/scenes/scriptable objects really use that default material, or has it
                            // been overriden?

                            userAssetIsFbxUsingMaterial = true;

                            materialAssignedAsDefault = usersFlattened[idxOfPrevious].AssetPath;
                            var fbxUsingTheMaterialAsDefault = userAsset.AssetPath;

                            if (debugLog)
                            {
                                Debug.LogFormat("Detected fbx using a material.\n" +
                                                "Material:\n{0}\n" +
                                                "Fbx:\n{1}\n" +
                                                "Checking the assets that use the fbx if they really also use the material...",
                                                materialAssignedAsDefault, fbxUsingTheMaterialAsDefault);
                            }

                            var isMaterialReallyBeingUsed =
                                AreInstancesOfFbxUsingMaterial(fbxUsingTheMaterialAsDefault, materialAssignedAsDefault,
                                                               assetDependencies, debugLog);

                            if (!isMaterialReallyBeingUsed)
                            {
#if BRT_ASSET_DEPENDENCY_DEBUG
                                userAsset.DebugInfo += " [using as default for material]";
#endif
                                usersFlattened[usersFlattened.Count - 1] = userAsset;
                                noNeedToAddUsersOfUser = true;
                            }
                        }

                        if (!noNeedToAddUsersOfUser &&
                            userAsset.AssetPath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase))
                        {
                            // something being used by a .cs script file is only as a default, initial value for that script
                            // after the script is instantiated, we have no guarantee that something is still being
                            // used, so it doesn't make sense to go further
#if BRT_ASSET_DEPENDENCY_DEBUG
                            userAsset.DebugInfo += " [using as default for script]";
#endif
                            usersFlattened[usersFlattened.Count - 1] = userAsset;
                            noNeedToAddUsersOfUser = true;
                        }

                        if (noNeedToAddUsersOfUser)
                        {
                            continue;
                        }
                    }

                    if (assetDependencies.ContainsKey(userAsset.AssetPath))
                    {
                        var usersOfUser = assetDependencies[userAsset.AssetPath].Users;
                        for (int n = 0, len = usersOfUser.Count; n < len; ++n)
                        {
                            AssetUserFlattened newEntry = new AssetUserFlattened(usersOfUser[n], userAsset.IndentLevel + 1);

                            if (userAssetIsFbxUsingMaterial)
                            {
                                if (!IsInstanceOfFbxUsingMaterial(userAsset.AssetPath, usersOfUser[n],
                                                                  materialAssignedAsDefault, assetDependencies, debugLog))
                                {
                                    continue;
                                }

                                if (usersOfUser[n].EndsWith(".unity", StringComparison.OrdinalIgnoreCase))
                                {
                                    // userAsset is an fbx which uses a material
                                    // newEntry is a scene that uses (an instance of) that fbx
                                    // BUT we are not sure that
                                    newEntry.IndentLevel = userAsset.IndentLevel;

                                    if (Contains(usersFlattened, newEntry, userAsset.AssetPath))
                                    {
                                        // already added
                                        continue;
                                    }
                                }

#if BRT_ASSET_DEPENDENCY_DEBUG
                                newEntry.DebugInfo = " [uses fbx which uses material]";
#endif
                            }

                            openFlattenedSet.Add(newEntry);
                        }
                    }
                }
            }

            if (debugLog)
            {
                Debug.Log("===================================================");

                foreach (var pair in assetDependencies)
                {
                    var stringBuilder = new StringBuilder();

                    stringBuilder.Append(pair.Key);

                    stringBuilder.Append(" used by:\n");

                    for (int n = 0, len = pair.Value.Users.Count; n < len; ++n)
                    {
                        stringBuilder.Append(n.ToString());
                        stringBuilder.Append(". ");
                        stringBuilder.Append(pair.Value.Users[n]);
                        stringBuilder.Append("\n");
                    }

                    Debug.Log(stringBuilder.ToString());
                }
            }
        }
示例#10
0
        public static void TestCheckAllScenesRecursive()
        {
            var assetDependencies = new AssetDependencies();

            CreateFromIncludedScenes(assetDependencies, true, true);
        }
示例#11
0
        public static void TestCheckAllScenesDirect()
        {
            var assetDependencies = new AssetDependencies();

            CreateFromIncludedScenes(assetDependencies, true, false);
        }