23 Commits

Author SHA1 Message Date
bel
3264d9ad55 can send messages back to specific and ALL viewers 2023-03-25 11:27:49 -06:00
bel
3f35f7f936 manual test w rusty-pipe v0.1.3 ok 2023-03-25 11:01:38 -06:00
bel
0cddc33ac6 update host mp env file 2023-03-25 10:59:07 -06:00
bel
a1a12b1873 input Getenvs to FlagXYZ 2023-03-25 10:58:13 -06:00
bel
ae1e32391c refresh neither leaks wraps, allows 2 of the same at once, nor closes raws 2023-03-25 10:50:39 -06:00
bel
97cc3ae151 refresh users global ch 2023-03-25 10:25:11 -06:00
bel
2113252e2d no lookup env 2023-03-25 10:20:05 -06:00
bel
2cae3c6d28 dont do raw.New, instead add raw.Raw.Refresh explicit 2023-03-25 10:17:36 -06:00
bel
de261ae400 todo 2023-03-25 10:15:23 -06:00
bel
3dd0a557d4 add ctx to v01 2023-03-25 10:15:14 -06:00
bel
51ae1b27b4 on refresh, recreate raw too, because i dont wanna be leaking by not Closing on refresh 2023-03-25 10:13:25 -06:00
bel
50e89492cf todo 2023-03-25 09:12:44 -06:00
bel
3d9ea1296c external test on player transformation 2023-03-25 09:12:10 -06:00
bel
db69f76aa0 unit tests are good and v01cfg transforms input if players and user in players 2023-03-25 09:06:43 -06:00
bel
0ee3a8b6e8 todo 2023-03-25 00:44:30 -06:00
bel
b379f1d82c sample cfg file 2023-03-25 00:43:51 -06:00
bel
c83f9d8700 load v01 config 2023-03-25 00:30:13 -06:00
bel
6289222b69 todo 2023-03-25 00:13:22 -06:00
bel
607a65e22e if debugging then print lag to stderr 2023-03-25 00:11:12 -06:00
bel
6bbb297c59 todo 2023-03-25 00:06:32 -06:00
bel
95866f7df0 upgrade host.config.mp.env to v01 2023-03-25 00:02:08 -06:00
bel
aaa949cc2a upgrade host.configs.rusty-pipe to v01 2023-03-25 00:01:35 -06:00
bel
ed2b7b7cb9 rename v1 to v01 for git tag 2023-03-24 22:27:56 -06:00
34 changed files with 673 additions and 210 deletions

1
go.mod
View File

