first commit

This commit is contained in:
Sky Johnson 2025-08-08 21:07:46 -05:00
commit 644b536a49
5 changed files with 482 additions and 0 deletions

19
README.md Normal file
View File

@ -0,0 +1,19 @@
# Dragon Knight
A hyper-performant version of Dragon Knight built with cool technologies.
# Backend
We're using Go! Since Dragon Knight is a game that's purely SSR, we opt to use the best library for this purpose; fasthttp. We have our own custom radix tree router to dispatch, and all events are handled on the server with speed. To avoid difficulty while in development, though, all our static assets and non-database data are in a folder adjacent to the executable, so we lose the advantage of embedding but gain dev speed.
# Database
Dragon Knight's database is small and generally doesn't see many writes. Therefore we can use a single monolith SQLite file as the database. We still use WAL and give it extra memory - performance is a blessing!
Because our backend language has no databases built-in, we have to bring in a dependency for this one - it is what it is. For this purpose, we use the CGO-free zombiezen library - which also tends to be among the most performant SQLite wrappers in Go.
To make life easier, we use an Active Record kind of pattern for database objects.
# Frontend
Pure HTML, CSS and JavaScript. We enhance the frontend with AJAX where applicable. Again, these assets are adjacent to the executable for dev speed.

7
cmd/main.go Normal file
View File

@ -0,0 +1,7 @@
package main
import "fmt"
func main() {
fmt.Println("Main!")
}

11
go.mod Normal file
View File

@ -0,0 +1,11 @@
module dk
go 1.24.6
require github.com/valyala/fasthttp v1.64.0
require (
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
)

10
go.sum Normal file
View File

@ -0,0 +1,10 @@
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.64.0 h1:QBygLLQmiAyiXuRhthf0tuRkqAFcrC42dckN2S+N3og=
github.com/valyala/fasthttp v1.64.0/go.mod h1:dGmFxwkWXSK0NbOSJuF7AMVzU+lkHz0wQVvVITv2UQA=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=

435
internal/router/router.go Normal file
View File

