Added rendering stuff, updated go mod

This commit is contained in:
Nathan Anderson 2024-08-08 18:51:51 -06:00
parent f7eaa0d08d
commit d426892e6a
9 changed files with 447 additions and 3 deletions

View File

@ -0,0 +1,11 @@
package main
import (
"fmt"
"sshpong/internal/renderer"
)
func main() {
fmt.Println("Starting renderer")
renderer.Render()
}

View File

@ -18,6 +18,45 @@
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gomod2nix": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1722589758,
"narHash": "sha256-sbbA8b6Q2vB/t/r1znHawoXLysCyD4L/6n6/RykiSnA=",
"owner": "tweag",
"repo": "gomod2nix",
"rev": "4e08ca09253ef996bd4c03afa383b23e35fe28a1",
"type": "github"
},
"original": {
"owner": "tweag",
"repo": "gomod2nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1722421184,
@ -37,6 +76,7 @@
"root": {
"inputs": {
"flake-utils": "flake-utils",
"gomod2nix": "gomod2nix",
"nixpkgs": "nixpkgs"
}
},
@ -54,6 +94,21 @@
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",

View File

@ -2,15 +2,22 @@
description = "An example project using flutter";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.gomod2nix = {
url = "github:tweag/gomod2nix";
inputs.nixpkgs.follows = "nixpkgs";
inputs.utils.follows = "utils";
};
outputs = {
self,
flake-utils,
nixpkgs,
gomod2nix,
...
} @ inputs:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {
inherit system;
overlays = [ gomod2nix.overlays.default ];
};
in {
devShell = pkgs.mkShell {
@ -19,6 +26,8 @@
gopls
gotools
go-tools
gomod2nix.packages.${system}.default
sqlite-interactive
];
};
});

2
go.mod
View File

@ -5,4 +5,6 @@ go 1.22.2
require (
github.com/google/uuid v1.6.0
google.golang.org/protobuf v1.34.2
golang.org/x/sys v0.23.0 // indirect
golang.org/x/term v0.22.0 // indirect
)

4
go.sum
View File

@ -2,3 +2,7 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=

15
gomod2nix.toml Normal file
View File

@ -0,0 +1,15 @@
schema = 3
[mod]
[mod."github.com/google/uuid"]
version = "v1.6.0"
hash = "sha256-VWl9sqUzdOuhW0KzQlv0gwwUQClYkmZwSydHG2sALYw="
[mod."golang.org/x/sys"]
version = "v0.23.0"
hash = "sha256-tC6QVLu72bADgINz26FUGdmYqKgsU45bHPg7sa0ZV7w="
[mod."golang.org/x/term"]
version = "v0.22.0"
hash = "sha256-tRx/y4ZIZzGAlDJ/8JW3AycC9bRXlNuRqO4V48sAEEc="
[mod."google.golang.org/protobuf"]
version = "v1.34.2"
hash = "sha256-nMTlrDEE2dbpWz50eQMPBQXCyQh4IdjrTIccaU0F3m0="

135
internal/ansii/ansii.go Normal file
View File

