mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 19:28:57 +01:00
buffered: Bug fix: Race conndition on delayedCommands
This CL fixes the race condtion on delayedCommands, which can be accessed and set to nil at the same time. This CL separates some operations for delayedCommands into slow- paths and fast-paths, and use mutex only at slow-paths for performance. The implementation is based on sync.Once. Fixes #1195
This commit is contained in:
parent
1735dda586
commit
d9cf1095d4
@ -15,16 +15,18 @@
|
||||
package buffered
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var (
|
||||
needsToDelayCommandsV int32 = 1
|
||||
|
||||
// delayedCommands represents a queue for image operations that are ordered before the game starts
|
||||
// (BeginFrame). Before the game starts, the package shareable doesn't determine the minimum/maximum texture
|
||||
// sizes (#879).
|
||||
delayedCommands []func() error
|
||||
delayedCommands = []func() error{}
|
||||
|
||||
delayedCommandsM sync.Mutex
|
||||
delayedCommandsFlushed uint32
|
||||
)
|
||||
|
||||
func flushDelayedCommands() error {
|
||||
@ -39,7 +41,19 @@ func flushDelayedCommands() error {
|
||||
}
|
||||
|
||||
func getDelayedFuncsAndClear() []func() error {
|
||||
atomic.StoreInt32(&needsToDelayCommandsV, 0)
|
||||
if atomic.LoadUint32(&delayedCommandsFlushed) == 0 {
|
||||
// Outline the slow-path to expect the fast-path is inlined.
|
||||
return getDelayedFuncsAndClearSlow()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDelayedFuncsAndClearSlow() []func() error {
|
||||
delayedCommandsM.Lock()
|
||||
defer delayedCommandsM.Unlock()
|
||||
|
||||
if delayedCommandsFlushed == 0 {
|
||||
defer atomic.StoreUint32(&delayedCommandsFlushed, 1)
|
||||
|
||||
fs := make([]func() error, len(delayedCommands))
|
||||
copy(fs, delayedCommands)
|
||||
@ -47,6 +61,36 @@ func getDelayedFuncsAndClear() []func() error {
|
||||
return fs
|
||||
}
|
||||
|
||||
func needsToDelayCommands() bool {
|
||||
return atomic.LoadInt32(&needsToDelayCommandsV) != 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func tryAddDelayedCommand(f func(obj interface{}) error, ondelayed func() interface{}) bool {
|
||||
if atomic.LoadUint32(&delayedCommandsFlushed) == 0 {
|
||||
// Outline the slow-path to expect the fast-path is inlined.
|
||||
tryAddDelayedCommandSlow(f, ondelayed)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func tryAddDelayedCommandSlow(f func(obj interface{}) error, ondelayed func() interface{}) {
|
||||
delayedCommandsM.Lock()
|
||||
defer delayedCommandsM.Unlock()
|
||||
|
||||
if delayedCommandsFlushed == 0 {
|
||||
var obj interface{}
|
||||
if ondelayed != nil {
|
||||
obj = ondelayed()
|
||||
}
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
return f(obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func checkDelayedCommandsNil(fname string) {
|
||||
if delayedCommands != nil {
|
||||
panic("buffered: the command queue is not available yet at " + fname)
|
||||
}
|
||||
}
|
||||
|
@ -55,11 +55,10 @@ func NewImage(width, height int, volatile bool) *Image {
|
||||
}
|
||||
|
||||
func (i *Image) initialize(width, height int, volatile bool) {
|
||||
if needsToDelayCommands() {
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
if tryAddDelayedCommand(func(obj interface{}) error {
|
||||
i.initialize(width, height, volatile)
|
||||
return nil
|
||||
})
|
||||
}, nil) {
|
||||
return
|
||||
}
|
||||
i.img = mipmap.New(width, height, volatile)
|
||||
@ -74,11 +73,10 @@ func NewScreenFramebufferImage(width, height int) *Image {
|
||||
}
|
||||
|
||||
func (i *Image) initializeAsScreenFramebuffer(width, height int) {
|
||||
if needsToDelayCommands() {
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
if tryAddDelayedCommand(func(obj interface{}) error {
|
||||
i.initializeAsScreenFramebuffer(width, height)
|
||||
return nil
|
||||
})
|
||||
}, nil) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -116,11 +114,10 @@ func (i *Image) resolvePendingFill() {
|
||||
}
|
||||
|
||||
func (i *Image) MarkDisposed() {
|
||||
if needsToDelayCommands() {
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
if tryAddDelayedCommand(func(obj interface{}) error {
|
||||
i.MarkDisposed()
|
||||
return nil
|
||||
})
|
||||
}, nil) {
|
||||
return
|
||||
}
|
||||
i.invalidatePendingPixels()
|
||||
@ -128,9 +125,7 @@ func (i *Image) MarkDisposed() {
|
||||
}
|
||||
|
||||
func (img *Image) Pixels(x, y, width, height int) (pix []byte, err error) {
|
||||
if needsToDelayCommands() {
|
||||
panic("buffered: the command queue is not available yet at At")
|
||||
}
|
||||
checkDelayedCommandsNil("Pixels")
|
||||
|
||||
if !image.Rect(x, y, x+width, y+height).In(image.Rect(0, 0, img.width, img.height)) {
|
||||
return nil, fmt.Errorf("buffered: out of range")
|
||||
@ -165,18 +160,15 @@ func (img *Image) Pixels(x, y, width, height int) (pix []byte, err error) {
|
||||
}
|
||||
|
||||
func (i *Image) Dump(name string, blackbg bool) error {
|
||||
if needsToDelayCommands() {
|
||||
panic("buffered: the command queue is not available yet at Dump")
|
||||
}
|
||||
checkDelayedCommandsNil("Dump")
|
||||
return i.img.Dump(name, blackbg)
|
||||
}
|
||||
|
||||
func (i *Image) Fill(clr color.RGBA) {
|
||||
if needsToDelayCommands() {
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
if tryAddDelayedCommand(func(obj interface{}) error {
|
||||
i.Fill(clr)
|
||||
return nil
|
||||
})
|
||||
}, nil) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -191,13 +183,14 @@ func (i *Image) ReplacePixels(pix []byte, x, y, width, height int) error {
|
||||
panic(fmt.Sprintf("buffered: len(pix) was %d but must be %d", len(pix), l))
|
||||
}
|
||||
|
||||
if needsToDelayCommands() {
|
||||
if tryAddDelayedCommand(func(copied interface{}) error {
|
||||
i.ReplacePixels(copied.([]byte), x, y, width, height)
|
||||
return nil
|
||||
}, func() interface{} {
|
||||
copied := make([]byte, len(pix))
|
||||
copy(copied, pix)
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
i.ReplacePixels(copied, x, y, width, height)
|
||||
return nil
|
||||
})
|
||||
return copied
|
||||
}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -237,11 +230,10 @@ func (i *Image) replacePendingPixels(pix []byte, x, y, width, height int) {
|
||||
}
|
||||
|
||||
func (i *Image) CopyPixels(img *Image, x, y, width, height int) error {
|
||||
if needsToDelayCommands() {
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
if tryAddDelayedCommand(func(obj interface{}) error {
|
||||
i.CopyPixels(img, x, y, width, height)
|
||||
return nil
|
||||
})
|
||||
}, nil) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -269,11 +261,10 @@ func (i *Image) DrawImage(src *Image, bounds image.Rectangle, a, b, c, d, tx, ty
|
||||
Ty: ty,
|
||||
}
|
||||
|
||||
if needsToDelayCommands() {
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
if tryAddDelayedCommand(func(obj interface{}) error {
|
||||
i.drawImage(src, bounds, g, colorm, mode, filter)
|
||||
return nil
|
||||
})
|
||||
}, nil) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -306,12 +297,11 @@ func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16,
|
||||
}
|
||||
}
|
||||
|
||||
if needsToDelayCommands() {
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
if tryAddDelayedCommand(func(obj interface{}) error {
|
||||
// Arguments are not copied. Copying is the caller's responsibility.
|
||||
i.DrawTriangles(src, vertices, indices, colorm, mode, filter, address, shader, uniforms)
|
||||
return nil
|
||||
})
|
||||
}, nil) {
|
||||
return
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user