diff --git a/utils/times/line.go b/utils/times/line.go new file mode 100644 index 0000000..73c24c1 --- /dev/null +++ b/utils/times/line.go @@ -0,0 +1,247 @@ +package times + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/slice" + "strings" + "time" +) + +// NewStateLine 创建一个从左向右由早到晚的状态时间线 +func NewStateLine[State generic.Basic](zero State) *StateLine[State] { + return &StateLine[State]{ + states: []State{zero}, + points: []time.Time{{}}, + trigger: [][]func(){{}}, + } +} + +// StateLine 状态时间线 +type StateLine[State generic.Basic] struct { + states []State // 每个时间点对应的状态 + points []time.Time // 每个时间点 + trigger [][]func() // 每个时间点对应的触发器 +} + +// Check 根据状态顺序检查时间线是否合法 +// - missingAllowed: 是否允许状态缺失,如果为 true,则状态可以不连续,如果为 false,则状态必须连续 +// +// 状态不连续表示时间线中存在状态缺失,例如: +// - 状态为 [1, 2, 3, 4, 5] 的时间线,如果 missingAllowed 为 true,则状态为 [1, 3, 5] 也是合法的 +// - 状态为 [1, 2, 3, 4, 5] 的时间线,如果 missingAllowed 为 false,则状态为 [1, 3, 5] 是不合法的 +func (slf *StateLine[State]) Check(missingAllowed bool, states ...State) bool { + var indexStored int + var indexInput int + + for indexStored < len(slf.states) && indexInput < len(states) { + if slf.states[indexStored] == states[indexInput] { + indexStored++ + indexInput++ + } else if missingAllowed { + indexInput++ + } else { + return false + } + } + + //如果存储序列还有剩余, 而输入序列已经遍历完 + if indexStored != len(slf.states) && indexInput == len(states) { + return false + } + + // 如果输入序列还有剩余, 而存储序列已经遍历完 + if indexStored == len(slf.states) && indexInput != len(states) && !missingAllowed { + return false + } + + return true +} + +// GetMissingStates 获取缺失的状态 +func (slf *StateLine[State]) GetMissingStates(states ...State) []State { + var missing = make([]State, 0, len(states)) + for _, state := range states { + if !slice.Contains(slf.states, state) { + missing = append(missing, state) + } + } + return missing +} + +// HasState 检查时间线中是否包含指定状态 +func (slf *StateLine[State]) HasState(state State) bool { + return slice.Contains(slf.states, state) +} + +// String 获取时间线的字符串表示 +func (slf *StateLine[State]) String() string { + var parts []string + for i := 0; i < len(slf.states); i++ { + parts = append(parts, fmt.Sprintf("[%v] %v", slf.states[i], slf.points[i])) + } + return strings.Join(parts, " > ") +} + +// AddState 添加一个状态到时间线中,状态不能与任一时间点重合,否则将被忽略 +// - onTrigger: 该状态绑定的触发器,该触发器不会被主动执行,需要主动获取触发器执行 +func (slf *StateLine[State]) AddState(state State, t time.Time, onTrigger ...func()) *StateLine[State] { + if slice.Contains(slf.states, state) { + return slf + } + // 将 t 按照从左到右由早到晚的顺序插入到 points 中 + for i := 0; i < len(slf.points); i++ { + if slf.points[i].After(t) { + slf.points = append(slf.points[:i], append([]time.Time{t}, slf.points[i:]...)...) + slf.states = append(slf.states[:i], append([]State{state}, slf.states[i:]...)...) + slf.trigger = append(slf.trigger[:i], append([][]func(){onTrigger}, slf.trigger[i:]...)...) + return slf + } + } + slf.points = append(slf.points, t) + slf.states = append(slf.states, state) + slf.trigger = append(slf.trigger, onTrigger) + return slf +} + +// GetTimeByState 获取指定状态的时间点 +func (slf *StateLine[State]) GetTimeByState(state State) time.Time { + for i := 0; i < len(slf.states); i++ { + if slf.states[i] == state { + return slf.points[i] + } + } + return time.Time{} +} + +// GetNextTimeByState 获取指定状态的下一个时间点 +func (slf *StateLine[State]) GetNextTimeByState(state State) time.Time { + for i := 0; i < len(slf.states); i++ { + if slf.states[i] == state && i < len(slf.points)-1 { + return slf.points[i+1] + } + } + return time.Time{} +} + +// GetPrevTimeByState 获取指定状态的上一个时间点 +func (slf *StateLine[State]) GetPrevTimeByState(state State) time.Time { + for i := len(slf.states) - 1; i >= 0; i-- { + if slf.states[i] == state && i > 0 { + return slf.points[i-1] + } + } + return time.Time{} +} + +// GetIndexByState 获取指定状态的索引 +func (slf *StateLine[State]) GetIndexByState(state State) int { + for i := 0; i < len(slf.states); i++ { + if slf.states[i] == state { + return i + } + } + return -1 +} + +// GetStateByTime 获取指定时间点的状态 +func (slf *StateLine[State]) GetStateByTime(t time.Time) State { + for i := len(slf.points) - 1; i >= 0; i-- { + if i == len(slf.points)-1 && slf.points[i].Before(t) { + return slf.states[0] + } + if slf.points[i].Before(t) { + return slf.states[i] + } + } + return slf.states[0] +} + +// GetTimeByIndex 获取指定索引的时间点 +func (slf *StateLine[State]) GetTimeByIndex(index int) time.Time { + return slf.points[index] +} + +// GetNextStateTimeByIndex 获取指定索引的下一个时间点 +func (slf *StateLine[State]) GetNextStateTimeByIndex(index int) time.Time { + return slf.points[index+1] +} + +// GetPrevStateTimeByIndex 获取指定索引的上一个时间点 +func (slf *StateLine[State]) GetPrevStateTimeByIndex(index int) time.Time { + return slf.points[index-1] +} + +// GetStateIndexByTime 获取指定时间点的索引 +func (slf *StateLine[State]) GetStateIndexByTime(t time.Time) int { + for i := len(slf.points) - 1; i >= 0; i-- { + if slf.points[i].Before(t) { + return i + } + } + return -1 +} + +// GetStateCount 获取状态数量 +func (slf *StateLine[State]) GetStateCount() int { + return len(slf.states) +} + +// GetStateByIndex 获取指定索引的状态 +func (slf *StateLine[State]) GetStateByIndex(index int) State { + return slf.states[index] +} + +// GetTriggerByTime 获取指定时间点的触发器 +func (slf *StateLine[State]) GetTriggerByTime(t time.Time) []func() { + for i := len(slf.points) - 1; i >= 0; i-- { + if slf.points[i].Before(t) { + return slf.trigger[i] + } + } + return nil +} + +// GetTriggerByIndex 获取指定索引的触发器 +func (slf *StateLine[State]) GetTriggerByIndex(index int) []func() { + return slf.trigger[index] +} + +// GetTriggerByState 获取指定状态的触发器 +func (slf *StateLine[State]) GetTriggerByState(state State) []func() { + for i := 0; i < len(slf.states); i++ { + if slf.states[i] == state { + return slf.trigger[i] + } + } + return nil +} + +// AddTriggerToState 给指定状态添加触发器 +func (slf *StateLine[State]) AddTriggerToState(state State, onTrigger ...func()) *StateLine[State] { + for i := 0; i < len(slf.states); i++ { + if slf.states[i] == state { + slf.trigger[i] = append(slf.trigger[i], onTrigger...) + return slf + } + } + return slf +} + +// Range 按照时间顺序遍历时间线 +func (slf *StateLine[State]) Range(handler func(index int, state State, t time.Time) bool) { + for i := 0; i < len(slf.points); i++ { + if !handler(i, slf.states[i], slf.points[i]) { + return + } + } +} + +// RangeReverse 按照时间逆序遍历时间线 +func (slf *StateLine[State]) RangeReverse(handler func(index int, state State, t time.Time) bool) { + for i := len(slf.points) - 1; i >= 0; i-- { + if !handler(i, slf.states[i], slf.points[i]) { + return + } + } +} diff --git a/utils/times/line_test.go b/utils/times/line_test.go new file mode 100644 index 0000000..e4b6eff --- /dev/null +++ b/utils/times/line_test.go @@ -0,0 +1,20 @@ +package times_test + +import ( + "github.com/kercylan98/minotaur/utils/times" + "testing" + "time" +) + +func TestNewStateLine(t *testing.T) { + sl := times.NewStateLine(0) + sl.AddState(1, time.Now()) + sl.AddState(2, time.Now().Add(-times.Hour)) + + sl.Range(func(index int, state int, ts time.Time) bool { + t.Log(index, state, ts) + return true + }) + + t.Log(sl.GetState(time.Now())) +}