/********* ** Private methods *********/ /// <summary>Try to get a method which matches the given delegate.</summary> /// <param name="instance">The token value provider.</param> /// <param name="name">The method name on <see cref="ConventionWrapper"/> being mapped.</param> /// <param name="methodDelegate">The created method delegate, if any.</param> /// <param name="error">The error indicating why creating the wrapper failed, if applicable.</param> private static bool TryWrapMethod <TDelegate>(object instance, string name, out TDelegate methodDelegate, out string error) where TDelegate : Delegate { methodDelegate = null; error = null; // check target method exists { MethodInfo[] candidates = instance.GetType().GetMethods().Where(p => p.Name == name).ToArray(); if (candidates.Length == 0) { return(false); } if (candidates.Length > 1) { return(Fail($"method {name} has multiple implementations.", out error)); } } // map to delegate try { methodDelegate = (TDelegate)Delegate.CreateDelegate(typeof(TDelegate), instance, name); error = null; return(true); } catch (ArgumentException ex) { methodDelegate = null; return(Fail(ConventionWrapper.GetMethodMismatchError(instance, name, ex), out error)); } }
/********* ** Public methods *********/ /// <summary>Try to create a convention wrapper for a given token instance.</summary> /// <param name="instance">The token instance.</param> /// <param name="reflection">Simplifies access to private code.</param> /// <param name="wrapper">The wrapper, if created successfully.</param> /// <param name="error">The error indicating why creating the wrapper failed, if applicable.</param> public static bool TryCreate(object instance, IReflectionHelper reflection, [NotNullWhen(true)] out ConventionWrapper?wrapper, [NotNullWhen(false)] out string?error) { error = null; var result = new ConventionWrapper(); // Map a delegate type to a token method and wrapper field by convention. // This assumes the delegate type name matches the method name, and the wrapper has a // field of the delegate type in the form {name}Impl. bool TryMap <TDelegate>([NotNullWhen(false)] out string?mapError) where TDelegate : Delegate
/********* ** Public methods *********/ /// <summary>Try to create a convention wrapper for a given token instance.</summary> /// <param name="instance">The token instance.</param> /// <param name="reflection">Simplifies access to private code.</param> /// <param name="wrapper">The wrapper, if created successfully.</param> /// <param name="error">The error indicating why creating the wrapper failed, if applicable.</param> public static bool TryCreate(object instance, IReflectionHelper reflection, out ConventionWrapper wrapper, out string error) { error = null; var result = new ConventionWrapper(); // Map a delegate type to a token method and wrapper field by convention. // This assumes the delegate type name matches the method name, and the wrapper has a // field of the delegate type in the form {name}Impl. bool TryMap <TDelegate>(out string mapError) where TDelegate : Delegate { string methodName = typeof(TDelegate).Name; string fieldName = $"{methodName}Impl"; if (ConventionWrapper.TryWrapMethod(instance, methodName, out TDelegate mapped, out mapError)) { reflection.GetField <TDelegate>(result, fieldName).SetValue(mapped); } return(mapError == null); } // detect unknown methods bool succeeded = true; { string[] unknownMethods = ( from MethodInfo method in instance.GetType().GetMethods() where typeof(ConventionWrapper).GetMethod(method.Name) == null select method.Name ) .ToArray(); if (unknownMethods.Any()) { succeeded = false; error = unknownMethods.Length == 1 ? $"found unsupported '{unknownMethods[0]}' method" : $"found unsupported methods: {string.Join(", ", unknownMethods)}"; } } // map implemented fields if (succeeded) { succeeded = // metadata TryMap <ConventionDelegates.IsMutable>(out error) && TryMap <ConventionDelegates.AllowsInput>(out error) && TryMap <ConventionDelegates.RequiresInput>(out error) && TryMap <ConventionDelegates.CanHaveMultipleValues>(out error) && TryMap <ConventionDelegates.GetValidInputs>(out error) && TryMap <ConventionDelegates.HasBoundedValues>(out error) && TryMap <ConventionDelegates.HasBoundedRangeValues>(out error) && TryMap <ConventionDelegates.TryValidateInput>(out error) && TryMap <ConventionDelegates.TryValidateValues>(out error) && TryMap <ConventionDelegates.NormalizeValue>(out error) // state && TryMap <ConventionDelegates.IsReady>(out error) && TryMap <ConventionDelegates.GetValues>(out error) && TryMap <ConventionDelegates.UpdateContext>(out error); } wrapper = succeeded ? result : null; return(succeeded); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="name">The token name. This only needs to be unique for your mod; Content Patcher will prefix it with your mod ID automatically, like <c>Pathoschild.ExampleMod/SomeTokenName</c>.</param> /// <param name="provider">The mod-provided value provider.</param> public ConventionValueProvider(string name, ConventionWrapper provider) { this.Name = name; this.Provider = provider; this.IsReady = provider.IsReady(); }