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  }