Example #1
0
        void SetupAssetbundle(AssetBundleManageData data)
        {
            if (0 < this._assetbundleNo)
            {
                var assetbundle = data.assetbundles[this._assetbundleNo - 1];

                List <string> list = new List <string>();
                list.Add(" ");                          // use "" to select the assetbundle.
                var em = assetbundle.GetAllContentsPath();
                list.AddRange(em.Select(s =>
                {
                    if (!string.IsNullOrEmpty(s) && s.IndexOf("Assets/") == 0)
                    {
                        return(s.Substring("Assets/".Length));
                    }
                    return(s);
                }));
                this._assetList  = list.ToArray();
                this._contentNum = this._assetList.Length - 1;

                if (this._source == Source.StreamingAssets)
                {
                    var streamingassets = "/StreamingAssets";
                    int idx             = assetbundle.Directory.IndexOf(streamingassets);
                    if (0 < idx && idx + streamingassets.Length + 1 < assetbundle.Directory.Length)
                    {
                        this._path = assetbundle.Directory.Substring(idx + streamingassets.Length + 1);
                    }
                }
            }
            else
            {
                this._assetList = null;
            }
        }
        public static void RebuildALl()
        {
            // Create all assetbundles
            AssetBundleManageData abd = AssetBundleManager.GetManageData(true);

            if (abd)
            {
                CreateAssetBundleAll(abd, true);
                Save(abd);
                AssetDatabase.Refresh();
            }
        }
        static void SendChangeToChild(AssetBundleManageData data, int no)
        {
            var bundles = data.assetbundles;

            for (int i = 0; i < bundles.Count; ++i)
            {
                if (no == bundles[i].ParentNo)
                {
                    //Debug.Log(string.Format("SendChangeToChild({0})->{1}", no, i));
                    bundles[i].Changed = true;
                    SendChangeToChild(data, i);
                }
            }
        }
        void Awake()
        {
            // if not exist setting data, create it.
            var path = AssetBundleManager.SettingDataPath;

            if (!File.Exists(Application.dataPath + "/" + path))
            {
                // if not exist, create setting data.
                if (!Directory.Exists(Application.dataPath + "/" + Path.GetDirectoryName(path)))
                {
                    Directory.CreateDirectory(Application.dataPath + "/" + Path.GetDirectoryName(path));
                }
                var create = AssetBundleManageData.CreateInstance <AssetBundleManageData>();
                AssetDatabase.CreateAsset(create, "Assets/" + path);
                AssetDatabase.Refresh();
            }
            var backupname = AssetBundleManager.SettingBackupDataPath;

            AssetDatabase.DeleteAsset("Assets/" + backupname);
            if (AssetDatabase.CopyAsset("Assets/" + path, "Assets/" + backupname))
            {
                AssetDatabase.Refresh();
                //var abd = AssetDatabase.LoadAssetAtPath("Assets/" + backupname, typeof(AssetBundleManageData)) as AssetBundleManageData;
                //abd.hideFlags = HideFlags.HideAndDontSave;
            }
            // load setting data.
            AssetBundleManageData abd = AssetBundleManager.GetManageData(true);

            if (abd == null)
            {
                return;
            }
            this._settingDataFilePath = path;
            // edit copy data
            this._assetBundleData = abd;            // AssetBundleManageData.Instantiate( abd ) as AssetBundleManageData;
            abd = null;
            // undo
            Undo.undoRedoPerformed += () =>
            {
                //Debug.Log("Undo:"+Undo.GetCurrentGroup());

                /*EditorUtility.SetDirty(me);
                 * Focus();
                 * this.RefreshAssetBundleList();*/
                this.Repaint();
                this._first = true;
            };
            this._undoObjects = new Object[] { this, this._assetBundleData };
            this._first       = true;
        }
        // Check if assets of assetbundle is updated
        static bool CheckUpdateOfBundleAssets(AssetBundleManageData data, AssetBundleData assetbundle)
        {
            if (assetbundle.GetAllContents().Count <= 0)
            {
                return(false);
            }
            List <string> files = new List <string>();

            foreach (var path in assetbundle.GetAllContentsPath())
            {
                if (File.Exists(path))
                {
                    files.Add(path);
                }
            }
            var targets = new BuildTarget[] { EditorUserBuildSettings.activeBuildTarget };

#if !DLL
            if (assetbundle.PlatformFolder && data.multiPlatform && 0 < data.targets.Count)
            {
                targets = data.targets.ToArray();
            }
#endif
            List <string> platformList = new List <string>();
            foreach (BuildTarget target in targets)
            {
                var path = assetbundle.Path;
#if !DLL
                if (assetbundle.PlatformFolder)
                {
                    var platformFolder = AssetBundleManager.GetPlatformFolder(target);
                    if (platformList.Contains(platformFolder))
                    {
                        continue;
                    }
                    platformList.Add(platformFolder);
                    path = GetFullPath(assetbundle, target);
                }
#endif
                if (AssetBundleUtility.CheckUpdateOfBundleAssets(files.ToArray(), path, (BuildAssetBundleOptions)assetbundle.Options))
                {
                    return(true);
                }
            }
            return(false);
        }
        static void Save(AssetBundleManageData data)
        {
            // save new setting data.
            var path = AssetBundleManager.SettingDataPath;

            if (!string.IsNullOrEmpty(path))
            {
                //AssetDatabase.DeleteAsset(path);
                string dir = Application.dataPath + "/" + Path.GetDirectoryName(path);
                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }
                path = "Assets/" + path;
                var backup = Instantiate(data);
                AssetDatabase.CreateAsset(backup, path);
            }
        }
        /// <summary>
        /// Load manage data
        /// </summary>
        /// <param name="loadBackup">if exist, load backup file</param>
        /// <returns></returns>
        public static AssetBundleManageData GetManageData(bool loadBackup)
        {
            AssetBundleManageData abd = null;

#if UNITY_EDITOR
            string path = SettingDataPath;
            if (loadBackup)
            {
                var backupname = SettingBackupDataPath;
                if (File.Exists(Application.dataPath + "/" + backupname))
                {
                    abd = AssetDatabase.LoadAssetAtPath("Assets/" + backupname, typeof(AssetBundleManageData)) as AssetBundleManageData;
                }
            }
            if (abd == null)
            {
                abd = AssetDatabase.LoadAssetAtPath("Assets/" + path, typeof(AssetBundleManageData)) as AssetBundleManageData;
            }
#endif
            return(abd);
        }
        static void AddAssetBundleToBuildMap(AssetBundleBuild[] buildMap, int position, AssetBundleManageData data, AssetBundleData assetbundle)
        {
            if (assetbundle.GetAllContents().Count <= 0)
            {
                buildMap[position].assetBundleName = "noname";
                buildMap[position].assetNames      = null;
                return;
            }
            // add all path to list
            List <string> files = new List <string>();

            foreach (var path in assetbundle.GetAllContentsPath())
            {
                if (File.Exists(path))
                {
                    files.Add(path);
                }
            }

            Debug.Log(nestString + "Adding Build " + assetbundle.File);

            buildMap[position].assetBundleName = assetbundle.File;
            Debug.Log("There're " + files.ToArray().Length + " file in " + assetbundle.File + " bundle. Created in " + assetbundle.Directory);
            buildMap[position].assetNames = files.ToArray();
        }
        static bool CreateAssetBundle(AssetBundleManageData data, AssetBundleData assetbundle, bool rebuild, bool dependency)
        {
            if (assetbundle.GetAllContents().Count <= 0)
            {
                return(false);
            }
            // add all path to list
            List <string> files = new List <string>();

            foreach (var path in assetbundle.GetAllContentsPath())
            {
                if (File.Exists(path))
                {
                    files.Add(path);
                }
            }
            // add special list asset for system
            BundleAssetList list     = null;
            string          listpath = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(data)) + "/list/" + assetbundle.File + "/list.asset";   //Application.temporaryCachePath + "/" + "list.asset";

            if (!assetbundle.ForStreamedScene)
            {
                list        = CreateInstance <BundleAssetList>();
                list.Assets = files.Select(file => {
                    FileInfo info         = new FileInfo(file);
                    BundleAssetInfo asset = new BundleAssetInfo(info.Name);
                    return(asset);
                }).ToArray();

                var directory = System.IO.Path.GetDirectoryName(listpath).Substring("Assets".Length);
                System.IO.Directory.CreateDirectory(Application.dataPath + directory);
                AssetDatabase.CreateAsset(list, listpath);
                //files.Add(listpath);
                AssetBundleUtility.AssetList = list;
                // BundleAssetList.cs(not exist file "BundleAssetList.cs" with DLL)
                //string bundleAssetsListPath = "Assets/" + Path.GetDirectoryName(AssetBundleManager.SettingDataPath) + "/Scripts/BundleAssetList.cs";
                //files.Add(bundleAssetsListPath);
            }
            var targets = new BuildTarget[] { EditorUserBuildSettings.activeBuildTarget };

