// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.IO; using FlaxEngine; namespace FlaxEditor.Modules { /// /// Caching local project data manager. Used to store and manage the information about expanded actor nodes in the scene tree and other local user data used by the editor. Stores data in the project cache directory. /// /// public sealed class ProjectCacheModule : EditorModule { private readonly string _cachePath; private bool _isDirty; private DateTime _lastSaveTime; private readonly HashSet _expandedActors = new HashSet(); private readonly HashSet _collapsedGroups = new HashSet(); private readonly Dictionary _customData = new Dictionary(); /// /// Gets or sets the automatic data save interval. /// public TimeSpan AutoSaveInterval { get; set; } = TimeSpan.FromSeconds(3); /// internal ProjectCacheModule(Editor editor) : base(editor) { // After editor options but before the others InitOrder = -900; _cachePath = StringUtils.CombinePaths(Globals.ProjectCacheFolder, "ProjectCache.dat"); _isDirty = true; } /// /// Determines whether actor identified by the given ID is expanded in the scene tree UI. /// /// The actor identifier. /// true if actor is expanded; otherwise, false. public bool IsExpandedActor(ref Guid id) { return _expandedActors.Contains(id); } /// /// Sets the actor expanded cached value. /// /// The identifier. /// If set to true actor will be cached as an expanded, otherwise false. public void SetExpandedActor(ref Guid id, bool isExpanded) { if (isExpanded) _expandedActors.Add(id); else _expandedActors.Remove(id); _isDirty = true; } /// /// Determines whether group identified by the given title is collapsed in the UI. /// /// The group title. /// true if group is collapsed; otherwise, false. public bool IsCollapsedGroup(string title) { return _collapsedGroups.Contains(title); } /// /// Sets the group collapsed cached value. /// /// The group title. /// If set to true group will be cached as an collapsed, otherwise false. public void SetCollapsedGroup(string title, bool isCollapsed) { if (isCollapsed) _collapsedGroups.Add(title); else _collapsedGroups.Remove(title); _isDirty = true; } /// /// Determines whether project cache contains custom data of the specified key. /// /// The key. /// true if has custom data of the specified key; otherwise, false. public bool HasCustomData(string key) { return _customData.ContainsKey(key); } /// /// Gets the custom data by the key. /// /// /// Use to check if a key is valid. /// /// The key. /// The custom data. public string GetCustomData(string key) { return _customData[key]; } /// /// Tries to get the custom data by the key. /// /// The key. /// When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized. /// The custom data. public bool TryGetCustomData(string key, out string value) { return _customData.TryGetValue(key, out value); } /// /// Sets the custom data. /// /// The key. /// The value. public void SetCustomData(string key, string value) { _customData[key] = value; _isDirty = true; } /// /// Removes the custom data. /// /// The key. public void RemoveCustomData(string key) { bool removed = _customData.Remove(key); _isDirty |= removed; } private void LoadGuarded() { using (var stream = new FileStream(_cachePath, FileMode.Open, FileAccess.Read, FileShare.Read)) using (var reader = new BinaryReader(stream)) { var version = reader.ReadInt32(); switch (version) { case 1: { int expandedActorsCount = reader.ReadInt32(); _expandedActors.Clear(); var bytes16 = new byte[16]; for (int i = 0; i < expandedActorsCount; i++) { reader.Read(bytes16, 0, 16); _expandedActors.Add(new Guid(bytes16)); } _collapsedGroups.Clear(); _customData.Clear(); break; } case 2: { int expandedActorsCount = reader.ReadInt32(); _expandedActors.Clear(); var bytes16 = new byte[16]; for (int i = 0; i < expandedActorsCount; i++) { reader.Read(bytes16, 0, 16); _expandedActors.Add(new Guid(bytes16)); } _collapsedGroups.Clear(); _customData.Clear(); int customDataCount = reader.ReadInt32(); for (int i = 0; i < customDataCount; i++) { var key = reader.ReadString(); var value = reader.ReadString(); _customData.Add(key, value); } break; } case 3: { int expandedActorsCount = reader.ReadInt32(); _expandedActors.Clear(); var bytes16 = new byte[16]; for (int i = 0; i < expandedActorsCount; i++) { reader.Read(bytes16, 0, 16); _expandedActors.Add(new Guid(bytes16)); } int collapsedGroupsCount = reader.ReadInt32(); _collapsedGroups.Clear(); for (int i = 0; i < collapsedGroupsCount; i++) { _collapsedGroups.Add(reader.ReadString()); } _customData.Clear(); int customDataCount = reader.ReadInt32(); for (int i = 0; i < customDataCount; i++) { var key = reader.ReadString(); var value = reader.ReadString(); _customData.Add(key, value); } break; } default: Editor.LogWarning("Unknown editor cache version."); return; } } } private void Load() { if (!File.Exists(_cachePath)) { Editor.LogWarning("Missing editor cache file."); return; } _lastSaveTime = DateTime.UtcNow; try { LoadGuarded(); } catch (Exception ex) { Editor.LogWarning(ex); Editor.LogError("Failed to load editor cache. " + ex.Message); } } private void SaveGuarded() { using (var stream = new FileStream(_cachePath, FileMode.Create, FileAccess.Write, FileShare.Read)) using (var writer = new BinaryWriter(stream)) { writer.Write(3); writer.Write(_expandedActors.Count); foreach (var e in _expandedActors) { writer.Write(e.ToByteArray()); } writer.Write(_collapsedGroups.Count); foreach (var e in _collapsedGroups) { writer.Write(e); } writer.Write(_customData.Count); foreach (var e in _customData) { writer.Write(e.Key); writer.Write(e.Value); } } } private void Save() { if (!_isDirty) return; _lastSaveTime = DateTime.UtcNow; try { SaveGuarded(); _isDirty = false; } catch (Exception ex) { Editor.LogWarning(ex); Editor.LogError("Failed to save editor cache. " + ex.Message); } } /// public override void OnInit() { Load(); } /// public override void OnUpdate() { var dt = DateTime.UtcNow - _lastSaveTime; if (dt >= AutoSaveInterval) { Save(); } } /// public override void OnExit() { Save(); } } }