From c983c32649f9f41e4685f25529f5c1cc08696607 Mon Sep 17 00:00:00 2001 From: Zhang Xiang Date: Thu, 17 Feb 2022 17:29:15 +0800 Subject: [PATCH 01/93] increase player finding range from 50 to 100 --- sharedMemoryAPI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index fe421bd..ae9d686 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -103,7 +103,7 @@ def __find_rf2_pid(self): def __playersDriverNum(self): """ Find the player's driver number """ - for _player in range(50): # self.Rf2Tele.mVehicles[0].mNumVehicles: + for _player in range(100): # self.Rf2Tele.mVehicles[0].mNumVehicles: if self.Rf2Scor.mVehicles[_player].mIsPlayer: break return _player From ae7354b4754c605965a468dee4fa898d6152867b Mon Sep 17 00:00:00 2001 From: Zhang Xiang Date: Thu, 17 Feb 2022 18:04:48 +0800 Subject: [PATCH 02/93] add force feedback mmap accessing --- rF2data.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rF2data.py b/rF2data.py index 435bb01..1e76144 100644 --- a/rF2data.py +++ b/rF2data.py @@ -700,6 +700,8 @@ def __init__(self): self.Rf2Scor = rF2Scoring.from_buffer(self._rf2_scor) self._rf2_ext = mmap.mmap(0, ctypes.sizeof(rF2Extended), "$rFactor2SMMP_Extended$") self.Rf2Ext = rF2Extended.from_buffer(self._rf2_ext) + self._rf2_ffb = mmap.mmap(0, ctypes.sizeof(rF2ForceFeedback), "$rFactor2SMMP_ForceFeedback$") + self.Rf2Ffb = rF2ForceFeedback.from_buffer(self._rf2_ffb) def close(self): # This didn't help with the errors @@ -707,6 +709,7 @@ def close(self): self._rf2_tele.close() self._rf2_scor.close() self._rf2_ext.close() + self._rf2_ffb.close() except BufferError: # "cannot close exported pointers exist" pass From f227afab8016b6f04b84f06ce0719750beb96480 Mon Sep 17 00:00:00 2001 From: Zhang Xiang Date: Fri, 18 Feb 2022 13:07:33 +0800 Subject: [PATCH 03/93] only update _player value if player number has changed --- sharedMemoryAPI.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index ae9d686..e6a35b1 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -30,6 +30,7 @@ def __init__(self): rF2data.SimInfo.__init__(self) self.versionCheckMsg = self.versionCheck() self.__find_rf2_pid() + self.last_found_player_number = 0 def versionCheck(self): """ @@ -103,9 +104,13 @@ def __find_rf2_pid(self): def __playersDriverNum(self): """ Find the player's driver number """ - for _player in range(100): # self.Rf2Tele.mVehicles[0].mNumVehicles: - if self.Rf2Scor.mVehicles[_player].mIsPlayer: - break + if self.Rf2Scor.mVehicles[self.last_found_player_number].mIsPlayer != 1: # only update if player number has changed + for _player in range(100): # self.Rf2Tele.mVehicles[0].mNumVehicles: + if self.Rf2Scor.mVehicles[_player].mIsPlayer: + self.last_found_player_number = _player + break + else: + _player = self.last_found_player_number return _player ########################################################### From 189b2ea06be52a71956e3066cd2c03be34bed2f7 Mon Sep 17 00:00:00 2001 From: Zhang Xiang Date: Fri, 18 Feb 2022 13:14:05 +0800 Subject: [PATCH 04/93] only update _player value if player number has changed --- sharedMemoryAPI.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index e6a35b1..b01e9af 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -104,13 +104,13 @@ def __find_rf2_pid(self): def __playersDriverNum(self): """ Find the player's driver number """ - if self.Rf2Scor.mVehicles[self.last_found_player_number].mIsPlayer != 1: # only update if player number has changed + if self.Rf2Scor.mVehicles[self.last_found_player_number].mIsPlayer == 1: + _player = self.last_found_player_number + else: # only update if player number has changed for _player in range(100): # self.Rf2Tele.mVehicles[0].mNumVehicles: if self.Rf2Scor.mVehicles[_player].mIsPlayer: self.last_found_player_number = _player break - else: - _player = self.last_found_player_number return _player ########################################################### From 7832c38c89502d857b8894bdb21d7b6b6120505f Mon Sep 17 00:00:00 2001 From: Zhang Xiang Date: Mon, 21 Feb 2022 15:33:55 +0800 Subject: [PATCH 05/93] removed == --- sharedMemoryAPI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index b01e9af..b0c6393 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -104,7 +104,7 @@ def __find_rf2_pid(self): def __playersDriverNum(self): """ Find the player's driver number """ - if self.Rf2Scor.mVehicles[self.last_found_player_number].mIsPlayer == 1: + if self.Rf2Scor.mVehicles[self.last_found_player_number].mIsPlayer: _player = self.last_found_player_number else: # only update if player number has changed for _player in range(100): # self.Rf2Tele.mVehicles[0].mNumVehicles: From 85058177a619c7cbc4c9ed43f20c0fdb6578e606 Mon Sep 17 00:00:00 2001 From: Zhang Xiang Date: Sat, 5 Mar 2022 11:00:54 +0800 Subject: [PATCH 06/93] change playersDriverNum to non-private method so it can be called for checking and using player index --- sharedMemoryAPI.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index b0c6393..a1b5f6a 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -102,7 +102,7 @@ def __find_rf2_pid(self): self.rf2_pid = pid break - def __playersDriverNum(self): + def playersDriverNum(self): """ Find the player's driver number """ if self.Rf2Scor.mVehicles[self.last_found_player_number].mIsPlayer: _player = self.last_found_player_number @@ -173,7 +173,7 @@ def isAiDriving(self): """ True: rF2 is running and the player is on track """ - return self.Rf2Scor.mVehicles[self.__playersDriverNum()].mControl == 1 + return self.Rf2Scor.mVehicles[self.playersDriverNum()].mControl == 1 # who's in control: -1=nobody (shouldn't get this), 0=local player, # 1=local AI, 2=remote, 3=replay (shouldn't get this) @@ -184,24 +184,24 @@ def driverName(self): Get the player's name """ return Cbytestring2Python( - self.Rf2Scor.mVehicles[self.__playersDriverNum()].mDriverName) + self.Rf2Scor.mVehicles[self.playersDriverNum()].mDriverName) def playersVehicleTelemetry(self): """ Get the variable for the player's vehicle """ - self.__playersDriverNum() - return self.Rf2Tele.mVehicles[self.__playersDriverNum()] + self.playersDriverNum() + return self.Rf2Tele.mVehicles[self.playersDriverNum()] def playersVehicleScoring(self): """ Get the variable for the player's vehicle """ - self.__playersDriverNum() - return self.Rf2Scor.mVehicles[self.__playersDriverNum()] + self.playersDriverNum() + return self.Rf2Scor.mVehicles[self.playersDriverNum()] def vehicleName(self): """ Get the vehicle's name """ return Cbytestring2Python( - self.Rf2Scor.mVehicles[self.__playersDriverNum()].mVehicleName) + self.Rf2Scor.mVehicles[self.playersDriverNum()].mVehicleName) def close(self): # This didn't help with the errors From ad691d0cfc42a7f8b96e04ef75af8b9e2927b523 Mon Sep 17 00:00:00 2001 From: Xiang Date: Tue, 3 May 2022 13:25:05 +0800 Subject: [PATCH 07/93] Removed last_found_player_number variable & duplicated calls to playersDriverNum() method --- sharedMemoryAPI.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index a1b5f6a..b025d9b 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -30,7 +30,6 @@ def __init__(self): rF2data.SimInfo.__init__(self) self.versionCheckMsg = self.versionCheck() self.__find_rf2_pid() - self.last_found_player_number = 0 def versionCheck(self): """ @@ -104,13 +103,9 @@ def __find_rf2_pid(self): def playersDriverNum(self): """ Find the player's driver number """ - if self.Rf2Scor.mVehicles[self.last_found_player_number].mIsPlayer: - _player = self.last_found_player_number - else: # only update if player number has changed - for _player in range(100): # self.Rf2Tele.mVehicles[0].mNumVehicles: - if self.Rf2Scor.mVehicles[_player].mIsPlayer: - self.last_found_player_number = _player - break + for _player in range(100): # self.Rf2Tele.mVehicles[0].mNumVehicles: + if self.Rf2Scor.mVehicles[_player].mIsPlayer: + break return _player ########################################################### @@ -188,12 +183,12 @@ def driverName(self): def playersVehicleTelemetry(self): """ Get the variable for the player's vehicle """ - self.playersDriverNum() + #self.playersDriverNum() return self.Rf2Tele.mVehicles[self.playersDriverNum()] def playersVehicleScoring(self): """ Get the variable for the player's vehicle """ - self.playersDriverNum() + #self.playersDriverNum() return self.Rf2Scor.mVehicles[self.playersDriverNum()] def vehicleName(self): From 74c4f6473b488da475120350203127c84d993ae7 Mon Sep 17 00:00:00 2001 From: Xiang Date: Tue, 3 May 2022 13:36:10 +0800 Subject: [PATCH 08/93] Removed last_found_player_number variable & duplicated calls to playersDriverNum() method --- sharedMemoryAPI.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index a1b5f6a..b025d9b 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -30,7 +30,6 @@ def __init__(self): rF2data.SimInfo.__init__(self) self.versionCheckMsg = self.versionCheck() self.__find_rf2_pid() - self.last_found_player_number = 0 def versionCheck(self): """ @@ -104,13 +103,9 @@ def __find_rf2_pid(self): def playersDriverNum(self): """ Find the player's driver number """ - if self.Rf2Scor.mVehicles[self.last_found_player_number].mIsPlayer: - _player = self.last_found_player_number - else: # only update if player number has changed - for _player in range(100): # self.Rf2Tele.mVehicles[0].mNumVehicles: - if self.Rf2Scor.mVehicles[_player].mIsPlayer: - self.last_found_player_number = _player - break + for _player in range(100): # self.Rf2Tele.mVehicles[0].mNumVehicles: + if self.Rf2Scor.mVehicles[_player].mIsPlayer: + break return _player ########################################################### @@ -188,12 +183,12 @@ def driverName(self): def playersVehicleTelemetry(self): """ Get the variable for the player's vehicle """ - self.playersDriverNum() + #self.playersDriverNum() return self.Rf2Tele.mVehicles[self.playersDriverNum()] def playersVehicleScoring(self): """ Get the variable for the player's vehicle """ - self.playersDriverNum() + #self.playersDriverNum() return self.Rf2Scor.mVehicles[self.playersDriverNum()] def vehicleName(self): From b6aa69d9584f0630cef6ee5c0a180cb0da9ac8d0 Mon Sep 17 00:00:00 2001 From: Xiang Date: Tue, 3 May 2022 16:15:29 +0800 Subject: [PATCH 09/93] Updating player index number in a separated thread at 10ms refresh rate --- sharedMemoryAPI.py | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index b025d9b..b4e1b7d 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -3,7 +3,8 @@ and add access functions to it. """ # pylint: disable=invalid-name - +import time +import threading import psutil try: @@ -30,6 +31,7 @@ def __init__(self): rF2data.SimInfo.__init__(self) self.versionCheckMsg = self.versionCheck() self.__find_rf2_pid() + self.players_index = 99 def versionCheck(self): """ @@ -101,7 +103,25 @@ def __find_rf2_pid(self): self.rf2_pid = pid break - def playersDriverNum(self): + def __playerIndexUpdate(self): + """ Find & update index number """ + while True: + for index in range(100): + if self.Rf2Scor.mVehicles[index].mIsPlayer: + self.players_index = index + break + time.sleep(0.01) + + def startUpdating(self): + """ Start player index update thread """ + index_thread = threading.Thread(target=self.__playerIndexUpdate) + index_thread.setDaemon(True) + index_thread.start() + + ########################################################### + # This function is no longer needed + + def __playersDriverNum(self): """ Find the player's driver number """ for _player in range(100): # self.Rf2Tele.mVehicles[0].mNumVehicles: if self.Rf2Scor.mVehicles[_player].mIsPlayer: @@ -168,7 +188,7 @@ def isAiDriving(self): """ True: rF2 is running and the player is on track """ - return self.Rf2Scor.mVehicles[self.playersDriverNum()].mControl == 1 + return self.Rf2Scor.mVehicles[self.players_index].mControl == 1 # who's in control: -1=nobody (shouldn't get this), 0=local player, # 1=local AI, 2=remote, 3=replay (shouldn't get this) @@ -179,24 +199,24 @@ def driverName(self): Get the player's name """ return Cbytestring2Python( - self.Rf2Scor.mVehicles[self.playersDriverNum()].mDriverName) + self.Rf2Scor.mVehicles[self.players_index].mDriverName) def playersVehicleTelemetry(self): """ Get the variable for the player's vehicle """ - #self.playersDriverNum() - return self.Rf2Tele.mVehicles[self.playersDriverNum()] + #self.players_index + return self.Rf2Tele.mVehicles[self.players_index] def playersVehicleScoring(self): """ Get the variable for the player's vehicle """ - #self.playersDriverNum() - return self.Rf2Scor.mVehicles[self.playersDriverNum()] + #self.players_index + return self.Rf2Scor.mVehicles[self.players_index] def vehicleName(self): """ Get the vehicle's name """ return Cbytestring2Python( - self.Rf2Scor.mVehicles[self.playersDriverNum()].mVehicleName) + self.Rf2Scor.mVehicles[self.players_index].mVehicleName) def close(self): # This didn't help with the errors From ea52707823444b827e2da022b959c05c1be3461b Mon Sep 17 00:00:00 2001 From: Xiang Date: Tue, 3 May 2022 16:39:44 +0800 Subject: [PATCH 10/93] add mInRealtimeFC for index update --- sharedMemoryAPI.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index b4e1b7d..19c47b2 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -106,10 +106,11 @@ def __find_rf2_pid(self): def __playerIndexUpdate(self): """ Find & update index number """ while True: - for index in range(100): - if self.Rf2Scor.mVehicles[index].mIsPlayer: - self.players_index = index - break + if self.Rf2Ext.mInRealtimeFC: + for index in range(100): + if self.Rf2Scor.mVehicles[index].mIsPlayer: + self.players_index = index + break time.sleep(0.01) def startUpdating(self): From d4fd509185d29fd9e6b51e218255323891a8abcc Mon Sep 17 00:00:00 2001 From: Xiang Date: Mon, 16 May 2022 15:40:08 +0800 Subject: [PATCH 11/93] removed mInRealtimeFC check for player index --- sharedMemoryAPI.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index 19c47b2..85a3c02 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -106,11 +106,11 @@ def __find_rf2_pid(self): def __playerIndexUpdate(self): """ Find & update index number """ while True: - if self.Rf2Ext.mInRealtimeFC: - for index in range(100): - if self.Rf2Scor.mVehicles[index].mIsPlayer: - self.players_index = index - break + #if self.Rf2Ext.mInRealtimeFC: + for _index in range(100): + if self.Rf2Scor.mVehicles[_index].mIsPlayer: + self.players_index = _index + break time.sleep(0.01) def startUpdating(self): @@ -204,12 +204,10 @@ def driverName(self): def playersVehicleTelemetry(self): """ Get the variable for the player's vehicle """ - #self.players_index return self.Rf2Tele.mVehicles[self.players_index] def playersVehicleScoring(self): """ Get the variable for the player's vehicle """ - #self.players_index return self.Rf2Scor.mVehicles[self.players_index] def vehicleName(self): From b20a5e0c85afcf6ef9ddac4699bd2ef97ee2c590 Mon Sep 17 00:00:00 2001 From: Xiang Date: Fri, 30 Sep 2022 17:57:25 +0800 Subject: [PATCH 12/93] Added numeric validation to eliminate data reading errors --- sharedMemoryAPI.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index 85a3c02..87a4c47 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -5,6 +5,7 @@ # pylint: disable=invalid-name import time import threading +import math import psutil try: @@ -103,12 +104,21 @@ def __find_rf2_pid(self): self.rf2_pid = pid break + def in2zero(self, value1): + """Convert none & inf & nan to zero""" + if type(value1) == int or type(value1) == float: + if math.isnan(value1) or math.isinf(value1): # bypass nan & inf + value1 = 0 + else: + value1 = 0 + return value1 + def __playerIndexUpdate(self): """ Find & update index number """ while True: #if self.Rf2Ext.mInRealtimeFC: for _index in range(100): - if self.Rf2Scor.mVehicles[_index].mIsPlayer: + if self.in2zero(self.Rf2Scor.mVehicles[_index].mIsPlayer): self.players_index = _index break time.sleep(0.01) From d071fab55870851c785f28fed2b0ef606de415e2 Mon Sep 17 00:00:00 2001 From: Xiang Date: Tue, 1 Nov 2022 09:53:15 +0800 Subject: [PATCH 13/93] add rf2 PID argument for SimInfo for reading data from dedicated server --- rF2data.py | 8 ++++---- sharedMemoryAPI.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rF2data.py b/rF2data.py index 1e76144..a7fd19e 100644 --- a/rF2data.py +++ b/rF2data.py @@ -691,14 +691,14 @@ class SubscribedBuffer(Enum): All = 255 class SimInfo: - def __init__(self): + def __init__(self, input_pid): - self._rf2_tele = mmap.mmap(0, ctypes.sizeof(rF2Telemetry), "$rFactor2SMMP_Telemetry$") + self._rf2_tele = mmap.mmap(0, ctypes.sizeof(rF2Telemetry), f"$rFactor2SMMP_Telemetry${input_pid}") self.Rf2Tele = rF2Telemetry.from_buffer(self._rf2_tele) - self._rf2_scor = mmap.mmap(0, ctypes.sizeof(rF2Scoring), "$rFactor2SMMP_Scoring$") + self._rf2_scor = mmap.mmap(0, ctypes.sizeof(rF2Scoring), f"$rFactor2SMMP_Scoring${input_pid}") self.Rf2Scor = rF2Scoring.from_buffer(self._rf2_scor) - self._rf2_ext = mmap.mmap(0, ctypes.sizeof(rF2Extended), "$rFactor2SMMP_Extended$") + self._rf2_ext = mmap.mmap(0, ctypes.sizeof(rF2Extended), f"$rFactor2SMMP_Extended${input_pid}") self.Rf2Ext = rF2Extended.from_buffer(self._rf2_ext) self._rf2_ffb = mmap.mmap(0, ctypes.sizeof(rF2ForceFeedback), "$rFactor2SMMP_ForceFeedback$") self.Rf2Ffb = rF2ForceFeedback.from_buffer(self._rf2_ffb) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index 87a4c47..519e5d5 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -28,8 +28,8 @@ class SimInfoAPI(rF2data.SimInfo): rf2_pid_counter = 0 # Counter to check if running rf2_running = False - def __init__(self): - rF2data.SimInfo.__init__(self) + def __init__(self, input_pid): + rF2data.SimInfo.__init__(self, input_pid) self.versionCheckMsg = self.versionCheck() self.__find_rf2_pid() self.players_index = 99 From f339e960640fe7b572183f99a50ce0ff95a97348 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sat, 19 Nov 2022 18:26:32 +0800 Subject: [PATCH 14/93] properly close mapping before exiting & additional condition check --- sharedMemoryAPI.py | 59 +++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index 519e5d5..cfe8b30 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -33,6 +33,10 @@ def __init__(self, input_pid): self.versionCheckMsg = self.versionCheck() self.__find_rf2_pid() self.players_index = 99 + self.idx_checking = False + self.total_vehicles = 1 + self.timer = 0 + print("sharedmemory mapping started") def versionCheck(self): """ @@ -104,40 +108,35 @@ def __find_rf2_pid(self): self.rf2_pid = pid break - def in2zero(self, value1): - """Convert none & inf & nan to zero""" - if type(value1) == int or type(value1) == float: - if math.isnan(value1) or math.isinf(value1): # bypass nan & inf - value1 = 0 - else: - value1 = 0 - return value1 + def playerIndexCheck(self): + """ Check player index number (max 128 players) """ + for _player in range(127): + if self.Rf2Scor.mVehicles[_player].mIsPlayer == 1: + break + return _player def __playerIndexUpdate(self): - """ Find & update index number """ + """ Update index number """ while True: - #if self.Rf2Ext.mInRealtimeFC: - for _index in range(100): - if self.in2zero(self.Rf2Scor.mVehicles[_index].mIsPlayer): - self.players_index = _index - break + if not self.idx_checking: + print("player index updating closed") + break + + self.timer += 0.01 + if self.timer > 0.3: + self.total_vehicles = self.Rf2Tele.mNumVehicles + self.timer = 0 + + self.players_index = self.playerIndexCheck() time.sleep(0.01) def startUpdating(self): """ Start player index update thread """ + self.idx_checking = True index_thread = threading.Thread(target=self.__playerIndexUpdate) index_thread.setDaemon(True) index_thread.start() - - ########################################################### - # This function is no longer needed - - def __playersDriverNum(self): - """ Find the player's driver number """ - for _player in range(100): # self.Rf2Tele.mVehicles[0].mNumVehicles: - if self.Rf2Scor.mVehicles[_player].mIsPlayer: - break - return _player + print("player index updating started") ########################################################### # Access functions @@ -228,12 +227,24 @@ def vehicleName(self): self.Rf2Scor.mVehicles[self.players_index].mVehicleName) def close(self): + # Stop index checking thread + self.idx_checking = False + time.sleep(0.2) # This didn't help with the errors try: + # Delete those objects first + del self.Rf2Tele + del self.Rf2Scor + del self.Rf2Ext + del self.Rf2Ffb + # Close shared memory mapping self._rf2_tele.close() self._rf2_scor.close() self._rf2_ext.close() + self._rf2_ffb.close() + print("sharedmemory mapping closed") except BufferError: # "cannot close exported pointers exist" + print("BufferError") pass def __del__(self): From 63f7288cf43290887a5ea20592c8b0dacd5569b8 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sun, 20 Nov 2022 20:39:34 +0800 Subject: [PATCH 15/93] Fixed player index checking method, deepcopy Telemetry & Scoring info and correctly synced with local player --- sharedMemoryAPI.py | 68 +++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index cfe8b30..7dfd628 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -6,6 +6,7 @@ import time import threading import math +import copy import psutil try: @@ -33,9 +34,9 @@ def __init__(self, input_pid): self.versionCheckMsg = self.versionCheck() self.__find_rf2_pid() self.players_index = 99 - self.idx_checking = False - self.total_vehicles = 1 - self.timer = 0 + self.LastTele = copy.deepcopy(self.Rf2Tele) + self.LastScor = copy.deepcopy(self.Rf2Scor) + self.data_updating = False print("sharedmemory mapping started") def versionCheck(self): @@ -108,35 +109,44 @@ def __find_rf2_pid(self): self.rf2_pid = pid break - def playerIndexCheck(self): + @staticmethod + def playerIndexCheck(value): """ Check player index number (max 128 players) """ for _player in range(127): - if self.Rf2Scor.mVehicles[_player].mIsPlayer == 1: + if value.mVehicles[_player].mIsPlayer == 1: break return _player - def __playerIndexUpdate(self): - """ Update index number """ - while True: - if not self.idx_checking: - print("player index updating closed") - break + @staticmethod + def data_verified(value): + """ Verify data """ + return value.mVersionUpdateEnd == value.mVersionUpdateBegin - self.timer += 0.01 - if self.timer > 0.3: - self.total_vehicles = self.Rf2Tele.mNumVehicles - self.timer = 0 + def __infoUpdate(self): + """ Update index number """ + while self.data_updating: + data_scor = copy.deepcopy(self.Rf2Scor) + if self.data_verified(data_scor): + self.players_index = self.playerIndexCheck(data_scor) + players_mid = data_scor.mVehicles[self.players_index].mID + self.LastScor = data_scor + + data_tele = copy.deepcopy(self.Rf2Tele) + if self.data_verified(data_tele): + if data_tele.mVehicles[self.players_index].mID == players_mid: + self.LastTele = data_tele - self.players_index = self.playerIndexCheck() time.sleep(0.01) + else: + print("sharedmemory updating stopped") def startUpdating(self): """ Start player index update thread """ - self.idx_checking = True - index_thread = threading.Thread(target=self.__playerIndexUpdate) + self.data_updating = True + index_thread = threading.Thread(target=self.__infoUpdate) index_thread.setDaemon(True) index_thread.start() - print("player index updating started") + print("sharedmemory updating started") ########################################################### # Access functions @@ -213,11 +223,11 @@ def driverName(self): def playersVehicleTelemetry(self): """ Get the variable for the player's vehicle """ - return self.Rf2Tele.mVehicles[self.players_index] + return self.LastTele.mVehicles[self.players_index] def playersVehicleScoring(self): """ Get the variable for the player's vehicle """ - return self.Rf2Scor.mVehicles[self.players_index] + return self.LastScor.mVehicles[self.players_index] def vehicleName(self): """ @@ -226,17 +236,19 @@ def vehicleName(self): return Cbytestring2Python( self.Rf2Scor.mVehicles[self.players_index].mVehicleName) - def close(self): + def closeSimInfo(self): # Stop index checking thread - self.idx_checking = False + self.data_updating = False time.sleep(0.2) # This didn't help with the errors try: - # Delete those objects first - del self.Rf2Tele - del self.Rf2Scor - del self.Rf2Ext - del self.Rf2Ffb + # Unassign those objects first + self._last_vehicle_telemetry = None + self._last_vehicle_scoring = None + self.Rf2Tele = None + self.Rf2Scor = None + self.Rf2Ext = None + self.Rf2Ffb = None # Close shared memory mapping self._rf2_tele.close() self._rf2_scor.close() From 9e85564b1f7d3fc7bc447eec5c40062ffdac5b06 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sun, 20 Nov 2022 22:05:36 +0800 Subject: [PATCH 16/93] Fixed players_mid variable that could be referenced before assign. --- sharedMemoryAPI.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index 7dfd628..87f77c0 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -34,6 +34,7 @@ def __init__(self, input_pid): self.versionCheckMsg = self.versionCheck() self.__find_rf2_pid() self.players_index = 99 + self.players_mid = 0 self.LastTele = copy.deepcopy(self.Rf2Tele) self.LastScor = copy.deepcopy(self.Rf2Scor) self.data_updating = False @@ -128,12 +129,12 @@ def __infoUpdate(self): data_scor = copy.deepcopy(self.Rf2Scor) if self.data_verified(data_scor): self.players_index = self.playerIndexCheck(data_scor) - players_mid = data_scor.mVehicles[self.players_index].mID + self.players_mid = data_scor.mVehicles[self.players_index].mID self.LastScor = data_scor data_tele = copy.deepcopy(self.Rf2Tele) if self.data_verified(data_tele): - if data_tele.mVehicles[self.players_index].mID == players_mid: + if data_tele.mVehicles[self.players_index].mID == self.players_mid: self.LastTele = data_tele time.sleep(0.01) From 47d02ab24ed120840aa3185ae1b15d3908207956 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sun, 20 Nov 2022 23:15:41 +0800 Subject: [PATCH 17/93] removed 2 unused variables --- sharedMemoryAPI.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index 87f77c0..2746389 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -244,8 +244,6 @@ def closeSimInfo(self): # This didn't help with the errors try: # Unassign those objects first - self._last_vehicle_telemetry = None - self._last_vehicle_scoring = None self.Rf2Tele = None self.Rf2Scor = None self.Rf2Ext = None From 8c6b345cd16eefa90d1c4fb1f6b31a4726e440f5 Mon Sep 17 00:00:00 2001 From: Xiang Date: Tue, 22 Nov 2022 11:29:07 +0800 Subject: [PATCH 18/93] update comments --- sharedMemoryAPI.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index 2746389..6242d4c 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -124,7 +124,7 @@ def data_verified(value): return value.mVersionUpdateEnd == value.mVersionUpdateBegin def __infoUpdate(self): - """ Update index number """ + """ Update shared memory data """ while self.data_updating: data_scor = copy.deepcopy(self.Rf2Scor) if self.data_verified(data_scor): @@ -142,7 +142,7 @@ def __infoUpdate(self): print("sharedmemory updating stopped") def startUpdating(self): - """ Start player index update thread """ + """ Start data updating thread """ self.data_updating = True index_thread = threading.Thread(target=self.__infoUpdate) index_thread.setDaemon(True) @@ -238,7 +238,7 @@ def vehicleName(self): self.Rf2Scor.mVehicles[self.players_index].mVehicleName) def closeSimInfo(self): - # Stop index checking thread + # Stop data updating thread self.data_updating = False time.sleep(0.2) # This didn't help with the errors From c7b8ae1fe9ccbc27d901f9a8a60c74dc54c13ebd Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 23 Nov 2022 12:34:58 +0800 Subject: [PATCH 19/93] update comments, removed unused import --- sharedMemoryAPI.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index 6242d4c..499e11e 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -5,7 +5,6 @@ # pylint: disable=invalid-name import time import threading -import math import copy import psutil @@ -111,31 +110,32 @@ def __find_rf2_pid(self): break @staticmethod - def playerIndexCheck(value): - """ Check player index number (max 128 players) """ - for _player in range(127): - if value.mVehicles[_player].mIsPlayer == 1: + def playerIndexCheck(input_data): + """ Check player index number on one same data piece """ + for _player in range(127): # max 128 players supported by API + if input_data.mVehicles[_player].mIsPlayer == 1: # use 1 to avoid chance of reading inf or NaN break return _player @staticmethod - def data_verified(value): + def data_verified(input_data): """ Verify data """ - return value.mVersionUpdateEnd == value.mVersionUpdateBegin + return input_data.mVersionUpdateEnd == input_data.mVersionUpdateBegin def __infoUpdate(self): """ Update shared memory data """ while self.data_updating: - data_scor = copy.deepcopy(self.Rf2Scor) + data_scor = copy.deepcopy(self.Rf2Scor) # use deepcopy to avoid data interruption if self.data_verified(data_scor): - self.players_index = self.playerIndexCheck(data_scor) - self.players_mid = data_scor.mVehicles[self.players_index].mID - self.LastScor = data_scor + self.players_index = self.playerIndexCheck(data_scor) # update player index + self.players_mid = data_scor.mVehicles[self.players_index].mID # update player mID + self.LastScor = data_scor # update scoring data data_tele = copy.deepcopy(self.Rf2Tele) if self.data_verified(data_tele): + # Compare player mID & sync data if data_tele.mVehicles[self.players_index].mID == self.players_mid: - self.LastTele = data_tele + self.LastTele = data_tele # update synced telemetry data time.sleep(0.01) else: From 01df5945306ee0331ebae3b3f8d95e8378101365 Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 24 Nov 2022 21:43:08 +0800 Subject: [PATCH 20/93] Improved synced methods, separated none-synced methods from synced methods to be compatible with old functions --- sharedMemoryAPI.py | 78 ++++++++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index 499e11e..016fae3 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -33,7 +33,7 @@ def __init__(self, input_pid): self.versionCheckMsg = self.versionCheck() self.__find_rf2_pid() self.players_index = 99 - self.players_mid = 0 + self.players_status = 0 self.LastTele = copy.deepcopy(self.Rf2Tele) self.LastScor = copy.deepcopy(self.Rf2Scor) self.data_updating = False @@ -109,37 +109,53 @@ def __find_rf2_pid(self): self.rf2_pid = pid break - @staticmethod - def playerIndexCheck(input_data): + def __playersDriverNum(self): + """ Find the player's driver number """ + index_list = copy.deepcopy(self.Rf2Scor.mVehicles) + for _player in range(127): + if index_list[_player].mIsPlayer == 1: + break + return _player + + ########################################################### + # Sync data for local player + + def __playerVerified(self, input_data): """ Check player index number on one same data piece """ + found = False # return false if failed to find player index for _player in range(127): # max 128 players supported by API - if input_data.mVehicles[_player].mIsPlayer == 1: # use 1 to avoid chance of reading inf or NaN + if input_data.mVehicles[_player].mIsPlayer == 1: # use 1 to avoid reading incorrect value + self.players_index = _player + found = True break - return _player + return found @staticmethod - def data_verified(input_data): + def dataVerified(input_data): """ Verify data """ return input_data.mVersionUpdateEnd == input_data.mVersionUpdateBegin def __infoUpdate(self): - """ Update shared memory data """ + """ Update synced player data """ + players_mid = 0 + while self.data_updating: data_scor = copy.deepcopy(self.Rf2Scor) # use deepcopy to avoid data interruption - if self.data_verified(data_scor): - self.players_index = self.playerIndexCheck(data_scor) # update player index - self.players_mid = data_scor.mVehicles[self.players_index].mID # update player mID - self.LastScor = data_scor # update scoring data - data_tele = copy.deepcopy(self.Rf2Tele) - if self.data_verified(data_tele): - # Compare player mID & sync data - if data_tele.mVehicles[self.players_index].mID == self.players_mid: - self.LastTele = data_tele # update synced telemetry data + + # Only update if data verified and player index found + if self.dataVerified(data_scor) and self.__playerVerified(data_scor): + self.LastScor = copy.deepcopy(data_scor) # use deepcopy to update scoring data + players_mid = self.LastScor.mVehicles[self.players_index].mID # update player mID + + # Only update if data verified and player mID matches + if self.dataVerified(data_tele) and data_tele.mVehicles[self.players_index].mID == players_mid: + self.LastTele = copy.deepcopy(data_tele) # use deepcopy to update synced telemetry data + self.players_status = self.LastTele.mVehicles[self.players_index].mIgnitionStarter # update player status time.sleep(0.01) else: - print("sharedmemory updating stopped") + print("sharedmemory synced player data updating thread stopped") def startUpdating(self): """ Start data updating thread """ @@ -147,7 +163,20 @@ def startUpdating(self): index_thread = threading.Thread(target=self.__infoUpdate) index_thread.setDaemon(True) index_thread.start() - print("sharedmemory updating started") + print("sharedmemory synced player data updating thread started") + + def stopUpdating(self): + """ Stop data updating thread """ + self.data_updating = False + time.sleep(0.2) + + def syncedVehicleTelemetry(self): + """ Get the variable for the player's vehicle """ + return self.LastTele.mVehicles[self.players_index] + + def syncedVehicleScoring(self): + """ Get the variable for the player's vehicle """ + return self.LastScor.mVehicles[self.players_index] ########################################################### # Access functions @@ -209,7 +238,7 @@ def isAiDriving(self): """ True: rF2 is running and the player is on track """ - return self.Rf2Scor.mVehicles[self.players_index].mControl == 1 + return self.Rf2Scor.mVehicles[self.__playersDriverNum()].mControl == 1 # who's in control: -1=nobody (shouldn't get this), 0=local player, # 1=local AI, 2=remote, 3=replay (shouldn't get this) @@ -220,27 +249,24 @@ def driverName(self): Get the player's name """ return Cbytestring2Python( - self.Rf2Scor.mVehicles[self.players_index].mDriverName) + self.Rf2Scor.mVehicles[self.__playersDriverNum()].mDriverName) def playersVehicleTelemetry(self): """ Get the variable for the player's vehicle """ - return self.LastTele.mVehicles[self.players_index] + return self.Rf2Tele.mVehicles[self.__playersDriverNum()] def playersVehicleScoring(self): """ Get the variable for the player's vehicle """ - return self.LastScor.mVehicles[self.players_index] + return self.Rf2Scor.mVehicles[self.__playersDriverNum()] def vehicleName(self): """ Get the vehicle's name """ return Cbytestring2Python( - self.Rf2Scor.mVehicles[self.players_index].mVehicleName) + self.Rf2Scor.mVehicles[self.__playersDriverNum()].mVehicleName) def closeSimInfo(self): - # Stop data updating thread - self.data_updating = False - time.sleep(0.2) # This didn't help with the errors try: # Unassign those objects first From c7c75e4b79101d5520eee5f392a554141a0d3db7 Mon Sep 17 00:00:00 2001 From: Xiang Date: Fri, 9 Dec 2022 14:47:37 +0800 Subject: [PATCH 21/93] periodically verify shared memory data version, auto restart memory mapping if data version stopped updating --- rF2data.py | 77 ++++++++++++++++++++++++++++++++++++++-------- sharedMemoryAPI.py | 70 +++++++++++++++++++++-------------------- 2 files changed, 102 insertions(+), 45 deletions(-) diff --git a/rF2data.py b/rF2data.py index a7fd19e..ffb0007 100644 --- a/rF2data.py +++ b/rF2data.py @@ -7,6 +7,7 @@ from enum import Enum import ctypes import mmap +import copy class rFactor2Constants: MAX_MAPPED_VEHICLES = 128 @@ -692,26 +693,78 @@ class SubscribedBuffer(Enum): class SimInfo: def __init__(self, input_pid): + self._input_pid = input_pid + self._rf2_tele = None # map shared memory + self._rf2_scor = None + self._rf2_ext = None + self._rf2_ffb = None + + self.Rf2Tele = None # raw data + self.Rf2Scor = None + self.Rf2Ext = None + self.Rf2Ffb = None + + self.DefTele = None # default copy of raw data + self.DefScor = None + self.DefExt = None + self.DefFfb = None + + self.LastTele = None # synced copy of raw data + self.LastScor = None + self.LastExt = None + self.LastFfb = None + + self.start_mmap() + self.set_default_mmap() + + def start_mmap(self): + """ Start memory mapping """ + self._rf2_tele = mmap.mmap(0, ctypes.sizeof(rF2Telemetry), f"$rFactor2SMMP_Telemetry${self._input_pid}") + self._rf2_scor = mmap.mmap(0, ctypes.sizeof(rF2Scoring), f"$rFactor2SMMP_Scoring${self._input_pid}") + self._rf2_ext = mmap.mmap(0, ctypes.sizeof(rF2Extended), f"$rFactor2SMMP_Extended${self._input_pid}") + self._rf2_ffb = mmap.mmap(0, ctypes.sizeof(rF2ForceFeedback), "$rFactor2SMMP_ForceFeedback$") - self._rf2_tele = mmap.mmap(0, ctypes.sizeof(rF2Telemetry), f"$rFactor2SMMP_Telemetry${input_pid}") self.Rf2Tele = rF2Telemetry.from_buffer(self._rf2_tele) - self._rf2_scor = mmap.mmap(0, ctypes.sizeof(rF2Scoring), f"$rFactor2SMMP_Scoring${input_pid}") self.Rf2Scor = rF2Scoring.from_buffer(self._rf2_scor) - self._rf2_ext = mmap.mmap(0, ctypes.sizeof(rF2Extended), f"$rFactor2SMMP_Extended${input_pid}") self.Rf2Ext = rF2Extended.from_buffer(self._rf2_ext) - self._rf2_ffb = mmap.mmap(0, ctypes.sizeof(rF2ForceFeedback), "$rFactor2SMMP_ForceFeedback$") self.Rf2Ffb = rF2ForceFeedback.from_buffer(self._rf2_ffb) + self.DefTele = copy.deepcopy(self.Rf2Tele) + self.DefScor = copy.deepcopy(self.Rf2Scor) + self.DefExt = copy.deepcopy(self.Rf2Ext) + self.DefFfb = copy.deepcopy(self.Rf2Ffb) + + def set_default_mmap(self): + """ Set default memory mapping data """ + self.LastTele = copy.deepcopy(self.DefTele) + self.LastScor = copy.deepcopy(self.DefScor) + self.LastExt = copy.deepcopy(self.DefExt) + self.LastFfb = copy.deepcopy(self.DefFfb) + + def reset_mmap(self): + """ Reset memory mapping """ + self.close() # close mmap first + self.start_mmap() + def close(self): - # This didn't help with the errors - try: - self._rf2_tele.close() - self._rf2_scor.close() - self._rf2_ext.close() - self._rf2_ffb.close() - except BufferError: # "cannot close exported pointers exist" - pass + """ Close memory mapping """ + # This didn't help with the errors + try: + # Unassign those objects first + self.Rf2Tele = None + self.Rf2Scor = None + self.Rf2Ext = None + self.Rf2Ffb = None + # Close shared memory mapping + self._rf2_tele.close() + self._rf2_scor.close() + self._rf2_ext.close() + self._rf2_ffb.close() + print("sharedmemory mapping closed") + except BufferError: # "cannot close exported pointers exist" + print("BufferError") + pass def __del__(self): self.close() diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index 016fae3..9799a72 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -28,14 +28,12 @@ class SimInfoAPI(rF2data.SimInfo): rf2_pid_counter = 0 # Counter to check if running rf2_running = False - def __init__(self, input_pid): + def __init__(self, input_pid=""): rF2data.SimInfo.__init__(self, input_pid) self.versionCheckMsg = self.versionCheck() self.__find_rf2_pid() + self.players_index = 99 - self.players_status = 0 - self.LastTele = copy.deepcopy(self.Rf2Tele) - self.LastScor = copy.deepcopy(self.Rf2Scor) self.data_updating = False print("sharedmemory mapping started") @@ -137,11 +135,18 @@ def dataVerified(input_data): def __infoUpdate(self): """ Update synced player data """ - players_mid = 0 + players_mid = 0 # player mID + last_version_update = 0 # store last data version update + re_version_update = 0 # store restarted data version update + mmap_restarted = True # whether has restarted memory mapping + check_counter = 0 # counter for data version update check + restore_counter = 0 # counter for restoring mmap data to default while self.data_updating: data_scor = copy.deepcopy(self.Rf2Scor) # use deepcopy to avoid data interruption data_tele = copy.deepcopy(self.Rf2Tele) + self.LastExt = copy.deepcopy(self.Rf2Ext) + self.LastFfb = copy.deepcopy(self.Rf2Ffb) # Only update if data verified and player index found if self.dataVerified(data_scor) and self.__playerVerified(data_scor): @@ -151,7 +156,31 @@ def __infoUpdate(self): # Only update if data verified and player mID matches if self.dataVerified(data_tele) and data_tele.mVehicles[self.players_index].mID == players_mid: self.LastTele = copy.deepcopy(data_tele) # use deepcopy to update synced telemetry data - self.players_status = self.LastTele.mVehicles[self.players_index].mIgnitionStarter # update player status + + # Start checking data version update status + check_counter += 1 + + if check_counter > 70: # active after around 1 seconds + if not mmap_restarted and last_version_update > 0 and last_version_update == self.LastScor.mVersionUpdateEnd: + self.reset_mmap() + mmap_restarted = True + re_version_update = self.LastScor.mVersionUpdateEnd + print(f"sharedmemory mapping restarted - version:{last_version_update}") + last_version_update = self.LastScor.mVersionUpdateEnd + check_counter = 0 # reset counter + + if mmap_restarted: + if re_version_update != self.LastScor.mVersionUpdateEnd: + mmap_restarted = False + restore_counter = 0 # reset counter + elif restore_counter < 71: + restore_counter += 1 + + if restore_counter == 70: # active after around 1 seconds + self.set_default_mmap() + print("sharedmemory mapping data reset to default") + + #print(f"c1:{check_counter:03.0f} c2:{restore_counter:03.0f} now:{self.LastScor.mVersionUpdateEnd:07.0f} last:{last_version_update:07.0f} re:{re_version_update:07.0f} {mmap_restarted}", end="\r") time.sleep(0.01) else: @@ -245,9 +274,7 @@ def isAiDriving(self): # didn't work self.Rf2Ext.mPhysics.mAIControl def driverName(self): - """ - Get the player's name - """ + """ Get the player's name """ return Cbytestring2Python( self.Rf2Scor.mVehicles[self.__playersDriverNum()].mDriverName) @@ -260,33 +287,10 @@ def playersVehicleScoring(self): return self.Rf2Scor.mVehicles[self.__playersDriverNum()] def vehicleName(self): - """ - Get the vehicle's name - """ + """ Get the vehicle's name """ return Cbytestring2Python( self.Rf2Scor.mVehicles[self.__playersDriverNum()].mVehicleName) - def closeSimInfo(self): - # This didn't help with the errors - try: - # Unassign those objects first - self.Rf2Tele = None - self.Rf2Scor = None - self.Rf2Ext = None - self.Rf2Ffb = None - # Close shared memory mapping - self._rf2_tele.close() - self._rf2_scor.close() - self._rf2_ext.close() - self._rf2_ffb.close() - print("sharedmemory mapping closed") - except BufferError: # "cannot close exported pointers exist" - print("BufferError") - pass - - def __del__(self): - self.close() - def Cbytestring2Python(bytestring): """ From 46a679a48eefa44dc245b53592b2b693059a6e94 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sat, 14 Jan 2023 15:53:01 +0800 Subject: [PATCH 22/93] set default counter status --- sharedMemoryAPI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index 9799a72..1367597 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -140,7 +140,7 @@ def __infoUpdate(self): re_version_update = 0 # store restarted data version update mmap_restarted = True # whether has restarted memory mapping check_counter = 0 # counter for data version update check - restore_counter = 0 # counter for restoring mmap data to default + restore_counter = 71 # counter for restoring mmap data to default while self.data_updating: data_scor = copy.deepcopy(self.Rf2Scor) # use deepcopy to avoid data interruption From 37893552a39c0d03b82076dfde430cf88504a150 Mon Sep 17 00:00:00 2001 From: Bernat Arlandis Date: Fri, 13 Jan 2023 09:51:33 +0100 Subject: [PATCH 23/93] Add Linux mmap support through files --- rF2data.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/rF2data.py b/rF2data.py index ffb0007..8839c29 100644 --- a/rF2data.py +++ b/rF2data.py @@ -8,6 +8,7 @@ import ctypes import mmap import copy +import platform class rFactor2Constants: MAX_MAPPED_VEHICLES = 128 @@ -720,10 +721,20 @@ def __init__(self, input_pid): def start_mmap(self): """ Start memory mapping """ - self._rf2_tele = mmap.mmap(0, ctypes.sizeof(rF2Telemetry), f"$rFactor2SMMP_Telemetry${self._input_pid}") - self._rf2_scor = mmap.mmap(0, ctypes.sizeof(rF2Scoring), f"$rFactor2SMMP_Scoring${self._input_pid}") - self._rf2_ext = mmap.mmap(0, ctypes.sizeof(rF2Extended), f"$rFactor2SMMP_Extended${self._input_pid}") - self._rf2_ffb = mmap.mmap(0, ctypes.sizeof(rF2ForceFeedback), "$rFactor2SMMP_ForceFeedback$") + if platform.system() == "Windows": + self._rf2_tele = mmap.mmap(0, ctypes.sizeof(rF2Telemetry), f"$rFactor2SMMP_Telemetry${self._input_pid}") + self._rf2_scor = mmap.mmap(0, ctypes.sizeof(rF2Scoring), f"$rFactor2SMMP_Scoring${self._input_pid}") + self._rf2_ext = mmap.mmap(0, ctypes.sizeof(rF2Extended), f"$rFactor2SMMP_Extended${self._input_pid}") + self._rf2_ffb = mmap.mmap(0, ctypes.sizeof(rF2ForceFeedback), "$rFactor2SMMP_ForceFeedback$") + else: + tele_file = open("/dev/shm/$rFactor2SMMP_Telemetry$", "r+") + self._rf2_tele = mmap.mmap(tele_file.fileno(), ctypes.sizeof(rF2Telemetry)) + scor_file = open("/dev/shm/$rFactor2SMMP_Scoring$", "r+") + self._rf2_scor = mmap.mmap(scor_file.fileno(), ctypes.sizeof(rF2Scoring)) + ext_file = open("/dev/shm/$rFactor2SMMP_Extended$", "r+") + self._rf2_ext = mmap.mmap(ext_file.fileno(), ctypes.sizeof(rF2Extended)) + ffb_file = open("/dev/shm/$rFactor2SMMP_ForceFeedback$", "r+") + self._rf2_ffb = mmap.mmap(ffb_file.fileno(), ctypes.sizeof(rF2ForceFeedback)) self.Rf2Tele = rF2Telemetry.from_buffer(self._rf2_tele) self.Rf2Scor = rF2Scoring.from_buffer(self._rf2_scor) From 47a0c6ea0c0883cd833b3a23161157f508296528 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sat, 11 Feb 2023 11:43:31 +0800 Subject: [PATCH 24/93] separated player-synced methods into SimInfoSync.py, reverted rF2data.py & sharedMemoryAPI.py back to upstream version --- SimInfoSync.py | 236 +++++++++++++++++++++++++++++++++++++++++++++ rF2data.py | 89 +++-------------- sharedMemoryAPI.py | 131 +++++-------------------- 3 files changed, 272 insertions(+), 184 deletions(-) create mode 100644 SimInfoSync.py diff --git a/SimInfoSync.py b/SimInfoSync.py new file mode 100644 index 0000000..dbc4f7f --- /dev/null +++ b/SimInfoSync.py @@ -0,0 +1,236 @@ +""" +sharedMemoryAPI with player-synced methods + +Inherit Python mapping of The Iron Wolf's rF2 Shared Memory Tools +and add access functions to it. +""" +# pylint: disable=invalid-name +import ctypes +import mmap +import time +import threading +import copy +import platform + +try: + from . import rF2data +except ImportError: # standalone, not package + import rF2data + + +class SimInfoSync(): + """ + API for rF2 shared memory + + Player-Synced data. + """ + + def __init__(self, input_pid=""): + self.players_index = 99 + self.data_updating = False + print("sharedmemory mapping started") + + self._input_pid = input_pid + + self._rf2_tele = None # map shared memory + self._rf2_scor = None + self._rf2_ext = None + self._rf2_ffb = None + + self.Rf2Tele = None # raw data + self.Rf2Scor = None + self.Rf2Ext = None + self.Rf2Ffb = None + + self.DefTele = None # default copy of raw data + self.DefScor = None + self.DefExt = None + self.DefFfb = None + + self.LastTele = None # synced copy of raw data + self.LastScor = None + self.LastExt = None + self.LastFfb = None + + self.start_mmap() + self.set_default_mmap() + + def start_mmap(self): + """ Start memory mapping """ + if platform.system() == "Windows": + self._rf2_tele = mmap.mmap(0, ctypes.sizeof(rF2data.rF2Telemetry), + f"$rFactor2SMMP_Telemetry${self._input_pid}") + self._rf2_scor = mmap.mmap(0, ctypes.sizeof(rF2data.rF2Scoring), + f"$rFactor2SMMP_Scoring${self._input_pid}") + self._rf2_ext = mmap.mmap(0, ctypes.sizeof(rF2data.rF2Extended), + f"$rFactor2SMMP_Extended${self._input_pid}") + self._rf2_ffb = mmap.mmap(0, ctypes.sizeof(rF2data.rF2ForceFeedback), + "$rFactor2SMMP_ForceFeedback$") + else: + tele_file = open("/dev/shm/$rFactor2SMMP_Telemetry$", "r+") + self._rf2_tele = mmap.mmap(tele_file.fileno(), ctypes.sizeof(rF2data.rF2Telemetry)) + scor_file = open("/dev/shm/$rFactor2SMMP_Scoring$", "r+") + self._rf2_scor = mmap.mmap(scor_file.fileno(), ctypes.sizeof(rF2data.rF2Scoring)) + ext_file = open("/dev/shm/$rFactor2SMMP_Extended$", "r+") + self._rf2_ext = mmap.mmap(ext_file.fileno(), ctypes.sizeof(rF2data.rF2Extended)) + ffb_file = open("/dev/shm/$rFactor2SMMP_ForceFeedback$", "r+") + self._rf2_ffb = mmap.mmap(ffb_file.fileno(), ctypes.sizeof(rF2data.rF2ForceFeedback)) + + self.Rf2Tele = rF2data.rF2Telemetry.from_buffer(self._rf2_tele) + self.Rf2Scor = rF2data.rF2Scoring.from_buffer(self._rf2_scor) + self.Rf2Ext = rF2data.rF2Extended.from_buffer(self._rf2_ext) + self.Rf2Ffb = rF2data.rF2ForceFeedback.from_buffer(self._rf2_ffb) + + self.DefTele = copy.deepcopy(self.Rf2Tele) + self.DefScor = copy.deepcopy(self.Rf2Scor) + self.DefExt = copy.deepcopy(self.Rf2Ext) + self.DefFfb = copy.deepcopy(self.Rf2Ffb) + + def set_default_mmap(self): + """ Set default memory mapping data """ + self.LastTele = copy.deepcopy(self.DefTele) + self.LastScor = copy.deepcopy(self.DefScor) + self.LastExt = copy.deepcopy(self.DefExt) + self.LastFfb = copy.deepcopy(self.DefFfb) + + def reset_mmap(self): + """ Reset memory mapping """ + self.close() # close mmap first + self.start_mmap() + + def close(self): + """ Close memory mapping """ + # This didn't help with the errors + try: + # Unassign those objects first + self.Rf2Tele = None + self.Rf2Scor = None + self.Rf2Ext = None + self.Rf2Ffb = None + # Close shared memory mapping + self._rf2_tele.close() + self._rf2_scor.close() + self._rf2_ext.close() + self._rf2_ffb.close() + print("sharedmemory mapping closed") + except BufferError: # "cannot close exported pointers exist" + print("BufferError") + pass + + ########################################################### + # Sync data for local player + + def __playerVerified(self, input_data): + """ Check player index number on one same data piece """ + found = False # return false if failed to find player index + for _player in range(127): # max 128 players supported by API + # Use 1 to avoid reading incorrect value + if input_data.mVehicles[_player].mIsPlayer == 1: + self.players_index = _player + found = True + break + return found + + @staticmethod + def dataVerified(input_data): + """ Verify data """ + return input_data.mVersionUpdateEnd == input_data.mVersionUpdateBegin + + def __infoUpdate(self): + """ Update synced player data """ + players_mid = 0 # player mID + last_version_update = 0 # store last data version update + re_version_update = 0 # store restarted data version update + mmap_restarted = True # whether has restarted memory mapping + check_counter = 0 # counter for data version update check + restore_counter = 71 # counter for restoring mmap data to default + + while self.data_updating: + data_scor = copy.deepcopy(self.Rf2Scor) # use deepcopy to avoid data interruption + data_tele = copy.deepcopy(self.Rf2Tele) + self.LastExt = copy.deepcopy(self.Rf2Ext) + self.LastFfb = copy.deepcopy(self.Rf2Ffb) + + # Only update if data verified and player index found + if self.dataVerified(data_scor) and self.__playerVerified(data_scor): + self.LastScor = copy.deepcopy(data_scor) # synced scoring + players_mid = self.LastScor.mVehicles[self.players_index].mID # update player mID + + # Only update if data verified and player mID matches + if (self.dataVerified(data_tele) and + data_tele.mVehicles[self.players_index].mID == players_mid): + self.LastTele = copy.deepcopy(data_tele) # synced telemetry + + # Start checking data version update status + check_counter += 1 + + if check_counter > 70: # active after around 1 seconds + if (not mmap_restarted and last_version_update > 0 + and last_version_update == self.LastScor.mVersionUpdateEnd): + self.reset_mmap() + mmap_restarted = True + re_version_update = self.LastScor.mVersionUpdateEnd + print(f"sharedmemory mapping restarted - version:{last_version_update}") + last_version_update = self.LastScor.mVersionUpdateEnd + check_counter = 0 # reset counter + + if mmap_restarted: + if re_version_update != self.LastScor.mVersionUpdateEnd: + mmap_restarted = False + restore_counter = 0 # reset counter + elif restore_counter < 71: + restore_counter += 1 + + if restore_counter == 70: # active after around 1 seconds + self.set_default_mmap() + print("sharedmemory mapping data reset to default") + + #print(f"c1:{check_counter:03.0f} " + # f"c2:{restore_counter:03.0f} " + # f"now:{self.LastScor.mVersionUpdateEnd:07.0f} " + # f"last:{last_version_update:07.0f} " + # f"re:{re_version_update:07.0f} " + # f"{mmap_restarted}", end="\r") + + time.sleep(0.01) + + print("sharedmemory synced player data updating thread stopped") + + def startUpdating(self): + """ Start data updating thread """ + self.data_updating = True + index_thread = threading.Thread(target=self.__infoUpdate) + index_thread.daemon=True + index_thread.start() + print("sharedmemory synced player data updating thread started") + + def stopUpdating(self): + """ Stop data updating thread """ + self.data_updating = False + time.sleep(0.2) + + def syncedVehicleTelemetry(self): + """ Get the variable for the player's vehicle """ + return self.LastTele.mVehicles[self.players_index] + + def syncedVehicleScoring(self): + """ Get the variable for the player's vehicle """ + return self.LastScor.mVehicles[self.players_index] + + ########################################################### + + def __del__(self): + self.close() + +if __name__ == '__main__': + # Example usage + info = SimInfoSync() + info.startUpdating() # start Shared Memory updating thread + version = info.LastExt.mVersion + v = bytes(version).partition(b'\0')[0].decode().rstrip() + clutch = info.LastTele.mVehicles[0].mUnfilteredClutch # 1.0 clutch down, 0 clutch up + gear = info.LastTele.mVehicles[0].mGear # -1 to 6 + print(f"Map version: {v}\n" + f"Gear: {gear}, Clutch position: {clutch}") + + info.stopUpdating() # stop sharedmemory synced player data updating thread diff --git a/rF2data.py b/rF2data.py index 8839c29..435bb01 100644 --- a/rF2data.py +++ b/rF2data.py @@ -7,8 +7,6 @@ from enum import Enum import ctypes import mmap -import copy -import platform class rFactor2Constants: MAX_MAPPED_VEHICLES = 128 @@ -693,89 +691,24 @@ class SubscribedBuffer(Enum): All = 255 class SimInfo: - def __init__(self, input_pid): - self._input_pid = input_pid + def __init__(self): - self._rf2_tele = None # map shared memory - self._rf2_scor = None - self._rf2_ext = None - self._rf2_ffb = None - - self.Rf2Tele = None # raw data - self.Rf2Scor = None - self.Rf2Ext = None - self.Rf2Ffb = None - - self.DefTele = None # default copy of raw data - self.DefScor = None - self.DefExt = None - self.DefFfb = None - - self.LastTele = None # synced copy of raw data - self.LastScor = None - self.LastExt = None - self.LastFfb = None - - self.start_mmap() - self.set_default_mmap() - - def start_mmap(self): - """ Start memory mapping """ - if platform.system() == "Windows": - self._rf2_tele = mmap.mmap(0, ctypes.sizeof(rF2Telemetry), f"$rFactor2SMMP_Telemetry${self._input_pid}") - self._rf2_scor = mmap.mmap(0, ctypes.sizeof(rF2Scoring), f"$rFactor2SMMP_Scoring${self._input_pid}") - self._rf2_ext = mmap.mmap(0, ctypes.sizeof(rF2Extended), f"$rFactor2SMMP_Extended${self._input_pid}") - self._rf2_ffb = mmap.mmap(0, ctypes.sizeof(rF2ForceFeedback), "$rFactor2SMMP_ForceFeedback$") - else: - tele_file = open("/dev/shm/$rFactor2SMMP_Telemetry$", "r+") - self._rf2_tele = mmap.mmap(tele_file.fileno(), ctypes.sizeof(rF2Telemetry)) - scor_file = open("/dev/shm/$rFactor2SMMP_Scoring$", "r+") - self._rf2_scor = mmap.mmap(scor_file.fileno(), ctypes.sizeof(rF2Scoring)) - ext_file = open("/dev/shm/$rFactor2SMMP_Extended$", "r+") - self._rf2_ext = mmap.mmap(ext_file.fileno(), ctypes.sizeof(rF2Extended)) - ffb_file = open("/dev/shm/$rFactor2SMMP_ForceFeedback$", "r+") - self._rf2_ffb = mmap.mmap(ffb_file.fileno(), ctypes.sizeof(rF2ForceFeedback)) + self._rf2_tele = mmap.mmap(0, ctypes.sizeof(rF2Telemetry), "$rFactor2SMMP_Telemetry$") self.Rf2Tele = rF2Telemetry.from_buffer(self._rf2_tele) + self._rf2_scor = mmap.mmap(0, ctypes.sizeof(rF2Scoring), "$rFactor2SMMP_Scoring$") self.Rf2Scor = rF2Scoring.from_buffer(self._rf2_scor) + self._rf2_ext = mmap.mmap(0, ctypes.sizeof(rF2Extended), "$rFactor2SMMP_Extended$") self.Rf2Ext = rF2Extended.from_buffer(self._rf2_ext) - self.Rf2Ffb = rF2ForceFeedback.from_buffer(self._rf2_ffb) - - self.DefTele = copy.deepcopy(self.Rf2Tele) - self.DefScor = copy.deepcopy(self.Rf2Scor) - self.DefExt = copy.deepcopy(self.Rf2Ext) - self.DefFfb = copy.deepcopy(self.Rf2Ffb) - - def set_default_mmap(self): - """ Set default memory mapping data """ - self.LastTele = copy.deepcopy(self.DefTele) - self.LastScor = copy.deepcopy(self.DefScor) - self.LastExt = copy.deepcopy(self.DefExt) - self.LastFfb = copy.deepcopy(self.DefFfb) - - def reset_mmap(self): - """ Reset memory mapping """ - self.close() # close mmap first - self.start_mmap() def close(self): - """ Close memory mapping """ - # This didn't help with the errors - try: - # Unassign those objects first - self.Rf2Tele = None - self.Rf2Scor = None - self.Rf2Ext = None - self.Rf2Ffb = None - # Close shared memory mapping - self._rf2_tele.close() - self._rf2_scor.close() - self._rf2_ext.close() - self._rf2_ffb.close() - print("sharedmemory mapping closed") - except BufferError: # "cannot close exported pointers exist" - print("BufferError") - pass + # This didn't help with the errors + try: + self._rf2_tele.close() + self._rf2_scor.close() + self._rf2_ext.close() + except BufferError: # "cannot close exported pointers exist" + pass def __del__(self): self.close() diff --git a/sharedMemoryAPI.py b/sharedMemoryAPI.py index 1367597..fe421bd 100644 --- a/sharedMemoryAPI.py +++ b/sharedMemoryAPI.py @@ -3,9 +3,7 @@ and add access functions to it. """ # pylint: disable=invalid-name -import time -import threading -import copy + import psutil try: @@ -28,15 +26,11 @@ class SimInfoAPI(rF2data.SimInfo): rf2_pid_counter = 0 # Counter to check if running rf2_running = False - def __init__(self, input_pid=""): - rF2data.SimInfo.__init__(self, input_pid) + def __init__(self): + rF2data.SimInfo.__init__(self) self.versionCheckMsg = self.versionCheck() self.__find_rf2_pid() - self.players_index = 99 - self.data_updating = False - print("sharedmemory mapping started") - def versionCheck(self): """ Lifted from @@ -109,104 +103,11 @@ def __find_rf2_pid(self): def __playersDriverNum(self): """ Find the player's driver number """ - index_list = copy.deepcopy(self.Rf2Scor.mVehicles) - for _player in range(127): - if index_list[_player].mIsPlayer == 1: + for _player in range(50): # self.Rf2Tele.mVehicles[0].mNumVehicles: + if self.Rf2Scor.mVehicles[_player].mIsPlayer: break return _player - ########################################################### - # Sync data for local player - - def __playerVerified(self, input_data): - """ Check player index number on one same data piece """ - found = False # return false if failed to find player index - for _player in range(127): # max 128 players supported by API - if input_data.mVehicles[_player].mIsPlayer == 1: # use 1 to avoid reading incorrect value - self.players_index = _player - found = True - break - return found - - @staticmethod - def dataVerified(input_data): - """ Verify data """ - return input_data.mVersionUpdateEnd == input_data.mVersionUpdateBegin - - def __infoUpdate(self): - """ Update synced player data """ - players_mid = 0 # player mID - last_version_update = 0 # store last data version update - re_version_update = 0 # store restarted data version update - mmap_restarted = True # whether has restarted memory mapping - check_counter = 0 # counter for data version update check - restore_counter = 71 # counter for restoring mmap data to default - - while self.data_updating: - data_scor = copy.deepcopy(self.Rf2Scor) # use deepcopy to avoid data interruption - data_tele = copy.deepcopy(self.Rf2Tele) - self.LastExt = copy.deepcopy(self.Rf2Ext) - self.LastFfb = copy.deepcopy(self.Rf2Ffb) - - # Only update if data verified and player index found - if self.dataVerified(data_scor) and self.__playerVerified(data_scor): - self.LastScor = copy.deepcopy(data_scor) # use deepcopy to update scoring data - players_mid = self.LastScor.mVehicles[self.players_index].mID # update player mID - - # Only update if data verified and player mID matches - if self.dataVerified(data_tele) and data_tele.mVehicles[self.players_index].mID == players_mid: - self.LastTele = copy.deepcopy(data_tele) # use deepcopy to update synced telemetry data - - # Start checking data version update status - check_counter += 1 - - if check_counter > 70: # active after around 1 seconds - if not mmap_restarted and last_version_update > 0 and last_version_update == self.LastScor.mVersionUpdateEnd: - self.reset_mmap() - mmap_restarted = True - re_version_update = self.LastScor.mVersionUpdateEnd - print(f"sharedmemory mapping restarted - version:{last_version_update}") - last_version_update = self.LastScor.mVersionUpdateEnd - check_counter = 0 # reset counter - - if mmap_restarted: - if re_version_update != self.LastScor.mVersionUpdateEnd: - mmap_restarted = False - restore_counter = 0 # reset counter - elif restore_counter < 71: - restore_counter += 1 - - if restore_counter == 70: # active after around 1 seconds - self.set_default_mmap() - print("sharedmemory mapping data reset to default") - - #print(f"c1:{check_counter:03.0f} c2:{restore_counter:03.0f} now:{self.LastScor.mVersionUpdateEnd:07.0f} last:{last_version_update:07.0f} re:{re_version_update:07.0f} {mmap_restarted}", end="\r") - - time.sleep(0.01) - else: - print("sharedmemory synced player data updating thread stopped") - - def startUpdating(self): - """ Start data updating thread """ - self.data_updating = True - index_thread = threading.Thread(target=self.__infoUpdate) - index_thread.setDaemon(True) - index_thread.start() - print("sharedmemory synced player data updating thread started") - - def stopUpdating(self): - """ Stop data updating thread """ - self.data_updating = False - time.sleep(0.2) - - def syncedVehicleTelemetry(self): - """ Get the variable for the player's vehicle """ - return self.LastTele.mVehicles[self.players_index] - - def syncedVehicleScoring(self): - """ Get the variable for the player's vehicle """ - return self.LastScor.mVehicles[self.players_index] - ########################################################### # Access functions @@ -274,23 +175,41 @@ def isAiDriving(self): # didn't work self.Rf2Ext.mPhysics.mAIControl def driverName(self): - """ Get the player's name """ + """ + Get the player's name + """ return Cbytestring2Python( self.Rf2Scor.mVehicles[self.__playersDriverNum()].mDriverName) def playersVehicleTelemetry(self): """ Get the variable for the player's vehicle """ + self.__playersDriverNum() return self.Rf2Tele.mVehicles[self.__playersDriverNum()] def playersVehicleScoring(self): """ Get the variable for the player's vehicle """ + self.__playersDriverNum() return self.Rf2Scor.mVehicles[self.__playersDriverNum()] def vehicleName(self): - """ Get the vehicle's name """ + """ + Get the vehicle's name + """ return Cbytestring2Python( self.Rf2Scor.mVehicles[self.__playersDriverNum()].mVehicleName) + def close(self): + # This didn't help with the errors + try: + self._rf2_tele.close() + self._rf2_scor.close() + self._rf2_ext.close() + except BufferError: # "cannot close exported pointers exist" + pass + + def __del__(self): + self.close() + def Cbytestring2Python(bytestring): """ From 2110f10f8be9ee474cf4a76e189b49f344c82126 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sat, 11 Feb 2023 12:21:21 +0800 Subject: [PATCH 25/93] change to lowercase filename --- SimInfoSync.py => sim_info_sync.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename SimInfoSync.py => sim_info_sync.py (100%) diff --git a/SimInfoSync.py b/sim_info_sync.py similarity index 100% rename from SimInfoSync.py rename to sim_info_sync.py From 1480741071f28efcccff372d05c4adbeee351ac0 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sat, 11 Feb 2023 13:45:03 +0800 Subject: [PATCH 26/93] fix max vehicle number --- sim_info_sync.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index dbc4f7f..cde5d39 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -28,8 +28,6 @@ class SimInfoSync(): def __init__(self, input_pid=""): self.players_index = 99 self.data_updating = False - print("sharedmemory mapping started") - self._input_pid = input_pid self._rf2_tele = None # map shared memory @@ -54,6 +52,7 @@ def __init__(self, input_pid=""): self.start_mmap() self.set_default_mmap() + print("sharedmemory mapping started") def start_mmap(self): """ Start memory mapping """ @@ -122,14 +121,12 @@ def close(self): def __playerVerified(self, input_data): """ Check player index number on one same data piece """ - found = False # return false if failed to find player index - for _player in range(127): # max 128 players supported by API + for _player in range(128): # max 128 players supported by API # Use 1 to avoid reading incorrect value if input_data.mVehicles[_player].mIsPlayer == 1: self.players_index = _player - found = True - break - return found + return True + return False # return false if failed to find player index @staticmethod def dataVerified(input_data): From acc525c806feff60ef2552e862c99b6ad6416744 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sun, 12 Feb 2023 21:28:59 +0800 Subject: [PATCH 27/93] wait until stopped --- sim_info_sync.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index cde5d39..5e6a37e 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -28,6 +28,7 @@ class SimInfoSync(): def __init__(self, input_pid=""): self.players_index = 99 self.data_updating = False + self.stopped = True self._input_pid = input_pid self._rf2_tele = None # map shared memory @@ -191,11 +192,13 @@ def __infoUpdate(self): time.sleep(0.01) + self.stopped = True print("sharedmemory synced player data updating thread stopped") def startUpdating(self): """ Start data updating thread """ self.data_updating = True + self.stopped = False index_thread = threading.Thread(target=self.__infoUpdate) index_thread.daemon=True index_thread.start() @@ -204,7 +207,8 @@ def startUpdating(self): def stopUpdating(self): """ Stop data updating thread """ self.data_updating = False - time.sleep(0.2) + while not self.stopped: # wait until stopped + time.sleep(0.01) def syncedVehicleTelemetry(self): """ Get the variable for the player's vehicle """ From 8f5ca72b8e4e0e02cd77d4f0fe0e7db823a1319c Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 16 Feb 2023 09:48:55 +0800 Subject: [PATCH 28/93] fallback to mID matching if mIsPlayer fails to retrieve player index, add additional mmap restart check --- sim_info_sync.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 5e6a37e..0f24481 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -17,6 +17,8 @@ except ImportError: # standalone, not package import rF2data +MAX_VEHICLES = 128 # max 128 players supported by API + class SimInfoSync(): """ @@ -58,13 +60,13 @@ def __init__(self, input_pid=""): def start_mmap(self): """ Start memory mapping """ if platform.system() == "Windows": - self._rf2_tele = mmap.mmap(0, ctypes.sizeof(rF2data.rF2Telemetry), + self._rf2_tele = mmap.mmap(-1, ctypes.sizeof(rF2data.rF2Telemetry), f"$rFactor2SMMP_Telemetry${self._input_pid}") - self._rf2_scor = mmap.mmap(0, ctypes.sizeof(rF2data.rF2Scoring), + self._rf2_scor = mmap.mmap(-1, ctypes.sizeof(rF2data.rF2Scoring), f"$rFactor2SMMP_Scoring${self._input_pid}") - self._rf2_ext = mmap.mmap(0, ctypes.sizeof(rF2data.rF2Extended), + self._rf2_ext = mmap.mmap(-1, ctypes.sizeof(rF2data.rF2Extended), f"$rFactor2SMMP_Extended${self._input_pid}") - self._rf2_ffb = mmap.mmap(0, ctypes.sizeof(rF2data.rF2ForceFeedback), + self._rf2_ffb = mmap.mmap(-1, ctypes.sizeof(rF2data.rF2ForceFeedback), "$rFactor2SMMP_ForceFeedback$") else: tele_file = open("/dev/shm/$rFactor2SMMP_Telemetry$", "r+") @@ -120,19 +122,25 @@ def close(self): ########################################################### # Sync data for local player - def __playerVerified(self, input_data): + def __playerVerified(self, data, pmid): """ Check player index number on one same data piece """ - for _player in range(128): # max 128 players supported by API + for index in range(MAX_VEHICLES): # Use 1 to avoid reading incorrect value - if input_data.mVehicles[_player].mIsPlayer == 1: - self.players_index = _player + if data.mVehicles[index].mIsPlayer == 1: + self.players_index = index + return True + #print("failed updating player index, using mID matching now") + for index in range(MAX_VEHICLES): + if pmid == data.mVehicles[index].mID: + self.players_index = index return True + #print("no matching mID") return False # return false if failed to find player index @staticmethod - def dataVerified(input_data): + def dataVerified(data): """ Verify data """ - return input_data.mVersionUpdateEnd == input_data.mVersionUpdateBegin + return data.mVersionUpdateEnd == data.mVersionUpdateBegin def __infoUpdate(self): """ Update synced player data """ @@ -150,7 +158,7 @@ def __infoUpdate(self): self.LastFfb = copy.deepcopy(self.Rf2Ffb) # Only update if data verified and player index found - if self.dataVerified(data_scor) and self.__playerVerified(data_scor): + if self.dataVerified(data_scor) and self.__playerVerified(data_scor, players_mid): self.LastScor = copy.deepcopy(data_scor) # synced scoring players_mid = self.LastScor.mVehicles[self.players_index].mID # update player mID @@ -183,8 +191,14 @@ def __infoUpdate(self): self.set_default_mmap() print("sharedmemory mapping data reset to default") + if re_version_update != 0 and data_scor.mVersionUpdateEnd != re_version_update: + re_version_update = 0 + self.reset_mmap() + print(f"sharedmemory mapping re-restarted - version:{last_version_update}") + #print(f"c1:{check_counter:03.0f} " # f"c2:{restore_counter:03.0f} " + # f"idx:{self.players_index:03.0f} " # f"now:{self.LastScor.mVersionUpdateEnd:07.0f} " # f"last:{last_version_update:07.0f} " # f"re:{re_version_update:07.0f} " From 9b2a3308f3e9d67ca1a753e67e699af573d919d9 Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 16 Feb 2023 09:55:37 +0800 Subject: [PATCH 29/93] switch players_mid to instance variable --- sim_info_sync.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 0f24481..6016cd6 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -29,6 +29,7 @@ class SimInfoSync(): def __init__(self, input_pid=""): self.players_index = 99 + self.players_mid = 0 self.data_updating = False self.stopped = True self._input_pid = input_pid @@ -122,19 +123,19 @@ def close(self): ########################################################### # Sync data for local player - def __playerVerified(self, data, pmid): + def __playerVerified(self, data): """ Check player index number on one same data piece """ for index in range(MAX_VEHICLES): # Use 1 to avoid reading incorrect value if data.mVehicles[index].mIsPlayer == 1: self.players_index = index return True - #print("failed updating player index, using mID matching now") + print("failed updating player index, using mID matching now") for index in range(MAX_VEHICLES): - if pmid == data.mVehicles[index].mID: + if self.players_mid == data.mVehicles[index].mID: self.players_index = index return True - #print("no matching mID") + print("no matching mID") return False # return false if failed to find player index @staticmethod @@ -144,7 +145,6 @@ def dataVerified(data): def __infoUpdate(self): """ Update synced player data """ - players_mid = 0 # player mID last_version_update = 0 # store last data version update re_version_update = 0 # store restarted data version update mmap_restarted = True # whether has restarted memory mapping @@ -158,13 +158,13 @@ def __infoUpdate(self): self.LastFfb = copy.deepcopy(self.Rf2Ffb) # Only update if data verified and player index found - if self.dataVerified(data_scor) and self.__playerVerified(data_scor, players_mid): + if self.dataVerified(data_scor) and self.__playerVerified(data_scor): self.LastScor = copy.deepcopy(data_scor) # synced scoring - players_mid = self.LastScor.mVehicles[self.players_index].mID # update player mID + self.players_mid = self.LastScor.mVehicles[self.players_index].mID # update player mID # Only update if data verified and player mID matches if (self.dataVerified(data_tele) and - data_tele.mVehicles[self.players_index].mID == players_mid): + data_tele.mVehicles[self.players_index].mID == self.players_mid): self.LastTele = copy.deepcopy(data_tele) # synced telemetry # Start checking data version update status From ac756fd0f35b8498e0f268299b79cf866c9276ae Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 16 Feb 2023 09:56:49 +0800 Subject: [PATCH 30/93] comments --- sim_info_sync.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 6016cd6..01a8e7c 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -130,12 +130,12 @@ def __playerVerified(self, data): if data.mVehicles[index].mIsPlayer == 1: self.players_index = index return True - print("failed updating player index, using mID matching now") + #print("failed updating player index, using mID matching now") for index in range(MAX_VEHICLES): if self.players_mid == data.mVehicles[index].mID: self.players_index = index return True - print("no matching mID") + #print("no matching mID") return False # return false if failed to find player index @staticmethod From 355e00826286b3c511b18eabdb253b1567654df0 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sun, 26 Feb 2023 13:54:12 +0800 Subject: [PATCH 31/93] uses player name matching for finding player index --- sim_info_sync.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 01a8e7c..726a4bb 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -17,6 +17,8 @@ except ImportError: # standalone, not package import rF2data +from .sharedMemoryAPI import Cbytestring2Python + MAX_VEHICLES = 128 # max 128 players supported by API @@ -125,17 +127,12 @@ def close(self): def __playerVerified(self, data): """ Check player index number on one same data piece """ + plr_name = Cbytestring2Python(data.mScoringInfo.mPlayerName) for index in range(MAX_VEHICLES): # Use 1 to avoid reading incorrect value - if data.mVehicles[index].mIsPlayer == 1: - self.players_index = index - return True - #print("failed updating player index, using mID matching now") - for index in range(MAX_VEHICLES): - if self.players_mid == data.mVehicles[index].mID: + if Cbytestring2Python(data.mVehicles[index].mDriverName) == plr_name: self.players_index = index return True - #print("no matching mID") return False # return false if failed to find player index @staticmethod From ccbe03eeb2b1fee522c0920b435e6757adc1b507 Mon Sep 17 00:00:00 2001 From: Xiang Date: Mon, 6 Mar 2023 13:42:04 +0800 Subject: [PATCH 32/93] Corrected ctypes-data-types in rF2data.py to match C-data-types in rF2State.h from Sharedmemory Plugin --- rF2data.py | 258 ++++++++++++++++++++++++++--------------------------- 1 file changed, 129 insertions(+), 129 deletions(-) diff --git a/rF2data.py b/rF2data.py index 435bb01..2ab1e92 100644 --- a/rF2data.py +++ b/rF2data.py @@ -173,10 +173,10 @@ class rF2Wheel(ctypes.Structure): ('mPressure', ctypes.c_double), # kPa (tire pressure) ('mTemperature', ctypes.c_double*3), # Kelvin (subtract 273.15 to get Celsius), left/center/right (not to be confused with inside/center/outside!) ('mWear', ctypes.c_double), # wear (0.0-1.0, fraction of maximum) ... this is not necessarily proportional with grip loss - ('mTerrainName', ctypes.c_ubyte*16), # the material prefixes from the TDF file + ('mTerrainName', ctypes.c_char*16), # the material prefixes from the TDF file ('mSurfaceType', ctypes.c_ubyte), # 0=dry, 1=wet, 2=grass, 3=dirt, 4=gravel, 5=rumblestrip, 6 = special - ('mFlat', ctypes.c_ubyte), # whether tire is flat - ('mDetached', ctypes.c_ubyte), # whether wheel is detached + ('mFlat', ctypes.c_bool), # whether tire is flat + ('mDetached', ctypes.c_bool), # whether wheel is detached ('mStaticUndeflectedRadius', ctypes.c_ubyte), # tire radius in centimeters ('mVerticalTireDeflection', ctypes.c_double), # how much is tire deflected from its (speed-sensitive) radius ('mWheelYLocation', ctypes.c_double), # wheel's y location relative to vehicle y location @@ -189,20 +189,20 @@ class rF2Wheel(ctypes.Structure): class rF2VehicleTelemetry(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mID', ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) + ('mID', ctypes.c_long), # slot ID (note that it can be re-used in multiplayer after someone leaves) ('mDeltaTime', ctypes.c_double), # time since last update (seconds) ('mElapsedTime', ctypes.c_double), # game session time - ('mLapNumber', ctypes.c_int), # current lap number + ('mLapNumber', ctypes.c_long), # current lap number ('mLapStartET', ctypes.c_double), # time this lap was started - ('mVehicleName', ctypes.c_ubyte*64), # current vehicle name - ('mTrackName', ctypes.c_ubyte*64), # current track name + ('mVehicleName', ctypes.c_char*64), # current vehicle name + ('mTrackName', ctypes.c_char*64), # current track name ('mPos', rF2Vec3), # world position in meters ('mLocalVel', rF2Vec3), # velocity (meters/sec) in local vehicle coordinates ('mLocalAccel', rF2Vec3), # acceleration (meters/sec^2) in local vehicle coordinates ('mOri', rF2Vec3*3), # rows of orientation matrix (use TelemQuat conversions if desired), also converts local ('mLocalRot', rF2Vec3), # rotation (radians/sec) in local vehicle coordinates ('mLocalRotAccel', rF2Vec3), # rotational acceleration (radians/sec^2) in local vehicle coordinates - ('mGear', ctypes.c_int), # -1=reverse, 0=neutral, 1+ = forward gears + ('mGear', ctypes.c_long), # -1=reverse, 0=neutral, 1+ = forward gears ('mEngineRPM', ctypes.c_double), # engine RPM ('mEngineWaterTemp', ctypes.c_double), # Celsius ('mEngineOilTemp', ctypes.c_double), # Celsius @@ -227,15 +227,15 @@ class rF2VehicleTelemetry(ctypes.Structure): ('mFuel', ctypes.c_double), # amount of fuel (liters) ('mEngineMaxRPM', ctypes.c_double), # rev limit ('mScheduledStops', ctypes.c_ubyte), # number of scheduled pitstops - ('mOverheating', ctypes.c_ubyte), # whether overheating icon is shown - ('mDetached', ctypes.c_ubyte), # whether any parts (besides wheels) have been detached - ('mHeadlights', ctypes.c_ubyte), # whether headlights are on + ('mOverheating', ctypes.c_bool), # whether overheating icon is shown + ('mDetached', ctypes.c_bool), # whether any parts (besides wheels) have been detached + ('mHeadlights', ctypes.c_bool), # whether headlights are on ('mDentSeverity', ctypes.c_ubyte*8), # dent severity at 8 locations around the car (0=none, 1=some, 2=more) ('mLastImpactET', ctypes.c_double), # time of last impact ('mLastImpactMagnitude', ctypes.c_double), # magnitude of last impact ('mLastImpactPos', rF2Vec3), # location of last impact ('mEngineTorque', ctypes.c_double), # current engine torque (including additive torque) (used to be mEngineTq, but there's little reason to abbreviate it) - ('mCurrentSector', ctypes.c_int), # the current sector (zero-based) with the pitlane stored in the sign bit (example: entering pits from third sector gives 0x80000002) + ('mCurrentSector', ctypes.c_long), # the current sector (zero-based) with the pitlane stored in the sign bit (example: entering pits from third sector gives 0x80000002) ('mSpeedLimiter', ctypes.c_ubyte), # whether speed limiter is on ('mMaxGears', ctypes.c_ubyte), # maximum forward gears ('mFrontTireCompoundIndex', ctypes.c_ubyte), # index within brand @@ -245,8 +245,8 @@ class rF2VehicleTelemetry(ctypes.Structure): ('mRearFlapActivated', ctypes.c_ubyte), # whether rear flap is activated ('mRearFlapLegalStatus', ctypes.c_ubyte), # 0=disallowed, 1=criteria detected but not allowed quite yet, 2 = allowed ('mIgnitionStarter', ctypes.c_ubyte), # 0=off 1=ignition 2 = ignition+starter - ('mFrontTireCompoundName', ctypes.c_ubyte*18), # name of front tire compound - ('mRearTireCompoundName', ctypes.c_ubyte*18), # name of rear tire compound + ('mFrontTireCompoundName', ctypes.c_char*18), # name of front tire compound + ('mRearTireCompoundName', ctypes.c_char*18), # name of rear tire compound ('mSpeedLimiterAvailable', ctypes.c_ubyte), # whether speed limiter is available ('mAntiStallActivated', ctypes.c_ubyte), # whether (hard) anti-stall is activated ('mUnused', ctypes.c_ubyte*2), # @@ -262,22 +262,22 @@ class rF2VehicleTelemetry(ctypes.Structure): class rF2ScoringInfo(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mTrackName', ctypes.c_ubyte*64), # current track name - ('mSession', ctypes.c_int), # current session (0=testday 1-4=practice 5-8=qual 9=warmup 10-13 = race) + ('mTrackName', ctypes.c_char*64), # current track name + ('mSession', ctypes.c_long), # current session (0=testday 1-4=practice 5-8=qual 9=warmup 10-13 = race) ('mCurrentET', ctypes.c_double), # current time ('mEndET', ctypes.c_double), # ending time - ('mMaxLaps', ctypes.c_int), # maximum laps + ('mMaxLaps', ctypes.c_long), # maximum laps ('mLapDist', ctypes.c_double), # distance around track ('pointer1', ctypes.c_ubyte*8), - ('mNumVehicles', ctypes.c_int), # current number of vehicles + ('mNumVehicles', ctypes.c_long), # current number of vehicles ('mGamePhase', ctypes.c_ubyte), - ('mYellowFlagState', ctypes.c_ubyte), - ('mSectorFlag', ctypes.c_ubyte*3), # whether there are any local yellows at the moment in each sector (not sure if sector 0 is first or last, so test) + ('mYellowFlagState', ctypes.c_char), + ('mSectorFlag', ctypes.c_char*3), # whether there are any local yellows at the moment in each sector (not sure if sector 0 is first or last, so test) ('mStartLight', ctypes.c_ubyte), # start light frame (number depends on track) ('mNumRedLights', ctypes.c_ubyte), # number of red lights in start sequence - ('mInRealtime', ctypes.c_ubyte), # in realtime as opposed to at the monitor - ('mPlayerName', ctypes.c_ubyte*32), # player name (including possible multiplayer override) - ('mPlrFileName', ctypes.c_ubyte*64), # may be encoded to be a legal filename + ('mInRealtime', ctypes.c_bool), # in realtime as opposed to at the monitor + ('mPlayerName', ctypes.c_char*32), # player name (including possible multiplayer override) + ('mPlrFileName', ctypes.c_char*64), # may be encoded to be a legal filename ('mDarkCloud', ctypes.c_double), # cloud darkness? 0.0-1.0 ('mRaining', ctypes.c_double), # raining severity 0.0-1.0 ('mAmbientTemp', ctypes.c_double), # temperature (Celsius) @@ -286,11 +286,11 @@ class rF2ScoringInfo(ctypes.Structure): ('mMinPathWetness', ctypes.c_double), # minimum wetness on main path 0.0-1.0 ('mMaxPathWetness', ctypes.c_double), # maximum wetness on main path 0.0-1.0 ('mGameMode', ctypes.c_ubyte), # 1 = server, 2 = client, 3 = server and client - ('mIsPasswordProtected', ctypes.c_ubyte), # is the server password protected - ('mServerPort', ctypes.c_short), # the port of the server (if on a server) - ('mServerPublicIP', ctypes.c_int), # the public IP address of the server (if on a server) - ('mMaxPlayers', ctypes.c_int), # maximum number of vehicles that can be in the session - ('mServerName', ctypes.c_ubyte*32), # name of the server + ('mIsPasswordProtected', ctypes.c_bool), # is the server password protected + ('mServerPort', ctypes.c_ushort), # the port of the server (if on a server) + ('mServerPublicIP', ctypes.c_ulong), # the public IP address of the server (if on a server) + ('mMaxPlayers', ctypes.c_long), # maximum number of vehicles that can be in the session + ('mServerName', ctypes.c_char*32), # name of the server ('mStartET', ctypes.c_float), # start time (seconds since midnight) of the event ('mAvgPathWetness', ctypes.c_double), # average wetness on main path 0.0-1.0 ('mExpansion', ctypes.c_ubyte*200), @@ -300,12 +300,12 @@ class rF2ScoringInfo(ctypes.Structure): class rF2VehicleScoring(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mID', ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) - ('mDriverName', ctypes.c_ubyte*32), # driver name - ('mVehicleName', ctypes.c_ubyte*64), # vehicle name + ('mID', ctypes.c_long), # slot ID (note that it can be re-used in multiplayer after someone leaves) + ('mDriverName', ctypes.c_char*32), # driver name + ('mVehicleName', ctypes.c_char*64), # vehicle name ('mTotalLaps', ctypes.c_short), # laps completed - ('mSector', ctypes.c_ubyte), # 0=sector3, 1=sector1, 2 = sector2 (don't ask why) - ('mFinishStatus', ctypes.c_ubyte), # 0=none, 1=finished, 2=dnf, 3 = dq + ('mSector', ctypes.c_byte), # 0=sector3, 1=sector1, 2 = sector2 (don't ask why) + ('mFinishStatus', ctypes.c_byte), # 0=none, 1=finished, 2=dnf, 3 = dq ('mLapDist', ctypes.c_double), # current distance around track ('mPathLateral', ctypes.c_double), # lateral position with respect to *very approximate* "center" path ('mTrackEdge', ctypes.c_double), # track edge (w.r.t. "center" path) on same side of track as vehicle @@ -319,15 +319,15 @@ class rF2VehicleScoring(ctypes.Structure): ('mCurSector2', ctypes.c_double), # current sector 2 (plus sector 1) if valid ('mNumPitstops', ctypes.c_short), # number of pitstops made ('mNumPenalties', ctypes.c_short), # number of outstanding penalties - ('mIsPlayer', ctypes.c_ubyte), # is this the player's vehicle - ('mControl', ctypes.c_ubyte), # who's in control: -1=nobody (shouldn't get this), 0=local player, 1=local AI, 2=remote, 3 = replay (shouldn't get this) - ('mInPits', ctypes.c_ubyte), # between pit entrance and pit exit (not always accurate for remote vehicles) + ('mIsPlayer', ctypes.c_bool), # is this the player's vehicle + ('mControl', ctypes.c_byte), # who's in control: -1=nobody (shouldn't get this), 0=local player, 1=local AI, 2=remote, 3 = replay (shouldn't get this) + ('mInPits', ctypes.c_bool), # between pit entrance and pit exit (not always accurate for remote vehicles) ('mPlace', ctypes.c_ubyte), # 1-based position - ('mVehicleClass', ctypes.c_ubyte*32), # vehicle class + ('mVehicleClass', ctypes.c_char*32), # vehicle class ('mTimeBehindNext', ctypes.c_double), # time behind vehicle in next higher place - ('mLapsBehindNext', ctypes.c_int), # laps behind vehicle in next higher place + ('mLapsBehindNext', ctypes.c_long), # laps behind vehicle in next higher place ('mTimeBehindLeader', ctypes.c_double), # time behind leader - ('mLapsBehindLeader', ctypes.c_int), # laps behind leader + ('mLapsBehindLeader', ctypes.c_long), # laps behind leader ('mLapStartET', ctypes.c_double), # time this lap was started ('mPos', rF2Vec3), # world position in meters ('mLocalVel', rF2Vec3), # velocity (meters/sec) in local vehicle coordinates @@ -339,14 +339,14 @@ class rF2VehicleScoring(ctypes.Structure): ('mPitState', ctypes.c_ubyte), # 0=none, 1=request, 2=entering, 3=stopped, 4 = exiting ('mServerScored', ctypes.c_ubyte), # whether this vehicle is being scored by server (could be off in qualifying or racing heats) ('mIndividualPhase', ctypes.c_ubyte), # game phases (described below) plus 9=after formation, 10=under yellow, 11 = under blue (not used) - ('mQualification', ctypes.c_int), # 1-based, can be -1 when invalid + ('mQualification', ctypes.c_long), # 1-based, can be -1 when invalid ('mTimeIntoLap', ctypes.c_double), # estimated time into lap ('mEstimatedLapTime', ctypes.c_double), # estimated laptime used for 'time behind' and 'time into lap' (note: this may changed based on vehicle and setup!?) - ('mPitGroup', ctypes.c_ubyte*24), # pit group (same as team name unless pit is shared) + ('mPitGroup', ctypes.c_char*24), # pit group (same as team name unless pit is shared) ('mFlag', ctypes.c_ubyte), # primary flag being shown to vehicle (currently only 0=green or 6 = blue) - ('mUnderYellow', ctypes.c_ubyte), # whether this car has taken a full-course caution flag at the start/finish line + ('mUnderYellow', ctypes.c_bool), # whether this car has taken a full-course caution flag at the start/finish line ('mCountLapFlag', ctypes.c_ubyte), # 0 = do not count lap or time, 1 = count lap but not time, 2 = count lap and time - ('mInGarageStall', ctypes.c_ubyte), # appears to be within the correct garage stall + ('mInGarageStall', ctypes.c_bool), # appears to be within the correct garage stall ('mUpgradePack', ctypes.c_ubyte*16), # Coded upgrades ('mPitLapDist', ctypes.c_float), # location of pit in terms of lap distance ('mBestLapSector1', ctypes.c_float), # sector 1 time from best lap (not necessarily the best sector 1 time) @@ -402,7 +402,7 @@ class rF2TrackRulesAction(ctypes.Structure): _pack_ = 4 _fields_ = [ ('mCommand', ctypes.c_int), # recommended action - ('mID', ctypes.c_int), # slot ID if applicable + ('mID', ctypes.c_long), # slot ID if applicable ('mET', ctypes.c_double), # elapsed time that event occurred, if applicable ] class rF2TrackRulesColumn(Enum): @@ -420,19 +420,19 @@ class rF2TrackRulesColumn(Enum): class rF2TrackRulesParticipant(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mID', ctypes.c_int), # slot ID + ('mID', ctypes.c_long), # slot ID ('mFrozenOrder', ctypes.c_short), # 0-based place when caution came out (not valid for formation laps) ('mPlace', ctypes.c_short), # 1-based place (typically used for the initialization of the formation lap track order) ('mYellowSeverity', ctypes.c_float), # a rating of how much this vehicle is contributing to a yellow flag (the sum of all vehicles is compared to TrackRulesV01::mSafetyCarThreshold) ('mCurrentRelativeDistance', ctypes.c_double), # equal to ( ( ScoringInfoV01::mLapDist * this->mRelativeLaps ) + VehicleScoringInfoV01::mLapDist ) - ('mRelativeLaps', ctypes.c_int), # current formation/caution laps relative to safety car (should generally be zero except when safety car crosses s/f line); this can be decremented to implement 'wave around' or 'beneficiary rule' (a.k.a. 'lucky dog' or 'free pass') + ('mRelativeLaps', ctypes.c_long), # current formation/caution laps relative to safety car (should generally be zero except when safety car crosses s/f line); this can be decremented to implement 'wave around' or 'beneficiary rule' (a.k.a. 'lucky dog' or 'free pass') ('mColumnAssignment', ctypes.c_int), # which column (line/lane) that participant is supposed to be in - ('mPositionAssignment', ctypes.c_int), # 0-based position within column (line/lane) that participant is supposed to be located at (-1 is invalid) + ('mPositionAssignment', ctypes.c_long), # 0-based position within column (line/lane) that participant is supposed to be located at (-1 is invalid) ('mPitsOpen', ctypes.c_ubyte), # whether the rules allow this particular vehicle to enter pits right now (input is 2=false or 3=true; if you want to edit it, set to 0=false or 1 = true) - ('mUpToSpeed', ctypes.c_ubyte), # while in the frozen order, this flag indicates whether the vehicle can be followed (this should be false for somebody who has temporarily spun and hasn't gotten back up to speed yet) - ('mUnused', ctypes.c_ubyte*2), # + ('mUpToSpeed', ctypes.c_bool), # while in the frozen order, this flag indicates whether the vehicle can be followed (this should be false for somebody who has temporarily spun and hasn't gotten back up to speed yet) + ('mUnused', ctypes.c_bool*2), # ('mGoalRelativeDistance', ctypes.c_double), # calculated based on where the leader is, and adjusted by the desired column spacing and the column/position assignments - ('mMessage', ctypes.c_ubyte*96), # a message for this participant to explain what is going on it will get run through translator on client machines + ('mMessage', ctypes.c_char*96), # a message for this participant to explain what is going on it will get run through translator on client machines ('mExpansion', ctypes.c_ubyte*192), ] class rF2TrackRulesStage(Enum): @@ -449,23 +449,23 @@ class rF2TrackRules(ctypes.Structure): ('mCurrentET', ctypes.c_double), # current time ('mStage', ctypes.c_int), # current stage ('mPoleColumn', ctypes.c_int), # column assignment where pole position seems to be located - ('mNumActions', ctypes.c_int), # number of recent actions + ('mNumActions', ctypes.c_long), # number of recent actions ('pointer1', ctypes.c_ubyte*8), - ('mNumParticipants', ctypes.c_int), # number of participants (vehicles) - ('mYellowFlagDetected', ctypes.c_ubyte), # whether yellow flag was requested or sum of participant mYellowSeverity's exceeds mSafetyCarThreshold + ('mNumParticipants', ctypes.c_long), # number of participants (vehicles) + ('mYellowFlagDetected', ctypes.c_bool), # whether yellow flag was requested or sum of participant mYellowSeverity's exceeds mSafetyCarThreshold ('mYellowFlagLapsWasOverridden', ctypes.c_ubyte), # whether mYellowFlagLaps (below) is an admin request (0=no 1=yes 2 = clear yellow) - ('mSafetyCarExists', ctypes.c_ubyte), # whether safety car even exists - ('mSafetyCarActive', ctypes.c_ubyte), # whether safety car is active - ('mSafetyCarLaps', ctypes.c_int), # number of laps + ('mSafetyCarExists', ctypes.c_bool), # whether safety car even exists + ('mSafetyCarActive', ctypes.c_bool), # whether safety car is active + ('mSafetyCarLaps', ctypes.c_long), # number of laps ('mSafetyCarThreshold', ctypes.c_float), # the threshold at which a safety car is called out (compared to the sum of TrackRulesParticipantV01::mYellowSeverity for each vehicle) ('mSafetyCarLapDist', ctypes.c_double), # safety car lap distance ('mSafetyCarLapDistAtStart', ctypes.c_float), # where the safety car starts from ('mPitLaneStartDist', ctypes.c_float), # where the waypoint branch to the pits breaks off (this may not be perfectly accurate) ('mTeleportLapDist', ctypes.c_float), # the front of the teleport locations (a useful first guess as to where to throw the green flag) ('mInputExpansion', ctypes.c_ubyte*256), - ('mYellowFlagState', ctypes.c_ubyte), # see ScoringInfoV01 for values + ('mYellowFlagState', ctypes.c_byte), # see ScoringInfoV01 for values ('mYellowFlagLaps', ctypes.c_short), # suggested number of laps to run under yellow (may be passed in with admin command) - ('mSafetyCarInstruction', ctypes.c_int), # 0=no change, 1=go active, 2 = head for pits + ('mSafetyCarInstruction', ctypes.c_long), # 0=no change, 1=go active, 2 = head for pits ('mSafetyCarSpeed', ctypes.c_float), # maximum speed at which to drive ('mSafetyCarMinimumSpacing', ctypes.c_float), # minimum spacing behind safety car (-1 to indicate no limit) ('mSafetyCarMaximumSpacing', ctypes.c_float), # maximum spacing behind safety car (-1 to indicate no limit) @@ -473,7 +473,7 @@ class rF2TrackRules(ctypes.Structure): ('mMaximumColumnSpacing', ctypes.c_float), # maximum desired spacing between vehicles in a column (-1 to indicate indeterminate/unenforced) ('mMinimumSpeed', ctypes.c_float), # minimum speed that anybody should be driving (-1 to indicate no limit) ('mMaximumSpeed', ctypes.c_float), # maximum speed that anybody should be driving (-1 to indicate no limit) - ('mMessage', ctypes.c_ubyte*96), # a message for everybody to explain what is going on (which will get run through translator on client machines) + ('mMessage', ctypes.c_char*96), # a message for everybody to explain what is going on (which will get run through translator on client machines) ('pointer2', ctypes.c_ubyte*8), ('mInputOutputExpansion', ctypes.c_ubyte*256), ] @@ -481,11 +481,11 @@ class rF2TrackRules(ctypes.Structure): class rF2PitMenu(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mCategoryIndex', ctypes.c_int), # index of the current category - ('mCategoryName', ctypes.c_ubyte*32), # name of the current category (untranslated) - ('mChoiceIndex', ctypes.c_int), # index of the current choice (within the current category) - ('mChoiceString', ctypes.c_ubyte*32), # name of the current choice (may have some translated words) - ('mNumChoices', ctypes.c_int), # total number of choices (0 < = mChoiceIndex < mNumChoices) + ('mCategoryIndex', ctypes.c_long), # index of the current category + ('mCategoryName', ctypes.c_char*32), # name of the current category (untranslated) + ('mChoiceIndex', ctypes.c_long), # index of the current choice (within the current category) + ('mChoiceString', ctypes.c_char*32), # name of the current choice (may have some translated words) + ('mNumChoices', ctypes.c_long), # total number of choices (0 < = mChoiceIndex < mNumChoices) ('mExpansion', ctypes.c_ubyte*256), # for future use ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] @@ -497,43 +497,43 @@ class rF2WeatherControlInfo(ctypes.Structure): ('mCloudiness', ctypes.c_double), # general cloudiness (0.0=clear to 1.0 = dark) ('mAmbientTempK', ctypes.c_double), # ambient temperature (Kelvin) ('mWindMaxSpeed', ctypes.c_double), # maximum speed of wind (ground speed, but it affects how fast the clouds move, too) - ('mApplyCloudinessInstantly', ctypes.c_ubyte), # preferably we roll the new clouds in, but you can instantly change them now - ('mUnused1', ctypes.c_ubyte), # - ('mUnused2', ctypes.c_ubyte), # - ('mUnused3', ctypes.c_ubyte), # + ('mApplyCloudinessInstantly', ctypes.c_bool), # preferably we roll the new clouds in, but you can instantly change them now + ('mUnused1', ctypes.c_bool), # + ('mUnused2', ctypes.c_bool), # + ('mUnused3', ctypes.c_bool), # ('mExpansion', ctypes.c_ubyte*508), # future use (humidity, pressure, air density, etc.) ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2MappedBufferVersionBlock(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2MappedBufferVersionBlockWithSize(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Telemetry(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. - ('mNumVehicles', ctypes.c_int), # current number of vehicles + ('mNumVehicles', ctypes.c_long), # current number of vehicles ('mVehicles', rF2VehicleTelemetry*rFactor2Constants.MAX_MAPPED_VEHICLES), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Scoring(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. ('mScoringInfo', rF2ScoringInfo), ('mVehicles', rF2VehicleScoring*rFactor2Constants.MAX_MAPPED_VEHICLES), @@ -542,8 +542,8 @@ class rF2Scoring(ctypes.Structure): class rF2Rules(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. ('mTrackRules', rF2TrackRules), ('mActions', rF2TrackRulesAction*rFactor2Constants.MAX_MAPPED_VEHICLES), @@ -553,8 +553,8 @@ class rF2Rules(ctypes.Structure): class rF2ForceFeedback(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. ('mForceValue', ctypes.c_double), # Current FFB value reported via InternalsPlugin::ForceFeedback. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] @@ -567,32 +567,32 @@ class rF2GraphicsInfo(ctypes.Structure): ('mAmbientRed', ctypes.c_double), ('mAmbientGreen', ctypes.c_double), ('mAmbientBlue', ctypes.c_double), - ('mID', ctypes.c_int), # slot ID being viewed (-1 if invalid) - ('mCameraType', ctypes.c_int), # see above comments for possible values + ('mID', ctypes.c_long), # slot ID being viewed (-1 if invalid) + ('mCameraType', ctypes.c_long), # see above comments for possible values ('mExpansion', ctypes.c_ubyte*128), # for future use (possibly camera name) ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Graphics(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. ('mGraphicsInfo', rF2GraphicsInfo), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2PitInfo(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. ('mPitMneu', rF2PitMenu), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Weather(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. ('mTrackNodeSize', ctypes.c_double), ('mWeatherInfo', rF2WeatherControlInfo), ] @@ -607,75 +607,75 @@ class rF2TrackedDamage(ctypes.Structure): class rF2VehScoringCapture(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mID', ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) + ('mID', ctypes.c_long), # slot ID (note that it can be re-used in multiplayer after someone leaves) ('mPlace', ctypes.c_ubyte), - ('mIsPlayer', ctypes.c_ubyte), - ('mFinishStatus', ctypes.c_ubyte), # 0=none, 1=finished, 2=dnf, 3 = dq + ('mIsPlayer', ctypes.c_bool), + ('mFinishStatus', ctypes.c_byte), # 0=none, 1=finished, 2=dnf, 3 = dq ] #untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] class rF2SessionTransitionCapture(ctypes.Structure): _pack_ = 4 _fields_ = [ ('mGamePhase', ctypes.c_ubyte), - ('mSession', ctypes.c_int), - ('mNumScoringVehicles', ctypes.c_int), + ('mSession', ctypes.c_long), + ('mNumScoringVehicles', ctypes.c_long), ('mScoringVehicles', rF2VehScoringCapture*rFactor2Constants.MAX_MAPPED_VEHICLES), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Extended(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. - ('mVersion', ctypes.c_ubyte*12), # API version - ('is64bit', ctypes.c_ubyte), # Is 64bit plugin? + ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. + ('mVersion', ctypes.c_char*12), # API version + ('is64bit', ctypes.c_bool), # Is 64bit plugin? ('mPhysics', rF2PhysicsOptions), ('mTrackedDamages', rF2TrackedDamage*rFactor2Constants.MAX_MAPPED_IDS), - ('mInRealtimeFC', ctypes.c_ubyte), # in realtime as opposed to at the monitor (reported via last EnterRealtime/ExitRealtime calls). - ('mMultimediaThreadStarted', ctypes.c_ubyte), # multimedia thread started (reported via ThreadStarted/ThreadStopped calls). - ('mSimulationThreadStarted', ctypes.c_ubyte), # simulation thread started (reported via ThreadStarted/ThreadStopped calls). - ('mSessionStarted', ctypes.c_ubyte), # Set to true on Session Started, set to false on Session Ended. - ('mTicksSessionStarted', ctypes.c_double), # Ticks when session started. - ('mTicksSessionEnded', ctypes.c_double), # Ticks when session ended. + ('mInRealtimeFC', ctypes.c_bool), # in realtime as opposed to at the monitor (reported via last EnterRealtime/ExitRealtime calls). + ('mMultimediaThreadStarted', ctypes.c_bool), # multimedia thread started (reported via ThreadStarted/ThreadStopped calls). + ('mSimulationThreadStarted', ctypes.c_bool), # simulation thread started (reported via ThreadStarted/ThreadStopped calls). + ('mSessionStarted', ctypes.c_bool), # Set to true on Session Started, set to false on Session Ended. + ('mTicksSessionStarted', ctypes.c_ulonglong), # Ticks when session started. + ('mTicksSessionEnded', ctypes.c_ulonglong), # Ticks when session ended. ('mSessionTransitionCapture', rF2SessionTransitionCapture),# Contains partial internals capture at session transition time. - ('mDisplayedMessageUpdateCapture', ctypes.c_ubyte*128), - ('mDirectMemoryAccessEnabled', ctypes.c_ubyte), - ('mTicksStatusMessageUpdated', ctypes.c_double), # Ticks when status message was updated; - ('mStatusMessage', ctypes.c_ubyte*rFactor2Constants.MAX_STATUS_MSG_LEN), - ('mTicksLastHistoryMessageUpdated', ctypes.c_double), # Ticks when last message history message was updated; - ('mLastHistoryMessage', ctypes.c_ubyte*rFactor2Constants.MAX_STATUS_MSG_LEN), + ('mDisplayedMessageUpdateCapture', ctypes.c_char*128), + ('mDirectMemoryAccessEnabled', ctypes.c_bool), + ('mTicksStatusMessageUpdated', ctypes.c_ulonglong), # Ticks when status message was updated; + ('mStatusMessage', ctypes.c_char*rFactor2Constants.MAX_STATUS_MSG_LEN), + ('mTicksLastHistoryMessageUpdated', ctypes.c_ulonglong), # Ticks when last message history message was updated; + ('mLastHistoryMessage', ctypes.c_char*rFactor2Constants.MAX_STATUS_MSG_LEN), ('mCurrentPitSpeedLimit', ctypes.c_float), # speed limit m/s. - ('mSCRPluginEnabled', ctypes.c_ubyte), # Is Stock Car Rules plugin enabled? - ('mSCRPluginDoubleFileType', ctypes.c_int), # Stock Car Rules plugin DoubleFileType value, only meaningful if mSCRPluginEnabled is true. - ('mTicksLSIPhaseMessageUpdated', ctypes.c_double), # Ticks when last LSI phase message was updated. - ('mLSIPhaseMessage', ctypes.c_ubyte*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mTicksLSIPitStateMessageUpdated', ctypes.c_double), # Ticks when last LSI pit state message was updated. - ('mLSIPitStateMessage', ctypes.c_ubyte*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mTicksLSIOrderInstructionMessageUpdated', ctypes.c_double), # Ticks when last LSI order instruction message was updated. - ('mLSIOrderInstructionMessage', ctypes.c_ubyte*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mTicksLSIRulesInstructionMessageUpdated', ctypes.c_double), # Ticks when last FCY rules message was updated. Currently, only SCR plugin sets that. - ('mLSIRulesInstructionMessage', ctypes.c_ubyte*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mUnsubscribedBuffersMask', ctypes.c_int), # Currently active UnsbscribedBuffersMask value. This will be allowed for clients to write to in the future, but not yet. - ('mHWControlInputEnabled', ctypes.c_ubyte), # HWControl input buffer is enabled. - ('mWeatherControlInputEnabled', ctypes.c_ubyte), # WeatherControl input buffer is enabled. - ('mRulesControlInputEnabled', ctypes.c_ubyte), # RulesControl input buffer is enabled. + ('mSCRPluginEnabled', ctypes.c_bool), # Is Stock Car Rules plugin enabled? + ('mSCRPluginDoubleFileType', ctypes.c_long), # Stock Car Rules plugin DoubleFileType value, only meaningful if mSCRPluginEnabled is true. + ('mTicksLSIPhaseMessageUpdated', ctypes.c_ulonglong), # Ticks when last LSI phase message was updated. + ('mLSIPhaseMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ('mTicksLSIPitStateMessageUpdated', ctypes.c_ulonglong), # Ticks when last LSI pit state message was updated. + ('mLSIPitStateMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ('mTicksLSIOrderInstructionMessageUpdated', ctypes.c_ulonglong), # Ticks when last LSI order instruction message was updated. + ('mLSIOrderInstructionMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ('mTicksLSIRulesInstructionMessageUpdated', ctypes.c_ulonglong), # Ticks when last FCY rules message was updated. Currently, only SCR plugin sets that. + ('mLSIRulesInstructionMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ('mUnsubscribedBuffersMask', ctypes.c_long), # Currently active UnsbscribedBuffersMask value. This will be allowed for clients to write to in the future, but not yet. + ('mHWControlInputEnabled', ctypes.c_bool), # HWControl input buffer is enabled. + ('mWeatherControlInputEnabled', ctypes.c_bool), # WeatherControl input buffer is enabled. + ('mRulesControlInputEnabled', ctypes.c_bool), # RulesControl input buffer is enabled. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2HWControl(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. ('mLayoutVersion', ctypes.c_int), - ('mControlName', ctypes.c_ubyte*rFactor2Constants.MAX_HWCONTROL_NAME_LEN), + ('mControlName', ctypes.c_char*rFactor2Constants.MAX_HWCONTROL_NAME_LEN), ('mfRetVal', ctypes.c_double), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2WeatherControl(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. ('mLayoutVersion', ctypes.c_int), ('mWeatherInfo', rF2WeatherControlInfo), ] From 05e497448762d3b65550d20781146b9feb83d801 Mon Sep 17 00:00:00 2001 From: Xiang Date: Mon, 6 Mar 2023 13:43:53 +0800 Subject: [PATCH 33/93] Reverted back to mIsPlayer finding method, removed unused code. --- sim_info_sync.py | 99 ++++++++++++------------------------------------ 1 file changed, 25 insertions(+), 74 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 726a4bb..701563e 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -31,7 +31,7 @@ class SimInfoSync(): def __init__(self, input_pid=""): self.players_index = 99 - self.players_mid = 0 + self.players_mid = 99 self.data_updating = False self.stopped = True self._input_pid = input_pid @@ -41,23 +41,13 @@ def __init__(self, input_pid=""): self._rf2_ext = None self._rf2_ffb = None - self.Rf2Tele = None # raw data - self.Rf2Scor = None - self.Rf2Ext = None - self.Rf2Ffb = None - - self.DefTele = None # default copy of raw data - self.DefScor = None - self.DefExt = None - self.DefFfb = None - self.LastTele = None # synced copy of raw data self.LastScor = None self.LastExt = None self.LastFfb = None self.start_mmap() - self.set_default_mmap() + self.copy_mmap() print("sharedmemory mapping started") def start_mmap(self): @@ -81,22 +71,12 @@ def start_mmap(self): ffb_file = open("/dev/shm/$rFactor2SMMP_ForceFeedback$", "r+") self._rf2_ffb = mmap.mmap(ffb_file.fileno(), ctypes.sizeof(rF2data.rF2ForceFeedback)) - self.Rf2Tele = rF2data.rF2Telemetry.from_buffer(self._rf2_tele) - self.Rf2Scor = rF2data.rF2Scoring.from_buffer(self._rf2_scor) - self.Rf2Ext = rF2data.rF2Extended.from_buffer(self._rf2_ext) - self.Rf2Ffb = rF2data.rF2ForceFeedback.from_buffer(self._rf2_ffb) - - self.DefTele = copy.deepcopy(self.Rf2Tele) - self.DefScor = copy.deepcopy(self.Rf2Scor) - self.DefExt = copy.deepcopy(self.Rf2Ext) - self.DefFfb = copy.deepcopy(self.Rf2Ffb) - - def set_default_mmap(self): - """ Set default memory mapping data """ - self.LastTele = copy.deepcopy(self.DefTele) - self.LastScor = copy.deepcopy(self.DefScor) - self.LastExt = copy.deepcopy(self.DefExt) - self.LastFfb = copy.deepcopy(self.DefFfb) + def copy_mmap(self): + """ Copy memory mapping data """ + self.LastTele = rF2data.rF2Telemetry.from_buffer_copy(self._rf2_tele) + self.LastScor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) + self.LastExt = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) + self.LastFfb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) def reset_mmap(self): """ Reset memory mapping """ @@ -107,11 +87,6 @@ def close(self): """ Close memory mapping """ # This didn't help with the errors try: - # Unassign those objects first - self.Rf2Tele = None - self.Rf2Scor = None - self.Rf2Ext = None - self.Rf2Ffb = None # Close shared memory mapping self._rf2_tele.close() self._rf2_scor.close() @@ -127,10 +102,8 @@ def close(self): def __playerVerified(self, data): """ Check player index number on one same data piece """ - plr_name = Cbytestring2Python(data.mScoringInfo.mPlayerName) for index in range(MAX_VEHICLES): - # Use 1 to avoid reading incorrect value - if Cbytestring2Python(data.mVehicles[index].mDriverName) == plr_name: + if data.mVehicles[index].mIsPlayer: self.players_index = index return True return False # return false if failed to find player index @@ -144,62 +117,40 @@ def __infoUpdate(self): """ Update synced player data """ last_version_update = 0 # store last data version update re_version_update = 0 # store restarted data version update - mmap_restarted = True # whether has restarted memory mapping + data_freezed = True # whether data is freezed check_counter = 0 # counter for data version update check - restore_counter = 71 # counter for restoring mmap data to default while self.data_updating: - data_scor = copy.deepcopy(self.Rf2Scor) # use deepcopy to avoid data interruption - data_tele = copy.deepcopy(self.Rf2Tele) - self.LastExt = copy.deepcopy(self.Rf2Ext) - self.LastFfb = copy.deepcopy(self.Rf2Ffb) + data_scor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) # use deepcopy to avoid data interruption + data_tele = rF2data.rF2Telemetry.from_buffer_copy(self._rf2_tele) + self.LastExt = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) + self.LastFfb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) # Only update if data verified and player index found - if self.dataVerified(data_scor) and self.__playerVerified(data_scor): + if not data_freezed and self.__playerVerified(data_scor): self.LastScor = copy.deepcopy(data_scor) # synced scoring self.players_mid = self.LastScor.mVehicles[self.players_index].mID # update player mID # Only update if data verified and player mID matches - if (self.dataVerified(data_tele) and - data_tele.mVehicles[self.players_index].mID == self.players_mid): + if data_tele.mVehicles[self.players_index].mID == self.players_mid: self.LastTele = copy.deepcopy(data_tele) # synced telemetry # Start checking data version update status check_counter += 1 - if check_counter > 70: # active after around 1 seconds - if (not mmap_restarted and last_version_update > 0 - and last_version_update == self.LastScor.mVersionUpdateEnd): + if check_counter > 200: # active after around 1 seconds + if (not data_freezed and last_version_update > 0 + and last_version_update == data_scor.mVersionUpdateEnd): self.reset_mmap() - mmap_restarted = True - re_version_update = self.LastScor.mVersionUpdateEnd + data_freezed = True + re_version_update = data_scor.mVersionUpdateEnd + self.LastTele.mVehicles[self.players_index].mIgnitionStarter = 0 print(f"sharedmemory mapping restarted - version:{last_version_update}") - last_version_update = self.LastScor.mVersionUpdateEnd + last_version_update = data_scor.mVersionUpdateEnd check_counter = 0 # reset counter - if mmap_restarted: - if re_version_update != self.LastScor.mVersionUpdateEnd: - mmap_restarted = False - restore_counter = 0 # reset counter - elif restore_counter < 71: - restore_counter += 1 - - if restore_counter == 70: # active after around 1 seconds - self.set_default_mmap() - print("sharedmemory mapping data reset to default") - - if re_version_update != 0 and data_scor.mVersionUpdateEnd != re_version_update: - re_version_update = 0 - self.reset_mmap() - print(f"sharedmemory mapping re-restarted - version:{last_version_update}") - - #print(f"c1:{check_counter:03.0f} " - # f"c2:{restore_counter:03.0f} " - # f"idx:{self.players_index:03.0f} " - # f"now:{self.LastScor.mVersionUpdateEnd:07.0f} " - # f"last:{last_version_update:07.0f} " - # f"re:{re_version_update:07.0f} " - # f"{mmap_restarted}", end="\r") + if data_freezed and re_version_update != data_scor.mVersionUpdateEnd: + data_freezed = False time.sleep(0.01) From fa3fd46dc58274a6f5305cde7fee67329789144a Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 8 Mar 2023 12:28:01 +0800 Subject: [PATCH 34/93] fixed data freeze issue that caused by mismatched player's index between rF2Scoring & rF2Telemetry --- sim_info_sync.py | 56 +++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 701563e..0212259 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -17,9 +17,7 @@ except ImportError: # standalone, not package import rF2data -from .sharedMemoryAPI import Cbytestring2Python - -MAX_VEHICLES = 128 # max 128 players supported by API +MAX_VEHICLES = rF2data.rFactor2Constants.MAX_MAPPED_VEHICLES class SimInfoSync(): @@ -30,8 +28,8 @@ class SimInfoSync(): """ def __init__(self, input_pid=""): - self.players_index = 99 - self.players_mid = 99 + self.players_scor_index = 99 + self.players_tele_index = 99 self.data_updating = False self.stopped = True self._input_pid = input_pid @@ -95,23 +93,25 @@ def close(self): print("sharedmemory mapping closed") except BufferError: # "cannot close exported pointers exist" print("BufferError") - pass ########################################################### # Sync data for local player - def __playerVerified(self, data): - """ Check player index number on one same data piece """ + @staticmethod + def __find_local_player_index_scor(data_scor): + """ Find player index in rF2Scoring """ for index in range(MAX_VEHICLES): - if data.mVehicles[index].mIsPlayer: - self.players_index = index - return True - return False # return false if failed to find player index + if data_scor.mVehicles[index].mIsPlayer: + return index + return 0 - @staticmethod - def dataVerified(data): - """ Verify data """ - return data.mVersionUpdateEnd == data.mVersionUpdateBegin + def find_player_index_tele(self, index_scor): + """ Find player index in rF2Telemetry using mID from rF2Scoring """ + scor_mid = self.LastScor.mVehicles[index_scor].mID + for index in range(MAX_VEHICLES): + if self.LastTele.mVehicles[index].mID == scor_mid: + return index + return 0 def __infoUpdate(self): """ Update synced player data """ @@ -121,30 +121,28 @@ def __infoUpdate(self): check_counter = 0 # counter for data version update check while self.data_updating: - data_scor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) # use deepcopy to avoid data interruption + data_scor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) data_tele = rF2data.rF2Telemetry.from_buffer_copy(self._rf2_tele) self.LastExt = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) self.LastFfb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) - # Only update if data verified and player index found - if not data_freezed and self.__playerVerified(data_scor): - self.LastScor = copy.deepcopy(data_scor) # synced scoring - self.players_mid = self.LastScor.mVehicles[self.players_index].mID # update player mID - - # Only update if data verified and player mID matches - if data_tele.mVehicles[self.players_index].mID == self.players_mid: - self.LastTele = copy.deepcopy(data_tele) # synced telemetry + # Update player index + if not data_freezed: + self.players_scor_index = self.__find_local_player_index_scor(data_scor) + self.LastScor = copy.deepcopy(data_scor) + self.players_tele_index = self.find_player_index_tele(self.players_scor_index) + self.LastTele = copy.deepcopy(data_tele) # Start checking data version update status check_counter += 1 - if check_counter > 200: # active after around 1 seconds + if check_counter > 330: # active after around 1 seconds if (not data_freezed and last_version_update > 0 and last_version_update == data_scor.mVersionUpdateEnd): self.reset_mmap() data_freezed = True re_version_update = data_scor.mVersionUpdateEnd - self.LastTele.mVehicles[self.players_index].mIgnitionStarter = 0 + self.syncedVehicleTelemetry().mIgnitionStarter = 0 print(f"sharedmemory mapping restarted - version:{last_version_update}") last_version_update = data_scor.mVersionUpdateEnd check_counter = 0 # reset counter @@ -174,11 +172,11 @@ def stopUpdating(self): def syncedVehicleTelemetry(self): """ Get the variable for the player's vehicle """ - return self.LastTele.mVehicles[self.players_index] + return self.LastTele.mVehicles[self.players_tele_index] def syncedVehicleScoring(self): """ Get the variable for the player's vehicle """ - return self.LastScor.mVehicles[self.players_index] + return self.LastScor.mVehicles[self.players_scor_index] ########################################################### From fee6fe34337073f705778e7f2559ae577c8d6447 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sat, 1 Apr 2023 18:44:55 +0800 Subject: [PATCH 35/93] fallback to index 99 if not found --- sim_info_sync.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 0212259..a911832 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -103,7 +103,7 @@ def __find_local_player_index_scor(data_scor): for index in range(MAX_VEHICLES): if data_scor.mVehicles[index].mIsPlayer: return index - return 0 + return 99 def find_player_index_tele(self, index_scor): """ Find player index in rF2Telemetry using mID from rF2Scoring """ @@ -111,7 +111,7 @@ def find_player_index_tele(self, index_scor): for index in range(MAX_VEHICLES): if self.LastTele.mVehicles[index].mID == scor_mid: return index - return 0 + return 99 def __infoUpdate(self): """ Update synced player data """ From 76a9b540109fd0d8b76762901d3c556337406ff0 Mon Sep 17 00:00:00 2001 From: Bernat Arlandis Date: Sun, 2 Apr 2023 00:23:56 +0200 Subject: [PATCH 36/93] Make ctypes compatible with Linux --- rF2data.py | 112 ++++++++++++++++++++++++++--------------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/rF2data.py b/rF2data.py index 2ab1e92..04a3c5c 100644 --- a/rF2data.py +++ b/rF2data.py @@ -189,10 +189,10 @@ class rF2Wheel(ctypes.Structure): class rF2VehicleTelemetry(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mID', ctypes.c_long), # slot ID (note that it can be re-used in multiplayer after someone leaves) + ('mID', ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) ('mDeltaTime', ctypes.c_double), # time since last update (seconds) ('mElapsedTime', ctypes.c_double), # game session time - ('mLapNumber', ctypes.c_long), # current lap number + ('mLapNumber', ctypes.c_int), # current lap number ('mLapStartET', ctypes.c_double), # time this lap was started ('mVehicleName', ctypes.c_char*64), # current vehicle name ('mTrackName', ctypes.c_char*64), # current track name @@ -202,7 +202,7 @@ class rF2VehicleTelemetry(ctypes.Structure): ('mOri', rF2Vec3*3), # rows of orientation matrix (use TelemQuat conversions if desired), also converts local ('mLocalRot', rF2Vec3), # rotation (radians/sec) in local vehicle coordinates ('mLocalRotAccel', rF2Vec3), # rotational acceleration (radians/sec^2) in local vehicle coordinates - ('mGear', ctypes.c_long), # -1=reverse, 0=neutral, 1+ = forward gears + ('mGear', ctypes.c_int), # -1=reverse, 0=neutral, 1+ = forward gears ('mEngineRPM', ctypes.c_double), # engine RPM ('mEngineWaterTemp', ctypes.c_double), # Celsius ('mEngineOilTemp', ctypes.c_double), # Celsius @@ -235,7 +235,7 @@ class rF2VehicleTelemetry(ctypes.Structure): ('mLastImpactMagnitude', ctypes.c_double), # magnitude of last impact ('mLastImpactPos', rF2Vec3), # location of last impact ('mEngineTorque', ctypes.c_double), # current engine torque (including additive torque) (used to be mEngineTq, but there's little reason to abbreviate it) - ('mCurrentSector', ctypes.c_long), # the current sector (zero-based) with the pitlane stored in the sign bit (example: entering pits from third sector gives 0x80000002) + ('mCurrentSector', ctypes.c_int), # the current sector (zero-based) with the pitlane stored in the sign bit (example: entering pits from third sector gives 0x80000002) ('mSpeedLimiter', ctypes.c_ubyte), # whether speed limiter is on ('mMaxGears', ctypes.c_ubyte), # maximum forward gears ('mFrontTireCompoundIndex', ctypes.c_ubyte), # index within brand @@ -263,13 +263,13 @@ class rF2ScoringInfo(ctypes.Structure): _pack_ = 4 _fields_ = [ ('mTrackName', ctypes.c_char*64), # current track name - ('mSession', ctypes.c_long), # current session (0=testday 1-4=practice 5-8=qual 9=warmup 10-13 = race) + ('mSession', ctypes.c_int), # current session (0=testday 1-4=practice 5-8=qual 9=warmup 10-13 = race) ('mCurrentET', ctypes.c_double), # current time ('mEndET', ctypes.c_double), # ending time - ('mMaxLaps', ctypes.c_long), # maximum laps + ('mMaxLaps', ctypes.c_int), # maximum laps ('mLapDist', ctypes.c_double), # distance around track ('pointer1', ctypes.c_ubyte*8), - ('mNumVehicles', ctypes.c_long), # current number of vehicles + ('mNumVehicles', ctypes.c_int), # current number of vehicles ('mGamePhase', ctypes.c_ubyte), ('mYellowFlagState', ctypes.c_char), ('mSectorFlag', ctypes.c_char*3), # whether there are any local yellows at the moment in each sector (not sure if sector 0 is first or last, so test) @@ -288,8 +288,8 @@ class rF2ScoringInfo(ctypes.Structure): ('mGameMode', ctypes.c_ubyte), # 1 = server, 2 = client, 3 = server and client ('mIsPasswordProtected', ctypes.c_bool), # is the server password protected ('mServerPort', ctypes.c_ushort), # the port of the server (if on a server) - ('mServerPublicIP', ctypes.c_ulong), # the public IP address of the server (if on a server) - ('mMaxPlayers', ctypes.c_long), # maximum number of vehicles that can be in the session + ('mServerPublicIP', ctypes.c_uint), # the public IP address of the server (if on a server) + ('mMaxPlayers', ctypes.c_int), # maximum number of vehicles that can be in the session ('mServerName', ctypes.c_char*32), # name of the server ('mStartET', ctypes.c_float), # start time (seconds since midnight) of the event ('mAvgPathWetness', ctypes.c_double), # average wetness on main path 0.0-1.0 @@ -300,7 +300,7 @@ class rF2ScoringInfo(ctypes.Structure): class rF2VehicleScoring(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mID', ctypes.c_long), # slot ID (note that it can be re-used in multiplayer after someone leaves) + ('mID', ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) ('mDriverName', ctypes.c_char*32), # driver name ('mVehicleName', ctypes.c_char*64), # vehicle name ('mTotalLaps', ctypes.c_short), # laps completed @@ -325,9 +325,9 @@ class rF2VehicleScoring(ctypes.Structure): ('mPlace', ctypes.c_ubyte), # 1-based position ('mVehicleClass', ctypes.c_char*32), # vehicle class ('mTimeBehindNext', ctypes.c_double), # time behind vehicle in next higher place - ('mLapsBehindNext', ctypes.c_long), # laps behind vehicle in next higher place + ('mLapsBehindNext', ctypes.c_int), # laps behind vehicle in next higher place ('mTimeBehindLeader', ctypes.c_double), # time behind leader - ('mLapsBehindLeader', ctypes.c_long), # laps behind leader + ('mLapsBehindLeader', ctypes.c_int), # laps behind leader ('mLapStartET', ctypes.c_double), # time this lap was started ('mPos', rF2Vec3), # world position in meters ('mLocalVel', rF2Vec3), # velocity (meters/sec) in local vehicle coordinates @@ -339,7 +339,7 @@ class rF2VehicleScoring(ctypes.Structure): ('mPitState', ctypes.c_ubyte), # 0=none, 1=request, 2=entering, 3=stopped, 4 = exiting ('mServerScored', ctypes.c_ubyte), # whether this vehicle is being scored by server (could be off in qualifying or racing heats) ('mIndividualPhase', ctypes.c_ubyte), # game phases (described below) plus 9=after formation, 10=under yellow, 11 = under blue (not used) - ('mQualification', ctypes.c_long), # 1-based, can be -1 when invalid + ('mQualification', ctypes.c_int), # 1-based, can be -1 when invalid ('mTimeIntoLap', ctypes.c_double), # estimated time into lap ('mEstimatedLapTime', ctypes.c_double), # estimated laptime used for 'time behind' and 'time into lap' (note: this may changed based on vehicle and setup!?) ('mPitGroup', ctypes.c_char*24), # pit group (same as team name unless pit is shared) @@ -402,7 +402,7 @@ class rF2TrackRulesAction(ctypes.Structure): _pack_ = 4 _fields_ = [ ('mCommand', ctypes.c_int), # recommended action - ('mID', ctypes.c_long), # slot ID if applicable + ('mID', ctypes.c_int), # slot ID if applicable ('mET', ctypes.c_double), # elapsed time that event occurred, if applicable ] class rF2TrackRulesColumn(Enum): @@ -420,14 +420,14 @@ class rF2TrackRulesColumn(Enum): class rF2TrackRulesParticipant(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mID', ctypes.c_long), # slot ID + ('mID', ctypes.c_int), # slot ID ('mFrozenOrder', ctypes.c_short), # 0-based place when caution came out (not valid for formation laps) ('mPlace', ctypes.c_short), # 1-based place (typically used for the initialization of the formation lap track order) ('mYellowSeverity', ctypes.c_float), # a rating of how much this vehicle is contributing to a yellow flag (the sum of all vehicles is compared to TrackRulesV01::mSafetyCarThreshold) ('mCurrentRelativeDistance', ctypes.c_double), # equal to ( ( ScoringInfoV01::mLapDist * this->mRelativeLaps ) + VehicleScoringInfoV01::mLapDist ) - ('mRelativeLaps', ctypes.c_long), # current formation/caution laps relative to safety car (should generally be zero except when safety car crosses s/f line); this can be decremented to implement 'wave around' or 'beneficiary rule' (a.k.a. 'lucky dog' or 'free pass') + ('mRelativeLaps', ctypes.c_int), # current formation/caution laps relative to safety car (should generally be zero except when safety car crosses s/f line); this can be decremented to implement 'wave around' or 'beneficiary rule' (a.k.a. 'lucky dog' or 'free pass') ('mColumnAssignment', ctypes.c_int), # which column (line/lane) that participant is supposed to be in - ('mPositionAssignment', ctypes.c_long), # 0-based position within column (line/lane) that participant is supposed to be located at (-1 is invalid) + ('mPositionAssignment', ctypes.c_int), # 0-based position within column (line/lane) that participant is supposed to be located at (-1 is invalid) ('mPitsOpen', ctypes.c_ubyte), # whether the rules allow this particular vehicle to enter pits right now (input is 2=false or 3=true; if you want to edit it, set to 0=false or 1 = true) ('mUpToSpeed', ctypes.c_bool), # while in the frozen order, this flag indicates whether the vehicle can be followed (this should be false for somebody who has temporarily spun and hasn't gotten back up to speed yet) ('mUnused', ctypes.c_bool*2), # @@ -449,14 +449,14 @@ class rF2TrackRules(ctypes.Structure): ('mCurrentET', ctypes.c_double), # current time ('mStage', ctypes.c_int), # current stage ('mPoleColumn', ctypes.c_int), # column assignment where pole position seems to be located - ('mNumActions', ctypes.c_long), # number of recent actions + ('mNumActions', ctypes.c_int), # number of recent actions ('pointer1', ctypes.c_ubyte*8), - ('mNumParticipants', ctypes.c_long), # number of participants (vehicles) + ('mNumParticipants', ctypes.c_int), # number of participants (vehicles) ('mYellowFlagDetected', ctypes.c_bool), # whether yellow flag was requested or sum of participant mYellowSeverity's exceeds mSafetyCarThreshold ('mYellowFlagLapsWasOverridden', ctypes.c_ubyte), # whether mYellowFlagLaps (below) is an admin request (0=no 1=yes 2 = clear yellow) ('mSafetyCarExists', ctypes.c_bool), # whether safety car even exists ('mSafetyCarActive', ctypes.c_bool), # whether safety car is active - ('mSafetyCarLaps', ctypes.c_long), # number of laps + ('mSafetyCarLaps', ctypes.c_int), # number of laps ('mSafetyCarThreshold', ctypes.c_float), # the threshold at which a safety car is called out (compared to the sum of TrackRulesParticipantV01::mYellowSeverity for each vehicle) ('mSafetyCarLapDist', ctypes.c_double), # safety car lap distance ('mSafetyCarLapDistAtStart', ctypes.c_float), # where the safety car starts from @@ -465,7 +465,7 @@ class rF2TrackRules(ctypes.Structure): ('mInputExpansion', ctypes.c_ubyte*256), ('mYellowFlagState', ctypes.c_byte), # see ScoringInfoV01 for values ('mYellowFlagLaps', ctypes.c_short), # suggested number of laps to run under yellow (may be passed in with admin command) - ('mSafetyCarInstruction', ctypes.c_long), # 0=no change, 1=go active, 2 = head for pits + ('mSafetyCarInstruction', ctypes.c_int), # 0=no change, 1=go active, 2 = head for pits ('mSafetyCarSpeed', ctypes.c_float), # maximum speed at which to drive ('mSafetyCarMinimumSpacing', ctypes.c_float), # minimum spacing behind safety car (-1 to indicate no limit) ('mSafetyCarMaximumSpacing', ctypes.c_float), # maximum spacing behind safety car (-1 to indicate no limit) @@ -481,11 +481,11 @@ class rF2TrackRules(ctypes.Structure): class rF2PitMenu(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mCategoryIndex', ctypes.c_long), # index of the current category + ('mCategoryIndex', ctypes.c_int), # index of the current category ('mCategoryName', ctypes.c_char*32), # name of the current category (untranslated) - ('mChoiceIndex', ctypes.c_long), # index of the current choice (within the current category) + ('mChoiceIndex', ctypes.c_int), # index of the current choice (within the current category) ('mChoiceString', ctypes.c_char*32), # name of the current choice (may have some translated words) - ('mNumChoices', ctypes.c_long), # total number of choices (0 < = mChoiceIndex < mNumChoices) + ('mNumChoices', ctypes.c_int), # total number of choices (0 < = mChoiceIndex < mNumChoices) ('mExpansion', ctypes.c_ubyte*256), # for future use ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] @@ -507,33 +507,33 @@ class rF2WeatherControlInfo(ctypes.Structure): class rF2MappedBufferVersionBlock(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2MappedBufferVersionBlockWithSize(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Telemetry(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. - ('mNumVehicles', ctypes.c_long), # current number of vehicles + ('mNumVehicles', ctypes.c_int), # current number of vehicles ('mVehicles', rF2VehicleTelemetry*rFactor2Constants.MAX_MAPPED_VEHICLES), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Scoring(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. ('mScoringInfo', rF2ScoringInfo), ('mVehicles', rF2VehicleScoring*rFactor2Constants.MAX_MAPPED_VEHICLES), @@ -542,8 +542,8 @@ class rF2Scoring(ctypes.Structure): class rF2Rules(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. ('mTrackRules', rF2TrackRules), ('mActions', rF2TrackRulesAction*rFactor2Constants.MAX_MAPPED_VEHICLES), @@ -553,8 +553,8 @@ class rF2Rules(ctypes.Structure): class rF2ForceFeedback(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mForceValue', ctypes.c_double), # Current FFB value reported via InternalsPlugin::ForceFeedback. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] @@ -567,32 +567,32 @@ class rF2GraphicsInfo(ctypes.Structure): ('mAmbientRed', ctypes.c_double), ('mAmbientGreen', ctypes.c_double), ('mAmbientBlue', ctypes.c_double), - ('mID', ctypes.c_long), # slot ID being viewed (-1 if invalid) - ('mCameraType', ctypes.c_long), # see above comments for possible values + ('mID', ctypes.c_int), # slot ID being viewed (-1 if invalid) + ('mCameraType', ctypes.c_int), # see above comments for possible values ('mExpansion', ctypes.c_ubyte*128), # for future use (possibly camera name) ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Graphics(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mGraphicsInfo', rF2GraphicsInfo), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2PitInfo(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mPitMneu', rF2PitMenu), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Weather(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mTrackNodeSize', ctypes.c_double), ('mWeatherInfo', rF2WeatherControlInfo), ] @@ -607,7 +607,7 @@ class rF2TrackedDamage(ctypes.Structure): class rF2VehScoringCapture(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mID', ctypes.c_long), # slot ID (note that it can be re-used in multiplayer after someone leaves) + ('mID', ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) ('mPlace', ctypes.c_ubyte), ('mIsPlayer', ctypes.c_bool), ('mFinishStatus', ctypes.c_byte), # 0=none, 1=finished, 2=dnf, 3 = dq @@ -617,16 +617,16 @@ class rF2SessionTransitionCapture(ctypes.Structure): _pack_ = 4 _fields_ = [ ('mGamePhase', ctypes.c_ubyte), - ('mSession', ctypes.c_long), - ('mNumScoringVehicles', ctypes.c_long), + ('mSession', ctypes.c_int), + ('mNumScoringVehicles', ctypes.c_int), ('mScoringVehicles', rF2VehScoringCapture*rFactor2Constants.MAX_MAPPED_VEHICLES), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Extended(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mVersion', ctypes.c_char*12), # API version ('is64bit', ctypes.c_bool), # Is 64bit plugin? ('mPhysics', rF2PhysicsOptions), @@ -646,7 +646,7 @@ class rF2Extended(ctypes.Structure): ('mLastHistoryMessage', ctypes.c_char*rFactor2Constants.MAX_STATUS_MSG_LEN), ('mCurrentPitSpeedLimit', ctypes.c_float), # speed limit m/s. ('mSCRPluginEnabled', ctypes.c_bool), # Is Stock Car Rules plugin enabled? - ('mSCRPluginDoubleFileType', ctypes.c_long), # Stock Car Rules plugin DoubleFileType value, only meaningful if mSCRPluginEnabled is true. + ('mSCRPluginDoubleFileType', ctypes.c_int), # Stock Car Rules plugin DoubleFileType value, only meaningful if mSCRPluginEnabled is true. ('mTicksLSIPhaseMessageUpdated', ctypes.c_ulonglong), # Ticks when last LSI phase message was updated. ('mLSIPhaseMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), ('mTicksLSIPitStateMessageUpdated', ctypes.c_ulonglong), # Ticks when last LSI pit state message was updated. @@ -655,7 +655,7 @@ class rF2Extended(ctypes.Structure): ('mLSIOrderInstructionMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), ('mTicksLSIRulesInstructionMessageUpdated', ctypes.c_ulonglong), # Ticks when last FCY rules message was updated. Currently, only SCR plugin sets that. ('mLSIRulesInstructionMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mUnsubscribedBuffersMask', ctypes.c_long), # Currently active UnsbscribedBuffersMask value. This will be allowed for clients to write to in the future, but not yet. + ('mUnsubscribedBuffersMask', ctypes.c_int), # Currently active UnsbscribedBuffersMask value. This will be allowed for clients to write to in the future, but not yet. ('mHWControlInputEnabled', ctypes.c_bool), # HWControl input buffer is enabled. ('mWeatherControlInputEnabled', ctypes.c_bool), # WeatherControl input buffer is enabled. ('mRulesControlInputEnabled', ctypes.c_bool), # RulesControl input buffer is enabled. @@ -664,8 +664,8 @@ class rF2Extended(ctypes.Structure): class rF2HWControl(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mLayoutVersion', ctypes.c_int), ('mControlName', ctypes.c_char*rFactor2Constants.MAX_HWCONTROL_NAME_LEN), ('mfRetVal', ctypes.c_double), @@ -674,8 +674,8 @@ class rF2HWControl(ctypes.Structure): class rF2WeatherControl(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_ulong), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_ulong), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mLayoutVersion', ctypes.c_int), ('mWeatherInfo', rF2WeatherControlInfo), ] From 70c34fc8e50c9c57d47a4289a0e651956db35e1c Mon Sep 17 00:00:00 2001 From: Xiang Date: Mon, 3 Apr 2023 17:23:59 +0800 Subject: [PATCH 37/93] add hybrid related entries (requires v3.7.15.1 sharedmemory plugin) --- rF2data.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rF2data.py b/rF2data.py index 2ab1e92..0279359 100644 --- a/rF2data.py +++ b/rF2data.py @@ -255,7 +255,14 @@ class rF2VehicleTelemetry(ctypes.Structure): ('mTurboBoostPressure', ctypes.c_double), # current turbo boost pressure if available ('mPhysicsToGraphicsOffset', ctypes.c_float*3), # offset from static CG to graphical center ('mPhysicalSteeringWheelRange', ctypes.c_float), # the *physical* steering wheel range - ('mExpansion', ctypes.c_ubyte*152), # for future use (note that the slot ID has been moved to mID above) + ('mDeltaBest', ctypes.c_double), # (omitted in error by S397) + ('mBatteryChargeFraction', ctypes.c_double), # Battery charge as fraction [0.0-1.0] + ('mElectricBoostMotorTorque', ctypes.c_double), # current torque of boost motor (can be negative when in regenerating mode) + ('mElectricBoostMotorRPM', ctypes.c_double), # current rpm of boost motor + ('mElectricBoostMotorTemperature', ctypes.c_double), # current temperature of boost motor + ('mElectricBoostWaterTemperature', ctypes.c_double), # current water temperature of boost motor cooler if present (0 otherwise) + ('mElectricBoostMotorState', ctypes.c_ubyte), # 0=unavailable 1=inactive, 2=propulsion, 3=regeneration + ('mExpansion', ctypes.c_ubyte*103), # for future use (note that the slot ID has been moved to mID above) ('mWheels', rF2Wheel*4), # wheel info (front left, front right, rear left, rear right) ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] From 03f216fe592ae9895a2a45d750ff299f71d1977b Mon Sep 17 00:00:00 2001 From: Bernat Arlandis Date: Wed, 5 Apr 2023 18:40:05 +0200 Subject: [PATCH 38/93] Create shmem files if they don't exist (Linux) --- sim_info_sync.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index a911832..2ea613c 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -48,6 +48,14 @@ def __init__(self, input_pid=""): self.copy_mmap() print("sharedmemory mapping started") + def linux_mmap(self, name, size): + file = open(name, "a+") + if file.tell() == 0: + file.write("\0" * size) + file.flush() + + return mmap.mmap(file.fileno(), size) + def start_mmap(self): """ Start memory mapping """ if platform.system() == "Windows": @@ -60,14 +68,10 @@ def start_mmap(self): self._rf2_ffb = mmap.mmap(-1, ctypes.sizeof(rF2data.rF2ForceFeedback), "$rFactor2SMMP_ForceFeedback$") else: - tele_file = open("/dev/shm/$rFactor2SMMP_Telemetry$", "r+") - self._rf2_tele = mmap.mmap(tele_file.fileno(), ctypes.sizeof(rF2data.rF2Telemetry)) - scor_file = open("/dev/shm/$rFactor2SMMP_Scoring$", "r+") - self._rf2_scor = mmap.mmap(scor_file.fileno(), ctypes.sizeof(rF2data.rF2Scoring)) - ext_file = open("/dev/shm/$rFactor2SMMP_Extended$", "r+") - self._rf2_ext = mmap.mmap(ext_file.fileno(), ctypes.sizeof(rF2data.rF2Extended)) - ffb_file = open("/dev/shm/$rFactor2SMMP_ForceFeedback$", "r+") - self._rf2_ffb = mmap.mmap(ffb_file.fileno(), ctypes.sizeof(rF2data.rF2ForceFeedback)) + self._rf2_tele = self.linux_mmap("/dev/shm/$rFactor2SMMP_Telemetry$", ctypes.sizeof(rF2data.rF2Telemetry)) + self._rf2_scor = self.linux_mmap("/dev/shm/$rFactor2SMMP_Scoring$", ctypes.sizeof(rF2data.rF2Scoring)) + self._rf2_ext = self.linux_mmap("/dev/shm/$rFactor2SMMP_Extended$", ctypes.sizeof(rF2data.rF2Extended)) + self._rf2_ffb = self.linux_mmap("/dev/shm/$rFactor2SMMP_ForceFeedback$", ctypes.sizeof(rF2data.rF2ForceFeedback)) def copy_mmap(self): """ Copy memory mapping data """ From 294f2781eeb7dd1857d16c764dada62f626410a3 Mon Sep 17 00:00:00 2001 From: Xiang Date: Fri, 7 Apr 2023 14:00:48 +0800 Subject: [PATCH 39/93] separate local player-only data, removed mmap restart --- sim_info_sync.py | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index a911832..1fbfe1a 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -28,22 +28,12 @@ class SimInfoSync(): """ def __init__(self, input_pid=""): - self.players_scor_index = 99 - self.players_tele_index = 99 + self.players_scor_index = MAX_VEHICLES - 1 + self.players_tele_index = MAX_VEHICLES - 1 self.data_updating = False self.stopped = True self._input_pid = input_pid - self._rf2_tele = None # map shared memory - self._rf2_scor = None - self._rf2_ext = None - self._rf2_ffb = None - - self.LastTele = None # synced copy of raw data - self.LastScor = None - self.LastExt = None - self.LastFfb = None - self.start_mmap() self.copy_mmap() print("sharedmemory mapping started") @@ -75,6 +65,8 @@ def copy_mmap(self): self.LastScor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) self.LastExt = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) self.LastFfb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) + self.LastScorPlayer = copy.deepcopy(self.LastScor.mVehicles[self.players_scor_index]) + self.LastTelePlayer = copy.deepcopy(self.LastTele.mVehicles[self.players_tele_index]) def reset_mmap(self): """ Reset memory mapping """ @@ -103,7 +95,7 @@ def __find_local_player_index_scor(data_scor): for index in range(MAX_VEHICLES): if data_scor.mVehicles[index].mIsPlayer: return index - return 99 + return MAX_VEHICLES - 1 def find_player_index_tele(self, index_scor): """ Find player index in rF2Telemetry using mID from rF2Scoring """ @@ -111,7 +103,7 @@ def find_player_index_tele(self, index_scor): for index in range(MAX_VEHICLES): if self.LastTele.mVehicles[index].mID == scor_mid: return index - return 99 + return MAX_VEHICLES - 1 def __infoUpdate(self): """ Update synced player data """ @@ -133,17 +125,20 @@ def __infoUpdate(self): self.players_tele_index = self.find_player_index_tele(self.players_scor_index) self.LastTele = copy.deepcopy(data_tele) + self.LastScorPlayer = copy.deepcopy(self.LastScor.mVehicles[self.players_scor_index]) + self.LastTelePlayer = copy.deepcopy(self.LastTele.mVehicles[self.players_tele_index]) + # Start checking data version update status check_counter += 1 - if check_counter > 330: # active after around 1 seconds - if (not data_freezed and last_version_update > 0 - and last_version_update == data_scor.mVersionUpdateEnd): - self.reset_mmap() + if check_counter > 330: # active after around 5 seconds + if (not data_freezed and + 0 < last_version_update == data_scor.mVersionUpdateEnd): + #self.reset_mmap() data_freezed = True re_version_update = data_scor.mVersionUpdateEnd self.syncedVehicleTelemetry().mIgnitionStarter = 0 - print(f"sharedmemory mapping restarted - version:{last_version_update}") + print(f"sharedmemory mapping - data version freeze detected:{last_version_update}") last_version_update = data_scor.mVersionUpdateEnd check_counter = 0 # reset counter @@ -172,11 +167,11 @@ def stopUpdating(self): def syncedVehicleTelemetry(self): """ Get the variable for the player's vehicle """ - return self.LastTele.mVehicles[self.players_tele_index] + return self.LastTelePlayer def syncedVehicleScoring(self): """ Get the variable for the player's vehicle """ - return self.LastScor.mVehicles[self.players_scor_index] + return self.LastScorPlayer ########################################################### From 10c3f343a8959cc0f6051d420dcafcebb9ecd1d6 Mon Sep 17 00:00:00 2001 From: Xiang Date: Fri, 7 Apr 2023 15:22:56 +0800 Subject: [PATCH 40/93] add thread check --- sim_info_sync.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 1fbfe1a..6652d1a 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -28,11 +28,12 @@ class SimInfoSync(): """ def __init__(self, input_pid=""): - self.players_scor_index = MAX_VEHICLES - 1 - self.players_tele_index = MAX_VEHICLES - 1 - self.data_updating = False self.stopped = True + self.data_updating = False + self._input_pid = input_pid + self.players_scor_index = MAX_VEHICLES - 1 + self.players_tele_index = MAX_VEHICLES - 1 self.start_mmap() self.copy_mmap() @@ -152,12 +153,13 @@ def __infoUpdate(self): def startUpdating(self): """ Start data updating thread """ - self.data_updating = True - self.stopped = False - index_thread = threading.Thread(target=self.__infoUpdate) - index_thread.daemon=True - index_thread.start() - print("sharedmemory synced player data updating thread started") + if self.stopped: + self.data_updating = True + self.stopped = False + index_thread = threading.Thread(target=self.__infoUpdate) + index_thread.daemon=True + index_thread.start() + print("sharedmemory synced player data updating thread started") def stopUpdating(self): """ Stop data updating thread """ From f00ef308887a96df3cc5c5d3a9deb227a73792d5 Mon Sep 17 00:00:00 2001 From: Xiang Date: Fri, 7 Apr 2023 19:06:46 +0800 Subject: [PATCH 41/93] longer update delay while inactive --- sim_info_sync.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 6652d1a..c0d7f8f 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -111,7 +111,9 @@ def __infoUpdate(self): last_version_update = 0 # store last data version update re_version_update = 0 # store restarted data version update data_freezed = True # whether data is freezed - check_counter = 0 # counter for data version update check + check_timer = 0 # timer for data version update check + check_timer_start = 0 + update_delay = 0.5 # longer delay while inactive while self.data_updating: data_scor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) @@ -130,23 +132,25 @@ def __infoUpdate(self): self.LastTelePlayer = copy.deepcopy(self.LastTele.mVehicles[self.players_tele_index]) # Start checking data version update status - check_counter += 1 + check_timer = time.time() - check_timer_start - if check_counter > 330: # active after around 5 seconds + if check_timer > 5: # active after around 5 seconds if (not data_freezed and - 0 < last_version_update == data_scor.mVersionUpdateEnd): + last_version_update == data_scor.mVersionUpdateEnd): #self.reset_mmap() + update_delay = 0.5 data_freezed = True re_version_update = data_scor.mVersionUpdateEnd self.syncedVehicleTelemetry().mIgnitionStarter = 0 print(f"sharedmemory mapping - data version freeze detected:{last_version_update}") last_version_update = data_scor.mVersionUpdateEnd - check_counter = 0 # reset counter + check_timer_start = time.time() # reset timer if data_freezed and re_version_update != data_scor.mVersionUpdateEnd: data_freezed = False + update_delay = 0.01 - time.sleep(0.01) + time.sleep(update_delay) self.stopped = True print("sharedmemory synced player data updating thread stopped") From 30cea3d66f186b8bd0ae8a61182638922ea2680e Mon Sep 17 00:00:00 2001 From: Xiang Date: Sat, 6 May 2023 10:40:33 +0800 Subject: [PATCH 42/93] fall back to INVALID_INDEX instead --- sim_info_sync.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 7fe62d7..a25e962 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -18,6 +18,7 @@ import rF2data MAX_VEHICLES = rF2data.rFactor2Constants.MAX_MAPPED_VEHICLES +INVALID_INDEX = -1 class SimInfoSync(): @@ -32,8 +33,8 @@ def __init__(self, input_pid=""): self.data_updating = False self._input_pid = input_pid - self.players_scor_index = MAX_VEHICLES - 1 - self.players_tele_index = MAX_VEHICLES - 1 + self.players_scor_index = INVALID_INDEX + self.players_tele_index = INVALID_INDEX self.start_mmap() self.copy_mmap() @@ -100,7 +101,7 @@ def __find_local_player_index_scor(data_scor): for index in range(MAX_VEHICLES): if data_scor.mVehicles[index].mIsPlayer: return index - return MAX_VEHICLES - 1 + return INVALID_INDEX def find_player_index_tele(self, index_scor): """ Find player index in rF2Telemetry using mID from rF2Scoring """ @@ -108,7 +109,7 @@ def find_player_index_tele(self, index_scor): for index in range(MAX_VEHICLES): if self.LastTele.mVehicles[index].mID == scor_mid: return index - return MAX_VEHICLES - 1 + return INVALID_INDEX def __infoUpdate(self): """ Update synced player data """ From c90c9179e86ea7dc0fc7c73e5e17d2ff3741367a Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 10 May 2023 03:08:39 +0800 Subject: [PATCH 43/93] fixed a desync problem due to mismatched mID --- sim_info_sync.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index a25e962..8d23f7c 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -96,13 +96,22 @@ def close(self): # Sync data for local player @staticmethod - def __find_local_player_index_scor(data_scor): + def __local_index_scor(data_scor): """ Find player index in rF2Scoring """ for index in range(MAX_VEHICLES): if data_scor.mVehicles[index].mIsPlayer: return index return INVALID_INDEX + @staticmethod + def __local_index_tele(index_scor, data_tele): + """ Find player index in rF2Telemetry using mID from rF2Scoring """ + scor_mid = data_tele.mVehicles[index_scor].mID + for index in range(MAX_VEHICLES): + if data_tele.mVehicles[index].mID == scor_mid: + return index + return INVALID_INDEX + def find_player_index_tele(self, index_scor): """ Find player index in rF2Telemetry using mID from rF2Scoring """ scor_mid = self.LastScor.mVehicles[index_scor].mID @@ -114,7 +123,6 @@ def find_player_index_tele(self, index_scor): def __infoUpdate(self): """ Update synced player data """ last_version_update = 0 # store last data version update - re_version_update = 0 # store restarted data version update data_freezed = True # whether data is freezed check_timer = 0 # timer for data version update check check_timer_start = 0 @@ -128,30 +136,29 @@ def __infoUpdate(self): # Update player index if not data_freezed: - self.players_scor_index = self.__find_local_player_index_scor(data_scor) - self.LastScor = copy.deepcopy(data_scor) - self.players_tele_index = self.find_player_index_tele(self.players_scor_index) - self.LastTele = copy.deepcopy(data_tele) + self.players_scor_index = self.__local_index_scor(data_scor) + self.players_tele_index = self.__local_index_tele(self.players_scor_index, data_tele) - self.LastScorPlayer = copy.deepcopy(self.LastScor.mVehicles[self.players_scor_index]) - self.LastTelePlayer = copy.deepcopy(self.LastTele.mVehicles[self.players_tele_index]) + if data_scor.mVehicles[self.players_scor_index].mID == data_tele.mVehicles[self.players_tele_index].mID: + self.LastScor = copy.deepcopy(data_scor) + self.LastTele = copy.deepcopy(data_tele) + self.LastScorPlayer = copy.deepcopy(data_scor.mVehicles[self.players_scor_index]) + self.LastTelePlayer = copy.deepcopy(data_tele.mVehicles[self.players_tele_index]) # Start checking data version update status check_timer = time.time() - check_timer_start - if check_timer > 5: # active after around 5 seconds + if check_timer > 3: # active after around 3 seconds if (not data_freezed and last_version_update == data_scor.mVersionUpdateEnd): - #self.reset_mmap() update_delay = 0.5 data_freezed = True - re_version_update = data_scor.mVersionUpdateEnd self.syncedVehicleTelemetry().mIgnitionStarter = 0 print(f"sharedmemory mapping - data version freeze detected:{last_version_update}") last_version_update = data_scor.mVersionUpdateEnd check_timer_start = time.time() # reset timer - if data_freezed and re_version_update != data_scor.mVersionUpdateEnd: + if last_version_update != data_scor.mVersionUpdateEnd: data_freezed = False update_delay = 0.01 From 0725d4caa42bd6811f7544bf5211e2488e51316f Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 10 May 2023 14:51:20 +0800 Subject: [PATCH 44/93] removed deepcopy --- sim_info_sync.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 8d23f7c..2f1c505 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -9,7 +9,6 @@ import mmap import time import threading -import copy import platform try: @@ -41,6 +40,7 @@ def __init__(self, input_pid=""): print("sharedmemory mapping started") def linux_mmap(self, name, size): + """ Linux memory mapping """ file = open(name, "a+") if file.tell() == 0: file.write("\0" * size) @@ -71,8 +71,8 @@ def copy_mmap(self): self.LastScor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) self.LastExt = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) self.LastFfb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) - self.LastScorPlayer = copy.deepcopy(self.LastScor.mVehicles[self.players_scor_index]) - self.LastTelePlayer = copy.deepcopy(self.LastTele.mVehicles[self.players_tele_index]) + self.LastScorPlayer = self.LastScor.mVehicles[self.players_scor_index] + self.LastTelePlayer = self.LastTele.mVehicles[self.players_tele_index] def reset_mmap(self): """ Reset memory mapping """ @@ -140,10 +140,10 @@ def __infoUpdate(self): self.players_tele_index = self.__local_index_tele(self.players_scor_index, data_tele) if data_scor.mVehicles[self.players_scor_index].mID == data_tele.mVehicles[self.players_tele_index].mID: - self.LastScor = copy.deepcopy(data_scor) - self.LastTele = copy.deepcopy(data_tele) - self.LastScorPlayer = copy.deepcopy(data_scor.mVehicles[self.players_scor_index]) - self.LastTelePlayer = copy.deepcopy(data_tele.mVehicles[self.players_tele_index]) + self.LastScor = data_scor + self.LastTele = data_tele + self.LastScorPlayer = self.LastScor.mVehicles[self.players_scor_index] + self.LastTelePlayer = self.LastTele.mVehicles[self.players_tele_index] # Start checking data version update status check_timer = time.time() - check_timer_start From 831ec020a2b1f3e64af4ce37af950aaccb365e36 Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 10 May 2023 18:12:15 +0800 Subject: [PATCH 45/93] revert back to old method to avoid data freeze --- sim_info_sync.py | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 2f1c505..2a46708 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -9,6 +9,7 @@ import mmap import time import threading +import copy import platform try: @@ -71,8 +72,8 @@ def copy_mmap(self): self.LastScor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) self.LastExt = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) self.LastFfb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) - self.LastScorPlayer = self.LastScor.mVehicles[self.players_scor_index] - self.LastTelePlayer = self.LastTele.mVehicles[self.players_tele_index] + self.LastScorPlayer = copy.deepcopy(self.LastScor.mVehicles[self.players_scor_index]) + self.LastTelePlayer = copy.deepcopy(self.LastTele.mVehicles[self.players_tele_index]) def reset_mmap(self): """ Reset memory mapping """ @@ -96,22 +97,13 @@ def close(self): # Sync data for local player @staticmethod - def __local_index_scor(data_scor): + def __find_local_player_index_scor(data_scor): """ Find player index in rF2Scoring """ for index in range(MAX_VEHICLES): if data_scor.mVehicles[index].mIsPlayer: return index return INVALID_INDEX - @staticmethod - def __local_index_tele(index_scor, data_tele): - """ Find player index in rF2Telemetry using mID from rF2Scoring """ - scor_mid = data_tele.mVehicles[index_scor].mID - for index in range(MAX_VEHICLES): - if data_tele.mVehicles[index].mID == scor_mid: - return index - return INVALID_INDEX - def find_player_index_tele(self, index_scor): """ Find player index in rF2Telemetry using mID from rF2Scoring """ scor_mid = self.LastScor.mVehicles[index_scor].mID @@ -123,6 +115,7 @@ def find_player_index_tele(self, index_scor): def __infoUpdate(self): """ Update synced player data """ last_version_update = 0 # store last data version update + re_version_update = 0 # store restarted data version update data_freezed = True # whether data is freezed check_timer = 0 # timer for data version update check check_timer_start = 0 @@ -136,29 +129,30 @@ def __infoUpdate(self): # Update player index if not data_freezed: - self.players_scor_index = self.__local_index_scor(data_scor) - self.players_tele_index = self.__local_index_tele(self.players_scor_index, data_tele) + self.players_scor_index = self.__find_local_player_index_scor(data_scor) + self.LastScor = copy.deepcopy(data_scor) + self.players_tele_index = self.find_player_index_tele(self.players_scor_index) + self.LastTele = copy.deepcopy(data_tele) - if data_scor.mVehicles[self.players_scor_index].mID == data_tele.mVehicles[self.players_tele_index].mID: - self.LastScor = data_scor - self.LastTele = data_tele - self.LastScorPlayer = self.LastScor.mVehicles[self.players_scor_index] - self.LastTelePlayer = self.LastTele.mVehicles[self.players_tele_index] + self.LastScorPlayer = copy.deepcopy(self.LastScor.mVehicles[self.players_scor_index]) + self.LastTelePlayer = copy.deepcopy(self.LastTele.mVehicles[self.players_tele_index]) # Start checking data version update status check_timer = time.time() - check_timer_start - if check_timer > 3: # active after around 3 seconds + if check_timer > 5: # active after around 5 seconds if (not data_freezed and last_version_update == data_scor.mVersionUpdateEnd): + #self.reset_mmap() update_delay = 0.5 data_freezed = True + re_version_update = data_scor.mVersionUpdateEnd self.syncedVehicleTelemetry().mIgnitionStarter = 0 print(f"sharedmemory mapping - data version freeze detected:{last_version_update}") last_version_update = data_scor.mVersionUpdateEnd check_timer_start = time.time() # reset timer - if last_version_update != data_scor.mVersionUpdateEnd: + if data_freezed and re_version_update != data_scor.mVersionUpdateEnd: data_freezed = False update_delay = 0.01 From 3ae14cac23c9cbb2a428c2582307350b3c302301 Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 11 May 2023 23:47:55 +0800 Subject: [PATCH 46/93] fixed data interruption issue in multiplayer session --- sim_info_sync.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 2a46708..d780760 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -68,11 +68,11 @@ def start_mmap(self): def copy_mmap(self): """ Copy memory mapping data """ - self.LastTele = rF2data.rF2Telemetry.from_buffer_copy(self._rf2_tele) - self.LastScor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) self.LastExt = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) self.LastFfb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) + self.LastScor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) self.LastScorPlayer = copy.deepcopy(self.LastScor.mVehicles[self.players_scor_index]) + self.LastTele = rF2data.rF2Telemetry.from_buffer_copy(self._rf2_tele) self.LastTelePlayer = copy.deepcopy(self.LastTele.mVehicles[self.players_tele_index]) def reset_mmap(self): @@ -112,6 +112,11 @@ def find_player_index_tele(self, index_scor): return index return INVALID_INDEX + @staticmethod + def ver_check(input_data): + """ Update version check """ + return input_data.mVersionUpdateEnd == input_data.mVersionUpdateBegin + def __infoUpdate(self): """ Update synced player data """ last_version_update = 0 # store last data version update @@ -128,14 +133,21 @@ def __infoUpdate(self): self.LastFfb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) # Update player index - if not data_freezed: - self.players_scor_index = self.__find_local_player_index_scor(data_scor) - self.LastScor = copy.deepcopy(data_scor) - self.players_tele_index = self.find_player_index_tele(self.players_scor_index) - self.LastTele = copy.deepcopy(data_tele) - - self.LastScorPlayer = copy.deepcopy(self.LastScor.mVehicles[self.players_scor_index]) - self.LastTelePlayer = copy.deepcopy(self.LastTele.mVehicles[self.players_tele_index]) + if not data_freezed and self.ver_check(data_scor) and self.ver_check(data_tele): + + for idx_scor in range(MAX_VEHICLES): + if data_scor.mVehicles[idx_scor].mIsPlayer: + self.players_scor_index = idx_scor + self.LastScor = copy.deepcopy(data_scor) + self.LastScorPlayer = copy.deepcopy(data_scor.mVehicles[idx_scor]) + + for idx_tele in range(MAX_VEHICLES): + if data_tele.mVehicles[idx_tele].mID == data_scor.mVehicles[idx_scor].mID: + self.players_tele_index = idx_tele + self.LastTele = copy.deepcopy(data_tele) + self.LastTelePlayer = copy.deepcopy(data_tele.mVehicles[idx_tele]) + break + break # Start checking data version update status check_timer = time.time() - check_timer_start From 63d2aa21d2c669d13b9834d7b5a473e6a0532c5d Mon Sep 17 00:00:00 2001 From: Xiang Date: Mon, 15 May 2023 10:51:02 +0800 Subject: [PATCH 47/93] reset local player data if in spectator mode --- sim_info_sync.py | 77 +++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index d780760..c6b2dfe 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -71,9 +71,9 @@ def copy_mmap(self): self.LastExt = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) self.LastFfb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) self.LastScor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) - self.LastScorPlayer = copy.deepcopy(self.LastScor.mVehicles[self.players_scor_index]) self.LastTele = rF2data.rF2Telemetry.from_buffer_copy(self._rf2_tele) - self.LastTelePlayer = copy.deepcopy(self.LastTele.mVehicles[self.players_tele_index]) + self.LastScorPlayer = copy.deepcopy(self.LastScor.mVehicles[INVALID_INDEX]) + self.LastTelePlayer = copy.deepcopy(self.LastTele.mVehicles[INVALID_INDEX]) def reset_mmap(self): """ Reset memory mapping """ @@ -96,16 +96,31 @@ def close(self): ########################################################### # Sync data for local player - @staticmethod - def __find_local_player_index_scor(data_scor): - """ Find player index in rF2Scoring """ - for index in range(MAX_VEHICLES): - if data_scor.mVehicles[index].mIsPlayer: - return index - return INVALID_INDEX + def __reset_local_player_data(self, data_scor, data_tele): + """ Reset local player data """ + self.players_scor_index = INVALID_INDEX + self.players_tele_index = INVALID_INDEX + self.LastScorPlayer = copy.deepcopy(data_scor.mVehicles[INVALID_INDEX]) + self.LastTelePlayer = copy.deepcopy(data_tele.mVehicles[INVALID_INDEX]) + print("sharedmemory mapping - local player data reset") + + def __local_player_data(self, data_scor, data_tele): + """ Get local player data """ + for idx_scor in range(MAX_VEHICLES): + if data_scor.mVehicles[idx_scor].mIsPlayer: + self.players_scor_index = idx_scor + self.LastScorPlayer = copy.deepcopy(data_scor.mVehicles[idx_scor]) + + for idx_tele in range(MAX_VEHICLES): + if data_tele.mVehicles[idx_tele].mID == data_scor.mVehicles[idx_scor].mID: + self.players_tele_index = idx_tele + self.LastTelePlayer = copy.deepcopy(data_tele.mVehicles[idx_tele]) + return True + return True + return False def find_player_index_tele(self, index_scor): - """ Find player index in rF2Telemetry using mID from rF2Scoring """ + """ Find player index using mID """ scor_mid = self.LastScor.mVehicles[index_scor].mID for index in range(MAX_VEHICLES): if self.LastTele.mVehicles[index].mID == scor_mid: @@ -120,10 +135,10 @@ def ver_check(input_data): def __infoUpdate(self): """ Update synced player data """ last_version_update = 0 # store last data version update - re_version_update = 0 # store restarted data version update data_freezed = True # whether data is freezed check_timer = 0 # timer for data version update check check_timer_start = 0 + reset_counter = 0 update_delay = 0.5 # longer delay while inactive while self.data_updating: @@ -134,37 +149,33 @@ def __infoUpdate(self): # Update player index if not data_freezed and self.ver_check(data_scor) and self.ver_check(data_tele): + player_data = self.__local_player_data(data_scor, data_tele) + self.LastScor = copy.deepcopy(data_scor) + self.LastTele = copy.deepcopy(data_tele) - for idx_scor in range(MAX_VEHICLES): - if data_scor.mVehicles[idx_scor].mIsPlayer: - self.players_scor_index = idx_scor - self.LastScor = copy.deepcopy(data_scor) - self.LastScorPlayer = copy.deepcopy(data_scor.mVehicles[idx_scor]) + # Stop & reset player data if local player index no longer exists + # 5 retries before reset + if not player_data and reset_counter < 6: + reset_counter += 1 + elif player_data: + reset_counter = 0 - for idx_tele in range(MAX_VEHICLES): - if data_tele.mVehicles[idx_tele].mID == data_scor.mVehicles[idx_scor].mID: - self.players_tele_index = idx_tele - self.LastTele = copy.deepcopy(data_tele) - self.LastTelePlayer = copy.deepcopy(data_tele.mVehicles[idx_tele]) - break - break + if reset_counter == 5: + self.__reset_local_player_data(data_scor, data_tele) # Start checking data version update status check_timer = time.time() - check_timer_start if check_timer > 5: # active after around 5 seconds - if (not data_freezed and - last_version_update == data_scor.mVersionUpdateEnd): - #self.reset_mmap() + if not data_freezed and last_version_update == data_scor.mVersionUpdateEnd: update_delay = 0.5 data_freezed = True - re_version_update = data_scor.mVersionUpdateEnd - self.syncedVehicleTelemetry().mIgnitionStarter = 0 + self.__reset_local_player_data(data_scor, data_tele) print(f"sharedmemory mapping - data version freeze detected:{last_version_update}") last_version_update = data_scor.mVersionUpdateEnd check_timer_start = time.time() # reset timer - if data_freezed and re_version_update != data_scor.mVersionUpdateEnd: + if data_freezed and last_version_update != data_scor.mVersionUpdateEnd: data_freezed = False update_delay = 0.01 @@ -178,16 +189,14 @@ def startUpdating(self): if self.stopped: self.data_updating = True self.stopped = False - index_thread = threading.Thread(target=self.__infoUpdate) - index_thread.daemon=True - index_thread.start() + self.data_thread = threading.Thread(target=self.__infoUpdate, daemon=True) + self.data_thread.start() print("sharedmemory synced player data updating thread started") def stopUpdating(self): """ Stop data updating thread """ self.data_updating = False - while not self.stopped: # wait until stopped - time.sleep(0.01) + self.data_thread.join() def syncedVehicleTelemetry(self): """ Get the variable for the player's vehicle """ From 07d688299e6a1dd12cf97e1bda18ad13ba1de2e2 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sun, 4 Jun 2023 01:24:50 +0800 Subject: [PATCH 48/93] use freezed condition check instead of reset data --- sim_info_sync.py | 119 ++++++++++++++++++++++++++++++----------------- 1 file changed, 76 insertions(+), 43 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index c6b2dfe..14652f6 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -11,6 +11,7 @@ import threading import copy import platform +import logging try: from . import rF2data @@ -21,24 +22,26 @@ INVALID_INDEX = -1 -class SimInfoSync(): +class SimInfoSync: """ API for rF2 shared memory Player-Synced data. """ - def __init__(self, input_pid=""): + def __init__(self, input_pid="", logger=__name__): self.stopped = True self.data_updating = False self._input_pid = input_pid + self._logger = logging.getLogger(logger) + self._freezed = False self.players_scor_index = INVALID_INDEX self.players_tele_index = INVALID_INDEX self.start_mmap() self.copy_mmap() - print("sharedmemory mapping started") + self._logger.info("sharedmemory mapping started") def linux_mmap(self, name, size): """ Linux memory mapping """ @@ -52,19 +55,39 @@ def linux_mmap(self, name, size): def start_mmap(self): """ Start memory mapping """ if platform.system() == "Windows": - self._rf2_tele = mmap.mmap(-1, ctypes.sizeof(rF2data.rF2Telemetry), - f"$rFactor2SMMP_Telemetry${self._input_pid}") - self._rf2_scor = mmap.mmap(-1, ctypes.sizeof(rF2data.rF2Scoring), - f"$rFactor2SMMP_Scoring${self._input_pid}") - self._rf2_ext = mmap.mmap(-1, ctypes.sizeof(rF2data.rF2Extended), - f"$rFactor2SMMP_Extended${self._input_pid}") - self._rf2_ffb = mmap.mmap(-1, ctypes.sizeof(rF2data.rF2ForceFeedback), - "$rFactor2SMMP_ForceFeedback$") + self._rf2_tele = mmap.mmap( + -1, ctypes.sizeof(rF2data.rF2Telemetry), + f"$rFactor2SMMP_Telemetry${self._input_pid}" + ) + self._rf2_scor = mmap.mmap( + -1, ctypes.sizeof(rF2data.rF2Scoring), + f"$rFactor2SMMP_Scoring${self._input_pid}" + ) + self._rf2_ext = mmap.mmap( + -1, ctypes.sizeof(rF2data.rF2Extended), + f"$rFactor2SMMP_Extended${self._input_pid}" + ) + self._rf2_ffb = mmap.mmap( + -1, ctypes.sizeof(rF2data.rF2ForceFeedback), + "$rFactor2SMMP_ForceFeedback$" + ) else: - self._rf2_tele = self.linux_mmap("/dev/shm/$rFactor2SMMP_Telemetry$", ctypes.sizeof(rF2data.rF2Telemetry)) - self._rf2_scor = self.linux_mmap("/dev/shm/$rFactor2SMMP_Scoring$", ctypes.sizeof(rF2data.rF2Scoring)) - self._rf2_ext = self.linux_mmap("/dev/shm/$rFactor2SMMP_Extended$", ctypes.sizeof(rF2data.rF2Extended)) - self._rf2_ffb = self.linux_mmap("/dev/shm/$rFactor2SMMP_ForceFeedback$", ctypes.sizeof(rF2data.rF2ForceFeedback)) + self._rf2_tele = self.linux_mmap( + "/dev/shm/$rFactor2SMMP_Telemetry$", + ctypes.sizeof(rF2data.rF2Telemetry) + ) + self._rf2_scor = self.linux_mmap( + "/dev/shm/$rFactor2SMMP_Scoring$", + ctypes.sizeof(rF2data.rF2Scoring) + ) + self._rf2_ext = self.linux_mmap( + "/dev/shm/$rFactor2SMMP_Extended$", + ctypes.sizeof(rF2data.rF2Extended) + ) + self._rf2_ffb = self.linux_mmap( + "/dev/shm/$rFactor2SMMP_ForceFeedback$", + ctypes.sizeof(rF2data.rF2ForceFeedback) + ) def copy_mmap(self): """ Copy memory mapping data """ @@ -82,27 +105,16 @@ def reset_mmap(self): def close(self): """ Close memory mapping """ - # This didn't help with the errors try: - # Close shared memory mapping self._rf2_tele.close() self._rf2_scor.close() self._rf2_ext.close() self._rf2_ffb.close() - print("sharedmemory mapping closed") + self._logger.info("sharedmemory mapping closed") except BufferError: # "cannot close exported pointers exist" - print("BufferError") + self._logger.error("failed to close mmap") ########################################################### - # Sync data for local player - - def __reset_local_player_data(self, data_scor, data_tele): - """ Reset local player data """ - self.players_scor_index = INVALID_INDEX - self.players_tele_index = INVALID_INDEX - self.LastScorPlayer = copy.deepcopy(data_scor.mVehicles[INVALID_INDEX]) - self.LastTelePlayer = copy.deepcopy(data_tele.mVehicles[INVALID_INDEX]) - print("sharedmemory mapping - local player data reset") def __local_player_data(self, data_scor, data_tele): """ Get local player data """ @@ -150,8 +162,8 @@ def __infoUpdate(self): # Update player index if not data_freezed and self.ver_check(data_scor) and self.ver_check(data_tele): player_data = self.__local_player_data(data_scor, data_tele) - self.LastScor = copy.deepcopy(data_scor) - self.LastTele = copy.deepcopy(data_tele) + self.LastScor = data_scor + self.LastTele = data_tele # Stop & reset player data if local player index no longer exists # 5 retries before reset @@ -159,9 +171,11 @@ def __infoUpdate(self): reset_counter += 1 elif player_data: reset_counter = 0 + self._freezed = False if reset_counter == 5: - self.__reset_local_player_data(data_scor, data_tele) + self._freezed = True + self._logger.info("sharedmemory mapping player data freezed") # Start checking data version update status check_timer = time.time() - check_timer_start @@ -170,19 +184,28 @@ def __infoUpdate(self): if not data_freezed and last_version_update == data_scor.mVersionUpdateEnd: update_delay = 0.5 data_freezed = True - self.__reset_local_player_data(data_scor, data_tele) - print(f"sharedmemory mapping - data version freeze detected:{last_version_update}") + self._freezed = True + self._logger.info( + "sharedmemory mapping freezed, version %s", + last_version_update + ) last_version_update = data_scor.mVersionUpdateEnd check_timer_start = time.time() # reset timer if data_freezed and last_version_update != data_scor.mVersionUpdateEnd: data_freezed = False + self._freezed = False update_delay = 0.01 + self._logger.info( + "sharedmemory mapping unfreezed, version %s", + data_scor.mVersionUpdateEnd + ) time.sleep(update_delay) self.stopped = True - print("sharedmemory synced player data updating thread stopped") + self._freezed = False + self._logger.info("sharedmemory data updating thread stopped") def startUpdating(self): """ Start data updating thread """ @@ -191,7 +214,7 @@ def startUpdating(self): self.stopped = False self.data_thread = threading.Thread(target=self.__infoUpdate, daemon=True) self.data_thread.start() - print("sharedmemory synced player data updating thread started") + self._logger.info("sharedmemory data updating thread started") def stopUpdating(self): """ Stop data updating thread """ @@ -206,20 +229,30 @@ def syncedVehicleScoring(self): """ Get the variable for the player's vehicle """ return self.LastScorPlayer - ########################################################### + def freezed(self): + """ Check whether data freezed """ + return self._freezed + + @staticmethod + def cbytes2str(bytestring): + """Convert bytes to string""" + if type(bytestring) == bytes: + return bytestring.decode().rstrip() + return "" def __del__(self): self.close() + if __name__ == '__main__': # Example usage info = SimInfoSync() - info.startUpdating() # start Shared Memory updating thread - version = info.LastExt.mVersion - v = bytes(version).partition(b'\0')[0].decode().rstrip() + info.startUpdating() + time.sleep(0.5) + version = info.cbytes2str(info.LastExt.mVersion) clutch = info.LastTele.mVehicles[0].mUnfilteredClutch # 1.0 clutch down, 0 clutch up - gear = info.LastTele.mVehicles[0].mGear # -1 to 6 - print(f"Map version: {v}\n" - f"Gear: {gear}, Clutch position: {clutch}") + gear = info.LastTele.mVehicles[0].mGear # -1 to 6 + print(f"API version: {version if version else 'unknown'}\n" + f"Gear: {gear}\nClutch position: {clutch}") - info.stopUpdating() # stop sharedmemory synced player data updating thread + info.stopUpdating() From 941b63b000c4ad7efdd87e2592236dd8c7889e25 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sun, 4 Jun 2023 13:05:44 +0800 Subject: [PATCH 49/93] simplify duplicated mmap code --- sim_info_sync.py | 73 +++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 41 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 14652f6..01b6703 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -43,51 +43,43 @@ def __init__(self, input_pid="", logger=__name__): self.copy_mmap() self._logger.info("sharedmemory mapping started") - def linux_mmap(self, name, size): + @staticmethod + def linux_mmap(name, size): """ Linux memory mapping """ - file = open(name, "a+") + file = open("/dev/shm/" + name, "a+") if file.tell() == 0: file.write("\0" * size) file.flush() - return mmap.mmap(file.fileno(), size) + def platform_mmap(self, name, size, pid=""): + """ Platform memory mapping """ + if platform.system() == "Windows": + return mmap.mmap(-1, size, f"{name}{pid}") + # Linux platform + return self.linux_mmap(name, size) + def start_mmap(self): """ Start memory mapping """ - if platform.system() == "Windows": - self._rf2_tele = mmap.mmap( - -1, ctypes.sizeof(rF2data.rF2Telemetry), - f"$rFactor2SMMP_Telemetry${self._input_pid}" - ) - self._rf2_scor = mmap.mmap( - -1, ctypes.sizeof(rF2data.rF2Scoring), - f"$rFactor2SMMP_Scoring${self._input_pid}" - ) - self._rf2_ext = mmap.mmap( - -1, ctypes.sizeof(rF2data.rF2Extended), - f"$rFactor2SMMP_Extended${self._input_pid}" - ) - self._rf2_ffb = mmap.mmap( - -1, ctypes.sizeof(rF2data.rF2ForceFeedback), - "$rFactor2SMMP_ForceFeedback$" - ) - else: - self._rf2_tele = self.linux_mmap( - "/dev/shm/$rFactor2SMMP_Telemetry$", - ctypes.sizeof(rF2data.rF2Telemetry) - ) - self._rf2_scor = self.linux_mmap( - "/dev/shm/$rFactor2SMMP_Scoring$", - ctypes.sizeof(rF2data.rF2Scoring) - ) - self._rf2_ext = self.linux_mmap( - "/dev/shm/$rFactor2SMMP_Extended$", - ctypes.sizeof(rF2data.rF2Extended) - ) - self._rf2_ffb = self.linux_mmap( - "/dev/shm/$rFactor2SMMP_ForceFeedback$", - ctypes.sizeof(rF2data.rF2ForceFeedback) - ) + self._rf2_tele = self.platform_mmap( + name="$rFactor2SMMP_Telemetry$", + size=ctypes.sizeof(rF2data.rF2Telemetry), + pid=self._input_pid + ) + self._rf2_scor = self.platform_mmap( + name="$rFactor2SMMP_Scoring$", + size=ctypes.sizeof(rF2data.rF2Scoring), + pid=self._input_pid + ) + self._rf2_ext = self.platform_mmap( + name="$rFactor2SMMP_Extended$", + size=ctypes.sizeof(rF2data.rF2Extended), + pid=self._input_pid + ) + self._rf2_ffb = self.platform_mmap( + name="$rFactor2SMMP_ForceFeedback$", + size=ctypes.sizeof(rF2data.rF2ForceFeedback) + ) def copy_mmap(self): """ Copy memory mapping data """ @@ -235,16 +227,16 @@ def freezed(self): @staticmethod def cbytes2str(bytestring): - """Convert bytes to string""" + """ Convert bytes to string """ if type(bytestring) == bytes: - return bytestring.decode().rstrip() + return bytestring.decode(errors="replace").rstrip() return "" def __del__(self): self.close() -if __name__ == '__main__': +if __name__ == "__main__": # Example usage info = SimInfoSync() info.startUpdating() @@ -254,5 +246,4 @@ def __del__(self): gear = info.LastTele.mVehicles[0].mGear # -1 to 6 print(f"API version: {version if version else 'unknown'}\n" f"Gear: {gear}\nClutch position: {clutch}") - info.stopUpdating() From 0c709ebf1c211c39701f25cd4c58d250abe682aa Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 21 Jun 2023 17:48:59 +0800 Subject: [PATCH 50/93] simplify access, add restart control --- sim_info_sync.py | 355 ++++++++++++++++++++++++++++------------------- 1 file changed, 214 insertions(+), 141 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 01b6703..2052a4a 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -4,7 +4,6 @@ Inherit Python mapping of The Iron Wolf's rF2 Shared Memory Tools and add access functions to it. """ -# pylint: disable=invalid-name import ctypes import mmap import time @@ -18,85 +17,65 @@ except ImportError: # standalone, not package import rF2data +PLATFORM = platform.system() MAX_VEHICLES = rF2data.rFactor2Constants.MAX_MAPPED_VEHICLES INVALID_INDEX = -1 -class SimInfoSync: - """ - API for rF2 shared memory - - Player-Synced data. - """ +class rF2MMap: + """Create mmap for accessing rF2 shared memory""" - def __init__(self, input_pid="", logger=__name__): - self.stopped = True - self.data_updating = False - - self._input_pid = input_pid + def __init__(self, logger=__name__): self._logger = logging.getLogger(logger) - self._freezed = False - self.players_scor_index = INVALID_INDEX - self.players_tele_index = INVALID_INDEX - - self.start_mmap() - self.copy_mmap() - self._logger.info("sharedmemory mapping started") - - @staticmethod - def linux_mmap(name, size): - """ Linux memory mapping """ - file = open("/dev/shm/" + name, "a+") - if file.tell() == 0: - file.write("\0" * size) - file.flush() - return mmap.mmap(file.fileno(), size) - - def platform_mmap(self, name, size, pid=""): - """ Platform memory mapping """ - if platform.system() == "Windows": - return mmap.mmap(-1, size, f"{name}{pid}") - # Linux platform - return self.linux_mmap(name, size) + self._rf2_pid = "" + self._rf2_scor = None + self._rf2_tele = None + self._rf2_ext = None + self._rf2_ffb = None + self._data_scor = None + self._data_tele = None + self._data_ext = None + self._data_ffb = None + self._player_scor = None + self._player_tele = None def start_mmap(self): - """ Start memory mapping """ - self._rf2_tele = self.platform_mmap( - name="$rFactor2SMMP_Telemetry$", - size=ctypes.sizeof(rF2data.rF2Telemetry), - pid=self._input_pid - ) + """Start memory mapping""" self._rf2_scor = self.platform_mmap( name="$rFactor2SMMP_Scoring$", size=ctypes.sizeof(rF2data.rF2Scoring), - pid=self._input_pid + pid=self._rf2_pid + ) + self._rf2_tele = self.platform_mmap( + name="$rFactor2SMMP_Telemetry$", + size=ctypes.sizeof(rF2data.rF2Telemetry), + pid=self._rf2_pid ) self._rf2_ext = self.platform_mmap( name="$rFactor2SMMP_Extended$", size=ctypes.sizeof(rF2data.rF2Extended), - pid=self._input_pid + pid=self._rf2_pid ) self._rf2_ffb = self.platform_mmap( name="$rFactor2SMMP_ForceFeedback$", - size=ctypes.sizeof(rF2data.rF2ForceFeedback) + size=ctypes.sizeof(rF2data.rF2ForceFeedback), ) + self._logger.info("sharedmemory mapping started") def copy_mmap(self): - """ Copy memory mapping data """ - self.LastExt = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) - self.LastFfb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) - self.LastScor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) - self.LastTele = rF2data.rF2Telemetry.from_buffer_copy(self._rf2_tele) - self.LastScorPlayer = copy.deepcopy(self.LastScor.mVehicles[INVALID_INDEX]) - self.LastTelePlayer = copy.deepcopy(self.LastTele.mVehicles[INVALID_INDEX]) - - def reset_mmap(self): - """ Reset memory mapping """ - self.close() # close mmap first - self.start_mmap() - - def close(self): - """ Close memory mapping """ + """Copy memory mapping data""" + self._data_scor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) + self._data_tele = rF2data.rF2Telemetry.from_buffer_copy(self._rf2_tele) + self._data_ext = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) + self._data_ffb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) + + def copy_mmap_player(self): + """Copy memory mapping player data""" + self._player_scor = copy.deepcopy(self._data_scor.mVehicles[INVALID_INDEX]) + self._player_tele = copy.deepcopy(self._data_tele.mVehicles[INVALID_INDEX]) + + def close_mmap(self): + """Close memory mapping""" try: self._rf2_tele.close() self._rf2_scor.close() @@ -106,144 +85,238 @@ def close(self): except BufferError: # "cannot close exported pointers exist" self._logger.error("failed to close mmap") - ########################################################### + def platform_mmap(self, name, size, pid=""): + """Platform memory mapping""" + if PLATFORM == "Windows": + return self.windows_mmap(name, size, pid) + return self.linux_mmap(name, size) + + @staticmethod + def windows_mmap(name, size, pid): + """Windows memory mapping""" + return mmap.mmap(-1, size, f"{name}{pid}") + + @staticmethod + def linux_mmap(name, size): + """Linux memory mapping""" + file = open("/dev/shm/" + name, "a+") + if file.tell() == 0: + file.write("\0" * size) + file.flush() + return mmap.mmap(file.fileno(), size) + + @property + def rf2PID(self): + """rF2 process id""" + return self._rf2_pid + + @rf2PID.setter + def rf2PID(self, pid): + """Set rF2 process id""" + self._rf2_pid = pid + + +class SimInfoSync(rF2MMap): + """ + API for rF2 shared memory + + Player-Synced data. + """ + + def __init__(self, logger=__name__): + super().__init__(logger) + self._stopped = True + self._updating = False + self._restarting = False + self._paused = True + self._player_scor_index = INVALID_INDEX + self._player_tele_index = INVALID_INDEX def __local_player_data(self, data_scor, data_tele): - """ Get local player data """ + """Get local player data""" for idx_scor in range(MAX_VEHICLES): if data_scor.mVehicles[idx_scor].mIsPlayer: - self.players_scor_index = idx_scor - self.LastScorPlayer = copy.deepcopy(data_scor.mVehicles[idx_scor]) + self._player_scor_index = idx_scor + self._player_scor = copy.deepcopy(data_scor.mVehicles[idx_scor]) + mid_scor = data_scor.mVehicles[idx_scor].mID for idx_tele in range(MAX_VEHICLES): - if data_tele.mVehicles[idx_tele].mID == data_scor.mVehicles[idx_scor].mID: - self.players_tele_index = idx_tele - self.LastTelePlayer = copy.deepcopy(data_tele.mVehicles[idx_tele]) + if data_tele.mVehicles[idx_tele].mID == mid_scor: + self._player_tele_index = idx_tele + self._player_tele = copy.deepcopy(data_tele.mVehicles[idx_tele]) return True return True return False def find_player_index_tele(self, index_scor): - """ Find player index using mID """ - scor_mid = self.LastScor.mVehicles[index_scor].mID + """Find player index using mID""" + scor_mid = self._data_scor.mVehicles[index_scor].mID for index in range(MAX_VEHICLES): - if self.LastTele.mVehicles[index].mID == scor_mid: + if self._data_tele.mVehicles[index].mID == scor_mid: return index return INVALID_INDEX @staticmethod - def ver_check(input_data): - """ Update version check """ - return input_data.mVersionUpdateEnd == input_data.mVersionUpdateBegin + def ver_check(data): + """Update version check""" + return data.mVersionUpdateEnd == data.mVersionUpdateBegin - def __infoUpdate(self): - """ Update synced player data """ + def __update(self): + """Update synced player data""" last_version_update = 0 # store last data version update data_freezed = True # whether data is freezed - check_timer = 0 # timer for data version update check check_timer_start = 0 reset_counter = 0 update_delay = 0.5 # longer delay while inactive - while self.data_updating: - data_scor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) - data_tele = rF2data.rF2Telemetry.from_buffer_copy(self._rf2_tele) - self.LastExt = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) - self.LastFfb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) - - # Update player index - if not data_freezed and self.ver_check(data_scor) and self.ver_check(data_tele): - player_data = self.__local_player_data(data_scor, data_tele) - self.LastScor = data_scor - self.LastTele = data_tele - - # Stop & reset player data if local player index no longer exists - # 5 retries before reset + while self._updating: + self.copy_mmap() + # Update player data & index + if (not data_freezed + and self.ver_check(self._data_scor) + and self.ver_check(self._data_tele)): + # Get player data + player_data = self.__local_player_data( + self._data_scor, self._data_tele) + # Pause if local player index no longer exists, 5 tries if not player_data and reset_counter < 6: reset_counter += 1 elif player_data: reset_counter = 0 - self._freezed = False - + self._paused = False + # Activate pause if reset_counter == 5: - self._freezed = True - self._logger.info("sharedmemory mapping player data freezed") + self._paused = True + self._logger.info("sharedmemory mapping player data paused") # Start checking data version update status - check_timer = time.time() - check_timer_start - - if check_timer > 5: # active after around 5 seconds - if not data_freezed and last_version_update == data_scor.mVersionUpdateEnd: + if time.time() - check_timer_start > 5: + if (not data_freezed + and last_version_update == self._data_scor.mVersionUpdateEnd): update_delay = 0.5 data_freezed = True - self._freezed = True + self._paused = True self._logger.info( - "sharedmemory mapping freezed, version %s", + "sharedmemory mapping data paused, version %s", last_version_update ) - last_version_update = data_scor.mVersionUpdateEnd + last_version_update = self._data_scor.mVersionUpdateEnd check_timer_start = time.time() # reset timer - if data_freezed and last_version_update != data_scor.mVersionUpdateEnd: + if (data_freezed + and last_version_update != self._data_scor.mVersionUpdateEnd): data_freezed = False - self._freezed = False + self._paused = False update_delay = 0.01 self._logger.info( - "sharedmemory mapping unfreezed, version %s", - data_scor.mVersionUpdateEnd + "sharedmemory mapping data unpaused, version %s", + self._data_scor.mVersionUpdateEnd ) time.sleep(update_delay) - self.stopped = True - self._freezed = False + self._stopped = True + self._paused = False self._logger.info("sharedmemory data updating thread stopped") - def startUpdating(self): - """ Start data updating thread """ - if self.stopped: - self.data_updating = True - self.stopped = False - self.data_thread = threading.Thread(target=self.__infoUpdate, daemon=True) - self.data_thread.start() - self._logger.info("sharedmemory data updating thread started") - - def stopUpdating(self): - """ Stop data updating thread """ - self.data_updating = False - self.data_thread.join() - - def syncedVehicleTelemetry(self): - """ Get the variable for the player's vehicle """ - return self.LastTelePlayer - - def syncedVehicleScoring(self): - """ Get the variable for the player's vehicle """ - return self.LastScorPlayer - - def freezed(self): - """ Check whether data freezed """ - return self._freezed + def start(self): + """Start data updating thread""" + if not self._stopped: + return None + self.start_mmap() + self.copy_mmap() + self.copy_mmap_player() + self._updating = True + self._stopped = False + self._thread = threading.Thread(target=self.__update, daemon=True) + self._thread.start() + self._logger.info("sharedmemory data updating thread started") + + def stop(self): + """Stop data updating thread""" + self._updating = False + self._thread.join() + self.close_mmap() + + def restart(self): + """Redtart data updating thread""" + if self._restarting: + return None + self._restarting = True + self.stop() + self.start() + self._restarting = False + + @property + def rf2Scor(self): + """rF2 vehicle scoring data""" + return self._data_scor + + @property + def rf2Tele(self): + """rF2 vehicle telemetry data""" + return self._data_tele + + @property + def rf2Ext(self): + """rF2 extended data""" + return self._data_ext + + @property + def rf2Ffb(self): + """rF2 force feedback data""" + return self._data_ffb + + @property + def playerTele(self): + """rF2 local player's vehicle telemetry data""" + return self._player_tele + + @property + def playerScor(self): + """rF2 local player's vehicle scoring data""" + return self._player_scor + + @property + def playerTeleIndex(self): + """rF2 local player's telemetry index""" + return self._player_tele_index + + @property + def playerScorIndex(self): + """rF2 local player's scoring index""" + return self._player_scor_index + + @property + def paused(self): + """Check whether data stopped updating""" + return self._paused @staticmethod def cbytes2str(bytestring): - """ Convert bytes to string """ + """Convert bytes to string""" if type(bytestring) == bytes: return bytestring.decode(errors="replace").rstrip() return "" - def __del__(self): - self.close() - if __name__ == "__main__": + # Add logger + logger = logging.getLogger(__name__) + test_handler = logging.StreamHandler() + logger.setLevel(logging.INFO) + logger.addHandler(test_handler) + # Example usage - info = SimInfoSync() - info.startUpdating() + info = SimInfoSync(__name__) + info.start() time.sleep(0.5) - version = info.cbytes2str(info.LastExt.mVersion) - clutch = info.LastTele.mVehicles[0].mUnfilteredClutch # 1.0 clutch down, 0 clutch up - gear = info.LastTele.mVehicles[0].mGear # -1 to 6 + version = info.cbytes2str(info.rf2Ext.mVersion) + clutch = info.rf2Tele.mVehicles[0].mUnfilteredClutch # 1.0 clutch down, 0 clutch up + gear = info.rf2Tele.mVehicles[0].mGear # -1 to 6 print(f"API version: {version if version else 'unknown'}\n" f"Gear: {gear}\nClutch position: {clutch}") - info.stopUpdating() + print("Test - API restart") + info.restart() + print("Test - API quit") + info.stop() From 35488cd8ea078d2447446305ceee4ec3c709a88c Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 22 Jun 2023 12:47:25 +0800 Subject: [PATCH 51/93] seprate local player scor & tele index finding method --- sim_info_sync.py | 81 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 2052a4a..d3c1170 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -131,22 +131,49 @@ def __init__(self, logger=__name__): self._paused = True self._player_scor_index = INVALID_INDEX self._player_tele_index = INVALID_INDEX + self._player_scor_mid = 0 - def __local_player_data(self, data_scor, data_tele): - """Get local player data""" + @staticmethod + def __find_local_scor_index(data_scor): + """Find local player scoring index""" for idx_scor in range(MAX_VEHICLES): if data_scor.mVehicles[idx_scor].mIsPlayer: - self._player_scor_index = idx_scor - self._player_scor = copy.deepcopy(data_scor.mVehicles[idx_scor]) - mid_scor = data_scor.mVehicles[idx_scor].mID - - for idx_tele in range(MAX_VEHICLES): - if data_tele.mVehicles[idx_tele].mID == mid_scor: - self._player_tele_index = idx_tele - self._player_tele = copy.deepcopy(data_tele.mVehicles[idx_tele]) - return True - return True - return False + return idx_scor + return INVALID_INDEX + + @staticmethod + def __find_local_tele_index(data_tele, mid_scor): + """Find local player telemetry index + + Telemetry index can be different from scoring index. + Use mID matching to find telemetry index. + """ + for idx_tele in range(MAX_VEHICLES): + if data_tele.mVehicles[idx_tele].mID == mid_scor: + return idx_tele + return INVALID_INDEX + + def __sync_local_player_data(self, data_scor, data_tele): + """Sync local player data + + 1. Find scoring index, break if not found. + 2. Update scoring index, mID, copy scoring data. + 3. Find telemetry index, break if not found. + 4. Update telemetry index, copy telemetry data. + """ + idx_scor = self.__find_local_scor_index(data_scor) + if idx_scor == INVALID_INDEX: + return False + self._player_scor_index = idx_scor + self._player_scor_mid = data_scor.mVehicles[idx_scor].mID + self._player_scor = copy.deepcopy(data_scor.mVehicles[idx_scor]) + + idx_tele = self.__find_local_tele_index(data_tele, self._player_scor_mid) + if idx_tele == INVALID_INDEX: + return True # found 1 index + self._player_tele_index = idx_tele + self._player_tele = copy.deepcopy(data_tele.mVehicles[idx_tele]) + return True # found 2 index def find_player_index_tele(self, index_scor): """Find player index using mID""" @@ -170,18 +197,22 @@ def __update(self): update_delay = 0.5 # longer delay while inactive while self._updating: - self.copy_mmap() + data_scor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) + data_tele = rF2data.rF2Telemetry.from_buffer_copy(self._rf2_tele) + self._data_ext = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) + self._data_ffb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) # Update player data & index if (not data_freezed - and self.ver_check(self._data_scor) - and self.ver_check(self._data_tele)): + and self.ver_check(data_scor) + and self.ver_check(data_tele)): + self._data_scor = data_scor + self._data_tele = data_tele # Get player data - player_data = self.__local_player_data( - self._data_scor, self._data_tele) + data_synced = self.__sync_local_player_data(data_scor, data_tele) # Pause if local player index no longer exists, 5 tries - if not player_data and reset_counter < 6: + if not data_synced and reset_counter < 6: reset_counter += 1 - elif player_data: + elif data_synced: reset_counter = 0 self._paused = False # Activate pause @@ -192,7 +223,7 @@ def __update(self): # Start checking data version update status if time.time() - check_timer_start > 5: if (not data_freezed - and last_version_update == self._data_scor.mVersionUpdateEnd): + and last_version_update == data_scor.mVersionUpdateEnd): update_delay = 0.5 data_freezed = True self._paused = True @@ -200,17 +231,17 @@ def __update(self): "sharedmemory mapping data paused, version %s", last_version_update ) - last_version_update = self._data_scor.mVersionUpdateEnd + last_version_update = data_scor.mVersionUpdateEnd check_timer_start = time.time() # reset timer if (data_freezed - and last_version_update != self._data_scor.mVersionUpdateEnd): + and last_version_update != data_scor.mVersionUpdateEnd): + update_delay = 0.01 data_freezed = False self._paused = False - update_delay = 0.01 self._logger.info( "sharedmemory mapping data unpaused, version %s", - self._data_scor.mVersionUpdateEnd + data_scor.mVersionUpdateEnd ) time.sleep(update_delay) From b7d8bf7c21212533e0214ee813eaec0465e30cb4 Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 22 Jun 2023 13:00:04 +0800 Subject: [PATCH 52/93] add comments for mmap methods --- sim_info_sync.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index d3c1170..292b1bb 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -63,14 +63,23 @@ def start_mmap(self): self._logger.info("sharedmemory mapping started") def copy_mmap(self): - """Copy memory mapping data""" + """Copy memory mapping data + + Accessing shared memory data by using buffer_copy on mmap data instance, + which ensures that orginal constantly updated mmap instance + would not unexpectedly interrupt data verification and synchronizing. + """ self._data_scor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) self._data_tele = rF2data.rF2Telemetry.from_buffer_copy(self._rf2_tele) self._data_ext = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) self._data_ffb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) def copy_mmap_player(self): - """Copy memory mapping player data""" + """Copy memory mapping player data + + Maintain a separate copy of synchronized local player's data + which avoids data interruption or desync in case of player index changes. + """ self._player_scor = copy.deepcopy(self._data_scor.mVehicles[INVALID_INDEX]) self._player_tele = copy.deepcopy(self._data_tele.mVehicles[INVALID_INDEX]) From 32787cec1845cf76fffd709221b20f010fceb183 Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 22 Jun 2023 13:50:14 +0800 Subject: [PATCH 53/93] add direct access mmap --- sim_info_sync.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 292b1bb..c27022e 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -62,10 +62,21 @@ def start_mmap(self): ) self._logger.info("sharedmemory mapping started") + def direct_access_mmap(self): + """Direct access memory mapping data + + Direct accessing mmap data instance by using from_buffer. + This may result unexpected data interruption or desync issue. + """ + self._data_scor = rF2data.rF2Scoring.from_buffer(self._rf2_scor) + self._data_tele = rF2data.rF2Telemetry.from_buffer(self._rf2_tele) + self._data_ext = rF2data.rF2Extended.from_buffer(self._rf2_ext) + self._data_ffb = rF2data.rF2ForceFeedback.from_buffer(self._rf2_ffb) + def copy_mmap(self): """Copy memory mapping data - Accessing shared memory data by using buffer_copy on mmap data instance, + Accessing shared memory data by using from_buffer_copy on mmap data instance, which ensures that orginal constantly updated mmap instance would not unexpectedly interrupt data verification and synchronizing. """ @@ -260,7 +271,10 @@ def __update(self): self._logger.info("sharedmemory data updating thread stopped") def start(self): - """Start data updating thread""" + """Start data updating thread + + Update & sync mmap data copy in separate thread. + """ if not self._stopped: return None self.start_mmap() From 7d0dcffc38d1d8e35eb1285fa3a407b1a6859de1 Mon Sep 17 00:00:00 2001 From: Xiang Date: Mon, 26 Jun 2023 02:30:29 +0800 Subject: [PATCH 54/93] add access mode control, reusable mmap class --- sim_info_sync.py | 252 ++++++++++++++++++++++++++--------------------- 1 file changed, 139 insertions(+), 113 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index c27022e..d3382c7 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -23,87 +23,76 @@ class rF2MMap: - """Create mmap for accessing rF2 shared memory""" + """Create mmap for accessing rF2 shared memory - def __init__(self, logger=__name__): + mmap_name: mmap filename, ex. $rFactor2SMMP_Scoring$ + rf2_data: rf2 data class defined in rF2data.py, ex. rF2data.rF2Scoring + rf2_pid: rf2 Process ID for server + logger: logger name + """ + + def __init__(self, mmap_name, rf2_data, rf2_pid="", logger=__name__): self._logger = logging.getLogger(logger) - self._rf2_pid = "" - self._rf2_scor = None - self._rf2_tele = None - self._rf2_ext = None - self._rf2_ffb = None - self._data_scor = None - self._data_tele = None - self._data_ext = None - self._data_ffb = None - self._player_scor = None - self._player_tele = None - - def start_mmap(self): - """Start memory mapping""" - self._rf2_scor = self.platform_mmap( - name="$rFactor2SMMP_Scoring$", - size=ctypes.sizeof(rF2data.rF2Scoring), + self._mmap_name = mmap_name + self._rf2_data = rf2_data + self._rf2_pid = rf2_pid + self._mmap_inst = None + self._mmap_data = None + self._direct_access_active = False + + def create(self, access_mode=0): + """Create mmap instance""" + self._mmap_inst = self.platform_mmap( + name=self._mmap_name, + size=ctypes.sizeof(self._rf2_data), pid=self._rf2_pid ) - self._rf2_tele = self.platform_mmap( - name="$rFactor2SMMP_Telemetry$", - size=ctypes.sizeof(rF2data.rF2Telemetry), - pid=self._rf2_pid - ) - self._rf2_ext = self.platform_mmap( - name="$rFactor2SMMP_Extended$", - size=ctypes.sizeof(rF2data.rF2Extended), - pid=self._rf2_pid + mode_text = "Direct" if access_mode else "Copy" + self._logger.info( + "sharedmemory - %s > ACTIVE: %s Access", + self._mmap_name.strip("$"), mode_text ) - self._rf2_ffb = self.platform_mmap( - name="$rFactor2SMMP_ForceFeedback$", - size=ctypes.sizeof(rF2data.rF2ForceFeedback), - ) - self._logger.info("sharedmemory mapping started") - def direct_access_mmap(self): - """Direct access memory mapping data + def close(self): + """Close memory mapping - Direct accessing mmap data instance by using from_buffer. - This may result unexpected data interruption or desync issue. + Create a final accessible mmap data copy before closing mmap instance. """ - self._data_scor = rF2data.rF2Scoring.from_buffer(self._rf2_scor) - self._data_tele = rF2data.rF2Telemetry.from_buffer(self._rf2_tele) - self._data_ext = rF2data.rF2Extended.from_buffer(self._rf2_ext) - self._data_ffb = rF2data.rF2ForceFeedback.from_buffer(self._rf2_ffb) + self.copy_access() + self._direct_access_active = False + try: + self._mmap_inst.close() + self._logger.info("sharedmemory - %s > CLOSE", self._mmap_name.strip("$")) + except BufferError: + self._logger.error("sharedmemory - failed to close mmap") + + @staticmethod + def version_check(data): + """Data version check""" + return data.mVersionUpdateEnd == data.mVersionUpdateBegin - def copy_mmap(self): - """Copy memory mapping data + def direct_access(self): + """Direct access memory map - Accessing shared memory data by using from_buffer_copy on mmap data instance, - which ensures that orginal constantly updated mmap instance - would not unexpectedly interrupt data verification and synchronizing. + Direct accessing mmap data instance. + May result data desync or interruption. """ - self._data_scor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) - self._data_tele = rF2data.rF2Telemetry.from_buffer_copy(self._rf2_tele) - self._data_ext = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) - self._data_ffb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) + if self._direct_access_active: + return None + self._mmap_data = self._rf2_data.from_buffer(self._mmap_inst) + self._direct_access_active = True - def copy_mmap_player(self): - """Copy memory mapping player data + def copy_access(self): + """Copy access memory map - Maintain a separate copy of synchronized local player's data - which avoids data interruption or desync in case of player index changes. + Accessing mmap data by copying mmap instance + and using version check to avoid data desync or interruption. """ - self._player_scor = copy.deepcopy(self._data_scor.mVehicles[INVALID_INDEX]) - self._player_tele = copy.deepcopy(self._data_tele.mVehicles[INVALID_INDEX]) - - def close_mmap(self): - """Close memory mapping""" - try: - self._rf2_tele.close() - self._rf2_scor.close() - self._rf2_ext.close() - self._rf2_ffb.close() - self._logger.info("sharedmemory mapping closed") - except BufferError: # "cannot close exported pointers exist" - self._logger.error("failed to close mmap") + data_temp = self._rf2_data.from_buffer_copy(self._mmap_inst) + if self.version_check(data_temp): + self._mmap_data = data_temp + elif not self._mmap_data: + self._mmap_data = data_temp def platform_mmap(self, name, size, pid=""): """Platform memory mapping""" @@ -126,25 +115,23 @@ def linux_mmap(name, size): return mmap.mmap(file.fileno(), size) @property - def rf2PID(self): - """rF2 process id""" - return self._rf2_pid + def data(self): + """Access rF2 mmap data""" + return self._mmap_data - @rf2PID.setter - def rf2PID(self, pid): - """Set rF2 process id""" - self._rf2_pid = pid - -class SimInfoSync(rF2MMap): +class SimInfoSync(): """ API for rF2 shared memory Player-Synced data. + Access mode: 0 = copy access, 1 = direct access """ - def __init__(self, logger=__name__): - super().__init__(logger) + def __init__(self, access_mode=0, rf2_pid="", logger=__name__): + self._access_mode = access_mode + self._logger = logging.getLogger(logger) + self._stopped = True self._updating = False self._restarting = False @@ -152,6 +139,7 @@ def __init__(self, logger=__name__): self._player_scor_index = INVALID_INDEX self._player_tele_index = INVALID_INDEX self._player_scor_mid = 0 + self.init_mmap(rf2_pid, logger) @staticmethod def __find_local_scor_index(data_scor): @@ -197,17 +185,12 @@ def __sync_local_player_data(self, data_scor, data_tele): def find_player_index_tele(self, index_scor): """Find player index using mID""" - scor_mid = self._data_scor.mVehicles[index_scor].mID + scor_mid = self._info_scor.data.mVehicles[index_scor].mID for index in range(MAX_VEHICLES): - if self._data_tele.mVehicles[index].mID == scor_mid: + if self._info_tele.data.mVehicles[index].mID == scor_mid: return index return INVALID_INDEX - @staticmethod - def ver_check(data): - """Update version check""" - return data.mVersionUpdateEnd == data.mVersionUpdateBegin - def __update(self): """Update synced player data""" last_version_update = 0 # store last data version update @@ -217,18 +200,12 @@ def __update(self): update_delay = 0.5 # longer delay while inactive while self._updating: - data_scor = rF2data.rF2Scoring.from_buffer_copy(self._rf2_scor) - data_tele = rF2data.rF2Telemetry.from_buffer_copy(self._rf2_tele) - self._data_ext = rF2data.rF2Extended.from_buffer_copy(self._rf2_ext) - self._data_ffb = rF2data.rF2ForceFeedback.from_buffer_copy(self._rf2_ffb) + self.update_mmap() # Update player data & index - if (not data_freezed - and self.ver_check(data_scor) - and self.ver_check(data_tele)): - self._data_scor = data_scor - self._data_tele = data_tele + if not data_freezed: # Get player data - data_synced = self.__sync_local_player_data(data_scor, data_tele) + data_synced = self.__sync_local_player_data( + self._info_scor.data, self._info_tele.data) # Pause if local player index no longer exists, 5 tries if not data_synced and reset_counter < 6: reset_counter += 1 @@ -238,37 +215,37 @@ def __update(self): # Activate pause if reset_counter == 5: self._paused = True - self._logger.info("sharedmemory mapping player data paused") + self._logger.info("sharedmemory - player data paused") # Start checking data version update status if time.time() - check_timer_start > 5: if (not data_freezed - and last_version_update == data_scor.mVersionUpdateEnd): + and last_version_update == self._info_scor.data.mVersionUpdateEnd): update_delay = 0.5 data_freezed = True self._paused = True self._logger.info( - "sharedmemory mapping data paused, version %s", + "sharedmemory - data paused, version %s", last_version_update ) - last_version_update = data_scor.mVersionUpdateEnd + last_version_update = self._info_scor.data.mVersionUpdateEnd check_timer_start = time.time() # reset timer if (data_freezed - and last_version_update != data_scor.mVersionUpdateEnd): + and last_version_update != self._info_scor.data.mVersionUpdateEnd): update_delay = 0.01 data_freezed = False self._paused = False self._logger.info( - "sharedmemory mapping data unpaused, version %s", - data_scor.mVersionUpdateEnd + "sharedmemory - data unpaused, version %s", + self._info_scor.data.mVersionUpdateEnd ) time.sleep(update_delay) self._stopped = True self._paused = False - self._logger.info("sharedmemory data updating thread stopped") + self._logger.info("sharedmemory - updating thread stopped") def start(self): """Start data updating thread @@ -277,14 +254,15 @@ def start(self): """ if not self._stopped: return None - self.start_mmap() - self.copy_mmap() + + self.create_mmap() + self.update_mmap() self.copy_mmap_player() self._updating = True self._stopped = False self._thread = threading.Thread(target=self.__update, daemon=True) self._thread.start() - self._logger.info("sharedmemory data updating thread started") + self._logger.info("sharedmemory - updating thread started") def stop(self): """Stop data updating thread""" @@ -292,8 +270,9 @@ def stop(self): self._thread.join() self.close_mmap() - def restart(self): - """Redtart data updating thread""" + def restart(self, access_mode=0): + """Restart data updating thread""" + self._access_mode = access_mode if self._restarting: return None self._restarting = True @@ -301,25 +280,72 @@ def restart(self): self.start() self._restarting = False + def init_mmap(self, rf2_pid, logger): + """Initialize mmap info""" + self._info_scor = rF2MMap( + "$rFactor2SMMP_Scoring$",rF2data.rF2Scoring, rf2_pid, logger) + self._info_tele = rF2MMap( + "$rFactor2SMMP_Telemetry$", rF2data.rF2Telemetry, rf2_pid, logger) + self._info_ext = rF2MMap( + "$rFactor2SMMP_Extended$", rF2data.rF2Extended, rf2_pid, logger) + self._info_ffb = rF2MMap( + "$rFactor2SMMP_ForceFeedback$", rF2data.rF2ForceFeedback, "", logger) + + def create_mmap(self): + """Create mmap instance""" + self._info_scor.create(self._access_mode) + self._info_tele.create(self._access_mode) + self._info_ext.create(self._access_mode) + self._info_ffb.create(self._access_mode) + + def close_mmap(self): + """Close mmap instance""" + self._info_scor.close() + self._info_tele.close() + self._info_ext.close() + self._info_ffb.close() + + def update_mmap(self): + """Update mmap data""" + if self._access_mode: + self._info_scor.direct_access() + self._info_tele.direct_access() + self._info_ext.direct_access() + self._info_ffb.direct_access() + else: + self._info_scor.copy_access() + self._info_tele.copy_access() + self._info_ext.copy_access() + self._info_ffb.copy_access() + + def copy_mmap_player(self): + """Copy memory mapping player data + + Maintain a separate copy of synchronized local player's data + which avoids data interruption or desync in case of player index changes. + """ + self._player_scor = copy.deepcopy(self._info_scor.data.mVehicles[INVALID_INDEX]) + self._player_tele = copy.deepcopy(self._info_tele.data.mVehicles[INVALID_INDEX]) + @property def rf2Scor(self): """rF2 vehicle scoring data""" - return self._data_scor + return self._info_scor.data @property def rf2Tele(self): """rF2 vehicle telemetry data""" - return self._data_tele + return self._info_tele.data @property def rf2Ext(self): """rF2 extended data""" - return self._data_ext + return self._info_ext.data @property def rf2Ffb(self): """rF2 force feedback data""" - return self._data_ffb + return self._info_ffb.data @property def playerTele(self): @@ -362,7 +388,7 @@ def cbytes2str(bytestring): logger.addHandler(test_handler) # Example usage - info = SimInfoSync(__name__) + info = SimInfoSync(access_mode=0, logger=__name__) info.start() time.sleep(0.5) version = info.cbytes2str(info.rf2Ext.mVersion) From cedc4069777c8a31bd84e7090bffaf5a7fdbe7b1 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sat, 8 Jul 2023 18:55:32 +0800 Subject: [PATCH 55/93] add pid & mode setter --- sim_info_sync.py | 69 +++++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index d3382c7..b57fe99 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -31,21 +31,22 @@ class rF2MMap: logger: logger name """ - def __init__(self, mmap_name, rf2_data, rf2_pid="", logger=__name__): + def __init__(self, mmap_name, rf2_data, logger=__name__): self._logger = logging.getLogger(logger) self._mmap_name = mmap_name self._rf2_data = rf2_data - self._rf2_pid = rf2_pid self._mmap_inst = None self._mmap_data = None + self._access_mode = 0 self._direct_access_active = False - def create(self, access_mode=0): + def create(self, access_mode=0, rf2_pid=""): """Create mmap instance""" + self._access_mode = access_mode self._mmap_inst = self.platform_mmap( name=self._mmap_name, size=ctypes.sizeof(self._rf2_data), - pid=self._rf2_pid + pid=rf2_pid ) mode_text = "Direct" if access_mode else "Copy" self._logger.info( @@ -114,6 +115,13 @@ def linux_mmap(name, size): file.flush() return mmap.mmap(file.fileno(), size) + def update(self): + """Update rF2 mmap data""" + if self._access_mode: + self.direct_access() + else: + self.copy_access() + @property def data(self): """Access rF2 mmap data""" @@ -128,10 +136,7 @@ class SimInfoSync(): Access mode: 0 = copy access, 1 = direct access """ - def __init__(self, access_mode=0, rf2_pid="", logger=__name__): - self._access_mode = access_mode - self._logger = logging.getLogger(logger) - + def __init__(self, logger=__name__): self._stopped = True self._updating = False self._restarting = False @@ -139,7 +144,10 @@ def __init__(self, access_mode=0, rf2_pid="", logger=__name__): self._player_scor_index = INVALID_INDEX self._player_tele_index = INVALID_INDEX self._player_scor_mid = 0 - self.init_mmap(rf2_pid, logger) + self._rf2_pid = "" + self._access_mode = 0 + self._logger = logging.getLogger(logger) + self.init_mmap(logger) @staticmethod def __find_local_scor_index(data_scor): @@ -270,9 +278,8 @@ def stop(self): self._thread.join() self.close_mmap() - def restart(self, access_mode=0): + def restart(self): """Restart data updating thread""" - self._access_mode = access_mode if self._restarting: return None self._restarting = True @@ -280,23 +287,23 @@ def restart(self, access_mode=0): self.start() self._restarting = False - def init_mmap(self, rf2_pid, logger): + def init_mmap(self, logger): """Initialize mmap info""" self._info_scor = rF2MMap( - "$rFactor2SMMP_Scoring$",rF2data.rF2Scoring, rf2_pid, logger) + "$rFactor2SMMP_Scoring$", rF2data.rF2Scoring, logger) self._info_tele = rF2MMap( - "$rFactor2SMMP_Telemetry$", rF2data.rF2Telemetry, rf2_pid, logger) + "$rFactor2SMMP_Telemetry$", rF2data.rF2Telemetry, logger) self._info_ext = rF2MMap( - "$rFactor2SMMP_Extended$", rF2data.rF2Extended, rf2_pid, logger) + "$rFactor2SMMP_Extended$", rF2data.rF2Extended, logger) self._info_ffb = rF2MMap( - "$rFactor2SMMP_ForceFeedback$", rF2data.rF2ForceFeedback, "", logger) + "$rFactor2SMMP_ForceFeedback$", rF2data.rF2ForceFeedback, logger) def create_mmap(self): """Create mmap instance""" - self._info_scor.create(self._access_mode) - self._info_tele.create(self._access_mode) - self._info_ext.create(self._access_mode) - self._info_ffb.create(self._access_mode) + self._info_scor.create(self._access_mode, self._rf2_pid) + self._info_tele.create(self._access_mode, self._rf2_pid) + self._info_ext.create(self._access_mode, self._rf2_pid) + self._info_ffb.create(self._access_mode, self._rf2_pid) def close_mmap(self): """Close mmap instance""" @@ -307,16 +314,10 @@ def close_mmap(self): def update_mmap(self): """Update mmap data""" - if self._access_mode: - self._info_scor.direct_access() - self._info_tele.direct_access() - self._info_ext.direct_access() - self._info_ffb.direct_access() - else: - self._info_scor.copy_access() - self._info_tele.copy_access() - self._info_ext.copy_access() - self._info_ffb.copy_access() + self._info_scor.update() + self._info_tele.update() + self._info_ext.update() + self._info_ffb.update() def copy_mmap_player(self): """Copy memory mapping player data @@ -327,6 +328,14 @@ def copy_mmap_player(self): self._player_scor = copy.deepcopy(self._info_scor.data.mVehicles[INVALID_INDEX]) self._player_tele = copy.deepcopy(self._info_tele.data.mVehicles[INVALID_INDEX]) + def setPID(self, pid=""): + """Set rf2 PID""" + self._rf2_pid = pid + + def setMode(self, mode=0): + """Set rf2 mmap access mode""" + self._access_mode = mode + @property def rf2Scor(self): """rF2 vehicle scoring data""" From 8501abbafc895c411fdede1f65b259ff0b46df7b Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 13 Jul 2023 17:46:03 +0800 Subject: [PATCH 56/93] fix example usage --- sim_info_sync.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index b57fe99..c53a939 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -397,7 +397,9 @@ def cbytes2str(bytestring): logger.addHandler(test_handler) # Example usage - info = SimInfoSync(access_mode=0, logger=__name__) + info = SimInfoSync(logger=__name__) + info.setMode(0) # optional, can be omitted + info.setPID("") # optional, can be omitted info.start() time.sleep(0.5) version = info.cbytes2str(info.rf2Ext.mVersion) From de9d99f0f46f5a1c72af4ab9a5a09a8a456c3680 Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 13 Jul 2023 18:25:14 +0800 Subject: [PATCH 57/93] revert back changes to c types to avoid index out of range issue --- rF2data.py | 196 ++++++++++++++++++++++++++--------------------------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/rF2data.py b/rF2data.py index 9a7372a..a5be637 100644 --- a/rF2data.py +++ b/rF2data.py @@ -173,10 +173,10 @@ class rF2Wheel(ctypes.Structure): ('mPressure', ctypes.c_double), # kPa (tire pressure) ('mTemperature', ctypes.c_double*3), # Kelvin (subtract 273.15 to get Celsius), left/center/right (not to be confused with inside/center/outside!) ('mWear', ctypes.c_double), # wear (0.0-1.0, fraction of maximum) ... this is not necessarily proportional with grip loss - ('mTerrainName', ctypes.c_char*16), # the material prefixes from the TDF file + ('mTerrainName', ctypes.c_ubyte*16), # the material prefixes from the TDF file ('mSurfaceType', ctypes.c_ubyte), # 0=dry, 1=wet, 2=grass, 3=dirt, 4=gravel, 5=rumblestrip, 6 = special - ('mFlat', ctypes.c_bool), # whether tire is flat - ('mDetached', ctypes.c_bool), # whether wheel is detached + ('mFlat', ctypes.c_ubyte), # whether tire is flat + ('mDetached', ctypes.c_ubyte), # whether wheel is detached ('mStaticUndeflectedRadius', ctypes.c_ubyte), # tire radius in centimeters ('mVerticalTireDeflection', ctypes.c_double), # how much is tire deflected from its (speed-sensitive) radius ('mWheelYLocation', ctypes.c_double), # wheel's y location relative to vehicle y location @@ -194,8 +194,8 @@ class rF2VehicleTelemetry(ctypes.Structure): ('mElapsedTime', ctypes.c_double), # game session time ('mLapNumber', ctypes.c_int), # current lap number ('mLapStartET', ctypes.c_double), # time this lap was started - ('mVehicleName', ctypes.c_char*64), # current vehicle name - ('mTrackName', ctypes.c_char*64), # current track name + ('mVehicleName', ctypes.c_ubyte*64), # current vehicle name + ('mTrackName', ctypes.c_ubyte*64), # current track name ('mPos', rF2Vec3), # world position in meters ('mLocalVel', rF2Vec3), # velocity (meters/sec) in local vehicle coordinates ('mLocalAccel', rF2Vec3), # acceleration (meters/sec^2) in local vehicle coordinates @@ -227,9 +227,9 @@ class rF2VehicleTelemetry(ctypes.Structure): ('mFuel', ctypes.c_double), # amount of fuel (liters) ('mEngineMaxRPM', ctypes.c_double), # rev limit ('mScheduledStops', ctypes.c_ubyte), # number of scheduled pitstops - ('mOverheating', ctypes.c_bool), # whether overheating icon is shown - ('mDetached', ctypes.c_bool), # whether any parts (besides wheels) have been detached - ('mHeadlights', ctypes.c_bool), # whether headlights are on + ('mOverheating', ctypes.c_ubyte), # whether overheating icon is shown + ('mDetached', ctypes.c_ubyte), # whether any parts (besides wheels) have been detached + ('mHeadlights', ctypes.c_ubyte), # whether headlights are on ('mDentSeverity', ctypes.c_ubyte*8), # dent severity at 8 locations around the car (0=none, 1=some, 2=more) ('mLastImpactET', ctypes.c_double), # time of last impact ('mLastImpactMagnitude', ctypes.c_double), # magnitude of last impact @@ -245,8 +245,8 @@ class rF2VehicleTelemetry(ctypes.Structure): ('mRearFlapActivated', ctypes.c_ubyte), # whether rear flap is activated ('mRearFlapLegalStatus', ctypes.c_ubyte), # 0=disallowed, 1=criteria detected but not allowed quite yet, 2 = allowed ('mIgnitionStarter', ctypes.c_ubyte), # 0=off 1=ignition 2 = ignition+starter - ('mFrontTireCompoundName', ctypes.c_char*18), # name of front tire compound - ('mRearTireCompoundName', ctypes.c_char*18), # name of rear tire compound + ('mFrontTireCompoundName', ctypes.c_ubyte*18), # name of front tire compound + ('mRearTireCompoundName', ctypes.c_ubyte*18), # name of rear tire compound ('mSpeedLimiterAvailable', ctypes.c_ubyte), # whether speed limiter is available ('mAntiStallActivated', ctypes.c_ubyte), # whether (hard) anti-stall is activated ('mUnused', ctypes.c_ubyte*2), # @@ -269,7 +269,7 @@ class rF2VehicleTelemetry(ctypes.Structure): class rF2ScoringInfo(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mTrackName', ctypes.c_char*64), # current track name + ('mTrackName', ctypes.c_ubyte*64), # current track name ('mSession', ctypes.c_int), # current session (0=testday 1-4=practice 5-8=qual 9=warmup 10-13 = race) ('mCurrentET', ctypes.c_double), # current time ('mEndET', ctypes.c_double), # ending time @@ -278,13 +278,13 @@ class rF2ScoringInfo(ctypes.Structure): ('pointer1', ctypes.c_ubyte*8), ('mNumVehicles', ctypes.c_int), # current number of vehicles ('mGamePhase', ctypes.c_ubyte), - ('mYellowFlagState', ctypes.c_char), - ('mSectorFlag', ctypes.c_char*3), # whether there are any local yellows at the moment in each sector (not sure if sector 0 is first or last, so test) + ('mYellowFlagState', ctypes.c_ubyte), + ('mSectorFlag', ctypes.c_ubyte*3), # whether there are any local yellows at the moment in each sector (not sure if sector 0 is first or last, so test) ('mStartLight', ctypes.c_ubyte), # start light frame (number depends on track) ('mNumRedLights', ctypes.c_ubyte), # number of red lights in start sequence - ('mInRealtime', ctypes.c_bool), # in realtime as opposed to at the monitor - ('mPlayerName', ctypes.c_char*32), # player name (including possible multiplayer override) - ('mPlrFileName', ctypes.c_char*64), # may be encoded to be a legal filename + ('mInRealtime', ctypes.c_ubyte), # in realtime as opposed to at the monitor + ('mPlayerName', ctypes.c_ubyte*32), # player name (including possible multiplayer override) + ('mPlrFileName', ctypes.c_ubyte*64), # may be encoded to be a legal filename ('mDarkCloud', ctypes.c_double), # cloud darkness? 0.0-1.0 ('mRaining', ctypes.c_double), # raining severity 0.0-1.0 ('mAmbientTemp', ctypes.c_double), # temperature (Celsius) @@ -293,11 +293,11 @@ class rF2ScoringInfo(ctypes.Structure): ('mMinPathWetness', ctypes.c_double), # minimum wetness on main path 0.0-1.0 ('mMaxPathWetness', ctypes.c_double), # maximum wetness on main path 0.0-1.0 ('mGameMode', ctypes.c_ubyte), # 1 = server, 2 = client, 3 = server and client - ('mIsPasswordProtected', ctypes.c_bool), # is the server password protected - ('mServerPort', ctypes.c_ushort), # the port of the server (if on a server) - ('mServerPublicIP', ctypes.c_uint), # the public IP address of the server (if on a server) + ('mIsPasswordProtected', ctypes.c_ubyte), # is the server password protected + ('mServerPort', ctypes.c_short), # the port of the server (if on a server) + ('mServerPublicIP', ctypes.c_int), # the public IP address of the server (if on a server) ('mMaxPlayers', ctypes.c_int), # maximum number of vehicles that can be in the session - ('mServerName', ctypes.c_char*32), # name of the server + ('mServerName', ctypes.c_ubyte*32), # name of the server ('mStartET', ctypes.c_float), # start time (seconds since midnight) of the event ('mAvgPathWetness', ctypes.c_double), # average wetness on main path 0.0-1.0 ('mExpansion', ctypes.c_ubyte*200), @@ -308,11 +308,11 @@ class rF2VehicleScoring(ctypes.Structure): _pack_ = 4 _fields_ = [ ('mID', ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) - ('mDriverName', ctypes.c_char*32), # driver name - ('mVehicleName', ctypes.c_char*64), # vehicle name + ('mDriverName', ctypes.c_ubyte*32), # driver name + ('mVehicleName', ctypes.c_ubyte*64), # vehicle name ('mTotalLaps', ctypes.c_short), # laps completed - ('mSector', ctypes.c_byte), # 0=sector3, 1=sector1, 2 = sector2 (don't ask why) - ('mFinishStatus', ctypes.c_byte), # 0=none, 1=finished, 2=dnf, 3 = dq + ('mSector', ctypes.c_ubyte), # 0=sector3, 1=sector1, 2 = sector2 (don't ask why) + ('mFinishStatus', ctypes.c_ubyte), # 0=none, 1=finished, 2=dnf, 3 = dq ('mLapDist', ctypes.c_double), # current distance around track ('mPathLateral', ctypes.c_double), # lateral position with respect to *very approximate* "center" path ('mTrackEdge', ctypes.c_double), # track edge (w.r.t. "center" path) on same side of track as vehicle @@ -326,11 +326,11 @@ class rF2VehicleScoring(ctypes.Structure): ('mCurSector2', ctypes.c_double), # current sector 2 (plus sector 1) if valid ('mNumPitstops', ctypes.c_short), # number of pitstops made ('mNumPenalties', ctypes.c_short), # number of outstanding penalties - ('mIsPlayer', ctypes.c_bool), # is this the player's vehicle - ('mControl', ctypes.c_byte), # who's in control: -1=nobody (shouldn't get this), 0=local player, 1=local AI, 2=remote, 3 = replay (shouldn't get this) - ('mInPits', ctypes.c_bool), # between pit entrance and pit exit (not always accurate for remote vehicles) + ('mIsPlayer', ctypes.c_ubyte), # is this the player's vehicle + ('mControl', ctypes.c_ubyte), # who's in control: -1=nobody (shouldn't get this), 0=local player, 1=local AI, 2=remote, 3 = replay (shouldn't get this) + ('mInPits', ctypes.c_ubyte), # between pit entrance and pit exit (not always accurate for remote vehicles) ('mPlace', ctypes.c_ubyte), # 1-based position - ('mVehicleClass', ctypes.c_char*32), # vehicle class + ('mVehicleClass', ctypes.c_ubyte*32), # vehicle class ('mTimeBehindNext', ctypes.c_double), # time behind vehicle in next higher place ('mLapsBehindNext', ctypes.c_int), # laps behind vehicle in next higher place ('mTimeBehindLeader', ctypes.c_double), # time behind leader @@ -349,11 +349,11 @@ class rF2VehicleScoring(ctypes.Structure): ('mQualification', ctypes.c_int), # 1-based, can be -1 when invalid ('mTimeIntoLap', ctypes.c_double), # estimated time into lap ('mEstimatedLapTime', ctypes.c_double), # estimated laptime used for 'time behind' and 'time into lap' (note: this may changed based on vehicle and setup!?) - ('mPitGroup', ctypes.c_char*24), # pit group (same as team name unless pit is shared) + ('mPitGroup', ctypes.c_ubyte*24), # pit group (same as team name unless pit is shared) ('mFlag', ctypes.c_ubyte), # primary flag being shown to vehicle (currently only 0=green or 6 = blue) - ('mUnderYellow', ctypes.c_bool), # whether this car has taken a full-course caution flag at the start/finish line + ('mUnderYellow', ctypes.c_ubyte), # whether this car has taken a full-course caution flag at the start/finish line ('mCountLapFlag', ctypes.c_ubyte), # 0 = do not count lap or time, 1 = count lap but not time, 2 = count lap and time - ('mInGarageStall', ctypes.c_bool), # appears to be within the correct garage stall + ('mInGarageStall', ctypes.c_ubyte), # appears to be within the correct garage stall ('mUpgradePack', ctypes.c_ubyte*16), # Coded upgrades ('mPitLapDist', ctypes.c_float), # location of pit in terms of lap distance ('mBestLapSector1', ctypes.c_float), # sector 1 time from best lap (not necessarily the best sector 1 time) @@ -436,10 +436,10 @@ class rF2TrackRulesParticipant(ctypes.Structure): ('mColumnAssignment', ctypes.c_int), # which column (line/lane) that participant is supposed to be in ('mPositionAssignment', ctypes.c_int), # 0-based position within column (line/lane) that participant is supposed to be located at (-1 is invalid) ('mPitsOpen', ctypes.c_ubyte), # whether the rules allow this particular vehicle to enter pits right now (input is 2=false or 3=true; if you want to edit it, set to 0=false or 1 = true) - ('mUpToSpeed', ctypes.c_bool), # while in the frozen order, this flag indicates whether the vehicle can be followed (this should be false for somebody who has temporarily spun and hasn't gotten back up to speed yet) - ('mUnused', ctypes.c_bool*2), # + ('mUpToSpeed', ctypes.c_ubyte), # while in the frozen order, this flag indicates whether the vehicle can be followed (this should be false for somebody who has temporarily spun and hasn't gotten back up to speed yet) + ('mUnused', ctypes.c_ubyte*2), # ('mGoalRelativeDistance', ctypes.c_double), # calculated based on where the leader is, and adjusted by the desired column spacing and the column/position assignments - ('mMessage', ctypes.c_char*96), # a message for this participant to explain what is going on it will get run through translator on client machines + ('mMessage', ctypes.c_ubyte*96), # a message for this participant to explain what is going on it will get run through translator on client machines ('mExpansion', ctypes.c_ubyte*192), ] class rF2TrackRulesStage(Enum): @@ -459,10 +459,10 @@ class rF2TrackRules(ctypes.Structure): ('mNumActions', ctypes.c_int), # number of recent actions ('pointer1', ctypes.c_ubyte*8), ('mNumParticipants', ctypes.c_int), # number of participants (vehicles) - ('mYellowFlagDetected', ctypes.c_bool), # whether yellow flag was requested or sum of participant mYellowSeverity's exceeds mSafetyCarThreshold + ('mYellowFlagDetected', ctypes.c_ubyte), # whether yellow flag was requested or sum of participant mYellowSeverity's exceeds mSafetyCarThreshold ('mYellowFlagLapsWasOverridden', ctypes.c_ubyte), # whether mYellowFlagLaps (below) is an admin request (0=no 1=yes 2 = clear yellow) - ('mSafetyCarExists', ctypes.c_bool), # whether safety car even exists - ('mSafetyCarActive', ctypes.c_bool), # whether safety car is active + ('mSafetyCarExists', ctypes.c_ubyte), # whether safety car even exists + ('mSafetyCarActive', ctypes.c_ubyte), # whether safety car is active ('mSafetyCarLaps', ctypes.c_int), # number of laps ('mSafetyCarThreshold', ctypes.c_float), # the threshold at which a safety car is called out (compared to the sum of TrackRulesParticipantV01::mYellowSeverity for each vehicle) ('mSafetyCarLapDist', ctypes.c_double), # safety car lap distance @@ -470,7 +470,7 @@ class rF2TrackRules(ctypes.Structure): ('mPitLaneStartDist', ctypes.c_float), # where the waypoint branch to the pits breaks off (this may not be perfectly accurate) ('mTeleportLapDist', ctypes.c_float), # the front of the teleport locations (a useful first guess as to where to throw the green flag) ('mInputExpansion', ctypes.c_ubyte*256), - ('mYellowFlagState', ctypes.c_byte), # see ScoringInfoV01 for values + ('mYellowFlagState', ctypes.c_ubyte), # see ScoringInfoV01 for values ('mYellowFlagLaps', ctypes.c_short), # suggested number of laps to run under yellow (may be passed in with admin command) ('mSafetyCarInstruction', ctypes.c_int), # 0=no change, 1=go active, 2 = head for pits ('mSafetyCarSpeed', ctypes.c_float), # maximum speed at which to drive @@ -480,7 +480,7 @@ class rF2TrackRules(ctypes.Structure): ('mMaximumColumnSpacing', ctypes.c_float), # maximum desired spacing between vehicles in a column (-1 to indicate indeterminate/unenforced) ('mMinimumSpeed', ctypes.c_float), # minimum speed that anybody should be driving (-1 to indicate no limit) ('mMaximumSpeed', ctypes.c_float), # maximum speed that anybody should be driving (-1 to indicate no limit) - ('mMessage', ctypes.c_char*96), # a message for everybody to explain what is going on (which will get run through translator on client machines) + ('mMessage', ctypes.c_ubyte*96), # a message for everybody to explain what is going on (which will get run through translator on client machines) ('pointer2', ctypes.c_ubyte*8), ('mInputOutputExpansion', ctypes.c_ubyte*256), ] @@ -489,9 +489,9 @@ class rF2PitMenu(ctypes.Structure): _pack_ = 4 _fields_ = [ ('mCategoryIndex', ctypes.c_int), # index of the current category - ('mCategoryName', ctypes.c_char*32), # name of the current category (untranslated) + ('mCategoryName', ctypes.c_ubyte*32), # name of the current category (untranslated) ('mChoiceIndex', ctypes.c_int), # index of the current choice (within the current category) - ('mChoiceString', ctypes.c_char*32), # name of the current choice (may have some translated words) + ('mChoiceString', ctypes.c_ubyte*32), # name of the current choice (may have some translated words) ('mNumChoices', ctypes.c_int), # total number of choices (0 < = mChoiceIndex < mNumChoices) ('mExpansion', ctypes.c_ubyte*256), # for future use ] @@ -504,33 +504,33 @@ class rF2WeatherControlInfo(ctypes.Structure): ('mCloudiness', ctypes.c_double), # general cloudiness (0.0=clear to 1.0 = dark) ('mAmbientTempK', ctypes.c_double), # ambient temperature (Kelvin) ('mWindMaxSpeed', ctypes.c_double), # maximum speed of wind (ground speed, but it affects how fast the clouds move, too) - ('mApplyCloudinessInstantly', ctypes.c_bool), # preferably we roll the new clouds in, but you can instantly change them now - ('mUnused1', ctypes.c_bool), # - ('mUnused2', ctypes.c_bool), # - ('mUnused3', ctypes.c_bool), # + ('mApplyCloudinessInstantly', ctypes.c_ubyte), # preferably we roll the new clouds in, but you can instantly change them now + ('mUnused1', ctypes.c_ubyte), # + ('mUnused2', ctypes.c_ubyte), # + ('mUnused3', ctypes.c_ubyte), # ('mExpansion', ctypes.c_ubyte*508), # future use (humidity, pressure, air density, etc.) ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2MappedBufferVersionBlock(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2MappedBufferVersionBlockWithSize(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Telemetry(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. ('mNumVehicles', ctypes.c_int), # current number of vehicles ('mVehicles', rF2VehicleTelemetry*rFactor2Constants.MAX_MAPPED_VEHICLES), @@ -539,8 +539,8 @@ class rF2Telemetry(ctypes.Structure): class rF2Scoring(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. ('mScoringInfo', rF2ScoringInfo), ('mVehicles', rF2VehicleScoring*rFactor2Constants.MAX_MAPPED_VEHICLES), @@ -549,8 +549,8 @@ class rF2Scoring(ctypes.Structure): class rF2Rules(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. ('mTrackRules', rF2TrackRules), ('mActions', rF2TrackRulesAction*rFactor2Constants.MAX_MAPPED_VEHICLES), @@ -560,8 +560,8 @@ class rF2Rules(ctypes.Structure): class rF2ForceFeedback(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. ('mForceValue', ctypes.c_double), # Current FFB value reported via InternalsPlugin::ForceFeedback. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] @@ -582,24 +582,24 @@ class rF2GraphicsInfo(ctypes.Structure): class rF2Graphics(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. ('mGraphicsInfo', rF2GraphicsInfo), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2PitInfo(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. ('mPitMneu', rF2PitMenu), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Weather(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. ('mTrackNodeSize', ctypes.c_double), ('mWeatherInfo', rF2WeatherControlInfo), ] @@ -616,8 +616,8 @@ class rF2VehScoringCapture(ctypes.Structure): _fields_ = [ ('mID', ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) ('mPlace', ctypes.c_ubyte), - ('mIsPlayer', ctypes.c_bool), - ('mFinishStatus', ctypes.c_byte), # 0=none, 1=finished, 2=dnf, 3 = dq + ('mIsPlayer', ctypes.c_ubyte), + ('mFinishStatus', ctypes.c_ubyte), # 0=none, 1=finished, 2=dnf, 3 = dq ] #untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] class rF2SessionTransitionCapture(ctypes.Structure): @@ -632,57 +632,57 @@ class rF2SessionTransitionCapture(ctypes.Structure): class rF2Extended(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. - ('mVersion', ctypes.c_char*12), # API version - ('is64bit', ctypes.c_bool), # Is 64bit plugin? + ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersion', ctypes.c_ubyte*12), # API version + ('is64bit', ctypes.c_ubyte), # Is 64bit plugin? ('mPhysics', rF2PhysicsOptions), ('mTrackedDamages', rF2TrackedDamage*rFactor2Constants.MAX_MAPPED_IDS), - ('mInRealtimeFC', ctypes.c_bool), # in realtime as opposed to at the monitor (reported via last EnterRealtime/ExitRealtime calls). - ('mMultimediaThreadStarted', ctypes.c_bool), # multimedia thread started (reported via ThreadStarted/ThreadStopped calls). - ('mSimulationThreadStarted', ctypes.c_bool), # simulation thread started (reported via ThreadStarted/ThreadStopped calls). - ('mSessionStarted', ctypes.c_bool), # Set to true on Session Started, set to false on Session Ended. - ('mTicksSessionStarted', ctypes.c_ulonglong), # Ticks when session started. - ('mTicksSessionEnded', ctypes.c_ulonglong), # Ticks when session ended. + ('mInRealtimeFC', ctypes.c_ubyte), # in realtime as opposed to at the monitor (reported via last EnterRealtime/ExitRealtime calls). + ('mMultimediaThreadStarted', ctypes.c_ubyte), # multimedia thread started (reported via ThreadStarted/ThreadStopped calls). + ('mSimulationThreadStarted', ctypes.c_ubyte), # simulation thread started (reported via ThreadStarted/ThreadStopped calls). + ('mSessionStarted', ctypes.c_ubyte), # Set to true on Session Started, set to false on Session Ended. + ('mTicksSessionStarted', ctypes.c_double), # Ticks when session started. + ('mTicksSessionEnded', ctypes.c_double), # Ticks when session ended. ('mSessionTransitionCapture', rF2SessionTransitionCapture),# Contains partial internals capture at session transition time. - ('mDisplayedMessageUpdateCapture', ctypes.c_char*128), - ('mDirectMemoryAccessEnabled', ctypes.c_bool), - ('mTicksStatusMessageUpdated', ctypes.c_ulonglong), # Ticks when status message was updated; - ('mStatusMessage', ctypes.c_char*rFactor2Constants.MAX_STATUS_MSG_LEN), - ('mTicksLastHistoryMessageUpdated', ctypes.c_ulonglong), # Ticks when last message history message was updated; - ('mLastHistoryMessage', ctypes.c_char*rFactor2Constants.MAX_STATUS_MSG_LEN), + ('mDisplayedMessageUpdateCapture', ctypes.c_ubyte*128), + ('mDirectMemoryAccessEnabled', ctypes.c_ubyte), + ('mTicksStatusMessageUpdated', ctypes.c_double), # Ticks when status message was updated; + ('mStatusMessage', ctypes.c_ubyte*rFactor2Constants.MAX_STATUS_MSG_LEN), + ('mTicksLastHistoryMessageUpdated', ctypes.c_double), # Ticks when last message history message was updated; + ('mLastHistoryMessage', ctypes.c_ubyte*rFactor2Constants.MAX_STATUS_MSG_LEN), ('mCurrentPitSpeedLimit', ctypes.c_float), # speed limit m/s. - ('mSCRPluginEnabled', ctypes.c_bool), # Is Stock Car Rules plugin enabled? + ('mSCRPluginEnabled', ctypes.c_ubyte), # Is Stock Car Rules plugin enabled? ('mSCRPluginDoubleFileType', ctypes.c_int), # Stock Car Rules plugin DoubleFileType value, only meaningful if mSCRPluginEnabled is true. - ('mTicksLSIPhaseMessageUpdated', ctypes.c_ulonglong), # Ticks when last LSI phase message was updated. - ('mLSIPhaseMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mTicksLSIPitStateMessageUpdated', ctypes.c_ulonglong), # Ticks when last LSI pit state message was updated. - ('mLSIPitStateMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mTicksLSIOrderInstructionMessageUpdated', ctypes.c_ulonglong), # Ticks when last LSI order instruction message was updated. - ('mLSIOrderInstructionMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mTicksLSIRulesInstructionMessageUpdated', ctypes.c_ulonglong), # Ticks when last FCY rules message was updated. Currently, only SCR plugin sets that. - ('mLSIRulesInstructionMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ('mTicksLSIPhaseMessageUpdated', ctypes.c_double), # Ticks when last LSI phase message was updated. + ('mLSIPhaseMessage', ctypes.c_ubyte*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ('mTicksLSIPitStateMessageUpdated', ctypes.c_double), # Ticks when last LSI pit state message was updated. + ('mLSIPitStateMessage', ctypes.c_ubyte*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ('mTicksLSIOrderInstructionMessageUpdated', ctypes.c_double), # Ticks when last LSI order instruction message was updated. + ('mLSIOrderInstructionMessage', ctypes.c_ubyte*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ('mTicksLSIRulesInstructionMessageUpdated', ctypes.c_double), # Ticks when last FCY rules message was updated. Currently, only SCR plugin sets that. + ('mLSIRulesInstructionMessage', ctypes.c_ubyte*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), ('mUnsubscribedBuffersMask', ctypes.c_int), # Currently active UnsbscribedBuffersMask value. This will be allowed for clients to write to in the future, but not yet. - ('mHWControlInputEnabled', ctypes.c_bool), # HWControl input buffer is enabled. - ('mWeatherControlInputEnabled', ctypes.c_bool), # WeatherControl input buffer is enabled. - ('mRulesControlInputEnabled', ctypes.c_bool), # RulesControl input buffer is enabled. + ('mHWControlInputEnabled', ctypes.c_ubyte), # HWControl input buffer is enabled. + ('mWeatherControlInputEnabled', ctypes.c_ubyte), # WeatherControl input buffer is enabled. + ('mRulesControlInputEnabled', ctypes.c_ubyte), # RulesControl input buffer is enabled. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2HWControl(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. ('mLayoutVersion', ctypes.c_int), - ('mControlName', ctypes.c_char*rFactor2Constants.MAX_HWCONTROL_NAME_LEN), + ('mControlName', ctypes.c_ubyte*rFactor2Constants.MAX_HWCONTROL_NAME_LEN), ('mfRetVal', ctypes.c_double), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2WeatherControl(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. ('mLayoutVersion', ctypes.c_int), ('mWeatherInfo', rF2WeatherControlInfo), ] From e68b639029e68ca2bcf9b68c6bf87efc6502dd5e Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 13 Jul 2023 19:51:08 +0800 Subject: [PATCH 58/93] change c_char back to c_ubyte to avoid mSectorFlag index out of range --- rF2data.py | 194 ++++++++++++++++++++++++++--------------------------- 1 file changed, 97 insertions(+), 97 deletions(-) diff --git a/rF2data.py b/rF2data.py index a5be637..f5fa58a 100644 --- a/rF2data.py +++ b/rF2data.py @@ -173,10 +173,10 @@ class rF2Wheel(ctypes.Structure): ('mPressure', ctypes.c_double), # kPa (tire pressure) ('mTemperature', ctypes.c_double*3), # Kelvin (subtract 273.15 to get Celsius), left/center/right (not to be confused with inside/center/outside!) ('mWear', ctypes.c_double), # wear (0.0-1.0, fraction of maximum) ... this is not necessarily proportional with grip loss - ('mTerrainName', ctypes.c_ubyte*16), # the material prefixes from the TDF file + ('mTerrainName', ctypes.c_char*16), # the material prefixes from the TDF file ('mSurfaceType', ctypes.c_ubyte), # 0=dry, 1=wet, 2=grass, 3=dirt, 4=gravel, 5=rumblestrip, 6 = special - ('mFlat', ctypes.c_ubyte), # whether tire is flat - ('mDetached', ctypes.c_ubyte), # whether wheel is detached + ('mFlat', ctypes.c_bool), # whether tire is flat + ('mDetached', ctypes.c_bool), # whether wheel is detached ('mStaticUndeflectedRadius', ctypes.c_ubyte), # tire radius in centimeters ('mVerticalTireDeflection', ctypes.c_double), # how much is tire deflected from its (speed-sensitive) radius ('mWheelYLocation', ctypes.c_double), # wheel's y location relative to vehicle y location @@ -194,8 +194,8 @@ class rF2VehicleTelemetry(ctypes.Structure): ('mElapsedTime', ctypes.c_double), # game session time ('mLapNumber', ctypes.c_int), # current lap number ('mLapStartET', ctypes.c_double), # time this lap was started - ('mVehicleName', ctypes.c_ubyte*64), # current vehicle name - ('mTrackName', ctypes.c_ubyte*64), # current track name + ('mVehicleName', ctypes.c_char*64), # current vehicle name + ('mTrackName', ctypes.c_char*64), # current track name ('mPos', rF2Vec3), # world position in meters ('mLocalVel', rF2Vec3), # velocity (meters/sec) in local vehicle coordinates ('mLocalAccel', rF2Vec3), # acceleration (meters/sec^2) in local vehicle coordinates @@ -227,9 +227,9 @@ class rF2VehicleTelemetry(ctypes.Structure): ('mFuel', ctypes.c_double), # amount of fuel (liters) ('mEngineMaxRPM', ctypes.c_double), # rev limit ('mScheduledStops', ctypes.c_ubyte), # number of scheduled pitstops - ('mOverheating', ctypes.c_ubyte), # whether overheating icon is shown - ('mDetached', ctypes.c_ubyte), # whether any parts (besides wheels) have been detached - ('mHeadlights', ctypes.c_ubyte), # whether headlights are on + ('mOverheating', ctypes.c_bool), # whether overheating icon is shown + ('mDetached', ctypes.c_bool), # whether any parts (besides wheels) have been detached + ('mHeadlights', ctypes.c_bool), # whether headlights are on ('mDentSeverity', ctypes.c_ubyte*8), # dent severity at 8 locations around the car (0=none, 1=some, 2=more) ('mLastImpactET', ctypes.c_double), # time of last impact ('mLastImpactMagnitude', ctypes.c_double), # magnitude of last impact @@ -245,8 +245,8 @@ class rF2VehicleTelemetry(ctypes.Structure): ('mRearFlapActivated', ctypes.c_ubyte), # whether rear flap is activated ('mRearFlapLegalStatus', ctypes.c_ubyte), # 0=disallowed, 1=criteria detected but not allowed quite yet, 2 = allowed ('mIgnitionStarter', ctypes.c_ubyte), # 0=off 1=ignition 2 = ignition+starter - ('mFrontTireCompoundName', ctypes.c_ubyte*18), # name of front tire compound - ('mRearTireCompoundName', ctypes.c_ubyte*18), # name of rear tire compound + ('mFrontTireCompoundName', ctypes.c_char*18), # name of front tire compound + ('mRearTireCompoundName', ctypes.c_char*18), # name of rear tire compound ('mSpeedLimiterAvailable', ctypes.c_ubyte), # whether speed limiter is available ('mAntiStallActivated', ctypes.c_ubyte), # whether (hard) anti-stall is activated ('mUnused', ctypes.c_ubyte*2), # @@ -269,7 +269,7 @@ class rF2VehicleTelemetry(ctypes.Structure): class rF2ScoringInfo(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mTrackName', ctypes.c_ubyte*64), # current track name + ('mTrackName', ctypes.c_char*64), # current track name ('mSession', ctypes.c_int), # current session (0=testday 1-4=practice 5-8=qual 9=warmup 10-13 = race) ('mCurrentET', ctypes.c_double), # current time ('mEndET', ctypes.c_double), # ending time @@ -278,13 +278,13 @@ class rF2ScoringInfo(ctypes.Structure): ('pointer1', ctypes.c_ubyte*8), ('mNumVehicles', ctypes.c_int), # current number of vehicles ('mGamePhase', ctypes.c_ubyte), - ('mYellowFlagState', ctypes.c_ubyte), + ('mYellowFlagState', ctypes.c_char), ('mSectorFlag', ctypes.c_ubyte*3), # whether there are any local yellows at the moment in each sector (not sure if sector 0 is first or last, so test) ('mStartLight', ctypes.c_ubyte), # start light frame (number depends on track) ('mNumRedLights', ctypes.c_ubyte), # number of red lights in start sequence - ('mInRealtime', ctypes.c_ubyte), # in realtime as opposed to at the monitor - ('mPlayerName', ctypes.c_ubyte*32), # player name (including possible multiplayer override) - ('mPlrFileName', ctypes.c_ubyte*64), # may be encoded to be a legal filename + ('mInRealtime', ctypes.c_bool), # in realtime as opposed to at the monitor + ('mPlayerName', ctypes.c_char*32), # player name (including possible multiplayer override) + ('mPlrFileName', ctypes.c_char*64), # may be encoded to be a legal filename ('mDarkCloud', ctypes.c_double), # cloud darkness? 0.0-1.0 ('mRaining', ctypes.c_double), # raining severity 0.0-1.0 ('mAmbientTemp', ctypes.c_double), # temperature (Celsius) @@ -293,11 +293,11 @@ class rF2ScoringInfo(ctypes.Structure): ('mMinPathWetness', ctypes.c_double), # minimum wetness on main path 0.0-1.0 ('mMaxPathWetness', ctypes.c_double), # maximum wetness on main path 0.0-1.0 ('mGameMode', ctypes.c_ubyte), # 1 = server, 2 = client, 3 = server and client - ('mIsPasswordProtected', ctypes.c_ubyte), # is the server password protected - ('mServerPort', ctypes.c_short), # the port of the server (if on a server) - ('mServerPublicIP', ctypes.c_int), # the public IP address of the server (if on a server) + ('mIsPasswordProtected', ctypes.c_bool), # is the server password protected + ('mServerPort', ctypes.c_ushort), # the port of the server (if on a server) + ('mServerPublicIP', ctypes.c_uint), # the public IP address of the server (if on a server) ('mMaxPlayers', ctypes.c_int), # maximum number of vehicles that can be in the session - ('mServerName', ctypes.c_ubyte*32), # name of the server + ('mServerName', ctypes.c_char*32), # name of the server ('mStartET', ctypes.c_float), # start time (seconds since midnight) of the event ('mAvgPathWetness', ctypes.c_double), # average wetness on main path 0.0-1.0 ('mExpansion', ctypes.c_ubyte*200), @@ -308,11 +308,11 @@ class rF2VehicleScoring(ctypes.Structure): _pack_ = 4 _fields_ = [ ('mID', ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) - ('mDriverName', ctypes.c_ubyte*32), # driver name - ('mVehicleName', ctypes.c_ubyte*64), # vehicle name + ('mDriverName', ctypes.c_char*32), # driver name + ('mVehicleName', ctypes.c_char*64), # vehicle name ('mTotalLaps', ctypes.c_short), # laps completed - ('mSector', ctypes.c_ubyte), # 0=sector3, 1=sector1, 2 = sector2 (don't ask why) - ('mFinishStatus', ctypes.c_ubyte), # 0=none, 1=finished, 2=dnf, 3 = dq + ('mSector', ctypes.c_byte), # 0=sector3, 1=sector1, 2 = sector2 (don't ask why) + ('mFinishStatus', ctypes.c_byte), # 0=none, 1=finished, 2=dnf, 3 = dq ('mLapDist', ctypes.c_double), # current distance around track ('mPathLateral', ctypes.c_double), # lateral position with respect to *very approximate* "center" path ('mTrackEdge', ctypes.c_double), # track edge (w.r.t. "center" path) on same side of track as vehicle @@ -326,11 +326,11 @@ class rF2VehicleScoring(ctypes.Structure): ('mCurSector2', ctypes.c_double), # current sector 2 (plus sector 1) if valid ('mNumPitstops', ctypes.c_short), # number of pitstops made ('mNumPenalties', ctypes.c_short), # number of outstanding penalties - ('mIsPlayer', ctypes.c_ubyte), # is this the player's vehicle - ('mControl', ctypes.c_ubyte), # who's in control: -1=nobody (shouldn't get this), 0=local player, 1=local AI, 2=remote, 3 = replay (shouldn't get this) - ('mInPits', ctypes.c_ubyte), # between pit entrance and pit exit (not always accurate for remote vehicles) + ('mIsPlayer', ctypes.c_bool), # is this the player's vehicle + ('mControl', ctypes.c_byte), # who's in control: -1=nobody (shouldn't get this), 0=local player, 1=local AI, 2=remote, 3 = replay (shouldn't get this) + ('mInPits', ctypes.c_bool), # between pit entrance and pit exit (not always accurate for remote vehicles) ('mPlace', ctypes.c_ubyte), # 1-based position - ('mVehicleClass', ctypes.c_ubyte*32), # vehicle class + ('mVehicleClass', ctypes.c_char*32), # vehicle class ('mTimeBehindNext', ctypes.c_double), # time behind vehicle in next higher place ('mLapsBehindNext', ctypes.c_int), # laps behind vehicle in next higher place ('mTimeBehindLeader', ctypes.c_double), # time behind leader @@ -349,11 +349,11 @@ class rF2VehicleScoring(ctypes.Structure): ('mQualification', ctypes.c_int), # 1-based, can be -1 when invalid ('mTimeIntoLap', ctypes.c_double), # estimated time into lap ('mEstimatedLapTime', ctypes.c_double), # estimated laptime used for 'time behind' and 'time into lap' (note: this may changed based on vehicle and setup!?) - ('mPitGroup', ctypes.c_ubyte*24), # pit group (same as team name unless pit is shared) + ('mPitGroup', ctypes.c_char*24), # pit group (same as team name unless pit is shared) ('mFlag', ctypes.c_ubyte), # primary flag being shown to vehicle (currently only 0=green or 6 = blue) - ('mUnderYellow', ctypes.c_ubyte), # whether this car has taken a full-course caution flag at the start/finish line + ('mUnderYellow', ctypes.c_bool), # whether this car has taken a full-course caution flag at the start/finish line ('mCountLapFlag', ctypes.c_ubyte), # 0 = do not count lap or time, 1 = count lap but not time, 2 = count lap and time - ('mInGarageStall', ctypes.c_ubyte), # appears to be within the correct garage stall + ('mInGarageStall', ctypes.c_bool), # appears to be within the correct garage stall ('mUpgradePack', ctypes.c_ubyte*16), # Coded upgrades ('mPitLapDist', ctypes.c_float), # location of pit in terms of lap distance ('mBestLapSector1', ctypes.c_float), # sector 1 time from best lap (not necessarily the best sector 1 time) @@ -436,10 +436,10 @@ class rF2TrackRulesParticipant(ctypes.Structure): ('mColumnAssignment', ctypes.c_int), # which column (line/lane) that participant is supposed to be in ('mPositionAssignment', ctypes.c_int), # 0-based position within column (line/lane) that participant is supposed to be located at (-1 is invalid) ('mPitsOpen', ctypes.c_ubyte), # whether the rules allow this particular vehicle to enter pits right now (input is 2=false or 3=true; if you want to edit it, set to 0=false or 1 = true) - ('mUpToSpeed', ctypes.c_ubyte), # while in the frozen order, this flag indicates whether the vehicle can be followed (this should be false for somebody who has temporarily spun and hasn't gotten back up to speed yet) - ('mUnused', ctypes.c_ubyte*2), # + ('mUpToSpeed', ctypes.c_bool), # while in the frozen order, this flag indicates whether the vehicle can be followed (this should be false for somebody who has temporarily spun and hasn't gotten back up to speed yet) + ('mUnused', ctypes.c_bool*2), # ('mGoalRelativeDistance', ctypes.c_double), # calculated based on where the leader is, and adjusted by the desired column spacing and the column/position assignments - ('mMessage', ctypes.c_ubyte*96), # a message for this participant to explain what is going on it will get run through translator on client machines + ('mMessage', ctypes.c_char*96), # a message for this participant to explain what is going on it will get run through translator on client machines ('mExpansion', ctypes.c_ubyte*192), ] class rF2TrackRulesStage(Enum): @@ -459,10 +459,10 @@ class rF2TrackRules(ctypes.Structure): ('mNumActions', ctypes.c_int), # number of recent actions ('pointer1', ctypes.c_ubyte*8), ('mNumParticipants', ctypes.c_int), # number of participants (vehicles) - ('mYellowFlagDetected', ctypes.c_ubyte), # whether yellow flag was requested or sum of participant mYellowSeverity's exceeds mSafetyCarThreshold + ('mYellowFlagDetected', ctypes.c_bool), # whether yellow flag was requested or sum of participant mYellowSeverity's exceeds mSafetyCarThreshold ('mYellowFlagLapsWasOverridden', ctypes.c_ubyte), # whether mYellowFlagLaps (below) is an admin request (0=no 1=yes 2 = clear yellow) - ('mSafetyCarExists', ctypes.c_ubyte), # whether safety car even exists - ('mSafetyCarActive', ctypes.c_ubyte), # whether safety car is active + ('mSafetyCarExists', ctypes.c_bool), # whether safety car even exists + ('mSafetyCarActive', ctypes.c_bool), # whether safety car is active ('mSafetyCarLaps', ctypes.c_int), # number of laps ('mSafetyCarThreshold', ctypes.c_float), # the threshold at which a safety car is called out (compared to the sum of TrackRulesParticipantV01::mYellowSeverity for each vehicle) ('mSafetyCarLapDist', ctypes.c_double), # safety car lap distance @@ -470,7 +470,7 @@ class rF2TrackRules(ctypes.Structure): ('mPitLaneStartDist', ctypes.c_float), # where the waypoint branch to the pits breaks off (this may not be perfectly accurate) ('mTeleportLapDist', ctypes.c_float), # the front of the teleport locations (a useful first guess as to where to throw the green flag) ('mInputExpansion', ctypes.c_ubyte*256), - ('mYellowFlagState', ctypes.c_ubyte), # see ScoringInfoV01 for values + ('mYellowFlagState', ctypes.c_byte), # see ScoringInfoV01 for values ('mYellowFlagLaps', ctypes.c_short), # suggested number of laps to run under yellow (may be passed in with admin command) ('mSafetyCarInstruction', ctypes.c_int), # 0=no change, 1=go active, 2 = head for pits ('mSafetyCarSpeed', ctypes.c_float), # maximum speed at which to drive @@ -480,7 +480,7 @@ class rF2TrackRules(ctypes.Structure): ('mMaximumColumnSpacing', ctypes.c_float), # maximum desired spacing between vehicles in a column (-1 to indicate indeterminate/unenforced) ('mMinimumSpeed', ctypes.c_float), # minimum speed that anybody should be driving (-1 to indicate no limit) ('mMaximumSpeed', ctypes.c_float), # maximum speed that anybody should be driving (-1 to indicate no limit) - ('mMessage', ctypes.c_ubyte*96), # a message for everybody to explain what is going on (which will get run through translator on client machines) + ('mMessage', ctypes.c_char*96), # a message for everybody to explain what is going on (which will get run through translator on client machines) ('pointer2', ctypes.c_ubyte*8), ('mInputOutputExpansion', ctypes.c_ubyte*256), ] @@ -489,9 +489,9 @@ class rF2PitMenu(ctypes.Structure): _pack_ = 4 _fields_ = [ ('mCategoryIndex', ctypes.c_int), # index of the current category - ('mCategoryName', ctypes.c_ubyte*32), # name of the current category (untranslated) + ('mCategoryName', ctypes.c_char*32), # name of the current category (untranslated) ('mChoiceIndex', ctypes.c_int), # index of the current choice (within the current category) - ('mChoiceString', ctypes.c_ubyte*32), # name of the current choice (may have some translated words) + ('mChoiceString', ctypes.c_char*32), # name of the current choice (may have some translated words) ('mNumChoices', ctypes.c_int), # total number of choices (0 < = mChoiceIndex < mNumChoices) ('mExpansion', ctypes.c_ubyte*256), # for future use ] @@ -504,33 +504,33 @@ class rF2WeatherControlInfo(ctypes.Structure): ('mCloudiness', ctypes.c_double), # general cloudiness (0.0=clear to 1.0 = dark) ('mAmbientTempK', ctypes.c_double), # ambient temperature (Kelvin) ('mWindMaxSpeed', ctypes.c_double), # maximum speed of wind (ground speed, but it affects how fast the clouds move, too) - ('mApplyCloudinessInstantly', ctypes.c_ubyte), # preferably we roll the new clouds in, but you can instantly change them now - ('mUnused1', ctypes.c_ubyte), # - ('mUnused2', ctypes.c_ubyte), # - ('mUnused3', ctypes.c_ubyte), # + ('mApplyCloudinessInstantly', ctypes.c_bool), # preferably we roll the new clouds in, but you can instantly change them now + ('mUnused1', ctypes.c_bool), # + ('mUnused2', ctypes.c_bool), # + ('mUnused3', ctypes.c_bool), # ('mExpansion', ctypes.c_ubyte*508), # future use (humidity, pressure, air density, etc.) ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2MappedBufferVersionBlock(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2MappedBufferVersionBlockWithSize(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Telemetry(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. ('mNumVehicles', ctypes.c_int), # current number of vehicles ('mVehicles', rF2VehicleTelemetry*rFactor2Constants.MAX_MAPPED_VEHICLES), @@ -539,8 +539,8 @@ class rF2Telemetry(ctypes.Structure): class rF2Scoring(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. ('mScoringInfo', rF2ScoringInfo), ('mVehicles', rF2VehicleScoring*rFactor2Constants.MAX_MAPPED_VEHICLES), @@ -549,8 +549,8 @@ class rF2Scoring(ctypes.Structure): class rF2Rules(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. ('mTrackRules', rF2TrackRules), ('mActions', rF2TrackRulesAction*rFactor2Constants.MAX_MAPPED_VEHICLES), @@ -560,8 +560,8 @@ class rF2Rules(ctypes.Structure): class rF2ForceFeedback(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mForceValue', ctypes.c_double), # Current FFB value reported via InternalsPlugin::ForceFeedback. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] @@ -582,24 +582,24 @@ class rF2GraphicsInfo(ctypes.Structure): class rF2Graphics(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mGraphicsInfo', rF2GraphicsInfo), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2PitInfo(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mPitMneu', rF2PitMenu), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Weather(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mTrackNodeSize', ctypes.c_double), ('mWeatherInfo', rF2WeatherControlInfo), ] @@ -616,8 +616,8 @@ class rF2VehScoringCapture(ctypes.Structure): _fields_ = [ ('mID', ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) ('mPlace', ctypes.c_ubyte), - ('mIsPlayer', ctypes.c_ubyte), - ('mFinishStatus', ctypes.c_ubyte), # 0=none, 1=finished, 2=dnf, 3 = dq + ('mIsPlayer', ctypes.c_bool), + ('mFinishStatus', ctypes.c_byte), # 0=none, 1=finished, 2=dnf, 3 = dq ] #untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] class rF2SessionTransitionCapture(ctypes.Structure): @@ -632,57 +632,57 @@ class rF2SessionTransitionCapture(ctypes.Structure): class rF2Extended(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. - ('mVersion', ctypes.c_ubyte*12), # API version - ('is64bit', ctypes.c_ubyte), # Is 64bit plugin? + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. + ('mVersion', ctypes.c_char*12), # API version + ('is64bit', ctypes.c_bool), # Is 64bit plugin? ('mPhysics', rF2PhysicsOptions), ('mTrackedDamages', rF2TrackedDamage*rFactor2Constants.MAX_MAPPED_IDS), - ('mInRealtimeFC', ctypes.c_ubyte), # in realtime as opposed to at the monitor (reported via last EnterRealtime/ExitRealtime calls). - ('mMultimediaThreadStarted', ctypes.c_ubyte), # multimedia thread started (reported via ThreadStarted/ThreadStopped calls). - ('mSimulationThreadStarted', ctypes.c_ubyte), # simulation thread started (reported via ThreadStarted/ThreadStopped calls). - ('mSessionStarted', ctypes.c_ubyte), # Set to true on Session Started, set to false on Session Ended. - ('mTicksSessionStarted', ctypes.c_double), # Ticks when session started. - ('mTicksSessionEnded', ctypes.c_double), # Ticks when session ended. + ('mInRealtimeFC', ctypes.c_bool), # in realtime as opposed to at the monitor (reported via last EnterRealtime/ExitRealtime calls). + ('mMultimediaThreadStarted', ctypes.c_bool), # multimedia thread started (reported via ThreadStarted/ThreadStopped calls). + ('mSimulationThreadStarted', ctypes.c_bool), # simulation thread started (reported via ThreadStarted/ThreadStopped calls). + ('mSessionStarted', ctypes.c_bool), # Set to true on Session Started, set to false on Session Ended. + ('mTicksSessionStarted', ctypes.c_ulonglong), # Ticks when session started. + ('mTicksSessionEnded', ctypes.c_ulonglong), # Ticks when session ended. ('mSessionTransitionCapture', rF2SessionTransitionCapture),# Contains partial internals capture at session transition time. - ('mDisplayedMessageUpdateCapture', ctypes.c_ubyte*128), - ('mDirectMemoryAccessEnabled', ctypes.c_ubyte), - ('mTicksStatusMessageUpdated', ctypes.c_double), # Ticks when status message was updated; - ('mStatusMessage', ctypes.c_ubyte*rFactor2Constants.MAX_STATUS_MSG_LEN), - ('mTicksLastHistoryMessageUpdated', ctypes.c_double), # Ticks when last message history message was updated; - ('mLastHistoryMessage', ctypes.c_ubyte*rFactor2Constants.MAX_STATUS_MSG_LEN), + ('mDisplayedMessageUpdateCapture', ctypes.c_char*128), + ('mDirectMemoryAccessEnabled', ctypes.c_bool), + ('mTicksStatusMessageUpdated', ctypes.c_ulonglong), # Ticks when status message was updated; + ('mStatusMessage', ctypes.c_char*rFactor2Constants.MAX_STATUS_MSG_LEN), + ('mTicksLastHistoryMessageUpdated', ctypes.c_ulonglong), # Ticks when last message history message was updated; + ('mLastHistoryMessage', ctypes.c_char*rFactor2Constants.MAX_STATUS_MSG_LEN), ('mCurrentPitSpeedLimit', ctypes.c_float), # speed limit m/s. - ('mSCRPluginEnabled', ctypes.c_ubyte), # Is Stock Car Rules plugin enabled? + ('mSCRPluginEnabled', ctypes.c_bool), # Is Stock Car Rules plugin enabled? ('mSCRPluginDoubleFileType', ctypes.c_int), # Stock Car Rules plugin DoubleFileType value, only meaningful if mSCRPluginEnabled is true. - ('mTicksLSIPhaseMessageUpdated', ctypes.c_double), # Ticks when last LSI phase message was updated. - ('mLSIPhaseMessage', ctypes.c_ubyte*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mTicksLSIPitStateMessageUpdated', ctypes.c_double), # Ticks when last LSI pit state message was updated. - ('mLSIPitStateMessage', ctypes.c_ubyte*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mTicksLSIOrderInstructionMessageUpdated', ctypes.c_double), # Ticks when last LSI order instruction message was updated. - ('mLSIOrderInstructionMessage', ctypes.c_ubyte*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mTicksLSIRulesInstructionMessageUpdated', ctypes.c_double), # Ticks when last FCY rules message was updated. Currently, only SCR plugin sets that. - ('mLSIRulesInstructionMessage', ctypes.c_ubyte*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ('mTicksLSIPhaseMessageUpdated', ctypes.c_ulonglong), # Ticks when last LSI phase message was updated. + ('mLSIPhaseMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ('mTicksLSIPitStateMessageUpdated', ctypes.c_ulonglong), # Ticks when last LSI pit state message was updated. + ('mLSIPitStateMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ('mTicksLSIOrderInstructionMessageUpdated', ctypes.c_ulonglong), # Ticks when last LSI order instruction message was updated. + ('mLSIOrderInstructionMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ('mTicksLSIRulesInstructionMessageUpdated', ctypes.c_ulonglong), # Ticks when last FCY rules message was updated. Currently, only SCR plugin sets that. + ('mLSIRulesInstructionMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), ('mUnsubscribedBuffersMask', ctypes.c_int), # Currently active UnsbscribedBuffersMask value. This will be allowed for clients to write to in the future, but not yet. - ('mHWControlInputEnabled', ctypes.c_ubyte), # HWControl input buffer is enabled. - ('mWeatherControlInputEnabled', ctypes.c_ubyte), # WeatherControl input buffer is enabled. - ('mRulesControlInputEnabled', ctypes.c_ubyte), # RulesControl input buffer is enabled. + ('mHWControlInputEnabled', ctypes.c_bool), # HWControl input buffer is enabled. + ('mWeatherControlInputEnabled', ctypes.c_bool), # WeatherControl input buffer is enabled. + ('mRulesControlInputEnabled', ctypes.c_bool), # RulesControl input buffer is enabled. ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2HWControl(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mLayoutVersion', ctypes.c_int), - ('mControlName', ctypes.c_ubyte*rFactor2Constants.MAX_HWCONTROL_NAME_LEN), + ('mControlName', ctypes.c_char*rFactor2Constants.MAX_HWCONTROL_NAME_LEN), ('mfRetVal', ctypes.c_double), ] #untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2WeatherControl(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_int), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_int), # Incremented after buffer write is done. + ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. + ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. ('mLayoutVersion', ctypes.c_int), ('mWeatherInfo', rF2WeatherControlInfo), ] From 8c100321e2fe552cfa29801e3bec3bde6c85cbce Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 13 Jul 2023 21:26:09 +0800 Subject: [PATCH 59/93] add player index override --- sim_info_sync.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index c53a939..4172d8f 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -141,6 +141,7 @@ def __init__(self, logger=__name__): self._updating = False self._restarting = False self._paused = True + self._override_player_index = False self._player_scor_index = INVALID_INDEX self._player_tele_index = INVALID_INDEX self._player_scor_mid = 0 @@ -177,12 +178,13 @@ def __sync_local_player_data(self, data_scor, data_tele): 3. Find telemetry index, break if not found. 4. Update telemetry index, copy telemetry data. """ - idx_scor = self.__find_local_scor_index(data_scor) - if idx_scor == INVALID_INDEX: - return False - self._player_scor_index = idx_scor - self._player_scor_mid = data_scor.mVehicles[idx_scor].mID - self._player_scor = copy.deepcopy(data_scor.mVehicles[idx_scor]) + if not self._override_player_index: + idx_scor = self.__find_local_scor_index(data_scor) + if idx_scor == INVALID_INDEX: + return False + self._player_scor_index = idx_scor + self._player_scor_mid = data_scor.mVehicles[self._player_scor_index].mID + self._player_scor = copy.deepcopy(data_scor.mVehicles[self._player_scor_index]) idx_tele = self.__find_local_tele_index(data_tele, self._player_scor_mid) if idx_tele == INVALID_INDEX: @@ -271,6 +273,9 @@ def start(self): self._thread = threading.Thread(target=self.__update, daemon=True) self._thread.start() self._logger.info("sharedmemory - updating thread started") + self._logger.info( + "sharedmemory - player index override: %s", + self._override_player_index) def stop(self): """Stop data updating thread""" @@ -336,6 +341,20 @@ def setMode(self, mode=0): """Set rf2 mmap access mode""" self._access_mode = mode + def setPlayerOverride(self, state=False): + """Set player index override state""" + self._override_player_index = state + + def setPlayerIndex(self, idx=INVALID_INDEX): + """Set player index""" + self._player_scor_index = idx + + def isPlayer(self, idx): + """Check whether index is player""" + if self._override_player_index: + return self._player_scor_index == idx + return self._info_scor.data.mVehicles[idx].mIsPlayer + @property def rf2Scor(self): """rF2 vehicle scoring data""" From 4fa69eef76c8890f6b81901e01372e38dc7556a6 Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 1 Nov 2023 09:36:58 +0800 Subject: [PATCH 60/93] add rf2ScorVeh & rf2TeleVeh methods that combine player and opponents vehicle info accessing --- sim_info_sync.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 4172d8f..d3dffc2 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -357,12 +357,12 @@ def isPlayer(self, idx): @property def rf2Scor(self): - """rF2 vehicle scoring data""" + """rF2 scoring data""" return self._info_scor.data @property def rf2Tele(self): - """rF2 vehicle telemetry data""" + """rF2 telemetry data""" return self._info_tele.data @property @@ -375,15 +375,25 @@ def rf2Ffb(self): """rF2 force feedback data""" return self._info_ffb.data - @property - def playerTele(self): - """rF2 local player's vehicle telemetry data""" - return self._player_tele + def rf2ScorVeh(self, index: int = None): + """rF2 vehicle scoring data - @property - def playerScor(self): - """rF2 local player's vehicle scoring data""" - return self._player_scor + Specify index for specific player. + None for local player. + """ + if index is None: + return self._player_scor + return self._info_scor.data.mVehicles[index] + + def rf2TeleVeh(self, index: int = None): + """rF2 vehicle telemetry data + + Specify index for specific player. + None for local player. + """ + if index is None: + return self._player_tele + return self._info_tele.data.mVehicles[index] @property def playerTeleIndex(self): @@ -422,8 +432,8 @@ def cbytes2str(bytestring): info.start() time.sleep(0.5) version = info.cbytes2str(info.rf2Ext.mVersion) - clutch = info.rf2Tele.mVehicles[0].mUnfilteredClutch # 1.0 clutch down, 0 clutch up - gear = info.rf2Tele.mVehicles[0].mGear # -1 to 6 + clutch = info.rf2TeleVeh(0).mUnfilteredClutch # 1.0 clutch down, 0 clutch up + gear = info.rf2TeleVeh(0).mGear # -1 to 6 print(f"API version: {version if version else 'unknown'}\n" f"Gear: {gear}\nClutch position: {clutch}") print("Test - API restart") From 0a0fa648c7f932b96a8d45119681621fe70455c0 Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 1 Nov 2023 15:34:38 +0800 Subject: [PATCH 61/93] move string conversion func to validator --- sim_info_sync.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index d3dffc2..df54de4 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -410,13 +410,6 @@ def paused(self): """Check whether data stopped updating""" return self._paused - @staticmethod - def cbytes2str(bytestring): - """Convert bytes to string""" - if type(bytestring) == bytes: - return bytestring.decode(errors="replace").rstrip() - return "" - if __name__ == "__main__": # Add logger @@ -431,7 +424,7 @@ def cbytes2str(bytestring): info.setPID("") # optional, can be omitted info.start() time.sleep(0.5) - version = info.cbytes2str(info.rf2Ext.mVersion) + version = info.rf2Ext.mVersion clutch = info.rf2TeleVeh(0).mUnfilteredClutch # 1.0 clutch down, 0 clutch up gear = info.rf2TeleVeh(0).mGear # -1 to 6 print(f"API version: {version if version else 'unknown'}\n" From dd81af76c5b57c5ccd25d6209f69100d4eeaad4d Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 1 Nov 2023 15:46:21 +0800 Subject: [PATCH 62/93] update comment --- sim_info_sync.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index df54de4..77d056c 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -1,8 +1,9 @@ """ -sharedMemoryAPI with player-synced methods +rF2 Memory Map & Shared Memory API accessing -Inherit Python mapping of The Iron Wolf's rF2 Shared Memory Tools -and add access functions to it. +Inherit Python mapping of The Iron Wolf's rF2 Shared Memory Tools, +with cross-platform (Linux) support, +and add access & synchronize functions to it. """ import ctypes import mmap From 82901731b16da56361a196674a2ff212591859b3 Mon Sep 17 00:00:00 2001 From: Xiang Date: Fri, 10 Nov 2023 00:36:34 +0800 Subject: [PATCH 63/93] auto-sync non-local player tele index, optimize index matching --- sim_info_sync.py | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 77d056c..8f4da83 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -152,20 +152,31 @@ def __init__(self, logger=__name__): self.init_mmap(logger) @staticmethod - def __find_local_scor_index(data_scor): - """Find local player scoring index""" + def __find_local_scor_index(data_scor, idx_last): + """Find local player scoring index + + Check last found index first. + If is not player, loop through all vehicles. + """ + if data_scor.mVehicles[idx_last].mIsPlayer: + return idx_last for idx_scor in range(MAX_VEHICLES): if data_scor.mVehicles[idx_scor].mIsPlayer: return idx_scor return INVALID_INDEX @staticmethod - def __find_local_tele_index(data_tele, mid_scor): - """Find local player telemetry index + def __sync_local_tele_index(data_tele, idx_scor, mid_scor): + """Sync local player telemetry index Telemetry index can be different from scoring index. Use mID matching to find telemetry index. + + Compare scor mid with tele mid first. + If not same, loop through all vehicles. """ + if data_tele.mVehicles[idx_scor].mID == mid_scor: + return idx_scor for idx_tele in range(MAX_VEHICLES): if data_tele.mVehicles[idx_tele].mID == mid_scor: return idx_tele @@ -180,26 +191,33 @@ def __sync_local_player_data(self, data_scor, data_tele): 4. Update telemetry index, copy telemetry data. """ if not self._override_player_index: - idx_scor = self.__find_local_scor_index(data_scor) + idx_scor = self.__find_local_scor_index(data_scor, self._player_scor_index) if idx_scor == INVALID_INDEX: return False self._player_scor_index = idx_scor self._player_scor_mid = data_scor.mVehicles[self._player_scor_index].mID self._player_scor = copy.deepcopy(data_scor.mVehicles[self._player_scor_index]) - idx_tele = self.__find_local_tele_index(data_tele, self._player_scor_mid) + idx_tele = self.__sync_local_tele_index( + data_tele, self._player_scor_index, self._player_scor_mid) if idx_tele == INVALID_INDEX: return True # found 1 index self._player_tele_index = idx_tele self._player_tele = copy.deepcopy(data_tele.mVehicles[idx_tele]) return True # found 2 index - def find_player_index_tele(self, index_scor): - """Find player index using mID""" - scor_mid = self._info_scor.data.mVehicles[index_scor].mID - for index in range(MAX_VEHICLES): - if self._info_tele.data.mVehicles[index].mID == scor_mid: - return index + def sync_tele_index(self, idx_scor): + """Sync telemetry index with scoring index using mID + + Compare scor mid with tele mid first. + If not same, loop through all vehicles. + """ + scor_mid = self._info_scor.data.mVehicles[idx_scor].mID + if self._info_tele.data.mVehicles[idx_scor].mID == scor_mid: + return idx_scor + for idx_tele in range(MAX_VEHICLES): + if self._info_tele.data.mVehicles[idx_tele].mID == scor_mid: + return idx_tele return INVALID_INDEX def __update(self): @@ -394,7 +412,7 @@ def rf2TeleVeh(self, index: int = None): """ if index is None: return self._player_tele - return self._info_tele.data.mVehicles[index] + return self._info_tele.data.mVehicles[self.sync_tele_index(index)] @property def playerTeleIndex(self): From ea261794cae7cba62eaa4db4295a5f3995f164df Mon Sep 17 00:00:00 2001 From: Xiang Date: Mon, 4 Dec 2023 19:05:19 +0800 Subject: [PATCH 64/93] refactor mmap accessing --- sim_info_sync.py | 555 +++++++++++++++++++++++++---------------------- 1 file changed, 291 insertions(+), 264 deletions(-) diff --git a/sim_info_sync.py b/sim_info_sync.py index 8f4da83..8c43656 100644 --- a/sim_info_sync.py +++ b/sim_info_sync.py @@ -1,17 +1,18 @@ """ -rF2 Memory Map & Shared Memory API accessing +rF2 Memory Map Inherit Python mapping of The Iron Wolf's rF2 Shared Memory Tools, -with cross-platform (Linux) support, -and add access & synchronize functions to it. +with player-synchronized accessing (by S.Victor) +and cross-platform Linux support (by Bernat) """ + +import copy import ctypes +import logging import mmap +import platform import time import threading -import copy -import platform -import logging try: from . import rF2data @@ -22,151 +23,187 @@ MAX_VEHICLES = rF2data.rFactor2Constants.MAX_MAPPED_VEHICLES INVALID_INDEX = -1 +logger = logging.getLogger(__name__) + -class rF2MMap: - """Create mmap for accessing rF2 shared memory +class RF2MMap: + """Create rF2 Memory Map mmap_name: mmap filename, ex. $rFactor2SMMP_Scoring$ - rf2_data: rf2 data class defined in rF2data.py, ex. rF2data.rF2Scoring - rf2_pid: rf2 Process ID for server - logger: logger name + rf2_data: rf2 data class defined in rF2data, ex. rF2data.rF2Scoring + rf2_pid: rf2 Process ID for accessing server data """ - def __init__(self, mmap_name, rf2_data, logger=__name__): - self._logger = logging.getLogger(logger) + def __init__(self, mmap_name: str, rf2_data) -> None: + self.mmap_id = mmap_name.strip("$") self._mmap_name = mmap_name self._rf2_data = rf2_data - self._mmap_inst = None - self._mmap_data = None + self._mmap_instance = None + self._mmap_output = None self._access_mode = 0 - self._direct_access_active = False + self._buffer_sharing = False - def create(self, access_mode=0, rf2_pid=""): + def create(self, access_mode: int = 0, rf2_pid: str = "") -> None: """Create mmap instance""" self._access_mode = access_mode - self._mmap_inst = self.platform_mmap( + self._mmap_instance = self.platform_mmap( name=self._mmap_name, size=ctypes.sizeof(self._rf2_data), pid=rf2_pid ) - mode_text = "Direct" if access_mode else "Copy" - self._logger.info( - "sharedmemory - %s > ACTIVE: %s Access", - self._mmap_name.strip("$"), mode_text - ) + mode = "Direct" if access_mode else "Copy" + logger.info("sharedmemory - ACTIVE: %s > %s Access", self.mmap_id, mode) - def close(self): + def close(self) -> None: """Close memory mapping Create a final accessible mmap data copy before closing mmap instance. """ - self.copy_access() - self._direct_access_active = False + self.buffer_copy() + self._buffer_sharing = False try: - self._mmap_inst.close() - self._logger.info("sharedmemory - %s > CLOSE", self._mmap_name.strip("$")) + self._mmap_instance.close() + logger.info("sharedmemory - CLOSED: %s", self.mmap_id) except BufferError: - self._logger.error("sharedmemory - failed to close mmap") + logger.error("sharedmemory - buffer error while closing mmap") @staticmethod - def version_check(data): - """Data version check""" + def is_valid(data: int) -> bool: + """Validate data version""" return data.mVersionUpdateEnd == data.mVersionUpdateBegin - def direct_access(self): - """Direct access memory map - - Direct accessing mmap data instance. - May result data desync or interruption. - """ - if self._direct_access_active: - return None - self._mmap_data = self._rf2_data.from_buffer(self._mmap_inst) - self._direct_access_active = True - - def copy_access(self): - """Copy access memory map - - Accessing mmap data by copying mmap instance - and using version check to avoid data desync or interruption. - """ - data_temp = self._rf2_data.from_buffer_copy(self._mmap_inst) - if self.version_check(data_temp): - self._mmap_data = data_temp - elif not self._mmap_data: - self._mmap_data = data_temp - - def platform_mmap(self, name, size, pid=""): + def buffer_share(self) -> None: + """Share buffer direct access, may result desync""" + if not self._buffer_sharing: + self._buffer_sharing = True + self._mmap_output = self._rf2_data.from_buffer(self._mmap_instance) + + def buffer_copy(self) -> None: + """Copy buffer access, check version before assign""" + data_temp = self._rf2_data.from_buffer_copy(self._mmap_instance) + if self.is_valid(data_temp): + self._mmap_output = data_temp + elif not self._mmap_output: + self._mmap_output = data_temp + + def platform_mmap(self, name: str, size: int, pid: str = "") -> mmap: """Platform memory mapping""" if PLATFORM == "Windows": return self.windows_mmap(name, size, pid) return self.linux_mmap(name, size) @staticmethod - def windows_mmap(name, size, pid): - """Windows memory mapping""" + def windows_mmap(name: str, size: int, pid: str) -> mmap: + """Windows mmap""" return mmap.mmap(-1, size, f"{name}{pid}") @staticmethod - def linux_mmap(name, size): - """Linux memory mapping""" + def linux_mmap(name: str, size: int) -> mmap: + """Linux mmap""" file = open("/dev/shm/" + name, "a+") if file.tell() == 0: file.write("\0" * size) file.flush() return mmap.mmap(file.fileno(), size) - def update(self): - """Update rF2 mmap data""" + def update(self) -> None: + """Update mmap data""" if self._access_mode: - self.direct_access() + self.buffer_share() else: - self.copy_access() + self.buffer_copy() @property def data(self): - """Access rF2 mmap data""" - return self._mmap_data + """Output mmap data""" + return self._mmap_output -class SimInfoSync(): - """ - API for rF2 shared memory +class MMapDataSet: + """Create mmap data set""" - Player-Synced data. - Access mode: 0 = copy access, 1 = direct access - """ + def __init__(self) -> None: + self._scor = RF2MMap("$rFactor2SMMP_Scoring$", rF2data.rF2Scoring) + self._tele = RF2MMap("$rFactor2SMMP_Telemetry$", rF2data.rF2Telemetry) + self._ext = RF2MMap("$rFactor2SMMP_Extended$", rF2data.rF2Extended) + self._ffb = RF2MMap("$rFactor2SMMP_ForceFeedback$", rF2data.rF2ForceFeedback) + self.mmap_active = [self._scor, self._tele, self._ext, self._ffb] - def __init__(self, logger=__name__): - self._stopped = True - self._updating = False - self._restarting = False - self._paused = True - self._override_player_index = False - self._player_scor_index = INVALID_INDEX - self._player_tele_index = INVALID_INDEX - self._player_scor_mid = 0 - self._rf2_pid = "" - self._access_mode = 0 - self._logger = logging.getLogger(logger) - self.init_mmap(logger) + def create_mmap(self, access_mode: int, rf2_pid: str) -> None: + """Create mmap instance""" + for data in self.mmap_active: + data.create(access_mode, rf2_pid) + + def close_mmap(self) -> None: + """Close mmap instance""" + for data in self.mmap_active: + data.close() + + def update_mmap(self) -> None: + """Update mmap data""" + for data in self.mmap_active: + data.update() + + @property + def scor(self): + """Scoring data""" + return self._scor.data + + @property + def tele(self): + """Telemetry data""" + return self._tele.data + + @property + def ext(self): + """Extended data""" + return self._ext.data + + @property + def ffb(self): + """Force feedback data""" + return self._ffb.data + + +class SyncData: + """Synchronize data with player ID""" + + def __init__(self) -> None: + self.dataset = MMapDataSet() + self.updating = False + self.update_thread = None + self.paused = True + + self.override_player_index = False + self.player_scor_index = INVALID_INDEX + self.player_scor = None + self.player_tele = None + + def copy_mmap_player(self) -> None: + """Copy memory mapping player data + + Maintain a separate copy of synchronized local player's data + which avoids data interruption or desync in case of player index changes. + """ + self.player_scor = copy.deepcopy(self.dataset.scor.mVehicles[INVALID_INDEX]) + self.player_tele = copy.deepcopy(self.dataset.tele.mVehicles[INVALID_INDEX]) @staticmethod - def __find_local_scor_index(data_scor, idx_last): + def __local_scor_index(data_scor, last_idx: int) -> int: """Find local player scoring index Check last found index first. - If is not player, loop through all vehicles. + If not player, loop through all vehicles. """ - if data_scor.mVehicles[idx_last].mIsPlayer: - return idx_last - for idx_scor in range(MAX_VEHICLES): - if data_scor.mVehicles[idx_scor].mIsPlayer: - return idx_scor + if data_scor.mVehicles[last_idx].mIsPlayer: + return last_idx + for scor_idx in range(MAX_VEHICLES): + if data_scor.mVehicles[scor_idx].mIsPlayer: + return scor_idx return INVALID_INDEX @staticmethod - def __sync_local_tele_index(data_tele, idx_scor, mid_scor): + def __local_tele_index(data_tele, scor_idx: int, scor_mid: int) -> int: """Sync local player telemetry index Telemetry index can be different from scoring index. @@ -175,14 +212,14 @@ def __sync_local_tele_index(data_tele, idx_scor, mid_scor): Compare scor mid with tele mid first. If not same, loop through all vehicles. """ - if data_tele.mVehicles[idx_scor].mID == mid_scor: - return idx_scor - for idx_tele in range(MAX_VEHICLES): - if data_tele.mVehicles[idx_tele].mID == mid_scor: - return idx_tele + if data_tele.mVehicles[scor_idx].mID == scor_mid: + return scor_idx + for tele_idx in range(MAX_VEHICLES): + if data_tele.mVehicles[tele_idx].mID == scor_mid: + return tele_idx return INVALID_INDEX - def __sync_local_player_data(self, data_scor, data_tele): + def __sync_player_data(self, data_scor, data_tele) -> bool: """Sync local player data 1. Find scoring index, break if not found. @@ -190,37 +227,62 @@ def __sync_local_player_data(self, data_scor, data_tele): 3. Find telemetry index, break if not found. 4. Update telemetry index, copy telemetry data. """ - if not self._override_player_index: - idx_scor = self.__find_local_scor_index(data_scor, self._player_scor_index) - if idx_scor == INVALID_INDEX: + if not self.override_player_index: + scor_idx = self.__local_scor_index(data_scor, self.player_scor_index) + if scor_idx == INVALID_INDEX: return False - self._player_scor_index = idx_scor - self._player_scor_mid = data_scor.mVehicles[self._player_scor_index].mID - self._player_scor = copy.deepcopy(data_scor.mVehicles[self._player_scor_index]) + self.player_scor_index = scor_idx + + self.player_scor = copy.deepcopy(data_scor.mVehicles[self.player_scor_index]) - idx_tele = self.__sync_local_tele_index( - data_tele, self._player_scor_index, self._player_scor_mid) - if idx_tele == INVALID_INDEX: + tele_idx = self.__local_tele_index( + data_tele, self.player_scor_index, self.player_scor.mID) + if tele_idx == INVALID_INDEX: return True # found 1 index - self._player_tele_index = idx_tele - self._player_tele = copy.deepcopy(data_tele.mVehicles[idx_tele]) + #self.player_tele_index = tele_idx + self.player_tele = copy.deepcopy(data_tele.mVehicles[tele_idx]) return True # found 2 index - def sync_tele_index(self, idx_scor): + def sync_tele_index(self, scor_idx: int) -> int: """Sync telemetry index with scoring index using mID Compare scor mid with tele mid first. If not same, loop through all vehicles. """ - scor_mid = self._info_scor.data.mVehicles[idx_scor].mID - if self._info_tele.data.mVehicles[idx_scor].mID == scor_mid: - return idx_scor - for idx_tele in range(MAX_VEHICLES): - if self._info_tele.data.mVehicles[idx_tele].mID == scor_mid: - return idx_tele + scor_mid = self.dataset.scor.mVehicles[scor_idx].mID + if self.dataset.tele.mVehicles[scor_idx].mID == scor_mid: + return scor_idx + for tele_idx in range(MAX_VEHICLES): + if self.dataset.tele.mVehicles[tele_idx].mID == scor_mid: + return tele_idx return INVALID_INDEX - def __update(self): + def start(self, access_mode: int, rf2_pid: str) -> None: + """Update & sync mmap data copy in separate thread""" + if self.updating: + logger.warning("sharedmemory - already updating !!!") + else: + self.updating = True + self.dataset.create_mmap(access_mode, rf2_pid) + self.dataset.update_mmap() + self.copy_mmap_player() + + self.update_thread = threading.Thread( + target=self.__update, daemon=True) + self.update_thread.start() + logger.info("sharedmemory - updating thread started") + logger.info("sharedmemory - player index override: %s", self.override_player_index) + + def stop(self) -> None: + """Join and stop updating thread, close mmap""" + if self.updating: + self.updating = False + self.update_thread.join() + self.dataset.close_mmap() + else: + logger.warning("sharedmemory - already stopped !!!") + + def __update(self) -> None: """Update synced player data""" last_version_update = 0 # store last data version update data_freezed = True # whether data is freezed @@ -228,227 +290,192 @@ def __update(self): reset_counter = 0 update_delay = 0.5 # longer delay while inactive - while self._updating: - self.update_mmap() + while self.updating: + self.dataset.update_mmap() # Update player data & index if not data_freezed: # Get player data - data_synced = self.__sync_local_player_data( - self._info_scor.data, self._info_tele.data) + data_synced = self.__sync_player_data( + self.dataset.scor, self.dataset.tele) # Pause if local player index no longer exists, 5 tries if not data_synced and reset_counter < 6: reset_counter += 1 elif data_synced: reset_counter = 0 - self._paused = False + self.paused = False # Activate pause if reset_counter == 5: - self._paused = True - self._logger.info("sharedmemory - player data paused") + self.paused = True + logger.info("sharedmemory - player data paused") # Start checking data version update status if time.time() - check_timer_start > 5: if (not data_freezed - and last_version_update == self._info_scor.data.mVersionUpdateEnd): + and last_version_update == self.dataset.scor.mVersionUpdateEnd): update_delay = 0.5 data_freezed = True - self._paused = True - self._logger.info( + self.paused = True + logger.info( "sharedmemory - data paused, version %s", last_version_update ) - last_version_update = self._info_scor.data.mVersionUpdateEnd + last_version_update = self.dataset.scor.mVersionUpdateEnd check_timer_start = time.time() # reset timer if (data_freezed - and last_version_update != self._info_scor.data.mVersionUpdateEnd): + and last_version_update != self.dataset.scor.mVersionUpdateEnd): update_delay = 0.01 data_freezed = False - self._paused = False - self._logger.info( + self.paused = False + logger.info( "sharedmemory - data unpaused, version %s", - self._info_scor.data.mVersionUpdateEnd + self.dataset.scor.mVersionUpdateEnd ) time.sleep(update_delay) - self._stopped = True - self._paused = False - self._logger.info("sharedmemory - updating thread stopped") + self.paused = False + logger.info("sharedmemory - updating thread stopped") - def start(self): - """Start data updating thread - Update & sync mmap data copy in separate thread. - """ - if not self._stopped: - return None - - self.create_mmap() - self.update_mmap() - self.copy_mmap_player() - self._updating = True - self._stopped = False - self._thread = threading.Thread(target=self.__update, daemon=True) - self._thread.start() - self._logger.info("sharedmemory - updating thread started") - self._logger.info( - "sharedmemory - player index override: %s", - self._override_player_index) - - def stop(self): - """Stop data updating thread""" - self._updating = False - self._thread.join() - self.close_mmap() - - def restart(self): - """Restart data updating thread""" - if self._restarting: - return None - self._restarting = True - self.stop() - self.start() - self._restarting = False - - def init_mmap(self, logger): - """Initialize mmap info""" - self._info_scor = rF2MMap( - "$rFactor2SMMP_Scoring$", rF2data.rF2Scoring, logger) - self._info_tele = rF2MMap( - "$rFactor2SMMP_Telemetry$", rF2data.rF2Telemetry, logger) - self._info_ext = rF2MMap( - "$rFactor2SMMP_Extended$", rF2data.rF2Extended, logger) - self._info_ffb = rF2MMap( - "$rFactor2SMMP_ForceFeedback$", rF2data.rF2ForceFeedback, logger) - - def create_mmap(self): - """Create mmap instance""" - self._info_scor.create(self._access_mode, self._rf2_pid) - self._info_tele.create(self._access_mode, self._rf2_pid) - self._info_ext.create(self._access_mode, self._rf2_pid) - self._info_ffb.create(self._access_mode, self._rf2_pid) +class RF2SM: + """ + RF2 shared memory data output - def close_mmap(self): - """Close mmap instance""" - self._info_scor.close() - self._info_tele.close() - self._info_ext.close() - self._info_ffb.close() + Optional parameters: + setMode: set access mode, 0 = copy access, 1 = direct access + setPID: set process ID for connecting to server data (str) + setPlayerOverride: enable player index override (bool) + setPlayerIndex: manually set player index (int) + """ - def update_mmap(self): - """Update mmap data""" - self._info_scor.update() - self._info_tele.update() - self._info_ext.update() - self._info_ffb.update() + def __init__(self) -> None: + self._sync = SyncData() + self.access_mode = 0 + self.rf2_pid = "" - def copy_mmap_player(self): - """Copy memory mapping player data + def start(self) -> None: + """Start data updating thread""" + self._sync.start(self.access_mode, self.rf2_pid) - Maintain a separate copy of synchronized local player's data - which avoids data interruption or desync in case of player index changes. - """ - self._player_scor = copy.deepcopy(self._info_scor.data.mVehicles[INVALID_INDEX]) - self._player_tele = copy.deepcopy(self._info_tele.data.mVehicles[INVALID_INDEX]) + def stop(self) -> None: + """Stop data updating thread""" + self._sync.stop() - def setPID(self, pid=""): + def setPID(self, pid: str = "") -> None: """Set rf2 PID""" - self._rf2_pid = pid + self.rf2_pid = str(pid) - def setMode(self, mode=0): + def setMode(self, mode: int = 0) -> None: """Set rf2 mmap access mode""" - self._access_mode = mode + self.access_mode = mode - def setPlayerOverride(self, state=False): + def setPlayerOverride(self, state: bool = False) -> None: """Set player index override state""" - self._override_player_index = state + self._sync.override_player_index = state - def setPlayerIndex(self, idx=INVALID_INDEX): + def setPlayerIndex(self, idx: int = INVALID_INDEX) -> None: """Set player index""" - self._player_scor_index = idx - - def isPlayer(self, idx): - """Check whether index is player""" - if self._override_player_index: - return self._player_scor_index == idx - return self._info_scor.data.mVehicles[idx].mIsPlayer + self._sync.player_scor_index = min(max(idx, INVALID_INDEX), MAX_VEHICLES - 1) @property - def rf2Scor(self): - """rF2 scoring data""" - return self._info_scor.data - - @property - def rf2Tele(self): - """rF2 telemetry data""" - return self._info_tele.data - - @property - def rf2Ext(self): - """rF2 extended data""" - return self._info_ext.data - - @property - def rf2Ffb(self): - """rF2 force feedback data""" - return self._info_ffb.data + def rf2ScorInfo(self): + """rF2 scoring info data""" + return self._sync.dataset.scor.mScoringInfo def rf2ScorVeh(self, index: int = None): - """rF2 vehicle scoring data + """rF2 scoring vehicle data Specify index for specific player. None for local player. """ if index is None: - return self._player_scor - return self._info_scor.data.mVehicles[index] + return self._sync.player_scor + return self._sync.dataset.scor.mVehicles[index] def rf2TeleVeh(self, index: int = None): - """rF2 vehicle telemetry data + """rF2 telemetry vehicle data Specify index for specific player. None for local player. """ if index is None: - return self._player_tele - return self._info_tele.data.mVehicles[self.sync_tele_index(index)] + return self._sync.player_tele + return self._sync.dataset.tele.mVehicles[self._sync.sync_tele_index(index)] @property - def playerTeleIndex(self): - """rF2 local player's telemetry index""" - return self._player_tele_index + def rf2Ext(self): + """rF2 extended data""" + return self._sync.dataset.ext @property - def playerScorIndex(self): + def rf2Ffb(self): + """rF2 force feedback data""" + return self._sync.dataset.ffb + + @property + def playerIndex(self) -> int: """rF2 local player's scoring index""" - return self._player_scor_index + return self._sync.player_scor_index + + def isPlayer(self, idx: int) -> bool: + """Check whether index is player""" + if self._sync.override_player_index: + return self._sync.player_scor_index == idx + return self._sync.dataset.scor.mVehicles[idx].mIsPlayer @property - def paused(self): + def isPaused(self) -> bool: """Check whether data stopped updating""" - return self._paused + return self._sync.paused if __name__ == "__main__": # Add logger - logger = logging.getLogger(__name__) test_handler = logging.StreamHandler() logger.setLevel(logging.INFO) logger.addHandler(test_handler) - # Example usage - info = SimInfoSync(logger=__name__) - info.setMode(0) # optional, can be omitted - info.setPID("") # optional, can be omitted + # Test run + SEPARATOR = "=" * 50 + print("Test API - Start") + info = RF2SM() + info.setMode(1) # set direct access + info.setPID("") + info.setPlayerOverride(True) # enable player override + info.setPlayerIndex(0) # set player index to 0 + info.start() + time.sleep(0.2) + + print(SEPARATOR) + print("Test API - Read") + version = info.rf2Ext.mVersion.decode() + driver = info.rf2ScorVeh(0).mDriverName.decode(encoding="iso-8859-1") + track = info.rf2ScorInfo.mTrackName.decode(encoding="iso-8859-1") + print(f"plugin version: {version if version else 'not running'}") + print(f"driver name : {driver if version else 'not running'}") + print(f"track name : {track if version else 'not running'}") + + print(SEPARATOR) + print("Test API - Restart") + info.stop() + info.setMode(0) # set copy access + info.setPlayerOverride(False) # disable player override + info.start() + + print(SEPARATOR) + print("Test API - Multi starts") + info.start() + info.start() + info.start() info.start() - time.sleep(0.5) - version = info.rf2Ext.mVersion - clutch = info.rf2TeleVeh(0).mUnfilteredClutch # 1.0 clutch down, 0 clutch up - gear = info.rf2TeleVeh(0).mGear # -1 to 6 - print(f"API version: {version if version else 'unknown'}\n" - f"Gear: {gear}\nClutch position: {clutch}") - print("Test - API restart") - info.restart() - print("Test - API quit") + + print(SEPARATOR) + print("Test API - Close") + info.stop() + + print(SEPARATOR) + print("Test API - Multi stop") + info.stop() + info.stop() info.stop() From 626fbce2bde435f08340e918797449b17afe8b71 Mon Sep 17 00:00:00 2001 From: Xiang Date: Mon, 4 Dec 2023 19:11:45 +0800 Subject: [PATCH 65/93] rename sim_info_sync to rF2MMap --- sim_info_sync.py => rF2MMap.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename sim_info_sync.py => rF2MMap.py (100%) diff --git a/sim_info_sync.py b/rF2MMap.py similarity index 100% rename from sim_info_sync.py rename to rF2MMap.py From 1ae74b9a3ec5bf9e8468d03431e509239fbd975b Mon Sep 17 00:00:00 2001 From: Xiang Date: Mon, 4 Dec 2023 23:48:56 +0800 Subject: [PATCH 66/93] cleanup duplicates --- rF2MMap.py | 124 ++++++++++++++++++++--------------------------------- 1 file changed, 47 insertions(+), 77 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index 8c43656..e9618f7 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -44,13 +44,14 @@ def __init__(self, mmap_name: str, rf2_data) -> None: self._buffer_sharing = False def create(self, access_mode: int = 0, rf2_pid: str = "") -> None: - """Create mmap instance""" + """Create mmap instance & initial accessible copy""" self._access_mode = access_mode self._mmap_instance = self.platform_mmap( name=self._mmap_name, size=ctypes.sizeof(self._rf2_data), pid=rf2_pid ) + self.__buffer_copy(True) mode = "Direct" if access_mode else "Copy" logger.info("sharedmemory - ACTIVE: %s > %s Access", self.mmap_id, mode) @@ -59,7 +60,7 @@ def close(self) -> None: Create a final accessible mmap data copy before closing mmap instance. """ - self.buffer_copy() + self.__buffer_copy(True) self._buffer_sharing = False try: self._mmap_instance.close() @@ -67,24 +68,29 @@ def close(self) -> None: except BufferError: logger.error("sharedmemory - buffer error while closing mmap") - @staticmethod - def is_valid(data: int) -> bool: - """Validate data version""" - return data.mVersionUpdateEnd == data.mVersionUpdateBegin + def update(self) -> None: + """Update mmap data""" + if self._access_mode: + self.__buffer_share() + else: + self.__buffer_copy() + + @property + def data(self): + """Output mmap data""" + return self._mmap_output - def buffer_share(self) -> None: + def __buffer_share(self) -> None: """Share buffer direct access, may result desync""" if not self._buffer_sharing: self._buffer_sharing = True self._mmap_output = self._rf2_data.from_buffer(self._mmap_instance) - def buffer_copy(self) -> None: + def __buffer_copy(self, skip_check=False) -> None: """Copy buffer access, check version before assign""" - data_temp = self._rf2_data.from_buffer_copy(self._mmap_instance) - if self.is_valid(data_temp): - self._mmap_output = data_temp - elif not self._mmap_output: - self._mmap_output = data_temp + temp = self._rf2_data.from_buffer_copy(self._mmap_instance) + if temp.mVersionUpdateEnd == temp.mVersionUpdateBegin or skip_check: + self._mmap_output = temp def platform_mmap(self, name: str, size: int, pid: str = "") -> mmap: """Platform memory mapping""" @@ -106,18 +112,6 @@ def linux_mmap(name: str, size: int) -> mmap: file.flush() return mmap.mmap(file.fileno(), size) - def update(self) -> None: - """Update mmap data""" - if self._access_mode: - self.buffer_share() - else: - self.buffer_copy() - - @property - def data(self): - """Output mmap data""" - return self._mmap_output - class MMapDataSet: """Create mmap data set""" @@ -179,73 +173,50 @@ def __init__(self) -> None: self.player_scor = None self.player_tele = None - def copy_mmap_player(self) -> None: - """Copy memory mapping player data + def copy_player_scor(self, index: int = INVALID_INDEX) -> None: + """Copy scoring player data""" + self.player_scor = copy.deepcopy(self.dataset.scor.mVehicles[index]) - Maintain a separate copy of synchronized local player's data - which avoids data interruption or desync in case of player index changes. - """ - self.player_scor = copy.deepcopy(self.dataset.scor.mVehicles[INVALID_INDEX]) - self.player_tele = copy.deepcopy(self.dataset.tele.mVehicles[INVALID_INDEX]) + def copy_player_tele(self, index: int = INVALID_INDEX) -> None: + """Copy telemetry player data""" + self.player_tele = copy.deepcopy(self.dataset.tele.mVehicles[index]) - @staticmethod - def __local_scor_index(data_scor, last_idx: int) -> int: + def __local_scor_index(self, last_idx: int) -> int: """Find local player scoring index Check last found index first. If not player, loop through all vehicles. """ - if data_scor.mVehicles[last_idx].mIsPlayer: + if self.dataset.scor.mVehicles[last_idx].mIsPlayer: return last_idx for scor_idx in range(MAX_VEHICLES): - if data_scor.mVehicles[scor_idx].mIsPlayer: + if self.dataset.scor.mVehicles[scor_idx].mIsPlayer: return scor_idx return INVALID_INDEX - @staticmethod - def __local_tele_index(data_tele, scor_idx: int, scor_mid: int) -> int: - """Sync local player telemetry index - - Telemetry index can be different from scoring index. - Use mID matching to find telemetry index. - - Compare scor mid with tele mid first. - If not same, loop through all vehicles. - """ - if data_tele.mVehicles[scor_idx].mID == scor_mid: - return scor_idx - for tele_idx in range(MAX_VEHICLES): - if data_tele.mVehicles[tele_idx].mID == scor_mid: - return tele_idx - return INVALID_INDEX - - def __sync_player_data(self, data_scor, data_tele) -> bool: - """Sync local player data - - 1. Find scoring index, break if not found. - 2. Update scoring index, mID, copy scoring data. - 3. Find telemetry index, break if not found. - 4. Update telemetry index, copy telemetry data. - """ + def __sync_player_data(self) -> bool: + """Sync local player data""" if not self.override_player_index: - scor_idx = self.__local_scor_index(data_scor, self.player_scor_index) + # Update scoring index + scor_idx = self.__local_scor_index(self.player_scor_index) if scor_idx == INVALID_INDEX: - return False + return False # index not found, not synced self.player_scor_index = scor_idx - - self.player_scor = copy.deepcopy(data_scor.mVehicles[self.player_scor_index]) - - tele_idx = self.__local_tele_index( - data_tele, self.player_scor_index, self.player_scor.mID) - if tele_idx == INVALID_INDEX: - return True # found 1 index - #self.player_tele_index = tele_idx - self.player_tele = copy.deepcopy(data_tele.mVehicles[tele_idx]) - return True # found 2 index + # Copy scoring data + self.copy_player_scor(self.player_scor_index) + # Update telemetry index + tele_idx = self.sync_tele_index(self.player_scor_index) + if tele_idx != INVALID_INDEX: + # Copy telemetry data + self.copy_player_tele(tele_idx) + return True # found index, synced def sync_tele_index(self, scor_idx: int) -> int: """Sync telemetry index with scoring index using mID + Telemetry index can be different from scoring index. + Use mID matching to find telemetry index. + Compare scor mid with tele mid first. If not same, loop through all vehicles. """ @@ -264,8 +235,8 @@ def start(self, access_mode: int, rf2_pid: str) -> None: else: self.updating = True self.dataset.create_mmap(access_mode, rf2_pid) - self.dataset.update_mmap() - self.copy_mmap_player() + self.copy_player_scor() + self.copy_player_tele() self.update_thread = threading.Thread( target=self.__update, daemon=True) @@ -295,8 +266,7 @@ def __update(self) -> None: # Update player data & index if not data_freezed: # Get player data - data_synced = self.__sync_player_data( - self.dataset.scor, self.dataset.tele) + data_synced = self.__sync_player_data() # Pause if local player index no longer exists, 5 tries if not data_synced and reset_counter < 6: reset_counter += 1 From e717293a03408cf4507dc776a949a8c312536afd Mon Sep 17 00:00:00 2001 From: Xiang Date: Tue, 5 Dec 2023 00:40:39 +0800 Subject: [PATCH 67/93] update logging format --- rF2MMap.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index e9618f7..896270d 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -53,7 +53,7 @@ def create(self, access_mode: int = 0, rf2_pid: str = "") -> None: ) self.__buffer_copy(True) mode = "Direct" if access_mode else "Copy" - logger.info("sharedmemory - ACTIVE: %s > %s Access", self.mmap_id, mode) + logger.info("sharedmemory: ACTIVE: %s (%s Access)", self.mmap_id, mode) def close(self) -> None: """Close memory mapping @@ -64,9 +64,9 @@ def close(self) -> None: self._buffer_sharing = False try: self._mmap_instance.close() - logger.info("sharedmemory - CLOSED: %s", self.mmap_id) + logger.info("sharedmemory: CLOSED: %s", self.mmap_id) except BufferError: - logger.error("sharedmemory - buffer error while closing mmap") + logger.error("sharedmemory: buffer error while closing mmap") def update(self) -> None: """Update mmap data""" @@ -231,7 +231,7 @@ def sync_tele_index(self, scor_idx: int) -> int: def start(self, access_mode: int, rf2_pid: str) -> None: """Update & sync mmap data copy in separate thread""" if self.updating: - logger.warning("sharedmemory - already updating !!!") + logger.warning("sharedmemory: UPDATING: already started") else: self.updating = True self.dataset.create_mmap(access_mode, rf2_pid) @@ -241,8 +241,8 @@ def start(self, access_mode: int, rf2_pid: str) -> None: self.update_thread = threading.Thread( target=self.__update, daemon=True) self.update_thread.start() - logger.info("sharedmemory - updating thread started") - logger.info("sharedmemory - player index override: %s", self.override_player_index) + logger.info("sharedmemory: UPDATING: thread started") + logger.info("sharedmemory: player index override: %s", self.override_player_index) def stop(self) -> None: """Join and stop updating thread, close mmap""" @@ -251,7 +251,7 @@ def stop(self) -> None: self.update_thread.join() self.dataset.close_mmap() else: - logger.warning("sharedmemory - already stopped !!!") + logger.warning("sharedmemory: UPDATING: already stopped") def __update(self) -> None: """Update synced player data""" @@ -276,7 +276,7 @@ def __update(self) -> None: # Activate pause if reset_counter == 5: self.paused = True - logger.info("sharedmemory - player data paused") + logger.info("sharedmemory: UPDATING: player data paused") # Start checking data version update status if time.time() - check_timer_start > 5: @@ -286,9 +286,8 @@ def __update(self) -> None: data_freezed = True self.paused = True logger.info( - "sharedmemory - data paused, version %s", - last_version_update - ) + "sharedmemory: UPDATING: paused, data version %s", + last_version_update) last_version_update = self.dataset.scor.mVersionUpdateEnd check_timer_start = time.time() # reset timer @@ -298,14 +297,13 @@ def __update(self) -> None: data_freezed = False self.paused = False logger.info( - "sharedmemory - data unpaused, version %s", - self.dataset.scor.mVersionUpdateEnd - ) + "sharedmemory: UPDATING: resumed, data version %s", + self.dataset.scor.mVersionUpdateEnd) time.sleep(update_delay) self.paused = False - logger.info("sharedmemory - updating thread stopped") + logger.info("sharedmemory: UPDATING: thread stopped") class RF2SM: From 0a007719369ea612adbfc72625e3ad44551cab7e Mon Sep 17 00:00:00 2001 From: Xiang Date: Tue, 5 Dec 2023 02:39:21 +0800 Subject: [PATCH 68/93] remove last index check --- rF2MMap.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index 896270d..ae347f4 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -181,14 +181,12 @@ def copy_player_tele(self, index: int = INVALID_INDEX) -> None: """Copy telemetry player data""" self.player_tele = copy.deepcopy(self.dataset.tele.mVehicles[index]) - def __local_scor_index(self, last_idx: int) -> int: + def __local_scor_index(self) -> int: """Find local player scoring index Check last found index first. If not player, loop through all vehicles. """ - if self.dataset.scor.mVehicles[last_idx].mIsPlayer: - return last_idx for scor_idx in range(MAX_VEHICLES): if self.dataset.scor.mVehicles[scor_idx].mIsPlayer: return scor_idx @@ -198,7 +196,7 @@ def __sync_player_data(self) -> bool: """Sync local player data""" if not self.override_player_index: # Update scoring index - scor_idx = self.__local_scor_index(self.player_scor_index) + scor_idx = self.__local_scor_index() if scor_idx == INVALID_INDEX: return False # index not found, not synced self.player_scor_index = scor_idx @@ -221,8 +219,6 @@ def sync_tele_index(self, scor_idx: int) -> int: If not same, loop through all vehicles. """ scor_mid = self.dataset.scor.mVehicles[scor_idx].mID - if self.dataset.tele.mVehicles[scor_idx].mID == scor_mid: - return scor_idx for tele_idx in range(MAX_VEHICLES): if self.dataset.tele.mVehicles[tele_idx].mID == scor_mid: return tele_idx From 2d89ff697fcc4827a7b30a1309a818b295f20ef9 Mon Sep 17 00:00:00 2001 From: Xiang Date: Tue, 5 Dec 2023 17:58:00 +0800 Subject: [PATCH 69/93] move mmap methods to outside functions --- rF2MMap.py | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index ae347f4..d418505 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -26,6 +26,27 @@ logger = logging.getLogger(__name__) +def platform_mmap(name: str, size: int, pid: str = "") -> mmap: + """Platform memory mapping""" + if PLATFORM == "Windows": + return windows_mmap(name, size, pid) + return linux_mmap(name, size) + + +def windows_mmap(name: str, size: int, pid: str) -> mmap: + """Windows mmap""" + return mmap.mmap(-1, size, f"{name}{pid}") + + +def linux_mmap(name: str, size: int) -> mmap: + """Linux mmap""" + file = open("/dev/shm/" + name, "a+") + if file.tell() == 0: + file.write("\0" * size) + file.flush() + return mmap.mmap(file.fileno(), size) + + class RF2MMap: """Create rF2 Memory Map @@ -46,7 +67,7 @@ def __init__(self, mmap_name: str, rf2_data) -> None: def create(self, access_mode: int = 0, rf2_pid: str = "") -> None: """Create mmap instance & initial accessible copy""" self._access_mode = access_mode - self._mmap_instance = self.platform_mmap( + self._mmap_instance = platform_mmap( name=self._mmap_name, size=ctypes.sizeof(self._rf2_data), pid=rf2_pid @@ -92,26 +113,6 @@ def __buffer_copy(self, skip_check=False) -> None: if temp.mVersionUpdateEnd == temp.mVersionUpdateBegin or skip_check: self._mmap_output = temp - def platform_mmap(self, name: str, size: int, pid: str = "") -> mmap: - """Platform memory mapping""" - if PLATFORM == "Windows": - return self.windows_mmap(name, size, pid) - return self.linux_mmap(name, size) - - @staticmethod - def windows_mmap(name: str, size: int, pid: str) -> mmap: - """Windows mmap""" - return mmap.mmap(-1, size, f"{name}{pid}") - - @staticmethod - def linux_mmap(name: str, size: int) -> mmap: - """Linux mmap""" - file = open("/dev/shm/" + name, "a+") - if file.tell() == 0: - file.write("\0" * size) - file.flush() - return mmap.mmap(file.fileno(), size) - class MMapDataSet: """Create mmap data set""" @@ -239,6 +240,7 @@ def start(self, access_mode: int, rf2_pid: str) -> None: self.update_thread.start() logger.info("sharedmemory: UPDATING: thread started") logger.info("sharedmemory: player index override: %s", self.override_player_index) + logger.info("sharedmemory: server process ID: %s", rf2_pid if rf2_pid else "DISABLED") def stop(self) -> None: """Join and stop updating thread, close mmap""" From 66f0b29dd2201d598346d32029db3c191988c960 Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 6 Dec 2023 16:34:48 +0800 Subject: [PATCH 70/93] use event wait --- rF2MMap.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index d418505..8efd777 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -168,6 +168,7 @@ def __init__(self) -> None: self.updating = False self.update_thread = None self.paused = True + self.event = threading.Event() self.override_player_index = False self.player_scor_index = INVALID_INDEX @@ -231,6 +232,7 @@ def start(self, access_mode: int, rf2_pid: str) -> None: logger.warning("sharedmemory: UPDATING: already started") else: self.updating = True + self.event.clear() self.dataset.create_mmap(access_mode, rf2_pid) self.copy_player_scor() self.copy_player_tele() @@ -245,6 +247,7 @@ def start(self, access_mode: int, rf2_pid: str) -> None: def stop(self) -> None: """Join and stop updating thread, close mmap""" if self.updating: + self.event.set() self.updating = False self.update_thread.join() self.dataset.close_mmap() @@ -259,7 +262,7 @@ def __update(self) -> None: reset_counter = 0 update_delay = 0.5 # longer delay while inactive - while self.updating: + while not self.event.wait(update_delay): self.dataset.update_mmap() # Update player data & index if not data_freezed: @@ -298,8 +301,6 @@ def __update(self) -> None: "sharedmemory: UPDATING: resumed, data version %s", self.dataset.scor.mVersionUpdateEnd) - time.sleep(update_delay) - self.paused = False logger.info("sharedmemory: UPDATING: thread stopped") From 525bffb84538c441a5696fb15785b8b904ce6260 Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 6 Dec 2023 18:55:48 +0800 Subject: [PATCH 71/93] open or create mmap file as binary --- rF2MMap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index 8efd777..8be4e81 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -40,9 +40,9 @@ def windows_mmap(name: str, size: int, pid: str) -> mmap: def linux_mmap(name: str, size: int) -> mmap: """Linux mmap""" - file = open("/dev/shm/" + name, "a+") + file = open("/dev/shm/" + name, "a+b") if file.tell() == 0: - file.write("\0" * size) + file.write(b"\0" * size) file.flush() return mmap.mmap(file.fileno(), size) From 3de46704eb50176c341503f844f9ad047d09d970 Mon Sep 17 00:00:00 2001 From: Xiang Date: Fri, 8 Dec 2023 21:40:25 +0800 Subject: [PATCH 72/93] create mID:index dictionary for matching mID --- rF2MMap.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index 8be4e81..56963cd 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -174,6 +174,7 @@ def __init__(self) -> None: self.player_scor_index = INVALID_INDEX self.player_scor = None self.player_tele = None + self.tele_idx_dict = {idx: idx for idx in range(128)} def copy_player_scor(self, index: int = INVALID_INDEX) -> None: """Copy scoring player data""" @@ -202,29 +203,28 @@ def __sync_player_data(self) -> bool: if scor_idx == INVALID_INDEX: return False # index not found, not synced self.player_scor_index = scor_idx - # Copy scoring data + # Copy player data self.copy_player_scor(self.player_scor_index) - # Update telemetry index - tele_idx = self.sync_tele_index(self.player_scor_index) - if tele_idx != INVALID_INDEX: - # Copy telemetry data - self.copy_player_tele(tele_idx) + self.copy_player_tele(self.sync_tele_index(self.player_scor_index)) return True # found index, synced - def sync_tele_index(self, scor_idx: int) -> int: - """Sync telemetry index with scoring index using mID + def __update_tele_index_dict(self, num_vehicles: int) -> None: + """Update telemetry player index dictionary for quick reference Telemetry index can be different from scoring index. - Use mID matching to find telemetry index. + Use mID matching to match telemetry index. + key: Tele mID + value: Tele index + """ + for idx in range(num_vehicles): + self.tele_idx_dict[self.dataset.tele.mVehicles[idx].mID] = idx - Compare scor mid with tele mid first. - If not same, loop through all vehicles. + def sync_tele_index(self, scor_idx: int) -> int: + """Find & sync telemetry index + + Match scoring mID with telemetry mID in tele_idx_dict """ - scor_mid = self.dataset.scor.mVehicles[scor_idx].mID - for tele_idx in range(MAX_VEHICLES): - if self.dataset.tele.mVehicles[tele_idx].mID == scor_mid: - return tele_idx - return INVALID_INDEX + return self.tele_idx_dict[self.dataset.scor.mVehicles[scor_idx].mID] def start(self, access_mode: int, rf2_pid: str) -> None: """Update & sync mmap data copy in separate thread""" @@ -264,6 +264,7 @@ def __update(self) -> None: while not self.event.wait(update_delay): self.dataset.update_mmap() + self.__update_tele_index_dict(self.dataset.tele.mNumVehicles) # Update player data & index if not data_freezed: # Get player data From 8a073e440b6da3c5c516ad7b4101045d1fb8708e Mon Sep 17 00:00:00 2001 From: Xiang Date: Sat, 9 Dec 2023 13:57:08 +0800 Subject: [PATCH 73/93] unnecessary deepcopy, use shallow copy instead --- rF2MMap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index 56963cd..fa10baa 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -178,11 +178,11 @@ def __init__(self) -> None: def copy_player_scor(self, index: int = INVALID_INDEX) -> None: """Copy scoring player data""" - self.player_scor = copy.deepcopy(self.dataset.scor.mVehicles[index]) + self.player_scor = copy.copy(self.dataset.scor.mVehicles[index]) def copy_player_tele(self, index: int = INVALID_INDEX) -> None: """Copy telemetry player data""" - self.player_tele = copy.deepcopy(self.dataset.tele.mVehicles[index]) + self.player_tele = copy.copy(self.dataset.tele.mVehicles[index]) def __local_scor_index(self) -> int: """Find local player scoring index From dac3f6c21f6487bf6eeb8e18f3c5a1edd24d53ff Mon Sep 17 00:00:00 2001 From: Xiang Date: Sat, 23 Dec 2023 17:18:07 +0800 Subject: [PATCH 74/93] set initial pause state to False --- rF2MMap.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index fa10baa..999f8d7 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -167,7 +167,7 @@ def __init__(self) -> None: self.dataset = MMapDataSet() self.updating = False self.update_thread = None - self.paused = True + self.paused = False self.event = threading.Event() self.override_player_index = False @@ -232,11 +232,14 @@ def start(self, access_mode: int, rf2_pid: str) -> None: logger.warning("sharedmemory: UPDATING: already started") else: self.updating = True - self.event.clear() + # Initialize mmap data self.dataset.create_mmap(access_mode, rf2_pid) - self.copy_player_scor() - self.copy_player_tele() - + self.__update_tele_index_dict(self.dataset.tele.mNumVehicles) + if not self.__sync_player_data(): + self.copy_player_scor() + self.copy_player_tele() + # Setup updating thread + self.event.clear() self.update_thread = threading.Thread( target=self.__update, daemon=True) self.update_thread.start() @@ -256,6 +259,7 @@ def stop(self) -> None: def __update(self) -> None: """Update synced player data""" + self.paused = False # make sure initial pause state is false last_version_update = 0 # store last data version update data_freezed = True # whether data is freezed check_timer_start = 0 @@ -302,7 +306,6 @@ def __update(self) -> None: "sharedmemory: UPDATING: resumed, data version %s", self.dataset.scor.mVersionUpdateEnd) - self.paused = False logger.info("sharedmemory: UPDATING: thread stopped") From 1f06fd63774d9985b380454507de8db363a95e07 Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 28 Dec 2023 13:12:08 +0800 Subject: [PATCH 75/93] use dict get --- rF2MMap.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rF2MMap.py b/rF2MMap.py index 999f8d7..f315f6d 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -224,7 +224,8 @@ def sync_tele_index(self, scor_idx: int) -> int: Match scoring mID with telemetry mID in tele_idx_dict """ - return self.tele_idx_dict[self.dataset.scor.mVehicles[scor_idx].mID] + return self.tele_idx_dict.get( + self.dataset.scor.mVehicles[scor_idx].mID, INVALID_INDEX) def start(self, access_mode: int, rf2_pid: str) -> None: """Update & sync mmap data copy in separate thread""" From 7b31720d7cb56e07b666f2ec490e24c75f621680 Mon Sep 17 00:00:00 2001 From: Xiang Date: Mon, 1 Jan 2024 13:56:22 +0800 Subject: [PATCH 76/93] update docstrings --- rF2MMap.py | 106 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 72 insertions(+), 34 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index f315f6d..e8541c1 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -6,6 +6,7 @@ and cross-platform Linux support (by Bernat) """ +from __future__ import annotations import copy import ctypes import logging @@ -50,12 +51,12 @@ def linux_mmap(name: str, size: int) -> mmap: class RF2MMap: """Create rF2 Memory Map - mmap_name: mmap filename, ex. $rFactor2SMMP_Scoring$ - rf2_data: rf2 data class defined in rF2data, ex. rF2data.rF2Scoring - rf2_pid: rf2 Process ID for accessing server data + Attributes: + mmap_name: mmap filename, ex. $rFactor2SMMP_Scoring$. + rf2_data: rf2 data class defined in rF2data, ex. rF2data.rF2Scoring. """ - def __init__(self, mmap_name: str, rf2_data) -> None: + def __init__(self, mmap_name: str, rf2_data: object) -> None: self.mmap_id = mmap_name.strip("$") self._mmap_name = mmap_name self._rf2_data = rf2_data @@ -65,7 +66,12 @@ def __init__(self, mmap_name: str, rf2_data) -> None: self._buffer_sharing = False def create(self, access_mode: int = 0, rf2_pid: str = "") -> None: - """Create mmap instance & initial accessible copy""" + """Create mmap instance & initial accessible copy + + Args: + access_mode: 0 = copy access, 1 = direct access. + rf2_pid: rf2 Process ID for accessing server data. + """ self._access_mode = access_mode self._mmap_instance = platform_mmap( name=self._mmap_name, @@ -107,8 +113,12 @@ def __buffer_share(self) -> None: self._buffer_sharing = True self._mmap_output = self._rf2_data.from_buffer(self._mmap_instance) - def __buffer_copy(self, skip_check=False) -> None: - """Copy buffer access, check version before assign""" + def __buffer_copy(self, skip_check: bool = False) -> None: + """Copy buffer access, check version before assign new data copy + + Args: + skip_check: skip data version check. + """ temp = self._rf2_data.from_buffer_copy(self._mmap_instance) if temp.mVersionUpdateEnd == temp.mVersionUpdateBegin or skip_check: self._mmap_output = temp @@ -122,21 +132,26 @@ def __init__(self) -> None: self._tele = RF2MMap("$rFactor2SMMP_Telemetry$", rF2data.rF2Telemetry) self._ext = RF2MMap("$rFactor2SMMP_Extended$", rF2data.rF2Extended) self._ffb = RF2MMap("$rFactor2SMMP_ForceFeedback$", rF2data.rF2ForceFeedback) - self.mmap_active = [self._scor, self._tele, self._ext, self._ffb] + self.mmap_pack = (self._scor, self._tele, self._ext, self._ffb) def create_mmap(self, access_mode: int, rf2_pid: str) -> None: - """Create mmap instance""" - for data in self.mmap_active: + """Create mmap instance + + Args: + access_mode: 0 = copy access, 1 = direct access. + rf2_pid: rf2 Process ID for accessing server data. + """ + for data in self.mmap_pack: data.create(access_mode, rf2_pid) def close_mmap(self) -> None: """Close mmap instance""" - for data in self.mmap_active: + for data in self.mmap_pack: data.close() def update_mmap(self) -> None: """Update mmap data""" - for data in self.mmap_active: + for data in self.mmap_pack: data.update() @property @@ -174,14 +189,14 @@ def __init__(self) -> None: self.player_scor_index = INVALID_INDEX self.player_scor = None self.player_tele = None - self.tele_idx_dict = {idx: idx for idx in range(128)} + self.tele_idx_dict = {_index: _index for _index in range(128)} def copy_player_scor(self, index: int = INVALID_INDEX) -> None: - """Copy scoring player data""" + """Copy scoring player data from matching index""" self.player_scor = copy.copy(self.dataset.scor.mVehicles[index]) def copy_player_tele(self, index: int = INVALID_INDEX) -> None: - """Copy telemetry player data""" + """Copy telemetry player data from matching index""" self.player_tele = copy.copy(self.dataset.tele.mVehicles[index]) def __local_scor_index(self) -> int: @@ -196,7 +211,12 @@ def __local_scor_index(self) -> int: return INVALID_INDEX def __sync_player_data(self) -> bool: - """Sync local player data""" + """Sync local player data + + Returns: + False, if no valid player scoring index found. + True, update player data copy. + """ if not self.override_player_index: # Update scoring index scor_idx = self.__local_scor_index() @@ -213,22 +233,40 @@ def __update_tele_index_dict(self, num_vehicles: int) -> None: Telemetry index can be different from scoring index. Use mID matching to match telemetry index. - key: Tele mID - value: Tele index + + Args: + num_vehicles: total number of vehicles. + + tele_idx_dict: + key: Tele mID. + value: Tele index. """ - for idx in range(num_vehicles): - self.tele_idx_dict[self.dataset.tele.mVehicles[idx].mID] = idx + for _index in range(num_vehicles): + self.tele_idx_dict[self.dataset.tele.mVehicles[_index].mID] = _index def sync_tele_index(self, scor_idx: int) -> int: - """Find & sync telemetry index + """Sync telemetry index + + Use scoring index to find scoring mID, + then match with telemetry mID in tele_idx_dict + to find telemetry index. - Match scoring mID with telemetry mID in tele_idx_dict + Args: + scor_idx: player scoring index. + + Returns: + Player telemetry index. """ return self.tele_idx_dict.get( self.dataset.scor.mVehicles[scor_idx].mID, INVALID_INDEX) def start(self, access_mode: int, rf2_pid: str) -> None: - """Update & sync mmap data copy in separate thread""" + """Update & sync mmap data copy in separate thread + + Args: + access_mode: 0 = copy access, 1 = direct access. + rf2_pid: rf2 Process ID for accessing server data. + """ if self.updating: logger.warning("sharedmemory: UPDATING: already started") else: @@ -335,7 +373,7 @@ def stop(self) -> None: self._sync.stop() def setPID(self, pid: str = "") -> None: - """Set rf2 PID""" + """Set rf2 process ID""" self.rf2_pid = str(pid) def setMode(self, mode: int = 0) -> None: @@ -346,16 +384,16 @@ def setPlayerOverride(self, state: bool = False) -> None: """Set player index override state""" self._sync.override_player_index = state - def setPlayerIndex(self, idx: int = INVALID_INDEX) -> None: + def setPlayerIndex(self, index: int = INVALID_INDEX) -> None: """Set player index""" - self._sync.player_scor_index = min(max(idx, INVALID_INDEX), MAX_VEHICLES - 1) + self._sync.player_scor_index = min(max(index, INVALID_INDEX), MAX_VEHICLES - 1) @property - def rf2ScorInfo(self): + def rf2ScorInfo(self) -> object: """rF2 scoring info data""" return self._sync.dataset.scor.mScoringInfo - def rf2ScorVeh(self, index: int = None): + def rf2ScorVeh(self, index: int | None = None) -> object: """rF2 scoring vehicle data Specify index for specific player. @@ -365,7 +403,7 @@ def rf2ScorVeh(self, index: int = None): return self._sync.player_scor return self._sync.dataset.scor.mVehicles[index] - def rf2TeleVeh(self, index: int = None): + def rf2TeleVeh(self, index: int | None = None) -> object: """rF2 telemetry vehicle data Specify index for specific player. @@ -376,12 +414,12 @@ def rf2TeleVeh(self, index: int = None): return self._sync.dataset.tele.mVehicles[self._sync.sync_tele_index(index)] @property - def rf2Ext(self): + def rf2Ext(self) -> object: """rF2 extended data""" return self._sync.dataset.ext @property - def rf2Ffb(self): + def rf2Ffb(self) -> object: """rF2 force feedback data""" return self._sync.dataset.ffb @@ -390,11 +428,11 @@ def playerIndex(self) -> int: """rF2 local player's scoring index""" return self._sync.player_scor_index - def isPlayer(self, idx: int) -> bool: + def isPlayer(self, index: int) -> bool: """Check whether index is player""" if self._sync.override_player_index: - return self._sync.player_scor_index == idx - return self._sync.dataset.scor.mVehicles[idx].mIsPlayer + return self._sync.player_scor_index == index + return self._sync.dataset.scor.mVehicles[index].mIsPlayer @property def isPaused(self) -> bool: From 7f0aff165e17687f7dc264947332b1e32c6e0bce Mon Sep 17 00:00:00 2001 From: Xiang Date: Mon, 1 Jan 2024 17:06:37 +0800 Subject: [PATCH 77/93] update annotations --- rF2MMap.py | 122 +++++++++++++++++++++++++++++------------------------ 1 file changed, 67 insertions(+), 55 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index e8541c1..689c98c 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -52,11 +52,16 @@ class RF2MMap: """Create rF2 Memory Map Attributes: - mmap_name: mmap filename, ex. $rFactor2SMMP_Scoring$. - rf2_data: rf2 data class defined in rF2data, ex. rF2data.rF2Scoring. + mmap_id: mmap name string. """ def __init__(self, mmap_name: str, rf2_data: object) -> None: + """Initialize memory map setting + + Args: + mmap_name: mmap filename, ex. $rFactor2SMMP_Scoring$. + rf2_data: rF2 data class defined in rF2data, ex. rF2data.rF2Scoring. + """ self.mmap_id = mmap_name.strip("$") self._mmap_name = mmap_name self._rf2_data = rf2_data @@ -70,7 +75,7 @@ def create(self, access_mode: int = 0, rf2_pid: str = "") -> None: Args: access_mode: 0 = copy access, 1 = direct access. - rf2_pid: rf2 Process ID for accessing server data. + rf2_pid: rF2 Process ID for accessing server data. """ self._access_mode = access_mode self._mmap_instance = platform_mmap( @@ -125,7 +130,11 @@ def __buffer_copy(self, skip_check: bool = False) -> None: class MMapDataSet: - """Create mmap data set""" + """Create mmap data set + + Attributes: + mmap_pack: Holds mmap instances list. + """ def __init__(self) -> None: self._scor = RF2MMap("$rFactor2SMMP_Scoring$", rF2data.rF2Scoring) @@ -139,7 +148,7 @@ def create_mmap(self, access_mode: int, rf2_pid: str) -> None: Args: access_mode: 0 = copy access, 1 = direct access. - rf2_pid: rf2 Process ID for accessing server data. + rf2_pid: rF2 Process ID for accessing server data. """ for data in self.mmap_pack: data.create(access_mode, rf2_pid) @@ -176,20 +185,29 @@ def ffb(self): class SyncData: - """Synchronize data with player ID""" + """Synchronize data with player ID + + Attributes: + dataset: mmap data set. + paused: Data update state (boolean). + override_player_index: Player index override state (boolean). + player_scor_index: Local player scoring index. + player_scor: Local player scoring data. + player_tele: Local player telemetry data. + """ def __init__(self) -> None: + self._updating = False + self._update_thread = None + self._event = threading.Event() + self._tele_idx_dict = {_index: _index for _index in range(128)} + self.dataset = MMapDataSet() - self.updating = False - self.update_thread = None self.paused = False - self.event = threading.Event() - self.override_player_index = False self.player_scor_index = INVALID_INDEX self.player_scor = None self.player_tele = None - self.tele_idx_dict = {_index: _index for _index in range(128)} def copy_player_scor(self, index: int = INVALID_INDEX) -> None: """Copy scoring player data from matching index""" @@ -200,11 +218,7 @@ def copy_player_tele(self, index: int = INVALID_INDEX) -> None: self.player_tele = copy.copy(self.dataset.tele.mVehicles[index]) def __local_scor_index(self) -> int: - """Find local player scoring index - - Check last found index first. - If not player, loop through all vehicles. - """ + """Find local player scoring index""" for scor_idx in range(MAX_VEHICLES): if self.dataset.scor.mVehicles[scor_idx].mIsPlayer: return scor_idx @@ -234,15 +248,13 @@ def __update_tele_index_dict(self, num_vehicles: int) -> None: Telemetry index can be different from scoring index. Use mID matching to match telemetry index. - Args: - num_vehicles: total number of vehicles. + _tele_idx_dict: Telemetry mID:index reference dictionary. - tele_idx_dict: - key: Tele mID. - value: Tele index. + Args: + num_vehicles: Total number of vehicles. """ for _index in range(num_vehicles): - self.tele_idx_dict[self.dataset.tele.mVehicles[_index].mID] = _index + self._tele_idx_dict[self.dataset.tele.mVehicles[_index].mID] = _index def sync_tele_index(self, scor_idx: int) -> int: """Sync telemetry index @@ -252,12 +264,12 @@ def sync_tele_index(self, scor_idx: int) -> int: to find telemetry index. Args: - scor_idx: player scoring index. + scor_idx: Player scoring index. Returns: Player telemetry index. """ - return self.tele_idx_dict.get( + return self._tele_idx_dict.get( self.dataset.scor.mVehicles[scor_idx].mID, INVALID_INDEX) def start(self, access_mode: int, rf2_pid: str) -> None: @@ -265,12 +277,12 @@ def start(self, access_mode: int, rf2_pid: str) -> None: Args: access_mode: 0 = copy access, 1 = direct access. - rf2_pid: rf2 Process ID for accessing server data. + rf2_pid: rF2 Process ID for accessing server data. """ - if self.updating: + if self._updating: logger.warning("sharedmemory: UPDATING: already started") else: - self.updating = True + self._updating = True # Initialize mmap data self.dataset.create_mmap(access_mode, rf2_pid) self.__update_tele_index_dict(self.dataset.tele.mNumVehicles) @@ -278,20 +290,20 @@ def start(self, access_mode: int, rf2_pid: str) -> None: self.copy_player_scor() self.copy_player_tele() # Setup updating thread - self.event.clear() - self.update_thread = threading.Thread( + self._event.clear() + self._update_thread = threading.Thread( target=self.__update, daemon=True) - self.update_thread.start() + self._update_thread.start() logger.info("sharedmemory: UPDATING: thread started") logger.info("sharedmemory: player index override: %s", self.override_player_index) logger.info("sharedmemory: server process ID: %s", rf2_pid if rf2_pid else "DISABLED") def stop(self) -> None: """Join and stop updating thread, close mmap""" - if self.updating: - self.event.set() - self.updating = False - self.update_thread.join() + if self._updating: + self._event.set() + self._updating = False + self._update_thread.join() self.dataset.close_mmap() else: logger.warning("sharedmemory: UPDATING: already stopped") @@ -305,7 +317,7 @@ def __update(self) -> None: reset_counter = 0 update_delay = 0.5 # longer delay while inactive - while not self.event.wait(update_delay): + while not self._event.wait(update_delay): self.dataset.update_mmap() self.__update_tele_index_dict(self.dataset.tele.mNumVehicles) # Update player data & index @@ -349,43 +361,39 @@ def __update(self) -> None: class RF2SM: - """ - RF2 shared memory data output - - Optional parameters: - setMode: set access mode, 0 = copy access, 1 = direct access - setPID: set process ID for connecting to server data (str) - setPlayerOverride: enable player index override (bool) - setPlayerIndex: manually set player index (int) - """ + """RF2 shared memory data output""" def __init__(self) -> None: self._sync = SyncData() - self.access_mode = 0 - self.rf2_pid = "" + self._access_mode = 0 + self._rf2_pid = "" def start(self) -> None: """Start data updating thread""" - self._sync.start(self.access_mode, self.rf2_pid) + self._sync.start(self._access_mode, self._rf2_pid) def stop(self) -> None: """Stop data updating thread""" self._sync.stop() def setPID(self, pid: str = "") -> None: - """Set rf2 process ID""" - self.rf2_pid = str(pid) + """Set rF2 process ID for connecting to server data""" + self._rf2_pid = str(pid) def setMode(self, mode: int = 0) -> None: - """Set rf2 mmap access mode""" - self.access_mode = mode + """Set rF2 mmap access mode + + Args: + mode: 0 = copy access, 1 = direct access + """ + self._access_mode = mode def setPlayerOverride(self, state: bool = False) -> None: - """Set player index override state""" + """Enable player index override state""" self._sync.override_player_index = state def setPlayerIndex(self, index: int = INVALID_INDEX) -> None: - """Set player index""" + """Manual override player index""" self._sync.player_scor_index = min(max(index, INVALID_INDEX), MAX_VEHICLES - 1) @property @@ -397,7 +405,9 @@ def rf2ScorVeh(self, index: int | None = None) -> object: """rF2 scoring vehicle data Specify index for specific player. - None for local player. + + Args: + index: None for local player. """ if index is None: return self._sync.player_scor @@ -407,7 +417,9 @@ def rf2TeleVeh(self, index: int | None = None) -> object: """rF2 telemetry vehicle data Specify index for specific player. - None for local player. + + Args: + index: None for local player. """ if index is None: return self._sync.player_tele From 626b16daa981cbc7c9b9b9ebc5c1fe9ca3f50867 Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 3 Jan 2024 12:25:21 +0800 Subject: [PATCH 78/93] remove loop --- rF2MMap.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index 689c98c..6988645 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -130,18 +130,13 @@ def __buffer_copy(self, skip_check: bool = False) -> None: class MMapDataSet: - """Create mmap data set - - Attributes: - mmap_pack: Holds mmap instances list. - """ + """Create mmap data set""" def __init__(self) -> None: self._scor = RF2MMap("$rFactor2SMMP_Scoring$", rF2data.rF2Scoring) self._tele = RF2MMap("$rFactor2SMMP_Telemetry$", rF2data.rF2Telemetry) self._ext = RF2MMap("$rFactor2SMMP_Extended$", rF2data.rF2Extended) self._ffb = RF2MMap("$rFactor2SMMP_ForceFeedback$", rF2data.rF2ForceFeedback) - self.mmap_pack = (self._scor, self._tele, self._ext, self._ffb) def create_mmap(self, access_mode: int, rf2_pid: str) -> None: """Create mmap instance @@ -150,18 +145,24 @@ def create_mmap(self, access_mode: int, rf2_pid: str) -> None: access_mode: 0 = copy access, 1 = direct access. rf2_pid: rF2 Process ID for accessing server data. """ - for data in self.mmap_pack: - data.create(access_mode, rf2_pid) + self._scor.create(access_mode, rf2_pid) + self._tele.create(access_mode, rf2_pid) + self._ext.create(access_mode, rf2_pid) + self._ffb.create(access_mode, rf2_pid) def close_mmap(self) -> None: """Close mmap instance""" - for data in self.mmap_pack: - data.close() + self._scor.close() + self._tele.close() + self._ext.close() + self._ffb.close() def update_mmap(self) -> None: """Update mmap data""" - for data in self.mmap_pack: - data.update() + self._scor.update() + self._tele.update() + self._ext.update() + self._ffb.update() @property def scor(self): From 922b74b623a20dc7bb5cc3b49e676a1b2c8c659c Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 27 Mar 2024 17:42:14 +0800 Subject: [PATCH 79/93] improved data update version check --- rF2MMap.py | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index 6988645..16ac34b 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -147,8 +147,8 @@ def create_mmap(self, access_mode: int, rf2_pid: str) -> None: """ self._scor.create(access_mode, rf2_pid) self._tele.create(access_mode, rf2_pid) - self._ext.create(access_mode, rf2_pid) - self._ffb.create(access_mode, rf2_pid) + self._ext.create(1, rf2_pid) + self._ffb.create(1, rf2_pid) def close_mmap(self) -> None: """Close mmap instance""" @@ -312,9 +312,10 @@ def stop(self) -> None: def __update(self) -> None: """Update synced player data""" self.paused = False # make sure initial pause state is false - last_version_update = 0 # store last data version update - data_freezed = True # whether data is freezed - check_timer_start = 0 + freezed_version = 0 # store freezed update version number + last_version_update = 0 # store last update version number + last_update_time = 0 + data_freezed = True # whether data is freezed reset_counter = 0 update_delay = 0.5 # longer delay while inactive @@ -336,27 +337,25 @@ def __update(self) -> None: self.paused = True logger.info("sharedmemory: UPDATING: player data paused") - # Start checking data version update status - if time.time() - check_timer_start > 5: - if (not data_freezed - and last_version_update == self.dataset.scor.mVersionUpdateEnd): - update_delay = 0.5 - data_freezed = True - self.paused = True - logger.info( - "sharedmemory: UPDATING: paused, data version %s", - last_version_update) + if last_version_update != self.dataset.scor.mVersionUpdateEnd: + last_update_time = time.time() last_version_update = self.dataset.scor.mVersionUpdateEnd - check_timer_start = time.time() # reset timer - if (data_freezed - and last_version_update != self.dataset.scor.mVersionUpdateEnd): + # Set freeze state if data stopped updating after 2s + if not data_freezed and time.time() - last_update_time > 2: + update_delay = 0.5 + data_freezed = True + self.paused = True + freezed_version = last_version_update + logger.info( + "sharedmemory: UPDATING: paused, data version %s", freezed_version) + + if data_freezed and freezed_version != last_version_update: update_delay = 0.01 data_freezed = False self.paused = False logger.info( - "sharedmemory: UPDATING: resumed, data version %s", - self.dataset.scor.mVersionUpdateEnd) + "sharedmemory: UPDATING: resumed, data version %s", last_version_update) logger.info("sharedmemory: UPDATING: thread stopped") From 5103b921600e42890de2e3a2c6b31b1928250584 Mon Sep 17 00:00:00 2001 From: Xiang Date: Mon, 1 Apr 2024 14:43:52 +0800 Subject: [PATCH 80/93] remove unnecessary update method --- rF2MMap.py | 41 ++++++++++++----------------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index 16ac34b..82a6704 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -63,11 +63,11 @@ def __init__(self, mmap_name: str, rf2_data: object) -> None: rf2_data: rF2 data class defined in rF2data, ex. rF2data.rF2Scoring. """ self.mmap_id = mmap_name.strip("$") + self.update = None self._mmap_name = mmap_name self._rf2_data = rf2_data self._mmap_instance = None self._mmap_output = None - self._access_mode = 0 self._buffer_sharing = False def create(self, access_mode: int = 0, rf2_pid: str = "") -> None: @@ -77,13 +77,16 @@ def create(self, access_mode: int = 0, rf2_pid: str = "") -> None: access_mode: 0 = copy access, 1 = direct access. rf2_pid: rF2 Process ID for accessing server data. """ - self._access_mode = access_mode self._mmap_instance = platform_mmap( name=self._mmap_name, size=ctypes.sizeof(self._rf2_data), pid=rf2_pid ) self.__buffer_copy(True) + if access_mode: + self.update = self.__buffer_share + else: + self.update = self.__buffer_copy mode = "Direct" if access_mode else "Copy" logger.info("sharedmemory: ACTIVE: %s (%s Access)", self.mmap_id, mode) @@ -100,13 +103,6 @@ def close(self) -> None: except BufferError: logger.error("sharedmemory: buffer error while closing mmap") - def update(self) -> None: - """Update mmap data""" - if self._access_mode: - self.__buffer_share() - else: - self.__buffer_copy() - @property def data(self): """Output mmap data""" @@ -469,6 +465,13 @@ def isPaused(self) -> bool: info.start() time.sleep(0.2) + print(SEPARATOR) + print("Test API - Restart") + info.stop() + info.setMode() # set copy access + info.setPlayerOverride() # disable player override + info.start() + print(SEPARATOR) print("Test API - Read") version = info.rf2Ext.mVersion.decode() @@ -478,26 +481,6 @@ def isPaused(self) -> bool: print(f"driver name : {driver if version else 'not running'}") print(f"track name : {track if version else 'not running'}") - print(SEPARATOR) - print("Test API - Restart") - info.stop() - info.setMode(0) # set copy access - info.setPlayerOverride(False) # disable player override - info.start() - - print(SEPARATOR) - print("Test API - Multi starts") - info.start() - info.start() - info.start() - info.start() - print(SEPARATOR) print("Test API - Close") info.stop() - - print(SEPARATOR) - print("Test API - Multi stop") - info.stop() - info.stop() - info.stop() From fc3969b6e520a5776883e73d738721d60a765d83 Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 4 Apr 2024 23:59:34 +0800 Subject: [PATCH 81/93] optimize pause check --- rF2MMap.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index 82a6704..0052694 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -288,8 +288,7 @@ def start(self, access_mode: int, rf2_pid: str) -> None: self.copy_player_tele() # Setup updating thread self._event.clear() - self._update_thread = threading.Thread( - target=self.__update, daemon=True) + self._update_thread = threading.Thread(target=self.__update, daemon=True) self._update_thread.start() logger.info("sharedmemory: UPDATING: thread started") logger.info("sharedmemory: player index override: %s", self.override_player_index) @@ -323,15 +322,16 @@ def __update(self) -> None: # Get player data data_synced = self.__sync_player_data() # Pause if local player index no longer exists, 5 tries - if not data_synced and reset_counter < 6: - reset_counter += 1 - elif data_synced: + if data_synced: reset_counter = 0 self.paused = False - # Activate pause - if reset_counter == 5: - self.paused = True - logger.info("sharedmemory: UPDATING: player data paused") + else: + if reset_counter < 6: + reset_counter += 1 + # Activate pause + if reset_counter == 5: + self.paused = True + logger.info("sharedmemory: UPDATING: player data paused") if last_version_update != self.dataset.scor.mVersionUpdateEnd: last_update_time = time.time() From 25ca7d9683854f2333dcc968775c6a52e77b141b Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 26 Sep 2024 12:42:12 +0800 Subject: [PATCH 82/93] improve access efficiency, remove unnecessary data copy --- rF2MMap.py | 115 ++++++++++++++++++++--------------------------------- 1 file changed, 43 insertions(+), 72 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index 0052694..4a1cdf1 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -7,7 +7,6 @@ """ from __future__ import annotations -import copy import ctypes import logging import mmap @@ -64,10 +63,10 @@ def __init__(self, mmap_name: str, rf2_data: object) -> None: """ self.mmap_id = mmap_name.strip("$") self.update = None + self.data = None self._mmap_name = mmap_name self._rf2_data = rf2_data self._mmap_instance = None - self._mmap_output = None self._buffer_sharing = False def create(self, access_mode: int = 0, rf2_pid: str = "") -> None: @@ -103,16 +102,11 @@ def close(self) -> None: except BufferError: logger.error("sharedmemory: buffer error while closing mmap") - @property - def data(self): - """Output mmap data""" - return self._mmap_output - def __buffer_share(self) -> None: """Share buffer direct access, may result desync""" if not self._buffer_sharing: self._buffer_sharing = True - self._mmap_output = self._rf2_data.from_buffer(self._mmap_instance) + self.data = self._rf2_data.from_buffer(self._mmap_instance) def __buffer_copy(self, skip_check: bool = False) -> None: """Copy buffer access, check version before assign new data copy @@ -122,17 +116,17 @@ def __buffer_copy(self, skip_check: bool = False) -> None: """ temp = self._rf2_data.from_buffer_copy(self._mmap_instance) if temp.mVersionUpdateEnd == temp.mVersionUpdateBegin or skip_check: - self._mmap_output = temp + self.data = temp class MMapDataSet: """Create mmap data set""" def __init__(self) -> None: - self._scor = RF2MMap("$rFactor2SMMP_Scoring$", rF2data.rF2Scoring) - self._tele = RF2MMap("$rFactor2SMMP_Telemetry$", rF2data.rF2Telemetry) - self._ext = RF2MMap("$rFactor2SMMP_Extended$", rF2data.rF2Extended) - self._ffb = RF2MMap("$rFactor2SMMP_ForceFeedback$", rF2data.rF2ForceFeedback) + self.scor = RF2MMap("$rFactor2SMMP_Scoring$", rF2data.rF2Scoring) + self.tele = RF2MMap("$rFactor2SMMP_Telemetry$", rF2data.rF2Telemetry) + self.ext = RF2MMap("$rFactor2SMMP_Extended$", rF2data.rF2Extended) + self.ffb = RF2MMap("$rFactor2SMMP_ForceFeedback$", rF2data.rF2ForceFeedback) def create_mmap(self, access_mode: int, rf2_pid: str) -> None: """Create mmap instance @@ -141,44 +135,24 @@ def create_mmap(self, access_mode: int, rf2_pid: str) -> None: access_mode: 0 = copy access, 1 = direct access. rf2_pid: rF2 Process ID for accessing server data. """ - self._scor.create(access_mode, rf2_pid) - self._tele.create(access_mode, rf2_pid) - self._ext.create(1, rf2_pid) - self._ffb.create(1, rf2_pid) + self.scor.create(access_mode, rf2_pid) + self.tele.create(access_mode, rf2_pid) + self.ext.create(1, rf2_pid) + self.ffb.create(1, rf2_pid) def close_mmap(self) -> None: """Close mmap instance""" - self._scor.close() - self._tele.close() - self._ext.close() - self._ffb.close() + self.scor.close() + self.tele.close() + self.ext.close() + self.ffb.close() def update_mmap(self) -> None: """Update mmap data""" - self._scor.update() - self._tele.update() - self._ext.update() - self._ffb.update() - - @property - def scor(self): - """Scoring data""" - return self._scor.data - - @property - def tele(self): - """Telemetry data""" - return self._tele.data - - @property - def ext(self): - """Extended data""" - return self._ext.data - - @property - def ffb(self): - """Force feedback data""" - return self._ffb.data + self.scor.update() + self.tele.update() + self.ext.update() + self.ffb.update() class SyncData: @@ -206,18 +180,10 @@ def __init__(self) -> None: self.player_scor = None self.player_tele = None - def copy_player_scor(self, index: int = INVALID_INDEX) -> None: - """Copy scoring player data from matching index""" - self.player_scor = copy.copy(self.dataset.scor.mVehicles[index]) - - def copy_player_tele(self, index: int = INVALID_INDEX) -> None: - """Copy telemetry player data from matching index""" - self.player_tele = copy.copy(self.dataset.tele.mVehicles[index]) - def __local_scor_index(self) -> int: """Find local player scoring index""" for scor_idx in range(MAX_VEHICLES): - if self.dataset.scor.mVehicles[scor_idx].mIsPlayer: + if self.dataset.scor.data.mVehicles[scor_idx].mIsPlayer: return scor_idx return INVALID_INDEX @@ -226,7 +192,7 @@ def __sync_player_data(self) -> bool: Returns: False, if no valid player scoring index found. - True, update player data copy. + True, set player data. """ if not self.override_player_index: # Update scoring index @@ -234,9 +200,9 @@ def __sync_player_data(self) -> bool: if scor_idx == INVALID_INDEX: return False # index not found, not synced self.player_scor_index = scor_idx - # Copy player data - self.copy_player_scor(self.player_scor_index) - self.copy_player_tele(self.sync_tele_index(self.player_scor_index)) + # Set player data + self.player_scor = self.dataset.scor.data.mVehicles[self.player_scor_index] + self.player_tele = self.dataset.tele.data.mVehicles[self.sync_tele_index(self.player_scor_index)] return True # found index, synced def __update_tele_index_dict(self, num_vehicles: int) -> None: @@ -251,7 +217,7 @@ def __update_tele_index_dict(self, num_vehicles: int) -> None: num_vehicles: Total number of vehicles. """ for _index in range(num_vehicles): - self._tele_idx_dict[self.dataset.tele.mVehicles[_index].mID] = _index + self._tele_idx_dict[self.dataset.tele.data.mVehicles[_index].mID] = _index def sync_tele_index(self, scor_idx: int) -> int: """Sync telemetry index @@ -267,7 +233,7 @@ def sync_tele_index(self, scor_idx: int) -> int: Player telemetry index. """ return self._tele_idx_dict.get( - self.dataset.scor.mVehicles[scor_idx].mID, INVALID_INDEX) + self.dataset.scor.data.mVehicles[scor_idx].mID, INVALID_INDEX) def start(self, access_mode: int, rf2_pid: str) -> None: """Update & sync mmap data copy in separate thread @@ -282,10 +248,10 @@ def start(self, access_mode: int, rf2_pid: str) -> None: self._updating = True # Initialize mmap data self.dataset.create_mmap(access_mode, rf2_pid) - self.__update_tele_index_dict(self.dataset.tele.mNumVehicles) + self.__update_tele_index_dict(self.dataset.tele.data.mNumVehicles) if not self.__sync_player_data(): - self.copy_player_scor() - self.copy_player_tele() + self.player_scor = self.dataset.scor.data.mVehicles[INVALID_INDEX] + self.player_tele = self.dataset.tele.data.mVehicles[INVALID_INDEX] # Setup updating thread self._event.clear() self._update_thread = threading.Thread(target=self.__update, daemon=True) @@ -316,7 +282,7 @@ def __update(self) -> None: while not self._event.wait(update_delay): self.dataset.update_mmap() - self.__update_tele_index_dict(self.dataset.tele.mNumVehicles) + self.__update_tele_index_dict(self.dataset.tele.data.mNumVehicles) # Update player data & index if not data_freezed: # Get player data @@ -333,9 +299,9 @@ def __update(self) -> None: self.paused = True logger.info("sharedmemory: UPDATING: player data paused") - if last_version_update != self.dataset.scor.mVersionUpdateEnd: + if last_version_update != self.dataset.scor.data.mVersionUpdateEnd: last_update_time = time.time() - last_version_update = self.dataset.scor.mVersionUpdateEnd + last_version_update = self.dataset.scor.data.mVersionUpdateEnd # Set freeze state if data stopped updating after 2s if not data_freezed and time.time() - last_update_time > 2: @@ -363,6 +329,11 @@ def __init__(self) -> None: self._sync = SyncData() self._access_mode = 0 self._rf2_pid = "" + # Assign mmap instance + self._scor = self._sync.dataset.scor + self._tele = self._sync.dataset.tele + self._ext = self._sync.dataset.ext + self._ffb = self._sync.dataset.ffb def start(self) -> None: """Start data updating thread""" @@ -395,7 +366,7 @@ def setPlayerIndex(self, index: int = INVALID_INDEX) -> None: @property def rf2ScorInfo(self) -> object: """rF2 scoring info data""" - return self._sync.dataset.scor.mScoringInfo + return self._scor.data.mScoringInfo def rf2ScorVeh(self, index: int | None = None) -> object: """rF2 scoring vehicle data @@ -407,7 +378,7 @@ def rf2ScorVeh(self, index: int | None = None) -> object: """ if index is None: return self._sync.player_scor - return self._sync.dataset.scor.mVehicles[index] + return self._scor.data.mVehicles[index] def rf2TeleVeh(self, index: int | None = None) -> object: """rF2 telemetry vehicle data @@ -419,17 +390,17 @@ def rf2TeleVeh(self, index: int | None = None) -> object: """ if index is None: return self._sync.player_tele - return self._sync.dataset.tele.mVehicles[self._sync.sync_tele_index(index)] + return self._tele.data.mVehicles[self._sync.sync_tele_index(index)] @property def rf2Ext(self) -> object: """rF2 extended data""" - return self._sync.dataset.ext + return self._ext.data @property def rf2Ffb(self) -> object: """rF2 force feedback data""" - return self._sync.dataset.ffb + return self._ffb.data @property def playerIndex(self) -> int: @@ -440,7 +411,7 @@ def isPlayer(self, index: int) -> bool: """Check whether index is player""" if self._sync.override_player_index: return self._sync.player_scor_index == index - return self._sync.dataset.scor.mVehicles[index].mIsPlayer + return self._scor.data.mVehicles[index].mIsPlayer @property def isPaused(self) -> bool: From 3d328d6ba1289fa4270ca55ca076d233bce223b2 Mon Sep 17 00:00:00 2001 From: Xiang Date: Sat, 30 Nov 2024 00:53:07 +0800 Subject: [PATCH 83/93] improve player index & mID finding methods --- rF2MMap.py | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index 4a1cdf1..3543f4b 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -47,6 +47,18 @@ def linux_mmap(name: str, size: int) -> mmap: return mmap.mmap(file.fileno(), size) +def local_scoring_index(scor_veh: rF2data.rF2VehicleScoring_Array_128) -> int: + """Find local player scoring index + + Args: + scor_veh: scoring mVehicles array. + """ + for scor_idx, veh_info in enumerate(scor_veh): + if veh_info.mIsPlayer: + return scor_idx + return INVALID_INDEX + + class RF2MMap: """Create rF2 Memory Map @@ -171,7 +183,7 @@ def __init__(self) -> None: self._updating = False self._update_thread = None self._event = threading.Event() - self._tele_idx_dict = {_index: _index for _index in range(128)} + self._tele_indexes = {_index: _index for _index in range(128)} self.dataset = MMapDataSet() self.paused = False @@ -180,13 +192,6 @@ def __init__(self) -> None: self.player_scor = None self.player_tele = None - def __local_scor_index(self) -> int: - """Find local player scoring index""" - for scor_idx in range(MAX_VEHICLES): - if self.dataset.scor.data.mVehicles[scor_idx].mIsPlayer: - return scor_idx - return INVALID_INDEX - def __sync_player_data(self) -> bool: """Sync local player data @@ -196,7 +201,7 @@ def __sync_player_data(self) -> bool: """ if not self.override_player_index: # Update scoring index - scor_idx = self.__local_scor_index() + scor_idx = local_scoring_index(self.dataset.scor.data.mVehicles) if scor_idx == INVALID_INDEX: return False # index not found, not synced self.player_scor_index = scor_idx @@ -205,25 +210,25 @@ def __sync_player_data(self) -> bool: self.player_tele = self.dataset.tele.data.mVehicles[self.sync_tele_index(self.player_scor_index)] return True # found index, synced - def __update_tele_index_dict(self, num_vehicles: int) -> None: + @staticmethod + def __update_tele_indexes(tele_data: rF2data.rF2Telemetry, tele_indexes: dict) -> None: """Update telemetry player index dictionary for quick reference Telemetry index can be different from scoring index. Use mID matching to match telemetry index. - _tele_idx_dict: Telemetry mID:index reference dictionary. - Args: - num_vehicles: Total number of vehicles. + tele_data: Telemetry data. + tele_indexes: Telemetry mID:index reference dictionary. """ - for _index in range(num_vehicles): - self._tele_idx_dict[self.dataset.tele.data.mVehicles[_index].mID] = _index + for tele_idx, veh_info in zip(range(tele_data.mNumVehicles), tele_data.mVehicles): + tele_indexes[veh_info.mID] = tele_idx def sync_tele_index(self, scor_idx: int) -> int: """Sync telemetry index Use scoring index to find scoring mID, - then match with telemetry mID in tele_idx_dict + then match with telemetry mID in reference dictionary to find telemetry index. Args: @@ -232,7 +237,7 @@ def sync_tele_index(self, scor_idx: int) -> int: Returns: Player telemetry index. """ - return self._tele_idx_dict.get( + return self._tele_indexes.get( self.dataset.scor.data.mVehicles[scor_idx].mID, INVALID_INDEX) def start(self, access_mode: int, rf2_pid: str) -> None: @@ -248,7 +253,7 @@ def start(self, access_mode: int, rf2_pid: str) -> None: self._updating = True # Initialize mmap data self.dataset.create_mmap(access_mode, rf2_pid) - self.__update_tele_index_dict(self.dataset.tele.data.mNumVehicles) + self.__update_tele_indexes(self.dataset.tele.data, self._tele_indexes) if not self.__sync_player_data(): self.player_scor = self.dataset.scor.data.mVehicles[INVALID_INDEX] self.player_tele = self.dataset.tele.data.mVehicles[INVALID_INDEX] @@ -282,7 +287,7 @@ def __update(self) -> None: while not self._event.wait(update_delay): self.dataset.update_mmap() - self.__update_tele_index_dict(self.dataset.tele.data.mNumVehicles) + self.__update_tele_indexes(self.dataset.tele.data, self._tele_indexes) # Update player data & index if not data_freezed: # Get player data From 1fcbed2b4136fef7ec8ebab76cc86dedfeff86e1 Mon Sep 17 00:00:00 2001 From: Xiang Date: Fri, 20 Dec 2024 14:46:43 +0800 Subject: [PATCH 84/93] fixed mmap instance not getting garbage-collected after closed, added log info for garbage collection, updated annotations --- rF2MMap.py | 148 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 102 insertions(+), 46 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index 3543f4b..e01ae04 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -11,8 +11,9 @@ import logging import mmap import platform -import time import threading +from typing import Sequence +from time import monotonic, sleep try: from . import rF2data @@ -26,19 +27,19 @@ logger = logging.getLogger(__name__) -def platform_mmap(name: str, size: int, pid: str = "") -> mmap: +def platform_mmap(name: str, size: int, pid: str = "") -> mmap.mmap: """Platform memory mapping""" if PLATFORM == "Windows": return windows_mmap(name, size, pid) return linux_mmap(name, size) -def windows_mmap(name: str, size: int, pid: str) -> mmap: +def windows_mmap(name: str, size: int, pid: str) -> mmap.mmap: """Windows mmap""" return mmap.mmap(-1, size, f"{name}{pid}") -def linux_mmap(name: str, size: int) -> mmap: +def linux_mmap(name: str, size: int) -> mmap.mmap: """Linux mmap""" file = open("/dev/shm/" + name, "a+b") if file.tell() == 0: @@ -47,7 +48,7 @@ def linux_mmap(name: str, size: int) -> mmap: return mmap.mmap(file.fileno(), size) -def local_scoring_index(scor_veh: rF2data.rF2VehicleScoring_Array_128) -> int: +def local_scoring_index(scor_veh: Sequence[rF2data.rF2VehicleScoring]) -> int: """Find local player scoring index Args: @@ -60,11 +61,16 @@ def local_scoring_index(scor_veh: rF2data.rF2VehicleScoring_Array_128) -> int: class RF2MMap: - """Create rF2 Memory Map + """Create rF2 Memory Map""" - Attributes: - mmap_id: mmap name string. - """ + __slots__ = ( + "_mmap_name", + "_rf2_data", + "_mmap_instance", + "_buffer_sharing", + "update", + "data", + ) def __init__(self, mmap_name: str, rf2_data: object) -> None: """Initialize memory map setting @@ -73,13 +79,15 @@ def __init__(self, mmap_name: str, rf2_data: object) -> None: mmap_name: mmap filename, ex. $rFactor2SMMP_Scoring$. rf2_data: rF2 data class defined in rF2data, ex. rF2data.rF2Scoring. """ - self.mmap_id = mmap_name.strip("$") - self.update = None - self.data = None self._mmap_name = mmap_name self._rf2_data = rf2_data self._mmap_instance = None self._buffer_sharing = False + self.update = None + self.data = None + + def __del__(self): + logger.info("sharedmemory: GC: MMap %s", self._mmap_name) def create(self, access_mode: int = 0, rf2_pid: str = "") -> None: """Create mmap instance & initial accessible copy @@ -99,7 +107,7 @@ def create(self, access_mode: int = 0, rf2_pid: str = "") -> None: else: self.update = self.__buffer_copy mode = "Direct" if access_mode else "Copy" - logger.info("sharedmemory: ACTIVE: %s (%s Access)", self.mmap_id, mode) + logger.info("sharedmemory: ACTIVE: %s (%s Access)", self._mmap_name, mode) def close(self) -> None: """Close memory mapping @@ -110,9 +118,10 @@ def close(self) -> None: self._buffer_sharing = False try: self._mmap_instance.close() - logger.info("sharedmemory: CLOSED: %s", self.mmap_id) + logger.info("sharedmemory: CLOSED: %s", self._mmap_name) except BufferError: logger.error("sharedmemory: buffer error while closing mmap") + self.update = None # unassign update method (for proper garbage collection) def __buffer_share(self) -> None: """Share buffer direct access, may result desync""" @@ -134,12 +143,22 @@ def __buffer_copy(self, skip_check: bool = False) -> None: class MMapDataSet: """Create mmap data set""" + __slots__ = ( + "scor", + "tele", + "ext", + "ffb", + ) + def __init__(self) -> None: self.scor = RF2MMap("$rFactor2SMMP_Scoring$", rF2data.rF2Scoring) self.tele = RF2MMap("$rFactor2SMMP_Telemetry$", rF2data.rF2Telemetry) self.ext = RF2MMap("$rFactor2SMMP_Extended$", rF2data.rF2Extended) self.ffb = RF2MMap("$rFactor2SMMP_ForceFeedback$", rF2data.rF2ForceFeedback) + def __del__(self): + logger.info("sharedmemory: GC: MMapDataSet") + def create_mmap(self, access_mode: int, rf2_pid: str) -> None: """Create mmap instance @@ -179,18 +198,34 @@ class SyncData: player_tele: Local player telemetry data. """ + __slots__ = ( + "_updating", + "_update_thread", + "_event", + "_tele_indexes", + "paused", + "override_player_index", + "player_scor_index", + "player_scor", + "player_tele", + "dataset", + ) + def __init__(self) -> None: self._updating = False self._update_thread = None self._event = threading.Event() self._tele_indexes = {_index: _index for _index in range(128)} - self.dataset = MMapDataSet() self.paused = False self.override_player_index = False self.player_scor_index = INVALID_INDEX self.player_scor = None self.player_tele = None + self.dataset = MMapDataSet() + + def __del__(self): + logger.info("sharedmemory: GC: SyncData") def __sync_player_data(self) -> bool: """Sync local player data @@ -280,7 +315,7 @@ def __update(self) -> None: self.paused = False # make sure initial pause state is false freezed_version = 0 # store freezed update version number last_version_update = 0 # store last update version number - last_update_time = 0 + last_update_time = 0.0 data_freezed = True # whether data is freezed reset_counter = 0 update_delay = 0.5 # longer delay while inactive @@ -296,33 +331,36 @@ def __update(self) -> None: if data_synced: reset_counter = 0 self.paused = False - else: - if reset_counter < 6: - reset_counter += 1 - # Activate pause - if reset_counter == 5: - self.paused = True - logger.info("sharedmemory: UPDATING: player data paused") - - if last_version_update != self.dataset.scor.data.mVersionUpdateEnd: - last_update_time = time.time() - last_version_update = self.dataset.scor.data.mVersionUpdateEnd - + elif reset_counter < 6: + reset_counter += 1 + if reset_counter == 5: + self.paused = True + logger.info("sharedmemory: UPDATING: player data paused") + + version_update = self.dataset.scor.data.mVersionUpdateEnd + if last_version_update != version_update: + last_version_update = version_update + last_update_time = monotonic() + + if data_freezed: + # Check while IN freeze state + if freezed_version != last_version_update: + update_delay = 0.01 + self.paused = data_freezed = False + logger.info( + "sharedmemory: UPDATING: resumed, data version %s", + last_version_update, + ) + # Check while NOT IN freeze state # Set freeze state if data stopped updating after 2s - if not data_freezed and time.time() - last_update_time > 2: + elif monotonic() - last_update_time > 2: update_delay = 0.5 - data_freezed = True - self.paused = True + self.paused = data_freezed = True freezed_version = last_version_update logger.info( - "sharedmemory: UPDATING: paused, data version %s", freezed_version) - - if data_freezed and freezed_version != last_version_update: - update_delay = 0.01 - data_freezed = False - self.paused = False - logger.info( - "sharedmemory: UPDATING: resumed, data version %s", last_version_update) + "sharedmemory: UPDATING: paused, data version %s", + freezed_version, + ) logger.info("sharedmemory: UPDATING: thread stopped") @@ -330,6 +368,16 @@ def __update(self) -> None: class RF2SM: """RF2 shared memory data output""" + __slots__ = ( + "_sync", + "_access_mode", + "_rf2_pid", + "_scor", + "_tele", + "_ext", + "_ffb", + ) + def __init__(self) -> None: self._sync = SyncData() self._access_mode = 0 @@ -340,6 +388,9 @@ def __init__(self) -> None: self._ext = self._sync.dataset.ext self._ffb = self._sync.dataset.ffb + def __del__(self): + logger.info("sharedmemory: GC: RF2SM") + def start(self) -> None: """Start data updating thread""" self._sync.start(self._access_mode, self._rf2_pid) @@ -369,11 +420,11 @@ def setPlayerIndex(self, index: int = INVALID_INDEX) -> None: self._sync.player_scor_index = min(max(index, INVALID_INDEX), MAX_VEHICLES - 1) @property - def rf2ScorInfo(self) -> object: + def rf2ScorInfo(self) -> rF2data.rF2ScoringInfo: """rF2 scoring info data""" return self._scor.data.mScoringInfo - def rf2ScorVeh(self, index: int | None = None) -> object: + def rf2ScorVeh(self, index: int | None = None) -> rF2data.rF2VehicleScoring: """rF2 scoring vehicle data Specify index for specific player. @@ -385,7 +436,7 @@ def rf2ScorVeh(self, index: int | None = None) -> object: return self._sync.player_scor return self._scor.data.mVehicles[index] - def rf2TeleVeh(self, index: int | None = None) -> object: + def rf2TeleVeh(self, index: int | None = None) -> rF2data.rF2VehicleTelemetry: """rF2 telemetry vehicle data Specify index for specific player. @@ -398,12 +449,12 @@ def rf2TeleVeh(self, index: int | None = None) -> object: return self._tele.data.mVehicles[self._sync.sync_tele_index(index)] @property - def rf2Ext(self) -> object: + def rf2Ext(self) -> rF2data.rF2Extended: """rF2 extended data""" return self._ext.data @property - def rf2Ffb(self) -> object: + def rf2Ffb(self) -> rF2data.rF2ForceFeedback: """rF2 force feedback data""" return self._ffb.data @@ -424,7 +475,8 @@ def isPaused(self) -> bool: return self._sync.paused -if __name__ == "__main__": +def test_api(): + """API test run""" # Add logger test_handler = logging.StreamHandler() logger.setLevel(logging.INFO) @@ -439,7 +491,7 @@ def isPaused(self) -> bool: info.setPlayerOverride(True) # enable player override info.setPlayerIndex(0) # set player index to 0 info.start() - time.sleep(0.2) + sleep(0.2) print(SEPARATOR) print("Test API - Restart") @@ -460,3 +512,7 @@ def isPaused(self) -> bool: print(SEPARATOR) print("Test API - Close") info.stop() + + +if __name__ == "__main__": + test_api() From d84e296ea6186f59907b5faa6907bec61fe4d09f Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 9 Jan 2025 13:13:29 +0800 Subject: [PATCH 85/93] fixed player data would prevent mmap from closing while using direct access --- rF2MMap.py | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index e01ae04..c1fa58c 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -12,6 +12,7 @@ import mmap import platform import threading +from copy import copy from typing import Sequence from time import monotonic, sleep @@ -60,27 +61,27 @@ def local_scoring_index(scor_veh: Sequence[rF2data.rF2VehicleScoring]) -> int: return INVALID_INDEX -class RF2MMap: - """Create rF2 Memory Map""" +class MMapControl: + """Memory map control""" __slots__ = ( "_mmap_name", - "_rf2_data", + "_buffer_data", "_mmap_instance", "_buffer_sharing", "update", "data", ) - def __init__(self, mmap_name: str, rf2_data: object) -> None: + def __init__(self, mmap_name: str, buffer_data: object) -> None: """Initialize memory map setting Args: mmap_name: mmap filename, ex. $rFactor2SMMP_Scoring$. - rf2_data: rF2 data class defined in rF2data, ex. rF2data.rF2Scoring. + buffer_data: buffer data class, ex. rF2data.rF2Scoring. """ self._mmap_name = mmap_name - self._rf2_data = rf2_data + self._buffer_data = buffer_data self._mmap_instance = None self._buffer_sharing = False self.update = None @@ -98,14 +99,14 @@ def create(self, access_mode: int = 0, rf2_pid: str = "") -> None: """ self._mmap_instance = platform_mmap( name=self._mmap_name, - size=ctypes.sizeof(self._rf2_data), + size=ctypes.sizeof(self._buffer_data), pid=rf2_pid ) - self.__buffer_copy(True) if access_mode: self.update = self.__buffer_share else: self.update = self.__buffer_copy + self.update(True) mode = "Direct" if access_mode else "Copy" logger.info("sharedmemory: ACTIVE: %s (%s Access)", self._mmap_name, mode) @@ -114,20 +115,20 @@ def close(self) -> None: Create a final accessible mmap data copy before closing mmap instance. """ - self.__buffer_copy(True) + self.data = copy(self.data) self._buffer_sharing = False try: self._mmap_instance.close() logger.info("sharedmemory: CLOSED: %s", self._mmap_name) except BufferError: - logger.error("sharedmemory: buffer error while closing mmap") + logger.error("sharedmemory: buffer error while closing %s", self._mmap_name) self.update = None # unassign update method (for proper garbage collection) - def __buffer_share(self) -> None: + def __buffer_share(self, _=None) -> None: """Share buffer direct access, may result desync""" if not self._buffer_sharing: self._buffer_sharing = True - self.data = self._rf2_data.from_buffer(self._mmap_instance) + self.data = self._buffer_data.from_buffer(self._mmap_instance) def __buffer_copy(self, skip_check: bool = False) -> None: """Copy buffer access, check version before assign new data copy @@ -135,7 +136,7 @@ def __buffer_copy(self, skip_check: bool = False) -> None: Args: skip_check: skip data version check. """ - temp = self._rf2_data.from_buffer_copy(self._mmap_instance) + temp = self._buffer_data.from_buffer_copy(self._mmap_instance) if temp.mVersionUpdateEnd == temp.mVersionUpdateBegin or skip_check: self.data = temp @@ -151,10 +152,10 @@ class MMapDataSet: ) def __init__(self) -> None: - self.scor = RF2MMap("$rFactor2SMMP_Scoring$", rF2data.rF2Scoring) - self.tele = RF2MMap("$rFactor2SMMP_Telemetry$", rF2data.rF2Telemetry) - self.ext = RF2MMap("$rFactor2SMMP_Extended$", rF2data.rF2Extended) - self.ffb = RF2MMap("$rFactor2SMMP_ForceFeedback$", rF2data.rF2ForceFeedback) + self.scor = MMapControl(rF2data.rFactor2Constants.MM_SCORING_FILE_NAME, rF2data.rF2Scoring) + self.tele = MMapControl(rF2data.rFactor2Constants.MM_TELEMETRY_FILE_NAME, rF2data.rF2Telemetry) + self.ext = MMapControl(rF2data.rFactor2Constants.MM_EXTENDED_FILE_NAME, rF2data.rF2Extended) + self.ffb = MMapControl(rF2data.rFactor2Constants.MM_FORCE_FEEDBACK_FILE_NAME, rF2data.rF2ForceFeedback) def __del__(self): logger.info("sharedmemory: GC: MMapDataSet") @@ -182,8 +183,8 @@ def update_mmap(self) -> None: """Update mmap data""" self.scor.update() self.tele.update() - self.ext.update() - self.ffb.update() + #self.ext.update() + #self.ffb.update() class SyncData: @@ -306,6 +307,9 @@ def stop(self) -> None: self._event.set() self._updating = False self._update_thread.join() + # Make final copy before close, otherwise mmap won't close if using direct access + self.player_scor = copy(self.player_scor) + self.player_tele = copy(self.player_tele) self.dataset.close_mmap() else: logger.warning("sharedmemory: UPDATING: already stopped") From a975c6d2ca06c87c3f335ed61ebf501bdba3f175 Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 9 Jan 2025 13:20:57 +0800 Subject: [PATCH 86/93] add missing rF2PluginControl & rF2RulesControl from latest rF2data.cs & constants, use enum.Flag for SubscribedBuffer, code reformat & cleanup --- rF2data.py | 1386 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 819 insertions(+), 567 deletions(-) diff --git a/rF2data.py b/rF2data.py index f5fa58a..23bf22d 100644 --- a/rF2data.py +++ b/rF2data.py @@ -4,729 +4,981 @@ """ # pylint: disable=C,R,W -from enum import Enum +from enum import Enum, auto, Flag import ctypes import mmap + class rFactor2Constants: - MAX_MAPPED_VEHICLES = 128 - MAX_MAPPED_IDS = 512 - MAX_RULES_INSTRUCTION_MSG_LEN = 96 - MAX_STATUS_MSG_LEN = 128 - MAX_HWCONTROL_NAME_LEN = 96 + MM_TELEMETRY_FILE_NAME: str = "$rFactor2SMMP_Telemetry$" + MM_SCORING_FILE_NAME: str = "$rFactor2SMMP_Scoring$" + MM_RULES_FILE_NAME: str = "$rFactor2SMMP_Rules$" + MM_FORCE_FEEDBACK_FILE_NAME: str = "$rFactor2SMMP_ForceFeedback$" + MM_GRAPHICS_FILE_NAME: str = "$rFactor2SMMP_Graphics$" + MM_PITINFO_FILE_NAME: str = "$rFactor2SMMP_PitInfo$" + MM_WEATHER_FILE_NAME: str = "$rFactor2SMMP_Weather$" + MM_EXTENDED_FILE_NAME: str = "$rFactor2SMMP_Extended$" + MM_HWCONTROL_FILE_NAME: str = "$rFactor2SMMP_HWControl$" + MM_HWCONTROL_LAYOUT_VERSION: int = 1 -""" + MM_WEATHER_CONTROL_FILE_NAME: str = "$rFactor2SMMP_WeatherControl$" + MM_WEATHER_CONTROL_LAYOUT_VERSION: int = 1 + + MM_RULES_CONTROL_FILE_NAME: str = "$rFactor2SMMP_RulesControl$" + MM_RULES_CONTROL_LAYOUT_VERSION: int = 1 + + MM_PLUGIN_CONTROL_FILE_NAME: str = "$rFactor2SMMP_PluginControl$" + MM_PLUGIN_CONTROL_LAYOUT_VERSION: int = 1 + + MAX_MAPPED_VEHICLES: int = 128 + MAX_MAPPED_IDS: int = 512 + MAX_STATUS_MSG_LEN: int = 128 + MAX_RULES_INSTRUCTION_MSG_LEN: int = 96 + MAX_HWCONTROL_NAME_LEN: int = 96 -#untranslated /* + RFACTOR2_PROCESS_NAME: str = "rFactor2" + RFACTOR2_DEVMODE_PROCESS_NAME: str = "rFactor2 Mod Mode" + RFACTOR2_DEDICATED_PROCESS_NAME: str = "rFactor2 Dedicated" + + +""" +# untranslated /* rF2 internal state mapping structures. Allows access to native C++ structs from C -#untranslated Must be kept in sync with Include\rF2State.h. -#untranslated See: MainForm.MainUpdate for sample on how to marshall from native in memory struct. -#untranslated Author: The Iron Wolf (vleonavicius@hotmail.com) -#untranslated Website: thecrewchief.org -#untranslated */ -#untranslated using Newtonsoft.Json; -#untranslated using System; -#untranslated using System.Runtime.InteropServices; -#untranslated using System.Xml.Serialization; -#untranslated namespace rF2SharedMemory +# untranslated Must be kept in sync with Include\rF2State.h. +# untranslated See: MainForm.MainUpdate for sample on how to marshall from native in memory struct. +# untranslated Author: The Iron Wolf (vleonavicius@hotmail.com) +# untranslated Website: thecrewchief.org +# untranslated */ +# untranslated using Newtonsoft.Json; +# untranslated using System; +# untranslated using System.Runtime.InteropServices; +# untranslated using System.Xml.Serialization; +# untranslated namespace rF2SharedMemory class rFactor2Constants - const string MM_TELEMETRY_FILE_NAME = "$rFactor2SMMP_Telemetry$"; - const string MM_SCORING_FILE_NAME = "$rFactor2SMMP_Scoring$"; - const string MM_RULES_FILE_NAME = "$rFactor2SMMP_Rules$"; - const string MM_FORCE_FEEDBACK_FILE_NAME = "$rFactor2SMMP_ForceFeedback$"; - const string MM_GRAPHICS_FILE_NAME = "$rFactor2SMMP_Graphics$"; - const string MM_PITINFO_FILE_NAME = "$rFactor2SMMP_PitInfo$"; - const string MM_WEATHER_FILE_NAME = "$rFactor2SMMP_Weather$"; - const string MM_EXTENDED_FILE_NAME = "$rFactor2SMMP_Extended$"; - const string MM_HWCONTROL_FILE_NAME = "$rFactor2SMMP_HWControl$"; - const int MM_HWCONTROL_LAYOUT_VERSION = 1; - const string MM_WEATHER_CONTROL_FILE_NAME = "$rFactor2SMMP_WeatherControl$"; - const int MM_WEATHER_CONTROL_LAYOUT_VERSION = 1; - const int MAX_MAPPED_VEHICLES = 128; - const int MAX_MAPPED_IDS = 512; - const int MAX_STATUS_MSG_LEN = 128; - const int MAX_RULES_INSTRUCTION_MSG_LEN = 96; - const int MAX_HWCONTROL_NAME_LEN = 96; - const string RFACTOR2_PROCESS_NAME = "rFactor2"; - const byte RowX = 0; - const byte RowY = 1; - const byte RowZ = 2; + const byte RowX = 0; + const byte RowY = 1; + const byte RowZ = 2; """ + + +class SubscribedBuffer(Flag): + """Subscribed buffer flag""" + + Telemetry = 1 + Scoring = 2 + Rules = 4 + MultiRules = 8 + ForceFeedback = 16 + Graphics = 32 + PitInfo = 64 + Weather = 128 + All = 255 + + class rF2GamePhase(Enum): - Garage = 0 - WarmUp = 1 - GridWalk = 2 - Formation = 3 - Countdown = 4 - GreenFlag = 5 - FullCourseYellow = 6 - SessionStopped = 7 - SessionOver = 8 - PausedOrHeartbeat = 9 + """ + Game phase states + + 0=Before session has begun, + 1=Reconnaissance laps (race only), + 2=Grid walk-through (race only), + 3=Formation lap (race only), + 4=Starting-light countdown has begun (race only), + 5=Green flag, + 6=Full course yellow / safety car, + 7=Session stopped, + 8=Session over, + 9=Paused (tag.2015.09.14 - this is new, and indicates that this is a heartbeat call to the plugin) + """ + + Garage = 0 + WarmUp = 1 + GridWalk = 2 + Formation = 3 + Countdown = 4 + GreenFlag = 5 + FullCourseYellow = 6 + SessionStopped = 7 + SessionOver = 8 + PausedOrHeartbeat = 9 + class rF2YellowFlagState(Enum): - Invalid = -1 - NoFlag = 0 - Pending = 1 - PitClosed = 2 - PitLeadLap = 3 - PitOpen = 4 - LastLap = 5 - Resume = 6 - RaceHalt = 7 + """ + Yellow flag states (applies to full-course only) + + -1=Invalid, + 0=None, + 1=Pending, + 2=Pits closed, + 3=Pit lead lap, + 4=Pits open, + 5=Last lap, + 6=Resume, + 7=Race halt (not currently used), + """ + + Invalid = -1 + NoFlag = 0 + Pending = 1 + PitClosed = 2 + PitLeadLap = 3 + PitOpen = 4 + LastLap = 5 + Resume = 6 + RaceHalt = 7 + class rF2SurfaceType(Enum): - Dry = 0 - Wet = 1 - Grass = 2 - Dirt = 3 - Gravel = 4 - Kerb = 5 - Special = 6 + """ + Surface type + + 0=dry, + 1=wet, + 2=grass, + 3=dirt, + 4=gravel, + 5=rumblestrip, + 6=special + """ + + Dry = 0 + Wet = 1 + Grass = 2 + Dirt = 3 + Gravel = 4 + Kerb = 5 + Special = 6 + class rF2Sector(Enum): - Sector3 = 0 - Sector1 = 1 - Sector2 = 2 + """ + Sector index + + 0=sector3, + 1=sector1, + 2=sector2 (don't ask why) + """ + + Sector3 = 0 + Sector1 = 1 + Sector2 = 2 + class rF2FinishStatus(Enum): - _None = 0 - Finished = 1 - Dnf = 2 - Dq = 3 + """Finish status + + 0=none, + 1=finished, + 2=dnf, + 3=dq + """ + + _None = 0 + Finished = 1 + Dnf = 2 + Dq = 3 + class rF2Control(Enum): - Nobody = -1 - Player = 0 - AI = 1 - Remote = 2 - Replay = 3 + """ + Who's in control + + -1=nobody (shouldn't get this), + 0=local player, + 1=local AI, + 2=remote, + 3=replay (shouldn't get this) + """ + + Nobody = -1 + Player = 0 + AI = 1 + Remote = 2 + Replay = 3 + class rF2WheelIndex(Enum): - FrontLeft = 0 - FrontRight = 1 - RearLeft = 2 - RearRight = 3 + """Wheel index + + front left=0, + front right=1, + rear left=2, + rear right=3 + """ + + FrontLeft = 0 + FrontRight = 1 + RearLeft = 2 + RearRight = 3 + class rF2PitState(Enum): - _None = 0 - Request = 1 - Entering = 2 - Stopped = 3 - Exiting = 4 + """Pit state + + 0=none, + 1=request, + 2=entering, + 3=stopped, + 4=exiting + """ + + _None = 0 + Request = 1 + Entering = 2 + Stopped = 3 + Exiting = 4 + class rF2PrimaryFlag(Enum): - Green = 0 - Blue = 6 + """ + Primary flag being shown to vehicle + + 0=green, + 6=blue + """ + + Green = 0 + Blue = 6 + class rF2CountLapFlag(Enum): - DoNotCountLap = 0 - CountLapButNotTime = 1 - CountLapAndTime = 2 + """Count lap flag + + 0=do not count lap or time, + 1=count lap but not time, + 2=count lap and time + """ + + DoNotCountLap = 0 + CountLapButNotTime = 1 + CountLapAndTime = 2 + class rF2RearFlapLegalStatus(Enum): - Disallowed = 0 - DetectedButNotAllowedYet = 1 - Alllowed = 2 + """ + Rear flap (DRS) status + + 0=disallowed, + 1=criteria detected but not allowed quite yet, + 2=allowed + """ + + Disallowed = 0 + DetectedButNotAllowedYet = 1 + Alllowed = 2 + class rF2IgnitionStarterStatus(Enum): - Off = 0 - Ignition = 1 - IgnitionAndStarter = 2 + """ + Ignition starter status + + 0=off, + 1=ignition, + 2=ignition+starter + """ + + Off = 0 + Ignition = 1 + IgnitionAndStarter = 2 + class rF2SafetyCarInstruction(Enum): - NoChange = 0 - GoActive = 1 - HeadForPits = 2 + """Safety car instruction + + 0=no change, + 1=go active, + 2=head for pits + """ + + NoChange = 0 + GoActive = 1 + HeadForPits = 2 -#untranslated namespace rFactor2Data -#untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] +class rF2TrackRulesCommand(Enum): + AddFromTrack = 0 + AddFromPit = auto() # exited pit during full-course yellow + AddFromUndq = auto() # during a full-course yellow, the admin reversed a disqualification + RemoveToPit = auto() # entered pit during full-course yellow + RemoveToDnf = auto() # vehicle DNF"d during full-course yellow + RemoveToDq = auto() # vehicle DQ"d during full-course yellow + RemoveToUnloaded = auto() # vehicle unloaded (possibly kicked out or banned) during full-course yellow + MoveToBack = auto() # misbehavior during full-course yellow, resulting in the penalty of being moved to the back of their current line + LongestTime = auto() # misbehavior during full-course yellow, resulting in the penalty of being moved to the back of the longest line + Maximum = auto() # should be last + + +class rF2TrackRulesColumn(Enum): + LeftLane = 0 + MidLefLane = 1 # mid-left + MiddleLane = 2 # middle + MidrRghtLane = 3 # mid-right + RightLane = 4 # right (outside) + MaxLanes = 5 # should be after the valid static lane choices + Invalid = MaxLanes + FreeChoice = 6 # free choice (dynamically chosen by driver) + Pending = 7 # depends on another participant"s free choice (dynamically set after another driver chooses) + Maximum = 8 # should be last + + +class rF2TrackRulesStage(Enum): + FormationInit = 0 + FormationUpdate = 1 # update of the formation lap + Normal = 2 # normal (non-yellow) update + CautionInit = 3 # initialization of a full-course yellow + CautionUpdate = 4 # update of a full-course yellow + Maximum = 5 # should be last + + +# untranslated namespace rFactor2Data +# untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] class rF2Vec3(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('x', ctypes.c_double), - ('y', ctypes.c_double), - ('z', ctypes.c_double), -] + ("x", ctypes.c_double), + ("y", ctypes.c_double), + ("z", ctypes.c_double), + ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Wheel(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mSuspensionDeflection', ctypes.c_double), # meters - ('mRideHeight', ctypes.c_double), # meters - ('mSuspForce', ctypes.c_double), # pushrod load in Newtons - ('mBrakeTemp', ctypes.c_double), # Celsius - ('mBrakePressure', ctypes.c_double), # currently 0.0-1.0, depending on driver input and brake balance; will convert to true brake pressure (kPa) in future - ('mRotation', ctypes.c_double), # radians/sec - ('mLateralPatchVel', ctypes.c_double), # lateral velocity at contact patch - ('mLongitudinalPatchVel', ctypes.c_double), # longitudinal velocity at contact patch - ('mLateralGroundVel', ctypes.c_double), # lateral velocity at contact patch - ('mLongitudinalGroundVel', ctypes.c_double), # longitudinal velocity at contact patch - ('mCamber', ctypes.c_double), # radians (positive is left for left-side wheels, right for right-side wheels) - ('mLateralForce', ctypes.c_double), # Newtons - ('mLongitudinalForce', ctypes.c_double), # Newtons - ('mTireLoad', ctypes.c_double), # Newtons - ('mGripFract', ctypes.c_double), # an approximation of what fraction of the contact patch is sliding - ('mPressure', ctypes.c_double), # kPa (tire pressure) - ('mTemperature', ctypes.c_double*3), # Kelvin (subtract 273.15 to get Celsius), left/center/right (not to be confused with inside/center/outside!) - ('mWear', ctypes.c_double), # wear (0.0-1.0, fraction of maximum) ... this is not necessarily proportional with grip loss - ('mTerrainName', ctypes.c_char*16), # the material prefixes from the TDF file - ('mSurfaceType', ctypes.c_ubyte), # 0=dry, 1=wet, 2=grass, 3=dirt, 4=gravel, 5=rumblestrip, 6 = special - ('mFlat', ctypes.c_bool), # whether tire is flat - ('mDetached', ctypes.c_bool), # whether wheel is detached - ('mStaticUndeflectedRadius', ctypes.c_ubyte), # tire radius in centimeters - ('mVerticalTireDeflection', ctypes.c_double), # how much is tire deflected from its (speed-sensitive) radius - ('mWheelYLocation', ctypes.c_double), # wheel's y location relative to vehicle y location - ('mToe', ctypes.c_double), # current toe angle w.r.t. the vehicle - ('mTireCarcassTemperature', ctypes.c_double), # rough average of temperature samples from carcass (Kelvin) - ('mTireInnerLayerTemperature', ctypes.c_double*3), # rough average of temperature samples from innermost layer of rubber (before carcass) (Kelvin) - ('mExpansion', ctypes.c_ubyte*24), # for future use + ("mSuspensionDeflection", ctypes.c_double), # meters + ("mRideHeight", ctypes.c_double), # meters + ("mSuspForce", ctypes.c_double), # pushrod load in Newtons + ("mBrakeTemp", ctypes.c_double), # Celsius + ("mBrakePressure", ctypes.c_double), # currently 0.0-1.0, depending on driver input and brake balance; will convert to true brake pressure (kPa) in future + ("mRotation", ctypes.c_double), # radians/sec + ("mLateralPatchVel", ctypes.c_double), # lateral velocity at contact patch + ("mLongitudinalPatchVel", ctypes.c_double), # longitudinal velocity at contact patch + ("mLateralGroundVel", ctypes.c_double), # lateral velocity at contact patch + ("mLongitudinalGroundVel", ctypes.c_double), # longitudinal velocity at contact patch + ("mCamber", ctypes.c_double), # radians (positive is left for left-side wheels, right for right-side wheels) + ("mLateralForce", ctypes.c_double), # Newtons + ("mLongitudinalForce", ctypes.c_double), # Newtons + ("mTireLoad", ctypes.c_double), # Newtons + ("mGripFract", ctypes.c_double), # an approximation of what fraction of the contact patch is sliding + ("mPressure", ctypes.c_double), # kPa (tire pressure) + ("mTemperature", ctypes.c_double*3), # Kelvin (subtract 273.15 to get Celsius), left/center/right (not to be confused with inside/center/outside!) + ("mWear", ctypes.c_double), # wear (0.0-1.0, fraction of maximum) ... this is not necessarily proportional with grip loss + ("mTerrainName", ctypes.c_char*16), # the material prefixes from the TDF file + ("mSurfaceType", ctypes.c_ubyte), # 0=dry, 1=wet, 2=grass, 3=dirt, 4=gravel, 5=rumblestrip, 6 = special + ("mFlat", ctypes.c_bool), # whether tire is flat + ("mDetached", ctypes.c_bool), # whether wheel is detached + ("mStaticUndeflectedRadius", ctypes.c_ubyte), # tire radius in centimeters + ("mVerticalTireDeflection", ctypes.c_double), # how much is tire deflected from its (speed-sensitive) radius + ("mWheelYLocation", ctypes.c_double), # wheel"s y location relative to vehicle y location + ("mToe", ctypes.c_double), # current toe angle w.r.t. the vehicle + ("mTireCarcassTemperature", ctypes.c_double), # rough average of temperature samples from carcass (Kelvin) + ("mTireInnerLayerTemperature", ctypes.c_double*3), # rough average of temperature samples from innermost layer of rubber (before carcass) (Kelvin) + ("mExpansion", ctypes.c_ubyte*24), # for future use ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2VehicleTelemetry(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mID', ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) - ('mDeltaTime', ctypes.c_double), # time since last update (seconds) - ('mElapsedTime', ctypes.c_double), # game session time - ('mLapNumber', ctypes.c_int), # current lap number - ('mLapStartET', ctypes.c_double), # time this lap was started - ('mVehicleName', ctypes.c_char*64), # current vehicle name - ('mTrackName', ctypes.c_char*64), # current track name - ('mPos', rF2Vec3), # world position in meters - ('mLocalVel', rF2Vec3), # velocity (meters/sec) in local vehicle coordinates - ('mLocalAccel', rF2Vec3), # acceleration (meters/sec^2) in local vehicle coordinates - ('mOri', rF2Vec3*3), # rows of orientation matrix (use TelemQuat conversions if desired), also converts local - ('mLocalRot', rF2Vec3), # rotation (radians/sec) in local vehicle coordinates - ('mLocalRotAccel', rF2Vec3), # rotational acceleration (radians/sec^2) in local vehicle coordinates - ('mGear', ctypes.c_int), # -1=reverse, 0=neutral, 1+ = forward gears - ('mEngineRPM', ctypes.c_double), # engine RPM - ('mEngineWaterTemp', ctypes.c_double), # Celsius - ('mEngineOilTemp', ctypes.c_double), # Celsius - ('mClutchRPM', ctypes.c_double), # clutch RPM - ('mUnfilteredThrottle', ctypes.c_double), # ranges 0.0-1.0 - ('mUnfilteredBrake', ctypes.c_double), # ranges 0.0-1.0 - ('mUnfilteredSteering', ctypes.c_double), # ranges -1.0-1.0 (left to right) - ('mUnfilteredClutch', ctypes.c_double), # ranges 0.0-1.0 - ('mFilteredThrottle', ctypes.c_double), # ranges 0.0-1.0 - ('mFilteredBrake', ctypes.c_double), # ranges 0.0-1.0 - ('mFilteredSteering', ctypes.c_double), # ranges -1.0-1.0 (left to right) - ('mFilteredClutch', ctypes.c_double), # ranges 0.0-1.0 - ('mSteeringShaftTorque', ctypes.c_double), # torque around steering shaft (used to be mSteeringArmForce, but that is not necessarily accurate for feedback purposes) - ('mFront3rdDeflection', ctypes.c_double), # deflection at front 3rd spring - ('mRear3rdDeflection', ctypes.c_double), # deflection at rear 3rd spring - ('mFrontWingHeight', ctypes.c_double), # front wing height - ('mFrontRideHeight', ctypes.c_double), # front ride height - ('mRearRideHeight', ctypes.c_double), # rear ride height - ('mDrag', ctypes.c_double), # drag - ('mFrontDownforce', ctypes.c_double), # front downforce - ('mRearDownforce', ctypes.c_double), # rear downforce - ('mFuel', ctypes.c_double), # amount of fuel (liters) - ('mEngineMaxRPM', ctypes.c_double), # rev limit - ('mScheduledStops', ctypes.c_ubyte), # number of scheduled pitstops - ('mOverheating', ctypes.c_bool), # whether overheating icon is shown - ('mDetached', ctypes.c_bool), # whether any parts (besides wheels) have been detached - ('mHeadlights', ctypes.c_bool), # whether headlights are on - ('mDentSeverity', ctypes.c_ubyte*8), # dent severity at 8 locations around the car (0=none, 1=some, 2=more) - ('mLastImpactET', ctypes.c_double), # time of last impact - ('mLastImpactMagnitude', ctypes.c_double), # magnitude of last impact - ('mLastImpactPos', rF2Vec3), # location of last impact - ('mEngineTorque', ctypes.c_double), # current engine torque (including additive torque) (used to be mEngineTq, but there's little reason to abbreviate it) - ('mCurrentSector', ctypes.c_int), # the current sector (zero-based) with the pitlane stored in the sign bit (example: entering pits from third sector gives 0x80000002) - ('mSpeedLimiter', ctypes.c_ubyte), # whether speed limiter is on - ('mMaxGears', ctypes.c_ubyte), # maximum forward gears - ('mFrontTireCompoundIndex', ctypes.c_ubyte), # index within brand - ('mRearTireCompoundIndex', ctypes.c_ubyte), # index within brand - ('mFuelCapacity', ctypes.c_double), # capacity in liters - ('mFrontFlapActivated', ctypes.c_ubyte), # whether front flap is activated - ('mRearFlapActivated', ctypes.c_ubyte), # whether rear flap is activated - ('mRearFlapLegalStatus', ctypes.c_ubyte), # 0=disallowed, 1=criteria detected but not allowed quite yet, 2 = allowed - ('mIgnitionStarter', ctypes.c_ubyte), # 0=off 1=ignition 2 = ignition+starter - ('mFrontTireCompoundName', ctypes.c_char*18), # name of front tire compound - ('mRearTireCompoundName', ctypes.c_char*18), # name of rear tire compound - ('mSpeedLimiterAvailable', ctypes.c_ubyte), # whether speed limiter is available - ('mAntiStallActivated', ctypes.c_ubyte), # whether (hard) anti-stall is activated - ('mUnused', ctypes.c_ubyte*2), # - ('mVisualSteeringWheelRange', ctypes.c_float), # the *visual* steering wheel range - ('mRearBrakeBias', ctypes.c_double), # fraction of brakes on rear - ('mTurboBoostPressure', ctypes.c_double), # current turbo boost pressure if available - ('mPhysicsToGraphicsOffset', ctypes.c_float*3), # offset from static CG to graphical center - ('mPhysicalSteeringWheelRange', ctypes.c_float), # the *physical* steering wheel range - ('mDeltaBest', ctypes.c_double), # (omitted in error by S397) - ('mBatteryChargeFraction', ctypes.c_double), # Battery charge as fraction [0.0-1.0] - ('mElectricBoostMotorTorque', ctypes.c_double), # current torque of boost motor (can be negative when in regenerating mode) - ('mElectricBoostMotorRPM', ctypes.c_double), # current rpm of boost motor - ('mElectricBoostMotorTemperature', ctypes.c_double), # current temperature of boost motor - ('mElectricBoostWaterTemperature', ctypes.c_double), # current water temperature of boost motor cooler if present (0 otherwise) - ('mElectricBoostMotorState', ctypes.c_ubyte), # 0=unavailable 1=inactive, 2=propulsion, 3=regeneration - ('mExpansion', ctypes.c_ubyte*103), # for future use (note that the slot ID has been moved to mID above) - ('mWheels', rF2Wheel*4), # wheel info (front left, front right, rear left, rear right) + ("mID", ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) + ("mDeltaTime", ctypes.c_double), # time since last update (seconds) + ("mElapsedTime", ctypes.c_double), # game session time + ("mLapNumber", ctypes.c_int), # current lap number + ("mLapStartET", ctypes.c_double), # time this lap was started + ("mVehicleName", ctypes.c_char*64), # current vehicle name + ("mTrackName", ctypes.c_char*64), # current track name + ("mPos", rF2Vec3), # world position in meters + ("mLocalVel", rF2Vec3), # velocity (meters/sec) in local vehicle coordinates + ("mLocalAccel", rF2Vec3), # acceleration (meters/sec^2) in local vehicle coordinates + ("mOri", rF2Vec3*3), # rows of orientation matrix (use TelemQuat conversions if desired), also converts local + ("mLocalRot", rF2Vec3), # rotation (radians/sec) in local vehicle coordinates + ("mLocalRotAccel", rF2Vec3), # rotational acceleration (radians/sec^2) in local vehicle coordinates + ("mGear", ctypes.c_int), # -1=reverse, 0=neutral, 1+ = forward gears + ("mEngineRPM", ctypes.c_double), # engine RPM + ("mEngineWaterTemp", ctypes.c_double), # Celsius + ("mEngineOilTemp", ctypes.c_double), # Celsius + ("mClutchRPM", ctypes.c_double), # clutch RPM + ("mUnfilteredThrottle", ctypes.c_double), # ranges 0.0-1.0 + ("mUnfilteredBrake", ctypes.c_double), # ranges 0.0-1.0 + ("mUnfilteredSteering", ctypes.c_double), # ranges -1.0-1.0 (left to right) + ("mUnfilteredClutch", ctypes.c_double), # ranges 0.0-1.0 + ("mFilteredThrottle", ctypes.c_double), # ranges 0.0-1.0 + ("mFilteredBrake", ctypes.c_double), # ranges 0.0-1.0 + ("mFilteredSteering", ctypes.c_double), # ranges -1.0-1.0 (left to right) + ("mFilteredClutch", ctypes.c_double), # ranges 0.0-1.0 + ("mSteeringShaftTorque", ctypes.c_double), # torque around steering shaft (used to be mSteeringArmForce, but that is not necessarily accurate for feedback purposes) + ("mFront3rdDeflection", ctypes.c_double), # deflection at front 3rd spring + ("mRear3rdDeflection", ctypes.c_double), # deflection at rear 3rd spring + ("mFrontWingHeight", ctypes.c_double), # front wing height + ("mFrontRideHeight", ctypes.c_double), # front ride height + ("mRearRideHeight", ctypes.c_double), # rear ride height + ("mDrag", ctypes.c_double), # drag + ("mFrontDownforce", ctypes.c_double), # front downforce + ("mRearDownforce", ctypes.c_double), # rear downforce + ("mFuel", ctypes.c_double), # amount of fuel (liters) + ("mEngineMaxRPM", ctypes.c_double), # rev limit + ("mScheduledStops", ctypes.c_ubyte), # number of scheduled pitstops + ("mOverheating", ctypes.c_bool), # whether overheating icon is shown + ("mDetached", ctypes.c_bool), # whether any parts (besides wheels) have been detached + ("mHeadlights", ctypes.c_bool), # whether headlights are on + ("mDentSeverity", ctypes.c_ubyte*8), # dent severity at 8 locations around the car (0=none, 1=some, 2=more) + ("mLastImpactET", ctypes.c_double), # time of last impact + ("mLastImpactMagnitude", ctypes.c_double), # magnitude of last impact + ("mLastImpactPos", rF2Vec3), # location of last impact + ("mEngineTorque", ctypes.c_double), # current engine torque (including additive torque) (used to be mEngineTq, but there"s little reason to abbreviate it) + ("mCurrentSector", ctypes.c_int), # the current sector (zero-based) with the pitlane stored in the sign bit (example: entering pits from third sector gives 0x80000002) + ("mSpeedLimiter", ctypes.c_ubyte), # whether speed limiter is on + ("mMaxGears", ctypes.c_ubyte), # maximum forward gears + ("mFrontTireCompoundIndex", ctypes.c_ubyte), # index within brand + ("mRearTireCompoundIndex", ctypes.c_ubyte), # index within brand + ("mFuelCapacity", ctypes.c_double), # capacity in liters + ("mFrontFlapActivated", ctypes.c_ubyte), # whether front flap is activated + ("mRearFlapActivated", ctypes.c_ubyte), # whether rear flap is activated + ("mRearFlapLegalStatus", ctypes.c_ubyte), # 0=disallowed, 1=criteria detected but not allowed quite yet, 2 = allowed + ("mIgnitionStarter", ctypes.c_ubyte), # 0=off 1=ignition 2 = ignition+starter + ("mFrontTireCompoundName", ctypes.c_char*18), # name of front tire compound + ("mRearTireCompoundName", ctypes.c_char*18), # name of rear tire compound + ("mSpeedLimiterAvailable", ctypes.c_ubyte), # whether speed limiter is available + ("mAntiStallActivated", ctypes.c_ubyte), # whether (hard) anti-stall is activated + ("mUnused", ctypes.c_ubyte*2), # + ("mVisualSteeringWheelRange", ctypes.c_float), # the *visual* steering wheel range + ("mRearBrakeBias", ctypes.c_double), # fraction of brakes on rear + ("mTurboBoostPressure", ctypes.c_double), # current turbo boost pressure if available + ("mPhysicsToGraphicsOffset", ctypes.c_float*3), # offset from static CG to graphical center + ("mPhysicalSteeringWheelRange", ctypes.c_float), # the *physical* steering wheel range + ("mDeltaBest", ctypes.c_double), # (omitted in error by S397) + ("mBatteryChargeFraction", ctypes.c_double), # Battery charge as fraction [0.0-1.0] + ("mElectricBoostMotorTorque", ctypes.c_double), # current torque of boost motor (can be negative when in regenerating mode) + ("mElectricBoostMotorRPM", ctypes.c_double), # current rpm of boost motor + ("mElectricBoostMotorTemperature", ctypes.c_double), # current temperature of boost motor + ("mElectricBoostWaterTemperature", ctypes.c_double), # current water temperature of boost motor cooler if present (0 otherwise) + ("mElectricBoostMotorState", ctypes.c_ubyte), # 0=unavailable 1=inactive, 2=propulsion, 3=regeneration + ("mExpansion", ctypes.c_ubyte*103), # for future use (note that the slot ID has been moved to mID above) + ("mWheels", rF2Wheel*4), # wheel info (front left, front right, rear left, rear right) ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2ScoringInfo(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mTrackName', ctypes.c_char*64), # current track name - ('mSession', ctypes.c_int), # current session (0=testday 1-4=practice 5-8=qual 9=warmup 10-13 = race) - ('mCurrentET', ctypes.c_double), # current time - ('mEndET', ctypes.c_double), # ending time - ('mMaxLaps', ctypes.c_int), # maximum laps - ('mLapDist', ctypes.c_double), # distance around track - ('pointer1', ctypes.c_ubyte*8), - ('mNumVehicles', ctypes.c_int), # current number of vehicles - ('mGamePhase', ctypes.c_ubyte), - ('mYellowFlagState', ctypes.c_char), - ('mSectorFlag', ctypes.c_ubyte*3), # whether there are any local yellows at the moment in each sector (not sure if sector 0 is first or last, so test) - ('mStartLight', ctypes.c_ubyte), # start light frame (number depends on track) - ('mNumRedLights', ctypes.c_ubyte), # number of red lights in start sequence - ('mInRealtime', ctypes.c_bool), # in realtime as opposed to at the monitor - ('mPlayerName', ctypes.c_char*32), # player name (including possible multiplayer override) - ('mPlrFileName', ctypes.c_char*64), # may be encoded to be a legal filename - ('mDarkCloud', ctypes.c_double), # cloud darkness? 0.0-1.0 - ('mRaining', ctypes.c_double), # raining severity 0.0-1.0 - ('mAmbientTemp', ctypes.c_double), # temperature (Celsius) - ('mTrackTemp', ctypes.c_double), # temperature (Celsius) - ('mWind', rF2Vec3), # wind speed - ('mMinPathWetness', ctypes.c_double), # minimum wetness on main path 0.0-1.0 - ('mMaxPathWetness', ctypes.c_double), # maximum wetness on main path 0.0-1.0 - ('mGameMode', ctypes.c_ubyte), # 1 = server, 2 = client, 3 = server and client - ('mIsPasswordProtected', ctypes.c_bool), # is the server password protected - ('mServerPort', ctypes.c_ushort), # the port of the server (if on a server) - ('mServerPublicIP', ctypes.c_uint), # the public IP address of the server (if on a server) - ('mMaxPlayers', ctypes.c_int), # maximum number of vehicles that can be in the session - ('mServerName', ctypes.c_char*32), # name of the server - ('mStartET', ctypes.c_float), # start time (seconds since midnight) of the event - ('mAvgPathWetness', ctypes.c_double), # average wetness on main path 0.0-1.0 - ('mExpansion', ctypes.c_ubyte*200), - ('pointer2', ctypes.c_ubyte*8), + ("mTrackName", ctypes.c_char*64), # current track name + ("mSession", ctypes.c_int), # current session (0=testday 1-4=practice 5-8=qual 9=warmup 10-13 = race) + ("mCurrentET", ctypes.c_double), # current time + ("mEndET", ctypes.c_double), # ending time + ("mMaxLaps", ctypes.c_int), # maximum laps + ("mLapDist", ctypes.c_double), # distance around track + ("pointer1", ctypes.c_ubyte*8), + ("mNumVehicles", ctypes.c_int), # current number of vehicles + ("mGamePhase", ctypes.c_ubyte), + ("mYellowFlagState", ctypes.c_char), + ("mSectorFlag", ctypes.c_ubyte*3), # whether there are any local yellows at the moment in each sector (not sure if sector 0 is first or last, so test) + ("mStartLight", ctypes.c_ubyte), # start light frame (number depends on track) + ("mNumRedLights", ctypes.c_ubyte), # number of red lights in start sequence + ("mInRealtime", ctypes.c_bool), # in realtime as opposed to at the monitor + ("mPlayerName", ctypes.c_char*32), # player name (including possible multiplayer override) + ("mPlrFileName", ctypes.c_char*64), # may be encoded to be a legal filename + ("mDarkCloud", ctypes.c_double), # cloud darkness? 0.0-1.0 + ("mRaining", ctypes.c_double), # raining severity 0.0-1.0 + ("mAmbientTemp", ctypes.c_double), # temperature (Celsius) + ("mTrackTemp", ctypes.c_double), # temperature (Celsius) + ("mWind", rF2Vec3), # wind speed + ("mMinPathWetness", ctypes.c_double), # minimum wetness on main path 0.0-1.0 + ("mMaxPathWetness", ctypes.c_double), # maximum wetness on main path 0.0-1.0 + ("mGameMode", ctypes.c_ubyte), # 1 = server, 2 = client, 3 = server and client + ("mIsPasswordProtected", ctypes.c_bool), # is the server password protected + ("mServerPort", ctypes.c_ushort), # the port of the server (if on a server) + ("mServerPublicIP", ctypes.c_uint), # the public IP address of the server (if on a server) + ("mMaxPlayers", ctypes.c_int), # maximum number of vehicles that can be in the session + ("mServerName", ctypes.c_char*32), # name of the server + ("mStartET", ctypes.c_float), # start time (seconds since midnight) of the event + ("mAvgPathWetness", ctypes.c_double), # average wetness on main path 0.0-1.0 + ("mExpansion", ctypes.c_ubyte*200), + ("pointer2", ctypes.c_ubyte*8), ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2VehicleScoring(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mID', ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) - ('mDriverName', ctypes.c_char*32), # driver name - ('mVehicleName', ctypes.c_char*64), # vehicle name - ('mTotalLaps', ctypes.c_short), # laps completed - ('mSector', ctypes.c_byte), # 0=sector3, 1=sector1, 2 = sector2 (don't ask why) - ('mFinishStatus', ctypes.c_byte), # 0=none, 1=finished, 2=dnf, 3 = dq - ('mLapDist', ctypes.c_double), # current distance around track - ('mPathLateral', ctypes.c_double), # lateral position with respect to *very approximate* "center" path - ('mTrackEdge', ctypes.c_double), # track edge (w.r.t. "center" path) on same side of track as vehicle - ('mBestSector1', ctypes.c_double), # best sector 1 - ('mBestSector2', ctypes.c_double), # best sector 2 (plus sector 1) - ('mBestLapTime', ctypes.c_double), # best lap time - ('mLastSector1', ctypes.c_double), # last sector 1 - ('mLastSector2', ctypes.c_double), # last sector 2 (plus sector 1) - ('mLastLapTime', ctypes.c_double), # last lap time - ('mCurSector1', ctypes.c_double), # current sector 1 if valid - ('mCurSector2', ctypes.c_double), # current sector 2 (plus sector 1) if valid - ('mNumPitstops', ctypes.c_short), # number of pitstops made - ('mNumPenalties', ctypes.c_short), # number of outstanding penalties - ('mIsPlayer', ctypes.c_bool), # is this the player's vehicle - ('mControl', ctypes.c_byte), # who's in control: -1=nobody (shouldn't get this), 0=local player, 1=local AI, 2=remote, 3 = replay (shouldn't get this) - ('mInPits', ctypes.c_bool), # between pit entrance and pit exit (not always accurate for remote vehicles) - ('mPlace', ctypes.c_ubyte), # 1-based position - ('mVehicleClass', ctypes.c_char*32), # vehicle class - ('mTimeBehindNext', ctypes.c_double), # time behind vehicle in next higher place - ('mLapsBehindNext', ctypes.c_int), # laps behind vehicle in next higher place - ('mTimeBehindLeader', ctypes.c_double), # time behind leader - ('mLapsBehindLeader', ctypes.c_int), # laps behind leader - ('mLapStartET', ctypes.c_double), # time this lap was started - ('mPos', rF2Vec3), # world position in meters - ('mLocalVel', rF2Vec3), # velocity (meters/sec) in local vehicle coordinates - ('mLocalAccel', rF2Vec3), # acceleration (meters/sec^2) in local vehicle coordinates - ('mOri', rF2Vec3*3), # rows of orientation matrix (use TelemQuat conversions if desired), also converts local - ('mLocalRot', rF2Vec3), # rotation (radians/sec) in local vehicle coordinates - ('mLocalRotAccel', rF2Vec3), # rotational acceleration (radians/sec^2) in local vehicle coordinates - ('mHeadlights', ctypes.c_ubyte), # status of headlights - ('mPitState', ctypes.c_ubyte), # 0=none, 1=request, 2=entering, 3=stopped, 4 = exiting - ('mServerScored', ctypes.c_ubyte), # whether this vehicle is being scored by server (could be off in qualifying or racing heats) - ('mIndividualPhase', ctypes.c_ubyte), # game phases (described below) plus 9=after formation, 10=under yellow, 11 = under blue (not used) - ('mQualification', ctypes.c_int), # 1-based, can be -1 when invalid - ('mTimeIntoLap', ctypes.c_double), # estimated time into lap - ('mEstimatedLapTime', ctypes.c_double), # estimated laptime used for 'time behind' and 'time into lap' (note: this may changed based on vehicle and setup!?) - ('mPitGroup', ctypes.c_char*24), # pit group (same as team name unless pit is shared) - ('mFlag', ctypes.c_ubyte), # primary flag being shown to vehicle (currently only 0=green or 6 = blue) - ('mUnderYellow', ctypes.c_bool), # whether this car has taken a full-course caution flag at the start/finish line - ('mCountLapFlag', ctypes.c_ubyte), # 0 = do not count lap or time, 1 = count lap but not time, 2 = count lap and time - ('mInGarageStall', ctypes.c_bool), # appears to be within the correct garage stall - ('mUpgradePack', ctypes.c_ubyte*16), # Coded upgrades - ('mPitLapDist', ctypes.c_float), # location of pit in terms of lap distance - ('mBestLapSector1', ctypes.c_float), # sector 1 time from best lap (not necessarily the best sector 1 time) - ('mBestLapSector2', ctypes.c_float), # sector 2 time from best lap (not necessarily the best sector 2 time) - ('mExpansion', ctypes.c_ubyte*48), # for future use + ("mID", ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) + ("mDriverName", ctypes.c_char*32), # driver name + ("mVehicleName", ctypes.c_char*64), # vehicle name + ("mTotalLaps", ctypes.c_short), # laps completed + ("mSector", ctypes.c_byte), # 0=sector3, 1=sector1, 2 = sector2 (don"t ask why) + ("mFinishStatus", ctypes.c_byte), # 0=none, 1=finished, 2=dnf, 3 = dq + ("mLapDist", ctypes.c_double), # current distance around track + ("mPathLateral", ctypes.c_double), # lateral position with respect to *very approximate* "center" path + ("mTrackEdge", ctypes.c_double), # track edge (w.r.t. "center" path) on same side of track as vehicle + ("mBestSector1", ctypes.c_double), # best sector 1 + ("mBestSector2", ctypes.c_double), # best sector 2 (plus sector 1) + ("mBestLapTime", ctypes.c_double), # best lap time + ("mLastSector1", ctypes.c_double), # last sector 1 + ("mLastSector2", ctypes.c_double), # last sector 2 (plus sector 1) + ("mLastLapTime", ctypes.c_double), # last lap time + ("mCurSector1", ctypes.c_double), # current sector 1 if valid + ("mCurSector2", ctypes.c_double), # current sector 2 (plus sector 1) if valid + ("mNumPitstops", ctypes.c_short), # number of pitstops made + ("mNumPenalties", ctypes.c_short), # number of outstanding penalties + ("mIsPlayer", ctypes.c_bool), # is this the player"s vehicle + ("mControl", ctypes.c_byte), # who"s in control: -1=nobody (shouldn"t get this), 0=local player, 1=local AI, 2=remote, 3 = replay (shouldn"t get this) + ("mInPits", ctypes.c_bool), # between pit entrance and pit exit (not always accurate for remote vehicles) + ("mPlace", ctypes.c_ubyte), # 1-based position + ("mVehicleClass", ctypes.c_char*32), # vehicle class + ("mTimeBehindNext", ctypes.c_double), # time behind vehicle in next higher place + ("mLapsBehindNext", ctypes.c_int), # laps behind vehicle in next higher place + ("mTimeBehindLeader", ctypes.c_double), # time behind leader + ("mLapsBehindLeader", ctypes.c_int), # laps behind leader + ("mLapStartET", ctypes.c_double), # time this lap was started + ("mPos", rF2Vec3), # world position in meters + ("mLocalVel", rF2Vec3), # velocity (meters/sec) in local vehicle coordinates + ("mLocalAccel", rF2Vec3), # acceleration (meters/sec^2) in local vehicle coordinates + ("mOri", rF2Vec3*3), # rows of orientation matrix (use TelemQuat conversions if desired), also converts local + ("mLocalRot", rF2Vec3), # rotation (radians/sec) in local vehicle coordinates + ("mLocalRotAccel", rF2Vec3), # rotational acceleration (radians/sec^2) in local vehicle coordinates + ("mHeadlights", ctypes.c_ubyte), # status of headlights + ("mPitState", ctypes.c_ubyte), # 0=none, 1=request, 2=entering, 3=stopped, 4 = exiting + ("mServerScored", ctypes.c_ubyte), # whether this vehicle is being scored by server (could be off in qualifying or racing heats) + ("mIndividualPhase", ctypes.c_ubyte), # game phases (described below) plus 9=after formation, 10=under yellow, 11 = under blue (not used) + ("mQualification", ctypes.c_int), # 1-based, can be -1 when invalid + ("mTimeIntoLap", ctypes.c_double), # estimated time into lap + ("mEstimatedLapTime", ctypes.c_double), # estimated laptime used for "time behind" and "time into lap" (note: this may changed based on vehicle and setup!?) + ("mPitGroup", ctypes.c_char*24), # pit group (same as team name unless pit is shared) + ("mFlag", ctypes.c_ubyte), # primary flag being shown to vehicle (currently only 0=green or 6 = blue) + ("mUnderYellow", ctypes.c_bool), # whether this car has taken a full-course caution flag at the start/finish line + ("mCountLapFlag", ctypes.c_ubyte), # 0 = do not count lap or time, 1 = count lap but not time, 2 = count lap and time + ("mInGarageStall", ctypes.c_bool), # appears to be within the correct garage stall + ("mUpgradePack", ctypes.c_ubyte*16), # Coded upgrades + ("mPitLapDist", ctypes.c_float), # location of pit in terms of lap distance + ("mBestLapSector1", ctypes.c_float), # sector 1 time from best lap (not necessarily the best sector 1 time) + ("mBestLapSector2", ctypes.c_float), # sector 2 time from best lap (not necessarily the best sector 2 time) + ("mExpansion", ctypes.c_ubyte*48), # for future use ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2PhysicsOptions(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mTractionControl', ctypes.c_ubyte), # 0 (off) - 3 (high) - ('mAntiLockBrakes', ctypes.c_ubyte), # 0 (off) - 2 (high) - ('mStabilityControl', ctypes.c_ubyte), # 0 (off) - 2 (high) - ('mAutoShift', ctypes.c_ubyte), # 0 (off), 1 (upshifts), 2 (downshifts), 3 (all) - ('mAutoClutch', ctypes.c_ubyte), # 0 (off), 1 (on) - ('mInvulnerable', ctypes.c_ubyte), # 0 (off), 1 (on) - ('mOppositeLock', ctypes.c_ubyte), # 0 (off), 1 (on) - ('mSteeringHelp', ctypes.c_ubyte), # 0 (off) - 3 (high) - ('mBrakingHelp', ctypes.c_ubyte), # 0 (off) - 2 (high) - ('mSpinRecovery', ctypes.c_ubyte), # 0 (off), 1 (on) - ('mAutoPit', ctypes.c_ubyte), # 0 (off), 1 (on) - ('mAutoLift', ctypes.c_ubyte), # 0 (off), 1 (on) - ('mAutoBlip', ctypes.c_ubyte), # 0 (off), 1 (on) - ('mFuelMult', ctypes.c_ubyte), # fuel multiplier (0x-7x) - ('mTireMult', ctypes.c_ubyte), # tire wear multiplier (0x-7x) - ('mMechFail', ctypes.c_ubyte), # mechanical failure setting; 0 (off), 1 (normal), 2 (timescaled) - ('mAllowPitcrewPush', ctypes.c_ubyte), # 0 (off), 1 (on) - ('mRepeatShifts', ctypes.c_ubyte), # accidental repeat shift prevention (0-5; see PLR file) - ('mHoldClutch', ctypes.c_ubyte), # for auto-shifters at start of race: 0 (off), 1 (on) - ('mAutoReverse', ctypes.c_ubyte), # 0 (off), 1 (on) - ('mAlternateNeutral', ctypes.c_ubyte), # Whether shifting up and down simultaneously equals neutral - ('mAIControl', ctypes.c_ubyte), # Whether player vehicle is currently under AI control - ('mUnused1', ctypes.c_ubyte), # - ('mUnused2', ctypes.c_ubyte), # - ('mManualShiftOverrideTime', ctypes.c_float), # time before auto-shifting can resume after recent manual shift - ('mAutoShiftOverrideTime', ctypes.c_float), # time before manual shifting can resume after recent auto shift - ('mSpeedSensitiveSteering', ctypes.c_float), # 0.0 (off) - 1.0 - ('mSteerRatioSpeed', ctypes.c_float), # speed (m/s) under which lock gets expanded to full + ("mTractionControl", ctypes.c_ubyte), # 0 (off) - 3 (high) + ("mAntiLockBrakes", ctypes.c_ubyte), # 0 (off) - 2 (high) + ("mStabilityControl", ctypes.c_ubyte), # 0 (off) - 2 (high) + ("mAutoShift", ctypes.c_ubyte), # 0 (off), 1 (upshifts), 2 (downshifts), 3 (all) + ("mAutoClutch", ctypes.c_ubyte), # 0 (off), 1 (on) + ("mInvulnerable", ctypes.c_ubyte), # 0 (off), 1 (on) + ("mOppositeLock", ctypes.c_ubyte), # 0 (off), 1 (on) + ("mSteeringHelp", ctypes.c_ubyte), # 0 (off) - 3 (high) + ("mBrakingHelp", ctypes.c_ubyte), # 0 (off) - 2 (high) + ("mSpinRecovery", ctypes.c_ubyte), # 0 (off), 1 (on) + ("mAutoPit", ctypes.c_ubyte), # 0 (off), 1 (on) + ("mAutoLift", ctypes.c_ubyte), # 0 (off), 1 (on) + ("mAutoBlip", ctypes.c_ubyte), # 0 (off), 1 (on) + ("mFuelMult", ctypes.c_ubyte), # fuel multiplier (0x-7x) + ("mTireMult", ctypes.c_ubyte), # tire wear multiplier (0x-7x) + ("mMechFail", ctypes.c_ubyte), # mechanical failure setting; 0 (off), 1 (normal), 2 (timescaled) + ("mAllowPitcrewPush", ctypes.c_ubyte), # 0 (off), 1 (on) + ("mRepeatShifts", ctypes.c_ubyte), # accidental repeat shift prevention (0-5; see PLR file) + ("mHoldClutch", ctypes.c_ubyte), # for auto-shifters at start of race: 0 (off), 1 (on) + ("mAutoReverse", ctypes.c_ubyte), # 0 (off), 1 (on) + ("mAlternateNeutral", ctypes.c_ubyte), # Whether shifting up and down simultaneously equals neutral + ("mAIControl", ctypes.c_ubyte), # Whether player vehicle is currently under AI control + ("mUnused1", ctypes.c_ubyte), # + ("mUnused2", ctypes.c_ubyte), # + ("mManualShiftOverrideTime", ctypes.c_float), # time before auto-shifting can resume after recent manual shift + ("mAutoShiftOverrideTime", ctypes.c_float), # time before manual shifting can resume after recent auto shift + ("mSpeedSensitiveSteering", ctypes.c_float), # 0.0 (off) - 1.0 + ("mSteerRatioSpeed", ctypes.c_float), # speed (m/s) under which lock gets expanded to full ] -class rF2TrackRulesCommand(Enum): - AddFromTrack = 0 -#untranslated AddFromPit, // exited pit during full-course yellow -#untranslated AddFromUndq, // during a full-course yellow, the admin reversed a disqualification -#untranslated RemoveToPit, // entered pit during full-course yellow -#untranslated RemoveToDnf, // vehicle DNF'd during full-course yellow -#untranslated RemoveToDq, // vehicle DQ'd during full-course yellow -#untranslated RemoveToUnloaded, // vehicle unloaded (possibly kicked out or banned) during full-course yellow -#untranslated MoveToBack, // misbehavior during full-course yellow, resulting in the penalty of being moved to the back of their current line -#untranslated LongestTime, // misbehavior during full-course yellow, resulting in the penalty of being moved to the back of the longest line -#untranslated Maximum // should be last -#untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] class rF2TrackRulesAction(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mCommand', ctypes.c_int), # recommended action - ('mID', ctypes.c_int), # slot ID if applicable - ('mET', ctypes.c_double), # elapsed time that event occurred, if applicable + ("mCommand", ctypes.c_int), # recommended action + ("mID", ctypes.c_int), # slot ID if applicable + ("mET", ctypes.c_double), # elapsed time that event occurred, if applicable ] -class rF2TrackRulesColumn(Enum): - LeftLane = 0 - MidLefLane = 1 # mid-left - MiddleLane = 2 # middle - MidrRghtLane = 3 # mid-right - RightLane = 4 # right (outside) - MaxLanes = 5 # should be after the valid static lane choices - Invalid = MaxLanes - FreeChoice = 6 # free choice (dynamically chosen by driver) - Pending = 7 # depends on another participant's free choice (dynamically set after another driver chooses) - Maximum = 8 # should be last -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2TrackRulesParticipant(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mID', ctypes.c_int), # slot ID - ('mFrozenOrder', ctypes.c_short), # 0-based place when caution came out (not valid for formation laps) - ('mPlace', ctypes.c_short), # 1-based place (typically used for the initialization of the formation lap track order) - ('mYellowSeverity', ctypes.c_float), # a rating of how much this vehicle is contributing to a yellow flag (the sum of all vehicles is compared to TrackRulesV01::mSafetyCarThreshold) - ('mCurrentRelativeDistance', ctypes.c_double), # equal to ( ( ScoringInfoV01::mLapDist * this->mRelativeLaps ) + VehicleScoringInfoV01::mLapDist ) - ('mRelativeLaps', ctypes.c_int), # current formation/caution laps relative to safety car (should generally be zero except when safety car crosses s/f line); this can be decremented to implement 'wave around' or 'beneficiary rule' (a.k.a. 'lucky dog' or 'free pass') - ('mColumnAssignment', ctypes.c_int), # which column (line/lane) that participant is supposed to be in - ('mPositionAssignment', ctypes.c_int), # 0-based position within column (line/lane) that participant is supposed to be located at (-1 is invalid) - ('mPitsOpen', ctypes.c_ubyte), # whether the rules allow this particular vehicle to enter pits right now (input is 2=false or 3=true; if you want to edit it, set to 0=false or 1 = true) - ('mUpToSpeed', ctypes.c_bool), # while in the frozen order, this flag indicates whether the vehicle can be followed (this should be false for somebody who has temporarily spun and hasn't gotten back up to speed yet) - ('mUnused', ctypes.c_bool*2), # - ('mGoalRelativeDistance', ctypes.c_double), # calculated based on where the leader is, and adjusted by the desired column spacing and the column/position assignments - ('mMessage', ctypes.c_char*96), # a message for this participant to explain what is going on it will get run through translator on client machines - ('mExpansion', ctypes.c_ubyte*192), + ("mID", ctypes.c_int), # slot ID + ("mFrozenOrder", ctypes.c_short), # 0-based place when caution came out (not valid for formation laps) + ("mPlace", ctypes.c_short), # 1-based place (typically used for the initialization of the formation lap track order) + ("mYellowSeverity", ctypes.c_float), # a rating of how much this vehicle is contributing to a yellow flag (the sum of all vehicles is compared to TrackRulesV01::mSafetyCarThreshold) + ("mCurrentRelativeDistance", ctypes.c_double), # equal to ( ( ScoringInfoV01::mLapDist * this->mRelativeLaps ) + VehicleScoringInfoV01::mLapDist ) + ("mRelativeLaps", ctypes.c_int), # current formation/caution laps relative to safety car (should generally be zero except when safety car crosses s/f line); this can be decremented to implement "wave around" or "beneficiary rule" (a.k.a. "lucky dog" or "free pass") + ("mColumnAssignment", ctypes.c_int), # which column (line/lane) that participant is supposed to be in + ("mPositionAssignment", ctypes.c_int), # 0-based position within column (line/lane) that participant is supposed to be located at (-1 is invalid) + ("mPitsOpen", ctypes.c_ubyte), # whether the rules allow this particular vehicle to enter pits right now (input is 2=false or 3=true; if you want to edit it, set to 0=false or 1 = true) + ("mUpToSpeed", ctypes.c_bool), # while in the frozen order, this flag indicates whether the vehicle can be followed (this should be false for somebody who has temporarily spun and hasn"t gotten back up to speed yet) + ("mUnused", ctypes.c_bool*2), # + ("mGoalRelativeDistance", ctypes.c_double), # calculated based on where the leader is, and adjusted by the desired column spacing and the column/position assignments + ("mMessage", ctypes.c_char*96), # a message for this participant to explain what is going on it will get run through translator on client machines + ("mExpansion", ctypes.c_ubyte*192), ] -class rF2TrackRulesStage(Enum): - FormationInit = 0 - FormationUpdate = 1 # update of the formation lap - Normal = 2 # normal (non-yellow) update - CautionInit = 3 # initialization of a full-course yellow - CautionUpdate = 4 # update of a full-course yellow - Maximum = 5 # should be last -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2TrackRules(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mCurrentET', ctypes.c_double), # current time - ('mStage', ctypes.c_int), # current stage - ('mPoleColumn', ctypes.c_int), # column assignment where pole position seems to be located - ('mNumActions', ctypes.c_int), # number of recent actions - ('pointer1', ctypes.c_ubyte*8), - ('mNumParticipants', ctypes.c_int), # number of participants (vehicles) - ('mYellowFlagDetected', ctypes.c_bool), # whether yellow flag was requested or sum of participant mYellowSeverity's exceeds mSafetyCarThreshold - ('mYellowFlagLapsWasOverridden', ctypes.c_ubyte), # whether mYellowFlagLaps (below) is an admin request (0=no 1=yes 2 = clear yellow) - ('mSafetyCarExists', ctypes.c_bool), # whether safety car even exists - ('mSafetyCarActive', ctypes.c_bool), # whether safety car is active - ('mSafetyCarLaps', ctypes.c_int), # number of laps - ('mSafetyCarThreshold', ctypes.c_float), # the threshold at which a safety car is called out (compared to the sum of TrackRulesParticipantV01::mYellowSeverity for each vehicle) - ('mSafetyCarLapDist', ctypes.c_double), # safety car lap distance - ('mSafetyCarLapDistAtStart', ctypes.c_float), # where the safety car starts from - ('mPitLaneStartDist', ctypes.c_float), # where the waypoint branch to the pits breaks off (this may not be perfectly accurate) - ('mTeleportLapDist', ctypes.c_float), # the front of the teleport locations (a useful first guess as to where to throw the green flag) - ('mInputExpansion', ctypes.c_ubyte*256), - ('mYellowFlagState', ctypes.c_byte), # see ScoringInfoV01 for values - ('mYellowFlagLaps', ctypes.c_short), # suggested number of laps to run under yellow (may be passed in with admin command) - ('mSafetyCarInstruction', ctypes.c_int), # 0=no change, 1=go active, 2 = head for pits - ('mSafetyCarSpeed', ctypes.c_float), # maximum speed at which to drive - ('mSafetyCarMinimumSpacing', ctypes.c_float), # minimum spacing behind safety car (-1 to indicate no limit) - ('mSafetyCarMaximumSpacing', ctypes.c_float), # maximum spacing behind safety car (-1 to indicate no limit) - ('mMinimumColumnSpacing', ctypes.c_float), # minimum desired spacing between vehicles in a column (-1 to indicate indeterminate/unenforced) - ('mMaximumColumnSpacing', ctypes.c_float), # maximum desired spacing between vehicles in a column (-1 to indicate indeterminate/unenforced) - ('mMinimumSpeed', ctypes.c_float), # minimum speed that anybody should be driving (-1 to indicate no limit) - ('mMaximumSpeed', ctypes.c_float), # maximum speed that anybody should be driving (-1 to indicate no limit) - ('mMessage', ctypes.c_char*96), # a message for everybody to explain what is going on (which will get run through translator on client machines) - ('pointer2', ctypes.c_ubyte*8), - ('mInputOutputExpansion', ctypes.c_ubyte*256), + ("mCurrentET", ctypes.c_double), # current time + ("mStage", ctypes.c_int), # current stage + ("mPoleColumn", ctypes.c_int), # column assignment where pole position seems to be located + ("mNumActions", ctypes.c_int), # number of recent actions + ("pointer1", ctypes.c_ubyte*8), + ("mNumParticipants", ctypes.c_int), # number of participants (vehicles) + ("mYellowFlagDetected", ctypes.c_bool), # whether yellow flag was requested or sum of participant mYellowSeverity"s exceeds mSafetyCarThreshold + ("mYellowFlagLapsWasOverridden", ctypes.c_ubyte), # whether mYellowFlagLaps (below) is an admin request (0=no 1=yes 2 = clear yellow) + ("mSafetyCarExists", ctypes.c_bool), # whether safety car even exists + ("mSafetyCarActive", ctypes.c_bool), # whether safety car is active + ("mSafetyCarLaps", ctypes.c_int), # number of laps + ("mSafetyCarThreshold", ctypes.c_float), # the threshold at which a safety car is called out (compared to the sum of TrackRulesParticipantV01::mYellowSeverity for each vehicle) + ("mSafetyCarLapDist", ctypes.c_double), # safety car lap distance + ("mSafetyCarLapDistAtStart", ctypes.c_float), # where the safety car starts from + ("mPitLaneStartDist", ctypes.c_float), # where the waypoint branch to the pits breaks off (this may not be perfectly accurate) + ("mTeleportLapDist", ctypes.c_float), # the front of the teleport locations (a useful first guess as to where to throw the green flag) + ("mInputExpansion", ctypes.c_ubyte*256), + ("mYellowFlagState", ctypes.c_byte), # see ScoringInfoV01 for values + ("mYellowFlagLaps", ctypes.c_short), # suggested number of laps to run under yellow (may be passed in with admin command) + ("mSafetyCarInstruction", ctypes.c_int), # 0=no change, 1=go active, 2 = head for pits + ("mSafetyCarSpeed", ctypes.c_float), # maximum speed at which to drive + ("mSafetyCarMinimumSpacing", ctypes.c_float), # minimum spacing behind safety car (-1 to indicate no limit) + ("mSafetyCarMaximumSpacing", ctypes.c_float), # maximum spacing behind safety car (-1 to indicate no limit) + ("mMinimumColumnSpacing", ctypes.c_float), # minimum desired spacing between vehicles in a column (-1 to indicate indeterminate/unenforced) + ("mMaximumColumnSpacing", ctypes.c_float), # maximum desired spacing between vehicles in a column (-1 to indicate indeterminate/unenforced) + ("mMinimumSpeed", ctypes.c_float), # minimum speed that anybody should be driving (-1 to indicate no limit) + ("mMaximumSpeed", ctypes.c_float), # maximum speed that anybody should be driving (-1 to indicate no limit) + ("mMessage", ctypes.c_char*96), # a message for everybody to explain what is going on (which will get run through translator on client machines) + ("pointer2", ctypes.c_ubyte*8), + ("mInputOutputExpansion", ctypes.c_ubyte*256), ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2PitMenu(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mCategoryIndex', ctypes.c_int), # index of the current category - ('mCategoryName', ctypes.c_char*32), # name of the current category (untranslated) - ('mChoiceIndex', ctypes.c_int), # index of the current choice (within the current category) - ('mChoiceString', ctypes.c_char*32), # name of the current choice (may have some translated words) - ('mNumChoices', ctypes.c_int), # total number of choices (0 < = mChoiceIndex < mNumChoices) - ('mExpansion', ctypes.c_ubyte*256), # for future use + ("mCategoryIndex", ctypes.c_int), # index of the current category + ("mCategoryName", ctypes.c_char*32), # name of the current category (untranslated) + ("mChoiceIndex", ctypes.c_int), # index of the current choice (within the current category) + ("mChoiceString", ctypes.c_char*32), # name of the current choice (may have some translated words) + ("mNumChoices", ctypes.c_int), # total number of choices (0 < = mChoiceIndex < mNumChoices) + ("mExpansion", ctypes.c_ubyte*256), # for future use ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2WeatherControlInfo(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mET', ctypes.c_double), # when you want this weather to take effect - ('mRaining', ctypes.c_double*9), # rain (0.0-1.0) at different nodes - ('mCloudiness', ctypes.c_double), # general cloudiness (0.0=clear to 1.0 = dark) - ('mAmbientTempK', ctypes.c_double), # ambient temperature (Kelvin) - ('mWindMaxSpeed', ctypes.c_double), # maximum speed of wind (ground speed, but it affects how fast the clouds move, too) - ('mApplyCloudinessInstantly', ctypes.c_bool), # preferably we roll the new clouds in, but you can instantly change them now - ('mUnused1', ctypes.c_bool), # - ('mUnused2', ctypes.c_bool), # - ('mUnused3', ctypes.c_bool), # - ('mExpansion', ctypes.c_ubyte*508), # future use (humidity, pressure, air density, etc.) + ("mET", ctypes.c_double), # when you want this weather to take effect + ("mRaining", ctypes.c_double*9), # rain (0.0-1.0) at different nodes + ("mCloudiness", ctypes.c_double), # general cloudiness (0.0=clear to 1.0 = dark) + ("mAmbientTempK", ctypes.c_double), # ambient temperature (Kelvin) + ("mWindMaxSpeed", ctypes.c_double), # maximum speed of wind (ground speed, but it affects how fast the clouds move, too) + ("mApplyCloudinessInstantly", ctypes.c_bool), # preferably we roll the new clouds in, but you can instantly change them now + ("mUnused1", ctypes.c_bool), + ("mUnused2", ctypes.c_bool), + ("mUnused3", ctypes.c_bool), + ("mExpansion", ctypes.c_ubyte*508), # future use (humidity, pressure, air density, etc.) ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2MappedBufferVersionBlock(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. + ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. + ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2MappedBufferVersionBlockWithSize(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. - ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. + ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. + ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. + ("mBytesUpdatedHint", ctypes.c_int), # How many bytes of the structure were written during the last update. ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Telemetry(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. - ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. - ('mNumVehicles', ctypes.c_int), # current number of vehicles - ('mVehicles', rF2VehicleTelemetry*rFactor2Constants.MAX_MAPPED_VEHICLES), + ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. + ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. + ("mBytesUpdatedHint", ctypes.c_int), # How many bytes of the structure were written during the last update. + ("mNumVehicles", ctypes.c_int), # current number of vehicles + ("mVehicles", rF2VehicleTelemetry*rFactor2Constants.MAX_MAPPED_VEHICLES), ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Scoring(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. - ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. - ('mScoringInfo', rF2ScoringInfo), - ('mVehicles', rF2VehicleScoring*rFactor2Constants.MAX_MAPPED_VEHICLES), + ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. + ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. + ("mBytesUpdatedHint", ctypes.c_int), # How many bytes of the structure were written during the last update. + ("mScoringInfo", rF2ScoringInfo), + ("mVehicles", rF2VehicleScoring*rFactor2Constants.MAX_MAPPED_VEHICLES), ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Rules(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. - ('mBytesUpdatedHint', ctypes.c_int), # How many bytes of the structure were written during the last update. - ('mTrackRules', rF2TrackRules), - ('mActions', rF2TrackRulesAction*rFactor2Constants.MAX_MAPPED_VEHICLES), - ('mParticipants', rF2TrackRulesParticipant*rFactor2Constants.MAX_MAPPED_VEHICLES), + ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. + ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. + ("mBytesUpdatedHint", ctypes.c_int), # How many bytes of the structure were written during the last update. + ("mTrackRules", rF2TrackRules), + ("mActions", rF2TrackRulesAction*rFactor2Constants.MAX_MAPPED_VEHICLES), + ("mParticipants", rF2TrackRulesParticipant*rFactor2Constants.MAX_MAPPED_VEHICLES), ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2ForceFeedback(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. - ('mForceValue', ctypes.c_double), # Current FFB value reported via InternalsPlugin::ForceFeedback. + ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. + ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. + ("mForceValue", ctypes.c_double), # Current FFB value reported via InternalsPlugin::ForceFeedback. ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2GraphicsInfo(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mCamPos', rF2Vec3), # camera position - ('mCamOri', rF2Vec3*3), # rows of orientation matrix (use TelemQuat conversions if desired), also converts local - ('mHWND', ctypes.c_ubyte*8), # app handle - ('mAmbientRed', ctypes.c_double), - ('mAmbientGreen', ctypes.c_double), - ('mAmbientBlue', ctypes.c_double), - ('mID', ctypes.c_int), # slot ID being viewed (-1 if invalid) - ('mCameraType', ctypes.c_int), # see above comments for possible values - ('mExpansion', ctypes.c_ubyte*128), # for future use (possibly camera name) + ("mCamPos", rF2Vec3), # camera position + ("mCamOri", rF2Vec3*3), # rows of orientation matrix (use TelemQuat conversions if desired), also converts local + ("mHWND", ctypes.c_ubyte*8), # app handle + ("mAmbientRed", ctypes.c_double), + ("mAmbientGreen", ctypes.c_double), + ("mAmbientBlue", ctypes.c_double), + ("mID", ctypes.c_int), # slot ID being viewed (-1 if invalid) + ("mCameraType", ctypes.c_int), # see above comments for possible values + ("mExpansion", ctypes.c_ubyte*128), # for future use (possibly camera name) ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Graphics(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. - ('mGraphicsInfo', rF2GraphicsInfo), + ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. + ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. + ("mGraphicsInfo", rF2GraphicsInfo), ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2PitInfo(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. - ('mPitMneu', rF2PitMenu), + ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. + ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. + ("mPitMneu", rF2PitMenu), ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Weather(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. - ('mTrackNodeSize', ctypes.c_double), - ('mWeatherInfo', rF2WeatherControlInfo), + ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. + ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. + ("mTrackNodeSize", ctypes.c_double), + ("mWeatherInfo", rF2WeatherControlInfo), ] -#untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] class rF2TrackedDamage(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mMaxImpactMagnitude', ctypes.c_double), # Max impact magnitude. Tracked on every telemetry update, and reset on visit to pits or Session restart. - ('mAccumulatedImpactMagnitude', ctypes.c_double), # Accumulated impact magnitude. Tracked on every telemetry update, and reset on visit to pits or Session restart. + ("mMaxImpactMagnitude", ctypes.c_double), # Max impact magnitude. Tracked on every telemetry update, and reset on visit to pits or Session restart. + ("mAccumulatedImpactMagnitude", ctypes.c_double), # Accumulated impact magnitude. Tracked on every telemetry update, and reset on visit to pits or Session restart. ] -#untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] class rF2VehScoringCapture(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mID', ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) - ('mPlace', ctypes.c_ubyte), - ('mIsPlayer', ctypes.c_bool), - ('mFinishStatus', ctypes.c_byte), # 0=none, 1=finished, 2=dnf, 3 = dq + ("mID", ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) + ("mPlace", ctypes.c_ubyte), + ("mIsPlayer", ctypes.c_bool), + ("mFinishStatus", ctypes.c_byte), # 0=none, 1=finished, 2=dnf, 3 = dq ] -#untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] class rF2SessionTransitionCapture(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mGamePhase', ctypes.c_ubyte), - ('mSession', ctypes.c_int), - ('mNumScoringVehicles', ctypes.c_int), - ('mScoringVehicles', rF2VehScoringCapture*rFactor2Constants.MAX_MAPPED_VEHICLES), + ("mGamePhase", ctypes.c_ubyte), + ("mSession", ctypes.c_int), + ("mNumScoringVehicles", ctypes.c_int), + ("mScoringVehicles", rF2VehScoringCapture*rFactor2Constants.MAX_MAPPED_VEHICLES), ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Extended(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. - ('mVersion', ctypes.c_char*12), # API version - ('is64bit', ctypes.c_bool), # Is 64bit plugin? - ('mPhysics', rF2PhysicsOptions), - ('mTrackedDamages', rF2TrackedDamage*rFactor2Constants.MAX_MAPPED_IDS), - ('mInRealtimeFC', ctypes.c_bool), # in realtime as opposed to at the monitor (reported via last EnterRealtime/ExitRealtime calls). - ('mMultimediaThreadStarted', ctypes.c_bool), # multimedia thread started (reported via ThreadStarted/ThreadStopped calls). - ('mSimulationThreadStarted', ctypes.c_bool), # simulation thread started (reported via ThreadStarted/ThreadStopped calls). - ('mSessionStarted', ctypes.c_bool), # Set to true on Session Started, set to false on Session Ended. - ('mTicksSessionStarted', ctypes.c_ulonglong), # Ticks when session started. - ('mTicksSessionEnded', ctypes.c_ulonglong), # Ticks when session ended. - ('mSessionTransitionCapture', rF2SessionTransitionCapture),# Contains partial internals capture at session transition time. - ('mDisplayedMessageUpdateCapture', ctypes.c_char*128), - ('mDirectMemoryAccessEnabled', ctypes.c_bool), - ('mTicksStatusMessageUpdated', ctypes.c_ulonglong), # Ticks when status message was updated; - ('mStatusMessage', ctypes.c_char*rFactor2Constants.MAX_STATUS_MSG_LEN), - ('mTicksLastHistoryMessageUpdated', ctypes.c_ulonglong), # Ticks when last message history message was updated; - ('mLastHistoryMessage', ctypes.c_char*rFactor2Constants.MAX_STATUS_MSG_LEN), - ('mCurrentPitSpeedLimit', ctypes.c_float), # speed limit m/s. - ('mSCRPluginEnabled', ctypes.c_bool), # Is Stock Car Rules plugin enabled? - ('mSCRPluginDoubleFileType', ctypes.c_int), # Stock Car Rules plugin DoubleFileType value, only meaningful if mSCRPluginEnabled is true. - ('mTicksLSIPhaseMessageUpdated', ctypes.c_ulonglong), # Ticks when last LSI phase message was updated. - ('mLSIPhaseMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mTicksLSIPitStateMessageUpdated', ctypes.c_ulonglong), # Ticks when last LSI pit state message was updated. - ('mLSIPitStateMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mTicksLSIOrderInstructionMessageUpdated', ctypes.c_ulonglong), # Ticks when last LSI order instruction message was updated. - ('mLSIOrderInstructionMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mTicksLSIRulesInstructionMessageUpdated', ctypes.c_ulonglong), # Ticks when last FCY rules message was updated. Currently, only SCR plugin sets that. - ('mLSIRulesInstructionMessage', ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), - ('mUnsubscribedBuffersMask', ctypes.c_int), # Currently active UnsbscribedBuffersMask value. This will be allowed for clients to write to in the future, but not yet. - ('mHWControlInputEnabled', ctypes.c_bool), # HWControl input buffer is enabled. - ('mWeatherControlInputEnabled', ctypes.c_bool), # WeatherControl input buffer is enabled. - ('mRulesControlInputEnabled', ctypes.c_bool), # RulesControl input buffer is enabled. + ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. + ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. + ("mVersion", ctypes.c_char*12), # API version + ("is64bit", ctypes.c_bool), # Is 64bit plugin? + ("mPhysics", rF2PhysicsOptions), + ("mTrackedDamages", rF2TrackedDamage*rFactor2Constants.MAX_MAPPED_IDS), + ("mInRealtimeFC", ctypes.c_bool), # in realtime as opposed to at the monitor (reported via last EnterRealtime/ExitRealtime calls). + ("mMultimediaThreadStarted", ctypes.c_bool), # multimedia thread started (reported via ThreadStarted/ThreadStopped calls). + ("mSimulationThreadStarted", ctypes.c_bool), # simulation thread started (reported via ThreadStarted/ThreadStopped calls). + ("mSessionStarted", ctypes.c_bool), # Set to true on Session Started, set to false on Session Ended. + ("mTicksSessionStarted", ctypes.c_ulonglong), # Ticks when session started. + ("mTicksSessionEnded", ctypes.c_ulonglong), # Ticks when session ended. + ("mSessionTransitionCapture", rF2SessionTransitionCapture), # Contains partial internals capture at session transition time. + ("mDisplayedMessageUpdateCapture", ctypes.c_char*128), + ("mDirectMemoryAccessEnabled", ctypes.c_bool), + ("mTicksStatusMessageUpdated", ctypes.c_ulonglong), # Ticks when status message was updated; + ("mStatusMessage", ctypes.c_char*rFactor2Constants.MAX_STATUS_MSG_LEN), + ("mTicksLastHistoryMessageUpdated", ctypes.c_ulonglong), # Ticks when last message history message was updated; + ("mLastHistoryMessage", ctypes.c_char*rFactor2Constants.MAX_STATUS_MSG_LEN), + ("mCurrentPitSpeedLimit", ctypes.c_float), # speed limit m/s. + ("mSCRPluginEnabled", ctypes.c_bool), # Is Stock Car Rules plugin enabled? + ("mSCRPluginDoubleFileType", ctypes.c_int), # Stock Car Rules plugin DoubleFileType value, only meaningful if mSCRPluginEnabled is true. + ("mTicksLSIPhaseMessageUpdated", ctypes.c_ulonglong), # Ticks when last LSI phase message was updated. + ("mLSIPhaseMessage", ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ("mTicksLSIPitStateMessageUpdated", ctypes.c_ulonglong), # Ticks when last LSI pit state message was updated. + ("mLSIPitStateMessage", ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ("mTicksLSIOrderInstructionMessageUpdated", ctypes.c_ulonglong), # Ticks when last LSI order instruction message was updated. + ("mLSIOrderInstructionMessage", ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ("mTicksLSIRulesInstructionMessageUpdated", ctypes.c_ulonglong), # Ticks when last FCY rules message was updated. Currently, only SCR plugin sets that. + ("mLSIRulesInstructionMessage", ctypes.c_char*rFactor2Constants.MAX_RULES_INSTRUCTION_MSG_LEN), + ("mUnsubscribedBuffersMask", ctypes.c_int), # Currently active UnsbscribedBuffersMask value. This will be allowed for clients to write to in the future, but not yet. + ("mHWControlInputEnabled", ctypes.c_bool), # HWControl input buffer is enabled. + ("mWeatherControlInputEnabled", ctypes.c_bool), # WeatherControl input buffer is enabled. + ("mRulesControlInputEnabled", ctypes.c_bool), # RulesControl input buffer is enabled. ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2HWControl(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. - ('mLayoutVersion', ctypes.c_int), - ('mControlName', ctypes.c_char*rFactor2Constants.MAX_HWCONTROL_NAME_LEN), - ('mfRetVal', ctypes.c_double), + ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. + ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. + ("mLayoutVersion", ctypes.c_int), + ("mControlName", ctypes.c_char*rFactor2Constants.MAX_HWCONTROL_NAME_LEN), + ("mfRetVal", ctypes.c_double), ] -#untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2WeatherControl(ctypes.Structure): _pack_ = 4 _fields_ = [ - ('mVersionUpdateBegin', ctypes.c_uint), # Incremented right before buffer is written to. - ('mVersionUpdateEnd', ctypes.c_uint), # Incremented after buffer write is done. - ('mLayoutVersion', ctypes.c_int), - ('mWeatherInfo', rF2WeatherControlInfo), + ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. + ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. + ("mLayoutVersion", ctypes.c_int), + ("mWeatherInfo", rF2WeatherControlInfo), ] -class SubscribedBuffer(Enum): - Telemetry = 1, - Scoring = 2, - Rules = 4, - MultiRules = 8, - ForceFeedback = 16, - Graphics = 32, - PitInfo = 64, - Weather = 128, - All = 255 -class SimInfo: - def __init__(self): +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] +class rF2RulesControl(ctypes.Structure): + _pack_ = 4 + _fields_ = [ + ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. + ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. + ("mLayoutVersion", ctypes.c_int), + ("mTrackRules", rF2TrackRules), + ("mActions", rF2TrackRulesAction*rFactor2Constants.MAX_MAPPED_VEHICLES), + ("mParticipants", rF2TrackRulesParticipant*rFactor2Constants.MAX_MAPPED_VEHICLES), + ] + + +# untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] +class rF2PluginControl(ctypes.Structure): + _pack_ = 4 + _fields_ = [ + ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. + ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. + ("mLayoutVersion", ctypes.c_int), + ("mRequestEnableBuffersMask", ctypes.c_int), + ("mRequestHWControlInput", ctypes.c_ubyte), + ("mRequestWeatherControlInput", ctypes.c_ubyte), + ("mRequestRulesControlInput", ctypes.c_ubyte), + ] - self._rf2_tele = mmap.mmap(0, ctypes.sizeof(rF2Telemetry), "$rFactor2SMMP_Telemetry$") + +class SimInfo: + """Simulation info from shared memory""" + + def __init__(self): + self._rf2_tele = mmap.mmap( + fileno=0, + length=ctypes.sizeof(rF2Telemetry), + tagname=rFactor2Constants.MM_TELEMETRY_FILE_NAME, + ) self.Rf2Tele = rF2Telemetry.from_buffer(self._rf2_tele) - self._rf2_scor = mmap.mmap(0, ctypes.sizeof(rF2Scoring), "$rFactor2SMMP_Scoring$") + + self._rf2_scor = mmap.mmap( + fileno=0, + length=ctypes.sizeof(rF2Scoring), + tagname=rFactor2Constants.MM_SCORING_FILE_NAME, + ) self.Rf2Scor = rF2Scoring.from_buffer(self._rf2_scor) - self._rf2_ext = mmap.mmap(0, ctypes.sizeof(rF2Extended), "$rFactor2SMMP_Extended$") + + self._rf2_ext = mmap.mmap( + fileno=0, + length=ctypes.sizeof(rF2Extended), + tagname=rFactor2Constants.MM_EXTENDED_FILE_NAME, + ) self.Rf2Ext = rF2Extended.from_buffer(self._rf2_ext) def close(self): - # This didn't help with the errors - try: - self._rf2_tele.close() - self._rf2_scor.close() - self._rf2_ext.close() - except BufferError: # "cannot close exported pointers exist" - pass + """Close memory map""" + self.Rf2Tele = None + self.Rf2Scor = None + self.Rf2Ext = None + try: # this did not help with the errors + self._rf2_tele.close() + self._rf2_scor.close() + self._rf2_ext.close() + except BufferError as e: + print("Error:", e) def __del__(self): self.close() -if __name__ == '__main__': - # Example usage + +def test(): + """Example usage""" info = SimInfo() - version = info.Rf2Ext.mVersion - v = bytes(version).partition(b'\0')[0].decode().rstrip() - clutch = info.Rf2Tele.mVehicles[0].mUnfilteredClutch # 1.0 clutch down, 0 clutch up - gear = info.Rf2Tele.mVehicles[0].mGear # -1 to 6 - print('Map version: %s\n' - 'Gear: %d, Clutch position: %d' % (v, gear, clutch)) + version = bytes(info.Rf2Ext.mVersion).decode().rstrip() + clutch = info.Rf2Tele.mVehicles[0].mUnfilteredClutch # 1.0 clutch down, 0 clutch up + gear = info.Rf2Tele.mVehicles[0].mGear # -1 to 6 + print(f"API version: {version if version else 'not found'}") + print(f"Gear: {gear}, Clutch position: {clutch}") + print(f"Unsubscribed buffers: {SubscribedBuffer(info.Rf2Ext.mUnsubscribedBuffersMask)}") + +if __name__ == "__main__": + test() From 61077889cd89cc6e1e835545f4400b7307a03c5d Mon Sep 17 00:00:00 2001 From: Xiang Date: Tue, 18 Feb 2025 22:44:01 +0800 Subject: [PATCH 87/93] get root logger name for logging, optimize copy access methods, only copy if data version changed to avoid unnecessary copy while idling --- rF2MMap.py | 52 +++++++++++++++++++++++++++++----------------------- rF2data.py | 36 ++++++++++++++++++------------------ 2 files changed, 47 insertions(+), 41 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index c1fa58c..c9be965 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -25,7 +25,15 @@ MAX_VEHICLES = rF2data.rFactor2Constants.MAX_MAPPED_VEHICLES INVALID_INDEX = -1 -logger = logging.getLogger(__name__) + +def get_root_logger_name(): + """Get root logger name""" + for logger_name in logging.root.manager.loggerDict: + return logger_name + return __name__ + + +logger = logging.getLogger(get_root_logger_name()) def platform_mmap(name: str, size: int, pid: str = "") -> mmap.mmap: @@ -67,13 +75,13 @@ class MMapControl: __slots__ = ( "_mmap_name", "_buffer_data", + "_buffer_version", "_mmap_instance", - "_buffer_sharing", "update", "data", ) - def __init__(self, mmap_name: str, buffer_data: object) -> None: + def __init__(self, mmap_name: str, buffer_data: ctypes.Structure) -> None: """Initialize memory map setting Args: @@ -82,8 +90,8 @@ def __init__(self, mmap_name: str, buffer_data: object) -> None: """ self._mmap_name = mmap_name self._buffer_data = buffer_data + self._buffer_version = None self._mmap_instance = None - self._buffer_sharing = False self.update = None self.data = None @@ -102,11 +110,15 @@ def create(self, access_mode: int = 0, rf2_pid: str = "") -> None: size=ctypes.sizeof(self._buffer_data), pid=rf2_pid ) + if access_mode: self.update = self.__buffer_share + self.data = self._buffer_data.from_buffer(self._mmap_instance) else: self.update = self.__buffer_copy - self.update(True) + self.data = self._buffer_data.from_buffer_copy(self._mmap_instance) + self._buffer_version = rF2data.rF2MappedBufferVersionBlock.from_buffer(self._mmap_instance) + mode = "Direct" if access_mode else "Copy" logger.info("sharedmemory: ACTIVE: %s (%s Access)", self._mmap_name, mode) @@ -115,8 +127,8 @@ def close(self) -> None: Create a final accessible mmap data copy before closing mmap instance. """ - self.data = copy(self.data) - self._buffer_sharing = False + self.data = self._buffer_data.from_buffer_copy(self._mmap_instance) + self._buffer_version = None try: self._mmap_instance.close() logger.info("sharedmemory: CLOSED: %s", self._mmap_name) @@ -124,21 +136,17 @@ def close(self) -> None: logger.error("sharedmemory: buffer error while closing %s", self._mmap_name) self.update = None # unassign update method (for proper garbage collection) - def __buffer_share(self, _=None) -> None: - """Share buffer direct access, may result desync""" - if not self._buffer_sharing: - self._buffer_sharing = True - self.data = self._buffer_data.from_buffer(self._mmap_instance) - - def __buffer_copy(self, skip_check: bool = False) -> None: - """Copy buffer access, check version before assign new data copy + def __buffer_share(self) -> None: + """Share buffer access, may result data desync""" - Args: - skip_check: skip data version check. - """ - temp = self._buffer_data.from_buffer_copy(self._mmap_instance) - if temp.mVersionUpdateEnd == temp.mVersionUpdateBegin or skip_check: - self.data = temp + def __buffer_copy(self) -> None: + """Copy buffer access, helps avoid data desync""" + # Copy if data version changed + if self._buffer_version.mVersionUpdateEnd != self.data.mVersionUpdateEnd: + temp = self._buffer_data.from_buffer_copy(self._mmap_instance) + # Check data integraty before assign copy + if temp.mVersionUpdateEnd == temp.mVersionUpdateBegin: + self.data = temp class MMapDataSet: @@ -183,8 +191,6 @@ def update_mmap(self) -> None: """Update mmap data""" self.scor.update() self.tele.update() - #self.ext.update() - #self.ffb.update() class SyncData: diff --git a/rF2data.py b/rF2data.py index 23bf22d..131db7e 100644 --- a/rF2data.py +++ b/rF2data.py @@ -4,9 +4,9 @@ """ # pylint: disable=C,R,W -from enum import Enum, auto, Flag import ctypes import mmap +from enum import Enum, Flag class rFactor2Constants: @@ -299,15 +299,15 @@ class rF2SafetyCarInstruction(Enum): class rF2TrackRulesCommand(Enum): AddFromTrack = 0 - AddFromPit = auto() # exited pit during full-course yellow - AddFromUndq = auto() # during a full-course yellow, the admin reversed a disqualification - RemoveToPit = auto() # entered pit during full-course yellow - RemoveToDnf = auto() # vehicle DNF"d during full-course yellow - RemoveToDq = auto() # vehicle DQ"d during full-course yellow - RemoveToUnloaded = auto() # vehicle unloaded (possibly kicked out or banned) during full-course yellow - MoveToBack = auto() # misbehavior during full-course yellow, resulting in the penalty of being moved to the back of their current line - LongestTime = auto() # misbehavior during full-course yellow, resulting in the penalty of being moved to the back of the longest line - Maximum = auto() # should be last + AddFromPit = 1 # exited pit during full-course yellow + AddFromUndq = 2 # during a full-course yellow, the admin reversed a disqualification + RemoveToPit = 3 # entered pit during full-course yellow + RemoveToDnf = 4 # vehicle DNF'd during full-course yellow + RemoveToDq = 5 # vehicle DQ'd during full-course yellow + RemoveToUnloaded = 6 # vehicle unloaded (possibly kicked out or banned) during full-course yellow + MoveToBack = 7 # misbehavior during full-course yellow, resulting in the penalty of being moved to the back of their current line + LongestTime = 8 # misbehavior during full-course yellow, resulting in the penalty of being moved to the back of the longest line + Maximum = 9 # should be last class rF2TrackRulesColumn(Enum): @@ -319,7 +319,7 @@ class rF2TrackRulesColumn(Enum): MaxLanes = 5 # should be after the valid static lane choices Invalid = MaxLanes FreeChoice = 6 # free choice (dynamically chosen by driver) - Pending = 7 # depends on another participant"s free choice (dynamically set after another driver chooses) + Pending = 7 # depends on another participant's free choice (dynamically set after another driver chooses) Maximum = 8 # should be last @@ -371,7 +371,7 @@ class rF2Wheel(ctypes.Structure): ("mDetached", ctypes.c_bool), # whether wheel is detached ("mStaticUndeflectedRadius", ctypes.c_ubyte), # tire radius in centimeters ("mVerticalTireDeflection", ctypes.c_double), # how much is tire deflected from its (speed-sensitive) radius - ("mWheelYLocation", ctypes.c_double), # wheel"s y location relative to vehicle y location + ("mWheelYLocation", ctypes.c_double), # wheel's y location relative to vehicle y location ("mToe", ctypes.c_double), # current toe angle w.r.t. the vehicle ("mTireCarcassTemperature", ctypes.c_double), # rough average of temperature samples from carcass (Kelvin) ("mTireInnerLayerTemperature", ctypes.c_double*3), # rough average of temperature samples from innermost layer of rubber (before carcass) (Kelvin) @@ -428,7 +428,7 @@ class rF2VehicleTelemetry(ctypes.Structure): ("mLastImpactET", ctypes.c_double), # time of last impact ("mLastImpactMagnitude", ctypes.c_double), # magnitude of last impact ("mLastImpactPos", rF2Vec3), # location of last impact - ("mEngineTorque", ctypes.c_double), # current engine torque (including additive torque) (used to be mEngineTq, but there"s little reason to abbreviate it) + ("mEngineTorque", ctypes.c_double), # current engine torque (including additive torque) (used to be mEngineTq, but there's little reason to abbreviate it) ("mCurrentSector", ctypes.c_int), # the current sector (zero-based) with the pitlane stored in the sign bit (example: entering pits from third sector gives 0x80000002) ("mSpeedLimiter", ctypes.c_ubyte), # whether speed limiter is on ("mMaxGears", ctypes.c_ubyte), # maximum forward gears @@ -509,7 +509,7 @@ class rF2VehicleScoring(ctypes.Structure): ("mDriverName", ctypes.c_char*32), # driver name ("mVehicleName", ctypes.c_char*64), # vehicle name ("mTotalLaps", ctypes.c_short), # laps completed - ("mSector", ctypes.c_byte), # 0=sector3, 1=sector1, 2 = sector2 (don"t ask why) + ("mSector", ctypes.c_byte), # 0=sector3, 1=sector1, 2 = sector2 (don't ask why) ("mFinishStatus", ctypes.c_byte), # 0=none, 1=finished, 2=dnf, 3 = dq ("mLapDist", ctypes.c_double), # current distance around track ("mPathLateral", ctypes.c_double), # lateral position with respect to *very approximate* "center" path @@ -524,8 +524,8 @@ class rF2VehicleScoring(ctypes.Structure): ("mCurSector2", ctypes.c_double), # current sector 2 (plus sector 1) if valid ("mNumPitstops", ctypes.c_short), # number of pitstops made ("mNumPenalties", ctypes.c_short), # number of outstanding penalties - ("mIsPlayer", ctypes.c_bool), # is this the player"s vehicle - ("mControl", ctypes.c_byte), # who"s in control: -1=nobody (shouldn"t get this), 0=local player, 1=local AI, 2=remote, 3 = replay (shouldn"t get this) + ("mIsPlayer", ctypes.c_bool), # is this the player's vehicle + ("mControl", ctypes.c_byte), # who's in control: -1=nobody (shouldn't get this), 0=local player, 1=local AI, 2=remote, 3 = replay (shouldn't get this) ("mInPits", ctypes.c_bool), # between pit entrance and pit exit (not always accurate for remote vehicles) ("mPlace", ctypes.c_ubyte), # 1-based position ("mVehicleClass", ctypes.c_char*32), # vehicle class @@ -618,7 +618,7 @@ class rF2TrackRulesParticipant(ctypes.Structure): ("mColumnAssignment", ctypes.c_int), # which column (line/lane) that participant is supposed to be in ("mPositionAssignment", ctypes.c_int), # 0-based position within column (line/lane) that participant is supposed to be located at (-1 is invalid) ("mPitsOpen", ctypes.c_ubyte), # whether the rules allow this particular vehicle to enter pits right now (input is 2=false or 3=true; if you want to edit it, set to 0=false or 1 = true) - ("mUpToSpeed", ctypes.c_bool), # while in the frozen order, this flag indicates whether the vehicle can be followed (this should be false for somebody who has temporarily spun and hasn"t gotten back up to speed yet) + ("mUpToSpeed", ctypes.c_bool), # while in the frozen order, this flag indicates whether the vehicle can be followed (this should be false for somebody who has temporarily spun and hasn't gotten back up to speed yet) ("mUnused", ctypes.c_bool*2), # ("mGoalRelativeDistance", ctypes.c_double), # calculated based on where the leader is, and adjusted by the desired column spacing and the column/position assignments ("mMessage", ctypes.c_char*96), # a message for this participant to explain what is going on it will get run through translator on client machines @@ -636,7 +636,7 @@ class rF2TrackRules(ctypes.Structure): ("mNumActions", ctypes.c_int), # number of recent actions ("pointer1", ctypes.c_ubyte*8), ("mNumParticipants", ctypes.c_int), # number of participants (vehicles) - ("mYellowFlagDetected", ctypes.c_bool), # whether yellow flag was requested or sum of participant mYellowSeverity"s exceeds mSafetyCarThreshold + ("mYellowFlagDetected", ctypes.c_bool), # whether yellow flag was requested or sum of participant mYellowSeverity's exceeds mSafetyCarThreshold ("mYellowFlagLapsWasOverridden", ctypes.c_ubyte), # whether mYellowFlagLaps (below) is an admin request (0=no 1=yes 2 = clear yellow) ("mSafetyCarExists", ctypes.c_bool), # whether safety car even exists ("mSafetyCarActive", ctypes.c_bool), # whether safety car is active From 9a734e22087cdbcdead5c6c75cafe3a1a7a313ee Mon Sep 17 00:00:00 2001 From: Xiang Date: Tue, 11 Mar 2025 18:04:44 +0800 Subject: [PATCH 88/93] remove player-sync code, updated example code for MMapControl --- rF2MMap.py | 397 +++-------------------------------------------------- 1 file changed, 20 insertions(+), 377 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index c9be965..5a2197f 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -1,9 +1,10 @@ """ -rF2 Memory Map +rF2 Memory Map Control -Inherit Python mapping of The Iron Wolf's rF2 Shared Memory Tools, -with player-synchronized accessing (by S.Victor) -and cross-platform Linux support (by Bernat) +Inherit Python mapping of The Iron Wolf's rF2 Shared Memory Tools + +Memory map control (by S.Victor) +Cross-platform Linux support (by Bernat) """ from __future__ import annotations @@ -11,10 +12,6 @@ import logging import mmap import platform -import threading -from copy import copy -from typing import Sequence -from time import monotonic, sleep try: from . import rF2data @@ -57,18 +54,6 @@ def linux_mmap(name: str, size: int) -> mmap.mmap: return mmap.mmap(file.fileno(), size) -def local_scoring_index(scor_veh: Sequence[rF2data.rF2VehicleScoring]) -> int: - """Find local player scoring index - - Args: - scor_veh: scoring mVehicles array. - """ - for scor_idx, veh_info in enumerate(scor_veh): - if veh_info.mIsPlayer: - return scor_idx - return INVALID_INDEX - - class MMapControl: """Memory map control""" @@ -149,342 +134,6 @@ def __buffer_copy(self) -> None: self.data = temp -class MMapDataSet: - """Create mmap data set""" - - __slots__ = ( - "scor", - "tele", - "ext", - "ffb", - ) - - def __init__(self) -> None: - self.scor = MMapControl(rF2data.rFactor2Constants.MM_SCORING_FILE_NAME, rF2data.rF2Scoring) - self.tele = MMapControl(rF2data.rFactor2Constants.MM_TELEMETRY_FILE_NAME, rF2data.rF2Telemetry) - self.ext = MMapControl(rF2data.rFactor2Constants.MM_EXTENDED_FILE_NAME, rF2data.rF2Extended) - self.ffb = MMapControl(rF2data.rFactor2Constants.MM_FORCE_FEEDBACK_FILE_NAME, rF2data.rF2ForceFeedback) - - def __del__(self): - logger.info("sharedmemory: GC: MMapDataSet") - - def create_mmap(self, access_mode: int, rf2_pid: str) -> None: - """Create mmap instance - - Args: - access_mode: 0 = copy access, 1 = direct access. - rf2_pid: rF2 Process ID for accessing server data. - """ - self.scor.create(access_mode, rf2_pid) - self.tele.create(access_mode, rf2_pid) - self.ext.create(1, rf2_pid) - self.ffb.create(1, rf2_pid) - - def close_mmap(self) -> None: - """Close mmap instance""" - self.scor.close() - self.tele.close() - self.ext.close() - self.ffb.close() - - def update_mmap(self) -> None: - """Update mmap data""" - self.scor.update() - self.tele.update() - - -class SyncData: - """Synchronize data with player ID - - Attributes: - dataset: mmap data set. - paused: Data update state (boolean). - override_player_index: Player index override state (boolean). - player_scor_index: Local player scoring index. - player_scor: Local player scoring data. - player_tele: Local player telemetry data. - """ - - __slots__ = ( - "_updating", - "_update_thread", - "_event", - "_tele_indexes", - "paused", - "override_player_index", - "player_scor_index", - "player_scor", - "player_tele", - "dataset", - ) - - def __init__(self) -> None: - self._updating = False - self._update_thread = None - self._event = threading.Event() - self._tele_indexes = {_index: _index for _index in range(128)} - - self.paused = False - self.override_player_index = False - self.player_scor_index = INVALID_INDEX - self.player_scor = None - self.player_tele = None - self.dataset = MMapDataSet() - - def __del__(self): - logger.info("sharedmemory: GC: SyncData") - - def __sync_player_data(self) -> bool: - """Sync local player data - - Returns: - False, if no valid player scoring index found. - True, set player data. - """ - if not self.override_player_index: - # Update scoring index - scor_idx = local_scoring_index(self.dataset.scor.data.mVehicles) - if scor_idx == INVALID_INDEX: - return False # index not found, not synced - self.player_scor_index = scor_idx - # Set player data - self.player_scor = self.dataset.scor.data.mVehicles[self.player_scor_index] - self.player_tele = self.dataset.tele.data.mVehicles[self.sync_tele_index(self.player_scor_index)] - return True # found index, synced - - @staticmethod - def __update_tele_indexes(tele_data: rF2data.rF2Telemetry, tele_indexes: dict) -> None: - """Update telemetry player index dictionary for quick reference - - Telemetry index can be different from scoring index. - Use mID matching to match telemetry index. - - Args: - tele_data: Telemetry data. - tele_indexes: Telemetry mID:index reference dictionary. - """ - for tele_idx, veh_info in zip(range(tele_data.mNumVehicles), tele_data.mVehicles): - tele_indexes[veh_info.mID] = tele_idx - - def sync_tele_index(self, scor_idx: int) -> int: - """Sync telemetry index - - Use scoring index to find scoring mID, - then match with telemetry mID in reference dictionary - to find telemetry index. - - Args: - scor_idx: Player scoring index. - - Returns: - Player telemetry index. - """ - return self._tele_indexes.get( - self.dataset.scor.data.mVehicles[scor_idx].mID, INVALID_INDEX) - - def start(self, access_mode: int, rf2_pid: str) -> None: - """Update & sync mmap data copy in separate thread - - Args: - access_mode: 0 = copy access, 1 = direct access. - rf2_pid: rF2 Process ID for accessing server data. - """ - if self._updating: - logger.warning("sharedmemory: UPDATING: already started") - else: - self._updating = True - # Initialize mmap data - self.dataset.create_mmap(access_mode, rf2_pid) - self.__update_tele_indexes(self.dataset.tele.data, self._tele_indexes) - if not self.__sync_player_data(): - self.player_scor = self.dataset.scor.data.mVehicles[INVALID_INDEX] - self.player_tele = self.dataset.tele.data.mVehicles[INVALID_INDEX] - # Setup updating thread - self._event.clear() - self._update_thread = threading.Thread(target=self.__update, daemon=True) - self._update_thread.start() - logger.info("sharedmemory: UPDATING: thread started") - logger.info("sharedmemory: player index override: %s", self.override_player_index) - logger.info("sharedmemory: server process ID: %s", rf2_pid if rf2_pid else "DISABLED") - - def stop(self) -> None: - """Join and stop updating thread, close mmap""" - if self._updating: - self._event.set() - self._updating = False - self._update_thread.join() - # Make final copy before close, otherwise mmap won't close if using direct access - self.player_scor = copy(self.player_scor) - self.player_tele = copy(self.player_tele) - self.dataset.close_mmap() - else: - logger.warning("sharedmemory: UPDATING: already stopped") - - def __update(self) -> None: - """Update synced player data""" - self.paused = False # make sure initial pause state is false - freezed_version = 0 # store freezed update version number - last_version_update = 0 # store last update version number - last_update_time = 0.0 - data_freezed = True # whether data is freezed - reset_counter = 0 - update_delay = 0.5 # longer delay while inactive - - while not self._event.wait(update_delay): - self.dataset.update_mmap() - self.__update_tele_indexes(self.dataset.tele.data, self._tele_indexes) - # Update player data & index - if not data_freezed: - # Get player data - data_synced = self.__sync_player_data() - # Pause if local player index no longer exists, 5 tries - if data_synced: - reset_counter = 0 - self.paused = False - elif reset_counter < 6: - reset_counter += 1 - if reset_counter == 5: - self.paused = True - logger.info("sharedmemory: UPDATING: player data paused") - - version_update = self.dataset.scor.data.mVersionUpdateEnd - if last_version_update != version_update: - last_version_update = version_update - last_update_time = monotonic() - - if data_freezed: - # Check while IN freeze state - if freezed_version != last_version_update: - update_delay = 0.01 - self.paused = data_freezed = False - logger.info( - "sharedmemory: UPDATING: resumed, data version %s", - last_version_update, - ) - # Check while NOT IN freeze state - # Set freeze state if data stopped updating after 2s - elif monotonic() - last_update_time > 2: - update_delay = 0.5 - self.paused = data_freezed = True - freezed_version = last_version_update - logger.info( - "sharedmemory: UPDATING: paused, data version %s", - freezed_version, - ) - - logger.info("sharedmemory: UPDATING: thread stopped") - - -class RF2SM: - """RF2 shared memory data output""" - - __slots__ = ( - "_sync", - "_access_mode", - "_rf2_pid", - "_scor", - "_tele", - "_ext", - "_ffb", - ) - - def __init__(self) -> None: - self._sync = SyncData() - self._access_mode = 0 - self._rf2_pid = "" - # Assign mmap instance - self._scor = self._sync.dataset.scor - self._tele = self._sync.dataset.tele - self._ext = self._sync.dataset.ext - self._ffb = self._sync.dataset.ffb - - def __del__(self): - logger.info("sharedmemory: GC: RF2SM") - - def start(self) -> None: - """Start data updating thread""" - self._sync.start(self._access_mode, self._rf2_pid) - - def stop(self) -> None: - """Stop data updating thread""" - self._sync.stop() - - def setPID(self, pid: str = "") -> None: - """Set rF2 process ID for connecting to server data""" - self._rf2_pid = str(pid) - - def setMode(self, mode: int = 0) -> None: - """Set rF2 mmap access mode - - Args: - mode: 0 = copy access, 1 = direct access - """ - self._access_mode = mode - - def setPlayerOverride(self, state: bool = False) -> None: - """Enable player index override state""" - self._sync.override_player_index = state - - def setPlayerIndex(self, index: int = INVALID_INDEX) -> None: - """Manual override player index""" - self._sync.player_scor_index = min(max(index, INVALID_INDEX), MAX_VEHICLES - 1) - - @property - def rf2ScorInfo(self) -> rF2data.rF2ScoringInfo: - """rF2 scoring info data""" - return self._scor.data.mScoringInfo - - def rf2ScorVeh(self, index: int | None = None) -> rF2data.rF2VehicleScoring: - """rF2 scoring vehicle data - - Specify index for specific player. - - Args: - index: None for local player. - """ - if index is None: - return self._sync.player_scor - return self._scor.data.mVehicles[index] - - def rf2TeleVeh(self, index: int | None = None) -> rF2data.rF2VehicleTelemetry: - """rF2 telemetry vehicle data - - Specify index for specific player. - - Args: - index: None for local player. - """ - if index is None: - return self._sync.player_tele - return self._tele.data.mVehicles[self._sync.sync_tele_index(index)] - - @property - def rf2Ext(self) -> rF2data.rF2Extended: - """rF2 extended data""" - return self._ext.data - - @property - def rf2Ffb(self) -> rF2data.rF2ForceFeedback: - """rF2 force feedback data""" - return self._ffb.data - - @property - def playerIndex(self) -> int: - """rF2 local player's scoring index""" - return self._sync.player_scor_index - - def isPlayer(self, index: int) -> bool: - """Check whether index is player""" - if self._sync.override_player_index: - return self._sync.player_scor_index == index - return self._scor.data.mVehicles[index].mIsPlayer - - @property - def isPaused(self) -> bool: - """Check whether data stopped updating""" - return self._sync.paused - - def test_api(): """API test run""" # Add logger @@ -495,33 +144,27 @@ def test_api(): # Test run SEPARATOR = "=" * 50 print("Test API - Start") - info = RF2SM() - info.setMode(1) # set direct access - info.setPID("") - info.setPlayerOverride(True) # enable player override - info.setPlayerIndex(0) # set player index to 0 - info.start() - sleep(0.2) - - print(SEPARATOR) - print("Test API - Restart") - info.stop() - info.setMode() # set copy access - info.setPlayerOverride() # disable player override - info.start() + scoring = MMapControl(rF2data.rFactor2Constants.MM_SCORING_FILE_NAME, rF2data.rF2Scoring) + scoring.create(1) + telemetry = MMapControl(rF2data.rFactor2Constants.MM_TELEMETRY_FILE_NAME, rF2data.rF2Telemetry) + telemetry.create(1) + extended = MMapControl(rF2data.rFactor2Constants.MM_EXTENDED_FILE_NAME, rF2data.rF2Extended) + extended.create(1) print(SEPARATOR) print("Test API - Read") - version = info.rf2Ext.mVersion.decode() - driver = info.rf2ScorVeh(0).mDriverName.decode(encoding="iso-8859-1") - track = info.rf2ScorInfo.mTrackName.decode(encoding="iso-8859-1") - print(f"plugin version: {version if version else 'not running'}") - print(f"driver name : {driver if version else 'not running'}") - print(f"track name : {track if version else 'not running'}") + version = extended.data.mVersion.decode() + track = scoring.data.mScoringInfo.mTrackName.decode(encoding="iso-8859-1") + vehicles = telemetry.data.mNumVehicles + print(f"plugin ver: {version if version else 'not running'}") + print(f"track name: {track if version else 'not running'}") + print(f"total cars: {vehicles if version else 'not running'}") print(SEPARATOR) print("Test API - Close") - info.stop() + scoring.close() + telemetry.close() + extended.close() if __name__ == "__main__": From 25e7c207f581ebc4268baf9343aedc3084347cf8 Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 11 Jun 2025 22:39:54 +0800 Subject: [PATCH 89/93] optimize buffer copy --- rF2MMap.py | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/rF2MMap.py b/rF2MMap.py index 5a2197f..79fc02a 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -8,6 +8,7 @@ """ from __future__ import annotations + import ctypes import logging import mmap @@ -59,24 +60,26 @@ class MMapControl: __slots__ = ( "_mmap_name", - "_buffer_data", - "_buffer_version", - "_mmap_instance", + "_mmap_buffer", + "_struct", + "_buffer", + "_version", "update", "data", ) - def __init__(self, mmap_name: str, buffer_data: ctypes.Structure) -> None: + def __init__(self, mmap_name: str, data_struct: ctypes.Structure) -> None: """Initialize memory map setting Args: mmap_name: mmap filename, ex. $rFactor2SMMP_Scoring$. - buffer_data: buffer data class, ex. rF2data.rF2Scoring. + data_struct: ctypes data structure, ex. rF2data.rF2Scoring. """ self._mmap_name = mmap_name - self._buffer_data = buffer_data - self._buffer_version = None - self._mmap_instance = None + self._mmap_buffer = None + self._struct = data_struct + self._buffer = bytearray() + self._version = None self.update = None self.data = None @@ -90,19 +93,20 @@ def create(self, access_mode: int = 0, rf2_pid: str = "") -> None: access_mode: 0 = copy access, 1 = direct access. rf2_pid: rF2 Process ID for accessing server data. """ - self._mmap_instance = platform_mmap( + self._mmap_buffer = platform_mmap( name=self._mmap_name, - size=ctypes.sizeof(self._buffer_data), + size=ctypes.sizeof(self._struct), pid=rf2_pid ) if access_mode: + self.data = self._struct.from_buffer(self._mmap_buffer) self.update = self.__buffer_share - self.data = self._buffer_data.from_buffer(self._mmap_instance) else: + self._buffer[:] = self._mmap_buffer + self.data = self._struct.from_buffer(self._buffer) + self._version = rF2data.rF2MappedBufferVersionBlock.from_buffer(self._mmap_buffer) self.update = self.__buffer_copy - self.data = self._buffer_data.from_buffer_copy(self._mmap_instance) - self._buffer_version = rF2data.rF2MappedBufferVersionBlock.from_buffer(self._mmap_instance) mode = "Direct" if access_mode else "Copy" logger.info("sharedmemory: ACTIVE: %s (%s Access)", self._mmap_name, mode) @@ -112,10 +116,10 @@ def close(self) -> None: Create a final accessible mmap data copy before closing mmap instance. """ - self.data = self._buffer_data.from_buffer_copy(self._mmap_instance) - self._buffer_version = None + self.data = self._struct.from_buffer_copy(self._mmap_buffer) + self._version = None try: - self._mmap_instance.close() + self._mmap_buffer.close() logger.info("sharedmemory: CLOSED: %s", self._mmap_name) except BufferError: logger.error("sharedmemory: buffer error while closing %s", self._mmap_name) @@ -127,11 +131,8 @@ def __buffer_share(self) -> None: def __buffer_copy(self) -> None: """Copy buffer access, helps avoid data desync""" # Copy if data version changed - if self._buffer_version.mVersionUpdateEnd != self.data.mVersionUpdateEnd: - temp = self._buffer_data.from_buffer_copy(self._mmap_instance) - # Check data integraty before assign copy - if temp.mVersionUpdateEnd == temp.mVersionUpdateBegin: - self.data = temp + if self._version.mVersionUpdateEnd != self.data.mVersionUpdateEnd: + self._buffer[:] = self._mmap_buffer def test_api(): From 2068ff2d550a75a28d15966cd7e2fc098dca6dcc Mon Sep 17 00:00:00 2001 From: Xiang Date: Sat, 19 Jul 2025 13:44:46 +0800 Subject: [PATCH 90/93] add helper classes with type hints & annotation reference to rF2data.py for IDE & type checker; fix "mPitMneu" typo --- rF2MMap.py | 10 +- rF2Type.py | 500 +++++++++++++++++++++++++++++++++++++++++++++++++++++ rF2data.py | 2 +- 3 files changed, 507 insertions(+), 5 deletions(-) create mode 100644 rF2Type.py diff --git a/rF2MMap.py b/rF2MMap.py index 79fc02a..f861994 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -16,11 +16,13 @@ try: from . import rF2data + from .rF2data import rFactor2Constants except ImportError: # standalone, not package import rF2data + from rF2data import rFactor2Constants PLATFORM = platform.system() -MAX_VEHICLES = rF2data.rFactor2Constants.MAX_MAPPED_VEHICLES +MAX_VEHICLES = rFactor2Constants.MAX_MAPPED_VEHICLES INVALID_INDEX = -1 @@ -145,11 +147,11 @@ def test_api(): # Test run SEPARATOR = "=" * 50 print("Test API - Start") - scoring = MMapControl(rF2data.rFactor2Constants.MM_SCORING_FILE_NAME, rF2data.rF2Scoring) + scoring = MMapControl(rFactor2Constants.MM_SCORING_FILE_NAME, rF2data.rF2Scoring) scoring.create(1) - telemetry = MMapControl(rF2data.rFactor2Constants.MM_TELEMETRY_FILE_NAME, rF2data.rF2Telemetry) + telemetry = MMapControl(rFactor2Constants.MM_TELEMETRY_FILE_NAME, rF2data.rF2Telemetry) telemetry.create(1) - extended = MMapControl(rF2data.rFactor2Constants.MM_EXTENDED_FILE_NAME, rF2data.rF2Extended) + extended = MMapControl(rFactor2Constants.MM_EXTENDED_FILE_NAME, rF2data.rF2Extended) extended.create(1) print(SEPARATOR) diff --git a/rF2Type.py b/rF2Type.py new file mode 100644 index 0000000..82cce49 --- /dev/null +++ b/rF2Type.py @@ -0,0 +1,500 @@ +""" +rF2 API data type hints & annotation + +Helper classes with type hints & annotation reference to rF2data.py for IDE & type checker. + +Annotate "ctypes type" as "Python type" according to table from: +https://docs.python.org/3/library/ctypes.html#fundamental-data-types + +Annotate array object as tuple[type, ...] to specify number of elements contained. +""" + +from __future__ import annotations + +from abc import ABC, abstractmethod + + +class _NOINIT(ABC): + """Disable instantiate""" + + @abstractmethod + def _(self): ... + + +class rF2Vec3(_NOINIT): + x: float + y: float + z: float + + +class rF2Wheel(_NOINIT): + mSuspensionDeflection: float + mRideHeight: float + mSuspForce: float + mBrakeTemp: float + mBrakePressure: float + mRotation: float + mLateralPatchVel: float + mLongitudinalPatchVel: float + mLateralGroundVel: float + mLongitudinalGroundVel: float + mCamber: float + mLateralForce: float + mLongitudinalForce: float + mTireLoad: float + mGripFract: float + mPressure: float + mTemperature: tuple[float, float, float] + mWear: float + mTerrainName: bytes + mSurfaceType: int + mFlat: bool + mDetached: bool + mStaticUndeflectedRadius: int + mVerticalTireDeflection: float + mWheelYLocation: float + mToe: float + mTireCarcassTemperature: float + mTireInnerLayerTemperature: tuple[float, float, float] + mExpansion: tuple[int, ...] + + +class rF2VehicleTelemetry(_NOINIT): + mID: int + mDeltaTime: float + mElapsedTime: float + mLapNumber: int + mLapStartET: float + mVehicleName: bytes + mTrackName: bytes + mPos: rF2Vec3 + mLocalVel: rF2Vec3 + mLocalAccel: rF2Vec3 + mOri: tuple[rF2Vec3, rF2Vec3, rF2Vec3] + mLocalRot: rF2Vec3 + mLocalRotAccel: rF2Vec3 + mGear: int + mEngineRPM: float + mEngineWaterTemp: float + mEngineOilTemp: float + mClutchRPM: float + mUnfilteredThrottle: float + mUnfilteredBrake: float + mUnfilteredSteering: float + mUnfilteredClutch: float + mFilteredThrottle: float + mFilteredBrake: float + mFilteredSteering: float + mFilteredClutch: float + mSteeringShaftTorque: float + mFront3rdDeflection: float + mRear3rdDeflection: float + mFrontWingHeight: float + mFrontRideHeight: float + mRearRideHeight: float + mDrag: float + mFrontDownforce: float + mRearDownforce: float + mFuel: float + mEngineMaxRPM: float + mScheduledStops: int + mOverheating: bool + mDetached: bool + mHeadlights: bool + mDentSeverity: tuple[int, int, int, int, int, int, int, int] + mLastImpactET: float + mLastImpactMagnitude: float + mLastImpactPos: rF2Vec3 + mEngineTorque: float + mCurrentSector: int + mSpeedLimiter: int + mMaxGears: int + mFrontTireCompoundIndex: int + mRearTireCompoundIndex: int + mFuelCapacity: float + mFrontFlapActivated: int + mRearFlapActivated: int + mRearFlapLegalStatus: int + mIgnitionStarter: int + mFrontTireCompoundName: bytes + mRearTireCompoundName: bytes + mSpeedLimiterAvailable: int + mAntiStallActivated: int + mUnused: tuple[int, int] + mVisualSteeringWheelRange: float + mRearBrakeBias: float + mTurboBoostPressure: float + mPhysicsToGraphicsOffset: tuple[float, float, float] + mPhysicalSteeringWheelRange: float + mDeltaBest: float + mBatteryChargeFraction: float + mElectricBoostMotorTorque: float + mElectricBoostMotorRPM: float + mElectricBoostMotorTemperature: float + mElectricBoostWaterTemperature: float + mElectricBoostMotorState: int + mExpansion: tuple[int, ...] + mWheels: tuple[rF2Wheel, rF2Wheel, rF2Wheel, rF2Wheel] + + +class rF2ScoringInfo(_NOINIT): + mTrackName: bytes + mSession: int + mCurrentET: float + mEndET: float + mMaxLaps: int + mLapDist: float + pointer1: tuple[int, ...] + mNumVehicles: int + mGamePhase: int + mYellowFlagState: int + mSectorFlag: tuple[int, int, int] + mStartLight: int + mNumRedLights: int + mInRealtime: bool + mPlayerName: bytes + mPlrFileName: bytes + mDarkCloud: float + mRaining: float + mAmbientTemp: float + mTrackTemp: float + mWind: rF2Vec3 + mMinPathWetness: float + mMaxPathWetness: float + mGameMode: int + mIsPasswordProtected: bool + mServerPort: int + mServerPublicIP: int + mMaxPlayers: int + mServerName: bytes + mStartET: float + mAvgPathWetness: float + mExpansion: tuple[int, ...] + pointer2: tuple[int, ...] + + +class rF2VehicleScoring(_NOINIT): + mID: int + mDriverName: bytes + mVehicleName: bytes + mTotalLaps: int + mSector: int + mFinishStatus: int + mLapDist: float + mPathLateral: float + mTrackEdge: float + mBestSector1: float + mBestSector2: float + mBestLapTime: float + mLastSector1: float + mLastSector2: float + mLastLapTime: float + mCurSector1: float + mCurSector2: float + mNumPitstops: int + mNumPenalties: int + mIsPlayer: bool + mControl: int + mInPits: bool + mPlace: int + mVehicleClass: bytes + mTimeBehindNext: float + mLapsBehindNext: int + mTimeBehindLeader: float + mLapsBehindLeader: int + mLapStartET: float + mPos: rF2Vec3 + mLocalVel: rF2Vec3 + mLocalAccel: rF2Vec3 + mOri: tuple[rF2Vec3, rF2Vec3, rF2Vec3] + mLocalRot: rF2Vec3 + mLocalRotAccel: rF2Vec3 + mHeadlights: int + mPitState: int + mServerScored: int + mIndividualPhase: int + mQualification: int + mTimeIntoLap: float + mEstimatedLapTime: float + mPitGroup: bytes + mFlag: int + mUnderYellow: bool + mCountLapFlag: int + mInGarageStall: bool + mUpgradePack: tuple[int, ...] + mPitLapDist: float + mBestLapSector1: float + mBestLapSector2: float + mExpansion: tuple[int, ...] + + +class rF2PhysicsOptions(_NOINIT): + mTractionControl: int + mAntiLockBrakes: int + mStabilityControl: int + mAutoShift: int + mAutoClutch: int + mInvulnerable: int + mOppositeLock: int + mSteeringHelp: int + mBrakingHelp: int + mSpinRecovery: int + mAutoPit: int + mAutoLift: int + mAutoBlip: int + mFuelMult: int + mTireMult: int + mMechFail: int + mAllowPitcrewPush: int + mRepeatShifts: int + mHoldClutch: int + mAutoReverse: int + mAlternateNeutral: int + mAIControl: int + mUnused1: int + mUnused2: int + mManualShiftOverrideTime: float + mAutoShiftOverrideTime: float + mSpeedSensitiveSteering: float + mSteerRatioSpeed: float + + +class rF2TrackRulesAction(_NOINIT): + mCommand: int + mID: int + mET: float + + +class rF2TrackRulesParticipant(_NOINIT): + mID: int + mFrozenOrder: int + mPlace: int + mYellowSeverity: float + mCurrentRelativeDistance: float + mRelativeLaps: int + mColumnAssignment: int + mPositionAssignment: int + mPitsOpen: int + mUpToSpeed: bool + mUnused: tuple[bool, bool] + mGoalRelativeDistance: float + mMessage: bytes + mExpansion: tuple[int, ...] + + +class rF2TrackRules(_NOINIT): + mCurrentET: float + mStage: int + mPoleColumn: int + mNumActions: int + pointer1: tuple[int, ...] + mNumParticipants: int + mYellowFlagDetected: bool + mYellowFlagLapsWasOverridden: int + mSafetyCarExists: bool + mSafetyCarActive: bool + mSafetyCarLaps: int + mSafetyCarThreshold: float + mSafetyCarLapDist: float + mSafetyCarLapDistAtStart: float + mPitLaneStartDist: float + mTeleportLapDist: float + mInputExpansion: tuple[int, ...] + mYellowFlagState: int + mYellowFlagLaps: int + mSafetyCarInstruction: int + mSafetyCarSpeed: float + mSafetyCarMinimumSpacing: float + mSafetyCarMaximumSpacing: float + mMinimumColumnSpacing: float + mMaximumColumnSpacing: float + mMinimumSpeed: float + mMaximumSpeed: float + mMessage: bytes + pointer2: tuple[int, ...] + mInputOutputExpansion: tuple[int, ...] + + +class rF2PitMenu(_NOINIT): + mCategoryIndex: int + mCategoryName: bytes + mChoiceIndex: int + mChoiceString: bytes + mNumChoices: int + mExpansion: tuple[int, ...] + + +class rF2WeatherControlInfo(_NOINIT): + mET: float + mRaining: tuple[float, float, float, float, float, float, float, float, float] + mCloudiness: float + mAmbientTempK: float + mWindMaxSpeed: float + mApplyCloudinessInstantly: bool + mUnused1: bool + mUnused2: bool + mUnused3: bool + mExpansion: tuple[int, ...] + + +class rF2MappedBufferVersionBlock(_NOINIT): + mVersionUpdateBegin: int + mVersionUpdateEnd: int + + +class rF2MappedBufferVersionBlockWithSize(_NOINIT): + mVersionUpdateBegin: int + mVersionUpdateEnd: int + mBytesUpdatedHint: int + + +class rF2Telemetry(_NOINIT): + mVersionUpdateBegin: int + mVersionUpdateEnd: int + mBytesUpdatedHint: int + mNumVehicles: int + mVehicles: tuple[rF2VehicleTelemetry, ...] + + +class rF2Scoring(_NOINIT): + mVersionUpdateBegin: int + mVersionUpdateEnd: int + mBytesUpdatedHint: int + mScoringInfo: rF2ScoringInfo + mVehicles: tuple[rF2VehicleScoring, ...] + + +class rF2Rules(_NOINIT): + mVersionUpdateBegin: int + mVersionUpdateEnd: int + mBytesUpdatedHint: int + mTrackRules: rF2TrackRules + mActions: tuple[rF2TrackRulesAction, ...] + mParticipants: tuple[rF2TrackRulesParticipant, ...] + + +class rF2ForceFeedback(_NOINIT): + mVersionUpdateBegin: int + mVersionUpdateEnd: int + mForceValue: float + + +class rF2GraphicsInfo(_NOINIT): + mCamPos: rF2Vec3 + mCamOri: tuple[rF2Vec3, rF2Vec3, rF2Vec3] + mHWND: tuple[int, ...] + mAmbientRed: float + mAmbientGreen: float + mAmbientBlue: float + mID: int + mCameraType: int + mExpansion: tuple[int, ...] + + +class rF2Graphics(_NOINIT): + mVersionUpdateBegin: int + mVersionUpdateEnd: int + mGraphicsInfo: rF2GraphicsInfo + + +class rF2PitInfo(_NOINIT): + mVersionUpdateBegin: int + mVersionUpdateEnd: int + mPitMenu: rF2PitMenu + + +class rF2Weather(_NOINIT): + mVersionUpdateBegin: int + mVersionUpdateEnd: int + mTrackNodeSize: float + mWeatherInfo: rF2WeatherControlInfo + + +class rF2TrackedDamage(_NOINIT): + mMaxImpactMagnitude: float + mAccumulatedImpactMagnitude: float + + +class rF2VehScoringCapture(_NOINIT): + mID: int + mPlace: int + mIsPlayer: bool + mFinishStatus: int + + +class rF2SessionTransitionCapture(_NOINIT): + mGamePhase: int + mSession: int + mNumScoringVehicles: int + mScoringVehicles: tuple[rF2VehScoringCapture, ...] + + +class rF2Extended(_NOINIT): + mVersionUpdateBegin: int + mVersionUpdateEnd: int + mVersion: bytes + is64bit: bool + mPhysics: rF2PhysicsOptions + mTrackedDamages: tuple[rF2TrackedDamage, ...] + mInRealtimeFC: bool + mMultimediaThreadStarted: bool + mSimulationThreadStarted: bool + mSessionStarted: bool + mTicksSessionStarted: int + mTicksSessionEnded: int + mSessionTransitionCapture: rF2SessionTransitionCapture + mDisplayedMessageUpdateCapture: bytes + mDirectMemoryAccessEnabled: bool + mTicksStatusMessageUpdated: int + mStatusMessage: bytes + mTicksLastHistoryMessageUpdated: int + mLastHistoryMessage: bytes + mCurrentPitSpeedLimit: float + mSCRPluginEnabled: bool + mSCRPluginDoubleFileType: int + mTicksLSIPhaseMessageUpdated: int + mLSIPhaseMessage: bytes + mTicksLSIPitStateMessageUpdated: int + mLSIPitStateMessage: bytes + mTicksLSIOrderInstructionMessageUpdated: int + mLSIOrderInstructionMessage: bytes + mTicksLSIRulesInstructionMessageUpdated: int + mLSIRulesInstructionMessage: bytes + mUnsubscribedBuffersMask: int + mHWControlInputEnabled: bool + mWeatherControlInputEnabled: bool + mRulesControlInputEnabled: bool + + +class rF2HWControl(_NOINIT): + mVersionUpdateBegin: int + mVersionUpdateEnd: int + mLayoutVersion: int + mControlName: bytes + mfRetVal: float + + +class rF2WeatherControl(_NOINIT): + mVersionUpdateBegin: int + mVersionUpdateEnd: int + mLayoutVersion: int + mWeatherInfo: rF2WeatherControlInfo + + +class rF2RulesControl(_NOINIT): + mVersionUpdateBegin: int + mVersionUpdateEnd: int + mLayoutVersion: int + mTrackRules: rF2TrackRules + mActions: tuple[rF2TrackRulesAction, ...] + mParticipants: tuple[rF2TrackRulesParticipant, ...] + + +class rF2PluginControl(_NOINIT): + mVersionUpdateBegin: int + mVersionUpdateEnd: int + mLayoutVersion: int + mRequestEnableBuffersMask: int + mRequestHWControlInput: int + mRequestWeatherControlInput: int + mRequestRulesControlInput: int diff --git a/rF2data.py b/rF2data.py index 131db7e..16ee7f7 100644 --- a/rF2data.py +++ b/rF2data.py @@ -791,7 +791,7 @@ class rF2PitInfo(ctypes.Structure): _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. ("mVersionUpdateEnd", ctypes.c_uint), # Incremented after buffer write is done. - ("mPitMneu", rF2PitMenu), + ("mPitMenu", rF2PitMenu), ] From c27d425616e7ce3e65a5ce3669372c4634a80711 Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 23 Jul 2025 03:48:14 +0800 Subject: [PATCH 91/93] additional version check to avoid desync --- rF2MMap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rF2MMap.py b/rF2MMap.py index f861994..d7291d7 100644 --- a/rF2MMap.py +++ b/rF2MMap.py @@ -133,7 +133,7 @@ def __buffer_share(self) -> None: def __buffer_copy(self) -> None: """Copy buffer access, helps avoid data desync""" # Copy if data version changed - if self._version.mVersionUpdateEnd != self.data.mVersionUpdateEnd: + if self.data.mVersionUpdateEnd != self._version.mVersionUpdateEnd == self._version.mVersionUpdateBegin: self._buffer[:] = self._mmap_buffer From da7494fc2d0318a51e9b7bc3240fb1996308eebc Mon Sep 17 00:00:00 2001 From: Xiang Date: Sun, 17 Aug 2025 17:51:29 +0800 Subject: [PATCH 92/93] add __slots__ --- rF2data.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/rF2data.py b/rF2data.py index 16ee7f7..1a865dd 100644 --- a/rF2data.py +++ b/rF2data.py @@ -335,6 +335,7 @@ class rF2TrackRulesStage(Enum): # untranslated namespace rFactor2Data # untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] class rF2Vec3(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("x", ctypes.c_double), @@ -345,6 +346,7 @@ class rF2Vec3(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Wheel(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mSuspensionDeflection", ctypes.c_double), # meters @@ -381,6 +383,7 @@ class rF2Wheel(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2VehicleTelemetry(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mID", ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) @@ -463,6 +466,7 @@ class rF2VehicleTelemetry(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2ScoringInfo(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mTrackName", ctypes.c_char*64), # current track name @@ -503,6 +507,7 @@ class rF2ScoringInfo(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2VehicleScoring(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mID", ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) @@ -562,6 +567,7 @@ class rF2VehicleScoring(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2PhysicsOptions(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mTractionControl", ctypes.c_ubyte), # 0 (off) - 3 (high) @@ -597,6 +603,7 @@ class rF2PhysicsOptions(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] class rF2TrackRulesAction(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mCommand", ctypes.c_int), # recommended action @@ -607,6 +614,7 @@ class rF2TrackRulesAction(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2TrackRulesParticipant(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mID", ctypes.c_int), # slot ID @@ -628,6 +636,7 @@ class rF2TrackRulesParticipant(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2TrackRules(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mCurrentET", ctypes.c_double), # current time @@ -665,6 +674,7 @@ class rF2TrackRules(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2PitMenu(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mCategoryIndex", ctypes.c_int), # index of the current category @@ -678,6 +688,7 @@ class rF2PitMenu(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2WeatherControlInfo(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mET", ctypes.c_double), # when you want this weather to take effect @@ -695,6 +706,7 @@ class rF2WeatherControlInfo(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2MappedBufferVersionBlock(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. @@ -704,6 +716,7 @@ class rF2MappedBufferVersionBlock(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2MappedBufferVersionBlockWithSize(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. @@ -714,6 +727,7 @@ class rF2MappedBufferVersionBlockWithSize(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Telemetry(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. @@ -726,6 +740,7 @@ class rF2Telemetry(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Scoring(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. @@ -738,6 +753,7 @@ class rF2Scoring(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Rules(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. @@ -751,6 +767,7 @@ class rF2Rules(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2ForceFeedback(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. @@ -761,6 +778,7 @@ class rF2ForceFeedback(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2GraphicsInfo(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mCamPos", rF2Vec3), # camera position @@ -777,6 +795,7 @@ class rF2GraphicsInfo(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Graphics(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. @@ -787,6 +806,7 @@ class rF2Graphics(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2PitInfo(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. @@ -797,6 +817,7 @@ class rF2PitInfo(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Weather(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. @@ -808,6 +829,7 @@ class rF2Weather(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] class rF2TrackedDamage(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mMaxImpactMagnitude", ctypes.c_double), # Max impact magnitude. Tracked on every telemetry update, and reset on visit to pits or Session restart. @@ -817,6 +839,7 @@ class rF2TrackedDamage(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] class rF2VehScoringCapture(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mID", ctypes.c_int), # slot ID (note that it can be re-used in multiplayer after someone leaves) @@ -828,6 +851,7 @@ class rF2VehScoringCapture(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, Pack = 4)] class rF2SessionTransitionCapture(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mGamePhase", ctypes.c_ubyte), @@ -839,6 +863,7 @@ class rF2SessionTransitionCapture(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2Extended(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. @@ -880,6 +905,7 @@ class rF2Extended(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2HWControl(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. @@ -892,6 +918,7 @@ class rF2HWControl(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2WeatherControl(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. @@ -903,6 +930,7 @@ class rF2WeatherControl(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2RulesControl(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. @@ -916,6 +944,7 @@ class rF2RulesControl(ctypes.Structure): # untranslated [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)] class rF2PluginControl(ctypes.Structure): + __slots__ = () _pack_ = 4 _fields_ = [ ("mVersionUpdateBegin", ctypes.c_uint), # Incremented right before buffer is written to. From d7ad1aa932bb1b65f465a893fc7bfd72d8124bf3 Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 10 Dec 2025 11:18:06 +0800 Subject: [PATCH 93/93] add new accessible API data, update test example --- rF2Type.py | 5 +++++ rF2data.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/rF2Type.py b/rF2Type.py index 82cce49..3a27706 100644 --- a/rF2Type.py +++ b/rF2Type.py @@ -225,6 +225,11 @@ class rF2VehicleScoring(_NOINIT): mPitLapDist: float mBestLapSector1: float mBestLapSector2: float + mSteamID: int + mVehFilename: str + mAttackMode: int + mFuelFraction: float + mDRSState: bool mExpansion: tuple[int, ...] diff --git a/rF2data.py b/rF2data.py index 1a865dd..39cda3f 100644 --- a/rF2data.py +++ b/rF2data.py @@ -561,7 +561,12 @@ class rF2VehicleScoring(ctypes.Structure): ("mPitLapDist", ctypes.c_float), # location of pit in terms of lap distance ("mBestLapSector1", ctypes.c_float), # sector 1 time from best lap (not necessarily the best sector 1 time) ("mBestLapSector2", ctypes.c_float), # sector 2 time from best lap (not necessarily the best sector 2 time) - ("mExpansion", ctypes.c_ubyte*48), # for future use + ("mSteamID", ctypes.c_ulonglong), # SteamID of the current driver (if any) + ("mVehFilename", ctypes.c_char*32), # filename of veh file used to identify this vehicle. + ("mAttackMode", ctypes.c_short), + ("mFuelFraction", ctypes.c_ubyte), # Percentage of fuel or battery left in vehicle. 0x00 = 0%; 0xFF = 100% + ("mDRSState", ctypes.c_bool), # DRS (RearFlap) state + ("mExpansion", ctypes.c_ubyte*4), # for future use ] @@ -1001,12 +1006,48 @@ def __del__(self): def test(): """Example usage""" info = SimInfo() - version = bytes(info.Rf2Ext.mVersion).decode().rstrip() - clutch = info.Rf2Tele.mVehicles[0].mUnfilteredClutch # 1.0 clutch down, 0 clutch up - gear = info.Rf2Tele.mVehicles[0].mGear # -1 to 6 - print(f"API version: {version if version else 'not found'}") - print(f"Gear: {gear}, Clutch position: {clutch}") - print(f"Unsubscribed buffers: {SubscribedBuffer(info.Rf2Ext.mUnsubscribedBuffersMask)}") + + scor_data = info.Rf2Scor + tele_data = info.Rf2Tele + ext_data = info.Rf2Ext + + selected_player_index = 0 + + player_scor_data = scor_data.mVehicles[selected_player_index] + player_tele_data = tele_data.mVehicles[selected_player_index] + + print("-"*40) + print("Plugin info:") + print("Version:", bytes(ext_data.mVersion).decode().rstrip()) + print("Unsubscribed buffers:", SubscribedBuffer(ext_data.mUnsubscribedBuffersMask)) + + print("-"*40) + print("Scoring info:") + print("Track name:", scor_data.mScoringInfo.mTrackName) + print("Local player name:", scor_data.mScoringInfo.mPlayerName) + print("Setting name:", scor_data.mScoringInfo.mPlrFileName) + print("Total vehicles:", scor_data.mScoringInfo.mNumVehicles) + + print("-"*40) + print("Selected Player scoring info:") + print("Slot ID:", player_scor_data.mID) + print("Driver name:", player_scor_data.mDriverName) + print("VEH file:", player_scor_data.mVehFilename) + print("Is local player:", player_scor_data.mIsPlayer) + + print("-"*40) + print("Selected player telemetry info:") + print("Slot ID:", player_tele_data.mID) + print("Gear:", player_tele_data.mGear) + print("Throttle:", player_tele_data.mUnfilteredThrottle) + print("Brake:", player_tele_data.mUnfilteredBrake) + print("Clutch:", player_tele_data.mUnfilteredClutch) + + player_scor_data = None + player_tele_data = None + scor_data = None + tele_data = None + ext_data = None if __name__ == "__main__":