mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-12-24 13:48:04 +08:00
- Refactor frame converter implementation - Update mp4 track to use ICodex - General refactoring and code improvements 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
168 lines
3.9 KiB
Go
168 lines
3.9 KiB
Go
package task
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
"runtime/debug"
|
|
"slices"
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
type Singleton[T comparable] struct {
|
|
instance atomic.Value
|
|
mux sync.Mutex
|
|
}
|
|
|
|
func (s *Singleton[T]) Load() T {
|
|
return s.instance.Load().(T)
|
|
}
|
|
|
|
func (s *Singleton[T]) Get(newF func() T) T {
|
|
ch := s.instance.Load() //fast
|
|
if ch == nil { // slow
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
if ch = s.instance.Load(); ch == nil {
|
|
ch = newF()
|
|
s.instance.Store(ch)
|
|
}
|
|
}
|
|
return ch.(T)
|
|
}
|
|
|
|
type EventLoop struct {
|
|
cases []reflect.SelectCase
|
|
children []ITask
|
|
addSub Singleton[chan any]
|
|
running atomic.Bool
|
|
}
|
|
|
|
func (e *EventLoop) getInput() chan any {
|
|
return e.addSub.Get(func() chan any {
|
|
return make(chan any, 20)
|
|
})
|
|
}
|
|
|
|
func (e *EventLoop) active(mt *Job) {
|
|
if mt.parent != nil {
|
|
mt.parent.eventLoop.active(mt.parent)
|
|
}
|
|
if e.running.CompareAndSwap(false, true) {
|
|
go e.run(mt)
|
|
}
|
|
}
|
|
|
|
func (e *EventLoop) add(mt *Job, sub any) (err error) {
|
|
shouldActive := true
|
|
switch sub.(type) {
|
|
case TaskStarter, TaskBlock, TaskGo:
|
|
case IJob:
|
|
shouldActive = false
|
|
}
|
|
select {
|
|
case e.getInput() <- sub:
|
|
if shouldActive || mt.IsStopped() {
|
|
e.active(mt)
|
|
}
|
|
return nil
|
|
default:
|
|
return ErrTooManyChildren
|
|
}
|
|
}
|
|
|
|
func (e *EventLoop) run(mt *Job) {
|
|
mt.Debug("event loop start", "jobId", mt.GetTaskID(), "type", mt.GetOwnerType())
|
|
ch := e.getInput()
|
|
e.cases = []reflect.SelectCase{{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch)}}
|
|
defer func() {
|
|
err := recover()
|
|
if err != nil {
|
|
mt.Error("job panic", "err", err, "stack", string(debug.Stack()))
|
|
if !ThrowPanic {
|
|
mt.Stop(errors.Join(err.(error), ErrPanic))
|
|
} else {
|
|
panic(err)
|
|
}
|
|
}
|
|
mt.Debug("event loop exit", "jobId", mt.GetTaskID(), "type", mt.GetOwnerType())
|
|
if !mt.handler.keepalive() {
|
|
if mt.blocked != nil {
|
|
mt.Stop(errors.Join(mt.blocked.StopReason(), ErrAutoStop))
|
|
} else {
|
|
mt.Stop(ErrAutoStop)
|
|
}
|
|
}
|
|
mt.blocked = nil
|
|
}()
|
|
|
|
// Main event loop - only exit when no more events AND no children
|
|
for {
|
|
if len(ch) == 0 && len(e.children) == 0 {
|
|
if e.running.CompareAndSwap(true, false) {
|
|
if len(ch) > 0 { // if add before running set to false
|
|
e.active(mt)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
mt.blocked = nil
|
|
if chosen, rev, ok := reflect.Select(e.cases); chosen == 0 {
|
|
if !ok {
|
|
mt.Debug("job addSub channel closed, exiting", "taskId", mt.GetTaskID())
|
|
mt.Stop(ErrAutoStop)
|
|
return
|
|
}
|
|
switch v := rev.Interface().(type) {
|
|
case func():
|
|
v()
|
|
case ITask:
|
|
if len(e.cases) >= 65535 {
|
|
mt.Warn("task children too many, may cause performance issue", "count", len(e.cases), "taskId", mt.GetTaskID(), "taskType", mt.GetTaskType(), "ownerType", mt.GetOwnerType())
|
|
v.Stop(ErrTooManyChildren)
|
|
continue
|
|
}
|
|
if mt.blocked = v; v.start() {
|
|
e.cases = append(e.cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(v.GetSignal())})
|
|
e.children = append(e.children, v)
|
|
mt.onChildStart(v)
|
|
} else {
|
|
mt.removeChild(v)
|
|
}
|
|
}
|
|
} else {
|
|
taskIndex := chosen - 1
|
|
child := e.children[taskIndex]
|
|
mt.blocked = child
|
|
switch tt := mt.blocked.(type) {
|
|
case IChannelTask:
|
|
if tt.IsStopped() {
|
|
switch ttt := tt.(type) {
|
|
case ITickTask:
|
|
ttt.GetTicker().Stop()
|
|
}
|
|
mt.onChildDispose(child)
|
|
mt.removeChild(child)
|
|
e.children = slices.Delete(e.children, taskIndex, taskIndex+1)
|
|
e.cases = slices.Delete(e.cases, chosen, chosen+1)
|
|
} else {
|
|
tt.Tick(rev.Interface())
|
|
}
|
|
default:
|
|
if !ok {
|
|
if mt.onChildDispose(child); child.checkRetry(child.StopReason()) {
|
|
if child.reset(); child.start() {
|
|
e.cases[chosen].Chan = reflect.ValueOf(child.GetSignal())
|
|
mt.onChildStart(child)
|
|
continue
|
|
}
|
|
}
|
|
mt.removeChild(child)
|
|
e.children = slices.Delete(e.children, taskIndex, taskIndex+1)
|
|
e.cases = slices.Delete(e.cases, chosen, chosen+1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|