open-match.dev/open-match@v1.8.1/examples/demo/updater/updater.go (about) 1 // Copyright 2019 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package updater provides the ability for concurrently running demo pieces to 16 // update a shared json object. 17 package updater 18 19 import ( 20 "context" 21 "encoding/json" 22 ) 23 24 // Updater is like a json object, with each field allowed to be updated 25 // concurrently by a different process. After processing updates, Updater will 26 // call a provided method with the json serialized value of all of its fields. 27 type Updater struct { 28 ctx context.Context 29 children map[string]*json.RawMessage 30 updates chan update 31 set SetFunc 32 } 33 34 // SetFunc serializes the value passed in into json and sets the associated field 35 // to that value. If nil is passed (BUT NOT a nil value of an interface), the 36 // field will be removed from the Updater's json object. 37 type SetFunc func(v interface{}) 38 39 // New creates an Updater. Set is called when fields update, using the json 40 // serialized value of Updater's tree. All updates after ctx is canceled are 41 // ignored. 42 func New(ctx context.Context, set func([]byte)) *Updater { 43 f := func(v interface{}) { 44 set([]byte(*forceMarshalJson(v))) 45 } 46 return NewNested(ctx, SetFunc(f)) 47 } 48 49 // NewNested creates an updater based on a field in another updater. This 50 // allows for grouping of related demo pieces into a single conceptual group. 51 func NewNested(ctx context.Context, set SetFunc) *Updater { 52 u := create(ctx, set) 53 go u.start() 54 return u 55 } 56 57 func create(ctx context.Context, set SetFunc) *Updater { 58 return &Updater{ 59 ctx: ctx, 60 children: make(map[string]*json.RawMessage), 61 updates: make(chan update), 62 set: set, 63 } 64 } 65 66 // ForField returns a function to set the latest value of that demo piece. 67 func (u *Updater) ForField(field string) SetFunc { 68 return SetFunc(func(v interface{}) { 69 var r *json.RawMessage 70 if v != nil { 71 r = forceMarshalJson(v) 72 } 73 74 select { 75 case <-u.ctx.Done(): 76 case u.updates <- update{field, r}: 77 } 78 }) 79 } 80 81 func (u *Updater) start() { 82 for { 83 u.set(u.children) 84 85 select { 86 case <-u.ctx.Done(): 87 u.set(nil) 88 return 89 case up := <-u.updates: 90 if up.value == nil { 91 delete(u.children, up.field) 92 } else { 93 u.children[up.field] = up.value 94 } 95 } 96 97 applyAllWaitingUpdates: 98 for { 99 select { 100 case up := <-u.updates: 101 if up.value == nil { 102 delete(u.children, up.field) 103 } else { 104 u.children[up.field] = up.value 105 } 106 default: 107 break applyAllWaitingUpdates 108 } 109 } 110 } 111 } 112 113 type update struct { 114 field string 115 value *json.RawMessage 116 } 117 118 // forceMarshalJson is like json.Marshal, but cannot fail. It will instead 119 // encode any error encountered into the json object on the field Error. 120 func forceMarshalJson(v interface{}) *json.RawMessage { 121 b, err := json.Marshal(v) 122 if err != nil { 123 e := struct { 124 Error string 125 }{ 126 err.Error(), 127 } 128 129 b, err = json.Marshal(e) 130 if err != nil { 131 b = []byte("{\"Error\":\"There was an error encoding the json message, additional there was an error encoding that error message.\"}") 132 } 133 } 134 135 r := json.RawMessage(b) 136 return &r 137 }