Files
gobngsdk/transport.go
2026-09-16 15:06:27 +01:00

136 lines
2.4 KiB
Go

package bngsdk
import (
"errors"
"log/slog"
"net"
"sync"
"time"
"unsafe"
)
// ErrReaderDisabled = errors.New("reader is currently turned off")
var (
ErrNoData = errors.New("no new data available")
readTimeout = (time.Second / 60) * 5 // N missed frames at 60fps
packetPool = sync.Pool{
New: func() any {
var b packetBuffer
return &b
},
}
)
type packetBuffer [unsafe.Sizeof(Outgauge{})]byte
type frame struct {
Buf *packetBuffer
Len int
}
type UDPTransport struct {
address *net.UDPAddr
connection *net.UDPConn
dataChan chan frame
}
func NewUDPReader(ip string, port int) (*UDPTransport, error) {
// Define the IP address and port to listen on
addr := &net.UDPAddr{
IP: net.ParseIP(ip),
Port: port,
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
return nil, err
}
udpT := UDPTransport{
address: addr,
connection: conn,
dataChan: make(chan frame, 1),
}
go udpT.udpSink()
return &udpT, nil
}
func NewUDPWriter(ip string, port int) (*UDPTransport, error) {
// Define the IP address and port to listen on
addr := &net.UDPAddr{
IP: net.ParseIP(ip),
Port: port,
}
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
return nil, err
}
return &UDPTransport{
address: addr,
connection: conn,
}, nil
}
// udpSink is a loop that will consume the most recent packets on the port
// instead of letting them pile up
func (ut *UDPTransport) udpSink() {
defer close(ut.dataChan)
for {
ut.connection.SetReadDeadline(time.Now().Add(readTimeout))
bufPtr := packetPool.Get().(*packetBuffer)
nBytes, _, err := ut.connection.ReadFromUDP(bufPtr[:])
if err != nil {
return
}
frame := frame{
Buf: bufPtr,
Len: nBytes,
}
select {
case ut.dataChan <- frame:
// Packet sent successfuly
default:
select {
case oldFrame := <-ut.dataChan:
packetPool.Put(oldFrame.Buf)
default:
}
ut.dataChan <- frame
}
}
}
func (ut *UDPTransport) Write(data []byte) (int, error) {
return ut.connection.Write(data)
}
func (ut *UDPTransport) Read(buffer []byte) (int, error) {
latestFrame, ok := <-ut.dataChan
if !ok {
slog.Error(ErrNoData.Error())
return 0, ErrNoData
}
nBytes := copy(buffer, latestFrame.Buf[:latestFrame.Len])
packetPool.Put(latestFrame.Buf)
return nBytes, nil
}
func (ut *UDPTransport) Close() error {
if ut.connection != nil {
return ut.connection.Close()
}
return nil
}