// package down_time -- счётчик обратного времени в мсек package down_time import ( "context" "fmt" // "log" "sync" "time" "wartank/pkg/components/parsetime" "wartank/pkg/components/safebool" "wartank/pkg/components/safeint" "wartank/pkg/types" ) const ( sleepInterval = time.Millisecond * 100 // Малый интервал сна в 100 мсек ) // DownTime -- счётчик обратного времени для игровой зоны (анга, база, битва и т.п.) type DownTime struct { zone types.IZone val *safeint.SafeInt // Фактическое значение счётчика parser *parsetime.ParseTime // Парсер значения chTick chan int // Канал секундных интервалов сна (для отображения) chCall chan int // Канал для отправки сигналов (для верхнего уровня) isWork *safebool.SafeBool // Признак работы timeTarget *safeint.SafeInt // Целевое время срабатывания ctx context.Context // Контекст для счётчика времени fnCancel func() // Функция отмены контекста для счётчика времени block sync.RWMutex } // NewCountTime -- возвращает новый *CountTime func NewCountTime(zone types.IZone, val int) *DownTime { if zone == nil { panic("NewCountTime(): IZone == nil") } ctx, fnCancel := context.WithCancel(zone.Ctx()) sf := &DownTime{ zone: zone, val: safeint.NewSafeInt(), chTick: make(chan int, 3), chCall: make(chan int, 2), isWork: safebool.NewSafeBool(), parser: parsetime.NewParseTime(), timeTarget: safeint.NewSafeInt(), ctx: ctx, fnCancel: fnCancel, } val = int(time.Now().UTC().Unix()) + val sf.timeTarget.Set(val) sf.isWork.Set() go sf.makeTick() go sf.run() return sf } // Запускает тикер для секундных интервалов func (sf *DownTime) makeTick() { defer func() { if !sf.isWork.Get() { return } sf.fnCancel() sf.isWork.Reset() close(sf.chTick) // log._rintf("CountTime.makeTick(): работа завершена") }() countSleep := 0 // Счётчик сна (10 периодов -- 1 секунда) for { select { case <-sf.ctx.Done(): // Отмена контекста бота // log._rintf("CountTime.makeTick(): отмена контекста бота\n") return default: if !sf.isWork.Get() { return } if countSleep >= 10 { sf.chTick <- 1 countSleep = 0 } countSleep++ time.Sleep(sleepInterval) } } } // Главный цикл обратного отсчёта func (sf *DownTime) run() { for range sf.chTick { time.Sleep(time.Millisecond * 100) timeNow := time.Now().UTC().Unix() if sf.timeTarget.Get() > int(timeNow) { continue } close(sf.chCall) sf.fnCancel() return } } // Stop -- останавливает работу cчётчика func (sf *DownTime) Stop() { sf.isWork.Reset() } // Get -- возвращает число оставшихся сек func (sf *DownTime) Get() int { return sf.val.Get() } // устанавливает число оставшихся сек func (sf *DownTime) parse(val string) error { sf.block.Lock() defer sf.block.Unlock() if val == "" { return fmt.Errorf("CountTime.parse(): val is empty") } sf.parser.Parse(val) _val := sf.parser.Hour().Get()*3600 + sf.parser.Min().Get()*60 + sf.parser.Min().Get() sf.val.Set(_val) _val = int(time.Now().UTC().Unix()) + sf.val.Get() sf.timeTarget.Set(_val) return nil } // устанавливает число оставшихся сек func (sf *DownTime) set_val(val int) error { sf.block.Lock() defer sf.block.Unlock() if val < 0 { return fmt.Errorf("CountTime.set_val(): val(%v)<0", val) } sf.val.Set(val) { // Обновить локальные счётчики if val < 60 { sf.parser.Hour().Reset() sf.parser.Min().Reset() sf.parser.Sec().Set(val) return nil } if 60 < val && val < 3600 { sf.parser.Hour().Reset() iMin := val / 60 sf.parser.Min().Set(iMin) val -= iMin * 60 sf.parser.Sec().Set(val) return nil } sf.parser.Hour().Set(val / 3600) val -= sf.parser.Hour().Get() * 3600 sf.parser.Min().Set(val / 60) val -= sf.parser.Min().Get() * 60 sf.parser.Sec().Set(val) // val = int(time.Now().UTC().Unix()) + sf.val.Get() } return nil } // String -- возвращает строковое представление оставшихся сек func (sf *DownTime) String() string { sf.block.RLock() defer sf.block.RUnlock() timeNow := time.Now().UTC().Unix() val := sf.timeTarget.Get() - int(timeNow) sf.set_val(val) return sf.parser.String() } // ChanSig -- возвращает канал чтения тиков func (sf *DownTime) ChanSig() <-chan int { return sf.chCall }