Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Review #2764

Open
wants to merge 19 commits into
base: dev
Choose a base branch
from
Open

Review #2764

wants to merge 19 commits into from

Conversation

warquys
Copy link
Contributor

@warquys warquys commented Aug 7, 2024

I don't want to impose my decisions. But, I want this to be change or at least be heard.
Some stuffs seem to me to have been rushed. So i added comamnt and do litle edition.
I don't know how you prefer to resolve the problem. So i pointed them with comment for the most.

Why not discord:
Discussions here will be easier than discord to keep up with change.

I also try to use the new Enum. But realy i do not get who it work.

When I fold the code, it seems to me that there is an error in the management of super classes.
And when i try, This isn't working, I don't know if it's me or the code. So I place my experiences here:

Interactive Test
> #reset core
Resetting execution engine.
Loading context from 'CSharpInteractive.rsp'.
> using System.Reflection;
>    /// <summary>
     /// An interface for all enum classes.
     /// </summary>
     public interface IEnumClass
     {

     }
> public abstract class UniqueUnmanagedEnumClass<TSource, TObject> : IComparable, IEquatable<TObject>, IComparable<TObject>, IComparer<TObject>, IConvertible, IEnumClass
        where TSource : unmanaged, IComparable, IFormattable, IConvertible, IComparable<TSource>, IEquatable<TSource>
        where TObject : UniqueUnmanagedEnumClass<TSource, TObject>
    {
        private static SortedList<TSource, TObject> values;
        private static int nextValue = int.MinValue;
        private static bool isDefined;

        private string name;

        /// <summary>
        /// Initializes a new instance of the <see cref="UniqueUnmanagedEnumClass{TSource, TObject}"/> class.
        /// </summary>
        public UniqueUnmanagedEnumClass()
        {
            values ??= new();
            TypeCode code = Convert.GetTypeCode(typeof(TSource).GetField("MinValue").GetValue(null));

            // @Nao If the value is not an Uxxxxx it will get an overflowLike if it use an Int16, Byte or SByte
            // Maybe use a long for nextValue and do "nextValue = typeof(TSource).GetField("MinValue").GetValue(null)"
            // it also be a struct containing only unmanged structThe best solution is proably to
            // look for upper version of the C# to resolve this with the new numeric interfaces.
            if (code is TypeCode.UInt16 or TypeCode.UInt32 or TypeCode.UInt64)
                nextValue = 0;

            lock (values)
            {
                TSource value;
                do
                {
                    value = (TSource)Convert.ChangeType(nextValue++, code);
                }
                while (values.ContainsKey(value));

                Value = value;

                // @nao If the value is register when a new instance is created.
                // Maybe put the ctor in protected to avoid exteranal code (outisde the enum) to create new values
                values.Add(value, (TObject)this);
            }
        }

        /// <summary>
        /// Gets all <typeparamref name="TObject"/> object instances.
        /// </summary>
        public static IEnumerable<TObject> Values => values.Values;

        /// <summary>
        /// Gets the value of the enum item.
        /// </summary>
        public TSource Value { get; }

        /// <summary>
        /// Gets the name determined from reflection.
        /// </summary>
        public string Name
        {
            get
            {
                if (isDefined)
                    return name;

                IEnumerable<FieldInfo> fields = typeof(TObject)
                    .GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public)
                    .Where(t => t.FieldType == typeof(TObject));

                foreach (FieldInfo field in fields)
                {
                    TObject instance = (TObject)field.GetValue(null);
                    instance.name = field.Name;
                }

                isDefined = true;
                return name;
            }
        }

        /// <summary>
        /// Implicitly converts the <see cref="UniqueUnmanagedEnumClass{TSource, TObject}"/> to <typeparamref name="TSource"/>.
        /// </summary>
        /// <param name="value">The value to convert.</param>
        public static implicit operator TSource(UniqueUnmanagedEnumClass<TSource, TObject> value) => value.Value;

        /// <summary>
        /// Implicitly converts the <typeparamref name="TSource"/> to <see cref="EnumClass{TSource, TObject}"/>.
        /// </summary>
        /// <param name="value">The value to convert.</param>
        public static implicit operator UniqueUnmanagedEnumClass<TSource, TObject>(TSource value) => values[value];

        /// <summary>
        /// Implicitly converts the <see cref="UniqueUnmanagedEnumClass{TSource, TObject}"/> to <typeparamref name="TObject"/>.
        /// </summary>
        /// <param name="value">The value to convert.</param>
        public static implicit operator TObject(UniqueUnmanagedEnumClass<TSource, TObject> value) => values[value];

        /// <summary>
        /// Casts the specified <paramref name="value"/> to the corresponding type.
        /// </summary>
        /// <param name="value">The enum value to be cast.</param>
        /// <returns>The cast object.</returns>
        public static TObject Cast(TSource value) => values[value];

        /// <summary>
        /// Casts the specified <paramref name="values"/> to the corresponding type.
        /// </summary>
        /// <param name="values">The enum values to be cast.</param>
        /// <returns>The cast object.</returns>
        public static IEnumerable<TObject> Cast(IEnumerable<TSource> values)
        {
            foreach (TSource value in values)
                yield return UniqueUnmanagedEnumClass<TSource, TObject>.values[value];
        }

        /// <summary>
        /// Casts the specified <paramref name="values"/> to the corresponding type.
        /// </summary>
        /// <typeparam name="T">The type to cast the enum to.</typeparam>
        /// <param name="values">The enum values to be cast.</param>
        /// <returns>The cast <typeparamref name="T"/> object.</returns>
        public static IEnumerable<T> Cast<T>(IEnumerable<TSource> values)
            where T : TObject
        {
            foreach (TSource value in values)
                yield return Cast<T>(value);
        }

        /// <summary>
        /// Casts the specified <paramref name="value"/> to the corresponding type.
        /// </summary>
        /// <typeparam name="T">The type to cast the enum to.</typeparam>
        /// <param name="value">The enum value to be cast.</param>
        /// <returns>The cast <typeparamref name="T"/> object.</returns>
        public static T Cast<T>(TSource value)
            where T : TObject => (T)values[value];

        /// <summary>
        /// Safely casts the specified <paramref name="value"/> to the corresponding type.
        /// </summary>
        /// <param name="value">The enum value to be cast.</param>
        /// <param name="result">The cast <paramref name="value"/>.</param>
        /// <returns><see langword="true"/> if the <paramref name="value"/> was cast; otherwise, <see langword="false"/>.</returns>
        public static bool SafeCast(TSource value, out TObject result) => values.TryGetValue(value, out result);

        /// <summary>
        /// Safely casts the specified <paramref name="values"/> to the corresponding type.
        /// </summary>
        /// <param name="values">The enum value to be cast.</param>
        /// <param name="results">The cast <paramref name="values"/>.</param>
        /// <returns><see langword="true"/> if the <paramref name="values"/> was cast; otherwise, <see langword="false"/>.</returns>
        public static bool SafeCast(IEnumerable<TSource> values, out IEnumerable<TObject> results)
        {
            results = null;

            List<TObject> tmpValues = new List<TObject>();
            foreach (TSource value in values)
            {
                if (!UniqueUnmanagedEnumClass<TSource, TObject>.values.TryGetValue(value, out TObject result))
                    return false;

                tmpValues.Add(result);
            }

            results = tmpValues;
            return true;
        }

        /// <summary>
        /// Retrieves an array of the values of the constants in a specified <see cref="UnmanagedEnumClass{TSource, TObject}"/>.
        /// </summary>
        /// <param name="type">The <see cref="UnmanagedEnumClass{TSource, TObject}"/> type.</param>
        /// <returns>An array of the values of the constants in a specified <see cref="UnmanagedEnumClass{TSource, TObject}"/>.</returns>
        public static TSource[] GetValues(Type type)
        {
            if (type is null)
                throw new NullReferenceException("The specified type parameter is null");

            if (!type.IsSubclassOf(typeof(UniqueUnmanagedEnumClass<TSource, TObject>)) && type.BaseType != typeof(UniqueUnmanagedEnumClass<TSource, TObject>))
                throw new Exception("The specified type parameter is not a UniqueUnmanagedEnumClass<TSource, TObject> type.");

            return typeof(TSource)
                .GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.GetField)
                .Where(field => field.FieldType == typeof(TSource))
                .Select(field => (TSource)field.GetValue(null))
                .ToArray();
        }

        /// <summary>
        /// Parses a <see cref="string"/> object.
        /// </summary>
        /// <param name="obj">The object to be parsed.</param>
        /// <returns>The corresponding <typeparamref name="TObject"/> object instance, or <see langword="null"/> if not found.</returns>
        public static TObject Parse(string obj)
        {
            foreach (TObject value in values.Values.Where(value => string.Compare(value.Name, obj, StringComparison.OrdinalIgnoreCase) == 0))
                return value;

            return null;
        }

        /// <summary>
        /// Converts the <see cref="UnmanagedEnumClass{TSource, TObject}"/> instance to a human-readable <see cref="string"/> representation.
        /// </summary>
        /// <returns>A human-readable <see cref="string"/> representation of the <see cref="UnmanagedEnumClass{TSource, TObject}"/> instance.</returns>
        public override string ToString() => Name;

        /// <summary>
        /// Determines whether the specified object is equal to the current object.
        /// </summary>
        /// <param name="obj">The object to compare.</param>
        /// <returns><see langword="true"/> if the object was equal; otherwise, <see langword="false"/>.</returns>
        public override bool Equals(object obj) =>
            obj != null && (obj is TSource value ? Value.Equals(value) : obj is TObject derived && Value.Equals(derived.Value));

        /// <summary>
        /// Determines whether the specified object is equal to the current object.
        /// </summary>
        /// <param name="other">The object to compare.</param>
        /// <returns><see langword="true"/> if the object was equal; otherwise, <see langword="false"/>.</returns>
        public bool Equals(TObject other) => Value.Equals(other.Value);

        /// <summary>
        /// Returns a the 32-bit signed hash code of the current object instance.
        /// </summary>
        /// <returns>The 32-bit signed hash code of the current object instance.</returns>
        public override int GetHashCode() => Value.GetHashCode();

        /// <summary>
        /// Compares the current instance with another object of the same type and returns
        /// an integer that indicates whether the current instance precedes, follows, or
        /// occurs in the same position in the sort order as the other object.
        /// </summary>
        /// <param name="other">An object to compare with this instance.</param>
        /// <returns>
        /// A value that indicates the relative order of the objects being compared.
        /// The return value has these meanings: Value Meaning Less than zero This instance precedes other in the sort order.
        /// Zero This instance occurs in the same position in the sort order as other.
        /// Greater than zero This instance follows other in the sort order.
        /// </returns>
        public int CompareTo(TObject other) => Value.CompareTo(other.Value);

        /// <summary>
        /// Compares the current instance with another object of the same type and returns
        /// an integer that indicates whether the current instance precedes, follows, or
        /// occurs in the same position in the sort order as the other object.
        /// </summary>
        /// <param name="obj">An object to compare with this instance.</param>
        /// <returns>
        /// A value that indicates the relative order of the objects being compared.
        /// The return value has these meanings: Value Meaning Less than zero This instance precedes other in the sort order.
        /// Zero This instance occurs in the same position in the sort order as other.
        /// Greater than zero This instance follows other in the sort order.
        /// </returns>
        public int CompareTo(object obj) =>
            obj == null ? -1 : obj is TSource value ? Value.CompareTo(value) : obj is TObject derived ? Value.CompareTo(derived.Value) : -1;

        /// <summary>
        /// Compares the specified object instance with another object of the same type and returns
        /// an integer that indicates whether the current instance precedes, follows, or
        /// occurs in the same position in the sort order as the other object.
        /// </summary>
        /// <param name="x">An object to compare.</param>
        /// <param name="y">Another object to compare.</param>
        /// <returns>
        /// A value that indicates the relative order of the objects being compared.
        /// The return value has these meanings: Value Meaning Less than zero This instance precedes other in the sort order.
        /// Zero This instance occurs in the same position in the sort order as other.
        /// Greater than zero This instance follows other in the sort order.
        /// </returns>
        public int Compare(TObject x, TObject y) => x == null ? -1 : y == null ? 1 : x.Value.CompareTo(y.Value);

		/// <inheritdoc/>
        TypeCode IConvertible.GetTypeCode() => Value.GetTypeCode();

        /// <inheritdoc/>
        bool IConvertible.ToBoolean(IFormatProvider provider) => Value.ToBoolean(provider);

        /// <inheritdoc/>
        char IConvertible.ToChar(IFormatProvider provider) => Value.ToChar(provider);

        /// <inheritdoc/>
        sbyte IConvertible.ToSByte(IFormatProvider provider) => Value.ToSByte(provider);

        /// <inheritdoc/>
        byte IConvertible.ToByte(IFormatProvider provider) => Value.ToByte(provider);

        /// <inheritdoc/>
        short IConvertible.ToInt16(IFormatProvider provider) => Value.ToInt16(provider);

        /// <inheritdoc/>
        ushort IConvertible.ToUInt16(IFormatProvider provider) => Value.ToUInt16(provider);

        /// <inheritdoc/>
        int IConvertible.ToInt32(IFormatProvider provider) => Value.ToInt32(provider);

        /// <inheritdoc/>
        uint IConvertible.ToUInt32(IFormatProvider provider) => Value.ToUInt32(provider);

        /// <inheritdoc/>
        long IConvertible.ToInt64(IFormatProvider provider) => Value.ToInt64(provider);

        /// <inheritdoc/>
        ulong IConvertible.ToUInt64(IFormatProvider provider) => Value.ToUInt64(provider);

        /// <inheritdoc/>
        float IConvertible.ToSingle(IFormatProvider provider) => Value.ToSingle(provider);

        /// <inheritdoc/>
        double IConvertible.ToDouble(IFormatProvider provider) => Value.ToDouble(provider);

        /// <inheritdoc/>
        decimal IConvertible.ToDecimal(IFormatProvider provider) => Value.ToDecimal(provider);

        /// <inheritdoc/>
        DateTime IConvertible.ToDateTime(IFormatProvider provider) => Value.ToDateTime(provider);

        /// <inheritdoc/>
        string IConvertible.ToString(IFormatProvider provider) => ToString();

        /// <inheritdoc/>
        object IConvertible.ToType(Type conversionType, IFormatProvider provider) => Value.ToType(conversionType, provider);
    }

