-
Notifications
You must be signed in to change notification settings - Fork 0
Refactor and enhance bHapticsManager architecture - (v1.1.0) #8
base: main
Are you sure you want to change the base?
Changes from all commits
f984ce3
87892cd
34ab9eb
de8ff82
b4c946d
e82763c
a0aef2d
8b08ab3
babddda
098752e
be18ff7
7ac12de
3dd7d45
d4c9ba3
48d889a
263d1a0
df76a6f
4e12e48
88e9073
f83e063
7bad13f
9ff03b8
905da5b
095eaea
71c88c2
fd616c6
441d690
4fcf10e
e2e576d
9391801
03f6de8
3b58dee
03f5898
377f8e3
c420dfb
dd95c31
458d750
b4e066c
788d3f5
9f84bca
4ca2353
1b0fdf7
17939ec
a1b03c2
89b2654
86964da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,37 +1,54 @@ | ||
| // BHapticsConnection.cs | ||
| // Handles connection initialization to bHaptics Player | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using Elements.Core; | ||
| using FrooxEngine; | ||
| using ResoniteModLoader; | ||
| using ModernBHaptics = bHapticsLib; | ||
| using LegacyBHaptics = Bhaptics.Tact; | ||
|
|
||
| namespace bHapticsManager { | ||
|
|
||
| public static class BHapticsConnection { | ||
| public class BHapticsConnection { | ||
| private static BHapticsConnection _instance = null!; | ||
| public static BHapticsConnection Instance => _instance ??= new BHapticsConnection(); | ||
|
|
||
| public static readonly Dictionary<ModernBHaptics.PositionID, (bool isActive, DateTime lastCheck)> DeviceCache = new(); | ||
|
|
||
| private static ModernBHapticsWorkerThread _workerThread; | ||
| private static ModernBHapticsWorkerThread _workerThread = null!; | ||
| private static bool _isInitialized = false; | ||
| private static readonly object _shutdownLock = new(); | ||
| private static bool _isShuttingDown = false; | ||
|
|
||
| // Events for device connection/disconnection - used by DeviceEventHandler | ||
| public event Action<LegacyBHaptics.PositionType>? DeviceConnected; | ||
| public event Action<LegacyBHaptics.PositionType>? DeviceDisconnected; | ||
|
|
||
| // Event raising methods | ||
| internal void RaiseDeviceConnected(LegacyBHaptics.PositionType position) { | ||
| DeviceConnected?.Invoke(position); | ||
| } | ||
|
|
||
| internal void RaiseDeviceDisconnected(LegacyBHaptics.PositionType position) { | ||
| DeviceDisconnected?.Invoke(position); | ||
| } | ||
|
Comment on lines
+28
to
+34
|
||
|
|
||
|
Comment on lines
+12
to
35
|
||
| /// Initializes connection to bHaptics Player and subscribes to events. | ||
| /// Called once during mod initialization. | ||
|
|
||
| public static void Initialize() { | ||
| public static bool Initialize() { | ||
| if (_isInitialized) { | ||
| ResoniteMod.Warn("Already initialized - skipping duplicate connection"); | ||
| return; | ||
| return true; | ||
| } | ||
|
nalathethird marked this conversation as resolved.
nalathethird marked this conversation as resolved.
|
||
|
|
||
| DeviceEventHandler.Subscribe(); | ||
|
|
||
| // Connect to bHaptics Player | ||
| bool connected = ModernBHaptics.bHapticsManager.Connect("Resonite", "Resonite", true, 10); | ||
|
|
||
| if (!connected) { | ||
| ResoniteMod.Error("Failed to connect to bHaptics Player!"); | ||
| ResoniteMod.Error("Make sure bHaptics Player is running and try restarting Resonite."); | ||
| return; | ||
| return false; | ||
| } | ||
|
|
||
| _isInitialized = true; | ||
|
|
@@ -43,11 +60,28 @@ public static void Initialize() { | |
|
|
||
| foreach (ModernBHaptics.PositionID pos in Enum.GetValues(typeof(ModernBHaptics.PositionID))) { | ||
| if (ModernBHaptics.bHapticsManager.IsDeviceConnected(pos)) { | ||
| ResoniteMod.Msg($" - {pos} device ready"); | ||
| ResoniteMod.Debug($"Device {pos} ready"); | ||
| // Add to cache | ||
| DeviceCache[pos] = (true, DateTime.Now); | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| public List<LegacyBHaptics.PositionType> GetConnectedDevices() { | ||
| var connectedDevices = new List<LegacyBHaptics.PositionType>(); | ||
|
|
||
| foreach (ModernBHaptics.PositionID pos in Enum.GetValues(typeof(ModernBHaptics.PositionID))) { | ||
| if (ModernBHaptics.bHapticsManager.IsDeviceConnected(pos)) { | ||
| var legacyPos = PositionMapper.MapModernToLegacy(pos); | ||
| if (!connectedDevices.Contains(legacyPos)) { | ||
| connectedDevices.Add(legacyPos); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return connectedDevices; | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -80,7 +114,7 @@ public static void StartWorkerThread() { | |
| _workerThread = new ModernBHapticsWorkerThread(inputInterface); | ||
| _workerThread.Start(); | ||
|
|
||
| ResoniteMod.Msg("Worker thread started successfully"); | ||
| ResoniteMod.Debug("Worker thread started successfully"); | ||
| } | ||
| catch (Exception ex) { | ||
| ResoniteMod.Error($"Failed to start worker thread: {ex.Message}"); | ||
|
|
@@ -90,25 +124,77 @@ public static void StartWorkerThread() { | |
| /// Shuts down the connection to bHaptics Player, stopping all patterns and clearing the device cache. | ||
|
|
||
| public static void Shutdown() { | ||
| lock (_shutdownLock) { | ||
| if (_isShuttingDown) { | ||
| ResoniteMod.Warn("Shutdown already in progress"); | ||
| return; | ||
| } | ||
| _isShuttingDown = true; | ||
| } | ||
|
|
||
| try { | ||
| _workerThread?.Stop(); | ||
| _workerThread = null; | ||
| ResoniteMod.Debug("Starting bHaptics connection shutdown..."); | ||
|
|
||
| // Stop worker thread first | ||
| if (_workerThread != null) { | ||
| try { | ||
| ResoniteMod.Debug("Stopping worker thread..."); | ||
| _workerThread.Stop(); | ||
| _workerThread = null!; | ||
| ResoniteMod.Debug("Worker thread stopped"); | ||
| } | ||
| catch (Exception ex) { | ||
| ResoniteMod.Error($"Error stopping worker thread: {ex}"); | ||
| } | ||
| } | ||
|
|
||
| ModernBHaptics.bHapticsManager.StopPlayingAll(); | ||
| // Stop all haptic playback | ||
| try { | ||
| ResoniteMod.Debug("Stopping all haptic playback..."); | ||
| ModernBHaptics.bHapticsManager.StopPlayingAll(); | ||
| ResoniteMod.Debug("Haptic playback stopped"); | ||
| } | ||
| catch (Exception ex) { | ||
| ResoniteMod.Error($"Error stopping haptic playback: {ex}"); | ||
| } | ||
|
|
||
| bool disconnected = ModernBHaptics.bHapticsManager.Disconnect(); | ||
| // Small delay to ensure all patterns are stopped | ||
| System.Threading.Thread.Sleep(100); | ||
|
|
||
| // Disconnect from bHaptics Player | ||
| try { | ||
| ResoniteMod.Debug("Disconnecting from bHaptics Player..."); | ||
| bool disconnected = ModernBHaptics.bHapticsManager.Disconnect(); | ||
|
|
||
| if (disconnected) { | ||
| ResoniteMod.Debug("Disconnected from bHaptics Player successfully"); | ||
| } else { | ||
| ResoniteMod.Warn("Disconnect returned false - may already be disconnected"); | ||
| } | ||
| } | ||
| catch (Exception ex) { | ||
| ResoniteMod.Error($"Error disconnecting from bHaptics Player: {ex}"); | ||
| } | ||
|
|
||
| if (disconnected) { | ||
| ResoniteMod.Msg("Disconnected successfully"); | ||
| } else { | ||
| ResoniteMod.Warn("Disconnect returned false"); | ||
| // Clear caches | ||
| try { | ||
| DeviceCache.Clear(); | ||
| ResoniteMod.Debug("Device cache cleared"); | ||
| } | ||
| catch (Exception ex) { | ||
| ResoniteMod.Error($"Error clearing device cache: {ex}"); | ||
| } | ||
|
|
||
| DeviceCache.Clear(); | ||
| _isInitialized = false; | ||
| ResoniteMod.Msg("bHaptics connection shutdown complete"); | ||
| } | ||
| catch (Exception ex) { | ||
| ResoniteMod.Error($"Error during shutdown: {ex.Message}"); | ||
| ResoniteMod.Error($"Error during bHaptics connection shutdown: {ex}"); | ||
| } | ||
| finally { | ||
| lock (_shutdownLock) { | ||
| _isShuttingDown = false; | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-thread-safe singleton implementation. The pattern
_instance ??= new BHapticsConnection()is not thread-safe and could result in multiple instances being created if accessed concurrently during initialization. Use a thread-safe initialization pattern such asLazy<T>or lock-based initialization.