// Copyright 2017 The Ebiten Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package clock manages game timers. // // There are two types of clocks internally: // // System clock: // A clock offered by the OS. // // Audio clock: // An audio clock that is used in the higher priority over the system clock. // An audio clock might not exist when the audio is not used. package clock import ( "time" "github.com/hajimehoshi/ebiten/internal/sync" ) const FPS = 60 var ( frames int64 audioTimeInFrames int64 lastAudioTimeInFrames int64 // lastSystemTime is the last system time in the previous Update. lastSystemTime int64 currentFPS float64 lastFPSUpdated int64 framesForFPS int64 ping func() m sync.Mutex ) func CurrentFPS() float64 { m.Lock() v := currentFPS m.Unlock() return v } func RegisterPing(pingFunc func()) { m.Lock() ping = pingFunc m.Unlock() } // ProceedAudioTimer increments the audio time by the given number of frames. func ProceedAudioTimer(num int64) { m.Lock() audioTimeInFrames += num m.Unlock() } func updateFPS(now int64) { if lastFPSUpdated == 0 { lastFPSUpdated = now } framesForFPS++ if time.Second > time.Duration(now-lastFPSUpdated) { return } currentFPS = float64(framesForFPS) * float64(time.Second) / float64(now-lastFPSUpdated) lastFPSUpdated = now framesForFPS = 0 } // Update updates the inner clock state and returns an integer value // indicating how many game frames the game should update. func Update() int { m.Lock() defer m.Unlock() n := now() if ping != nil { ping() } // Initialize lastSystemTime if needed. if lastSystemTime == 0 { lastSystemTime = n } diff := n - lastSystemTime if diff < 0 { return 0 } count := 0 syncWithSystemClock := false if audioTimeInFrames > 0 && lastAudioTimeInFrames != audioTimeInFrames { // If the audio clock is updated, use this. if frames < audioTimeInFrames { count = int(audioTimeInFrames - frames) } lastAudioTimeInFrames = audioTimeInFrames // Now the current lastSystemTime value is not meaningful, // force to sync lastSystemTime with the system timer. syncWithSystemClock = true } else { // Use system clock when the audio clock is not updated yet. // As the audio clock can be updated discountinuously, // the system clock is still needed. if diff > 5*int64(time.Second)/FPS { // The previous time is too old. // Let's force to sync the game time with the system clock. syncWithSystemClock = true } else { count = int(diff * FPS / int64(time.Second)) } } // Stabilize FPS. // Without this adjustment, count can be unstable like 0, 2, 0, 2, ... if count == 0 && (int64(time.Second)/FPS/2) < diff { count = 1 } if count == 2 && (int64(time.Second)/FPS*3/2) > diff { count = 1 } frames += int64(count) if syncWithSystemClock { lastSystemTime = n } else { lastSystemTime += int64(count) * int64(time.Second) / FPS } updateFPS(n) return count }