diff --git a/README.md b/README.md index 44f91a0..b9fd184 100644 --- a/README.md +++ b/README.md @@ -217,13 +217,13 @@ public partial class DataStoreManager : MonoBehaviour, IDataStoreManager   -When the generator synthesises the interface, it has to decide which members of your class show up on it. By default that's every public method and property. `[SingletonInclude]` and `[SingletonIgnore]` give you fine-grained control. They are mutually exclusive *per class* — using `[SingletonInclude]` anywhere on the class flips the generator into **explicit mode**. +When the generator synthesises the interface, it has to decide which members of your class show up on it. By default that's every public method, property, and event. `[SingletonInclude]` and `[SingletonIgnore]` give you fine-grained control. They are mutually exclusive *per class* — using `[SingletonInclude]` anywhere on the class flips the generator into **explicit mode**. > **Note** — Neither attribute applies when you supply your own interface via `[Singleton(typeof(IFoo))]`; in that case the interface contract is whatever you typed. **Auto mode** *(default — no `[SingletonInclude]` on the class)* -Every public instance method and property is on the interface. Use `[SingletonIgnore]` to hide individual members: +Every public instance method, property, and event is on the interface. Use `[SingletonIgnore]` to hide individual members: ```csharp [Singleton] @@ -255,7 +255,7 @@ public partial class SoundManager : MonoBehaviour **Constraints** -- Both attributes target methods and properties only. +- Both attributes target methods, properties, and events only. - `[SingletonInclude]` members must be **public** and **non-static** — the analyzer raises `ER0xxx` diagnostics otherwise. diff --git a/src/generators-src/EngineRoom.Generators/Helpers/SymbolFormatter.cs b/src/generators-src/EngineRoom.Generators/Helpers/SymbolFormatter.cs index 1b7aa67..cc0ddf8 100644 --- a/src/generators-src/EngineRoom.Generators/Helpers/SymbolFormatter.cs +++ b/src/generators-src/EngineRoom.Generators/Helpers/SymbolFormatter.cs @@ -34,6 +34,7 @@ public static string FormatAsInterfaceMember(ISymbol symbol) { IMethodSymbol method => FormatMethod(method), IPropertySymbol property => FormatProperty(property), + IEventSymbol @event => FormatEvent(@event), _ => string.Empty, }; } @@ -81,6 +82,17 @@ private static string FormatProperty(IPropertySymbol property) return builder.ToString(); } + private static string FormatEvent(IEventSymbol @event) + { + var builder = new StringBuilder(); + builder.Append("event "); + builder.Append(@event.Type.ToDisplayString(FullyQualifiedType)); + builder.Append(' '); + builder.Append(@event.Name); + builder.Append(';'); + return builder.ToString(); + } + private static string FormatParameter(IParameterSymbol parameter) { var builder = new StringBuilder(); diff --git a/src/generators-src/EngineRoom.Generators/Singleton/SingletonGenerator.cs b/src/generators-src/EngineRoom.Generators/Singleton/SingletonGenerator.cs index 336c13f..66ed6b9 100644 --- a/src/generators-src/EngineRoom.Generators/Singleton/SingletonGenerator.cs +++ b/src/generators-src/EngineRoom.Generators/Singleton/SingletonGenerator.cs @@ -186,6 +186,7 @@ private static bool IsAutoIncludeCandidate(INamedTypeSymbol classSymbol, ISymbol { IMethodSymbol method => method.MethodKind == MethodKind.Ordinary, IPropertySymbol property => !property.IsIndexer, + IEventSymbol => true, _ => false, }; } diff --git a/src/generators-unity/Assets/Demo/Singletons/Code/GameManager.cs b/src/generators-unity/Assets/Demo/Singletons/Code/GameManager.cs index cc82feb..049b236 100644 --- a/src/generators-unity/Assets/Demo/Singletons/Code/GameManager.cs +++ b/src/generators-unity/Assets/Demo/Singletons/Code/GameManager.cs @@ -1,3 +1,4 @@ +using System; using EngineRoom.Runtime.Singleton; using UnityEngine; @@ -6,6 +7,8 @@ namespace EngineRoom.Demo.Singletons [Singleton] public partial class GameManager : MonoBehaviour { + public event Action CountChanged; + public int Count => _count; [Dependency] private ISoundManager _soundManager; @@ -26,6 +29,7 @@ public void RegisterTap() _dataStoreManager.SetScore(_count); _soundManager.PlayTap(); _uiManager.SetCount(_count); + CountChanged?.Invoke(_count); } } } diff --git a/src/generators-unity/Assets/Demo/Singletons/Tests/EngineRoom.Demo.Singletons.Tests.asmdef b/src/generators-unity/Assets/Demo/Singletons/Tests/EngineRoom.Demo.Singletons.Tests.asmdef index cfc1f70..5c1ecd8 100644 --- a/src/generators-unity/Assets/Demo/Singletons/Tests/EngineRoom.Demo.Singletons.Tests.asmdef +++ b/src/generators-unity/Assets/Demo/Singletons/Tests/EngineRoom.Demo.Singletons.Tests.asmdef @@ -4,12 +4,9 @@ "references": [ "EngineRoom.Demo.Singletons", "EngineRoom.Generators.Runtime", - "UnityEngine.TestRunner", - "UnityEditor.TestRunner" - ], - "includePlatforms": [ - "Editor" + "UnityEngine.TestRunner" ], + "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": true, diff --git a/src/generators-unity/Assets/Demo/Singletons/Tests/GameManagerTests.cs b/src/generators-unity/Assets/Demo/Singletons/Tests/GameManagerTests.cs index 8ceeb21..cfc943c 100644 --- a/src/generators-unity/Assets/Demo/Singletons/Tests/GameManagerTests.cs +++ b/src/generators-unity/Assets/Demo/Singletons/Tests/GameManagerTests.cs @@ -1,12 +1,14 @@ +using System.Collections; using NUnit.Framework; using UnityEngine; +using UnityEngine.TestTools; namespace EngineRoom.Demo.Singletons.Tests { public class GameManagerTests { - [Test] - public void RegisterTap_IncrementsCount_AndNotifiesSoundAndUi() + [UnityTest] + public IEnumerator RegisterTap_IncrementsCount_AndNotifiesSoundAndUi() { var store = MockDataStoreManager.Install(); var sound = MockSoundManager.Install(); @@ -14,6 +16,8 @@ public void RegisterTap_IncrementsCount_AndNotifiesSoundAndUi() var game = GameManager.Create(); + yield return null; + try { game.RegisterTap(); @@ -28,5 +32,32 @@ public void RegisterTap_IncrementsCount_AndNotifiesSoundAndUi() Object.DestroyImmediate(((GameManager)game).gameObject); } } + + [UnityTest] + public IEnumerator RegisterTap_RaisesCountChangedEventOnInterface() + { + MockDataStoreManager.Install(); + MockSoundManager.Install(); + MockUIManager.Install(); + + var game = GameManager.Create(); + + yield return null; + + try + { + var observed = -1; + game.CountChanged += value => observed = value; + + game.RegisterTap(); + game.RegisterTap(); + + Assert.AreEqual(2, observed); + } + finally + { + Object.DestroyImmediate(((GameManager)game).gameObject); + } + } } } diff --git a/src/generators-unity/Packages/games.engine-room.generators/Runtime/Plugins/EngineRoom.Generators.dll b/src/generators-unity/Packages/games.engine-room.generators/Runtime/Plugins/EngineRoom.Generators.dll index e66297c..6460a01 100644 Binary files a/src/generators-unity/Packages/games.engine-room.generators/Runtime/Plugins/EngineRoom.Generators.dll and b/src/generators-unity/Packages/games.engine-room.generators/Runtime/Plugins/EngineRoom.Generators.dll differ diff --git a/src/generators-unity/Packages/games.engine-room.generators/Runtime/Singleton/Attributes/SingletonIgnoreAttribute.cs b/src/generators-unity/Packages/games.engine-room.generators/Runtime/Singleton/Attributes/SingletonIgnoreAttribute.cs index 63dc43e..d07247f 100644 --- a/src/generators-unity/Packages/games.engine-room.generators/Runtime/Singleton/Attributes/SingletonIgnoreAttribute.cs +++ b/src/generators-unity/Packages/games.engine-room.generators/Runtime/Singleton/Attributes/SingletonIgnoreAttribute.cs @@ -4,12 +4,12 @@ namespace EngineRoom.Runtime.Singleton { /// /// On a -decorated class that has no member - /// tagged with , marks a public method or - /// property to be excluded from the generated singleton interface. Has no - /// effect when the class is in explicit mode (i.e. uses + /// tagged with , marks a public method, + /// property, or event to be excluded from the generated singleton interface. + /// Has no effect when the class is in explicit mode (i.e. uses /// elsewhere). /// - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)] public sealed class SingletonIgnoreAttribute : Attribute { } diff --git a/src/generators-unity/Packages/games.engine-room.generators/Runtime/Singleton/Attributes/SingletonIncludeAttribute.cs b/src/generators-unity/Packages/games.engine-room.generators/Runtime/Singleton/Attributes/SingletonIncludeAttribute.cs index 2b1c1d9..54f6f9b 100644 --- a/src/generators-unity/Packages/games.engine-room.generators/Runtime/Singleton/Attributes/SingletonIncludeAttribute.cs +++ b/src/generators-unity/Packages/games.engine-room.generators/Runtime/Singleton/Attributes/SingletonIncludeAttribute.cs @@ -3,10 +3,10 @@ namespace EngineRoom.Runtime.Singleton { /// - /// Marks a method or property on a -decorated class + /// Marks a method, property, or event on a -decorated class /// to be projected onto the generated singleton interface. /// - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)] public sealed class SingletonIncludeAttribute : Attribute { }