> public class Enum1 : UniqueUnmanagedEnumClass<uint, Enum1>
{
    /// <summary>
    /// Represents an invalid custom role.
    /// </summary>
    public static readonly Enum1 None = new();

    public static readonly Enum1 Foo = new();

    public static readonly Enum1 Bar = new();
}

public class Enum2 : UniqueUnmanagedEnumClass<uint, Enum1>
{
    /// <summary>
    /// Represents an invalid custom role.
    /// </summary>
    public static readonly Enum1 None = new();

    public static readonly Enum1 Foo = new();
    
        public static readonly Enum1 Bar = new();
}

public class Enum1Ex : Enum1
{
    public static readonly Enum1 ExtenedValue = new();

    public static readonly Enum1Ex ExtenedValue2 = new();
}

public class Enum2Ex : Enum2
{
    public static readonly Enum2 ExtenedValue1 = new();

    public static readonly Enum2Ex ExtenedValue2 = new();
}
> Enum1.Bar.Name
"Bar"
> Enum1Ex.Bar.Name
"Bar"
> Enum1Ex.ExtenedValue.Name
null
> Enum1Ex.ExtenedValue2.Name
null
> Enum2Ex.ExtenedValue1.Name
System.TypeInitializationException: The type initializer for 'Enum2Ex' threw an exception.
> Enum2Ex.ExtenedValue2.Name
System.TypeInitializationException: The type initializer for 'Enum2Ex' threw an exception.
> Enum2Ex.ExtenedValue2
System.TypeInitializationException: The type initializer for 'Enum2Ex' threw an exception.
> try { _ = Enum2Ex.ExtenedValue2.Name; } catch (Exception e) { WriteLine(e?.InnerException?.ToString() ?? "None"); }
System.InvalidCastException: Unable to cast object of type 'Enum2' to type 'Enum1'.
   at Submission#4.UniqueUnmanagedEnumClass`2..ctor()
   at Submission#5.Enum2..ctor()
   at Submission#5.Enum2Ex..cctor()
