upgrade to support writing to a memmap file

This commit is contained in:
2026-08-23 22:56:14 +01:00
parent 76a091b076
commit 9adc0a9456
10 changed files with 300 additions and 169 deletions
+6 -3
View File
@@ -8,9 +8,10 @@ type Msg struct {
} }
const ( const (
MEMMAPFILENAME = "IRSDKMemMapFileName"
SimStatusUrl string = "http://127.0.0.1:32034/get_sim_status?object=simStatus" SimStatusUrl string = "http://127.0.0.1:32034/get_sim_status?object=simStatus"
IRSDK_DATAVALIDEVENTNAME string = "Local\\IRSDKDataValidEvent" IRSDK_DATAVALIDEVENTNAME string = "Local\\IRSDKDataValidEvent"
IRSDK_MEMMAPFILENAME string = "Local\\IRSDKMemMapFileName" IRSDK_MEMMAPFILENAME string = "Local\\" + MEMMAPFILENAME
IRSDK_BROADCASTMSGNAME string = "IRSDK_BROADCASTMSG" IRSDK_BROADCASTMSGNAME string = "IRSDK_BROADCASTMSG"
fileMapSize uint32 = 1164 * 1024 fileMapSize uint32 = 1164 * 1024
connTimeout int64 = 30 connTimeout int64 = 30
@@ -133,9 +134,11 @@ var (
irsdkPitSpeedLimiter = bitfieldValue{0x10, "irsdk_pitSpeedLimiter"} irsdkPitSpeedLimiter = bitfieldValue{0x10, "irsdk_pitSpeedLimiter"}
irsdkRevLimiterActive = bitfieldValue{0x20, "irsdk_revLimiterActive"} irsdkRevLimiterActive = bitfieldValue{0x20, "irsdk_revLimiterActive"}
irsdkAbsActive = bitfieldValue{0x100, "irsdk_absActive"} irsdkAbsActive = bitfieldValue{0x100, "irsdk_absActive"}
irsdkEngineWarnings = []bitfieldValue{irsdkWaterTempWarning, irsdkFuelPressureWarning, irsdkEngineWarnings = []bitfieldValue{
irsdkWaterTempWarning, irsdkFuelPressureWarning,
irsdkOilPressureWarning, irsdkEngineStalled, irsdkPitSpeedLimiter, irsdkRevLimiterActive, irsdkOilPressureWarning, irsdkEngineStalled, irsdkPitSpeedLimiter, irsdkRevLimiterActive,
irsdkAbsActive} irsdkAbsActive,
}
) )
// enum irsdk_EngineWarnings // enum irsdk_EngineWarnings
+7 -7
View File
@@ -1,11 +1,11 @@
package goirsdk package goirsdk
import ( import (
"github.com/ESilva15/goirsdk/logger"
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/ESilva15/goirsdk/logger"
) )
const ( const (
@@ -28,18 +28,18 @@ func (i *IBT) readSubheader() error {
var subheaderRaw [SubHeaderSize]byte var subheaderRaw [SubHeaderSize]byte
_, err := i.File.ReadAt(subheaderRaw[:], HeaderSize) _, err := i.File.ReadAt(subheaderRaw[:], HeaderSize)
if err != nil { if err != nil {
return fmt.Errorf("Failed to read disk subheaders from file: %v", err) return fmt.Errorf("failed to read disk subheaders from file: %v", err)
} }
i.SubHeaders, err = parseTelemetrySubHeader(subheaderRaw) i.SubHeaders, err = parseTelemetrySubHeader(subheaderRaw)
if err != nil { if err != nil {
return fmt.Errorf("Unable to parse disk subheaders from file: %v", err) return fmt.Errorf("unable to parse disk subheaders from file: %v", err)
} }
// Write to the output file - TODO add the check // Write to the output file - TODO add the check
if i.IBTExport != nil { if i.Opts.IBTExport {
err = i.exportIBT(subheaderRaw[:], HeaderSize) err = i.exportIBT(subheaderRaw[:], HeaderSize)
if err != nil { if err != nil {
log.Printf("Failed to export subheaders: %v\n", err) log.Printf("failed to export subheaders: %v\n", err)
} }
} }
+10 -9
View File
@@ -3,7 +3,6 @@ package main
import ( import (
"fmt" "fmt"
"log" "log"
"os"
"time" "time"
"github.com/ESilva15/goirsdk" "github.com/ESilva15/goirsdk"
@@ -14,21 +13,23 @@ func msToKph(v float32) int {
} }
func main() { func main() {
// Open the data source file
file, err := os.Open("/path/to/ibtFile")
if err != nil {
log.Fatalf("Failed to open IBT file: %v", err)
}
// Instantiate our iRacing SDK instance // Instantiate our iRacing SDK instance
irsdk, err := goirsdk.Init(file, "", "") irsdk, err := goirsdk.Init(goirsdk.Options{
SourceType: goirsdk.IBTFile,
SourcePath: "../../../testTelemetry/gt3_mustang_bathurst.ibt",
// SourcePath: "./exported.ibt",
IBTExportType: goirsdk.SharedMemoryFile,
// IBTExportPath: "./exported.ibt",
IBTExport: true,
})
if err != nil { if err != nil {
log.Fatalf("Failed to create iRacing interface: %v", err) log.Fatalf("Failed to create iRacing interface: %v", err)
} }
defer irsdk.Close() defer irsdk.Close()
// Set up a loop to iterate our data // Set up a loop to iterate our data
mainLoopTicker := time.NewTicker(time.Second / 60) // TODO: revert this 240 back to 60 because i recorded the thing wrong or whatever
mainLoopTicker := time.NewTicker(time.Second / 240)
defer mainLoopTicker.Stop() defer mainLoopTicker.Stop()
for { for {
+19 -30
View File
@@ -1,11 +1,11 @@
package goirsdk package goirsdk
import ( import (
"github.com/ESilva15/goirsdk/logger"
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/ESilva15/goirsdk/logger"
) )
const ( const (
@@ -15,30 +15,19 @@ const (
// TelemetryHeaders struct to hold an IBT file's headers // TelemetryHeaders struct to hold an IBT file's headers
type TelemetryHeaders struct { type TelemetryHeaders struct {
Version int32 Version int32
// Status of 1 indicates a completed session and status of 0 a live session Status int32 // Status of 1 indicates a completed session and status of 0 a live session
Status int32 TickRate int32 // TickRate indicates the frequency of writes (usually 60)
// TickRate indicates the frequency of writes (usually 60) SessionInfoUpdate int32 // SessionInfoUpdate indicates the number of times the SessionInfo was
TickRate int32
// SessionInfoUpdate indicates the number of times the SessionInfo was
// updated. 0 for finished sessions and >1 for active sessions // updated. 0 for finished sessions and >1 for active sessions
SessionInfoUpdate int32 SessionInfoLength int32 // SessionInfoLength is the length of the session info buffer
// SessionInfoLength is the length of the session info buffer SessionInfoOffset int32 // SessionInfoOffset is the offset of the session info in the buffer
SessionInfoLength int32 NumVars int32 // NumVars is the number of variables in each input
// SessionInfoOffset is the offset of the session info in the buffer VarHeaderOffset int32 // VarHeaderOffset is the offset of the VarHeader
SessionInfoOffset int32 NumBuf int32 // NumBuf will be 1 for static files and 3 for live telemetry files
// NumVars is the number of variables in each input BufLen int32 // BufLen is the length for parsing VarHeader values
NumVars int32 Padding [12]byte // Padding
// VarHeaderOffset is the offset of the VarHeader BufOffset int32 // I still don't know what this is:
VarHeaderOffset int32
// NumBuf will be 1 for static files and 3 for live telemetry files
NumBuf int32
// BufLen is the length for parsing VarHeader values
BufLen int32
// Padding
Padding [12]byte
// I still don't know what this is:
BufOffset int32
} }
// readHeader will read the header out of the telemetry data // readHeader will read the header out of the telemetry data
@@ -48,18 +37,18 @@ func (i *IBT) readHeader() error {
var headerRaw [FileHeaderSize]byte var headerRaw [FileHeaderSize]byte
_, err := i.File.ReadAt(headerRaw[:], 0) _, err := i.File.ReadAt(headerRaw[:], 0)
if err != nil { if err != nil {
return fmt.Errorf("Failed to read headers from file: %v", err) return fmt.Errorf("failed to read headers from file: %v", err)
} }
i.Headers, err = parseTelemetryHeader(headerRaw) i.Headers, err = parseTelemetryHeader(headerRaw)
if err != nil { if err != nil {
return fmt.Errorf("Unable to read headers from file: %v", err) return fmt.Errorf("unable to read headers from file: %v", err)
} }
// Write to the output file - TODO: this should only write if necessary // Write to the output file - TODO: this should only write if necessary
if i.IBTExport != nil { if i.Opts.IBTExport {
err = i.exportIBT(headerRaw[:], 0) err = i.exportIBT(headerRaw[:], 0)
if err != nil { if err != nil {
log.Printf("Failed to export headers: %v\n", err) log.Printf("Failed to export headers: %v\n", err)
} }
} }
+117 -62
View File
@@ -3,23 +3,15 @@ package goirsdk
import ( import (
"fmt" "fmt"
"io"
"os" "os"
"io"
"github.com/ESilva15/goirsdk/logger" "github.com/ESilva15/goirsdk/logger"
"github.com/ESilva15/goirsdk/sharedMem"
"github.com/ESilva15/goirsdk/winutils" "github.com/ESilva15/goirsdk/winutils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
const (
ibtFile = "./telemetryFiles/mx5_2016Okayama_full_2024_10_19_22_02_12.ibt"
)
func msToKph(v float32) int {
return int((3600 * v) / 1000)
}
// Reader is an interface to represent the readable data that can be either // Reader is an interface to represent the readable data that can be either
// a .ibt file (or live data, hopefully) // a .ibt file (or live data, hopefully)
type Reader interface { type Reader interface {
@@ -28,18 +20,47 @@ type Reader interface {
io.ReadCloser io.ReadCloser
} }
type Writer interface {
io.WriterAt
io.Closer
}
type TelemetryContainer int
const (
IBTFile TelemetryContainer = iota
SharedMemoryFile TelemetryContainer = iota
)
type Options struct {
SourceType TelemetryContainer // type of source data
SourcePath string // Path to source
IBTExportType TelemetryContainer // export type of telemetry: store .ibt or replay in shm
IBTExportPath string // path where to export the data
IBTExport bool // whether to export the telemetry data
SessionInfoExport bool // whether to export the session info data
SessionInfoExportPath string // path where to export the session info
}
// IBT struct will hold the relevant data for a given IBT file // IBT struct will hold the relevant data for a given IBT file
type IBT struct { type IBT struct {
File Reader // Source of the data File Reader // Source of the data
IBTExport *os.File // If set, it will export the IBT data to the file Opts Options
IBTExportPath string // Path for IBT export // TODO: IBTExporter should be an interface because we need to support shm too
YAMLExport *os.File // If set, it will export the session YAML to the file IBTExporter Writer
YAMLExportPath string // Path for YAML export // IBTExport *os.File // If set, it will export the IBT data to the file
Headers *TelemetryHeaders // IBT file Headers // IBTExportPath string // Path for IBT export
SubHeaders *DiskSubHeader // IBT file Sub Headers // YAMLExport *os.File // If set, it will export the session YAML to the file
SessionInfo *SessionInfoYAML // IBT file Session Info // YAMLExportPath string // Path for YAML export
Vars *TelemetryVars // Vars will hold the telemetry data winUtils *winutils.IRacingWinUtils // WinUtils gives access to the system utilities
winUtils *winutils.IRacingWinUtils // 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
// Actual FILE
Headers *TelemetryHeaders // IBT file Headers
SubHeaders *DiskSubHeader // IBT file Sub Headers
SessionInfo *SessionInfoYAML // IBT file Session Info
Vars *TelemetryVars // Vars will hold the telemetry data
} }
func (i *IBT) IsConnected() bool { func (i *IBT) IsConnected() bool {
@@ -58,7 +79,7 @@ func (i *IBT) IsConnected() bool {
func (i *IBT) exportYAML() error { func (i *IBT) exportYAML() error {
log := logger.GetInstance() log := logger.GetInstance()
file, err := os.OpenFile(i.YAMLExportPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) file, err := os.OpenFile(i.Opts.SessionInfoExportPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil { if err != nil {
log.Printf("Failed to open file for YAML export: %v\n", err) log.Printf("Failed to open file for YAML export: %v\n", err)
return fmt.Errorf("failed to open output file for YAML: %v", err) return fmt.Errorf("failed to open output file for YAML: %v", err)
@@ -79,11 +100,10 @@ func (i *IBT) exportYAML() error {
func (i *IBT) exportIBT(data []byte, offset int64) error { func (i *IBT) exportIBT(data []byte, offset int64) error {
log := logger.GetInstance() log := logger.GetInstance()
_, err := i.IBTExport.WriteAt(data, offset) _, err := i.IBTExporter.WriteAt(data, offset)
if err != nil { if err != nil {
i.IBTExport.Close() i.IBTExporter.Close()
i.IBTExport = nil i.IBTExporter = nil
log.Println("Won't attempt to export anymore") log.Println("Won't attempt to export anymore")
return err return err
} }
@@ -91,61 +111,96 @@ func (i *IBT) exportIBT(data []byte, offset int64) error {
return nil return nil
} }
// Init serves to initialize and get a hold of a IBT struct func (i *IBT) openSource() error {
// f -> is the source data, pass nil for the SDK to read live data or a
// *os.File to read from a file
// exportTelem -> is a string with the path to export the telemetry data, pass
// an empty string to not export any data
// exportTelem -> is a string with the path to export the session info data, pass
// an empty string to not export any data
func Init(f Reader, exportTelem string, exportYAML string) (*IBT, error) {
// log := logger.GetInstance()
// Read the header of the file
var err error var err error
ibt := IBT{
File: f,
IBTExport: nil,
IBTExportPath: exportTelem,
YAMLExport: nil,
YAMLExportPath: exportYAML,
Vars: &TelemetryVars{},
winUtils: nil,
}
// If requested to output to a telemetry file switch i.Opts.SourceType {
if exportTelem != "" { case SharedMemoryFile:
ibt.IBTExport, err = os.OpenFile(exportTelem, os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
return nil, fmt.Errorf("failed to open ibt export file: %v", err)
}
}
if ibt.File == nil {
// User is requesting us to read live data - present in the mem map file // User is requesting us to read live data - present in the mem map file
ibt.File, err = winutils.OpenMemMap(IRSDK_MEMMAPFILENAME, fileMapSize) i.File, err = winutils.OpenMemMap(IRSDK_MEMMAPFILENAME, fileMapSize)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to open memory mapped file: %v", err) return fmt.Errorf("failed to open memory mapped file: %v", err)
} }
// To use our windows interface we need to initialize it first // To use our windows interface we need to initialize it first
// it will return a struct with a pointer to the windows handles // 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 // if, for some reason, we need to stub out this to run in on Linux its easier
ibt.winUtils, err = winutils.Init() i.winUtils, err = winutils.Init()
if err != nil { if err != nil {
return nil, err return err
} }
// We need to open the windows event thing // We need to open the windows event thing
err = ibt.winUtils.OpenWinEvent(IRSDK_DATAVALIDEVENTNAME) err = i.winUtils.OpenWinEvent(IRSDK_DATAVALIDEVENTNAME)
if err != nil { if err != nil {
return nil, err return err
} }
// We need to open the broadcast channel // We need to open the broadcast channel
err = ibt.winUtils.OpenBroadcastChannel(IRSDK_BROADCASTMSGNAME) err = i.winUtils.OpenBroadcastChannel(IRSDK_BROADCASTMSGNAME)
if err != nil { if err != nil {
return nil, err return err
}
case IBTFile:
i.File, err = os.Open(i.Opts.SourcePath)
if err != nil {
return fmt.Errorf("failed to open file `%s`: %+v", i.Opts.SourcePath, err)
}
default:
return fmt.Errorf("a source type must be specified")
}
return nil
}
func (i *IBT) openExporter() error {
var err error
switch i.Opts.IBTExportType {
case SharedMemoryFile:
// Lets create a shared memory file!
shm, err := sharedMem.Create(MEMMAPFILENAME, fileMapSize)
if err != nil {
return fmt.Errorf("unable to create memory map file: %+v", err)
}
i.IBTExporter = shm
case IBTFile:
i.IBTExporter, err = os.OpenFile(i.Opts.IBTExportPath, os.O_CREATE|os.O_RDWR, 0o644)
if err != nil {
return fmt.Errorf("failed to open ibt export file: %v", err)
}
}
return nil
}
// Init serves to initialize and get a hold of a IBT struct
// Receives an Options struct with the required configurations
func Init(opts Options) (*IBT, error) {
// log := logger.GetInstance()
// Create our irsdk instance
var err error
ibt := IBT{
Opts: opts,
Vars: &TelemetryVars{},
winUtils: nil,
}
// Setup the source
err = ibt.openSource()
if err != nil {
return nil, err
}
// Setup the IBT data export - can be either shared memory or data file
if opts.IBTExport {
err = ibt.openExporter()
if err != nil {
// We log this only, or return some type of message
// Set the option to false so we won't export
ibt.Opts.IBTExport = false
} }
} }
+3 -3
View File
@@ -30,7 +30,7 @@ func lapTimeRepresentation(t float32) string {
} }
func TestFunctionality(t *testing.T) { func TestFunctionality(t *testing.T) {
input, err := os.Open("../testTelemetry/supercars_race_watkins_glenn.ibt") input, err := os.Open("../../testTelemetry/supercars_race_watkins_glenn.ibt")
if err != nil { if err != nil {
t.Fatal("Was unable to prepare telemetry file for testing.") t.Fatal("Was unable to prepare telemetry file for testing.")
} }
@@ -62,7 +62,7 @@ func TestFunctionality(t *testing.T) {
// driversBehind := i.Vars.Vars["CarIdxF2Time"].Value.([]float32) // driversBehind := i.Vars.Vars["CarIdxF2Time"].Value.([]float32)
drivers := i.SessionInfo.DriverInfo.Drivers drivers := i.SessionInfo.DriverInfo.Drivers
myIdx := i.SessionInfo.DriverInfo.DriverCarIdx myIdx := i.SessionInfo.DriverInfo.DriverCarIdx
standings := make([]StandingsLine, len(drivers)) standings := make([]StandingsLine, len(drivers))
@@ -90,7 +90,7 @@ func TestFunctionality(t *testing.T) {
return standings[i].LapPct >= standings[j].LapPct return standings[i].LapPct >= standings[j].LapPct
}) })
fmt.Printf("%v\n", driversEstTime) fmt.Printf("%v\n", driversEstTime)
// for p, v := range standings { // for p, v := range standings {
// fmt.Printf("[%2d] %-30s %13f %13f\n", // fmt.Printf("[%2d] %-30s %13f %13f\n",
// p+1, v.DriverName, v.LapPct, driversEstTime[p] - driversEstTime[myIdx]) // p+1, v.DriverName, v.LapPct, driversEstTime[p] - driversEstTime[myIdx])
+4 -4
View File
@@ -1,13 +1,13 @@
package goirsdk package goirsdk
import ( import (
"github.com/ESilva15/goirsdk/logger"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
"strings" "strings"
"github.com/ESilva15/goirsdk/logger"
"golang.org/x/text/encoding/charmap" "golang.org/x/text/encoding/charmap"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -321,7 +321,7 @@ func (i *IBT) readSessionInfo() error {
} }
// Write to the output file // Write to the output file
if i.IBTExport != nil { if i.Opts.IBTExport {
err := i.exportIBT(sessionInfoStringRaw[:], int64(i.Headers.SessionInfoOffset)) err := i.exportIBT(sessionInfoStringRaw[:], int64(i.Headers.SessionInfoOffset))
if err != nil { if err != nil {
log.Printf("Failed to export offline telemetry data: %v", err) log.Printf("Failed to export offline telemetry data: %v", err)
@@ -334,7 +334,7 @@ func (i *IBT) readSessionInfo() error {
} }
// Write to YAML output file // Write to YAML output file
if i.YAMLExportPath != "" { if i.Opts.SessionInfoExport {
err := i.exportYAML() err := i.exportYAML()
if err != nil { if err != nil {
log.Printf("Failed to export YAML string: %v\n", err) log.Printf("Failed to export YAML string: %v\n", err)
+40 -17
View File
@@ -40,11 +40,13 @@ var (
} }
) )
type IRacingState int type (
type VarType struct { IRacingState int
Size int // Size is the var type size in bytes VarType struct {
Name string // Name is the irsdk var name Size int // Size is the var type size in bytes
} Name string // Name is the irsdk var name
}
)
type IBTVar struct { type IBTVar struct {
Type int32 Type int32
@@ -106,9 +108,10 @@ type varBuffer struct {
} }
type TelemetryVars struct { type TelemetryVars struct {
Tick int32 // Keeps track of the current data buffer tick Tick int32 // Keeps track of the current data buffer tick
RecorderTick int32 // Counts from 0 when creating a telemetry file from a replay or live data RecorderTick int32 // Counts from 0 when creating a telemetry file from a
Vars map[string]Var // Variables content // replay or live data
Vars map[string]Var // Variables content
} }
func (i *IBT) readVariablerHeaders() error { func (i *IBT) readVariablerHeaders() error {
@@ -123,7 +126,7 @@ func (i *IBT) readVariablerHeaders() error {
return err return err
} }
if i.IBTExport != nil { if i.Opts.IBTExport {
err = i.exportIBT(rbuf, int64(i.Headers.VarHeaderOffset+k*VarHeaderSize)) err = i.exportIBT(rbuf, int64(i.Headers.VarHeaderOffset+k*VarHeaderSize))
if err != nil { if err != nil {
// Don't outright kill it here - maybe nowhere else // Don't outright kill it here - maybe nowhere else
@@ -306,6 +309,7 @@ func (i *IBT) readData(buf []byte) error {
// Update will read the next data chunk from the telemetry data, works for both the // Update will read the next data chunk from the telemetry data, works for both the
// live and offline data // live and offline data
func (i *IBT) Update(timeout time.Duration) (IRacingState, error) { func (i *IBT) Update(timeout time.Duration) (IRacingState, error) {
// This is what happens if we are reading live data
if i.winUtils != nil { if i.winUtils != nil {
// Put a way to check if the sim is active here // Put a way to check if the sim is active here
// fmt.Println("NOT CHECKING IF SIM IS ACTIVE - ADD ME") // fmt.Println("NOT CHECKING IF SIM IS ACTIVE - ADD ME")
@@ -344,10 +348,19 @@ func (i *IBT) Update(timeout time.Duration) (IRacingState, error) {
return Failed, err return Failed, err
} }
if i.IBTExport != nil { if i.Opts.IBTExport {
err = i.exportIBT(buf, int64(i.Headers.BufOffset+i.Vars.RecorderTick*i.Headers.BufLen)) // Dirty attempt at getting this to work to write to a memory mapped file
if err != nil { switch i.Opts.IBTExportType {
log.Printf("Failed to export live telemetry data: %v", err) case IBTFile:
err = i.exportIBT(buf, int64(i.Headers.BufOffset+i.Vars.RecorderTick*i.Headers.BufLen))
if err != nil {
log.Printf("Failed to export live telemetry data: %v", err)
}
case SharedMemoryFile:
err = i.exportIBT(buf, int64(i.Headers.BufOffset))
if err != nil {
log.Printf("Failed to export live telemetry data: %v", err)
}
} }
} }
@@ -363,6 +376,7 @@ func (i *IBT) Update(timeout time.Duration) (IRacingState, error) {
// Document why this is here, I don't remember the exact words right now // Document why this is here, I don't remember the exact words right now
i.Vars.RecorderTick++ i.Vars.RecorderTick++
} else { } else {
// This is what happens if we are reading from an .ibt file
// This will get the dataframe corresponding to a given tick // This will get the dataframe corresponding to a given tick
start := i.Headers.BufOffset + i.Vars.Tick*i.Headers.BufLen start := i.Headers.BufOffset + i.Vars.Tick*i.Headers.BufLen
buf := make([]byte, i.Headers.BufLen) buf := make([]byte, i.Headers.BufLen)
@@ -370,10 +384,19 @@ func (i *IBT) Update(timeout time.Duration) (IRacingState, error) {
// Make this happen in a different thread, or have this send to a queue that has a thread // Make this happen in a different thread, or have this send to a queue that has a thread
// writing to a file // writing to a file
if i.IBTExport != nil { if i.Opts.IBTExport {
err = i.exportIBT(buf, int64(start)) // Dirty attempt at getting this to work to write to a memory mapped file
if err != nil { switch i.Opts.IBTExportType {
log.Printf("Failed to export offline telemetry data: %v", err) case IBTFile:
err = i.exportIBT(buf, int64(start))
if err != nil {
log.Printf("Failed to export offline telemetry data: %v", err)
}
case SharedMemoryFile:
err = i.exportIBT(buf, int64(i.Headers.BufOffset))
if err != nil {
log.Printf("Failed to export live telemetry data: %v", err)
}
} }
} }
+88 -25
View File
@@ -1,53 +1,116 @@
//go:build (linux && cgo) || (darwin && cgo) //go:build (linux && cgo) || (darwin && cgo)
// +build linux,cgo darwin,cgo
package winutils package winutils
import ( import (
"errors" "fmt"
"sync" "net"
"os"
"time" "time"
) )
const (
WAIT_OBJECT_0 = 0
WAIT_TIMEOUT = 258
)
var (
once sync.Once
ErrUnsupportedOS = errors.New("not found")
)
type utils struct { type utils struct {
socketPath string
listener *net.UnixConn
} }
// INITIALIZATION
func newUtils() (*utils, error) { func newUtils() (*utils, error) {
return nil, ErrUnsupportedOS return &utils{}, nil
} }
func (u *utils) Close() { func (u *utils) Close() {
if u.listener != nil {
u.listener.Close()
os.Remove(u.socketPath)
}
} }
// openEvent opens a windows.Handle for a given event // OpenEvent creates or connects to a Unix socket for event signaling on Linux
func (u *utils) OpenEvent(eventName string) error { func (u *utils) OpenEvent(eventName string) error {
return ErrUnsupportedOS 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
} }
// OpenBroadcastChannel opens up a broadcast channel to send commands to iracing
func (u *utils) OpenBroadcastChannel(name string) error { func (u *utils) OpenBroadcastChannel(name string) error {
return ErrUnsupportedOS // No-op or log stub on Linux
return nil
} }
// INITIALIZATION // CheckValidDataEvent waits for a pulse byte sent over the socket
// openEvent waits for a good response for some given time
func (u *utils) CheckValidDataEvent(timeout time.Duration) bool { func (u *utils) CheckValidDataEvent(timeout time.Duration) bool {
return false 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
} }
// SendBroadcastMessage sends a message trough the broadcast channel
func (u *utils) SendBroadcastMessage(id, p1, p2 uintptr) error { func (u *utils) SendBroadcastMessage(id, p1, p2 uintptr) error {
return ErrUnsupportedOS 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
// }
+6 -9
View File
@@ -1,5 +1,4 @@
//go:build windows && cgo // go:build windows
// +build windows,cgo
package winutils package winutils
@@ -16,13 +15,11 @@ const (
WAIT_TIMEOUT = 258 WAIT_TIMEOUT = 258
) )
var ( var once sync.Once
once sync.Once
)
type utils struct { type utils struct {
user32DLL *windows.LazyDLL user32DLL *windows.LazyDLL
wEvent *windows.Handle wEvent windows.Handle
wBroadcastChn uintptr wBroadcastChn uintptr
} }
@@ -34,7 +31,7 @@ func newUtils() (*utils, error) {
} }
func (u *utils) Close() { func (u *utils) Close() {
closeEvent(u.wEvent) closeEvent(&u.wEvent)
// Do we need to unload the user32DLL ??? // Do we need to unload the user32DLL ???
// Do we need to close the broadcast channel ??? // Do we need to close the broadcast channel ???
} }
@@ -50,7 +47,7 @@ func (u *utils) OpenEvent(eventName string) error {
if err != nil { if err != nil {
return err return err
} }
u.wEvent = &event u.wEvent = event
return nil return nil
} }
@@ -90,7 +87,7 @@ func (u *utils) CheckValidDataEvent(timeout time.Duration) bool {
t0 := time.Now().UnixNano() t0 := time.Now().UnixNano()
timeoutInt := uint32(timeout / time.Millisecond) timeoutInt := uint32(timeout / time.Millisecond)
result, err := windows.WaitForSingleObject(*u.wEvent, timeoutInt) result, err := windows.WaitForSingleObject(u.wEvent, timeoutInt)
if err != nil { if err != nil {
remaining := timeoutInt - uint32((time.Now().UnixNano()-t0)/1000000) remaining := timeoutInt - uint32((time.Now().UnixNano()-t0)/1000000)
if remaining > 0 { if remaining > 0 {