@@ -5,4 +5,5 @@ go 1.19
require (
github.com/go-yaml/yaml v2.1.0+incompatible // indirect
github.com/micmonay/keybd_event v1.1.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

3
go.sum
View File

@@ -2,3 +2,6 @@ github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwn
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/micmonay/keybd_event v1.1.1 h1:rv7omwXWYL9Lgf3PUq6uBgJI2k1yGkL/GD6dxc6nmSs=
github.com/micmonay/keybd_event v1.1.1/go.mod h1:CGMWMDNgsfPljzrAWoybUOSKafQPZpv+rLigt2LzNGI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@@ -1,6 +1,7 @@
export DEBUG=1
export DEBUG=true
export RAW_UDP=17070
export BUTTON_V01=true
export WRAP_REFRESH_ON_SIGUSR1=true
export WRAP_REMAP_FILE=./config.d/mayhem-party.d/remap.d/live.yaml
export MAIN_INTERVAL_DURATION=5ms
export OUTPUT_KEYBOARD=false
export BUTTON_V01_CONFIG=./config.d/mayhem-party.d/v01.yaml

View File

@@ -1 +1 @@
players_offset_0.yaml
players_offset_4.yaml

View File

@@ -0,0 +1,21 @@
feedback:
addr: :17071
users:
bel:
player: 2
message: its bel
broadcast:
message: 8
players:
- {}
- transformation:
w: t
a: f
s: g
d: h
q: r
e: y
1: 5
2: 6
3: 7
4: 8

View File

@@ -4,21 +4,23 @@ streams:
engine:
name: gui
gui:
user: bel
press: {prefix: "", suffix: ""}
release: {prefix: "!", suffix: ""}
release: {prefix: "", suffix: ""}
format: '{"T":{{ms}},"U":"{{user}}","Y":"{{pressed}}","N":"{{released}}"}'
buttons:
up: '1'
down: '2'
left: '3'
right: '4'
l: '5'
r: '6'
a: '7'
b: '8'
x: '9'
y: '0'
up: 'w'
down: 's'
left: 'a'
right: 'd'
l: 'e'
r: 'q'
a: '1'
b: '2'
x: '3'
y: '4'
output:
debug: false
debug: true
engine:
name: udp
udp:

View File

@@ -4,21 +4,23 @@ streams:
engine:
name: gui
gui:
user: zach
press: {prefix: "", suffix: ""}
release: {prefix: "!", suffix: ""}
release: {prefix: "", suffix: ""}
format: '{"T":{{ms}},"U":"{{user}}","Y":"{{pressed}}","N":"{{released}}"}'
buttons:
up: 'q'
down: 'w'
left: 'e'
right: 'r'
l: 't'
r: 'y'
a: 'u'
b: 'i'
x: 'o'
y: 'p'
up: 'w'
down: 's'
left: 'a'
right: 'd'
l: 'e'
r: 'q'
a: '1'
b: '2'
x: '3'
y: '4'
output:
debug: false
debug: true
engine:
name: udp
udp:

View File

@@ -4,21 +4,23 @@ streams:
engine:
name: gui
gui:
user: mason
press: {prefix: "", suffix: ""}
release: {prefix: "!", suffix: ""}
release: {prefix: "", suffix: ""}
format: '{"T":{{ms}},"U":"{{user}}","Y":"{{pressed}}","N":"{{released}}"}'
buttons:
up: 'a'
up: 'w'
down: 's'
left: 'd'
right: 'f'
l: 'g'
r: 'h'
a: 'j'
b: 'k'
x: 'l'
y: ';'
left: 'a'
right: 'd'
l: 'e'
r: 'q'
a: '1'
b: '2'
x: '3'
y: '4'
output:
debug: false
debug: true
engine:
name: udp
udp:

View File

@@ -4,21 +4,23 @@ streams:
engine:
name: gui
gui:
user: nat
press: {prefix: "", suffix: ""}
release: {prefix: "!", suffix: ""}
release: {prefix: "", suffix: ""}
format: '{"T":{{ms}},"U":"{{user}}","Y":"{{pressed}}","N":"{{released}}"}'
buttons:
up: 'z'
down: 'x'
left: 'c'
right: 'v'
l: 'b'
r: 'n'
a: 'm'
b: ','
x: '-'
y: '/'
up: 'w'
down: 's'
left: 'a'
right: 'd'
l: 'e'
r: 'q'
a: '1'
b: '2'
x: '3'
y: '4'
output:
debug: false
debug: true
engine:
name: udp
udp:

View File

@@ -4,21 +4,23 @@ streams:
engine:
name: gui
gui:
user: roxy
press: {prefix: "", suffix: ""}
release: {prefix: "!", suffix: ""}
release: {prefix: "", suffix: ""}
format: '{"T":{{ms}},"U":"{{user}}","Y":"{{pressed}}","N":"{{released}}"}'
buttons:
up: '!'
down: '@'
left: '#'
right: '$'
l: '%'
r: '^'
a: '&'
b: '*'
x: '('
y: ')'
up: 'w'
down: 's'
left: 'a'
right: 'd'
l: 'e'
r: 'q'
a: '1'
b: '2'
x: '3'
y: '4'
output:
debug: false
debug: true
engine:
name: udp
udp:

View File

@@ -0,0 +1,28 @@
streams:
input:
debug: false
engine:
name: gui
gui:
user: chase
press: {prefix: "", suffix: ""}
release: {prefix: "", suffix: ""}
format: '{"T":{{ms}},"U":"{{user}}","Y":"{{pressed}}","N":"{{released}}"}'
buttons:
up: 'w'
down: 's'
left: 'a'
right: 'd'
l: 'e'
r: 'q'
a: '1'
b: '2'
x: '3'
y: '4'
output:
debug: true
engine:
name: udp
udp:
host: mayhem-party.home.blapointe.com
port: 17070

View File

@@ -0,0 +1,28 @@
streams:
input:
debug: false
engine:
name: gui
gui:
user: bill
press: {prefix: "", suffix: ""}
release: {prefix: "", suffix: ""}
format: '{"T":{{ms}},"U":"{{user}}","Y":"{{pressed}}","N":"{{released}}"}'
buttons:
up: 'w'
down: 's'
left: 'a'
right: 'd'
l: 'e'
r: 'q'
a: '1'
b: '2'
x: '3'
y: '4'
output:
debug: true
engine:
name: udp
udp:
host: mayhem-party.home.blapointe.com
port: 17070

View File

@@ -6,14 +6,19 @@ import (
"os"
)
var (
FlagButtonV01 = os.Getenv("BUTTON_V01") == "true"
)
type Parser interface {
Read() []Button
Close()
CloseWrap() raw.Raw
}
func New(ctx context.Context, src raw.Raw) Parser {
if os.Getenv("BUTTON_PARSER_V1") == "true" {
return NewV1(src)
if FlagButtonV01 {
return NewV01(ctx, src)
}
return NewPlaintext(src)
}

View File

@@ -7,5 +7,5 @@ import (
func TestParser(t *testing.T) {
var _ button.Parser = button.Plaintext{}
var _ button.Parser = button.V1{}
var _ button.Parser = button.V01{}
}

View File

@@ -5,6 +5,10 @@ import (
"os"
)
var (
FlagButtonPlaintextRelease = os.Getenv("BUTTON_PLAINTEXT_RELEASE")
)
type Plaintext struct {
src raw.Raw
release byte
@@ -12,8 +16,8 @@ type Plaintext struct {
func NewPlaintext(src raw.Raw) Plaintext {
releaseChar := byte('!')
if v := os.Getenv("BUTTON_PLAINTEXT_RELEASE"); v != "" {
releaseChar = byte(v[0])
if FlagButtonPlaintextRelease != "" {
releaseChar = byte(FlagButtonPlaintextRelease[0])
}
return Plaintext{
src: src,
@@ -23,6 +27,8 @@ func NewPlaintext(src raw.Raw) Plaintext {
func (p Plaintext) Close() { p.src.Close() }
func (p Plaintext) CloseWrap() raw.Raw { return p.src }
func (p Plaintext) Read() []Button {
b := p.src.Read()
buttons := make([]Button, 0, len(b))

View File

@@ -0,0 +1,16 @@
users:
bel:
player: 0
message: "hi"
players:
- buttons:
up: "w"
down: "s"
left: "a"
right: "d"
l: "q"
r: "e"
a: "1"
b: "2"
x: "3"
y: "4"

View File

@@ -0,0 +1,155 @@
package button
import (
"context"
"encoding/json"
"io/ioutil"
"log"
"mayhem-party/src/device/input/raw"
"net/http"
"os"
"time"
"gopkg.in/yaml.v2"
)
var (
FlagDebug = os.Getenv("DEBUG") == "true"
FlagButtonV01Config = os.Getenv("BUTTON_V01_CONFIG")
)
type (
V01 struct {
ctx context.Context
can context.CancelFunc
src raw.Raw
cfg v01Cfg
}
v01Msg struct {
T int64
U string
Y string
N string
}
v01Cfg struct {
Feedback struct {
Addr string
}
Users map[string]struct {
Player int
Message string
}
Players []struct {
Transformation v01Transformation
}
}
v01Transformation map[string]string
)
func NewV01(ctx context.Context, src raw.Raw) V01 {
var cfg v01Cfg
b, _ := ioutil.ReadFile(FlagButtonV01Config)
yaml.Unmarshal(b, &cfg)
ctx, can := context.WithCancel(ctx)
result := V01{
ctx: ctx,
can: can,
src: src,
cfg: cfg,
}
go result.listen()
return result
}
func (v01 V01) listen() {
if v01.cfg.Feedback.Addr == "" {
return
}
s := &http.Server{
Addr: v01.cfg.Feedback.Addr,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r = r.WithContext(v01.ctx)
user, ok := v01.cfg.Users[r.URL.Query().Get("user")]
if !ok {
user = v01.cfg.Users["broadcast"]
}
w.Write([]byte(user.Message))
}),
}
go func() {
<-v01.ctx.Done()
log.Println("closing v01 server")
s.Close()
}()
log.Println("starting v01 server")
if err := s.ListenAndServe(); err != nil && v01.ctx.Err() == nil {
log.Println("err with v01 server", err)
panic(err)
}
}
func (v01 V01) CloseWrap() raw.Raw {
v01.can()
return v01.src
}
func (v01 V01) Close() {
v01.can()
v01.src.Close()
}
func (v01 V01) Read() []Button {
line := v01.src.Read()
var msg v01Msg
if err := json.Unmarshal(line, &msg); err != nil {
log.Printf("%v: %s", err, line)
}
v01.telemetry(msg)
return v01.cfg.transform(msg).buttons()
}
func (cfg v01Cfg) transform(msg v01Msg) v01Msg {
if len(cfg.Players) == 0 {
return msg
}
user := cfg.Users[msg.U]
if user.Player < 1 {
msg.Y = ""
msg.N = ""
return msg
}
player := cfg.Players[user.Player-1]
msg.Y = player.Transformation.pipe(msg.Y)
msg.N = player.Transformation.pipe(msg.N)
return msg
}
func (t v01Transformation) pipe(s string) string {
for i := range s {
if v := t[s[i:i+1]]; v != "" {
s = s[:i] + v[:1] + s[i+1:]
}
}
return s
}
func (v01 V01) telemetry(msg v01Msg) {
if FlagDebug {
log.Printf("%s|%dms", msg.U, time.Now().UnixNano()/int64(time.Millisecond)-msg.T)
}
}
func (msg v01Msg) buttons() []Button {
buttons := make([]Button, len(msg.Y)+len(msg.N))
for i := range msg.Y {
buttons[i] = Button{Char: msg.Y[i], Down: true}
}
for i := range msg.N {
buttons[len(msg.Y)+i] = Button{Char: msg.N[i], Down: false}
}
if FlagDebug {
log.Printf("%+v", msg)
}
return buttons
}

View File

@@ -0,0 +1,137 @@
package button_test
import (
"context"
"fmt"
"io"
"mayhem-party/src/device/input/button"
"net/http"
"os"
"path"
"testing"
"time"
)
func TestV01(t *testing.T) {
src := constSrc(fmt.Sprintf(`{"T":%v,"U":"bel","Y":"abc","N":"cde"}`, time.Now().UnixNano()/int64(time.Millisecond)-50))
t.Logf("(%v) %s", len(src), src.Read())
v01 := button.NewV01(context.Background(), src)
defer v01.Close()
got := v01.Read()
want := []button.Button{
{Down: true, Char: 'a'},
{Down: true, Char: 'b'},
{Down: true, Char: 'c'},
{Down: false, Char: 'c'},
{Down: false, Char: 'd'},
{Down: false, Char: 'e'},
}
if len(got) != len(want) {
t.Fatal(len(want), len(got))
}
for i := range got {
if got[i] != want[i] {
t.Errorf("[%d] want %+v got %+v", i, want[i], got[i])
}
}
}
func TestV01WithCfg(t *testing.T) {
d := t.TempDir()
p := path.Join(d, "cfg.yaml")
os.WriteFile(p, []byte(`
users:
bel:
player: 2
players:
- transformation:
w: t
- transformation:
w: i
`), os.ModePerm)
button.FlagButtonV01Config = p
t.Run("unknown user ignored", func(t *testing.T) {
v01 := button.NewV01(context.Background(), constSrc(`{"U":"qt","Y":"w"}`))
defer v01.Close()
got := v01.Read()
if len(got) != 0 {
t.Error(got)
}
})
t.Run("player2", func(t *testing.T) {
v01 := button.NewV01(context.Background(), constSrc(`{"U":"bel","Y":"w","N":"w"}`))
defer v01.Close()
got := v01.Read()
if len(got) != 2 {
t.Error(got)
}
if got[0] != (button.Button{Char: 'i', Down: true}) {
t.Error(got[0])
}
if got[1] != (button.Button{Char: 'i', Down: false}) {
t.Error(got[1])
}
})
}
func TestV01Feedback(t *testing.T) {
d := t.TempDir()
p := path.Join(d, "cfg.yaml")
os.WriteFile(p, []byte(`
feedback:
addr: :27071
users:
bel:
player: 2
message: to bel
broadcast:
message: to everyone
players:
- transformation:
w: t
- transformation:
w: i
`), os.ModePerm)
button.FlagButtonV01Config = p
ctx, can := context.WithCancel(context.Background())
defer can()
v01 := button.NewV01(ctx, constSrc(`{"U":"qt","Y":"w"}`))
defer v01.Close()
for {
time.Sleep(time.Millisecond * 100)
resp, err := http.Get("http://localhost:27071?user=bel")
if err != nil {
continue
}
resp.Body.Close()
break
}
t.Run("specific user", func(t *testing.T) {
resp, err := http.Get("http://localhost:27071?user=bel")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
if string(b) != "to bel" {
t.Error(b)
}
})
t.Run("broadcast", func(t *testing.T) {
resp, err := http.Get("http://localhost:27071")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
if string(b) != "to everyone" {
t.Error(b)
}
})
}

View File

@@ -0,0 +1,51 @@
package button
import (
"testing"
)
func TestV01TransformationPipe(t *testing.T) {
cases := map[string]struct {
input string
xform map[string]string
want string
}{
"empty input": {
xform: map[string]string{"a": "bc"},
},
"empty xform": {
input: "aa",
want: "aa",
},
"all": {
input: "aa",
xform: map[string]string{"a": "cc"},
want: "cc",
},
"last": {
input: "ba",
xform: map[string]string{"a": "cc"},
want: "bc",
},
"first": {
input: "ab",
xform: map[string]string{"a": "cc"},
want: "cb",
},
"noop": {
input: "bb",
xform: map[string]string{"a": "bc"},
want: "bb",
},
}
for name, d := range cases {
c := d
t.Run(name, func(t *testing.T) {
got := v01Transformation(c.xform).pipe(c.input)
if got != c.want {
t.Errorf("%+v(%s) want %s got %s", c.xform, c.input, c.want, got)
}
})
}
}

View File

@@ -1,55 +0,0 @@
package button
import (
"encoding/json"
"log"
"mayhem-party/src/device/input/raw"
"os"
)
var debugging = os.Getenv("DEBUG") == "true"
type (
V1 struct {
src raw.Raw
}
v1Msg struct {
T int64
U string
Y string
N string
}
)
func NewV1(src raw.Raw) V1 {
return V1{
src: src,
}
}
func (v1 V1) Close() {
v1.src.Close()
}
func (v1 V1) Read() []Button {
line := v1.src.Read()
var msg v1Msg
if err := json.Unmarshal(line, &msg); err != nil {
log.Printf("%v: %s", err, line)
}
return msg.buttons()
}
func (msg v1Msg) buttons() []Button {
buttons := make([]Button, len(msg.Y)+len(msg.N))
for i := range msg.Y {
buttons[i] = Button{Char: msg.Y[i], Down: true}
}
for i := range msg.N {
buttons[len(msg.Y)+i] = Button{Char: msg.N[i], Down: false}
}
if debugging {
log.Printf("%+v", msg)
}
return buttons
}

View File

@@ -1,29 +0,0 @@
package button_test
import (
"mayhem-party/src/device/input/button"
"testing"
)
func TestV1(t *testing.T) {
src := constSrc(`{"T":1,"U":"bel","Y":"abc","N":"cde"}`)
t.Logf("(%v) %s", len(src), src.Read())
v1 := button.NewV1(src)
got := v1.Read()
want := []button.Button{
{Down: true, Char: 'a'},
{Down: true, Char: 'b'},
{Down: true, Char: 'c'},
{Down: false, Char: 'c'},
{Down: false, Char: 'd'},
{Down: false, Char: 'e'},
}
if len(got) != len(want) {
t.Fatal(len(want), len(got))
}
for i := range got {
if got[i] != want[i] {
t.Errorf("[%d] want %+v got %+v", i, want[i], got[i])
}
}
}

View File

@@ -3,6 +3,8 @@ package input_test
import (
"context"
"mayhem-party/src/device/input"
"mayhem-party/src/device/input/raw"
"mayhem-party/src/device/input/wrap"
"os"
"path"
"testing"
@@ -30,11 +32,11 @@ func TestNewRemapped(t *testing.T) {
t.Fatal(err)
}
os.Setenv("WRAP_REMAP_FILE", remap)
os.Setenv("RAW_RANDOM_WEIGHT_FILE", rand)
wrap.FlagRemapFile = remap
raw.FlagRawRandomWeightFile = rand
t.Cleanup(func() {
os.Unsetenv("WRAP_REMAP_FILE")
os.Unsetenv("RAW_RANDOM_WEIGHT_FILE")
wrap.FlagRemapFile = ""
raw.FlagRawRandomWeightFile = ""
})
r := input.New(context.Background())
@@ -50,9 +52,9 @@ func TestNewRemapped(t *testing.T) {
}
func TestNewBuffered(t *testing.T) {
os.Setenv("WRAP_BUFFERED", "true")
wrap.FlagBuffered = true
t.Cleanup(func() {
os.Unsetenv("WRAP_BUFFERED")
wrap.FlagBuffered = false
})
r := input.New(context.Background())
@@ -71,9 +73,9 @@ func TestNewRandomWeightFile(t *testing.T) {
t.Fatal(err)
}
os.Setenv("RAW_RANDOM_WEIGHT_FILE", p)
raw.FlagRawRandomWeightFile = p
t.Cleanup(func() {
os.Unsetenv("RAW_RANDOM_WEIGHT_FILE")
raw.FlagRawRandomWeightFile = ""
})
r := input.New(context.Background())

View File

@@ -50,7 +50,7 @@ func (kb Keyboard) Read() []byte {
if err != nil && err != io.EOF {
panic(err)
}
if os.Getenv("DEBUG") == "true" {
if FlagDebug {
log.Printf("raw.Keyboard.Read() %s", b[:n])
}
return b[:n]

View File

@@ -6,21 +6,27 @@ import (
"strconv"
)
var (
FlagRawKeyboard = os.Getenv("RAW_KEYBOARD") == "true"
FlagRawUDP = os.Getenv("RAW_UDP")
FlagRawRandomWeightFile = os.Getenv("RAW_RANDOM_WEIGHT_FILE")
)
type Raw interface {
Read() []byte
Close()
}
func New(ctx context.Context) Raw {
if os.Getenv("RAW_KEYBOARD") == "true" {
if FlagRawKeyboard {
return NewKeyboard()
}
if port, _ := strconv.Atoi(os.Getenv("RAW_UDP")); port != 0 {
if port, _ := strconv.Atoi(FlagRawUDP); port != 0 {
return NewUDP(ctx, port)
}
generator := randomCharFromRange('a', 'g')
if p, ok := os.LookupEnv("RAW_RANDOM_WEIGHT_FILE"); ok && len(p) > 0 {
generator = randomCharFromWeightFile(p)
if FlagRawRandomWeightFile != "" {
generator = randomCharFromWeightFile(FlagRawRandomWeightFile)
}
return NewRandom(generator)
}

View File

@@ -8,6 +8,10 @@ import (
"strconv"
)
var (
FlagDebug = os.Getenv("DEBUG") == "true"
)
type UDP struct {
conn net.PacketConn
c chan []byte
@@ -29,14 +33,13 @@ func NewUDP(ctx context.Context, port int) UDP {
}
func (udp UDP) listen() {
debugging := os.Getenv("DEBUG") == "true"
for udp.ctx.Err() == nil {
buff := make([]byte, 256)
n, _, err := udp.conn.ReadFrom(buff)
if err != nil && udp.ctx.Err() == nil {
panic(err)
}
if debugging {
if FlagDebug {
log.Printf("raw.UDP.Read() => %s", buff[:n])
}
select {

View File

@@ -3,11 +3,16 @@ package wrap
import (
"context"
"mayhem-party/src/device/input/button"
"mayhem-party/src/device/input/raw"
"os"
"sync"
"time"
)
var (
FlagBufferedStickyDuration = os.Getenv("WRAP_BUFFERED_STICKY_DURATION")
)
type Buffered struct {
ctx context.Context
can context.CancelFunc
@@ -21,7 +26,7 @@ type Buffered struct {
func NewBuffered(ctx context.Context, input Wrap) *Buffered {
ctx, can := context.WithCancel(ctx)
expirationInterval := time.Millisecond * 125
if d, err := time.ParseDuration(os.Getenv("WRAP_BUFFERED_STICKY_DURATION")); err == nil {
if d, err := time.ParseDuration(FlagBufferedStickyDuration); err == nil {
expirationInterval = d
}
result := &Buffered{
@@ -57,6 +62,11 @@ func (b *Buffered) listen() {
}
}
func (b *Buffered) CloseWrap() raw.Raw {
b.can()
return b.input.CloseWrap()
}
func (b *Buffered) Close() {
b.input.Close()
b.can()

View File

@@ -1,24 +0,0 @@
package wrap
import (
"mayhem-party/src/device/input/button"
"mayhem-party/src/device/input/raw"
)
type Protocol struct {
src raw.Raw
}
func NewProtocol(src raw.Raw) Protocol {
return Protocol{
src: src,
}
}
func (p Protocol) Close() {
p.src.Close()
}
func (p Protocol) Read() []button.Button {
panic(nil)
}

View File

@@ -4,8 +4,18 @@ import (
"context"
"log"
"mayhem-party/src/device/input/button"
"mayhem-party/src/device/input/raw"
"os"
"os/signal"
"syscall"
)
var (
chSigUsr1 = func() chan os.Signal {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGUSR1)
return c
}()
)
type Refresh struct {
@@ -13,14 +23,12 @@ type Refresh struct {
input Wrap
}
func NewRefreshCh(sig os.Signal) <-chan os.Signal {
c := make(chan os.Signal, 1)
signal.Notify(c, sig)
return c
func NewRefresh(ctx context.Context, newWrap func() Wrap) *Refresh {
return NewRefreshWith(ctx, newWrap, chSigUsr1)
}
func NewRefresh(newWrap func() Wrap, ch <-chan os.Signal) *Refresh {
ctx, can := context.WithCancel(context.Background())
func NewRefreshWith(ctx context.Context, newWrap func() Wrap, ch <-chan os.Signal) *Refresh {
ctx, can := context.WithCancel(ctx)
result := &Refresh{
can: can,
input: newWrap(),
@@ -33,6 +41,7 @@ func NewRefresh(newWrap func() Wrap, ch <-chan os.Signal) *Refresh {
return
case sig := <-ch:
log.Println("refreshing for", sig)
result.input.CloseWrap()
result.input = newWrap()
}
}
@@ -40,6 +49,11 @@ func NewRefresh(newWrap func() Wrap, ch <-chan os.Signal) *Refresh {
return result
}
func (r *Refresh) CloseWrap() raw.Raw {
r.can()
return r.input.CloseWrap()
}
func (r *Refresh) Read() []button.Button {
return r.input.Read()
}

View File

@@ -1,6 +1,8 @@
package wrap
import (
"context"
"mayhem-party/src/device/input/button"
"os"
"syscall"
"testing"
@@ -15,7 +17,7 @@ func TestRefresh(t *testing.T) {
}
ch := make(chan os.Signal, 1)
defer close(ch)
refresh := NewRefresh(generator, ch)
refresh := NewRefreshWith(context.Background(), generator, ch)
defer refresh.Close()
assertIts := func(t *testing.T, b byte) {
@@ -42,3 +44,51 @@ func TestRefresh(t *testing.T) {
assertIts(t, byte('c'))
})
}
func TestRefreshDoesntCloseSources(t *testing.T) {
src := &telemetrySrc{}
newParsers := 0
newParser := func() Wrap {
newParsers += 1
return button.NewPlaintext(src)
}
ctx, can := context.WithCancel(context.Background())
defer can()
refresh := NewRefresh(ctx, newParser)
if newParsers != 1 {
t.Error(newParsers)
}
for i := 0; i < 5; i++ {
refresh.Read()
}
if want := (telemetrySrc{reads: 5}); *src != want {
t.Errorf("%+v", *src)
} else if newParsers != 1 {
t.Error(newParsers)
}
for i := 0; i < 5; i++ {
chSigUsr1 <- syscall.SIGINT
}
time.Sleep(time.Millisecond * 250)
if want := (telemetrySrc{reads: 5}); *src != want {
t.Errorf("want %+v, got %+v", want, *src)
} else if newParsers != 6 {
t.Error(newParsers)
}
}
type telemetrySrc struct {
closes int
reads int
}
func (src *telemetrySrc) Close() {
src.closes += 1
}
func (src *telemetrySrc) Read() []byte {
src.reads += 1
return []byte("foo")
}

View File

@@ -2,6 +2,7 @@ package wrap
import (
"mayhem-party/src/device/input/button"
"mayhem-party/src/device/input/raw"
"os"
"github.com/go-yaml/yaml"
@@ -38,6 +39,10 @@ func NewRemap(input Wrap, m map[byte]byte) Remap {
}
}
func (re Remap) CloseWrap() raw.Raw {
return re.input.CloseWrap()
}
func (re Remap) Close() {
re.input.Close()
}

View File

@@ -3,36 +3,42 @@ package wrap
import (
"context"
"mayhem-party/src/device/input/button"
"mayhem-party/src/device/input/raw"
"os"
"syscall"
)
var (
FlagBuffered = os.Getenv("WRAP_BUFFERED") == "true"
FlagRemapFile = os.Getenv("WRAP_REMAP_FILE")
FlagRefreshOnSigUsr1 = os.Getenv("WRAP_REFRESH_ON_SIGUSR1") == "true"
)
type Wrap interface {
Read() []button.Button
Close()
CloseWrap() raw.Raw
}
func New(ctx context.Context, srcFunc func() button.Parser) Wrap {
maker := func() Wrap {
return srcFunc()
}
if os.Getenv("WRAP_BUFFERED") == "true" {
if FlagBuffered {
oldMaker := maker
maker = func() Wrap {
return NewBuffered(ctx, oldMaker())
}
}
if p := os.Getenv("WRAP_REMAP_FILE"); p != "" {
if FlagRemapFile != "" {
oldMaker := maker
maker = func() Wrap {
return NewRemapFromFile(oldMaker(), p)
return NewRemapFromFile(oldMaker(), FlagRemapFile)
}
}
if os.Getenv("WRAP_REFRESH_ON_SIGUSR1") != "" {
if FlagRefreshOnSigUsr1 {
oldMaker := maker
c := NewRefreshCh(syscall.SIGUSR1)
maker = func() Wrap {
return NewRefresh(oldMaker, c)
return NewRefresh(ctx, oldMaker)
}
}
return maker()

View File

@@ -2,6 +2,7 @@ package wrap
import (
"mayhem-party/src/device/input/button"
"mayhem-party/src/device/input/raw"
"testing"
)
@@ -10,12 +11,12 @@ func TestWrap(t *testing.T) {
var _ Wrap = &Refresh{}
var _ Wrap = &Buffered{}
var _ Wrap = &Remap{}
var _ Wrap = Protocol{}
}
type dummyParser button.Button
func (d dummyParser) Close() {}
func (d dummyParser) CloseWrap() raw.Raw { return nil }
func (d dummyParser) Close() {}
func (d dummyParser) Read() []button.Button {
return []button.Button{button.Button(d)}
}

View File

@@ -17,7 +17,7 @@ func Main(ctx context.Context) error {
defer reader.Close()
interval := time.Millisecond * 50
if intervalS, ok := os.LookupEnv("MAIN_INTERVAL_DURATION"); !ok {
if intervalS := os.Getenv("MAIN_INTERVAL_DURATION"); intervalS != "" {
} else if v, err := time.ParseDuration(intervalS); err != nil {
panic(err)
} else {

View File

@@ -1,6 +1,6 @@
todo:
- change from 'a','b','c' from rust to just 11,21,31,41 so playerName is known implicitly
- lag via UDP formatted inputs as space-delimited TS PID buttonIdx buttonIdx buttonIdx
- v01cfg includes messages to send per client and exposes http server for it
- send clients messages to display
- input.MayhemParty as a logical wrapper from mod10 but then gotta translate back
to char for keyboard things somewhere; space delimited?
- todo: rusty configs have "name" for each client
@@ -42,3 +42,15 @@ done:
- todo: input.MayhemParty as a logical wrapper from %10 but then gotta translate back
to char for keyboard things somewhere; space delimited?
ts: Fri Mar 24 21:16:39 MDT 2023
- todo: change from 'a','b','c' from rust to just 11,21,31,41 so playerName is known
implicitly
ts: Sat Mar 25 00:06:21 MDT 2023
- todo: lag via UDP formatted inputs as space-delimited TS PID buttonIdx buttonIdx
buttonIdx
ts: Sat Mar 25 00:13:19 MDT 2023
- todo: map keys triggered by user to player idx and their keys
ts: Sat Mar 25 00:44:19 MDT 2023
- todo: use button.V01Cfg; map keys triggered by user to player idx and their keys
ts: Sat Mar 25 09:12:43 MDT 2023
- todo: v01cfg includes messages to send per client and exposes tcp server for it
ts: Sat Mar 25 10:09:06 MDT 2023