Compare commits

..

10 Commits

@ -0,0 +1,69 @@
package main
import (
"context"
"database/sql"
_ "embed"
"log"
_ "modernc.org/sqlite"
models "code.ndumas.com/ndumas/muddy/db"
)
//go:embed schema.sql
var ddl string
func run() error {
return nil
}
func main() {
ctx := context.Background()
db, err := sql.Open("sqlite", ":memory:")
if err != nil {
log.Printf("error opening sqlite db: %s\n", err)
return
}
// create tables
if _, err := db.ExecContext(ctx, ddl); err != nil {
log.Printf("error creating tables: %s\n", err)
return
}
queries := models.New(db)
obsvID, err := queries.CreateObservable(
ctx,
models.CreateObservableParams{
Name: "The Crossroads",
Description: "You stand at the intersection of two well trod roads. You don't remember how you got here, where you were going, or who you were. All you know is that you must go forward.",
},
)
if err != nil {
log.Printf("error creating room observable: %s\n", err)
return
}
if err != nil {
log.Printf("error creating room observable: %s\n", err)
return
}
locID, err := queries.CreateLocation(
ctx,
models.CreateLocationParams{
X: 0,
Y: 0,
Z: 0,
World: 0,
Observable: obsvID,
},
)
log.Printf("created room: %d\n", locID)
}

@ -0,0 +1,32 @@
CREATE TABLE locations (
id INTEGER PRIMARY KEY,
x INTEGER NOT NULL,
y INTEGER NOT NULL,
z INTEGER NOT NULL,
world INTEGER NOT NULL,
observable INTEGER NOT NULL references observables(id)
);
CREATE TABLE observables (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL
);
CREATE TABLE inventories (
id INTEGER PRIMARY KEY
);
CREATE TABLE pools (
id INTEGER PRIMARY KEY,
type INTEGER NOT NULL
);
CREATE TABLE exits (
id INTEGER PRIMARY KEY,
origin INTEGER NOT NULL,
dest INTEGER NOT NULL,
FOREIGN KEY (origin) REFERENCES locations (id),
FOREIGN KEY (dest) REFERENCES locations (id)
);

@ -0,0 +1,57 @@
package main
import (
"context"
"flag"
"io"
"log/slog"
"os"
"os/signal"
"syscall"
"time"
"code.ndumas.com/ndumas/muddy/core"
)
func main() {
var (
level string
logFile string
)
flag.StringVar(&level, "level", "INFO", "Log level: INFO/info, DEBUG/debug, or ERROR/error.")
flag.StringVar(&logFile, "log", "out.log", "Path to log file.")
flag.Parse()
f, err := os.Create(logFile)
if err != nil {
slog.With(
slog.Any("error", err),
).Error("could not open log file")
os.Exit(1)
}
appConfig := core.AppConfig{
Port: ":3001",
LogDest: io.MultiWriter(f, os.Stderr),
LogLevel: level,
TickInterval: time.Millisecond * 30,
}
app := core.New(appConfig)
ctx, cancel := context.WithCancel(context.Background())
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
app.L.Info("shutting down")
cancel()
}()
app.Run(ctx)
}

