refactor: 迁移 concurrent.BalanceMap 至 mappings.SyncMap,优化泛型函数签名
This commit is contained in:
parent
e30c5788c1
commit
e3475c6c07
|
@ -0,0 +1,221 @@
|
||||||
|
package mappings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/kercylan98/minotaur/utils/collection"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewSyncMap 创建一个 SyncMap
|
||||||
|
func NewSyncMap[K comparable, V any](source ...map[K]V) *SyncMap[K, V] {
|
||||||
|
m := &SyncMap[K, V]{
|
||||||
|
data: make(map[K]V),
|
||||||
|
}
|
||||||
|
if len(source) > 0 {
|
||||||
|
m.data = collection.MergeMaps(source...)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncMap 是基于 sync.RWMutex 实现的线程安全的 map
|
||||||
|
// - 适用于要考虑并发读写但是并发读写的频率不高的情况
|
||||||
|
type SyncMap[K comparable, V any] struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
data map[K]V
|
||||||
|
atom bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set 设置一个值
|
||||||
|
func (sm *SyncMap[K, V]) Set(key K, value V) {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.Lock()
|
||||||
|
defer sm.lock.Unlock()
|
||||||
|
}
|
||||||
|
sm.data[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 获取一个值
|
||||||
|
func (sm *SyncMap[K, V]) Get(key K) V {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.RLock()
|
||||||
|
defer sm.lock.RUnlock()
|
||||||
|
}
|
||||||
|
return sm.data[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atom 原子操作
|
||||||
|
func (sm *SyncMap[K, V]) Atom(handle func(m map[K]V)) {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.Lock()
|
||||||
|
defer sm.lock.Unlock()
|
||||||
|
}
|
||||||
|
handle(sm.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exist 判断是否存在
|
||||||
|
func (sm *SyncMap[K, V]) Exist(key K) bool {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.RLock()
|
||||||
|
defer sm.lock.RUnlock()
|
||||||
|
}
|
||||||
|
_, exist := sm.data[key]
|
||||||
|
return exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExist 获取一个值并判断是否存在
|
||||||
|
func (sm *SyncMap[K, V]) GetExist(key K) (V, bool) {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.RLock()
|
||||||
|
defer sm.lock.RUnlock()
|
||||||
|
}
|
||||||
|
value, exist := sm.data[key]
|
||||||
|
return value, exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete 删除一个值
|
||||||
|
func (sm *SyncMap[K, V]) Delete(key K) {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.Lock()
|
||||||
|
defer sm.lock.Unlock()
|
||||||
|
}
|
||||||
|
delete(sm.data, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteGet 删除一个值并返回
|
||||||
|
func (sm *SyncMap[K, V]) DeleteGet(key K) V {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.Lock()
|
||||||
|
defer sm.lock.Unlock()
|
||||||
|
}
|
||||||
|
v := sm.data[key]
|
||||||
|
delete(sm.data, key)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteGetExist 删除一个值并返回是否存在
|
||||||
|
func (sm *SyncMap[K, V]) DeleteGetExist(key K) (V, bool) {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.Lock()
|
||||||
|
defer sm.lock.Unlock()
|
||||||
|
}
|
||||||
|
v, exist := sm.data[key]
|
||||||
|
delete(sm.data, key)
|
||||||
|
return v, exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteExist 删除一个值并返回是否存在
|
||||||
|
func (sm *SyncMap[K, V]) DeleteExist(key K) bool {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.Lock()
|
||||||
|
defer sm.lock.Unlock()
|
||||||
|
}
|
||||||
|
if _, exist := sm.data[key]; !exist {
|
||||||
|
sm.lock.Unlock()
|
||||||
|
return exist
|
||||||
|
}
|
||||||
|
delete(sm.data, key)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear 清空
|
||||||
|
func (sm *SyncMap[K, V]) Clear() {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.Lock()
|
||||||
|
defer sm.lock.Unlock()
|
||||||
|
}
|
||||||
|
for k := range sm.data {
|
||||||
|
delete(sm.data, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearHandle 清空并处理
|
||||||
|
func (sm *SyncMap[K, V]) ClearHandle(handle func(key K, value V)) {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.Lock()
|
||||||
|
defer sm.lock.Unlock()
|
||||||
|
}
|
||||||
|
for k, v := range sm.data {
|
||||||
|
handle(k, v)
|
||||||
|
delete(sm.data, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range 遍历所有值,如果 handle 返回 true 则停止遍历
|
||||||
|
func (sm *SyncMap[K, V]) Range(handle func(key K, value V) bool) {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.Lock()
|
||||||
|
defer sm.lock.Unlock()
|
||||||
|
}
|
||||||
|
for k, v := range sm.data {
|
||||||
|
key, value := k, v
|
||||||
|
if handle(key, value) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys 获取所有的键
|
||||||
|
func (sm *SyncMap[K, V]) Keys() []K {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.RLock()
|
||||||
|
defer sm.lock.RUnlock()
|
||||||
|
}
|
||||||
|
var s = make([]K, 0, len(sm.data))
|
||||||
|
for k := range sm.data {
|
||||||
|
s = append(s, k)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slice 获取所有的值
|
||||||
|
func (sm *SyncMap[K, V]) Slice() []V {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.RLock()
|
||||||
|
defer sm.lock.RUnlock()
|
||||||
|
}
|
||||||
|
var s = make([]V, 0, len(sm.data))
|
||||||
|
for _, v := range sm.data {
|
||||||
|
s = append(s, v)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map 转换为普通 map
|
||||||
|
func (sm *SyncMap[K, V]) Map() map[K]V {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.RLock()
|
||||||
|
defer sm.lock.RUnlock()
|
||||||
|
}
|
||||||
|
var m = make(map[K]V)
|
||||||
|
for k, v := range sm.data {
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size 获取数量
|
||||||
|
func (sm *SyncMap[K, V]) Size() int {
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.RLock()
|
||||||
|
defer sm.lock.RUnlock()
|
||||||
|
}
|
||||||
|
return len(sm.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *SyncMap[K, V]) MarshalJSON() ([]byte, error) {
|
||||||
|
m := sm.Map()
|
||||||
|
return json.Marshal(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *SyncMap[K, V]) UnmarshalJSON(bytes []byte) error {
|
||||||
|
var m = make(map[K]V)
|
||||||
|
if !sm.atom {
|
||||||
|
sm.lock.Lock()
|
||||||
|
sm.lock.Unlock()
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(bytes, &m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sm.data = m
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,220 +0,0 @@
|
||||||
package concurrent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewBalanceMap 创建一个并发安全且性能在普通读写和并发读写的情况下较为平衡的字典数据结构
|
|
||||||
func NewBalanceMap[Key comparable, value any](options ...BalanceMapOption[Key, value]) *BalanceMap[Key, value] {
|
|
||||||
m := &BalanceMap[Key, value]{
|
|
||||||
data: make(map[Key]value),
|
|
||||||
}
|
|
||||||
for _, option := range options {
|
|
||||||
option(m)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// BalanceMap 并发安全且性能在普通读写和并发读写的情况下较为平衡的字典数据结构
|
|
||||||
// - 适用于要考虑并发读写但是并发读写的频率不高的情况
|
|
||||||
type BalanceMap[Key comparable, Value any] struct {
|
|
||||||
lock sync.RWMutex
|
|
||||||
data map[Key]Value
|
|
||||||
atom bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set 设置一个值
|
|
||||||
func (slf *BalanceMap[Key, Value]) Set(key Key, value Value) {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.Lock()
|
|
||||||
defer slf.lock.Unlock()
|
|
||||||
}
|
|
||||||
slf.data[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get 获取一个值
|
|
||||||
func (slf *BalanceMap[Key, Value]) Get(key Key) Value {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.RLock()
|
|
||||||
defer slf.lock.RUnlock()
|
|
||||||
}
|
|
||||||
return slf.data[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Atom 原子操作
|
|
||||||
func (slf *BalanceMap[Key, Value]) Atom(handle func(m map[Key]Value)) {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.Lock()
|
|
||||||
defer slf.lock.Unlock()
|
|
||||||
}
|
|
||||||
handle(slf.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exist 判断是否存在
|
|
||||||
func (slf *BalanceMap[Key, Value]) Exist(key Key) bool {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.RLock()
|
|
||||||
defer slf.lock.RUnlock()
|
|
||||||
}
|
|
||||||
_, exist := slf.data[key]
|
|
||||||
return exist
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetExist 获取一个值并判断是否存在
|
|
||||||
func (slf *BalanceMap[Key, Value]) GetExist(key Key) (Value, bool) {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.RLock()
|
|
||||||
defer slf.lock.RUnlock()
|
|
||||||
}
|
|
||||||
value, exist := slf.data[key]
|
|
||||||
return value, exist
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete 删除一个值
|
|
||||||
func (slf *BalanceMap[Key, Value]) Delete(key Key) {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.Lock()
|
|
||||||
defer slf.lock.Unlock()
|
|
||||||
}
|
|
||||||
delete(slf.data, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteGet 删除一个值并返回
|
|
||||||
func (slf *BalanceMap[Key, Value]) DeleteGet(key Key) Value {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.Lock()
|
|
||||||
defer slf.lock.Unlock()
|
|
||||||
}
|
|
||||||
v := slf.data[key]
|
|
||||||
delete(slf.data, key)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteGetExist 删除一个值并返回是否存在
|
|
||||||
func (slf *BalanceMap[Key, Value]) DeleteGetExist(key Key) (Value, bool) {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.Lock()
|
|
||||||
defer slf.lock.Unlock()
|
|
||||||
}
|
|
||||||
v, exist := slf.data[key]
|
|
||||||
delete(slf.data, key)
|
|
||||||
return v, exist
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteExist 删除一个值并返回是否存在
|
|
||||||
func (slf *BalanceMap[Key, Value]) DeleteExist(key Key) bool {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.Lock()
|
|
||||||
defer slf.lock.Unlock()
|
|
||||||
}
|
|
||||||
if _, exist := slf.data[key]; !exist {
|
|
||||||
slf.lock.Unlock()
|
|
||||||
return exist
|
|
||||||
}
|
|
||||||
delete(slf.data, key)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear 清空
|
|
||||||
func (slf *BalanceMap[Key, Value]) Clear() {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.Lock()
|
|
||||||
defer slf.lock.Unlock()
|
|
||||||
}
|
|
||||||
for k := range slf.data {
|
|
||||||
delete(slf.data, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearHandle 清空并处理
|
|
||||||
func (slf *BalanceMap[Key, Value]) ClearHandle(handle func(key Key, value Value)) {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.Lock()
|
|
||||||
defer slf.lock.Unlock()
|
|
||||||
}
|
|
||||||
for k, v := range slf.data {
|
|
||||||
handle(k, v)
|
|
||||||
delete(slf.data, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range 遍历所有值,如果 handle 返回 true 则停止遍历
|
|
||||||
func (slf *BalanceMap[Key, Value]) Range(handle func(key Key, value Value) bool) {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.Lock()
|
|
||||||
defer slf.lock.Unlock()
|
|
||||||
}
|
|
||||||
for k, v := range slf.data {
|
|
||||||
key, value := k, v
|
|
||||||
if handle(key, value) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keys 获取所有的键
|
|
||||||
func (slf *BalanceMap[Key, Value]) Keys() []Key {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.RLock()
|
|
||||||
defer slf.lock.RUnlock()
|
|
||||||
}
|
|
||||||
var s = make([]Key, 0, len(slf.data))
|
|
||||||
for k := range slf.data {
|
|
||||||
s = append(s, k)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slice 获取所有的值
|
|
||||||
func (slf *BalanceMap[Key, Value]) Slice() []Value {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.RLock()
|
|
||||||
defer slf.lock.RUnlock()
|
|
||||||
}
|
|
||||||
var s = make([]Value, 0, len(slf.data))
|
|
||||||
for _, v := range slf.data {
|
|
||||||
s = append(s, v)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map 转换为普通 map
|
|
||||||
func (slf *BalanceMap[Key, Value]) Map() map[Key]Value {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.RLock()
|
|
||||||
defer slf.lock.RUnlock()
|
|
||||||
}
|
|
||||||
var m = make(map[Key]Value)
|
|
||||||
for k, v := range slf.data {
|
|
||||||
m[k] = v
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size 获取数量
|
|
||||||
func (slf *BalanceMap[Key, Value]) Size() int {
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.RLock()
|
|
||||||
defer slf.lock.RUnlock()
|
|
||||||
}
|
|
||||||
return len(slf.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (slf *BalanceMap[Key, Value]) MarshalJSON() ([]byte, error) {
|
|
||||||
m := slf.Map()
|
|
||||||
return json.Marshal(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (slf *BalanceMap[Key, Value]) UnmarshalJSON(bytes []byte) error {
|
|
||||||
var m = make(map[Key]Value)
|
|
||||||
if !slf.atom {
|
|
||||||
slf.lock.Lock()
|
|
||||||
slf.lock.Unlock()
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(bytes, &m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
slf.data = m
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package concurrent
|
|
||||||
|
|
||||||
// BalanceMapOption BalanceMap 的选项
|
|
||||||
type BalanceMapOption[Key comparable, Value any] func(m *BalanceMap[Key, Value])
|
|
||||||
|
|
||||||
// WithBalanceMapSource 通过传入的 map 初始化
|
|
||||||
func WithBalanceMapSource[Key comparable, Value any](source map[Key]Value) BalanceMapOption[Key, Value] {
|
|
||||||
return func(m *BalanceMap[Key, Value]) {
|
|
||||||
m.data = source
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ package leaderboard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/kercylan98/minotaur/utils/concurrent"
|
"github.com/kercylan98/minotaur/utils/collection/mappings"
|
||||||
"github.com/kercylan98/minotaur/utils/generic"
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ func NewBinarySearch[CompetitorID comparable, Score generic.Ordered](options ...
|
||||||
leaderboard := &BinarySearch[CompetitorID, Score]{
|
leaderboard := &BinarySearch[CompetitorID, Score]{
|
||||||
binarySearchEvent: new(binarySearchEvent[CompetitorID, Score]),
|
binarySearchEvent: new(binarySearchEvent[CompetitorID, Score]),
|
||||||
rankCount: 100,
|
rankCount: 100,
|
||||||
competitors: concurrent.NewBalanceMap[CompetitorID, Score](),
|
competitors: mappings.NewSyncMap[CompetitorID, Score](),
|
||||||
}
|
}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(leaderboard)
|
option(leaderboard)
|
||||||
|
@ -23,7 +23,7 @@ type BinarySearch[CompetitorID comparable, Score generic.Ordered] struct {
|
||||||
*binarySearchEvent[CompetitorID, Score]
|
*binarySearchEvent[CompetitorID, Score]
|
||||||
asc bool
|
asc bool
|
||||||
rankCount int
|
rankCount int
|
||||||
competitors *concurrent.BalanceMap[CompetitorID, Score]
|
competitors *mappings.SyncMap[CompetitorID, Score]
|
||||||
scores []*scoreItem[CompetitorID, Score] // CompetitorID, Score
|
scores []*scoreItem[CompetitorID, Score] // CompetitorID, Score
|
||||||
|
|
||||||
rankChangeEventHandles []BinarySearchRankChangeEventHandle[CompetitorID, Score]
|
rankChangeEventHandles []BinarySearchRankChangeEventHandle[CompetitorID, Score]
|
||||||
|
@ -264,11 +264,11 @@ func (slf *BinarySearch[CompetitorID, Score]) competitor(competitorId Competitor
|
||||||
|
|
||||||
func (slf *BinarySearch[CompetitorID, Score]) UnmarshalJSON(bytes []byte) error {
|
func (slf *BinarySearch[CompetitorID, Score]) UnmarshalJSON(bytes []byte) error {
|
||||||
var t struct {
|
var t struct {
|
||||||
Competitors *concurrent.BalanceMap[CompetitorID, Score] `json:"competitors,omitempty"`
|
Competitors *mappings.SyncMap[CompetitorID, Score] `json:"competitors,omitempty"`
|
||||||
Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"`
|
Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"`
|
||||||
Asc bool `json:"asc,omitempty"`
|
Asc bool `json:"asc,omitempty"`
|
||||||
}
|
}
|
||||||
t.Competitors = concurrent.NewBalanceMap[CompetitorID, Score]()
|
t.Competitors = mappings.NewSyncMap[CompetitorID, Score]()
|
||||||
if err := json.Unmarshal(bytes, &t); err != nil {
|
if err := json.Unmarshal(bytes, &t); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -280,9 +280,9 @@ func (slf *BinarySearch[CompetitorID, Score]) UnmarshalJSON(bytes []byte) error
|
||||||
|
|
||||||
func (slf *BinarySearch[CompetitorID, Score]) MarshalJSON() ([]byte, error) {
|
func (slf *BinarySearch[CompetitorID, Score]) MarshalJSON() ([]byte, error) {
|
||||||
var t struct {
|
var t struct {
|
||||||
Competitors *concurrent.BalanceMap[CompetitorID, Score] `json:"competitors,omitempty"`
|
Competitors *mappings.SyncMap[CompetitorID, Score] `json:"competitors,omitempty"`
|
||||||
Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"`
|
Scores []*scoreItem[CompetitorID, Score] `json:"scores,omitempty"`
|
||||||
Asc bool `json:"asc,omitempty"`
|
Asc bool `json:"asc,omitempty"`
|
||||||
}
|
}
|
||||||
t.Competitors = slf.competitors
|
t.Competitors = slf.competitors
|
||||||
t.Scores = slf.scores
|
t.Scores = slf.scores
|
||||||
|
|
Loading…
Reference in New Issue