Hochwasser/rpc/repl.go

207 lines
4.9 KiB
Go
Raw Permalink Normal View History

2020-02-14 22:29:58 +01:00
package rpc
import (
"bufio"
"encoding/hex"
"fmt"
"image"
"image/color"
"os"
"strconv"
"strings"
2020-12-31 18:44:56 +01:00
"github.com/SpeckiJ/Hochwasser/pixelflut"
2020-02-14 22:29:58 +01:00
"github.com/SpeckiJ/Hochwasser/render"
)
// Fluter implements flut operations that can be triggered via a REPL
type Fluter interface {
2020-12-31 18:44:56 +01:00
getTask() pixelflut.FlutTask
applyTask(pixelflut.FlutTask)
2020-02-14 22:29:58 +01:00
stopTask()
toggleMetrics()
}
const commandMode = "cmd"
const textMode = "txt"
2020-02-14 22:29:58 +01:00
// RunREPL starts reading os.Stdin for commands to apply to the given Fluter
func RunREPL(f Fluter) {
mode := commandMode
2020-12-28 00:58:50 +01:00
textSize := 10
var textCol image.Image = image.White
var bgCol image.Image = image.Transparent
2020-02-14 22:29:58 +01:00
fmt.Print("[rán] REPL is active. ")
printHelp()
2020-02-14 22:29:58 +01:00
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
inputStr := scanner.Text()
switch strings.ToLower(mode) {
2020-02-14 22:29:58 +01:00
case textMode:
if strings.ToLower(inputStr) == commandMode {
2020-02-14 22:29:58 +01:00
fmt.Println("[rán] command mode")
mode = commandMode
continue
}
t := f.getTask()
2020-02-15 13:58:01 +01:00
t.Img = render.RenderText(inputStr, textSize, textCol, bgCol)
2020-02-14 22:29:58 +01:00
f.applyTask(t)
case commandMode:
input := strings.Split(inputStr, " ")
cmd := strings.ToLower(input[0])
args := input[1:]
t := f.getTask()
printTask := true
2020-02-14 22:29:58 +01:00
switch cmd {
case "stop":
t.Paused = true
2020-02-14 22:29:58 +01:00
f.stopTask()
printTask = false
2020-02-14 22:29:58 +01:00
case "start":
t.Paused = false
2020-02-14 22:29:58 +01:00
case "offset", "of":
if len(args) >= 1 && args[0][0] == 'r' {
t.Offset.Random = true
t.Offset.Mask = nil
if len(args) >= 2 {
fmt.Println(strings.Join(args[1:], " "))
mask, err := render.ReadImage(strings.Join(args[1:], " "))
if err != nil {
fmt.Printf("couldn't read mask image: %s\n", err)
continue
}
t.Offset.Mask = mask
}
} else if len(args) == 2 {
t.Offset.Random = false
t.Offset.Mask = nil
2020-02-14 22:29:58 +01:00
x, err := strconv.Atoi(args[0])
y, err2 := strconv.Atoi(args[1])
if err == nil && err2 == nil {
t.Offset.Point = image.Pt(x, y)
2020-02-14 22:29:58 +01:00
}
}
case "connections", "c":
2020-02-14 22:29:58 +01:00
if len(args) == 1 {
if conns, err := strconv.Atoi(args[0]); err == nil {
t.MaxConns = conns
}
}
case "address", "a":
if len(args) == 1 {
t.Address = args[0]
}
case "order", "o":
if len(args) == 1 {
t.RenderOrder = pixelflut.NewOrder(args[0])
}
2020-02-14 22:29:58 +01:00
2020-12-29 18:28:19 +01:00
case "rgbsplit":
t.RGBSplit = !t.RGBSplit
2020-02-14 22:29:58 +01:00
case "txt":
if len(args) > 0 {
if size, err := strconv.Atoi(args[0]); err == nil {
textSize = size
}
}
if len(args) > 1 {
2020-12-28 00:58:50 +01:00
textCol = parseColorOrPalette(args[1])
2020-02-14 22:29:58 +01:00
}
2020-02-15 13:58:01 +01:00
if len(args) > 2 {
2020-12-28 00:58:50 +01:00
bgCol = parseColorOrPalette(args[2])
}
if len(args) < 4 {
fmt.Printf("[rán] text mode, return via '%v'\n", strings.ToUpper(commandMode))
2020-12-28 00:58:50 +01:00
mode = textMode
printTask = false
2020-12-28 00:58:50 +01:00
} else {
input := strings.Join(args[3:], " ")
t.Img = render.RenderText(input, textSize, textCol, bgCol)
2020-02-15 13:58:01 +01:00
}
2020-02-14 22:29:58 +01:00
case "img", "i":
2020-02-14 22:29:58 +01:00
if len(args) > 0 {
path := strings.Join(args, " ")
if img, err := render.ReadImage(path); err != nil {
fmt.Println(err)
2020-12-31 18:44:56 +01:00
continue
2020-02-14 22:29:58 +01:00
} else {
2020-12-31 17:42:53 +01:00
t.Img = img
2020-02-14 22:29:58 +01:00
}
}
case "metrics":
f.toggleMetrics()
printTask = false
default:
printTask = false
fmt.Print("[rán] unknown command. ")
printHelp()
}
2020-02-14 22:29:58 +01:00
if printTask {
fmt.Println(t)
2020-02-14 22:29:58 +01:00
}
f.applyTask(t)
2020-02-14 22:29:58 +01:00
}
}
}
2020-12-28 00:58:50 +01:00
func printHelp() {
fmt.Println(`available commands:
2020-12-31 18:44:56 +01:00
start start fluting
stop pause fluting
c <n> set number of connections per client
a <host>:<port> set target server
2020-12-31 18:44:56 +01:00
offset <x> <y> set top-left offset
offset rand random offset for each draw
metrics toggle bandwidth reporting (may cost some performance)
i <filepath> set image
2020-12-31 18:44:56 +01:00
txt <scale> <color <bgcolor> <txt> send text
txt [<scale> [<color> [<bgcolor>]] enter interactive text mode
rgbsplit toggle RGB split effect
o set order (l,r,t,b,shuffle)`)
}
2020-12-28 00:58:50 +01:00
// try to parse as hex-encoded RGB color,
// alternatively treat it as palette name. If both fail,
// give image.Transparent
func parseColorOrPalette(input string) image.Image {
if input == "w" {
return image.NewUniform(color.White)
} else if input == "b" {
return image.NewUniform(color.Black)
} else if input == "t" {
return image.Transparent
} else if col, err := hex.DecodeString(input); err == nil && len(col) >= 3 {
2020-12-28 00:58:50 +01:00
var alpha byte = 0xff
if len(col) == 4 {
alpha = col[3]
}
return image.NewUniform(color.NRGBA{col[0], col[1], col[2], alpha})
}
if pal := render.PrideFlags[input]; len(pal) != 0 {
2020-12-29 21:09:00 +01:00
return &render.StripePattern{Palette: pal, Size: 13}
}
if p, ok := render.DynPatterns[input]; ok {
return p
}
return image.Transparent
2020-12-28 00:58:50 +01:00
}