@ -1,24 +1,37 @@
package core
import (
"bufio"
"context"
"io"
"log/slog"
"os"
"time"
"github.com/EngoEngine/ecs"
)
type Printer struct {
}
type AppConfig struct {
LogDest io.Writer
LogLevel string
Port string
TickInterval time.Duration
World *ecs.World
}
type App struct {
Config AppConfig
W *ecs.World
L *slog.Logger
}
func New(level string, logFile io.Writer) *App {
func New(ac AppConfig) *App {
var logLevel slog.Level
switch level {
switch ac.LogLevel {
case "INFO", "info":
logLevel = slog.LevelInfo
case "DEBUG", "debug":
@ -28,43 +41,40 @@ func New(level string, logFile io.Writer) *App {
}
l := slog.New(slog.NewTextHandler(
logFile, &slog.HandlerOptions{
ac.LogDest, &slog.HandlerOptions{
Level: logLevel,
},
),
)
world := &ecs.World{}
ac.World = world
return &App{
L: l,
W: world,
Config: ac,
}
}
func (a *App) Run(ctx context.Context) {
go func() {
t := time.NewTicker(time.Millisecond * 30)
t := time.NewTicker(a.Config.TickInterval)
for range t.C {
a.W.Update(1)
}
}()
scanner := bufio.NewScanner(os.Stdin)
var line string
s := NewServer(ctx, a.L, a.Config.Port, world)
go s.Start()
for {
select {
case <-ctx.Done():
a.L.Error("cancelled")
return
default:
if scanner.Scan() {
line = scanner.Text()
case session := <-s.Sessions:
go session.Start()
}
a.L.Info("scanning input")
a.L.With(slog.String("input", line)).Info("received command")
}
}
}

@ -0,0 +1,68 @@
package core
import (
"context"
"log/slog"
"net"
"time"
"github.com/EngoEngine/ecs"
)
type Server struct {
l *slog.Logger
port string
ctx context.Context
Sessions chan *Session
ln net.Listener
w *ecs.World
}
func NewServer(ctx context.Context, l *slog.Logger, port string, w *ecs.World) *Server {
var s Server
c := make(chan *Session)
s.Sessions = c
lc := net.ListenConfig{
KeepAlive: 5 * time.Minute,
KeepAliveConfig: net.KeepAliveConfig{
Enable: true,
Idle: 3 * time.Minute,
Interval: 15 * time.Second,
},
}
ln, err := lc.Listen(ctx, "tcp", port)
if err != nil {
s.l.With(
slog.Any("error", err),
).Error("could not start tcp listener")
return &s
}
s.ctx = ctx
s.ln = ln
s.l = l
s.port = port
s.w = w
return &s
}
func (s *Server) Start() {
s.l.Debug("starting telnet server")
for {
select {
case <-s.ctx.Done():
return
default:
conn, err := s.ln.Accept()
if err != nil {
s.l.With(
slog.Any("error", err),
).Error("could not accept tcp connection")
}
s.Sessions <- NewSession(s.ctx, conn, Printer{}, s.l)
}
}
}

@ -0,0 +1,75 @@
package core
import (
"bufio"
"context"
"fmt"
"log/slog"
"net"
"github.com/EngoEngine/ecs"
"github.com/google/uuid"
)
type Session struct {
c net.Conn
ctx context.Context
p Printer
l *slog.Logger
w *ecs.World
sessionID uuid.UUID
userID uint64
}
func NewSession(ctx context.Context, c net.Conn, p Printer, l *slog.Logger, w *ecs.World) *Session {
sid := uuid.New()
return &Session{
ctx: ctx,
l: l.With(slog.String("sid", sid.String())),
c: c,
w: w,
p: p,
sessionID: sid,
}
}
func (s *Session) bufferInput(commands chan string) {
scanner := bufio.NewScanner(s.c)
defer close(commands)
for scanner.Scan() {
err := scanner.Err()
switch err {
case nil:
commands <- scanner.Text()
default:
s.l.With(slog.Any("error", scanner.Err())).Error("error scanning input")
return
}
}
s.l.Debug("session terminated")
}
func (s *Session) Start() {
s.l.Debug("starting telnet session")
fmt.Fprint(s.c, "Welcome to Bing Bong.\n")
commands := make(chan string)
go s.bufferInput(commands)
for command := range commands {
s.l.With(slog.String("command", command)).Debug("command recieved")
_, err := fmt.Fprintf(s.c, "> %q\n", command)
if err != nil {
s.l.With(
slog.Any("error", err),
).Error("connection closed on write")
return
}
}
}

@ -4,35 +4,32 @@
package db
import (
"database/sql"
)
type Exit struct {
ID int64
To sql.NullInt64
From sql.NullInt64
ID int64 `db:"id" json:"id"`
Origin int64 `db:"origin" json:"origin"`
Dest int64 `db:"dest" json:"dest"`
}
type Inventory struct {
ID int64
ID int64 `db:"id" json:"id"`
}
type Location struct {
ID int64
X int64
Y int64
Z int64
World int64
ID int64 `db:"id" json:"id"`
X int64 `db:"x" json:"x"`
Y int64 `db:"y" json:"y"`
Z int64 `db:"z" json:"z"`
World int64 `db:"world" json:"world"`
Observable int64 `db:"observable" json:"observable"`
}
type Observable struct {
ID int64
Name string
Description string
ID int64 `db:"id" json:"id"`
Name string `db:"name" json:"name"`
Description string `db:"description" json:"description"`
}
type Pool struct {
ID int64
Type int64
ID int64 `db:"id" json:"id"`
Type int64 `db:"type" json:"type"`
}

@ -0,0 +1,56 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
package db
import (
"context"
)
type Querier interface {
// Location
//
// insert INTO locations (x,y,z,world, observable) VALUES (?,?,?,?,?)
CreateLocation(ctx context.Context, arg CreateLocationParams) (int64, error)
// Observable
//
// insert INTO observables (name, description) VALUES (?,?)
CreateObservable(ctx context.Context, arg CreateObservableParams) (int64, error)
//DestroyLocation
//
// delete from locations where id = ?
DestroyLocation(ctx context.Context, id int64) error
// Exit
//
// select id, origin, dest from exits where id = ? LIMIT 1
GetExit(ctx context.Context, id int64) (Exit, error)
//GetLocation
//
// select id, x, y, z, world, observable from locations where id = ? LIMIT 1
GetLocation(ctx context.Context, id int64) (Location, error)
//GetLocationExitsFrom
//
// select id, origin, dest from exits where dest = ?
GetLocationExitsFrom(ctx context.Context, dest int64) ([]Exit, error)
//GetLocationExitsTo
//
// select id, origin, dest from exits where origin = ?
GetLocationExitsTo(ctx context.Context, origin int64) ([]Exit, error)
//GetObservable
//
// select id, name, description from observables where id = ? LIMIT 1
GetObservable(ctx context.Context, id int64) (Observable, error)
//UpdateLocation
//
// update locations SET
// x = ?,
// y = ?,
// z = ?,
// world = ?,
// observable = ?
// where id = ?
UpdateLocation(ctx context.Context, arg UpdateLocationParams) error
}
var _ Querier = (*Queries)(nil)

@ -7,34 +7,90 @@ package db
import (
"context"
"database/sql"
)
const createLocation = `-- name: CreateLocation :exec
insert INTO LOCATIONS (x,y,z,world) VALUES (?,?,?,?)
const createLocation = `-- name: CreateLocation :execlastid
insert INTO locations (x,y,z,world, observable) VALUES (?,?,?,?,?)
`
type CreateLocationParams struct {
X int64
Y int64
Z int64
World int64
X int64 `db:"x" json:"x"`
Y int64 `db:"y" json:"y"`
Z int64 `db:"z" json:"z"`
World int64 `db:"world" json:"world"`
Observable int64 `db:"observable" json:"observable"`
}
func (q *Queries) CreateLocation(ctx context.Context, arg CreateLocationParams) error {
_, err := q.db.ExecContext(ctx, createLocation,
// Location
//
// insert INTO locations (x,y,z,world, observable) VALUES (?,?,?,?,?)
func (q *Queries) CreateLocation(ctx context.Context, arg CreateLocationParams) (int64, error) {
result, err := q.db.ExecContext(ctx, createLocation,
arg.X,
arg.Y,
arg.Z,
arg.World,
arg.Observable,
)
if err != nil {
return 0, err
}
return result.LastInsertId()
}
const createObservable = `-- name: CreateObservable :execlastid
insert INTO observables (name, description) VALUES (?,?)
`
type CreateObservableParams struct {
Name string `db:"name" json:"name"`
Description string `db:"description" json:"description"`
}
// Observable
//
// insert INTO observables (name, description) VALUES (?,?)
func (q *Queries) CreateObservable(ctx context.Context, arg CreateObservableParams) (int64, error) {
result, err := q.db.ExecContext(ctx, createObservable, arg.Name, arg.Description)
if err != nil {
return 0, err
}
return result.LastInsertId()
}
const destroyLocation = `-- name: DestroyLocation :exec
delete from locations where id = ?
`
// DestroyLocation
//
// delete from locations where id = ?
func (q *Queries) DestroyLocation(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, destroyLocation, id)
return err
}
const getExit = `-- name: GetExit :one
select id, origin, dest from exits where id = ? LIMIT 1
`
// Exit
//
// select id, origin, dest from exits where id = ? LIMIT 1
func (q *Queries) GetExit(ctx context.Context, id int64) (Exit, error) {
row := q.db.QueryRowContext(ctx, getExit, id)
var i Exit
err := row.Scan(&i.ID, &i.Origin, &i.Dest)
return i, err
}
const getLocation = `-- name: GetLocation :one
select id, x, y, z, world from locations where id = ? LIMIT 1
select id, x, y, z, world, observable from locations where id = ? LIMIT 1
`
// GetLocation
//
// select id, x, y, z, world, observable from locations where id = ? LIMIT 1
func (q *Queries) GetLocation(ctx context.Context, id int64) (Location, error) {
row := q.db.QueryRowContext(ctx, getLocation, id)
var i Location
@ -44,16 +100,20 @@ func (q *Queries) GetLocation(ctx context.Context, id int64) (Location, error) {
&i.Y,
&i.Z,
&i.World,
&i.Observable,
)
return i, err
}
const getLocationExitsFrom = `-- name: GetLocationExitsFrom :many
select id, "to", "from" from exits where from = ? LIMIT 1
select id, origin, dest from exits where dest = ?
`
func (q *Queries) GetLocationExitsFrom(ctx context.Context, from sql.NullInt64) ([]Exit, error) {
rows, err := q.db.QueryContext(ctx, getLocationExitsFrom, from)
// GetLocationExitsFrom
//
// select id, origin, dest from exits where dest = ?
func (q *Queries) GetLocationExitsFrom(ctx context.Context, dest int64) ([]Exit, error) {
rows, err := q.db.QueryContext(ctx, getLocationExitsFrom, dest)
if err != nil {
return nil, err
}
@ -61,7 +121,7 @@ func (q *Queries) GetLocationExitsFrom(ctx context.Context, from sql.NullInt64)
var items []Exit
for rows.Next() {
var i Exit
if err := rows.Scan(&i.ID, &i.To, &i.From); err != nil {
if err := rows.Scan(&i.ID, &i.Origin, &i.Dest); err != nil {
return nil, err
}
items = append(items, i)
@ -76,11 +136,14 @@ func (q *Queries) GetLocationExitsFrom(ctx context.Context, from sql.NullInt64)
}
const getLocationExitsTo = `-- name: GetLocationExitsTo :many
select id, "to", "from" from exits where to = ? LIMIT 1
select id, origin, dest from exits where origin = ?
`
func (q *Queries) GetLocationExitsTo(ctx context.Context, to sql.NullInt64) ([]Exit, error) {
rows, err := q.db.QueryContext(ctx, getLocationExitsTo, to)
// GetLocationExitsTo
//
// select id, origin, dest from exits where origin = ?
func (q *Queries) GetLocationExitsTo(ctx context.Context, origin int64) ([]Exit, error) {
rows, err := q.db.QueryContext(ctx, getLocationExitsTo, origin)
if err != nil {
return nil, err
}
@ -88,7 +151,7 @@ func (q *Queries) GetLocationExitsTo(ctx context.Context, to sql.NullInt64) ([]E
var items []Exit
for rows.Next() {
var i Exit
if err := rows.Scan(&i.ID, &i.To, &i.From); err != nil {
if err := rows.Scan(&i.ID, &i.Origin, &i.Dest); err != nil {
return nil, err
}
items = append(items, i)
@ -101,3 +164,57 @@ func (q *Queries) GetLocationExitsTo(ctx context.Context, to sql.NullInt64) ([]E
}
return items, nil
}
const getObservable = `-- name: GetObservable :one
select id, name, description from observables where id = ? LIMIT 1
`
// GetObservable
//
// select id, name, description from observables where id = ? LIMIT 1
func (q *Queries) GetObservable(ctx context.Context, id int64) (Observable, error) {
row := q.db.QueryRowContext(ctx, getObservable, id)
var i Observable
err := row.Scan(&i.ID, &i.Name, &i.Description)
return i, err
}
const updateLocation = `-- name: UpdateLocation :exec
update locations SET
x = ?,
y = ?,
z = ?,
world = ?,
observable = ?
where id = ?
`
type UpdateLocationParams struct {
X int64 `db:"x" json:"x"`
Y int64 `db:"y" json:"y"`
Z int64 `db:"z" json:"z"`
World int64 `db:"world" json:"world"`
Observable int64 `db:"observable" json:"observable"`
ID int64 `db:"id" json:"id"`
}
// UpdateLocation
//
// update locations SET
// x = ?,
// y = ?,
// z = ?,
// world = ?,
// observable = ?
// where id = ?
func (q *Queries) UpdateLocation(ctx context.Context, arg UpdateLocationParams) error {
_, err := q.db.ExecContext(ctx, updateLocation,
arg.X,
arg.Y,
arg.Z,
arg.World,
arg.Observable,
arg.ID,
)
return err
}

@ -1,11 +1,30 @@
-- name: CreateLocation :exec
insert INTO LOCATIONS (x,y,z,world) VALUES (?,?,?,?);
-- Location
-- name: CreateLocation :execlastid
insert INTO locations (x,y,z,world, observable) VALUES (?,?,?,?,?);
-- name: GetLocation :one
select * from locations where id = ? LIMIT 1;
-- name: UpdateLocation :exec
update locations SET
x = ?,
y = ?,
z = ?,
world = ?,
observable = ?
where id = ?;
-- name: DestroyLocation :exec
delete from locations where id = ?;
-- name: GetLocationExitsTo :many
select * from exits where to = ? LIMIT 1;
-- Exit
-- name: GetExit :one
select * from exits where id = ? LIMIT 1;
-- name: GetLocationExitsTo :many
select * from exits where origin = ?;
-- name: GetLocationExitsFrom :many
select * from exits where from = ? LIMIT 1;
select * from exits where dest = ?;
-- Observable
-- name: CreateObservable :execlastid
insert INTO observables (name, description) VALUES (?,?);
-- name: GetObservable :one
select * from observables where id = ? LIMIT 1;

@ -1,28 +1,32 @@
create TABLE locations (
CREATE TABLE locations (
id INTEGER PRIMARY KEY,
x INTEGER NOT NULL,
y INTEGER NOT NULL,
z INTEGER NOT NULL,
world INTEGER NOT NULL
)
world INTEGER NOT NULL,
observable INTEGER NOT NULL references observables(id)
);
create TABLE exits (
id INTEGER PRIMARY KEY,
to INTEGER REFERENCES locations(id),
from INTEGER REFERENCES locations(id)
)
create TABLE observables (
CREATE TABLE observables (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL
)
);
create TABLE inventories (
CREATE TABLE inventories (
id INTEGER PRIMARY KEY
)
);
create TABLE pools (
CREATE TABLE pools (
id INTEGER PRIMARY KEY,
type INTEGER NOT NULL
)
);
CREATE TABLE exits (
id INTEGER PRIMARY KEY,
origin INTEGER NOT NULL,
dest INTEGER NOT NULL,
FOREIGN KEY (origin) REFERENCES locations (id),
FOREIGN KEY (dest) REFERENCES locations (id)
);

@ -6,5 +6,8 @@ sql:
gen:
go:
emit_db_tags: true
emit_interface: true
emit_json_tags: true
emit_sql_as_comment: true
package: "db"
out: "db"

Loading…
Cancel
Save