Branch data Line data Source code
1 : : #ifndef GAMECONNECTOR_H
2 : : #define GAMECONNECTOR_H
3 : :
4 : : #ifdef _WIN32
5 : : #include <windows.h>
6 : : #else
7 : : #include "io/lmu_sm_interface/LinuxMock.h"
8 : : #endif
9 : :
10 : : #include "io/lmu_sm_interface/LmuSharedMemoryWrapper.h"
11 : : #include "io/lmu_sm_interface/SafeSharedMemoryLock.h"
12 : : #include <mutex>
13 : : #include <atomic>
14 : : #include <cstring>
15 : :
16 : : namespace FFBEngineTests { class GameConnectorTestAccessor; }
17 : :
18 : : class GameConnector {
19 : : public:
20 : : static GameConnector& Get();
21 : :
22 : : // Attempt to connect to LMU Shared Memory
23 : : bool TryConnect();
24 : :
25 : : // Disconnect and clean up resources
26 : : void Disconnect();
27 : :
28 : : // Check for Legacy rFactor 2 Plugin conflict
29 : : bool CheckLegacyConflict();
30 : :
31 : : // Is connected to LMU SM?
32 : : bool IsConnected() const;
33 : :
34 : : // Thread-safe copy of telemetry data
35 : : // Returns true if in realtime (driving) mode, false if in menu/replay
36 : : bool CopyTelemetry(SharedMemoryObjectOut& dest);
37 : :
38 : : // Returns true if telemetry data hasn't changed for more than timeout (v0.7.15)
39 : : bool IsStale(long timeoutMs = 100) const;
40 : :
41 : : // Robust State Accessors (#267)
42 : 3240 : bool IsSessionActive() const { return m_sessionActive.load(std::memory_order_relaxed); }
43 : 6402 : bool IsInRealtime() const { return m_inRealtime.load(std::memory_order_relaxed); }
44 : 12788 : long GetSessionType() const { return m_currentSessionType.load(std::memory_order_relaxed); }
45 : 10 : unsigned char GetGamePhase() const { return m_currentGamePhase.load(std::memory_order_relaxed); }
46 : 6466 : signed char GetPlayerControl() const { return m_playerControl.load(std::memory_order_relaxed); }
47 : :
48 : : // Composite predicate: true only when the player is physically behind the wheel
49 : : // and actively in control (not paused, not in garage UI, not AI-controlled).
50 : : // Addresses the ESC-menu-while-on-track edge case (session transition.md).
51 : 3169 : bool IsPlayerActivelyDriving() const {
52 : 3169 : return m_inRealtime.load(std::memory_order_relaxed)
53 [ + + ]: 8 : && m_playerControl.load(std::memory_order_relaxed) == 0 // Player (not AI/replay/nobody)
54 [ + + + + ]: 3176 : && m_currentGamePhase.load(std::memory_order_relaxed) != 9; // 9 == Paused
55 : : }
56 : :
57 : : private:
58 : : struct TransitionState {
59 : : unsigned char optionsLocation = 255;
60 : : bool inRealtime = false;
61 : : unsigned char gamePhase = 255;
62 : : long session = -1;
63 : : signed char control = -2;
64 : : unsigned char pitState = 255;
65 : : char vehicleName[64] = { 0 };
66 : : char trackName[64] = { 0 };
67 : : char optionsPage[32] = { 0 };
68 : : float steeringRange = -1.0f;
69 : : bool playerHasVehicle = false; // investigation: quit-to-menu detection (#7.4a)
70 : : int numVehicles = -1; // investigation: quit-to-menu detection (#7.4b)
71 : : uint32_t eventState[SME_MAX] = { 0 };
72 : : std::chrono::steady_clock::time_point lastEventLogTime[SME_MAX];
73 : : } m_prevState;
74 : :
75 : : // CheckTransitions orchestrates the two-phase update:
76 : : // 1. _UpdateStateFromSnapshot — unconditionally syncs atomics from the SM buffer
77 : : // 2. _LogTransitions — detects changes vs m_prevState and emits log lines
78 : : void CheckTransitions(const SharedMemoryObjectOut& current);
79 : : void _UpdateStateFromSnapshot(const SharedMemoryObjectOut& current);
80 : : void _LogTransitions(const SharedMemoryObjectOut& current);
81 : :
82 : : // String-lookup helpers (extracted for reuse and testability)
83 : : static const char* SmeEventName(int eventIndex);
84 : : static const char* GamePhaseName(unsigned char phase);
85 : : static const char* SessionTypeName(long session);
86 : : static const char* ControlModeName(signed char control);
87 : : static const char* PitStateName(unsigned char pitState);
88 : :
89 : : friend class FFBEngineTests::GameConnectorTestAccessor;
90 : :
91 : : GameConnector();
92 : : ~GameConnector();
93 : :
94 : : SharedMemoryLayout* m_pSharedMemLayout = nullptr;
95 : : mutable std::optional<SafeSharedMemoryLock> m_smLock;
96 : : HANDLE m_hMapFile = NULL;
97 : : mutable HWND m_hwndGame = NULL;
98 : : DWORD m_processId = 0;
99 : :
100 : : std::atomic<bool> m_connected{false};
101 : : mutable std::recursive_mutex m_mutex;
102 : :
103 : : // Robust State Machine (#267)
104 : : std::atomic<bool> m_sessionActive{ false };
105 : : std::atomic<bool> m_inRealtime{ false };
106 : : std::atomic<long> m_currentSessionType{ -1 };
107 : : std::atomic<unsigned char> m_currentGamePhase{ 255 };
108 : : std::atomic<signed char> m_playerControl{ -2 };
109 : :
110 : : // Heartbeat for staleness detection (v0.7.15)
111 : : double m_lastElapsedTime = -1.0;
112 : : double m_lastSteering = 0.0; // Issue #184: Steering heartbeat
113 : : mutable std::chrono::steady_clock::time_point m_lastUpdateLocalTime;
114 : :
115 : : // Quit-to-menu detection (#7.5): armed when mInRealtime goes true→false.
116 : : // If SME_ENTER fires within the deadline window, we know the user quit to
117 : : // the main menu (not just returned to the garage monitor).
118 : : bool m_pendingMenuCheck = false;
119 : : std::chrono::steady_clock::time_point m_menuCheckDeadline;
120 : :
121 : : void _DisconnectLocked();
122 : : };
123 : : #endif // GAMECONNECTOR_H
|