go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/projects/nodes/pkg/incrutil/cutoff.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package incrutil 9 10 import ( 11 "context" 12 "fmt" 13 14 "github.com/wcharczuk/go-incr" 15 ) 16 17 // Cutoff returns a new wrapping cutoff incremental. 18 // 19 // The goal of the cutoff incremental is to stop recomputation at a given 20 // node if the difference between the previous and latest values are not 21 // significant enough to warrant a full recomputation of the children of this node. 22 func Cutoff[A any](scope incr.Scope, fn incr.CutoffFunc[A]) CutoffIncr[A] { 23 return CutoffContext[A](scope, func(_ context.Context, oldv, newv A) (bool, error) { 24 return fn(oldv, newv), nil 25 }) 26 } 27 28 // CutoffContext returns a new wrapping cutoff incremental. 29 // 30 // The goal of the cutoff incremental is to stop recomputation at a given 31 // node if the difference between the previous and latest values are not 32 // significant enough to warrant a full recomputation of the children of this node. 33 func CutoffContext[A any](scope incr.Scope, fn incr.CutoffContextFunc[A]) CutoffIncr[A] { 34 return incr.WithinScope(scope, &cutoffIncr[A]{ 35 n: incr.NewNode("cutoff"), 36 fn: fn, 37 }) 38 } 39 40 // CutoffIncr is an incremental node that implements the ICutoff interface. 41 type CutoffIncr[A any] interface { 42 incr.Incr[A] 43 incr.IStabilize 44 incr.ICutoff 45 IInputs 46 ISetInput 47 IRemoveInput 48 IRawValue 49 ISetRawValue 50 IRawFunction 51 ISetRawFunction 52 } 53 54 var ( 55 _ incr.Incr[string] = (*cutoffIncr[string])(nil) 56 _ incr.IStabilize = (*cutoffIncr[string])(nil) 57 _ incr.ICutoff = (*cutoffIncr[string])(nil) 58 _ fmt.Stringer = (*cutoffIncr[string])(nil) 59 ) 60 61 // cutoffIncr is a concrete implementation of Incr for 62 // the cutoff operator. 63 type cutoffIncr[A any] struct { 64 n *incr.Node 65 i incr.Incr[A] 66 value A 67 fn incr.CutoffContextFunc[A] 68 } 69 70 func (c *cutoffIncr[A]) Parents() []incr.INode { 71 if c.i != nil { 72 return []incr.INode{c.i} 73 } 74 return nil 75 } 76 77 func (c *cutoffIncr[A]) Value() A { 78 return c.value 79 } 80 81 func (c *cutoffIncr[A]) Stabilize(ctx context.Context) error { 82 c.value = c.i.Value() 83 return nil 84 } 85 86 func (c *cutoffIncr[A]) Cutoff(ctx context.Context) (bool, error) { 87 cutoff, err := c.fn(ctx, c.value, c.i.Value()) 88 if err != nil { 89 return cutoff, fmt.Errorf("%v; %w", c, err) 90 } 91 return cutoff, err 92 } 93 94 func (c *cutoffIncr[A]) Node() *incr.Node { 95 return c.n 96 } 97 98 func (c *cutoffIncr[A]) String() string { return c.n.String() } 99 100 func (c *cutoffIncr[A]) SetInput(name string, i incr.INode, _ bool) error { 101 typed, ok := i.(incr.Incr[A]) 102 if !ok || typed == nil { 103 return fmt.Errorf("invalid untyped input for map node %T", i) 104 } 105 c.i = typed 106 LinkNodes(c, i) 107 return nil 108 } 109 110 func (c *cutoffIncr[A]) RemoveInput(name string) error { 111 if c.i != nil { 112 UnlinkNodes(c, c.i) 113 c.i = nil 114 return nil 115 } 116 return fmt.Errorf("remove input; input is unset") 117 } 118 119 func (c *cutoffIncr[A]) Inputs() map[string]incr.INode { 120 if c.i != nil { 121 return map[string]incr.INode{ 122 "input": c.i, 123 } 124 } 125 return nil 126 } 127 128 func (c *cutoffIncr[A]) RawFunction() any { 129 return c.fn 130 } 131 132 func (c *cutoffIncr[A]) SetRawFunction(fn any) error { 133 switch typed := fn.(type) { 134 case incr.CutoffContextFunc[A]: 135 c.fn = typed 136 return nil 137 case incr.CutoffFunc[A]: 138 c.fn = func(_ context.Context, old, new A) (bool, error) { 139 return typed(old, new), nil 140 } 141 return nil 142 default: 143 return fmt.Errorf("invalid function type for node: %T", fn) 144 } 145 } 146 147 func (c *cutoffIncr[A]) RawValue() (output any) { 148 return c.value 149 } 150 151 func (c *cutoffIncr[A]) SetRawValue(value any) error { 152 var typed A 153 if err := CastAny(value, &typed); err != nil { 154 return err 155 } 156 c.value = typed 157 return nil 158 }