go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/projects/nodes/pkg/incrutil/cutoff2.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 Cutoff2[A, B any](scope incr.Scope, fn Cutoff2Func[A, B]) Cutoff2Incr[A, B] { 23 return Cutoff2Context[A, B](scope, func(_ context.Context, input A, oldv, newv B) (bool, error) { 24 return fn(input, 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 Cutoff2Context[A, B any](scope incr.Scope, fn Cutoff2ContextFunc[A, B]) Cutoff2Incr[A, B] { 34 return incr.WithinScope(scope, &cutoff2Incr[A, B]{ 35 n: incr.NewNode("cutoff2"), 36 fn: fn, 37 }) 38 } 39 40 // Cutoff2Func is a function that implements cutoff checking. 41 type Cutoff2Func[A, B any] func(A, B, B) bool 42 43 // Cutoff2ContextFunc is a function that implements cutoff checking 44 // and takes a context. 45 type Cutoff2ContextFunc[A, B any] func(context.Context, A, B, B) (bool, error) 46 47 // CutoffIncr is an incremental node that implements the ICutoff interface. 48 type Cutoff2Incr[A, B any] interface { 49 incr.Incr[B] 50 incr.IStabilize 51 incr.ICutoff 52 ISetInput 53 IRemoveInput 54 IInputs 55 IRawValue 56 ISetRawValue 57 IRawFunction 58 ISetRawFunction 59 } 60 61 var ( 62 _ incr.Incr[string] = (*cutoff2Incr[int, string])(nil) 63 _ Cutoff2Incr[int, string] = (*cutoff2Incr[int, string])(nil) 64 _ incr.IStabilize = (*cutoff2Incr[int, string])(nil) 65 _ incr.ICutoff = (*cutoff2Incr[int, string])(nil) 66 _ fmt.Stringer = (*cutoff2Incr[int, string])(nil) 67 ) 68 69 // cutoffIncr is a concrete implementation of Incr for 70 // the cutoff operator. 71 type cutoff2Incr[A, B any] struct { 72 n *incr.Node 73 epsilon incr.Incr[A] 74 input incr.Incr[B] 75 value B 76 fn Cutoff2ContextFunc[A, B] 77 } 78 79 func (c *cutoff2Incr[A, B]) Parents() (out []incr.INode) { 80 if c.input != nil { 81 out = append(out, c.input) 82 } 83 if c.epsilon != nil { 84 out = append(out, c.epsilon) 85 } 86 return 87 } 88 89 func (c *cutoff2Incr[A, B]) Value() B { 90 return c.value 91 } 92 93 func (c *cutoff2Incr[A, B]) Stabilize(ctx context.Context) error { 94 c.value = c.input.Value() 95 return nil 96 } 97 98 func (c *cutoff2Incr[A, B]) Cutoff(ctx context.Context) (bool, error) { 99 cutoff, err := c.fn(ctx, c.epsilon.Value(), c.value, c.input.Value()) 100 if err != nil { 101 return cutoff, fmt.Errorf("%v; %w", c, err) 102 } 103 return cutoff, err 104 } 105 106 func (c *cutoff2Incr[A, B]) Node() *incr.Node { 107 return c.n 108 } 109 110 func (c *cutoff2Incr[A, B]) String() string { return c.n.String() } 111 112 func (c *cutoff2Incr[A, B]) Inputs() map[string]incr.INode { 113 output := make(map[string]incr.INode) 114 if c.epsilon != nil { 115 output["epsilon"] = c.epsilon 116 } 117 if c.input != nil { 118 output["input"] = c.input 119 } 120 return output 121 } 122 123 func (c *cutoff2Incr[A, B]) SetInput(name string, input incr.INode, _ bool) error { 124 if name == "epsilon" { 125 typed, ok := input.(incr.Incr[A]) 126 if !ok { 127 return fmt.Errorf("invalid input type for input 0: %T", input) 128 } 129 c.epsilon = typed 130 LinkNodes(c, c.epsilon) 131 return nil 132 } 133 if name == "input" { 134 typed, ok := input.(incr.Incr[B]) 135 if !ok { 136 return fmt.Errorf("invalid input type for input 0: %T", input) 137 } 138 c.input = typed 139 LinkNodes(c, c.input) 140 return nil 141 } 142 return fmt.Errorf("invalid input name: %v", name) 143 } 144 145 func (c *cutoff2Incr[A, B]) RemoveInput(name string) error { 146 if name == "epsilon" { 147 if c.epsilon != nil { 148 UnlinkNodes(c, c.epsilon) 149 c.epsilon = nil 150 return nil 151 } 152 return fmt.Errorf("remove input; epsilon is unset") 153 } 154 if name == "input" { 155 if c.input != nil { 156 UnlinkNodes(c, c.input) 157 c.input = nil 158 return nil 159 } 160 return fmt.Errorf("remove input; input is unset") 161 } 162 return fmt.Errorf("invalid input name: %v", name) 163 } 164 165 func (c *cutoff2Incr[A, B]) RawFunction() any { 166 return c.fn 167 } 168 169 func (c *cutoff2Incr[A, B]) SetRawFunction(fn any) error { 170 switch typed := fn.(type) { 171 case Cutoff2ContextFunc[A, B]: 172 c.fn = typed 173 return nil 174 case Cutoff2Func[A, B]: 175 c.fn = func(_ context.Context, i A, old, new B) (bool, error) { 176 return typed(i, old, new), nil 177 } 178 return nil 179 default: 180 return fmt.Errorf("invalid function type for node: %T", fn) 181 } 182 } 183 184 func (c *cutoff2Incr[A, B]) RawValue() (output any) { 185 return c.value 186 } 187 188 func (c *cutoff2Incr[A, B]) SetRawValue(value any) error { 189 var typed B 190 if err := CastAny(value, &typed); err != nil { 191 return err 192 } 193 c.value = typed 194 return nil 195 }