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  }