ModLoader.vb 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791
  1. Public Module ModLoader
  2. '各类加载器
  3. ''' <summary>
  4. ''' 加载器的统一基类。
  5. ''' </summary>
  6. Public MustInherit Class LoaderBase
  7. Implements ILoadingTrigger
  8. Public ReadOnly Property IsLoader As Boolean = True Implements ILoadingTrigger.IsLoader
  9. '基础属性
  10. ''' <summary>
  11. ''' 加载器的标识编号。
  12. ''' </summary>
  13. Public Uuid As Integer = GetUuid()
  14. ''' <summary>
  15. ''' 加载器的名称。
  16. ''' </summary>
  17. Public Name As String = "未命名任务 " & Uuid & "#"
  18. ''' <summary>
  19. ''' 用于状态改变检测的同步锁。
  20. ''' </summary>
  21. Public ReadOnly LockState As New Object
  22. ''' <summary>
  23. ''' 父加载器。
  24. ''' </summary>
  25. Public Parent As LoaderBase = Nothing
  26. ''' <summary>
  27. ''' 最上级的加载器。
  28. ''' </summary>
  29. Public ReadOnly Property RealParent As LoaderBase
  30. Get
  31. Try
  32. RealParent = Parent
  33. While RealParent IsNot Nothing AndAlso RealParent.Parent IsNot Nothing
  34. RealParent = RealParent.Parent
  35. End While
  36. Catch ex As Exception
  37. Logger.Error(ex, $"获取父加载器失败({Name})")
  38. Return Nothing
  39. End Try
  40. End Get
  41. End Property
  42. Public Overridable Sub InitParent(Parent As LoaderBase)
  43. Me.Parent = Parent
  44. End Sub
  45. '事件
  46. ''' <summary>
  47. ''' 当状态改变时,在工作线程触发代码。在添加事件后,必须将 HasOnStateChangedThread 设为 True。
  48. ''' </summary>
  49. Public Event OnStateChangedThread(Loader As LoaderBase, NewState As LoadState, OldState As LoadState)
  50. Public HasOnStateChangedThread As Boolean = False
  51. ''' <summary>
  52. ''' 当状态改变时,在 UI 线程触发代码。
  53. ''' </summary>
  54. Public Event OnStateChangedUi(Loader As LoaderBase, NewState As LoadState, OldState As LoadState)
  55. ''' <summary>
  56. ''' 简易的在 UI 线程添加触发事件的方式。主要用于在新建 Loader 时直接使用 With 绑定事件,以及进行老代码兼容。
  57. ''' </summary>
  58. Public WriteOnly Property OnStateChanged As Action(Of LoaderBase)
  59. Set(value As Action(Of LoaderBase))
  60. AddHandler OnStateChangedUi, Sub(Loader As LoaderBase, NewState As LoadState, OldState As LoadState) value(Loader)
  61. End Set
  62. End Property
  63. ''' <summary>
  64. ''' 在加载器目标事件执行完成,加载器状态即将变为 Finish 时调用。可以视为扩展加载器目标事件。
  65. ''' </summary>
  66. Public Event PreviewFinish(Loader As LoaderBase)
  67. Protected Sub RaisePreviewFinish()
  68. RaiseEvent PreviewFinish(Me)
  69. End Sub
  70. '状态监控
  71. ''' <summary>
  72. ''' 加载器的状态。
  73. ''' </summary>
  74. Public Property State As LoadState
  75. Get
  76. Return _State
  77. End Get
  78. Set(value As LoadState)
  79. If _State = value Then Return
  80. Dim OldState = _State
  81. If value = LoadState.Finished AndAlso Settings.Get(Of Boolean)("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000))
  82. _State = value
  83. If value = LoadState.Canceled Then CancellationTokenSource?.Cancel()
  84. Logger.Log($"加载器 {[GetType].Name} {Name} 状态改变:{value}", If(value = LoadState.Failed, LogLevel.Error, LogLevel.Info))
  85. '实现 ILoadingTrigger 接口与 OnStateChanged 回调
  86. RunInUi(
  87. Sub()
  88. Select Case value
  89. Case LoadState.Loading
  90. LoadingState = MyLoading.MyLoadingState.Run
  91. Case LoadState.Failed
  92. LoadingState = MyLoading.MyLoadingState.Error
  93. Case Else
  94. LoadingState = MyLoading.MyLoadingState.Stop
  95. End Select
  96. RaiseEvent OnStateChangedUi(Me, value, OldState)
  97. End Sub)
  98. If HasOnStateChangedThread Then RunInThread(Sub() RaiseEvent OnStateChangedThread(Me, value, OldState))
  99. End Set
  100. End Property
  101. Private _State As LoadState = LoadState.Waiting
  102. Public Property LoadingState As MyLoading.MyLoadingState Implements ILoadingTrigger.LoadingState
  103. Get
  104. Return _LoadingState
  105. End Get
  106. Set(value As MyLoading.MyLoadingState)
  107. If _LoadingState = value Then Return
  108. Dim OldState = _LoadingState
  109. _LoadingState = value
  110. RaiseEvent LoadingStateChanged(value, OldState)
  111. End Set
  112. End Property
  113. Private _LoadingState As MyLoading.MyLoadingState = MyLoading.MyLoadingState.Stop
  114. Public Event LoadingStateChanged(NewState As MyLoading.MyLoadingState, OldState As MyLoading.MyLoadingState) Implements ILoadingTrigger.LoadingStateChanged
  115. ''' <summary>
  116. ''' 若加载器出错,可提供给外部参考的异常。
  117. ''' </summary>
  118. Public Property [Error] As Exception = Nothing
  119. ''' <summary>
  120. ''' 使用 LoaderCombo 加载时,该任务是否会阻碍后续任务的进行。
  121. ''' </summary>
  122. Public Block As Boolean = True
  123. ''' <summary>
  124. ''' 该加载器是否显示在列表中。
  125. ''' </summary>
  126. Public Show As Boolean = True
  127. ''' <summary>
  128. ''' 当前加载器是否由 IsForceRestart 强制调起。
  129. ''' 这个属性自身不会干任何事,而是提供给加载器执行的函数,使得加载器调用另一个加载器时,可以继承强制重启属性。
  130. ''' </summary>
  131. Public IsForceRestarting As Boolean = False
  132. '进度监控
  133. ''' <summary>
  134. ''' 加载器的执行进度,为 0 至 1 的小数。
  135. ''' </summary>
  136. Public Overridable Property Progress As Double
  137. Get
  138. Select Case State
  139. Case LoadState.Waiting
  140. Return 0
  141. Case LoadState.Loading
  142. Return If(_Progress = -1, 0, _Progress)
  143. Case Else
  144. Return 1
  145. End Select
  146. End Get
  147. Set(value As Double)
  148. If _Progress = value Then Return
  149. Dim OldValue = _Progress
  150. _Progress = value
  151. RaiseEvent ProgressChanged(value, OldValue)
  152. End Set
  153. End Property
  154. Private _Progress As Double = -1
  155. Public Event ProgressChanged(NewProgress As Double, OldProgress As Double) Implements ILoadingTrigger.ProgressChanged
  156. ''' <summary>
  157. ''' 创建一个 ProgressProvider,在其进度改变时,并将其进度单向映射到此加载器上。
  158. ''' 这是 Loader 对新框架的兼容层。
  159. ''' </summary>
  160. Public Function CreateSyncProgressProvider(Optional FromPercentage As Double = 0, Optional ToPercentage As Double = 1) As ProgressProvider
  161. Dim Provider As New ProgressProvider
  162. AddHandler Provider.ProgressChanged,
  163. Sub(ChangedProgress)
  164. Dim RawProgress = Provider.GetRaw()
  165. Progress = FromPercentage + (RawProgress.actual + RawProgress.skiped) * (ToPercentage - FromPercentage)
  166. End Sub
  167. Return Provider
  168. End Function
  169. ''' <summary>
  170. ''' 计算总进度时的权重。它应该为预计时间(秒)。
  171. ''' </summary>
  172. Public Property ProgressWeight As Double = 1
  173. '状态变化
  174. Public MustOverride Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
  175. Public MustOverride Sub Cancel()
  176. Public MustOverride Sub Failed(Ex As Exception)
  177. Private CancellationTokenSource As CancellationTokenSource
  178. ''' <summary>
  179. ''' 创建一个随此加载器取消的取消令牌;令牌取消时也会调用此加载器的 Cancel。
  180. ''' 这是 Loader 对新框架的兼容层。
  181. ''' </summary>
  182. Public Function CreateCancellationToken() As CancellationToken
  183. SyncLock LockState
  184. If CancellationTokenSource Is Nothing OrElse CancellationTokenSource.IsCancellationRequested Then
  185. CancellationTokenSource = New CancellationTokenSource
  186. CancellationTokenSource.Token.Register(AddressOf Cancel)
  187. End If
  188. If State = LoadState.Canceled Then CancellationTokenSource.Cancel()
  189. Return CancellationTokenSource.Token
  190. End SyncLock
  191. End Function
  192. '等待结束
  193. Public Const WaitForExitTimeoutMessage As String = "等待加载器执行超时。"
  194. ''' <summary>
  195. ''' 无限期地等待加载器完成,直到结束或抛出异常。若加载器尚未开始,则会开始执行。
  196. ''' </summary>
  197. Public Sub WaitForExit(Optional Input As Object = Nothing, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional IsForceRestart As Boolean = False,
  198. Optional c As CancellationToken = Nothing, Optional p As ProgressProvider = Nothing)
  199. Start(Input, IsForceRestart)
  200. Do While State = LoadState.Loading
  201. If LoaderToSyncProgress IsNot Nothing Then LoaderToSyncProgress.Progress = Progress
  202. p?.Set(Progress)
  203. c.ThrowIfCancellationRequested()
  204. Thread.Sleep(10)
  205. Loop
  206. If State = LoadState.Finished Then
  207. Return
  208. ElseIf State = LoadState.Canceled Then
  209. Throw New OperationCanceledException("加载器执行已中断")
  210. ElseIf IsNothing([Error]) Then
  211. Throw New Exception("未知错误!")
  212. Else
  213. Throw New Exception([Error].Message, [Error]) '保留调用堆栈,同时不影响信息输出与单元测试
  214. End If
  215. End Sub
  216. ''' <summary>
  217. ''' 等待加载器完成,直到结束、抛出异常或超时。若加载器尚未开始,则会开始执行。
  218. ''' </summary>
  219. ''' <param name="Timeout">等待的超时时间,以毫秒为单位。</param>
  220. ''' <param name="TimeoutMessage">若执行超时,将会抛出的异常信息。</param>
  221. Public Sub WaitForExitTime(Timeout As Integer, Optional Input As Object = Nothing, Optional TimeoutMessage As String = WaitForExitTimeoutMessage, Optional LoaderToSyncProgress As Object = Nothing, Optional IsForceRestart As Boolean = False)
  222. Start(Input, IsForceRestart)
  223. Do While State = LoadState.Loading
  224. If LoaderToSyncProgress IsNot Nothing Then LoaderToSyncProgress.Progress = Progress
  225. Thread.Sleep(10)
  226. Timeout -= 10
  227. If Timeout < 0 Then Throw New TimeoutException(TimeoutMessage)
  228. Loop
  229. If State = LoadState.Finished Then
  230. Return
  231. ElseIf State = LoadState.Canceled Then
  232. Throw New OperationCanceledException("加载器执行已中断")
  233. ElseIf IsNothing([Error]) Then
  234. Throw New Exception("未知错误!")
  235. Else
  236. Throw [Error]
  237. End If
  238. End Sub
  239. '相同重载
  240. Public Overrides Function Equals(obj As Object) As Boolean
  241. Dim base = TryCast(obj, LoaderBase)
  242. Return base IsNot Nothing AndAlso Uuid = base.Uuid
  243. End Function
  244. End Class
  245. ''' <summary>
  246. ''' 用于异步执行并监控单一函数的加载器。
  247. ''' </summary>
  248. Public Class LoaderTask(Of TIn, TOut)
  249. Inherits LoaderBase
  250. '线程设定
  251. Protected Friend ThreadPriority As ThreadPriority
  252. '执行事件
  253. Protected Friend LoadDelegate As Action(Of LoaderTask(Of TIn, TOut))
  254. Protected Friend InputDelegate As Func(Of Object)
  255. '状态指示
  256. ''' <summary>
  257. ''' 当前执行线程是否应当中断。只应用在加载器的工作线程中判断,不可跨线程调用。
  258. ''' </summary>
  259. Public ReadOnly Property IsCanceled As Boolean
  260. Get
  261. Return IsCanceledWithThread(Thread.CurrentThread)
  262. End Get
  263. End Property
  264. ''' <summary>
  265. ''' 当前执行线程是否应当中断。需要手动提供加载器线程,用于需要跨线程检查的情况。
  266. ''' </summary>
  267. Public Function IsCanceledWithThread(Thread As Thread) As Boolean
  268. Return LastRunningThread Is Nothing OrElse Not ReferenceEquals(Thread, LastRunningThread) OrElse State = LoadState.Canceled
  269. End Function
  270. ''' <summary>
  271. ''' 在输入相同时使用原有结果的超时,单位为毫秒。
  272. ''' </summary>
  273. Public ReloadTimeout As Integer = -1
  274. ''' <summary>
  275. ''' 上次完成加载时的时间。
  276. ''' </summary>
  277. Public LastFinishedTime As Long = 0
  278. ''' <summary>
  279. ''' 最后一次运行加载器的线程。可能为 Nothing,或线程已结束。
  280. ''' </summary>
  281. Public LastRunningThread As Thread = Nothing
  282. '输入输出
  283. Public Input As TIn = Nothing
  284. Public Output As TOut = Nothing
  285. '获取输入
  286. Public Function StartGetInput(Optional Input As TIn = Nothing, Optional InputDelegate As Func(Of TIn) = Nothing) As TIn 'InputDelegate 参数存在匿名调用
  287. If InputDelegate Is Nothing Then InputDelegate = Me.InputDelegate
  288. Dim NewInput As TIn = Nothing '若 InputType 不能为 Nothing,则会导致 Input Is Nothing 永远失败,因此需要额外判断
  289. If (Input Is Nothing OrElse (NewInput IsNot Nothing AndAlso Input.Equals(NewInput))) AndAlso InputDelegate IsNot Nothing Then
  290. RunInUiWait(Sub() Input = InputDelegate())
  291. End If
  292. Return Input
  293. End Function
  294. '代码执行
  295. Public Function ShouldStart(ByRef Input As TIn, Optional IsForceRestart As Boolean = False, Optional IgnoreReloadTimeout As Boolean = False) As Boolean
  296. '获取输入
  297. Try
  298. Input = StartGetInput(Input)
  299. Catch ex As Exception
  300. Logger.Error(ex, $"加载输入获取失败({Name})", LogBehavior.Toast)
  301. [Error] = ex
  302. SyncLock LockState
  303. State = LoadState.Failed
  304. End SyncLock
  305. End Try
  306. '检验输入以确定情况
  307. If IsForceRestart Then Return True '强制要求重启
  308. If ((Input Is Nothing) <> (Me.Input Is Nothing)) OrElse (Input IsNot Nothing AndAlso Not Input.Equals(Me.Input)) Then Return True '输入不同
  309. If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束…
  310. (IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse
  311. LastFinishedTime = 0 OrElse GetTimeMs() - LastFinishedTime < ReloadTimeout) Then '…且没有超时…
  312. Return False '…则不重试
  313. Else
  314. Return True '否则需要重启
  315. End If
  316. End Function
  317. Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
  318. '确认是否开始加载
  319. If ShouldStart(Input, IsForceRestart) Then
  320. '输入不同或失败,开始加载
  321. If State = LoadState.Loading Then TriggerThreadInterrupt()
  322. Me.Input = Input
  323. SyncLock LockState
  324. State = LoadState.Loading
  325. Progress = -1
  326. End SyncLock
  327. Else
  328. Return
  329. End If
  330. LastRunningThread = New Thread(
  331. Sub()
  332. Try
  333. IsForceRestarting = IsForceRestart
  334. Logger.Trace(Function() $"加载线程 {Name} 已{If(IsForceRestarting, "强制", "")}启动")
  335. LoadDelegate(Me)
  336. If IsCanceled Then
  337. Logger.Warn($"加载线程 {Name} 已中断但线程正常运行至结束,输出被弃用(最新线程:{If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId)})")
  338. Return
  339. End If
  340. Logger.Trace(Function() $"加载线程 {Name} 已完成")
  341. RaisePreviewFinish()
  342. State = LoadState.Finished
  343. LastFinishedTime = GetTimeMs() '未中断,本次输出有效
  344. Catch ex As Exception
  345. If ex.IsCanceled Then
  346. '如果线程是因为判断到 IsCanceled 而提前中止,则代表已有新线程被重启,此时不应当改为 Canceled
  347. '如果线程是在没有 IsCanceled 时手动引发了取消异常,则代表没有重启线程,这通常代表用户手动取消,应当改为 Canceled
  348. Logger.Trace(ex, $"加载线程 {Name} 已取消,当前已完成 {Math.Round(Progress * 100)}%")
  349. If Not IsCanceled Then State = LoadState.Canceled
  350. Else
  351. Failed(ex)
  352. End If
  353. End Try
  354. End Sub) With {.Name = "L/" & Name, .Priority = ThreadPriority}
  355. Try
  356. LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsCanceled
  357. Catch ex As ThreadStateException '若遇到偶发的 “线程正在运行或被终止”,则等待后重试
  358. Thread.Sleep(500)
  359. LastRunningThread.Start()
  360. End Try
  361. End Sub
  362. Public Overrides Sub Failed(ex As Exception)
  363. [Error] = ex
  364. SyncLock LockState
  365. If IsCanceled OrElse State >= LoadState.Finished Then Return
  366. State = LoadState.Failed
  367. End SyncLock
  368. Logger.Warn(ex, $"加载线程 {Name} 出错,已完成 {Math.Round(Progress * 100)}%")
  369. TriggerThreadInterrupt()
  370. End Sub
  371. Public Overrides Sub Cancel()
  372. SyncLock LockState
  373. If State <> LoadState.Loading Then Return
  374. State = LoadState.Canceled
  375. End SyncLock
  376. TriggerThreadInterrupt()
  377. End Sub
  378. Private Sub TriggerThreadInterrupt()
  379. If LastRunningThread Is Nothing Then Return
  380. If LastRunningThread.IsAlive Then
  381. LastRunningThread.Interrupt()
  382. Logger.Trace(Function() $"加载线程 {Name} 已中断")
  383. End If
  384. LastRunningThread = Nothing
  385. End Sub
  386. Public Sub New()
  387. '仅仅是为了避免一些智障报错(继承类必须重写 New 的情况)
  388. End Sub
  389. Public Sub New(Name As String, LoadDelegate As Action(Of LoaderTask(Of TIn, TOut)), Optional InputDelegate As Func(Of TIn) = Nothing, Optional Priority As ThreadPriority = ThreadPriority.Normal)
  390. Me.Name = Name
  391. Me.LoadDelegate = LoadDelegate
  392. Me.InputDelegate = InputDelegate
  393. ThreadPriority = Priority
  394. End Sub
  395. End Class
  396. ''' <summary>
  397. ''' 新 Worker 对老 Loader 的兼容包装类。
  398. ''' </summary>
  399. Public Class LoaderWorker
  400. Inherits LoaderBase
  401. Public WithEvents Worker As IWorker
  402. Public Sub New(Name As String, Worker As IWorker)
  403. Me.Name = Name
  404. Me.Worker = Worker
  405. AddHandler Worker.Progress.ProgressChanged, Sub(Raw) Progress = Raw.actual + Raw.skiped
  406. End Sub
  407. Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
  408. SyncLock LockState
  409. State = LoadState.Loading
  410. End SyncLock
  411. Logger.Trace(Function() $"Worker {Name} 已启动")
  412. IsForceRestarting = IsForceRestart
  413. Worker.Start()
  414. End Sub
  415. Private Sub Worker_Succeeded() Handles Worker.Succeeded
  416. Logger.Trace(Function() $"Worker {Name} 已完成")
  417. RaisePreviewFinish()
  418. SyncLock LockState
  419. State = LoadState.Finished
  420. End SyncLock
  421. End Sub
  422. Public Overrides Sub Cancel() Handles Worker.Canceled
  423. SyncLock LockState
  424. If State <> LoadState.Loading Then Return
  425. State = LoadState.Canceled
  426. End SyncLock
  427. Logger.Trace(Function() $"Worker {Name} 已取消,当前已完成 {Math.Round(Progress * 100)}%")
  428. Worker.Cancel()
  429. End Sub
  430. Public Overrides Sub Failed(Ex As Exception) Handles Worker.Failed
  431. SyncLock LockState
  432. If State >= LoadState.Finished Then Return
  433. State = LoadState.Failed
  434. End SyncLock
  435. Logger.Warn(Ex, $"Worker {Name} 出错,已完成 {Math.Round(Progress * 100)}%")
  436. [Error] = Ex
  437. Worker.Cancel()
  438. End Sub
  439. End Class
  440. ''' <summary>
  441. ''' 支持多个加载器连续运作的复合加载器。
  442. ''' </summary>
  443. Public Class LoaderCombo(Of InputType)
  444. Inherits LoaderBase
  445. Public Overrides Property Progress() As Double
  446. Get
  447. Select Case State
  448. Case LoadState.Waiting
  449. Return 0
  450. Case LoadState.Loading
  451. Dim Total As Double = 0, Finished As Double = 0
  452. For Each Loader In Loaders
  453. Total += Loader.ProgressWeight
  454. Finished += Loader.ProgressWeight * Loader.Progress
  455. Next
  456. If Total = 0 Then Return 0
  457. Return Finished / Total
  458. Case Else
  459. Return 1
  460. End Select
  461. End Get
  462. Set(value As Double)
  463. Throw New Exception("多重加载器不支持设置进度")
  464. End Set
  465. End Property
  466. Public Loaders As New List(Of LoaderBase)
  467. Public Input As InputType
  468. Public Sub New(Name As String, Loaders As IEnumerable(Of LoaderBase))
  469. Me.Loaders.Clear()
  470. For Each Loader As LoaderBase In Loaders
  471. If Loader IsNot Nothing Then
  472. Me.Loaders.Add(Loader)
  473. AddHandler Loader.OnStateChangedThread, AddressOf SubTaskStateChanged
  474. Loader.HasOnStateChangedThread = True
  475. End If
  476. Next
  477. InitParent(Nothing)
  478. Me.Name = Name
  479. End Sub
  480. Public Overrides Sub InitParent(Parent As LoaderBase)
  481. Me.Parent = Parent
  482. For Each Loader In Loaders
  483. Loader.InitParent(Me)
  484. Next
  485. End Sub
  486. Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
  487. IsForceRestarting = IsForceRestart
  488. '改变状态
  489. SyncLock LockState
  490. If State = LoadState.Loading Then
  491. Return
  492. Else
  493. State = LoadState.Loading
  494. End If
  495. End SyncLock
  496. '启动加载
  497. Me.Input = Input
  498. If IsForceRestart Then
  499. For Each Loader In Loaders
  500. Loader.State = LoadState.Waiting
  501. Next
  502. End If
  503. RunInThread(AddressOf Update)
  504. End Sub
  505. Public Overrides Sub Cancel()
  506. '改变状态
  507. SyncLock LockState
  508. If State = LoadState.Loading OrElse State = LoadState.Waiting Then
  509. State = LoadState.Canceled
  510. Else
  511. Return
  512. End If
  513. End SyncLock
  514. RunInThread(
  515. Sub()
  516. '中断加载器
  517. For Each Loader In Loaders
  518. Loader.Cancel()
  519. Next
  520. End Sub)
  521. End Sub
  522. Public Overrides Sub Failed(Ex As Exception)
  523. [Error] = Ex '先设置错误再调整状态,防止父加载器获取不到异常
  524. SyncLock LockState
  525. If State >= LoadState.Finished Then Return
  526. State = LoadState.Failed
  527. End SyncLock
  528. For Each Loader In Loaders
  529. Loader.Cancel()
  530. Next
  531. FrmMain.BtnExtraDownload.ShowRefresh()
  532. End Sub
  533. ''' <summary>
  534. ''' 子任务状态变更。
  535. ''' </summary>
  536. Private Sub SubTaskStateChanged(Loader As LoaderBase, NewState As LoadState, OldState As LoadState)
  537. Select Case NewState
  538. Case LoadState.Loading
  539. '开始,啥都不干
  540. Case LoadState.Waiting
  541. '子加载器可能由于外部输入改变而暂时变为 Waiting,之后会立即重新启动
  542. '所以啥都不干就行
  543. Case LoadState.Finished
  544. '正常结束,触发刷新
  545. Update()
  546. Case LoadState.Canceled
  547. '被中断,这个任务也中断
  548. Cancel()
  549. Case LoadState.Failed
  550. '完蛋,出错了
  551. Failed(New Exception(Loader.Name & "失败", Loader.Error))
  552. End Select
  553. End Sub
  554. ''' <summary>
  555. ''' 触发一次更新,以启动新加载器或完成。
  556. ''' </summary>
  557. Private Sub Update()
  558. If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Canceled Then Return
  559. Dim IsFinished As Boolean = True
  560. Dim Blocked As Boolean = False
  561. Dim Input As Object = Me.Input
  562. For Each Loader In Loaders
  563. Select Case Loader.State
  564. Case LoadState.Finished
  565. '检查 LoaderTask 是否需要重启
  566. If GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderTask(Of ,))) Then
  567. If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
  568. Logger.Info($"由于输入条件变更,重启已完成的加载器 {Loader.Name}")
  569. GoTo Restart
  570. End If
  571. '更新下一个加载器的输入
  572. Input = CType(Loader, Object).Output
  573. End If
  574. '如果不让继续启动,且已有加载器正在加载中,就不继续启动
  575. If Loader.Block AndAlso Not IsFinished Then Blocked = True
  576. Case LoadState.Loading
  577. '检查 LoaderTask 是否需要重启
  578. If GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderTask(Of ,))) Then
  579. If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
  580. Logger.Warn($"由于输入条件变更,重启进行中的加载器 {Loader.Name}")
  581. GoTo Restart
  582. End If
  583. End If
  584. '已经有正在加载中的了,不需要再启动了
  585. IsFinished = False
  586. Blocked = True
  587. Case Else
  588. Restart:
  589. '未启动,则启动加载器
  590. IsFinished = False
  591. If Blocked Then Continue For
  592. If Input IsNot Nothing Then
  593. '若输入类型与下一个加载器相同才继续
  594. If GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderTask(Of ,))) OrElse GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderCombo(Of ))) Then
  595. Loader.Start(If(Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IsForceRestarting)
  596. ElseIf TypeOf Loader Is LoaderWorker Then
  597. Loader.Start(IsForceRestart:=IsForceRestarting)
  598. ElseIf TypeOf Loader Is LoaderDownload Then
  599. Loader.Start(If(TypeOf Input Is List(Of NetFile), Input, Nothing), IsForceRestarting)
  600. Else
  601. Throw New Exception("未知的加载器类型(" & Loader.GetType.Name & ")")
  602. End If
  603. Else
  604. Loader.Start(IsForceRestart:=IsForceRestarting)
  605. End If
  606. '阻止继续
  607. If Loader.Block Then Blocked = True
  608. End Select
  609. Next
  610. If IsFinished Then
  611. '顺利完成,贼棒
  612. RaisePreviewFinish()
  613. State = LoadState.Finished
  614. FrmMain.BtnExtraDownload.ShowRefresh()
  615. End If
  616. End Sub
  617. ''' <summary>
  618. ''' 获得最底层的,应被显示给用户的加载器列表,并追加于 List。
  619. ''' </summary>
  620. Public Shared Sub GetLoaderList(Loader As Object, ByRef List As List(Of LoaderBase), Optional RequireShow As Boolean = True)
  621. For Each SubLoader In Loader.Loaders
  622. If SubLoader.Show OrElse Not RequireShow Then List.Add(SubLoader)
  623. If GeneralUtils.IsGenericInstanceOf(SubLoader, GetType(LoaderCombo(Of ))) Then GetLoaderList(SubLoader, List)
  624. Next
  625. End Sub
  626. ''' <summary>
  627. ''' 获得最底层的,应被显示给用户的加载器列表,并追加于 List。
  628. ''' </summary>
  629. Public Sub GetLoaderList(ByRef List As List(Of LoaderBase), Optional RequireShow As Boolean = True)
  630. GetLoaderList(Me, List, RequireShow)
  631. End Sub
  632. ''' <summary>
  633. ''' 获得最底层的,应被显示给用户的加载器列表。
  634. ''' </summary>
  635. Public Function GetLoaderList(Optional RequireShow As Boolean = True) As List(Of LoaderBase)
  636. Dim List As New List(Of LoaderBase)
  637. GetLoaderList(List, RequireShow)
  638. Return List
  639. End Function
  640. End Class
  641. '任务栏进度条
  642. Public LoaderTaskbar As New ConcurrentList(Of LoaderBase)
  643. Public LoaderTaskbarProgress As Double = 0 '平滑后的进度
  644. Private LoaderTaskbarProgressLast As Shell.TaskbarItemProgressState = Shell.TaskbarItemProgressState.None
  645. Public Sub LoaderTaskbarAdd(Of T)(Loader As LoaderCombo(Of T))
  646. If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRemove(Loader)
  647. LoaderTaskbar.Add(Loader)
  648. Logger.Info($"{Loader.Name} 已加入任务列表")
  649. End Sub
  650. Public Sub LoaderTaskbarProgressRefresh()
  651. Try
  652. Dim NewState As Shell.TaskbarItemProgressState
  653. Dim NewProgress As Double = LoaderTaskbarProgressGet()
  654. '若单个任务已中止,或全部任务已完成,则刷新并移除
  655. For Each Task In LoaderTaskbar
  656. If LoaderTaskbar.All(Function(l) l.State <> LoadState.Loading) OrElse
  657. (Task.State = LoadState.Waiting OrElse Task.State = LoadState.Canceled) Then
  658. FrmSpeedLeft?.TaskRefresh(Task)
  659. LoaderTaskbar.Remove(Task)
  660. Logger.Info($"{Task.Name} 已移出任务列表")
  661. End If
  662. Next
  663. '更新平滑后的进度
  664. If NewProgress <= 0 OrElse NewProgress >= 1 OrElse LoaderTaskbarProgress > NewProgress Then
  665. LoaderTaskbarProgress = NewProgress
  666. Else
  667. LoaderTaskbarProgress = LoaderTaskbarProgress * 0.9 + NewProgress * 0.1
  668. End If
  669. RunInUi(Sub() FrmMain.BtnExtraDownload.Progress = LoaderTaskbarProgress)
  670. '更新任务栏信息
  671. If Not LoaderTaskbar.Any() OrElse LoaderTaskbarProgress = 1 Then
  672. NewState = Shell.TaskbarItemProgressState.None
  673. ElseIf LoaderTaskbarProgress < 0.015 Then
  674. NewState = Shell.TaskbarItemProgressState.Indeterminate
  675. Else
  676. NewState = Shell.TaskbarItemProgressState.Normal
  677. FrmMain.TaskbarItemInfo.ProgressValue = LoaderTaskbarProgress
  678. End If
  679. If LoaderTaskbarProgressLast <> NewState Then
  680. LoaderTaskbarProgressLast = NewState
  681. FrmMain.TaskbarItemInfo.ProgressState = NewState
  682. FrmMain.BtnExtraDownload.ShowRefresh()
  683. End If
  684. Catch ex As Exception
  685. Logger.Error(ex, "刷新任务栏进度显示失败")
  686. End Try
  687. End Sub
  688. Public Function LoaderTaskbarProgressGet() As Double
  689. Try
  690. If Not LoaderTaskbar.Any Then Return 1
  691. Return LoaderTaskbar.Select(Function(l) l.Progress).Average().Clamp(0, 1)
  692. Catch ex As Exception
  693. Logger.Error(ex, "获取任务栏进度出错")
  694. Return 0.5
  695. End Try
  696. End Function
  697. '文件夹刷新类委托
  698. Private LoaderFolderDictionary As New Dictionary(Of LoaderBase, LoaderFolderDictionaryEntry)
  699. Private Structure LoaderFolderDictionaryEntry
  700. Public LastCheckTime As Date?
  701. Public FolderPath As String
  702. Public Overrides Function Equals(obj As Object) As Boolean
  703. If TypeOf obj IsNot LoaderFolderDictionaryEntry Then Return False
  704. Dim entry = DirectCast(obj, LoaderFolderDictionaryEntry)
  705. Return EqualityComparer(Of Date?).Default.Equals(LastCheckTime, entry.LastCheckTime) AndAlso
  706. FolderPath = entry.FolderPath
  707. End Function
  708. End Structure
  709. Public Enum LoaderFolderRunType
  710. RunOnUpdated
  711. ForceRun
  712. UpdateOnly
  713. End Enum
  714. ''' <summary>
  715. ''' 执行以文件夹检测作为输入的加载器。加载器需以文件夹路径为输入值。
  716. ''' 返回是否执行了加载器。
  717. ''' </summary>
  718. ''' <param name="ExtraPath">用于检查文件夹修改的额外路径。该路径不会传入加载器。</param>
  719. Public Function LoaderFolderRun(Loader As LoaderBase, FolderPath As String, Type As LoaderFolderRunType, Optional MaxDepth As Integer = 0, Optional ExtraPath As String = "", Optional WaitForExit As Boolean = False) As Boolean
  720. Dim Value As New LoaderFolderDictionaryEntry With {.FolderPath = FolderPath & ExtraPath, .LastCheckTime = Nothing}
  721. Try
  722. '获取数据
  723. Dim FolderInfo = DirectoryUtils.GetInfo(FolderPath & ExtraPath)
  724. Value.LastCheckTime = If(FolderInfo.Exists, GetActualLastWriteTimeUtc(FolderInfo, MaxDepth), DirectCast(Nothing, Date?))
  725. '如果已经检查过,则跳过
  726. If Type = LoaderFolderRunType.RunOnUpdated AndAlso LoaderFolderDictionary.ContainsKey(Loader) Then
  727. If FolderInfo.Exists Then
  728. If LoaderFolderDictionary(Loader).LastCheckTime IsNot Nothing AndAlso
  729. Value.Equals(LoaderFolderDictionary(Loader)) Then Return False
  730. Else
  731. If LoaderFolderDictionary(Loader).LastCheckTime Is Nothing Then Return False
  732. End If
  733. End If
  734. Catch ex As Exception
  735. Logger.Warn(ex, "文件夹加载器启动检测出错")
  736. End Try
  737. '写入检查数据
  738. LoaderFolderDictionary(Loader) = Value
  739. '开始检查
  740. If Type = LoaderFolderRunType.UpdateOnly Then Return False
  741. If WaitForExit Then
  742. Loader.WaitForExit(FolderPath, IsForceRestart:=True)
  743. Else
  744. Loader.Start(FolderPath, IsForceRestart:=True)
  745. End If
  746. Return True
  747. End Function
  748. Private Function GetActualLastWriteTimeUtc(FolderInfo As DirectoryInfo, MaxDepth As Integer) As Date
  749. Dim Time As Date = FolderInfo.LastWriteTimeUtc
  750. If MaxDepth > 0 Then
  751. For Each Folder In FolderInfo.EnumerateDirectories
  752. Dim FolderTime As Date = GetActualLastWriteTimeUtc(Folder, MaxDepth - 1)
  753. If FolderTime > Time Then Time = FolderTime
  754. Next
  755. End If
  756. Return Time
  757. End Function
  758. End Module