mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 08:45:38 +02:00
feat: Controller support, performance enchancements, multi-stage images, fixes (#304)
## Description Oops.. another massive PR 🥲 This PR contains multiple improvements and changes. Firstly, thanks gst-wayland-display's PR [here](https://github.com/games-on-whales/gst-wayland-display/pull/20). NVIDIA path is now way more efficient than before. Secondly, adding controller support was a massive hurdle, requiring me to start another project [vimputti](https://github.com/DatCaptainHorse/vimputti) - which allows simple virtual controller inputs in isolated containers. Well, it's not simple, it includes LD_PRELOAD shims and other craziness, but the library API is simple to use.. Thirdly, split runner image into 3 separate stages, base + build + runtime, should help keep things in check in future, also added GitHub Actions CI builds for v2 to v4 builds (hopefully they pass..). Fourth, replaced the runner's runtime Steam patching with better and simpler bubblewrap patch, massive thanks to `games-on-whales` to figuring it out better! Fifth, relay for once needed some changes, the new changes are still mostly WIP, but I'll deal with them next time I have energy.. I'm spent now. Needed to include these changes as relay needed a minor change to allow rumble events to flow back to client peer. Sixth.. tons of package updates, minor code improvements and the usual. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * End-to-end gamepad/controller support (attach/detach, buttons, sticks, triggers, rumble) with client/server integration and virtual controller plumbing. * Optional Prometheus metrics endpoint and WebTransport support. * Background vimputti manager process added for controller handling. * **Improvements** * Multi-variant container image builds and streamlined runtime images. * Zero-copy video pipeline and encoder improvements for lower latency. * Updated Steam compat mapping and dependency/toolchain refreshes. * **Bug Fixes** * More robust GPU detection, input/fullscreen lifecycle, startup/entrypoint, and container runtime fixes. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
a3ee9aadd9
commit
c62a22b552
@@ -30,29 +30,12 @@ func InitWebRTCAPI() error {
|
||||
return fmt.Errorf("failed to register extensions: %w", err)
|
||||
}
|
||||
|
||||
// Default codecs cover most of our needs
|
||||
// Default codecs cover our needs
|
||||
err = mediaEngine.RegisterDefaultCodecs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add H.265 for special cases
|
||||
videoRTCPFeedback := []webrtc.RTCPFeedback{{"goog-remb", ""}, {"ccm", "fir"}, {"nack", ""}, {"nack", "pli"}}
|
||||
for _, codec := range []webrtc.RTPCodecParameters{
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH265, ClockRate: 90000, RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 48,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeRTX, ClockRate: 90000, SDPFmtpLine: "apt=48"},
|
||||
PayloadType: 49,
|
||||
},
|
||||
} {
|
||||
if err = mediaEngine.RegisterCodec(codec, webrtc.RTPCodecTypeVideo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Interceptor registry
|
||||
interceptorRegistry := &interceptor.Registry{}
|
||||
|
||||
@@ -98,7 +81,8 @@ func InitWebRTCAPI() error {
|
||||
slog.Info("Using WebRTC UDP Port Range", "start", flags.WebRTCUDPStart, "end", flags.WebRTCUDPEnd)
|
||||
}
|
||||
|
||||
settingEngine.SetIncludeLoopbackCandidate(true) // Just in case
|
||||
// Improves speed when sending offers to browsers (https://github.com/pion/webrtc/issues/3174)
|
||||
settingEngine.SetIncludeLoopbackCandidate(true)
|
||||
|
||||
// Create a new API object with our customized settings
|
||||
globalWebRTCAPI = webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine), webrtc.WithSettingEngine(settingEngine), webrtc.WithInterceptorRegistry(interceptorRegistry))
|
||||
|
||||
@@ -24,6 +24,8 @@ type Flags struct {
|
||||
AutoAddLocalIP bool // Automatically add local IP to NAT 1 to 1 IPs
|
||||
NAT11IP string // WebRTC NAT 1 to 1 IP - allows specifying IP of relay if behind NAT
|
||||
PersistDir string // Directory to save persistent data to
|
||||
Metrics bool // Enable metrics endpoint
|
||||
MetricsPort int // Port for metrics endpoint
|
||||
}
|
||||
|
||||
func (flags *Flags) DebugLog() {
|
||||
@@ -39,6 +41,8 @@ func (flags *Flags) DebugLog() {
|
||||
"autoAddLocalIP", flags.AutoAddLocalIP,
|
||||
"webrtcNAT11IPs", flags.NAT11IP,
|
||||
"persistDir", flags.PersistDir,
|
||||
"metrics", flags.Metrics,
|
||||
"metricsPort", flags.MetricsPort,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -79,12 +83,14 @@ func InitFlags() {
|
||||
flag.IntVar(&globalFlags.WebRTCUDPStart, "webrtcUDPStart", getEnvAsInt("WEBRTC_UDP_START", 0), "WebRTC UDP port range start")
|
||||
flag.IntVar(&globalFlags.WebRTCUDPEnd, "webrtcUDPEnd", getEnvAsInt("WEBRTC_UDP_END", 0), "WebRTC UDP port range end")
|
||||
flag.StringVar(&globalFlags.STUNServer, "stunServer", getEnvAsString("STUN_SERVER", "stun.l.google.com:19302"), "WebRTC STUN server")
|
||||
flag.IntVar(&globalFlags.UDPMuxPort, "webrtcUDPMux", getEnvAsInt("WEBRTC_UDP_MUX", 8088), "WebRTC UDP mux port")
|
||||
flag.BoolVar(&globalFlags.AutoAddLocalIP, "autoAddLocalIP", getEnvAsBool("AUTO_ADD_LOCAL_IP", true), "Automatically add local IP to NAT 1 to 1 IPs")
|
||||
flag.IntVar(&globalFlags.UDPMuxPort, "webrtcUDPMux", getEnvAsInt("WEBRTC_UDP_MUX", 9099), "WebRTC UDP mux port")
|
||||
flag.BoolVar(&globalFlags.AutoAddLocalIP, "autoAddLocalIP", getEnvAsBool("AUTO_ADD_LOCAL_IP", false), "Automatically add local IP to NAT 1 to 1 IPs")
|
||||
// String with comma separated IPs
|
||||
nat11IP := ""
|
||||
flag.StringVar(&nat11IP, "webrtcNAT11IP", getEnvAsString("WEBRTC_NAT_IP", ""), "WebRTC NAT 1 to 1 IP")
|
||||
flag.StringVar(&globalFlags.PersistDir, "persistDir", getEnvAsString("PERSIST_DIR", "./persist-data"), "Directory to save persistent data to")
|
||||
flag.BoolVar(&globalFlags.Metrics, "metrics", getEnvAsBool("METRICS", false), "Enable metrics endpoint")
|
||||
flag.IntVar(&globalFlags.MetricsPort, "metricsPort", getEnvAsInt("METRICS_PORT", 3030), "Port for metrics endpoint")
|
||||
// Parse flags
|
||||
flag.Parse()
|
||||
|
||||
|
||||
@@ -4,24 +4,31 @@ import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"relay/internal/common"
|
||||
"relay/internal/shared"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/host"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
|
||||
"github.com/libp2p/go-libp2p/p2p/protocol/ping"
|
||||
"github.com/libp2p/go-libp2p/p2p/security/noise"
|
||||
"github.com/libp2p/go-libp2p/p2p/transport/quicreuse"
|
||||
"github.com/libp2p/go-libp2p/p2p/transport/tcp"
|
||||
ws "github.com/libp2p/go-libp2p/p2p/transport/websocket"
|
||||
webtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"github.com/pion/webrtc/v4"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
// -- Variables --
|
||||
@@ -30,17 +37,9 @@ var globalRelay *Relay
|
||||
|
||||
// -- Structs --
|
||||
|
||||
// RelayInfo contains light information of Relay, in mesh-friendly format
|
||||
type RelayInfo struct {
|
||||
ID peer.ID
|
||||
MeshAddrs []string // Addresses of this relay
|
||||
MeshRooms *common.SafeMap[string, shared.RoomInfo] // Rooms hosted by this relay
|
||||
MeshLatencies *common.SafeMap[string, time.Duration] // Latencies to other peers from this relay
|
||||
}
|
||||
|
||||
// Relay structure enhanced with metrics and state
|
||||
type Relay struct {
|
||||
RelayInfo
|
||||
*PeerInfo
|
||||
|
||||
Host host.Host // libp2p host for peer-to-peer networking
|
||||
PubSub *pubsub.PubSub // PubSub for state synchronization
|
||||
@@ -48,7 +47,6 @@ type Relay struct {
|
||||
|
||||
// Local
|
||||
LocalRooms *common.SafeMap[ulid.ULID, *shared.Room] // room ID -> local Room struct (hosted by this relay)
|
||||
LocalMeshPeers *common.SafeMap[peer.ID, *RelayInfo] // peer ID -> mesh peer relay info (connected to this relay)
|
||||
LocalMeshConnections *common.SafeMap[peer.ID, *webrtc.PeerConnection] // peer ID -> PeerConnection (connected to this relay)
|
||||
|
||||
// Protocols
|
||||
@@ -60,11 +58,43 @@ type Relay struct {
|
||||
}
|
||||
|
||||
func NewRelay(ctx context.Context, port int, identityKey crypto.PrivKey) (*Relay, error) {
|
||||
// If metrics are enabled, start the metrics server first
|
||||
metricsOpts := make([]libp2p.Option, 0)
|
||||
var rmgr network.ResourceManager
|
||||
if common.GetFlags().Metrics {
|
||||
go func() {
|
||||
slog.Info("Starting prometheus metrics server at '/debug/metrics/prometheus'", "port", common.GetFlags().MetricsPort)
|
||||
http.Handle("/debug/metrics/prometheus", promhttp.Handler())
|
||||
if err := http.ListenAndServe(fmt.Sprintf(":%d", common.GetFlags().MetricsPort), nil); err != nil {
|
||||
slog.Error("Failed to start metrics server", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
rcmgr.MustRegisterWith(prometheus.DefaultRegisterer)
|
||||
|
||||
str, err := rcmgr.NewStatsTraceReporter()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
rmgr, err = rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.DefaultLimits.AutoScale()), rcmgr.WithTraceReporter(str))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
metricsOpts = append(metricsOpts, libp2p.ResourceManager(rmgr))
|
||||
metricsOpts = append(metricsOpts, libp2p.PrometheusRegisterer(prometheus.DefaultRegisterer))
|
||||
} else {
|
||||
rmgr = nil
|
||||
}
|
||||
|
||||
listenAddrs := []string{
|
||||
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port), // IPv4 - Raw TCP
|
||||
fmt.Sprintf("/ip6/::/tcp/%d", port), // IPv6 - Raw TCP
|
||||
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d/ws", port), // IPv4 - TCP WebSocket
|
||||
fmt.Sprintf("/ip6/::/tcp/%d/ws", port), // IPv6 - TCP WebSocket
|
||||
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port), // IPv4 - Raw TCP
|
||||
fmt.Sprintf("/ip6/::/tcp/%d", port), // IPv6 - Raw TCP
|
||||
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d/ws", port), // IPv4 - TCP WebSocket
|
||||
fmt.Sprintf("/ip6/::/tcp/%d/ws", port), // IPv6 - TCP WebSocket
|
||||
fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic-v1/webtransport", port), // IPv4 - UDP QUIC WebTransport
|
||||
fmt.Sprintf("/ip6/::/udp/%d/quic-v1/webtransport", port), // IPv6 - UDP QUIC WebTransport
|
||||
}
|
||||
|
||||
var muAddrs []multiaddr.Multiaddr
|
||||
@@ -78,11 +108,12 @@ func NewRelay(ctx context.Context, port int, identityKey crypto.PrivKey) (*Relay
|
||||
|
||||
// Initialize libp2p host
|
||||
p2pHost, err := libp2p.New(
|
||||
// TODO: Currently static identity
|
||||
libp2p.ChainOptions(metricsOpts...),
|
||||
libp2p.Identity(identityKey),
|
||||
// Enable required transports
|
||||
libp2p.Transport(tcp.NewTCPTransport),
|
||||
libp2p.Transport(ws.New),
|
||||
libp2p.Transport(webtransport.New),
|
||||
// Other options
|
||||
libp2p.ListenAddrs(muAddrs...),
|
||||
libp2p.Security(noise.ID, noise.New),
|
||||
@@ -91,6 +122,7 @@ func NewRelay(ctx context.Context, port int, identityKey crypto.PrivKey) (*Relay
|
||||
libp2p.EnableNATService(),
|
||||
libp2p.EnableAutoNATv2(),
|
||||
libp2p.ShareTCPListener(),
|
||||
libp2p.QUICReuse(quicreuse.NewConnManager),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create libp2p host for relay: %w", err)
|
||||
@@ -105,23 +137,13 @@ func NewRelay(ctx context.Context, port int, identityKey crypto.PrivKey) (*Relay
|
||||
// Initialize Ping Service
|
||||
pingSvc := ping.NewPingService(p2pHost)
|
||||
|
||||
var addresses []string
|
||||
for _, addr := range p2pHost.Addrs() {
|
||||
addresses = append(addresses, addr.String())
|
||||
}
|
||||
|
||||
r := &Relay{
|
||||
RelayInfo: RelayInfo{
|
||||
ID: p2pHost.ID(),
|
||||
MeshAddrs: addresses,
|
||||
MeshRooms: common.NewSafeMap[string, shared.RoomInfo](),
|
||||
MeshLatencies: common.NewSafeMap[string, time.Duration](),
|
||||
},
|
||||
Host: p2pHost,
|
||||
PubSub: p2pPubsub,
|
||||
PingService: pingSvc,
|
||||
LocalRooms: common.NewSafeMap[ulid.ULID, *shared.Room](),
|
||||
LocalMeshPeers: common.NewSafeMap[peer.ID, *RelayInfo](),
|
||||
PeerInfo: NewPeerInfo(p2pHost.ID(), p2pHost.Addrs()),
|
||||
Host: p2pHost,
|
||||
PubSub: p2pPubsub,
|
||||
PingService: pingSvc,
|
||||
LocalRooms: common.NewSafeMap[ulid.ULID, *shared.Room](),
|
||||
LocalMeshConnections: common.NewSafeMap[peer.ID, *webrtc.PeerConnection](),
|
||||
}
|
||||
|
||||
// Add network notifier after relay is initialized
|
||||
@@ -152,7 +174,7 @@ func NewRelay(ctx context.Context, port int, identityKey crypto.PrivKey) (*Relay
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func InitRelay(ctx context.Context, ctxCancel context.CancelFunc) error {
|
||||
func InitRelay(ctx context.Context, ctxCancel context.CancelFunc) (*Relay, error) {
|
||||
var err error
|
||||
persistentDir := common.GetFlags().PersistDir
|
||||
|
||||
@@ -164,7 +186,7 @@ func InitRelay(ctx context.Context, ctxCancel context.CancelFunc) error {
|
||||
if hasIdentity {
|
||||
_, err = os.Stat(persistentDir + "/identity.key")
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to check identity key file: %w", err)
|
||||
return nil, fmt.Errorf("failed to check identity key file: %w", err)
|
||||
} else if os.IsNotExist(err) {
|
||||
hasIdentity = false
|
||||
}
|
||||
@@ -172,17 +194,17 @@ func InitRelay(ctx context.Context, ctxCancel context.CancelFunc) error {
|
||||
if !hasIdentity {
|
||||
// Make sure the persistent directory exists
|
||||
if err = os.MkdirAll(persistentDir, 0700); err != nil {
|
||||
return fmt.Errorf("failed to create persistent data directory: %w", err)
|
||||
return nil, fmt.Errorf("failed to create persistent data directory: %w", err)
|
||||
}
|
||||
// Generate
|
||||
slog.Info("Generating new identity for relay")
|
||||
privKey, err = common.GenerateED25519Key()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate new identity: %w", err)
|
||||
return nil, fmt.Errorf("failed to generate new identity: %w", err)
|
||||
}
|
||||
// Save the key
|
||||
if err = common.SaveED25519Key(privKey, persistentDir+"/identity.key"); err != nil {
|
||||
return fmt.Errorf("failed to save identity key: %w", err)
|
||||
return nil, fmt.Errorf("failed to save identity key: %w", err)
|
||||
}
|
||||
slog.Info("New identity generated and saved", "path", persistentDir+"/identity.key")
|
||||
} else {
|
||||
@@ -190,25 +212,45 @@ func InitRelay(ctx context.Context, ctxCancel context.CancelFunc) error {
|
||||
// Load the key
|
||||
privKey, err = common.LoadED25519Key(persistentDir + "/identity.key")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load identity key: %w", err)
|
||||
return nil, fmt.Errorf("failed to load identity key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to libp2p crypto.PrivKey
|
||||
identityKey, err = crypto.UnmarshalEd25519PrivateKey(privKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal ED25519 private key: %w", err)
|
||||
return nil, fmt.Errorf("failed to unmarshal ED25519 private key: %w", err)
|
||||
}
|
||||
|
||||
globalRelay, err = NewRelay(ctx, common.GetFlags().EndpointPort, identityKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create relay: %w", err)
|
||||
return nil, fmt.Errorf("failed to create relay: %w", err)
|
||||
}
|
||||
|
||||
if err = common.InitWebRTCAPI(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slog.Info("Relay initialized", "id", globalRelay.ID)
|
||||
return nil
|
||||
|
||||
// Load previous peers on startup
|
||||
defaultFile := common.GetFlags().PersistDir + "/peerstore.json"
|
||||
if err = globalRelay.LoadFromFile(defaultFile); err != nil {
|
||||
slog.Warn("Failed to load previous peer store", "error", err)
|
||||
} else {
|
||||
globalRelay.Peers.Range(func(id peer.ID, pi *PeerInfo) bool {
|
||||
if len(pi.Addrs) <= 0 {
|
||||
slog.Warn("Peer from peer store has no addresses", "peer", id)
|
||||
return true
|
||||
}
|
||||
|
||||
// Connect to first address only
|
||||
if err = globalRelay.ConnectToPeer(context.Background(), pi.Addrs[0]); err != nil {
|
||||
slog.Error("Failed to connect to peer from peer store", "peer", id, "error", err)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return globalRelay, nil
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ type discoveryNotifee struct {
|
||||
|
||||
func (d *discoveryNotifee) HandlePeerFound(pi peer.AddrInfo) {
|
||||
if d.relay != nil {
|
||||
if err := d.relay.connectToRelay(context.Background(), &pi); err != nil {
|
||||
if err := d.relay.connectToPeer(context.Background(), &pi); err != nil {
|
||||
slog.Error("failed to connect to discovered relay", "peer", pi.ID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (r *Relay) publishRelayMetrics(ctx context.Context) error {
|
||||
// Check all peer latencies
|
||||
r.checkAllPeerLatencies(ctx)
|
||||
|
||||
data, err := json.Marshal(r.RelayInfo)
|
||||
data, err := json.Marshal(r.PeerInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal relay status: %w", err)
|
||||
}
|
||||
@@ -109,8 +109,8 @@ func (r *Relay) measureLatencyToPeer(ctx context.Context, peerID peer.ID) {
|
||||
if result.Error != nil {
|
||||
slog.Warn("Latency check failed, removing peer from local peers map", "peer", peerID, "err", result.Error)
|
||||
// Remove from MeshPeers if ping failed
|
||||
if r.LocalMeshPeers.Has(peerID) {
|
||||
r.LocalMeshPeers.Delete(peerID)
|
||||
if r.Peers.Has(peerID) {
|
||||
r.Peers.Delete(peerID)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -123,6 +123,6 @@ func (r *Relay) measureLatencyToPeer(ctx context.Context, peerID peer.ID) {
|
||||
latency = 1 * time.Microsecond
|
||||
}
|
||||
|
||||
r.RelayInfo.MeshLatencies.Set(peerID.String(), latency)
|
||||
r.PeerInfo.Latencies.Set(peerID, latency)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ type networkNotifier struct {
|
||||
|
||||
// Connected is called when a connection is established
|
||||
func (n *networkNotifier) Connected(net network.Network, conn network.Conn) {
|
||||
if n.relay == nil {
|
||||
if n.relay != nil {
|
||||
n.relay.onPeerConnected(conn.RemotePeer())
|
||||
}
|
||||
}
|
||||
@@ -75,8 +75,8 @@ func (r *Relay) setupPubSub(ctx context.Context) error {
|
||||
|
||||
// --- Connection Management ---
|
||||
|
||||
// connectToRelay is internal method to connect to a relay peer using multiaddresses
|
||||
func (r *Relay) connectToRelay(ctx context.Context, peerInfo *peer.AddrInfo) error {
|
||||
// connectToPeer is internal method to connect to a peer using multiaddresses
|
||||
func (r *Relay) connectToPeer(ctx context.Context, peerInfo *peer.AddrInfo) error {
|
||||
if peerInfo.ID == r.ID {
|
||||
return errors.New("cannot connect to self")
|
||||
}
|
||||
@@ -94,19 +94,14 @@ func (r *Relay) connectToRelay(ctx context.Context, peerInfo *peer.AddrInfo) err
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConnectToRelay connects to another relay by its multiaddress.
|
||||
func (r *Relay) ConnectToRelay(ctx context.Context, addr string) error {
|
||||
ma, err := multiaddr.NewMultiaddr(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid multiaddress: %w", err)
|
||||
}
|
||||
|
||||
peerInfo, err := peer.AddrInfoFromP2pAddr(ma)
|
||||
// ConnectToPeer connects to another peer by its multiaddress.
|
||||
func (r *Relay) ConnectToPeer(ctx context.Context, addr multiaddr.Multiaddr) error {
|
||||
peerInfo, err := peer.AddrInfoFromP2pAddr(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract peer info: %w", err)
|
||||
}
|
||||
|
||||
return r.connectToRelay(ctx, peerInfo)
|
||||
return r.connectToPeer(ctx, peerInfo)
|
||||
}
|
||||
|
||||
// printConnectInstructions logs the multiaddresses for connecting to this relay.
|
||||
|
||||
77
packages/relay/internal/core/peer.go
Normal file
77
packages/relay/internal/core/peer.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"os"
|
||||
"relay/internal/common"
|
||||
"relay/internal/shared"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
// PeerInfo contains information of a peer, in light transmit-friendly format
|
||||
type PeerInfo struct {
|
||||
ID peer.ID
|
||||
Addrs []multiaddr.Multiaddr // Addresses of this peer
|
||||
Peers *common.SafeMap[peer.ID, *PeerInfo] // Peers connected to this peer
|
||||
Latencies *common.SafeMap[peer.ID, time.Duration] // Latencies to other peers from this peer
|
||||
Rooms *common.SafeMap[string, shared.RoomInfo] // Rooms this peer is part of or owner of
|
||||
}
|
||||
|
||||
func NewPeerInfo(id peer.ID, addrs []multiaddr.Multiaddr) *PeerInfo {
|
||||
return &PeerInfo{
|
||||
ID: id,
|
||||
Addrs: addrs,
|
||||
Peers: common.NewSafeMap[peer.ID, *PeerInfo](),
|
||||
Latencies: common.NewSafeMap[peer.ID, time.Duration](),
|
||||
Rooms: common.NewSafeMap[string, shared.RoomInfo](),
|
||||
}
|
||||
}
|
||||
|
||||
// SaveToFile saves the peer store to a JSON file in persistent path
|
||||
func (pi *PeerInfo) SaveToFile(filePath string) error {
|
||||
if len(filePath) <= 0 {
|
||||
return errors.New("filepath is not set")
|
||||
}
|
||||
|
||||
// Marshal the peer store to JSON array (we don't need to store IDs..)
|
||||
data, err := pi.Peers.MarshalJSON()
|
||||
if err != nil {
|
||||
return errors.New("failed to marshal peer store data: " + err.Error())
|
||||
}
|
||||
|
||||
// Save the data to a file
|
||||
if err = os.WriteFile(filePath, data, 0644); err != nil {
|
||||
return errors.New("failed to save peer store to file: " + err.Error())
|
||||
}
|
||||
|
||||
slog.Info("PeerStore saved to file", "path", filePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadFromFile loads the peer store from a JSON file in persistent path
|
||||
func (pi *PeerInfo) LoadFromFile(filePath string) error {
|
||||
if len(filePath) <= 0 {
|
||||
return errors.New("filepath is not set")
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
slog.Info("PeerStore file does not exist, starting with empty store")
|
||||
return nil // No peers to load
|
||||
}
|
||||
return errors.New("failed to read peer store file: " + err.Error())
|
||||
}
|
||||
|
||||
// Unmarshal the JSON data into the peer store
|
||||
if err = pi.Peers.UnmarshalJSON(data); err != nil {
|
||||
return errors.New("failed to unmarshal peer store data: " + err.Error())
|
||||
}
|
||||
|
||||
slog.Info("PeerStore loaded from file", "path", filePath)
|
||||
return nil
|
||||
}
|
||||
@@ -40,15 +40,15 @@ type StreamConnection struct {
|
||||
// StreamProtocol deals with meshed stream forwarding
|
||||
type StreamProtocol struct {
|
||||
relay *Relay
|
||||
servedConns *common.SafeMap[peer.ID, *StreamConnection] // peer ID -> StreamConnection (for served streams)
|
||||
incomingConns *common.SafeMap[string, *StreamConnection] // room name -> StreamConnection (for incoming pushed streams)
|
||||
requestedConns *common.SafeMap[string, *StreamConnection] // room name -> StreamConnection (for requested streams from other relays)
|
||||
servedConns *common.SafeMap[string, *common.SafeMap[peer.ID, *StreamConnection]] // room name -> (peer ID -> StreamConnection) (for served streams)
|
||||
incomingConns *common.SafeMap[string, *StreamConnection] // room name -> StreamConnection (for incoming pushed streams)
|
||||
requestedConns *common.SafeMap[string, *StreamConnection] // room name -> StreamConnection (for requested streams from other relays)
|
||||
}
|
||||
|
||||
func NewStreamProtocol(relay *Relay) *StreamProtocol {
|
||||
protocol := &StreamProtocol{
|
||||
relay: relay,
|
||||
servedConns: common.NewSafeMap[peer.ID, *StreamConnection](),
|
||||
servedConns: common.NewSafeMap[string, *common.SafeMap[peer.ID, *StreamConnection]](),
|
||||
incomingConns: common.NewSafeMap[string, *StreamConnection](),
|
||||
requestedConns: common.NewSafeMap[string, *StreamConnection](),
|
||||
}
|
||||
@@ -66,6 +66,7 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
||||
brw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
|
||||
safeBRW := common.NewSafeBufioRW(brw)
|
||||
|
||||
var currentRoomName string // Track the current room for this stream
|
||||
iceHolder := make([]webrtc.ICECandidateInit, 0)
|
||||
for {
|
||||
data, err := safeBRW.Receive()
|
||||
@@ -101,7 +102,9 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
||||
continue
|
||||
}
|
||||
|
||||
currentRoomName = roomName // Store the room name
|
||||
slog.Info("Received stream request for room", "room", roomName)
|
||||
|
||||
room := sp.relay.GetRoomByName(roomName)
|
||||
if room == nil || !room.IsOnline() || room.OwnerID != sp.relay.ID {
|
||||
// TODO: Allow forward requests to other relays from here?
|
||||
@@ -126,8 +129,12 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
||||
pc, err := common.CreatePeerConnection(func() {
|
||||
slog.Info("PeerConnection closed for requested stream", "room", roomName)
|
||||
// Cleanup the stream connection
|
||||
if ok := sp.servedConns.Has(stream.Conn().RemotePeer()); ok {
|
||||
sp.servedConns.Delete(stream.Conn().RemotePeer())
|
||||
if roomMap, ok := sp.servedConns.Get(roomName); ok {
|
||||
roomMap.Delete(stream.Conn().RemotePeer())
|
||||
// If the room map is empty, delete it
|
||||
if roomMap.Len() == 0 {
|
||||
sp.servedConns.Delete(roomName)
|
||||
}
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
@@ -204,7 +211,12 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
||||
}
|
||||
|
||||
// Store the connection
|
||||
sp.servedConns.Set(stream.Conn().RemotePeer(), &StreamConnection{
|
||||
roomMap, ok := sp.servedConns.Get(roomName)
|
||||
if !ok {
|
||||
roomMap = common.NewSafeMap[peer.ID, *StreamConnection]()
|
||||
sp.servedConns.Set(roomName, roomMap)
|
||||
}
|
||||
roomMap.Set(stream.Conn().RemotePeer(), &StreamConnection{
|
||||
pc: pc,
|
||||
ndc: ndc,
|
||||
})
|
||||
@@ -216,17 +228,25 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
||||
slog.Error("Failed to unmarshal ICE message", "err", err)
|
||||
continue
|
||||
}
|
||||
if conn, ok := sp.servedConns.Get(stream.Conn().RemotePeer()); ok && conn.pc.RemoteDescription() != nil {
|
||||
if err := conn.pc.AddICECandidate(iceMsg.Candidate); err != nil {
|
||||
slog.Error("Failed to add ICE candidate", "err", err)
|
||||
}
|
||||
for _, heldIce := range iceHolder {
|
||||
if err := conn.pc.AddICECandidate(heldIce); err != nil {
|
||||
slog.Error("Failed to add held ICE candidate", "err", err)
|
||||
// Use currentRoomName to get the connection from nested map
|
||||
if len(currentRoomName) > 0 {
|
||||
if roomMap, ok := sp.servedConns.Get(currentRoomName); ok {
|
||||
if conn, ok := roomMap.Get(stream.Conn().RemotePeer()); ok && conn.pc.RemoteDescription() != nil {
|
||||
if err := conn.pc.AddICECandidate(iceMsg.Candidate); err != nil {
|
||||
slog.Error("Failed to add ICE candidate", "err", err)
|
||||
}
|
||||
for _, heldIce := range iceHolder {
|
||||
if err := conn.pc.AddICECandidate(heldIce); err != nil {
|
||||
slog.Error("Failed to add held ICE candidate", "err", err)
|
||||
}
|
||||
}
|
||||
// Clear the held candidates
|
||||
iceHolder = make([]webrtc.ICECandidateInit, 0)
|
||||
} else {
|
||||
// Hold the candidate until remote description is set
|
||||
iceHolder = append(iceHolder, iceMsg.Candidate)
|
||||
}
|
||||
}
|
||||
// Clear the held candidates
|
||||
iceHolder = make([]webrtc.ICECandidateInit, 0)
|
||||
} else {
|
||||
// Hold the candidate until remote description is set
|
||||
iceHolder = append(iceHolder, iceMsg.Candidate)
|
||||
@@ -237,12 +257,19 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
||||
slog.Error("Failed to unmarshal answer from signaling message", "err", err)
|
||||
continue
|
||||
}
|
||||
if conn, ok := sp.servedConns.Get(stream.Conn().RemotePeer()); ok {
|
||||
if err := conn.pc.SetRemoteDescription(answerMsg.SDP); err != nil {
|
||||
slog.Error("Failed to set remote description for answer", "err", err)
|
||||
continue
|
||||
// Use currentRoomName to get the connection from nested map
|
||||
if len(currentRoomName) > 0 {
|
||||
if roomMap, ok := sp.servedConns.Get(currentRoomName); ok {
|
||||
if conn, ok := roomMap.Get(stream.Conn().RemotePeer()); ok {
|
||||
if err := conn.pc.SetRemoteDescription(answerMsg.SDP); err != nil {
|
||||
slog.Error("Failed to set remote description for answer", "err", err)
|
||||
continue
|
||||
}
|
||||
slog.Debug("Set remote description for answer")
|
||||
} else {
|
||||
slog.Warn("Received answer without active PeerConnection")
|
||||
}
|
||||
}
|
||||
slog.Debug("Set remote description for answer")
|
||||
} else {
|
||||
slog.Warn("Received answer without active PeerConnection")
|
||||
}
|
||||
@@ -452,7 +479,7 @@ func (sp *StreamProtocol) handleStreamPush(stream network.Stream) {
|
||||
data, err := safeBRW.Receive()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) || errors.Is(err, network.ErrReset) {
|
||||
slog.Debug("Stream push connection closed by peer", "peer", stream.Conn().RemotePeer())
|
||||
slog.Debug("Stream push connection closed by peer", "peer", stream.Conn().RemotePeer(), "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -568,6 +595,21 @@ func (sp *StreamProtocol) handleStreamPush(stream network.Stream) {
|
||||
room.DataChannel.RegisterOnClose(func() {
|
||||
slog.Debug("DataChannel closed for pushed stream", "room", room.Name)
|
||||
})
|
||||
room.DataChannel.RegisterMessageCallback("input", func(data []byte) {
|
||||
if room.DataChannel != nil {
|
||||
// Pass to servedConns DataChannels for this specific room
|
||||
if roomMap, ok := sp.servedConns.Get(room.Name); ok {
|
||||
roomMap.Range(func(peerID peer.ID, conn *StreamConnection) bool {
|
||||
if conn.ndc != nil {
|
||||
if err = conn.ndc.SendBinary(data); err != nil {
|
||||
slog.Error("Failed to forward input message from pushed stream to viewer", "room", room.Name, "peer", peerID, "err", err)
|
||||
}
|
||||
}
|
||||
return true // Continue iteration
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Set the DataChannel in the incomingConns map
|
||||
if conn, ok := sp.incomingConns.Get(room.Name); ok {
|
||||
@@ -687,7 +729,7 @@ func (sp *StreamProtocol) handleStreamPush(stream network.Stream) {
|
||||
func (sp *StreamProtocol) RequestStream(ctx context.Context, room *shared.Room, peerID peer.ID) error {
|
||||
stream, err := sp.relay.Host.NewStream(ctx, peerID, protocolStreamRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create stream request: %w", err)
|
||||
return fmt.Errorf("failed to create stream: %w", err)
|
||||
}
|
||||
|
||||
return sp.requestStream(stream, room)
|
||||
|
||||
@@ -57,15 +57,15 @@ func (r *Relay) DeleteRoomIfEmpty(room *shared.Room) {
|
||||
|
||||
// GetRemoteRoomByName returns room from mesh by name
|
||||
func (r *Relay) GetRemoteRoomByName(roomName string) *shared.RoomInfo {
|
||||
for _, room := range r.MeshRooms.Copy() {
|
||||
for _, room := range r.Rooms.Copy() {
|
||||
if room.Name == roomName && room.OwnerID != r.ID {
|
||||
// Make sure connection is alive
|
||||
if r.Host.Network().Connectedness(room.OwnerID) == network.Connected {
|
||||
return &room
|
||||
} else {
|
||||
slog.Debug("Removing stale peer, owns a room without connection", "room", roomName, "peer", room.OwnerID)
|
||||
r.onPeerDisconnected(room.OwnerID)
|
||||
}
|
||||
|
||||
slog.Debug("Removing stale peer, owns a room without connection", "room", roomName, "peer", room.OwnerID)
|
||||
r.onPeerDisconnected(room.OwnerID)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -72,8 +72,8 @@ func (r *Relay) handleRelayMetricsMessages(ctx context.Context, sub *pubsub.Subs
|
||||
continue
|
||||
}
|
||||
|
||||
var info RelayInfo
|
||||
if err := json.Unmarshal(msg.Data, &info); err != nil {
|
||||
var info PeerInfo
|
||||
if err = json.Unmarshal(msg.Data, &info); err != nil {
|
||||
slog.Error("Failed to unmarshal relay status", "from", msg.GetFrom(), "data_len", len(msg.Data), "err", err)
|
||||
continue
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func (r *Relay) handleRelayMetricsMessages(ctx context.Context, sub *pubsub.Subs
|
||||
// --- State Check Functions ---
|
||||
// hasConnectedPeer checks if peer is in map and has a valid connection
|
||||
func (r *Relay) hasConnectedPeer(peerID peer.ID) bool {
|
||||
if _, ok := r.LocalMeshPeers.Get(peerID); !ok {
|
||||
if _, ok := r.Peers.Get(peerID); !ok {
|
||||
return false
|
||||
}
|
||||
if r.Host.Network().Connectedness(peerID) != network.Connected {
|
||||
@@ -102,14 +102,14 @@ func (r *Relay) hasConnectedPeer(peerID peer.ID) bool {
|
||||
// --- State Change Functions ---
|
||||
|
||||
// onPeerStatus updates the status of a peer based on received metrics, adding local perspective
|
||||
func (r *Relay) onPeerStatus(recvInfo RelayInfo) {
|
||||
r.LocalMeshPeers.Set(recvInfo.ID, &recvInfo)
|
||||
func (r *Relay) onPeerStatus(recvInfo PeerInfo) {
|
||||
r.Peers.Set(recvInfo.ID, &recvInfo)
|
||||
}
|
||||
|
||||
// onPeerConnected is called when a new peer connects to the relay
|
||||
func (r *Relay) onPeerConnected(peerID peer.ID) {
|
||||
// Add to local peer map
|
||||
r.LocalMeshPeers.Set(peerID, &RelayInfo{
|
||||
r.Peers.Set(peerID, &PeerInfo{
|
||||
ID: peerID,
|
||||
})
|
||||
|
||||
@@ -131,16 +131,12 @@ func (r *Relay) onPeerConnected(peerID peer.ID) {
|
||||
func (r *Relay) onPeerDisconnected(peerID peer.ID) {
|
||||
slog.Info("Mesh peer disconnected, deleting from local peer map", "peer", peerID)
|
||||
// Remove peer from local mesh peers
|
||||
if r.LocalMeshPeers.Has(peerID) {
|
||||
r.LocalMeshPeers.Delete(peerID)
|
||||
if r.Peers.Has(peerID) {
|
||||
r.Peers.Delete(peerID)
|
||||
}
|
||||
// Remove any rooms associated with this peer
|
||||
if r.MeshRooms.Has(peerID.String()) {
|
||||
r.MeshRooms.Delete(peerID.String())
|
||||
}
|
||||
// Remove any latencies associated with this peer
|
||||
if r.LocalMeshPeers.Has(peerID) {
|
||||
r.LocalMeshPeers.Delete(peerID)
|
||||
if r.Rooms.Has(peerID.String()) {
|
||||
r.Rooms.Delete(peerID.String())
|
||||
}
|
||||
|
||||
// TODO: If any rooms were routed through this peer, handle that case
|
||||
@@ -155,7 +151,7 @@ func (r *Relay) updateMeshRoomStates(peerID peer.ID, states []shared.RoomInfo) {
|
||||
}
|
||||
|
||||
// If previously did not exist, but does now, request a connection if participants exist for our room
|
||||
existed := r.MeshRooms.Has(state.ID.String())
|
||||
existed := r.Rooms.Has(state.ID.String())
|
||||
if !existed {
|
||||
// Request connection to this peer if we have participants in our local room
|
||||
if room, ok := r.LocalRooms.Get(state.ID); ok {
|
||||
@@ -168,6 +164,6 @@ func (r *Relay) updateMeshRoomStates(peerID peer.ID, states []shared.RoomInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
r.MeshRooms.Set(state.ID.String(), state)
|
||||
r.Rooms.Set(state.ID.String(), state)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: latency_tracker.proto
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: messages.proto
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: types.proto
|
||||
|
||||
@@ -416,6 +416,481 @@ func (x *ProtoKeyUp) GetKey() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// ControllerAttach message
|
||||
type ProtoControllerAttach struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "ControllerAttach"
|
||||
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // One of the following enums: "ps", "xbox" or "switch"
|
||||
Slot int32 `protobuf:"varint,3,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAttach) Reset() {
|
||||
*x = ProtoControllerAttach{}
|
||||
mi := &file_types_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAttach) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoControllerAttach) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoControllerAttach) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[7]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoControllerAttach.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoControllerAttach) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAttach) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAttach) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAttach) GetSlot() int32 {
|
||||
if x != nil {
|
||||
return x.Slot
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ControllerDetach message
|
||||
type ProtoControllerDetach struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "ControllerDetach"
|
||||
Slot int32 `protobuf:"varint,2,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProtoControllerDetach) Reset() {
|
||||
*x = ProtoControllerDetach{}
|
||||
mi := &file_types_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProtoControllerDetach) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoControllerDetach) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoControllerDetach) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[8]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoControllerDetach.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoControllerDetach) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *ProtoControllerDetach) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerDetach) GetSlot() int32 {
|
||||
if x != nil {
|
||||
return x.Slot
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ControllerButton message
|
||||
type ProtoControllerButton struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "ControllerButtons"
|
||||
Slot int32 `protobuf:"varint,2,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
||||
Button int32 `protobuf:"varint,3,opt,name=button,proto3" json:"button,omitempty"` // Button code (linux input event code)
|
||||
Pressed bool `protobuf:"varint,4,opt,name=pressed,proto3" json:"pressed,omitempty"` // true if pressed, false if released
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProtoControllerButton) Reset() {
|
||||
*x = ProtoControllerButton{}
|
||||
mi := &file_types_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProtoControllerButton) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoControllerButton) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoControllerButton) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[9]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoControllerButton.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoControllerButton) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
func (x *ProtoControllerButton) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerButton) GetSlot() int32 {
|
||||
if x != nil {
|
||||
return x.Slot
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerButton) GetButton() int32 {
|
||||
if x != nil {
|
||||
return x.Button
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerButton) GetPressed() bool {
|
||||
if x != nil {
|
||||
return x.Pressed
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ControllerTriggers message
|
||||
type ProtoControllerTrigger struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "ControllerTriggers"
|
||||
Slot int32 `protobuf:"varint,2,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
||||
Trigger int32 `protobuf:"varint,3,opt,name=trigger,proto3" json:"trigger,omitempty"` // Trigger number (0 for left, 1 for right)
|
||||
Value int32 `protobuf:"varint,4,opt,name=value,proto3" json:"value,omitempty"` // trigger value (-32768 to 32767)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProtoControllerTrigger) Reset() {
|
||||
*x = ProtoControllerTrigger{}
|
||||
mi := &file_types_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProtoControllerTrigger) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoControllerTrigger) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoControllerTrigger) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[10]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoControllerTrigger.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoControllerTrigger) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
func (x *ProtoControllerTrigger) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerTrigger) GetSlot() int32 {
|
||||
if x != nil {
|
||||
return x.Slot
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerTrigger) GetTrigger() int32 {
|
||||
if x != nil {
|
||||
return x.Trigger
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerTrigger) GetValue() int32 {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ControllerSticks message
|
||||
type ProtoControllerStick struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "ControllerStick"
|
||||
Slot int32 `protobuf:"varint,2,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
||||
Stick int32 `protobuf:"varint,3,opt,name=stick,proto3" json:"stick,omitempty"` // Stick number (0 for left, 1 for right)
|
||||
X int32 `protobuf:"varint,4,opt,name=x,proto3" json:"x,omitempty"` // X axis value (-32768 to 32767)
|
||||
Y int32 `protobuf:"varint,5,opt,name=y,proto3" json:"y,omitempty"` // Y axis value (-32768 to 32767)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProtoControllerStick) Reset() {
|
||||
*x = ProtoControllerStick{}
|
||||
mi := &file_types_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProtoControllerStick) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoControllerStick) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoControllerStick) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[11]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoControllerStick.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoControllerStick) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{11}
|
||||
}
|
||||
|
||||
func (x *ProtoControllerStick) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerStick) GetSlot() int32 {
|
||||
if x != nil {
|
||||
return x.Slot
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerStick) GetStick() int32 {
|
||||
if x != nil {
|
||||
return x.Stick
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerStick) GetX() int32 {
|
||||
if x != nil {
|
||||
return x.X
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerStick) GetY() int32 {
|
||||
if x != nil {
|
||||
return x.Y
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ControllerAxis message
|
||||
type ProtoControllerAxis struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "ControllerAxis"
|
||||
Slot int32 `protobuf:"varint,2,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
||||
Axis int32 `protobuf:"varint,3,opt,name=axis,proto3" json:"axis,omitempty"` // Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
|
||||
Value int32 `protobuf:"varint,4,opt,name=value,proto3" json:"value,omitempty"` // axis value (-1 to 1)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAxis) Reset() {
|
||||
*x = ProtoControllerAxis{}
|
||||
mi := &file_types_proto_msgTypes[12]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAxis) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoControllerAxis) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoControllerAxis) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[12]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoControllerAxis.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoControllerAxis) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{12}
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAxis) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAxis) GetSlot() int32 {
|
||||
if x != nil {
|
||||
return x.Slot
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAxis) GetAxis() int32 {
|
||||
if x != nil {
|
||||
return x.Axis
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAxis) GetValue() int32 {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ControllerRumble message
|
||||
type ProtoControllerRumble struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "ControllerRumble"
|
||||
Slot int32 `protobuf:"varint,2,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
||||
LowFrequency int32 `protobuf:"varint,3,opt,name=low_frequency,json=lowFrequency,proto3" json:"low_frequency,omitempty"` // Low frequency rumble (0-65535)
|
||||
HighFrequency int32 `protobuf:"varint,4,opt,name=high_frequency,json=highFrequency,proto3" json:"high_frequency,omitempty"` // High frequency rumble (0-65535)
|
||||
Duration int32 `protobuf:"varint,5,opt,name=duration,proto3" json:"duration,omitempty"` // Duration in milliseconds
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProtoControllerRumble) Reset() {
|
||||
*x = ProtoControllerRumble{}
|
||||
mi := &file_types_proto_msgTypes[13]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProtoControllerRumble) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoControllerRumble) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoControllerRumble) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[13]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoControllerRumble.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoControllerRumble) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{13}
|
||||
}
|
||||
|
||||
func (x *ProtoControllerRumble) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerRumble) GetSlot() int32 {
|
||||
if x != nil {
|
||||
return x.Slot
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerRumble) GetLowFrequency() int32 {
|
||||
if x != nil {
|
||||
return x.LowFrequency
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerRumble) GetHighFrequency() int32 {
|
||||
if x != nil {
|
||||
return x.HighFrequency
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerRumble) GetDuration() int32 {
|
||||
if x != nil {
|
||||
return x.Duration
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Union of all Input types
|
||||
type ProtoInput struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
@@ -428,6 +903,13 @@ type ProtoInput struct {
|
||||
// *ProtoInput_MouseKeyUp
|
||||
// *ProtoInput_KeyDown
|
||||
// *ProtoInput_KeyUp
|
||||
// *ProtoInput_ControllerAttach
|
||||
// *ProtoInput_ControllerDetach
|
||||
// *ProtoInput_ControllerButton
|
||||
// *ProtoInput_ControllerTrigger
|
||||
// *ProtoInput_ControllerStick
|
||||
// *ProtoInput_ControllerAxis
|
||||
// *ProtoInput_ControllerRumble
|
||||
InputType isProtoInput_InputType `protobuf_oneof:"input_type"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -435,7 +917,7 @@ type ProtoInput struct {
|
||||
|
||||
func (x *ProtoInput) Reset() {
|
||||
*x = ProtoInput{}
|
||||
mi := &file_types_proto_msgTypes[7]
|
||||
mi := &file_types_proto_msgTypes[14]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -447,7 +929,7 @@ func (x *ProtoInput) String() string {
|
||||
func (*ProtoInput) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoInput) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[7]
|
||||
mi := &file_types_proto_msgTypes[14]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -460,7 +942,7 @@ func (x *ProtoInput) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ProtoInput.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoInput) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{7}
|
||||
return file_types_proto_rawDescGZIP(), []int{14}
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetInputType() isProtoInput_InputType {
|
||||
@@ -533,6 +1015,69 @@ func (x *ProtoInput) GetKeyUp() *ProtoKeyUp {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetControllerAttach() *ProtoControllerAttach {
|
||||
if x != nil {
|
||||
if x, ok := x.InputType.(*ProtoInput_ControllerAttach); ok {
|
||||
return x.ControllerAttach
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetControllerDetach() *ProtoControllerDetach {
|
||||
if x != nil {
|
||||
if x, ok := x.InputType.(*ProtoInput_ControllerDetach); ok {
|
||||
return x.ControllerDetach
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetControllerButton() *ProtoControllerButton {
|
||||
if x != nil {
|
||||
if x, ok := x.InputType.(*ProtoInput_ControllerButton); ok {
|
||||
return x.ControllerButton
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetControllerTrigger() *ProtoControllerTrigger {
|
||||
if x != nil {
|
||||
if x, ok := x.InputType.(*ProtoInput_ControllerTrigger); ok {
|
||||
return x.ControllerTrigger
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetControllerStick() *ProtoControllerStick {
|
||||
if x != nil {
|
||||
if x, ok := x.InputType.(*ProtoInput_ControllerStick); ok {
|
||||
return x.ControllerStick
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetControllerAxis() *ProtoControllerAxis {
|
||||
if x != nil {
|
||||
if x, ok := x.InputType.(*ProtoInput_ControllerAxis); ok {
|
||||
return x.ControllerAxis
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetControllerRumble() *ProtoControllerRumble {
|
||||
if x != nil {
|
||||
if x, ok := x.InputType.(*ProtoInput_ControllerRumble); ok {
|
||||
return x.ControllerRumble
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isProtoInput_InputType interface {
|
||||
isProtoInput_InputType()
|
||||
}
|
||||
@@ -565,6 +1110,34 @@ type ProtoInput_KeyUp struct {
|
||||
KeyUp *ProtoKeyUp `protobuf:"bytes,7,opt,name=key_up,json=keyUp,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ProtoInput_ControllerAttach struct {
|
||||
ControllerAttach *ProtoControllerAttach `protobuf:"bytes,8,opt,name=controller_attach,json=controllerAttach,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ProtoInput_ControllerDetach struct {
|
||||
ControllerDetach *ProtoControllerDetach `protobuf:"bytes,9,opt,name=controller_detach,json=controllerDetach,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ProtoInput_ControllerButton struct {
|
||||
ControllerButton *ProtoControllerButton `protobuf:"bytes,10,opt,name=controller_button,json=controllerButton,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ProtoInput_ControllerTrigger struct {
|
||||
ControllerTrigger *ProtoControllerTrigger `protobuf:"bytes,11,opt,name=controller_trigger,json=controllerTrigger,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ProtoInput_ControllerStick struct {
|
||||
ControllerStick *ProtoControllerStick `protobuf:"bytes,12,opt,name=controller_stick,json=controllerStick,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ProtoInput_ControllerAxis struct {
|
||||
ControllerAxis *ProtoControllerAxis `protobuf:"bytes,13,opt,name=controller_axis,json=controllerAxis,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ProtoInput_ControllerRumble struct {
|
||||
ControllerRumble *ProtoControllerRumble `protobuf:"bytes,14,opt,name=controller_rumble,json=controllerRumble,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*ProtoInput_MouseMove) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_MouseMoveAbs) isProtoInput_InputType() {}
|
||||
@@ -579,6 +1152,20 @@ func (*ProtoInput_KeyDown) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_KeyUp) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_ControllerAttach) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_ControllerDetach) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_ControllerButton) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_ControllerTrigger) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_ControllerStick) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_ControllerAxis) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_ControllerRumble) isProtoInput_InputType() {}
|
||||
|
||||
var File_types_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_types_proto_rawDesc = "" +
|
||||
@@ -608,7 +1195,41 @@ const file_types_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"ProtoKeyUp\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x10\n" +
|
||||
"\x03key\x18\x02 \x01(\x05R\x03key\"\xab\x03\n" +
|
||||
"\x03key\x18\x02 \x01(\x05R\x03key\"O\n" +
|
||||
"\x15ProtoControllerAttach\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x0e\n" +
|
||||
"\x02id\x18\x02 \x01(\tR\x02id\x12\x12\n" +
|
||||
"\x04slot\x18\x03 \x01(\x05R\x04slot\"?\n" +
|
||||
"\x15ProtoControllerDetach\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
|
||||
"\x04slot\x18\x02 \x01(\x05R\x04slot\"q\n" +
|
||||
"\x15ProtoControllerButton\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
|
||||
"\x04slot\x18\x02 \x01(\x05R\x04slot\x12\x16\n" +
|
||||
"\x06button\x18\x03 \x01(\x05R\x06button\x12\x18\n" +
|
||||
"\apressed\x18\x04 \x01(\bR\apressed\"p\n" +
|
||||
"\x16ProtoControllerTrigger\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
|
||||
"\x04slot\x18\x02 \x01(\x05R\x04slot\x12\x18\n" +
|
||||
"\atrigger\x18\x03 \x01(\x05R\atrigger\x12\x14\n" +
|
||||
"\x05value\x18\x04 \x01(\x05R\x05value\"p\n" +
|
||||
"\x14ProtoControllerStick\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
|
||||
"\x04slot\x18\x02 \x01(\x05R\x04slot\x12\x14\n" +
|
||||
"\x05stick\x18\x03 \x01(\x05R\x05stick\x12\f\n" +
|
||||
"\x01x\x18\x04 \x01(\x05R\x01x\x12\f\n" +
|
||||
"\x01y\x18\x05 \x01(\x05R\x01y\"g\n" +
|
||||
"\x13ProtoControllerAxis\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
|
||||
"\x04slot\x18\x02 \x01(\x05R\x04slot\x12\x12\n" +
|
||||
"\x04axis\x18\x03 \x01(\x05R\x04axis\x12\x14\n" +
|
||||
"\x05value\x18\x04 \x01(\x05R\x05value\"\xa7\x01\n" +
|
||||
"\x15ProtoControllerRumble\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
|
||||
"\x04slot\x18\x02 \x01(\x05R\x04slot\x12#\n" +
|
||||
"\rlow_frequency\x18\x03 \x01(\x05R\flowFrequency\x12%\n" +
|
||||
"\x0ehigh_frequency\x18\x04 \x01(\x05R\rhighFrequency\x12\x1a\n" +
|
||||
"\bduration\x18\x05 \x01(\x05R\bduration\"\xc0\a\n" +
|
||||
"\n" +
|
||||
"ProtoInput\x126\n" +
|
||||
"\n" +
|
||||
@@ -620,7 +1241,15 @@ const file_types_proto_rawDesc = "" +
|
||||
"\fmouse_key_up\x18\x05 \x01(\v2\x16.proto.ProtoMouseKeyUpH\x00R\n" +
|
||||
"mouseKeyUp\x120\n" +
|
||||
"\bkey_down\x18\x06 \x01(\v2\x13.proto.ProtoKeyDownH\x00R\akeyDown\x12*\n" +
|
||||
"\x06key_up\x18\a \x01(\v2\x11.proto.ProtoKeyUpH\x00R\x05keyUpB\f\n" +
|
||||
"\x06key_up\x18\a \x01(\v2\x11.proto.ProtoKeyUpH\x00R\x05keyUp\x12K\n" +
|
||||
"\x11controller_attach\x18\b \x01(\v2\x1c.proto.ProtoControllerAttachH\x00R\x10controllerAttach\x12K\n" +
|
||||
"\x11controller_detach\x18\t \x01(\v2\x1c.proto.ProtoControllerDetachH\x00R\x10controllerDetach\x12K\n" +
|
||||
"\x11controller_button\x18\n" +
|
||||
" \x01(\v2\x1c.proto.ProtoControllerButtonH\x00R\x10controllerButton\x12N\n" +
|
||||
"\x12controller_trigger\x18\v \x01(\v2\x1d.proto.ProtoControllerTriggerH\x00R\x11controllerTrigger\x12H\n" +
|
||||
"\x10controller_stick\x18\f \x01(\v2\x1b.proto.ProtoControllerStickH\x00R\x0fcontrollerStick\x12E\n" +
|
||||
"\x0fcontroller_axis\x18\r \x01(\v2\x1a.proto.ProtoControllerAxisH\x00R\x0econtrollerAxis\x12K\n" +
|
||||
"\x11controller_rumble\x18\x0e \x01(\v2\x1c.proto.ProtoControllerRumbleH\x00R\x10controllerRumbleB\f\n" +
|
||||
"\n" +
|
||||
"input_typeB\x16Z\x14relay/internal/protob\x06proto3"
|
||||
|
||||
@@ -636,30 +1265,44 @@ func file_types_proto_rawDescGZIP() []byte {
|
||||
return file_types_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
|
||||
var file_types_proto_goTypes = []any{
|
||||
(*ProtoMouseMove)(nil), // 0: proto.ProtoMouseMove
|
||||
(*ProtoMouseMoveAbs)(nil), // 1: proto.ProtoMouseMoveAbs
|
||||
(*ProtoMouseWheel)(nil), // 2: proto.ProtoMouseWheel
|
||||
(*ProtoMouseKeyDown)(nil), // 3: proto.ProtoMouseKeyDown
|
||||
(*ProtoMouseKeyUp)(nil), // 4: proto.ProtoMouseKeyUp
|
||||
(*ProtoKeyDown)(nil), // 5: proto.ProtoKeyDown
|
||||
(*ProtoKeyUp)(nil), // 6: proto.ProtoKeyUp
|
||||
(*ProtoInput)(nil), // 7: proto.ProtoInput
|
||||
(*ProtoMouseMove)(nil), // 0: proto.ProtoMouseMove
|
||||
(*ProtoMouseMoveAbs)(nil), // 1: proto.ProtoMouseMoveAbs
|
||||
(*ProtoMouseWheel)(nil), // 2: proto.ProtoMouseWheel
|
||||
(*ProtoMouseKeyDown)(nil), // 3: proto.ProtoMouseKeyDown
|
||||
(*ProtoMouseKeyUp)(nil), // 4: proto.ProtoMouseKeyUp
|
||||
(*ProtoKeyDown)(nil), // 5: proto.ProtoKeyDown
|
||||
(*ProtoKeyUp)(nil), // 6: proto.ProtoKeyUp
|
||||
(*ProtoControllerAttach)(nil), // 7: proto.ProtoControllerAttach
|
||||
(*ProtoControllerDetach)(nil), // 8: proto.ProtoControllerDetach
|
||||
(*ProtoControllerButton)(nil), // 9: proto.ProtoControllerButton
|
||||
(*ProtoControllerTrigger)(nil), // 10: proto.ProtoControllerTrigger
|
||||
(*ProtoControllerStick)(nil), // 11: proto.ProtoControllerStick
|
||||
(*ProtoControllerAxis)(nil), // 12: proto.ProtoControllerAxis
|
||||
(*ProtoControllerRumble)(nil), // 13: proto.ProtoControllerRumble
|
||||
(*ProtoInput)(nil), // 14: proto.ProtoInput
|
||||
}
|
||||
var file_types_proto_depIdxs = []int32{
|
||||
0, // 0: proto.ProtoInput.mouse_move:type_name -> proto.ProtoMouseMove
|
||||
1, // 1: proto.ProtoInput.mouse_move_abs:type_name -> proto.ProtoMouseMoveAbs
|
||||
2, // 2: proto.ProtoInput.mouse_wheel:type_name -> proto.ProtoMouseWheel
|
||||
3, // 3: proto.ProtoInput.mouse_key_down:type_name -> proto.ProtoMouseKeyDown
|
||||
4, // 4: proto.ProtoInput.mouse_key_up:type_name -> proto.ProtoMouseKeyUp
|
||||
5, // 5: proto.ProtoInput.key_down:type_name -> proto.ProtoKeyDown
|
||||
6, // 6: proto.ProtoInput.key_up:type_name -> proto.ProtoKeyUp
|
||||
7, // [7:7] is the sub-list for method output_type
|
||||
7, // [7:7] is the sub-list for method input_type
|
||||
7, // [7:7] is the sub-list for extension type_name
|
||||
7, // [7:7] is the sub-list for extension extendee
|
||||
0, // [0:7] is the sub-list for field type_name
|
||||
0, // 0: proto.ProtoInput.mouse_move:type_name -> proto.ProtoMouseMove
|
||||
1, // 1: proto.ProtoInput.mouse_move_abs:type_name -> proto.ProtoMouseMoveAbs
|
||||
2, // 2: proto.ProtoInput.mouse_wheel:type_name -> proto.ProtoMouseWheel
|
||||
3, // 3: proto.ProtoInput.mouse_key_down:type_name -> proto.ProtoMouseKeyDown
|
||||
4, // 4: proto.ProtoInput.mouse_key_up:type_name -> proto.ProtoMouseKeyUp
|
||||
5, // 5: proto.ProtoInput.key_down:type_name -> proto.ProtoKeyDown
|
||||
6, // 6: proto.ProtoInput.key_up:type_name -> proto.ProtoKeyUp
|
||||
7, // 7: proto.ProtoInput.controller_attach:type_name -> proto.ProtoControllerAttach
|
||||
8, // 8: proto.ProtoInput.controller_detach:type_name -> proto.ProtoControllerDetach
|
||||
9, // 9: proto.ProtoInput.controller_button:type_name -> proto.ProtoControllerButton
|
||||
10, // 10: proto.ProtoInput.controller_trigger:type_name -> proto.ProtoControllerTrigger
|
||||
11, // 11: proto.ProtoInput.controller_stick:type_name -> proto.ProtoControllerStick
|
||||
12, // 12: proto.ProtoInput.controller_axis:type_name -> proto.ProtoControllerAxis
|
||||
13, // 13: proto.ProtoInput.controller_rumble:type_name -> proto.ProtoControllerRumble
|
||||
14, // [14:14] is the sub-list for method output_type
|
||||
14, // [14:14] is the sub-list for method input_type
|
||||
14, // [14:14] is the sub-list for extension type_name
|
||||
14, // [14:14] is the sub-list for extension extendee
|
||||
0, // [0:14] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_types_proto_init() }
|
||||
@@ -667,7 +1310,7 @@ func file_types_proto_init() {
|
||||
if File_types_proto != nil {
|
||||
return
|
||||
}
|
||||
file_types_proto_msgTypes[7].OneofWrappers = []any{
|
||||
file_types_proto_msgTypes[14].OneofWrappers = []any{
|
||||
(*ProtoInput_MouseMove)(nil),
|
||||
(*ProtoInput_MouseMoveAbs)(nil),
|
||||
(*ProtoInput_MouseWheel)(nil),
|
||||
@@ -675,6 +1318,13 @@ func file_types_proto_init() {
|
||||
(*ProtoInput_MouseKeyUp)(nil),
|
||||
(*ProtoInput_KeyDown)(nil),
|
||||
(*ProtoInput_KeyUp)(nil),
|
||||
(*ProtoInput_ControllerAttach)(nil),
|
||||
(*ProtoInput_ControllerDetach)(nil),
|
||||
(*ProtoInput_ControllerButton)(nil),
|
||||
(*ProtoInput_ControllerTrigger)(nil),
|
||||
(*ProtoInput_ControllerStick)(nil),
|
||||
(*ProtoInput_ControllerAxis)(nil),
|
||||
(*ProtoInput_ControllerRumble)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
@@ -682,7 +1332,7 @@ func file_types_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_types_proto_rawDesc), len(file_types_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 8,
|
||||
NumMessages: 15,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -49,25 +49,12 @@ func (r *Room) removeParticipantByID(pID ulid.ULID) {
|
||||
}
|
||||
}
|
||||
|
||||
// Removes all participants from a Room
|
||||
/*func (r *Room) removeAllParticipants() {
|
||||
for id, participant := range r.Participants.Copy() {
|
||||
if err := r.signalParticipantOffline(participant); err != nil {
|
||||
slog.Error("Failed to signal participant offline", "participant", participant.ID, "room", r.Name, "err", err)
|
||||
}
|
||||
r.Participants.Delete(id)
|
||||
slog.Debug("Removed participant from room", "participant", id, "room", r.Name)
|
||||
}
|
||||
}*/
|
||||
|
||||
// IsOnline checks if the room is online (has both audio and video tracks)
|
||||
func (r *Room) IsOnline() bool {
|
||||
return r.AudioTrack != nil && r.VideoTrack != nil
|
||||
}
|
||||
|
||||
func (r *Room) SetTrack(trackType webrtc.RTPCodecType, track *webrtc.TrackLocalStaticRTP) {
|
||||
//oldOnline := r.IsOnline()
|
||||
|
||||
switch trackType {
|
||||
case webrtc.RTPCodecTypeAudio:
|
||||
r.AudioTrack = track
|
||||
@@ -76,69 +63,4 @@ func (r *Room) SetTrack(trackType webrtc.RTPCodecType, track *webrtc.TrackLocalS
|
||||
default:
|
||||
slog.Warn("Unknown track type", "room", r.Name, "trackType", trackType)
|
||||
}
|
||||
|
||||
/*newOnline := r.IsOnline()
|
||||
if oldOnline != newOnline {
|
||||
if newOnline {
|
||||
slog.Debug("Room online, participants will be signaled", "room", r.Name)
|
||||
r.signalParticipantsWithTracks()
|
||||
} else {
|
||||
slog.Debug("Room offline, signaling participants", "room", r.Name)
|
||||
r.signalParticipantsOffline()
|
||||
}
|
||||
|
||||
// TODO: Publish updated state to mesh
|
||||
go func() {
|
||||
if err := r.Relay.publishRoomStates(context.Background()); err != nil {
|
||||
slog.Error("Failed to publish room states on change", "room", r.Name, "err", err)
|
||||
}
|
||||
}()
|
||||
}*/
|
||||
}
|
||||
|
||||
/* TODO: libp2p'ify
|
||||
func (r *Room) signalParticipantsWithTracks() {
|
||||
for _, participant := range r.Participants.Copy() {
|
||||
if err := r.signalParticipantWithTracks(participant); err != nil {
|
||||
slog.Error("Failed to signal participant with tracks", "participant", participant.ID, "room", r.Name, "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Room) signalParticipantWithTracks(participant *Participant) error {
|
||||
if r.AudioTrack != nil {
|
||||
if err := participant.addTrack(r.AudioTrack); err != nil {
|
||||
return fmt.Errorf("failed to add audio track: %w", err)
|
||||
}
|
||||
}
|
||||
if r.VideoTrack != nil {
|
||||
if err := participant.addTrack(r.VideoTrack); err != nil {
|
||||
return fmt.Errorf("failed to add video track: %w", err)
|
||||
}
|
||||
}
|
||||
if err := participant.signalOffer(); err != nil {
|
||||
return fmt.Errorf("failed to signal offer: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Room) signalParticipantsOffline() {
|
||||
for _, participant := range r.Participants.Copy() {
|
||||
if err := r.signalParticipantOffline(participant); err != nil {
|
||||
slog.Error("Failed to signal participant offline", "participant", participant.ID, "room", r.Name, "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// signalParticipantOffline signals a single participant offline
|
||||
func (r *Room) signalParticipantOffline(participant *Participant) error {
|
||||
// Skip if websocket is nil or closed
|
||||
if participant.WebSocket == nil || participant.WebSocket.IsClosed() {
|
||||
return nil
|
||||
}
|
||||
if err := participant.WebSocket.SendAnswerMessageWS(connections.AnswerOffline); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user