Skip to content

Commit 9a00aa5

Browse files
committed
loopWebUI is stoppable
1 parent 08658ef commit 9a00aa5

2 files changed

Lines changed: 61 additions & 22 deletions

File tree

internal/bot/ui.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,44 @@
11
package bot
22

33
import (
4+
"context"
45
"fmt"
6+
"log"
57
"net/http"
8+
"time"
69
)
710

811
func (b *Bot) loopWebUI() {
12+
b.wg.Add(1)
13+
defer b.wg.Done()
14+
915
mux := http.NewServeMux()
1016

1117
mux.HandleFunc("/ws", b.HandleWS)
1218

13-
fmt.Println("[UI] Dashboard live at http://localhost:8080")
14-
http.ListenAndServe(":8080", mux)
19+
srv := &http.Server{
20+
Addr: ":8080",
21+
Handler: mux,
22+
}
23+
24+
go func() {
25+
fmt.Println("[UI] Dashboard live at http://localhost:8080")
26+
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
27+
log.Printf("[UI] HTTP server error: %v", err)
28+
}
29+
}()
30+
31+
<-b.stopChan
32+
33+
log.Println("[UI] Shutting down server...")
34+
35+
// Create a context with a timeout so it doesn't hang forever
36+
// if a browser tab stays connected
37+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
38+
defer cancel()
39+
40+
if err := srv.Shutdown(ctx); err != nil {
41+
log.Printf("[UI] Shutdown error: %v", err)
42+
}
43+
log.Println("[UI] Server stopped.")
1544
}

internal/bot/ui_handlers.go

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ func (b *Bot) HandleWS(w http.ResponseWriter, r *http.Request) {
3434
if err != nil {
3535
return
3636
}
37+
// This ensures that when this function exits, the socket is closed.
38+
// Closing the socket will also force the "Command Reader" goroutine to exit.
3739
defer conn.Close()
3840

3941
// --- 1. THE COMMAND READER (Browser -> Go) ---
@@ -57,28 +59,36 @@ func (b *Bot) HandleWS(w http.ResponseWriter, r *http.Request) {
5759
}()
5860

5961
// --- 2. THE STATE WRITER (Go -> Browser) ---
60-
// Stream updates every 100ms
62+
ticker := time.NewTicker(100 * time.Millisecond)
63+
defer ticker.Stop()
64+
6165
for {
62-
// Create a snapshot from your current bot fields
63-
snap := BotSnapshot{
64-
FishingEnabled: b.fishingEnabled,
65-
Name: b.state.CaptureFrame().Player.Name,
66-
X: b.state.CaptureFrame().Player.Pos.X,
67-
Y: b.state.CaptureFrame().Player.Pos.Y,
68-
Z: b.state.CaptureFrame().Player.Pos.Z,
69-
Waypoints: []Waypoint{
70-
{ID: "wp-1", Type: "Walk", X: 32345, Y: 32222, Z: 7},
71-
{ID: "wp-2", Type: "Walk", X: 32350, Y: 32230, Z: 7},
72-
{ID: "wp-3", Type: "Rope", X: 32350, Y: 32230, Z: 7},
73-
{ID: "wp-4", Type: "Walk", X: 32352, Y: 32235, Z: 6},
74-
},
75-
}
66+
select {
67+
// EXIT if the Bot is stopped via Stop()
68+
case <-b.stopChan:
69+
return
7670

77-
payload, _ := json.Marshal(snap)
78-
if err := conn.WriteMessage(websocket.TextMessage, payload); err != nil {
79-
break
80-
}
71+
// EXECUTE update every tick
72+
case <-ticker.C:
73+
snap := BotSnapshot{
74+
FishingEnabled: b.fishingEnabled,
75+
Name: b.state.CaptureFrame().Player.Name,
76+
X: b.state.CaptureFrame().Player.Pos.X,
77+
Y: b.state.CaptureFrame().Player.Pos.Y,
78+
Z: b.state.CaptureFrame().Player.Pos.Z,
79+
Waypoints: []Waypoint{
80+
{ID: "wp-1", Type: "Walk", X: 32345, Y: 32222, Z: 7},
81+
{ID: "wp-2", Type: "Walk", X: 32350, Y: 32230, Z: 7},
82+
{ID: "wp-3", Type: "Rope", X: 32350, Y: 32230, Z: 7},
83+
{ID: "wp-4", Type: "Walk", X: 32352, Y: 32235, Z: 6},
84+
},
85+
}
8186

82-
time.Sleep(100 * time.Millisecond)
87+
// We use WriteJSON directly to simplify the code
88+
if err := conn.WriteJSON(snap); err != nil {
89+
// If the browser tab is closed, this will error out and exit the loop
90+
return
91+
}
92+
}
8393
}
8494
}

0 commit comments

Comments
 (0)