Skip to content

Commit

Permalink
Command refactor start
Browse files Browse the repository at this point in the history
  • Loading branch information
Tides committed Jan 18, 2024
1 parent 21ae3f3 commit a78ad7f
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 68 deletions.
2 changes: 1 addition & 1 deletion Obsidian.API/_Attributes/CommandInfoAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Obsidian.API;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class CommandInfoAttribute : Attribute
{
public string Description { get; }
Expand Down
18 changes: 5 additions & 13 deletions Obsidian.API/_Types/CommandContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,13 @@

namespace Obsidian.API;

public sealed class CommandContext
public sealed class CommandContext(string message, ICommandSender commandSender, IPlayer player, IServer server)
{
public IPlayer? Player { get; private set; }
public IServer Server { get; private set; }
public ICommandSender Sender { get; }
public IPlayer? Player { get; } = player;
public IServer Server { get; } = server;
public ICommandSender Sender { get; } = commandSender;
public bool IsPlayer => Player != null;

public PluginBase? Plugin { get; internal set; }
internal string Message { get; }

public CommandContext(string message, ICommandSender commandSender, IPlayer player, IServer server)
{
Server = server;
Sender = commandSender;
Player = player;
Message = message;
}
internal string Message { get; } = message;
}
14 changes: 11 additions & 3 deletions Obsidian/Commands/Builders/CommandBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Obsidian.API.BlockStates;
using Microsoft.Extensions.DependencyInjection;
using Obsidian.API.BlockStates;
using Obsidian.Commands.Framework;
using Obsidian.Commands.Framework.Entities;
using Obsidian.Plugins;
Expand All @@ -13,7 +14,7 @@ public sealed class CommandBuilder

public string Name { get; }

public Type ModuleType { get; }
public Type? ModuleType { get; }

public string? Description { get; private set; } = default!;

Expand All @@ -33,8 +34,15 @@ private CommandBuilder(string name, Type moduleType)
this.ModuleType = moduleType;
}

private CommandBuilder(string name)
{
this.Name = name;
}

public static CommandBuilder Create(string name, Type moduleType) => new(name, moduleType);

public static CommandBuilder Create(string name) => new(name);

public CommandBuilder WithDescription(string? description)
{
this.Description = description;
Expand Down Expand Up @@ -128,7 +136,6 @@ public CommandBuilder CanIssueAs(CommandIssuers issuers)
return this;
}


public Command Build(CommandHandler commandHandler, PluginContainer pluginContainer) => new()
{
Name = this.Name,
Expand All @@ -142,5 +149,6 @@ public CommandBuilder CanIssueAs(CommandIssuers issuers)
ModuleType = this.ModuleType,
CommandHandler = commandHandler,
PluginContainer = pluginContainer,
ModuleFactory = this.ModuleType != null ? ActivatorUtilities.CreateFactory(this.ModuleType, []) : null
};
}
23 changes: 23 additions & 0 deletions Obsidian/Commands/CommandModuleFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Microsoft.Extensions.DependencyInjection;
using Obsidian.Commands.Framework;
using Obsidian.Plugins;
using System.Reflection;

namespace Obsidian.Commands;
public static class CommandModuleFactory
{
public static object? CreateModule(ObjectFactory factory, CommandContext context, PluginContainer pluginContainer)
{
var module = factory.Invoke(pluginContainer.ServiceScope.ServiceProvider, null);
var moduleType = module.GetType();

var commandContextProperty = moduleType.GetProperties().FirstOrDefault(x => x.GetCustomAttribute<CommandContextAttribute>() != null)
?? throw new InvalidOperationException("Failed to find CommandContext property.");

commandContextProperty.SetValue(module, context);

pluginContainer.InjectServices(null, module);

return module;
}
}
6 changes: 6 additions & 0 deletions Obsidian/Commands/Framework/CommandContextAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Obsidian.Commands.Framework;

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class CommandContextAttribute : Attribute
{
}
47 changes: 20 additions & 27 deletions Obsidian/Commands/Framework/CommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Obsidian.Commands.Framework;

