// package down_time -- счётчик обратного времени в мсек package down_time import ( "context" "fmt" // "log" "sync" "time" . "wartank/app/lev0/alias" . "wartank/app/lev0/types" "wartank/app/lev1/product/parser_time" . "gitp78su.ipnodns.ru/svi/kern" . "gitp78su.ipnodns.ru/svi/kern/krn/ktypes" ) const ( спатьИнтервал = time.Millisecond * 1000 // Малый интервал сна в 100 мсек ) // ВремОбрат -- счётчик обратного времени для игровой зоны (анга, база, битва и т.п.) type ВремОбрат struct { сцена ИАренаКонтекст // Сцена, которой принадлежит отсчёт времени остатПарсер ИПарсерВремя // Парсер значения (мсек) текущ ISafeInt // Фактическое значение счётчика в мсек лимит ISafeInt // Целевое время срабатывания в мсек еслиРаботает ISafeBool // Признак работы канВызов chan int // Канал для отправки сигналов (для верхнего уровня) кнт context.Context // Контекст для счётчика времени фнОтмена func() // Функция отмены контекста для счётчика времени блок sync.RWMutex } // НовВремОбрат -- возвращает новый *CountTime func НовВремОбрат(сцена ИАренаКонтекст, время АМилСек) *ВремОбрат { if сцена == nil { panic("НовВремОбрат(): ИСцена == nil") } кнт, фнОтмена := context.WithCancel(сцена.Контекст()) сам := &ВремОбрат{ сцена: сцена, текущ: NewSafeInt(), канВызов: make(chan int, 2), еслиРаботает: NewSafeBool(), остатПарсер: parser_time.НовПарсерВремя(), лимит: NewSafeInt(), кнт: кнт, фнОтмена: фнОтмена, } мСек := АМилСек(time.Now().UTC().UnixMilli()) + время сам.лимит.Set(int(мСек)) сам.еслиРаботает.Set() go сам.пуск() go сам.закрыть() _ = ИВремяОстат(сам) return сам } // ПолучМилСек -- возвращает оставшееся хранимое время остатка func (сам *ВремОбрат) ПолучМилСек() АМилСек { return сам.остатПарсер.ПолучМилСек() } // Запускает тикер для интервалов сна (через каждые 1000 мСек) func (сам *ВремОбрат) пуск() { defer close(сам.канВызов) фнЖдать := func() { time.Sleep(спатьИнтервал) timeNow := time.Now().UTC().UnixMilli() цТекущ := сам.текущ.Get() цТекущ -= 1000 if цТекущ < 0 { цТекущ = 0 } сам.текущ.Set(цТекущ) if сам.лимит.Get() > int(timeNow) || цТекущ > 0 { return } сам.канВызов <- 1 сам.лимит.Set(int(timeNow) + int(сам.остатПарсер.ПолучМилСек())) } for { select { case <-сам.кнт.Done(): // Отмена контекста тикера (а может и сцены, может и бота) return default: фнЖдать() } } } // Сброс -- сбрасывает оставшееся время в ноль func (сам *ВремОбрат) Сброс() { сам.остатПарсер.Сброс() сам.текущ.Reset() сам.лимит.Reset() } // Стоп -- останавливает работу счётчика func (сам *ВремОбрат) Стоп() { сам.фнОтмена() } // Уст -- устанавливает число оставшихся сек func (сам *ВремОбрат) Уст(время АВремя) error { сам.блок.Lock() defer сам.блок.Unlock() if ош := сам.остатПарсер.Уст(время); ош != nil { return fmt.Errorf("ВремОбрат(): ошибка при установке времени, ош=\n\t%w", ош) } _val := сам.остатПарсер.ПолучМилСек() сам.текущ.Set(int(_val)) val := int(time.Now().UTC().UnixMilli()) + сам.текущ.Get() сам.лимит.Set(val) return nil } // String -- возвращает строковое представление оставшихся сек func (сам *ВремОбрат) String() string { сам.блок.RLock() defer сам.блок.RUnlock() цОстат := сам.текущ.Get() остат := time.Millisecond * time.Duration(цОстат) стрВрем := остат.String() return стрВрем } // КаналСиг -- возвращает канал чтения тиков func (сам *ВремОбрат) КаналСиг() <-chan int { return сам.канВызов } func (сам *ВремОбрат) закрыть() { <-сам.кнт.Done() сам.блок.Lock() defer сам.блок.Unlock() if !сам.еслиРаботает.Get() { return } сам.еслиРаботает.Set() }