From db3cb706969cc7ef4e791d1e718c7590537dbbe2 Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Fri, 5 Jul 2019 12:38:02 -0700 Subject: [PATCH] Use sync.Map for maintaining map of all coins --- cointop/cointop.go | 35 ++++++++++++++++++++++----------- cointop/list.go | 47 +++++++++++++++++++++++++++++++++----------- cointop/portfolio.go | 4 +++- cointop/table.go | 14 ++++++++----- 4 files changed, 70 insertions(+), 30 deletions(-) diff --git a/cointop/cointop.go b/cointop/cointop.go index 204d607..a4158c4 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -41,7 +41,7 @@ type Views struct { // State is the state preferences of cointop type State struct { allCoins []*Coin - allCoinsSlugMap map[string]*Coin + allCoinsSlugMap sync.Map coins []*Coin chartPoints [][]termui.Cell currencyConversion string @@ -165,7 +165,6 @@ func NewCointop(config *Config) (*Cointop, error) { chartRangesMap: chartRangesMap(), limiter: time.Tick(2 * time.Second), State: &State{ - allCoinsSlugMap: make(map[string]*Coin), allCoins: []*Coin{}, currencyConversion: "USD", // DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility. @@ -272,12 +271,21 @@ func NewCointop(config *Config) (*Cointop, error) { return nil, ErrInvalidAPIChoice } + allCoinsSlugMap := make(map[string]*Coin) coinscachekey := ct.cacheKey("allCoinsSlugMap") - filecache.Get(coinscachekey, &ct.State.allCoinsSlugMap) + filecache.Get(coinscachekey, &allCoinsSlugMap) - for k := range ct.State.allCoinsSlugMap { - ct.State.allCoins = append(ct.State.allCoins, ct.State.allCoinsSlugMap[k]) + for k, v := range allCoinsSlugMap { + ct.State.allCoinsSlugMap.Store(k, v) } + + ct.State.allCoinsSlugMap.Range(func(key, value interface{}) bool { + if coin, ok := value.(*Coin); ok { + ct.State.allCoins = append(ct.State.allCoins, coin) + } + return true + }) + if len(ct.State.allCoins) > 1 { max := len(ct.State.allCoins) if max > 100 { @@ -289,15 +297,18 @@ func NewCointop(config *Config) (*Cointop, error) { // DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility. // Here we're doing a lookup based on symbol and setting the favorite to the coin name instead of coin symbol. - for i := range ct.State.allCoinsSlugMap { - coin := ct.State.allCoinsSlugMap[i] - for k := range ct.State.favoritesBySymbol { - if coin.Symbol == k { - ct.State.favorites[coin.Name] = true - delete(ct.State.favoritesBySymbol, k) + ct.State.allCoinsSlugMap.Range(func(key, value interface{}) bool { + if coin, ok := value.(*Coin); ok { + for k := range ct.State.favoritesBySymbol { + if coin.Symbol == k { + ct.State.favorites[coin.Name] = true + delete(ct.State.favoritesBySymbol, k) + } } } - } + + return true + }) var globaldata []float64 chartcachekey := ct.cacheKey(fmt.Sprintf("%s_%s", "globaldata", strings.Replace(ct.State.selectedChartRange, " ", "", -1))) diff --git a/cointop/list.go b/cointop/list.go index 2649c38..f494a97 100644 --- a/cointop/list.go +++ b/cointop/list.go @@ -58,14 +58,20 @@ func (ct *Cointop) processCoins(coins []types.Coin) { updatecoinsmux.Lock() defer updatecoinsmux.Unlock() + allCoinsSlugMap := make(map[string]*Coin) + ct.State.allCoinsSlugMap.Range(func(key, value interface{}) bool { + allCoinsSlugMap[key.(string)] = value.(*Coin) + return true + }) + cachekey := ct.cacheKey("allCoinsSlugMap") - ct.cache.Set(cachekey, ct.State.allCoinsSlugMap, 10*time.Second) - filecache.Set(cachekey, ct.State.allCoinsSlugMap, 24*time.Hour) + ct.cache.Set(cachekey, allCoinsSlugMap, 10*time.Second) + filecache.Set(cachekey, allCoinsSlugMap, 24*time.Hour) for _, v := range coins { k := v.Name - last := ct.State.allCoinsSlugMap[k] - ct.State.allCoinsSlugMap[k] = &Coin{ + ilast, _ := ct.State.allCoinsSlugMap.Load(k) + ct.State.allCoinsSlugMap.Store(k, &Coin{ ID: v.ID, Name: v.Name, Symbol: v.Symbol, @@ -79,23 +85,38 @@ func (ct *Cointop) processCoins(coins []types.Coin) { PercentChange24H: v.PercentChange24H, PercentChange7D: v.PercentChange7D, LastUpdated: v.LastUpdated, - } - if last != nil { - ct.State.allCoinsSlugMap[k].Favorite = last.Favorite + }) + if ilast != nil { + last, _ := ilast.(*Coin) + if last != nil { + ivalue, _ := ct.State.allCoinsSlugMap.Load(k) + l, _ := ivalue.(*Coin) + l.Favorite = last.Favorite + ct.State.allCoinsSlugMap.Store(k, l) + } } } - if len(ct.State.allCoins) < len(ct.State.allCoinsSlugMap) { + + size := 0 + // NOTE: there's no Len method on sync.Map so need to manually count + ct.State.allCoinsSlugMap.Range(func(key, value interface{}) bool { + size++ + return true + }) + + if len(ct.State.allCoins) < size { list := []*Coin{} for _, v := range coins { k := v.Name - coin := ct.State.allCoinsSlugMap[k] + icoin, _ := ct.State.allCoinsSlugMap.Load(k) + coin, _ := icoin.(*Coin) list = append(list, coin) } ct.State.allCoins = append(ct.State.allCoins, list...) } else { // update list in place without changing order - for i := range ct.State.allCoinsSlugMap { - cm := ct.State.allCoinsSlugMap[i] + ct.State.allCoinsSlugMap.Range(func(key, value interface{}) bool { + cm, _ := value.(*Coin) for k := range ct.State.allCoins { c := ct.State.allCoins[k] if c.ID == cm.ID { @@ -116,7 +137,9 @@ func (ct *Cointop) processCoins(coins []types.Coin) { c.Favorite = cm.Favorite } } - } + + return true + }) } time.AfterFunc(10*time.Millisecond, func() { diff --git a/cointop/portfolio.go b/cointop/portfolio.go index 706df13..bacb67c 100644 --- a/cointop/portfolio.go +++ b/cointop/portfolio.go @@ -168,7 +168,9 @@ func (ct *Cointop) PortfolioEntry(c *Coin) (*PortfolioEntry, bool) { } func (ct *Cointop) setPortfolioEntry(coin string, holdings float64) { - c, _ := ct.State.allCoinsSlugMap[strings.ToLower(coin)] + + ic, _ := ct.State.allCoinsSlugMap.Load(strings.ToLower(coin)) + c, _ := ic.(*Coin) p, isNew := ct.PortfolioEntry(c) if isNew { key := strings.ToLower(coin) diff --git a/cointop/table.go b/cointop/table.go index 2a748a9..19ccceb 100644 --- a/cointop/table.go +++ b/cointop/table.go @@ -203,12 +203,16 @@ func (ct *Cointop) RefreshTable() error { func (ct *Cointop) updateTable() error { sliced := []*Coin{} - for i := range ct.State.allCoinsSlugMap { - v := ct.State.allCoinsSlugMap[i] - if ct.State.favorites[v.Name] { - v.Favorite = true + ct.State.allCoinsSlugMap.Range(func(key, value interface{}) bool { + k := key.(string) + if v, ok := value.(*Coin); ok { + if ct.State.favorites[v.Name] { + v.Favorite = true + ct.State.allCoinsSlugMap.Store(k, v) + } } - } + return true + }) if ct.State.filterByFavorites { for i := range ct.State.allCoins {