~ruther/NosSmooth.Local

23b5f3909ab87cb87e066d6cb3506cb124a36442 — Rutherther 2 years ago 922401c
feat(shared): implement support for optional modules and hooks
M src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SharedHookManager.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SharedHookManager.cs +67 -34
@@ 44,112 44,137 @@ public class SharedHookManager
    /// <param name="browserManager">The browser manager.</param>
    /// <param name="options">The initial options to be respected.</param>
    /// <returns>The dictionary containing all of the hooks.</returns>
    public Result<Dictionary<string, INostaleHook>> InitializeInstance
    public (Dictionary<string, INostaleHook>, IResult) InitializeInstance
        (NosBindingManager bindingManager, NosBrowserManager browserManager, HookManagerOptions options)
    {
        IResult result = Result.FromSuccess();
        if (!_initialized)
        {
            var result = _underlyingManager.Initialize(bindingManager, browserManager);
            result = _underlyingManager.Initialize(bindingManager, browserManager);
            _initialized = true;

            if (!result.IsSuccess)
            {
                return Result<Dictionary<string, INostaleHook>>.FromError(result.Error);
            }
        }

        var hooks = new Dictionary<string, INostaleHook>();

        // TODO: initialize using reflection
        hooks.Add
        HandleAdd
        (
            _underlyingManager.Periodic.Name,
            hooks,
            IHookManager.PeriodicName,
            InitializeSingleHook
            (
                new PeriodicHook(_underlyingManager.Periodic),
                _underlyingManager.Periodic,
                u => new PeriodicHook(u),
                options.PeriodicHook
            )
        );

        hooks.Add
        HandleAdd
        (
            _underlyingManager.EntityFocus.Name,
            hooks,
            IHookManager.EntityFocusName,
            InitializeSingleHook
            (
                new EntityFocusHook(_underlyingManager.EntityFocus),
                _underlyingManager.EntityFocus,
                u => new EntityFocusHook(u),
                options.EntityFocusHook
            )
        );

        hooks.Add
        HandleAdd
        (
            _underlyingManager.EntityFollow.Name,
            hooks,
            IHookManager.EntityFollowName,
            InitializeSingleHook
            (
                new EntityFollowHook(_underlyingManager.EntityFollow),
                _underlyingManager.EntityFollow,
                u => new EntityFollowHook(u),
                options.EntityFollowHook
            )
        );

        hooks.Add
        HandleAdd
        (
            _underlyingManager.EntityUnfollow.Name,
            hooks,
            IHookManager.EntityUnfollowName,
            InitializeSingleHook
            (
                new EntityUnfollowHook(_underlyingManager.EntityUnfollow),
                _underlyingManager.EntityUnfollow,
                u => new EntityUnfollowHook(u),
                options.EntityUnfollowHook
            )
        );

        hooks.Add
        HandleAdd
        (
            _underlyingManager.PacketReceive.Name,
            hooks,
            IHookManager.PacketReceiveName,
            InitializeSingleHook
            (
                new PacketReceiveHook(_underlyingManager.PacketReceive),
                _underlyingManager.PacketReceive,
                u => new PacketReceiveHook(u),
                options.PacketReceiveHook
            )
        );

        hooks.Add
        HandleAdd
        (
            _underlyingManager.PacketSend.Name,
            hooks,
            IHookManager.PacketSendName,
            InitializeSingleHook
            (
                new PacketSendHook(_underlyingManager.PacketSend),
                _underlyingManager.PacketSend,
                u => new PacketSendHook(u),
                options.PacketSendHook
            )
        );

        hooks.Add
        HandleAdd
        (
            _underlyingManager.PetWalk.Name,
            hooks,
            IHookManager.PetWalkName,
            InitializeSingleHook
            (
                new PetWalkHook(_underlyingManager.PetWalk),
                _underlyingManager.PetWalk,
                u => new PetWalkHook(u),
                options.PetWalkHook
            )
        );

        hooks.Add
        HandleAdd
        (
            _underlyingManager.PlayerWalk.Name,
            hooks,
            IHookManager.CharacterWalkName,
            InitializeSingleHook
            (
                new PlayerWalkHook(_underlyingManager.PlayerWalk),
                _underlyingManager.PlayerWalk,
                u => new PlayerWalkHook(u),
                options.PlayerWalkHook
            )
        );

        return hooks;
        return (hooks, result);
    }

    private INostaleHook<TFunction, TWrapperFunction, TEventArgs> InitializeSingleHook<TFunction, TWrapperFunction,
        TEventArgs>(SingleHook<TFunction, TWrapperFunction, TEventArgs> hook, HookOptions options)
    private INostaleHook<TFunction, TWrapperFunction, TEventArgs>? InitializeSingleHook<THook, TFunction,
        TWrapperFunction,
        TEventArgs>
    (
        Optional<THook> hookOptional,
        Func<THook, SingleHook<TFunction, TWrapperFunction, TEventArgs>> hookCreator,
        HookOptions options
    )
        where THook : notnull
        where TFunction : Delegate
        where TWrapperFunction : Delegate
        where TEventArgs : System.EventArgs
    {
        if (!hookOptional.TryGet(out var underlyingHook))
        {
            return null;
        }

        var hook = hookCreator(underlyingHook);
        hook.StateChanged += (_, state) =>
        {
            if (!_hookedCount.ContainsKey(hook.Name))


@@ 176,4 201,12 @@ public class SharedHookManager

        return hook;
    }

    private void HandleAdd(Dictionary<string, INostaleHook> hooks, string name, INostaleHook? hook)
    {
        if (hook is not null)
        {
            hooks.Add(name, hook);
        }
    }
}
\ No newline at end of file

