Fix SSE architecture for reliable connections #13

Merged
ryan merged 9 commits from fix/sse-architecture into main 2026-03-03 23:33:14 +00:00
3 changed files with 19 additions and 4 deletions
Showing only changes of commit cedcadfe3c - Show all commits

View File

@@ -14,6 +14,7 @@ import (
"github.com/ryanhamamura/games/connect4" "github.com/ryanhamamura/games/connect4"
"github.com/ryanhamamura/games/features/c4game/pages" "github.com/ryanhamamura/games/features/c4game/pages"
"github.com/ryanhamamura/games/features/c4game/services" "github.com/ryanhamamura/games/features/c4game/services"
sharedcomponents "github.com/ryanhamamura/games/features/common/components"
"github.com/ryanhamamura/games/sessions" "github.com/ryanhamamura/games/sessions"
) )
@@ -112,7 +113,10 @@ func HandleGameEvents(store *connect4.Store, svc *services.GameService, sm *scs.
return sse.PatchElementTempl(pages.GameContent(g, myColor, room.Messages(), chatCfg)) return sse.PatchElementTempl(pages.GameContent(g, myColor, room.Messages(), chatCfg))
} }
// Send initial state // Send initial connection indicator and state
if err := sse.PatchElementTempl(sharedcomponents.ConnectionIndicator(time.Now().UnixMilli())); err != nil {
return
}
if err := patchAll(); err != nil { if err := patchAll(); err != nil {
return return
} }
@@ -147,7 +151,10 @@ func HandleGameEvents(store *connect4.Store, svc *services.GameService, sm *scs.
} }
case <-heartbeat.C: case <-heartbeat.C:
// Heartbeat just keeps the connection alive by triggering a game state refresh // Heartbeat updates connection indicator and refreshes game state
if err := sse.PatchElementTempl(sharedcomponents.ConnectionIndicator(time.Now().UnixMilli())); err != nil {
return
}
if err := patchAll(); err != nil { if err := patchAll(); err != nil {
return return
} }

View File

@@ -18,6 +18,7 @@ templ GamePage(g *connect4.Game, myColor int, messages []chat.Message, chatCfg c
data-signals="{chatMsg: ''}" data-signals="{chatMsg: ''}"
data-init={ fmt.Sprintf("@get('/games/%s/events',{requestCancellation:'disabled'})", g.ID) } data-init={ fmt.Sprintf("@get('/games/%s/events',{requestCancellation:'disabled'})", g.ID) }
> >
@sharedcomponents.ConnectionIndicator(0)
@GameContent(g, myColor, messages, chatCfg) @GameContent(g, myColor, messages, chatCfg)
</main> </main>
} }

View File

@@ -12,6 +12,7 @@ import (
"github.com/ryanhamamura/games/chat" "github.com/ryanhamamura/games/chat"
chatcomponents "github.com/ryanhamamura/games/chat/components" chatcomponents "github.com/ryanhamamura/games/chat/components"
sharedcomponents "github.com/ryanhamamura/games/features/common/components"
"github.com/ryanhamamura/games/features/snakegame/pages" "github.com/ryanhamamura/games/features/snakegame/pages"
"github.com/ryanhamamura/games/features/snakegame/services" "github.com/ryanhamamura/games/features/snakegame/services"
"github.com/ryanhamamura/games/sessions" "github.com/ryanhamamura/games/sessions"
@@ -117,7 +118,10 @@ func HandleSnakeEvents(snakeStore *snake.SnakeStore, svc *services.GameService,
return sse.PatchElementTempl(pages.GameContent(sg, mySlot, chatMessages(), chatCfg, gameID)) return sse.PatchElementTempl(pages.GameContent(sg, mySlot, chatMessages(), chatCfg, gameID))
} }
// Send initial render // Send initial connection indicator and render
if err := sse.PatchElementTempl(sharedcomponents.ConnectionIndicator(time.Now().UnixMilli())); err != nil {
return
}
if err := patchAll(); err != nil { if err := patchAll(); err != nil {
return return
} }
@@ -141,7 +145,10 @@ func HandleSnakeEvents(snakeStore *snake.SnakeStore, svc *services.GameService,
return return
case <-heartbeat.C: case <-heartbeat.C:
// Heartbeat just refreshes game state // Heartbeat updates connection indicator and refreshes game state
if err := sse.PatchElementTempl(sharedcomponents.ConnectionIndicator(time.Now().UnixMilli())); err != nil {
return
}
if err := patchAll(); err != nil { if err := patchAll(); err != nil {
return return
} }