using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Artemis.Core.DataModelExpansions;
namespace Artemis.Core.Modules
{
///
/// Allows you to add new data to the Artemis data model
///
public abstract class Module : Module where T : DataModel
{
///
/// The data model driving this module
/// Note: This default data model is automatically registered and instantiated upon plugin enable
///
public T DataModel
{
get => InternalDataModel as T ?? throw new InvalidOperationException("Internal datamodel does not match the type of the data model");
internal set => InternalDataModel = value;
}
///
/// Hide the provided property using a lambda expression, e.g. HideProperty(dm => dm.TimeDataModel.CurrentTimeUTC)
///
/// A lambda expression pointing to the property to ignore
public void HideProperty(Expression> propertyLambda)
{
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
if (!HiddenPropertiesList.Any(p => p.Equals(propertyInfo)))
HiddenPropertiesList.Add(propertyInfo);
}
///
/// Stop hiding the provided property using a lambda expression, e.g. ShowProperty(dm =>
/// dm.TimeDataModel.CurrentTimeUTC)
///
/// A lambda expression pointing to the property to stop ignoring
public void ShowProperty(Expression> propertyLambda)
{
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo));
}
internal override void InternalEnable()
{
DataModel = Activator.CreateInstance();
DataModel.Module = this;
DataModel.DataModelDescription = GetDataModelDescription();
base.InternalEnable();
}
internal override void InternalDisable()
{
Deactivate(true);
base.InternalDisable();
}
}
///
/// For internal use only, please use .
///
public abstract class Module : PluginFeature
{
private readonly List<(DefaultCategoryName, string)> _defaultProfilePaths = new();
private readonly List<(DefaultCategoryName, string)> _pendingDefaultProfilePaths = new();
///
/// Gets a list of all properties ignored at runtime using IgnoreProperty(x => x.y)
///
protected internal readonly List HiddenPropertiesList = new();
///
/// Gets a read only collection of default profile paths
///
public IReadOnlyCollection<(DefaultCategoryName, string)> DefaultProfilePaths => _defaultProfilePaths.AsReadOnly();
///
/// The modules display name that's shown in the menu
///
public string? DisplayName { get; protected set; }
///
/// The modules display icon that's shown in the UI accepts:
///
/// Either set to the name of a Material Icon see ( for available
/// icons) or set to a path relative to the plugin folder pointing to a .svg file
///
///
public string? DisplayIcon { get; set; }
///
/// Gets whether this module is activated. A module can only be active while its
/// are met
///
public bool IsActivated { get; internal set; }
///
/// Gets whether this module's activation was due to an override, can only be true if is
///
///
public bool IsActivatedOverride { get; private set; }
///
/// Gets whether this module should update while is . When
/// set to and any timed updates will not get called during an
/// activation override.
/// Defaults to
///
public bool UpdateDuringActivationOverride { get; protected set; }
///
/// A list of activation requirements
/// Note: if empty the module is always activated
///
public List ActivationRequirements { get; } = new();
///
/// Gets or sets the activation requirement mode, defaults to
///
public ActivationRequirementType ActivationRequirementMode { get; set; } = ActivationRequirementType.Any;
///
/// Gets a boolean indicating whether this module is always available to profiles or only to profiles that specifically
/// target this module.
///
/// Note: if there are any ; otherwise
///
///
///
public bool IsAlwaysAvailable => ActivationRequirements.Count == 0;
///
/// Gets whether updating this module is currently allowed
///
public bool IsUpdateAllowed => IsActivated && (UpdateDuringActivationOverride || !IsActivatedOverride);
///
/// Gets a list of all properties ignored at runtime using IgnoreProperty(x => x.y)
///
public ReadOnlyCollection HiddenProperties => HiddenPropertiesList.AsReadOnly();
internal DataModel? InternalDataModel { get; set; }
///
/// Called each frame when the module should update
///
/// Time in seconds since the last update
public abstract void Update(double deltaTime);
///
/// Called when the are met or during an override
///
///
/// If true, the activation was due to an override. This usually means the module was activated
/// by the profile editor
///
public virtual void ModuleActivated(bool isOverride)
{
}
///
/// Called when the are no longer met or during an override
///
///
/// If true, the deactivation was due to an override. This usually means the module was deactivated
/// by the profile editor
///
public virtual void ModuleDeactivated(bool isOverride)
{
}
///
/// Evaluates the activation requirements following the and returns the result
///
/// The evaluated result of the activation requirements
public bool EvaluateActivationRequirements()
{
if (!ActivationRequirements.Any())
return true;
if (ActivationRequirementMode == ActivationRequirementType.All)
return ActivationRequirements.All(r => r.Evaluate());
if (ActivationRequirementMode == ActivationRequirementType.Any)
return ActivationRequirements.Any(r => r.Evaluate());
return false;
}
///
/// Override to provide your own data model description. By default this returns a description matching your plugin
/// name and description
///
///
public virtual DataModelPropertyAttribute GetDataModelDescription()
{
return new() {Name = Plugin.Info.Name, Description = Plugin.Info.Description};
}
///
/// Adds a default profile by reading it from the file found at the provided path
///
/// The category in which to place the default profile
/// A path pointing towards a profile file. May be relative to the plugin directory.
///
/// if the default profile was added; if it was not because it is
/// already in the list.
///
protected bool AddDefaultProfile(DefaultCategoryName category, string file)
{
// It can be null if the plugin has not loaded yet in which case Plugin.ResolveRelativePath fails
if (Plugin == null!)
{
if (_pendingDefaultProfilePaths.Contains((category, file)))
return false;
_pendingDefaultProfilePaths.Add((category, file));
return true;
}
if (!Path.IsPathRooted(file))
file = Plugin.ResolveRelativePath(file);
// Ensure the file exists
if (!File.Exists(file))
throw new ArtemisPluginFeatureException(this, $"Could not find default profile at {file}.");
if (_defaultProfilePaths.Contains((category, file)))
return false;
_defaultProfilePaths.Add((category, file));
return true;
}
internal virtual void InternalUpdate(double deltaTime)
{
StartUpdateMeasure();
if (IsUpdateAllowed)
Update(deltaTime);
StopUpdateMeasure();
}
internal virtual void Activate(bool isOverride)
{
if (IsActivated)
return;
IsActivatedOverride = isOverride;
ModuleActivated(isOverride);
IsActivated = true;
}
internal virtual void Deactivate(bool isOverride)
{
if (!IsActivated)
return;
IsActivatedOverride = false;
IsActivated = false;
ModuleDeactivated(isOverride);
}
#region Overrides of PluginFeature
///
internal override void InternalEnable()
{
foreach ((DefaultCategoryName categoryName, var path) in _pendingDefaultProfilePaths)
AddDefaultProfile(categoryName, path);
_pendingDefaultProfilePaths.Clear();
base.InternalEnable();
}
#endregion
internal virtual void Reactivate(bool isDeactivateOverride, bool isActivateOverride)
{
if (!IsActivated)
return;
Deactivate(isDeactivateOverride);
Activate(isActivateOverride);
}
}
///
/// Describes in what way the activation requirements of a module must be met
///
public enum ActivationRequirementType
{
///
/// Any activation requirement must be met for the module to activate
///
Any,
///
/// All activation requirements must be met for the module to activate
///
All
}
}