github.com/15mga/kiwi@v0.0.2-0.20240324021231-b95d5c3ac751/ecs/frame.go (about)

     1  package ecs
     2  
     3  import (
     4  	"context"
     5  	"github.com/15mga/kiwi/ds"
     6  	"github.com/15mga/kiwi/worker"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/15mga/kiwi"
    11  	"github.com/15mga/kiwi/util"
    12  )
    13  
    14  const (
    15  	cmdFrameAddSystem = "add_system"
    16  	cmdFrameDelSystem = "del_system"
    17  	cmdFrameJob       = "job_system"
    18  )
    19  
    20  type (
    21  	frameOption struct {
    22  		maxFrame      int64
    23  		tickDur       time.Duration
    24  		systems       []ISystem
    25  		beforeDispose FnFrame
    26  	}
    27  	FrameOption func(o *frameOption)
    28  )
    29  
    30  func FrameMax(frames int64) FrameOption {
    31  	return func(o *frameOption) {
    32  		o.maxFrame = frames
    33  	}
    34  }
    35  
    36  func FrameTickDur(dur time.Duration) FrameOption {
    37  	return func(o *frameOption) {
    38  		o.tickDur = dur
    39  	}
    40  }
    41  
    42  func FrameSystems(systems ...ISystem) FrameOption {
    43  	return func(o *frameOption) {
    44  		o.systems = systems
    45  	}
    46  }
    47  
    48  func FrameBeforeDispose(fn FnFrame) FrameOption {
    49  	return func(o *frameOption) {
    50  		o.beforeDispose = fn
    51  	}
    52  }
    53  
    54  func NewFrame(scene *Scene, opts ...FrameOption) *Frame {
    55  	o := &frameOption{
    56  		maxFrame: 0,
    57  		tickDur:  time.Millisecond * 100,
    58  	}
    59  	for _, opt := range opts {
    60  		opt(o)
    61  	}
    62  	ctx, ccl := context.WithCancel(util.Ctx())
    63  	now := time.Now().UnixMilli()
    64  	f := &Frame{
    65  		option:       o,
    66  		startTime:    now,
    67  		nowMillSecs:  now,
    68  		scene:        scene,
    69  		systems:      o.systems,
    70  		typeToSystem: make(map[TSystem]ISystem, len(o.systems)),
    71  		jobToSystem:  make(map[worker.JobName]ISystem, len(o.systems)),
    72  		sign:         make(chan struct{}, 1),
    73  		ctx:          ctx,
    74  		ccl:          ccl,
    75  	}
    76  	for _, system := range o.systems {
    77  		f.typeToSystem[system.Type()] = system
    78  	}
    79  	f.cmdBuffer = newBuffer()
    80  	f.before = ds.NewFnLink()
    81  	f.after = ds.NewFnLink()
    82  	return f
    83  }
    84  
    85  type Frame struct {
    86  	option       *frameOption
    87  	currFrame    int64
    88  	maxFrame     int64
    89  	totalFrameMs int64
    90  	maxMs        int64
    91  	deltaMs      int64
    92  	startTime    int64
    93  	nowMillSecs  int64
    94  	scene        *Scene
    95  	systems      []ISystem
    96  	typeToSystem map[TSystem]ISystem
    97  	jobToSystem  map[worker.JobName]ISystem
    98  	cmdBuffer    *Buffer
    99  	before       *ds.FnLink
   100  	after        *ds.FnLink
   101  	mtx          sync.Mutex
   102  	sign         chan struct{}
   103  	head         *job
   104  	tail         *job
   105  	ctx          context.Context
   106  	ccl          context.CancelFunc
   107  }
   108  
   109  func (f *Frame) Num() int64 {
   110  	return f.currFrame
   111  }
   112  
   113  func (f *Frame) DeltaMillSec() int64 {
   114  	return f.deltaMs
   115  }
   116  
   117  func (f *Frame) StartTime() int64 {
   118  	return f.startTime
   119  }
   120  
   121  func (f *Frame) NowMillSecs() int64 {
   122  	return f.nowMillSecs
   123  }
   124  
   125  func (f *Frame) Scene() *Scene {
   126  	return f.scene
   127  }
   128  
   129  // Before 每帧末尾调用
   130  func (f *Frame) Before() *ds.FnLink {
   131  	return f.before
   132  }
   133  
   134  // After 每帧末尾调用
   135  func (f *Frame) After() *ds.FnLink {
   136  	return f.after
   137  }
   138  
   139  // GetSystem 注意协程安全
   140  func (f *Frame) GetSystem(typ TSystem) (ISystem, bool) {
   141  	sys, ok := f.typeToSystem[typ]
   142  	return sys, ok
   143  }
   144  
   145  func (f *Frame) Start() {
   146  	completeCh := kiwi.BeforeExitCh("stop frame")
   147  	go func() {
   148  		defer func() {
   149  			if f.option.beforeDispose != nil {
   150  				f.option.beforeDispose(f)
   151  			}
   152  
   153  			for _, system := range f.systems {
   154  				kiwi.Info("stop system", util.M{
   155  					"type": system.Type(),
   156  				})
   157  				system.OnStop()
   158  			}
   159  
   160  			kiwi.Info("dispose scene", util.M{
   161  				"scene type": f.scene.typ,
   162  				"scene id":   f.scene.id,
   163  			})
   164  			f.scene.Dispose()
   165  
   166  			if f.currFrame > 0 {
   167  				kiwi.Info("frames", util.M{
   168  					"total":   f.totalFrameMs,
   169  					"average": f.totalFrameMs / f.currFrame,
   170  					"max":     f.maxMs,
   171  					"frames":  f.currFrame,
   172  				})
   173  			}
   174  			close(completeCh)
   175  		}()
   176  
   177  		for _, system := range f.systems {
   178  			system.OnBeforeStart()
   179  			system.OnStart(f)
   180  			system.OnAfterStart()
   181  			kiwi.Info("start system", util.M{
   182  				"type": system.Type(),
   183  			})
   184  		}
   185  
   186  		ctx := f.ctx
   187  		ticker := time.NewTicker(f.option.tickDur)
   188  		for {
   189  			select {
   190  			case <-ctx.Done():
   191  				kiwi.Debug("ctx done", nil)
   192  				ticker.Stop()
   193  				return
   194  			case <-ticker.C:
   195  				f.tick()
   196  			case <-f.sign:
   197  				f.do()
   198  			}
   199  		}
   200  	}()
   201  }
   202  
   203  func (f *Frame) Stop() {
   204  	f.ccl()
   205  }
   206  
   207  func (f *Frame) tick() {
   208  	f.currFrame++
   209  	now := time.Now().UnixMilli()
   210  	ms := now - f.nowMillSecs
   211  	f.nowMillSecs = now
   212  	f.before.InvokeAndReset()
   213  	for _, s := range f.systems {
   214  		s.OnUpdate()
   215  	}
   216  	f.after.InvokeAndReset()
   217  	f.deltaMs = ms
   218  	frameDur := time.Now().UnixMilli() - now
   219  	//kiwi.Debug("frame", util.M{
   220  	//	"curr": f.currFrame,
   221  	//	"dur":  frameDur,
   222  	//})
   223  	f.totalFrameMs += frameDur
   224  	if ms > f.maxMs {
   225  		f.maxMs = ms
   226  	}
   227  }
   228  
   229  func (f *Frame) AddSystem(system ISystem, before TSystem) {
   230  	f.push(cmdFrameAddSystem, system, before)
   231  }
   232  
   233  func (f *Frame) DelSystem(t TSystem) {
   234  	f.push(cmdFrameDelSystem, t)
   235  }
   236  
   237  // PushJob frame 外部使用,协程安全的
   238  func (f *Frame) PushJob(name JobName, data ...any) {
   239  	f.push(cmdFrameJob, name, data)
   240  }
   241  
   242  func (f *Frame) AfterClearTags(tags ...string) {
   243  	f.after.Push(func() {
   244  		f.Scene().ClearTags(tags...)
   245  	})
   246  }
   247  
   248  func (f *Frame) onAddSystem(data []any) {
   249  	system, before := util.SplitSlc2[ISystem, TSystem](data)
   250  	t := system.Type()
   251  	idx := len(f.systems)
   252  	for i, s := range f.systems {
   253  		if s.Type() == t {
   254  			kiwi.Error2(util.EcExist, util.M{
   255  				"system": t,
   256  			})
   257  			return
   258  		}
   259  		if s.Type() == before {
   260  			idx = i
   261  			break
   262  		}
   263  	}
   264  	system.OnStart(f)
   265  	system.OnAfterStart()
   266  	kiwi.Info("start system", util.M{
   267  		"type": system.Type(),
   268  	})
   269  	f.systems = append(append(f.systems[:idx], system), f.systems[idx:]...)
   270  	f.typeToSystem[system.Type()] = system
   271  }
   272  
   273  func (f *Frame) onDelSystem(data []any) {
   274  	t := data[0].(TSystem)
   275  	for i, s := range f.systems {
   276  		if s.Type() == t {
   277  			s.OnStop()
   278  			kiwi.Info("stop system", util.M{
   279  				"type": s.Type(),
   280  			})
   281  			f.systems = append(f.systems[:i], f.systems[i+1:]...)
   282  			delete(f.typeToSystem, t)
   283  			break
   284  		}
   285  	}
   286  }
   287  
   288  func (f *Frame) onJobSystem(data []any) {
   289  	name, params := util.SplitSlc2[string, []any](data)
   290  	f.PutJob(name, params...)
   291  }
   292  
   293  // PutJob system内部使用,注意协程安全,需要回到主协程使用
   294  func (f *Frame) PutJob(name JobName, data ...any) {
   295  	system, ok := f.jobToSystem[name]
   296  	if !ok {
   297  		kiwi.Error2(util.EcNotExist, util.M{
   298  			"system": name,
   299  		})
   300  		return
   301  	}
   302  	system.PutJob(name, data...)
   303  }
   304  
   305  func (f *Frame) push(cmd JobName, data ...any) {
   306  	j := _JobPool.Get().(*job)
   307  	j.Name = cmd
   308  	j.Data = data
   309  
   310  	f.mtx.Lock()
   311  	if f.head != nil {
   312  		f.tail.next = j
   313  	} else {
   314  		f.head = j
   315  	}
   316  	f.tail = j
   317  	f.mtx.Unlock()
   318  
   319  	select {
   320  	case f.sign <- struct{}{}:
   321  	default:
   322  	}
   323  }
   324  
   325  func (f *Frame) bindJob(name worker.JobName, system ISystem) {
   326  	f.jobToSystem[name] = system
   327  }
   328  
   329  func (f *Frame) do() {
   330  	f.mtx.Lock()
   331  	head := f.head
   332  	f.head = nil
   333  	f.tail = nil
   334  	f.mtx.Unlock()
   335  
   336  	if head == nil {
   337  		return
   338  	}
   339  
   340  	for j := head; j != nil; {
   341  		switch j.Name {
   342  		case cmdFrameAddSystem:
   343  			f.onAddSystem(j.Data)
   344  		case cmdFrameDelSystem:
   345  			f.onDelSystem(j.Data)
   346  		case cmdFrameJob:
   347  			f.onJobSystem(j.Data)
   348  		}
   349  		next := j.next
   350  		j.Data = nil
   351  		j.next = nil
   352  		_JobPool.Put(j)
   353  		j = next
   354  	}
   355  }