@ -0,0 +1,435 @@
package router
import (
"fmt"
"github.com/valyala/fasthttp"
)
type Ctx = *fasthttp.RequestCtx
// Handler is a request handler with parameters.
type Handler func(ctx Ctx, params []string)
func (h Handler) Serve(ctx Ctx, params []string) {
h(ctx, params)
}
type Middleware func(Handler) Handler
type node struct {
segment string
handler Handler
children []*node
isDynamic bool
isWildcard bool
maxParams uint8
}
type Router struct {
get *node
post *node
put *node
patch *node
delete *node
middleware []Middleware
paramsBuffer []string // Pre-allocated buffer for parameters
}
type Group struct {
router *Router
prefix string
middleware []Middleware
}
// Creates a new Router instance.
func New() *Router {
return &Router{
get: &node{},
post: &node{},
put: &node{},
patch: &node{},
delete: &node{},
middleware: []Middleware{},
paramsBuffer: make([]string, 64),
}
}
// Implements the Handler interface for fasthttp
func (r *Router) ServeHTTP(ctx *fasthttp.RequestCtx) {
path := string(ctx.Path())
method := string(ctx.Method())
h, params, found := r.Lookup(method, path)
if !found {
ctx.SetStatusCode(fasthttp.StatusNotFound)
return
}
h(ctx, params)
}
// Returns a fasthttp request handler
func (r *Router) Handler() fasthttp.RequestHandler {
return r.ServeHTTP
}
// Adds middleware to the router.
func (r *Router) Use(mw ...Middleware) *Router {
r.middleware = append(r.middleware, mw...)
return r
}
// Creates a new route group.
func (r *Router) Group(prefix string) *Group {
return &Group{router: r, prefix: prefix, middleware: []Middleware{}}
}
// Adds middleware to the group.
func (g *Group) Use(mw ...Middleware) *Group {
g.middleware = append(g.middleware, mw...)
return g
}
// Creates a nested group.
func (g *Group) Group(prefix string) *Group {
return &Group{
router: g.router,
prefix: g.prefix + prefix,
middleware: append([]Middleware{}, g.middleware...),
}
}
// Applies middleware in reverse order.
func applyMiddleware(h Handler, mw []Middleware) Handler {
for i := len(mw) - 1; i >= 0; i-- {
h = mw[i](h)
}
return h
}
// Registers a handler for the given method and path.
func (r *Router) Handle(method, path string, h Handler) error {
root := r.methodNode(method)
if root == nil {
return fmt.Errorf("unsupported method: %s", method)
}
return r.addRoute(root, path, h, r.middleware)
}
func (r *Router) methodNode(method string) *node {
switch method {
case "GET":
return r.get
case "POST":
return r.post
case "PUT":
return r.put
case "PATCH":
return r.patch
case "DELETE":
return r.delete
default:
return nil
}
}
// Registers a GET handler.
func (r *Router) Get(path string, h Handler) error {
return r.Handle("GET", path, h)
}
// Registers a POST handler.
func (r *Router) Post(path string, h Handler) error {
return r.Handle("POST", path, h)
}
// Registers a PUT handler.
func (r *Router) Put(path string, h Handler) error {
return r.Handle("PUT", path, h)
}
// Registers a PATCH handler.
func (r *Router) Patch(path string, h Handler) error {
return r.Handle("PATCH", path, h)
}
// Registers a DELETE handler.
func (r *Router) Delete(path string, h Handler) error {
return r.Handle("DELETE", path, h)
}
func (g *Group) buildGroupMiddleware() []Middleware {
mw := append([]Middleware{}, g.router.middleware...)
return append(mw, g.middleware...)
}
// Registers a handler in the group.
func (g *Group) Handle(method, path string, h Handler) error {
root := g.router.methodNode(method)
if root == nil {
return fmt.Errorf("unsupported method: %s", method)
}
return g.router.addRoute(root, g.prefix+path, h, g.buildGroupMiddleware())
}
// Registers a GET handler in the group.
func (g *Group) Get(path string, h Handler) error {
return g.Handle("GET", path, h)
}
// Registers a POST handler in the group.
func (g *Group) Post(path string, h Handler) error {
return g.Handle("POST", path, h)
}
// Registers a PUT handler in the group.
func (g *Group) Put(path string, h Handler) error {
return g.Handle("PUT", path, h)
}
// Registers a PATCH handler in the group.
func (g *Group) Patch(path string, h Handler) error {
return g.Handle("PATCH", path, h)
}
// Registers a DELETE handler in the group.
func (g *Group) Delete(path string, h Handler) error {
return g.Handle("DELETE", path, h)
}
// Applies specific middleware for next registration.
func (r *Router) WithMiddleware(mw ...Middleware) *MiddlewareRouter {
return &MiddlewareRouter{router: r, middleware: mw}
}
// Applies specific middleware for next group route.
func (g *Group) WithMiddleware(mw ...Middleware) *MiddlewareGroup {
return &MiddlewareGroup{group: g, middleware: mw}
}
type MiddlewareRouter struct {
router *Router
middleware []Middleware
}
type MiddlewareGroup struct {
group *Group
middleware []Middleware
}
func (mr *MiddlewareRouter) buildMiddleware() []Middleware {
mw := append([]Middleware{}, mr.router.middleware...)
return append(mw, mr.middleware...)
}
// Registers a handler with middleware router.
func (mr *MiddlewareRouter) Handle(method, path string, h Handler) error {
root := mr.router.methodNode(method)
if root == nil {
return fmt.Errorf("unsupported method: %s", method)
}
return mr.router.addRoute(root, path, h, mr.buildMiddleware())
}
// Registers a GET handler with middleware router.
func (mr *MiddlewareRouter) Get(path string, h Handler) error {
return mr.Handle("GET", path, h)
}
// Registers a POST handler with middleware router.
func (mr *MiddlewareRouter) Post(path string, h Handler) error {
return mr.Handle("POST", path, h)
}
// Registers a PUT handler with middleware router.
func (mr *MiddlewareRouter) Put(path string, h Handler) error {
return mr.Handle("PUT", path, h)
}
// Registers a PATCH handler with middleware router.
func (mr *MiddlewareRouter) Patch(path string, h Handler) error {
return mr.Handle("PATCH", path, h)
}
// Registers a DELETE handler with middleware router.
func (mr *MiddlewareRouter) Delete(path string, h Handler) error {
return mr.Handle("DELETE", path, h)
}
func (mg *MiddlewareGroup) buildMiddleware() []Middleware {
mw := append([]Middleware{}, mg.group.router.middleware...)
mw = append(mw, mg.group.middleware...)
return append(mw, mg.middleware...)
}
// Registers a handler with middleware group.
func (mg *MiddlewareGroup) Handle(method, path string, h Handler) error {
root := mg.group.router.methodNode(method)
if root == nil {
return fmt.Errorf("unsupported method: %s", method)
}
return mg.group.router.addRoute(root, mg.group.prefix+path, h, mg.buildMiddleware())
}
// Registers a GET handler with middleware group.
func (mg *MiddlewareGroup) Get(path string, h Handler) error {
return mg.Handle("GET", path, h)
}
// Registers a POST handler with middleware group.
func (mg *MiddlewareGroup) Post(path string, h Handler) error {
return mg.Handle("POST", path, h)
}
// Registers a PUT handler with middleware group.
func (mg *MiddlewareGroup) Put(path string, h Handler) error {
return mg.Handle("PUT", path, h)
}
// Registers a PATCH handler with middleware group.
func (mg *MiddlewareGroup) Patch(path string, h Handler) error {
return mg.Handle("PATCH", path, h)
}
// Registers a DELETE handler with middleware group.
func (mg *MiddlewareGroup) Delete(path string, h Handler) error {
return mg.Handle("DELETE", path, h)
}
// Adapts a standard fasthttp.RequestHandler to the router's Handler
func StandardHandler(handler fasthttp.RequestHandler) Handler {
return func(ctx Ctx, _ []string) {
handler(ctx)
}
}
// Extracts the next path segment.
func readSegment(path string, start int) (segment string, end int, hasMore bool) {
if start >= len(path) {
return "", start, false
}
if path[start] == '/' {
start++
}
if start >= len(path) {
return "", start, false
}
end = start
for end < len(path) && path[end] != '/' {
end++
}
return path[start:end], end, end < len(path)
}
// Adds a new route to the trie.
func (r *Router) addRoute(root *node, path string, h Handler, mw []Middleware) error {
h = applyMiddleware(h, mw)
if path == "/" {
root.handler = h
return nil
}
current := root
pos := 0
lastWC := false
count := uint8(0)
for {
seg, newPos, more := readSegment(path, pos)
if seg == "" {
break
}
isDyn := len(seg) > 1 && seg[0] == ':'
isWC := len(seg) > 0 && seg[0] == '*'
if isWC {
if lastWC || more {
return fmt.Errorf("wildcard must be the last segment in the path")
}
lastWC = true
}
if isDyn || isWC {
count++
}
var child *node
for _, c := range current.children {
if c.segment == seg {
child = c
break
}
}
if child == nil {
child = &node{segment: seg, isDynamic: isDyn, isWildcard: isWC}
current.children = append(current.children, child)
}
if child.maxParams < count {
child.maxParams = count
}
current = child
pos = newPos
}
current.handler = h
return nil
}
// Finds a handler matching method and path.
func (r *Router) Lookup(method, path string) (Handler, []string, bool) {
root := r.methodNode(method)
if root == nil {
return nil, nil, false
}
if path == "/" {
return root.handler, nil, root.handler != nil
}
buffer := r.paramsBuffer
if cap(buffer) < int(root.maxParams) {
buffer = make([]string, root.maxParams)
r.paramsBuffer = buffer
}
buffer = buffer[:0]
h, paramCount, found := match(root, path, 0, &buffer)
if !found {
return nil, nil, false
}
return h, buffer[:paramCount], true
}
// Traverses the trie to find a handler.
func match(current *node, path string, start int, params *[]string) (Handler, int, bool) {
paramCount := 0
for _, c := range current.children {
if c.isWildcard {
rem := path[start:]
if len(rem) > 0 && rem[0] == '/' {
rem = rem[1:]
}
*params = append(*params, rem)
return c.handler, 1, c.handler != nil
}
}
seg, pos, more := readSegment(path, start)
if seg == "" {
return current.handler, 0, current.handler != nil
}
for _, c := range current.children {
if c.segment == seg || c.isDynamic {
if c.isDynamic {
*params = append(*params, seg)
paramCount++
}
if !more {
return c.handler, paramCount, c.handler != nil
}
h, nestedCount, ok := match(c, path, pos, params)
if ok {
return h, paramCount + nestedCount, true
}
}
}
return nil, 0, false
}