| | 1 | | using System; |
| | 2 | | using System.Collections.Generic; |
| | 3 | | using UnityEngine; |
| | 4 | | using UnityEngine.Assertions; |
| | 5 | |
|
| | 6 | | namespace DCL |
| | 7 | | { |
| | 8 | | public delegate IPlugin PluginBuilder(); |
| | 9 | |
|
| | 10 | | /// <summary> |
| | 11 | | /// This class implements a plugin system pattern described in: |
| | 12 | | /// https://github.com/decentraland/adr/blob/main/docs/ADR-56-plugin-system.md |
| | 13 | | /// |
| | 14 | | /// - Many plugins can share the same feature flag |
| | 15 | | /// - Plugins are registered by using a PluginBuilder delegate that must create and return the plugin instance. |
| | 16 | | /// - Any active plugin is an instantiated plugin. A disabled plugin is a never created or disposed plugin. |
| | 17 | | /// - No Initialize() pattern. Plugins abide to RAII idiom. |
| | 18 | | /// - Feature flags can be set automatically by using SetFeatureFlagsData. |
| | 19 | | /// </summary> |
| | 20 | | public class PluginSystem : IDisposable |
| | 21 | | { |
| 1 | 22 | | private static ILogger logger = new Logger(Debug.unityLogger); |
| 8 | 23 | | internal PluginGroup allPlugins = new PluginGroup(); |
| 8 | 24 | | private Dictionary<string, PluginGroup> pluginGroupByFlag = new Dictionary<string, PluginGroup>(); |
| | 25 | | private BaseVariable<FeatureFlag> featureFlagsDataSource; |
| | 26 | |
|
| | 27 | | /// <summary> |
| | 28 | | /// Returns true if the plugin defined by the PluginBuilder delegate is currently enabled. |
| | 29 | | /// |
| | 30 | | /// An enabled plugin is an instantiated and currently active plugin. |
| | 31 | | /// </summary> |
| | 32 | | /// <param name="pluginBuilder">The PluginBuilder delegate instance used to instance this plugin.</param> |
| | 33 | | /// <returns>True if the plugin defined by the PluginBuilder delegate is currently enabled</returns> |
| | 34 | | public bool IsEnabled<T>() where T : IPlugin |
| | 35 | | { |
| 14 | 36 | | Type type = typeof(T); |
| 14 | 37 | | if (!allPlugins.plugins.ContainsKey(type)) |
| 0 | 38 | | return false; |
| | 39 | |
|
| 14 | 40 | | return allPlugins.plugins[type].isEnabled; |
| | 41 | | } |
| | 42 | |
|
| | 43 | | /// <summary> |
| | 44 | | /// Registers a plugin builder and binds it to a feature flag. |
| | 45 | | /// </summary> |
| | 46 | | /// <param name="pluginBuilder">The builder used to construct the plugin.</param> |
| | 47 | | /// <param name="featureFlag">The flag to be bounded. When this flag is true, the plugin will be created.</param |
| | 48 | | public void RegisterWithFlag<T>(PluginBuilder pluginBuilder, string featureFlag) where T : IPlugin |
| | 49 | | { |
| 8 | 50 | | Register<T>(pluginBuilder, false); |
| 8 | 51 | | BindFlag<T>(pluginBuilder, featureFlag); |
| 8 | 52 | | } |
| | 53 | |
|
| | 54 | | /// <summary> |
| | 55 | | /// Registers a new plugin to be built by the PluginBuilder delegate. |
| | 56 | | /// </summary> |
| | 57 | | /// <param name="pluginBuilder">The pluginBuilder instance. This instance will create the plugin when enabled.</ |
| | 58 | | /// <param name="enable">If true, the plugin will be constructed as soon as this method is called.</param> |
| | 59 | | public void Register<T>(PluginBuilder pluginBuilder, bool enable = true) where T : IPlugin |
| | 60 | | { |
| 11 | 61 | | Type type = typeof(T); |
| 11 | 62 | | Assert.IsNotNull(pluginBuilder); |
| | 63 | |
|
| 11 | 64 | | PluginInfo pluginInfo = new PluginInfo() { builder = pluginBuilder }; |
| | 65 | |
|
| 11 | 66 | | if (allPlugins.ContainsKey(type)) |
| | 67 | | { |
| 1 | 68 | | Unregister<T>(); |
| | 69 | | } |
| | 70 | |
|
| 11 | 71 | | allPlugins.Add(type, pluginInfo); |
| | 72 | |
|
| 11 | 73 | | pluginInfo.enableOnInit = enable; |
| 11 | 74 | | } |
| | 75 | |
|
| | 76 | | /// <summary> |
| | 77 | | /// Unregisters a registered plugin. If the plugin was active, it will be disposed. |
| | 78 | | /// </summary> |
| | 79 | | /// <param name="plugin">The plugin builder instance used to register the plugin.</param> |
| | 80 | | public void Unregister<T>() where T : IPlugin |
| | 81 | | { |
| 1 | 82 | | Type type = typeof(T); |
| 1 | 83 | | if ( !allPlugins.plugins.ContainsKey(type)) |
| 0 | 84 | | return; |
| | 85 | |
|
| 1 | 86 | | PluginInfo info = allPlugins.plugins[type]; |
| | 87 | |
|
| 1 | 88 | | string flag = info.flag; |
| | 89 | |
|
| 1 | 90 | | if (flag != null) |
| | 91 | | { |
| 0 | 92 | | if (pluginGroupByFlag.ContainsKey(flag)) |
| | 93 | | { |
| 0 | 94 | | if (pluginGroupByFlag[flag].ContainsKey(type)) |
| | 95 | | { |
| 0 | 96 | | pluginGroupByFlag[flag].Remove(type); |
| | 97 | | } |
| | 98 | | } |
| | 99 | | } |
| | 100 | |
|
| 1 | 101 | | allPlugins.Remove(type); |
| 1 | 102 | | info.Disable(); |
| 1 | 103 | | } |
| | 104 | |
|
| | 105 | | /// <summary> |
| | 106 | | /// Initialize all enabled and registered plugin. |
| | 107 | | /// </summary> |
| | 108 | | public void Initialize() |
| | 109 | | { |
| 22 | 110 | | foreach ( var kvp in allPlugins.plugins ) |
| | 111 | | { |
| 8 | 112 | | PluginInfo info = kvp.Value; |
| 8 | 113 | | if (info.enableOnInit) |
| 2 | 114 | | info.Enable(); |
| | 115 | | } |
| 3 | 116 | | } |
| | 117 | |
|
| | 118 | | /// <summary> |
| | 119 | | /// Bind a feature flag to the given plugin builder. |
| | 120 | | /// When the given feature flag is set to true, this class will construct the plugin, initializing it. |
| | 121 | | /// </summary> |
| | 122 | | /// <param name="plugin">The given plugin builder.</param> |
| | 123 | | /// <param name="featureFlag">The given feature flag. If this feature flag is set to true the plugin will be cre |
| | 124 | | public void BindFlag<T>(PluginBuilder plugin, string featureFlag) where T : IPlugin |
| | 125 | | { |
| 8 | 126 | | Type type = typeof(T); |
| 8 | 127 | | Assert.IsNotNull(plugin); |
| | 128 | |
|
| 8 | 129 | | if ( !pluginGroupByFlag.ContainsKey(featureFlag) ) |
| 4 | 130 | | pluginGroupByFlag.Add(featureFlag, new PluginGroup()); |
| | 131 | |
|
| 8 | 132 | | allPlugins.plugins[type].flag = featureFlag; |
| 8 | 133 | | pluginGroupByFlag[featureFlag].Add(type, allPlugins.plugins[type]); |
| 8 | 134 | | } |
| | 135 | |
|
| | 136 | | /// <summary> |
| | 137 | | /// Sets a feature flag. Disabling or enabling any plugin that was bounded to it. |
| | 138 | | /// </summary> |
| | 139 | | /// <param name="featureFlag">The given feature flag to set.</param> |
| | 140 | | /// <param name="enabled">The feature flag is enabled?</param> |
| | 141 | | public void SetFlag(string featureFlag, bool enabled) |
| | 142 | | { |
| 5 | 143 | | if (enabled) |
| 5 | 144 | | EnableFlag(featureFlag); |
| | 145 | | else |
| 0 | 146 | | DisableFlag(featureFlag); |
| 0 | 147 | | } |
| | 148 | |
|
| | 149 | | /// <summary> |
| | 150 | | /// Enables a given feature flag. This enables any plugin that was bounded to it. |
| | 151 | | /// Enabling a plugin means that the plugin instance will be created. |
| | 152 | | /// </summary> |
| | 153 | | /// <param name="featureFlag">The feature flag to enable</param> |
| | 154 | | public void EnableFlag(string featureFlag) |
| | 155 | | { |
| 5 | 156 | | if ( !pluginGroupByFlag.ContainsKey(featureFlag) ) |
| 1 | 157 | | return; |
| | 158 | |
|
| 4 | 159 | | PluginGroup pluginGroup = pluginGroupByFlag[featureFlag]; |
| | 160 | |
|
| 24 | 161 | | foreach ( var feature in pluginGroup.plugins ) |
| | 162 | | { |
| 8 | 163 | | PluginInfo info = feature.Value; |
| 8 | 164 | | info.Enable(); |
| | 165 | | } |
| 4 | 166 | | } |
| | 167 | |
|
| | 168 | | /// <summary> |
| | 169 | | /// Disables a given feature flag. This disables any plugin that was bounded to it. |
| | 170 | | /// Disabling a plugin means that the plugin instance will be disposed. |
| | 171 | | /// </summary> |
| | 172 | | /// <param name="featureFlag">The feature flag to enable</param> |
| | 173 | | public void DisableFlag(string featureFlag) |
| | 174 | | { |
| 0 | 175 | | if ( !pluginGroupByFlag.ContainsKey(featureFlag) ) |
| 0 | 176 | | return; |
| | 177 | |
|
| 0 | 178 | | PluginGroup pluginGroup = pluginGroupByFlag[featureFlag]; |
| | 179 | |
|
| 0 | 180 | | foreach ( var feature in pluginGroup.plugins ) |
| | 181 | | { |
| 0 | 182 | | PluginInfo info = feature.Value; |
| 0 | 183 | | info.Disable(); |
| | 184 | | } |
| 0 | 185 | | } |
| | 186 | |
|
| | 187 | | /// <summary> |
| | 188 | | /// Sets the FeatureFlag instance used to listen for feature flag changes. |
| | 189 | | /// |
| | 190 | | /// After setting the feature flags data of the given instance, the PluginSystem |
| | 191 | | /// will react to all the feature flag changes. |
| | 192 | | /// </summary> |
| | 193 | | /// <param name="featureFlagsBaseVariable">The data object to listen to.</param> |
| | 194 | | public void SetFeatureFlagsData(BaseVariable<FeatureFlag> featureFlagsBaseVariable) |
| | 195 | | { |
| 4 | 196 | | if ( featureFlagsDataSource != null ) |
| 0 | 197 | | featureFlagsDataSource.OnChange -= OnFeatureFlagsChange; |
| | 198 | |
|
| 4 | 199 | | featureFlagsDataSource = featureFlagsBaseVariable; |
| 4 | 200 | | featureFlagsDataSource.OnChange += OnFeatureFlagsChange; |
| 4 | 201 | | OnFeatureFlagsChange(featureFlagsBaseVariable.Get(), featureFlagsBaseVariable.Get()); |
| 4 | 202 | | } |
| | 203 | |
|
| | 204 | | private void OnFeatureFlagsChange(FeatureFlag current, FeatureFlag previous) |
| | 205 | | { |
| 6 | 206 | | Assert.IsNotNull(current, "Current feature flags object should never be null!"); |
| | 207 | |
|
| 22 | 208 | | foreach ( var flag in current.flags ) |
| | 209 | | { |
| 5 | 210 | | SetFlag(flag.Key, flag.Value); |
| | 211 | | } |
| 6 | 212 | | } |
| | 213 | |
|
| | 214 | | public void Dispose() |
| | 215 | | { |
| 36 | 216 | | foreach ( var kvp in allPlugins.plugins ) |
| | 217 | | { |
| 10 | 218 | | PluginInfo info = kvp.Value; |
| 10 | 219 | | info.Disable(); |
| | 220 | | } |
| | 221 | |
|
| 8 | 222 | | if ( featureFlagsDataSource != null ) |
| 4 | 223 | | featureFlagsDataSource.OnChange -= OnFeatureFlagsChange; |
| 8 | 224 | | } |
| | 225 | | } |
| | 226 | | } |