> public class Enum1 : UniqueUnmanagedEnumClass<uint, Enum1>
{
    /// <summary>
    /// Represents an invalid custom role.
    /// </summary>
    public static readonly Enum1 None = new();

    public static readonly Enum1 Foo = new();

    public static readonly Enum1 Bar = new();
}

public class Enum2 : UniqueUnmanagedEnumClass<uint, Enum2>
{
    /// <summary>
    /// Represents an invalid custom role.
    /// </summary>
    public static readonly Enum1 None = new();

    public static readonly Enum1 Foo = new();
    
    public static readonly Enum1 Bar = new();
}

public class Enum1Ex : Enum1
{
    public static readonly Enum1 ExtenedValue1 = new();

    public static readonly Enum1Ex ExtenedValue2 = new();
}

public class Enum2Ex : Enum2
{
    public static readonly Enum2 ExtenedValue1 = new();

    public static readonly Enum2Ex ExtenedValue2 = new();
}
> Enum1.Bar.Name
"Bar"
> Enum1Ex.Bar.Name
"Bar"
> Enum1Ex.ExtenedValue1.Name
null
> Enum1Ex.ExtenedValue2.Name
null
> Enum2Ex.ExtenedValue1.Name
null

@github-actions github-actions bot added regarding-api An issue or PR targeting the Exiled API project CustomModules An issue or PR targeting the Exiled Custom Modules project labels Aug 7, 2024
@NaoUnderscore
Copy link
Collaborator