#if !DLL
            if (assetbundle.PlatformFolder && data.multiPlatform && 0 < data.targets.Count)
            {
                targets = data.targets.ToArray();
            }
#endif

            bool          ret          = false;
            List <string> platformList = new List <string>();
            foreach (BuildTarget target in targets)
            {
                var path = assetbundle.Path.Replace(AssetBundleManager.PlatformFolder, AssetBundleManager.GetPlatformFolder(Application.platform));
#if !DLL
                if (assetbundle.PlatformFolder)
                {
                    var platformFolder = AssetBundleManager.GetPlatformFolder(target);
                    if (platformList.Contains(platformFolder))
                    {
                        continue;
                    }
                    platformList.Add(platformFolder);
                    path = GetFullPath(assetbundle, target);
                }
#endif
                var dir       = Path.GetDirectoryName(path);
                var assetName = Path.GetFileName(path);
                if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);                    //AssetDatabase.CreateFolder(Path.GetDirectoryName(dir), Path.GetFileName(dir));
                }
                if (!string.IsNullOrEmpty(dir))
                {
                    dir = dir + "/";
                }

                if (!assetbundle.ForStreamedScene)
                {
                    if (!assetbundle.Separated)
                    {
                        Debug.Log(nestString + "Build " + assetbundle.File);

                        /*if( AssetBundleUtilityNew.CreateBundle(files.ToArray(), dir, assetName, (BuildAssetBundleOptions)assetbundle.Options, target, rebuild | assetbundle.Changed) )
                         * {
                         *      ret = true;
                         * }*/
                        //Hoavq change to new build system
                        AssetBundleBuild[] buildMap = new AssetBundleBuild[1];

                        /*buildMap[0].assetBundleName = assetName;
                         * Debug.Log("There're " + files.ToArray().Length + " file in " + assetName + " bundle. Created in " + dir);
                         * buildMap[0].assetNames = files.ToArray();*/

                        AddAssetBundleToBuildMap(buildMap, 0, data, assetbundle);
                        BuildPipeline.BuildAssetBundles(dir, buildMap, (BuildAssetBundleOptions)assetbundle.Options, target);
                    }
                }
            }
            assetbundle.Changed = false;
            return(ret);
        }
        static void CreateAssetBundleAll(AssetBundleManageData data, bool rebuild)
        {
            var bundles = data.assetbundles;
            // parent-child
            Dictionary <int, List <int> > family = new Dictionary <int, List <int> >();

            for (int i = 0; i < bundles.Count; ++i)
            {
                int parent = bundles[i].ParentNo;
                if (0 <= parent)
                {
                    if (!family.ContainsKey(parent))
                    {
                        family.Add(parent, new List <int>());
                    }
                    family[parent].Add(i);
                }
            }
            for (int i = 0; i < bundles.Count; ++i)
            {
                int parent = bundles[i].ParentNo;
                if (parent < 0)
                {
                    if (family.ContainsKey(i))
                    {
                        // family
                        System.Action <int, bool> buildchild = null;
                        nestString = "  ";
                        buildchild = (no, parentbuild) =>
                        {
                            BuildPipeline.PushAssetDependencies();
                            parentbuild |= CreateAssetBundle(data, bundles[no], rebuild | parentbuild, true);
                            nestString  += "  ";
                            foreach (var child in family[no])
                            {
                                if (family.ContainsKey(child))
                                {
                                    buildchild(child, parentbuild);
                                }
                                else
                                {
                                    CreateAssetBundle(data, bundles[child], rebuild | parentbuild, true);
                                }
                            }
                            BuildPipeline.PopAssetDependencies();
                            nestString = nestString.Substring(0, nestString.Length - 2);
                        };
                        nestString = "";
                        buildchild(i, false);
                    }
                    else
                    {
                        // single
                        nestString = "";
                        CreateAssetBundle(data, bundles[i], rebuild, false);
                    }
                }
            }
            // delete all list.asset
            string listpath = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(data)) + "/list";

            AssetDatabase.DeleteAsset(listpath);
        }
        static void CreateAssetBundleWithDependency(AssetBundleManageData data, int no, bool rebuild)
        {
            var           bundles       = data.assetbundles;
            List <string> filesToDelete = new List <string>();
            var           assetbundle   = bundles[no];//Main assetbundle need to be built

            int        wno        = no;
            bool       changed    = bundles[wno].Changed;
            List <int> dependency = new List <int>();

            dependency.Add(wno);

            while (0 <= bundles[wno].ParentNo)
            {
                wno = bundles[wno].ParentNo;
                dependency.Add(wno);
                changed |= bundles[wno].Changed;
                Debug.Log("Add dependency asset " + bundles[wno].File + " to delete list");
                filesToDelete.Add(bundles[wno].File);
                filesToDelete.Add(bundles[wno].File + ".manifest");
            }

            if (!rebuild && !changed && !CheckUpdateOfBundleAssets(data, bundles[wno]))
            {
                // no update
                Debug.Log(" Skip(because not update) " + bundles[no].File);
                return;
            }

            bool build = false;

            AssetBundleBuild[] buildMap = new AssetBundleBuild[dependency.Count];

            for (int i = 0; i < dependency.Count; i++)
            {
                AddAssetBundleToBuildMap(buildMap, i, data, bundles[dependency[i]]);
            }

            if (rebuild)
            {
                var targets = new BuildTarget[] { EditorUserBuildSettings.activeBuildTarget };

                if (assetbundle.PlatformFolder && data.multiPlatform && 0 < data.targets.Count)
                {
                    targets = data.targets.ToArray();
                }

                List <string> platformList = new List <string>();

                foreach (BuildTarget target in targets)
                {
                    var path = assetbundle.Path.Replace(AssetBundleManager.PlatformFolder, AssetBundleManager.GetPlatformFolder(Application.platform));
                    if (assetbundle.PlatformFolder)
                    {
                        var platformFolder = AssetBundleManager.GetPlatformFolder(target);
                        if (platformList.Contains(platformFolder))
                        {
                            continue;
                        }
                        platformList.Add(platformFolder);
                        path = GetFullPath(assetbundle, target);
                    }

                    var dir = Path.GetDirectoryName(path);
                    if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
                    {
                        Directory.CreateDirectory(dir);
                    }
                    if (!string.IsNullOrEmpty(dir))
                    {
                        dir = dir + "/";
                    }

                    BuildPipeline.BuildAssetBundles(dir, buildMap, (BuildAssetBundleOptions)assetbundle.Options, target);

                    //Cleanup after build
                    string src = dir + AssetBundleManager.GetPlatformFolder(target);// target.ToString();
                    string des = dir + assetbundle.File + ".mf";

                    if (File.Exists(des))
                    {
                        File.Delete(des);
                    }

                    Debug.Log("Rename file " + src + " ====> " + des);
                    System.IO.File.Move(src, des);

                    //filesToDelete.Add(target.ToString() + ".manifest");
                    filesToDelete.Add(AssetBundleManager.GetPlatformFolder(target) + ".manifest");
                    filesToDelete.Add(assetbundle.File + ".manifest");

                    foreach (string delfile in filesToDelete)
                    {
                        File.Delete(dir + delfile);
                    }

                    //Zip file
                    string[] zipFiles =
                    {
                        dir + assetbundle.File,
                        dir + assetbundle.File + ".mf"
                    };
                    //target.ToString() ==> iOS
                    //AssetBundleManager.GetPlatformFolder(target) ==> iPhone
                    using (ZipFile zip = new ZipFile())
                    {
                        foreach (string file in zipFiles)
                        {
                            zip.AddFile(file, target.ToString());
                        }
                        zip.Save(assetbundle.Directory.Replace(AssetBundleManager.PlatformFolder, assetbundle.File)
                                 + "_" + target.ToString() + ".zip");
                    }
                }
                build = true;
            }


            // send changing to child .
            if (build && ((BuildAssetBundleOptions)bundles[no].Options & BuildAssetBundleOptions.DeterministicAssetBundle) == 0)
            {
                SendChangeToChild(data, no);
            }
            // delete all list.asset
            string listpath = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(data)) + "/list";

            AssetDatabase.DeleteAsset(listpath);
        }
        static bool CreateAssetBundle(AssetBundleManageData data, AssetBundleData assetbundle, bool rebuild, bool dependency)
        {
            if (assetbundle.GetAllContents().Count <= 0)
            {
                return(false);
            }
            // add all path to list
            List <string> files = new List <string>();

            foreach (var path in assetbundle.GetAllContentsPath())
            {
                if (File.Exists(path))
                {
                    files.Add(path);
                }
            }
            // add special list asset for system
            BundleAssetList list     = null;
            string          listpath = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(data)) + "/list/" + assetbundle.File + "/list.asset";   //Application.temporaryCachePath + "/" + "list.asset";

            if (!assetbundle.ForStreamedScene)
            {
                list        = CreateInstance <BundleAssetList>();
                list.Assets = files.Select(file => {
                    FileInfo info         = new FileInfo(file);
                    BundleAssetInfo asset = new BundleAssetInfo(info.Name);
                    return(asset);
                }).ToArray();

                var directory = System.IO.Path.GetDirectoryName(listpath).Substring("Assets".Length);
                System.IO.Directory.CreateDirectory(Application.dataPath + directory);
                AssetDatabase.CreateAsset(list, listpath);
                //files.Add(listpath);
                AssetBundleUtility.AssetList = list;
                // BundleAssetList.cs(not exist file "BundleAssetList.cs" with DLL)
                //string bundleAssetsListPath = "Assets/" + Path.GetDirectoryName(AssetBundleManager.SettingDataPath) + "/Scripts/BundleAssetList.cs";
                //files.Add(bundleAssetsListPath);
            }
            var targets = new BuildTarget[] { EditorUserBuildSettings.activeBuildTarget };

