clock.go (3217B)
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 calcCountFromTPS(tps int64, now int64) int { 61 if tps == 0 { 62 return 0 63 } 64 if tps < 0 { 65 panic("clock: tps must >= 0") 66 } 67 68 diff := now - lastSystemTime 69 if diff < 0 { 70 return 0 71 } 72 73 count := 0 74 syncWithSystemClock := false 75 76 if diff > int64(time.Second)*5/60 { 77 // The previous time is too old. 78 // Let's force to sync the game time with the system clock. 79 syncWithSystemClock = true 80 } else { 81 count = int(diff * tps / int64(time.Second)) 82 } 83 84 // Stabilize FPS. 85 // Without this adjustment, count can be unstable like 0, 2, 0, 2, ... 86 if count == 0 && (int64(time.Second)/tps/2) < diff { 87 count = 1 88 } 89 if count == 2 && (int64(time.Second)/tps*3/2) > diff { 90 count = 1 91 } 92 93 if syncWithSystemClock { 94 lastSystemTime = now 95 } else { 96 lastSystemTime += int64(count) * int64(time.Second) / tps 97 } 98 99 return count 100 } 101 102 func updateFPSAndTPS(now int64, count int) { 103 fpsCount++ 104 tpsCount += count 105 if now < lastUpdated { 106 panic("clock: lastUpdated must be older than now") 107 } 108 if time.Second > time.Duration(now-lastUpdated) { 109 return 110 } 111 currentFPS = float64(fpsCount) * float64(time.Second) / float64(now-lastUpdated) 112 currentTPS = float64(tpsCount) * float64(time.Second) / float64(now-lastUpdated) 113 lastUpdated = now 114 fpsCount = 0 115 tpsCount = 0 116 } 117 118 const UncappedTPS = -1 119 120 // Update updates the inner clock state and returns an integer value 121 // indicating how many times the game should update based on given tps. 122 // tps represents TPS (ticks per second). 123 // If tps is UncappedTPS, Update always returns 1. 124 // If tps <= 0 and not UncappedTPS, Update always returns 0. 125 // 126 // Update is expected to be called per frame. 127 func Update(tps int) int { 128 m.Lock() 129 defer m.Unlock() 130 131 n := now() 132 if lastNow > n { 133 // This ensures that now() must be monotonic (#875). 134 panic("clock: lastNow must be older than n") 135 } 136 lastNow = n 137 138 c := 0 139 if tps == UncappedTPS { 140 c = 1 141 } else if tps > 0 { 142 c = calcCountFromTPS(int64(tps), n) 143 } 144 updateFPSAndTPS(n, c) 145 146 return c 147 }