private/40-state.ps1
|
# ----------------------------------------------------------------------------- state -- function New-UI4MEState { <# .SYNOPSIS Observable value. Read .Value, change with .Set(new) or .Update({ param($old) ... }); bound components follow. #> [CmdletBinding()] param([AllowNull()][object]$Value) $state = [pscustomobject]@{ Value = $Value; Subscribers = (New-Object System.Collections.ArrayList); Next = 0 } $state.PSObject.TypeNames.Insert(0, 'UI4ME.UiState') $state | Add-Member -MemberType ScriptMethod -Name Set -Value { param([AllowNull()][object]$NewValue) if ($null -eq $NewValue -and $null -eq $this.Value) { return } if ($null -ne $NewValue -and $null -ne $this.Value -and -not ($NewValue -is [System.Collections.IEnumerable] -and -not ($NewValue -is [string])) -and [object]::Equals($this.Value, $NewValue)) { return } $this.Value = $NewValue foreach ($subscriber in @($this.Subscribers)) { try { $null = Invoke-UiCallable $subscriber.Callback @(, $NewValue) } catch { Write-UiError $_ 'state subscriber' } } } $state | Add-Member -MemberType ScriptMethod -Name Update -Value { param([scriptblock]$Transform) $this.Set((& $Transform $this.Value)) } $state | Add-Member -MemberType ScriptMethod -Name Subscribe -Value { param($Callback) $this.Next = [int]$this.Next + 1 [void]$this.Subscribers.Add([pscustomobject]@{ Token = $this.Next; Callback = $Callback }) return $this.Next } $state | Add-Member -MemberType ScriptMethod -Name Unsubscribe -Value { param([int]$Token) foreach ($subscriber in @($this.Subscribers)) { if ($subscriber.Token -eq $Token) { [void]$this.Subscribers.Remove($subscriber) } } } return $state } function New-UI4MEComputed { <# .SYNOPSIS A read-only state whose value is -Expression, recomputed whenever any -DependsOn state changes. #> [CmdletBinding()] param([Parameter(Mandatory)][object[]]$DependsOn, [Parameter(Mandatory)][scriptblock]$Expression) $computed = New-UI4MEState -Value (& $Expression) $data = [pscustomobject]@{ Target = $computed; Expression = $Expression } foreach ($dependency in $DependsOn) { if (-not (Test-UiState $dependency)) { throw '-DependsOn takes states from New-UI4MEState/New-UI4MEComputed' } $null = $dependency.Subscribe((New-UiCallable 'Update-UiComputed' $data)) } return $computed } function Update-UiComputed { param($Data, $ignored) $Data.Target.Set((& $Data.Expression)) } |