From 6ce5a110da9bd375e5e80fda2ef767fb3df01ad7 Mon Sep 17 00:00:00 2001 From: Simon Korzunov Date: Sat, 15 Jan 2022 23:43:51 -0800 Subject: [PATCH 1/9] PoC for diffing enumerator for scalars --- src/Fabulous.Tests/TestUI.Widgets.fs | 16 +- src/Fabulous/AttributeDefinitions.fs | 28 ---- src/Fabulous/Attributes.fs | 19 +-- src/Fabulous/Core.fs | 196 ++++++++++++++++++++-- src/Fabulous/Reconciler.fs | 234 ++++++++++++++------------- 5 files changed, 320 insertions(+), 173 deletions(-) diff --git a/src/Fabulous.Tests/TestUI.Widgets.fs b/src/Fabulous.Tests/TestUI.Widgets.fs index e151c1580..85a6ee1fa 100644 --- a/src/Fabulous.Tests/TestUI.Widgets.fs +++ b/src/Fabulous.Tests/TestUI.Widgets.fs @@ -19,8 +19,8 @@ open Tests.TestUI_ViewNode //-------Widgets module Widgets = - let register<'T when 'T :> TestViewElement and 'T: (new: unit -> 'T)> () = - let key = WidgetDefinitionStore.getNextKey () + let register<'T when 'T :> TestViewElement and 'T: (new : unit -> 'T)> () = + let key = WidgetDefinitionStore.getNextKey() let definition = { Key = key @@ -29,7 +29,7 @@ module Widgets = CreateView = fun (widget, context, parentNode) -> let name = typeof<'T>.Name - printfn $"Creating view for {name}" + // printfn $"Creating view for {name}" let view = new 'T() let weakReference = WeakReference(view) @@ -102,9 +102,9 @@ type WidgetExtensions() = [] type View private () = - static let TestLabelKey = Widgets.register () - static let TestButtonKey = Widgets.register () - static let TestStackKey = Widgets.register () + static let TestLabelKey = Widgets.register() + static let TestButtonKey = Widgets.register() + static let TestStackKey = Widgets.register() static member Label<'msg>(text: string) = WidgetBuilder<'msg, TestLabelMarker>(TestLabelKey, Attributes.Text.Text.WithValue(text)) @@ -120,7 +120,7 @@ type View private () = static member Stack<'msg, 'marker when 'marker :> IMarker>() = CollectionBuilder<'msg, TestStackMarker, 'marker>( TestStackKey, - StackList.empty (), + StackList.empty(), Attributes.Container.Children ) @@ -151,7 +151,7 @@ type CollectionBuilderExtensions = // TODO optimize this one with addMut { Widgets = x - |> Seq.map (fun wb -> wb.Compile()) + |> Seq.map(fun wb -> wb.Compile()) |> Seq.toArray |> MutStackArray1.fromArray } diff --git a/src/Fabulous/AttributeDefinitions.fs b/src/Fabulous/AttributeDefinitions.fs index b5ec369dd..bdd8a70bd 100644 --- a/src/Fabulous/AttributeDefinitions.fs +++ b/src/Fabulous/AttributeDefinitions.fs @@ -1,20 +1,7 @@ namespace Fabulous open Fabulous -open System.Collections.Generic -type IAttributeDefinition = - abstract member Key: AttributeKey - abstract member UpdateNode: newValueOpt: obj voption * node: IViewNode -> unit - -[] -type ScalarAttributeComparison = - | Identical - | Different - -type IScalarAttributeDefinition = - inherit IAttributeDefinition - abstract member CompareBoxed: a: obj * b: obj -> ScalarAttributeComparison /// Attribute definition for scalar properties type ScalarAttributeDefinition<'inputType, 'modelType, 'valueType> = @@ -95,18 +82,3 @@ type WidgetCollectionAttributeDefinition = | ValueSome v -> ValueSome(unbox> v) x.UpdateNode(newValueOpt, node) - -module AttributeDefinitionStore = - let private _attributes = - Dictionary() - - let mutable private _nextKey = 0 - - let get key = _attributes.[key] - let set key value = _attributes.[key] <- value - let remove key = _attributes.Remove(key) |> ignore - - let getNextKey () : AttributeKey = - let key = _nextKey - _nextKey <- _nextKey + 1 - key diff --git a/src/Fabulous/Attributes.fs b/src/Fabulous/Attributes.fs index 4f6cf2ed5..19db52eae 100644 --- a/src/Fabulous/Attributes.fs +++ b/src/Fabulous/Attributes.fs @@ -32,7 +32,7 @@ module Attributes = (compare: 'modelType * 'modelType -> ScalarAttributeComparison) (updateNode: 'valueType voption * IViewNode -> unit) = - let key = AttributeDefinitionStore.getNextKey () + let key = AttributeDefinitionStore.getNextKey() let definition = { Key = key @@ -51,7 +51,7 @@ module Attributes = (applyDiff: WidgetDiff * IViewNode -> unit) (updateNode: Widget voption * IViewNode -> unit) = - let key = AttributeDefinitionStore.getNextKey () + let key = AttributeDefinitionStore.getNextKey() let definition: WidgetAttributeDefinition = { Key = key @@ -68,7 +68,7 @@ module Attributes = (applyDiff: ArraySlice * IViewNode -> unit) (updateNode: ArraySlice voption * IViewNode -> unit) = - let key = AttributeDefinitionStore.getNextKey () + let key = AttributeDefinitionStore.getNextKey() let definition: WidgetCollectionAttributeDefinition = { Key = key @@ -84,9 +84,8 @@ module Attributes = let applyDiff (diff: WidgetDiff, node: IViewNode) = let childNode = get node.Target - match diff.ScalarChanges with - | ValueSome changes -> childNode.ApplyScalarDiffs(changes) - | ValueNone -> () + childNode.ApplyScalarDiffs(&diff.ScalarChanges) + match diff.WidgetChanges with | ValueSome slice -> childNode.ApplyWidgetDiffs(ArraySlice.toSpan slice) @@ -127,9 +126,7 @@ module Attributes = let childNode = node.TreeContext.GetViewNode(box targetColl.[index]) - match widgetDiff.ScalarChanges with - | ValueSome changes -> childNode.ApplyScalarDiffs(changes) - | ValueNone -> () + childNode.ApplyScalarDiffs(&widgetDiff.ScalarChanges) match widgetDiff.WidgetChanges with | ValueSome slice -> childNode.ApplyWidgetDiffs(ArraySlice.toSpan slice) @@ -182,7 +179,7 @@ module Attributes = node.TreeContext.Dispatch(newMsg) let defineEventNoArg name (getEvent: obj -> IEvent) = - let key = AttributeDefinitionStore.getNextKey () + let key = AttributeDefinitionStore.getNextKey() let definition: ScalarAttributeDefinition = { Key = key @@ -212,7 +209,7 @@ module Attributes = definition let defineEvent<'args> name (getEvent: obj -> IEvent, 'args>) = - let key = AttributeDefinitionStore.getNextKey () + let key = AttributeDefinitionStore.getNextKey() let definition: ScalarAttributeDefinition<_, _, _> = { Key = key diff --git a/src/Fabulous/Core.fs b/src/Fabulous/Core.fs index bd8147402..90268d929 100644 --- a/src/Fabulous/Core.fs +++ b/src/Fabulous/Core.fs @@ -1,6 +1,9 @@ namespace Fabulous open System +open System.Collections.Generic +open System.Runtime.CompilerServices +open Fabulous /// Dev notes: /// @@ -56,6 +59,27 @@ and [] Widget = WidgetAttributes: WidgetAttribute [] voption WidgetCollectionAttributes: WidgetCollectionAttribute [] voption } + +[] +type ScalarAttributeComparison = + | Identical + | Different + +module AttributeDefinitionStore = + let private _attributes = Dictionary() + + let mutable private _nextKey = 0 + + let get key = _attributes.[key] + let set key value = _attributes.[key] <- value + let remove key = _attributes.Remove(key) |> ignore + + let getNextKey () : AttributeKey = + let key = _nextKey + _nextKey <- _nextKey + 1 + key + + [] type ScalarChange = | Added of added: ScalarAttribute @@ -80,30 +104,180 @@ and [] WidgetCollectionItemChange = | Remove of removed: int and [] WidgetDiff = - { ScalarChanges: ScalarChange [] voption + { ScalarChanges: ScalarChanges WidgetChanges: ArraySlice voption WidgetCollectionChanges: ArraySlice voption } +and IAttributeDefinition = + abstract member Key : AttributeKey + abstract member UpdateNode : newValueOpt: obj voption * node: IViewNode -> unit + +and IScalarAttributeDefinition = + inherit IAttributeDefinition + abstract member CompareBoxed : a: obj * b: obj -> ScalarAttributeComparison + /// Context of the whole view tree -[] -type ViewTreeContext = + +and [] ViewTreeContext = { CanReuseView: Widget -> Widget -> bool GetViewNode: obj -> IViewNode Dispatch: obj -> unit } and IViewNode = - abstract member Target: obj - abstract member Parent: IViewNode voption - abstract member TreeContext: ViewTreeContext - abstract member MapMsg: (obj -> obj) voption with get, set + abstract member Target : obj + abstract member Parent : IViewNode voption + abstract member TreeContext : ViewTreeContext + abstract member MapMsg : (obj -> obj) voption with get, set // note that Widget is struct type, thus we have boxing via option // we don't have MemoizedWidget set for 99.9% of the cases // thus makes sense to have overhead of boxing // in order to save space - abstract member MemoizedWidget: Widget option with get, set + abstract member MemoizedWidget : Widget option with get, set abstract member TryGetHandler<'T> : AttributeKey -> 'T voption abstract member SetHandler<'T> : AttributeKey * 'T voption -> unit - abstract member ApplyScalarDiffs: ScalarChange [] -> unit - abstract member ApplyWidgetDiffs: Span -> unit - abstract member ApplyWidgetCollectionDiffs: Span -> unit + abstract member ApplyScalarDiffs : ScalarChanges inref -> unit + abstract member ApplyWidgetDiffs : Span -> unit + abstract member ApplyWidgetCollectionDiffs : Span -> unit + + + +// TODO break and chain here +and [] ScalarChanges + ( + prev: ScalarAttribute [] voption, + next: ScalarAttribute [] voption + ) = + member _.GetEnumerator() : ScalarChangesEnumerator = + ScalarChangesEnumerator( + match prev, next with + | ValueNone, ValueNone -> EnumerationMode.Empty + | ValueSome prev, ValueNone -> EnumerationMode.AllAddedOrRemoved(prev, false) + | ValueNone, ValueSome next -> EnumerationMode.AllAddedOrRemoved(next, true) + | ValueSome prev, ValueSome next -> EnumerationMode.ActualDiff(prev, next) + ) + +and [] EnumerationMode = + | AllAddedOrRemoved of struct (ScalarAttribute [] * bool) + | Empty + | ActualDiff of prevNext: struct (ScalarAttribute [] * ScalarAttribute []) + +and [] ScalarChangesEnumerator(mode: EnumerationMode) = + + [] + val mutable private current: ScalarChange + + [] + val mutable private prevIndex: int + + [] + val mutable private nextIndex: int + + member e.Current = e.current + + member e.MoveNext() = + match mode with + | EnumerationMode.Empty -> false + | EnumerationMode.AllAddedOrRemoved (struct (attributes, added)) -> + // use prevIndex regardless if it is for adding or removal + let i = e.prevIndex + + if i < attributes.Length then + let attribute = attributes.[i] + + e.current <- + match added with + | false -> ScalarChange.Removed attribute + | true -> ScalarChange.Added attribute + + e.prevIndex <- i + 1 + true + else + false + + | EnumerationMode.ActualDiff (struct (prev, next)) -> + let mutable prevIndex = e.prevIndex + let mutable nextIndex = e.nextIndex + + let prevLength = prev.Length + let nextLength = next.Length + + let mutable res: bool voption = ValueNone + // that needs to be in a loop until we have a change + + while ValueOption.isNone res do + + if not(prevIndex >= prevLength && nextIndex >= nextLength) then + if prevIndex = prevLength then + // that means we are done with the prev and only need to add next's tail to added + e.current <- ScalarChange.Added next.[nextIndex] + res <- ValueSome true + nextIndex <- nextIndex + 1 + + elif nextIndex = nextLength then + // that means that we are done with new items and only need prev's tail to removed + e.current <- ScalarChange.Removed prev.[prevIndex] + res <- ValueSome true + prevIndex <- prevIndex + 1 + + else + // we haven't reached either of the ends + let prevAttr = prev.[prevIndex] + let nextAttr = next.[nextIndex] + + let prevKey = prevAttr.Key + let nextKey = nextAttr.Key + + match prevKey.CompareTo nextKey with + | c when c < 0 -> + // prev key is less than next -> remove prev key + e.current <- ScalarChange.Removed prev.[prevIndex] + res <- ValueSome true + prevIndex <- prevIndex + 1 + + | c when c > 0 -> + // prev key is more than next -> add next item + e.current <- ScalarChange.Added prev.[nextIndex] + res <- ValueSome true + nextIndex <- nextIndex + 1 + + | _ -> + // means that we are targeting the same attribute + let definition = + AttributeDefinitionStore.get prevAttr.Key :?> IScalarAttributeDefinition + + match definition.CompareBoxed(prevAttr.Value, nextAttr.Value) with + // Previous and next values are identical, we don't need to do anything + | ScalarAttributeComparison.Identical -> () + + // New value completely replaces the old value + | ScalarAttributeComparison.Different -> + e.current <- ScalarChange.Updated next.[nextIndex] + res <- ValueSome true + + // move both pointers + prevIndex <- prevIndex + 1 + nextIndex <- nextIndex + 1 + + else + res <- ValueSome false + + e.prevIndex <- prevIndex + e.nextIndex <- nextIndex + + match res with + | ValueNone -> false + | ValueSome res -> res + + + interface IEnumerator with + member this.Current = this.current + member this.Current: obj = upcast this.current + + member this.Reset() = + this.prevIndex <- 0 + this.nextIndex <- 0 + this.current <- Unchecked.defaultof<_> + + member __.Dispose() = () + member this.MoveNext() : bool = this.MoveNext() diff --git a/src/Fabulous/Reconciler.fs b/src/Fabulous/Reconciler.fs index f14fad6fd..4e5f6fef7 100644 --- a/src/Fabulous/Reconciler.fs +++ b/src/Fabulous/Reconciler.fs @@ -27,96 +27,96 @@ module Reconciler = /// compare values /// /// break when we reached both ends of the arrays - let rec diffScalarAttributes - (prev: ScalarAttribute [] voption) - (next: ScalarAttribute [] voption) - : ScalarChange [] voption = - match (prev, next) with - | ValueNone, ValueNone -> ValueNone - - // all were deleted - | ValueSome prev, ValueNone -> - prev - |> Array.map ScalarChange.Removed - |> ValueSome - | ValueNone, ValueSome next -> next |> Array.map ScalarChange.Added |> ValueSome - | ValueSome prev, ValueSome next -> - - let mutable result = DiffBuilder.create () - - let mutable prevIndex = 0 - let mutable nextIndex = 0 - - let prevLength = prev.Length - let nextLength = next.Length - - while not (prevIndex >= prevLength && nextIndex >= nextLength) do - if prevIndex = prevLength then - // that means we are done with the prev and only need to add next's tail to added - //result <- StackArray3.add(&result, (ScalarChange.Added next.[nextIndex])) - DiffBuilder.addOpMut &result DiffBuilder.Add (uint16 nextIndex) - nextIndex <- nextIndex + 1 - - elif nextIndex = nextLength then - // that means that we are done with new items and only need prev's tail to removed - // result <- StackArray3.add(&result, ScalarChange.Removed prev.[prevIndex]) - DiffBuilder.addOpMut &result DiffBuilder.Remove (uint16 prevIndex) - prevIndex <- prevIndex + 1 - - else - // we haven't reached either of the ends - let prevAttr = prev.[prevIndex] - let nextAttr = next.[nextIndex] - - let prevKey = prevAttr.Key - let nextKey = nextAttr.Key - - match prevKey.CompareTo nextKey with - | c when c < 0 -> - // prev key is less than next -> remove prev key - DiffBuilder.addOpMut &result DiffBuilder.Remove (uint16 prevIndex) - // result <- StackArray3.add(&result, ScalarChange.Removed prevAttr) - prevIndex <- prevIndex + 1 - - | c when c > 0 -> - // prev key is more than next -> add next item - // result <- StackArray3.add(&result, ScalarChange.Added nextAttr) - DiffBuilder.addOpMut &result DiffBuilder.Add (uint16 nextIndex) - nextIndex <- nextIndex + 1 - - | _ -> - // means that we are targeting the same attribute - - let definition = - AttributeDefinitionStore.get prevAttr.Key :?> IScalarAttributeDefinition - - match definition.CompareBoxed(prevAttr.Value, nextAttr.Value) with - // Previous and next values are identical, we don't need to do anything - | ScalarAttributeComparison.Identical -> () - - // New value completely replaces the old value - | ScalarAttributeComparison.Different -> - DiffBuilder.addOpMut &result DiffBuilder.Change (uint16 nextIndex) - - // move both pointers - prevIndex <- prevIndex + 1 - nextIndex <- nextIndex + 1 - - - match DiffBuilder.lenght &result with - | 0 -> ValueNone - | _ -> - ValueSome( - DiffBuilder.toArray - &result - (fun op -> - match op with - | DiffBuilder.Added i -> ScalarChange.Added next.[int i] - | DiffBuilder.Removed i -> ScalarChange.Removed prev.[int i] - | DiffBuilder.Changed i -> ScalarChange.Updated next.[int i]) - ) - - and diffWidgetAttributes +// let rec diffScalarAttributes +// (prev: ScalarAttribute [] voption) +// (next: ScalarAttribute [] voption) +// : ScalarChange [] voption = +// match (prev, next) with +// | ValueNone, ValueNone -> ValueNone +// +// // all were deleted +// | ValueSome prev, ValueNone -> +// prev +// |> Array.map ScalarChange.Removed +// |> ValueSome +// | ValueNone, ValueSome next -> next |> Array.map ScalarChange.Added |> ValueSome +// | ValueSome prev, ValueSome next -> +// +// let mutable result = DiffBuilder.create () +// +// let mutable prevIndex = 0 +// let mutable nextIndex = 0 +// +// let prevLength = prev.Length +// let nextLength = next.Length +// +// while not (prevIndex >= prevLength && nextIndex >= nextLength) do +// if prevIndex = prevLength then +// // that means we are done with the prev and only need to add next's tail to added +// //result <- StackArray3.add(&result, (ScalarChange.Added next.[nextIndex])) +// DiffBuilder.addOpMut &result DiffBuilder.Add (uint16 nextIndex) +// nextIndex <- nextIndex + 1 +// +// elif nextIndex = nextLength then +// // that means that we are done with new items and only need prev's tail to removed +// // result <- StackArray3.add(&result, ScalarChange.Removed prev.[prevIndex]) +// DiffBuilder.addOpMut &result DiffBuilder.Remove (uint16 prevIndex) +// prevIndex <- prevIndex + 1 +// +// else +// // we haven't reached either of the ends +// let prevAttr = prev.[prevIndex] +// let nextAttr = next.[nextIndex] +// +// let prevKey = prevAttr.Key +// let nextKey = nextAttr.Key +// +// match prevKey.CompareTo nextKey with +// | c when c < 0 -> +// // prev key is less than next -> remove prev key +// DiffBuilder.addOpMut &result DiffBuilder.Remove (uint16 prevIndex) +// // result <- StackArray3.add(&result, ScalarChange.Removed prevAttr) +// prevIndex <- prevIndex + 1 +// +// | c when c > 0 -> +// // prev key is more than next -> add next item +// // result <- StackArray3.add(&result, ScalarChange.Added nextAttr) +// DiffBuilder.addOpMut &result DiffBuilder.Add (uint16 nextIndex) +// nextIndex <- nextIndex + 1 +// +// | _ -> +// // means that we are targeting the same attribute +// +// let definition = +// AttributeDefinitionStore.get prevAttr.Key :?> IScalarAttributeDefinition +// +// match definition.CompareBoxed(prevAttr.Value, nextAttr.Value) with +// // Previous and next values are identical, we don't need to do anything +// | ScalarAttributeComparison.Identical -> () +// +// // New value completely replaces the old value +// | ScalarAttributeComparison.Different -> +// DiffBuilder.addOpMut &result DiffBuilder.Change (uint16 nextIndex) +// +// // move both pointers +// prevIndex <- prevIndex + 1 +// nextIndex <- nextIndex + 1 +// +// +// match DiffBuilder.lenght &result with +// | 0 -> ValueNone +// | _ -> +// ValueSome( +// DiffBuilder.toArray +// &result +// (fun op -> +// match op with +// | DiffBuilder.Added i -> ScalarChange.Added next.[int i] +// | DiffBuilder.Removed i -> ScalarChange.Removed prev.[int i] +// | DiffBuilder.Changed i -> ScalarChange.Updated next.[int i]) +// ) + + let rec diffWidgetAttributes (canReuseView: Widget -> Widget -> bool) (prev: WidgetAttribute [] voption) (next: WidgetAttribute [] voption) @@ -146,15 +146,15 @@ module Reconciler = let prevLength = prev.Length let nextLength = next.Length - while not (prevIndex >= prevLength && nextIndex >= nextLength) do + while not(prevIndex >= prevLength && nextIndex >= nextLength) do if prevIndex = prevLength then // that means we are done with the prev and only need to add next's tail to added - result <- MutStackArray1.addMut (&result, WidgetChange.Added next.[nextIndex]) + result <- MutStackArray1.addMut(&result, WidgetChange.Added next.[nextIndex]) nextIndex <- nextIndex + 1 elif nextIndex = nextLength then // that means that we are done with new items and only need prev's tail to removed - result <- MutStackArray1.addMut (&result, WidgetChange.Removed prev.[prevIndex]) + result <- MutStackArray1.addMut(&result, WidgetChange.Removed prev.[prevIndex]) prevIndex <- prevIndex + 1 else @@ -170,12 +170,12 @@ module Reconciler = match prevKey.CompareTo nextKey with | c when c < 0 -> // prev key is less than next -> remove prev key - result <- MutStackArray1.addMut (&result, WidgetChange.Removed prevAttr) + result <- MutStackArray1.addMut(&result, WidgetChange.Removed prevAttr) prevIndex <- prevIndex + 1 | c when c > 0 -> // prev key is more than next -> add next item - result <- MutStackArray1.addMut (&result, WidgetChange.Added nextAttr) + result <- MutStackArray1.addMut(&result, WidgetChange.Added nextAttr) nextIndex <- nextIndex + 1 | _ -> @@ -197,7 +197,7 @@ module Reconciler = match changeOpt with | ValueNone -> () - | ValueSome change -> result <- MutStackArray1.addMut (&result, change) + | ValueSome change -> result <- MutStackArray1.addMut(&result, change) MutStackArray1.toArraySlice &result @@ -233,11 +233,11 @@ module Reconciler = let prevLength = prev.Length let nextLength = next.Length - while not (prevIndex >= prevLength && nextIndex >= nextLength) do + while not(prevIndex >= prevLength && nextIndex >= nextLength) do if prevIndex = prevLength then // that means we are done with the prev and only need to add next's tail to added // DiffBuilder.addOpMut &result DiffBuilder.Add (uint16 nextIndex) - result <- MutStackArray1.addMut (&result, WidgetCollectionChange.Added next.[nextIndex]) + result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Added next.[nextIndex]) nextIndex <- nextIndex + 1 @@ -245,7 +245,7 @@ module Reconciler = elif nextIndex = nextLength then // that means that we are done with new items and only need prev's tail to removed // DiffBuilder.addOpMut &result DiffBuilder.Remove (uint16 prevIndex) - result <- MutStackArray1.addMut (&result, WidgetCollectionChange.Removed prev.[prevIndex]) + result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Removed prev.[prevIndex]) prevIndex <- prevIndex + 1 @@ -264,12 +264,12 @@ module Reconciler = | c when c < 0 -> // prev key is less than next -> remove prev key - result <- MutStackArray1.addMut (&result, WidgetCollectionChange.Removed prevAttr) + result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Removed prevAttr) prevIndex <- prevIndex + 1 | c when c > 0 -> // prev key is more than next -> add next item - result <- MutStackArray1.addMut (&result, WidgetCollectionChange.Added nextAttr) + result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Added nextAttr) nextIndex <- nextIndex + 1 | _ -> @@ -288,7 +288,7 @@ module Reconciler = let change = WidgetCollectionChange.Updated struct (nextAttr, slice) - result <- MutStackArray1.addMut (&result, change) + result <- MutStackArray1.addMut(&result, change) MutStackArray1.toArraySlice &result @@ -304,7 +304,7 @@ module Reconciler = if prev.Length > next.Length then for i = next.Length to prev.Length - 1 do - result <- MutStackArray1.addMut (&result, WidgetCollectionItemChange.Remove i) + result <- MutStackArray1.addMut(&result, WidgetCollectionItemChange.Remove i) for i = 0 to next.Length - 1 do let currItem = next.[i] @@ -329,7 +329,7 @@ module Reconciler = match changeOpt with | ValueNone -> () - | ValueSome change -> result <- MutStackArray1.addMut (&result, change) + | ValueSome change -> result <- MutStackArray1.addMut(&result, change) MutStackArray1.toArraySlice &result @@ -354,7 +354,7 @@ module Reconciler = | ValueSome widget -> widget.WidgetCollectionAttributes let scalarDiffs = - diffScalarAttributes prevScalarAttributes next.ScalarAttributes + ScalarChanges(prevScalarAttributes, next.ScalarAttributes) let widgetDiffs = diffWidgetAttributes canReuseView prevWidgetAttributes next.WidgetAttributes @@ -363,13 +363,19 @@ module Reconciler = diffWidgetCollectionAttributes canReuseView prevWidgetCollectionAttributes next.WidgetCollectionAttributes - match (scalarDiffs, widgetDiffs, collectionDiffs) with - | ValueNone, ValueNone, ValueNone -> ValueNone - | _ -> - ValueSome - { ScalarChanges = scalarDiffs - WidgetChanges = widgetDiffs - WidgetCollectionChanges = collectionDiffs } + ValueSome + { ScalarChanges = scalarDiffs + WidgetChanges = widgetDiffs + WidgetCollectionChanges = collectionDiffs } + + // TODO convert to Enumerator all diffs +// match (scalarDiffs, widgetDiffs, collectionDiffs) with +// | ValueNone, ValueNone, ValueNone -> ValueNone +// | _ -> +// ValueSome +// { ScalarChanges = scalarDiffs +// WidgetChanges = widgetDiffs +// WidgetCollectionChanges = collectionDiffs } /// Diffs changes and applies them on the target let update @@ -381,9 +387,7 @@ module Reconciler = match diffWidget canReuseView prevOpt next with | ValueNone -> () | ValueSome diff -> - match diff.ScalarChanges with - | ValueSome changes -> node.ApplyScalarDiffs(changes) - | ValueNone -> () + node.ApplyScalarDiffs(&diff.ScalarChanges) match diff.WidgetChanges with | ValueSome slice -> node.ApplyWidgetDiffs(ArraySlice.toSpan slice) From 89a4b8b9dc0351b025789ab08d80b1a53c6b5928 Mon Sep 17 00:00:00 2001 From: Simon Korzunov Date: Sun, 16 Jan 2022 22:28:38 -0800 Subject: [PATCH 2/9] First iteration of streaming diffing e2e --- src/Fabulous.Tests/APISketchTests.fs | 56 +-- src/Fabulous/AttributeDefinitions.fs | 2 +- src/Fabulous/Attributes.fs | 28 +- src/Fabulous/Core.fs | 375 +++++++++++++++++-- src/Fabulous/Reconciler.fs | 518 +++++++++++++-------------- 5 files changed, 640 insertions(+), 339 deletions(-) diff --git a/src/Fabulous.Tests/APISketchTests.fs b/src/Fabulous.Tests/APISketchTests.fs index 8e7ee5a7a..1b9b03486 100644 --- a/src/Fabulous.Tests/APISketchTests.fs +++ b/src/Fabulous.Tests/APISketchTests.fs @@ -21,13 +21,13 @@ let rec findOptional (root: TestViewElement) (id: string) : TestViewElement opti children |> Array.ofSeq - |> Array.fold (fun res child -> res |> Option.orElse (findOptional child id)) None + |> Array.fold(fun res child -> res |> Option.orElse(findOptional child id)) None | _ -> None let find<'a when 'a :> TestViewElement> (root: TestViewElement) (id: string) : 'a = findOptional root id - |> Option.defaultWith (fun () -> failwith "not found") + |> Option.defaultWith(fun () -> failwith "not found") :?> 'a @@ -46,7 +46,7 @@ module SimpleLabelTests = let view model = Label(model.text) .textColor(model.color) - .automationId ("label") + .automationId("label") let init () = { text = "hi"; color = "red" } @@ -82,7 +82,7 @@ module ButtonTests = let view model = Button(model.count.ToString(), Increment) - .automationId ("btn") + .automationId("btn") let init () = { count = 0 } @@ -112,7 +112,7 @@ module SimpleStackTests = let update msg model = match msg with - | Delete id -> model |> List.filter (fun (id_, _) -> id_ <> id) + | Delete id -> model |> List.filter(fun (id_, _) -> id_ <> id) | AddNew (id, text) -> (id, text) :: model | ChangeText (id, text) -> model @@ -127,9 +127,9 @@ module SimpleStackTests = (Stack() { yield! model - |> List.map (fun (id, text) -> (Label(text).automationId (id.ToString()))) + |> List.map(fun (id, text) -> (Label(text).automationId(id.ToString()))) }) - .automationId ("stack") + .automationId("stack") let init () = [] @@ -150,7 +150,7 @@ module SimpleStackTests = // add first instance.ProcessMessage(AddNew(1, "yo")) - Assert.AreEqual(stack.Children.Count, 1) + Assert.AreEqual(1, stack.Children.Count) let label = stack.Children.[0] :?> TestLabel :> IText @@ -198,10 +198,10 @@ module ComputationExpressionTest = let JustValue () = let view model = // requires implemented "Yield" - Stack() { Button("inc", Inc).automationId ("inc") } + Stack() { Button("inc", Inc).automationId("inc") } let instance = - StatefulWidget.mkSimpleView (fun () -> 0) update view + StatefulWidget.mkSimpleView(fun () -> 0) update view |> Run.Instance let tree = (instance.Start()) @@ -217,15 +217,15 @@ module ComputationExpressionTest = // requires implemented "Zero" (Stack() { if (model % 2 = 0) then - Label("label").automationId ("label") + Label("label").automationId("label") else - Button("btn", Inc).automationId ("btn") + Button("btn", Inc).automationId("btn") }) - .automationId ("stack") + .automationId("stack") let instance = - StatefulWidget.mkSimpleView (fun () -> 0) update view + StatefulWidget.mkSimpleView(fun () -> 0) update view |> Run.Instance let tree = (instance.Start()) @@ -260,11 +260,11 @@ module ComputationExpressionTest = // requires implemented "YieldFrom" yield! [ for i in 0 .. model -> i.ToString() ] - |> List.map (fun i -> Label(i).automationId (i)) + |> List.map(fun i -> Label(i).automationId(i)) } let instance = - StatefulWidget.mkSimpleView (fun () -> count) update view + StatefulWidget.mkSimpleView(fun () -> count) update view |> Run.Instance let tree = (instance.Start()) @@ -284,11 +284,11 @@ module ComputationExpressionTest = let view model = Stack() { // requires implemented "For" - for i in 0 .. model -> Label(i.ToString()).automationId (i.ToString()) + for i in 0 .. model -> Label(i.ToString()).automationId(i.ToString()) } let instance = - StatefulWidget.mkSimpleView (fun () -> count) update view + StatefulWidget.mkSimpleView(fun () -> count) update view |> Run.Instance let tree = (instance.Start()) @@ -353,13 +353,13 @@ module MapViewTests = mapMsg (Button("+1", AddOne) .automationId("add") - .textColor ("red")) + .textColor("red")) Label(model.ToString()) .automationId("label") - .textColor ("blue") + .textColor("blue") - View.map mapMsg (Button("-2", RemoveTwo).automationId ("remove")) + View.map mapMsg (Button("-2", RemoveTwo).automationId("remove")) } @@ -406,12 +406,12 @@ module MemoTests = let view model = Stack() { Label(model.notMemoized) - .automationId ("not_memo") + .automationId("not_memo") View.lazy' (fun i -> renderCount <- renderCount + 1 - Label(string i).automationId ("memo")) + Label(string i).automationId("memo")) model.memoTrigger } @@ -465,10 +465,10 @@ module MemoTests = let view model = (Stack() { match model with - | Btn -> View.lazy' (fun i -> Button(string i, Change).automationId ("btn")) model - | Lbl -> View.lazy' (fun i -> Label(string i).automationId ("label")) model + | Btn -> View.lazy'(fun i -> Button(string i, Change).automationId("btn")) model + | Lbl -> View.lazy'(fun i -> Label(string i).automationId("label")) model }) - .automationId ("stack") + .automationId("stack") [] let Test () = @@ -520,7 +520,7 @@ module MemoTests = Label("one") .record(true) .textColor("blue") - .automationId ("label")) + .automationId("label")) model | Label2 -> View.lazy' @@ -528,7 +528,7 @@ module MemoTests = Label("two") .record(true) .textColor("blue") - .automationId ("label")) + .automationId("label")) (string model) } diff --git a/src/Fabulous/AttributeDefinitions.fs b/src/Fabulous/AttributeDefinitions.fs index bdd8a70bd..b133f8d8d 100644 --- a/src/Fabulous/AttributeDefinitions.fs +++ b/src/Fabulous/AttributeDefinitions.fs @@ -62,7 +62,7 @@ type WidgetAttributeDefinition = type WidgetCollectionAttributeDefinition = { Key: AttributeKey Name: string - ApplyDiff: ArraySlice * IViewNode -> unit + ApplyDiff: WidgetCollectionItemChanges * IViewNode -> unit UpdateNode: ArraySlice voption * IViewNode -> unit } member x.WithValue(value: ArraySlice) : WidgetCollectionAttribute = diff --git a/src/Fabulous/Attributes.fs b/src/Fabulous/Attributes.fs index 19db52eae..acb475395 100644 --- a/src/Fabulous/Attributes.fs +++ b/src/Fabulous/Attributes.fs @@ -65,7 +65,7 @@ module Attributes = /// Define a custom attribute storing a widget collection let defineWidgetCollectionWithConverter name - (applyDiff: ArraySlice * IViewNode -> unit) + (applyDiff: WidgetCollectionItemChanges * IViewNode -> unit) (updateNode: ArraySlice voption * IViewNode -> unit) = let key = AttributeDefinitionStore.getNextKey() @@ -85,16 +85,10 @@ module Attributes = let childNode = get node.Target childNode.ApplyScalarDiffs(&diff.ScalarChanges) + childNode.ApplyWidgetDiffs(&diff.WidgetChanges) + childNode.ApplyWidgetCollectionDiffs(&diff.WidgetCollectionChanges) - match diff.WidgetChanges with - | ValueSome slice -> childNode.ApplyWidgetDiffs(ArraySlice.toSpan slice) - | ValueNone -> () - - match diff.WidgetCollectionChanges with - | ValueSome slice -> childNode.ApplyWidgetCollectionDiffs(ArraySlice.toSpan slice) - | ValueNone -> () - let updateNode (newValueOpt: Widget voption, node: IViewNode) = match newValueOpt with | ValueNone -> set node.Target null @@ -108,15 +102,15 @@ module Attributes = /// Define an attribute storing a collection of Widget let defineWidgetCollection<'itemType> name (getCollection: obj -> System.Collections.Generic.IList<'itemType>) = - let applyDiff (diffs: ArraySlice, node: IViewNode) = + let applyDiff (diffs: WidgetCollectionItemChanges, node: IViewNode) = let targetColl = getCollection node.Target - for diff in ArraySlice.toSpan diffs do + for diff in diffs do match diff with | WidgetCollectionItemChange.Remove index -> targetColl.RemoveAt(index) | _ -> () - for diff in ArraySlice.toSpan diffs do + for diff in diffs do match diff with | WidgetCollectionItemChange.Insert (index, widget) -> let view = Helpers.createViewForWidget node widget @@ -127,14 +121,8 @@ module Attributes = node.TreeContext.GetViewNode(box targetColl.[index]) childNode.ApplyScalarDiffs(&widgetDiff.ScalarChanges) - - match widgetDiff.WidgetChanges with - | ValueSome slice -> childNode.ApplyWidgetDiffs(ArraySlice.toSpan slice) - | ValueNone -> () - - match widgetDiff.WidgetCollectionChanges with - | ValueSome slice -> childNode.ApplyWidgetCollectionDiffs(ArraySlice.toSpan slice) - | ValueNone -> () + childNode.ApplyWidgetDiffs(&widgetDiff.WidgetChanges) + childNode.ApplyWidgetCollectionDiffs(&widgetDiff.WidgetCollectionChanges) | WidgetCollectionItemChange.Replace (index, widget) -> let view = Helpers.createViewForWidget node widget diff --git a/src/Fabulous/Core.fs b/src/Fabulous/Core.fs index 90268d929..26f74f7ab 100644 --- a/src/Fabulous/Core.fs +++ b/src/Fabulous/Core.fs @@ -65,6 +65,22 @@ type ScalarAttributeComparison = | Identical | Different + +[] +type EnumerationMode<'a> = + | AllAddedOrRemoved of struct ('a [] * bool) + | Empty + | ActualDiff of prevNext: struct ('a [] * 'a []) + + +module EnumerationMode = + let fromOptions prev next = + match prev, next with + | ValueNone, ValueNone -> EnumerationMode.Empty + | ValueSome prev, ValueNone -> EnumerationMode.AllAddedOrRemoved(prev, false) + | ValueNone, ValueSome next -> EnumerationMode.AllAddedOrRemoved(next, true) + | ValueSome prev, ValueSome next -> EnumerationMode.ActualDiff(prev, next) + module AttributeDefinitionStore = let private _attributes = Dictionary() @@ -95,7 +111,7 @@ and [] WidgetChange = and [] WidgetCollectionChange = | Added of added: WidgetCollectionAttribute | Removed of removed: WidgetCollectionAttribute - | Updated of updated: struct (WidgetCollectionAttribute * ArraySlice) + | Updated of updated: struct (WidgetCollectionAttribute * WidgetCollectionItemChanges) and [] WidgetCollectionItemChange = | Insert of widgetInserted: struct (int * Widget) @@ -105,8 +121,35 @@ and [] WidgetCollectionItemChange = and [] WidgetDiff = { ScalarChanges: ScalarChanges - WidgetChanges: ArraySlice voption - WidgetCollectionChanges: ArraySlice voption } + WidgetChanges: WidgetChanges + WidgetCollectionChanges: WidgetCollectionChanges } + + static member inline create + ( + prevOpt: Widget voption, + next: Widget, + canReuseView: Widget -> Widget -> bool + ) : WidgetDiff = + + let prevScalarAttributes = + match prevOpt with + | ValueNone -> ValueNone + | ValueSome widget -> widget.ScalarAttributes + + let prevWidgetAttributes = + match prevOpt with + | ValueNone -> ValueNone + | ValueSome widget -> widget.WidgetAttributes + + let prevWidgetCollectionAttributes = + match prevOpt with + | ValueNone -> ValueNone + | ValueSome widget -> widget.WidgetCollectionAttributes + + { ScalarChanges = ScalarChanges(prevScalarAttributes, next.ScalarAttributes) + WidgetChanges = WidgetChanges(prevWidgetAttributes, next.WidgetAttributes, canReuseView) + WidgetCollectionChanges = + WidgetCollectionChanges(prevWidgetCollectionAttributes, next.WidgetCollectionAttributes, canReuseView) } and IAttributeDefinition = abstract member Key : AttributeKey @@ -137,8 +180,8 @@ and IViewNode = abstract member TryGetHandler<'T> : AttributeKey -> 'T voption abstract member SetHandler<'T> : AttributeKey * 'T voption -> unit abstract member ApplyScalarDiffs : ScalarChanges inref -> unit - abstract member ApplyWidgetDiffs : Span -> unit - abstract member ApplyWidgetCollectionDiffs : Span -> unit + abstract member ApplyWidgetDiffs : WidgetChanges inref -> unit + abstract member ApplyWidgetCollectionDiffs : WidgetCollectionChanges inref -> unit @@ -148,21 +191,39 @@ and [] ScalarChanges prev: ScalarAttribute [] voption, next: ScalarAttribute [] voption ) = - member _.GetEnumerator() : ScalarChangesEnumerator = - ScalarChangesEnumerator( - match prev, next with - | ValueNone, ValueNone -> EnumerationMode.Empty - | ValueSome prev, ValueNone -> EnumerationMode.AllAddedOrRemoved(prev, false) - | ValueNone, ValueSome next -> EnumerationMode.AllAddedOrRemoved(next, true) - | ValueSome prev, ValueSome next -> EnumerationMode.ActualDiff(prev, next) - ) - -and [] EnumerationMode = - | AllAddedOrRemoved of struct (ScalarAttribute [] * bool) - | Empty - | ActualDiff of prevNext: struct (ScalarAttribute [] * ScalarAttribute []) + member _.GetEnumerator() = + ScalarChangesEnumerator(EnumerationMode.fromOptions prev next) + +and [] WidgetChanges + ( + prev: WidgetAttribute [] voption, + next: WidgetAttribute [] voption, + canReuseView: Widget -> Widget -> bool + ) = + member _.GetEnumerator() = + WidgetChangesEnumerator(EnumerationMode.fromOptions prev next, canReuseView) + +and [] WidgetCollectionChanges + ( + prev: WidgetCollectionAttribute [] voption, + next: WidgetCollectionAttribute [] voption, + canReuseView: Widget -> Widget -> bool + ) = + member _.GetEnumerator() = + WidgetCollectionChangesEnumerator(EnumerationMode.fromOptions prev next, canReuseView) + -and [] ScalarChangesEnumerator(mode: EnumerationMode) = +and [] WidgetCollectionItemChanges + ( + prev: ArraySlice, + next: ArraySlice, + canReuseView: Widget -> Widget -> bool + ) = + member _.GetEnumerator() = + WidgetCollectionItemChangesEnumerator(ArraySlice.toSpan prev, ArraySlice.toSpan next, canReuseView) + +// enumerators +and [] ScalarChangesEnumerator(mode: EnumerationMode) = [] val mutable private current: ScalarChange @@ -269,15 +330,273 @@ and [] ScalarChangesEnumerator(mode: EnumerationMode) = | ValueNone -> false | ValueSome res -> res +and [] WidgetChangesEnumerator + ( + mode: EnumerationMode, + canReuseView: Widget -> Widget -> bool + ) = + + [] + val mutable private current: WidgetChange + + [] + val mutable private prevIndex: int + + [] + val mutable private nextIndex: int + + member e.Current = e.current + + member e.MoveNext() = + match mode with + | EnumerationMode.Empty -> false + | EnumerationMode.AllAddedOrRemoved (struct (values, added)) -> + // use prevIndex regardless if it is for adding or removal + let i = e.prevIndex + + if i < values.Length then + let value = values.[i] + + e.current <- + match added with + | false -> WidgetChange.Removed value + | true -> WidgetChange.Added value + + e.prevIndex <- i + 1 + true + else + false + + | EnumerationMode.ActualDiff (struct (prev, next)) -> + let mutable prevIndex = e.prevIndex + let mutable nextIndex = e.nextIndex + + let prevLength = prev.Length + let nextLength = next.Length + + let mutable res: bool voption = ValueNone + // that needs to be in a loop until we have a change + + while ValueOption.isNone res do + if not(prevIndex >= prevLength && nextIndex >= nextLength) then + if prevIndex = prevLength then + // that means we are done with the prev and only need to add next's tail to added + e.current <- WidgetChange.Added next.[nextIndex] + res <- ValueSome true + nextIndex <- nextIndex + 1 + + elif nextIndex = nextLength then + // that means that we are done with new items and only need prev's tail to removed + e.current <- WidgetChange.Removed prev.[prevIndex] + res <- ValueSome true + prevIndex <- prevIndex + 1 + + else + // we haven't reached either of the ends + let prevAttr = prev.[prevIndex] + let nextAttr = next.[nextIndex] + + let prevKey = prevAttr.Key + let nextKey = nextAttr.Key + let prevWidget = prevAttr.Value + let nextWidget = nextAttr.Value + + match prevKey.CompareTo nextKey with + | c when c < 0 -> + // prev key is less than next -> remove prev key + e.current <- WidgetChange.Removed prevAttr + res <- ValueSome true + + prevIndex <- prevIndex + 1 + + | c when c > 0 -> + // prev key is more than next -> add next item + e.current <- WidgetChange.Added nextAttr + res <- ValueSome true + nextIndex <- nextIndex + 1 + + | _ -> + // means that we are targeting the same attribute + + // move both pointers + prevIndex <- prevIndex + 1 + nextIndex <- nextIndex + 1 + + if prevWidget <> nextWidget then + + let change = + if canReuseView prevWidget nextWidget then + let diff = + WidgetDiff.create((ValueSome prevWidget), nextWidget, canReuseView) + + WidgetChange.Updated struct (nextAttr, diff) + else + WidgetChange.ReplacedBy nextAttr + + e.current <- change + res <- ValueSome true + + else + res <- ValueSome false + + e.prevIndex <- prevIndex + e.nextIndex <- nextIndex + + match res with + | ValueNone -> false + | ValueSome res -> res + +and [] WidgetCollectionChangesEnumerator + ( + mode: EnumerationMode, + canReuseView: Widget -> Widget -> bool + ) = + + [] + val mutable private current: WidgetCollectionChange + + [] + val mutable private prevIndex: int + + [] + val mutable private nextIndex: int + + member e.Current = e.current + + member e.MoveNext() = + match mode with + | EnumerationMode.Empty -> false + | EnumerationMode.AllAddedOrRemoved (struct (values, added)) -> + // use prevIndex regardless if it is for adding or removal + let i = e.prevIndex + + if i < values.Length then + let value = values.[i] + + e.current <- + match added with + | false -> WidgetCollectionChange.Removed value + | true -> WidgetCollectionChange.Added value + + e.prevIndex <- i + 1 + true + else + false + + | EnumerationMode.ActualDiff (struct (prev, next)) -> + let mutable prevIndex = e.prevIndex + let mutable nextIndex = e.nextIndex + + let prevLength = prev.Length + let nextLength = next.Length + + // that needs to be in a loop until we have a change + + let res = + if not(prevIndex >= prevLength && nextIndex >= nextLength) then + if prevIndex = prevLength then + // that means we are done with the prev and only need to add next's tail to added + e.current <- WidgetCollectionChange.Added next.[nextIndex] + nextIndex <- nextIndex + 1 + + elif nextIndex = nextLength then + // that means that we are done with new items and only need prev's tail to removed + e.current <- WidgetCollectionChange.Removed prev.[prevIndex] + prevIndex <- prevIndex + 1 + else + // we haven't reached either of the ends + let prevAttr = prev.[prevIndex] + let nextAttr = next.[nextIndex] + + let prevKey = prevAttr.Key + let nextKey = nextAttr.Key + let prevWidgetColl = prevAttr.Value + let nextWidgetColl = nextAttr.Value + + match prevKey.CompareTo nextKey with + | c when c < 0 -> + // prev key is less than next -> remove prev key + e.current <- WidgetCollectionChange.Removed prevAttr + prevIndex <- prevIndex + 1 + + | c when c > 0 -> + // prev key is more than next -> add next item + e.current <- WidgetCollectionChange.Added nextAttr + nextIndex <- nextIndex + 1 + + | _ -> + // means that we are targeting the same attribute + + // move both pointers + prevIndex <- prevIndex + 1 + nextIndex <- nextIndex + 1 + + let diff = + WidgetCollectionItemChanges(prevWidgetColl, nextWidgetColl, canReuseView) + + e.current <- WidgetCollectionChange.Updated struct (nextAttr, diff) + + true + else + false + + e.prevIndex <- prevIndex + e.nextIndex <- nextIndex + + res + +and [] WidgetCollectionItemChangesEnumerator + ( + prev: Span, + next: Span, + canReuseView: Widget -> Widget -> bool + ) = + [] + val mutable private current: WidgetCollectionItemChange + + [] + val mutable private index: int + + [] + val mutable private tailIndex: int + + member e.Current = e.current + + member e.MoveNext() = + let tailIndex = e.tailIndex + let i = e.index + + if prev.Length > next.Length + && tailIndex < prev.Length - next.Length then + + e.current <- WidgetCollectionItemChange.Remove(next.Length - tailIndex) + e.tailIndex <- tailIndex + 1 + + true + + elif i < next.Length then + let currItem = next.[i] + + let prevItemOpt = + if (i >= prev.Length) then + ValueNone + else + ValueSome prev.[i] + + match prevItemOpt with + | ValueNone -> e.current <- WidgetCollectionItemChange.Insert struct (i, currItem) + + | ValueSome prevItem when canReuseView prevItem currItem -> + + e.current <- + WidgetCollectionItemChange.Update + struct (i, WidgetDiff.create(ValueSome prevItem, currItem, canReuseView)) - interface IEnumerator with - member this.Current = this.current - member this.Current: obj = upcast this.current + | ValueSome _ -> e.current <- WidgetCollectionItemChange.Replace struct (i, currItem) - member this.Reset() = - this.prevIndex <- 0 - this.nextIndex <- 0 - this.current <- Unchecked.defaultof<_> + e.index <- i + 1 + true - member __.Dispose() = () - member this.MoveNext() : bool = this.MoveNext() + else + // means that we are done iterating + false diff --git a/src/Fabulous/Reconciler.fs b/src/Fabulous/Reconciler.fs index 4e5f6fef7..aa5d3987a 100644 --- a/src/Fabulous/Reconciler.fs +++ b/src/Fabulous/Reconciler.fs @@ -1,7 +1,6 @@ namespace Fabulous open Fabulous -open Fabulous.StackAllocatedCollections module Reconciler = /// Let's imagine that we have the following situation @@ -116,257 +115,257 @@ module Reconciler = // | DiffBuilder.Changed i -> ScalarChange.Updated next.[int i]) // ) - let rec diffWidgetAttributes - (canReuseView: Widget -> Widget -> bool) - (prev: WidgetAttribute [] voption) - (next: WidgetAttribute [] voption) - : ArraySlice voption = - - match (prev, next) with - | ValueNone, ValueNone -> ValueNone - - // all were deleted - | ValueSome prev, ValueNone -> - prev - |> Array.map WidgetChange.Removed - |> ArraySlice.fromArray - - | ValueNone, ValueSome next -> - next - |> Array.map WidgetChange.Added - |> ArraySlice.fromArray - - | ValueSome prev, ValueSome next -> - - let mutable result = MutStackArray1.Empty - - let mutable prevIndex = 0 - let mutable nextIndex = 0 - - let prevLength = prev.Length - let nextLength = next.Length - - while not(prevIndex >= prevLength && nextIndex >= nextLength) do - if prevIndex = prevLength then - // that means we are done with the prev and only need to add next's tail to added - result <- MutStackArray1.addMut(&result, WidgetChange.Added next.[nextIndex]) - nextIndex <- nextIndex + 1 - - elif nextIndex = nextLength then - // that means that we are done with new items and only need prev's tail to removed - result <- MutStackArray1.addMut(&result, WidgetChange.Removed prev.[prevIndex]) - prevIndex <- prevIndex + 1 - - else - // we haven't reached either of the ends - let prevAttr = prev.[prevIndex] - let nextAttr = next.[nextIndex] - - let prevKey = prevAttr.Key - let nextKey = nextAttr.Key - let prevWidget = prevAttr.Value - let nextWidget = nextAttr.Value - - match prevKey.CompareTo nextKey with - | c when c < 0 -> - // prev key is less than next -> remove prev key - result <- MutStackArray1.addMut(&result, WidgetChange.Removed prevAttr) - prevIndex <- prevIndex + 1 - - | c when c > 0 -> - // prev key is more than next -> add next item - result <- MutStackArray1.addMut(&result, WidgetChange.Added nextAttr) - nextIndex <- nextIndex + 1 - - | _ -> - // means that we are targeting the same attribute - - // move both pointers - prevIndex <- prevIndex + 1 - nextIndex <- nextIndex + 1 - - let changeOpt = - if prevWidget = nextWidget then - ValueNone - elif canReuseView prevWidget nextWidget then - match diffWidget canReuseView (ValueSome prevWidget) nextWidget with - | ValueNone -> ValueNone - | ValueSome diffs -> ValueSome(WidgetChange.Updated struct (nextAttr, diffs)) - else - ValueSome(WidgetChange.ReplacedBy nextAttr) - - match changeOpt with - | ValueNone -> () - | ValueSome change -> result <- MutStackArray1.addMut(&result, change) - - MutStackArray1.toArraySlice &result - - - and diffWidgetCollectionAttributes - (canReuseView: Widget -> Widget -> bool) - (prev: WidgetCollectionAttribute [] voption) - (next: WidgetCollectionAttribute [] voption) - : ArraySlice voption = - - match (prev, next) with - | ValueNone, ValueNone -> ValueNone - - // all were deleted - | ValueSome prev, ValueNone -> - prev - |> Array.map WidgetCollectionChange.Removed - |> ArraySlice.fromArray - - | ValueNone, ValueSome next -> - next - |> Array.map WidgetCollectionChange.Added - |> ArraySlice.fromArray - - | ValueSome prev, ValueSome next -> - - let mutable result = MutStackArray1.Empty - - - let mutable prevIndex = 0 - let mutable nextIndex = 0 - - let prevLength = prev.Length - let nextLength = next.Length - - while not(prevIndex >= prevLength && nextIndex >= nextLength) do - if prevIndex = prevLength then - // that means we are done with the prev and only need to add next's tail to added - // DiffBuilder.addOpMut &result DiffBuilder.Add (uint16 nextIndex) - result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Added next.[nextIndex]) - - - nextIndex <- nextIndex + 1 - - elif nextIndex = nextLength then - // that means that we are done with new items and only need prev's tail to removed - // DiffBuilder.addOpMut &result DiffBuilder.Remove (uint16 prevIndex) - result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Removed prev.[prevIndex]) - - - prevIndex <- prevIndex + 1 - - else - // we haven't reached either of the ends - let prevAttr = prev.[prevIndex] - let nextAttr = next.[nextIndex] - - let prevKey = prevAttr.Key - let nextKey = nextAttr.Key - let prevWidgetColl = prevAttr.Value - let nextWidgetColl = nextAttr.Value - - match prevKey.CompareTo nextKey with - | c when c < 0 -> - // prev key is less than next -> remove prev key - - result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Removed prevAttr) - prevIndex <- prevIndex + 1 - - | c when c > 0 -> - // prev key is more than next -> add next item - result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Added nextAttr) - nextIndex <- nextIndex + 1 - - | _ -> - // means that we are targeting the same attribute - - // move both pointers - prevIndex <- prevIndex + 1 - nextIndex <- nextIndex + 1 - - let diff = - diffWidgetCollections canReuseView prevWidgetColl nextWidgetColl - - match diff with - | ValueNone -> () - | ValueSome slice -> - let change = - WidgetCollectionChange.Updated struct (nextAttr, slice) - - result <- MutStackArray1.addMut(&result, change) - - MutStackArray1.toArraySlice &result - - and diffWidgetCollections - (canReuseView: Widget -> Widget -> bool) - (prev: ArraySlice) - (next: ArraySlice) - : ArraySlice voption = - let mutable result = MutStackArray1.Empty - - let prev = ArraySlice.toSpan prev - let next = ArraySlice.toSpan next - - if prev.Length > next.Length then - for i = next.Length to prev.Length - 1 do - result <- MutStackArray1.addMut(&result, WidgetCollectionItemChange.Remove i) - - for i = 0 to next.Length - 1 do - let currItem = next.[i] - // index < 0 || index >= array.Length ? (FSharpOption) null : FSharpOption.Some(array[index]); - let prevItemOpt = - if (i >= prev.Length) then - ValueNone - else - ValueSome prev.[i] - - let changeOpt = - match prevItemOpt with - | ValueNone -> ValueSome(WidgetCollectionItemChange.Insert struct (i, currItem)) - - | ValueSome prevItem when canReuseView prevItem currItem -> - - match diffWidget canReuseView (ValueSome prevItem) currItem with - | ValueNone -> ValueNone - | ValueSome diffs -> ValueSome(WidgetCollectionItemChange.Update struct (i, diffs)) - - | ValueSome _ -> ValueSome(WidgetCollectionItemChange.Replace struct (i, currItem)) - - match changeOpt with - | ValueNone -> () - | ValueSome change -> result <- MutStackArray1.addMut(&result, change) - - MutStackArray1.toArraySlice &result - - and diffWidget - (canReuseView: Widget -> Widget -> bool) - (prevOpt: Widget voption) - (next: Widget) - : WidgetDiff voption = - let prevScalarAttributes = - match prevOpt with - | ValueNone -> ValueNone - | ValueSome widget -> widget.ScalarAttributes - - let prevWidgetAttributes = - match prevOpt with - | ValueNone -> ValueNone - | ValueSome widget -> widget.WidgetAttributes - - let prevWidgetCollectionAttributes = - match prevOpt with - | ValueNone -> ValueNone - | ValueSome widget -> widget.WidgetCollectionAttributes - - let scalarDiffs = - ScalarChanges(prevScalarAttributes, next.ScalarAttributes) - - let widgetDiffs = - diffWidgetAttributes canReuseView prevWidgetAttributes next.WidgetAttributes - - let collectionDiffs = - diffWidgetCollectionAttributes canReuseView prevWidgetCollectionAttributes next.WidgetCollectionAttributes - - - ValueSome - { ScalarChanges = scalarDiffs - WidgetChanges = widgetDiffs - WidgetCollectionChanges = collectionDiffs } + // let rec diffWidgetAttributes +// (canReuseView: Widget -> Widget -> bool) +// (prev: WidgetAttribute [] voption) +// (next: WidgetAttribute [] voption) +// : ArraySlice voption = +// +// match (prev, next) with +// | ValueNone, ValueNone -> ValueNone +// +// // all were deleted +// | ValueSome prev, ValueNone -> +// prev +// |> Array.map WidgetChange.Removed +// |> ArraySlice.fromArray +// +// | ValueNone, ValueSome next -> +// next +// |> Array.map WidgetChange.Added +// |> ArraySlice.fromArray +// +// | ValueSome prev, ValueSome next -> +// +// let mutable result = MutStackArray1.Empty +// +// let mutable prevIndex = 0 +// let mutable nextIndex = 0 +// +// let prevLength = prev.Length +// let nextLength = next.Length +// +// while not(prevIndex >= prevLength && nextIndex >= nextLength) do +// if prevIndex = prevLength then +// // that means we are done with the prev and only need to add next's tail to added +// result <- MutStackArray1.addMut(&result, WidgetChange.Added next.[nextIndex]) +// nextIndex <- nextIndex + 1 +// +// elif nextIndex = nextLength then +// // that means that we are done with new items and only need prev's tail to removed +// result <- MutStackArray1.addMut(&result, WidgetChange.Removed prev.[prevIndex]) +// prevIndex <- prevIndex + 1 +// +// else +// // we haven't reached either of the ends +// let prevAttr = prev.[prevIndex] +// let nextAttr = next.[nextIndex] +// +// let prevKey = prevAttr.Key +// let nextKey = nextAttr.Key +// let prevWidget = prevAttr.Value +// let nextWidget = nextAttr.Value +// +// match prevKey.CompareTo nextKey with +// | c when c < 0 -> +// // prev key is less than next -> remove prev key +// result <- MutStackArray1.addMut(&result, WidgetChange.Removed prevAttr) +// prevIndex <- prevIndex + 1 +// +// | c when c > 0 -> +// // prev key is more than next -> add next item +// result <- MutStackArray1.addMut(&result, WidgetChange.Added nextAttr) +// nextIndex <- nextIndex + 1 +// +// | _ -> +// // means that we are targeting the same attribute +// +// // move both pointers +// prevIndex <- prevIndex + 1 +// nextIndex <- nextIndex + 1 +// +// let changeOpt = +// if prevWidget = nextWidget then +// ValueNone +// elif canReuseView prevWidget nextWidget then +// match diffWidget canReuseView (ValueSome prevWidget) nextWidget with +// | ValueNone -> ValueNone +// | ValueSome diffs -> ValueSome(WidgetChange.Updated struct (nextAttr, diffs)) +// else +// ValueSome(WidgetChange.ReplacedBy nextAttr) +// +// match changeOpt with +// | ValueNone -> () +// | ValueSome change -> result <- MutStackArray1.addMut(&result, change) +// +// MutStackArray1.toArraySlice &result +// +// +// and diffWidgetCollectionAttributes +// (canReuseView: Widget -> Widget -> bool) +// (prev: WidgetCollectionAttribute [] voption) +// (next: WidgetCollectionAttribute [] voption) +// : ArraySlice voption = +// +// match (prev, next) with +// | ValueNone, ValueNone -> ValueNone +// +// // all were deleted +// | ValueSome prev, ValueNone -> +// prev +// |> Array.map WidgetCollectionChange.Removed +// |> ArraySlice.fromArray +// +// | ValueNone, ValueSome next -> +// next +// |> Array.map WidgetCollectionChange.Added +// |> ArraySlice.fromArray +// +// | ValueSome prev, ValueSome next -> +// +// let mutable result = MutStackArray1.Empty +// +// +// let mutable prevIndex = 0 +// let mutable nextIndex = 0 +// +// let prevLength = prev.Length +// let nextLength = next.Length +// +// while not(prevIndex >= prevLength && nextIndex >= nextLength) do +// if prevIndex = prevLength then +// // that means we are done with the prev and only need to add next's tail to added +// // DiffBuilder.addOpMut &result DiffBuilder.Add (uint16 nextIndex) +// result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Added next.[nextIndex]) +// +// +// nextIndex <- nextIndex + 1 +// +// elif nextIndex = nextLength then +// // that means that we are done with new items and only need prev's tail to removed +// // DiffBuilder.addOpMut &result DiffBuilder.Remove (uint16 prevIndex) +// result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Removed prev.[prevIndex]) +// +// +// prevIndex <- prevIndex + 1 +// +// else +// // we haven't reached either of the ends +// let prevAttr = prev.[prevIndex] +// let nextAttr = next.[nextIndex] +// +// let prevKey = prevAttr.Key +// let nextKey = nextAttr.Key +// let prevWidgetColl = prevAttr.Value +// let nextWidgetColl = nextAttr.Value +// +// match prevKey.CompareTo nextKey with +// | c when c < 0 -> +// // prev key is less than next -> remove prev key +// +// result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Removed prevAttr) +// prevIndex <- prevIndex + 1 +// +// | c when c > 0 -> +// // prev key is more than next -> add next item +// result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Added nextAttr) +// nextIndex <- nextIndex + 1 +// +// | _ -> +// // means that we are targeting the same attribute +// +// // move both pointers +// prevIndex <- prevIndex + 1 +// nextIndex <- nextIndex + 1 +// +// let diff = +// diffWidgetCollections canReuseView prevWidgetColl nextWidgetColl +// +// match diff with +// | ValueNone -> () +// | ValueSome slice -> +// let change = +// WidgetCollectionChange.Updated struct (nextAttr, slice) +// +// result <- MutStackArray1.addMut(&result, change) +// +// MutStackArray1.toArraySlice &result +// +// and diffWidgetCollections +// (canReuseView: Widget -> Widget -> bool) +// (prev: ArraySlice) +// (next: ArraySlice) +// : ArraySlice voption = +// let mutable result = MutStackArray1.Empty +// +// let prev = ArraySlice.toSpan prev +// let next = ArraySlice.toSpan next +// +// if prev.Length > next.Length then +// for i = next.Length to prev.Length - 1 do +// result <- MutStackArray1.addMut(&result, WidgetCollectionItemChange.Remove i) +// +// for i = 0 to next.Length - 1 do +// let currItem = next.[i] +// // index < 0 || index >= array.Length ? (FSharpOption) null : FSharpOption.Some(array[index]); +// let prevItemOpt = +// if (i >= prev.Length) then +// ValueNone +// else +// ValueSome prev.[i] +// +// let changeOpt = +// match prevItemOpt with +// | ValueNone -> ValueSome(WidgetCollectionItemChange.Insert struct (i, currItem)) +// +// | ValueSome prevItem when canReuseView prevItem currItem -> +// +// match diffWidget canReuseView (ValueSome prevItem) currItem with +// | ValueNone -> ValueNone +// | ValueSome diffs -> ValueSome(WidgetCollectionItemChange.Update struct (i, diffs)) +// +// | ValueSome _ -> ValueSome(WidgetCollectionItemChange.Replace struct (i, currItem)) +// +// match changeOpt with +// | ValueNone -> () +// | ValueSome change -> result <- MutStackArray1.addMut(&result, change) +// +// MutStackArray1.toArraySlice &result +// +// and diffWidget +// (canReuseView: Widget -> Widget -> bool) +// (prevOpt: Widget voption) +// (next: Widget) +// : WidgetDiff voption = +// let prevScalarAttributes = +// match prevOpt with +// | ValueNone -> ValueNone +// | ValueSome widget -> widget.ScalarAttributes +// +// let prevWidgetAttributes = +// match prevOpt with +// | ValueNone -> ValueNone +// | ValueSome widget -> widget.WidgetAttributes +// +// let prevWidgetCollectionAttributes = +// match prevOpt with +// | ValueNone -> ValueNone +// | ValueSome widget -> widget.WidgetCollectionAttributes +// +// let scalarDiffs = +// ScalarChanges(prevScalarAttributes, next.ScalarAttributes) +// +// let widgetDiffs = +// diffWidgetAttributes canReuseView prevWidgetAttributes next.WidgetAttributes +// +// let collectionDiffs = +// diffWidgetCollectionAttributes canReuseView prevWidgetCollectionAttributes next.WidgetCollectionAttributes +// +// +// ValueSome +// { ScalarChanges = scalarDiffs +// WidgetChanges = widgetDiffs +// WidgetCollectionChanges = collectionDiffs } // TODO convert to Enumerator all diffs // match (scalarDiffs, widgetDiffs, collectionDiffs) with @@ -384,15 +383,10 @@ module Reconciler = (next: Widget) (node: IViewNode) : unit = - match diffWidget canReuseView prevOpt next with - | ValueNone -> () - | ValueSome diff -> - node.ApplyScalarDiffs(&diff.ScalarChanges) - match diff.WidgetChanges with - | ValueSome slice -> node.ApplyWidgetDiffs(ArraySlice.toSpan slice) - | ValueNone -> () + let diff = + WidgetDiff.create(prevOpt, next, canReuseView) - match diff.WidgetCollectionChanges with - | ValueSome slice -> node.ApplyWidgetCollectionDiffs(ArraySlice.toSpan slice) - | ValueNone -> () + node.ApplyScalarDiffs(&diff.ScalarChanges) + node.ApplyWidgetDiffs(&diff.WidgetChanges) + node.ApplyWidgetCollectionDiffs(&diff.WidgetCollectionChanges) From 593540aa4337ea0c5360238d0c870d8df3d40ae0 Mon Sep 17 00:00:00 2001 From: Simon Korzunov Date: Mon, 17 Jan 2022 22:27:00 -0800 Subject: [PATCH 3/9] WIP reorganized streamed diffing + removed tuple allocations --- src/Fabulous.Tests/TestUI.Attributes.fs | 4 +- src/Fabulous.Tests/TestUI.ViewUpdaters.fs | 8 +- src/Fabulous/AttributeDefinitions.fs | 42 +- src/Fabulous/Attributes.fs | 36 +- src/Fabulous/Core.fs | 545 ---------------------- src/Fabulous/Fabulous.fsproj | 2 + src/Fabulous/IViewNode.fs | 25 + src/Fabulous/MapMsg.fs | 2 +- src/Fabulous/Memo.fs | 10 +- src/Fabulous/Reconciler.fs | 381 +-------------- src/Fabulous/Runners.fs | 17 +- src/Fabulous/ViewNode.fs | 137 +++--- src/Fabulous/WidgetDiff.fs | 536 +++++++++++++++++++++ 13 files changed, 707 insertions(+), 1038 deletions(-) create mode 100644 src/Fabulous/IViewNode.fs create mode 100644 src/Fabulous/WidgetDiff.fs diff --git a/src/Fabulous.Tests/TestUI.Attributes.fs b/src/Fabulous.Tests/TestUI.Attributes.fs index 6ce45dbe8..82b61530f 100644 --- a/src/Fabulous.Tests/TestUI.Attributes.fs +++ b/src/Fabulous.Tests/TestUI.Attributes.fs @@ -6,7 +6,7 @@ open Tests.Platform module Attributes = let definePressable name = - let key = AttributeDefinitionStore.getNextKey () + let key = AttributeDefinitionStore.getNextKey() let definition: ScalarAttributeDefinition = { Key = key @@ -15,7 +15,7 @@ module Attributes = ConvertValue = id Compare = ScalarAttributeComparers.noCompare UpdateNode = - fun (newValueOpt, node) -> + fun struct (newValueOpt, node) -> let btn = node.Target :?> IButton diff --git a/src/Fabulous.Tests/TestUI.ViewUpdaters.fs b/src/Fabulous.Tests/TestUI.ViewUpdaters.fs index e9a93ee19..8f260fddb 100644 --- a/src/Fabulous.Tests/TestUI.ViewUpdaters.fs +++ b/src/Fabulous.Tests/TestUI.ViewUpdaters.fs @@ -37,19 +37,19 @@ open Tests.Platform // navigationPage.PushAsync(page) |> ignore -let updateText (newValueOpt: string voption, node: IViewNode) = +let updateText struct (newValueOpt: string voption, node: IViewNode) = let textElement = node.Target :?> IText textElement.Text <- ValueOption.defaultValue "" newValueOpt -let updateRecord (newValueOpt: bool voption, node: IViewNode) = +let updateRecord struct (newValueOpt: bool voption, node: IViewNode) = let textElement = node.Target :?> TestLabel textElement.record <- ValueOption.defaultValue false newValueOpt -let updateTextColor (newValueOpt: string voption, node: IViewNode) = +let updateTextColor struct (newValueOpt: string voption, node: IViewNode) = let textElement = node.Target :?> IText textElement.TextColor <- ValueOption.defaultValue "" newValueOpt -let updateAutomationId (newValueOpt: string voption, node: IViewNode) = +let updateAutomationId struct (newValueOpt: string voption, node: IViewNode) = let el = node.Target :?> TestViewElement el.AutomationId <- ValueOption.defaultValue "" newValueOpt diff --git a/src/Fabulous/AttributeDefinitions.fs b/src/Fabulous/AttributeDefinitions.fs index b133f8d8d..2dcdf0a8e 100644 --- a/src/Fabulous/AttributeDefinitions.fs +++ b/src/Fabulous/AttributeDefinitions.fs @@ -1,7 +1,16 @@ namespace Fabulous +open System.Collections.Generic open Fabulous +type IAttributeDefinition = + abstract member Key : AttributeKey + abstract member UpdateNode : struct (obj voption * IViewNode) -> unit + +type IScalarAttributeDefinition = + inherit IAttributeDefinition + abstract member CompareBoxed : a: obj * b: obj -> ScalarAttributeComparison + /// Attribute definition for scalar properties type ScalarAttributeDefinition<'inputType, 'modelType, 'valueType> = @@ -9,8 +18,8 @@ type ScalarAttributeDefinition<'inputType, 'modelType, 'valueType> = Name: string Convert: 'inputType -> 'modelType ConvertValue: 'modelType -> 'valueType - Compare: 'modelType * 'modelType -> ScalarAttributeComparison - UpdateNode: 'valueType voption * IViewNode -> unit } + Compare: struct ('modelType * 'modelType) -> ScalarAttributeComparison + UpdateNode: struct ('valueType voption * IViewNode) -> unit } member x.WithValue(value) : ScalarAttribute = { Key = x.Key @@ -25,7 +34,7 @@ type ScalarAttributeDefinition<'inputType, 'modelType, 'valueType> = member x.CompareBoxed(a, b) = x.Compare(unbox<'modelType> a, unbox<'modelType> b) - member x.UpdateNode(newValueOpt, node) = + member x.UpdateNode(struct (newValueOpt, node)) = let newValueOpt = match newValueOpt with | ValueNone -> ValueNone @@ -37,8 +46,8 @@ type ScalarAttributeDefinition<'inputType, 'modelType, 'valueType> = type WidgetAttributeDefinition = { Key: AttributeKey Name: string - ApplyDiff: WidgetDiff * IViewNode -> unit - UpdateNode: Widget voption * IViewNode -> unit } + ApplyDiff: struct (WidgetDiff * IViewNode) -> unit + UpdateNode: struct (Widget voption * IViewNode) -> unit } member x.WithValue(value: Widget) : WidgetAttribute = { Key = x.Key @@ -50,7 +59,7 @@ type WidgetAttributeDefinition = interface IAttributeDefinition with member x.Key = x.Key - member x.UpdateNode(newValueOpt, node) = + member x.UpdateNode(struct (newValueOpt, node)) = let newValueOpt = match newValueOpt with | ValueNone -> ValueNone @@ -62,8 +71,8 @@ type WidgetAttributeDefinition = type WidgetCollectionAttributeDefinition = { Key: AttributeKey Name: string - ApplyDiff: WidgetCollectionItemChanges * IViewNode -> unit - UpdateNode: ArraySlice voption * IViewNode -> unit } + ApplyDiff: struct (WidgetCollectionItemChanges * IViewNode) -> unit + UpdateNode: struct (ArraySlice voption * IViewNode) -> unit } member x.WithValue(value: ArraySlice) : WidgetCollectionAttribute = { Key = x.Key @@ -75,10 +84,25 @@ type WidgetCollectionAttributeDefinition = interface IAttributeDefinition with member x.Key = x.Key - member x.UpdateNode(newValueOpt, node) = + member x.UpdateNode(struct (newValueOpt, node)) = let newValueOpt = match newValueOpt with | ValueNone -> ValueNone | ValueSome v -> ValueSome(unbox> v) x.UpdateNode(newValueOpt, node) + +module AttributeDefinitionStore = + let private _attributes = + Dictionary() + + let mutable private _nextKey = 0 + + let get key = _attributes.[key] + let set key value = _attributes.[key] <- value + let remove key = _attributes.Remove(key) |> ignore + + let getNextKey () : AttributeKey = + let key = _nextKey + _nextKey <- _nextKey + 1 + key diff --git a/src/Fabulous/Attributes.fs b/src/Fabulous/Attributes.fs index acb475395..e5697d169 100644 --- a/src/Fabulous/Attributes.fs +++ b/src/Fabulous/Attributes.fs @@ -15,9 +15,9 @@ module Helpers = view module ScalarAttributeComparers = - let noCompare (_, _) = ScalarAttributeComparison.Different + let noCompare struct (_, _) = ScalarAttributeComparison.Different - let equalityCompare (a, b) = + let equalityCompare struct (a, b) = if a = b then ScalarAttributeComparison.Identical else @@ -29,8 +29,8 @@ module Attributes = name (convert: 'inputType -> 'modelType) (convertValue: 'modelType -> 'valueType) - (compare: 'modelType * 'modelType -> ScalarAttributeComparison) - (updateNode: 'valueType voption * IViewNode -> unit) + (compare: struct ('modelType * 'modelType) -> ScalarAttributeComparison) + (updateNode: struct ('valueType voption * IViewNode) -> unit) = let key = AttributeDefinitionStore.getNextKey() @@ -48,8 +48,8 @@ module Attributes = /// Define a custom attribute storing a widget let defineWidgetWithConverter name - (applyDiff: WidgetDiff * IViewNode -> unit) - (updateNode: Widget voption * IViewNode -> unit) + (applyDiff: struct (WidgetDiff * IViewNode) -> unit) + (updateNode: struct (Widget voption * IViewNode) -> unit) = let key = AttributeDefinitionStore.getNextKey() @@ -65,8 +65,8 @@ module Attributes = /// Define a custom attribute storing a widget collection let defineWidgetCollectionWithConverter name - (applyDiff: WidgetCollectionItemChanges * IViewNode -> unit) - (updateNode: ArraySlice voption * IViewNode -> unit) + (applyDiff: struct (WidgetCollectionItemChanges * IViewNode) -> unit) + (updateNode: struct (ArraySlice voption * IViewNode) -> unit) = let key = AttributeDefinitionStore.getNextKey() @@ -81,15 +81,13 @@ module Attributes = /// Define an attribute storing a Widget for a CLR property let defineWidget<'T when 'T: null> (name: string) (get: obj -> IViewNode) (set: obj -> 'T -> unit) = - let applyDiff (diff: WidgetDiff, node: IViewNode) = + let applyDiff struct (diff: WidgetDiff, node: IViewNode) = let childNode = get node.Target - childNode.ApplyScalarDiffs(&diff.ScalarChanges) - childNode.ApplyWidgetDiffs(&diff.WidgetChanges) - childNode.ApplyWidgetCollectionDiffs(&diff.WidgetCollectionChanges) + childNode.ApplyDiff(&diff) - let updateNode (newValueOpt: Widget voption, node: IViewNode) = + let updateNode struct (newValueOpt: Widget voption, node: IViewNode) = match newValueOpt with | ValueNone -> set node.Target null | ValueSome widget -> @@ -102,7 +100,7 @@ module Attributes = /// Define an attribute storing a collection of Widget let defineWidgetCollection<'itemType> name (getCollection: obj -> System.Collections.Generic.IList<'itemType>) = - let applyDiff (diffs: WidgetCollectionItemChanges, node: IViewNode) = + let applyDiff struct (diffs: WidgetCollectionItemChanges, node: IViewNode) = let targetColl = getCollection node.Target for diff in diffs do @@ -120,9 +118,7 @@ module Attributes = let childNode = node.TreeContext.GetViewNode(box targetColl.[index]) - childNode.ApplyScalarDiffs(&widgetDiff.ScalarChanges) - childNode.ApplyWidgetDiffs(&widgetDiff.WidgetChanges) - childNode.ApplyWidgetCollectionDiffs(&widgetDiff.WidgetCollectionChanges) + childNode.ApplyDiff(&widgetDiff) | WidgetCollectionItemChange.Replace (index, widget) -> let view = Helpers.createViewForWidget node widget @@ -130,7 +126,7 @@ module Attributes = | _ -> () - let updateNode (newValueOpt: ArraySlice voption, node: IViewNode) = + let updateNode struct (newValueOpt: ArraySlice voption, node: IViewNode) = let targetColl = getCollection node.Target targetColl.Clear() @@ -176,7 +172,7 @@ module Attributes = ConvertValue = id Compare = ScalarAttributeComparers.noCompare UpdateNode = - fun (newValueOpt, node) -> + fun struct (newValueOpt, node) -> let event = getEvent node.Target match node.TryGetHandler(key) with @@ -206,7 +202,7 @@ module Attributes = ConvertValue = id Compare = ScalarAttributeComparers.noCompare UpdateNode = - fun (newValueOpt: ('args -> obj) voption, node: IViewNode) -> + fun struct (newValueOpt: ('args -> obj) voption, node: IViewNode) -> let event = getEvent node.Target match node.TryGetHandler(key) with diff --git a/src/Fabulous/Core.fs b/src/Fabulous/Core.fs index 26f74f7ab..b814ddc1e 100644 --- a/src/Fabulous/Core.fs +++ b/src/Fabulous/Core.fs @@ -1,8 +1,5 @@ namespace Fabulous -open System -open System.Collections.Generic -open System.Runtime.CompilerServices open Fabulous /// Dev notes: @@ -58,545 +55,3 @@ and [] Widget = ScalarAttributes: ScalarAttribute [] voption WidgetAttributes: WidgetAttribute [] voption WidgetCollectionAttributes: WidgetCollectionAttribute [] voption } - - -[] -type ScalarAttributeComparison = - | Identical - | Different - - -[] -type EnumerationMode<'a> = - | AllAddedOrRemoved of struct ('a [] * bool) - | Empty - | ActualDiff of prevNext: struct ('a [] * 'a []) - - -module EnumerationMode = - let fromOptions prev next = - match prev, next with - | ValueNone, ValueNone -> EnumerationMode.Empty - | ValueSome prev, ValueNone -> EnumerationMode.AllAddedOrRemoved(prev, false) - | ValueNone, ValueSome next -> EnumerationMode.AllAddedOrRemoved(next, true) - | ValueSome prev, ValueSome next -> EnumerationMode.ActualDiff(prev, next) - -module AttributeDefinitionStore = - let private _attributes = Dictionary() - - let mutable private _nextKey = 0 - - let get key = _attributes.[key] - let set key value = _attributes.[key] <- value - let remove key = _attributes.Remove(key) |> ignore - - let getNextKey () : AttributeKey = - let key = _nextKey - _nextKey <- _nextKey + 1 - key - - -[] -type ScalarChange = - | Added of added: ScalarAttribute - | Removed of removed: ScalarAttribute - | Updated of updated: ScalarAttribute - -and [] WidgetChange = - | Added of added: WidgetAttribute - | Removed of removed: WidgetAttribute - | Updated of updated: struct (WidgetAttribute * WidgetDiff) - | ReplacedBy of replacedBy: WidgetAttribute - -and [] WidgetCollectionChange = - | Added of added: WidgetCollectionAttribute - | Removed of removed: WidgetCollectionAttribute - | Updated of updated: struct (WidgetCollectionAttribute * WidgetCollectionItemChanges) - -and [] WidgetCollectionItemChange = - | Insert of widgetInserted: struct (int * Widget) - | Replace of widgetReplaced: struct (int * Widget) - | Update of widgetUpdated: struct (int * WidgetDiff) - | Remove of removed: int - -and [] WidgetDiff = - { ScalarChanges: ScalarChanges - WidgetChanges: WidgetChanges - WidgetCollectionChanges: WidgetCollectionChanges } - - static member inline create - ( - prevOpt: Widget voption, - next: Widget, - canReuseView: Widget -> Widget -> bool - ) : WidgetDiff = - - let prevScalarAttributes = - match prevOpt with - | ValueNone -> ValueNone - | ValueSome widget -> widget.ScalarAttributes - - let prevWidgetAttributes = - match prevOpt with - | ValueNone -> ValueNone - | ValueSome widget -> widget.WidgetAttributes - - let prevWidgetCollectionAttributes = - match prevOpt with - | ValueNone -> ValueNone - | ValueSome widget -> widget.WidgetCollectionAttributes - - { ScalarChanges = ScalarChanges(prevScalarAttributes, next.ScalarAttributes) - WidgetChanges = WidgetChanges(prevWidgetAttributes, next.WidgetAttributes, canReuseView) - WidgetCollectionChanges = - WidgetCollectionChanges(prevWidgetCollectionAttributes, next.WidgetCollectionAttributes, canReuseView) } - -and IAttributeDefinition = - abstract member Key : AttributeKey - abstract member UpdateNode : newValueOpt: obj voption * node: IViewNode -> unit - -and IScalarAttributeDefinition = - inherit IAttributeDefinition - abstract member CompareBoxed : a: obj * b: obj -> ScalarAttributeComparison - -/// Context of the whole view tree - -and [] ViewTreeContext = - { CanReuseView: Widget -> Widget -> bool - GetViewNode: obj -> IViewNode - Dispatch: obj -> unit } - -and IViewNode = - abstract member Target : obj - abstract member Parent : IViewNode voption - abstract member TreeContext : ViewTreeContext - abstract member MapMsg : (obj -> obj) voption with get, set - - // note that Widget is struct type, thus we have boxing via option - // we don't have MemoizedWidget set for 99.9% of the cases - // thus makes sense to have overhead of boxing - // in order to save space - abstract member MemoizedWidget : Widget option with get, set - abstract member TryGetHandler<'T> : AttributeKey -> 'T voption - abstract member SetHandler<'T> : AttributeKey * 'T voption -> unit - abstract member ApplyScalarDiffs : ScalarChanges inref -> unit - abstract member ApplyWidgetDiffs : WidgetChanges inref -> unit - abstract member ApplyWidgetCollectionDiffs : WidgetCollectionChanges inref -> unit - - - -// TODO break and chain here -and [] ScalarChanges - ( - prev: ScalarAttribute [] voption, - next: ScalarAttribute [] voption - ) = - member _.GetEnumerator() = - ScalarChangesEnumerator(EnumerationMode.fromOptions prev next) - -and [] WidgetChanges - ( - prev: WidgetAttribute [] voption, - next: WidgetAttribute [] voption, - canReuseView: Widget -> Widget -> bool - ) = - member _.GetEnumerator() = - WidgetChangesEnumerator(EnumerationMode.fromOptions prev next, canReuseView) - -and [] WidgetCollectionChanges - ( - prev: WidgetCollectionAttribute [] voption, - next: WidgetCollectionAttribute [] voption, - canReuseView: Widget -> Widget -> bool - ) = - member _.GetEnumerator() = - WidgetCollectionChangesEnumerator(EnumerationMode.fromOptions prev next, canReuseView) - - -and [] WidgetCollectionItemChanges - ( - prev: ArraySlice, - next: ArraySlice, - canReuseView: Widget -> Widget -> bool - ) = - member _.GetEnumerator() = - WidgetCollectionItemChangesEnumerator(ArraySlice.toSpan prev, ArraySlice.toSpan next, canReuseView) - -// enumerators -and [] ScalarChangesEnumerator(mode: EnumerationMode) = - - [] - val mutable private current: ScalarChange - - [] - val mutable private prevIndex: int - - [] - val mutable private nextIndex: int - - member e.Current = e.current - - member e.MoveNext() = - match mode with - | EnumerationMode.Empty -> false - | EnumerationMode.AllAddedOrRemoved (struct (attributes, added)) -> - // use prevIndex regardless if it is for adding or removal - let i = e.prevIndex - - if i < attributes.Length then - let attribute = attributes.[i] - - e.current <- - match added with - | false -> ScalarChange.Removed attribute - | true -> ScalarChange.Added attribute - - e.prevIndex <- i + 1 - true - else - false - - | EnumerationMode.ActualDiff (struct (prev, next)) -> - let mutable prevIndex = e.prevIndex - let mutable nextIndex = e.nextIndex - - let prevLength = prev.Length - let nextLength = next.Length - - let mutable res: bool voption = ValueNone - // that needs to be in a loop until we have a change - - while ValueOption.isNone res do - - if not(prevIndex >= prevLength && nextIndex >= nextLength) then - if prevIndex = prevLength then - // that means we are done with the prev and only need to add next's tail to added - e.current <- ScalarChange.Added next.[nextIndex] - res <- ValueSome true - nextIndex <- nextIndex + 1 - - elif nextIndex = nextLength then - // that means that we are done with new items and only need prev's tail to removed - e.current <- ScalarChange.Removed prev.[prevIndex] - res <- ValueSome true - prevIndex <- prevIndex + 1 - - else - // we haven't reached either of the ends - let prevAttr = prev.[prevIndex] - let nextAttr = next.[nextIndex] - - let prevKey = prevAttr.Key - let nextKey = nextAttr.Key - - match prevKey.CompareTo nextKey with - | c when c < 0 -> - // prev key is less than next -> remove prev key - e.current <- ScalarChange.Removed prev.[prevIndex] - res <- ValueSome true - prevIndex <- prevIndex + 1 - - | c when c > 0 -> - // prev key is more than next -> add next item - e.current <- ScalarChange.Added prev.[nextIndex] - res <- ValueSome true - nextIndex <- nextIndex + 1 - - | _ -> - // means that we are targeting the same attribute - let definition = - AttributeDefinitionStore.get prevAttr.Key :?> IScalarAttributeDefinition - - match definition.CompareBoxed(prevAttr.Value, nextAttr.Value) with - // Previous and next values are identical, we don't need to do anything - | ScalarAttributeComparison.Identical -> () - - // New value completely replaces the old value - | ScalarAttributeComparison.Different -> - e.current <- ScalarChange.Updated next.[nextIndex] - res <- ValueSome true - - // move both pointers - prevIndex <- prevIndex + 1 - nextIndex <- nextIndex + 1 - - else - res <- ValueSome false - - e.prevIndex <- prevIndex - e.nextIndex <- nextIndex - - match res with - | ValueNone -> false - | ValueSome res -> res - -and [] WidgetChangesEnumerator - ( - mode: EnumerationMode, - canReuseView: Widget -> Widget -> bool - ) = - - [] - val mutable private current: WidgetChange - - [] - val mutable private prevIndex: int - - [] - val mutable private nextIndex: int - - member e.Current = e.current - - member e.MoveNext() = - match mode with - | EnumerationMode.Empty -> false - | EnumerationMode.AllAddedOrRemoved (struct (values, added)) -> - // use prevIndex regardless if it is for adding or removal - let i = e.prevIndex - - if i < values.Length then - let value = values.[i] - - e.current <- - match added with - | false -> WidgetChange.Removed value - | true -> WidgetChange.Added value - - e.prevIndex <- i + 1 - true - else - false - - | EnumerationMode.ActualDiff (struct (prev, next)) -> - let mutable prevIndex = e.prevIndex - let mutable nextIndex = e.nextIndex - - let prevLength = prev.Length - let nextLength = next.Length - - let mutable res: bool voption = ValueNone - // that needs to be in a loop until we have a change - - while ValueOption.isNone res do - if not(prevIndex >= prevLength && nextIndex >= nextLength) then - if prevIndex = prevLength then - // that means we are done with the prev and only need to add next's tail to added - e.current <- WidgetChange.Added next.[nextIndex] - res <- ValueSome true - nextIndex <- nextIndex + 1 - - elif nextIndex = nextLength then - // that means that we are done with new items and only need prev's tail to removed - e.current <- WidgetChange.Removed prev.[prevIndex] - res <- ValueSome true - prevIndex <- prevIndex + 1 - - else - // we haven't reached either of the ends - let prevAttr = prev.[prevIndex] - let nextAttr = next.[nextIndex] - - let prevKey = prevAttr.Key - let nextKey = nextAttr.Key - let prevWidget = prevAttr.Value - let nextWidget = nextAttr.Value - - match prevKey.CompareTo nextKey with - | c when c < 0 -> - // prev key is less than next -> remove prev key - e.current <- WidgetChange.Removed prevAttr - res <- ValueSome true - - prevIndex <- prevIndex + 1 - - | c when c > 0 -> - // prev key is more than next -> add next item - e.current <- WidgetChange.Added nextAttr - res <- ValueSome true - nextIndex <- nextIndex + 1 - - | _ -> - // means that we are targeting the same attribute - - // move both pointers - prevIndex <- prevIndex + 1 - nextIndex <- nextIndex + 1 - - if prevWidget <> nextWidget then - - let change = - if canReuseView prevWidget nextWidget then - let diff = - WidgetDiff.create((ValueSome prevWidget), nextWidget, canReuseView) - - WidgetChange.Updated struct (nextAttr, diff) - else - WidgetChange.ReplacedBy nextAttr - - e.current <- change - res <- ValueSome true - - else - res <- ValueSome false - - e.prevIndex <- prevIndex - e.nextIndex <- nextIndex - - match res with - | ValueNone -> false - | ValueSome res -> res - -and [] WidgetCollectionChangesEnumerator - ( - mode: EnumerationMode, - canReuseView: Widget -> Widget -> bool - ) = - - [] - val mutable private current: WidgetCollectionChange - - [] - val mutable private prevIndex: int - - [] - val mutable private nextIndex: int - - member e.Current = e.current - - member e.MoveNext() = - match mode with - | EnumerationMode.Empty -> false - | EnumerationMode.AllAddedOrRemoved (struct (values, added)) -> - // use prevIndex regardless if it is for adding or removal - let i = e.prevIndex - - if i < values.Length then - let value = values.[i] - - e.current <- - match added with - | false -> WidgetCollectionChange.Removed value - | true -> WidgetCollectionChange.Added value - - e.prevIndex <- i + 1 - true - else - false - - | EnumerationMode.ActualDiff (struct (prev, next)) -> - let mutable prevIndex = e.prevIndex - let mutable nextIndex = e.nextIndex - - let prevLength = prev.Length - let nextLength = next.Length - - // that needs to be in a loop until we have a change - - let res = - if not(prevIndex >= prevLength && nextIndex >= nextLength) then - if prevIndex = prevLength then - // that means we are done with the prev and only need to add next's tail to added - e.current <- WidgetCollectionChange.Added next.[nextIndex] - nextIndex <- nextIndex + 1 - - elif nextIndex = nextLength then - // that means that we are done with new items and only need prev's tail to removed - e.current <- WidgetCollectionChange.Removed prev.[prevIndex] - prevIndex <- prevIndex + 1 - else - // we haven't reached either of the ends - let prevAttr = prev.[prevIndex] - let nextAttr = next.[nextIndex] - - let prevKey = prevAttr.Key - let nextKey = nextAttr.Key - let prevWidgetColl = prevAttr.Value - let nextWidgetColl = nextAttr.Value - - match prevKey.CompareTo nextKey with - | c when c < 0 -> - // prev key is less than next -> remove prev key - e.current <- WidgetCollectionChange.Removed prevAttr - prevIndex <- prevIndex + 1 - - | c when c > 0 -> - // prev key is more than next -> add next item - e.current <- WidgetCollectionChange.Added nextAttr - nextIndex <- nextIndex + 1 - - | _ -> - // means that we are targeting the same attribute - - // move both pointers - prevIndex <- prevIndex + 1 - nextIndex <- nextIndex + 1 - - let diff = - WidgetCollectionItemChanges(prevWidgetColl, nextWidgetColl, canReuseView) - - e.current <- WidgetCollectionChange.Updated struct (nextAttr, diff) - - true - else - false - - e.prevIndex <- prevIndex - e.nextIndex <- nextIndex - - res - -and [] WidgetCollectionItemChangesEnumerator - ( - prev: Span, - next: Span, - canReuseView: Widget -> Widget -> bool - ) = - [] - val mutable private current: WidgetCollectionItemChange - - [] - val mutable private index: int - - [] - val mutable private tailIndex: int - - member e.Current = e.current - - member e.MoveNext() = - let tailIndex = e.tailIndex - let i = e.index - - if prev.Length > next.Length - && tailIndex < prev.Length - next.Length then - - e.current <- WidgetCollectionItemChange.Remove(next.Length - tailIndex) - e.tailIndex <- tailIndex + 1 - - true - - elif i < next.Length then - let currItem = next.[i] - - let prevItemOpt = - if (i >= prev.Length) then - ValueNone - else - ValueSome prev.[i] - - match prevItemOpt with - | ValueNone -> e.current <- WidgetCollectionItemChange.Insert struct (i, currItem) - - | ValueSome prevItem when canReuseView prevItem currItem -> - - e.current <- - WidgetCollectionItemChange.Update - struct (i, WidgetDiff.create(ValueSome prevItem, currItem, canReuseView)) - - | ValueSome _ -> e.current <- WidgetCollectionItemChange.Replace struct (i, currItem) - - e.index <- i + 1 - true - - else - // means that we are done iterating - false diff --git a/src/Fabulous/Fabulous.fsproj b/src/Fabulous/Fabulous.fsproj index 915a6734b..9e83cf180 100644 --- a/src/Fabulous/Fabulous.fsproj +++ b/src/Fabulous/Fabulous.fsproj @@ -12,6 +12,8 @@ + + diff --git a/src/Fabulous/IViewNode.fs b/src/Fabulous/IViewNode.fs new file mode 100644 index 000000000..aa2418392 --- /dev/null +++ b/src/Fabulous/IViewNode.fs @@ -0,0 +1,25 @@ +namespace Fabulous + + +/// Context of the whole view tree +[] +type ViewTreeContext = + { CanReuseView: Widget -> Widget -> bool + GetViewNode: obj -> IViewNode + Dispatch: obj -> unit } + +and IViewNode = + abstract member Target : obj + abstract member Parent : IViewNode voption + abstract member TreeContext : ViewTreeContext + abstract member MapMsg : (obj -> obj) voption with get, set + + // note that Widget is struct type, thus we have boxing via option + // we don't have MemoizedWidget set for 99.9% of the cases + // thus makes sense to have overhead of boxing + // in order to save space + abstract member MemoizedWidget : Widget option with get, set + abstract member TryGetHandler<'T> : AttributeKey -> 'T voption + abstract member SetHandler<'T> : AttributeKey * 'T voption -> unit + + abstract member ApplyDiff : WidgetDiff inref -> unit diff --git a/src/Fabulous/MapMsg.fs b/src/Fabulous/MapMsg.fs index 655e0073c..7076a8899 100644 --- a/src/Fabulous/MapMsg.fs +++ b/src/Fabulous/MapMsg.fs @@ -7,7 +7,7 @@ module MapMsg = id id ScalarAttributeComparers.noCompare - (fun (value, node) -> + (fun struct (value, node) -> match value with | ValueNone -> node.MapMsg <- ValueNone | ValueSome fn -> node.MapMsg <- ValueSome fn) diff --git a/src/Fabulous/Memo.fs b/src/Fabulous/Memo.fs index e6f03a3b8..2a1a0b5bf 100644 --- a/src/Fabulous/Memo.fs +++ b/src/Fabulous/Memo.fs @@ -24,8 +24,8 @@ module Memo = type Memoized<'t> = { phantom: 't } - let private MemoAttributeKey = AttributeDefinitionStore.getNextKey () - let internal MemoWidgetKey = WidgetDefinitionStore.getNextKey () + let private MemoAttributeKey = AttributeDefinitionStore.getNextKey() + let internal MemoWidgetKey = WidgetDefinitionStore.getNextKey() let inline private getMemoData (widget: Widget) : MemoData = match widget.ScalarAttributes with @@ -35,7 +35,7 @@ module Memo = let internal canReuseMemoizedWidget prev next = (getMemoData prev).MarkerType = (getMemoData next).MarkerType - let private compareAttributes (prev: MemoData, next: MemoData) : ScalarAttributeComparison = + let private compareAttributes struct (prev: MemoData, next: MemoData) : ScalarAttributeComparison = match (prev.KeyType = next.KeyType, prev.MarkerType = next.MarkerType) with | true, true -> match next.KeyComparer next.KeyData prev.KeyData with @@ -43,7 +43,7 @@ module Memo = | false -> ScalarAttributeComparison.Different | _ -> ScalarAttributeComparison.Different - let private updateNode (data: MemoData voption, node: IViewNode) : unit = + let private updateNode struct (data: MemoData voption, node: IViewNode) : unit = match data with | ValueSome memoData -> let memoizedWidget = memoData.CreateWidget memoData.KeyData @@ -75,7 +75,7 @@ module Memo = let private widgetDefinition: WidgetDefinition = { Key = MemoWidgetKey Name = "Memo" - TargetType = Unchecked.defaultof<_> // Memo isn't allowed in lists, so this will never get called + TargetType = Unchecked.defaultof<_> CreateView = fun (widget, context, parentNode) -> diff --git a/src/Fabulous/Reconciler.fs b/src/Fabulous/Reconciler.fs index aa5d3987a..a85dd8e55 100644 --- a/src/Fabulous/Reconciler.fs +++ b/src/Fabulous/Reconciler.fs @@ -3,380 +3,13 @@ open Fabulous module Reconciler = - /// Let's imagine that we have the following situation - /// prev = [|1,2,6,7|] note that it is sorted - /// next = [|8,5,6,2|] unsorted - /// In reality we have Key and Value, but let's pretend that we only care about keys for now - /// - /// then the desired outcome is this - /// added = [5, 8] - /// removed = [1, 7] - /// - /// Approach - /// 1. we sort both arrays - /// prev = [|1,2,6,7|] - /// next = [|2,5,6,8|] - /// - /// 2. Starting from index 0 for both of the arrays - /// if prevItem < nextItem then - /// inc prevIndex, add prevItem to removed, goto 2 - /// if prevItem > nextItem - /// inc nextIndex, add nextItem to added, goto 2 - /// else (meaning equals) - /// compare values - /// - /// break when we reached both ends of the arrays -// let rec diffScalarAttributes -// (prev: ScalarAttribute [] voption) -// (next: ScalarAttribute [] voption) -// : ScalarChange [] voption = -// match (prev, next) with -// | ValueNone, ValueNone -> ValueNone -// -// // all were deleted -// | ValueSome prev, ValueNone -> -// prev -// |> Array.map ScalarChange.Removed -// |> ValueSome -// | ValueNone, ValueSome next -> next |> Array.map ScalarChange.Added |> ValueSome -// | ValueSome prev, ValueSome next -> -// -// let mutable result = DiffBuilder.create () -// -// let mutable prevIndex = 0 -// let mutable nextIndex = 0 -// -// let prevLength = prev.Length -// let nextLength = next.Length -// -// while not (prevIndex >= prevLength && nextIndex >= nextLength) do -// if prevIndex = prevLength then -// // that means we are done with the prev and only need to add next's tail to added -// //result <- StackArray3.add(&result, (ScalarChange.Added next.[nextIndex])) -// DiffBuilder.addOpMut &result DiffBuilder.Add (uint16 nextIndex) -// nextIndex <- nextIndex + 1 -// -// elif nextIndex = nextLength then -// // that means that we are done with new items and only need prev's tail to removed -// // result <- StackArray3.add(&result, ScalarChange.Removed prev.[prevIndex]) -// DiffBuilder.addOpMut &result DiffBuilder.Remove (uint16 prevIndex) -// prevIndex <- prevIndex + 1 -// -// else -// // we haven't reached either of the ends -// let prevAttr = prev.[prevIndex] -// let nextAttr = next.[nextIndex] -// -// let prevKey = prevAttr.Key -// let nextKey = nextAttr.Key -// -// match prevKey.CompareTo nextKey with -// | c when c < 0 -> -// // prev key is less than next -> remove prev key -// DiffBuilder.addOpMut &result DiffBuilder.Remove (uint16 prevIndex) -// // result <- StackArray3.add(&result, ScalarChange.Removed prevAttr) -// prevIndex <- prevIndex + 1 -// -// | c when c > 0 -> -// // prev key is more than next -> add next item -// // result <- StackArray3.add(&result, ScalarChange.Added nextAttr) -// DiffBuilder.addOpMut &result DiffBuilder.Add (uint16 nextIndex) -// nextIndex <- nextIndex + 1 -// -// | _ -> -// // means that we are targeting the same attribute -// -// let definition = -// AttributeDefinitionStore.get prevAttr.Key :?> IScalarAttributeDefinition -// -// match definition.CompareBoxed(prevAttr.Value, nextAttr.Value) with -// // Previous and next values are identical, we don't need to do anything -// | ScalarAttributeComparison.Identical -> () -// -// // New value completely replaces the old value -// | ScalarAttributeComparison.Different -> -// DiffBuilder.addOpMut &result DiffBuilder.Change (uint16 nextIndex) -// -// // move both pointers -// prevIndex <- prevIndex + 1 -// nextIndex <- nextIndex + 1 -// -// -// match DiffBuilder.lenght &result with -// | 0 -> ValueNone -// | _ -> -// ValueSome( -// DiffBuilder.toArray -// &result -// (fun op -> -// match op with -// | DiffBuilder.Added i -> ScalarChange.Added next.[int i] -// | DiffBuilder.Removed i -> ScalarChange.Removed prev.[int i] -// | DiffBuilder.Changed i -> ScalarChange.Updated next.[int i]) -// ) - // let rec diffWidgetAttributes -// (canReuseView: Widget -> Widget -> bool) -// (prev: WidgetAttribute [] voption) -// (next: WidgetAttribute [] voption) -// : ArraySlice voption = -// -// match (prev, next) with -// | ValueNone, ValueNone -> ValueNone -// -// // all were deleted -// | ValueSome prev, ValueNone -> -// prev -// |> Array.map WidgetChange.Removed -// |> ArraySlice.fromArray -// -// | ValueNone, ValueSome next -> -// next -// |> Array.map WidgetChange.Added -// |> ArraySlice.fromArray -// -// | ValueSome prev, ValueSome next -> -// -// let mutable result = MutStackArray1.Empty -// -// let mutable prevIndex = 0 -// let mutable nextIndex = 0 -// -// let prevLength = prev.Length -// let nextLength = next.Length -// -// while not(prevIndex >= prevLength && nextIndex >= nextLength) do -// if prevIndex = prevLength then -// // that means we are done with the prev and only need to add next's tail to added -// result <- MutStackArray1.addMut(&result, WidgetChange.Added next.[nextIndex]) -// nextIndex <- nextIndex + 1 -// -// elif nextIndex = nextLength then -// // that means that we are done with new items and only need prev's tail to removed -// result <- MutStackArray1.addMut(&result, WidgetChange.Removed prev.[prevIndex]) -// prevIndex <- prevIndex + 1 -// -// else -// // we haven't reached either of the ends -// let prevAttr = prev.[prevIndex] -// let nextAttr = next.[nextIndex] -// -// let prevKey = prevAttr.Key -// let nextKey = nextAttr.Key -// let prevWidget = prevAttr.Value -// let nextWidget = nextAttr.Value -// -// match prevKey.CompareTo nextKey with -// | c when c < 0 -> -// // prev key is less than next -> remove prev key -// result <- MutStackArray1.addMut(&result, WidgetChange.Removed prevAttr) -// prevIndex <- prevIndex + 1 -// -// | c when c > 0 -> -// // prev key is more than next -> add next item -// result <- MutStackArray1.addMut(&result, WidgetChange.Added nextAttr) -// nextIndex <- nextIndex + 1 -// -// | _ -> -// // means that we are targeting the same attribute -// -// // move both pointers -// prevIndex <- prevIndex + 1 -// nextIndex <- nextIndex + 1 -// -// let changeOpt = -// if prevWidget = nextWidget then -// ValueNone -// elif canReuseView prevWidget nextWidget then -// match diffWidget canReuseView (ValueSome prevWidget) nextWidget with -// | ValueNone -> ValueNone -// | ValueSome diffs -> ValueSome(WidgetChange.Updated struct (nextAttr, diffs)) -// else -// ValueSome(WidgetChange.ReplacedBy nextAttr) -// -// match changeOpt with -// | ValueNone -> () -// | ValueSome change -> result <- MutStackArray1.addMut(&result, change) -// -// MutStackArray1.toArraySlice &result -// -// -// and diffWidgetCollectionAttributes -// (canReuseView: Widget -> Widget -> bool) -// (prev: WidgetCollectionAttribute [] voption) -// (next: WidgetCollectionAttribute [] voption) -// : ArraySlice voption = -// -// match (prev, next) with -// | ValueNone, ValueNone -> ValueNone -// -// // all were deleted -// | ValueSome prev, ValueNone -> -// prev -// |> Array.map WidgetCollectionChange.Removed -// |> ArraySlice.fromArray -// -// | ValueNone, ValueSome next -> -// next -// |> Array.map WidgetCollectionChange.Added -// |> ArraySlice.fromArray -// -// | ValueSome prev, ValueSome next -> -// -// let mutable result = MutStackArray1.Empty -// -// -// let mutable prevIndex = 0 -// let mutable nextIndex = 0 -// -// let prevLength = prev.Length -// let nextLength = next.Length -// -// while not(prevIndex >= prevLength && nextIndex >= nextLength) do -// if prevIndex = prevLength then -// // that means we are done with the prev and only need to add next's tail to added -// // DiffBuilder.addOpMut &result DiffBuilder.Add (uint16 nextIndex) -// result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Added next.[nextIndex]) -// -// -// nextIndex <- nextIndex + 1 -// -// elif nextIndex = nextLength then -// // that means that we are done with new items and only need prev's tail to removed -// // DiffBuilder.addOpMut &result DiffBuilder.Remove (uint16 prevIndex) -// result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Removed prev.[prevIndex]) -// -// -// prevIndex <- prevIndex + 1 -// -// else -// // we haven't reached either of the ends -// let prevAttr = prev.[prevIndex] -// let nextAttr = next.[nextIndex] -// -// let prevKey = prevAttr.Key -// let nextKey = nextAttr.Key -// let prevWidgetColl = prevAttr.Value -// let nextWidgetColl = nextAttr.Value -// -// match prevKey.CompareTo nextKey with -// | c when c < 0 -> -// // prev key is less than next -> remove prev key -// -// result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Removed prevAttr) -// prevIndex <- prevIndex + 1 -// -// | c when c > 0 -> -// // prev key is more than next -> add next item -// result <- MutStackArray1.addMut(&result, WidgetCollectionChange.Added nextAttr) -// nextIndex <- nextIndex + 1 -// -// | _ -> -// // means that we are targeting the same attribute -// -// // move both pointers -// prevIndex <- prevIndex + 1 -// nextIndex <- nextIndex + 1 -// -// let diff = -// diffWidgetCollections canReuseView prevWidgetColl nextWidgetColl -// -// match diff with -// | ValueNone -> () -// | ValueSome slice -> -// let change = -// WidgetCollectionChange.Updated struct (nextAttr, slice) -// -// result <- MutStackArray1.addMut(&result, change) -// -// MutStackArray1.toArraySlice &result -// -// and diffWidgetCollections -// (canReuseView: Widget -> Widget -> bool) -// (prev: ArraySlice) -// (next: ArraySlice) -// : ArraySlice voption = -// let mutable result = MutStackArray1.Empty -// -// let prev = ArraySlice.toSpan prev -// let next = ArraySlice.toSpan next -// -// if prev.Length > next.Length then -// for i = next.Length to prev.Length - 1 do -// result <- MutStackArray1.addMut(&result, WidgetCollectionItemChange.Remove i) -// -// for i = 0 to next.Length - 1 do -// let currItem = next.[i] -// // index < 0 || index >= array.Length ? (FSharpOption) null : FSharpOption.Some(array[index]); -// let prevItemOpt = -// if (i >= prev.Length) then -// ValueNone -// else -// ValueSome prev.[i] -// -// let changeOpt = -// match prevItemOpt with -// | ValueNone -> ValueSome(WidgetCollectionItemChange.Insert struct (i, currItem)) -// -// | ValueSome prevItem when canReuseView prevItem currItem -> -// -// match diffWidget canReuseView (ValueSome prevItem) currItem with -// | ValueNone -> ValueNone -// | ValueSome diffs -> ValueSome(WidgetCollectionItemChange.Update struct (i, diffs)) -// -// | ValueSome _ -> ValueSome(WidgetCollectionItemChange.Replace struct (i, currItem)) -// -// match changeOpt with -// | ValueNone -> () -// | ValueSome change -> result <- MutStackArray1.addMut(&result, change) -// -// MutStackArray1.toArraySlice &result -// -// and diffWidget -// (canReuseView: Widget -> Widget -> bool) -// (prevOpt: Widget voption) -// (next: Widget) -// : WidgetDiff voption = -// let prevScalarAttributes = -// match prevOpt with -// | ValueNone -> ValueNone -// | ValueSome widget -> widget.ScalarAttributes -// -// let prevWidgetAttributes = -// match prevOpt with -// | ValueNone -> ValueNone -// | ValueSome widget -> widget.WidgetAttributes -// -// let prevWidgetCollectionAttributes = -// match prevOpt with -// | ValueNone -> ValueNone -// | ValueSome widget -> widget.WidgetCollectionAttributes -// -// let scalarDiffs = -// ScalarChanges(prevScalarAttributes, next.ScalarAttributes) -// -// let widgetDiffs = -// diffWidgetAttributes canReuseView prevWidgetAttributes next.WidgetAttributes -// -// let collectionDiffs = -// diffWidgetCollectionAttributes canReuseView prevWidgetCollectionAttributes next.WidgetCollectionAttributes -// -// -// ValueSome -// { ScalarChanges = scalarDiffs -// WidgetChanges = widgetDiffs -// WidgetCollectionChanges = collectionDiffs } + let private compareScalars (struct (key, a, b): struct (AttributeKey * obj * obj)) : ScalarAttributeComparison = + let def = + AttributeDefinitionStore.get key :?> IScalarAttributeDefinition - // TODO convert to Enumerator all diffs -// match (scalarDiffs, widgetDiffs, collectionDiffs) with -// | ValueNone, ValueNone, ValueNone -> ValueNone -// | _ -> -// ValueSome -// { ScalarChanges = scalarDiffs -// WidgetChanges = widgetDiffs -// WidgetCollectionChanges = collectionDiffs } + def.CompareBoxed(a, b) - /// Diffs changes and applies them on the target let update (canReuseView: Widget -> Widget -> bool) (prevOpt: Widget voption) @@ -385,8 +18,6 @@ module Reconciler = : unit = let diff = - WidgetDiff.create(prevOpt, next, canReuseView) + WidgetDiff.create(prevOpt, next, canReuseView, compareScalars) - node.ApplyScalarDiffs(&diff.ScalarChanges) - node.ApplyWidgetDiffs(&diff.WidgetChanges) - node.ApplyWidgetCollectionDiffs(&diff.WidgetCollectionChanges) + node.ApplyDiff(&diff) diff --git a/src/Fabulous/Runners.fs b/src/Fabulous/Runners.fs index 38253203c..92b10c5bf 100644 --- a/src/Fabulous/Runners.fs +++ b/src/Fabulous/Runners.fs @@ -16,9 +16,9 @@ type IRunner = type IViewAdapter = inherit IDisposable - abstract CreateView: unit -> obj - abstract Attach: obj -> unit - abstract Detach: bool -> unit + abstract CreateView : unit -> obj + abstract Attach : obj -> unit + abstract Detach : bool -> unit module RunnerStore = let private _runners = Dictionary() @@ -55,7 +55,7 @@ module Runners = type Runner<'arg, 'model, 'msg>(key: StateKey, program: Program<'arg, 'model, 'msg>) = let rec processMsg msg = - let model = unbox (StateStore.get key) + let model = unbox(StateStore.get key) let newModel, cmd = program.Update(msg, model) StateStore.set key newModel @@ -83,7 +83,7 @@ module Runners = member _.Stop() = () let create<'arg, 'model, 'msg> (program: Program<'arg, 'model, 'msg>) = - let key = StateStore.getNextKey () + let key = StateStore.getNextKey() let runner = Runner(key, program) RunnerStore.set key runner runner @@ -113,10 +113,10 @@ module ViewAdapters = member private _.Dispatch(msg) = if _allowDispatch then - dispatch (unbox msg) + dispatch(unbox msg) member this.CreateView() = - let state = unbox (StateStore.get stateKey) + let state = unbox(StateStore.get stateKey) let widget = view state _widget <- widget @@ -144,7 +144,6 @@ module ViewAdapters = let node = getViewNode _root - // TODO handle the case when Type of the widget changes Reconciler.update canReuseView (ValueSome prevWidget) currentWidget node _allowDispatch <- true @@ -157,7 +156,7 @@ module ViewAdapters = member _.Detach(shouldDestroyNode) = () let create<'arg, 'model, 'msg> (getViewNode: obj -> IViewNode) (runner: Runner<'arg, 'model, 'msg>) = - let key = ViewAdapterStore.getNextKey () + let key = ViewAdapterStore.getNextKey() let viewAdapter = new ViewAdapter<'model, 'msg>( diff --git a/src/Fabulous/ViewNode.fs b/src/Fabulous/ViewNode.fs index 73598fdcc..24fbd8007 100644 --- a/src/Fabulous/ViewNode.fs +++ b/src/Fabulous/ViewNode.fs @@ -3,6 +3,7 @@ open Fabulous /// Define the logic to apply diffs and store event handlers of its target control +[] type ViewNode(parentNode: IViewNode voption, treeContext: ViewTreeContext, targetRef: System.WeakReference) = // TODO consider combine handlers mapMsg and property bag @@ -10,6 +11,70 @@ type ViewNode(parentNode: IViewNode voption, treeContext: ViewTreeContext, targe // ViewNode is supposed to be mutable, stateful and persistent object let mutable _handlers: Map = Map.empty + member inline private this.ApplyScalarDiffs(diffs: ScalarChanges inref) = + for diff in diffs do + match diff with + | ScalarChange.Added added -> + let definition = + AttributeDefinitionStore.get added.Key :?> IScalarAttributeDefinition + + definition.UpdateNode(ValueSome added.Value, this) + + | ScalarChange.Removed removed -> + let definition = + AttributeDefinitionStore.get removed.Key :?> IScalarAttributeDefinition + + definition.UpdateNode(ValueNone, this) + + | ScalarChange.Updated newAttr -> + let definition = + AttributeDefinitionStore.get newAttr.Key :?> IScalarAttributeDefinition + + definition.UpdateNode(ValueSome newAttr.Value, this) + + member inline private this.ApplyWidgetDiffs(diffs: WidgetChanges inref) = + for diff in diffs do + match diff with + | WidgetChange.Added newWidget + | WidgetChange.ReplacedBy newWidget -> + let definition = + AttributeDefinitionStore.get newWidget.Key :?> WidgetAttributeDefinition + + definition.UpdateNode(ValueSome newWidget.Value, this :> IViewNode) + + | WidgetChange.Removed removed -> + let definition = + AttributeDefinitionStore.get removed.Key :?> WidgetAttributeDefinition + + definition.UpdateNode(ValueNone, this :> IViewNode) + + | WidgetChange.Updated struct (newAttr, diffs) -> + let definition = + AttributeDefinitionStore.get newAttr.Key :?> WidgetAttributeDefinition + + definition.ApplyDiff(diffs, this :> IViewNode) + + member inline private this.ApplyWidgetCollectionDiffs(diffs: WidgetCollectionChanges inref) = + for diff in diffs do + match diff with + | WidgetCollectionChange.Added added -> + let definition = + AttributeDefinitionStore.get added.Key :?> WidgetCollectionAttributeDefinition + + definition.UpdateNode(ValueSome added.Value, this :> IViewNode) + + | WidgetCollectionChange.Removed removed -> + let definition = + AttributeDefinitionStore.get removed.Key :?> WidgetCollectionAttributeDefinition + + definition.UpdateNode(ValueNone, this :> IViewNode) + + | WidgetCollectionChange.Updated struct (newAttr, diffs) -> + let definition = + AttributeDefinitionStore.get newAttr.Key :?> WidgetCollectionAttributeDefinition + + definition.ApplyDiff(diffs, this :> IViewNode) + interface IViewNode with member _.Target = targetRef.Target member _.TreeContext = treeContext @@ -32,75 +97,11 @@ type ViewNode(parentNode: IViewNode voption, treeContext: ViewTreeContext, targe | ValueNone -> None | ValueSome h -> Some(box h)) - member this.ApplyScalarDiffs(diffs) = - if not targetRef.IsAlive then - () - else - for diff in diffs do - match diff with - | ScalarChange.Added added -> - let definition = - AttributeDefinitionStore.get added.Key :?> IScalarAttributeDefinition - - definition.UpdateNode(ValueSome added.Value, this) - - | ScalarChange.Removed removed -> - let definition = - AttributeDefinitionStore.get removed.Key :?> IScalarAttributeDefinition - - definition.UpdateNode(ValueNone, this) - - | ScalarChange.Updated newAttr -> - let definition = - AttributeDefinitionStore.get newAttr.Key :?> IScalarAttributeDefinition - - definition.UpdateNode(ValueSome newAttr.Value, this) - - member this.ApplyWidgetDiffs(diffs) = - if not targetRef.IsAlive then - () - else - for diff in diffs do - match diff with - | WidgetChange.Added newWidget - | WidgetChange.ReplacedBy newWidget -> - let definition = - AttributeDefinitionStore.get newWidget.Key :?> WidgetAttributeDefinition - - definition.UpdateNode(ValueSome newWidget.Value, this :> IViewNode) - - | WidgetChange.Removed removed -> - let definition = - AttributeDefinitionStore.get removed.Key :?> WidgetAttributeDefinition - - definition.UpdateNode(ValueNone, this :> IViewNode) - - | WidgetChange.Updated struct (newAttr, diffs) -> - let definition = - AttributeDefinitionStore.get newAttr.Key :?> WidgetAttributeDefinition - - definition.ApplyDiff(diffs, this :> IViewNode) - member this.ApplyWidgetCollectionDiffs(diffs) = + member x.ApplyDiff(diff) = if not targetRef.IsAlive then () else - for diff in diffs do - match diff with - | WidgetCollectionChange.Added added -> - let definition = - AttributeDefinitionStore.get added.Key :?> WidgetCollectionAttributeDefinition - - definition.UpdateNode(ValueSome added.Value, this :> IViewNode) - - | WidgetCollectionChange.Removed removed -> - let definition = - AttributeDefinitionStore.get removed.Key :?> WidgetCollectionAttributeDefinition - - definition.UpdateNode(ValueNone, this :> IViewNode) - - | WidgetCollectionChange.Updated struct (newAttr, diffs) -> - let definition = - AttributeDefinitionStore.get newAttr.Key :?> WidgetCollectionAttributeDefinition - - definition.ApplyDiff(diffs, this :> IViewNode) + x.ApplyScalarDiffs(&diff.ScalarChanges) + x.ApplyWidgetDiffs(&diff.WidgetChanges) + x.ApplyWidgetCollectionDiffs(&diff.WidgetCollectionChanges) diff --git a/src/Fabulous/WidgetDiff.fs b/src/Fabulous/WidgetDiff.fs new file mode 100644 index 000000000..40db78766 --- /dev/null +++ b/src/Fabulous/WidgetDiff.fs @@ -0,0 +1,536 @@ +namespace Fabulous + +open System +open System.Runtime.CompilerServices +open Fabulous + +[] +type ScalarAttributeComparison = + | Identical + | Different + +///// Tuple cannot be used with IsByRefLike structs +///// this is essentially a Tuple of 2 that can work with them +//[] +//type Pair<'a, 'b> = +// struct +// val First: 'a +// val Second: 'b +// new(a: 'a, b: 'b) = { First = a; Second = b } +// end + +[] +type EnumerationMode<'a> = + | AllAddedOrRemoved of struct ('a [] * bool) + | Empty + | ActualDiff of prevNext: struct ('a [] * 'a []) + + +module EnumerationMode = + let fromOptions prev next = + match prev, next with + | ValueNone, ValueNone -> EnumerationMode.Empty + | ValueSome prev, ValueNone -> EnumerationMode.AllAddedOrRemoved(prev, false) + | ValueNone, ValueSome next -> EnumerationMode.AllAddedOrRemoved(next, true) + | ValueSome prev, ValueSome next -> EnumerationMode.ActualDiff(prev, next) + +[] +type ScalarChange = + | Added of added: ScalarAttribute + | Removed of removed: ScalarAttribute + | Updated of updated: ScalarAttribute + +and [] WidgetChange = + | Added of added: WidgetAttribute + | Removed of removed: WidgetAttribute + | Updated of updated: struct (WidgetAttribute * WidgetDiff) + | ReplacedBy of replacedBy: WidgetAttribute + +and [] WidgetCollectionChange = + | Added of added: WidgetCollectionAttribute + | Removed of removed: WidgetCollectionAttribute + | Updated of updated: struct (WidgetCollectionAttribute * WidgetCollectionItemChanges) + +and [] WidgetCollectionItemChange = + | Insert of widgetInserted: struct (int * Widget) + | Replace of widgetReplaced: struct (int * Widget) + | Update of widgetUpdated: struct (int * WidgetDiff) + | Remove of removed: int + +and [] WidgetDiff = + { ScalarChanges: ScalarChanges + WidgetChanges: WidgetChanges + WidgetCollectionChanges: WidgetCollectionChanges } + + static member inline create + ( + prevOpt: Widget voption, + next: Widget, + canReuseView: Widget -> Widget -> bool, + compareScalars: struct (AttributeKey * obj * obj) -> ScalarAttributeComparison + ) : WidgetDiff = + + let prevScalarAttributes = + match prevOpt with + | ValueNone -> ValueNone + | ValueSome widget -> widget.ScalarAttributes + + let prevWidgetAttributes = + match prevOpt with + | ValueNone -> ValueNone + | ValueSome widget -> widget.WidgetAttributes + + let prevWidgetCollectionAttributes = + match prevOpt with + | ValueNone -> ValueNone + | ValueSome widget -> widget.WidgetCollectionAttributes + + { ScalarChanges = ScalarChanges(prevScalarAttributes, next.ScalarAttributes, compareScalars) + WidgetChanges = WidgetChanges(prevWidgetAttributes, next.WidgetAttributes, canReuseView, compareScalars) + WidgetCollectionChanges = + WidgetCollectionChanges( + prevWidgetCollectionAttributes, + next.WidgetCollectionAttributes, + canReuseView, + compareScalars + ) } + +and [] ScalarChanges + ( + prev: ScalarAttribute [] voption, + next: ScalarAttribute [] voption, + compareScalars: struct (AttributeKey * obj * obj) -> ScalarAttributeComparison + ) = + member _.GetEnumerator() = + ScalarChangesEnumerator(EnumerationMode.fromOptions prev next, compareScalars) + +and [] WidgetChanges + ( + prev: WidgetAttribute [] voption, + next: WidgetAttribute [] voption, + canReuseView: Widget -> Widget -> bool, + compareScalars: struct (AttributeKey * obj * obj) -> ScalarAttributeComparison + ) = + member _.GetEnumerator() = + WidgetChangesEnumerator(EnumerationMode.fromOptions prev next, canReuseView, compareScalars) + +and [] WidgetCollectionChanges + ( + prev: WidgetCollectionAttribute [] voption, + next: WidgetCollectionAttribute [] voption, + canReuseView: Widget -> Widget -> bool, + compareScalars: struct (AttributeKey * obj * obj) -> ScalarAttributeComparison + ) = + member _.GetEnumerator() = + WidgetCollectionChangesEnumerator(EnumerationMode.fromOptions prev next, canReuseView, compareScalars) + + +and [] WidgetCollectionItemChanges + ( + prev: ArraySlice, + next: ArraySlice, + canReuseView: Widget -> Widget -> bool, + compareScalars: struct (AttributeKey * obj * obj) -> ScalarAttributeComparison + ) = + member _.GetEnumerator() = + WidgetCollectionItemChangesEnumerator( + ArraySlice.toSpan prev, + ArraySlice.toSpan next, + canReuseView, + compareScalars + ) + +// enumerators +and [] ScalarChangesEnumerator + ( + mode: EnumerationMode, + compareScalars: struct (AttributeKey * obj * obj) -> ScalarAttributeComparison + ) = + + [] + val mutable private current: ScalarChange + + [] + val mutable private prevIndex: int + + [] + val mutable private nextIndex: int + + member e.Current = e.current + + member e.MoveNext() = + match mode with + | EnumerationMode.Empty -> false + | EnumerationMode.AllAddedOrRemoved (attributes, added) -> + // use prevIndex regardless if it is for adding or removal + let i = e.prevIndex + + if i < attributes.Length then + let attribute = attributes.[i] + + e.current <- + match added with + | false -> ScalarChange.Removed attribute + | true -> ScalarChange.Added attribute + + e.prevIndex <- i + 1 + true + else + false + + | EnumerationMode.ActualDiff (prev, next) -> + let mutable prevIndex = e.prevIndex + let mutable nextIndex = e.nextIndex + + let prevLength = prev.Length + let nextLength = next.Length + + let mutable res: bool voption = ValueNone + // that needs to be in a loop until we have a change + + while ValueOption.isNone res do + + if not(prevIndex >= prevLength && nextIndex >= nextLength) then + if prevIndex = prevLength then + // that means we are done with the prev and only need to add next's tail to added + e.current <- ScalarChange.Added next.[nextIndex] + res <- ValueSome true + nextIndex <- nextIndex + 1 + + elif nextIndex = nextLength then + // that means that we are done with new items and only need prev's tail to removed + e.current <- ScalarChange.Removed prev.[prevIndex] + res <- ValueSome true + prevIndex <- prevIndex + 1 + + else + // we haven't reached either of the ends + let prevAttr = prev.[prevIndex] + let nextAttr = next.[nextIndex] + + let prevKey = prevAttr.Key + let nextKey = nextAttr.Key + + match prevKey.CompareTo nextKey with + | c when c < 0 -> + // prev key is less than next -> remove prev key + e.current <- ScalarChange.Removed prev.[prevIndex] + res <- ValueSome true + prevIndex <- prevIndex + 1 + + | c when c > 0 -> + // prev key is more than next -> add next item + e.current <- ScalarChange.Added prev.[nextIndex] + res <- ValueSome true + nextIndex <- nextIndex + 1 + + | _ -> + // means that we are targeting the same attribute + + match compareScalars struct (prevKey, prevAttr.Value, nextAttr.Value) with + // Previous and next values are identical, we don't need to do anything + | ScalarAttributeComparison.Identical -> () + + // New value completely replaces the old value + | ScalarAttributeComparison.Different -> + e.current <- ScalarChange.Updated next.[nextIndex] + res <- ValueSome true + + // move both pointers + prevIndex <- prevIndex + 1 + nextIndex <- nextIndex + 1 + + else + res <- ValueSome false + + e.prevIndex <- prevIndex + e.nextIndex <- nextIndex + + match res with + | ValueNone -> false + | ValueSome res -> res + +and [] WidgetChangesEnumerator + ( + mode: EnumerationMode, + canReuseView: Widget -> Widget -> bool, + compareScalars: struct (AttributeKey * obj * obj) -> ScalarAttributeComparison + ) = + + [] + val mutable private current: WidgetChange + + [] + val mutable private prevIndex: int + + [] + val mutable private nextIndex: int + + member e.Current = e.current + + member e.MoveNext() = + match mode with + | EnumerationMode.Empty -> false + | EnumerationMode.AllAddedOrRemoved (struct (values, added)) -> + // use prevIndex regardless if it is for adding or removal + let i = e.prevIndex + + if i < values.Length then + let value = values.[i] + + e.current <- + match added with + | false -> WidgetChange.Removed value + | true -> WidgetChange.Added value + + e.prevIndex <- i + 1 + true + else + false + + | EnumerationMode.ActualDiff (struct (prev, next)) -> + let mutable prevIndex = e.prevIndex + let mutable nextIndex = e.nextIndex + + let prevLength = prev.Length + let nextLength = next.Length + + let mutable res: bool voption = ValueNone + // that needs to be in a loop until we have a change + + while ValueOption.isNone res do + if not(prevIndex >= prevLength && nextIndex >= nextLength) then + if prevIndex = prevLength then + // that means we are done with the prev and only need to add next's tail to added + e.current <- WidgetChange.Added next.[nextIndex] + res <- ValueSome true + nextIndex <- nextIndex + 1 + + elif nextIndex = nextLength then + // that means that we are done with new items and only need prev's tail to removed + e.current <- WidgetChange.Removed prev.[prevIndex] + res <- ValueSome true + prevIndex <- prevIndex + 1 + + else + // we haven't reached either of the ends + let prevAttr = prev.[prevIndex] + let nextAttr = next.[nextIndex] + + let prevKey = prevAttr.Key + let nextKey = nextAttr.Key + let prevWidget = prevAttr.Value + let nextWidget = nextAttr.Value + + match prevKey.CompareTo nextKey with + | c when c < 0 -> + // prev key is less than next -> remove prev key + e.current <- WidgetChange.Removed prevAttr + res <- ValueSome true + + prevIndex <- prevIndex + 1 + + | c when c > 0 -> + // prev key is more than next -> add next item + e.current <- WidgetChange.Added nextAttr + res <- ValueSome true + nextIndex <- nextIndex + 1 + + | _ -> + // means that we are targeting the same attribute + + // move both pointers + prevIndex <- prevIndex + 1 + nextIndex <- nextIndex + 1 + + if prevWidget <> nextWidget then + + let change = + if canReuseView prevWidget nextWidget then + let diff = + WidgetDiff.create( + (ValueSome prevWidget), + nextWidget, + canReuseView, + compareScalars + ) + + WidgetChange.Updated(nextAttr, diff) + else + WidgetChange.ReplacedBy nextAttr + + e.current <- change + res <- ValueSome true + + else + res <- ValueSome false + + e.prevIndex <- prevIndex + e.nextIndex <- nextIndex + + match res with + | ValueNone -> false + | ValueSome res -> res + +and [] WidgetCollectionChangesEnumerator + ( + mode: EnumerationMode, + canReuseView: Widget -> Widget -> bool, + compareScalars: struct (AttributeKey * obj * obj) -> ScalarAttributeComparison + ) = + + [] + val mutable private current: WidgetCollectionChange + + [] + val mutable private prevIndex: int + + [] + val mutable private nextIndex: int + + member e.Current = e.current + + member e.MoveNext() = + match mode with + | EnumerationMode.Empty -> false + | EnumerationMode.AllAddedOrRemoved (struct (values, added)) -> + // use prevIndex regardless if it is for adding or removal + let i = e.prevIndex + + if i < values.Length then + let value = values.[i] + + e.current <- + match added with + | false -> WidgetCollectionChange.Removed value + | true -> WidgetCollectionChange.Added value + + e.prevIndex <- i + 1 + true + else + false + + | EnumerationMode.ActualDiff (struct (prev, next)) -> + let mutable prevIndex = e.prevIndex + let mutable nextIndex = e.nextIndex + + let prevLength = prev.Length + let nextLength = next.Length + + // that needs to be in a loop until we have a change + + let res = + if not(prevIndex >= prevLength && nextIndex >= nextLength) then + if prevIndex = prevLength then + // that means we are done with the prev and only need to add next's tail to added + e.current <- WidgetCollectionChange.Added next.[nextIndex] + nextIndex <- nextIndex + 1 + + elif nextIndex = nextLength then + // that means that we are done with new items and only need prev's tail to removed + e.current <- WidgetCollectionChange.Removed prev.[prevIndex] + prevIndex <- prevIndex + 1 + else + // we haven't reached either of the ends + let prevAttr = prev.[prevIndex] + let nextAttr = next.[nextIndex] + + let prevKey = prevAttr.Key + let nextKey = nextAttr.Key + let prevWidgetColl = prevAttr.Value + let nextWidgetColl = nextAttr.Value + + match prevKey.CompareTo nextKey with + | c when c < 0 -> + // prev key is less than next -> remove prev key + e.current <- WidgetCollectionChange.Removed prevAttr + prevIndex <- prevIndex + 1 + + | c when c > 0 -> + // prev key is more than next -> add next item + e.current <- WidgetCollectionChange.Added nextAttr + nextIndex <- nextIndex + 1 + + | _ -> + // means that we are targeting the same attribute + + // move both pointers + prevIndex <- prevIndex + 1 + nextIndex <- nextIndex + 1 + + let diff = + WidgetCollectionItemChanges( + prevWidgetColl, + nextWidgetColl, + canReuseView, + compareScalars + ) + + e.current <- WidgetCollectionChange.Updated(nextAttr, diff) + + true + else + false + + e.prevIndex <- prevIndex + e.nextIndex <- nextIndex + + res + +and [] WidgetCollectionItemChangesEnumerator + ( + prev: Span, + next: Span, + canReuseView: Widget -> Widget -> bool, + compareScalars: struct (AttributeKey * obj * obj) -> ScalarAttributeComparison + ) = + [] + val mutable private current: WidgetCollectionItemChange + + [] + val mutable private index: int + + [] + val mutable private tailIndex: int + + member e.Current = e.current + + member e.MoveNext() = + let tailIndex = e.tailIndex + let i = e.index + + if prev.Length > next.Length + && tailIndex < prev.Length - next.Length then + + e.current <- WidgetCollectionItemChange.Remove(next.Length - tailIndex) + e.tailIndex <- tailIndex + 1 + + true + + elif i < next.Length then + let currItem = next.[i] + + let prevItemOpt = + if (i >= prev.Length) then + ValueNone + else + ValueSome prev.[i] + + match prevItemOpt with + | ValueNone -> e.current <- WidgetCollectionItemChange.Insert(i, currItem) + + | ValueSome prevItem when canReuseView prevItem currItem -> + + let diff = + WidgetDiff.create(ValueSome prevItem, currItem, canReuseView, compareScalars) + + e.current <- WidgetCollectionItemChange.Update(i, diff) + + | ValueSome _ -> e.current <- WidgetCollectionItemChange.Replace(i, currItem) + + e.index <- i + 1 + true + + else + // means that we are done iterating + false From 423535c66bff0a33fba2402861ae6bed4b1b1dd0 Mon Sep 17 00:00:00 2001 From: Simon Korzunov Date: Mon, 17 Jan 2022 23:08:42 -0800 Subject: [PATCH 4/9] slightly faster version of calling a function --- src/Fabulous.Tests/TestUI.Attributes.fs | 2 +- src/Fabulous.Tests/TestUI.ViewUpdaters.fs | 8 +++--- src/Fabulous/AttributeDefinitions.fs | 32 +++++++++++------------ src/Fabulous/Attributes.fs | 28 ++++++++++---------- src/Fabulous/MapMsg.fs | 2 +- src/Fabulous/Memo.fs | 4 +-- src/Fabulous/Reconciler.fs | 2 +- src/Fabulous/ViewNode.fs | 18 ++++++------- src/Fabulous/WidgetDiff.fs | 11 -------- 9 files changed, 48 insertions(+), 59 deletions(-) diff --git a/src/Fabulous.Tests/TestUI.Attributes.fs b/src/Fabulous.Tests/TestUI.Attributes.fs index 82b61530f..4e4d4743a 100644 --- a/src/Fabulous.Tests/TestUI.Attributes.fs +++ b/src/Fabulous.Tests/TestUI.Attributes.fs @@ -15,7 +15,7 @@ module Attributes = ConvertValue = id Compare = ScalarAttributeComparers.noCompare UpdateNode = - fun struct (newValueOpt, node) -> + fun newValueOpt node -> let btn = node.Target :?> IButton diff --git a/src/Fabulous.Tests/TestUI.ViewUpdaters.fs b/src/Fabulous.Tests/TestUI.ViewUpdaters.fs index 8f260fddb..9731565e7 100644 --- a/src/Fabulous.Tests/TestUI.ViewUpdaters.fs +++ b/src/Fabulous.Tests/TestUI.ViewUpdaters.fs @@ -37,19 +37,19 @@ open Tests.Platform // navigationPage.PushAsync(page) |> ignore -let updateText struct (newValueOpt: string voption, node: IViewNode) = +let updateText (newValueOpt: string voption) (node: IViewNode) = let textElement = node.Target :?> IText textElement.Text <- ValueOption.defaultValue "" newValueOpt -let updateRecord struct (newValueOpt: bool voption, node: IViewNode) = +let updateRecord (newValueOpt: bool voption) (node: IViewNode) = let textElement = node.Target :?> TestLabel textElement.record <- ValueOption.defaultValue false newValueOpt -let updateTextColor struct (newValueOpt: string voption, node: IViewNode) = +let updateTextColor (newValueOpt: string voption) (node: IViewNode) = let textElement = node.Target :?> IText textElement.TextColor <- ValueOption.defaultValue "" newValueOpt -let updateAutomationId struct (newValueOpt: string voption, node: IViewNode) = +let updateAutomationId (newValueOpt: string voption) (node: IViewNode) = let el = node.Target :?> TestViewElement el.AutomationId <- ValueOption.defaultValue "" newValueOpt diff --git a/src/Fabulous/AttributeDefinitions.fs b/src/Fabulous/AttributeDefinitions.fs index 2dcdf0a8e..31d32131b 100644 --- a/src/Fabulous/AttributeDefinitions.fs +++ b/src/Fabulous/AttributeDefinitions.fs @@ -5,11 +5,11 @@ open Fabulous type IAttributeDefinition = abstract member Key : AttributeKey - abstract member UpdateNode : struct (obj voption * IViewNode) -> unit + abstract member UpdateNode : obj voption -> IViewNode -> unit type IScalarAttributeDefinition = inherit IAttributeDefinition - abstract member CompareBoxed : a: obj * b: obj -> ScalarAttributeComparison + abstract member CompareBoxed : a: obj -> b: obj -> ScalarAttributeComparison /// Attribute definition for scalar properties @@ -18,8 +18,8 @@ type ScalarAttributeDefinition<'inputType, 'modelType, 'valueType> = Name: string Convert: 'inputType -> 'modelType ConvertValue: 'modelType -> 'valueType - Compare: struct ('modelType * 'modelType) -> ScalarAttributeComparison - UpdateNode: struct ('valueType voption * IViewNode) -> unit } + Compare: 'modelType -> 'modelType -> ScalarAttributeComparison + UpdateNode: 'valueType voption -> IViewNode -> unit } member x.WithValue(value) : ScalarAttribute = { Key = x.Key @@ -31,23 +31,23 @@ type ScalarAttributeDefinition<'inputType, 'modelType, 'valueType> = interface IScalarAttributeDefinition with member x.Key = x.Key - member x.CompareBoxed(a, b) = - x.Compare(unbox<'modelType> a, unbox<'modelType> b) + member x.CompareBoxed a b = + x.Compare(unbox<'modelType> a) (unbox<'modelType> b) - member x.UpdateNode(struct (newValueOpt, node)) = + member x.UpdateNode newValueOpt node = let newValueOpt = match newValueOpt with | ValueNone -> ValueNone | ValueSome v -> ValueSome(x.ConvertValue(unbox<'modelType> v)) - x.UpdateNode(newValueOpt, node) + x.UpdateNode newValueOpt node /// Attribute definition for widget properties type WidgetAttributeDefinition = { Key: AttributeKey Name: string - ApplyDiff: struct (WidgetDiff * IViewNode) -> unit - UpdateNode: struct (Widget voption * IViewNode) -> unit } + ApplyDiff: WidgetDiff -> IViewNode -> unit + UpdateNode: Widget voption -> IViewNode -> unit } member x.WithValue(value: Widget) : WidgetAttribute = { Key = x.Key @@ -59,20 +59,20 @@ type WidgetAttributeDefinition = interface IAttributeDefinition with member x.Key = x.Key - member x.UpdateNode(struct (newValueOpt, node)) = + member x.UpdateNode newValueOpt node = let newValueOpt = match newValueOpt with | ValueNone -> ValueNone | ValueSome v -> ValueSome(unbox v) - x.UpdateNode(newValueOpt, node) + x.UpdateNode newValueOpt node /// Attribute definition for collection properties type WidgetCollectionAttributeDefinition = { Key: AttributeKey Name: string - ApplyDiff: struct (WidgetCollectionItemChanges * IViewNode) -> unit - UpdateNode: struct (ArraySlice voption * IViewNode) -> unit } + ApplyDiff: WidgetCollectionItemChanges -> IViewNode -> unit + UpdateNode: ArraySlice voption -> IViewNode -> unit } member x.WithValue(value: ArraySlice) : WidgetCollectionAttribute = { Key = x.Key @@ -84,13 +84,13 @@ type WidgetCollectionAttributeDefinition = interface IAttributeDefinition with member x.Key = x.Key - member x.UpdateNode(struct (newValueOpt, node)) = + member x.UpdateNode newValueOpt node = let newValueOpt = match newValueOpt with | ValueNone -> ValueNone | ValueSome v -> ValueSome(unbox> v) - x.UpdateNode(newValueOpt, node) + x.UpdateNode newValueOpt node module AttributeDefinitionStore = let private _attributes = diff --git a/src/Fabulous/Attributes.fs b/src/Fabulous/Attributes.fs index e5697d169..6f730514d 100644 --- a/src/Fabulous/Attributes.fs +++ b/src/Fabulous/Attributes.fs @@ -15,9 +15,9 @@ module Helpers = view module ScalarAttributeComparers = - let noCompare struct (_, _) = ScalarAttributeComparison.Different + let noCompare _ _ = ScalarAttributeComparison.Different - let equalityCompare struct (a, b) = + let equalityCompare a b = if a = b then ScalarAttributeComparison.Identical else @@ -29,8 +29,8 @@ module Attributes = name (convert: 'inputType -> 'modelType) (convertValue: 'modelType -> 'valueType) - (compare: struct ('modelType * 'modelType) -> ScalarAttributeComparison) - (updateNode: struct ('valueType voption * IViewNode) -> unit) + (compare: 'modelType -> 'modelType -> ScalarAttributeComparison) + (updateNode: 'valueType voption -> IViewNode -> unit) = let key = AttributeDefinitionStore.getNextKey() @@ -48,8 +48,8 @@ module Attributes = /// Define a custom attribute storing a widget let defineWidgetWithConverter name - (applyDiff: struct (WidgetDiff * IViewNode) -> unit) - (updateNode: struct (Widget voption * IViewNode) -> unit) + (applyDiff: WidgetDiff -> IViewNode -> unit) + (updateNode: Widget voption -> IViewNode -> unit) = let key = AttributeDefinitionStore.getNextKey() @@ -65,8 +65,8 @@ module Attributes = /// Define a custom attribute storing a widget collection let defineWidgetCollectionWithConverter name - (applyDiff: struct (WidgetCollectionItemChanges * IViewNode) -> unit) - (updateNode: struct (ArraySlice voption * IViewNode) -> unit) + (applyDiff: WidgetCollectionItemChanges -> IViewNode -> unit) + (updateNode: ArraySlice voption -> IViewNode -> unit) = let key = AttributeDefinitionStore.getNextKey() @@ -81,13 +81,13 @@ module Attributes = /// Define an attribute storing a Widget for a CLR property let defineWidget<'T when 'T: null> (name: string) (get: obj -> IViewNode) (set: obj -> 'T -> unit) = - let applyDiff struct (diff: WidgetDiff, node: IViewNode) = + let applyDiff (diff: WidgetDiff) (node: IViewNode) = let childNode = get node.Target childNode.ApplyDiff(&diff) - let updateNode struct (newValueOpt: Widget voption, node: IViewNode) = + let updateNode (newValueOpt: Widget voption) (node: IViewNode) = match newValueOpt with | ValueNone -> set node.Target null | ValueSome widget -> @@ -100,7 +100,7 @@ module Attributes = /// Define an attribute storing a collection of Widget let defineWidgetCollection<'itemType> name (getCollection: obj -> System.Collections.Generic.IList<'itemType>) = - let applyDiff struct (diffs: WidgetCollectionItemChanges, node: IViewNode) = + let applyDiff (diffs: WidgetCollectionItemChanges) (node: IViewNode) = let targetColl = getCollection node.Target for diff in diffs do @@ -126,7 +126,7 @@ module Attributes = | _ -> () - let updateNode struct (newValueOpt: ArraySlice voption, node: IViewNode) = + let updateNode (newValueOpt: ArraySlice voption) (node: IViewNode) = let targetColl = getCollection node.Target targetColl.Clear() @@ -172,7 +172,7 @@ module Attributes = ConvertValue = id Compare = ScalarAttributeComparers.noCompare UpdateNode = - fun struct (newValueOpt, node) -> + fun newValueOpt node -> let event = getEvent node.Target match node.TryGetHandler(key) with @@ -202,7 +202,7 @@ module Attributes = ConvertValue = id Compare = ScalarAttributeComparers.noCompare UpdateNode = - fun struct (newValueOpt: ('args -> obj) voption, node: IViewNode) -> + fun (newValueOpt: ('args -> obj) voption) (node: IViewNode) -> let event = getEvent node.Target match node.TryGetHandler(key) with diff --git a/src/Fabulous/MapMsg.fs b/src/Fabulous/MapMsg.fs index 7076a8899..9e97cdf8c 100644 --- a/src/Fabulous/MapMsg.fs +++ b/src/Fabulous/MapMsg.fs @@ -7,7 +7,7 @@ module MapMsg = id id ScalarAttributeComparers.noCompare - (fun struct (value, node) -> + (fun value node -> match value with | ValueNone -> node.MapMsg <- ValueNone | ValueSome fn -> node.MapMsg <- ValueSome fn) diff --git a/src/Fabulous/Memo.fs b/src/Fabulous/Memo.fs index 2a1a0b5bf..9b37f72e9 100644 --- a/src/Fabulous/Memo.fs +++ b/src/Fabulous/Memo.fs @@ -35,7 +35,7 @@ module Memo = let internal canReuseMemoizedWidget prev next = (getMemoData prev).MarkerType = (getMemoData next).MarkerType - let private compareAttributes struct (prev: MemoData, next: MemoData) : ScalarAttributeComparison = + let private compareAttributes (prev: MemoData) (next: MemoData) : ScalarAttributeComparison = match (prev.KeyType = next.KeyType, prev.MarkerType = next.MarkerType) with | true, true -> match next.KeyComparer next.KeyData prev.KeyData with @@ -43,7 +43,7 @@ module Memo = | false -> ScalarAttributeComparison.Different | _ -> ScalarAttributeComparison.Different - let private updateNode struct (data: MemoData voption, node: IViewNode) : unit = + let private updateNode (data: MemoData voption) (node: IViewNode) : unit = match data with | ValueSome memoData -> let memoizedWidget = memoData.CreateWidget memoData.KeyData diff --git a/src/Fabulous/Reconciler.fs b/src/Fabulous/Reconciler.fs index a85dd8e55..ac596fa8c 100644 --- a/src/Fabulous/Reconciler.fs +++ b/src/Fabulous/Reconciler.fs @@ -8,7 +8,7 @@ module Reconciler = let def = AttributeDefinitionStore.get key :?> IScalarAttributeDefinition - def.CompareBoxed(a, b) + def.CompareBoxed a b let update (canReuseView: Widget -> Widget -> bool) diff --git a/src/Fabulous/ViewNode.fs b/src/Fabulous/ViewNode.fs index 24fbd8007..beeae5fbd 100644 --- a/src/Fabulous/ViewNode.fs +++ b/src/Fabulous/ViewNode.fs @@ -18,19 +18,19 @@ type ViewNode(parentNode: IViewNode voption, treeContext: ViewTreeContext, targe let definition = AttributeDefinitionStore.get added.Key :?> IScalarAttributeDefinition - definition.UpdateNode(ValueSome added.Value, this) + definition.UpdateNode(ValueSome added.Value) this | ScalarChange.Removed removed -> let definition = AttributeDefinitionStore.get removed.Key :?> IScalarAttributeDefinition - definition.UpdateNode(ValueNone, this) + definition.UpdateNode ValueNone this | ScalarChange.Updated newAttr -> let definition = AttributeDefinitionStore.get newAttr.Key :?> IScalarAttributeDefinition - definition.UpdateNode(ValueSome newAttr.Value, this) + definition.UpdateNode(ValueSome newAttr.Value) this member inline private this.ApplyWidgetDiffs(diffs: WidgetChanges inref) = for diff in diffs do @@ -40,19 +40,19 @@ type ViewNode(parentNode: IViewNode voption, treeContext: ViewTreeContext, targe let definition = AttributeDefinitionStore.get newWidget.Key :?> WidgetAttributeDefinition - definition.UpdateNode(ValueSome newWidget.Value, this :> IViewNode) + definition.UpdateNode(ValueSome newWidget.Value) (this :> IViewNode) | WidgetChange.Removed removed -> let definition = AttributeDefinitionStore.get removed.Key :?> WidgetAttributeDefinition - definition.UpdateNode(ValueNone, this :> IViewNode) + definition.UpdateNode ValueNone (this :> IViewNode) | WidgetChange.Updated struct (newAttr, diffs) -> let definition = AttributeDefinitionStore.get newAttr.Key :?> WidgetAttributeDefinition - definition.ApplyDiff(diffs, this :> IViewNode) + definition.ApplyDiff diffs (this :> IViewNode) member inline private this.ApplyWidgetCollectionDiffs(diffs: WidgetCollectionChanges inref) = for diff in diffs do @@ -61,19 +61,19 @@ type ViewNode(parentNode: IViewNode voption, treeContext: ViewTreeContext, targe let definition = AttributeDefinitionStore.get added.Key :?> WidgetCollectionAttributeDefinition - definition.UpdateNode(ValueSome added.Value, this :> IViewNode) + definition.UpdateNode(ValueSome added.Value) (this :> IViewNode) | WidgetCollectionChange.Removed removed -> let definition = AttributeDefinitionStore.get removed.Key :?> WidgetCollectionAttributeDefinition - definition.UpdateNode(ValueNone, this :> IViewNode) + definition.UpdateNode ValueNone (this :> IViewNode) | WidgetCollectionChange.Updated struct (newAttr, diffs) -> let definition = AttributeDefinitionStore.get newAttr.Key :?> WidgetCollectionAttributeDefinition - definition.ApplyDiff(diffs, this :> IViewNode) + definition.ApplyDiff diffs (this :> IViewNode) interface IViewNode with member _.Target = targetRef.Target diff --git a/src/Fabulous/WidgetDiff.fs b/src/Fabulous/WidgetDiff.fs index 40db78766..63993176a 100644 --- a/src/Fabulous/WidgetDiff.fs +++ b/src/Fabulous/WidgetDiff.fs @@ -9,23 +9,12 @@ type ScalarAttributeComparison = | Identical | Different -///// Tuple cannot be used with IsByRefLike structs -///// this is essentially a Tuple of 2 that can work with them -//[] -//type Pair<'a, 'b> = -// struct -// val First: 'a -// val Second: 'b -// new(a: 'a, b: 'b) = { First = a; Second = b } -// end - [] type EnumerationMode<'a> = | AllAddedOrRemoved of struct ('a [] * bool) | Empty | ActualDiff of prevNext: struct ('a [] * 'a []) - module EnumerationMode = let fromOptions prev next = match prev, next with From aa7a2d5b819d95385e11dff934d609a262a4ed28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9=20Larivi=C3=A8re?= Date: Tue, 18 Jan 2022 09:31:43 +0100 Subject: [PATCH 5/9] Format code with Fantomas --- src/Fabulous.Tests/APISketchTests.fs | 54 ++++++++++++------------- src/Fabulous.Tests/TestUI.Attributes.fs | 2 +- src/Fabulous.Tests/TestUI.Widgets.fs | 14 +++---- src/Fabulous/AttributeDefinitions.fs | 6 +-- src/Fabulous/Attributes.fs | 10 ++--- src/Fabulous/IViewNode.fs | 12 +++--- src/Fabulous/Memo.fs | 4 +- src/Fabulous/Reconciler.fs | 2 +- src/Fabulous/Runners.fs | 16 ++++---- src/Fabulous/WidgetDiff.fs | 10 ++--- 10 files changed, 65 insertions(+), 65 deletions(-) diff --git a/src/Fabulous.Tests/APISketchTests.fs b/src/Fabulous.Tests/APISketchTests.fs index 1b9b03486..cbd1a82e4 100644 --- a/src/Fabulous.Tests/APISketchTests.fs +++ b/src/Fabulous.Tests/APISketchTests.fs @@ -21,13 +21,13 @@ let rec findOptional (root: TestViewElement) (id: string) : TestViewElement opti children |> Array.ofSeq - |> Array.fold(fun res child -> res |> Option.orElse(findOptional child id)) None + |> Array.fold (fun res child -> res |> Option.orElse (findOptional child id)) None | _ -> None let find<'a when 'a :> TestViewElement> (root: TestViewElement) (id: string) : 'a = findOptional root id - |> Option.defaultWith(fun () -> failwith "not found") + |> Option.defaultWith (fun () -> failwith "not found") :?> 'a @@ -46,7 +46,7 @@ module SimpleLabelTests = let view model = Label(model.text) .textColor(model.color) - .automationId("label") + .automationId ("label") let init () = { text = "hi"; color = "red" } @@ -82,7 +82,7 @@ module ButtonTests = let view model = Button(model.count.ToString(), Increment) - .automationId("btn") + .automationId ("btn") let init () = { count = 0 } @@ -112,7 +112,7 @@ module SimpleStackTests = let update msg model = match msg with - | Delete id -> model |> List.filter(fun (id_, _) -> id_ <> id) + | Delete id -> model |> List.filter (fun (id_, _) -> id_ <> id) | AddNew (id, text) -> (id, text) :: model | ChangeText (id, text) -> model @@ -127,9 +127,9 @@ module SimpleStackTests = (Stack() { yield! model - |> List.map(fun (id, text) -> (Label(text).automationId(id.ToString()))) + |> List.map (fun (id, text) -> (Label(text).automationId (id.ToString()))) }) - .automationId("stack") + .automationId ("stack") let init () = [] @@ -198,10 +198,10 @@ module ComputationExpressionTest = let JustValue () = let view model = // requires implemented "Yield" - Stack() { Button("inc", Inc).automationId("inc") } + Stack() { Button("inc", Inc).automationId ("inc") } let instance = - StatefulWidget.mkSimpleView(fun () -> 0) update view + StatefulWidget.mkSimpleView (fun () -> 0) update view |> Run.Instance let tree = (instance.Start()) @@ -217,15 +217,15 @@ module ComputationExpressionTest = // requires implemented "Zero" (Stack() { if (model % 2 = 0) then - Label("label").automationId("label") + Label("label").automationId ("label") else - Button("btn", Inc).automationId("btn") + Button("btn", Inc).automationId ("btn") }) - .automationId("stack") + .automationId ("stack") let instance = - StatefulWidget.mkSimpleView(fun () -> 0) update view + StatefulWidget.mkSimpleView (fun () -> 0) update view |> Run.Instance let tree = (instance.Start()) @@ -260,11 +260,11 @@ module ComputationExpressionTest = // requires implemented "YieldFrom" yield! [ for i in 0 .. model -> i.ToString() ] - |> List.map(fun i -> Label(i).automationId(i)) + |> List.map (fun i -> Label(i).automationId (i)) } let instance = - StatefulWidget.mkSimpleView(fun () -> count) update view + StatefulWidget.mkSimpleView (fun () -> count) update view |> Run.Instance let tree = (instance.Start()) @@ -284,11 +284,11 @@ module ComputationExpressionTest = let view model = Stack() { // requires implemented "For" - for i in 0 .. model -> Label(i.ToString()).automationId(i.ToString()) + for i in 0 .. model -> Label(i.ToString()).automationId (i.ToString()) } let instance = - StatefulWidget.mkSimpleView(fun () -> count) update view + StatefulWidget.mkSimpleView (fun () -> count) update view |> Run.Instance let tree = (instance.Start()) @@ -353,13 +353,13 @@ module MapViewTests = mapMsg (Button("+1", AddOne) .automationId("add") - .textColor("red")) + .textColor ("red")) Label(model.ToString()) .automationId("label") - .textColor("blue") + .textColor ("blue") - View.map mapMsg (Button("-2", RemoveTwo).automationId("remove")) + View.map mapMsg (Button("-2", RemoveTwo).automationId ("remove")) } @@ -406,12 +406,12 @@ module MemoTests = let view model = Stack() { Label(model.notMemoized) - .automationId("not_memo") + .automationId ("not_memo") View.lazy' (fun i -> renderCount <- renderCount + 1 - Label(string i).automationId("memo")) + Label(string i).automationId ("memo")) model.memoTrigger } @@ -465,10 +465,10 @@ module MemoTests = let view model = (Stack() { match model with - | Btn -> View.lazy'(fun i -> Button(string i, Change).automationId("btn")) model - | Lbl -> View.lazy'(fun i -> Label(string i).automationId("label")) model + | Btn -> View.lazy' (fun i -> Button(string i, Change).automationId ("btn")) model + | Lbl -> View.lazy' (fun i -> Label(string i).automationId ("label")) model }) - .automationId("stack") + .automationId ("stack") [] let Test () = @@ -520,7 +520,7 @@ module MemoTests = Label("one") .record(true) .textColor("blue") - .automationId("label")) + .automationId ("label")) model | Label2 -> View.lazy' @@ -528,7 +528,7 @@ module MemoTests = Label("two") .record(true) .textColor("blue") - .automationId("label")) + .automationId ("label")) (string model) } diff --git a/src/Fabulous.Tests/TestUI.Attributes.fs b/src/Fabulous.Tests/TestUI.Attributes.fs index 4e4d4743a..7a7c7bf11 100644 --- a/src/Fabulous.Tests/TestUI.Attributes.fs +++ b/src/Fabulous.Tests/TestUI.Attributes.fs @@ -6,7 +6,7 @@ open Tests.Platform module Attributes = let definePressable name = - let key = AttributeDefinitionStore.getNextKey() + let key = AttributeDefinitionStore.getNextKey () let definition: ScalarAttributeDefinition = { Key = key diff --git a/src/Fabulous.Tests/TestUI.Widgets.fs b/src/Fabulous.Tests/TestUI.Widgets.fs index 85a6ee1fa..b294b4f99 100644 --- a/src/Fabulous.Tests/TestUI.Widgets.fs +++ b/src/Fabulous.Tests/TestUI.Widgets.fs @@ -19,8 +19,8 @@ open Tests.TestUI_ViewNode //-------Widgets module Widgets = - let register<'T when 'T :> TestViewElement and 'T: (new : unit -> 'T)> () = - let key = WidgetDefinitionStore.getNextKey() + let register<'T when 'T :> TestViewElement and 'T: (new: unit -> 'T)> () = + let key = WidgetDefinitionStore.getNextKey () let definition = { Key = key @@ -102,9 +102,9 @@ type WidgetExtensions() = [] type View private () = - static let TestLabelKey = Widgets.register() - static let TestButtonKey = Widgets.register() - static let TestStackKey = Widgets.register() + static let TestLabelKey = Widgets.register () + static let TestButtonKey = Widgets.register () + static let TestStackKey = Widgets.register () static member Label<'msg>(text: string) = WidgetBuilder<'msg, TestLabelMarker>(TestLabelKey, Attributes.Text.Text.WithValue(text)) @@ -120,7 +120,7 @@ type View private () = static member Stack<'msg, 'marker when 'marker :> IMarker>() = CollectionBuilder<'msg, TestStackMarker, 'marker>( TestStackKey, - StackList.empty(), + StackList.empty (), Attributes.Container.Children ) @@ -151,7 +151,7 @@ type CollectionBuilderExtensions = // TODO optimize this one with addMut { Widgets = x - |> Seq.map(fun wb -> wb.Compile()) + |> Seq.map (fun wb -> wb.Compile()) |> Seq.toArray |> MutStackArray1.fromArray } diff --git a/src/Fabulous/AttributeDefinitions.fs b/src/Fabulous/AttributeDefinitions.fs index 31d32131b..6deddd5b1 100644 --- a/src/Fabulous/AttributeDefinitions.fs +++ b/src/Fabulous/AttributeDefinitions.fs @@ -4,12 +4,12 @@ open System.Collections.Generic open Fabulous type IAttributeDefinition = - abstract member Key : AttributeKey - abstract member UpdateNode : obj voption -> IViewNode -> unit + abstract member Key: AttributeKey + abstract member UpdateNode: obj voption -> IViewNode -> unit type IScalarAttributeDefinition = inherit IAttributeDefinition - abstract member CompareBoxed : a: obj -> b: obj -> ScalarAttributeComparison + abstract member CompareBoxed: a: obj -> b: obj -> ScalarAttributeComparison /// Attribute definition for scalar properties diff --git a/src/Fabulous/Attributes.fs b/src/Fabulous/Attributes.fs index 6f730514d..42641e530 100644 --- a/src/Fabulous/Attributes.fs +++ b/src/Fabulous/Attributes.fs @@ -32,7 +32,7 @@ module Attributes = (compare: 'modelType -> 'modelType -> ScalarAttributeComparison) (updateNode: 'valueType voption -> IViewNode -> unit) = - let key = AttributeDefinitionStore.getNextKey() + let key = AttributeDefinitionStore.getNextKey () let definition = { Key = key @@ -51,7 +51,7 @@ module Attributes = (applyDiff: WidgetDiff -> IViewNode -> unit) (updateNode: Widget voption -> IViewNode -> unit) = - let key = AttributeDefinitionStore.getNextKey() + let key = AttributeDefinitionStore.getNextKey () let definition: WidgetAttributeDefinition = { Key = key @@ -68,7 +68,7 @@ module Attributes = (applyDiff: WidgetCollectionItemChanges -> IViewNode -> unit) (updateNode: ArraySlice voption -> IViewNode -> unit) = - let key = AttributeDefinitionStore.getNextKey() + let key = AttributeDefinitionStore.getNextKey () let definition: WidgetCollectionAttributeDefinition = { Key = key @@ -163,7 +163,7 @@ module Attributes = node.TreeContext.Dispatch(newMsg) let defineEventNoArg name (getEvent: obj -> IEvent) = - let key = AttributeDefinitionStore.getNextKey() + let key = AttributeDefinitionStore.getNextKey () let definition: ScalarAttributeDefinition = { Key = key @@ -193,7 +193,7 @@ module Attributes = definition let defineEvent<'args> name (getEvent: obj -> IEvent, 'args>) = - let key = AttributeDefinitionStore.getNextKey() + let key = AttributeDefinitionStore.getNextKey () let definition: ScalarAttributeDefinition<_, _, _> = { Key = key diff --git a/src/Fabulous/IViewNode.fs b/src/Fabulous/IViewNode.fs index aa2418392..945c7cd55 100644 --- a/src/Fabulous/IViewNode.fs +++ b/src/Fabulous/IViewNode.fs @@ -9,17 +9,17 @@ type ViewTreeContext = Dispatch: obj -> unit } and IViewNode = - abstract member Target : obj - abstract member Parent : IViewNode voption - abstract member TreeContext : ViewTreeContext - abstract member MapMsg : (obj -> obj) voption with get, set + abstract member Target: obj + abstract member Parent: IViewNode voption + abstract member TreeContext: ViewTreeContext + abstract member MapMsg: (obj -> obj) voption with get, set // note that Widget is struct type, thus we have boxing via option // we don't have MemoizedWidget set for 99.9% of the cases // thus makes sense to have overhead of boxing // in order to save space - abstract member MemoizedWidget : Widget option with get, set + abstract member MemoizedWidget: Widget option with get, set abstract member TryGetHandler<'T> : AttributeKey -> 'T voption abstract member SetHandler<'T> : AttributeKey * 'T voption -> unit - abstract member ApplyDiff : WidgetDiff inref -> unit + abstract member ApplyDiff: WidgetDiff inref -> unit diff --git a/src/Fabulous/Memo.fs b/src/Fabulous/Memo.fs index 9b37f72e9..0fc2e959d 100644 --- a/src/Fabulous/Memo.fs +++ b/src/Fabulous/Memo.fs @@ -24,8 +24,8 @@ module Memo = type Memoized<'t> = { phantom: 't } - let private MemoAttributeKey = AttributeDefinitionStore.getNextKey() - let internal MemoWidgetKey = WidgetDefinitionStore.getNextKey() + let private MemoAttributeKey = AttributeDefinitionStore.getNextKey () + let internal MemoWidgetKey = WidgetDefinitionStore.getNextKey () let inline private getMemoData (widget: Widget) : MemoData = match widget.ScalarAttributes with diff --git a/src/Fabulous/Reconciler.fs b/src/Fabulous/Reconciler.fs index ac596fa8c..67583f7f3 100644 --- a/src/Fabulous/Reconciler.fs +++ b/src/Fabulous/Reconciler.fs @@ -18,6 +18,6 @@ module Reconciler = : unit = let diff = - WidgetDiff.create(prevOpt, next, canReuseView, compareScalars) + WidgetDiff.create (prevOpt, next, canReuseView, compareScalars) node.ApplyDiff(&diff) diff --git a/src/Fabulous/Runners.fs b/src/Fabulous/Runners.fs index 92b10c5bf..3b90ec539 100644 --- a/src/Fabulous/Runners.fs +++ b/src/Fabulous/Runners.fs @@ -16,9 +16,9 @@ type IRunner = type IViewAdapter = inherit IDisposable - abstract CreateView : unit -> obj - abstract Attach : obj -> unit - abstract Detach : bool -> unit + abstract CreateView: unit -> obj + abstract Attach: obj -> unit + abstract Detach: bool -> unit module RunnerStore = let private _runners = Dictionary() @@ -55,7 +55,7 @@ module Runners = type Runner<'arg, 'model, 'msg>(key: StateKey, program: Program<'arg, 'model, 'msg>) = let rec processMsg msg = - let model = unbox(StateStore.get key) + let model = unbox (StateStore.get key) let newModel, cmd = program.Update(msg, model) StateStore.set key newModel @@ -83,7 +83,7 @@ module Runners = member _.Stop() = () let create<'arg, 'model, 'msg> (program: Program<'arg, 'model, 'msg>) = - let key = StateStore.getNextKey() + let key = StateStore.getNextKey () let runner = Runner(key, program) RunnerStore.set key runner runner @@ -113,10 +113,10 @@ module ViewAdapters = member private _.Dispatch(msg) = if _allowDispatch then - dispatch(unbox msg) + dispatch (unbox msg) member this.CreateView() = - let state = unbox(StateStore.get stateKey) + let state = unbox (StateStore.get stateKey) let widget = view state _widget <- widget @@ -156,7 +156,7 @@ module ViewAdapters = member _.Detach(shouldDestroyNode) = () let create<'arg, 'model, 'msg> (getViewNode: obj -> IViewNode) (runner: Runner<'arg, 'model, 'msg>) = - let key = ViewAdapterStore.getNextKey() + let key = ViewAdapterStore.getNextKey () let viewAdapter = new ViewAdapter<'model, 'msg>( diff --git a/src/Fabulous/WidgetDiff.fs b/src/Fabulous/WidgetDiff.fs index 63993176a..a4e271d6e 100644 --- a/src/Fabulous/WidgetDiff.fs +++ b/src/Fabulous/WidgetDiff.fs @@ -179,7 +179,7 @@ and [] ScalarChangesEnumerator while ValueOption.isNone res do - if not(prevIndex >= prevLength && nextIndex >= nextLength) then + if not (prevIndex >= prevLength && nextIndex >= nextLength) then if prevIndex = prevLength then // that means we are done with the prev and only need to add next's tail to added e.current <- ScalarChange.Added next.[nextIndex] @@ -288,7 +288,7 @@ and [] WidgetChangesEnumerator // that needs to be in a loop until we have a change while ValueOption.isNone res do - if not(prevIndex >= prevLength && nextIndex >= nextLength) then + if not (prevIndex >= prevLength && nextIndex >= nextLength) then if prevIndex = prevLength then // that means we are done with the prev and only need to add next's tail to added e.current <- WidgetChange.Added next.[nextIndex] @@ -337,7 +337,7 @@ and [] WidgetChangesEnumerator let change = if canReuseView prevWidget nextWidget then let diff = - WidgetDiff.create( + WidgetDiff.create ( (ValueSome prevWidget), nextWidget, canReuseView, @@ -409,7 +409,7 @@ and [] WidgetCollectionChangesEnumerator // that needs to be in a loop until we have a change let res = - if not(prevIndex >= prevLength && nextIndex >= nextLength) then + if not (prevIndex >= prevLength && nextIndex >= nextLength) then if prevIndex = prevLength then // that means we are done with the prev and only need to add next's tail to added e.current <- WidgetCollectionChange.Added next.[nextIndex] @@ -511,7 +511,7 @@ and [] WidgetCollectionItemChangesEnumerator | ValueSome prevItem when canReuseView prevItem currItem -> let diff = - WidgetDiff.create(ValueSome prevItem, currItem, canReuseView, compareScalars) + WidgetDiff.create (ValueSome prevItem, currItem, canReuseView, compareScalars) e.current <- WidgetCollectionItemChange.Update(i, diff) From 8d572dd6f183effb620632a801e3f027dd07f780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9=20Larivi=C3=A8re?= Date: Tue, 18 Jan 2022 19:49:17 +0100 Subject: [PATCH 6/9] Adapt Fabulous.XamarinForms to compile with latest changes --- src/Fabulous.XamarinForms/Attributes.fs | 6 ++--- src/Fabulous.XamarinForms/ViewUpdaters.fs | 26 ++++++------------- src/Fabulous.XamarinForms/WidgetExtensions.fs | 4 +-- .../Xamarin.Forms.Core.Attributes.fs | 14 +++++----- 4 files changed, 20 insertions(+), 30 deletions(-) diff --git a/src/Fabulous.XamarinForms/Attributes.fs b/src/Fabulous.XamarinForms/Attributes.fs index ccd13262e..e8e442c2d 100644 --- a/src/Fabulous.XamarinForms/Attributes.fs +++ b/src/Fabulous.XamarinForms/Attributes.fs @@ -30,14 +30,14 @@ module Attributes = (bindableProperty: BindableProperty) (convert: 'inputType -> 'modelType) (convertValue: 'modelType -> 'valueType) - (compare: 'modelType * 'modelType -> ScalarAttributeComparison) + (compare: 'modelType -> 'modelType -> ScalarAttributeComparison) = Attributes.defineScalarWithConverter<'inputType, 'modelType, 'valueType> bindableProperty.PropertyName convert convertValue compare - (fun (newValueOpt, node) -> + (fun newValueOpt node -> let target = node.Target :?> BindableObject match newValueOpt with @@ -53,7 +53,7 @@ module Attributes = id id ScalarAttributeComparers.equalityCompare - (fun (newValueOpt, node) -> + (fun newValueOpt node -> let target = node.Target :?> BindableObject match newValueOpt with diff --git a/src/Fabulous.XamarinForms/ViewUpdaters.fs b/src/Fabulous.XamarinForms/ViewUpdaters.fs index fe170c143..4038ab8d0 100644 --- a/src/Fabulous.XamarinForms/ViewUpdaters.fs +++ b/src/Fabulous.XamarinForms/ViewUpdaters.fs @@ -4,7 +4,7 @@ open Fabulous open Xamarin.Forms module ViewUpdaters = - let updateSliderMinMax (newValueOpt: (float * float) voption, node: IViewNode) = + let updateSliderMinMax (newValueOpt: (float * float) voption) (node: IViewNode) = let slider = node.Target :?> Slider match newValueOpt with @@ -22,7 +22,7 @@ module ViewUpdaters = slider.SetValue(Slider.MinimumProperty, min) slider.SetValue(Slider.MaximumProperty, max) - let updateStepperMinMax (newValueOpt: (float * float) voption, node: IViewNode) = + let updateStepperMinMax (newValueOpt: (float * float) voption) (node: IViewNode) = let stepper = node.Target :?> Stepper match newValueOpt with @@ -40,7 +40,7 @@ module ViewUpdaters = stepper.SetValue(Stepper.MinimumProperty, min) stepper.SetValue(Stepper.MaximumProperty, max) - let updateGridColumnDefinitions (newValueOpt: Dimension [] voption, node: IViewNode) = + let updateGridColumnDefinitions (newValueOpt: Dimension [] voption) (node: IViewNode) = let grid = node.Target :?> Grid match newValueOpt with @@ -58,7 +58,7 @@ module ViewUpdaters = grid.ColumnDefinitions.Add(ColumnDefinition(Width = gridLength)) - let updateGridRowDefinitions (newValueOpt: Dimension [] voption, node: IViewNode) = + let updateGridRowDefinitions (newValueOpt: Dimension [] voption) (node: IViewNode) = let grid = node.Target :?> Grid match newValueOpt with @@ -78,11 +78,11 @@ module ViewUpdaters = /// NOTE: Would be better to have a custom diff logic for Navigation /// because it's a Stack and not a random access collection - let applyDiffNavigationPagePages (diffs: ArraySlice, node: IViewNode) = + let applyDiffNavigationPagePages (diffs: WidgetCollectionItemChanges) (node: IViewNode) = let navigationPage = node.Target :?> NavigationPage let pages = List.ofSeq navigationPage.Pages - for diff in ArraySlice.toSpan diffs do + for diff in diffs do match diff with | WidgetCollectionItemChange.Insert (index, widget) -> if index >= pages.Length then @@ -110,17 +110,7 @@ module ViewUpdaters = let childNode = node.TreeContext.GetViewNode(box pages.[index]) - match diff.ScalarChanges with - | ValueSome changes -> childNode.ApplyScalarDiffs(changes) - | ValueNone -> () - - match diff.WidgetChanges with - | ValueSome slice -> childNode.ApplyWidgetDiffs(ArraySlice.toSpan slice) - | ValueNone -> () - - match diff.WidgetCollectionChanges with - | ValueSome slice -> childNode.ApplyWidgetCollectionDiffs(ArraySlice.toSpan slice) - | ValueNone -> () + childNode.ApplyDiff(&diff) | WidgetCollectionItemChange.Replace (index, widget) -> @@ -163,7 +153,7 @@ module ViewUpdaters = navigationPage.PushAsync(temp.Pop(), false) |> ignore - let updateNavigationPagePages (newValueOpt: ArraySlice voption, node: IViewNode) = + let updateNavigationPagePages (newValueOpt: ArraySlice voption) (node: IViewNode) = let navigationPage = node.Target :?> NavigationPage navigationPage.PopToRootAsync(false) |> ignore diff --git a/src/Fabulous.XamarinForms/WidgetExtensions.fs b/src/Fabulous.XamarinForms/WidgetExtensions.fs index 8a97ae58d..c8df39a41 100644 --- a/src/Fabulous.XamarinForms/WidgetExtensions.fs +++ b/src/Fabulous.XamarinForms/WidgetExtensions.fs @@ -14,7 +14,7 @@ module AdditionalAttributes = let UseSafeArea = Attributes.define "Page_UseSafeArea" - (fun (newValueOpt, node) -> + (fun newValueOpt node -> let page = node.Target :?> Xamarin.Forms.Page let value = @@ -28,7 +28,7 @@ module AdditionalAttributes = let ToolbarPlacement = Attributes.define "TabbedPage_ToolbarPlacement" - (fun (newValueOpt, node) -> + (fun newValueOpt node -> let tabbedPage = node.Target :?> Xamarin.Forms.TabbedPage let value = diff --git a/src/Fabulous.XamarinForms/Xamarin.Forms.Core.Attributes.fs b/src/Fabulous.XamarinForms/Xamarin.Forms.Core.Attributes.fs index 7865eeab1..d72cd65e1 100644 --- a/src/Fabulous.XamarinForms/Xamarin.Forms.Core.Attributes.fs +++ b/src/Fabulous.XamarinForms/Xamarin.Forms.Core.Attributes.fs @@ -15,7 +15,7 @@ module Application = let Resources = Attributes.define "Application_Resources" - (fun (newValueOpt, node) -> + (fun newValueOpt node -> let application = node.Target :?> Xamarin.Forms.Application @@ -29,7 +29,7 @@ module Application = let UserAppTheme = Attributes.define "Application_UserAppTheme" - (fun (newValueOpt, node) -> + (fun newValueOpt node -> let application = node.Target :?> Xamarin.Forms.Application @@ -399,7 +399,7 @@ module ToolbarItem = let Order = Attributes.define "ToolbarItem_Order" - (fun (newValueOpt, node) -> + (fun newValueOpt node -> let toolbarItem = node.Target :?> Xamarin.Forms.ToolbarItem @@ -515,7 +515,7 @@ module ItemsView = for x in modelValue.OriginalItems do modelValue.Template x }) - (fun (a, b) -> ScalarAttributeComparers.equalityCompare (a.OriginalItems, b.OriginalItems)) + (fun a b -> ScalarAttributeComparers.equalityCompare a.OriginalItems b.OriginalItems) let GroupedItemsSource<'T> = Attributes.defineBindableWithComparer, GroupedWidgetItems<'T>, IEnumerable> @@ -526,7 +526,7 @@ module ItemsView = for x in modelValue.OriginalItems do modelValue.Template x }) - (fun (a, b) -> ScalarAttributeComparers.equalityCompare (a.OriginalItems, b.OriginalItems)) + (fun a b -> ScalarAttributeComparers.equalityCompare a.OriginalItems b.OriginalItems) module ItemsViewOfCell = let ItemsSource<'T> = @@ -539,7 +539,7 @@ module ItemsViewOfCell = for x in modelValue.OriginalItems do modelValue.Template x }) - (fun (a, b) -> ScalarAttributeComparers.equalityCompare (a.OriginalItems, b.OriginalItems)) + (fun a b -> ScalarAttributeComparers.equalityCompare a.OriginalItems b.OriginalItems) let GroupedItemsSource<'T> = Attributes.defineBindableWithComparer, GroupedWidgetItems<'T>, IEnumerable> @@ -551,7 +551,7 @@ module ItemsViewOfCell = for x in modelValue.OriginalItems do modelValue.Template x }) - (fun (a, b) -> ScalarAttributeComparers.equalityCompare (a.OriginalItems, b.OriginalItems)) + (fun a b -> ScalarAttributeComparers.equalityCompare a.OriginalItems b.OriginalItems) module ListView = let RowHeight = From 2d8006defcbb4591f571c1cab2ec3854776db2f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9=20Larivi=C3=A8re?= Date: Tue, 18 Jan 2022 19:50:57 +0100 Subject: [PATCH 7/9] Readd deleted technical comment --- src/Fabulous/Memo.fs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Fabulous/Memo.fs b/src/Fabulous/Memo.fs index 0fc2e959d..f152d87dc 100644 --- a/src/Fabulous/Memo.fs +++ b/src/Fabulous/Memo.fs @@ -72,6 +72,8 @@ module Memo = + // Memo isn't allowed in lists, TargetType will never get called, + // so Unchecked.defaultof is an acceptable value let private widgetDefinition: WidgetDefinition = { Key = MemoWidgetKey Name = "Memo" From 279d906cf811824e090ddf1d569a6a5daf32ec3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9=20Larivi=C3=A8re?= Date: Wed, 19 Jan 2022 08:42:08 +0100 Subject: [PATCH 8/9] Rename Core.fs as Primitives.fs --- src/Fabulous/Fabulous.fsproj | 42 ++++++++++++------------- src/Fabulous/{Core.fs => Primitives.fs} | 0 2 files changed, 21 insertions(+), 21 deletions(-) rename src/Fabulous/{Core.fs => Primitives.fs} (100%) diff --git a/src/Fabulous/Fabulous.fsproj b/src/Fabulous/Fabulous.fsproj index 9e83cf180..86bee2d29 100644 --- a/src/Fabulous/Fabulous.fsproj +++ b/src/Fabulous/Fabulous.fsproj @@ -7,28 +7,28 @@ https://github.com/TimLariviere/Fabulous-new - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - + + \ No newline at end of file diff --git a/src/Fabulous/Core.fs b/src/Fabulous/Primitives.fs similarity index 100% rename from src/Fabulous/Core.fs rename to src/Fabulous/Primitives.fs From 47a3fac35fc515c6f583f1bb773466bf73a05663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9=20Larivi=C3=A8re?= Date: Wed, 19 Jan 2022 08:57:53 +0100 Subject: [PATCH 9/9] Make Slider and Stepper min/max a struct tuple --- src/Fabulous.XamarinForms/ViewUpdaters.fs | 4 ++-- src/Fabulous.XamarinForms/Xamarin.Forms.Core.Attributes.fs | 4 ++-- src/Fabulous.XamarinForms/Xamarin.Forms.Core.fs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Fabulous.XamarinForms/ViewUpdaters.fs b/src/Fabulous.XamarinForms/ViewUpdaters.fs index 4038ab8d0..7f7a18cc6 100644 --- a/src/Fabulous.XamarinForms/ViewUpdaters.fs +++ b/src/Fabulous.XamarinForms/ViewUpdaters.fs @@ -4,7 +4,7 @@ open Fabulous open Xamarin.Forms module ViewUpdaters = - let updateSliderMinMax (newValueOpt: (float * float) voption) (node: IViewNode) = + let updateSliderMinMax (newValueOpt: struct (float * float) voption) (node: IViewNode) = let slider = node.Target :?> Slider match newValueOpt with @@ -22,7 +22,7 @@ module ViewUpdaters = slider.SetValue(Slider.MinimumProperty, min) slider.SetValue(Slider.MaximumProperty, max) - let updateStepperMinMax (newValueOpt: (float * float) voption) (node: IViewNode) = + let updateStepperMinMax (newValueOpt: struct (float * float) voption) (node: IViewNode) = let stepper = node.Target :?> Stepper match newValueOpt with diff --git a/src/Fabulous.XamarinForms/Xamarin.Forms.Core.Attributes.fs b/src/Fabulous.XamarinForms/Xamarin.Forms.Core.Attributes.fs index d72cd65e1..b89f630cb 100644 --- a/src/Fabulous.XamarinForms/Xamarin.Forms.Core.Attributes.fs +++ b/src/Fabulous.XamarinForms/Xamarin.Forms.Core.Attributes.fs @@ -241,7 +241,7 @@ module Switch = module Slider = let MinimumMaximum = - Attributes.define "Slider_MinimumMaximum" ViewUpdaters.updateSliderMinMax + Attributes.define "Slider_MinimumMaximum" ViewUpdaters.updateSliderMinMax let Value = Attributes.defineBindable Xamarin.Forms.Slider.ValueProperty @@ -495,7 +495,7 @@ module Stepper = Attributes.defineBindable Xamarin.Forms.Stepper.IncrementProperty let MinimumMaximum = - Attributes.define "Stepper_MinimumMaximum" ViewUpdaters.updateStepperMinMax + Attributes.define "Stepper_MinimumMaximum" ViewUpdaters.updateStepperMinMax let Value = Attributes.defineBindable Xamarin.Forms.Stepper.ValueProperty diff --git a/src/Fabulous.XamarinForms/Xamarin.Forms.Core.fs b/src/Fabulous.XamarinForms/Xamarin.Forms.Core.fs index e9856825c..fe7d565a4 100644 --- a/src/Fabulous.XamarinForms/Xamarin.Forms.Core.fs +++ b/src/Fabulous.XamarinForms/Xamarin.Forms.Core.fs @@ -256,7 +256,7 @@ type ViewBuilders private () = ViewKeys.Slider, Slider.Value.WithValue(value), Slider.ValueChanged.WithValue(fun args -> onValueChanged args.NewValue |> box), - Slider.MinimumMaximum.WithValue(min, max) + Slider.MinimumMaximum.WithValue(struct (min, max)) ) static member inline ActivityIndicator<'msg>(isRunning: bool) = @@ -398,7 +398,7 @@ type ViewBuilders private () = ViewKeys.Stepper, Stepper.Value.WithValue(value), Stepper.ValueChanged.WithValue(fun args -> onValueChanged args.NewValue |> box), - Stepper.MinimumMaximum.WithValue((min, max)) + Stepper.MinimumMaximum.WithValue(struct (min, max)) ) static member inline ListView<'msg, 'itemData, 'itemMarker when 'itemMarker :> ICell>(items: seq<'itemData>) =