@ -0,0 +1,135 @@
package ansii
import (
"fmt"
"os"
"strings"
"golang.org/x/term"
)
type ANSI string
const (
reset ANSI = "\033[0m"
plain ANSI = ""
bold ANSI = "\033[1m"
underline ANSI = "\033[4m"
black ANSI = "\033[30m"
red ANSI = "\033[31m"
green ANSI = "\033[32m"
yellow ANSI = "\033[33m"
blue ANSI = "\033[34m"
purple ANSI = "\033[35m"
cyan ANSI = "\033[36m"
white ANSI = "\033[37m"
blackBg ANSI = "\033[40m"
redBg ANSI = "\033[41m"
greenBg ANSI = "\033[42m"
yellowBg ANSI = "\033[43m"
blueBg ANSI = "\033[44m"
purpleBg ANSI = "\033[45m"
cyanBg ANSI = "\033[46m"
whiteBg ANSI = "\033[47m"
clearScreen ANSI = "\033[2J"
hideCursor ANSI = "\033[?25l"
showCursor ANSI = "\033[?25h"
)
type Offset struct {
X int
Y int
}
type style struct {
Reset ANSI
Plain ANSI
Bold ANSI
Underline ANSI
}
type color struct {
Black ANSI
Red ANSI
Green ANSI
Yellow ANSI
Blue ANSI
Purple ANSI
Cyan ANSI
White ANSI
}
type screen struct {
ClearScreen ANSI
HideCursor ANSI
ShowCursor ANSI
}
type ascii struct {
Block string
}
func GetTermSize() (width int, height int) {
var fd int = int(os.Stdout.Fd())
width, height, err := term.GetSize(fd)
if err != nil {
fmt.Println(string(Screen.ClearScreen) + "Fatal: error getting terminal size.")
os.Exit(1)
}
return width, height
}
func MakeTermRaw() (*term.State, error) {
var fd int = int(os.Stdout.Fd())
return term.MakeRaw(fd)
}
func RestoreTerm(prev *term.State) error {
var fd int = int(os.Stdout.Fd())
return term.Restore(fd, prev)
}
func (s screen) PlaceCursor(offset Offset) ANSI {
return ANSI(fmt.Sprintf("\033[%d;%dH", offset.Y, offset.X))
}
var (
Styles = style{Bold: bold, Underline: underline, Reset: reset, Plain: plain}
Colors = color{Red: red, Green: green, Yellow: yellow, Blue: blue, Purple: purple, Cyan: cyan, White: white}
Screen = screen{ClearScreen: clearScreen, HideCursor: hideCursor, ShowCursor: showCursor}
Blocks = ascii{Block: "█"}
)
// Draws a box of dimensions `height` and `width` at `offset`.
// The `offset` is the top left cell of the square.
// Blocks that would be placed off screen are clipped.
func DrawBox(builder *strings.Builder, offset Offset, height int, width int, style ANSI) {
builder.WriteString(string(style))
for hIdx := 0; hIdx < height; hIdx++ {
if hIdx == 0 || hIdx == height-1 {
for wIdx := 0; wIdx < width; wIdx++ {
DrawPixel(builder, Offset{X: offset.X + wIdx, Y: offset.Y + hIdx})
}
} else {
DrawPixel(builder, Offset{X: offset.X, Y: offset.Y + hIdx})
DrawPixel(builder, Offset{X: offset.X + width - 1, Y: offset.Y + hIdx})
}
}
builder.WriteString(string(Styles.Reset))
return
}
func DrawPixel(builder *strings.Builder, offset Offset) {
termWidth, termHeight := GetTermSize()
if offset.X > termWidth || offset.Y > termHeight || offset.X < 0 || offset.Y < 0 {
return
}
builder.WriteString(string(Screen.PlaceCursor(offset) + ANSI(Blocks.Block)))
}
func DrawPixelStyle(builder *strings.Builder, offset Offset, style ANSI) {
builder.WriteString(string(style))
DrawPixel(builder, offset)
builder.WriteString(string(Styles.Reset))
}

View File

