github.com/vugu/vugu@v0.3.5/mod-check-default.go (about)

     1  // +build !tinygo
     2  
     3  package vugu
     4  
     5  import (
     6  	"bytes"
     7  	"errors"
     8  	"fmt"
     9  	"reflect"
    10  	"strings"
    11  	"unsafe"
    12  
    13  	"github.com/vugu/xxhash"
    14  )
    15  
    16  // type ModCheckedString string
    17  // func (s ModCheckedString) ModCheck(mt *ModTracker, oldData interface{}) (isModified bool, newData interface{}) {
    18  // }
    19  
    20  // ModTracker tracks modifications and maintains the appropriate state for this.
    21  type ModTracker struct {
    22  	old map[interface{}]mtResult
    23  	cur map[interface{}]mtResult
    24  }
    25  
    26  // TrackNext moves the "current" information to the "old" position - starting a new round of change tracking.
    27  // Calls to ModCheckAll after calling TrackNext will compare current values to the values from before the call to TrackNext.
    28  // It is generally called once at the start of each build and render cycle.  This method must be called at least
    29  // once before doing modification checks.
    30  func (mt *ModTracker) TrackNext() {
    31  
    32  	// lazy initialize
    33  	if mt.old == nil {
    34  		mt.old = make(map[interface{}]mtResult)
    35  	}
    36  	if mt.cur == nil {
    37  		mt.cur = make(map[interface{}]mtResult)
    38  	}
    39  
    40  	// remove all elements from old map
    41  	for k := range mt.old {
    42  		delete(mt.old, k)
    43  	}
    44  
    45  	// and swap them
    46  	mt.old, mt.cur = mt.cur, mt.old
    47  
    48  }
    49  
    50  func (mt *ModTracker) dump() []byte {
    51  	var buf bytes.Buffer
    52  	fmt.Fprintf(&buf, "-- cur (len=%d): --\n", len(mt.cur))
    53  	for k, v := range mt.cur {
    54  		fmt.Fprintf(&buf, " %#v = %#v\n", k, v)
    55  	}
    56  	fmt.Fprintf(&buf, "-- old (len=%d): --\n", len(mt.old))
    57  	for k, v := range mt.old {
    58  		fmt.Fprintf(&buf, " %#v = %#v\n", k, v)
    59  	}
    60  	return buf.Bytes()
    61  }
    62  
    63  // Otherwise pointers to some built-in types are supported including all primitive single-value types -
    64  // bool, int/uint and all variations, both float types, both complex types, string.
    65  // Pointers to supported types are supported.
    66  // Arrays, slices and maps(nope!) using supported types are supported.
    67  // Pointers to structs will be checked by
    68  // checking each field with a struct tag like `vugu:"modcheck"`.  Slices and arrays of
    69  // structs are okay, since their members have a stable position in memory and a pointer
    70  // can be taken.  Maps using structs however must use pointers to them
    71  // (restriction applies to both keys and values) to be supported.
    72  
    73  // ModCheckAll performs a modification check on the values provided.
    74  // For values implementing the ModChecker interface, the ModCheck method will be called.
    75  // All values passed should be pointers to the types described below.
    76  // Single-value primitive types are supported.  Structs are supported and
    77  // and are traversed by calling ModCheckAll on each with the tag `vugu:"modcheck"`.
    78  // Arrays and slices of supported types are supported, their length is compared as well
    79  // as a pointer to each member.
    80  // As a special case []byte is treated like a string.
    81  // Maps are not supported at this time.
    82  // Other weird and wonderful things like channels and funcs are not supported.
    83  // Passing an unsupported type will result in a panic.
    84  func (mt *ModTracker) ModCheckAll(values ...interface{}) (ret bool) {
    85  
    86  	for _, v := range values {
    87  
    88  		// check if we've already done a mod check on v
    89  		curres, ok := mt.cur[v]
    90  		if ok {
    91  			ret = ret || curres.modified
    92  			continue
    93  		}
    94  
    95  		// look up the old data
    96  		oldres := mt.old[v]
    97  
    98  		// the result of the mod check on v goes here
    99  		var mod bool
   100  		var newdata interface{}
   101  
   102  		{
   103  			// see if it implements the ModChecker interface
   104  			mc, ok := v.(ModChecker)
   105  			if ok {
   106  				mod, newdata = mc.ModCheck(mt, oldres.data)
   107  				goto handleData
   108  			}
   109  
   110  			// support for certain built-in types
   111  			switch vt := v.(type) {
   112  
   113  			case *string:
   114  				oldval, ok := oldres.data.(string)
   115  				mod = !ok || oldval != *vt
   116  				newdata = *vt
   117  				goto handleData
   118  
   119  			case *[]byte: // special case of []byte, handled like string not slice
   120  				oldval, ok := oldres.data.(string)
   121  				vts := string(*vt)
   122  				mod = !ok || oldval != vts
   123  				newdata = vts
   124  				goto handleData
   125  
   126  			case *bool:
   127  				oldval, ok := oldres.data.(bool)
   128  				mod = !ok || oldval != *vt
   129  				newdata = *vt
   130  				goto handleData
   131  
   132  			case *int:
   133  				oldval, ok := oldres.data.(int)
   134  				mod = !ok || oldval != *vt
   135  				newdata = *vt
   136  				goto handleData
   137  
   138  			case *int8:
   139  				oldval, ok := oldres.data.(int8)
   140  				mod = !ok || oldval != *vt
   141  				newdata = *vt
   142  				goto handleData
   143  
   144  			case *int16:
   145  				oldval, ok := oldres.data.(int16)
   146  				mod = !ok || oldval != *vt
   147  				newdata = *vt
   148  				goto handleData
   149  
   150  			case *int32:
   151  				oldval, ok := oldres.data.(int32)
   152  				mod = !ok || oldval != *vt
   153  				newdata = *vt
   154  				goto handleData
   155  
   156  			case *int64:
   157  				oldval, ok := oldres.data.(int64)
   158  				mod = !ok || oldval != *vt
   159  				newdata = *vt
   160  				goto handleData
   161  
   162  			case *uint:
   163  				oldval, ok := oldres.data.(uint)
   164  				mod = !ok || oldval != *vt
   165  				newdata = *vt
   166  				goto handleData
   167  
   168  			case *uint8:
   169  				oldval, ok := oldres.data.(uint8)
   170  				mod = !ok || oldval != *vt
   171  				newdata = *vt
   172  				goto handleData
   173  
   174  			case *uint16:
   175  				oldval, ok := oldres.data.(uint16)
   176  				mod = !ok || oldval != *vt
   177  				newdata = *vt
   178  				goto handleData
   179  
   180  			case *uint32:
   181  				oldval, ok := oldres.data.(uint32)
   182  				mod = !ok || oldval != *vt
   183  				newdata = *vt
   184  				goto handleData
   185  
   186  			case *uint64:
   187  				oldval, ok := oldres.data.(uint64)
   188  				mod = !ok || oldval != *vt
   189  				newdata = *vt
   190  				goto handleData
   191  
   192  			case *float32:
   193  				oldval, ok := oldres.data.(float32)
   194  				mod = !ok || oldval != *vt
   195  				newdata = *vt
   196  				goto handleData
   197  
   198  			case *float64:
   199  				oldval, ok := oldres.data.(float64)
   200  				mod = !ok || oldval != *vt
   201  				newdata = *vt
   202  				goto handleData
   203  
   204  			case *complex64:
   205  				oldval, ok := oldres.data.(complex64)
   206  				mod = !ok || oldval != *vt
   207  				newdata = *vt
   208  				goto handleData
   209  
   210  			case *complex128:
   211  				oldval, ok := oldres.data.(complex128)
   212  				mod = !ok || oldval != *vt
   213  				newdata = *vt
   214  				goto handleData
   215  
   216  			}
   217  
   218  			// when the scalpel (type switch) doesn't do it,
   219  			// gotta use the bonesaw (reflection)
   220  
   221  			rv := reflect.ValueOf(v)
   222  
   223  			// check pointer and deref
   224  			if rv.Kind() != reflect.Ptr {
   225  				panic(errors.New("type not implemented (pointer required): " + rv.String()))
   226  			}
   227  			rvv := rv.Elem()
   228  
   229  			// slice and array are treated the same
   230  			if rvv.Kind() == reflect.Slice || rvv.Kind() == reflect.Array {
   231  
   232  				l := rvv.Len()
   233  
   234  				// for slices and arrays we compute the hash of the raw contents,
   235  				// takes care of length and sequence changes
   236  				ha := xxhash.New()
   237  
   238  				var el0t reflect.Type
   239  
   240  				if l > 0 {
   241  					// use the unsafe package to make a byte slice corresponding to the raw slice contents
   242  					var bs []byte
   243  					bsh := (*reflect.SliceHeader)(unsafe.Pointer(&bs))
   244  
   245  					el0 := rvv.Index(0)
   246  					el0t = el0.Type()
   247  
   248  					bsh.Data = el0.Addr().Pointer() // point to first element of slice
   249  					bsh.Len = l * int(el0t.Size())
   250  					bsh.Cap = bsh.Len
   251  
   252  					// hash it
   253  					ha.Write(bs)
   254  				}
   255  
   256  				hashval := ha.Sum64()
   257  
   258  				// use hashval as our data, and mark as modified if different
   259  				oldval, ok := oldres.data.(uint64)
   260  				mod = !ok || oldval != hashval
   261  				newdata = hashval
   262  
   263  				// for types that by definition have already been checked with the hash above, we're done
   264  				if el0t != nil {
   265  					switch el0t.Kind() {
   266  					case reflect.Bool,
   267  						reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
   268  						reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
   269  						reflect.Uintptr,
   270  						reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128,
   271  						reflect.String:
   272  						goto handleData
   273  					}
   274  				}
   275  
   276  				// recurse into each element and check, update mod as we go
   277  				// NOTE: for "deep" element types that can have changes within them,
   278  				// it's important to recurse into children even if mod is already
   279  				// true, otherwise we'll never call ModCheckAll on these children and
   280  				// never get an unmodified response
   281  
   282  				for i := 0; i < l; i++ {
   283  
   284  					// get pointer to the individual element and recurse into it
   285  					elv := rvv.Index(i).Addr().Interface()
   286  					mod = mt.ModCheckAll(elv) || mod
   287  				}
   288  
   289  				goto handleData
   290  
   291  			}
   292  
   293  			// for structs we iterate over the fields looked for tagged ones
   294  			if rvv.Kind() == reflect.Struct {
   295  
   296  				// just use bool(true) as the data value for a struct - this way it will always be modified the first time,
   297  				// but every subsequent check will solely depend on the checks on it's fields
   298  				oldval, ok := oldres.data.(bool)
   299  				mod = !ok || !oldval
   300  				newdata = true
   301  
   302  				rvvt := rvv.Type()
   303  				for i := 0; i < rvvt.NumField(); i++ {
   304  					// skip untagged fields
   305  					if !hasTagPart(rvvt.Field(i).Tag.Get("vugu"), "data") {
   306  						continue
   307  					}
   308  
   309  					// call ModCheckAll on pointer to field
   310  					mod = mt.ModCheckAll(
   311  						rvv.Field(i).Addr().Interface(),
   312  					) || mod
   313  
   314  				}
   315  
   316  				goto handleData
   317  			}
   318  
   319  			// random stream of conciousness: should we check for a ModCheck implementation here and call it?
   320  			// We might want to follow these pointers or rather recurse into them (if not nil?)
   321  			// with a ModCheckAll.  Also look at how this ties into maps (if at all), since theoretically
   322  			// we could compare a map by storing its length and basically doing for k, v := range m { ...ModCheckAll(&k,&v)... }
   323  			// actually no that won't work because k and v will have different locations each time - but still could
   324  			// potentially make some mapKeyValue struct that does what we need - but not vital to solve right now.
   325  			// Pointers to pointers will probably come up first, and is likely why you are reading this comment.
   326  
   327  			// pointer (meaning we were originally passed a pointer to a pointer)
   328  			if rvv.Kind() == reflect.Ptr {
   329  
   330  				// use pointer value as data...
   331  				vv := rvv.Pointer()
   332  				oldval, ok := oldres.data.(uintptr)
   333  				mod = !ok || oldval != vv
   334  				newdata = vv
   335  
   336  				// ...but also recurse and call ModChecker with one level of pointer dereferencing, if not nil
   337  				if !rvv.IsNil() {
   338  					mod = mt.ModCheckAll(
   339  						rvv.Interface(),
   340  					) || mod
   341  				}
   342  
   343  				goto handleData
   344  			}
   345  
   346  			panic(errors.New("type not implemented: " + reflect.TypeOf(v).String()))
   347  
   348  		}
   349  	handleData:
   350  		mt.cur[v] = mtResult{modified: mod, data: newdata}
   351  		ret = ret || mod
   352  
   353  	}
   354  
   355  	return ret
   356  }
   357  
   358  // ModCheck(oldData interface{}) (isModified bool, newData interface{})
   359  
   360  // hm, this may not work - what happens if we call ModCheck twice in a row?? we need a clear
   361  // way to demark the "old" and "new" versions of data - it might be that the comparison and
   362  // the gather of the new value need to be separate methods?  Or can ModTracker somehow
   363  // prevent ModCheck from being call twice in the same pass (or prevent that from being an issue)
   364  
   365  // actually that might work - if there is a call on ModTracker that moves "new" to "old", it
   366  // woudl be pretty clear - and then calling this would only update the "new" value.  Also the
   367  // presence of something in the "new" data would mean it's already been called in this pass
   368  // and so can be deduplicated.
   369  
   370  func hasTagPart(tagstr, part string) bool {
   371  	for _, p := range strings.Split(tagstr, ",") {
   372  		if p == part {
   373  			return true
   374  		}
   375  	}
   376  	return false
   377  }