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 }