diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..cf47c23 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,56 @@ +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 + cancel() + }() + + app.Run(ctx) +} diff --git a/core/core.go b/core/core.go index b7edec6..0f5908d 100644 --- a/core/core.go +++ b/core/core.go @@ -1,24 +1,35 @@ 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 +} + type App struct { - W *ecs.World - L *slog.Logger + 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,7 +39,7 @@ func New(level string, logFile io.Writer) *App { } l := slog.New(slog.NewTextHandler( - logFile, &slog.HandlerOptions{ + ac.LogDest, &slog.HandlerOptions{ Level: logLevel, }, ), @@ -37,34 +48,25 @@ func New(level string, logFile io.Writer) *App { world := &ecs.World{} return &App{ - L: l, - W: world, + 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 - for { - select { - case <-ctx.Done(): - a.L.Error("cancelled") - return - default: - if scanner.Scan() { - line = scanner.Text() - } - a.L.Info("scanning input") - a.L.With(slog.String("input", line)).Info("received command") - } - } + s := NewServer(ctx, a.L, a.Config.Port) + go s.Start() + for session := range s.Sessions { + go session.Start() + } } diff --git a/core/server.go b/core/server.go new file mode 100644 index 0000000..4fdc535 --- /dev/null +++ b/core/server.go @@ -0,0 +1,94 @@ +package core + +import ( + "bufio" + "context" + "fmt" + "log/slog" + "net" +) + +type Server struct { + l *slog.Logger + port string + ctx context.Context + Sessions chan *Session + ln net.Listener +} + +func NewServer(ctx context.Context, l *slog.Logger, port string) *Server { + var s Server + + // TODO: Check for context cancellation and cleanup + + c := make(chan *Session) + s.Sessions = c + ln, err := net.Listen("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 + + return &s +} + +func (s *Server) Start() { + s.l.Debug("starting telnet server") + // TODO: Check for context cancellation and cleanup + for { + 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) + + } +} + +type Session struct { + c net.Conn + ctx context.Context + p Printer + l *slog.Logger + + userID uint64 +} + +func NewSession(ctx context.Context, c net.Conn, p Printer, l *slog.Logger) *Session { + return &Session{ + ctx: ctx, + l: l, + p: p, + c: c, + } +} + +func (s *Session) Start() { + s.l.Debug("starting telnet session") + fmt.Fprint(s.c, "Welcome to Bing Bong.\n") + scanner := bufio.NewScanner(s.c) + var line string + for { + select { + case <-s.ctx.Done(): + s.l.Error("cancelled") + return + default: + if scanner.Scan() { + line = scanner.Text() + fmt.Fprintf(s.c, "> %q\n", line) + } + // a.L.Info("scanning input") + s.l.With(slog.String("input", line)).Info("received command") + } + } +}