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
Parameter injection for commands #248
Conversation
…mically. This reduces the boilerplate code needed to parse arguments in each command, and reduces the need to maintain command documentation with @command. Example: @command(aliases = "/set", desc = "Set all the blocks inside the selection to a block") @CommandPermissions("worldedit.region.set") @logging(REGION) void setBlocks(LocalPlayer player, EditSession editSession, @selection Region region, Pattern replaceWith) { // Perform command }
😮 😮 😮 Very nice. Good utilization of the Github PR as well, very nice indeed. One thing you may want to consider, is factoring all this code out of WorldEdit. Over at Overcast Network, we've factored out your framework into several packages. Originally, a few months back, we just used what's in WorldEdit with a few minor changes. Recently, we factored it out to add support for both bukkit and bungee. If you want to take a look at it, the repo is here: https://github.com/OvercastNetwork/sk89q-command-framework Now, with all the included refactoring you're doing, might be a great opportunity to factor your command framework out of WorldEdit and into a separate repository for other plugin developers to use. We've enjoyed using our command framework in bukkit, and now bungee, and I'm sure other plugin developers would as well. |
I've considered it and even started a repo or two before, but I've been partly on the fence, and also partly busy and not worrying too much about it. I'll think about it again. Note: I also added some more sections to my initial PR post. |
We, and other plugin developers would really appreciate it. Also, if you could take our bukkit/bungee/core module system into consideration, we would be even happier. |
I write most of my libraries, when reasonable, to be API-agnostic, so this could be used in anything, even a standalone program. I know some of the libraries in WE are Bukkit-specific, but I can't vouch for what other people do with my libraries after I have written them. |
could you allow this to use primitives where you're now only allowing boxed classes? (i.e. "int iterations" instead of "Integer iterations") |
Oh, you can use both primitives and their autoboxed equivalents. The |
Any objections to merging this? The commands were tested, and frankly, I don't want to look at this anymore at the moment -- just rather use it :P I might put it into a separate library, but later because I want to work on some other things first. |
Nope go ahead |
I would prefer if you factored it out into a new library now as opposed to later. Just my opinion |
A second lib for this new system -- even if temporarily in the WE repo -- would probably be easier to do now rather than later... But no, no objections. Edit: Never mind, already packaged outside of com.sk89q.worldedit |
Factoring out is just copy code and add as a dependency (+shade). I don't want to yet because I don't want to do a half ass dump into a repo, and then have someone do something like bundle it into their plugin and give us dependency hell. If I'm going to do it, I'm going to go the entire way. |
Well, I actually could maybe do that towards the end of the week, but y'know, it'd be more of a minor organizational move because WorldEdit has to shade all of these libraries. I can't distribute WE and require dependencies. I'd likely put it into https://github.com/sk89q/rebar if I do. |
What if developers want to use your framework, but not rebar? |
The point is that there's many different frameworks/APIs/whatever in worldedit and in the end we either have one dependency or worldedit ends up shading 100 dependencies. |
I don't see what the particular problem with using a library that does more than one thing is. Having several projects means that I have to maintain and release them all individually, which is a lot of work, work that could be spent on writing code instead. And to be fair, Rebar (or the project behind that) is the reason why Bukkit has |
Some people may have concerns about the size of their files/security reasons, but in terms of a MineCraft plugin library there probably isn't any reason to have these concerns. |
There's one thing you must do, trust what sk89q does he know what is good. |
yeah, security is kind of a bs concern when it comes to unused stuff |
1.6's coming out soon so I'll probably be starting on another branch off this and adding to it. Primarily breaking up EditSession. |
oh, good that you mention it. I have some uncommitted cleanup changes there. I'll drop them then. diff --git src/main/java/com/sk89q/worldedit/EditSession.java src/main/java/com/sk89q/worldedit/EditSession.java
index 59b26ce..35627e4 100644
--- src/main/java/com/sk89q/worldedit/EditSession.java
+++ src/main/java/com/sk89q/worldedit/EditSession.java
@@ -19,6 +19,7 @@
package com.sk89q.worldedit;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
@@ -533,6 +534,18 @@ public int countBlock(Region region, Set<Integer> searchIDs) {
return countBlocks(region, passOn);
}
+ private static boolean containsFuzzy(Collection<BaseBlock> collection, Object o) {
+ // allow -1 data in the searchBlocks to match any type
+ for (BaseBlock b : collection) {
+ if (o instanceof BaseBlock) {
+ if (b.equalsFuzzy((BaseBlock) o)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/**
* Count the number of blocks of a list of types in a region.
*
@@ -543,22 +556,6 @@ public int countBlock(Region region, Set<Integer> searchIDs) {
public int countBlocks(Region region, Set<BaseBlock> searchBlocks) {
int count = 0;
- // allow -1 data in the searchBlocks to match any type
- Set<BaseBlock> newSet = new HashSet<BaseBlock>() {
- @Override
- public boolean contains(Object o) {
- for (BaseBlock b : this.toArray(new BaseBlock[this.size()])) {
- if (o instanceof BaseBlock) {
- if (b.equalsFuzzy((BaseBlock) o)) {
- return true;
- }
- }
- }
- return false;
- }
- };
- newSet.addAll(searchBlocks);
-
if (region instanceof CuboidRegion) {
// Doing this for speed
Vector min = region.getMinimumPoint();
@@ -577,7 +574,7 @@ public boolean contains(Object o) {
Vector pt = new Vector(x, y, z);
BaseBlock compare = new BaseBlock(getBlockType(pt), getBlockData(pt));
- if (newSet.contains(compare)) {
+ if (containsFuzzy(searchBlocks, compare)) {
++count;
}
}
@@ -586,7 +583,7 @@ public boolean contains(Object o) {
} else {
for (Vector pt : region) {
BaseBlock compare = new BaseBlock(getBlockType(pt), getBlockData(pt));
- if (newSet.contains(compare)) {
+ if (containsFuzzy(searchBlocks, compare)) {
++count;
}
} |
Well, I'm probably not going to touch EditSession yet. See #250 |
Is this system dead for the foreseeable future, or just this PR? Edit: Never mind, "Switch to a declarative command registration framework (i.e. the new-commands branch or something similar)." |
This adds support for parameter injection with commands.
I don't actually want to merge right away because I want to test a bit more, but I'd like some input (and any objections).
Old vs. new
becomes a more manageable
Complex constructs
You can also use more complex constructs.
which results in
//smooth [-n] [<iterations=1>]
Backwards compatibility
Old-style commands are not known to the new code, but they work just fine. They are processed using the same framework as with the "newer" approach.
However,
usage =
(and other relevant properties) must be defined on@Command
otherwise usage information would be unavailable.Bindings
Adding support for a new type of parameter is easy.
Exception handling
Previously, non-command related exceptions were handled in an ugly big try-catch that couldn't be 'reused' anywhere else. Now it's stored off in a reusuable class.
Invocation listeners
WorldEdit has an annotation-based logging mechanism, and this was handled by overriding the method performing the invoke() call. Now it's much cleaner, and you can mix and match modules.
Better responses
Before, we had to define
usage = "var1 var2 [var3]
" by hand, but that was annoying and it could become out of date if the command was changed but the annotation wasn't. Now the new command builder generates these usage strings automatically and uses the names of the parameters (in the source code) to fill in parameter names.It also provides generally better responses if you type too many arguments or too few. Each argument parser can also throw its own exceptions and present detailed error messages.
Auto-complete
There's also support for generating suggestions dynamically (for when you press TAB to auto-complete) but I'm currently not using it and it needs some minor work first. For example, if you pressed tab on
n
for a direction, it could complete it tonorth
. No work is needed on part of the command.