clock.go (3559B)
1 // Copyright 2017 The Ebiten Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package clock manages game timers. 16 package clock 17 18 import ( 19 "sync" 20 "time" 21 ) 22 23 var ( 24 lastNow int64 25 26 // lastSystemTime is the last system time in the previous Update. 27 // lastSystemTime indicates the logical time in the game, so this can be bigger than the curren time. 28 lastSystemTime int64 29 30 currentFPS float64 31 currentTPS float64 32 lastUpdated int64 33 fpsCount = 0 34 tpsCount = 0 35 36 m sync.Mutex 37 ) 38 39 func init() { 40 n := now() 41 lastNow = n 42 lastSystemTime = n 43 lastUpdated = n 44 } 45 46 func CurrentFPS() float64 { 47 m.Lock() 48 v := currentFPS 49 m.Unlock() 50 return v 51 } 52 53 func CurrentTPS() float64 { 54 m.Lock() 55 v := currentTPS 56 m.Unlock() 57 return v 58 } 59 60 func max(a, b int64) int64 { 61 if a < b { 62 return b 63 } 64 return a 65 } 66 67 func calcCountFromTPS(tps int64, now int64) int { 68 if tps == 0 { 69 return 0 70 } 71 if tps < 0 { 72 panic("clock: tps must >= 0") 73 } 74 75 diff := now - lastSystemTime 76 if diff < 0 { 77 return 0 78 } 79 80 count := 0 81 syncWithSystemClock := false 82 83 // Detect whether the previous time is too old. 84 // Use either 5 ticks or 5/60 sec in the case when TPS is too big like 300 (#1444). 85 if diff > max(int64(time.Second)*5/tps, int64(time.Second)*5/60) { 86 // The previous time is too old. 87 // Let's force to sync the game time with the system clock. 88 syncWithSystemClock = true 89 } else { 90 count = int(diff * tps / int64(time.Second)) 91 } 92 93 // Stabilize the count. 94 // Without this adjustment, count can be unstable like 0, 2, 0, 2, ... 95 // TODO: Brush up this logic so that this will work with any FPS. Now this works only when FPS = TPS. 96 if count == 0 && (int64(time.Second)/tps/2) < diff { 97 count = 1 98 } 99 if count == 2 && (int64(time.Second)/tps*3/2) > diff { 100 count = 1 101 } 102 103 if syncWithSystemClock { 104 lastSystemTime = now 105 } else { 106 lastSystemTime += int64(count) * int64(time.Second) / tps 107 } 108 109 return count 110 } 111 112 func updateFPSAndTPS(now int64, count int) { 113 fpsCount++ 114 tpsCount += count 115 if now < lastUpdated { 116 panic("clock: lastUpdated must be older than now") 117 } 118 if time.Second > time.Duration(now-lastUpdated) { 119 return 120 } 121 currentFPS = float64(fpsCount) * float64(time.Second) / float64(now-lastUpdated) 122 currentTPS = float64(tpsCount) * float64(time.Second) / float64(now-lastUpdated) 123 lastUpdated = now 124 fpsCount = 0 125 tpsCount = 0 126 } 127 128 const SyncWithFPS = -1 129 130 // Update updates the inner clock state and returns an integer value 131 // indicating how many times the game should update based on given tps. 132 // tps represents TPS (ticks per second). 133 // If tps is SyncWithFPS, Update always returns 1. 134 // If tps <= 0 and not SyncWithFPS, Update always returns 0. 135 // 136 // Update is expected to be called per frame. 137 func Update(tps int) int { 138 m.Lock() 139 defer m.Unlock() 140 141 n := now() 142 if lastNow > n { 143 // This ensures that now() must be monotonic (#875). 144 panic("clock: lastNow must be older than n") 145 } 146 lastNow = n 147 148 c := 0 149 if tps == SyncWithFPS { 150 c = 1 151 } else if tps > 0 { 152 c = calcCountFromTPS(int64(tps), n) 153 } 154 updateFPSAndTPS(n, c) 155 156 return c 157 }