ebiten: Add AppendInputChars, AppendGamepadIDs, and AppendTouchIDs

These functions reduce unnecessary allocations of arrays.

Closes #1692
This commit is contained in:
Hajime Hoshi 2021-07-10 02:03:48 +09:00
parent 99a6b1b03e
commit 431cd33839
9 changed files with 100 additions and 55 deletions

View File

@ -83,6 +83,7 @@ type Input struct {
mouseInitPosY int mouseInitPosY int
mouseDir Dir mouseDir Dir
touches []ebiten.TouchID
touchState touchState touchState touchState
touchID ebiten.TouchID touchID ebiten.TouchID
touchInitPosX int touchInitPosX int
@ -147,12 +148,13 @@ func (i *Input) Update() {
case mouseStateSettled: case mouseStateSettled:
i.mouseState = mouseStateNone i.mouseState = mouseStateNone
} }
i.touches = ebiten.AppendTouchIDs(i.touches[:0])
switch i.touchState { switch i.touchState {
case touchStateNone: case touchStateNone:
ts := ebiten.TouchIDs() if len(i.touches) == 1 {
if len(ts) == 1 { i.touchID = i.touches[0]
i.touchID = ts[0] x, y := ebiten.TouchPosition(i.touches[0])
x, y := ebiten.TouchPosition(ts[0])
i.touchInitPosX = x i.touchInitPosX = x
i.touchInitPosY = y i.touchInitPosY = y
i.touchLastPosX = x i.touchLastPosX = x
@ -160,21 +162,20 @@ func (i *Input) Update() {
i.touchState = touchStatePressing i.touchState = touchStatePressing
} }
case touchStatePressing: case touchStatePressing:
ts := ebiten.TouchIDs() if len(i.touches) >= 2 {
if len(ts) >= 2 {
break break
} }
if len(ts) == 1 { if len(i.touches) == 1 {
if ts[0] != i.touchID { if i.touches[0] != i.touchID {
i.touchState = touchStateInvalid i.touchState = touchStateInvalid
} else { } else {
x, y := ebiten.TouchPosition(ts[0]) x, y := ebiten.TouchPosition(i.touches[0])
i.touchLastPosX = x i.touchLastPosX = x
i.touchLastPosY = y i.touchLastPosY = y
} }
break break
} }
if len(ts) == 0 { if len(i.touches) == 0 {
dx := i.touchLastPosX - i.touchInitPosX dx := i.touchLastPosX - i.touchInitPosX
dy := i.touchLastPosY - i.touchInitPosY dy := i.touchLastPosY - i.touchInitPosY
d, ok := vecToDir(dx, dy) d, ok := vecToDir(dx, dy)
@ -188,7 +189,7 @@ func (i *Input) Update() {
case touchStateSettled: case touchStateSettled:
i.touchState = touchStateNone i.touchState = touchStateNone
case touchStateInvalid: case touchStateInvalid:
if len(ebiten.TouchIDs()) == 0 { if len(i.touches) == 0 {
i.touchState = touchStateNone i.touchState = touchStateNone
} }
} }

View File

@ -21,6 +21,7 @@ import (
// Input manages the input state including gamepads and keyboards. // Input manages the input state including gamepads and keyboards.
type Input struct { type Input struct {
gamepadIDs []ebiten.GamepadID
virtualGamepadButtonStates map[virtualGamepadButton]int virtualGamepadButtonStates map[virtualGamepadButton]int
gamepadConfig gamepadConfig gamepadConfig gamepadConfig
} }
@ -28,7 +29,8 @@ type Input struct {
// GamepadIDButtonPressed returns a gamepad ID where at least one button is pressed. // GamepadIDButtonPressed returns a gamepad ID where at least one button is pressed.
// If no button is pressed, GamepadIDButtonPressed returns -1. // If no button is pressed, GamepadIDButtonPressed returns -1.
func (i *Input) GamepadIDButtonPressed() ebiten.GamepadID { func (i *Input) GamepadIDButtonPressed() ebiten.GamepadID {
for _, id := range ebiten.GamepadIDs() { i.gamepadIDs = ebiten.AppendGamepadIDs(i.gamepadIDs[:0])
for _, id := range i.gamepadIDs {
for b := ebiten.GamepadButton(0); b <= ebiten.GamepadButtonMax; b++ { for b := ebiten.GamepadButton(0); b <= ebiten.GamepadButtonMax; b++ {
if ebiten.IsGamepadButtonPressed(id, b) { if ebiten.IsGamepadButtonPressed(id, b) {
return id return id

View File

@ -176,6 +176,8 @@ type Game struct {
pipeTileYs []int pipeTileYs []int
gameoverCount int gameoverCount int
gamepadIDs []ebiten.GamepadID
} }
func NewGame() *Game { func NewGame() *Game {
@ -204,7 +206,7 @@ func isAnyKeyJustPressed() bool {
return false return false
} }
func jump() bool { func (g *Game) jump() bool {
if isAnyKeyJustPressed() { if isAnyKeyJustPressed() {
return true return true
} }
@ -214,7 +216,8 @@ func jump() bool {
if len(inpututil.JustPressedTouchIDs()) > 0 { if len(inpututil.JustPressedTouchIDs()) > 0 {
return true return true
} }
for _, g := range ebiten.GamepadIDs() { g.gamepadIDs = ebiten.AppendGamepadIDs(g.gamepadIDs[:0])
for _, g := range g.gamepadIDs {
for i := 0; i < ebiten.GamepadButtonNum(g); i++ { for i := 0; i < ebiten.GamepadButtonNum(g); i++ {
if inpututil.IsGamepadButtonJustPressed(g, ebiten.GamepadButton(i)) { if inpututil.IsGamepadButtonJustPressed(g, ebiten.GamepadButton(i)) {
return true return true
@ -231,13 +234,13 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
func (g *Game) Update() error { func (g *Game) Update() error {
switch g.mode { switch g.mode {
case ModeTitle: case ModeTitle:
if jump() { if g.jump() {
g.mode = ModeGame g.mode = ModeGame
} }
case ModeGame: case ModeGame:
g.x16 += 32 g.x16 += 32
g.cameraX += 2 g.cameraX += 2
if jump() { if g.jump() {
g.vy16 = -96 g.vy16 = -96
jumpPlayer.Rewind() jumpPlayer.Rewind()
jumpPlayer.Play() jumpPlayer.Play()
@ -260,7 +263,7 @@ func (g *Game) Update() error {
if g.gameoverCount > 0 { if g.gameoverCount > 0 {
g.gameoverCount-- g.gameoverCount--
} }
if g.gameoverCount == 0 && jump() { if g.gameoverCount == 0 && g.jump() {
g.init() g.init()
g.mode = ModeTitle g.mode = ModeTitle
} }

View File

@ -60,7 +60,8 @@ func init() {
} }
type Game struct { type Game struct {
count int touches []ebiten.TouchID
count int
} }
func (g *Game) Update() error { func (g *Game) Update() error {
@ -74,7 +75,8 @@ func (g *Game) Update() error {
} }
// Paint the brush by touches // Paint the brush by touches
for _, t := range ebiten.TouchIDs() { g.touches = ebiten.AppendTouchIDs(g.touches[:0])
for _, t := range g.touches {
x, y := ebiten.TouchPosition(t) x, y := ebiten.TouchPosition(t)
g.paint(canvasImage, x, y) g.paint(canvasImage, x, y)
drawn = true drawn = true
@ -102,7 +104,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
mx, my := ebiten.CursorPosition() mx, my := ebiten.CursorPosition()
msg := fmt.Sprintf("(%d, %d)", mx, my) msg := fmt.Sprintf("(%d, %d)", mx, my)
for _, t := range ebiten.TouchIDs() { for _, t := range g.touches {
x, y := ebiten.TouchPosition(t) x, y := ebiten.TouchPosition(t)
msg += fmt.Sprintf("\n(%d, %d) touch %d", x, y, t) msg += fmt.Sprintf("\n(%d, %d) touch %d", x, y, t)
} }

View File

@ -115,9 +115,10 @@ const (
) )
type Game struct { type Game struct {
sprites Sprites touchIDs []ebiten.TouchID
op ebiten.DrawImageOptions sprites Sprites
inited bool op ebiten.DrawImageOptions
inited bool
} }
func (g *Game) init() { func (g *Game) init() {
@ -144,8 +145,8 @@ func (g *Game) init() {
} }
} }
func leftTouched() bool { func (g *Game) leftTouched() bool {
for _, id := range ebiten.TouchIDs() { for _, id := range g.touchIDs {
x, _ := ebiten.TouchPosition(id) x, _ := ebiten.TouchPosition(id)
if x < screenWidth/2 { if x < screenWidth/2 {
return true return true
@ -154,8 +155,8 @@ func leftTouched() bool {
return false return false
} }
func rightTouched() bool { func (g *Game) rightTouched() bool {
for _, id := range ebiten.TouchIDs() { for _, id := range g.touchIDs {
x, _ := ebiten.TouchPosition(id) x, _ := ebiten.TouchPosition(id)
if x >= screenWidth/2 { if x >= screenWidth/2 {
return true return true
@ -168,9 +169,10 @@ func (g *Game) Update() error {
if !g.inited { if !g.inited {
g.init() g.init()
} }
g.touchIDs = ebiten.AppendTouchIDs(g.touchIDs[:0])
// Decrease the number of the sprites. // Decrease the number of the sprites.
if ebiten.IsKeyPressed(ebiten.KeyArrowLeft) || leftTouched() { if ebiten.IsKeyPressed(ebiten.KeyArrowLeft) || g.leftTouched() {
g.sprites.num -= 20 g.sprites.num -= 20
if g.sprites.num < MinSprites { if g.sprites.num < MinSprites {
g.sprites.num = MinSprites g.sprites.num = MinSprites
@ -178,7 +180,7 @@ func (g *Game) Update() error {
} }
// Increase the number of the sprites. // Increase the number of the sprites.
if ebiten.IsKeyPressed(ebiten.KeyArrowRight) || rightTouched() { if ebiten.IsKeyPressed(ebiten.KeyArrowRight) || g.rightTouched() {
g.sprites.num += 20 g.sprites.num += 20
if MaxSprites < g.sprites.num { if MaxSprites < g.sprites.num {
g.sprites.num = MaxSprites g.sprites.num = MaxSprites

View File

@ -74,10 +74,11 @@ type Game struct {
x, y float64 x, y float64
zoom float64 zoom float64
touches map[ebiten.TouchID]*touch touchIDs []ebiten.TouchID
pinch *pinch touches map[ebiten.TouchID]*touch
pan *pan pinch *pinch
taps []tap pan *pan
taps []tap
} }
func (g *Game) Update() error { func (g *Game) Update() error {
@ -111,15 +112,17 @@ func (g *Game) Update() error {
// What touches are new in this frame? // What touches are new in this frame?
for _, id := range inpututil.JustPressedTouchIDs() { for _, id := range inpututil.JustPressedTouchIDs() {
x, y := ebiten.TouchPosition(id) x, y := ebiten.TouchPosition(id)
g.touches[ebiten.TouchID(id)] = &touch{ g.touches[id] = &touch{
originX: x, originY: y, originX: x, originY: y,
currX: x, currY: y, currX: x, currY: y,
} }
} }
g.touchIDs = ebiten.AppendTouchIDs(g.touchIDs[:0])
// Update the current position and durations of any touches that have // Update the current position and durations of any touches that have
// neither begun nor ended in this frame. // neither begun nor ended in this frame.
for _, id := range ebiten.TouchIDs() { for _, id := range g.touchIDs {
t := g.touches[id] t := g.touches[id]
t.duration = inpututil.TouchPressDuration(id) t.duration = inpututil.TouchPressDuration(id)
t.currX, t.currY = ebiten.TouchPosition(id) t.currX, t.currY = ebiten.TouchPosition(id)
@ -133,7 +136,7 @@ func (g *Game) Update() error {
// If the diff between their origins is different to the diff between // If the diff between their origins is different to the diff between
// their currents and if these two are not already a pinch, then this is // their currents and if these two are not already a pinch, then this is
// a new pinch! // a new pinch!
id1, id2 := ebiten.TouchIDs()[0], ebiten.TouchIDs()[1] id1, id2 := g.touchIDs[0], g.touchIDs[1]
t1, t2 := g.touches[id1], g.touches[id2] t1, t2 := g.touches[id1], g.touches[id2]
originDiff := distance(t1.originX, t1.originY, t2.originX, t2.originY) originDiff := distance(t1.originX, t1.originY, t2.originX, t2.originY)
currDiff := distance(t1.currX, t1.currY, t2.currX, t2.currY) currDiff := distance(t1.currX, t1.currY, t2.currX, t2.currY)
@ -149,7 +152,7 @@ func (g *Game) Update() error {
} }
case 1: case 1:
// Potentially this is a new pan. // Potentially this is a new pan.
id := ebiten.TouchIDs()[0] id := g.touchIDs[0]
t := g.touches[id] t := g.touches[id]
if !t.wasPinch && g.pan == nil && g.pinch == nil { if !t.wasPinch && g.pan == nil && g.pinch == nil {
diff := math.Abs(distance(t.originX, t.originY, t.currX, t.currY)) diff := math.Abs(distance(t.originX, t.originY, t.currX, t.currY))

View File

@ -48,15 +48,17 @@ func repeatingKeyPressed(key ebiten.Key) bool {
} }
type Game struct { type Game struct {
runes []rune
text string text string
counter int counter int
} }
func (g *Game) Update() error { func (g *Game) Update() error {
// Add a string from InputChars, that returns string input by users. // Add a string by AppendInputChars, that appends runes input by users.
// Note that InputChars result changes every frame, so you need to call this // Note that AppendInputChars result changes every frame, so you need to call this
// every frame. // every frame.
g.text += string(ebiten.InputChars()) g.runes = ebiten.AppendInputChars(g.runes[:0])
g.text += string(g.runes)
// Adjust the string to be at most 10 lines. // Adjust the string to be at most 10 lines.
ss := strings.Split(g.text, "\n") ss := strings.Split(g.text, "\n")

View File

@ -18,21 +18,30 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
) )
// InputChars return "printable" runes read from the keyboard at the time update is called. // AppendInputChars appends "printable" runes, read from the keyboard at the time update is called, to runes,
// and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
// //
// InputChars represents the environment's locale-dependent translation of keyboard // AppendInputChars represents the environment's locale-dependent translation of keyboard
// input to Unicode characters. // input to Unicode characters.
// //
// IsKeyPressed is based on a mapping of device (US keyboard) codes to input device keys. // IsKeyPressed is based on a mapping of device (US keyboard) codes to input device keys.
// "Control" and modifier keys should be handled with IsKeyPressed. // "Control" and modifier keys should be handled with IsKeyPressed.
// //
// InputChars is concurrent-safe. // AppendInputChars is concurrent-safe.
// //
// On Android (ebitenmobile), EbitenView must be focusable to enable to handle keyboard keys. // On Android (ebitenmobile), EbitenView must be focusable to enable to handle keyboard keys.
// //
// Keyboards don't work on iOS yet (#1090). // Keyboards don't work on iOS yet (#1090).
func AppendInputChars(runes []rune) []rune {
return uiDriver().Input().AppendInputChars(runes)
}
// InputChars return "printable" runes read from the keyboard at the time update is called.
//
// Deprecated: as of v2.2.0. Use AppendInputChars instead.
func InputChars() []rune { func InputChars() []rune {
return uiDriver().Input().AppendInputChars(nil) return AppendInputChars(nil)
} }
// IsKeyPressed returns a boolean indicating whether key is pressed. // IsKeyPressed returns a boolean indicating whether key is pressed.
@ -134,13 +143,21 @@ func GamepadName(id GamepadID) string {
return uiDriver().Input().GamepadName(id) return uiDriver().Input().GamepadName(id)
} }
// AppendGamepadIDs appends available gamepad IDs to gamepadIDs, and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendGamepadIDs is concurrent-safe.
//
// AppendGamepadIDs doesn't append anything on iOS.
func AppendGamepadIDs(gamepadIDs []GamepadID) []GamepadID {
return uiDriver().Input().AppendGamepadIDs(gamepadIDs)
}
// GamepadIDs returns a slice indicating available gamepad IDs. // GamepadIDs returns a slice indicating available gamepad IDs.
// //
// GamepadIDs is concurrent-safe. // Deprecated: as of v2.2.0. Use AppendGamepadIDs instead.
//
// GamepadIDs always returns an empty slice on iOS.
func GamepadIDs() []GamepadID { func GamepadIDs() []GamepadID {
return uiDriver().Input().AppendGamepadIDs(nil) return AppendGamepadIDs(nil)
} }
// GamepadAxisNum returns the number of axes of the gamepad (id). // GamepadAxisNum returns the number of axes of the gamepad (id).
@ -188,17 +205,25 @@ func IsGamepadButtonPressed(id GamepadID, button GamepadButton) bool {
// TouchID represents a touch's identifier. // TouchID represents a touch's identifier.
type TouchID = driver.TouchID type TouchID = driver.TouchID
// TouchIDs returns the current touch states. // AppendTouchIDs appends the current touch states to touches, and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
// //
// If you want to know whether a touch started being pressed in the current frame, // If you want to know whether a touch started being pressed in the current frame,
// use inpututil.JustPressedTouchIDs // use inpututil.JustPressedTouchIDs
// //
// TouchIDs returns nil when there are no touches. // AppendTouchIDs doesn't append anything when there are no touches.
// TouchIDs always returns nil on desktops. // AppendTouchIDs always does nothing on desktops.
// //
// TouchIDs is concurrent-safe. // AppendTouchIDs is concurrent-safe.
func AppendTouchIDs(touches []TouchID) []TouchID {
return uiDriver().Input().AppendTouchIDs(touches)
}
// TouchIDs returns the current touch states.
//
// Deperecated: as of v2.2.0. Use AppendTouchIDs instead.
func TouchIDs() []TouchID { func TouchIDs() []TouchID {
return uiDriver().Input().AppendTouchIDs(nil) return AppendTouchIDs(nil)
} }
// TouchPosition returns the position for the touch of the specified ID. // TouchPosition returns the position for the touch of the specified ID.

View File

@ -40,6 +40,9 @@ type inputState struct {
touchDurations map[ebiten.TouchID]int touchDurations map[ebiten.TouchID]int
prevTouchDurations map[ebiten.TouchID]int prevTouchDurations map[ebiten.TouchID]int
gamepadIDsBuf []ebiten.GamepadID
touchIDsBuf []ebiten.TouchID
m sync.RWMutex m sync.RWMutex
} }
@ -117,7 +120,8 @@ func (i *inputState) update() {
for id := range i.gamepadIDs { for id := range i.gamepadIDs {
delete(i.gamepadIDs, id) delete(i.gamepadIDs, id)
} }
for _, id := range ebiten.GamepadIDs() { i.gamepadIDsBuf = ebiten.AppendGamepadIDs(i.gamepadIDsBuf[:0])
for _, id := range i.gamepadIDsBuf {
i.gamepadIDs[id] = struct{}{} i.gamepadIDs[id] = struct{}{}
if _, ok := i.gamepadButtonDurations[id]; !ok { if _, ok := i.gamepadButtonDurations[id]; !ok {
i.gamepadButtonDurations[id] = make([]int, ebiten.GamepadButtonMax+1) i.gamepadButtonDurations[id] = make([]int, ebiten.GamepadButtonMax+1)
@ -150,7 +154,8 @@ func (i *inputState) update() {
for id := range i.touchIDs { for id := range i.touchIDs {
delete(i.touchIDs, id) delete(i.touchIDs, id)
} }
for _, id := range ebiten.TouchIDs() { i.touchIDsBuf = ebiten.AppendTouchIDs(i.touchIDsBuf[:0])
for _, id := range i.touchIDsBuf {
i.touchIDs[id] = struct{}{} i.touchIDs[id] = struct{}{}
i.touchDurations[id]++ i.touchDurations[id]++
} }