From 31a2f85407f8670a840d0c21fffe7cafe000f387 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Mon, 29 Jun 2026 11:27:27 +0100 Subject: [PATCH] added a view to the recording utility of BeamNG too --- cmd/record.go | 11 ++- cmd/replay.go | 1 + mockserver/record.go | 159 ++++++++++++++++++++++++++++++++++++------- mockserver/views.go | 19 ++++++ 4 files changed, 163 insertions(+), 27 deletions(-) diff --git a/cmd/record.go b/cmd/record.go index 983a3af..f764873 100644 --- a/cmd/record.go +++ b/cmd/record.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "fmt" "github.com/ESilva15/BeamNGMockOg/mockserver" @@ -13,7 +14,15 @@ func recordAction(cmd *cobra.Command, args []string) { address, _ := cmd.Flags().GetString("address") port, _ := cmd.Flags().GetInt("port") - if err := mockserver.Record(address, port, outputFile); err != nil { + recorder, err := mockserver.NewRecorder(outputFile, address, port) + if err != nil { + panic(fmt.Sprintf("failed to create new BeamNG recorder: %+v", err)) + } + defer recorder.Close() + + // NOTE: is this doing anything at all?? + ctx := context.Background() + if err := recorder.Record(ctx); err != nil { fmt.Printf("Something went wrong while recording the file: %v", err) } } diff --git a/cmd/replay.go b/cmd/replay.go index ce297bf..447dbfd 100644 --- a/cmd/replay.go +++ b/cmd/replay.go @@ -21,6 +21,7 @@ func replayAction(cmd *cobra.Command, args []string) { 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) diff --git a/mockserver/record.go b/mockserver/record.go index 72bf229..5cb486a 100644 --- a/mockserver/record.go +++ b/mockserver/record.go @@ -1,47 +1,154 @@ package mockserver import ( + "bytes" + "context" "encoding/binary" - "log" + "fmt" "os" + "strings" + "sync" "time" bngsdk "github.com/ESilva15/gobngsdk" ) -// NOTE: add some visual feedback of whats happening. -// Maybe reuse the replay view function +type recorderViewData struct { + TotalBytes int + Data []byte +} + +type Recorder struct { + SDK bngsdk.BeamNGSDK + OutputFile *os.File + TotalBytes int + // Views + mut sync.RWMutex + viewDataMut sync.RWMutex + viewData recorderViewData + viewCh chan *recorderViewData + recorderCh chan []byte +} + +func NewRecorder(fp string, address string, port int) (*Recorder, 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) + + return &recorder, nil +} + +func (r *Recorder) Close() { + r.SDK.Close() + + if r.OutputFile != nil { + r.OutputFile.Close() + } +} + +func (r *Recorder) record(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case data := <-r.recorderCh: + r.mut.RLock() + err := binary.Write(r.OutputFile, binary.LittleEndian, data) + r.TotalBytes += len(data) + r.mut.RUnlock() + + if err != nil { + // NOTE: find a way of logging this somehow + } + } + } +} + +func (r *Recorder) view(ctx context.Context) { + var s strings.Builder + var nBytes int + var bytesReader bytes.Reader + og := bngsdk.Outgauge{} + + for { + select { + case <-ctx.Done(): + return + case viewData := <-r.viewCh: + s.Reset() + fmt.Fprintf(os.Stdout, "\x1b[2J\x1b[H") + + r.viewDataMut.RLock() + nBytes = viewData.TotalBytes + r.viewDataMut.RUnlock() + + stringifyRecordingProgress(&s, nBytes) + fmt.Fprintf(&s, "\n\n") + + bytesReader.Reset(viewData.Data) + err := binary.Read(&bytesReader, binary.LittleEndian, &og) + if err != nil { + fmt.Fprintf(&s, "\n\nFAILED TO PARSE DATA\nError: %+v\n\n", err) + fmt.Fprint(os.Stdout, s.String()) + continue + } + + stringifyOutgaugeData(&s, &og) + + fmt.Fprint(os.Stdout, s.String()) + } + } +} // Record records data from the UDP connection created by address and port -func Record(address string, port int, filePath string) error { +func (r *Recorder) Record(ctx context.Context) error { ticker := time.NewTicker(time.Second / 60) defer ticker.Stop() - // Create the output file - bin, err := os.Create(filePath) - if err != nil { - return err - } - defer bin.Close() - - // Create the BeamNGSDK instance - beam, err := bngsdk.Init(address, port) - if err != nil { - return err - } - defer beam.Close() + go r.record(ctx) + go r.view(ctx) for { - err := beam.ReadData() - if err != nil { - return err - } + select { + case <-ctx.Done(): + return nil + case <-ticker.C: + err := r.SDK.ReadData() + if err != nil { + return err + } - err = binary.Write(bin, binary.LittleEndian, beam.Data) - if err != nil { - log.Fatal(err) - } + r.viewData.TotalBytes = r.TotalBytes + r.viewData.Data = r.SDK.Buffer - <-ticker.C + // Send the data to the view + select { + case r.viewCh <- &r.viewData: + // Sent the data + default: + // Dropped the frame! + } + + // Send the data to the UDP socket + select { + case r.recorderCh <- r.SDK.Buffer: + // Sent the data + default: + // Dropped the frame! + } + } } } diff --git a/mockserver/views.go b/mockserver/views.go index f8f2af8..809516e 100644 --- a/mockserver/views.go +++ b/mockserver/views.go @@ -7,6 +7,25 @@ import ( bngsdk "github.com/ESilva15/gobngsdk" ) +const ( + _ = iota + KiB = 1 << (10 * iota) + MiB + GiB +) + +func stringifyRecordingProgress(s *strings.Builder, nBytes int) { + if nBytes < KiB { + fmt.Fprintf(s, "%d B", nBytes) + } else if nBytes < MiB { + fmt.Fprintf(s, "%.2f KiB", float64(nBytes)/float64(KiB)) + } else if nBytes < GiB { + fmt.Fprintf(s, "%.2f MiB", float64(nBytes)/float64(MiB)) + } else { + fmt.Fprintf(s, "%.2f GiB", float64(nBytes)/float64(GiB)) + } +} + func stringifyOutgaugeData(s *strings.Builder, og *bngsdk.Outgauge) { // NOTE: write a string serialization function on the SDK itself fmt.Fprint(s, "Outgauge {\n")