Skip to content

Commit 879aef6

Browse files
authored
Merge pull request #25 from Immersive-Plugins-Team/roosevelt
Roosevelt
2 parents 9e9ae60 + e23f830 commit 879aef6

16 files changed

Lines changed: 223 additions & 77 deletions

IPT.Common/BasePlugin.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using LSPD_First_Response.Mod.API;
2+
using System;
3+
4+
namespace IPT.Common
5+
{
6+
/// <summary>
7+
/// A robust base class for LSPDFR plugins that correctly handles the RPH/LSPDFR lifecycle.
8+
/// </summary>
9+
public abstract class PluginBase : Plugin
10+
{
11+
/// <summary>
12+
/// Called once by RPH when the plugin is first loaded. This is the only time this method is called.
13+
/// Use it to create your composition root and other long-lived objects.
14+
/// </summary>
15+
public abstract void OnPluginLoaded();
16+
17+
/// <summary>
18+
/// Called once by RPH when the plugin is about to be fully unloaded from the AppDomain.
19+
/// Use it to dispose of all resources.
20+
/// </summary>
21+
public abstract void OnPluginUnloaded();
22+
23+
/// <summary>
24+
/// Called every time the player goes on duty.
25+
/// Use it to start your plugin's fibers and services.
26+
/// </summary>
27+
public abstract void OnDuty();
28+
29+
/// <summary>
30+
/// Called every time the player goes off duty.
31+
/// Use it to stop your plugin's fibers and services.
32+
/// </summary>
33+
public abstract void OffDuty();
34+
35+
/// <summary>
36+
/// The RPH entry point. Do not override this in your derived class.
37+
/// </summary>
38+
public override sealed void Initialize()
39+
{
40+
// The base class handles all the complex wiring.
41+
Functions.OnOnDutyStateChanged += OnDutyStateChangedHandler;
42+
AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
43+
OnPluginLoaded();
44+
}
45+
46+
/// <summary>
47+
/// Fucking pointless.
48+
/// </summary>
49+
public override sealed void Finally() { }
50+
51+
private void OnDutyStateChangedHandler(bool onDuty)
52+
{
53+
if (onDuty) OnDuty();
54+
else OffDuty();
55+
}
56+
57+
private void OnDomainUnload(object sender, EventArgs e)
58+
{
59+
OnPluginUnloaded();
60+
}
61+
}
62+
}

