Skip to content

Commit 20b5bda

Browse files
authored
Add optional ark-repl server for monitoring (#268)
1 parent 19ae33b commit 20b5bda

File tree

6 files changed

+168
-69
lines changed

6 files changed

+168
-69
lines changed

game/game.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ import (
88

99
// Game container
1010
type Game struct {
11-
Model *app.App
11+
App *app.App
1212
Screen res.Screen
1313
Mouse res.Mouse
1414

1515
canvasHelper *canvasHelper
1616
}
1717

1818
// NewGame returns a new game
19-
func NewGame(mod *app.App) Game {
19+
func NewGame(app *app.App) Game {
2020
return Game{
21-
Model: mod,
21+
App: app,
2222
Screen: res.Screen{Image: nil, Width: 0, Height: 0},
2323
canvasHelper: newCanvasHelper(),
2424
}
@@ -48,7 +48,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
4848
// Update the game.
4949
func (g *Game) Update() error {
5050
g.updateMouse()
51-
g.Model.Update()
51+
g.App.Update()
5252
return nil
5353
}
5454

@@ -57,7 +57,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
5757
g.Screen.Image = screen
5858
g.Screen.Width = screen.Bounds().Dx()
5959
g.Screen.Height = screen.Bounds().Dy()
60-
g.Model.UpdateUI()
60+
g.App.UpdateUI()
6161
}
6262

6363
func (g *Game) updateMouse() {

game/run.go

Lines changed: 66 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,14 @@ func run(g *Game, name string, mapLoc save.MapLocation, load save.LoadType, isEd
4747

4848
func runMenu(g *Game, tab int) {
4949
ebiten.SetVsyncEnabled(true)
50-
g.Model = app.New()
50+
g.App = app.New()
5151

52-
ecs.AddResource(&g.Model.World, &g.Screen)
52+
ecs.AddResource(&g.App.World, &g.Screen)
5353

5454
sprites := res.NewSprites(GameData, "data/gfx", "paper")
55-
ecs.AddResource(&g.Model.World, &sprites)
55+
ecs.AddResource(&g.App.World, &sprites)
5656

57-
achievements := achievements.New(&g.Model.World, GameData, "data/json/achievements.json", "user/achievements.json")
57+
achievements := achievements.New(&g.App.World, GameData, "data/json/achievements.json", "user/achievements.json")
5858

5959
fonts := res.NewFonts(GameData)
6060
ui := menu.NewUI(GameData, saveFolder, mapsFolder, tab, &sprites, &fonts, achievements,
@@ -66,135 +66,135 @@ func runMenu(g *Game, tab int) {
6666
},
6767
)
6868

69-
ecs.AddResource(&g.Model.World, &ui)
69+
ecs.AddResource(&g.App.World, &ui)
7070

71-
g.Model.AddSystem(&menu.UpdateUI{})
72-
g.Model.AddUISystem(&menu.DrawUI{})
71+
g.App.AddSystem(&menu.UpdateUI{})
72+
g.App.AddUISystem(&menu.DrawUI{})
7373

74-
g.Model.Initialize()
74+
g.App.Initialize()
7575
}
7676

7777
func runGame(g *Game, load save.LoadType, name string, mapLoc save.MapLocation, tileSet string, isEditor bool) error {
7878
ebiten.SetVsyncEnabled(true)
7979

80-
g.Model = app.New()
80+
g.App = app.New()
8181

8282
// Register components for deserialization,
8383
// where it does not happen in systems already.
84-
_ = ecs.ComponentID[comp.CardAnimation](&g.Model.World)
84+
_ = ecs.ComponentID[comp.CardAnimation](&g.App.World)
8585

8686
// =========== Resources ===========
8787

8888
rules := res.NewRules(GameData, "data/json/rules.json")
89-
ecs.AddResource(&g.Model.World, &rules)
89+
ecs.AddResource(&g.App.World, &rules)
9090

9191
gameSpeed := res.GameSpeed{
9292
MinSpeed: -2,
9393
MaxSpeed: 3,
9494
}
95-
ecs.AddResource(&g.Model.World, &gameSpeed)
95+
ecs.AddResource(&g.App.World, &gameSpeed)
9696

9797
gameTick := res.GameTick{}
98-
ecs.AddResource(&g.Model.World, &gameTick)
98+
ecs.AddResource(&g.App.World, &gameTick)
9999

100100
terrain := res.NewTerrain(rules.WorldSize, rules.WorldSize)
101-
ecs.AddResource(&g.Model.World, &terrain)
101+
ecs.AddResource(&g.App.World, &terrain)
102102

103103
terrainEntities := res.TerrainEntities{Grid: res.NewGrid[ecs.Entity](rules.WorldSize, rules.WorldSize)}
104-
ecs.AddResource(&g.Model.World, &terrainEntities)
104+
ecs.AddResource(&g.App.World, &terrainEntities)
105105

106106
landUse := res.NewLandUse(rules.WorldSize, rules.WorldSize)
107-
ecs.AddResource(&g.Model.World, &landUse)
107+
ecs.AddResource(&g.App.World, &landUse)
108108

109109
landUseEntities := res.LandUseEntities{Grid: res.NewGrid[ecs.Entity](rules.WorldSize, rules.WorldSize)}
110-
ecs.AddResource(&g.Model.World, &landUseEntities)
110+
ecs.AddResource(&g.App.World, &landUseEntities)
111111

112112
buildable := res.NewBuildable(rules.WorldSize, rules.WorldSize)
113-
ecs.AddResource(&g.Model.World, &buildable)
113+
ecs.AddResource(&g.App.World, &buildable)
114114

115115
selection := res.Selection{}
116-
ecs.AddResource(&g.Model.World, &selection)
116+
ecs.AddResource(&g.App.World, &selection)
117117

118118
bounds := res.WorldBounds{}
119-
ecs.AddResource(&g.Model.World, &bounds)
119+
ecs.AddResource(&g.App.World, &bounds)
120120

121121
editor := res.EditorMode{IsEditor: isEditor}
122-
ecs.AddResource(&g.Model.World, &editor)
122+
ecs.AddResource(&g.App.World, &editor)
123123

124124
saveTime := res.SaveTime{}
125-
ecs.AddResource(&g.Model.World, &saveTime)
125+
ecs.AddResource(&g.App.World, &saveTime)
126126

127127
randomTerrains := res.RandomTerrains{
128128
TotalAvailable: rules.InitialRandomTerrains,
129129
}
130-
ecs.AddResource(&g.Model.World, &randomTerrains)
130+
ecs.AddResource(&g.App.World, &randomTerrains)
131131

132132
update := res.UpdateInterval{
133133
Interval: TPS,
134134
Countdown: 60,
135135
}
136-
ecs.AddResource(&g.Model.World, &update)
136+
ecs.AddResource(&g.App.World, &update)
137137

138138
sprites := res.NewSprites(GameData, "data/gfx", tileSet)
139-
ecs.AddResource(&g.Model.World, &sprites)
139+
ecs.AddResource(&g.App.World, &sprites)
140140

141141
view := res.NewView(sprites.TileWidth, sprites.TileHeight)
142-
ecs.AddResource(&g.Model.World, &view)
142+
ecs.AddResource(&g.App.World, &view)
143143

144144
production := res.NewProduction()
145-
ecs.AddResource(&g.Model.World, &production)
145+
ecs.AddResource(&g.App.World, &production)
146146

147147
stock := res.NewStock(rules.InitialResources)
148-
ecs.AddResource(&g.Model.World, &stock)
148+
ecs.AddResource(&g.App.World, &stock)
149149

150-
ecs.AddResource(&g.Model.World, &g.Screen)
151-
ecs.AddResource(&g.Model.World, &g.Mouse)
150+
ecs.AddResource(&g.App.World, &g.Screen)
151+
ecs.AddResource(&g.App.World, &g.Mouse)
152152

153153
saveEvent := res.SaveEvent{}
154-
ecs.AddResource(&g.Model.World, &saveEvent)
154+
ecs.AddResource(&g.App.World, &saveEvent)
155155

156156
fonts := res.NewFonts(GameData)
157-
ecs.AddResource(&g.Model.World, &fonts)
157+
ecs.AddResource(&g.App.World, &fonts)
158158

159-
factory := res.NewEntityFactory(&g.Model.World)
160-
ecs.AddResource(&g.Model.World, &factory)
159+
factory := res.NewEntityFactory(&g.App.World)
160+
ecs.AddResource(&g.App.World, &factory)
161161

162-
achievements := achievements.New(&g.Model.World, GameData, "data/json/achievements.json", "user/achievements.json")
163-
ecs.AddResource(&g.Model.World, achievements)
162+
achievements := achievements.New(&g.App.World, GameData, "data/json/achievements.json", "user/achievements.json")
163+
ecs.AddResource(&g.App.World, achievements)
164164

165165
// =========== Systems ===========
166166

167167
if load == save.LoadTypeGame {
168-
g.Model.AddSystem(&sys.InitTerrainLoaded{})
168+
g.App.AddSystem(&sys.InitTerrainLoaded{})
169169
} else if load == save.LoadTypeMap {
170-
g.Model.AddSystem(&sys.InitTerrainMap{
170+
g.App.AddSystem(&sys.InitTerrainMap{
171171
FS: GameData,
172172
MapFolder: mapsFolder,
173173
Map: mapLoc,
174174
})
175175
} else {
176-
g.Model.AddSystem(&sys.InitTerrain{})
176+
g.App.AddSystem(&sys.InitTerrain{})
177177
}
178-
g.Model.AddSystem(&sys.InitUI{})
179-
180-
g.Model.AddSystem(&sys.Tick{})
181-
g.Model.AddSystem(&sys.UpdateProduction{})
182-
g.Model.AddSystem(&sys.UpdatePopulation{})
183-
g.Model.AddSystem(&sys.DoProduction{})
184-
g.Model.AddSystem(&sys.DoConsumption{})
185-
g.Model.AddSystem(&sys.Haul{})
186-
g.Model.AddSystem(&sys.UpdateStats{})
187-
g.Model.AddSystem(&sys.RemoveMarkers{
178+
g.App.AddSystem(&sys.InitUI{})
179+
180+
g.App.AddSystem(&sys.Tick{})
181+
g.App.AddSystem(&sys.UpdateProduction{})
182+
g.App.AddSystem(&sys.UpdatePopulation{})
183+
g.App.AddSystem(&sys.DoProduction{})
184+
g.App.AddSystem(&sys.DoConsumption{})
185+
g.App.AddSystem(&sys.Haul{})
186+
g.App.AddSystem(&sys.UpdateStats{})
187+
g.App.AddSystem(&sys.RemoveMarkers{
188188
MaxTime: TPS,
189189
})
190190

191-
g.Model.AddSystem(&sys.Build{})
192-
g.Model.AddSystem(&sys.AssignHaulers{})
193-
g.Model.AddSystem(&sys.Achievements{
191+
g.App.AddSystem(&sys.Build{})
192+
g.App.AddSystem(&sys.AssignHaulers{})
193+
g.App.AddSystem(&sys.Achievements{
194194
PlayerFile: "user/achievements.json",
195195
})
196196

197-
g.Model.AddSystem(&sys.PanAndZoom{
197+
g.App.AddSystem(&sys.PanAndZoom{
198198
PanButton: ebiten.MouseButton1,
199199
ZoomInKey: '+',
200200
ZoomOutKey: '-',
@@ -203,15 +203,15 @@ func runGame(g *Game, load save.LoadType, name string, mapLoc save.MapLocation,
203203
MaxZoom: 4,
204204
})
205205

206-
g.Model.AddSystem(&sys.UpdateUI{})
207-
g.Model.AddSystem(&sys.Cheats{})
208-
g.Model.AddSystem(&sys.SaveGame{
206+
g.App.AddSystem(&sys.UpdateUI{})
207+
g.App.AddSystem(&sys.Cheats{})
208+
g.App.AddSystem(&sys.SaveGame{
209209
SaveFolder: "save",
210210
MapFolder: "maps",
211211
Name: name,
212212
MainMenuFunc: func() { runMenu(g, 0) },
213213
})
214-
g.Model.AddSystem(&sys.GameControls{
214+
g.App.AddSystem(&sys.GameControls{
215215
PauseKey: ebiten.KeySpace,
216216
SlowerKey: '[',
217217
FasterKey: ']',
@@ -220,22 +220,22 @@ func runGame(g *Game, load save.LoadType, name string, mapLoc save.MapLocation,
220220

221221
// =========== UI Systems ===========
222222

223-
g.Model.AddUISystem(&render.CenterView{})
224-
g.Model.AddUISystem(&render.Terrain{})
225-
g.Model.AddUISystem(&render.Markers{
223+
g.App.AddUISystem(&render.CenterView{})
224+
g.App.AddUISystem(&render.Terrain{})
225+
g.App.AddUISystem(&render.Markers{
226226
MinOffset: view.TileHeight * 2,
227227
MaxOffset: view.TileHeight*2 + 30,
228228
Duration: TPS,
229229
})
230-
g.Model.AddUISystem(&render.UI{})
231-
g.Model.AddUISystem(&render.CardAnimation{
230+
g.App.AddUISystem(&render.UI{})
231+
g.App.AddUISystem(&render.CardAnimation{
232232
MaxOffset: 200,
233233
Duration: TPS / 4,
234234
})
235235

236236
// =========== Load game ===========
237237
if load == save.LoadTypeGame {
238-
err := save.LoadWorld(&g.Model.World, saveFolder, name)
238+
err := save.LoadWorld(&g.App.World, saveFolder, name)
239239
if err != nil {
240240
return err
241241
}
@@ -245,9 +245,11 @@ func runGame(g *Game, load save.LoadType, name string, mapLoc save.MapLocation,
245245
view.TileHeight = sprites.TileHeight
246246
}
247247

248+
addRepl(g.App)
249+
248250
// =========== Run ===========
249251

250-
g.Model.Initialize()
252+
g.App.Initialize()
251253

252254
return nil
253255
}

game/run_nowasm.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33
package game
44

55
import (
6+
"os"
7+
"strings"
8+
69
"github.com/hajimehoshi/ebiten/v2"
10+
"github.com/mlange-42/ark-repl/repl"
11+
"github.com/mlange-42/ark-tools/app"
12+
"github.com/mlange-42/ark/ecs"
13+
"github.com/mlange-42/tiny-world/game/res"
714
)
815

916
type canvasHelper struct{}
@@ -16,3 +23,28 @@ func (c *canvasHelper) isMouseInside(width, height int) bool {
1623
x, y := ebiten.CursorPosition()
1724
return x >= 0 && y >= 0 && x < width && y < height
1825
}
26+
27+
func addRepl(app *app.App) {
28+
startServer := len(os.Args) > 1 && os.Args[1] == "monitor"
29+
if !startServer {
30+
return
31+
}
32+
33+
callbacks := repl.Callbacks{
34+
Pause: func(out *strings.Builder) {
35+
ecs.GetResource[res.GameSpeed](&app.World).Pause = true
36+
},
37+
Resume: func(out *strings.Builder) {
38+
ecs.GetResource[res.GameSpeed](&app.World).Pause = false
39+
},
40+
Ticks: func() int {
41+
return int(ecs.GetResource[res.GameTick](&app.World).Tick)
42+
},
43+
}
44+
45+
repl := repl.NewRepl(&app.World, callbacks)
46+
47+
app.AddUISystem(repl.System())
48+
49+
repl.StartServer(":9000")
50+
}

game/run_wasm.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package game
44

55
import (
66
"syscall/js"
7+
8+
"github.com/mlange-42/ark-tools/app"
79
)
810

911
type canvasHelper struct {
@@ -41,3 +43,5 @@ func (c *canvasHelper) onMouseLeave(this js.Value, args []js.Value) interface{}
4143
c.mouseInside = false
4244
return nil
4345
}
46+
47+
func addRepl(app *app.App) {}

go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/ebitenui/ebitenui v0.7.2
77
github.com/hajimehoshi/ebiten/v2 v2.9.1
88
github.com/mlange-42/ark v0.6.1
9+
github.com/mlange-42/ark-repl v0.0.0-20251008145212-e1503a00bccd
910
github.com/mlange-42/ark-serde v0.3.0
1011
github.com/mlange-42/ark-tools v0.2.1
1112
github.com/spf13/cobra v1.10.1
@@ -16,15 +17,22 @@ require (
1617
github.com/ebitengine/hideconsole v1.0.0 // indirect
1718
github.com/ebitengine/purego v0.9.0 // indirect
1819
github.com/frustra/bbcode v0.0.0-20201127003707-6ef347fbe1c8 // indirect
20+
github.com/gdamore/encoding v1.0.0 // indirect
21+
github.com/gdamore/tcell/v2 v2.7.4 // indirect
1922
github.com/go-text/typesetting v0.3.0 // indirect
2023
github.com/goccy/go-json v0.10.5 // indirect
2124
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2225
github.com/jezek/xgb v1.1.1 // indirect
26+
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
27+
github.com/mattn/go-runewidth v0.0.15 // indirect
28+
github.com/mum4k/termdash v0.20.0 // indirect
29+
github.com/nsf/termbox-go v1.1.1 // indirect
2330
github.com/rivo/uniseg v0.4.7 // indirect
2431
github.com/spf13/pflag v1.0.9 // indirect
2532
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
2633
golang.org/x/image v0.32.0 // indirect
2734
golang.org/x/sync v0.17.0 // indirect
2835
golang.org/x/sys v0.36.0 // indirect
36+
golang.org/x/term v0.17.0 // indirect
2937
golang.org/x/text v0.30.0 // indirect
3038
)

0 commit comments

Comments
 (0)