M src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHook.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHook.cs +89 -85
@@ 1,86 1,90 @@
//
//  SingleHook.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.ComponentModel;
using NosSmooth.Extensions.SharedBinding.EventArgs;
using NosSmooth.LocalBinding.Hooks;
using Remora.Results;

namespace NosSmooth.Extensions.SharedBinding.Hooks;

/// <summary>
/// A hook for a single instance of NosSmooth sharing with the rest of application.
/// </summary>
/// <typeparam name="TFunction">The function delegate.</typeparam>
/// <typeparam name="TWrapperFunction">A wrapper function that abstracts the call to original function. May get the neccessary object to call the function and accept only relevant arguments.</typeparam>
/// <typeparam name="TEventArgs">The event args used in case of a call.</typeparam>
public class SingleHook<TFunction, TWrapperFunction, TEventArgs> : INostaleHook<TFunction, TWrapperFunction, TEventArgs>
    where TFunction : Delegate
    where TWrapperFunction : Delegate
    where TEventArgs : System.EventArgs
{
    private readonly INostaleHook<TFunction, TWrapperFunction, TEventArgs> _underlyingHook;

    /// <summary>
    /// Initializes a new instance of the <see cref="SingleHook{TFunction, TWrapperFunction, TEventArgs}"/> class.
    /// </summary>
    /// <param name="underlyingHook">The underlying hook.</param>
    public SingleHook(INostaleHook<TFunction, TWrapperFunction, TEventArgs> underlyingHook)
    {
        _underlyingHook = underlyingHook;
    }

    /// <summary>
    /// Called upon Enable or Disable.
    /// </summary>
    public event EventHandler<HookStateEventArgs>? StateChanged;

    /// <inheritdoc />
    public string Name => _underlyingHook.Name;

    /// <inheritdoc />
    public bool IsEnabled { get; private set; }

    /// <inheritdoc />
    public Result Enable()
    {
        if (!IsEnabled)
        {
            IsEnabled = true;
            StateChanged?.Invoke(this, new HookStateEventArgs(true));
            _underlyingHook.Called += FireCalled;
        }

        return Result.FromSuccess();
    }

    /// <inheritdoc />
    public Result Disable()
    {
        if (IsEnabled)
        {
            IsEnabled = true;
            StateChanged?.Invoke(this, new HookStateEventArgs(false));
            _underlyingHook.Called -= FireCalled;
        }

        return Result.FromSuccess();
    }

    private void FireCalled(object? owner, TEventArgs eventArgs)
    {
        Called?.Invoke(this, eventArgs);
    }

    /// <inheritdoc />
    public TWrapperFunction WrapperFunction => _underlyingHook.WrapperFunction;

    /// <inheritdoc />
    public TFunction OriginalFunction => _underlyingHook.OriginalFunction;

    /// <inheritdoc />
    public event EventHandler<TEventArgs>? Called;
//
//  SingleHook.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.ComponentModel;
using NosSmooth.Extensions.SharedBinding.EventArgs;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Hooks;
using Remora.Results;

namespace NosSmooth.Extensions.SharedBinding.Hooks;

/// <summary>
/// A hook for a single instance of NosSmooth sharing with the rest of application.
/// </summary>
/// <typeparam name="TFunction">The function delegate.</typeparam>
/// <typeparam name="TWrapperFunction">A wrapper function that abstracts the call to original function. May get the neccessary object to call the function and accept only relevant arguments.</typeparam>
/// <typeparam name="TEventArgs">The event args used in case of a call.</typeparam>
public class SingleHook<TFunction, TWrapperFunction, TEventArgs> : INostaleHook<TFunction, TWrapperFunction, TEventArgs>
    where TFunction : Delegate
    where TWrapperFunction : Delegate
    where TEventArgs : System.EventArgs
{
    private readonly INostaleHook<TFunction, TWrapperFunction, TEventArgs> _underlyingHook;

    /// <summary>
    /// Initializes a new instance of the <see cref="SingleHook{TFunction, TWrapperFunction, TEventArgs}"/> class.
    /// </summary>
    /// <param name="underlyingHook">The underlying hook.</param>
    public SingleHook(INostaleHook<TFunction, TWrapperFunction, TEventArgs> underlyingHook)
    {
        _underlyingHook = underlyingHook;
    }

    /// <summary>
    /// Called upon Enable or Disable.
    /// </summary>
    public event EventHandler<HookStateEventArgs>? StateChanged;

    /// <inheritdoc />
    public bool IsUsable => _underlyingHook.IsUsable;

    /// <inheritdoc />
    public string Name => _underlyingHook.Name;

    /// <inheritdoc />
    public bool IsEnabled { get; private set; }

    /// <inheritdoc />
    public Result Enable()
    {
        if (!IsEnabled)
        {
            IsEnabled = true;
            StateChanged?.Invoke(this, new HookStateEventArgs(true));
            _underlyingHook.Called += FireCalled;
        }

        return Result.FromSuccess();
    }

    /// <inheritdoc />
    public Result Disable()
    {
        if (IsEnabled)
        {
            IsEnabled = true;
            StateChanged?.Invoke(this, new HookStateEventArgs(false));
            _underlyingHook.Called -= FireCalled;
        }

        return Result.FromSuccess();
    }

    private void FireCalled(object? owner, TEventArgs eventArgs)
    {
        Called?.Invoke(this, eventArgs);
    }

    /// <inheritdoc />
    public Optional<TWrapperFunction> WrapperFunction => _underlyingHook.WrapperFunction;

    /// <inheritdoc />
    public TFunction OriginalFunction => _underlyingHook.OriginalFunction;

    /// <inheritdoc />
    public event EventHandler<TEventArgs>? Called;
}
\ No newline at end of file

M src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHookManager.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHookManager.cs +58 -20
@@ 20,6 20,7 @@ public class SingleHookManager : IHookManager
    private readonly SharedHookManager _sharedHookManager;
    private readonly HookManagerOptions _options;
    private Dictionary<string, INostaleHook> _hooks;
    private bool _initialized;

    /// <summary>
    /// Initializes a new instance of the <see cref="SingleHookManager"/> class.


@@ 34,28 35,28 @@ public class SingleHookManager : IHookManager
    }

    /// <inheritdoc />
    public IPacketSendHook PacketSend => GetHook<IPacketSendHook>(IHookManager.PacketSendName);
    public Optional<IPacketSendHook> PacketSend => GetHook<IPacketSendHook>(IHookManager.PacketSendName);

    /// <inheritdoc />
    public IPacketReceiveHook PacketReceive => GetHook<IPacketReceiveHook>(IHookManager.PacketReceiveName);
    public Optional<IPacketReceiveHook> PacketReceive => GetHook<IPacketReceiveHook>(IHookManager.PacketReceiveName);

    /// <inheritdoc />
    public IPlayerWalkHook PlayerWalk => GetHook<IPlayerWalkHook>(IHookManager.CharacterWalkName);
    public Optional<IPlayerWalkHook> PlayerWalk => GetHook<IPlayerWalkHook>(IHookManager.CharacterWalkName);

    /// <inheritdoc />
    public IEntityFollowHook EntityFollow => GetHook<IEntityFollowHook>(IHookManager.EntityFollowName);
    public Optional<IEntityFollowHook> EntityFollow => GetHook<IEntityFollowHook>(IHookManager.EntityFollowName);

    /// <inheritdoc />
    public IEntityUnfollowHook EntityUnfollow => GetHook<IEntityUnfollowHook>(IHookManager.EntityUnfollowName);
    public Optional<IEntityUnfollowHook> EntityUnfollow => GetHook<IEntityUnfollowHook>(IHookManager.EntityUnfollowName);

    /// <inheritdoc />
    public IPetWalkHook PetWalk => GetHook<IPetWalkHook>(IHookManager.PetWalkName);
    public Optional<IPetWalkHook> PetWalk => GetHook<IPetWalkHook>(IHookManager.PetWalkName);

    /// <inheritdoc />
    public IEntityFocusHook EntityFocus => GetHook<IEntityFocusHook>(IHookManager.EntityFocusName);
    public Optional<IEntityFocusHook> EntityFocus => GetHook<IEntityFocusHook>(IHookManager.EntityFocusName);

    /// <inheritdoc />
    public IPeriodicHook Periodic => GetHook<IPeriodicHook>(IHookManager.PeriodicName);
    public Optional<IPeriodicHook> Periodic => GetHook<IPeriodicHook>(IHookManager.PeriodicName);

    /// <inheritdoc />
    public IReadOnlyList<INostaleHook> Hooks => _hooks.Values.ToList();