public sealed class CommandHandler
{
private readonly ILogger? logger;
internal readonly ILogger? logger;
private readonly List<Command> _commands;
private readonly CommandParser _commandParser;
private readonly List<BaseArgumentParser> _argumentParsers;
Expand Down Expand Up @@ -55,6 +55,25 @@ public BaseArgumentParser GetArgumentParser(Type argumentType) =>

public Command[] GetAllCommands() => _commands.ToArray();

public void RegisterCommand(PluginContainer pluginContainer, string name, Delegate commandDelegate)
{
var method = commandDelegate.Method;

var commandInfo = method.GetCustomAttribute<CommandInfoAttribute>();
var checks = method.GetCustomAttributes<BaseExecutionCheckAttribute>();
var issuers = method.GetCustomAttribute<IssuerScopeAttribute>()?.Issuers ?? CommandHelpers.DefaultIssuerScope;

var command = CommandBuilder.Create(name)
.WithDescription(commandInfo?.Description)
.WithUsage(commandInfo?.Usage)
.AddExecutionChecks(checks)
.CanIssueAs(issuers)
.AddOverload(method)
.Build(this, pluginContainer);

_commands.Add(command);
}

public void AddArgumentParser(BaseArgumentParser parser) => _argumentParsers.Add(parser);

public void UnregisterPluginCommands(PluginContainer plugin) => _commands.RemoveAll(x => x.PluginContainer == plugin);
Expand All @@ -81,32 +100,6 @@ public void RegisterCommands(PluginContainer pluginContainer)
}
}

public object? CreateCommandRootInstance(Type moduleType, PluginContainer pluginContainer)
{
ArgumentNullException.ThrowIfNull(moduleType);

object? instance = Activator.CreateInstance(moduleType);
if (instance is null)
return null;

var injectables = moduleType.GetProperties().Where(x => x.GetCustomAttribute<InjectAttribute>() != null);
foreach (var injectable in injectables)
{
//Plugins should stick to services and not be able to have access to other plugin base class.
//if (injectable.PropertyType == typeof(PluginBase) || injectable.PropertyType == plugin.Plugin.GetType())
//{
// injectable.SetValue(instance, plugin.Plugin);
//}
//else
//{
//}

pluginContainer.InjectServices(this.logger!, instance);
}

return instance;
}

private void RegisterSubgroups(Type moduleType, PluginContainer pluginContainer, Command? parent = null)
{
// find all command groups under this command
Expand Down
34 changes: 34 additions & 0 deletions Obsidian/Commands/Framework/CommandModuleBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Obsidian.API.Plugins;
using System.Diagnostics;

namespace Obsidian.Commands.Framework;
public abstract class CommandModuleBase
{
private CommandContext? commandContext;

[CommandContext]
public CommandContext CommandContext
{
get
{
if (commandContext == null)
throw new UnreachableException();//TODO empty command context maybe??

return this.commandContext;
}
set
{
ArgumentNullException.ThrowIfNull(value);

this.commandContext = value;
}
}

public IPlayer? Player => this.CommandContext.Player;
public IServer Server => this.CommandContext.Server;
public ICommandSender Sender => this.CommandContext.Sender;

public bool IsPlayer => this.CommandContext.IsPlayer;

public PluginBase? Plugin => this.CommandContext.Plugin;
}
27 changes: 19 additions & 8 deletions Obsidian/Commands/Framework/Entities/Command.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Obsidian.API.Utilities;
using Microsoft.Extensions.DependencyInjection;
using Obsidian.API.Utilities;
using Obsidian.Commands.Framework.Exceptions;
using Obsidian.Plugins;
using System;
using System.Reflection;

namespace Obsidian.Commands.Framework.Entities;
Expand All @@ -9,7 +11,7 @@ public sealed class Command
{
internal CommandIssuers AllowedIssuers { get; init; }

public required Type ModuleType { get; init; }
public required Type? ModuleType { get; init; }

public required CommandHandler CommandHandler { get; init; }
public required PluginContainer PluginContainer { get; init; }
Expand All @@ -24,6 +26,8 @@ public sealed class Command

public Command? Parent { get; init; }

public ObjectFactory? ModuleFactory { get; init; }

internal Command() { }

public bool CheckCommand(string[] input, Command? parent)
Expand Down Expand Up @@ -78,14 +82,24 @@ public async Task ExecuteAsync(CommandContext context, string[] args)
|| x.GetParameters().Last().GetCustomAttribute<RemainingAttribute>() != null);

// Create instance of declaring type to execute.
var module = CommandHandler.CreateCommandRootInstance(ModuleType, PluginContainer);

if(this.ModuleType != null)
{
await this.ExecuteFromModuleAsync(method, context, args);
return;
}

}

