mirror of
https://github.com/lukaszraczylo/go-telegram.git
synced 2026-06-05 22:43:59 +00:00
0ee539e991
Move the three conventional Values keys ("command", "command_args", "regex_match") to typed fields on Context. Router and group routing write the fields directly; the Values map is allocated lazily via the new Set method and reserved for user-defined custom keys.
Allocation impact (M4 Max, b.Loop()):
DispatchCommand: 5 allocs/op -> 1, 153ns -> 69ns (-55%)
DispatchTextRegex: 5 allocs/op -> 2, 181ns -> 107ns (-41%)
DispatchFilter: 2 allocs/op -> 1, 32ns -> 19ns (-41%)
NewContext: 5.79ns -> 1.60ns
Trade-off: Context struct grew from ~48B to ~96B (three new fields), so filter-only paths pay ~50B more per dispatch. Command/regex paths save ~320B + 4 allocs each, which dominates for typical bot workloads.
Handlers reading c.Values["command"], c.Values["command_args"], or c.Values["regex_match"] now get nil; the typed fields c.Command, c.CommandArgs, c.RegexMatch are the new accessors. Custom keys still work via c.Set(k, v) and c.Values[k].
66 lines
2.2 KiB
Go
66 lines
2.2 KiB
Go
// Package dispatch provides a typed router for Telegram updates. It
|
|
// consumes any transport.Updater and dispatches updates to handlers
|
|
// registered by command, regex, or update-payload kind.
|
|
package dispatch
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/lukaszraczylo/go-telegram/api"
|
|
"github.com/lukaszraczylo/go-telegram/client"
|
|
)
|
|
|
|
// Context bundles the per-update state every handler receives.
|
|
//
|
|
// Ctx is the request context propagated from Router.Run; cancelling the
|
|
// run cancels every handler.
|
|
//
|
|
// Bot is the API client. Handlers reply by calling api.SendMessage(c.Ctx,
|
|
// c.Bot, ...) etc.
|
|
//
|
|
// Update is the raw update; payload-typed handlers also receive a
|
|
// narrowed pointer to one of its sub-fields.
|
|
//
|
|
// Command, CommandArgs and RegexMatch are populated by the router for
|
|
// the matching route kind; they replace the previous "command",
|
|
// "command_args" and "regex_match" entries in Values, which were the
|
|
// only conventional keys. Values remains for user-defined custom keys.
|
|
//
|
|
// Command is the matched bot command (e.g. "/start"); empty when the
|
|
// route is not a command match.
|
|
//
|
|
// CommandArgs is everything after the command; empty when no command
|
|
// matched or the command had no trailing text.
|
|
//
|
|
// RegexMatch is the regex sub-matches when an OnText/OnCallback regex
|
|
// route matched; nil otherwise.
|
|
//
|
|
// Values is lazily allocated for user-defined keys. Handlers that don't
|
|
// write pay no allocation. Reads against a nil map return the zero
|
|
// value. Writers must use Set instead of indexing the map directly.
|
|
type Context struct {
|
|
Ctx context.Context
|
|
Bot *client.Bot
|
|
Update *api.Update
|
|
Command string
|
|
CommandArgs string
|
|
RegexMatch []string
|
|
Values map[string]any
|
|
}
|
|
|
|
// NewContext constructs a Context. Used by Router internally; exposed for
|
|
// custom test harnesses.
|
|
func NewContext(ctx context.Context, b *client.Bot, u *api.Update) *Context {
|
|
return &Context{Ctx: ctx, Bot: b, Update: u}
|
|
}
|
|
|
|
// Set writes key/val into Values, allocating the map on first use. Use
|
|
// this instead of `c.Values[k] = v` so the no-write path stays
|
|
// allocation-free.
|
|
func (c *Context) Set(key string, val any) {
|
|
if c.Values == nil {
|
|
c.Values = make(map[string]any, 2)
|
|
}
|
|
c.Values[key] = val
|
|
}
|