@@ 63,14 64,10 @@ public class SingleHookManager : IHookManager
    /// <inheritdoc />
    public IResult Initialize(NosBindingManager bindingManager, NosBrowserManager browserManager)
    {
        var hooksResult = _sharedHookManager.InitializeInstance(bindingManager, browserManager, _options);
        if (!hooksResult.IsDefined(out var hooks))
        {
            return hooksResult;
        }

        _initialized = true;
        var (hooks, result) = _sharedHookManager.InitializeInstance(bindingManager, browserManager, _options);
        _hooks = hooks;
        return Result.FromSuccess();
        return result;
    }

    /// <inheritdoc />


@@ 79,7 76,7 @@ public class SingleHookManager : IHookManager
        foreach (var name in names)
        {
            var hook = GetHook<INostaleHook>(name);
            hook.Enable();
            hook.TryDo(h => h.Enable());
        }
    }



@@ 89,7 86,7 @@ public class SingleHookManager : IHookManager
        foreach (var name in names)
        {
            var hook = GetHook<INostaleHook>(name);
            hook.Disable();
            hook.TryDo(h => h.Disable());
        }
    }



@@ 111,15 108,56 @@ public class SingleHookManager : IHookManager
        }
    }

    private T GetHook<T>(string name)
    /// <inheritdoc/>
    public bool IsHookLoaded<THook>()
        where THook : INostaleHook
        => IsHookLoaded(typeof(THook));

    /// <inheritdoc/>
    public bool IsHookUsable<THook>()
        where THook : INostaleHook
        => IsHookUsable(typeof(THook));

    /// <inheritdoc/>
    public bool IsHookLoaded(Type hookType)
        => GetHook(hookType).IsPresent;

    /// <inheritdoc/>
    public bool IsHookUsable(Type hookType)
        => GetHook(hookType).TryGet(out var h) && h.IsUsable;

    private Optional<T> GetHook<T>(string name)
        where T : INostaleHook
    {
        if (!_hooks.ContainsKey(name) || _hooks[name] is not T typed)
        if (!_initialized)
        {
            throw new InvalidOperationException
                ($"Could not load hook {name}. Did you forget to call IHookManager.Initialize?");
                ($"Could not load hook {typeof(T)}. Did you forget to call IHookManager.Initialize?");
        }

        var hook = _hooks.Values.FirstOrDefault(x => x is T);
        if (hook is not T typed)
        {
            return Optional<T>.Empty;
        }

        return typed;
    }

    private Optional<INostaleHook> GetHook(Type hookType)
    {
        if (!_initialized)
        {
            throw new InvalidOperationException
                ($"Could not load hook {hookType.Name}. Did you forget to call IHookManager.Initialize?");
        }

        var hook = _hooks.Values.FirstOrDefault(x => x.GetType() == hookType);
        if (hook is null)
        {
            return Optional<INostaleHook>.Empty;
        }

        return new Optional<INostaleHook>(hook);
    }
}
\ No newline at end of file