Interesting, we aren't supporting multiple inheritance levels, we must fix it.

@NaoUnderscore NaoUnderscore added bug Something isn't working help wanted Extra attention is needed requires-testing Things need to be verified by an Exiled Developer/Contributor P1 First Priority labels Aug 8, 2024
Exiled.API/Features/Core/Generic/EBehaviour.cs Outdated Show resolved Hide resolved
Exiled.API/Features/Core/Generic/Singleton.cs Outdated Show resolved Hide resolved
Exiled.API/Features/Core/Generic/Singleton.cs Outdated Show resolved Hide resolved
@@ -59,6 +64,10 @@ public Singleton(T value)
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <param name="instance">The object instance.</param>
/// <returns><see langword="true"/> if the object instance is not null and can be casted as the specified type; otherwise, <see langword="false"/>.</returns>
// @nao It will return the Instance.
// But with this curent class definition you can register derived class of T.
// They can be registred but not retrived. Also why make this method generic if this not to get
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was high writing most of this code

@@ -40,6 +40,10 @@ public UniqueUnmanagedEnumClass()
values ??= new();
TypeCode code = Convert.GetTypeCode(typeof(TSource).GetField("MinValue").GetValue(null));

// @Nao If the value is not an Uxxxxx it will get an overflow. Like if it use an Int16, Byte or SByte
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right

