From eed8c562b27b1667190d36c971513f8726ac71ee Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sat, 12 Sep 2026 14:39:48 +0100 Subject: [PATCH] added events to signal fresh data --- constants.go | 12 +- eventutils/eventutils.go | 66 ++++++ eventutils/eventutils_linux.go | 174 +++++++++++++++ eventutils/eventutils_test.go | 202 ++++++++++++++++++ .../eventutils_windows.go | 40 +++- irsdk.go | 39 +++- irsdk_test.go | 198 ++++++++--------- mmaputils/mmaputils.go | 47 ---- mmaputils/mmaputils_linux.go | 129 ----------- variables.go | 35 ++- 10 files changed, 630 insertions(+), 312 deletions(-) create mode 100644 eventutils/eventutils.go create mode 100644 eventutils/eventutils_linux.go create mode 100644 eventutils/eventutils_test.go rename mmaputils/mmaputils_windows.go => eventutils/eventutils_windows.go (72%) delete mode 100644 mmaputils/mmaputils.go delete mode 100644 mmaputils/mmaputils_linux.go diff --git a/constants.go b/constants.go index 1aa2cb0..ede8b5e 100644 --- a/constants.go +++ b/constants.go @@ -12,13 +12,15 @@ type Msg struct { } const ( + DATAVALIDEVENTNAME string = "IRSDKDataValidEvent" MEMMAPFILENAME = "IRSDKMemMapFileName" SimStatusUrl string = "http://127.0.0.1:32034/get_sim_status?object=simStatus" - IRSDK_DATAVALIDEVENTNAME string = "Local\\IRSDKDataValidEvent" - IRSDK_MEMMAPFILENAME string = "Local\\" + MEMMAPFILENAME - IRSDK_BROADCASTMSGNAME string = "IRSDK_BROADCASTMSG" - fileMapSize uint32 = 1164 * 1024 - connTimeout int64 = 30 + IRSDK_DATAVALIDEVENTNAME string = "Local\\" + DATAVALIDEVENTNAME + // IRSDK_DATAVALIDEVENTNAME string = DATAVALIDEVENTNAME + IRSDK_MEMMAPFILENAME string = "Local\\" + MEMMAPFILENAME + IRSDK_BROADCASTMSGNAME string = "IRSDK_BROADCASTMSG" + fileMapSize uint32 = 1164 * 1024 + connTimeout int64 = 30 ) const ( diff --git a/eventutils/eventutils.go b/eventutils/eventutils.go new file mode 100644 index 0000000..df4463f --- /dev/null +++ b/eventutils/eventutils.go @@ -0,0 +1,66 @@ +// I should rename winutils to something else but what this package does +// is interface some windows stuff that we need for the: +// - Broadcast Channel +// - Valid Data Event windows thing +package mmaputils + +import ( + "io" + "time" +) + +type Reader interface { + io.Reader + io.ReaderAt + io.ReadCloser +} + +type EventUtils struct { + Utils *utils +} + +func Init() (*EventUtils, error) { + u, err := newUtils() + if err != nil { + return nil, err + } + return &EventUtils{u}, nil +} + +func (u *EventUtils) Close() { + if u.Utils == nil { + return + } + + u.Utils.Close() +} + +// OpenEvent will open the named windows event +func (u *EventUtils) OpenEvent(name string) error { + return u.Utils.OpenEvent(name) +} + +// OpenBroadcastChannel will open the broadcast channel +func (u *EventUtils) OpenBroadcastChannel(name string) error { + return u.Utils.OpenBroadcastChannel(name) +} + +// CheckValidDataEvent checks if our windows even is telling us we are good to go +func (u *EventUtils) CheckValidDataEvent(timeout time.Duration) bool { + return u.Utils.CheckValidDataEvent(timeout) +} + +// SignalEvent triggers a frame pulse (used by mock servers and tests) +func (u *EventUtils) SignalEvent() error { + return u.Utils.SignalEvent() +} + +// SignalEvent triggers a frame pulse (used by mock servers and tests) +func SignalEvent(name string) { + signalEvent(name) +} + +// CleanupEvent cleans up event resources (used by mock servers and tests) +func CleanupEvent(name string) { + cleanupEvent(name) +} diff --git a/eventutils/eventutils_linux.go b/eventutils/eventutils_linux.go new file mode 100644 index 0000000..adb25bf --- /dev/null +++ b/eventutils/eventutils_linux.go @@ -0,0 +1,174 @@ +//go:build linux && cgo + +package mmaputils + +/* +#include +#include +#include +#include +#include + +static inline void* open_posix_semaphore(const char* name) { + sem_t* sem = sem_open(name, O_CREAT, 0666, 0); + if (sem == SEM_FAILED) { + return NULL; + } + return (void*)sem; +} + +static inline void close_posix_semaphore(void* sem_ptr) { + if (sem_ptr != NULL) { + sem_close((sem_t*)sem_ptr); + } +} + +static inline int timed_wait_posix_semaphore(void* sem_ptr, long timeout_ms) { + if (sem_ptr == NULL) { + return -1; + } + sem_t* sem = (sem_t*)sem_ptr; + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + + ts.tv_sec += timeout_ms / 1000; + ts.tv_nsec += (timeout_ms % 1000) * 1000000; + + if (ts.tv_nsec >= 1000000000) { + ts.tv_sec += ts.tv_nsec / 1000000000; + ts.tv_nsec %= 1000000000; + } + + int res; + do { + res = sem_timedwait(sem, &ts); + } while (res == -1 && errno == EINTR); // Retry if interrupted by Go GC/scheduler + + return res; +} + +static inline int signal_posix_semaphore(const char* name) { + if (name == NULL) { + return -1; + } + sem_t* sem = sem_open(name, 0); + if (sem == SEM_FAILED) { + return -1; + } + int res = sem_post(sem); + sem_close(sem); + return res; +} + +static inline int post_posix_semaphore(void* sem_ptr) { + if (sem_ptr != NULL) { + sem_post((sem_t*)sem_ptr); + } + return -1; +} + +static inline int unlink_posix_semaphore(const char* name) { + if (name == NULL) { + return -1; + } + return sem_unlink(name); +} +*/ +import "C" + +import ( + "fmt" + "strings" + "time" + "unsafe" + + "github.com/ESilva15/goirsdk/sharedMem" +) + +type utils struct { + semName string + sem unsafe.Pointer +} + +func newUtils() (*utils, error) { + return &utils{}, nil +} + +func (u *utils) Close() { + if u.sem != nil { + C.close_posix_semaphore(u.sem) + u.sem = nil + } +} + +// OpenMemMap returns a Reader interface that can be used to read the data +// No need to encapsulate it +func OpenMemMap(name string, size uint32) (Reader, error) { + file, err := sharedMem.Open(name, size) + if err != nil { + return nil, fmt.Errorf("failed to open `%s` with err: %+v", name, err) + } + + return file, nil +} + +// OpenEvent creates or connects to a Unix socket for event signaling on Linux +func (u *utils) OpenEvent(eventName string) error { + u.semName = "/" + strings.TrimPrefix(eventName, "/") + + cName := C.CString(u.semName) + defer C.free(unsafe.Pointer(cName)) + + sem := C.open_posix_semaphore(cName) + if sem == nil { + return fmt.Errorf("failed to open POSIX semaphore: %s", u.semName) + } + + u.sem = sem + return nil +} + +func (u *utils) OpenBroadcastChannel(name string) error { + // No-op or log stub on Linux + return nil +} + +// CheckValidDataEvent waits for a pulse byte sent over the socket +func (u *utils) CheckValidDataEvent(timeout time.Duration) bool { + if u.sem == nil { + return false + } + + ms := C.long(timeout.Milliseconds()) + res := C.timed_wait_posix_semaphore(u.sem, ms) + + return res == 0 +} + +func (u *utils) SendBroadcastMessage(id, p1, p2 uintptr) error { + return nil +} + +func (u *utils) SignalEvent() error { + if u.sem == nil { + return fmt.Errorf("u.sem is not set") + } + + C.post_posix_semaphore(u.sem) + return nil +} + +func signalEvent(name string) { + semName := "/" + strings.TrimPrefix(name, "/") + cName := C.CString(semName) + defer C.free(unsafe.Pointer(cName)) + C.signal_posix_semaphore(cName) +} + +func cleanupEvent(name string) { + semName := "/" + strings.TrimPrefix(name, "/") + cName := C.CString(semName) + defer C.free(unsafe.Pointer(cName)) + C.unlink_posix_semaphore(cName) +} diff --git a/eventutils/eventutils_test.go b/eventutils/eventutils_test.go new file mode 100644 index 0000000..ad9731b --- /dev/null +++ b/eventutils/eventutils_test.go @@ -0,0 +1,202 @@ +package mmaputils_test + +import ( + "testing" + "time" + + eventutils "github.com/ESilva15/goirsdk/eventutils" +) + +const testEventName = "test_iRSDKDataValidEvent" + +// Tests initialization and resource cleanup +func TestInitAndClose(t *testing.T) { + sdkUtils, err := eventutils.Init() + if err != nil { + t.Fatalf("Init() failed: %v", err) + } + if sdkUtils == nil { + t.Fatalf("Init() returned nil struct") + } + + sdkUtils.Close() +} + +// Tests timeout behavior when telemetry stalls or stops +func TestCheckValidDataEvent_Timeout(t *testing.T) { + defer eventutils.CleanupEvent(testEventName) + + sdkUtils, err := eventutils.Init() + if err != nil { + t.Fatalf("Init() failed: %v", err) + } + defer sdkUtils.Close() + + if err := sdkUtils.OpenEvent(testEventName); err != nil { + t.Fatalf("OpenEvent() failed: %v", err) + } + + timeout := 40 * time.Millisecond + start := time.Now() + + // Should return false because no producer signaled the event + got := sdkUtils.CheckValidDataEvent(timeout) + elapsed := time.Since(start) + + if got != false { + t.Errorf("expected CheckValidDataEvent to return false on timeout, got true") + } + + if elapsed < timeout { + t.Errorf("expected timeout to wait at least %v, returned early after %v", timeout, elapsed) + } +} + +// Tests successful unblocking when a frame signal arrives +func TestCheckValidDataEvent_Signaled(t *testing.T) { + defer eventutils.CleanupEvent(testEventName) + + sdkUtils, err := eventutils.Init() + if err != nil { + t.Fatalf("Init() failed: %v", err) + } + defer sdkUtils.Close() + + if err := sdkUtils.OpenEvent(testEventName); err != nil { + t.Fatalf("OpenEvent() failed: %v", err) + } + + // Simulate mock producer sending a frame signal after 10ms + go func() { + time.Sleep(10 * time.Millisecond) + eventutils.SignalEvent(testEventName) + }() + + start := time.Now() + got := sdkUtils.CheckValidDataEvent(200 * time.Millisecond) + elapsed := time.Since(start) + + if got != true { + t.Errorf("expected CheckValidDataEvent to return true on signal, got false") + } + + if elapsed >= 150*time.Millisecond { + t.Errorf("CheckValidDataEvent took too long (%v), failed to wake up immediately", elapsed) + } +} + +// Tests multi-frame streaming loop (simulating steady 60Hz feed) +func TestCheckValidDataEvent_MultipleTicks(t *testing.T) { + defer eventutils.CleanupEvent(testEventName) + + sdkUtils, err := eventutils.Init() + if err != nil { + t.Fatalf("Init() failed: %v", err) + } + defer sdkUtils.Close() + + if err := sdkUtils.OpenEvent(testEventName); err != nil { + t.Fatalf("OpenEvent() failed: %v", err) + } + + frameCount := 5 + go func() { + for i := 0; i < frameCount; i++ { + time.Sleep(10 * time.Millisecond) + eventutils.SignalEvent(testEventName) + } + }() + + for i := 0; i < frameCount; i++ { + ok := sdkUtils.CheckValidDataEvent(100 * time.Millisecond) + if !ok { + t.Fatalf("failed to receive pulse for tick %d", i) + } + } +} + +func Test60FPS60SecondsSemaphores(t *testing.T) { + if testing.Short() { + t.Skip("Skipping 60-second endurance test in short mode") + } + + const eventName = "test_60fps_event" + const targetFPS = 60 + const durationSeconds = 60 + const totalFrames = targetFPS * durationSeconds // 3,600 frames + const frameInterval = time.Second / targetFPS // ~16.666ms + + // Timeout per frame set to 100ms to absorb normal OS thread scheduling jitter + const frameWaitTimeout = 250 * time.Millisecond + + // Cleanup prior event state + eventutils.CleanupEvent(eventName) + defer eventutils.CleanupEvent(eventName) + + // Initialize Reader + uReader, err := eventutils.Init() + if err != nil { + t.Fatalf("Failed to initialize reader: %v", err) + } + defer uReader.Close() + + if err := uReader.OpenEvent(eventName); err != nil { + t.Fatalf("Failed to open event on reader: %v", err) + } + + // Initialize Writer + uWriter, err := eventutils.Init() + if err != nil { + t.Fatalf("Failed to initialize writer: %v", err) + } + defer uWriter.Close() + + if err := uWriter.OpenEvent(eventName); err != nil { + t.Fatalf("Failed to open event on writer: %v", err) + } + + stopWriter := make(chan struct{}) + writerDone := make(chan struct{}) + + // Producer Goroutine: Emits pulse at 60 FPS + go func() { + defer close(writerDone) + ticker := time.NewTicker(frameInterval) + defer ticker.Stop() + + for { + select { + case <-stopWriter: + return + case <-ticker.C: + if err := uWriter.SignalEvent(); err != nil { + t.Errorf("SignalEvent failed on writer: %v", err) + return + } + } + } + }() + + startTime := time.Now() + receivedFrames := 0 + + // Consumer Loop: Consumes 3,600 frames continuously + for i := 1; i <= totalFrames; i++ { + ok := uReader.CheckValidDataEvent(frameWaitTimeout) + if !ok { + close(stopWriter) + <-writerDone + t.Fatalf("FAILED at frame %d/%d (elapsed: %v). Semaphore timed out after %v.", + i, totalFrames, time.Since(startTime), frameWaitTimeout) + } + receivedFrames++ + } + + elapsed := time.Since(startTime) + close(stopWriter) + <-writerDone + + actualFPS := float64(receivedFrames) / elapsed.Seconds() + t.Logf("Passed: Processed %d/%d frames continuously in %v (Average FPS: %.2f)", + receivedFrames, totalFrames, elapsed, actualFPS) +} diff --git a/mmaputils/mmaputils_windows.go b/eventutils/eventutils_windows.go similarity index 72% rename from mmaputils/mmaputils_windows.go rename to eventutils/eventutils_windows.go index b9c89a7..a70a0bc 100644 --- a/mmaputils/mmaputils_windows.go +++ b/eventutils/eventutils_windows.go @@ -1,9 +1,11 @@ -// go:build windows +//go:build windows package mmaputils import ( + "fmt" "sync" + "syscall" "time" "unsafe" @@ -55,9 +57,14 @@ func (u *utils) OpenEvent(eventName string) error { return err } - event, err := windows.OpenEvent(windows.SYNCHRONIZE, false, name) + // Request EVENT_MODIFY_STATE so SetEvent can be called on this handle + event, err := windows.OpenEvent(windows.SYNCHRONIZE|windows.EVENT_MODIFY_STATE, false, name) if err != nil { - return err + // If event does not exist yet, create it + event, err = windows.CreateEvent(nil, 0, 0, name) + if err != nil { + return err + } } u.wEvent = event @@ -87,6 +94,33 @@ func (u *utils) OpenBroadcastChannel(name string) error { return nil } +func (u *utils) SignalEvent() error { + if u.wEvent != 0 { + err := windows.SetEvent(u.wEvent) + if err != nil { + return fmt.Errorf("failed to signal Win32 event: %+v", err) + } + } + + return nil +} + +func signalEvent(name string) { + cName, err := syscall.UTF16PtrFromString(name) + if err != nil { + return + } + h, err := windows.OpenEvent(windows.EVENT_MODIFY_STATE, false, cName) + if err == nil { + windows.SetEvent(h) + windows.CloseHandle(h) + } +} + +func cleanupEvent(name string) { + // Win32 events clean up automatically when handles close +} + // INITIALIZATION // closeEvent closes a given windows.Handle diff --git a/irsdk.go b/irsdk.go index a671ddb..b039a03 100644 --- a/irsdk.go +++ b/irsdk.go @@ -7,7 +7,7 @@ import ( "log/slog" "os" - "github.com/ESilva15/goirsdk/mmaputils" + eventutils "github.com/ESilva15/goirsdk/eventutils" "github.com/ESilva15/goirsdk/sharedMem" "gopkg.in/yaml.v3" ) @@ -48,7 +48,7 @@ type IBT struct { File Reader // Source of the data Opts Options IBTExporter Writer - winUtils *mmaputils.IRacingWinUtils // WinUtils gives access to the system utilities + winUtils *eventutils.EventUtils // WinUtils gives access to the system utilities // TODO: fragment this struct a little bit, for now I want to actually get // stuff done so its enough to work as is @@ -95,7 +95,7 @@ func (i *IBT) exportYAML() error { } func (i *IBT) exportIBT(data []byte, offset int64) error { - _, err := i.IBTExporter.WriteAt(data, offset) + nBytes, err := i.IBTExporter.WriteAt(data, offset) if err != nil { i.IBTExporter.Close() i.IBTExporter = nil @@ -103,6 +103,16 @@ func (i *IBT) exportIBT(data []byte, offset int64) error { return err } + if nBytes > 0 { + // Send the event stating the data has been created + err = i.winUtils.Utils.SignalEvent() + if err != nil { + i.Opts.Logger.Debug("failed to signal event", "err", err) + } else { + i.Opts.Logger.Debug("no error signaling: ", "nBytes", nBytes) + } + } + return nil } @@ -112,7 +122,7 @@ func (i *IBT) openSource() error { switch i.Opts.SourceType { case SharedMemoryFile: // User is requesting us to read live data - present in the mem map file - i.File, err = mmaputils.OpenMemMap(MEMMAPFILENAME, fileMapSize) + i.File, err = eventutils.OpenMemMap(MEMMAPFILENAME, fileMapSize) if err != nil { return fmt.Errorf("failed to open memory mapped file: %+v", err) } @@ -120,7 +130,7 @@ func (i *IBT) openSource() error { // To use our windows interface we need to initialize it first // it will return a struct with a pointer to the windows handles // if, for some reason, we need to stub out this to run in on Linux its easier - i.winUtils, err = mmaputils.Init() + i.winUtils, err = eventutils.Init() if err != nil { return err } @@ -177,11 +187,18 @@ func Init(opts Options) (*IBT, error) { // Create our irsdk instance var err error ibt := IBT{ - Opts: opts, - Vars: &TelemetryVars{}, - winUtils: nil, + Opts: opts, + Vars: &TelemetryVars{}, } + // Set up the event utils + evutils, err := eventutils.Init() + if err != nil { + return nil, err + } + evutils.OpenEvent(IRSDK_DATAVALIDEVENTNAME) + ibt.winUtils = evutils + // Setup the source err = ibt.openSource() if err != nil { @@ -219,7 +236,7 @@ func Init(opts Options) (*IBT, error) { // Read the telemetry vars info err = ibt.readVariablerHeaders() if err != nil { - return nil, fmt.Errorf("Unable to parser variable headers from file: %v", err) + return nil, fmt.Errorf("unable to parser variable headers from file: %v", err) } return &ibt, nil @@ -241,3 +258,7 @@ func (i *IBT) Close() { i.winUtils.Close() } } + +// LastTick returns the last tick +// func (i *IBT) LastTick() int { +// } diff --git a/irsdk_test.go b/irsdk_test.go index d4ac41c..9e6335c 100644 --- a/irsdk_test.go +++ b/irsdk_test.go @@ -1,101 +1,101 @@ package goirsdk -import ( - "fmt" - "log" - "os" - "sort" - "testing" - "time" -) - -type StandingsLine struct { - CarIdx int - LapPct float32 - Lap int32 - DriverName string - EstTime float32 - TimeBehind float32 -} - -func lapTimeRepresentation(t float32) string { - if t < 0 { - t = 0 - } - - wholeSeconds := int64(t) - lapTime := time.Unix(wholeSeconds, int64((t-float32(wholeSeconds))*1e9)) - - return lapTime.Format("04:05.000") -} - -func TestFunctionality(t *testing.T) { - input, err := os.Open("../../testTelemetry/supercars_race_watkins_glenn.ibt") - if err != nil { - t.Fatal("Was unable to prepare telemetry file for testing.") - } - - i, _ := Init(input, "", "") - defer i.Close() - - // Set up a loop to iterate our data - mainLoopTicker := time.NewTicker(time.Second / 60) - defer mainLoopTicker.Stop() - - for { - // Update the data that the SDK is holding with the next tick - _, err := i.Update(100 * time.Millisecond) - if err != nil { - log.Printf("could not update data: %v", err) - continue - } - - // Vehicle Movement data gathered from the names we can find on the - // telemetry_docs.pdf file - // - I wish to make this less verbose if possible - if _, ok := i.Vars.Vars["CarIdxPosition"]; !ok { - log.Fatal("Field `CarIdxPosition` doesn't exist") - } - driversLapDistPct := i.Vars.Vars["CarIdxLapDistPct"].Value.([]float32) - driversEstTime := i.Vars.Vars["CarIdxEstTime"].Value.([]float32) - driversLap := i.Vars.Vars["CarIdxLap"].Value.([]int32) - // driversBehind := i.Vars.Vars["CarIdxF2Time"].Value.([]float32) - - drivers := i.SessionInfo.DriverInfo.Drivers - myIdx := i.SessionInfo.DriverInfo.DriverCarIdx - - standings := make([]StandingsLine, len(drivers)) - - fmt.Printf("\033[?25l\033[2J\033[H") - for k := range len(drivers) { - if drivers[k].CarIsPaceCar == 1 || drivers[k].IsSpectator == 1 { - continue - } - - standings[k] = StandingsLine{ - CarIdx: k, - LapPct: driversLapDistPct[k], - DriverName: drivers[k].UserName, - EstTime: driversEstTime[k], - Lap: driversLap[k], - TimeBehind: driversEstTime[myIdx], - } - } - - sort.Slice(standings, func(i int, j int) bool { - if standings[i].Lap > int32(standings[j].Lap) { - return true - } - - return standings[i].LapPct >= standings[j].LapPct - }) - - fmt.Printf("%v\n", driversEstTime) - // for p, v := range standings { - // fmt.Printf("[%2d] %-30s %13f %13f\n", - // p+1, v.DriverName, v.LapPct, driversEstTime[p] - driversEstTime[myIdx]) - // } - - <-mainLoopTicker.C - } -} +// import ( +// "fmt" +// "log" +// "os" +// "sort" +// "testing" +// "time" +// ) +// +// type StandingsLine struct { +// CarIdx int +// LapPct float32 +// Lap int32 +// DriverName string +// EstTime float32 +// TimeBehind float32 +// } +// +// func lapTimeRepresentation(t float32) string { +// if t < 0 { +// t = 0 +// } +// +// wholeSeconds := int64(t) +// lapTime := time.Unix(wholeSeconds, int64((t-float32(wholeSeconds))*1e9)) +// +// return lapTime.Format("04:05.000") +// } +// +// func TestFunctionality(t *testing.T) { +// input, err := os.Open("../../testTelemetry/supercars_race_watkins_glenn.ibt") +// if err != nil { +// t.Fatal("Was unable to prepare telemetry file for testing.") +// } +// +// i, _ := Init(input, "", "") +// defer i.Close() +// +// // Set up a loop to iterate our data +// mainLoopTicker := time.NewTicker(time.Second / 60) +// defer mainLoopTicker.Stop() +// +// for { +// // Update the data that the SDK is holding with the next tick +// _, err := i.Update(100 * time.Millisecond) +// if err != nil { +// log.Printf("could not update data: %v", err) +// continue +// } +// +// // Vehicle Movement data gathered from the names we can find on the +// // telemetry_docs.pdf file +// // - I wish to make this less verbose if possible +// if _, ok := i.Vars.Vars["CarIdxPosition"]; !ok { +// log.Fatal("Field `CarIdxPosition` doesn't exist") +// } +// driversLapDistPct := i.Vars.Vars["CarIdxLapDistPct"].Value.([]float32) +// driversEstTime := i.Vars.Vars["CarIdxEstTime"].Value.([]float32) +// driversLap := i.Vars.Vars["CarIdxLap"].Value.([]int32) +// // driversBehind := i.Vars.Vars["CarIdxF2Time"].Value.([]float32) +// +// drivers := i.SessionInfo.DriverInfo.Drivers +// myIdx := i.SessionInfo.DriverInfo.DriverCarIdx +// +// standings := make([]StandingsLine, len(drivers)) +// +// fmt.Printf("\033[?25l\033[2J\033[H") +// for k := range len(drivers) { +// if drivers[k].CarIsPaceCar == 1 || drivers[k].IsSpectator == 1 { +// continue +// } +// +// standings[k] = StandingsLine{ +// CarIdx: k, +// LapPct: driversLapDistPct[k], +// DriverName: drivers[k].UserName, +// EstTime: driversEstTime[k], +// Lap: driversLap[k], +// TimeBehind: driversEstTime[myIdx], +// } +// } +// +// sort.Slice(standings, func(i int, j int) bool { +// if standings[i].Lap > int32(standings[j].Lap) { +// return true +// } +// +// return standings[i].LapPct >= standings[j].LapPct +// }) +// +// fmt.Printf("%v\n", driversEstTime) +// // for p, v := range standings { +// // fmt.Printf("[%2d] %-30s %13f %13f\n", +// // p+1, v.DriverName, v.LapPct, driversEstTime[p] - driversEstTime[myIdx]) +// // } +// +// <-mainLoopTicker.C +// } +// } diff --git a/mmaputils/mmaputils.go b/mmaputils/mmaputils.go deleted file mode 100644 index d810893..0000000 --- a/mmaputils/mmaputils.go +++ /dev/null @@ -1,47 +0,0 @@ -// I should rename winutils to something else but what this package does -// is interface some windows stuff that we need for the: -// - Broadcast Channel -// - Valid Data Event windows thing -package mmaputils - -import ( - "io" - "time" -) - -type Reader interface { - io.Reader - io.ReaderAt - io.ReadCloser -} - -type IRacingWinUtils struct { - Utils *utils -} - -func Init() (*IRacingWinUtils, error) { - u, err := newUtils() - if err != nil { - return nil, err - } - return &IRacingWinUtils{u}, nil -} - -func (u *IRacingWinUtils) Close() { - u.Utils.Close() -} - -// OpenWinEvent will open the named windows event -func (u *IRacingWinUtils) OpenWinEvent(name string) error { - return u.Utils.OpenEvent(name) -} - -// OpenBroadcastChannel will open the broadcast channel -func (u *IRacingWinUtils) OpenBroadcastChannel(name string) error { - return u.Utils.OpenBroadcastChannel(name) -} - -// CheckValidDataEvent checks if our windows even is telling us we are good to go -func (u *IRacingWinUtils) CheckValidDataEvent(timeout time.Duration) bool { - return u.Utils.CheckValidDataEvent(timeout) -} diff --git a/mmaputils/mmaputils_linux.go b/mmaputils/mmaputils_linux.go deleted file mode 100644 index e88f4ee..0000000 --- a/mmaputils/mmaputils_linux.go +++ /dev/null @@ -1,129 +0,0 @@ -//go:build (linux && cgo) || (darwin && cgo) - -package mmaputils - -import ( - "fmt" - "net" - "os" - "time" - - "github.com/ESilva15/goirsdk/sharedMem" -) - -type utils struct { - socketPath string - listener *net.UnixConn -} - -func newUtils() (*utils, error) { - return &utils{}, nil -} - -func (u *utils) Close() { - if u.listener != nil { - u.listener.Close() - os.Remove(u.socketPath) - } -} - -// OpenMemMap returns a Reader interface that can be used to read the data -// No need to encapsulate it -func OpenMemMap(name string, size uint32) (Reader, error) { - file, err := sharedMem.Open(name, size) - if err != nil { - return nil, fmt.Errorf("failed to open `%s` with err: %+v", name, err) - } - - return file, nil -} - -// OpenEvent creates or connects to a Unix socket for event signaling on Linux -func (u *utils) OpenEvent(eventName string) error { - u.socketPath = fmt.Sprintf("/tmp/iracing_%s.sock", eventName) - _ = os.Remove(u.socketPath) - - addr, err := net.ResolveUnixAddr("unixgram", u.socketPath) - if err != nil { - return err - } - - l, err := net.ListenUnixgram("unixgram", addr) - if err != nil { - return err - } - u.listener = l - return nil -} - -func (u *utils) OpenBroadcastChannel(name string) error { - // No-op or log stub on Linux - return nil -} - -// CheckValidDataEvent waits for a pulse byte sent over the socket -func (u *utils) CheckValidDataEvent(timeout time.Duration) bool { - if u.listener == nil { - return false - } - - _ = u.listener.SetReadDeadline(time.Now().Add(timeout)) - buf := make([]byte, 1) - _, err := u.listener.Read(buf) - return err == nil -} - -func (u *utils) SendBroadcastMessage(id, p1, p2 uintptr) error { - return nil -} - -// package winutils -// -// import ( -// "errors" -// "sync" -// "time" -// ) -// -// const ( -// WAIT_OBJECT_0 = 0 -// WAIT_TIMEOUT = 258 -// ) -// -// var ( -// once sync.Once -// ErrUnsupportedOS = errors.New("not found") -// ) -// -// type utils struct { -// } -// -// // INITIALIZATION -// func newUtils() (*utils, error) { -// return nil, ErrUnsupportedOS -// } -// -// func (u *utils) Close() { -// } -// -// // openEvent opens a windows.Handle for a given event -// func (u *utils) OpenEvent(eventName string) error { -// return ErrUnsupportedOS -// } -// -// // OpenBroadcastChannel opens up a broadcast channel to send commands to iracing -// func (u *utils) OpenBroadcastChannel(name string) error { -// return ErrUnsupportedOS -// } -// -// // INITIALIZATION -// -// // openEvent waits for a good response for some given time -// func (u *utils) CheckValidDataEvent(timeout time.Duration) bool { -// return false -// } -// -// // SendBroadcastMessage sends a message trough the broadcast channel -// func (u *utils) SendBroadcastMessage(id, p1, p2 uintptr) error { -// return ErrUnsupportedOS -// } diff --git a/variables.go b/variables.go index a6ca088..a47d86d 100644 --- a/variables.go +++ b/variables.go @@ -261,11 +261,6 @@ func (i *IBT) readData(buf []byte) error { v.Value = math.Float64frombits(uint64(binary.LittleEndian.Uint64(rbuf))) } } - // -------------- - - if k == "SessionState" { - i.Opts.Logger.Debug(fmt.Sprintf("SessionState: %+v", v)) - } i.Vars.Vars[k] = v } @@ -312,6 +307,10 @@ func (i *IBT) Update(timeout time.Duration) (IRacingState, error) { buf := make([]byte, i.Headers.BufLen) _, err := i.File.ReadAt(buf, int64(start)) + if err == io.EOF { + return Ended, nil + } + if err != nil { return Failed, err } @@ -321,10 +320,10 @@ func (i *IBT) Update(timeout time.Duration) (IRacingState, error) { var offset int64 = 0 switch i.Opts.IBTExportType { case IBTFile: - i.Opts.Logger.Debug("Reading live data and exporting to IBT file") + // i.Opts.Logger.Debug("Reading live data and exporting to IBT file") offset = int64(i.Headers.BufOffset + i.Vars.RecorderTick*i.Headers.BufLen) case SharedMemoryFile: - i.Opts.Logger.Debug("Reading live data and exporting to SHM file") + // i.Opts.Logger.Debug("Reading live data and exporting to SHM file") offset = int64(i.Headers.BufOffset) } @@ -339,10 +338,6 @@ func (i *IBT) Update(timeout time.Duration) (IRacingState, error) { return Unknown, err } - if err == io.EOF { - return Ended, nil - } - // Document why this is here, I don't remember the exact words right now i.Vars.RecorderTick++ case IBTFile: @@ -352,6 +347,13 @@ func (i *IBT) Update(timeout time.Duration) (IRacingState, error) { buf := make([]byte, i.Headers.BufLen) _, err := i.File.ReadAt(buf, int64(start)) + if err == io.EOF { + return Ended, nil + } + if err != nil { + return Unknown, err + } + // Make this happen in a different thread, or have this send to a queue that has a thread // writing to a file if i.Opts.IBTExport { @@ -359,10 +361,10 @@ func (i *IBT) Update(timeout time.Duration) (IRacingState, error) { var offset int64 = 0 switch i.Opts.IBTExportType { case IBTFile: - i.Opts.Logger.Debug("Reading IBT file and exporting to IBT file") + // i.Opts.Logger.Debug("Reading IBT file and exporting to IBT file") offset = int64(start) case SharedMemoryFile: - i.Opts.Logger.Debug("Reading IBT file and exporting to SHM file") + // i.Opts.Logger.Debug("Reading IBT file and exporting to SHM file") offset = int64(i.Headers.BufOffset) } @@ -372,13 +374,6 @@ func (i *IBT) Update(timeout time.Duration) (IRacingState, error) { } } - if err == io.EOF { - return Ended, nil - } - if err != nil { - return Unknown, err - } - err = i.readData(buf) if err != nil && err != io.EOF { log.Fatalf("What happened?\n%v\n", err)