From ee657ba60a3fdf4f62bc2b463e8d51f01c5f4ca0 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 19 Mar 2017 16:52:29 +0000 Subject: [PATCH] Remove indirection via LatestMap Structs like StringLatestMap now use ps.Map directly, which saves a memory allocation for LatestEntry.Value to point to. The values in the ps.Map are now pointers, which saves a memory allocation indirecting a value type to an interface{} --- extras/generate_latest_map | 107 ++++++++++----- report/latest_map.go | 169 ----------------------- report/latest_map_generated.go | 206 ++++++++++++++++++++--------- report/latest_map_internal_test.go | 21 +-- 4 files changed, 218 insertions(+), 285 deletions(-) delete mode 100644 report/latest_map.go diff --git a/extras/generate_latest_map b/extras/generate_latest_map index 04e886a1cc..83172c1776 100755 --- a/extras/generate_latest_map +++ b/extras/generate_latest_map @@ -19,9 +19,11 @@ function generate_header() { package report import ( + "fmt" "time" "github.com/ugorji/go/codec" + "github.com/weaveworks/ps" ) EOF } @@ -31,7 +33,7 @@ function generate_latest_map() { local data_type="$2" local uppercase_data_type="${data_type^}" local lowercase_data_type="${data_type,}" - local wire_entry_type="wire${uppercase_data_type}LatestEntry" + local entry_type="${uppercase_data_type}LatestEntry" local decoder_type="${lowercase_data_type}LatestEntryDecoder" local iface_decoder_variable="${uppercase_data_type}LatestEntryDecoder" local latest_map_type="${uppercase_data_type}LatestMap" @@ -44,29 +46,26 @@ function generate_latest_map() { local json_value='`json:"value"`' cat <>"${out_file}" - type ${wire_entry_type} struct { + type ${entry_type} struct { Timestamp time.Time ${json_timestamp} Value ${data_type} ${json_value} } - type ${decoder_type} struct {} - - func (d *${decoder_type}) Decode(decoder *codec.Decoder, entry *LatestEntry) { - wire := ${wire_entry_type}{} - decoder.Decode(&wire) - entry.Timestamp = wire.Timestamp - entry.Value = wire.Value + // String returns the StringLatestEntry's string representation. + func (e *${entry_type}) String() string { + return fmt.Sprintf("%v (%s)", e.Value, e.Timestamp.String()) } - // ${iface_decoder_variable} is an implementation of LatestEntryDecoder - // that decodes the LatestEntry instances having a ${data_type} value. - var ${iface_decoder_variable} LatestEntryDecoder = &${decoder_type}{} + // Equal returns true if the supplied StringLatestEntry is equal to this one. + func (e *${entry_type}) Equal(e2 *${entry_type}) bool { + return e.Timestamp.Equal(e2.Timestamp) && e.Value == e2.Value + } // ${latest_map_type} holds latest ${data_type} instances. - type ${latest_map_type} LatestMap + type ${latest_map_type} struct { ps.Map } // ${empty_latest_map_variable} is an empty ${latest_map_type}. Start with this. - var ${empty_latest_map_variable} = (${latest_map_type})(MakeLatestMapWithDecoder(${iface_decoder_variable})) + var ${empty_latest_map_variable} = ${latest_map_type}{ps.NewMap()} // ${make_function} makes an empty ${latest_map_type}. func ${make_function}() ${latest_map_type} { @@ -75,77 +74,117 @@ function generate_latest_map() { // Copy is a noop, as ${latest_map_type}s are immutable. func (m ${latest_map_type}) Copy() ${latest_map_type} { - return (${latest_map_type})((LatestMap)(m).Copy()) + return m } // Size returns the number of elements. func (m ${latest_map_type}) Size() int { - return (LatestMap)(m).Size() + if m.Map == nil { + return 0 + } + return m.Map.Size() } // Merge produces a fresh ${latest_map_type} containing the keys from both inputs. // When both inputs contain the same key, the newer value is used. func (m ${latest_map_type}) Merge(other ${latest_map_type}) ${latest_map_type} { - return (${latest_map_type})((LatestMap)(m).Merge((LatestMap)(other))) + output := mergeMaps(m.Map, other.Map, func(a, b interface{}) bool { + return a.(*${entry_type}).Timestamp.Before(b.(*${entry_type}).Timestamp) + }) + return ${latest_map_type}{output} } // Lookup the value for the given key. func (m ${latest_map_type}) Lookup(key string) (${data_type}, bool) { - v, ok := (LatestMap)(m).Lookup(key) + v, _, ok := m.LookupEntry(key) if !ok { var zero ${data_type} return zero, false } - return v.(${data_type}), true + return v, true } // LookupEntry returns the raw entry for the given key. func (m ${latest_map_type}) LookupEntry(key string) (${data_type}, time.Time, bool) { - v, timestamp, ok := (LatestMap)(m).LookupEntry(key) + if m.Map == nil { + var zero ${data_type} + return zero, time.Time{}, false + } + value, ok := m.Map.Lookup(key) if !ok { - var zero ${data_type} - return zero, timestamp, false + var zero ${data_type} + return zero, time.Time{}, false } - return v.(${data_type}), timestamp, true + e := value.(*${entry_type}) + return e.Value, e.Timestamp, true } // Set the value for the given key. func (m ${latest_map_type}) Set(key string, timestamp time.Time, value ${data_type}) ${latest_map_type} { - return (${latest_map_type})((LatestMap)(m).Set(key, timestamp, value)) + if m.Map == nil { + m.Map = ps.NewMap() + } + return ${latest_map_type}{m.Map.Set(key, &${entry_type}{timestamp, value})} } // Delete the value for the given key. func (m ${latest_map_type}) Delete(key string) ${latest_map_type} { - return (${latest_map_type})((LatestMap)(m).Delete(key)) + if m.Map == nil { + return m + } + return ${latest_map_type}{m.Map.Delete(key)} } // ForEach executes fn on each key value pair in the map. func (m ${latest_map_type}) ForEach(fn func(k string, timestamp time.Time, v ${data_type})) { - (LatestMap)(m).ForEach(func(key string, ts time.Time, value interface{}) { - fn(key, ts, value.(${data_type})) - }) + if m.Map != nil { + m.Map.ForEach(func(key string, value interface{}) { + fn(key, value.(*${entry_type}).Timestamp, value.(*${entry_type}).Value) + }) + } } // String returns the ${latest_map_type}'s string representation. func (m ${latest_map_type}) String() string { - return (LatestMap)(m).String() + return mapToString(m.Map) } // DeepEqual tests equality with other ${latest_map_type}. func (m ${latest_map_type}) DeepEqual(n ${latest_map_type}) bool { - return (LatestMap)(m).DeepEqual((LatestMap)(n)) + return mapEqual(m.Map, n.Map, func(val, otherValue interface{}) bool { + return val.(*${entry_type}).Equal(otherValue.(*${entry_type})) + }) + } + + func (m ${latest_map_type}) toIntermediate() map[string]${entry_type} { + intermediate := make(map[string]${entry_type}, m.Size()) + if m.Map != nil { + m.Map.ForEach(func(key string, val interface{}) { + intermediate[key] = *val.(*${entry_type}) + }) + } + return intermediate } // CodecEncodeSelf implements codec.Selfer. func (m *${latest_map_type}) CodecEncodeSelf(encoder *codec.Encoder) { - (*LatestMap)(m).CodecEncodeSelf(encoder) + if m.Map != nil { + encoder.Encode(m.toIntermediate()) + } else { + encoder.Encode(nil) + } } // CodecDecodeSelf implements codec.Selfer. func (m *${latest_map_type}) CodecDecodeSelf(decoder *codec.Decoder) { - bm := (*LatestMap)(m) - bm.decoder = ${iface_decoder_variable} - bm.CodecDecodeSelf(decoder) + out := mapRead(decoder, func(isNil bool) interface{} { + value := &${entry_type}{} + if !isNil { + decoder.Decode(value) + } + return value + }) + *m = ${latest_map_type}{out} } // MarshalJSON shouldn't be used, use CodecEncodeSelf instead. diff --git a/report/latest_map.go b/report/latest_map.go deleted file mode 100644 index 37cbdf6c1e..0000000000 --- a/report/latest_map.go +++ /dev/null @@ -1,169 +0,0 @@ -package report - -import ( - "fmt" - "time" - - "github.com/ugorji/go/codec" - "github.com/weaveworks/ps" -) - -// LatestEntryDecoder is an interface for decoding the LatestEntry instances. -type LatestEntryDecoder interface { - Decode(decoder *codec.Decoder, entry *LatestEntry) -} - -// LatestMap is a persistent map which support latest-win merges. We -// have to embed ps.Map as its interface. LatestMaps are immutable. -type LatestMap struct { - ps.Map - decoder LatestEntryDecoder -} - -// LatestEntry represents a timestamped value inside the LatestMap. -type LatestEntry struct { - Timestamp time.Time `json:"timestamp"` - Value interface{} `json:"value"` -} - -// String returns the LatestEntry's string representation. -func (e LatestEntry) String() string { - return fmt.Sprintf("%v (%s)", e.Value, e.Timestamp.String()) -} - -// Equal returns true if the supplied LatestEntry is equal to this one. -func (e LatestEntry) Equal(e2 LatestEntry) bool { - return e.Timestamp.Equal(e2.Timestamp) && e.Value == e2.Value -} - -// MakeLatestMapWithDecoder makes an empty LatestMap holding custom values. -func MakeLatestMapWithDecoder(decoder LatestEntryDecoder) LatestMap { - return LatestMap{ps.NewMap(), decoder} -} - -// Copy is a noop, as LatestMaps are immutable. -func (m LatestMap) Copy() LatestMap { - return m -} - -// Size returns the number of elements. -func (m LatestMap) Size() int { - if m.Map == nil { - return 0 - } - return m.Map.Size() -} - -// Merge produces a fresh StringLatestMap containing the keys from -// both inputs. When both inputs contain the same key, the newer value -// is used. -func (m LatestMap) Merge(other LatestMap) LatestMap { - if m.decoder != other.decoder { - panic(fmt.Sprintf("Cannot merge maps with different entry value types, this has %#v, other has %#v", m.decoder, other.decoder)) - } - output := mergeMaps(m.Map, other.Map, func(a, b interface{}) bool { - return a.(LatestEntry).Timestamp.Before(b.(LatestEntry).Timestamp) - }) - return LatestMap{output, m.decoder} -} - -// Lookup the value for the given key. -func (m LatestMap) Lookup(key string) (interface{}, bool) { - v, _, ok := m.LookupEntry(key) - return v, ok -} - -// LookupEntry returns the raw entry for the given key. -func (m LatestMap) LookupEntry(key string) (interface{}, time.Time, bool) { - if m.Map == nil { - return nil, time.Time{}, false - } - value, ok := m.Map.Lookup(key) - if !ok { - return nil, time.Time{}, false - } - e := value.(LatestEntry) - return e.Value, e.Timestamp, true -} - -// Set sets the value for the given key. -func (m LatestMap) Set(key string, timestamp time.Time, value interface{}) LatestMap { - if m.Map == nil { - m = MakeLatestMapWithDecoder(m.decoder) - } - return LatestMap{m.Map.Set(key, LatestEntry{timestamp, value}), m.decoder} -} - -// Delete the value for the given key. -func (m LatestMap) Delete(key string) LatestMap { - if m.Map == nil { - m = MakeLatestMapWithDecoder(m.decoder) - } - return LatestMap{m.Map.Delete(key), m.decoder} -} - -// ForEach executes fn on each key, timestamp, value triple in the map. -func (m LatestMap) ForEach(fn func(k string, ts time.Time, v interface{})) { - if m.Map == nil { - return - } - m.Map.ForEach(func(key string, value interface{}) { - fn(key, value.(LatestEntry).Timestamp, value.(LatestEntry).Value) - }) -} - -// String returns the LatestMap's string representation. -func (m LatestMap) String() string { - return mapToString(m.Map) -} - -// DeepEqual tests equality with other LatestMap. -func (m LatestMap) DeepEqual(n LatestMap) bool { - if m.decoder != n.decoder { - panic(fmt.Sprintf("Cannot check equality of maps with different entry value types, this has %#v, other has %#v", m.decoder, n.decoder)) - } - return mapEqual(m.Map, n.Map, func(val, otherValue interface{}) bool { - return val.(LatestEntry).Equal(otherValue.(LatestEntry)) - }) -} - -func (m LatestMap) toIntermediate() map[string]LatestEntry { - intermediate := make(map[string]LatestEntry, m.Size()) - if m.Map != nil { - m.Map.ForEach(func(key string, val interface{}) { - intermediate[key] = val.(LatestEntry) - }) - } - return intermediate -} - -// CodecEncodeSelf implements codec.Selfer. -func (m *LatestMap) CodecEncodeSelf(encoder *codec.Encoder) { - if m.Map != nil { - encoder.Encode(m.toIntermediate()) - } else { - encoder.Encode(nil) - } -} - -// CodecDecodeSelf implements codec.Selfer. -func (m *LatestMap) CodecDecodeSelf(decoder *codec.Decoder) { - out := mapRead(decoder, func(isNil bool) interface{} { - value := LatestEntry{} - if !isNil { - m.decoder.Decode(decoder, &value) - } - return value - }) - *m = LatestMap{out, m.decoder} -} - -// MarshalJSON shouldn't be used, use CodecEncodeSelf instead. -func (LatestMap) MarshalJSON() ([]byte, error) { - panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead") -} - -// UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead. -func (*LatestMap) UnmarshalJSON(b []byte) error { - panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead") -} diff --git a/report/latest_map_generated.go b/report/latest_map_generated.go index cd062bcb48..3e596c207b 100644 --- a/report/latest_map_generated.go +++ b/report/latest_map_generated.go @@ -1,37 +1,36 @@ // Generated file, do not edit. -// To regenerate, run ./tools/generate_latest_map ./report/latest_map_generated.go string NodeControlData +// To regenerate, run ../extras/generate_latest_map ./latest_map_generated.go string NodeControlData package report import ( + "fmt" "time" "github.com/ugorji/go/codec" + "github.com/weaveworks/ps" ) -type wireStringLatestEntry struct { +type StringLatestEntry struct { Timestamp time.Time `json:"timestamp"` Value string `json:"value"` } -type stringLatestEntryDecoder struct{} - -func (d *stringLatestEntryDecoder) Decode(decoder *codec.Decoder, entry *LatestEntry) { - wire := wireStringLatestEntry{} - decoder.Decode(&wire) - entry.Timestamp = wire.Timestamp - entry.Value = wire.Value +// String returns the StringLatestEntry's string representation. +func (e *StringLatestEntry) String() string { + return fmt.Sprintf("%v (%s)", e.Value, e.Timestamp.String()) } -// StringLatestEntryDecoder is an implementation of LatestEntryDecoder -// that decodes the LatestEntry instances having a string value. -var StringLatestEntryDecoder LatestEntryDecoder = &stringLatestEntryDecoder{} +// Equal returns true if the supplied StringLatestEntry is equal to this one. +func (e *StringLatestEntry) Equal(e2 *StringLatestEntry) bool { + return e.Timestamp.Equal(e2.Timestamp) && e.Value == e2.Value +} // StringLatestMap holds latest string instances. -type StringLatestMap LatestMap +type StringLatestMap struct{ ps.Map } // EmptyStringLatestMap is an empty StringLatestMap. Start with this. -var EmptyStringLatestMap = (StringLatestMap)(MakeLatestMapWithDecoder(StringLatestEntryDecoder)) +var EmptyStringLatestMap = StringLatestMap{ps.NewMap()} // MakeStringLatestMap makes an empty StringLatestMap. func MakeStringLatestMap() StringLatestMap { @@ -40,77 +39,117 @@ func MakeStringLatestMap() StringLatestMap { // Copy is a noop, as StringLatestMaps are immutable. func (m StringLatestMap) Copy() StringLatestMap { - return (StringLatestMap)((LatestMap)(m).Copy()) + return m } // Size returns the number of elements. func (m StringLatestMap) Size() int { - return (LatestMap)(m).Size() + if m.Map == nil { + return 0 + } + return m.Map.Size() } // Merge produces a fresh StringLatestMap containing the keys from both inputs. // When both inputs contain the same key, the newer value is used. func (m StringLatestMap) Merge(other StringLatestMap) StringLatestMap { - return (StringLatestMap)((LatestMap)(m).Merge((LatestMap)(other))) + output := mergeMaps(m.Map, other.Map, func(a, b interface{}) bool { + return a.(*StringLatestEntry).Timestamp.Before(b.(*StringLatestEntry).Timestamp) + }) + return StringLatestMap{output} } // Lookup the value for the given key. func (m StringLatestMap) Lookup(key string) (string, bool) { - v, ok := (LatestMap)(m).Lookup(key) + v, _, ok := m.LookupEntry(key) if !ok { var zero string return zero, false } - return v.(string), true + return v, true } // LookupEntry returns the raw entry for the given key. func (m StringLatestMap) LookupEntry(key string) (string, time.Time, bool) { - v, timestamp, ok := (LatestMap)(m).LookupEntry(key) + if m.Map == nil { + var zero string + return zero, time.Time{}, false + } + value, ok := m.Map.Lookup(key) if !ok { var zero string - return zero, timestamp, false + return zero, time.Time{}, false } - return v.(string), timestamp, true + e := value.(*StringLatestEntry) + return e.Value, e.Timestamp, true } // Set the value for the given key. func (m StringLatestMap) Set(key string, timestamp time.Time, value string) StringLatestMap { - return (StringLatestMap)((LatestMap)(m).Set(key, timestamp, value)) + if m.Map == nil { + m.Map = ps.NewMap() + } + return StringLatestMap{m.Map.Set(key, &StringLatestEntry{timestamp, value})} } // Delete the value for the given key. func (m StringLatestMap) Delete(key string) StringLatestMap { - return (StringLatestMap)((LatestMap)(m).Delete(key)) + if m.Map == nil { + return m + } + return StringLatestMap{m.Map.Delete(key)} } // ForEach executes fn on each key value pair in the map. func (m StringLatestMap) ForEach(fn func(k string, timestamp time.Time, v string)) { - (LatestMap)(m).ForEach(func(key string, ts time.Time, value interface{}) { - fn(key, ts, value.(string)) - }) + if m.Map != nil { + m.Map.ForEach(func(key string, value interface{}) { + fn(key, value.(*StringLatestEntry).Timestamp, value.(*StringLatestEntry).Value) + }) + } } // String returns the StringLatestMap's string representation. func (m StringLatestMap) String() string { - return (LatestMap)(m).String() + return mapToString(m.Map) } // DeepEqual tests equality with other StringLatestMap. func (m StringLatestMap) DeepEqual(n StringLatestMap) bool { - return (LatestMap)(m).DeepEqual((LatestMap)(n)) + return mapEqual(m.Map, n.Map, func(val, otherValue interface{}) bool { + return val.(*StringLatestEntry).Equal(otherValue.(*StringLatestEntry)) + }) +} + +func (m StringLatestMap) toIntermediate() map[string]StringLatestEntry { + intermediate := make(map[string]StringLatestEntry, m.Size()) + if m.Map != nil { + m.Map.ForEach(func(key string, val interface{}) { + intermediate[key] = *val.(*StringLatestEntry) + }) + } + return intermediate } // CodecEncodeSelf implements codec.Selfer. func (m *StringLatestMap) CodecEncodeSelf(encoder *codec.Encoder) { - (*LatestMap)(m).CodecEncodeSelf(encoder) + if m.Map != nil { + encoder.Encode(m.toIntermediate()) + } else { + encoder.Encode(nil) + } } // CodecDecodeSelf implements codec.Selfer. func (m *StringLatestMap) CodecDecodeSelf(decoder *codec.Decoder) { - bm := (*LatestMap)(m) - bm.decoder = StringLatestEntryDecoder - bm.CodecDecodeSelf(decoder) + out := mapRead(decoder, func(isNil bool) interface{} { + value := &StringLatestEntry{} + if !isNil { + decoder.Decode(value) + } + return value + }) + *m = StringLatestMap{out} } // MarshalJSON shouldn't be used, use CodecEncodeSelf instead. @@ -123,29 +162,26 @@ func (*StringLatestMap) UnmarshalJSON(b []byte) error { panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead") } -type wireNodeControlDataLatestEntry struct { +type NodeControlDataLatestEntry struct { Timestamp time.Time `json:"timestamp"` Value NodeControlData `json:"value"` } -type nodeControlDataLatestEntryDecoder struct{} - -func (d *nodeControlDataLatestEntryDecoder) Decode(decoder *codec.Decoder, entry *LatestEntry) { - wire := wireNodeControlDataLatestEntry{} - decoder.Decode(&wire) - entry.Timestamp = wire.Timestamp - entry.Value = wire.Value +// String returns the StringLatestEntry's string representation. +func (e *NodeControlDataLatestEntry) String() string { + return fmt.Sprintf("%v (%s)", e.Value, e.Timestamp.String()) } -// NodeControlDataLatestEntryDecoder is an implementation of LatestEntryDecoder -// that decodes the LatestEntry instances having a NodeControlData value. -var NodeControlDataLatestEntryDecoder LatestEntryDecoder = &nodeControlDataLatestEntryDecoder{} +// Equal returns true if the supplied StringLatestEntry is equal to this one. +func (e *NodeControlDataLatestEntry) Equal(e2 *NodeControlDataLatestEntry) bool { + return e.Timestamp.Equal(e2.Timestamp) && e.Value == e2.Value +} // NodeControlDataLatestMap holds latest NodeControlData instances. -type NodeControlDataLatestMap LatestMap +type NodeControlDataLatestMap struct{ ps.Map } // EmptyNodeControlDataLatestMap is an empty NodeControlDataLatestMap. Start with this. -var EmptyNodeControlDataLatestMap = (NodeControlDataLatestMap)(MakeLatestMapWithDecoder(NodeControlDataLatestEntryDecoder)) +var EmptyNodeControlDataLatestMap = NodeControlDataLatestMap{ps.NewMap()} // MakeNodeControlDataLatestMap makes an empty NodeControlDataLatestMap. func MakeNodeControlDataLatestMap() NodeControlDataLatestMap { @@ -154,77 +190,117 @@ func MakeNodeControlDataLatestMap() NodeControlDataLatestMap { // Copy is a noop, as NodeControlDataLatestMaps are immutable. func (m NodeControlDataLatestMap) Copy() NodeControlDataLatestMap { - return (NodeControlDataLatestMap)((LatestMap)(m).Copy()) + return m } // Size returns the number of elements. func (m NodeControlDataLatestMap) Size() int { - return (LatestMap)(m).Size() + if m.Map == nil { + return 0 + } + return m.Map.Size() } // Merge produces a fresh NodeControlDataLatestMap containing the keys from both inputs. // When both inputs contain the same key, the newer value is used. func (m NodeControlDataLatestMap) Merge(other NodeControlDataLatestMap) NodeControlDataLatestMap { - return (NodeControlDataLatestMap)((LatestMap)(m).Merge((LatestMap)(other))) + output := mergeMaps(m.Map, other.Map, func(a, b interface{}) bool { + return a.(*NodeControlDataLatestEntry).Timestamp.Before(b.(*NodeControlDataLatestEntry).Timestamp) + }) + return NodeControlDataLatestMap{output} } // Lookup the value for the given key. func (m NodeControlDataLatestMap) Lookup(key string) (NodeControlData, bool) { - v, ok := (LatestMap)(m).Lookup(key) + v, _, ok := m.LookupEntry(key) if !ok { var zero NodeControlData return zero, false } - return v.(NodeControlData), true + return v, true } // LookupEntry returns the raw entry for the given key. func (m NodeControlDataLatestMap) LookupEntry(key string) (NodeControlData, time.Time, bool) { - v, timestamp, ok := (LatestMap)(m).LookupEntry(key) + if m.Map == nil { + var zero NodeControlData + return zero, time.Time{}, false + } + value, ok := m.Map.Lookup(key) if !ok { var zero NodeControlData - return zero, timestamp, false + return zero, time.Time{}, false } - return v.(NodeControlData), timestamp, true + e := value.(*NodeControlDataLatestEntry) + return e.Value, e.Timestamp, true } // Set the value for the given key. func (m NodeControlDataLatestMap) Set(key string, timestamp time.Time, value NodeControlData) NodeControlDataLatestMap { - return (NodeControlDataLatestMap)((LatestMap)(m).Set(key, timestamp, value)) + if m.Map == nil { + m.Map = ps.NewMap() + } + return NodeControlDataLatestMap{m.Map.Set(key, &NodeControlDataLatestEntry{timestamp, value})} } // Delete the value for the given key. func (m NodeControlDataLatestMap) Delete(key string) NodeControlDataLatestMap { - return (NodeControlDataLatestMap)((LatestMap)(m).Delete(key)) + if m.Map == nil { + return m + } + return NodeControlDataLatestMap{m.Map.Delete(key)} } // ForEach executes fn on each key value pair in the map. func (m NodeControlDataLatestMap) ForEach(fn func(k string, timestamp time.Time, v NodeControlData)) { - (LatestMap)(m).ForEach(func(key string, ts time.Time, value interface{}) { - fn(key, ts, value.(NodeControlData)) - }) + if m.Map != nil { + m.Map.ForEach(func(key string, value interface{}) { + fn(key, value.(*NodeControlDataLatestEntry).Timestamp, value.(*NodeControlDataLatestEntry).Value) + }) + } } // String returns the NodeControlDataLatestMap's string representation. func (m NodeControlDataLatestMap) String() string { - return (LatestMap)(m).String() + return mapToString(m.Map) } // DeepEqual tests equality with other NodeControlDataLatestMap. func (m NodeControlDataLatestMap) DeepEqual(n NodeControlDataLatestMap) bool { - return (LatestMap)(m).DeepEqual((LatestMap)(n)) + return mapEqual(m.Map, n.Map, func(val, otherValue interface{}) bool { + return val.(*NodeControlDataLatestEntry).Equal(otherValue.(*NodeControlDataLatestEntry)) + }) +} + +func (m NodeControlDataLatestMap) toIntermediate() map[string]NodeControlDataLatestEntry { + intermediate := make(map[string]NodeControlDataLatestEntry, m.Size()) + if m.Map != nil { + m.Map.ForEach(func(key string, val interface{}) { + intermediate[key] = *val.(*NodeControlDataLatestEntry) + }) + } + return intermediate } // CodecEncodeSelf implements codec.Selfer. func (m *NodeControlDataLatestMap) CodecEncodeSelf(encoder *codec.Encoder) { - (*LatestMap)(m).CodecEncodeSelf(encoder) + if m.Map != nil { + encoder.Encode(m.toIntermediate()) + } else { + encoder.Encode(nil) + } } // CodecDecodeSelf implements codec.Selfer. func (m *NodeControlDataLatestMap) CodecDecodeSelf(decoder *codec.Decoder) { - bm := (*LatestMap)(m) - bm.decoder = NodeControlDataLatestEntryDecoder - bm.CodecDecodeSelf(decoder) + out := mapRead(decoder, func(isNil bool) interface{} { + value := &NodeControlDataLatestEntry{} + if !isNil { + decoder.Decode(value) + } + return value + }) + *m = NodeControlDataLatestMap{out} } // MarshalJSON shouldn't be used, use CodecEncodeSelf instead. diff --git a/report/latest_map_internal_test.go b/report/latest_map_internal_test.go index 16c1cfddcb..653ba3bf7d 100644 --- a/report/latest_map_internal_test.go +++ b/report/latest_map_internal_test.go @@ -32,6 +32,10 @@ func TestLatestMapAdd(t *testing.T) { func TestLatestMapLookupEntry(t *testing.T) { now := time.Now() + type LatestEntry struct { + Timestamp time.Time + Value interface{} + } entry := LatestEntry{Timestamp: now, Value: "Bar"} have := EmptyStringLatestMap.Set("foo", entry.Timestamp, entry.Value.(string)) if got, timestamp, ok := have.LookupEntry("foo"); !ok || got != entry.Value || !timestamp.Equal(entry.Timestamp) { @@ -216,20 +220,3 @@ func TestLatestMapMergeEqualDecoderTypes(t *testing.T) { m2 := MakeStringLatestMap().Set("b", time.Now(), "foo") m1.Merge(m2) } - -type TestLatestEntryDecoder struct{} - -func (d *TestLatestEntryDecoder) Decode(decoder *codec.Decoder, entry *LatestEntry) { - decoder.Decode(entry) -} - -func TestLatestMapMergeDifferentDecoderTypes(t *testing.T) { - defer func() { - if r := recover(); r == nil { - t.Error("Merging two maps with different decoders should panic") - } - }() - m1 := MakeStringLatestMap().Set("a", time.Now(), "bar") - m2 := ((StringLatestMap)(MakeLatestMapWithDecoder(&TestLatestEntryDecoder{}))).Set("b", time.Now(), "foo") - m1.Merge(m2) -}