@@ -53,6 +57,9 @@ public UniqueUnmanagedEnumClass()
while (values.ContainsKey(value));

Value = value;

// @nao If the value is register when a new instance is created.
// Maybe put the ctor in protected to avoid exteranal code (outisde the enum) to create new values
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal protected can be cool if Exiled need to create dedicated new instance in an helper class

Exiled.CustomModules/API/Features/RespawnManager.cs Outdated Show resolved Hide resolved
{
ev.MaxWaveSize = customTeam.Size;
return;
}

// @Nao, it cool to use the event system for this. But if an other plugin
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right

Copy link
Member

@iamalexrouse iamalexrouse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm gonna ignore the Grade 12 spelling.

Exiled.API/Features/Core/GameEntity.cs Outdated Show resolved Hide resolved
Exiled.API/Features/Core/Generic/EBehaviour.cs Outdated Show resolved Hide resolved
Exiled.API/Features/Core/Generic/Singleton.cs Outdated Show resolved Hide resolved
Exiled.API/Features/Core/Generic/Singleton.cs Outdated Show resolved Hide resolved
Exiled.API/Features/Respawn.cs Show resolved Hide resolved
@warquys
Copy link
Contributor Author

warquys commented Sep 11, 2024

I do not get the point of TypeCastObject do the C# default is, (T) and as has limitation ? Or is it to add new "Castable" that are not of the same type.

