10000 adding #360 by DerEchtePilz · Pull Request #369 · CommandAPI/CommandAPI · GitHub
[go: up one dir, main page]

Skip to content

adding #360 #369

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

Merged
merged 29 commits into from
Dec 17, 2022
Merged

adding #360 #369

merged 29 commits into from
Dec 17, 2022

Conversation

DerEchtePilz
Copy link
Member

Hello!

It's me again.

I opened this PR so you can decide if you want to have it in 8.6.0 or later (or if you want me to change/add anything).

@DerEchtePilz
Copy link
Member Author

Also, just wanted to mention that this currently is untested which will be done until noon on Saturday.

@DerEchtePilz
Copy link
Member Author

As requested on discord, I will now add some code examples and explain my changes a little more.

1. Internal changes

To be able to implement this without having to use if-statements (in fact, I didn't use a single one), I changed the executeWith method in the IExecutorTyped interface to now also take in a Map<String, Object>. This map is filled with content in CommandAPIHandler#generateCommand.

1.1 Interfaces and executes...

Before I made this PR, each executor had two interfaces: ...CommandExecutor and ...ResultingCommandExecutor.
Now, two more interfaces have been added to support the use of the map:

  • ...ExecutionInfo
  • ...ResultingExecutionInfo

The ...ExecutionInfo and ...ResultingExecutionInfo interfaces both have one method:

  • ...ExecutionInfo:
void run(BlockCommandSender sender, Object[] args, Map<String, Object> argsMap) throws WrapperCommandSyntaxException;
  • ...ResultingExecutionInfo:
int run(BlockCommandSender sender, Object[] args, Map<String, Object> argsMap) throws WrapperCommandSyntaxException;

Those two interfaces are now also available through the use of the .executes... methods.
The already existing .executes... methods have now been overloaded to also accept the new ExecutionInfo interfaces:

public T executesPlayer(PlayerExecutionInfo info) {
	this.executor.addNormalExecutor(info);
	return (T) this;
}

public T executesPlayer(PlayerResultingExecutionInfo info) {
	this.executor.addResultingExecutor(info);
	return (T) this;
}

Use the new methods in your code

For this section, I will just add some examples I wrote:

new CommandAPICommand("nomap")
	.withArguments(new ItemStackArgument("item"))
	.executesPlayer((player, args) -> {
		// This command does not have access to the map with stores the arguments mapped to their node names
		// We have to get the ItemStack as normal
		ItemStack itemStack = (ItemStack) args[0];
		player.getInventory().addItem(itemStack);
	})
	.register();

new CommandAPICommand("withmap")
	.withArguments(new ItemStackArgument("item"))
	.executesPlayer((player, args, argsMap) -> {
		// This command has access to the map which stores the arguments mapped to their node names
		// Here we can get the ItemStack with two ways:
		// The known way:
		ItemStack possibilityOne = (ItemStack) args[0];

		// Or:
		ItemStack possibilityTwo = (ItemStack) argsMap.get("item");

		player.getInventory().addItem(possibilityOne, possibilityTwo);
	})
	.register();

2. Tests

As written previously, I did not test this yet but I am confident that it should work.
I will do that until Saturday und will update you when I am done.

@JorelAli
Copy link
Member

Not scheduling for 8.6.0 - too close to the expected release date. I'm more than happy to have this for 9.0.0.

My problem with this implementation is the limited scope for future extensibility. The CommandAPI has been around for just over four years and we're only now adding more accessibly functionality to the .executes() methods - it's quite possible that another four years down the line we may want to add more functionality to our .executes() method. The choice to use another functional interface with three parameters is limiting and leads into questionable design decisions, such as:

  • What if we want to add a fourth parameter later? What about a fifth? We'll end up with code that gets unnecessarily large:

    .executesPlayer((player, args, argsMap, somethingElse, someOtherStuff) -> {
    
    })
  • How do we make linters happy? In programming (in general), linters like to complain about unused parameters. Ideally, the CommandAPI should avoid exposing parameters that users are unlikely to used, which is not the case with your implementation:

    .executesPlayer((player, args, argsMap) -> {
      // Almost all u
    8000
    sers that are going to use this method will just use 'player'
      // and argsMap. We end up forcing the user to add this unused 'args' parameter.
      // In Kotlin, we can dismiss this with an underscore, but you can't do that in Java!
    })

I think going with a record would be better. It would allow us (CommandAPI development team) to be able to expand the list of accessible functions easily without causing backwards-incompatible changes and without CommandAPI users needing to change their executor implementation. Perhaps something like this would be better?

new CommandAPICommand("withmap")
    .withArguments(new ItemStackArgument("item"))
    .executesPlayer(info -> {
        ItemStack possibilityOne = info.args()[0];             // Access using args()
        ItemStack possibilityTwo = info.argsMap().get("item"); // Access using argsMap()
        Player player = info.sender(); // Access command sender. Using generics, we can make this a Player instead of a CommandSender object.
    })
    .register();

@DerEchtePilz
Copy link
Member Author

Yes, I read that record suggestion in #360 and also tried to implement it like that.
I didn‘t manage to do that and decided to go with that solution, knowing it is not ideal.
I am more than happy to add that record, how would I do it in general? Would a Function work?

@JorelAli
Copy link
Member
JorelAli commented Nov 25, 2022

I am more than happy to add that record, how would I do it in general? Would a Function work?

I envision something along the lines of creating a generalized record like this:

public record ExecutionInfo<Sender extends CommandSender> (
    Sender sender,
    Object[] args,
    Map<String, Object> argsMap
) {}

I think in the long run it would be safer to create a generic functional interface for our two types rather than using Function<ExecutionInfo, Integer> and Consumer<ExecutionInfo> (especially for Kotlin purposes). Say we add an INormalExecution and an IResultingExecution which follows something like this:

public interface INormalExecution<Sender extends CommandSender> extends IExecutorTyped {

    @Override
    default int executeWith(CommandSender sender, Object[] args) throws WrapperCommandSyntaxException {
        // Some implementation here... either changing the parameters to this function
        // to using ExecutionInfo or something? I didn't think this far ahead
        return 1;
    }

    void run(ExecutionInfo<Sender> info) throws WrapperCommandSyntaxException;

}

Then I think it's safe to say we can add our implementations for each execution type and then link them into Executable. For example, for a normal player execution:

@FunctionalInterface
public interface PlayerCommandExecution extends INormalExecution<Player> {

    void run(ExecutionInfo<Player> player) throws WrapperCommandSyntaxException;

    @Override
    default ExecutorType getType() {
        return ExecutorType.PLAYER;
    }
}

And then linking:

// class dev.jorel.commandapi.Executable {

    public T executesPlayer(PlayerCommandExecution executor) {
        this.executor.addNormalExecutor(executor); // Something to this effect
        return (T) this;
     }
     
// }

If all of the names of "executor" and "execution" and "executable" start getting too complicated, feel free to choose alternatives for the new classes!

In short, the main idea is that we have one centralized record which is easy to update with new changes, and then each new executor implements that.

@DerEchtePilz
Copy link
Member Author

This is now tested and should work!

Also, I decided to not include the CommandArgs class which Lucas suggested on discord as I decided to include the Object[] and Map<String, Object> anyway.

@DerEchtePilz DerEchtePilz marked this pull request as draft December 9, 2022 18:11
@DerEchtePilz DerEchtePilz marked this pull request as ready for review December 9, 2022 20:40
@JorelAli
< 8000 clipboard-copy aria-label="Copy link" for="issuecomment-1348416559-permalink" role="menuitem" data-view-component="true" class="dropdown-item btn-link"> Copy link
Member

@DerEchtePilz Please update this branch with the latest changes from JorelAli:dev/dev which includes the multi-platform refactor so this can be merged cleanly. For all intents and purposes:

  • Changes in commandapi-core can be moved to commandapi-bukkit-core
  • Changes in commandapi-kotlin can be moved to commandapi-kotlin-bukkit
  • Changes in commandapi-plugin-test can be moved to commandapi-bukkit-plugin-test (don't worry if this doesn't compile, this whole project is in development)

@DerEchtePilz
Copy link
Member Author

So, what I've decided to do is that I will first resolve those issues right here (because honestly, I kinda do not want to mess anything up) and then I will update this branch.

Can someone please tell me how to update this branch? I've merged conflicting files before but it is important (for me, you and this pull request) that everything goes well.

@DerEchtePilz DerEchtePilz marked this pull request as draft December 14, 2022 15:47
@DerEchtePilz DerEchtePilz marked this pull request as ready for review December 14, 2022 15:52
Copy link
Member
@JorelAli JorelAli left a comment

Choose a reason for hiding this comment

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

A few minor tid-bits that I noticed during manual testing, but nothing severe enough to prevent merging it over. These minor points should be addressed before 9.0.0 goes out though.

/**
* @return The wrapper type of this command
*/
WrapperType senderWrapper();
Copy link
Member

Choose a reason for hiding this comment

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

This is exposed to the end user. A comment stating that this is only used internally, and you should be using sender() instead would be nice.

* @param nodeName The node name of this argument. This was set when initializing an argument
* @return an argument which has the given node name
*/
public Object get(String nodeName) {
Copy link
Member

Choose a reason for hiding this comment

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

This method can return null (if nodeName is not found). This should be made known to the end user (by using the javax.annotation.Nullable annotation) and it should be stated in the JavaDocs that this can return null if nodeName is not found.

@JorelAli JorelAli merged commit 340902c into CommandAPI:dev/dev Dec 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
0