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