DataCollectionSysyem.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. using ServiceCenter.Extensions;
  2. using ServiceCenter.SqlSugars;
  3. using SqlSugar;
  4. using System.Collections.Concurrent;
  5. using System.ComponentModel;
  6. using System.Text;
  7. using WCS.Core;
  8. using WCS.Entity.Protocol.DataStructure;
  9. using WCS.Entity.Protocol.HUB;
  10. using WCS.Entity.Protocol.Robot;
  11. using WCS.Entity.Protocol.Station;
  12. using WCS.WorkEngineering.Worlds;
  13. namespace WCS.WorkEngineering.Systems
  14. {
  15. /// <summary>
  16. /// 数据采集系统
  17. /// </summary>
  18. [BelongTo(typeof(MainWorld))]
  19. [Description("数据采集系统")]
  20. public class DataCollectionSysyem : DeviceSystem<Device<IStation520>>
  21. {
  22. public static DeviceDataPack pack = new DeviceDataPack();
  23. private static object locker = new object();
  24. public DataCollectionSysyem()
  25. {
  26. }
  27. /// <summary>
  28. /// 所有设备数据
  29. /// Key 是不同设备所使用的类型 例如DeviceDataCollection<SRMData>
  30. /// value 不同设备的具体数据
  31. /// </summary>
  32. public static ConcurrentDictionary<string, DeviceData> AllDatas = new ConcurrentDictionary<string, DeviceData>();
  33. protected override bool ParallelDo => true;
  34. protected override bool SaveLogsToFile => true;
  35. public override bool Select(Device dev)
  36. {
  37. return dev.Code == "1";
  38. }
  39. public override void Do(Device<IStation520> objDev)
  40. {
  41. var db = new SqlSugarHelper().PLC;
  42. //var beforeTime = DateTime.Now.AddDays(-13);
  43. var beforeTime = DateTime.Now.AddDays(-1);
  44. var startTime = beforeTime.AddHours(-beforeTime.Hour).AddMinutes(-beforeTime.Minute).AddSeconds(-beforeTime.Second);
  45. var endTime = startTime.AddDays(+1).AddSeconds(-1);
  46. var robot521 = db.Queryable<WCS_Robot521>().Where(x => x.Frame >= startTime && x.Frame <= endTime).ToList().GroupBy(x => x.Code).ToList();
  47. #region 处理机械臂
  48. Parallel.ForEach(robot521, infos =>
  49. {
  50. // 获取第一条数据与最后一条数据
  51. var start = infos.OrderBy(x => x.Frame).First();
  52. var end = infos.OrderByDescending(x => x.Frame).First();
  53. //通过是否有最后一条数据来确定当前数据是否有分析存储过
  54. if (db.Queryable<DevRunInfo>().Any(x => x.Code == infos.Key && x.Frame == end.Frame && x.Alarm == null)) return;
  55. //获取前一天最后的一条数据
  56. var yesterEnd = db.Queryable<DevRunInfo>().Where(x => x.Code == infos.Key && x.Frame < endTime).OrderByDescending(x => x.Frame).First();
  57. List<DevRunInfo> runInfos = new List<DevRunInfo>();
  58. foreach (var info in infos)
  59. {
  60. if (info.Frame == start.Frame) //当天的第一条数据
  61. {
  62. if (yesterEnd == null) //如果没有前一天的最后一条数据,就有用当天的第一条数据状态做计算
  63. {
  64. runInfos.Add(new DevRunInfo()
  65. {
  66. Frame = info.Frame,
  67. Code = info.Code,
  68. RunMode = info.RobotMode.GetDescription(),
  69. RunStatus = info.RunStatus.GetDescription(),
  70. StartTime = startTime,
  71. EndTime = info.Frame,
  72. Duration = Convert.ToUInt64((info.Frame - startTime).TotalMilliseconds)
  73. });
  74. }
  75. else //如果有就用前一天的最后一条数据做计算
  76. {
  77. runInfos.Add(new DevRunInfo()
  78. {
  79. Frame = info.Frame,
  80. Code = info.Code,
  81. RunMode = yesterEnd.RunMode,
  82. RunStatus = yesterEnd.RunStatus,
  83. StartTime = startTime,
  84. EndTime = info.Frame,
  85. Duration = Convert.ToUInt64((info.Frame - startTime).TotalMilliseconds)
  86. });
  87. }
  88. }
  89. else if (info.Frame == end.Frame) //当天的最后一条数据
  90. {
  91. runInfos.Add(new DevRunInfo()
  92. {
  93. Frame = info.Frame,
  94. Code = info.Code,
  95. RunMode = info.RobotMode.GetDescription(),
  96. RunStatus = info.RunStatus.GetDescription(),
  97. StartTime = info.Frame,
  98. EndTime = endTime,
  99. Duration = Convert.ToUInt64((endTime - info.Frame).TotalMilliseconds)
  100. });
  101. }
  102. else //中间数据
  103. {
  104. if (start.RobotMode == info.RobotMode && start.RunStatus == info.RunStatus) continue;
  105. runInfos.Add(new DevRunInfo()
  106. {
  107. Frame = info.Frame,
  108. Code = info.Code,
  109. RunMode = start.RobotMode.GetDescription(),
  110. RunStatus = start.RunStatus.GetDescription(),
  111. StartTime = start.Frame,
  112. EndTime = info.Frame,
  113. Duration = Convert.ToUInt64((info.Frame - start.Frame).TotalMilliseconds)
  114. });
  115. start = info;
  116. }
  117. }
  118. db.Insertable(runInfos).ExecuteCommand();
  119. });
  120. var robot522 = db.Queryable<WCS_Robot522>().Where(x => x.Frame >= startTime && x.Frame <= endTime).ToList().GroupBy(x => x.Code).ToList();
  121. //Parallel.ForEach(robot522, infos =>
  122. //{
  123. // var start = infos.OrderBy(x => x.Frame).First();
  124. // var end = infos.OrderByDescending(x => x.Frame).First();
  125. // if (db.Queryable<DevRunInfo>().Any(x => x.Frame == end.Frame && x.RunMode == null && x.RunStatus == null)) return;
  126. // List<DevRunInfo> runInfos = new List<DevRunInfo>();
  127. // foreach (var info in infos)
  128. // {
  129. // if (info.Frame == start.Frame)
  130. // {
  131. // runInfos.Add(new DevRunInfo()
  132. // {
  133. // Frame = info.Frame,
  134. // Code = info.Code,
  135. // Alarm = info.Alarm.ToString(),
  136. // StartTime = startTime,
  137. // EndTime = info.Frame,
  138. // Duration = Convert.ToUInt64((info.Frame - startTime).Milliseconds)
  139. // });
  140. // }
  141. // else if (info.Frame == end.Frame)
  142. // {
  143. // runInfos.Add(new DevRunInfo()
  144. // {
  145. // Frame = info.Frame,
  146. // Code = info.Code,
  147. // Alarm = info.Alarm.ToString(),
  148. // StartTime = info.Frame,
  149. // EndTime = endTime,
  150. // Duration = Convert.ToUInt64((endTime - info.Frame).Milliseconds)
  151. // });
  152. // }
  153. // else
  154. // {
  155. // if (start.Alarm != info.Alarm)
  156. // {
  157. // runInfos.Add(new DevRunInfo()
  158. // {
  159. // Frame = info.Frame,
  160. // Code = info.Code,
  161. // Alarm = info.Alarm.ToString(),
  162. // StartTime = start.Frame,
  163. // EndTime = info.Frame,
  164. // Duration = Convert.ToUInt64((info.Frame - start.Frame).Milliseconds)
  165. // });
  166. // start = info;
  167. // }
  168. // }
  169. // }
  170. // db.Insertable(runInfos).ExecuteCommand();
  171. //});
  172. #endregion 处理机械臂
  173. var a = 1;
  174. //var sql = new StringBuilder();
  175. //try
  176. //{
  177. // var sw = new Stopwatch();
  178. // sw.Start();
  179. // var pack = new DeviceDataPack();
  180. // var frame = DateTime.Now;
  181. // pack.Frame = World.Frame;
  182. // sql.Append("INSERT INTO ");
  183. // var ps = pack.GetType().GetProperties().OrderBy(x => x.Name);
  184. // var db = new SqlSugarHelper().PLC;
  185. // Parallel.ForEach(ps, p =>
  186. // {
  187. // if (!p.PropertyType.IsArray) return;
  188. // var t = p.PropertyType.GetElementType();
  189. // if (t.IsGenericType)
  190. // {
  191. // var entType = t.GetGenericArguments()[0];
  192. // var protType = GetProtocolType(entType);
  193. // if (protType == null) return;
  194. // var devices = Device.All.Where(v => v.HasProtocol(protType));
  195. // List<object> arr = new List<object>();
  196. // Parallel.ForEach(devices, x =>
  197. // {
  198. // try
  199. // {
  200. // var protObj = x.Protocol(protType, World) as ProtocolProxyBase;
  201. // if (protObj.Frame < DateTime.Now.AddYears(-24))
  202. // {
  203. // protObj.Frame = frame;
  204. // }
  205. // if (protObj.Db.failed)
  206. // {
  207. // return;
  208. // }
  209. // var obj = Activator.CreateInstance(t);
  210. // t.GetProperty("Code").SetValue(obj, x.Code);
  211. // var value = WCS.Core.Extentions.Copy(protObj, entType, frame);
  212. // t.GetProperty("Data").SetValue(obj, value);
  213. // t.GetProperty("Frame").SetValue(obj, protObj.Frame);
  214. // entType.GetProperty("Code").SetValue(value, x.Code);
  215. // arr.Add(obj);
  216. // }
  217. // catch
  218. // {
  219. // }
  220. // });
  221. // var m = typeof(Enumerable).GetMethod("OfType",
  222. // System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
  223. // m = m.MakeGenericMethod(t);
  224. // var arr2 = m.Invoke(null, new object[] { arr });
  225. // m = typeof(Enumerable).GetMethod("ToArray",
  226. // System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
  227. // m = m.MakeGenericMethod(t);
  228. // var arr3 = m.Invoke(null, new object[] { arr2 });
  229. // p.SetValue(pack, arr3);
  230. // }
  231. // });
  232. // var sw3 = new Stopwatch();
  233. // sw3.Start();
  234. // //开始存储设备信息
  235. // RedisHub.Monitor.RPush("Packs", pack);
  236. // if (RedisHub.Monitor.LLen("Packs") > 50000)
  237. // {
  238. // RedisHub.Monitor.LTrim("Packs", 5000, -1);
  239. // }
  240. // #region 存储设备报警信息
  241. // List<EquipmentAlarm> equipmentAlarms = new List<EquipmentAlarm>();
  242. // equipmentAlarms.AddRange(pack.Robot522.Where(x => x.Data.Alarm != 0).Select(x => new EquipmentAlarm()
  243. // {
  244. // Code = x.Code,
  245. // Msg = x.Data.Alarm.ToString(),
  246. // Time = x.Frame
  247. // }));
  248. // equipmentAlarms.AddRange(pack.SRM537.Where(x => x.Data.Alarm != 0).Select(x => new EquipmentAlarm()
  249. // {
  250. // Code = x.Code,
  251. // Msg = x.Data.Alarm.ToString(),
  252. // Time = x.Frame
  253. // }));
  254. // equipmentAlarms.AddRange(pack.Station523.Where(x => x.Data.Alarm != 0).Select(x => new EquipmentAlarm()
  255. // {
  256. // Code = x.Code,
  257. // Msg = x.Data.Alarm.ToString(),
  258. // Time = x.Frame
  259. // }));
  260. // equipmentAlarms.AddRange(pack.Truss523.Where(x => x.Data.Alarm != 0).Select(x => new EquipmentAlarm()
  261. // {
  262. // Code = x.Code,
  263. // Msg = x.Data.Alarm.ToString(),
  264. // Time = x.Frame
  265. // }));
  266. // RedisHub.Default.Set(nameof(EquipmentAlarm), JsonConvert.SerializeObject(equipmentAlarms));
  267. // #endregion 存储设备报警信息
  268. // #region 存储设备状态信息
  269. // List<EquipmentStatus> equipmentStatus = new List<EquipmentStatus>();
  270. // equipmentStatus.AddRange(pack.RGV521.Where(x => x.Data.WorkMode != 0).Select(x => new EquipmentStatus()
  271. // {
  272. // Code = x.Code,
  273. // con = x.Data.WorkMode.GetDescription(),
  274. // Status = x.Data.WorkMode.ToInt(),
  275. // Time = x.Frame
  276. // }));
  277. // equipmentStatus.AddRange(pack.Robot521.Where(x => x.Data.RobotMode != 0).Select(x => new EquipmentStatus()
  278. // {
  279. // Code = x.Code,
  280. // con = x.Data.RobotMode.GetDescription(),
  281. // Status = x.Data.RobotMode.ToInt(),
  282. // Time = x.Frame
  283. // }));
  284. // equipmentStatus.AddRange(pack.SRM521.Where(x => x.Data.AutoStatus != 0).Select(x => new EquipmentStatus()
  285. // {
  286. // Code = x.Code,
  287. // con = x.Data.AutoStatus.GetDescription(),
  288. // Status = x.Data.AutoStatus.ToInt(),
  289. // Time = x.Frame
  290. // }));
  291. // equipmentStatus.AddRange(pack.Station521.Where(x => x.Data.Mode != 0).Select(x => new EquipmentStatus()
  292. // {
  293. // Code = x.Code,
  294. // con = x.Data.Mode.GetDescription(),
  295. // Status = x.Data.Mode.ToInt(),
  296. // Time = x.Frame
  297. // }));
  298. // equipmentStatus.AddRange(pack.Truss521.Where(x => x.Data.Status != 0).Select(x => new EquipmentStatus()
  299. // {
  300. // Code = x.Code,
  301. // con = x.Data.Status.GetDescription(),
  302. // Status = x.Data.Status.ToInt(),
  303. // Time = x.Frame
  304. // }));
  305. // RedisHub.Default.Set(nameof(EquipmentStatus), JsonConvert.SerializeObject(equipmentStatus));
  306. // #endregion 存储设备状态信息
  307. // sw3.Stop();
  308. // World.Log($"redis存储耗时:{sw3.ElapsedMilliseconds}");
  309. // var sw4 = new Stopwatch();
  310. // sw4.Start();
  311. // Parallel.ForEach(pack.GetType().GetProperties().OrderBy(x => x.Name), ps =>
  312. // {
  313. // if (ps.PropertyType == typeof(ProtocolData<WCS_BCR80>[]))
  314. // {
  315. // if (pack.BCR80.Any()) db.Insertable(pack.BCR80.Select(x => x.Data).ToList()).ExecuteCommand();
  316. // }
  317. // else if (ps.PropertyType == typeof(ProtocolData<WCS_BCR81>[]))
  318. // {
  319. // if (pack.BCR81.Any()) db.Insertable(pack.BCR81.Select(x => x.Data).ToList()).ExecuteCommand();
  320. // }
  321. // else if (ps.PropertyType == typeof(ProtocolData<WCS_BCR83>[]))
  322. // {
  323. // if (pack.BCR83.Any()) db.Insertable(pack.BCR83.Select(x => x.Data).ToList()).ExecuteCommand();
  324. // }
  325. // else if (ps.PropertyType == typeof(ProtocolData<WCS_RGV520>[]))
  326. // {
  327. // if (pack.RGV520.Any()) db.Insertable(pack.RGV520.Select(x => x.Data).ToList()).ExecuteCommand();
  328. // }
  329. // else if (ps.PropertyType == typeof(ProtocolData<WCS_RGV521>[]))
  330. // {
  331. // if (pack.RGV521.Any()) db.Insertable(pack.RGV521.Select(x => x.Data).ToList()).ExecuteCommand();
  332. // }
  333. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Robot520>[]))
  334. // {
  335. // if (pack.Robot520.Any()) db.Insertable(pack.Robot520.Select(x => x.Data).ToList()).ExecuteCommand();
  336. // }
  337. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Robot521>[]))
  338. // {
  339. // if (pack.Robot521.Any()) db.Insertable(pack.Robot521.Select(x => x.Data).ToList()).ExecuteCommand();
  340. // }
  341. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Robot522>[]))
  342. // {
  343. // if (pack.Robot522.Any()) db.Insertable(pack.Robot522.Select(x => x.Data).ToList()).ExecuteCommand();
  344. // }
  345. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Robot530>[]))
  346. // {
  347. // if (pack.Robot530.Any()) db.Insertable(pack.Robot530.Select(x => x.Data).ToList()).ExecuteCommand();
  348. // }
  349. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Robot531>[]))
  350. // {
  351. // if (pack.Robot531.Any()) db.Insertable(pack.Robot531.Select(x => x.Data).ToList()).ExecuteCommand();
  352. // }
  353. // else if (ps.PropertyType == typeof(ProtocolData<WCS_SRM520>[]))
  354. // {
  355. // if (pack.SRM520.Any()) db.Insertable(pack.SRM520.Select(x => x.Data).ToList()).ExecuteCommand();
  356. // }
  357. // else if (ps.PropertyType == typeof(ProtocolData<WCS_SRM521>[]))
  358. // {
  359. // if (pack.SRM521.Any()) db.Insertable(pack.SRM521.Select(x => x.Data).ToList()).ExecuteCommand();
  360. // }
  361. // else if (ps.PropertyType == typeof(ProtocolData<WCS_SRM537>[]))
  362. // {
  363. // if (pack.SRM537.Any()) db.Insertable(pack.SRM537.Select(x => x.Data).ToList()).ExecuteCommand();
  364. // }
  365. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Station520>[]))
  366. // {
  367. // if (pack.Station520.Any()) db.Insertable(pack.Station520.Select(x => x.Data).ToList()).ExecuteCommand();
  368. // }
  369. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Station521>[]))
  370. // {
  371. // if (pack.Station521.Any()) db.Insertable(pack.Station521.Select(x => x.Data).ToList()).ExecuteCommand();
  372. // }
  373. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Station523>[]))
  374. // {
  375. // if (pack.Station523.Any()) db.Insertable(pack.Station523.Select(x => x.Data).ToList()).UseParameter().ExecuteCommand();
  376. // }
  377. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Station524>[]))
  378. // {
  379. // if (pack.Station524.Any()) db.Insertable(pack.Station524.Select(x => x.Data).ToList()).UseParameter().ExecuteCommand();
  380. // }
  381. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Station525>[]))
  382. // {
  383. // if (pack.Station525.Any()) db.Insertable(pack.Station525.Select(x => x.Data).ToList()).ExecuteCommand();
  384. // }
  385. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Station90>[]))
  386. // {
  387. // if (pack.Station90.Any()) db.Insertable(pack.Station90.Select(x => x.Data).ToList()).ExecuteCommand();
  388. // }
  389. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Station91>[]))
  390. // {
  391. // if (pack.Station91.Any()) db.Insertable(pack.Station91.Select(x => x.Data).ToList()).ExecuteCommand();
  392. // }
  393. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Truss520>[]))
  394. // {
  395. // if (pack.Truss520.Any()) db.Insertable(pack.Truss520.Select(x => x.Data).ToList()).ExecuteCommand();
  396. // }
  397. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Truss521>[]))
  398. // {
  399. // if (pack.Truss521.Any()) db.Insertable(pack.Truss521.Select(x => x.Data).ToList()).ExecuteCommand();
  400. // }
  401. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Truss523>[]))
  402. // {
  403. // if (pack.Truss523.Any()) db.Insertable(pack.Truss523.Select(x => x.Data).ToList()).ExecuteCommand();
  404. // }
  405. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Truss530>[]))
  406. // {
  407. // if (pack.Truss530.Any()) db.Insertable(pack.Truss530.Select(x => x.Data).ToList()).ExecuteCommand();
  408. // }
  409. // else if (ps.PropertyType == typeof(ProtocolData<WCS_Truss531>[]))
  410. // {
  411. // if (pack.Truss531.Any()) db.Insertable(pack.Truss531.Select(x => x.Data).ToList()).ExecuteCommand();
  412. // }
  413. // });
  414. // sw4.Stop();
  415. // World.Log($"执行SQL耗时:{sw4.ElapsedMilliseconds}");
  416. // sw.Stop();
  417. // World.Log($"数据采集耗时:{sw.ElapsedMilliseconds}");
  418. //}
  419. //catch (Exception e)
  420. //{
  421. // World.Log($"错误内容:{e.Message}");
  422. //}
  423. }
  424. public void Set(StringBuilder sql, string cSql)
  425. {
  426. lock (locker)
  427. {
  428. sql.Append(cSql);
  429. }
  430. }
  431. private Type GetProtocolType(Type source)
  432. {
  433. var t = source.GetInterfaces().FirstOrDefault(v => v.GetInterfaces().Any(d => d.Name == "IProtocol"));
  434. var t1 = source.GetInterfaces().FirstOrDefault(v => v.GetInterfaces().Any(d => d.Name == "IProtocol"));
  435. return t;
  436. }
  437. private object AppendLock = new object();
  438. public StringBuilder Append(StringBuilder sql, string value)
  439. {
  440. lock (AppendLock)
  441. {
  442. return sql.Append(value);
  443. }
  444. }
  445. public string GetString(string value)
  446. {
  447. return value.Replace("INSERT INTO ", "")
  448. .Replace(",N'", ",'")
  449. .Replace("\0", "")
  450. .Replace("wcs_", "")
  451. .Replace("(N'", "('") + "\r";
  452. }
  453. }
  454. /// <summary>
  455. /// 设备报警
  456. /// </summary>
  457. public class EquipmentAlarm
  458. {
  459. /// <summary>
  460. /// 设备号
  461. /// </summary>
  462. public string Code { get; set; }
  463. /// <summary>
  464. /// 内容
  465. /// </summary>
  466. public string Msg { get; set; }
  467. /// <summary>
  468. /// 时间
  469. /// </summary>
  470. public DateTime Time { get; set; }
  471. }
  472. /// <summary>
  473. /// 设备状态信息
  474. /// </summary>
  475. public class EquipmentStatus
  476. {
  477. /// <summary>
  478. /// 设备号
  479. /// </summary>
  480. public string Code { get; set; }
  481. /// <summary>
  482. /// 内容
  483. /// </summary>
  484. public string con { get; set; }
  485. /// <summary>
  486. /// 内容
  487. /// </summary>
  488. public int Status { get; set; }
  489. /// <summary>
  490. /// 时间
  491. /// </summary>
  492. public DateTime Time { get; set; }
  493. }
  494. /// <summary>
  495. ///
  496. /// </summary>
  497. /// <typeparam name="T"></typeparam>
  498. public class Quest<T>
  499. {
  500. public T Data { get; set; }
  501. }
  502. }