Code style - Use file scoped namespaces

Code style - Ran code cleanup
This commit is contained in:
Robert committed 2022-08-21 11:36:15 +02:00
1 parent e68e16df4d
commit f6090dc296
691 files changed
+37301 -38119

No files matched your search

@@ -1,25 +1,24 @@
namespace Artemis.Core.Modules
namespace Artemis.Core.Modules;
/// <summary>
/// Evaluates to true or false by returning the value of ActivationMet
/// </summary>
public class BooleanActivationRequirement : IModuleActivationRequirement
{
/// <summary>
/// Evaluates to true or false by returning the value of ActivationMet
/// Gets or sets whether the activation requirement is met
/// </summary>
public class BooleanActivationRequirement : IModuleActivationRequirement
public bool ActivationMet { get; set; }
/// <inheritdoc />
public bool Evaluate()
{
/// <summary>
/// Gets or sets whether the activation requirement is met
/// </summary>
public bool ActivationMet { get; set; }
return ActivationMet;
}
/// <inheritdoc />
public bool Evaluate()
{
return ActivationMet;
}
/// <inheritdoc />
public string GetUserFriendlyDescription()
{
return "No description available";
}
/// <inheritdoc />
public string GetUserFriendlyDescription()
{
return "No description available";
}
}
@@ -1,20 +1,19 @@
namespace Artemis.Core.Modules
namespace Artemis.Core.Modules;
/// <summary>
/// Evaluates to true or false by checking requirements specific to the implementation
/// </summary>
public interface IModuleActivationRequirement
{
/// <summary>
/// Evaluates to true or false by checking requirements specific to the implementation
/// Called to determine whether the activation requirement is met
/// </summary>
public interface IModuleActivationRequirement
{
/// <summary>
/// Called to determine whether the activation requirement is met
/// </summary>
/// <returns></returns>
bool Evaluate();
/// <returns></returns>
bool Evaluate();
/// <summary>
/// Returns a user-friendly description of the activation requirement, should include parameters if applicable
/// </summary>
/// <returns>A user-friendly description of the activation requirement</returns>
string GetUserFriendlyDescription();
}
/// <summary>
/// Returns a user-friendly description of the activation requirement, should include parameters if applicable
/// </summary>
/// <returns>A user-friendly description of the activation requirement</returns>
string GetUserFriendlyDescription();
}
@@ -5,62 +5,61 @@ using System.IO;
using System.Linq;
using Artemis.Core.Services;
namespace Artemis.Core.Modules
namespace Artemis.Core.Modules;
/// <summary>
/// Evaluates to true or false by checking if the specified process is running
/// </summary>
public class ProcessActivationRequirement : IModuleActivationRequirement
{
/// <summary>
/// Evaluates to true or false by checking if the specified process is running
/// Creates a new instance of the <see cref="ProcessActivationRequirement" /> class
/// </summary>
public class ProcessActivationRequirement : IModuleActivationRequirement
/// <param name="processName">The name of the process that must run</param>
/// <param name="location">The location of where the process must be running from (optional)</param>
public ProcessActivationRequirement(string? processName, string? location = null)
{
/// <summary>
/// Creates a new instance of the <see cref="ProcessActivationRequirement" /> class
/// </summary>
/// <param name="processName">The name of the process that must run</param>
/// <param name="location">The location of where the process must be running from (optional)</param>
public ProcessActivationRequirement(string? processName, string? location = null)
{
if (string.IsNullOrWhiteSpace(processName) && string.IsNullOrWhiteSpace(location))
throw new ArgumentNullException($"Atleast one {nameof(processName)} and {nameof(location)} must not be null");
if (string.IsNullOrWhiteSpace(processName) && string.IsNullOrWhiteSpace(location))
throw new ArgumentNullException($"Atleast one {nameof(processName)} and {nameof(location)} must not be null");
ProcessName = processName;
Location = location;
}
ProcessName = processName;
Location = location;
}
/// <summary>
/// The name of the process that must run
/// </summary>
public string? ProcessName { get; set; }
/// <summary>
/// The name of the process that must run
/// </summary>
public string? ProcessName { get; set; }
/// <summary>
/// The location of where the process must be running from
/// </summary>
public string? Location { get; set; }
/// <summary>
/// The location of where the process must be running from
/// </summary>
public string? Location { get; set; }
internal static IProcessMonitorService? ProcessMonitorService { get; set; }
internal static IProcessMonitorService? ProcessMonitorService { get; set; }
/// <inheritdoc />
public bool Evaluate()
{
if (ProcessMonitorService == null || ProcessName == null && Location == null)
return false;
/// <inheritdoc />
public bool Evaluate()
{
if (ProcessMonitorService == null || (ProcessName == null && Location == null))
return false;
IEnumerable<Process> processes = ProcessMonitorService.GetRunningProcesses();
if (ProcessName != null)
processes = processes.Where(p => string.Equals(p.ProcessName, ProcessName, StringComparison.InvariantCultureIgnoreCase));
if (Location != null)
processes = processes.Where(p => string.Equals(Path.GetDirectoryName(p.GetProcessFilename()), Location, StringComparison.InvariantCultureIgnoreCase));
IEnumerable<Process> processes = ProcessMonitorService.GetRunningProcesses();
if (ProcessName != null)
processes = processes.Where(p => string.Equals(p.ProcessName, ProcessName, StringComparison.InvariantCultureIgnoreCase));
if (Location != null)
processes = processes.Where(p => string.Equals(Path.GetDirectoryName(p.GetProcessFilename()), Location, StringComparison.InvariantCultureIgnoreCase));
return processes.Any();
}
return processes.Any();
}
/// <inheritdoc />
public string GetUserFriendlyDescription()
{
string description = $"Requirement met when \"{ProcessName}.exe\" is running";
if (Location != null)
description += $" from \"{Location}\"";
/// <inheritdoc />
public string GetUserFriendlyDescription()
{
string description = $"Requirement met when \"{ProcessName}.exe\" is running";
if (Location != null)
description += $" from \"{Location}\"";
return description;
}
return description;
}
}
@@ -1,11 +1,10 @@
using System;
namespace Artemis.Core.Modules
namespace Artemis.Core.Modules;
/// <summary>
/// Represents an attribute that marks a data model property to be ignored by the UI
/// </summary>
public class DataModelIgnoreAttribute : Attribute
{
/// <summary>
/// Represents an attribute that marks a data model property to be ignored by the UI
/// </summary>
public class DataModelIgnoreAttribute : Attribute
{
}
}
@@ -1,51 +1,50 @@
using System;
namespace Artemis.Core.Modules
namespace Artemis.Core.Modules;
/// <summary>
/// Represents an attribute that describes a data model property
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class DataModelPropertyAttribute : Attribute
{
/// <summary>
/// Represents an attribute that describes a data model property
/// Gets or sets the user-friendly name for this property, shown in the UI.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class DataModelPropertyAttribute : Attribute
{
/// <summary>
/// Gets or sets the user-friendly name for this property, shown in the UI.
/// </summary>
public string? Name { get; set; }
public string? Name { get; set; }
/// <summary>
/// Gets or sets the user-friendly description for this property, shown in the UI.
/// </summary>
public string? Description { get; set; }
/// <summary>
/// Gets or sets the user-friendly description for this property, shown in the UI.
/// </summary>
public string? Description { get; set; }
/// <summary>
/// Gets or sets the an optional prefix to show before displaying elements in the UI.
/// </summary>
public string? Prefix { get; set; }
/// <summary>
/// Gets or sets the an optional prefix to show before displaying elements in the UI.
/// </summary>
public string? Prefix { get; set; }
/// <summary>
/// Gets or sets an optional affix to show behind displaying elements in the UI.
/// </summary>
public string? Affix { get; set; }
/// <summary>
/// Gets or sets an optional affix to show behind displaying elements in the UI.
/// </summary>
public string? Affix { get; set; }
/// <summary>
/// Gets or sets the name of list items, only applicable to enumerable data model properties
/// </summary>
public string? ListItemName { get; set; }
/// <summary>
/// Gets or sets the name of list items, only applicable to enumerable data model properties
/// </summary>
public string? ListItemName { get; set; }
/// <summary>
/// Gets or sets an optional maximum value, this value is not enforced but used for percentage calculations.
/// </summary>
public object? MaxValue { get; set; }
/// <summary>
/// Gets or sets an optional maximum value, this value is not enforced but used for percentage calculations.
/// </summary>
public object? MaxValue { get; set; }
/// <summary>
/// Gets or sets an optional minimum value, this value is not enforced but used for percentage calculations.
/// </summary>
public object? MinValue { get; set; }
/// <summary>
/// Gets or sets an optional minimum value, this value is not enforced but used for percentage calculations.
/// </summary>
public object? MinValue { get; set; }
/// <summary>
/// Gets or sets whether this property resets the max depth of the data model, defaults to true
/// </summary>
public bool ResetsDepth { get; set; } = true;
}
/// <summary>
/// Gets or sets whether this property resets the max depth of the data model, defaults to true
/// </summary>
public bool ResetsDepth { get; set; } = true;
}
+345 -354
View File
@@ -7,381 +7,372 @@ using System.Reflection;
using Humanizer;
using Newtonsoft.Json;
namespace Artemis.Core.Modules
namespace Artemis.Core.Modules;
/// <summary>
/// Represents a data model that contains information on a game/application etc.
/// </summary>
public abstract class DataModel
{
private readonly List<DataModelPath> _activePaths = new();
private readonly HashSet<string> _activePathsHashSet = new();
private readonly Dictionary<string, DynamicChild> _dynamicChildren = new();
/// <summary>
/// Represents a data model that contains information on a game/application etc.
/// Creates a new instance of the <see cref="DataModel" /> class
/// </summary>
public abstract class DataModel
protected DataModel()
{
private readonly HashSet<string> _activePathsHashSet = new();
private readonly List<DataModelPath> _activePaths = new();
private readonly Dictionary<string, DynamicChild> _dynamicChildren = new();
// These are both set right after construction to keep the constructor of inherited classes clean
Module = null!;
DataModelDescription = null!;
/// <summary>
/// Creates a new instance of the <see cref="DataModel" /> class
/// </summary>
protected DataModel()
ActivePaths = new ReadOnlyCollection<DataModelPath>(_activePaths);
DynamicChildren = new ReadOnlyDictionary<string, DynamicChild>(_dynamicChildren);
}
/// <summary>
/// Gets the module this data model belongs to
/// </summary>
[JsonIgnore]
[DataModelIgnore]
public Module Module { get; internal set; }
/// <summary>
/// Gets the <see cref="DataModelPropertyAttribute" /> describing this data model
/// </summary>
[JsonIgnore]
[DataModelIgnore]
public DataModelPropertyAttribute DataModelDescription { get; internal set; }
/// <summary>
/// Gets the is expansion status indicating whether this data model expands the main data model
/// </summary>
[DataModelIgnore]
public bool IsExpansion { get; internal set; }
/// <summary>
/// Gets an read-only dictionary of all dynamic children
/// </summary>
[DataModelIgnore]
public ReadOnlyDictionary<string, DynamicChild> DynamicChildren { get; }
/// <summary>
/// Gets a read-only list of <see cref="DataModelPath" />s targeting this data model
/// </summary>
[DataModelIgnore]
public ReadOnlyCollection<DataModelPath> ActivePaths { get; }
/// <summary>
/// Returns a read-only collection of all properties in this datamodel that are to be ignored
/// </summary>
/// <returns></returns>
public ReadOnlyCollection<PropertyInfo> GetHiddenProperties()
{
return Module.HiddenProperties;
}
/// <summary>
/// Gets the property description of the provided property info
/// </summary>
/// <returns>If found, the property description attribute, otherwise <see langword="null" />.</returns>
public virtual DataModelPropertyAttribute? GetPropertyDescription(PropertyInfo propertyInfo)
{
return (DataModelPropertyAttribute?) Attribute.GetCustomAttribute(propertyInfo, typeof(DataModelPropertyAttribute));
}
/// <summary>
/// Occurs when a dynamic child has been added to this data model
/// </summary>
public event EventHandler<DynamicDataModelChildEventArgs>? DynamicChildAdded;
/// <summary>
/// Occurs when a dynamic child has been removed from this data model
/// </summary>
public event EventHandler<DynamicDataModelChildEventArgs>? DynamicChildRemoved;
/// <summary>
/// Occurs when a dynamic child has been added to this data model
/// </summary>
public event EventHandler<DataModelPathEventArgs>? ActivePathAdded;
/// <summary>
/// Occurs when a dynamic child has been removed from this data model
/// </summary>
public event EventHandler<DataModelPathEventArgs>? ActivePathRemoved;
/// <summary>
/// Invokes the <see cref="DynamicChildAdded" /> event
/// </summary>
protected virtual void OnDynamicDataModelAdded(DynamicDataModelChildEventArgs e)
{
DynamicChildAdded?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="DynamicChildRemoved" /> event
/// </summary>
protected virtual void OnDynamicDataModelRemoved(DynamicDataModelChildEventArgs e)
{
DynamicChildRemoved?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="ActivePathAdded" /> event
/// </summary>
protected virtual void OnActivePathAdded(DataModelPathEventArgs e)
{
ActivePathAdded?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="ActivePathRemoved" /> event
/// </summary>
protected virtual void OnActivePathRemoved(DataModelPathEventArgs e)
{
ActivePathRemoved?.Invoke(this, e);
}
#region Dynamic children
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="initialValue">The initial value of the dynamic child</param>
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
public DynamicChild<T> AddDynamicChild<T>(string key, T initialValue)
{
return AddDynamicChild(key, initialValue, new DataModelPropertyAttribute());
}
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="initialValue">The initial value of the dynamic child</param>
/// <param name="name">A human readable name for your dynamic child, shown in the UI</param>
/// <param name="description">An optional description, shown in the UI</param>
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
public DynamicChild<T> AddDynamicChild<T>(string key, T initialValue, string name, string? description = null)
{
return AddDynamicChild(key, initialValue, new DataModelPropertyAttribute {Name = name, Description = description});
}
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="initialValue">The initial value of the dynamic child</param>
/// <param name="attribute">A data model property attribute describing the dynamic child</param>
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
public DynamicChild<T> AddDynamicChild<T>(string key, T initialValue, DataModelPropertyAttribute attribute)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (initialValue == null) throw new ArgumentNullException(nameof(initialValue));
if (attribute == null) throw new ArgumentNullException(nameof(attribute));
if (key.Contains('.'))
throw new ArtemisCoreException("The provided key contains an illegal character (.)");
if (_dynamicChildren.ContainsKey(key))
throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " +
"because the key is already in use on by another dynamic property this data model.");
if (GetType().GetProperty(key) != null)
throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " +
"because the key is already in use by a static property on this data model.");
// Make sure a name is on the attribute or funny things might happen
attribute.Name ??= key.Humanize();
if (initialValue is DataModel dynamicDataModel)
{
// These are both set right after construction to keep the constructor of inherited classes clean
Module = null!;
DataModelDescription = null!;
ActivePaths = new ReadOnlyCollection<DataModelPath>(_activePaths);
DynamicChildren = new ReadOnlyDictionary<string, DynamicChild>(_dynamicChildren);
dynamicDataModel.Module = Module;
dynamicDataModel.DataModelDescription = attribute;
}
/// <summary>
/// Gets the module this data model belongs to
/// </summary>
[JsonIgnore]
[DataModelIgnore]
public Module Module { get; internal set; }
DynamicChild<T> dynamicChild = new(initialValue, key, attribute);
_dynamicChildren.Add(key, dynamicChild);
/// <summary>
/// Gets the <see cref="DataModelPropertyAttribute" /> describing this data model
/// </summary>
[JsonIgnore]
[DataModelIgnore]
public DataModelPropertyAttribute DataModelDescription { get; internal set; }
OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key));
return dynamicChild;
}
/// <summary>
/// Gets the is expansion status indicating whether this data model expands the main data model
/// </summary>
[DataModelIgnore]
public bool IsExpansion { get; internal set; }
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <param name="key">The key of the dynamic child</param>
public DynamicChild GetDynamicChild(string key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
return DynamicChildren[key];
}
/// <summary>
/// Gets an read-only dictionary of all dynamic children
/// </summary>
[DataModelIgnore]
public ReadOnlyDictionary<string, DynamicChild> DynamicChildren { get; }
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <typeparam name="T">The typer of dynamic child you are expecting</typeparam>
/// <param name="key">The key of the dynamic child</param>
/// <returns></returns>
public DynamicChild<T> GetDynamicChild<T>(string key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
return (DynamicChild<T>) DynamicChildren[key];
}
/// <summary>
/// Gets a read-only list of <see cref="DataModelPath" />s targeting this data model
/// </summary>
[DataModelIgnore]
public ReadOnlyCollection<DataModelPath> ActivePaths { get; }
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <param name="key">The key of the dynamic child</param>
/// <param name="dynamicChild">
/// When this method returns, the <see cref="DynamicChild" /> associated with the specified key,
/// if the key is found; otherwise, <see langword="null" />. This parameter is passed uninitialized.
/// </param>
/// <returns>
/// <see langword="true" /> if the data model contains the dynamic child; otherwise <see langword="false" />
/// </returns>
public bool TryGetDynamicChild(string key, [MaybeNullWhen(false)] out DynamicChild dynamicChild)
{
if (key == null) throw new ArgumentNullException(nameof(key));
/// <summary>
/// Returns a read-only collection of all properties in this datamodel that are to be ignored
/// </summary>
/// <returns></returns>
public ReadOnlyCollection<PropertyInfo> GetHiddenProperties()
dynamicChild = null;
if (!DynamicChildren.TryGetValue(key, out DynamicChild? value))
return false;
dynamicChild = value;
return true;
}
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <typeparam name="T">The typer of dynamic child you are expecting</typeparam>
/// <param name="key">The key of the dynamic child</param>
/// <param name="dynamicChild">
/// When this method returns, the <see cref="DynamicChild{T}" /> associated with the specified
/// key, if the key is found and the type matches; otherwise, <see langword="null" />. This parameter is passed
/// uninitialized.
/// </param>
/// <returns>
/// <see langword="true" /> if the data model contains the dynamic child; otherwise <see langword="false" />
/// </returns>
public bool TryGetDynamicChild<T>(string key, [MaybeNullWhen(false)] out DynamicChild<T> dynamicChild)
{
if (key == null) throw new ArgumentNullException(nameof(key));
dynamicChild = null;
if (!DynamicChildren.TryGetValue(key, out DynamicChild? value))
return false;
if (value is not DynamicChild<T> typedDynamicChild)
return false;
dynamicChild = typedDynamicChild;
return true;
}
/// <summary>
/// Removes a dynamic child from the data model by its key
/// </summary>
/// <param name="key">The key of the dynamic child to remove</param>
public void RemoveDynamicChildByKey(string key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild))
return;
_dynamicChildren.Remove(key);
OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild, key));
}
/// <summary>
/// Removes a dynamic child from this data model
/// </summary>
/// <param name="dynamicChild">The dynamic data child to remove</param>
public void RemoveDynamicChild(DynamicChild dynamicChild)
{
if (dynamicChild == null) throw new ArgumentNullException(nameof(dynamicChild));
List<string> keys = _dynamicChildren.Where(kvp => kvp.Value.BaseValue == dynamicChild).Select(kvp => kvp.Key).ToList();
foreach (string key in keys)
{
return Module.HiddenProperties;
}
/// <summary>
/// Gets the property description of the provided property info
/// </summary>
/// <returns>If found, the property description attribute, otherwise <see langword="null"/>.</returns>
public virtual DataModelPropertyAttribute? GetPropertyDescription(PropertyInfo propertyInfo)
{
return (DataModelPropertyAttribute?) Attribute.GetCustomAttribute(propertyInfo, typeof(DataModelPropertyAttribute));
}
#region Dynamic children
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="initialValue">The initial value of the dynamic child</param>
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
public DynamicChild<T> AddDynamicChild<T>(string key, T initialValue)
{
return AddDynamicChild(key, initialValue, new DataModelPropertyAttribute());
}
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="initialValue">The initial value of the dynamic child</param>
/// <param name="name">A human readable name for your dynamic child, shown in the UI</param>
/// <param name="description">An optional description, shown in the UI</param>
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
public DynamicChild<T> AddDynamicChild<T>(string key, T initialValue, string name, string? description = null)
{
return AddDynamicChild(key, initialValue, new DataModelPropertyAttribute {Name = name, Description = description});
}
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="initialValue">The initial value of the dynamic child</param>
/// <param name="attribute">A data model property attribute describing the dynamic child</param>
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
public DynamicChild<T> AddDynamicChild<T>(string key, T initialValue, DataModelPropertyAttribute attribute)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (initialValue == null) throw new ArgumentNullException(nameof(initialValue));
if (attribute == null) throw new ArgumentNullException(nameof(attribute));
if (key.Contains('.'))
throw new ArtemisCoreException("The provided key contains an illegal character (.)");
if (_dynamicChildren.ContainsKey(key))
{
throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " +
"because the key is already in use on by another dynamic property this data model.");
}
if (GetType().GetProperty(key) != null)
{
throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " +
"because the key is already in use by a static property on this data model.");
}
// Make sure a name is on the attribute or funny things might happen
attribute.Name ??= key.Humanize();
if (initialValue is DataModel dynamicDataModel)
{
dynamicDataModel.Module = Module;
dynamicDataModel.DataModelDescription = attribute;
}
DynamicChild<T> dynamicChild = new(initialValue, key, attribute);
_dynamicChildren.Add(key, dynamicChild);
OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key));
return dynamicChild;
}
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <param name="key">The key of the dynamic child</param>
public DynamicChild GetDynamicChild(string key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
return DynamicChildren[key];
}
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <typeparam name="T">The typer of dynamic child you are expecting</typeparam>
/// <param name="key">The key of the dynamic child</param>
/// <returns></returns>
public DynamicChild<T> GetDynamicChild<T>(string key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
return (DynamicChild<T>) DynamicChildren[key];
}
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <param name="key">The key of the dynamic child</param>
/// <param name="dynamicChild">
/// When this method returns, the <see cref="DynamicChild" /> associated with the specified key,
/// if the key is found; otherwise, <see langword="null" />. This parameter is passed uninitialized.
/// </param>
/// <returns>
/// <see langword="true" /> if the data model contains the dynamic child; otherwise <see langword="false" />
/// </returns>
public bool TryGetDynamicChild(string key, [MaybeNullWhen(false)] out DynamicChild dynamicChild)
{
if (key == null) throw new ArgumentNullException(nameof(key));
dynamicChild = null;
if (!DynamicChildren.TryGetValue(key, out DynamicChild? value))
return false;
dynamicChild = value;
return true;
}
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <typeparam name="T">The typer of dynamic child you are expecting</typeparam>
/// <param name="key">The key of the dynamic child</param>
/// <param name="dynamicChild">
/// When this method returns, the <see cref="DynamicChild{T}" /> associated with the specified
/// key, if the key is found and the type matches; otherwise, <see langword="null" />. This parameter is passed
/// uninitialized.
/// </param>
/// <returns>
/// <see langword="true" /> if the data model contains the dynamic child; otherwise <see langword="false" />
/// </returns>
public bool TryGetDynamicChild<T>(string key, [MaybeNullWhen(false)] out DynamicChild<T> dynamicChild)
{
if (key == null) throw new ArgumentNullException(nameof(key));
dynamicChild = null;
if (!DynamicChildren.TryGetValue(key, out DynamicChild? value))
return false;
if (value is not DynamicChild<T> typedDynamicChild)
return false;
dynamicChild = typedDynamicChild;
return true;
}
/// <summary>
/// Removes a dynamic child from the data model by its key
/// </summary>
/// <param name="key">The key of the dynamic child to remove</param>
public void RemoveDynamicChildByKey(string key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild))
return;
_dynamicChildren.Remove(key);
OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild, key));
}
}
/// <summary>
/// Removes a dynamic child from this data model
/// </summary>
/// <param name="dynamicChild">The dynamic data child to remove</param>
public void RemoveDynamicChild(DynamicChild dynamicChild)
/// <summary>
/// Removes all dynamic children from this data model
/// </summary>
public void ClearDynamicChildren()
{
while (_dynamicChildren.Any())
RemoveDynamicChildByKey(_dynamicChildren.First().Key);
}
// Used a runtime by data model paths only
internal T? GetDynamicChildValue<T>(string key)
{
if (TryGetDynamicChild(key, out DynamicChild? dynamicChild) && dynamicChild.BaseValue != null)
{
if (dynamicChild == null) throw new ArgumentNullException(nameof(dynamicChild));
List<string> keys = _dynamicChildren.Where(kvp => kvp.Value.BaseValue == dynamicChild).Select(kvp => kvp.Key).ToList();
foreach (string key in keys)
{
_dynamicChildren.Remove(key);
OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild, key));
}
}
/// <summary>
/// Removes all dynamic children from this data model
/// </summary>
public void ClearDynamicChildren()
{
while (_dynamicChildren.Any())
RemoveDynamicChildByKey(_dynamicChildren.First().Key);
}
// Used a runtime by data model paths only
internal T? GetDynamicChildValue<T>(string key)
{
if (TryGetDynamicChild(key, out DynamicChild? dynamicChild) && dynamicChild.BaseValue != null)
{
if (dynamicChild.BaseValue is T value)
return value;
return default;
}
if (dynamicChild.BaseValue is T value)
return value;
return default;
}
#endregion
#region Paths
/// <summary>
/// Determines whether the provided dot-separated path is in use
/// </summary>
/// <param name="path">The path to check per example: <c>MyDataModelChild.MyDataModelProperty</c></param>
/// <param name="includeChildren">
/// If <see langword="true" /> any child of the given path will return true as well; if
/// <see langword="false" /> only an exact path match returns <see langword="true" />.
/// </param>
internal bool IsPropertyInUse(string path, bool includeChildren)
{
path = path.ToUpperInvariant();
lock (_activePaths)
{
return includeChildren
? _activePathsHashSet.Any(p => p.StartsWith(path, StringComparison.Ordinal))
: _activePathsHashSet.Contains(path);
}
}
internal void AddDataModelPath(DataModelPath path)
{
lock (_activePaths)
{
if (_activePaths.Contains(path))
return;
_activePaths.Add(path);
// Add to the hashset if this is the first path pointing
string hashPath = path.Path.ToUpperInvariant();
if (!_activePathsHashSet.Contains(hashPath))
_activePathsHashSet.Add(hashPath);
}
OnActivePathAdded(new DataModelPathEventArgs(path));
}
internal void RemoveDataModelPath(DataModelPath path)
{
lock (_activePaths)
{
if (!_activePaths.Remove(path))
return;
// Remove from the hashset if this was the last path pointing there
if (_activePaths.All(p => p.Path != path.Path))
_activePathsHashSet.Remove(path.Path.ToUpperInvariant());
}
OnActivePathRemoved(new DataModelPathEventArgs(path));
}
#endregion
#region Events
/// <summary>
/// Occurs when a dynamic child has been added to this data model
/// </summary>
public event EventHandler<DynamicDataModelChildEventArgs>? DynamicChildAdded;
/// <summary>
/// Occurs when a dynamic child has been removed from this data model
/// </summary>
public event EventHandler<DynamicDataModelChildEventArgs>? DynamicChildRemoved;
/// <summary>
/// Occurs when a dynamic child has been added to this data model
/// </summary>
public event EventHandler<DataModelPathEventArgs>? ActivePathAdded;
/// <summary>
/// Occurs when a dynamic child has been removed from this data model
/// </summary>
public event EventHandler<DataModelPathEventArgs>? ActivePathRemoved;
/// <summary>
/// Invokes the <see cref="DynamicChildAdded" /> event
/// </summary>
protected virtual void OnDynamicDataModelAdded(DynamicDataModelChildEventArgs e)
{
DynamicChildAdded?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="DynamicChildRemoved" /> event
/// </summary>
protected virtual void OnDynamicDataModelRemoved(DynamicDataModelChildEventArgs e)
{
DynamicChildRemoved?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="ActivePathAdded" /> event
/// </summary>
protected virtual void OnActivePathAdded(DataModelPathEventArgs e)
{
ActivePathAdded?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="ActivePathRemoved" /> event
/// </summary>
protected virtual void OnActivePathRemoved(DataModelPathEventArgs e)
{
ActivePathRemoved?.Invoke(this, e);
}
#endregion
return default;
}
#endregion
#region Paths
/// <summary>
/// Determines whether the provided dot-separated path is in use
/// </summary>
/// <param name="path">The path to check per example: <c>MyDataModelChild.MyDataModelProperty</c></param>
/// <param name="includeChildren">
/// If <see langword="true" /> any child of the given path will return true as well; if
/// <see langword="false" /> only an exact path match returns <see langword="true" />.
/// </param>
internal bool IsPropertyInUse(string path, bool includeChildren)
{
path = path.ToUpperInvariant();
lock (_activePaths)
{
return includeChildren
? _activePathsHashSet.Any(p => p.StartsWith(path, StringComparison.Ordinal))
: _activePathsHashSet.Contains(path);
}
}
internal void AddDataModelPath(DataModelPath path)
{
lock (_activePaths)
{
if (_activePaths.Contains(path))
return;
_activePaths.Add(path);
// Add to the hashset if this is the first path pointing
string hashPath = path.Path.ToUpperInvariant();
if (!_activePathsHashSet.Contains(hashPath))
_activePathsHashSet.Add(hashPath);
}
OnActivePathAdded(new DataModelPathEventArgs(path));
}
internal void RemoveDataModelPath(DataModelPath path)
{
lock (_activePaths)
{
if (!_activePaths.Remove(path))
return;
// Remove from the hashset if this was the last path pointing there
if (_activePaths.All(p => p.Path != path.Path))
_activePathsHashSet.Remove(path.Path.ToUpperInvariant());
}
OnActivePathRemoved(new DataModelPathEventArgs(path));
}
#endregion
}
@@ -1,66 +1,65 @@
using System;
namespace Artemis.Core.Modules
namespace Artemis.Core.Modules;
/// <summary>
/// Represents a dynamic child value with its property attribute
/// </summary>
public class DynamicChild<T> : DynamicChild
{
/// <summary>
/// Represents a dynamic child value with its property attribute
/// </summary>
public class DynamicChild<T> : DynamicChild
internal DynamicChild(T value, string key, DataModelPropertyAttribute attribute) : base(key, attribute, typeof(T))
{
internal DynamicChild(T value, string key, DataModelPropertyAttribute attribute) : base(key, attribute, typeof(T))
{
Value = value;
}
/// <summary>
/// Gets or sets the current value of the dynamic child
/// </summary>
public T Value { get; set; }
/// <inheritdoc />
protected override object? GetValue()
{
return Value;
}
Value = value;
}
/// <summary>
/// Represents a dynamic child value with its property attribute
/// Gets or sets the current value of the dynamic child
/// </summary>
public abstract class DynamicChild
public T Value { get; set; }
/// <inheritdoc />
protected override object? GetValue()
{
internal DynamicChild(string key, DataModelPropertyAttribute attribute, Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
Key = key ?? throw new ArgumentNullException(nameof(key));
Attribute = attribute ?? throw new ArgumentNullException(nameof(attribute));
Type = type;
}
/// <summary>
/// Gets the key of the dynamic child
/// </summary>
public string Key { get; }
/// <summary>
/// Gets the attribute describing the dynamic child
/// </summary>
public DataModelPropertyAttribute Attribute { get; }
/// <summary>
/// Gets the type of <see cref="BaseValue" />
/// </summary>
public Type Type { get; }
/// <summary>
/// Gets the current value of the dynamic child
/// </summary>
public object? BaseValue => GetValue();
/// <summary>
/// Gets the current value of the dynamic child
/// </summary>
/// <returns>The current value of the dynamic child</returns>
protected abstract object? GetValue();
return Value;
}
}
/// <summary>
/// Represents a dynamic child value with its property attribute
/// </summary>
public abstract class DynamicChild
{
internal DynamicChild(string key, DataModelPropertyAttribute attribute, Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
Key = key ?? throw new ArgumentNullException(nameof(key));
Attribute = attribute ?? throw new ArgumentNullException(nameof(attribute));
Type = type;
}
/// <summary>
/// Gets the key of the dynamic child
/// </summary>
public string Key { get; }
/// <summary>
/// Gets the attribute describing the dynamic child
/// </summary>
public DataModelPropertyAttribute Attribute { get; }
/// <summary>
/// Gets the type of <see cref="BaseValue" />
/// </summary>
public Type Type { get; }
/// <summary>
/// Gets the current value of the dynamic child
/// </summary>
public object? BaseValue => GetValue();
/// <summary>
/// Gets the current value of the dynamic child
/// </summary>
/// <returns>The current value of the dynamic child</returns>
protected abstract object? GetValue();
}
@@ -1,10 +1,8 @@
namespace Artemis.Core.Modules
namespace Artemis.Core.Modules;
/// <summary>
/// The base class for any view model that belongs to a module
/// </summary>
public interface IModuleViewModel
{
/// <summary>
/// The base class for any view model that belongs to a module
/// </summary>
public interface IModuleViewModel
{
}
}
+299 -300
View File
@@ -7,341 +7,340 @@ using System.Linq.Expressions;
using System.Reflection;
using System.Text;
namespace Artemis.Core.Modules
namespace Artemis.Core.Modules;
/// <summary>
/// Allows you to add new data to the Artemis data model
/// </summary>
public abstract class Module<T> : Module where T : DataModel, new()
{
/// <summary>
/// Allows you to add new data to the Artemis data model
/// The data model driving this module
/// <para>Note: This default data model is automatically registered and instantiated upon plugin enable</para>
/// </summary>
public abstract class Module<T> : Module where T : DataModel, new()
public T DataModel
{
/// <summary>
/// The data model driving this module
/// <para>Note: This default data model is automatically registered and instantiated upon plugin enable</para>
/// </summary>
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;
}
/// <summary>
/// Hide the provided property using a lambda expression, e.g.
/// <c>HideProperty(dm => dm.TimeDataModel.CurrentTimeUTC)</c>
/// </summary>
/// <param name="propertyLambda">A lambda expression pointing to the property to ignore</param>
public void HideProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
{
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
if (!HiddenPropertiesList.Any(p => p.Equals(propertyInfo)))
HiddenPropertiesList.Add(propertyInfo);
}
/// <summary>
/// Stop hiding the provided property using a lambda expression, e.g.
/// <c>ShowProperty(dm => dm.TimeDataModel.CurrentTimeUTC)</c>
/// </summary>
/// <param name="propertyLambda">A lambda expression pointing to the property to stop ignoring</param>
public void ShowProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
{
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo));
}
/// <summary>
/// Determines whether the provided dot-separated path is actively being used by Artemis
/// <para>Note: <see cref="IsPropertyInUse" /> is slightly faster but string-based.</para>
/// </summary>
/// <param name="propertyLambda">
/// The path to check per example: <c>IsPropertyInUse(dm => dm.TimeDataModel.CurrentTimeUTC)</c>
/// </param>
/// <param name="includeChildren">
/// If <see langword="true" /> any child of the given path will return true as well; if
/// <see langword="false" /> only an exact path match returns <see langword="true" />.
/// </param>
public bool IsPropertyInUse<TProperty>(Expression<Func<T, TProperty>> propertyLambda, bool includeChildren)
{
string path = GetMemberPath((MemberExpression) propertyLambda.Body);
return IsPropertyInUse(path, includeChildren);
}
/// <summary>
/// Determines whether the provided dot-separated path is actively being used by Artemis
/// </summary>
/// <param name="path">The path to check per example: <c>MyDataModelChild.MyDataModelProperty</c></param>
/// <param name="includeChildren">
/// If <see langword="true" /> any child of the given path will return true as well; if
/// <see langword="false" /> only an exact path match returns <see langword="true" />.
/// </param>
public bool IsPropertyInUse(string path, bool includeChildren)
{
return DataModel.IsPropertyInUse(path, includeChildren);
}
internal override void InternalEnable()
{
DataModel = new T();
DataModel.Module = this;
DataModel.DataModelDescription = GetDataModelDescription();
base.InternalEnable();
}
internal override void InternalDisable()
{
Deactivate(true);
base.InternalDisable();
}
private static string GetMemberPath(MemberExpression? me)
{
StringBuilder builder = new();
while (me != null)
{
builder.Insert(0, me.Member.Name);
me = me.Expression as MemberExpression;
if (me != null)
builder.Insert(0, ".");
}
return builder.ToString();
}
get => InternalDataModel as T ?? throw new InvalidOperationException("Internal datamodel does not match the type of the data model");
internal set => InternalDataModel = value;
}
/// <summary>
/// For internal use only, please use <see cref="Module{T}" />.
/// Hide the provided property using a lambda expression, e.g.
/// <c>HideProperty(dm => dm.TimeDataModel.CurrentTimeUTC)</c>
/// </summary>
public abstract class Module : PluginFeature
/// <param name="propertyLambda">A lambda expression pointing to the property to ignore</param>
public void HideProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
{
private readonly List<(DefaultCategoryName, string)> _defaultProfilePaths = new();
private readonly List<(DefaultCategoryName, string)> _pendingDefaultProfilePaths = new();
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
if (!HiddenPropertiesList.Any(p => p.Equals(propertyInfo)))
HiddenPropertiesList.Add(propertyInfo);
}
/// <summary>
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
/// </summary>
protected internal readonly List<PropertyInfo> HiddenPropertiesList = new();
/// <summary>
/// Stop hiding the provided property using a lambda expression, e.g.
/// <c>ShowProperty(dm => dm.TimeDataModel.CurrentTimeUTC)</c>
/// </summary>
/// <param name="propertyLambda">A lambda expression pointing to the property to stop ignoring</param>
public void ShowProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
{
PropertyInfo propertyInfo = ReflectionUtilities.GetPropertyInfo(DataModel, propertyLambda);
HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo));
}
/// <summary>
/// The base constructor of the <see cref="Module" /> class.
/// </summary>
protected Module()
/// <summary>
/// Determines whether the provided dot-separated path is actively being used by Artemis
/// <para>Note: <see cref="IsPropertyInUse" /> is slightly faster but string-based.</para>
/// </summary>
/// <param name="propertyLambda">
/// The path to check per example: <c>IsPropertyInUse(dm => dm.TimeDataModel.CurrentTimeUTC)</c>
/// </param>
/// <param name="includeChildren">
/// If <see langword="true" /> any child of the given path will return true as well; if
/// <see langword="false" /> only an exact path match returns <see langword="true" />.
/// </param>
public bool IsPropertyInUse<TProperty>(Expression<Func<T, TProperty>> propertyLambda, bool includeChildren)
{
string path = GetMemberPath((MemberExpression) propertyLambda.Body);
return IsPropertyInUse(path, includeChildren);
}
/// <summary>
/// Determines whether the provided dot-separated path is actively being used by Artemis
/// </summary>
/// <param name="path">The path to check per example: <c>MyDataModelChild.MyDataModelProperty</c></param>
/// <param name="includeChildren">
/// If <see langword="true" /> any child of the given path will return true as well; if
/// <see langword="false" /> only an exact path match returns <see langword="true" />.
/// </param>
public bool IsPropertyInUse(string path, bool includeChildren)
{
return DataModel.IsPropertyInUse(path, includeChildren);
}
internal override void InternalEnable()
{
DataModel = new T();
DataModel.Module = this;
DataModel.DataModelDescription = GetDataModelDescription();
base.InternalEnable();
}
internal override void InternalDisable()
{
Deactivate(true);
base.InternalDisable();
}
private static string GetMemberPath(MemberExpression? me)
{
StringBuilder builder = new();
while (me != null)
{
DefaultProfilePaths = new ReadOnlyCollection<(DefaultCategoryName, string)>(_defaultProfilePaths);
HiddenProperties = new ReadOnlyCollection<PropertyInfo>(HiddenPropertiesList);
builder.Insert(0, me.Member.Name);
me = me.Expression as MemberExpression;
if (me != null)
builder.Insert(0, ".");
}
/// <summary>
/// Gets a read only collection of default profile paths
/// </summary>
public IReadOnlyCollection<(DefaultCategoryName, string)> DefaultProfilePaths { get; }
return builder.ToString();
}
}
/// <summary>
/// A list of activation requirements
/// <para>
/// If this list is not <see langword="null" /> and not empty <see cref="IsAlwaysAvailable" /> becomes
/// <see langword="false" /> and the data of this module is only available to profiles specifically targeting it.
/// </para>
/// </summary>
public abstract List<IModuleActivationRequirement>? ActivationRequirements { get; }
/// <summary>
/// For internal use only, please use <see cref="Module{T}" />.
/// </summary>
public abstract class Module : PluginFeature
{
private readonly List<(DefaultCategoryName, string)> _defaultProfilePaths = new();
private readonly List<(DefaultCategoryName, string)> _pendingDefaultProfilePaths = new();
/// <summary>
/// Gets whether this module is activated. A module can only be active while its <see cref="ActivationRequirements" />
/// are met
/// </summary>
public bool IsActivated { get; internal set; }
/// <summary>
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
/// </summary>
protected internal readonly List<PropertyInfo> HiddenPropertiesList = new();
/// <summary>
/// Gets whether this module's activation was due to an override, can only be true if <see cref="IsActivated" /> is
/// <see langword="true" />
/// </summary>
public bool IsActivatedOverride { get; private set; }
/// <summary>
/// The base constructor of the <see cref="Module" /> class.
/// </summary>
protected Module()
{
DefaultProfilePaths = new ReadOnlyCollection<(DefaultCategoryName, string)>(_defaultProfilePaths);
HiddenProperties = new ReadOnlyCollection<PropertyInfo>(HiddenPropertiesList);
}
/// <summary>
/// Gets whether this module should update while <see cref="IsActivatedOverride" /> is <see langword="true" />. When
/// set to <see langword="false" /> <see cref="Update" /> and any timed updates will not get called during an
/// activation override.
/// <para>Defaults to <see langword="false" /></para>
/// </summary>
public bool UpdateDuringActivationOverride { get; protected set; }
/// <summary>
/// Gets a read only collection of default profile paths
/// </summary>
public IReadOnlyCollection<(DefaultCategoryName, string)> DefaultProfilePaths { get; }
/// <summary>
/// Gets or sets the activation requirement mode, defaults to <see cref="ActivationRequirementType.Any" />
/// </summary>
public ActivationRequirementType ActivationRequirementMode { get; set; } = ActivationRequirementType.Any;
/// <summary>
/// A list of activation requirements
/// <para>
/// If this list is not <see langword="null" /> and not empty <see cref="IsAlwaysAvailable" /> becomes
/// <see langword="false" /> and the data of this module is only available to profiles specifically targeting it.
/// </para>
/// </summary>
public abstract List<IModuleActivationRequirement>? ActivationRequirements { get; }
/// <summary>
/// Gets a boolean indicating whether this module is always available to profiles or only to profiles that specifically
/// target this module.
/// <para>
/// Note: <see langword="true" /> if there are any <see cref="ActivationRequirements" />; otherwise
/// <see langword="false" />
/// </para>
/// </summary>
public bool IsAlwaysAvailable => ActivationRequirements == null || ActivationRequirements.Count == 0;
/// <summary>
/// Gets whether this module is activated. A module can only be active while its <see cref="ActivationRequirements" />
/// are met
/// </summary>
public bool IsActivated { get; internal set; }
/// <summary>
/// Gets whether updating this module is currently allowed
/// </summary>
public bool IsUpdateAllowed => IsActivated && (UpdateDuringActivationOverride || !IsActivatedOverride);
/// <summary>
/// Gets whether this module's activation was due to an override, can only be true if <see cref="IsActivated" /> is
/// <see langword="true" />
/// </summary>
public bool IsActivatedOverride { get; private set; }
/// <summary>
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
/// </summary>
public ReadOnlyCollection<PropertyInfo> HiddenProperties { get; }
/// <summary>
/// Gets whether this module should update while <see cref="IsActivatedOverride" /> is <see langword="true" />. When
/// set to <see langword="false" /> <see cref="Update" /> and any timed updates will not get called during an
/// activation override.
/// <para>Defaults to <see langword="false" /></para>
/// </summary>
public bool UpdateDuringActivationOverride { get; protected set; }
internal DataModel? InternalDataModel { get; set; }
/// <summary>
/// Gets or sets the activation requirement mode, defaults to <see cref="ActivationRequirementType.Any" />
/// </summary>
public ActivationRequirementType ActivationRequirementMode { get; set; } = ActivationRequirementType.Any;
/// <summary>
/// Called each frame when the module should update
/// </summary>
/// <param name="deltaTime">Time in seconds since the last update</param>
public abstract void Update(double deltaTime);
/// <summary>
/// Gets a boolean indicating whether this module is always available to profiles or only to profiles that specifically
/// target this module.
/// <para>
/// Note: <see langword="true" /> if there are any <see cref="ActivationRequirements" />; otherwise
/// <see langword="false" />
/// </para>
/// </summary>
public bool IsAlwaysAvailable => ActivationRequirements == null || ActivationRequirements.Count == 0;
/// <summary>
/// Called when the <see cref="ActivationRequirements" /> are met or during an override
/// </summary>
/// <param name="isOverride">
/// If true, the activation was due to an override. This usually means the module was activated
/// by the profile editor
/// </param>
public virtual void ModuleActivated(bool isOverride)
/// <summary>
/// Gets whether updating this module is currently allowed
/// </summary>
public bool IsUpdateAllowed => IsActivated && (UpdateDuringActivationOverride || !IsActivatedOverride);
/// <summary>
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
/// </summary>
public ReadOnlyCollection<PropertyInfo> HiddenProperties { get; }
internal DataModel? InternalDataModel { get; set; }
/// <summary>
/// Called each frame when the module should update
/// </summary>
/// <param name="deltaTime">Time in seconds since the last update</param>
public abstract void Update(double deltaTime);
/// <summary>
/// Called when the <see cref="ActivationRequirements" /> are met or during an override
/// </summary>
/// <param name="isOverride">
/// If true, the activation was due to an override. This usually means the module was activated
/// by the profile editor
/// </param>
public virtual void ModuleActivated(bool isOverride)
{
}
/// <summary>
/// Called when the <see cref="ActivationRequirements" /> are no longer met or during an override
/// </summary>
/// <param name="isOverride">
/// If true, the deactivation was due to an override. This usually means the module was deactivated
/// by the profile editor
/// </param>
public virtual void ModuleDeactivated(bool isOverride)
{
}
/// <summary>
/// Evaluates the activation requirements following the <see cref="ActivationRequirementMode" /> and returns the result
/// </summary>
/// <returns>The evaluated result of the activation requirements</returns>
public bool EvaluateActivationRequirements()
{
if (IsAlwaysAvailable)
return true;
if (ActivationRequirementMode == ActivationRequirementType.All)
return ActivationRequirements!.All(r => r.Evaluate());
if (ActivationRequirementMode == ActivationRequirementType.Any)
return ActivationRequirements!.Any(r => r.Evaluate());
return false;
}
/// <summary>
/// Override to provide your own data model description. By default this returns a description matching your plugin
/// name and description
/// </summary>
/// <returns></returns>
public virtual DataModelPropertyAttribute GetDataModelDescription()
{
return new DataModelPropertyAttribute {Name = Info.Name, Description = Info.Description};
}
/// <summary>
/// Adds a default profile by reading it from the file found at the provided path
/// </summary>
/// <param name="category">The category in which to place the default profile</param>
/// <param name="file">A path pointing towards a profile file. May be relative to the plugin directory.</param>
/// <returns>
/// <see langword="true" /> if the default profile was added; <see langword="false" /> if it was not because it is
/// already in the list.
/// </returns>
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!)
{
}
/// <summary>
/// Called when the <see cref="ActivationRequirements" /> are no longer met or during an override
/// </summary>
/// <param name="isOverride">
/// If true, the deactivation was due to an override. This usually means the module was deactivated
/// by the profile editor
/// </param>
public virtual void ModuleDeactivated(bool isOverride)
{
}
/// <summary>
/// Evaluates the activation requirements following the <see cref="ActivationRequirementMode" /> and returns the result
/// </summary>
/// <returns>The evaluated result of the activation requirements</returns>
public bool EvaluateActivationRequirements()
{
if (IsAlwaysAvailable)
return true;
if (ActivationRequirementMode == ActivationRequirementType.All)
return ActivationRequirements!.All(r => r.Evaluate());
if (ActivationRequirementMode == ActivationRequirementType.Any)
return ActivationRequirements!.Any(r => r.Evaluate());
return false;
}
/// <summary>
/// Override to provide your own data model description. By default this returns a description matching your plugin
/// name and description
/// </summary>
/// <returns></returns>
public virtual DataModelPropertyAttribute GetDataModelDescription()
{
return new DataModelPropertyAttribute {Name = Info.Name, Description = Info.Description};
}
/// <summary>
/// Adds a default profile by reading it from the file found at the provided path
/// </summary>
/// <param name="category">The category in which to place the default profile</param>
/// <param name="file">A path pointing towards a profile file. May be relative to the plugin directory.</param>
/// <returns>
/// <see langword="true" /> if the default profile was added; <see langword="false" /> if it was not because it is
/// already in the list.
/// </returns>
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)))
if (_pendingDefaultProfilePaths.Contains((category, file)))
return false;
_defaultProfilePaths.Add((category, file));
_pendingDefaultProfilePaths.Add((category, file));
return true;
}
internal virtual void InternalUpdate(double deltaTime)
{
StartUpdateMeasure();
if (IsUpdateAllowed)
Update(deltaTime);
StopUpdateMeasure();
}
if (!Path.IsPathRooted(file))
file = Plugin.ResolveRelativePath(file);
internal virtual void Activate(bool isOverride)
{
if (IsActivated)
return;
// Ensure the file exists
if (!File.Exists(file))
throw new ArtemisPluginFeatureException(this, $"Could not find default profile at {file}.");
IsActivatedOverride = isOverride;
ModuleActivated(isOverride);
IsActivated = true;
}
if (_defaultProfilePaths.Contains((category, file)))
return false;
_defaultProfilePaths.Add((category, file));
internal virtual void Deactivate(bool isOverride)
{
if (!IsActivated)
return;
IsActivatedOverride = false;
IsActivated = false;
ModuleDeactivated(isOverride);
}
#region Overrides of PluginFeature
/// <inheritdoc />
internal override void InternalEnable()
{
foreach ((DefaultCategoryName categoryName, string? 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);
}
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
/// <inheritdoc />
internal override void InternalEnable()
{
foreach ((DefaultCategoryName categoryName, string? 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);
}
}
/// <summary>
/// Describes in what way the activation requirements of a module must be met
/// </summary>
public enum ActivationRequirementType
{
/// <summary>
/// Any activation requirement must be met for the module to activate
/// </summary>
Any,
/// <summary>
/// Describes in what way the activation requirements of a module must be met
/// All activation requirements must be met for the module to activate
/// </summary>
public enum ActivationRequirementType
{
/// <summary>
/// Any activation requirement must be met for the module to activate
/// </summary>
Any,
/// <summary>
/// All activation requirements must be met for the module to activate
/// </summary>
All
}
All
}