From 202dd75a655bd61b029655223b17b92312b3880e Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Wed, 16 Sep 2026 23:48:18 +0100 Subject: [PATCH] updated the beamng replayer --- .gitignore | 2 + cmd/beamng.go | 9 +- internal/mockservers/beamng/record.go | 113 ++++++++++++------------ internal/mockservers/beamng/replay.go | 119 ++++++-------------------- internal/mockservers/beamng/views.go | 96 ++++++++++----------- 5 files changed, 139 insertions(+), 200 deletions(-) diff --git a/.gitignore b/.gitignore index 66391f2..e34276b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.bin *.work* +*.pprof +*.log diff --git a/cmd/beamng.go b/cmd/beamng.go index 8307e7a..488029b 100644 --- a/cmd/beamng.go +++ b/cmd/beamng.go @@ -3,6 +3,7 @@ package cmd import ( "context" "fmt" + "log/slog" beamng "github.com/ESilva15/TelemetryMockserver/internal/mockservers/beamng" @@ -59,14 +60,16 @@ func replayAction(cmd *cobra.Command, args []string) { replayer, err := beamng.NewReplayer(address, port, inputFile) if err != nil { - fmt.Printf("Something went wrong setting up the player: %+v", err) + slog.Error("Something went wrong setting up the player", "err", err) return } // NOTE: is this doing anything at all?? ctx := context.Background() - if err := replayer.Replay(ctx, loop); err != nil { - fmt.Printf("Something went wrong while playing the file: %v", err) + err = replayer.Replay(ctx, loop) + if err != nil { + slog.Error("Something went wrong while playing the file", "err", err) + return } } diff --git a/internal/mockservers/beamng/record.go b/internal/mockservers/beamng/record.go index 6a11622..ee8e1d4 100644 --- a/internal/mockservers/beamng/record.go +++ b/internal/mockservers/beamng/record.go @@ -3,7 +3,7 @@ package mockserver import ( "bytes" "context" - "encoding/binary" + "errors" "fmt" "os" "sync" @@ -30,24 +30,25 @@ type Recorder struct { } func NewRecorder(fp string, address string, port int) (*Recorder, error) { - var recorder Recorder - var err error + // var recorder Recorder + // var err error + // + // recorder.SDK, err = bngsdk.Init(address, port) + // if err != nil { + // return &Recorder{}, err + // } + // + // recorder.OutputFile, err = os.Create(fp) + // if err != nil { + // return &Recorder{}, err + // } + // + // recorder.viewData = recorderViewData{} + // recorder.viewCh = make(chan *recorderViewData, 1) + // recorder.recorderCh = make(chan []byte, 1) - recorder.SDK, err = bngsdk.Init(address, port) - if err != nil { - return &Recorder{}, err - } - - recorder.OutputFile, err = os.Create(fp) - if err != nil { - return &Recorder{}, err - } - - recorder.viewData = recorderViewData{} - recorder.viewCh = make(chan *recorderViewData, 1) - recorder.recorderCh = make(chan []byte, 1) - - return &recorder, nil + // return &recorder, nil + return nil, errors.New("not implemented") } func (r *Recorder) Close() { @@ -59,21 +60,21 @@ func (r *Recorder) Close() { } func (r *Recorder) record(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - case data := <-r.recorderCh: - r.mut.Lock() - err := binary.Write(r.OutputFile, binary.LittleEndian, r.SDK.Data) - r.TotalBytes += len(data) - r.mut.Unlock() - - if err != nil { - // NOTE: find a way of logging this somehow - } - } - } + // for { + // select { + // case <-ctx.Done(): + // return + // case data := <-r.recorderCh: + // r.mut.Lock() + // err := binary.Write(r.OutputFile, binary.LittleEndian, r.SDK.Data) + // r.TotalBytes += len(data) + // r.mut.Unlock() + // + // if err != nil { + // // NOTE: find a way of logging this somehow + // } + // } + // } } func (r *Recorder) view(ctx context.Context) { @@ -98,7 +99,7 @@ func (r *Recorder) view(ctx context.Context) { r.viewDataMut.RLock() nBytes = viewData.TotalBytes - stringifyOutgaugeData(&buf, &r.SDK) + // stringifyOutgaugeData(&buf, &r.SDK) r.viewDataMut.RUnlock() _, _ = buf.WriteTo(os.Stdout) @@ -119,28 +120,28 @@ func (r *Recorder) Record(ctx context.Context) error { case <-ctx.Done(): return nil case <-ticker.C: - err := r.SDK.ReadData() - if err != nil { - return err - } - - r.viewData.TotalBytes = r.TotalBytes - - // Send the data to the view - select { - case r.viewCh <- &r.viewData: - // Sent the data - default: - // Dropped the frame! - } - - // Write the data to the file - select { - case r.recorderCh <- r.SDK.Buffer: - // Sent the data - default: - // Dropped the frame! - } + // err := r.SDK.ReadData(100 * time.Millisecond) + // if err != nil { + // return err + // } + // + // r.viewData.TotalBytes = r.TotalBytes + // + // // Send the data to the view + // select { + // case r.viewCh <- &r.viewData: + // // Sent the data + // default: + // // Dropped the frame! + // } + // + // // Write the data to the file + // select { + // case r.recorderCh <- r.SDK.GetBuffer(): + // // Sent the data + // default: + // // Dropped the frame! + // } } } } diff --git a/internal/mockservers/beamng/replay.go b/internal/mockservers/beamng/replay.go index 4430b4d..19b4ccd 100644 --- a/internal/mockservers/beamng/replay.go +++ b/internal/mockservers/beamng/replay.go @@ -5,32 +5,29 @@ package mockserver import ( "bytes" "context" - "encoding/binary" "fmt" - "io" + "log/slog" + "math" "os" "sync" "time" - "unsafe" bngsdk "github.com/ESilva15/gobngsdk" ) type ViewData struct { - SDK *bngsdk.BeamNGSDK + Outgauge bngsdk.Outgauge SizeRead int64 + FileSize int64 } // Replayer does the replaying // Should we make a "player" struct that can record and replay? type Replayer struct { - SDK bngsdk.BeamNGSDK - DataSourcePath string - Socket *UDPTransport + SDK *bngsdk.BeamNGSDK // Streams dataViewCh chan ViewData - socketCh chan []byte // Mut mut sync.RWMutex @@ -40,21 +37,21 @@ type Replayer struct { } func NewReplayer(address string, port int, fp string) (*Replayer, error) { - udp, err := NewUDPTransport(address, port) + sdk, err := bngsdk.NewBngSDK(bngsdk.Options{ + Logger: slog.Default().With("SDK", "BeamNG"), + SourceType: bngsdk.BinaryFile, + BinSourcePath: fp, + ExportUDPAddress: address, + ExportUDPPort: port, + Loop: true, + }) if err != nil { return nil, err } replayer := &Replayer{ - DataSourcePath: fp, - SDK: bngsdk.BeamNGSDK{ - Data: bngsdk.Outgauge{}, - Buffer: make([]byte, unsafe.Sizeof(bngsdk.Outgauge{})), - }, - Socket: udp, - viewData: ViewData{}, + SDK: sdk, dataViewCh: make(chan ViewData, 1), - socketCh: make(chan []byte, 1), } return replayer, nil @@ -62,15 +59,7 @@ func NewReplayer(address string, port int, fp string) (*Replayer, error) { // renderToTerminal will render the data for the users viewing pleasure func (r *Replayer) renderToTerminal(ctx context.Context) { - fileInfo, err := os.Stat(r.DataSourcePath) - if err != nil { - // NOTE: learn how to handle this error - // return fmt.Errorf("error stating file: %v", err) - } - var buf bytes.Buffer - var bytesReader bytes.Reader - buf.Grow(2048) for { @@ -79,62 +68,27 @@ func (r *Replayer) renderToTerminal(ctx context.Context) { return case data := <-r.dataViewCh: // Reset to the start of the terminal - percent := int(float64(data.SizeRead) / float64(fileInfo.Size()) * 100) + // percent := int(float64(data.SizeRead) / float64(data.FileSize) * 100) + percent := int(math.Round((float64(data.SizeRead) / float64(data.FileSize)) * 100)) buf.Reset() buf.WriteString("\x1b[2J\x1b[H") fmt.Fprintf(&buf, "\x1b]0;%s - Replaying %d%%\x07", ProgramName, percent) - bytesReader.Reset(data.SDK.Buffer) - err := binary.Read(&bytesReader, binary.LittleEndian, &r.SDK.Data) - if err != nil { - fmt.Fprintf(&buf, "FAILED TO PARSE DATA\nError: %+v", err) - _, _ = buf.WriteTo(os.Stdout) - continue - } - fmt.Fprintf(&buf, "Replayed: %d%%\n", percent) - stringifyOutgaugeData(&buf, data.SDK) + stringifyOutgaugeData(&buf, &data.Outgauge) buf.WriteTo(os.Stdout) } } } -// writeToUDPSocket will write the telemetry data to the UDP socket -func (r *Replayer) writeToUDPSocket(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - case data := <-r.socketCh: - r.mut.RLock() - _, err := r.Socket.Send(data) - r.mut.RUnlock() - if err != nil { - panic(fmt.Sprintf("error writing buffer to socket: %+v", err)) - continue - // NOTE: log the error somewhere maybe - // return err - } - } - } -} - // Replay replays a given file in a UDP server : func (r *Replayer) Replay(ctx context.Context, loop bool) error { - bin, err := os.Open(r.DataSourcePath) - if err != nil { - return fmt.Errorf("error opening file: %v", err) - } - - reader := NewGobReader(bin) - go r.renderToTerminal(ctx) - go r.writeToUDPSocket(ctx) - ticker := time.NewTicker(time.Second / 60) + ticker := time.NewTicker(time.Second / 240) defer ticker.Stop() for { select { @@ -142,30 +96,18 @@ func (r *Replayer) Replay(ctx context.Context, loop bool) error { return ctx.Err() case <-ticker.C: - r.mut.Lock() - err := reader.Next(r.SDK.Buffer) - r.mut.Unlock() - - if err == io.EOF { - if !loop { - return r.Socket.Close() - } - - err = reader.Reset() - if err != nil { - return err - } - - continue - } - + og, err := r.SDK.Update() if err != nil { + // TODO: log here + slog.Error("an error occurred when updating", "err", err) return err } - // NOTE: Really like this??? - r.viewData.SDK = &r.SDK - r.viewData.SizeRead = reader.TotalRead + r.mut.Lock() + r.viewData.SizeRead = r.SDK.GetTotalRead() + r.viewData.FileSize = r.SDK.GetSourceSize() + r.viewData.Outgauge = *og + r.mut.Unlock() // Send the data to the view select { @@ -174,15 +116,6 @@ func (r *Replayer) Replay(ctx context.Context, loop bool) error { default: // Dropped the frame! } - - // Send the data to the UDP socket - select { - case r.socketCh <- r.SDK.Buffer: - // Sent the data - default: - // Dropped the frame! - } - } } } diff --git a/internal/mockservers/beamng/views.go b/internal/mockservers/beamng/views.go index d508b10..7a06ca1 100644 --- a/internal/mockservers/beamng/views.go +++ b/internal/mockservers/beamng/views.go @@ -26,64 +26,64 @@ func stringifyRecordingProgress(s *bytes.Buffer, nBytes int) { } } -func stringifyOutgaugeData(s *bytes.Buffer, sdk *bngsdk.BeamNGSDK) { +func stringifyOutgaugeData(s *bytes.Buffer, og *bngsdk.Outgauge) { // NOTE: write a string serialization function on the SDK itself fmt.Fprint(s, "Outgauge {\n") - fmt.Fprintf(s, " Time: %d ms\n", sdk.Data.Time) - fmt.Fprintf(s, " Car: %s\n", sdk.Data.Car) - fmt.Fprintf(s, " Flags: %b\n", sdk.Data.Flags) - fmt.Fprintf(s, " Gear: %d\n", sdk.Data.Gear) - fmt.Fprintf(s, " Plid: %d\n", sdk.Data.Plid) - fmt.Fprintf(s, " Speed: %f m/s\n", sdk.Data.Speed) - fmt.Fprintf(s, " RPM: %f RPM\n", sdk.Data.RPM) - fmt.Fprintf(s, " Turbo: %f Bar\n", sdk.Data.Turbo) - fmt.Fprintf(s, " EngTemp: %f °C\n", sdk.Data.EngTemp) - fmt.Fprintf(s, " Fuel: %f\n", sdk.Data.Fuel) - fmt.Fprintf(s, " OilPressure: %f Bar\n", sdk.Data.OilPressure) - fmt.Fprintf(s, " OilTemp: %f °C\n", sdk.Data.OilTemp) - fmt.Fprintf(s, " DashLights: %b\n", sdk.Data.DashLights) - fmt.Fprintf(s, " ShowLights: %b\n", sdk.Data.ShowLights) - fmt.Fprintf(s, " Throttle: %f\n", sdk.Data.Throttle) - fmt.Fprintf(s, " Brakes: %f\n", sdk.Data.Brake) - fmt.Fprintf(s, " Clutch: %f\n", sdk.Data.Clutch) - fmt.Fprintf(s, " Display1: %s\n", sdk.Data.Display1) - fmt.Fprintf(s, " Display2: %s\n", sdk.Data.Display2) - fmt.Fprintf(s, " ID: %d\n", sdk.Data.ID) + fmt.Fprintf(s, " Time: %d ms\n", og.Time) + fmt.Fprintf(s, " Car: %s\n", og.Car) + fmt.Fprintf(s, " Flags: %b\n", og.Flags) + fmt.Fprintf(s, " Gear: %d\n", og.Gear) + fmt.Fprintf(s, " Plid: %d\n", og.Plid) + fmt.Fprintf(s, " Speed: %f m/s\n", og.Speed) + fmt.Fprintf(s, " RPM: %f RPM\n", og.RPM) + fmt.Fprintf(s, " Turbo: %f Bar\n", og.Turbo) + fmt.Fprintf(s, " EngTemp: %f °C\n", og.EngTemp) + fmt.Fprintf(s, " Fuel: %f\n", og.Fuel) + fmt.Fprintf(s, " OilPressure: %f Bar\n", og.OilPressure) + fmt.Fprintf(s, " OilTemp: %f °C\n", og.OilTemp) + fmt.Fprintf(s, " DashLights: %b\n", og.DashLights) + fmt.Fprintf(s, " ShowLights: %b\n", og.ShowLights) + fmt.Fprintf(s, " Throttle: %f\n", og.Throttle) + fmt.Fprintf(s, " Brakes: %f\n", og.Brake) + fmt.Fprintf(s, " Clutch: %f\n", og.Clutch) + fmt.Fprintf(s, " Display1: %s\n", og.Display1) + fmt.Fprintf(s, " Display2: %s\n", og.Display2) + fmt.Fprintf(s, " ID: %d\n", og.ID) fmt.Fprint(s, "}\n\n") fmt.Fprint(s, "DashLights {\n") - fmt.Fprintf(s, " DL_SHIFT: %t\n", sdk.HasShiftLight()) - fmt.Fprintf(s, " DL_FULLBEAM: %t\n", sdk.HasHighBeamLight()) - fmt.Fprintf(s, " DL_HANDBRAKE: %t\n", sdk.HasHandbrakeLight()) - fmt.Fprintf(s, " DL_PITSPEED: %t\n", sdk.HasPitspeed()) - fmt.Fprintf(s, " DL_TC: %t\n", sdk.HasTractionControlLight()) - fmt.Fprintf(s, " DL_SIGNAL_L: %t\n", sdk.HasLeftIndicatorLight()) - fmt.Fprintf(s, " DL_SIGNAL_R: %t\n", sdk.HasRightIndicatorLight()) - fmt.Fprintf(s, " DL_SIGNAL_ANY: %t\n", sdk.HasAnyIndicatorLight()) - fmt.Fprintf(s, " DL_OILWARN: %t\n", sdk.HasOilLight()) - fmt.Fprintf(s, " DL_BATTERY: %t\n", sdk.HasBatteryLight()) - fmt.Fprintf(s, " DL_ABS: %t\n", sdk.HasABSLight()) - fmt.Fprintf(s, " DL_SPARE: %t\n", sdk.Data.DashLights&bngsdk.DL_SPARE != 0) + fmt.Fprintf(s, " DL_SHIFT: %t\n", og.HasShiftLight()) + fmt.Fprintf(s, " DL_FULLBEAM: %t\n", og.HasHighBeamLight()) + fmt.Fprintf(s, " DL_HANDBRAKE: %t\n", og.HasHandbrakeLight()) + fmt.Fprintf(s, " DL_PITSPEED: %t\n", og.HasPitspeed()) + fmt.Fprintf(s, " DL_TC: %t\n", og.HasTractionControlLight()) + fmt.Fprintf(s, " DL_SIGNAL_L: %t\n", og.HasLeftIndicatorLight()) + fmt.Fprintf(s, " DL_SIGNAL_R: %t\n", og.HasRightIndicatorLight()) + fmt.Fprintf(s, " DL_SIGNAL_ANY: %t\n", og.HasAnyIndicatorLight()) + fmt.Fprintf(s, " DL_OILWARN: %t\n", og.HasOilLight()) + fmt.Fprintf(s, " DL_BATTERY: %t\n", og.HasBatteryLight()) + fmt.Fprintf(s, " DL_ABS: %t\n", og.HasABSLight()) + fmt.Fprintf(s, " DL_SPARE: %t\n", og.HasSpare()) fmt.Fprint(s, "}\n\n") fmt.Fprint(s, "ShowLights {\n") // Fixed typo "ShowLigths" - fmt.Fprintf(s, " DL_SHIFT: %t\n", sdk.ShiftLight()) - fmt.Fprintf(s, " DL_FULLBEAM: %t\n", sdk.HighBeam()) - fmt.Fprintf(s, " DL_HANDBRAKE: %t\n", sdk.Handbrake()) - fmt.Fprintf(s, " DL_PITSPEED: %t\n", sdk.Pitspeed()) - fmt.Fprintf(s, " DL_TC: %t\n", sdk.TractionControl()) - fmt.Fprintf(s, " DL_SIGNAL_L: %t\n", sdk.LeftIndicator()) - fmt.Fprintf(s, " DL_SIGNAL_R: %t\n", sdk.RightIndicator()) - fmt.Fprintf(s, " DL_SIGNAL_ANY: %t\n", sdk.AnyIndicator()) - fmt.Fprintf(s, " DL_OILWARN: %t\n", sdk.OilLight()) - fmt.Fprintf(s, " DL_BATTERY: %t\n", sdk.BatteryLight()) - fmt.Fprintf(s, " DL_ABS: %t\n", sdk.ABS()) - fmt.Fprintf(s, " DL_SPARE: %t\n", sdk.Data.ShowLights&bngsdk.DL_SPARE != 0) + fmt.Fprintf(s, " DL_SHIFT: %t\n", og.ShiftLight()) + fmt.Fprintf(s, " DL_FULLBEAM: %t\n", og.HighBeam()) + fmt.Fprintf(s, " DL_HANDBRAKE: %t\n", og.Handbrake()) + fmt.Fprintf(s, " DL_PITSPEED: %t\n", og.Pitspeed()) + fmt.Fprintf(s, " DL_TC: %t\n", og.TractionControl()) + fmt.Fprintf(s, " DL_SIGNAL_L: %t\n", og.LeftIndicator()) + fmt.Fprintf(s, " DL_SIGNAL_R: %t\n", og.RightIndicator()) + fmt.Fprintf(s, " DL_SIGNAL_ANY: %t\n", og.AnyIndicator()) + fmt.Fprintf(s, " DL_OILWARN: %t\n", og.OilLight()) + fmt.Fprintf(s, " DL_BATTERY: %t\n", og.BatteryLight()) + fmt.Fprintf(s, " DL_ABS: %t\n", og.ABS()) + fmt.Fprintf(s, " DL_SPARE: %t\n", og.Spare()) fmt.Fprint(s, "}\n\n") fmt.Fprint(s, "Flags {\n") - fmt.Fprintf(s, " OG_TURBO (Has Turbo): %t\n", sdk.HasTurbo()) - fmt.Fprintf(s, " OG_KM (Is Metric): %t\n", sdk.PrefersKm()) - fmt.Fprintf(s, " OG_BAR (Pressure): %t\n", sdk.PrefersBAR()) + fmt.Fprintf(s, " OG_TURBO (Has Turbo): %t\n", og.HasTurbo()) + fmt.Fprintf(s, " OG_KM (Is Metric): %t\n", og.PrefersKm()) + fmt.Fprintf(s, " OG_BAR (Pressure): %t\n", og.PrefersBAR()) fmt.Fprint(s, "}") }