From 63c17fde44cee4f27322cdca677bb3d264cc1709 Mon Sep 17 00:00:00 2001 From: mcocdaa <2021137961@qq.com> Date: Sat, 7 Nov 2026 04:32:14 +0100 Subject: [PATCH] feat(bridge-rimtalk): migrate onto Core Bridge Kit & open skip-check channels, add CI test workflow refactor: delegate bridge coordination and detection to Core shared components Replace the local BridgeModuleCoordinator logic and RimTalkDetector caching with Core's ExternalModBridgeCoordinator and SoftDependencyDetector, and migrate skip checks to IChannelSkipCheck with channel constants. Use DialogueTriggerKinds constants instead of string literals, and drop the unused RimTalkEvent invalidation trigger from the context pull bridge. Add a CI workflow that runs unit tests via the shared reusable workflow. --- .github/workflows/test.yml | 22 +++++++ Source/Bridge/BridgeModuleCoordinator.cs | 62 ++++++------------- Source/Bridge/ContextPullBridge.cs | 2 +- Source/Bridge/DialogueGate.cs | 7 ++- Source/Bridge/RimTalkBridgeCoordinator.cs | 20 +++--- Source/Detection/RimTalkDetector.cs | 57 +++++------------ Source/Extensions/RimTalkDialogueSkipCheck.cs | 5 +- .../Extensions/RimTalkFloatMenuSkipCheck.cs | 3 +- 8 files changed, 77 insertions(+), 101 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..fb71105 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,22 @@ +name: Unit Tests + +on: + push: + branches: [main, master] + paths: + - 'Source/**' + - 'Tests/**' + - 'ArchTests/**' + - '.github/workflows/test.yml' + pull_request: + branches: [main, master] + paths: + - 'Source/**' + - 'Tests/**' + - 'ArchTests/**' + - '.github/workflows/test.yml' + +jobs: + test: + uses: RimWorld-RimMind-Mod/Workflows/.github/workflows/test.yml@main + secrets: inherit diff --git a/Source/Bridge/BridgeModuleCoordinator.cs b/Source/Bridge/BridgeModuleCoordinator.cs index ac81a81..ac4a385 100644 --- a/Source/Bridge/BridgeModuleCoordinator.cs +++ b/Source/Bridge/BridgeModuleCoordinator.cs @@ -1,15 +1,13 @@ using System; using System.Collections.Generic; using RimMind.Application.Common.Interfaces.Extension; +using RimMind.Application.Features.Bridges; namespace RimMind.Bridge.RimTalk.Bridge { internal sealed class BridgeModuleCoordinator { - private readonly IReadOnlyList _modules; - private readonly Func _isDependencyActive; - private readonly Action _log; - private readonly Action _warn; + private readonly ExternalModBridgeCoordinator _inner; public BridgeModuleCoordinator( IReadOnlyList modules, @@ -17,48 +15,26 @@ public BridgeModuleCoordinator( Action log, Action warn) { - _modules = modules ?? throw new ArgumentNullException(nameof(modules)); - _isDependencyActive = isDependencyActive - ?? throw new ArgumentNullException(nameof(isDependencyActive)); - _log = log ?? throw new ArgumentNullException(nameof(log)); - _warn = warn ?? throw new ArgumentNullException(nameof(warn)); + if (modules == null) throw new ArgumentNullException(nameof(modules)); + if (isDependencyActive == null) throw new ArgumentNullException(nameof(isDependencyActive)); + if (log == null) throw new ArgumentNullException(nameof(log)); + if (warn == null) throw new ArgumentNullException(nameof(warn)); + + _inner = new ExternalModBridgeCoordinator( + isDependencyActive, + modules, + gates: null, + logInfo: log, + logWarn: warn, + inactiveMessage: "RimTalk not active, bridge modules skipped."); } - public void RegisterAll() - { - if (!_isDependencyActive()) - { - _log("RimTalk not active, bridge modules skipped."); - return; - } + public bool AreAllRegistered => _inner.AreAllRegistered; - foreach (IBridgeModule module in _modules) - { - try - { - module.Register(); - _log($"{module.Id} registered={module.IsRegistered}."); - } - catch (Exception ex) - { - _warn($"{module.Id} registration failed: {ex.Message}"); - } - } - } + public void RegisterAll() => _inner.RegisterAll(); - public void UnregisterAll() - { - foreach (IBridgeModule module in _modules) - { - try - { - module.Unregister(); - } - catch (Exception ex) - { - _warn($"{module.Id} cleanup failed: {ex.Message}"); - } - } - } + public void UnregisterAll() => _inner.UnregisterAll(); + + public void Refresh() => _inner.Refresh(); } } diff --git a/Source/Bridge/ContextPullBridge.cs b/Source/Bridge/ContextPullBridge.cs index 9962d7b..34871c0 100644 --- a/Source/Bridge/ContextPullBridge.cs +++ b/Source/Bridge/ContextPullBridge.cs @@ -83,7 +83,7 @@ private void RegisterRimTalkHistoryProvider() if (!RimMindPawnLookup.IsActiveSpawnedColonist(pawn)) return null; return BuildRimTalkHistoryContext(pawn!); }, null, ct: ct).ConfigureAwait(false); - }, ModId, stalenessTicks: 3000, invalidationTriggers: new[] { "RimTalkEvent" })); + }, ModId, stalenessTicks: 3000)); // RimTalk history changes are external (no bus event): staleness only } private bool ResolveTypes() diff --git a/Source/Bridge/DialogueGate.cs b/Source/Bridge/DialogueGate.cs index 993a0e7..8277b88 100644 --- a/Source/Bridge/DialogueGate.cs +++ b/Source/Bridge/DialogueGate.cs @@ -1,3 +1,4 @@ +using RimMind.Application.Common.Constants; using RimMind.Bridge.RimTalk.Detection; using RimMind.Bridge.RimTalk.Settings; using Verse; @@ -16,9 +17,9 @@ public static bool ShouldSkipDialogue(Pawn? pawn, string? triggerType) return triggerType switch { - "Chitchat" => settings.skipChitchat, - "Auto" => settings.skipAutoDialogue, - "PlayerInput" => settings.skipPlayerDialogue && !settings.forceRimMindPlayerDialogue, + DialogueTriggerKinds.Chitchat => settings.skipChitchat, + DialogueTriggerKinds.Auto => settings.skipAutoDialogue, + DialogueTriggerKinds.PlayerInput => settings.skipPlayerDialogue && !settings.forceRimMindPlayerDialogue, _ => false }; } diff --git a/Source/Bridge/RimTalkBridgeCoordinator.cs b/Source/Bridge/RimTalkBridgeCoordinator.cs index cceb5cc..eb41f66 100644 --- a/Source/Bridge/RimTalkBridgeCoordinator.cs +++ b/Source/Bridge/RimTalkBridgeCoordinator.cs @@ -1,16 +1,16 @@ using System.Collections.Generic; -using RimMind.Bridge.RimTalk.Detection; using RimMind.Application.Common.Interfaces.Extension; +using RimMind.Application.Features.Bridges; +using RimMind.Bridge.RimTalk.Detection; using Verse; namespace RimMind.Bridge.RimTalk.Bridge { /// - /// Coordinates registration of RimTalk bridge modules. + /// Coordinates registration of RimTalk bridge modules using Core's . /// Modules are iterated uniformly; each module decides whether to actually register /// based on its own settings/detector checks inside . - /// Unregister iterates all modules but each module's Unregister is a no-op if not registered, - /// making Register/Unregister symmetric (previously Register was conditional, Unregister unconditional). + /// Unregister iterates all modules symmetrically. /// public static class RimTalkBridgeCoordinator { @@ -26,12 +26,14 @@ private static List GetModules() => new PersonaPushBridge() }; - private static BridgeModuleCoordinator CreateCoordinator() => - new BridgeModuleCoordinator( - GetModules(), + private static ExternalModBridgeCoordinator CreateCoordinator() => + new ExternalModBridgeCoordinator( () => RimTalkDetector.IsRimTalkActive, - message => Log.Message($"[RimMind-Bridge-RimTalk] {message}"), - message => Log.Warning($"[RimMind-Bridge-RimTalk] {message}")); + GetModules(), + gates: null, + logInfo: message => Log.Message($"[RimMind-Bridge-RimTalk] {message}"), + logWarn: message => Log.Warning($"[RimMind-Bridge-RimTalk] {message}"), + inactiveMessage: "RimTalk not active, bridge modules skipped."); public static void Register() { diff --git a/Source/Detection/RimTalkDetector.cs b/Source/Detection/RimTalkDetector.cs index 7337dc3..34da57c 100644 --- a/Source/Detection/RimTalkDetector.cs +++ b/Source/Detection/RimTalkDetector.cs @@ -1,5 +1,5 @@ -using System; using HarmonyLib; +using RimMind.Application.Features.Bridges; using Verse; namespace RimMind.Bridge.RimTalk.Detection @@ -7,11 +7,17 @@ namespace RimMind.Bridge.RimTalk.Detection public static class RimTalkDetector { public const string RimTalkPackageId = "cj.rimtalk"; - - private static bool? _cachedResult; - private static int _cacheTick = -1; + private const string RimTalkApiTypeName = "RimTalk.API.RimTalkPromptAPI"; private const int CacheIntervalTicks = 6000; + internal static readonly SoftDependencyDetector Detector = new SoftDependencyDetector( + RimTalkPackageId, + new[] { RimTalkApiTypeName }, + ModsConfig.IsActive, + AccessTools.TypeByName, + () => SafeTicksGame, + CacheIntervalTicks); + private static int SafeTicksGame { get @@ -31,52 +37,19 @@ private static int SafeTicksGame public static bool IsRimTalkActive { - get - { - int now = SafeTicksGame; - if (_cachedResult == null || (now > 0 && now - _cacheTick > CacheIntervalTicks)) - { - _cachedResult = ModsConfig.IsActive(RimTalkPackageId); - if (now > 0) - { - _cacheTick = now; - } - } - return _cachedResult.Value; - } - internal set - { - _cachedResult = value; - } + get => Detector.IsModActive(); + internal set => Detector.SetForcedModActiveForTesting(value); } public static void InvalidateCache() { - _cachedResult = null; - _cacheTick = -1; - _apiAvailable = null; - _apiChecked = false; + Detector.ResetCache(); } - private static bool? _apiAvailable; - private static bool _apiChecked; - public static bool IsRimTalkApiAvailable { - get - { - if (!_apiChecked) - { - _apiAvailable = IsRimTalkActive && AccessTools.TypeByName("RimTalk.API.RimTalkPromptAPI") != null; - _apiChecked = true; - } - return _apiAvailable ?? false; - } - internal set - { - _apiAvailable = value; - _apiChecked = true; - } + get => Detector.IsAvailable(); + internal set => Detector.SetForcedApiAvailableForTesting(value); } } } diff --git a/Source/Extensions/RimTalkDialogueSkipCheck.cs b/Source/Extensions/RimTalkDialogueSkipCheck.cs index 995bd52..8cf140e 100644 --- a/Source/Extensions/RimTalkDialogueSkipCheck.cs +++ b/Source/Extensions/RimTalkDialogueSkipCheck.cs @@ -2,11 +2,12 @@ namespace RimMind.Bridge.RimTalk { - internal sealed class RimTalkDialogueSkipCheck : ISkipCheck + internal sealed class RimTalkDialogueSkipCheck : IChannelSkipCheck { public string Id => "rimtalk_bridge_dialogue"; public string OwnerModId => "RimMindBridgeRimTalk"; public SkipCheckKind Kind => SkipCheckKind.Dialogue; - public bool ShouldSkip(in SkipCheckArgs args) => Bridge.DialogueGate.ShouldSkipDialogue(args.Pawn as Verse.Pawn, args.Trigger); + public string Channel => SkipCheckChannels.Dialogue; + public bool ShouldSkip(in SkipCheckArgs args) => Bridge.DialogueGate.ShouldSkipDialogue(args.Pawn as Verse.Pawn, args.Trigger ?? args.Tag); } } diff --git a/Source/Extensions/RimTalkFloatMenuSkipCheck.cs b/Source/Extensions/RimTalkFloatMenuSkipCheck.cs index 29778ea..a64dffb 100644 --- a/Source/Extensions/RimTalkFloatMenuSkipCheck.cs +++ b/Source/Extensions/RimTalkFloatMenuSkipCheck.cs @@ -2,11 +2,12 @@ namespace RimMind.Bridge.RimTalk { - internal sealed class RimTalkFloatMenuSkipCheck : ISkipCheck + internal sealed class RimTalkFloatMenuSkipCheck : IChannelSkipCheck { public string Id => "rimtalk_bridge_floatmenu"; public string OwnerModId => "RimMindBridgeRimTalk"; public SkipCheckKind Kind => SkipCheckKind.FloatMenu; + public string Channel => SkipCheckChannels.FloatMenu; public bool ShouldSkip(in SkipCheckArgs args) => Bridge.DialogueGate.ShouldSkipFloatMenuOption(); } }