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  }