Skip to content
This repository was archived by the owner on Jan 31, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
f984ce3
Refactor and enhance bHapticsManager architecture
nalathethird Nov 23, 2025
87892cd
Update bHapticsManager/HapticPlayerPatches.cs
nalathethird Nov 23, 2025
34ab9eb
Update bHapticsManager/DiagnosticPatches.cs
nalathethird Nov 23, 2025
de8ff82
Update bHapticsManager/DiagnosticPatches.cs
nalathethird Nov 23, 2025
b4c946d
Update bHapticsManager/RemoteHapticSource.cs
nalathethird Nov 23, 2025
e82763c
Update bHapticsManager/bHapticsManager.cs
nalathethird Nov 23, 2025
a0aef2d
Update bHapticsManager/ModernBHapticsWorkerThread.cs
nalathethird Nov 23, 2025
8b08ab3
Update bHapticsManager/TorsoMapperFix.cs
nalathethird Nov 23, 2025
babddda
Update bHapticsManager/HapticMethodPatches.cs
nalathethird Nov 23, 2025
098752e
Initial plan
Copilot Nov 23, 2025
be18ff7
Update bHapticsManager/bHapticsManager.cs
nalathethird Nov 23, 2025
7ac12de
Update bHapticsManager/bHapticsManager.cs
nalathethird Nov 23, 2025
3dd7d45
Update bHapticsManager/ModernBHapticsWorkerThread.cs
nalathethird Nov 23, 2025
d4c9ba3
Update bHapticsManager/bHapticsManager.cs
nalathethird Nov 23, 2025
48d889a
Initial plan
Copilot Nov 23, 2025
263d1a0
Update bHapticsManager/ModernBHapticsWorkerThread.cs
nalathethird Nov 23, 2025
df76a6f
Initial plan
Copilot Nov 23, 2025
4e12e48
Fix WorldFocused event handler race condition by unsubscribing after …
Copilot Nov 23, 2025
88e9073
Refactor WorldFocused handler to use local function for better readab…
Copilot Nov 23, 2025
f83e063
Simplify boolean comparison: replace '== false' with '!'
Copilot Nov 23, 2025
7bad13f
Add thread synchronization to DeviceEventHandler
Copilot Nov 23, 2025
9ff03b8
Add error handling to allow retry attempts on initialization failure
Copilot Nov 23, 2025
905da5b
Improve disposal pattern to prevent potential deadlock
Copilot Nov 23, 2025
095eaea
Use Interlocked.Exchange for thread-safe reset of _initialized flag
Copilot Nov 23, 2025
71c88c2
Merge branch 'bug/thread-not-started' into copilot/sub-pr-8
nalathethird Nov 23, 2025
fd616c6
Merge pull request #11 from nalathethird/copilot/sub-pr-8-another-one
nalathethird Nov 23, 2025
441d690
Update bHapticsManager/DeviceEventHandler.cs
nalathethird Nov 23, 2025
4fcf10e
Update bHapticsManager/DeviceEventHandler.cs
nalathethird Nov 23, 2025
e2e576d
Add _disposed checks inside locks and protect Initialize
Copilot Nov 23, 2025
9391801
Update bHapticsManager/bHapticsManager.cs
nalathethird Nov 23, 2025
03f6de8
Merge pull request #9 from nalathethird/copilot/sub-pr-8
nalathethird Nov 23, 2025
3b58dee
Merge pull request #10 from nalathethird/copilot/sub-pr-8-again
nalathethird Nov 23, 2025
03f5898
Update bHapticsManager/ModernBHapticsWorkerThread.cs
nalathethird Nov 23, 2025
377f8e3
Initial plan
Copilot Nov 23, 2025
c420dfb
Initial plan
Copilot Nov 23, 2025
dd95c31
Remove unused _devicePoints dictionary and _lock object from DeviceRe…
Copilot Nov 23, 2025
458d750
Fix race condition in WorldFocused event handler
Copilot Nov 23, 2025
b4e066c
Address user feedback: remove unused _devicePoints and adjust haptic …
Copilot Nov 23, 2025
788d3f5
Improve comments based on code review feedback
Copilot Nov 23, 2025
9f84bca
Update bHapticsManager/bHapticsManager.cs
nalathethird Nov 23, 2025
4ca2353
Update bHapticsManager/bHapticsManager.cs
nalathethird Nov 23, 2025
1b0fdf7
Update bHapticsManager/LegacyCompatibilityLayer.cs
nalathethird Nov 23, 2025
17939ec
Merge pull request #13 from nalathethird/copilot/sub-pr-8-another-one
nalathethird Nov 23, 2025
a1b03c2
Merge pull request #12 from nalathethird/copilot/sub-pr-8-again
nalathethird Nov 23, 2025
89b2654
Update bHapticsManager/bHapticsManager.cs
nalathethird Nov 23, 2025
86964da
Update bHapticsManager/LegacyCompatibilityLayer.cs
nalathethird Nov 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 108 additions & 22 deletions bHapticsManager/BHapticsConnection.cs
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();
Comment on lines +13 to +14
Copy link

Copilot AI Nov 23, 2025

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 as Lazy<T> or lock-based initialization.

Suggested change
private static BHapticsConnection _instance = null!;
public static BHapticsConnection Instance => _instance ??= new BHapticsConnection();
private static readonly Lazy<BHapticsConnection> _instance = new Lazy<BHapticsConnection>(() => new BHapticsConnection());
public static BHapticsConnection Instance => _instance.Value;

Copilot uses AI. Check for mistakes.

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
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instance methods are invoking events on an instance, but the Initialize() and Shutdown() methods that should be using these events are static. The event raising methods RaiseDeviceConnected and RaiseDeviceDisconnected will only work on the singleton instance, but there's no code path shown that actually calls these methods. Consider making the connection lifecycle instance-based or clarifying how events should be raised.

Copilot uses AI. Check for mistakes.

Comment on lines +12 to 35
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixing static and instance members in a singleton pattern is confusing and error-prone. The class has both instance events (DeviceConnected, DeviceDisconnected) and static methods (Initialize, Shutdown, StartWorkerThread). This creates ambiguity about which members operate on the singleton instance vs static state. Consider making all members either static or instance-based for consistency.

Copilot uses AI. Check for mistakes.
/// 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;
}
Comment thread
nalathethird marked this conversation as resolved.
Comment thread
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;
Expand All @@ -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;
}


Expand Down Expand Up @@ -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}");
Expand All @@ -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;
}
}
}

Expand Down
Loading