| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- using Newtonsoft.Json;
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.ComponentModel;
- using System.IO;
- using System.Linq;
- using TeamAAS.Communication.Config;
- using TeamAAS.Communication.Models;
- namespace TeamAAS.Communication
- {
- /// <summary>
- /// 全局变量管理器。懒汉单例:<c>GlobalVariableManager.Instance</c>。
- /// 变量分两级作用域:Global(机器级固定,不随产品变) / Product(随产品变)。
- /// 内存里是单一 <see cref="Variables"/> 列表(公式绑定层直接读它,零改动),
- /// Scope 只决定持久化去向与编辑入口:
- /// Global 子集存 Config\global_vars.json;Product 子集存 Products\<名>\global_vars.json。
- /// </summary>
- public class GlobalVariableManager
- {
- #region 单例
- private static readonly Lazy<GlobalVariableManager> _instance =
- new Lazy<GlobalVariableManager>(() => new GlobalVariableManager(), isThreadSafe: true);
- /// <summary>懒汉单例入口。首次访问时初始化,线程安全。</summary>
- public static GlobalVariableManager Instance => _instance.Value;
- #endregion
- private GlobalVariableManager()
- {
- Variables.CollectionChanged += (s, e) =>
- {
- if (e.NewItems != null)
- foreach (GlobalVariableModel v in e.NewItems)
- v.PropertyChanged += OnVariablePropertyChanged;
- if (e.OldItems != null)
- foreach (GlobalVariableModel v in e.OldItems)
- v.PropertyChanged -= OnVariablePropertyChanged;
- if (e.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Replace)
- VariablesChanged?.Invoke();
- };
- }
- private void OnVariablePropertyChanged(object sender, PropertyChangedEventArgs e)
- {
- VariablesChanged?.Invoke();
- }
- /// <summary>全部全局变量(Global + 当前产品的 Product)。公式/结果注册表直接读它。</summary>
- public ObservableCollection<GlobalVariableModel> Variables { get; }
- = new ObservableCollection<GlobalVariableModel>();
- public event Action VariablesChanged;
- #region 变量 CRUD
- /// <summary>新建变量并加入 live 列表(默认 Product 作用域)。</summary>
- public void AddVariable(string dataType, VarScope scope = VarScope.Product)
- {
- Variables.Add(GlobalVariableModel.Create(dataType, scope, Variables.Count));
- }
- /// <summary>
- /// 按名称写入变量值,供插件/通讯/流程写入任意类型(object 变量可承载任何对象)。
- /// 变量不存在时返回 false。写入后自动触发 VariablesChanged → ResultRegistry 刷新公式数据。
- /// </summary>
- public bool SetValue(string varName, object value)
- {
- if (string.IsNullOrWhiteSpace(varName)) return false;
- var v = Variables.FirstOrDefault(x => x.Name == varName);
- if (v == null) return false;
- v.Value = value;
- return true;
- }
- public object GetValue(string varName)
- {
- var v = Variables.FirstOrDefault(x => x.Name == varName);
- return v?.Value;
- }
- public void DeleteVariable(GlobalVariableModel variable)
- {
- if (variable == null) return;
- int idx = Variables.IndexOf(variable);
- if (idx < 0) return;
- Variables.RemoveAt(idx);
- Reindex();
- }
- public void MoveUp(GlobalVariableModel variable)
- {
- if (variable == null) return;
- int idx = Variables.IndexOf(variable);
- if (idx <= 0) return;
- Variables.Move(idx, idx - 1);
- Reindex();
- }
- public void MoveDown(GlobalVariableModel variable)
- {
- if (variable == null) return;
- int idx = Variables.IndexOf(variable);
- if (idx >= Variables.Count - 1) return;
- Variables.Move(idx, idx + 1);
- Reindex();
- }
- private void Reindex()
- {
- for (int i = 0; i < Variables.Count; i++)
- Variables[i].Index = i;
- }
- public Dictionary<string, object> GetAllValuesDict()
- {
- var dict = new Dictionary<string, object>();
- foreach (var v in Variables)
- {
- if (!string.IsNullOrWhiteSpace(v.Name))
- dict[v.Name] = v.Value;
- }
- return dict;
- }
- public Dictionary<string, Dictionary<string, object>> GetAsResultRegistryFormat()
- {
- var nodeDict = new Dictionary<string, object>();
- foreach (var v in Variables)
- {
- if (!string.IsNullOrWhiteSpace(v.Name))
- nodeDict[v.Name] = v.Value;
- }
- return new Dictionary<string, Dictionary<string, object>>
- {
- ["GlobalVariables"] = nodeDict
- };
- }
- #endregion
- #region 作用域克隆 / 提交(供编辑器 克隆-编辑-提交,取消可回滚)
- /// <summary>返回指定作用域变量的深拷贝列表(编辑器工作集,改动不影响 live)。</summary>
- public List<GlobalVariableModel> CloneScoped(VarScope scope)
- => Variables.Where(v => v.Scope == scope).Select(v => v.Clone()).ToList();
- /// <summary>
- /// 用给定项替换 live <see cref="Variables"/> 中该作用域的全部变量(另一作用域保持不动)。
- /// 会触发 CollectionChanged → VariablesChanged,ResultRegistry 已订阅并自动刷新公式数据。
- /// 必须在 UI 线程调用(Variables 绑定 UI)。
- /// </summary>
- public void ReplaceScoped(VarScope scope, IEnumerable<GlobalVariableModel> items)
- {
- for (int i = Variables.Count - 1; i >= 0; i--)
- if (Variables[i].Scope == scope) Variables.RemoveAt(i);
- if (items != null)
- foreach (var v in items)
- {
- v.Scope = scope; // 统一盖章,防外部传入 Scope 不一致
- Variables.Add(v);
- }
- Reindex();
- }
- #endregion
- #region 持久化:Global 机器级 / Product 产品级子集
- /// <summary>机器级 Global 变量文件:Config\global_vars.json。</summary>
- public static string GlobalFixedVarsFile => TeamAAS.PathHelper.GlobalFixedVarsFile;
- /// <summary>启动时载入机器级 Global 变量(替换 live 列表中的 Global 子集)。</summary>
- public void LoadGlobalScoped()
- {
- try
- {
- var list = new List<GlobalVariableModel>();
- var path = GlobalFixedVarsFile;
- if (File.Exists(path))
- {
- var json = File.ReadAllText(path);
- list = JsonConvert.DeserializeObject<List<GlobalVariableModel>>(json) ?? new List<GlobalVariableModel>();
- }
- foreach (var v in list) v.Scope = VarScope.Global;
- ReplaceScoped(VarScope.Global, list);
- }
- catch { }
- }
- /// <summary>把 live 列表中 Global 子集写入机器级文件。</summary>
- public void SaveGlobalScoped()
- {
- try
- {
- var path = GlobalFixedVarsFile;
- var dir = Path.GetDirectoryName(path);
- if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir);
- var list = Variables.Where(v => v.Scope == VarScope.Global).ToList();
- File.WriteAllText(path, JsonConvert.SerializeObject(list, Formatting.Indented));
- }
- catch { }
- }
- /// <summary>把 live 列表中 Product 子集序列化到指定产品快照文件。</summary>
- public void SaveSnapshot(string filePath)
- {
- try
- {
- var dir = Path.GetDirectoryName(filePath);
- if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
- Directory.CreateDirectory(dir);
- var snap = new GlobalVarSnapshot
- {
- Variables = Variables.Where(v => v.Scope == VarScope.Product).ToList()
- };
- File.WriteAllText(filePath, JsonConvert.SerializeObject(snap, Formatting.Indented));
- }
- catch { }
- }
- /// <summary>
- /// 从产品快照恢复 Product 子集(Global 子集保持不动)。
- /// 文件不存在(老产品没存过)时清空 Product 子集——切到新产品应从空开始,而非继承上个产品的变量。
- /// 必须在 UI 线程调用。
- /// </summary>
- public bool LoadSnapshot(string filePath)
- {
- try
- {
- var list = new List<GlobalVariableModel>();
- if (File.Exists(filePath))
- {
- var json = File.ReadAllText(filePath);
- var snap = JsonConvert.DeserializeObject<GlobalVarSnapshot>(json);
- list = snap?.Variables ?? new List<GlobalVariableModel>();
- }
- foreach (var v in list) v.Scope = VarScope.Product;
- ReplaceScoped(VarScope.Product, list);
- return File.Exists(filePath);
- }
- catch { return false; }
- }
- #endregion
- /// <summary>读取某产品快照文件的 Product 变量(不改 live 列表);文件不存在返回空列表。</summary>
- public List<GlobalVariableModel> ReadSnapshotList(string filePath)
- {
- try
- {
- if (!File.Exists(filePath)) return new List<GlobalVariableModel>();
- var snap = JsonConvert.DeserializeObject<GlobalVarSnapshot>(File.ReadAllText(filePath));
- var list = snap?.Variables ?? new List<GlobalVariableModel>();
- foreach (var v in list) v.Scope = VarScope.Product;
- return list;
- }
- catch { return new List<GlobalVariableModel>(); }
- }
- /// <summary>把给定 Product 变量写入某产品快照文件(不改 live 列表)。</summary>
- public void WriteSnapshotList(string filePath, IEnumerable<GlobalVariableModel> items)
- {
- try
- {
- var dir = Path.GetDirectoryName(filePath);
- if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir);
- var list = (items ?? Enumerable.Empty<GlobalVariableModel>()).ToList();
- foreach (var v in list) v.Scope = VarScope.Product;
- var snap = new GlobalVarSnapshot { Variables = list };
- File.WriteAllText(filePath, JsonConvert.SerializeObject(snap, Formatting.Indented));
- }
- catch { }
- }
- /// <summary>加载全局事件配置(心跳 / 产品切换事件)。静态,与变量作用域无关。</summary>
- public static GlobalEventConfig LoadGlobalEventConfig()
- {
- try
- {
- var path = TeamAAS.PathHelper.GlobalEventConfigFile;
- if (!File.Exists(path)) return new GlobalEventConfig();
- var json = File.ReadAllText(path);
- return JsonConvert.DeserializeObject<GlobalEventConfig>(json) ?? new GlobalEventConfig();
- }
- catch { return new GlobalEventConfig(); }
- }
- }
- }
|