private async Task ExecuteFromModuleAsync(MethodInfo method, CommandContext context, string[] args)
{
var module = CommandModuleFactory.CreateModule(this.ModuleFactory!, context, this.PluginContainer);

// Get required params
var methodparams = method.GetParameters().Skip(1).ToArray();

// Set first parameter to be the context.
var parsedargs = new object[methodparams.Length + 1];
parsedargs[0] = context;

// TODO comments
for (int i = 0; i < methodparams.Length; i++)
Expand All @@ -105,14 +119,11 @@ public async Task ExecuteAsync(CommandContext context, string[] args)
{
var parser = CommandHandler.GetArgumentParser(paraminfo.ParameterType);

// sets args for parser method
var parseargs = new object?[3] { arg, context, null };

// cast with reflection?
if (parser.TryParseArgument(arg, context, out var parserResult))
{
// parse success!
parsedargs[i + 1] = parserResult;
parsedargs[i] = parserResult;
}
else
{
Expand Down
4 changes: 2 additions & 2 deletions Obsidian/Plugins/PluginContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public PluginContainer(PluginBase plugin, PluginInfo info, Assembly assembly, As
/// <summary>
/// Inject the scoped services into
/// </summary>
public void InjectServices(ILogger logger, object? target = null)
public void InjectServices(ILogger? logger, object? target = null)
{
var properties = target is null ? this.pluginType!.WithInjectAttribute() : target.GetType().WithInjectAttribute();

Expand All @@ -67,7 +67,7 @@ public void InjectServices(ILogger logger, object? target = null)
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to inject service.");
logger?.LogError(ex, "Failed to inject service.");
}
}

Expand Down
22 changes: 8 additions & 14 deletions Obsidian/Plugins/PluginRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,50 +12,44 @@ public sealed class PluginRegistry(PluginManager pluginManager, EventDispatcher
private readonly CommandHandler commandHandler = commandHandler;
private readonly ILogger logger = logger;

//TODO REGISTER DELEGATES
public PluginContainer PluginContainer => this.pluginManager.GetPluginContainerByAssembly();

public IPluginRegistry MapCommand(string name, Delegate handler)
{

this.commandHandler.RegisterCommand(this.PluginContainer, name, handler);
return this;
}

//TODO REGISTER DELEGATES
public IPluginRegistry MapCommand(string name, ValueTaskContextDelegate<CommandContext> contextDelegate)
{

this.commandHandler.RegisterCommand(this.PluginContainer, name, contextDelegate);
return this;
}

public IPluginRegistry MapCommands()
{
this.commandHandler.RegisterCommands(this.pluginManager.GetPluginContainerByAssembly());
this.commandHandler.RegisterCommands(this.PluginContainer);

return this;
}

public IPluginRegistry MapEvent<TEventArgs>(ValueTaskContextDelegate<TEventArgs> contextDelegate, Priority priority = Priority.Low) where TEventArgs : BaseMinecraftEventArgs
{
var pluginContainer = this.pluginManager.GetPluginContainerByAssembly();

this.eventDispatcher.RegisterEvent(pluginContainer, contextDelegate, priority);
this.eventDispatcher.RegisterEvent(this.PluginContainer, contextDelegate, priority);

return this;
}

public IPluginRegistry MapEvent(Delegate handler, Priority priority = Priority.Low)
{
var pluginContainer = this.pluginManager.GetPluginContainerByAssembly();

this.eventDispatcher.RegisterEvent(pluginContainer, handler, priority);
this.eventDispatcher.RegisterEvent(this.PluginContainer, handler, priority);

return this;
}

public IPluginRegistry MapEvents()
{
var pluginContainer = this.pluginManager.GetPluginContainerByAssembly();

this.eventDispatcher.RegisterEvents(pluginContainer);
this.eventDispatcher.RegisterEvents(this.PluginContainer);

return this;
}
Expand Down

0 comments on commit a78ad7f

Please sign in to comment.