#if !DLL
            if (assetbundle.PlatformFolder && data.multiPlatform && 0 < data.targets.Count)
            {
                targets = data.targets.ToArray();
            }
#endif

            bool          ret          = false;
            List <string> platformList = new List <string>();
            foreach (BuildTarget target in targets)
            {
                var path = assetbundle.Path.Replace(AssetBundleManager.PlatformFolder, AssetBundleManager.GetPlatformFolder(Application.platform));
#if !DLL
                if (assetbundle.PlatformFolder)
                {
                    var platformFolder = AssetBundleManager.GetPlatformFolder(target);
                    if (platformList.Contains(platformFolder))
                    {
                        continue;
                    }
                    platformList.Add(platformFolder);
                    path = GetFullPath(assetbundle, target);
                }
#endif
                var dir = Path.GetDirectoryName(path);
                if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);                    //AssetDatabase.CreateFolder(Path.GetDirectoryName(dir), Path.GetFileName(dir));
                }
                if (!string.IsNullOrEmpty(dir))
                {
                    dir = dir + "/";
                }

                if (!assetbundle.ForStreamedScene)
                {
                    if (!assetbundle.Separated)
                    {
                        Debug.Log(nestString + "Build " + assetbundle.File);
                        if (AssetBundleUtility.CreateBundle(files.ToArray(), path, (BuildAssetBundleOptions)assetbundle.Options, target, rebuild | assetbundle.Changed))
                        {
                            ret = true;
                        }
                    }
                    else
                    {
                        var infos = new BundleAssetInfo[] { new BundleAssetInfo("") };
                        list.Assets = infos;
                        AssetBundleUtility.AssetList = list;
                        foreach (var file in files)
                        {
                            infos[0].Name = file;
                            string name = Path.GetFileNameWithoutExtension(file) + ".unity3d";
                            Debug.Log(nestString + "Build " + name);
                            if (AssetBundleUtility.CreateBundle(file, dir + name, (BuildAssetBundleOptions)assetbundle.Options, target, rebuild | assetbundle.Changed))
                            {
                                ret = true;
                            }
                            BuildPipeline.PopAssetDependencies();
                            BuildPipeline.PushAssetDependencies();
                        }
                    }
                }
                else
                {
                    if (!assetbundle.Separated)
                    {
                        Debug.Log(nestString + "Build " + assetbundle.File);
                        if (AssetBundleUtility.CreateSceneBundle(files.ToArray(), path, (BuildAssetBundleOptions)assetbundle.Options, target, rebuild | assetbundle.Changed))
                        {
                            ret = true;
                        }
                    }
                    else
                    {
                        foreach (var file in files)
                        {
                            string name = Path.GetFileNameWithoutExtension(file) + ".unity3d";
                            Debug.Log(nestString + "Build " + name);
                            if (AssetBundleUtility.CreateSceneBundle(file, dir + name, (BuildAssetBundleOptions)assetbundle.Options, target, rebuild | assetbundle.Changed))
                            {
                                ret = true;
                            }
                            BuildPipeline.PopAssetDependencies();
                            BuildPipeline.PushAssetDependencies();
                        }
                    }
                }
            }
            assetbundle.Changed = false;
            //AssetDatabase.DeleteAsset(listpath);	Child assetbundle require list.asset of parent assetbundle.
            //AssetDatabase.Refresh();
            return(ret);
        }
        static void CreateAssetBundleWithDependency(AssetBundleManageData data, int no, bool rebuild)
        {
            var        bundles    = data.assetbundles;
            int        wno        = no;
            bool       changed    = bundles[wno].Changed;   // || ((BuildAssetBundleOptions)bundles[wno].Options & BuildAssetBundleOptions.DeterministicAssetBundle) == 0;
            List <int> dependency = new List <int>();

            dependency.Add(wno);
            while (0 <= bundles[wno].ParentNo)
            {
                wno = bundles[wno].ParentNo;
                dependency.Add(wno);
                changed |= bundles[wno].Changed;
            }
            if (!rebuild && !changed && !CheckUpdateOfBundleAssets(data, bundles[wno]))
            {
                // no update
                Debug.Log(" Skip(because not update) " + bundles[no].File);
                return;
            }
            bool build = false;

            if (1 < dependency.Count)
            {
                if (!rebuild)
                {
                    /*for( int i = dependency.Count - 1; 0 <= i; --i )
                     * {
                     *      //Debug.Log(string.Format("{0}: changed:{1} option:{2}", bundles[dependency[i]].File, bundles[dependency[i]].Changed, bundles[dependency[i]].Options));
                     *      if( !bundles[dependency[i]].Changed && ( (BuildAssetBundleOptions)bundles[dependency[i]].Options & BuildAssetBundleOptions.DeterministicAssetBundle ) != 0 )
                     *              dependency.RemoveAt( i );	// child assetbundle can rebuild.
                     *      else
                     *              break;
                     * }*/
                }
                dependency.Reverse();
                //Debug.Log("Build: " + string.Join("->", dependency.Select(d => d.ToString()).ToArray()));
                nestString = "";
                foreach (var d in dependency)
                {
                    BuildPipeline.PushAssetDependencies();
                    CreateAssetBundle(data, bundles[d], true, true);                            // if you build parent, must build child.
                    nestString += "  ";
                    build       = true;
                }
                for (int i = 0; i < dependency.Count; ++i)
                {
                    BuildPipeline.PopAssetDependencies();
                }
            }
            else
            {
                nestString = "";
                CreateAssetBundle(data, bundles[no], rebuild, false);
                build = true;
            }
            // send changing to child .
            if (build && ((BuildAssetBundleOptions)bundles[no].Options & BuildAssetBundleOptions.DeterministicAssetBundle) == 0)
            {
                SendChangeToChild(data, no);
            }
            // delete all list.asset
            string listpath = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(data)) + "/list";

            AssetDatabase.DeleteAsset(listpath);
        }