I know that implementing cast operators don't allow is and as. However, if the method requires a specific type. There is no problem because the cast will take place before the call.

Is it to do runtime editable castable object ? If yes, where do you need this ? I do not see an usage of ITypeCast in any method. And the class using TypeCastMono and TypeCastObject only use the is, (T) and as with static generique.

The code has been simplified to no longer use a dictionary to manage multiple instances of `Singleton<T>`. Now a single instance of `T` is used via a static `Instance` property.

The `Create` method has been changed to set the instance if it is not already set, and a new `Destroy` method has been added to reset the instance to `null`.

The `TryGet` and `Destroy` methods have been simplified to work with the new single instance implementation.

The calls to `Singleton<VirtualPlugin>.Create(this)` and `Singleton<VirtualPlugin>.Destroy(this)` in `VirtualPlugin.cs` have been commented out, with annotations suggesting that use of the class itself as a singleton might require a different approach to avoid overwriting other instances.
@warquys
Copy link
Contributor Author

warquys commented Sep 12, 2024

@NaoUnderscore I rewrite the Singleton, are you ok ?

@NaoUnderscore
Copy link
Collaborator

I do not get the point of TypeCastObject do the C# default is, (T) and as has limitation ? Or is it to add new "Castable" that are not of the same type.

I know that implementing cast operators don't allow is and as. However, if the method requires a specific type. There is no problem because the cast will take place before the call.

Is it to do runtime editable castable object ? If yes, where do you need this ? I do not see an usage of ITypeCast in any method. And the class using TypeCastMono and TypeCastObject only use the is, (T) and as with static generique.

That's a way to make everything more versatile, so we can safely define our own Cast overloads, which means that if we want to change something in TypeCaseObject that's going to be reflected to all classes using it, it's useful in case of custom modules.

@NaoUnderscore
Copy link
Collaborator

@NaoUnderscore I rewrite the Singleton, are you ok ?

Go ahead, I've made some changes to StaticActor and their generic version as well.

warquys and others added 8 commits September 14, 2024 02:46
The code has been simplified to no longer use a dictionary to manage multiple instances of `Singleton<T>`. Now a single instance of `T` is used via a static `Instance` property.

The `Create` method has been changed to set the instance if it is not already set, and a new `Destroy` method has been added to reset the instance to `null`.

The `TryGet` and `Destroy` methods have been simplified to work with the new single instance implementation.

The calls to `Singleton<VirtualPlugin>.Create(this)` and `Singleton<VirtualPlugin>.Destroy(this)` in `VirtualPlugin.cs` have been commented out, with annotations suggesting that use of the class itself as a singleton might require a different approach to avoid overwriting other instances.
@warquys
Copy link
Contributor Author

warquys commented Sep 22, 2024

I no longer have time to work on this project.
Sorry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working CustomModules An issue or PR targeting the Exiled Custom Modules project help wanted Extra attention is needed P1 First Priority regarding-api An issue or PR targeting the Exiled API project requires-testing Things need to be verified by an Exiled Developer/Contributor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants