mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-10-26 14:00:21 +08:00
212 lines
4.5 KiB
Go
212 lines
4.5 KiB
Go
package util
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"os"
|
|
"reflect"
|
|
"slices"
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
var idG atomic.Uint32
|
|
|
|
func GetNextTaskID() uint32 {
|
|
return idG.Add(1)
|
|
}
|
|
|
|
var RootTask MarcoLongTask
|
|
|
|
func init() {
|
|
RootTask.initTask(context.Background(), &RootTask)
|
|
RootTask.Description = map[string]any{
|
|
"title": "RootTask",
|
|
}
|
|
RootTask.Logger = slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
}
|
|
|
|
type MarcoLongTask struct {
|
|
MarcoTask
|
|
}
|
|
|
|
func (m *MarcoLongTask) initTask(ctx context.Context, task ITask) {
|
|
m.MarcoTask.initTask(ctx, task)
|
|
m.keepAlive = true
|
|
}
|
|
|
|
func (m *MarcoLongTask) GetTaskType() string {
|
|
return "long"
|
|
}
|
|
|
|
// MarcoTask include sub tasks
|
|
type MarcoTask struct {
|
|
Task
|
|
addSub chan ITask
|
|
children []ITask
|
|
lazyRun sync.Once
|
|
keepAlive bool
|
|
}
|
|
|
|
func (m *MarcoTask) GetTaskType() string {
|
|
return "marco"
|
|
}
|
|
|
|
func (mt *MarcoTask) getMaroTask() *MarcoTask {
|
|
return mt
|
|
}
|
|
|
|
func (mt *MarcoTask) initTask(ctx context.Context, task ITask) {
|
|
mt.Task.initTask(ctx, task)
|
|
mt.shutdown = nil
|
|
mt.addSub = make(chan ITask, 10)
|
|
}
|
|
|
|
func (mt *MarcoTask) dispose() {
|
|
reason := mt.StopReason()
|
|
if mt.Logger != nil {
|
|
mt.Debug("task dispose", "reason", reason, "taskId", mt.ID, "taskType", mt.GetTaskType(), "ownerType", mt.GetOwnerType())
|
|
}
|
|
mt.disposeHandler()
|
|
close(mt.addSub)
|
|
_ = mt.WaitStopped()
|
|
if mt.Logger != nil {
|
|
mt.Debug("task disposed", "reason", reason, "taskId", mt.ID, "taskType", mt.GetTaskType(), "ownerType", mt.GetOwnerType())
|
|
}
|
|
for _, listener := range mt.afterDisposeListeners {
|
|
listener()
|
|
}
|
|
}
|
|
|
|
func (mt *MarcoTask) lazyStart(t ITask) {
|
|
task := t.GetTask()
|
|
if mt.IsStopped() {
|
|
task.startup.Reject(mt.StopReason())
|
|
return
|
|
}
|
|
if task.ID == 0 {
|
|
task.ID = GetNextTaskID()
|
|
}
|
|
if task.parent == nil {
|
|
task.parent = mt
|
|
}
|
|
if task.Logger == nil {
|
|
task.Logger = mt.Logger
|
|
}
|
|
if task.startHandler == nil {
|
|
task.startHandler = EmptyStart
|
|
}
|
|
if task.disposeHandler == nil {
|
|
task.disposeHandler = EmptyDispose
|
|
}
|
|
mt.lazyRun.Do(func() {
|
|
mt.shutdown = NewPromise(context.Background())
|
|
go mt.run()
|
|
})
|
|
mt.addSub <- t
|
|
}
|
|
|
|
func (mt *MarcoTask) RangeSubTask(callback func(task ITask) bool) {
|
|
for _, task := range mt.children {
|
|
callback(task)
|
|
}
|
|
}
|
|
|
|
func (mt *MarcoTask) AddTask(task ITask) *Task {
|
|
t := task.GetTask()
|
|
if t.parentCtx != nil && task.IsStopped() { //reuse task
|
|
t.parent = nil
|
|
return mt.AddTaskWithContext(t.parentCtx, task)
|
|
}
|
|
return mt.AddTaskWithContext(mt.Context, task)
|
|
}
|
|
|
|
func (mt *MarcoTask) AddTaskWithContext(ctx context.Context, t ITask) (task *Task) {
|
|
if ctx == nil && mt.Context == nil {
|
|
panic("context is nil")
|
|
}
|
|
if task = t.GetTask(); task.parent == nil {
|
|
t.initTask(ctx, t)
|
|
}
|
|
mt.lazyStart(t)
|
|
return
|
|
}
|
|
|
|
func (mt *MarcoTask) Call(callback func() error) {
|
|
task := CreateTaskByCallBack(callback, nil)
|
|
_ = mt.AddTask(task).WaitStarted()
|
|
}
|
|
|
|
func CreateTaskByCallBack(start func() error, dispose func()) *Task {
|
|
var task Task
|
|
task.startHandler = func() error {
|
|
err := start()
|
|
if err == nil && dispose == nil {
|
|
err = ErrCallbackTask
|
|
}
|
|
return err
|
|
}
|
|
task.disposeHandler = dispose
|
|
return &task
|
|
}
|
|
|
|
func (mt *MarcoTask) AddChan(channel any, callback any) *ChannelTask {
|
|
var chanTask ChannelTask
|
|
chanTask.initTask(mt.Context, &chanTask)
|
|
chanTask.channel = reflect.ValueOf(channel)
|
|
chanTask.callback = reflect.ValueOf(callback)
|
|
mt.lazyStart(&chanTask)
|
|
return &chanTask
|
|
}
|
|
|
|
func (mt *MarcoTask) run() {
|
|
cases := []reflect.SelectCase{{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(mt.addSub)}}
|
|
defer func() {
|
|
err := recover()
|
|
if err != nil {
|
|
mt.Stop(err.(error))
|
|
}
|
|
stopReason := mt.StopReason()
|
|
for _, task := range mt.children {
|
|
task.Stop(stopReason)
|
|
if task.getParent() == mt {
|
|
task.dispose()
|
|
}
|
|
}
|
|
mt.children = nil
|
|
mt.addSub = nil
|
|
mt.shutdown.Fulfill(stopReason)
|
|
}()
|
|
for {
|
|
if chosen, rev, ok := reflect.Select(cases); chosen == 0 {
|
|
if !ok {
|
|
return
|
|
}
|
|
if task := rev.Interface().(ITask); task.getParent() == mt {
|
|
if err := task.start(); err == nil {
|
|
mt.children = append(mt.children, task)
|
|
cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: task.getSignal()})
|
|
} else {
|
|
task.Stop(err)
|
|
}
|
|
}
|
|
} else {
|
|
taskIndex := chosen - 1
|
|
task := mt.children[taskIndex]
|
|
if !ok {
|
|
if task.getParent() == mt {
|
|
task.dispose()
|
|
}
|
|
mt.children = slices.Delete(mt.children, taskIndex, taskIndex+1)
|
|
cases = slices.Delete(cases, chosen, chosen+1)
|
|
|
|
} else if c, ok := task.(IChannelTask); ok {
|
|
c.tick(rev)
|
|
}
|
|
}
|
|
if !mt.keepAlive && len(mt.children) == 0 {
|
|
mt.Stop(ErrAutoStop)
|
|
}
|
|
}
|
|
}
|