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 }