Upstream hajimehoshi/ebiten state of the touched files before 040fcc781c
This commit is contained in:
1 parent
ec545f506b
commit
87bac3749b
3 files changed
+2081
No files matched your search
@@ -0,0 +1,341 @@
|
||||
// Copyright 2018 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 audio
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
dummyContext struct {
|
||||
// suspendErr and resumeErr are the errors Suspend and Resume return, to simulate a device
|
||||
// which fails to suspend or resume.
|
||||
suspendErr error
|
||||
resumeErr error
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
dummyPlayer struct {
|
||||
r io.Reader
|
||||
playing bool
|
||||
volume float64
|
||||
|
||||
// eof is whether the source has been exhausted by playing through. Like the real players,
|
||||
// a player which has finished its source refuses to play until Seek resets this state.
|
||||
eof bool
|
||||
|
||||
// drained is whether the simulated device has output the data it had buffered when the
|
||||
// source was exhausted. See BufferedSize.
|
||||
drained bool
|
||||
|
||||
// readGen is incremented by PauseAndStopReading to stop the goroutine reading r.
|
||||
readGen int
|
||||
|
||||
// err is the first non-EOF error the source returned. Like the real players, a player
|
||||
// whose source failed stops for good and reports the error from Err.
|
||||
err error
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
)
|
||||
|
||||
func (c *dummyContext) NewPlayer(r io.Reader) player {
|
||||
return &dummyPlayer{
|
||||
r: r,
|
||||
volume: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dummyContext) MaxBufferSize() int {
|
||||
return 48000 * channelCount * bitDepthInBytesInt16 / 4
|
||||
}
|
||||
|
||||
func (c *dummyContext) Suspend() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.suspendErr
|
||||
}
|
||||
|
||||
func (c *dummyContext) Resume() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.resumeErr
|
||||
}
|
||||
|
||||
func (c *dummyContext) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *dummyPlayer) Pause() {
|
||||
p.mu.Lock()
|
||||
p.playing = false
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
func (p *dummyPlayer) Play() {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.eof || p.err != nil {
|
||||
return
|
||||
}
|
||||
p.playing = true
|
||||
gen := p.readGen
|
||||
go func() {
|
||||
var buf [4096]byte
|
||||
for {
|
||||
stopped, err := p.readOnce(gen, buf[:])
|
||||
if stopped {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
// The source is exhausted only when it was played through. A paused player would still
|
||||
// have unplayed data in its buffer in a real player.
|
||||
if p.playing {
|
||||
p.eof = true
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// readOnce performs one read from the source with the mutex held, so that PauseAndStopReading waits
|
||||
// for an in-flight read. stopped reports that reading must not continue: PauseAndStopReading was
|
||||
// called, or the source failed. err is io.EOF when the source is exhausted.
|
||||
func (p *dummyPlayer) readOnce(gen int, buf []byte) (stopped bool, err error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.readGen != gen {
|
||||
return true, nil
|
||||
}
|
||||
_, err = p.r.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
p.err = err
|
||||
p.playing = false
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (p *dummyPlayer) PauseAndStopReading() {
|
||||
p.mu.Lock()
|
||||
p.playing = false
|
||||
p.readGen++
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
func (p *dummyPlayer) IsPlaying() bool {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.playing
|
||||
}
|
||||
|
||||
func (p *dummyPlayer) Volume() float64 {
|
||||
// The volume is not shared with the goroutine Play spawns, but the mutex is held so that the
|
||||
// test double is uniformly safe like the real player it stands in for.
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.volume
|
||||
}
|
||||
|
||||
func (p *dummyPlayer) SetVolume(volume float64) {
|
||||
// See Volume for why the mutex is held.
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.volume = volume
|
||||
}
|
||||
|
||||
// BufferedSize always reports an empty buffer, as the simulated device consumes the data as soon as
|
||||
// it is read.
|
||||
//
|
||||
// The context calls this exactly once per tick, before it reads the position and checks IsPlaying.
|
||||
// The player keeps playing for one more tick after its source is exhausted, so that the position
|
||||
// reaches its final value while the player is still playing, as a real player which still has
|
||||
// buffered data to output.
|
||||
func (p *dummyPlayer) BufferedSize() int {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.eof {
|
||||
if p.drained {
|
||||
p.playing = false
|
||||
}
|
||||
p.drained = true
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p *dummyPlayer) Err() error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.err
|
||||
}
|
||||
|
||||
func (p *dummyPlayer) SetBufferSize(bufferSize int) {
|
||||
}
|
||||
|
||||
func (p *dummyPlayer) Seek(offset int64, whence int) (int64, error) {
|
||||
// Seeking discards the buffered data and resets the finished state as real players do, so
|
||||
// that the source can be played again.
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.eof = false
|
||||
p.drained = false
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var dummyContextForTesting = &dummyContext{}
|
||||
|
||||
func init() {
|
||||
driverForTesting = dummyContextForTesting
|
||||
}
|
||||
|
||||
// SetSuspendErrorForTesting makes the simulated device fail to suspend with err, or succeed when err
|
||||
// is nil.
|
||||
func SetSuspendErrorForTesting(err error) {
|
||||
c := dummyContextForTesting
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.suspendErr = err
|
||||
}
|
||||
|
||||
// SetResumeErrorForTesting makes the simulated device fail to resume with err, or succeed when err is
|
||||
// nil.
|
||||
func SetResumeErrorForTesting(err error) {
|
||||
c := dummyContextForTesting
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.resumeErr = err
|
||||
}
|
||||
|
||||
type dummyHook struct {
|
||||
onSuspend func() error
|
||||
onResume func() error
|
||||
updates []func(vmGuest bool) error
|
||||
}
|
||||
|
||||
func (h *dummyHook) OnSuspendAudio(f func() error) {
|
||||
h.onSuspend = f
|
||||
}
|
||||
|
||||
func (h *dummyHook) OnResumeAudio(f func() error) {
|
||||
h.onResume = f
|
||||
}
|
||||
|
||||
func (h *dummyHook) AppendHookOnBeforeUpdateWithVMGuestInfo(f func(vmGuest bool) error) {
|
||||
h.updates = append(h.updates, f)
|
||||
}
|
||||
|
||||
func init() {
|
||||
hookerForTesting = &dummyHook{}
|
||||
}
|
||||
|
||||
// SuspendForTesting runs the suspend hook the current context registered.
|
||||
func SuspendForTesting() error {
|
||||
return hookerForTesting.(*dummyHook).onSuspend()
|
||||
}
|
||||
|
||||
// ResumeForTesting runs the resume hook the current context registered.
|
||||
func ResumeForTesting() error {
|
||||
return hookerForTesting.(*dummyHook).onResume()
|
||||
}
|
||||
|
||||
func UpdateForTesting() error {
|
||||
for _, f := range hookerForTesting.(*dummyHook).updates {
|
||||
if err := f(false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func PlayersCountForTesting() int {
|
||||
c := CurrentContext()
|
||||
c.m.Lock()
|
||||
n := len(c.playingPlayers)
|
||||
c.m.Unlock()
|
||||
return n
|
||||
}
|
||||
|
||||
func EnsurePlayerForTesting(p *Player) error {
|
||||
// ensurePlayer accesses player state with its caller holding the mutex, as Play and startIfPending do.
|
||||
p.p.m.Lock()
|
||||
defer p.p.m.Unlock()
|
||||
return p.p.ensurePlayer()
|
||||
}
|
||||
|
||||
func BufferSizeForTesting(p *Player) int {
|
||||
// initBufferSize is protected by the player mutex and can change when the underlying player is created.
|
||||
p.p.m.Lock()
|
||||
defer p.p.m.Unlock()
|
||||
return p.p.initBufferSize
|
||||
}
|
||||
|
||||
// PlayingButUntrackedForTesting reports whether the player is playing but is not tracked by its
|
||||
// context as a playing player. This must never be true: an untracked playing player is not
|
||||
// updated by the context and is not guarded from being garbage-collected.
|
||||
//
|
||||
// The player's lock is held across both checks, so that a player which is being started or
|
||||
// stopped concurrently is not reported.
|
||||
func PlayingButUntrackedForTesting(p *Player) bool {
|
||||
pi := p.p
|
||||
|
||||
// isPlaying requires its caller to hold the player mutex. Keep it held across both checks.
|
||||
pi.m.Lock()
|
||||
defer pi.m.Unlock()
|
||||
|
||||
if pi.closed || !pi.isPlaying() {
|
||||
return false
|
||||
}
|
||||
|
||||
c := pi.context
|
||||
// The context mutex protects playingPlayers from concurrent additions and removals.
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
_, ok := c.playingPlayers[pi]
|
||||
return !ok
|
||||
}
|
||||
|
||||
// ContextCreatedForTesting reports whether the underlying audio device has been created.
|
||||
func ContextCreatedForTesting() bool {
|
||||
c := CurrentContext()
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
return c.playerFactory.currentContext() != nil
|
||||
}
|
||||
|
||||
func ResetContextForTesting() {
|
||||
theContextLock.Lock()
|
||||
defer theContextLock.Unlock()
|
||||
theContext = nil
|
||||
}
|
||||
|
||||
func (i *InfiniteLoop) SetNoBlendForTesting(value bool) {
|
||||
i.noBlendForTesting = value
|
||||
}
|
||||
|
||||
// NewTimeStreamForTesting returns the stream a Player reads from, over a seekable src in signed 16bit
|
||||
// integer stereo.
|
||||
func NewTimeStreamForTesting(src io.ReadSeeker, sampleRate int) (io.ReadSeeker, error) {
|
||||
return newTimeStream(src, true, sampleRate, bitDepthInBytesInt16)
|
||||
}
|
||||
Reference in new issue
Block a user