Skip to content
This repository was archived by the owner on Sep 24, 2022. It is now read-only.

Commit 8f3e2ed

Browse files
authored
Remove memory leak (#61)
* Integrate Go profiler * Exclude profile logs * Remove memory leak * Remove obsolete API call
1 parent 2ab5fd4 commit 8f3e2ed

File tree

12 files changed

+68
-28
lines changed

12 files changed

+68
-28
lines changed

.dockerignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
*.md
33
/build
44
*.md
5+
/perf

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33

44
# Build
55
/build
6+
7+
# Performance diagnostic files
8+
/perf

game/gamemap/map.go

-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ type Map struct {
5454

5555
func (m Map) DrawMap() {
5656
m.batch.DrawSprite(m.screenX, m.screenY, math.MaxInt32, mapBound, 1)
57-
m.batch.RenderBatch()
5857
}
5958

6059
func (m Map) DrawGrid(batch graphics.Batch) {

graphics/ebiten.go

+20-21
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ type Ebiten struct {
1919
reverseY bool
2020
texts []*ebitenText
2121
batches []*ebitenBatch
22+
buffer *ebiten.Image
2223
}
2324

24-
func (e Ebiten) Clear() {
25-
e.texts = make([]*ebitenText, 0)
26-
e.batches = make([]*ebitenBatch, 0)
25+
func (e *Ebiten) initBuffer(width int, height int) {
26+
e.buffer = ebiten.NewImage(width, height)
2727
}
2828

29-
func (e *Ebiten) NewText(face font.Face, x int, y int, width int, height int, scale float64, alignment alignment) Text {
29+
func (e *Ebiten) NewText(face font.Face, x int, y int, width int, height int, _ float64, alignment alignment) Text {
3030
buf := bytes.Buffer{}
31-
return &ebitenText{
31+
txt := &ebitenText{
3232
x: x,
3333
y: y,
3434
width: width,
@@ -38,9 +38,11 @@ func (e *Ebiten) NewText(face font.Face, x int, y int, width int, height int, sc
3838
graphics: e,
3939
alignment: alignment,
4040
}
41+
e.texts = append(e.texts, txt)
42+
return txt
4143
}
4244

43-
func (e *Ebiten) RenderTexts(screen *ebiten.Image) {
45+
func (e *Ebiten) RenderTexts(target *ebiten.Image) {
4446
for _, t := range e.texts {
4547
bound := text.BoundString(t.fontFace, t.textContent)
4648
width := float64(bound.Max.X - bound.Min.X)
@@ -49,17 +51,19 @@ func (e *Ebiten) RenderTexts(screen *ebiten.Image) {
4951
x := float64(t.x) + float64(t.width)/2 - width/2
5052
y := float64(t.y) + float64(t.height)/2 - height/2
5153

52-
adjustedY := adjustY(e.reverseY, screen, int(y), 0)
54+
adjustedY := adjustY(e.reverseY, target, int(y), 0)
5355

5456
switch t.alignment {
5557
case AlignCenter:
56-
text.Draw(screen, t.textContent, t.fontFace, int(x), adjustedY, color.White)
58+
text.Draw(target, t.textContent, t.fontFace, int(x), adjustedY, color.White)
5759
}
5860
}
5961
}
6062

6163
func (e *Ebiten) StartNewBatch(spriteSheet image.Image) Batch {
62-
return newEbitenBatch(ebiten.NewImageFromImage(spriteSheet), e)
64+
batch := newEbitenBatch(ebiten.NewImageFromImage(spriteSheet), e)
65+
e.batches = append(e.batches, batch)
66+
return batch
6367
}
6468

6569
func (e Ebiten) SetCursorVisible(isVisible bool) {
@@ -72,9 +76,10 @@ func (e Ebiten) SetCursorVisible(isVisible bool) {
7276

7377
func (e *Ebiten) Render(screen *ebiten.Image) {
7478
for _, batch := range e.batches {
75-
batch.renderBatch(screen)
79+
batch.renderBatch(e.buffer)
7680
}
77-
e.RenderTexts(screen)
81+
e.RenderTexts(e.buffer)
82+
screen.DrawImage(e.buffer, nil)
7883
}
7984

8085
func NewEbiten(reverseY bool) Ebiten {
@@ -102,11 +107,9 @@ func (t *ebitenText) Write(p []byte) (int, error) {
102107
}
103108

104109
func (t *ebitenText) Draw() {
105-
t.buf.Flush()
110+
_ = t.buf.Flush()
106111
buf, _ := ioutil.ReadAll(t.buf)
107112
t.textContent = string(buf)
108-
109-
t.graphics.texts = append(t.graphics.texts, t)
110113
}
111114

112115
var _ Batch = (*ebitenBatch)(nil)
@@ -117,11 +120,7 @@ type ebitenBatch struct {
117120
spritesDrawn []*spriteDrawn
118121
}
119122

120-
func (e *ebitenBatch) RenderBatch() {
121-
e.ebiten.batches = append(e.ebiten.batches, e)
122-
}
123-
124-
func (e *ebitenBatch) renderBatch(screen *ebiten.Image) {
123+
func (e *ebitenBatch) renderBatch(target *ebiten.Image) {
125124
sort.SliceStable(e.spritesDrawn, func(i, j int) bool {
126125
if e.ebiten.reverseY {
127126
return e.spritesDrawn[i].z > e.spritesDrawn[j].z
@@ -151,13 +150,13 @@ func (e *ebitenBatch) renderBatch(screen *ebiten.Image) {
151150
op.GeoM.Scale(spriteDrawn.scale, spriteDrawn.scale)
152151

153152
scaledHeight := float64(spriteDrawn.imageBound.Height) * spriteDrawn.scale
154-
adjustedY := adjustY(e.ebiten.reverseY, screen, spriteDrawn.y, scaledHeight)
153+
adjustedY := adjustY(e.ebiten.reverseY, target, spriteDrawn.y, scaledHeight)
155154
op.GeoM.Translate(
156155
float64(spriteDrawn.x),
157156
float64(adjustedY),
158157
)
159158

160-
screen.DrawImage(e.spriteSheet.SubImage(bound).(*ebiten.Image), op)
159+
target.DrawImage(e.spriteSheet.SubImage(bound).(*ebiten.Image), op)
161160
}
162161
e.spritesDrawn = make([]*spriteDrawn, 0)
163162
}

graphics/graphics.go

-2
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,9 @@ type Text interface {
2727

2828
type Batch interface {
2929
DrawSprite(x int, y int, z int, imageBound Bound, scale float64)
30-
RenderBatch()
3130
}
3231

3332
type Graphics interface {
34-
Clear()
3533
NewText(face font.Face, x int, y int, width int, height int, scale float64, alignment alignment) Text
3634
StartNewBatch(spriteSheet image.Image) Batch
3735
SetCursorVisible(isVisible bool)

graphics/window.go

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type EbitenWindow struct {
2929
func (e *EbitenWindow) Init() {
3030
ebiten.SetWindowSize(e.windowConfig.Width, e.windowConfig.Height)
3131
ebiten.SetWindowTitle(e.windowConfig.Title)
32+
e.ebiten.initBuffer(e.windowConfig.Width, e.windowConfig.Height)
3233
e.prevTime = time.Now()
3334
}
3435

main.go

+29
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package main
22

33
import (
4+
"flag"
45
"log"
6+
"os"
7+
"runtime/pprof"
58

69
"candy/assets"
710
"candy/graphics"
@@ -11,7 +14,33 @@ import (
1114
"github.com/hajimehoshi/ebiten/v2"
1215
)
1316

17+
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
18+
var memprofile = flag.String("memprofile", "", "write memory profile to this file")
19+
1420
func main() {
21+
flag.Parse()
22+
// Enable CPU profiling
23+
if *cpuprofile != "" {
24+
f, err := os.Create(*cpuprofile)
25+
if err != nil {
26+
log.Fatal(err)
27+
}
28+
defer f.Close()
29+
pprof.StartCPUProfile(f)
30+
defer pprof.StopCPUProfile()
31+
}
32+
33+
// Enable memory profiling
34+
if *memprofile != "" {
35+
f, err := os.Create(*memprofile)
36+
if err != nil {
37+
log.Fatal(err)
38+
}
39+
defer f.Close()
40+
pprof.WriteHeapProfile(f)
41+
return
42+
}
43+
1544
eb := graphics.NewEbiten(true)
1645

1746
ass, err := assets.LoadAssets("public")

screen/game.go

-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ func (g Game) Draw() {
4444

4545
g.backpack.Draw(g.spriteSheetBatch)
4646
g.rightSideBar.Draw(g.spriteSheetBatch)
47-
48-
g.spriteSheetBatch.RenderBatch()
4947
}
5048

5149
func (g Game) HandleInput(in input.Input) {

screen/signin.go

-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ func (s SignIn) Destroy() {
3939

4040
func (s SignIn) Draw() {
4141
s.batch.DrawSprite(0, 0, 1, signInBackgroundBound, 1)
42-
s.batch.RenderBatch()
4342
}
4443

4544
func (s SignIn) Update(timeElapsed time.Duration) {

scripts/perf-cpu

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
3+
rm -rf perf
4+
mkdir perf
5+
6+
go build -o build/main main.go
7+
./build/main --cpuprofile=perf/cpu.perf

scripts/perf-mem

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
3+
rm -rf perf
4+
mkdir perf
5+
6+
go build -o build/main main.go
7+
./build/main --memprofile=perf/memory.perf

ui/engine.go

-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ func (r RenderEngine) Draw() {
4747
Height: imageBound.Max.Y,
4848
}
4949
r.batch.DrawSprite(0, 0, 0, bound, 1)
50-
r.batch.RenderBatch()
5150
}
5251

5352
func (r RenderEngine) generateLayout(component Component) {

0 commit comments

Comments
 (0)