IPT.Common/Core/EventAggregator.cs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace IPT.Common.Core
6+
{
7+
/// <summary>
8+
/// A thread-safe event aggregator for decoupled messaging.
9+
/// Enables components to communicate without holding direct references to each other.
10+
/// </summary>
11+
public sealed class EventAggregator
12+
{
13+
private readonly Dictionary<Type, List<object>> _subscriptions = new Dictionary<Type, List<object>>();
14+
private readonly object _lock = new object();
15+
16+
public EventAggregator() { }
17+
18+
/// <summary>
19+
/// Publishes a message to all subscribers of the message type.
20+
/// </summary>
21+
public void Publish<TMessage>(TMessage message)
22+
{
23+
var messageType = typeof(TMessage);
24+
List<object> subscribers;
25+
26+
lock (_lock)
27+
{
28+
if (!_subscriptions.ContainsKey(messageType)) return;
29+
subscribers = _subscriptions[messageType].ToList();
30+
}
31+
32+
foreach (var subscriber in subscribers)
33+
{
34+
if (subscriber is Action<TMessage> action) action(message);
35+
}
36+
}
37+
38+
/// <summary>
39+
/// Subscribes a callback to a specific message type.
40+
/// </summary>
41+
/// <returns>An IDisposable token that can be used to unsubscribe.</returns>
42+
public IDisposable Subscribe<TMessage>(Action<TMessage> action)
43+
{
44+
var messageType = typeof(TMessage);
45+
46+
lock (_lock)
47+
{
48+
if (!_subscriptions.ContainsKey(messageType)) _subscriptions[messageType] = new List<object>();
49+
_subscriptions[messageType].Add(action);
50+
}
51+
52+
return new Subscription<TMessage>(this, action);
53+
}
54+
55+
private void Unsubscribe<TMessage>(Action<TMessage> action)
56+
{
57+
var messageType = typeof(TMessage);
58+
59+
lock (_lock)
60+
{
61+
if (_subscriptions.ContainsKey(messageType))
62+
{
63+
_subscriptions[messageType].Remove(action);
64+
if (_subscriptions[messageType].Count == 0) _subscriptions.Remove(messageType);
65+
}
66+
}
67+
}
68+
69+
private class Subscription<TMessage> : IDisposable
70+
{
71+
private readonly EventAggregator _eventAggregator;
72+
private readonly Action<TMessage> _action;
73+
private bool _isDisposed;
74+
75+
public Subscription(EventAggregator eventAggregator, Action<TMessage> action)
76+
{
77+
_eventAggregator = eventAggregator;
78+
_action = action;
79+
}
80+
81+
public void Dispose()
82+
{
83+
if (!_isDisposed)
84+
{
85+
_eventAggregator.Unsubscribe(_action);
86+
_isDisposed = true;
87+
}
88+
}
89+
}
90+
}
91+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System.Collections.Generic;
2+
using System.ComponentModel;
3+
using System.Runtime.CompilerServices;
4+
using IPT.Common.Fibers;
5+
6+
namespace IPT.Common.Fibers
7+
{
8+
/// <summary>
9+
/// A base class for models that implements the INotifyPropertyChanged interface.
10+
/// </summary>
11+
public abstract class ObservableFiber : GenericFiber, INotifyPropertyChanged
12+
{
13+
protected ObservableFiber(string name, int interval) : base(name, interval) { }
14+
15+
public event PropertyChangedEventHandler PropertyChanged;
16+
17+
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
18+
{
19+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
20+
}
21+
22+
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
23+
{
24+
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
25+
field = value;
26+
OnPropertyChanged(propertyName);
27+
return true;
28+
}
29+
}
30+
}

IPT.Common/Handlers/InputHandler.cs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
using System;
2-
using System.Collections.Generic;
32
using IPT.Common.Fibers;
43
using IPT.Common.User;
54
using IPT.Common.User.Inputs;
6-
using Rage;
75

86
namespace IPT.Common.Handlers
97
{
@@ -12,9 +10,9 @@ namespace IPT.Common.Handlers
1210
/// </summary>
1311
public class InputHandler : GenericFiber
1412
{
15-
public event Action<GenericCombo> OnInputPressed;
16-
public event Action<GenericCombo> OnInputReleased;
17-
public event Action<HoldableCombo, bool> OnHoldableInput;
13+
public event Action<GenericCombo> ComboPressed;
14+
public event Action<GenericCombo> ComboReleased;
15+
public event Action<HoldableCombo, bool> HoldableComboInput;
1816

1917
private readonly Configuration _config;
2018

@@ -45,11 +43,11 @@ protected override void DoSomething()
4543
switch (holdable.Check())
4644
{
4745
case InputState.ShortPress:
48-
OnHoldableInput?.Invoke(holdable, false);
46+
HoldableComboInput?.Invoke(holdable, false);
4947
API.Events.FireHoldableUserInput(holdable, false); // legacy
5048
break;
5149
case InputState.LongPress:
52-
OnHoldableInput?.Invoke(holdable, true);
50+
HoldableComboInput?.Invoke(holdable, true);
5351
API.Events.FireHoldableUserInput(holdable, true); // legacy
5452
break;
5553
}
@@ -60,11 +58,11 @@ protected override void DoSomething()
6058
switch (combo.Check())
6159
{
6260
case InputState.Pressed:
63-
OnInputPressed?.Invoke(combo);
61+
ComboPressed?.Invoke(combo);
6462
API.Events.FireUserInputChanged(combo);
6563
break;
6664
case InputState.Released:
67-
OnInputReleased?.Invoke(combo);
65+
ComboReleased?.Invoke(combo);
6866
API.Events.FireUserInputChanged(combo);
6967
break;
7068
}

IPT.Common/IPT.Common.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@
8787
<Compile Include="API\MathHelperExtensions.cs" />
8888
<Compile Include="API\Notifications.cs" />
8989
<Compile Include="API\Player.cs" />
90+
<Compile Include="BasePlugin.cs" />
9091
<Compile Include="Callouts.cs" />
92+
<Compile Include="Core\EventAggregator.cs" />
9193
<Compile Include="Enums.cs" />
9294
<Compile Include="Extensions.cs" />
9395
<Compile Include="Fibers\ComboFiber.cs" />
@@ -96,6 +98,7 @@
9698
<Compile Include="CommonPlugin.cs" />
9799
<Compile Include="Handlers\PlayerHandler.cs" />
98100
<Compile Include="Handlers\PlayerStateManager.cs" />
101+
<Compile Include="Fibers\ObservableFiber.cs" />
99102
<Compile Include="Properties\AssemblyInfo.cs" />
100103
<Compile Include="User\Configuration.cs" />
101104
<Compile Include="User\Inputs\ButtonCombo.cs" />
@@ -121,7 +124,7 @@
121124
<None Include="packages.config" />
122125
</ItemGroup>
123126
<ItemGroup>
124-
<Folder Include="Internal\" />
127+
<Folder Include="Models\" />
125128
</ItemGroup>
126129
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
127130
<PropertyGroup>

IPT.Common/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@
3131
// You can specify all the values or you can default the Build and Revision Numbers
3232
// by using the '*' as shown below:
3333
// [assembly: AssemblyVersion("1.0.*")]
34-
[assembly: AssemblyVersion("1.5.0.0")]
35-
[assembly: AssemblyFileVersion("1.5.0.0")]
34+
[assembly: AssemblyVersion("1.5.0.4")]
35+
[assembly: AssemblyFileVersion("1.5.0.4")]

IPT.Common/User/Configuration.cs

Lines changed: 16 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,62 +8,40 @@
88

99
namespace IPT.Common.User
1010
{
11-
/// <summary>
12-
/// A base class for generating a plugin-specific Configuration class.
13-
/// </summary>
14-
public abstract class Configuration
11+
public abstract class Configuration : IDisposable
1512
{
16-
#pragma warning disable S1104, SA1401, CS1591, SA1600
13+
public delegate void SettingChangedEventHandler(Setting setting);
14+
public event SettingChangedEventHandler SettingChanged;
1715
public SettingInt LogLevel = Logging.GetLogLevelSetting();
18-
#pragma warning restore S1104, SA1401, CS1591, SA1600
1916

20-
private readonly List<Setting> allSettings;
17+
private readonly List<Setting> _allSettings;
2118

22-
/// <summary>
23-
/// Initializes a new instance of the <see cref="Configuration"/> class.
24-
/// </summary>
2519
protected Configuration()
2620
{
27-
this.allSettings = this.GetAllSettings();
21+
_allSettings = GetAllSettings();
22+
foreach (var setting in _allSettings) setting.OnValueChanged += OnSettingValueChanged;
2823
}
2924

30-
/// <summary>
31-
/// Gets a list of all settings objects.
32-
/// </summary>
33-
public List<Setting> AllSettings
25+
public List<Setting> AllSettings { get => _allSettings; }
26+
27+
public void Dispose()
3428
{
35-
get
36-
{
37-
return this.allSettings;
38-
}
29+
foreach (var setting in _allSettings) setting.OnValueChanged -= OnSettingValueChanged;
3930
}
4031

41-
/// <summary>
42-
/// Gets a list of generic combos defined in the settings.
43-
/// </summary>
44-
/// <returns>A list of combos.</returns>
4532
public List<GenericCombo> GetInputCombos()
4633
{
4734
var combos = new List<GenericCombo>();
4835
foreach (var setting in this.AllSettings)
4936
{
50-
if (setting.GetValue() is GenericCombo combo)
51-
{
52-
combos.Add(combo);
53-
}
37+
if (setting.GetValue() is GenericCombo combo) combos.Add(combo);
5438
}
5539

5640
return combos;
5741
}
5842

59-
/// <summary>
60-
/// Load the settings.
61-
/// </summary>
6243
public abstract void Load();
6344

64-
/// <summary>
65-
/// Logs all of the settings.
66-
/// </summary>
6745
public void Log()
6846
{
6947
Logging.Info("============================================================");
@@ -77,10 +55,6 @@ public void Log()
7755
Logging.Info("============================================================");
7856
}
7957

80-
/// <summary>
81-
/// Loads settings from INI file.
82-
/// </summary>
83-
/// <param name="filename">The filename of the INI file. Expects path relative to the GTAV folder.</param>
8458
protected void LoadINI(string filename)
8559
{
8660
var ini = new InitializationFile(filename);
@@ -100,37 +74,25 @@ protected void LoadINI(string filename)
10074
}
10175
}
10276

103-
/// <summary>
104-
/// Saves settings to the INI file.
105-
/// </summary>
106-
/// <param name="filename">The filename of the INI file. Expects path relative to the GTAV folder.</param>
10777
protected void SaveINI(string filename)
10878
{
10979
InitializationFile ini = new InitializationFile(filename);
110-
if (ini.Exists())
111-
{
112-
ini.Delete();
113-
}
114-
80+
if (ini.Exists()) ini.Delete();
11581
ini.Create();
116-
foreach (var entry in this.AllSettings)
117-
{
118-
entry.Save(ini);
119-
}
82+
foreach (var entry in this.AllSettings) entry.Save(ini);
12083
}
12184

12285
private List<Setting> GetAllSettings()
12386
{
12487
var settings = new List<Setting>();
12588
foreach (var field in this.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance))
12689
{
127-
if (field.GetValue(this) is Setting setting)
128-
{
129-
settings.Add(setting);
130-
}
90+
if (field.GetValue(this) is Setting setting) settings.Add(setting);
13191
}
13292

13393
return settings;
13494
}
95+
96+
private void OnSettingValueChanged(Setting setting) => SettingChanged?.Invoke(setting);
13597
}
13698
}

0 commit comments

Comments
 (0)