down_time.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. // package down_time -- счётчик обратного времени в мсек
  2. package down_time
  3. import (
  4. "context"
  5. "fmt"
  6. // "log"
  7. "sync"
  8. "time"
  9. "wartank/pkg/components/parsetime"
  10. "wartank/pkg/components/safebool"
  11. "wartank/pkg/components/safeint"
  12. "wartank/pkg/types"
  13. )
  14. const (
  15. sleepInterval = time.Millisecond * 100 // Малый интервал сна в 100 мсек
  16. )
  17. // DownTime -- счётчик обратного времени для игровой зоны (анга, база, битва и т.п.)
  18. type DownTime struct {
  19. zone types.IZone
  20. val *safeint.SafeInt // Фактическое значение счётчика
  21. parser *parsetime.ParseTime // Парсер значения
  22. chTick chan int // Канал секундных интервалов сна (для отображения)
  23. chCall chan int // Канал для отправки сигналов (для верхнего уровня)
  24. isWork *safebool.SafeBool // Признак работы
  25. timeTarget *safeint.SafeInt // Целевое время срабатывания
  26. ctx context.Context // Контекст для счётчика времени
  27. fnCancel func() // Функция отмены контекста для счётчика времени
  28. block sync.RWMutex
  29. }
  30. // NewCountTime -- возвращает новый *CountTime
  31. func NewCountTime(zone types.IZone, val int) *DownTime {
  32. if zone == nil {
  33. panic("NewCountTime(): IZone == nil")
  34. }
  35. ctx, fnCancel := context.WithCancel(zone.Ctx())
  36. sf := &DownTime{
  37. zone: zone,
  38. val: safeint.NewSafeInt(),
  39. chTick: make(chan int, 3),
  40. chCall: make(chan int, 2),
  41. isWork: safebool.NewSafeBool(),
  42. parser: parsetime.NewParseTime(),
  43. timeTarget: safeint.NewSafeInt(),
  44. ctx: ctx,
  45. fnCancel: fnCancel,
  46. }
  47. val = int(time.Now().UTC().Unix()) + val
  48. sf.timeTarget.Set(val)
  49. sf.isWork.Set()
  50. go sf.makeTick()
  51. go sf.run()
  52. return sf
  53. }
  54. // Запускает тикер для секундных интервалов
  55. func (sf *DownTime) makeTick() {
  56. defer func() {
  57. if !sf.isWork.Get() {
  58. return
  59. }
  60. sf.fnCancel()
  61. sf.isWork.Reset()
  62. close(sf.chTick)
  63. // log._rintf("CountTime.makeTick(): работа завершена")
  64. }()
  65. countSleep := 0 // Счётчик сна (10 периодов -- 1 секунда)
  66. for {
  67. select {
  68. case <-sf.ctx.Done(): // Отмена контекста бота
  69. // log._rintf("CountTime.makeTick(): отмена контекста бота\n")
  70. return
  71. default:
  72. if !sf.isWork.Get() {
  73. return
  74. }
  75. if countSleep >= 10 {
  76. sf.chTick <- 1
  77. countSleep = 0
  78. }
  79. countSleep++
  80. time.Sleep(sleepInterval)
  81. }
  82. }
  83. }
  84. // Главный цикл обратного отсчёта
  85. func (sf *DownTime) run() {
  86. for range sf.chTick {
  87. time.Sleep(time.Millisecond * 100)
  88. timeNow := time.Now().UTC().Unix()
  89. if sf.timeTarget.Get() > int(timeNow) {
  90. continue
  91. }
  92. close(sf.chCall)
  93. sf.fnCancel()
  94. return
  95. }
  96. }
  97. // Stop -- останавливает работу cчётчика
  98. func (sf *DownTime) Stop() {
  99. sf.isWork.Reset()
  100. }
  101. // Get -- возвращает число оставшихся сек
  102. func (sf *DownTime) Get() int {
  103. return sf.val.Get()
  104. }
  105. // устанавливает число оставшихся сек
  106. func (sf *DownTime) parse(val string) error {
  107. sf.block.Lock()
  108. defer sf.block.Unlock()
  109. if val == "" {
  110. return fmt.Errorf("CountTime.parse(): val is empty")
  111. }
  112. sf.parser.Parse(val)
  113. _val := sf.parser.Hour().Get()*3600 + sf.parser.Min().Get()*60 + sf.parser.Min().Get()
  114. sf.val.Set(_val)
  115. _val = int(time.Now().UTC().Unix()) + sf.val.Get()
  116. sf.timeTarget.Set(_val)
  117. return nil
  118. }
  119. // устанавливает число оставшихся сек
  120. func (sf *DownTime) set_val(val int) error {
  121. sf.block.Lock()
  122. defer sf.block.Unlock()
  123. if val < 0 {
  124. return fmt.Errorf("CountTime.set_val(): val(%v)<0", val)
  125. }
  126. sf.val.Set(val)
  127. { // Обновить локальные счётчики
  128. if val < 60 {
  129. sf.parser.Hour().Reset()
  130. sf.parser.Min().Reset()
  131. sf.parser.Sec().Set(val)
  132. return nil
  133. }
  134. if 60 < val && val < 3600 {
  135. sf.parser.Hour().Reset()
  136. iMin := val / 60
  137. sf.parser.Min().Set(iMin)
  138. val -= iMin * 60
  139. sf.parser.Sec().Set(val)
  140. return nil
  141. }
  142. sf.parser.Hour().Set(val / 3600)
  143. val -= sf.parser.Hour().Get() * 3600
  144. sf.parser.Min().Set(val / 60)
  145. val -= sf.parser.Min().Get() * 60
  146. sf.parser.Sec().Set(val)
  147. // val = int(time.Now().UTC().Unix()) + sf.val.Get()
  148. }
  149. return nil
  150. }
  151. // String -- возвращает строковое представление оставшихся сек
  152. func (sf *DownTime) String() string {
  153. sf.block.RLock()
  154. defer sf.block.RUnlock()
  155. timeNow := time.Now().UTC().Unix()
  156. val := sf.timeTarget.Get() - int(timeNow)
  157. sf.set_val(val)
  158. return sf.parser.String()
  159. }
  160. // ChanSig -- возвращает канал чтения тиков
  161. func (sf *DownTime) ChanSig() <-chan int {
  162. return sf.chCall
  163. }