@ -1,7 +1,195 @@
package renderer
import "fmt"
import (
"fmt"
"os"
"sshpong/internal/ansii"
"strings"
"time"
"unicode/utf8"
)
func render() {
fmt.Println("Test")
var (
targetFps float64 = 60.0
targetFpMilli float64 = float64(targetFps) / 1000.0
millisecondTimeFrame float64 = float64(1 / targetFpMilli)
quit chan bool
userInput chan rune
playerX int = 10
playerY int = 10
)
func Render() {
now := time.Now()
err := doFpsTest()
if err != nil {
fmt.Println("Error ", err)
return
}
then := time.Now()
total := float64(float64(then.UnixMicro()-now.UnixMicro()) / 1000.0)
fmt.Printf("\n\nTook %.3f milliseconds\n", total)
}
func writeCheckerBoard(height int, width int, builder *strings.Builder) {
for i := 0; i < height; i++ {
for j := 0; j < width; j++ {
if i%2 == 0 {
if j%2 == 0 {
builder.WriteString("█")
} else {
builder.WriteString(" ")
}
} else {
if j%2 == 0 {
builder.WriteString(" ")
} else {
builder.WriteString("█")
}
}
}
}
}
func drawScreen(frameNum int, startMs float64) (frameTimeMs float64) {
_ = frameNum
_, height := ansii.GetTermSize()
var builder = strings.Builder{}
builder.WriteString(string(ansii.Screen.ClearScreen))
// writeCheckerBoard(height, width, &builder)
// var xOffset = frameNum % width
// ansii.DrawBox(&builder, ansii.Offset{X: 0, Y: 0}, 5, 8, ansii.Colors.Purple)
ansii.DrawBox(&builder, ansii.Offset{X: playerX, Y: playerY}, 5, 1, ansii.Colors.Cyan)
ansii.DrawPixelStyle(&builder, ansii.Offset{X: playerX, Y: playerY}, ansii.Colors.Purple)
ansii.DrawPixelStyle(&builder, ansii.Offset{X: playerX, Y: playerY + 5}, ansii.Colors.Purple)
// Quit instructions
builder.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: 0, Y: height})))
builder.WriteString("q to quit")
os.Stdout.WriteString(builder.String())
var frameTimeMilli = (float64(time.Now().UnixNano()) / 1_000_000.0) - startMs
// builder.WriteString(string(ansii.Screen.Coordinate(0, 0)))
return frameTimeMilli
}
func drawFrameStats(frameNum int, frameTimeMs float64) {
width, height := ansii.GetTermSize()
var spareTimeMilli = millisecondTimeFrame - frameTimeMs
os.Stdout.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: width - 12, Y: height - 2})))
os.Stdout.WriteString(fmt.Sprintf("Frame #: %d", frameNum))
os.Stdout.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: width - 19, Y: height - 1})))
os.Stdout.WriteString(fmt.Sprintf("Frame Time: %.4fms", frameTimeMs))
os.Stdout.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: width - 20, Y: height})))
os.Stdout.WriteString(fmt.Sprintf("Spare Time: %.4fms", spareTimeMilli))
}
func handleInput(rawInput rune) {
action := ProcessInput(rawInput)
width, height := ansii.GetTermSize()
switch action {
case Quit:
fmt.Println("Quitting...")
close(quit)
case Left, LeftArrow:
playerX = max(playerX-1, 0)
case Right, RightArrow:
playerX = min(playerX+1, width)
case Up, UpArrow:
playerY = max(playerY-1, 0)
case Down, DownArrow:
playerY = min(playerY+1, height)
case Unknown:
default:
os.Stdout.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: 0, Y: height - 2})))
os.Stdout.WriteString("Unrecognized Input: " + string(action))
close(quit)
}
}
func waitForFpsLock(startMs float64) {
for {
var nowMs = float64(time.Now().UnixNano()) / 1_000_000.0
if nowMs-startMs >= millisecondTimeFrame {
break
}
}
}
func doFpsTest() error {
prev, err := ansii.MakeTermRaw()
if err != nil {
return err
}
defer ansii.RestoreTerm(prev)
quit = make(chan bool, 1)
userInput = make(chan rune, 1)
// User input loop
go func() {
for {
buf := make([]byte, 3)
n, err := os.Stdin.Read(buf)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading from stdin: %v\n", err)
continue
}
if n > 0 {
if buf[0] == 0x1b { // ESC
if n > 1 && buf[1] == '[' { // ESC [
switch buf[2] {
case 'A':
userInput <- '↑' // Up arrow
case 'B':
userInput <- '↓' // Down arrow
case 'C':
userInput <- '→' // Right arrow
case 'D':
userInput <- '←' // Left arrow
default:
userInput <- '?'
}
} else {
userInput <- '?'
}
} else {
r, _ := utf8.DecodeRune(buf)
userInput <- r
}
}
}
}()
// Rendering loop
go func() {
for i := 0; i <= 10_000; i++ {
startMs := float64(time.Now().UnixNano()) / 1_000_000.0
select {
case <-quit:
return
default:
frameTimeMs := drawScreen(i, startMs)
drawFrameStats(i, frameTimeMs)
waitForFpsLock(startMs)
}
}
close(quit)
}()
os.Stdout.WriteString(string(ansii.Screen.HideCursor))
defer os.Stdout.WriteString(string(ansii.Screen.ShowCursor))
for {
select {
case <-quit:
fmt.Println("Exiting")
return nil
// case ui := <-userInput:
case input := <-userInput:
handleInput(input)
// fmt.Println(ui)
default:
}
}
}

View File

@ -0,0 +1,25 @@
package renderer
type UiAction rune
const (
Unknown UiAction = iota
Quit UiAction = 81 // 'Q'
Left UiAction = 65
Up UiAction = 87
Right UiAction = 68
Down UiAction = 83
LeftArrow UiAction = 8592
UpArrow UiAction = 8593
RightArrow UiAction = 8594
DownArrow UiAction = 8595
)
func ProcessInput(rawInput rune) (action UiAction) {
inputVal := int(rawInput)
// Convert to UpperCase
if inputVal >= 97 && inputVal <= 122 {
inputVal = inputVal - 32
}
return UiAction(inputVal)
}