mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-11 00:05:36 +02:00
## Description ### First commit Restructured protobuf schemas to make them easier to use across languages, switched to using them in-place of JSON for signaling as well, so there's no 2 different message formats flying about. Few new message types to deal with clients and nestri-servers better (not final format, may see changes still). General cleanup of dead/unused code along some bug squashing and package updates. TODO for future commits: - [x] Fix additional controllers not doing inputs (possibly needs vimputti changes) - [x] ~~Restructure relay protocols code a bit, to reduce bloatiness of the currently single file for them, more code re-use.~~ - Gonna keep this PR somewhat manageable without poking more at relay.. - [x] ~~Try to fix issue where with multiple clients, static stream content causes video to freeze until there's some movement.~~ - Was caused by server tuned profile being `throughput-performance`, causing CPU latency to be too high. - [x] Ponder the orb ### Second + third commit Redid the controller polling handling and fixed multi-controller handling in vimputti and nestri code sides. Remove some dead relay code as well to clean up the protocol source file, we'll revisit the meshing functionality later. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added software rendering option and MangoHud runtime config; controller sessions now support reconnection and batched state updates with persistent session IDs. * **Bug Fixes** * Restored previously-filtered NES-like gamepads so they connect correctly. * **Chores** * Modernized dependencies and protobuf tooling, migrated to protobuf-based messaging and streaming, and removed obsolete CUDA build steps. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
128 lines
2.9 KiB
Go
128 lines
2.9 KiB
Go
package common
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
gen "relay/internal/proto"
|
|
"sync"
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
// readUvarint reads an unsigned varint from the reader
|
|
func readUvarint(r io.ByteReader) (uint64, error) {
|
|
return binary.ReadUvarint(r)
|
|
}
|
|
|
|
// writeUvarint writes an unsigned varint to the writer
|
|
func writeUvarint(w io.Writer, x uint64) error {
|
|
buf := make([]byte, binary.MaxVarintLen64)
|
|
n := binary.PutUvarint(buf, x)
|
|
_, err := w.Write(buf[:n])
|
|
return err
|
|
}
|
|
|
|
// SafeBufioRW wraps a bufio.ReadWriter for sending and receiving JSON and protobufs safely
|
|
type SafeBufioRW struct {
|
|
brw *bufio.ReadWriter
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
func NewSafeBufioRW(brw *bufio.ReadWriter) *SafeBufioRW {
|
|
return &SafeBufioRW{brw: brw}
|
|
}
|
|
|
|
func (bu *SafeBufioRW) SendProto(msg proto.Message) error {
|
|
bu.mutex.Lock()
|
|
defer bu.mutex.Unlock()
|
|
|
|
protoData, err := proto.Marshal(msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write varint length prefix
|
|
if err := writeUvarint(bu.brw, uint64(len(protoData))); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write the Protobuf data
|
|
if _, err := bu.brw.Write(protoData); err != nil {
|
|
return err
|
|
}
|
|
|
|
return bu.brw.Flush()
|
|
}
|
|
|
|
func (bu *SafeBufioRW) ReceiveProto(msg proto.Message) error {
|
|
bu.mutex.RLock()
|
|
defer bu.mutex.RUnlock()
|
|
|
|
// Read varint length prefix
|
|
length, err := readUvarint(bu.brw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Read the Protobuf data
|
|
data := make([]byte, length)
|
|
if _, err := io.ReadFull(bu.brw, data); err != nil {
|
|
return err
|
|
}
|
|
|
|
return proto.Unmarshal(data, msg)
|
|
}
|
|
|
|
type CreateMessageOptions struct {
|
|
SequenceID string
|
|
Latency *gen.ProtoLatencyTracker
|
|
}
|
|
|
|
func CreateMessage(payload proto.Message, payloadType string, opts *CreateMessageOptions) (*gen.ProtoMessage, error) {
|
|
msg := &gen.ProtoMessage{
|
|
MessageBase: &gen.ProtoMessageBase{
|
|
PayloadType: payloadType,
|
|
},
|
|
}
|
|
|
|
if opts != nil {
|
|
if opts.Latency != nil {
|
|
msg.MessageBase.Latency = opts.Latency
|
|
} else if opts.SequenceID != "" {
|
|
msg.MessageBase.Latency = &gen.ProtoLatencyTracker{
|
|
SequenceId: opts.SequenceID,
|
|
Timestamps: []*gen.ProtoTimestampEntry{
|
|
{
|
|
Stage: "created",
|
|
Time: timestamppb.Now(),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
// Use reflection to set the oneof field automatically
|
|
msgReflect := msg.ProtoReflect()
|
|
payloadReflect := payload.ProtoReflect()
|
|
|
|
oneofDesc := msgReflect.Descriptor().Oneofs().ByName("payload")
|
|
if oneofDesc == nil {
|
|
return nil, errors.New("payload oneof not found")
|
|
}
|
|
|
|
fields := oneofDesc.Fields()
|
|
for i := 0; i < fields.Len(); i++ {
|
|
field := fields.Get(i)
|
|
if field.Message() != nil && field.Message().FullName() == payloadReflect.Descriptor().FullName() {
|
|
msgReflect.Set(field, protoreflect.ValueOfMessage(payloadReflect))
|
|
return msg, nil
|
|
}
|
|
}
|
|
|
|
return nil, errors.New("payload type not found in oneof")
|
|
}
|