M src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PeriodicHook.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PeriodicHook.cs +26 -25
@@ 1,26 1,27 @@
//
//  PeriodicHook.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.LocalBinding.Hooks;

namespace NosSmooth.Extensions.SharedBinding.Hooks.Specific;

/// <summary>
/// A hook of a periodic function,
/// preferably called every frame.
/// </summary>
internal class PeriodicHook :
    SingleHook<IPeriodicHook.PeriodicDelegate, IPeriodicHook.PeriodicDelegate, System.EventArgs>, IPeriodicHook
{
    /// <summary>
    /// Initializes a new instance of the <see cref="PeriodicHook"/> class.
    /// </summary>
    /// <param name="underlyingHook">The underlying hook.</param>
    public PeriodicHook(INostaleHook<IPeriodicHook.PeriodicDelegate, IPeriodicHook.PeriodicDelegate, System.EventArgs> underlyingHook)
        : base(underlyingHook)
    {
    }
//
//  PeriodicHook.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Hooks;

namespace NosSmooth.Extensions.SharedBinding.Hooks.Specific;

/// <summary>
/// A hook of a periodic function,
/// preferably called every frame.
/// </summary>
internal class PeriodicHook :
    SingleHook<IPeriodicHook.PeriodicDelegate, IPeriodicHook.PeriodicDelegate, System.EventArgs>, IPeriodicHook
{
    /// <summary>
    /// Initializes a new instance of the <see cref="PeriodicHook"/> class.
    /// </summary>
    /// <param name="underlyingHook">The underlying hook.</param>
    public PeriodicHook(INostaleHook<IPeriodicHook.PeriodicDelegate, IPeriodicHook.PeriodicDelegate, System.EventArgs> underlyingHook)
        : base(underlyingHook)
    {
    }
}
\ No newline at end of file

Do not follow this link